Dialogue history rewrite WIP

actorid
scrawl 12 years ago
parent 6cd28d1156
commit 78e6dab9d2

@ -32,6 +32,7 @@ add_openmw_dir (mwgui
itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog
enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons
merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks
keywordsearch
) )
add_openmw_dir (mwdialogue add_openmw_dir (mwdialogue

@ -41,7 +41,7 @@ namespace MWBase
//calbacks for the GUI //calbacks for the GUI
virtual void keywordSelected (const std::string& keyword) = 0; virtual void keywordSelected (const std::string& keyword) = 0;
virtual void goodbyeSelected() = 0; virtual void goodbyeSelected() = 0;
virtual void questionAnswered (const std::string& answer) = 0; virtual void questionAnswered (int answer) = 0;
virtual bool checkServiceRefused () = 0; virtual bool checkServiceRefused () = 0;

@ -161,7 +161,7 @@ namespace MWDialogue
parseText (info->mResponse); parseText (info->mResponse);
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext));
executeScript (info->mResultScript); executeScript (info->mResultScript);
mLastTopic = Misc::StringUtils::lowerCase(it->mId); mLastTopic = Misc::StringUtils::lowerCase(it->mId);
mLastDialogue = *info; mLastDialogue = *info;
@ -263,6 +263,7 @@ namespace MWDialogue
parseText (info->mResponse); parseText (info->mResponse);
std::string title;
if (dialogue.mType==ESM::Dialogue::Persuasion) if (dialogue.mType==ESM::Dialogue::Persuasion)
{ {
std::string modifiedTopic = "s" + topic; std::string modifiedTopic = "s" + topic;
@ -272,13 +273,13 @@ namespace MWDialogue
const MWWorld::Store<ESM::GameSetting>& gmsts = const MWWorld::Store<ESM::GameSetting>& gmsts =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
win->addTitle (gmsts.find (modifiedTopic)->getString()); title = gmsts.find (modifiedTopic)->getString();
} }
else else
win->addTitle (topic); title = topic;
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); 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); MWBase::Environment::get().getJournal()->addTopic (topic, info->mId);
executeScript (info->mResultScript); executeScript (info->mResultScript);
@ -289,9 +290,7 @@ namespace MWDialogue
else else
{ {
// no response found, print a fallback text // no response found, print a fallback text
win->addTitle (topic); win->addResponse ("", topic);
win->addText ("");
} }
} }
@ -424,12 +423,9 @@ namespace MWDialogue
mTemporaryDispositionChange = 0; mTemporaryDispositionChange = 0;
} }
void DialogueManager::questionAnswered (const std::string& answer) void DialogueManager::questionAnswered (int answer)
{
if (mChoiceMap.find(answer) != mChoiceMap.end())
{ {
mChoice = mChoiceMap[answer]; mChoice = answer;
if (mDialogueMap.find(mLastTopic) != mDialogueMap.end()) if (mDialogueMap.find(mLastTopic) != mDialogueMap.end())
{ {
@ -443,12 +439,12 @@ namespace MWDialogue
std::string text = info->mResponse; std::string text = info->mResponse;
parseText (text); parseText (text);
mChoiceMap.clear();
mChoice = -1; mChoice = -1;
mIsInChoice = false; mIsInChoice = false;
MWBase::Environment::get().getWindowManager()->getDialogueWindow()->clearChoices();
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (Interpreter::fixDefinesDialog(text, interpreterContext)); MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse (Interpreter::fixDefinesDialog(text, interpreterContext));
MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId); MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId);
executeScript (info->mResultScript); executeScript (info->mResultScript);
mLastDialogue = *info; mLastDialogue = *info;
@ -458,19 +454,11 @@ namespace MWDialogue
updateTopics(); updateTopics();
} }
}
void DialogueManager::printError (const std::string& error)
{
MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
win->addText(error);
}
void DialogueManager::askQuestion (const std::string& question, int choice) void DialogueManager::askQuestion (const std::string& question, int choice)
{ {
MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
win->askQuestion(question); win->addChoice(question, choice);
mChoiceMap[Misc::StringUtils::lowerCase(question)] = choice;
mIsInChoice = true; mIsInChoice = true;
} }
@ -551,10 +539,10 @@ namespace MWDialogue
const MWWorld::Store<ESM::GameSetting>& gmsts = const MWWorld::Store<ESM::GameSetting>& gmsts =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
win->addTitle (gmsts.find ("sServiceRefusal")->getString());
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); 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); executeScript (info->mResultScript);
return true; return true;
@ -565,9 +553,7 @@ namespace MWDialogue
std::vector<HyperTextToken> ParseHyperText(const std::string& text) std::vector<HyperTextToken> ParseHyperText(const std::string& text)
{ {
std::vector<HyperTextToken> result; std::vector<HyperTextToken> result;
MyGUI::UString utext(text); MyGUI::UString utext(text);
size_t pos_begin, pos_end, iteration_pos = 0; size_t pos_begin, pos_end, iteration_pos = 0;
for(;;) for(;;)
{ {

@ -30,7 +30,6 @@ namespace MWDialogue
bool mTalkedTo; bool mTalkedTo;
int mChoice; int mChoice;
std::map<std::string, int> mChoiceMap;
std::string mLastTopic; std::string mLastTopic;
ESM::DialInfo mLastDialogue; ESM::DialInfo mLastDialogue;
bool mIsInChoice; bool mIsInChoice;
@ -46,8 +45,6 @@ namespace MWDialogue
bool compile (const std::string& cmd,std::vector<Interpreter::Type_Code>& code); bool compile (const std::string& cmd,std::vector<Interpreter::Type_Code>& code);
void executeScript (const std::string& script); void executeScript (const std::string& script);
void printError (const std::string& error);
void executeTopic (const std::string& topic, bool randomResponse=false); void executeTopic (const std::string& topic, bool randomResponse=false);
public: public:
@ -72,7 +69,7 @@ namespace MWDialogue
//calbacks for the GUI //calbacks for the GUI
virtual void keywordSelected (const std::string& keyword); virtual void keywordSelected (const std::string& keyword);
virtual void goodbyeSelected(); virtual void goodbyeSelected();
virtual void questionAnswered (const std::string& answer); virtual void questionAnswered (int answer);
virtual void persuade (int type); virtual void persuade (int type);
virtual int getTemporaryDispositionChange () const; virtual int getTemporaryDispositionChange () const;

@ -120,7 +120,7 @@ struct MWGui::TypesetBookImpl : TypesetBook
size_t pageCount () const { return mPages.size (); } size_t pageCount () const { return mPages.size (); }
std::pair <int, int> getSize () const std::pair <unsigned int, unsigned int> getSize () const
{ {
return std::make_pair (mRect.width (), mRect.height ()); return std::make_pair (mRect.width (), mRect.height ());
} }

@ -27,7 +27,7 @@ namespace MWGui
/// it is the largest distance from the left edge to the /// it is the largest distance from the left edge to the
/// right edge. The second integer is the height of all /// right edge. The second integer is the height of all
/// text combined prior to pagination. /// text combined prior to pagination.
virtual std::pair <int, int> getSize () const = 0; virtual std::pair <unsigned int, unsigned int> getSize () const = 0;
}; };
/// A factory class for creating a typeset book instance. /// A factory class for creating a typeset book instance.

@ -18,29 +18,18 @@
#include "spellbuyingwindow.hpp" #include "spellbuyingwindow.hpp"
#include "inventorywindow.hpp" #include "inventorywindow.hpp"
#include "travelwindow.hpp" #include "travelwindow.hpp"
#include "bookpage.hpp"
/**
*Copied from the internet.
*/
namespace namespace
{ {
MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text)
std::string lower_string(const std::string& str)
{ {
std::string lowerCase = Misc::StringUtils::lowerCase (str); typedef MWGui::BookTypesetter::Utf8Point point;
return lowerCase; point begin = reinterpret_cast <point> (text);
}
std::string::size_type find_str_ci(const std::string& str, const std::string& substr,size_t pos) return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text));
{
return lower_string(str).find(lower_string(substr),pos);
}
bool sortByLength (const std::string& left, const std::string& right)
{
return left.size() > right.size();
} }
} }
@ -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<std::string, Link*>& 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<size_t, size_t> Range;
std::map<Range, intptr_t> 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<Range, intptr_t>::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() DialogueWindow::DialogueWindow()
: WindowBase("openmw_dialogue_window.layout") : WindowBase("openmw_dialogue_window.layout")
, mPersuasionDialog() , mPersuasionDialog()
@ -129,15 +215,6 @@ namespace MWGui
//History view //History view
getWidget(mHistory, "History"); 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 //Topics list
getWidget(mTopicsList, "TopicsList"); getWidget(mTopicsList, "TopicsList");
@ -149,12 +226,20 @@ namespace MWGui
getWidget(mDispositionBar, "Disposition"); getWidget(mDispositionBar, "Disposition");
getWidget(mDispositionText,"DispositionText"); 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<MyGUI::Window*>(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); static_cast<MyGUI::Window*>(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize);
} }
void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender)
{ {
/*
MyGUI::ISubWidgetText* t = mHistory->getClient()->getSubWidgetText(); MyGUI::ISubWidgetText* t = mHistory->getClient()->getSubWidgetText();
if(t == NULL) if(t == NULL)
return; return;
@ -198,19 +283,22 @@ namespace MWGui
if(color == "#572D21") if(color == "#572D21")
MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key));
} }
*/
} }
void DialogueWindow::onWindowResize(MyGUI::Window* _sender) void DialogueWindow::onWindowResize(MyGUI::Window* _sender)
{ {
mTopicsList->adjustSize(); mTopicsList->adjustSize();
updateHistory();
} }
void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
{ {
if (mHistory->getVScrollPosition() - _rel*0.3 < 0) if (!mScrollBar->getVisible())
mHistory->setVScrollPosition(0); return;
else mScrollBar->setScrollPosition(std::min(static_cast<int>(mScrollBar->getScrollRange()-1),
mHistory->setVScrollPosition(mHistory->getVScrollPosition() - _rel*0.3); std::max(0, static_cast<int>(mScrollBar->getScrollPosition() - _rel*0.3))));
onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition());
} }
void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) void DialogueWindow::onByeClicked(MyGUI::Widget* _sender)
@ -231,7 +319,7 @@ namespace MWGui
} }
if (id >= separatorPos) if (id >= separatorPos)
MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(topic)); MWBase::Environment::get().getDialogueManager()->keywordSelected(Misc::StringUtils::lowerCase(topic));
else else
{ {
const MWWorld::Store<ESM::GameSetting> &gmst = const MWWorld::Store<ESM::GameSetting> &gmst =
@ -296,13 +384,25 @@ namespace MWGui
mTopicsList->clear(); mTopicsList->clear();
mHyperLinks.clear(); mHyperLinks.clear();
mHistory->setCaption("");
for (std::vector<DialogueText*>::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it)
delete (*it);
mHistoryContents.clear();
for (std::vector<Link*>::iterator it = mLinks.begin(); it != mLinks.end(); ++it)
delete (*it);
mLinks.clear();
updateOptions(); updateOptions();
} }
void DialogueWindow::setKeywords(std::list<std::string> keyWords) void DialogueWindow::setKeywords(std::list<std::string> keyWords)
{ {
mTopicsList->clear(); mTopicsList->clear();
for (std::map<std::string, Link*>::iterator it = mTopicLinks.begin(); it != mTopicLinks.end(); ++it)
delete it->second;
mTopicLinks.clear();
mKeywordSearch.clear();
bool isCompanion = !MWWorld::Class::get(mPtr).getScript(mPtr).empty() bool isCompanion = !MWWorld::Class::get(mPtr).getScript(mPtr).empty()
&& mPtr.getRefData().getLocals().getIntVar(MWWorld::Class::get(mPtr).getScript(mPtr), "companion"); && mPtr.getRefData().getLocals().getIntVar(MWWorld::Class::get(mPtr).getScript(mPtr), "companion");
@ -346,46 +446,15 @@ namespace MWGui
for(std::list<std::string>::iterator it = keyWords.begin(); it != keyWords.end(); ++it) for(std::list<std::string>::iterator it = keyWords.begin(); it != keyWords.end(); ++it)
{ {
mTopicsList->addItem(*it); mTopicsList->addItem(*it);
}
mTopicsList->adjustSize();
}
void DialogueWindow::removeKeyword(std::string keyWord)
{
if(mTopicsList->hasItem(keyWord))
{
mTopicsList->removeItem(keyWord);
}
mTopicsList->adjustSize();
}
void addColorInString(std::string& str, const std::string& keyword,std::string color1, std::string color2) Topic* t = new Topic(*it);
{ mTopicLinks[*it] = t;
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) mKeywordSearch.seed(*it, intptr_t(t));
return;
} }
mTopicsList->adjustSize();
str.insert(pos,color1); updateHistory();
pos += color1.length();
pos += keyword.length();
str.insert(pos,color2);
pos+= color2.length();
}
} }
std::string DialogueWindow::parseText(const std::string& text) std::string DialogueWindow::parseText(const std::string& text)
@ -410,18 +479,19 @@ namespace MWGui
separatorReached = true; separatorReached = true;
} }
// sort by length to make sure longer topics are replaced first
std::sort(topics.begin(), topics.end(), sortByLength);
std::vector<MWDialogue::HyperTextToken> hypertext = MWDialogue::ParseHyperText(text); std::vector<MWDialogue::HyperTextToken> hypertext = MWDialogue::ParseHyperText(text);
/*
size_t historySize = 0; size_t historySize = 0;
if(mHistory->getClient()->getSubWidgetText() != NULL) if(mHistory->getClient()->getSubWidgetText() != NULL)
{ {
historySize = mHistory->getOnlyText().size(); historySize = mHistory->getOnlyText().size();
} }
*/
std::string result; std::string result;
/*
size_t hypertextPos = 0; size_t hypertextPos = 0;
for (size_t i = 0; i < hypertext.size(); ++i) for (size_t i = 0; i < hypertext.size(); ++i)
{ {
@ -461,38 +531,115 @@ namespace MWGui
hypertextPos += MyGUI::UString(hypertext[i].mText).length(); hypertextPos += MyGUI::UString(hypertext[i].mText).length();
} }
*/
return result; return result;
} }
void DialogueWindow::addText(std::string text) void DialogueWindow::updateHistory(bool scrollbar)
{
if (!scrollbar && mScrollBar->getVisible())
{
mHistory->setSize(mHistory->getSize()+MyGUI::IntSize(mScrollBar->getWidth(),0));
mScrollBar->setVisible(false);
}
if (scrollbar && !mScrollBar->getVisible())
{ {
mHistory->addDialogText("#B29154"+parseText(text)+"#B29154"); mHistory->setSize(mHistory->getSize()-MyGUI::IntSize(mScrollBar->getWidth(),0));
mScrollBar->setVisible(true);
} }
void DialogueWindow::addMessageBox(const std::string& text) BookTypesetter::Ptr typesetter = BookTypesetter::create (mHistory->getWidth(), std::numeric_limits<int>().max());
for (std::vector<DialogueText*>::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<std::string, int>::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::notifyLinkClicked (TypesetBook::InteractiveId link)
{
reinterpret_cast<Link*>(link)->activated();
}
void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos)
{ {
mHistory->addDialogText("\n#FFFFFF"+text+"#B29154"); mHistory->setPosition(0,-pos);
} }
void DialogueWindow::addTitle(std::string text) void DialogueWindow::addResponse(const std::string &text, const std::string &title)
{ {
// This is called from the dialogue manager, so text is // This is called from the dialogue manager, so text is
// case-smashed - thus we have to retrieve the correct case // case-smashed - thus we have to retrieve the correct case
// of the text through the topic list. // of the title through the topic list.
std::string realTitle = title;
if (realTitle != "")
{
for (size_t i=0; i<mTopicsList->getItemCount(); ++i) for (size_t i=0; i<mTopicsList->getItemCount(); ++i)
{ {
std::string item = mTopicsList->getItemNameAt(i); std::string item = mTopicsList->getItemNameAt(i);
if (lower_string(item) == text) if (Misc::StringUtils::lowerCase(item) == title)
text = item; {
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() void DialogueWindow::updateOptions()
@ -500,7 +647,6 @@ namespace MWGui
//Clear the list of topics //Clear the list of topics
mTopicsList->clear(); mTopicsList->clear();
mHyperLinks.clear(); mHyperLinks.clear();
mHistory->eraseText(0, mHistory->getTextLength());
if (mPtr.getTypeName() == typeid(ESM::NPC).name()) if (mPtr.getTypeName() == typeid(ESM::NPC).name())
{ {
@ -513,7 +659,7 @@ namespace MWGui
void DialogueWindow::goodbye() void DialogueWindow::goodbye()
{ {
mHistory->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sGoodbye")->getString()); //mHistory->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sGoodbye")->getString());
mTopicsList->setEnabled(false); mTopicsList->setEnabled(false);
mEnabled = false; mEnabled = false;
} }

@ -4,6 +4,10 @@
#include "windowbase.hpp" #include "windowbase.hpp"
#include "referenceinterface.hpp" #include "referenceinterface.hpp"
#include "bookpage.hpp"
#include "keywordsearch.hpp"
namespace MWGui namespace MWGui
{ {
class WindowManager; class WindowManager;
@ -21,7 +25,8 @@ namespace MWGui
namespace MWGui namespace MWGui
{ {
class DialogueHistory; class DialogueHistoryViewModel;
class BookPage;
class PersuasionDialog : public WindowModal class PersuasionDialog : public WindowModal
{ {
@ -44,6 +49,55 @@ namespace MWGui
void onPersuade (MyGUI::Widget* sender); 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 <std::string, intptr_t> KeywordSearchT;
struct DialogueText
{
virtual ~DialogueText() {}
virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& 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<std::string, Link*>& 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<std::string, Link*>& topicLinks);
};
struct Goodbye : DialogueText
{
virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks);
};
class DialogueWindow: public WindowBase, public ReferenceInterface class DialogueWindow: public WindowBase, public ReferenceInterface
{ {
public: public:
@ -52,19 +106,19 @@ namespace MWGui
// Events // Events
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
/** Event : Dialog finished, OK button clicked.\n void notifyLinkClicked (TypesetBook::InteractiveId link);
signature : void method()\n
*/
EventHandle_Void eventBye;
void startDialogue(MWWorld::Ptr actor, std::string npcName); void startDialogue(MWWorld::Ptr actor, std::string npcName);
void stopDialogue(); void stopDialogue();
void setKeywords(std::list<std::string> keyWord); void setKeywords(std::list<std::string> 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 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 goodbye();
void onFrame(); void onFrame();
@ -89,6 +143,10 @@ namespace MWGui
void onMouseWheel(MyGUI::Widget* _sender, int _rel); void onMouseWheel(MyGUI::Widget* _sender, int _rel);
void onWindowResize(MyGUI::Window* _sender); void onWindowResize(MyGUI::Window* _sender);
void onScrollbarMoved (MyGUI::ScrollBar* sender, size_t pos);
void updateHistory(bool scrollbar=false);
virtual void onReferenceUnavailable(); virtual void onReferenceUnavailable();
struct HyperLink struct HyperLink
@ -108,11 +166,22 @@ namespace MWGui
bool mEnabled; bool mEnabled;
DialogueHistory* mHistory; std::vector<DialogueText*> mHistoryContents;
std::map<std::string, int> mChoices;
std::vector<Link*> mLinks;
std::map<std::string, Link*> mTopicLinks;
KeywordSearchT mKeywordSearch;
BookPage* mHistory;
Widgets::MWList* mTopicsList; Widgets::MWList* mTopicsList;
MyGUI::ScrollBar* mScrollBar;
MyGUI::ProgressPtr mDispositionBar; MyGUI::ProgressPtr mDispositionBar;
MyGUI::EditBox* mDispositionText; MyGUI::EditBox* mDispositionText;
std::stringstream mText;
PersuasionDialog mPersuasionDialog; PersuasionDialog mPersuasionDialog;
std::map<size_t, HyperLink> mHyperLinks; std::map<size_t, HyperLink> mHyperLinks;

@ -1,78 +1,11 @@
#include "dialoguehistory.hpp" #include "dialoguehistory.hpp"
#include "../mwbase/windowmanager.hpp"
#include "widgets.hpp"
#include "../mwworld/esmstore.hpp"
#include <iostream>
#include <iterator>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
namespace MWGui namespace MWGui
{ {
MyGUI::UString DialogueHistory::getColorAtPos(size_t _pos) DialogueHistoryViewModel::DialogueHistoryViewModel()
{
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");
} }
} }

@ -1,19 +1,23 @@
#ifndef MWGUI_DIALOGE_HISTORY_H #ifndef MWGUI_DIALOGE_HISTORY_H
#define MWGUI_DIALOGE_HISTORY_H #define MWGUI_DIALOGE_HISTORY_H
#include <openengine/gui/layout.hpp> #include "keywordsearch.hpp"
#include <platform/stdint.h>
namespace MWGui namespace MWGui
{ {
class DialogueHistory : public MyGUI::EditBox class DialogueHistoryViewModel
{ {
MYGUI_RTTI_DERIVED( DialogueHistory )
public: public:
Widget* getClient() { return mClient; } DialogueHistoryViewModel();
MyGUI::UString getColorAtPos(size_t _pos);
MyGUI::UString getColorTextAt(size_t _pos); private:
void addDialogHeading(const MyGUI::UString& parText); typedef KeywordSearch <std::string, intptr_t> KeywordSearchT;
void addDialogText(const MyGUI::UString& parText);
mutable bool mKeywordSearchLoaded;
mutable KeywordSearchT mKeywordSearch;
}; };
} }
#endif #endif

@ -1,17 +1,20 @@
#include "journalviewmodel.hpp" #include "journalviewmodel.hpp"
#include <map>
#include <sstream>
#include <boost/make_shared.hpp>
#include <MyGUI_LanguageManager.h>
#include <components/misc/utf8stream.hpp>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp" #include "../mwbase/journal.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwdialogue/journalentry.hpp" #include "../mwdialogue/journalentry.hpp"
#include <MyGUI_LanguageManager.h> #include "keywordsearch.hpp"
#include <components/misc/utf8stream.hpp>
#include <map>
#include <sstream>
#include <boost/make_shared.hpp>
using namespace MWGui; using namespace MWGui;
@ -19,143 +22,12 @@ namespace MWGui { struct JournalViewModelImpl; }
static void injectMonthName (std::ostream & os, int month); static void injectMonthName (std::ostream & os, int month);
template <typename string_t, typename value_t>
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 <wchar_t, Entry> 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 struct MWGui::JournalViewModelImpl : JournalViewModel
{ {
typedef KeywordSearch <std::string, intptr_t> keyword_search_t; typedef KeywordSearch <std::string, intptr_t> KeywordSearchT;
mutable bool mKeywordSearchLoaded; mutable bool mKeywordSearchLoaded;
mutable keyword_search_t mKeywordSearch; mutable KeywordSearchT mKeywordSearch;
std::locale mLocale; std::locale mLocale;
@ -253,7 +125,7 @@ struct MWGui::JournalViewModelImpl : JournalViewModel
std::string::const_iterator i = utf8text.begin (); 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)) while (i != utf8text.end () && mModel->mKeywordSearch.search (i, utf8text.end (), match))
{ {
@ -300,8 +172,8 @@ struct MWGui::JournalViewModelImpl : JournalViewModel
mutable std::string timestamp_buffer; mutable std::string timestamp_buffer;
JournalEntryImpl (JournalViewModelImpl const * Model, iterator_t itr) : JournalEntryImpl (JournalViewModelImpl const * model, iterator_t itr) :
BaseEntry <iterator_t, JournalEntry> (Model, itr) BaseEntry <iterator_t, JournalEntry> (model, itr)
{} {}
std::string getText () const std::string getText () const

@ -152,6 +152,10 @@ namespace
MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); 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; Book journalBook;
if (mModel->isEmpty ()) if (mModel->isEmpty ())
journalBook = createEmptyJournalBook (); journalBook = createEmptyJournalBook ();

@ -0,0 +1,139 @@
#ifndef MWGUI_KEYWORDSEARCH_H
#define MWGUI_KEYWORDSEARCH_H
#include <map>
#include <locale>
#include <stdexcept>
template <typename string_t, typename value_t>
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 <wchar_t, Entry> 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

@ -108,7 +108,6 @@ namespace MWGui
mGui = mGuiManager->getGui(); mGui = mGuiManager->getGui();
//Register own widgets with MyGUI //Register own widgets with MyGUI
MyGUI::FactoryManager::getInstance().registerFactory<DialogueHistory>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWAttribute>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWAttribute>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSpell>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSpell>("Widget");

@ -23,7 +23,6 @@ namespace Interpreter{
} }
std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context){ std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context){
unsigned int start = 0; unsigned int start = 0;
std::ostringstream retval; std::ostringstream retval;
for(unsigned int i = 0; i < text.length(); i++){ for(unsigned int i = 0; i < text.length(); i++){

@ -5,14 +5,13 @@
<Widget type="Widget" skin="MW_Box" position="8 8 415 381" align="Stretch" name = "Client"/> <Widget type="Widget" skin="MW_Box" position="8 8 415 381" align="Stretch" name = "Client"/>
<!-- The Dialogue history --> <Widget type="Widget" position="13 13 391 371" align="ALIGN_LEFT ALIGN_TOP ALIGN_STRETCH">
<Widget type="DialogueHistory" skin="MW_TextBoxEdit" position="13 13 405 371" name="History" align="ALIGN_LEFT ALIGN_TOP ALIGN_STRETCH"> <Widget type="BookPage" skin="MW_BookPage" position="0 0 391 371" name="History" align="ALIGN_LEFT ALIGN_TOP ALIGN_STRETCH">
<Property key="Static" value="true"/> </Widget>
<Property key="WordWrap" value="true"/> </Widget>
<Property key="MultiLine" value="1" />
<Property key="VisibleVScroll" value="1" /> <Widget type="ScrollBar" skin="MW_VScroll" position="404 13 14 371" align="ALIGN_RIGHT ALIGN_VSTRETCH" name="VScroll">
<!-- box for receiving mouse events --> <Property key="Visible" value="false"/>
<Widget type="Widget" skin="" position="0 0 400 375" name="EventBox" align="ALIGN_LEFT ALIGN_TOP ALIGN_STRETCH"/>
</Widget> </Widget>
<!-- The disposition bar--> <!-- The disposition bar-->

Loading…
Cancel
Save