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:
parent
55ca037411
commit
50d688c2fc
3 changed files with 564 additions and 1 deletions
|
@ -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
|
||||
|
|
472
apps/openmw/mwgui/journalviewmodel.cpp
Normal file
472
apps/openmw/mwgui/journalviewmodel.cpp
Normal 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> ();
|
||||
}
|
91
apps/openmw/mwgui/journalviewmodel.hpp
Normal file
91
apps/openmw/mwgui/journalviewmodel.hpp
Normal 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
|
Loading…
Reference in a new issue