diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index b995300d50..e2d28a808d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -360,7 +360,7 @@ void OMW::Engine::go() // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); - mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts)); + mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage)); // Sets up the input system mEnvironment.setInputManager (new MWInput::InputManager (*mOgre, diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c177912d4f..30bfced06a 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwmechanics/stat.hpp" #include "../mwgui/mode.hpp" @@ -233,6 +235,8 @@ namespace MWBase virtual void startSpellMaking(MWWorld::Ptr actor) = 0; virtual void startEnchanting(MWWorld::Ptr actor) = 0; virtual void startTraining(MWWorld::Ptr actor) = 0; + + virtual const Translation::Storage& getTranslationDataStorage() const = 0; }; } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 26d5af2021..68837be3c6 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -52,7 +52,7 @@ namespace return lowerCase; } - bool stringCompareNoCase (std::string first, std::string second) + bool stringCompareNoCase (const std::string& first, const std::string& second) { unsigned int i=0; while ( (i::iterator it; - for(it = mActorKnownTopics.begin();it != mActorKnownTopics.end();++it) + std::vector hypertext = ParseHyperText(text); + + //calculation of standard form fir all hyperlinks + for (size_t i = 0; i < hypertext.size(); ++i) { - size_t pos = find_str_ci(text,*it,0); - if(pos !=std::string::npos) + if (hypertext[i].mLink) { - mKnownTopics[*it] = true; + size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText); + for(; asterisk_count > 0; --asterisk_count) + hypertext[i].mText.append("*"); + + hypertext[i].mText = mTranslationDataStorage.topicStandardForm(hypertext[i].mText); } } + + for (size_t i = 0; i < hypertext.size(); ++i) + { + std::list::iterator it; + for(it = mActorKnownTopics.begin(); it != mActorKnownTopics.end(); ++it) + { + if (hypertext[i].mLink) + { + if( hypertext[i].mText == *it ) + { + mKnownTopics[hypertext[i].mText] = true; + } + } + else if( !mTranslationDataStorage.hasTranslation() ) + { + size_t pos = find_str_ci(hypertext[i].mText, *it, 0); + if(pos !=std::string::npos) + { + mKnownTopics[*it] = true; + } + } + } + } + updateTopics(); } @@ -156,7 +186,7 @@ namespace MWDialogue } parseText (info->mResponse); - + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript); @@ -298,7 +328,7 @@ namespace MWDialogue { if (filter.search (*iter)) { - mActorKnownTopics.push_back (toLower (iter->mId)); + mActorKnownTopics.push_back(toLower (iter->mId)); //does the player know the topic? if (mKnownTopics.find (toLower (iter->mId)) != mKnownTopics.end()) @@ -415,7 +445,7 @@ namespace MWDialogue mIsInChoice = false; std::string text = info->mResponse; parseText (text); - + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (Interpreter::fixDefinesDialog(text, interpreterContext)); executeScript (info->mResultScript); @@ -499,4 +529,57 @@ namespace MWDialogue { mTemporaryDispositionChange += delta; } + + std::vector ParseHyperText(const std::string& text) + { + std::vector result; + + MyGUI::UString utext(text); + + size_t pos_begin, pos_end, iteration_pos = 0; + for(;;) + { + pos_begin = utext.find('@', iteration_pos); + if (pos_begin != std::string::npos) + pos_end = utext.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) + { + result.push_back( HyperTextToken(utext.substr(iteration_pos, pos_begin - iteration_pos), false) ); + + std::string link = utext.substr(pos_begin + 1, pos_end - pos_begin - 1); + result.push_back( HyperTextToken(link, true) ); + + iteration_pos = pos_end + 1; + } + else + { + result.push_back( HyperTextToken(utext.substr(iteration_pos), false) ); + break; + } + } + + return result; + } + + size_t RemovePseudoAsterisks(std::string& phrase) + { + size_t pseudoAsterisksCount = 0; + const char specialPseudoAsteriskCharacter = 127; + + if( !phrase.empty() ) + { + std::string::reverse_iterator rit = phrase.rbegin(); + + while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) + { + pseudoAsterisksCount++; + ++rit; + } + } + + phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount); + + return pseudoAsterisksCount; + } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 98b27f7744..1ca2ae5ebc 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -7,6 +7,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" @@ -20,6 +21,7 @@ namespace MWDialogue std::map mKnownTopics;// Those are the topics the player knows. std::list mActorKnownTopics; + Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; std::ostream mErrorStream; Compiler::StreamErrorHandler mErrorHandler; @@ -50,7 +52,7 @@ namespace MWDialogue public: - DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose); + DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose, Translation::Storage& translationDataStorage); virtual void startDialogue (const MWWorld::Ptr& actor); @@ -72,6 +74,21 @@ namespace MWDialogue virtual int getTemporaryDispositionChange () const; virtual void applyTemporaryDispositionChange (int delta); }; + + + struct HyperTextToken + { + HyperTextToken(const std::string& text, bool link) : mText(text), mLink(link) {} + + std::string mText; + bool mLink; + }; + + // In translations (at least Russian) the links are marked with @#, so + // it should be a function to parse it + std::vector ParseHyperText(const std::string& text); + + size_t RemovePseudoAsterisks(std::string& phrase); } #endif diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 258e9174cf..0dbe379413 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -16,6 +16,8 @@ #include "../mwmechanics/npcstats.hpp" +#include "../mwdialogue/dialoguemanagerimp.hpp" + #include "dialogue_history.hpp" #include "widgets.hpp" #include "list.hpp" @@ -52,7 +54,6 @@ bool sortByLength (const std::string& left, const std::string& right) { return left.size() > right.size(); } - } @@ -131,6 +132,7 @@ DialogueWindow::DialogueWindow(MWBase::WindowManager& parWindowManager) , mPersuasionDialog(parWindowManager) , mEnabled(false) , mServices(0) + , mWindowManager(parWindowManager) { // Centre dialog center(); @@ -180,9 +182,30 @@ void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) if(color != "#B29154") { MyGUI::UString key = mHistory->getColorTextAt(cursorPosition); - if(color == "#686EBA") MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key)); - if(color == "#572D21") MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); + if(color == "#686EBA") + { + std::map::iterator i = mHyperLinks.upper_bound(cursorPosition); + if( !mHyperLinks.empty() ) + { + --i; + + if( i->first + i->second.mLength > cursorPosition) + { + MWBase::Environment::get().getDialogueManager()->keywordSelected(i->second.mTrueValue); + } + } + else + { + // the link was colored, but it is not in mHyperLinks. + // It means that those liunks are not marked with @# and found + // by topic name search + MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key)); + } + } + + if(color == "#572D21") + MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); } } @@ -258,6 +281,7 @@ void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) setTitle(npcName); mTopicsList->clear(); + mHyperLinks.clear(); mHistory->setCaption(""); updateOptions(); } @@ -340,7 +364,7 @@ void addColorInString(std::string& str, const std::string& keyword,std::string c } } -std::string DialogueWindow::parseText(std::string text) +std::string DialogueWindow::parseText(const std::string& text) { bool separatorReached = false; // only parse topics that are below the separator (this prevents actions like "Barter" that are not topics from getting blue-colored) @@ -358,11 +382,55 @@ std::string DialogueWindow::parseText(std::string text) // sort by length to make sure longer topics are replaced first std::sort(topics.begin(), topics.end(), sortByLength); - for(std::vector::const_iterator it = topics.begin(); it != topics.end(); ++it) + std::vector hypertext = MWDialogue::ParseHyperText(text); + + size_t historySize = 0; + if(mHistory->getClient()->getSubWidgetText() != nullptr) + { + historySize = mHistory->getOnlyText().size(); + } + + std::string result; + size_t hypertextPos = 0; + for (size_t i = 0; i < hypertext.size(); ++i) { - addColorInString(text,*it,"#686EBA","#B29154"); + if (hypertext[i].mLink) + { + size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText); + std::string standardForm = hypertext[i].mText; + for(; asterisk_count > 0; --asterisk_count) + standardForm.append("*"); + + standardForm = + mWindowManager.getTranslationDataStorage().topicStandardForm(standardForm); + + if( std::find(topics.begin(), topics.end(), std::string(standardForm) ) != topics.end() ) + { + result.append("#686EBA").append(hypertext[i].mText).append("#B29154"); + + mHyperLinks[historySize+hypertextPos].mLength = MyGUI::UString(hypertext[i].mText).length(); + mHyperLinks[historySize+hypertextPos].mTrueValue = lower_string(standardForm); + } + else + result += hypertext[i].mText; + } + else + { + if( !mWindowManager.getTranslationDataStorage().hasTranslation() ) + { + for(std::vector::const_iterator it = topics.begin(); it != topics.end(); ++it) + { + addColorInString(hypertext[i].mText, *it, "#686EBA", "#B29154"); + } + } + + result += hypertext[i].mText; + } + + hypertextPos += MyGUI::UString(hypertext[i].mText).length(); } - return text; + + return result; } void DialogueWindow::addText(std::string text) @@ -394,6 +462,7 @@ void DialogueWindow::updateOptions() { //Clear the list of topics mTopicsList->clear(); + mHyperLinks.clear(); mHistory->eraseText(0, mHistory->getTextLength()); if (mPtr.getTypeName() == typeid(ESM::NPC).name()) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 082d925246..3eeea01cd3 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -92,12 +92,18 @@ namespace MWGui virtual void onReferenceUnavailable(); + struct HyperLink + { + size_t mLength; + std::string mTrueValue; + }; + private: void updateOptions(); /** *Helper function that add topic keyword in blue in a text. */ - std::string parseText(std::string text); + std::string parseText(const std::string& text); int mServices; @@ -109,6 +115,10 @@ namespace MWGui MyGUI::EditPtr mDispositionText; PersuasionDialog mPersuasionDialog; + + MWBase::WindowManager& mWindowManager; + + std::map mHyperLinks; }; } #endif diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index abbc6172fe..9615e95f73 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -82,7 +82,7 @@ namespace MWGui oss << price; toAdd->setUserString("price",oss.str()); - toAdd->setCaptionWithReplacing(travelId+" - "+boost::lexical_cast(price)+"#{sgp}"); + toAdd->setCaptionWithReplacing("#{sCell=" + travelId + "} - " + boost::lexical_cast(price)+"#{sgp}"); toAdd->setSize(toAdd->getTextSize().width,sLineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); toAdd->setUserString("Destination", travelId); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index caf6a4ab07..12e72eaa9b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1050,3 +1050,8 @@ void WindowManager::startTraining(MWWorld::Ptr actor) { mTrainingWindow->startTraining(actor); } + +const Translation::Storage& WindowManager::getTranslationDataStorage() const +{ + return mTranslationDataStorage; +} diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index aaa856aef2..8bcb88e224 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -225,6 +225,8 @@ namespace MWGui virtual void startEnchanting(MWWorld::Ptr actor); virtual void startTraining(MWWorld::Ptr actor); + virtual const Translation::Storage& getTranslationDataStorage() const; + private: OEngine::GUI::MyGUIManager *mGuiManager; HUD *mHud; diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index b559c6c1c1..fb5b038612 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -81,17 +81,7 @@ namespace Translation std::string Storage::topicID(const std::string& phrase) const { - std::string result; - - //seeking for the standard phrase form - std::map::const_iterator phraseFormsIterator = - mPhraseForms.find(phrase); - - if (phraseFormsIterator != mPhraseForms.end()) - result = phraseFormsIterator->second; - else - result = phrase; - + std::string result = topicStandardForm(phrase); //seeking for the topic ID std::map::const_iterator topicIDIterator = @@ -103,8 +93,26 @@ namespace Translation return result; } + std::string Storage::topicStandardForm(const std::string& phrase) const + { + std::map::const_iterator phraseFormsIterator = + mPhraseForms.find(phrase); + + if (phraseFormsIterator != mPhraseForms.end()) + return phraseFormsIterator->second; + else + return phrase; + } + void Storage::setEncoding (const ToUTF8::FromType& encoding) { mEncoding = encoding; } + + bool Storage::hasTranslation() const + { + return !mCellNamesTranslations.empty() || + !mTopicIDs.empty() || + !mPhraseForms.empty(); + } } diff --git a/components/translation/translation.hpp b/components/translation/translation.hpp index 668d4c067f..80d44d871d 100644 --- a/components/translation/translation.hpp +++ b/components/translation/translation.hpp @@ -16,8 +16,13 @@ namespace Translation std::string translateCellName(const std::string& cellName) const; std::string topicID(const std::string& phrase) const; + // Standard form usually means nominative case + std::string topicStandardForm(const std::string& phrase) const; + void setEncoding (const ToUTF8::FromType& encoding); + bool hasTranslation() const; + private: typedef std::map ContainerType; @@ -30,7 +35,7 @@ namespace Translation ToUTF8::FromType mEncoding; - std::map mCellNamesTranslations, mTopicIDs, mPhraseForms; + ContainerType mCellNamesTranslations, mTopicIDs, mPhraseForms; }; }