1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-03 14:45:37 +00:00

Created a view-model to present journal data to the UI in the form it

intends to display it.
This commit is contained in:
Nathan Jeffords 2013-01-27 12:01:37 -08:00
parent 55ca037411
commit 50d688c2fc
3 changed files with 564 additions and 1 deletions

View file

@ -31,7 +31,7 @@ add_openmw_dir (mwgui
confirmationdialog alchemywindow referenceinterface spellwindow mainmenu quickkeysmenu
itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog
enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons
merchantrepair repair soulgemdialog companionwindow bookpage
merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel
)
add_openmw_dir (mwdialogue

View file

@ -0,0 +1,472 @@
#include "journalviewmodel.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp"
#include "../mwbase/environment.hpp"
#include "../mwdialogue/journalentry.hpp"
//#include "MyGUI_LanguageManager.h"
#include <components/misc/utf8stream.hpp>
#include <map>
#include <sstream>
using namespace MWGui;
namespace MWGui { struct JournalViewModel; }
static void injectMonthName (std::ostream & os, int month);
template <typename string_t, typename value_t>
class keyword_search
{
public:
typedef typename string_t::const_iterator point;
struct match
{
point Beg;
point End;
value_t Value;
};
void seed (string_t Keyword, value_t Value)
{
seed_impl (std::move (Keyword), std::move (Value), 0, Root);
}
void clear ()
{
Root.Children.clear ();
Root.Keyword.clear ();
}
bool search (point Beg, point End, match & Match)
{
for (auto i = Beg; i != End; ++i)
{
// check first character
auto candidate = Root.Children.find (std::tolower (*i, Locale));
// no match, on to next character
if (candidate == Root.Children.end ())
continue;
// see how far the match goes
auto j = i;
while ((j + 1) != End)
{
auto next = candidate->second.Children.find (std::tolower (*++j, Locale));
if (next == candidate->second.Children.end ())
break;
candidate = next;
}
// didn't match enough to disambiguate, on to next character
if (!candidate->second.Keyword.size ())
continue;
// match the rest of the keyword
auto t = candidate->second.Keyword.begin () + (j - i);
while (j != End && t != candidate->second.Keyword.end ())
{
if (std::tolower (*j, Locale) != std::tolower (*t, Locale))
break;
++j, ++t;
}
// didn't match full keyword, on to next character
if (t != candidate->second.Keyword.end ())
continue;
// we did it, report the good news
Match.Value = candidate->second.Value;
Match.Beg = i;
Match.End = 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 Keyword;
value_t Value;
childen_t Children;
};
void seed_impl (string_t Keyword, value_t Value, size_t Depth, entry & Entry)
{
auto ch = tolower (Keyword.at (Depth), Locale);
auto j = Entry.Children.find (ch);
if (j == Entry.Children.end ())
{
Entry.Children [ch].Value = std::move (Value);
Entry.Children [ch].Keyword = std::move (Keyword);
}
else
{
if (j->second.Keyword.size () > 0)
{
if (Keyword == j->second.Keyword)
throw std::runtime_error ("duplicate keyword inserted");
auto pushValue = std::move (j->second.Value);
auto pushKeyword = std::move (j->second.Keyword);
j->second.Keyword.clear ();
if (Depth >= pushKeyword.size ())
throw std::runtime_error ("unexpected");
seed_impl (std::move (pushKeyword), std::move (pushValue), Depth+1, j->second);
}
seed_impl (std::move (Keyword), std::move (Value), Depth+1, j->second);
}
}
entry Root;
std::locale Locale;
};
struct MWGui::JournalViewModel : IJournalViewModel
{
typedef keyword_search <std::string, intptr_t> foobar;
mutable bool FooBar_loaded;
mutable foobar FooBar;
std::locale Locale;
JournalViewModel ()
{
FooBar_loaded = false;
}
virtual ~JournalViewModel ()
{
}
//TODO: replace this nasty BS
static utf8_span to_utf8_span (std::string const & str)
{
if (str.size () == 0)
return utf8_span (utf8_point (NULL), utf8_point (NULL));
utf8_point point = reinterpret_cast <utf8_point> (&str.front ());
return utf8_span (point, point + str.size ());
}
void load ()
{
}
void unload ()
{
FooBar.clear ();
FooBar_loaded = false;
}
void ensure_FooBar_loaded () const
{
if (!FooBar_loaded)
{
auto journal = MWBase::Environment::get().getJournal();
for(auto i = journal->topicBegin(); i != journal->topicEnd (); ++i)
FooBar.seed (i->first, intptr_t (&i->second));
FooBar_loaded = true;
}
}
wchar_t tolower (wchar_t ch) const { return std::tolower (ch, Locale); }
bool is_empty () const
{
auto journal = MWBase::Environment::get().getJournal();
return journal->begin () == journal->end ();
}
template <typename iterator_t, typename IInterface>
struct base_entry : IInterface
{
iterator_t itr;
JournalViewModel const * Model;
base_entry (JournalViewModel const * Model, iterator_t itr) :
Model (Model), itr (itr), loaded (false)
{}
virtual ~base_entry () {}
mutable bool loaded;
mutable std::string utf8text;
virtual std::string getText () const = 0;
void ensure_loaded () const
{
if (!loaded)
{
utf8text = getText ();
loaded = true;
}
}
utf8_span body () const
{
ensure_loaded ();
return to_utf8_span (utf8text);
}
void visit_spans (std::function < void (topic_id, size_t, size_t)> visitor) const
{
ensure_loaded ();
Model->ensure_FooBar_loaded ();
std::string::const_iterator i = utf8text.begin ();
foobar::match Match;
while (i != utf8text.end () && Model->FooBar.search (i, utf8text.end (), Match))
{
if (i != Match.Beg)
visitor (0, i - utf8text.begin (), Match.Beg - utf8text.begin ());
visitor (Match.Value, Match.Beg - utf8text.begin (), Match.End - utf8text.begin ());
i = Match.End;
}
if (i != utf8text.end ())
visitor (0, i - utf8text.begin (), utf8text.size ());
}
};
void visit_quest_names (bool active_only, std::function <void (quest_id, utf8_span)> visitor) const
{
auto journal = MWBase::Environment::get ().getJournal ();
for (auto i = journal->questBegin (); i != journal->questEnd (); ++i)
{
if (active_only && i->second.isFinished ())
continue;
visitor (reinterpret_cast <quest_id> (&i->second), to_utf8_span (i->first));
}
}
void visit_quest_name (quest_id questId, std::function <void (utf8_span)> visitor) const
{
MWDialogue::Quest const * quest = reinterpret_cast <MWDialogue::Quest const *> (questId);
auto name = quest->getName ();
visitor (to_utf8_span (name));
}
template <typename iterator_t>
struct journal_entry : base_entry <iterator_t, IJournalEntry>
{
using base_entry <iterator_t, IJournalEntry>::itr;
mutable std::string timestamp_buffer;
journal_entry (JournalViewModel const * Model, iterator_t itr) :
base_entry <iterator_t, IJournalEntry> (Model, itr)
{}
std::string getText () const
{
return itr->getText(MWBase::Environment::get().getWorld()->getStore());
}
utf8_span timestamp () const
{
if (timestamp_buffer.empty ())
{
std::ostringstream os;
os << itr->mDayOfMonth << ' ';
injectMonthName (os, itr->mMonth);
os << " (Day " << (itr->mDay + 1) << ')';
timestamp_buffer = os.str ();
}
return to_utf8_span (timestamp_buffer);
}
};
void visit_journal_entries (quest_id questId, std::function <void (IJournalEntry const &)> visitor) const
{
auto journal = MWBase::Environment::get().getJournal();
if (questId != 0)
{
MWDialogue::Quest const * quest = reinterpret_cast <MWDialogue::Quest const *> (questId);
for(auto i = journal->begin(); i != journal->end (); ++i)
{
for (auto j = quest->begin (); j != quest->end (); ++j)
{
if (i->mInfoId == *j)
visitor (journal_entry <decltype (i)> (this, i));
}
}
}
else
{
for(auto i = journal->begin(); i != journal->end (); ++i)
visitor (journal_entry <decltype (i)> (this, i));
}
}
void visit_topics (std::function <void (topic_id, utf8_span)> visitor) const
{
throw std::runtime_error ("not implemented");
}
void visit_topic_name (topic_id topicId, std::function <void (utf8_span)> visitor) const
{
auto & Topic = * reinterpret_cast <MWDialogue::Topic const *> (topicId);
visitor (to_utf8_span (Topic.getName ()));
}
void visit_topic_names_starting_with (int character, std::function < void (topic_id , utf8_span) > visitor) const
{
auto journal = MWBase::Environment::get().getJournal();
for (auto i = journal->topicBegin (); i != journal->topicEnd (); ++i)
{
if (i->first [0] != std::tolower (character, Locale))
continue;
visitor (topic_id (&i->second), to_utf8_span (i->first));
}
}
void visit_topic_entries (topic_id topicId, std::function <void (ITopicEntry const &)> visitor) const
{
auto & Topic = * reinterpret_cast <MWDialogue::Topic const *> (topicId);
for (auto i = Topic.begin (); i != Topic.end (); ++i)
{
typedef decltype (Topic.begin()) iterator_t;
struct entry : base_entry <iterator_t, ITopicEntry>
{
MWDialogue::Topic const & Topic;
mutable std::string source_buffer;
entry (JournalViewModel const * Model, MWDialogue::Topic const & Topic, iterator_t itr) :
base_entry (Model, itr), Topic (Topic)
{}
std::string getText () const
{
return Topic.getEntry (*itr).getText(MWBase::Environment::get().getWorld()->getStore());
}
utf8_span source () const
{
if (source_buffer.empty ())
source_buffer = "someone";
return to_utf8_span (source_buffer);
}
};
visitor (entry (this, Topic, i));
}
}
};
static void injectMonthName (std::ostream & os, int month)
{
#ifndef MYGUI_SIGNLETON_FIXED_FOR_MSVC
static char const * month_names [] =
{
"Morning Star",
"Sun's Dawn",
"First Seed",
"Rain's Hand",
"Second Seed",
"Midyear",
"Sun's Height",
"Last Seed",
"Hearthfire",
"Frostfall",
"Sun's Dusk",
"Evening Star",
};
if (month >= 0 && month <= 11)
os << month_names [month ];
else
os << month ;
#else
auto lm = MyGUI::LanguageManager::getInstance();
if (month == 0)
os << lm.getTag ("sMonthMorningstar");
else if (month == 1)
os << lm.getTag ("sMonthSunsdawn");
else if (month == 2)
os << lm.getTag ("sMonthFirstseed");
else if (month == 3)
os << lm.getTag ("sMonthRainshand");
else if (month == 4)
os << lm.getTag ("sMonthSecondseed");
else if (month == 5)
os << lm.getTag ("sMonthMidyear");
else if (month == 6)
os << lm.getTag ("sMonthSunsheight");
else if (month == 7)
os << lm.getTag ("sMonthLastseed");
else if (month == 8)
os << lm.getTag ("sMonthHeartfire");
else if (month == 9)
os << lm.getTag ("sMonthFrostfall");
else if (month == 10)
os << lm.getTag ("sMonthSunsdusk");
else if (month == 11)
os << lm.getTag ("sMonthEveningstar");
else
os << month;
#endif
}
IJournalViewModel::ptr IJournalViewModel::create ()
{
return std::make_shared <JournalViewModel> ();
}

View file

@ -0,0 +1,91 @@
#ifndef MWGUI_JOURNALVIEWMODEL_HPP
#define MWGUI_JOURNALVIEWMODEL_HPP
#include <string>
#include <memory>
#include <functional>
#include <platform/stdint.h>
namespace MWGui
{
/// View-Model for the journal GUI
///
/// This interface defines an abstract data model suited
// specifically to the needs of the journal GUI. It isolates
/// the journal GUI from the implementation details of the
/// game data store.
struct IJournalViewModel
{
typedef std::shared_ptr <IJournalViewModel> ptr;
typedef intptr_t quest_id;
typedef intptr_t topic_id;
typedef uint8_t const * utf8_point;
typedef std::pair <utf8_point, utf8_point> utf8_span;
/// The base interface for both journal entries and topics.
struct IEntry
{
/// returns the body text for the journal entry
///
/// This function returns a borrowed reference to the body of the
/// journal entry. The returned reference becomes invalid when the
/// entry is destroyed.
virtual utf8_span body () const = 0;
/// Visits each subset of text in the body, delivering the beginning
/// and end of the span relative to the body, and a valid topic ID if
/// the span represents a keyword, or zero if not.
virtual void visit_spans (std::function <void (topic_id, size_t, size_t)> visitor) const = 0;
};
/// An interface to topic data.
struct ITopicEntry : IEntry
{
/// Returns a pre-formatted span of UTF8 encoded text representing
/// the name of the NPC this portion of dialog was heard from.
virtual utf8_span source () const = 0;
};
/// An interface to journal data.
struct IJournalEntry : IEntry
{
/// Returns a pre-formatted span of UTF8 encoded text representing
/// the in-game date this entry was added to the journal.
virtual utf8_span timestamp () const = 0;
};
/// called prior to journal opening
virtual void load () = 0;
/// called prior to journal closing
virtual void unload () = 0;
/// returns true if their are no journal entries to display
virtual bool is_empty () const = 0;
/// provides access to the name of the quest with the specified identifier
virtual void visit_quest_name (topic_id topicId, std::function <void (utf8_span)> visitor) const = 0;
/// walks the active and optionally completed, quests providing the quest id and name
virtual void visit_quest_names (bool active_only, std::function <void (quest_id, utf8_span)> visitor) const = 0;
/// walks over the journal entries related to the specified quest identified by its id
virtual void visit_journal_entries (quest_id questId, std::function <void (IJournalEntry const &)> visitor) const = 0;
/// provides the name of the topic specified by its id
virtual void visit_topic_name (topic_id topicId, std::function <void (utf8_span)> visitor) const = 0;
/// walks over the topics whose names start with the specified character providing the topics id and name
virtual void visit_topic_names_starting_with (int character, std::function < void (topic_id , utf8_span) > visitor) const = 0;
/// walks over the topic entries for the topic specified by its identifier
virtual void visit_topic_entries (topic_id topicId, std::function <void (ITopicEntry const &)> visitor) const = 0;
// create an instance of the default journal view model implementation
static ptr create ();
};
}
#endif // MWGUI_JOURNALVIEWMODEL_HPP