diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c56cffb74..1e0141a63 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -32,6 +32,7 @@ add_openmw_dir (mwgui itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks + keywordsearch ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index de39b212a..aafc0a12f 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -41,7 +41,7 @@ namespace MWBase //calbacks for the GUI virtual void keywordSelected (const std::string& keyword) = 0; virtual void goodbyeSelected() = 0; - virtual void questionAnswered (const std::string& answer) = 0; + virtual void questionAnswered (int answer) = 0; virtual bool checkServiceRefused () = 0; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 220adf566..e66cfa6e2 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -161,7 +161,7 @@ namespace MWDialogue parseText (info->mResponse); MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); + win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript); mLastTopic = Misc::StringUtils::lowerCase(it->mId); mLastDialogue = *info; @@ -263,6 +263,7 @@ namespace MWDialogue parseText (info->mResponse); + std::string title; if (dialogue.mType==ESM::Dialogue::Persuasion) { std::string modifiedTopic = "s" + topic; @@ -272,13 +273,13 @@ namespace MWDialogue const MWWorld::Store& gmsts = MWBase::Environment::get().getWorld()->getStore().get(); - win->addTitle (gmsts.find (modifiedTopic)->getString()); + title = gmsts.find (modifiedTopic)->getString(); } else - win->addTitle (topic); + title = topic; MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); + win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title); MWBase::Environment::get().getJournal()->addTopic (topic, info->mId); executeScript (info->mResultScript); @@ -289,9 +290,7 @@ namespace MWDialogue else { // no response found, print a fallback text - win->addTitle (topic); - win->addText ("…"); - + win->addResponse ("…", topic); } } @@ -424,53 +423,42 @@ namespace MWDialogue mTemporaryDispositionChange = 0; } - void DialogueManager::questionAnswered (const std::string& answer) + void DialogueManager::questionAnswered (int answer) { + mChoice = answer; - if (mChoiceMap.find(answer) != mChoiceMap.end()) + if (mDialogueMap.find(mLastTopic) != mDialogueMap.end()) { - mChoice = mChoiceMap[answer]; + Filter filter (mActor, mChoice, mTalkedTo); - if (mDialogueMap.find(mLastTopic) != mDialogueMap.end()) + if (mDialogueMap[mLastTopic].mType == ESM::Dialogue::Topic + || mDialogueMap[mLastTopic].mType == ESM::Dialogue::Greeting) { - Filter filter (mActor, mChoice, mTalkedTo); - - if (mDialogueMap[mLastTopic].mType == ESM::Dialogue::Topic - || mDialogueMap[mLastTopic].mType == ESM::Dialogue::Greeting) + if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic], true)) { - if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic], true)) - { - std::string text = info->mResponse; - parseText (text); - - mChoiceMap.clear(); - mChoice = -1; - mIsInChoice = false; - - MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (Interpreter::fixDefinesDialog(text, interpreterContext)); - MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId); - executeScript (info->mResultScript); - mLastDialogue = *info; - } + std::string text = info->mResponse; + parseText (text); + + mChoice = -1; + mIsInChoice = false; + MWBase::Environment::get().getWindowManager()->getDialogueWindow()->clearChoices(); + + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse (Interpreter::fixDefinesDialog(text, interpreterContext)); + MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId); + executeScript (info->mResultScript); + mLastDialogue = *info; } } - - updateTopics(); } - } - void DialogueManager::printError (const std::string& error) - { - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->addText(error); + updateTopics(); } void DialogueManager::askQuestion (const std::string& question, int choice) { MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->askQuestion(question); - mChoiceMap[Misc::StringUtils::lowerCase(question)] = choice; + win->addChoice(question, choice); mIsInChoice = true; } @@ -551,10 +539,10 @@ namespace MWDialogue const MWWorld::Store& gmsts = MWBase::Environment::get().getWorld()->getStore().get(); - win->addTitle (gmsts.find ("sServiceRefusal")->getString()); - MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); + + win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), + gmsts.find ("sServiceRefusal")->getString()); executeScript (info->mResultScript); return true; @@ -565,9 +553,7 @@ namespace MWDialogue std::vector ParseHyperText(const std::string& text) { std::vector result; - MyGUI::UString utext(text); - size_t pos_begin, pos_end, iteration_pos = 0; for(;;) { diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index a7bec31a6..13f7cb098 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -30,7 +30,6 @@ namespace MWDialogue bool mTalkedTo; int mChoice; - std::map mChoiceMap; std::string mLastTopic; ESM::DialInfo mLastDialogue; bool mIsInChoice; @@ -46,8 +45,6 @@ namespace MWDialogue bool compile (const std::string& cmd,std::vector& code); void executeScript (const std::string& script); - void printError (const std::string& error); - void executeTopic (const std::string& topic, bool randomResponse=false); public: @@ -72,7 +69,7 @@ namespace MWDialogue //calbacks for the GUI virtual void keywordSelected (const std::string& keyword); virtual void goodbyeSelected(); - virtual void questionAnswered (const std::string& answer); + virtual void questionAnswered (int answer); virtual void persuade (int type); virtual int getTemporaryDispositionChange () const; diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 1270404e9..20879fd59 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -120,7 +120,7 @@ struct MWGui::TypesetBookImpl : TypesetBook size_t pageCount () const { return mPages.size (); } - std::pair getSize () const + std::pair getSize () const { return std::make_pair (mRect.width (), mRect.height ()); } diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 1af3009f8..28aa371cf 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -27,7 +27,7 @@ namespace MWGui /// it is the largest distance from the left edge to the /// right edge. The second integer is the height of all /// text combined prior to pagination. - virtual std::pair getSize () const = 0; + virtual std::pair getSize () const = 0; }; /// A factory class for creating a typeset book instance. diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index b596cef01..7d6a298ab 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -18,29 +18,18 @@ #include "spellbuyingwindow.hpp" #include "inventorywindow.hpp" #include "travelwindow.hpp" +#include "bookpage.hpp" -/** -*Copied from the internet. -*/ namespace { - - std::string lower_string(const std::string& str) + MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) { - std::string lowerCase = Misc::StringUtils::lowerCase (str); + typedef MWGui::BookTypesetter::Utf8Point point; - return lowerCase; - } + point begin = reinterpret_cast (text); - std::string::size_type find_str_ci(const std::string& str, const std::string& substr,size_t pos) - { - return lower_string(str).find(lower_string(substr),pos); - } - - bool sortByLength (const std::string& left, const std::string& right) - { - return left.size() > right.size(); + return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text)); } } @@ -116,6 +105,103 @@ namespace MWGui // -------------------------------------------------------------------------------------------------- + Response::Response(const std::string &text, const std::string &title) + : mTitle(title) + { + mText = text; + } + + void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) + { + BookTypesetter::Style* title = typesetter->createStyle("EB Garamond", MyGUI::Colour::White); + BookTypesetter::Style* body = typesetter->createStyle("EB Garamond", MyGUI::Colour::Green); + typesetter->sectionBreak(9); + if (mTitle != "") + typesetter->write(title, to_utf8_span(mTitle.c_str())); + typesetter->sectionBreak(9); + + typedef std::pair Range; + std::map hyperLinks; + + size_t pos_begin, pos_end, iteration_pos = 0; + for(;;) + { + pos_begin = mText.find('@', iteration_pos); + if (pos_begin != std::string::npos) + pos_end = mText.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) + { + std::string link = mText.substr(pos_begin + 1, pos_end - pos_begin - 1); + const char specialPseudoAsteriskCharacter = 127; + std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*'); + std::string topicName = MWBase::Environment::get().getWindowManager()-> + getTranslationDataStorage().topicStandardForm(link); + + std::string displayName = link; + MWDialogue::RemovePseudoAsterisks(displayName); + + mText.replace(pos_begin, pos_end+1, displayName); + + assert(topicLinks.find(topicName) != topicLinks.end()); + hyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = intptr_t(topicLinks[topicName]); + } + } + + typesetter->addContent(to_utf8_span(mText.c_str())); + + for (std::map::iterator it = hyperLinks.begin(); it != hyperLinks.end(); ++it) + { + intptr_t topicId = it->second; + BookTypesetter::Style* style = typesetter->createStyle("EB Garamond", MyGUI::Colour::Green); + const MyGUI::Colour linkHot (0.40f, 0.40f, 0.80f); + const MyGUI::Colour linkNormal (0.20f, 0.20f, 0.60f); + const MyGUI::Colour linkActive (0.50f, 0.50f, 1.00f); + style = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); + typesetter->write(style, it->first.first, it->first.second); + } + + std::string::const_iterator i = mText.begin (); + KeywordSearchT::Match match; + while (i != mText.end () && keywordSearch->search (i, mText.end (), match)) + { + if (i != match.mBeg) + addTopicLink (typesetter, 0, i - mText.begin (), match.mBeg - mText.begin ()); + + addTopicLink (typesetter, match.mValue, match.mBeg - mText.begin (), match.mEnd - mText.begin ()); + + i = match.mEnd; + } + + if (i != mText.end ()) + addTopicLink (typesetter, 0, i - mText.begin (), mText.size ()); + } + + void Response::addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) + { + BookTypesetter::Style* style = typesetter->createStyle("EB Garamond", MyGUI::Colour::Green); + const MyGUI::Colour linkHot (0.40f, 0.40f, 0.80f); + const MyGUI::Colour linkNormal (0.20f, 0.20f, 0.60f); + const MyGUI::Colour linkActive (0.50f, 0.50f, 1.00f); + if (topicId) + style = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); + typesetter->write (style, begin, end); + } + + // -------------------------------------------------------------------------------------------------- + + void Choice::activated() + { + MWBase::Environment::get().getDialogueManager()->questionAnswered(mChoiceId); + } + + void Topic::activated() + { + MWBase::Environment::get().getDialogueManager()->keywordSelected(Misc::StringUtils::lowerCase(mTopicId)); + } + + // -------------------------------------------------------------------------------------------------- + DialogueWindow::DialogueWindow() : WindowBase("openmw_dialogue_window.layout") , mPersuasionDialog() @@ -129,15 +215,6 @@ namespace MWGui //History view getWidget(mHistory, "History"); - mHistory->setOverflowToTheLeft(true); - mHistory->setMaxTextLength(1000000); - MyGUI::Widget* eventbox; - - //An EditBox cannot receive mouse click events, so we use an - //invisible widget on top of the editbox to receive them - getWidget(eventbox, "EventBox"); - eventbox->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onHistoryClicked); - eventbox->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel); //Topics list getWidget(mTopicsList, "TopicsList"); @@ -149,12 +226,20 @@ namespace MWGui getWidget(mDispositionBar, "Disposition"); getWidget(mDispositionText,"DispositionText"); + getWidget(mScrollBar, "VScroll"); + + mScrollBar->eventScrollChangePosition += MyGUI::newDelegate(this, &DialogueWindow::onScrollbarMoved); + mHistory->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel); + + BookPage::ClickCallback callback = boost::bind (&DialogueWindow::notifyLinkClicked, this, _1); + mHistory->adviseLinkClicked(callback); static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); } void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) { + /* MyGUI::ISubWidgetText* t = mHistory->getClient()->getSubWidgetText(); if(t == NULL) return; @@ -198,19 +283,22 @@ namespace MWGui if(color == "#572D21") MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); } + */ } void DialogueWindow::onWindowResize(MyGUI::Window* _sender) { mTopicsList->adjustSize(); + updateHistory(); } void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (mHistory->getVScrollPosition() - _rel*0.3 < 0) - mHistory->setVScrollPosition(0); - else - mHistory->setVScrollPosition(mHistory->getVScrollPosition() - _rel*0.3); + if (!mScrollBar->getVisible()) + return; + mScrollBar->setScrollPosition(std::min(static_cast(mScrollBar->getScrollRange()-1), + std::max(0, static_cast(mScrollBar->getScrollPosition() - _rel*0.3)))); + onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition()); } void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) @@ -231,7 +319,7 @@ namespace MWGui } if (id >= separatorPos) - MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(topic)); + MWBase::Environment::get().getDialogueManager()->keywordSelected(Misc::StringUtils::lowerCase(topic)); else { const MWWorld::Store &gmst = @@ -296,13 +384,25 @@ namespace MWGui mTopicsList->clear(); mHyperLinks.clear(); - mHistory->setCaption(""); + + for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) + delete (*it); + mHistoryContents.clear(); + + for (std::vector::iterator it = mLinks.begin(); it != mLinks.end(); ++it) + delete (*it); + mLinks.clear(); + updateOptions(); } void DialogueWindow::setKeywords(std::list keyWords) { mTopicsList->clear(); + for (std::map::iterator it = mTopicLinks.begin(); it != mTopicLinks.end(); ++it) + delete it->second; + mTopicLinks.clear(); + mKeywordSearch.clear(); bool isCompanion = !MWWorld::Class::get(mPtr).getScript(mPtr).empty() && mPtr.getRefData().getLocals().getIntVar(MWWorld::Class::get(mPtr).getScript(mPtr), "companion"); @@ -346,46 +446,15 @@ namespace MWGui for(std::list::iterator it = keyWords.begin(); it != keyWords.end(); ++it) { mTopicsList->addItem(*it); - } - mTopicsList->adjustSize(); - } - void DialogueWindow::removeKeyword(std::string keyWord) - { - if(mTopicsList->hasItem(keyWord)) - { - mTopicsList->removeItem(keyWord); + Topic* t = new Topic(*it); + mTopicLinks[*it] = t; + + mKeywordSearch.seed(*it, intptr_t(t)); } mTopicsList->adjustSize(); - } - - void addColorInString(std::string& str, const std::string& keyword,std::string color1, std::string color2) - { - size_t pos = 0; - while((pos = find_str_ci(str,keyword, pos)) != std::string::npos) - { - // do not add color if this portion of text is already colored. - { - MyGUI::TextIterator iterator (str); - MyGUI::UString colour; - while(iterator.moveNext()) - { - size_t iteratorPos = iterator.getPosition(); - iterator.getTagColour(colour); - if (iteratorPos == pos) - break; - } - if (colour == color1) - return; - } - - str.insert(pos,color1); - pos += color1.length(); - pos += keyword.length(); - str.insert(pos,color2); - pos+= color2.length(); - } + updateHistory(); } std::string DialogueWindow::parseText(const std::string& text) @@ -410,18 +479,19 @@ namespace MWGui separatorReached = true; } - // sort by length to make sure longer topics are replaced first - std::sort(topics.begin(), topics.end(), sortByLength); - std::vector hypertext = MWDialogue::ParseHyperText(text); + /* size_t historySize = 0; if(mHistory->getClient()->getSubWidgetText() != NULL) { historySize = mHistory->getOnlyText().size(); } + */ std::string result; + + /* size_t hypertextPos = 0; for (size_t i = 0; i < hypertext.size(); ++i) { @@ -461,38 +531,115 @@ namespace MWGui hypertextPos += MyGUI::UString(hypertext[i].mText).length(); } - + */ return result; } - void DialogueWindow::addText(std::string text) + void DialogueWindow::updateHistory(bool scrollbar) { - mHistory->addDialogText("#B29154"+parseText(text)+"#B29154"); + if (!scrollbar && mScrollBar->getVisible()) + { + mHistory->setSize(mHistory->getSize()+MyGUI::IntSize(mScrollBar->getWidth(),0)); + mScrollBar->setVisible(false); + } + if (scrollbar && !mScrollBar->getVisible()) + { + mHistory->setSize(mHistory->getSize()-MyGUI::IntSize(mScrollBar->getWidth(),0)); + mScrollBar->setVisible(true); + } + + BookTypesetter::Ptr typesetter = BookTypesetter::create (mHistory->getWidth(), std::numeric_limits().max()); + + for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) + (*it)->write(typesetter, &mKeywordSearch, mTopicLinks); + + + BookTypesetter::Style* body = typesetter->createStyle("EB Garamond", MyGUI::Colour::White); + + // choices + for (std::map::iterator it = mChoices.begin(); it != mChoices.end(); ++it) + { + Choice* link = new Choice(it->second); + mLinks.push_back(link); + + typesetter->lineBreak(); + const MyGUI::Colour linkHot (0.40f, 0.40f, 0.80f); + const MyGUI::Colour linkNormal (0.20f, 0.20f, 0.60f); + const MyGUI::Colour linkActive (0.50f, 0.50f, 1.00f); + BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, linkNormal, linkHot, linkActive, + TypesetBook::InteractiveId(link)); + typesetter->write(questionStyle, to_utf8_span(it->first.c_str())); + } + + TypesetBook::Ptr book = typesetter->complete(); + mHistory->showPage(book, 0); + size_t viewHeight = mHistory->getParent()->getHeight(); + if (!scrollbar && book->getSize().second > viewHeight) + updateHistory(true); + else if (scrollbar) + { + mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second)); + size_t range = book->getSize().second - viewHeight; + mScrollBar->setScrollRange(range); + mScrollBar->setScrollPosition(range-1); + onScrollbarMoved(mScrollBar, range-1); + } + else + { + // no scrollbar + onScrollbarMoved(mScrollBar, 0); + } } - void DialogueWindow::addMessageBox(const std::string& text) + void DialogueWindow::notifyLinkClicked (TypesetBook::InteractiveId link) { - mHistory->addDialogText("\n#FFFFFF"+text+"#B29154"); + reinterpret_cast(link)->activated(); } - void DialogueWindow::addTitle(std::string text) + void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos) + { + mHistory->setPosition(0,-pos); + } + + void DialogueWindow::addResponse(const std::string &text, const std::string &title) { // This is called from the dialogue manager, so text is // case-smashed - thus we have to retrieve the correct case - // of the text through the topic list. - for (size_t i=0; igetItemCount(); ++i) + // of the title through the topic list. + std::string realTitle = title; + if (realTitle != "") { - std::string item = mTopicsList->getItemNameAt(i); - if (lower_string(item) == text) - text = item; + for (size_t i=0; igetItemCount(); ++i) + { + std::string item = mTopicsList->getItemNameAt(i); + if (Misc::StringUtils::lowerCase(item) == title) + { + realTitle = item; + break; + } + } } - mHistory->addDialogHeading(text); + mHistoryContents.push_back(new Response(text, realTitle)); + updateHistory(); + } + + void DialogueWindow::addMessageBox(const std::string& text) + { + //mHistoryContents.push_back(new Message(text)); + updateHistory(); + } + + void DialogueWindow::addChoice(const std::string& choice, int id) + { + mChoices[choice] = id; + updateHistory(); } - void DialogueWindow::askQuestion(std::string question) + void DialogueWindow::clearChoices() { - mHistory->addDialogText("#572D21"+question+"#B29154"+" "); + mChoices.clear(); + updateHistory(); } void DialogueWindow::updateOptions() @@ -500,7 +647,6 @@ namespace MWGui //Clear the list of topics mTopicsList->clear(); mHyperLinks.clear(); - mHistory->eraseText(0, mHistory->getTextLength()); if (mPtr.getTypeName() == typeid(ESM::NPC).name()) { @@ -513,7 +659,7 @@ namespace MWGui void DialogueWindow::goodbye() { - mHistory->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->getString()); + //mHistory->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->getString()); mTopicsList->setEnabled(false); mEnabled = false; } diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 74fa8051c..43c5039a0 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -4,6 +4,10 @@ #include "windowbase.hpp" #include "referenceinterface.hpp" +#include "bookpage.hpp" + +#include "keywordsearch.hpp" + namespace MWGui { class WindowManager; @@ -21,7 +25,8 @@ namespace MWGui namespace MWGui { - class DialogueHistory; + class DialogueHistoryViewModel; + class BookPage; class PersuasionDialog : public WindowModal { @@ -44,6 +49,55 @@ namespace MWGui void onPersuade (MyGUI::Widget* sender); }; + + struct Link + { + virtual ~Link() {} + virtual void activated () = 0; + }; + + struct Topic : Link + { + Topic(const std::string& id) : mTopicId(id) {} + std::string mTopicId; + virtual void activated (); + }; + + struct Choice : Link + { + Choice(int id) : mChoiceId(id) {} + int mChoiceId; + virtual void activated (); + }; + + typedef KeywordSearch KeywordSearchT; + + struct DialogueText + { + virtual ~DialogueText() {} + virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) = 0; + std::string mText; + }; + + struct Response : DialogueText + { + Response(const std::string& text, const std::string& title = ""); + virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks); + void addTopicLink (BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end); + std::string mTitle; + }; + + struct Message : DialogueText + { + Message(const std::string& text); + virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks); + }; + + struct Goodbye : DialogueText + { + virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks); + }; + class DialogueWindow: public WindowBase, public ReferenceInterface { public: @@ -52,19 +106,19 @@ namespace MWGui // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; - /** Event : Dialog finished, OK button clicked.\n - signature : void method()\n - */ - EventHandle_Void eventBye; + void notifyLinkClicked (TypesetBook::InteractiveId link); void startDialogue(MWWorld::Ptr actor, std::string npcName); void stopDialogue(); void setKeywords(std::list keyWord); - void removeKeyword(std::string keyWord); - void addText(std::string text); + + void addResponse (const std::string& text, const std::string& title=""); + void addMessageBox(const std::string& text); - void addTitle(std::string text); - void askQuestion(std::string question); + + void addChoice(const std::string& choice, int id); + void clearChoices(); + void goodbye(); void onFrame(); @@ -89,6 +143,10 @@ namespace MWGui void onMouseWheel(MyGUI::Widget* _sender, int _rel); void onWindowResize(MyGUI::Window* _sender); + void onScrollbarMoved (MyGUI::ScrollBar* sender, size_t pos); + + void updateHistory(bool scrollbar=false); + virtual void onReferenceUnavailable(); struct HyperLink @@ -108,11 +166,22 @@ namespace MWGui bool mEnabled; - DialogueHistory* mHistory; + std::vector mHistoryContents; + std::map mChoices; + + std::vector mLinks; + std::map mTopicLinks; + + KeywordSearchT mKeywordSearch; + + BookPage* mHistory; Widgets::MWList* mTopicsList; + MyGUI::ScrollBar* mScrollBar; MyGUI::ProgressPtr mDispositionBar; MyGUI::EditBox* mDispositionText; + std::stringstream mText; + PersuasionDialog mPersuasionDialog; std::map mHyperLinks; diff --git a/apps/openmw/mwgui/dialoguehistory.cpp b/apps/openmw/mwgui/dialoguehistory.cpp index d99d55bc1..0163ba12f 100644 --- a/apps/openmw/mwgui/dialoguehistory.cpp +++ b/apps/openmw/mwgui/dialoguehistory.cpp @@ -1,78 +1,11 @@ #include "dialoguehistory.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "widgets.hpp" - -#include "../mwworld/esmstore.hpp" - -#include -#include - -#include -#include namespace MWGui { - MyGUI::UString DialogueHistory::getColorAtPos(size_t _pos) - { - MyGUI::UString colour = MyGUI::TextIterator::convertTagColour(getTextColour()); - MyGUI::TextIterator iterator(getCaption()); - while(iterator.moveNext()) - { - size_t pos = iterator.getPosition(); - iterator.getTagColour(colour); - if (pos < _pos) - continue; - else if (pos == _pos) - break; - } - return colour; - } - - MyGUI::UString DialogueHistory::getColorTextAt(size_t _pos) - { - bool breakOnNext = false; - MyGUI::UString colour = MyGUI::TextIterator::convertTagColour(getTextColour()); - MyGUI::UString colour2 = colour; - MyGUI::TextIterator iterator(getCaption()); - MyGUI::TextIterator col_start = iterator; - while(iterator.moveNext()) - { - size_t pos = iterator.getPosition(); - iterator.getTagColour(colour); - if(colour != colour2) - { - if(breakOnNext) - { - return getOnlyText().substr(col_start.getPosition(), iterator.getPosition()-col_start.getPosition()); - } - col_start = iterator; - colour2 = colour; - } - if (pos < _pos) - continue; - else if (pos == _pos) - { - breakOnNext = true; - } - } - return ""; - } - - void DialogueHistory::addDialogHeading(const MyGUI::UString& parText) - { - MyGUI::UString head("\n#D8C09A"); - head.append(parText); - head.append("#B29154\n"); - addText(head); - } - - void DialogueHistory::addDialogText(const MyGUI::UString& parText) - { - addText(parText); - addText("\n"); - } +DialogueHistoryViewModel::DialogueHistoryViewModel() +{ +} } diff --git a/apps/openmw/mwgui/dialoguehistory.hpp b/apps/openmw/mwgui/dialoguehistory.hpp index 4cf6de621..e39ddcc0a 100644 --- a/apps/openmw/mwgui/dialoguehistory.hpp +++ b/apps/openmw/mwgui/dialoguehistory.hpp @@ -1,19 +1,23 @@ #ifndef MWGUI_DIALOGE_HISTORY_H #define MWGUI_DIALOGE_HISTORY_H -#include +#include "keywordsearch.hpp" + +#include namespace MWGui { - class DialogueHistory : public MyGUI::EditBox + class DialogueHistoryViewModel { - MYGUI_RTTI_DERIVED( DialogueHistory ) - public: - Widget* getClient() { return mClient; } - MyGUI::UString getColorAtPos(size_t _pos); - MyGUI::UString getColorTextAt(size_t _pos); - void addDialogHeading(const MyGUI::UString& parText); - void addDialogText(const MyGUI::UString& parText); + public: + DialogueHistoryViewModel(); + + private: + typedef KeywordSearch KeywordSearchT; + + mutable bool mKeywordSearchLoaded; + mutable KeywordSearchT mKeywordSearch; + }; } #endif diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index a9f2868ff..cdb7e2da4 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -1,17 +1,20 @@ #include "journalviewmodel.hpp" +#include +#include +#include + +#include + +#include + #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/environment.hpp" #include "../mwdialogue/journalentry.hpp" -#include - -#include +#include "keywordsearch.hpp" -#include -#include -#include using namespace MWGui; @@ -19,143 +22,12 @@ namespace MWGui { struct JournalViewModelImpl; } static void injectMonthName (std::ostream & os, int month); -template -class KeywordSearch -{ -public: - - typedef typename string_t::const_iterator Point; - - struct Match - { - Point mBeg; - Point mEnd; - value_t mValue; - }; - - void seed (string_t keyword, value_t value) - { - seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), 0, mRoot); - } - - void clear () - { - mRoot.mChildren.clear (); - mRoot.mKeyword.clear (); - } - - bool search (Point beg, Point end, Match & match) - { - for (Point i = beg; i != end; ++i) - { - // check first character - typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (std::tolower (*i, mLocale)); - - // no match, on to next character - if (candidate == mRoot.mChildren.end ()) - continue; - - // see how far the match goes - Point j = i; - - while ((j + 1) != end) - { - typename Entry::childen_t::iterator next = candidate->second.mChildren.find (std::tolower (*++j, mLocale)); - - if (next == candidate->second.mChildren.end ()) - break; - - candidate = next; - } - - // didn't match enough to disambiguate, on to next character - if (!candidate->second.mKeyword.size ()) - continue; - - // match the rest of the keyword - typename string_t::const_iterator t = candidate->second.mKeyword.begin () + (j - i); - - while (j != end && t != candidate->second.mKeyword.end ()) - { - if (std::tolower (*j, mLocale) != std::tolower (*t, mLocale)) - break; - - ++j, ++t; - } - - // didn't match full keyword, on to next character - if (t != candidate->second.mKeyword.end ()) - continue; - - // we did it, report the good news - match.mValue = candidate->second.mValue; - match.mBeg = i; - match.mEnd = j; - - return true; - } - - // no match in range, report the bad news - return false; - } - -private: - - struct Entry - { - typedef std::map childen_t; - - string_t mKeyword; - value_t mValue; - childen_t mChildren; - }; - - void seed_impl (string_t keyword, value_t value, size_t depth, Entry & entry) - { - int ch = tolower (keyword.at (depth), mLocale); - - typename Entry::childen_t::iterator j = entry.mChildren.find (ch); - - if (j == entry.mChildren.end ()) - { - entry.mChildren [ch].mValue = /*std::move*/ (value); - entry.mChildren [ch].mKeyword = /*std::move*/ (keyword); - } - else - { - if (j->second.mKeyword.size () > 0) - { - if (keyword == j->second.mKeyword) - throw std::runtime_error ("duplicate keyword inserted"); - - value_t pushValue = /*std::move*/ (j->second.mValue); - string_t pushKeyword = /*std::move*/ (j->second.mKeyword); - - j->second.mKeyword.clear (); - - if (depth >= pushKeyword.size ()) - throw std::runtime_error ("unexpected"); - - if (depth+1 < pushKeyword.size()) - seed_impl (/*std::move*/ (pushKeyword), /*std::move*/ (pushValue), depth+1, j->second); - } - - if (depth+1 < keyword.size()) - seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), depth+1, j->second); - } - - } - - Entry mRoot; - std::locale mLocale; -}; - struct MWGui::JournalViewModelImpl : JournalViewModel { - typedef KeywordSearch keyword_search_t; + typedef KeywordSearch KeywordSearchT; mutable bool mKeywordSearchLoaded; - mutable keyword_search_t mKeywordSearch; + mutable KeywordSearchT mKeywordSearch; std::locale mLocale; @@ -253,7 +125,7 @@ struct MWGui::JournalViewModelImpl : JournalViewModel std::string::const_iterator i = utf8text.begin (); - keyword_search_t::Match match; + KeywordSearchT::Match match; while (i != utf8text.end () && mModel->mKeywordSearch.search (i, utf8text.end (), match)) { @@ -300,8 +172,8 @@ struct MWGui::JournalViewModelImpl : JournalViewModel mutable std::string timestamp_buffer; - JournalEntryImpl (JournalViewModelImpl const * Model, iterator_t itr) : - BaseEntry (Model, itr) + JournalEntryImpl (JournalViewModelImpl const * model, iterator_t itr) : + BaseEntry (model, itr) {} std::string getText () const diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index ab765ebe6..09801bcf3 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -152,6 +152,10 @@ namespace MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); + /// \todo Wiping the whole book layout each time the journal is opened is probably too costly for a large journal (eg 300+ pages). + /// There should be a way to keep the existing layout and append new entries to the end of it. + /// However, that still leaves the problem of having to add links to previously unknown, but now known topics, so + /// we maybe need to find another way to speed things up. Book journalBook; if (mModel->isEmpty ()) journalBook = createEmptyJournalBook (); diff --git a/apps/openmw/mwgui/keywordsearch.cpp b/apps/openmw/mwgui/keywordsearch.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/apps/openmw/mwgui/keywordsearch.hpp b/apps/openmw/mwgui/keywordsearch.hpp new file mode 100644 index 000000000..ef2369f3b --- /dev/null +++ b/apps/openmw/mwgui/keywordsearch.hpp @@ -0,0 +1,139 @@ +#ifndef MWGUI_KEYWORDSEARCH_H +#define MWGUI_KEYWORDSEARCH_H + +#include +#include +#include + +template +class KeywordSearch +{ +public: + + typedef typename string_t::const_iterator Point; + + struct Match + { + Point mBeg; + Point mEnd; + value_t mValue; + }; + + void seed (string_t keyword, value_t value) + { + seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), 0, mRoot); + } + + void clear () + { + mRoot.mChildren.clear (); + mRoot.mKeyword.clear (); + } + + bool search (Point beg, Point end, Match & match) + { + for (Point i = beg; i != end; ++i) + { + // check first character + typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (std::tolower (*i, mLocale)); + + // no match, on to next character + if (candidate == mRoot.mChildren.end ()) + continue; + + // see how far the match goes + Point j = i; + + while ((j + 1) != end) + { + typename Entry::childen_t::iterator next = candidate->second.mChildren.find (std::tolower (*++j, mLocale)); + + if (next == candidate->second.mChildren.end ()) + break; + + candidate = next; + } + + // didn't match enough to disambiguate, on to next character + if (!candidate->second.mKeyword.size ()) + continue; + + // match the rest of the keyword + typename string_t::const_iterator t = candidate->second.mKeyword.begin () + (j - i); + + while (j != end && t != candidate->second.mKeyword.end ()) + { + if (std::tolower (*j, mLocale) != std::tolower (*t, mLocale)) + break; + + ++j, ++t; + } + + // didn't match full keyword, on to next character + if (t != candidate->second.mKeyword.end ()) + continue; + + // we did it, report the good news + match.mValue = candidate->second.mValue; + match.mBeg = i; + match.mEnd = j; + + return true; + } + + // no match in range, report the bad news + return false; + } + +private: + + struct Entry + { + typedef std::map childen_t; + + string_t mKeyword; + value_t mValue; + childen_t mChildren; + }; + + void seed_impl (string_t keyword, value_t value, size_t depth, Entry & entry) + { + int ch = tolower (keyword.at (depth), mLocale); + + typename Entry::childen_t::iterator j = entry.mChildren.find (ch); + + if (j == entry.mChildren.end ()) + { + entry.mChildren [ch].mValue = /*std::move*/ (value); + entry.mChildren [ch].mKeyword = /*std::move*/ (keyword); + } + else + { + if (j->second.mKeyword.size () > 0) + { + if (keyword == j->second.mKeyword) + throw std::runtime_error ("duplicate keyword inserted"); + + value_t pushValue = /*std::move*/ (j->second.mValue); + string_t pushKeyword = /*std::move*/ (j->second.mKeyword); + + j->second.mKeyword.clear (); + + if (depth >= pushKeyword.size ()) + throw std::runtime_error ("unexpected"); + + if (depth+1 < pushKeyword.size()) + seed_impl (/*std::move*/ (pushKeyword), /*std::move*/ (pushValue), depth+1, j->second); + } + + if (depth+1 < keyword.size()) + seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), depth+1, j->second); + } + + } + + Entry mRoot; + std::locale mLocale; +}; + +#endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 46c57bb5f..33d953ea7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -108,7 +108,6 @@ namespace MWGui mGui = mGuiManager->getGui(); //Register own widgets with MyGUI - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 18e5f81c1..52f892b42 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -23,7 +23,6 @@ namespace Interpreter{ } std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context){ - unsigned int start = 0; std::ostringstream retval; for(unsigned int i = 0; i < text.length(); i++){ diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 9a9da72d4..46090d000 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -5,14 +5,13 @@ - - - - - - - - + + + + + + +