diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b5d4badf26..47ee63fbc3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -25,13 +25,14 @@ add_openmw_dir (mwinput add_openmw_dir (mwgui textinput widgets race class birth review windowmanagerimp console dialogue - dialoguehistory windowbase statswindow messagebox journalwindow charactercreation + windowbase statswindow messagebox journalwindow charactercreation mapwindow windowpinnablebase tooltips scrollwindow bookwindow list formatting inventorywindow container hud countdialog tradewindow settingswindow confirmationdialog alchemywindow referenceinterface spellwindow mainmenu quickkeysmenu itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons - merchantrepair repair soulgemdialog companionwindow + 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 de39b212ad..aafc0a12f3 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/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 976d7d84c1..1832bf5420 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -201,6 +201,8 @@ namespace MWBase ///< Hides dialog and schedules dialog to be deleted. virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector()) = 0; + virtual void staticMessageBox(const std::string& message) = 0; + virtual void removeStaticMessageBox() = 0; virtual void enterPressed () = 0; virtual int readPressedButton() = 0; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 220adf566a..e66cfa6e26 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); + std::string text = info->mResponse; + parseText (text); - mChoiceMap.clear(); - mChoice = -1; - mIsInChoice = false; + mChoice = -1; + mIsInChoice = false; + MWBase::Environment::get().getWindowManager()->getDialogueWindow()->clearChoices(); - 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; - } + 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 a7bec31a6a..13f7cb098c 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 new file mode 100644 index 0000000000..ef688be1bd --- /dev/null +++ b/apps/openmw/mwgui/bookpage.cpp @@ -0,0 +1,1227 @@ +#include "bookpage.hpp" + +#include "MyGUI_FontManager.h" +#include "MyGUI_RenderItem.h" +#include "MyGUI_RenderManager.h" +#include "MyGUI_TextureUtility.h" +#include "MyGUI_FactoryManager.h" + +#include +#include +#include +#include + +#include + +namespace MWGui +{ +struct TypesetBookImpl; +struct PageDisplay; +struct BookPageImpl; + +static bool ucsSpace (int codePoint); +static bool ucsLineBreak (int codePoint); +static bool ucsBreakingSpace (int codePoint); + +struct BookTypesetter::Style { virtual ~Style () {} }; + +struct TypesetBookImpl : TypesetBook +{ + typedef std::vector Content; + typedef std::list Contents; + typedef Utf8Stream::Point Utf8Point; + typedef std::pair Range; + + struct StyleImpl : BookTypesetter::Style + { + MyGUI::IFont* mFont; + MyGUI::Colour mHotColour; + MyGUI::Colour mActiveColour; + MyGUI::Colour mNormalColour; + InteractiveId mInteractiveId; + + bool match (MyGUI::IFont* tstFont, MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour, + MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId) + { + return (mFont == tstFont) && + partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); + } + + bool match (char const * tstFont, MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour, + MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId) + { + return (mFont->getResourceName () == tstFont) && + partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); + } + + bool partal_match (MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour, + MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId) + { + return + (mHotColour == tstHotColour ) && + (mActiveColour == tstActiveColour ) && + (mNormalColour == tstNormalColour ) && + (mInteractiveId == tstInteractiveId ) ; + } + }; + + typedef std::list Styles; + + struct Run + { + StyleImpl* mStyle; + Range mRange; + int mLeft, mRight; + int mPrintableChars; + }; + + typedef std::vector Runs; + + struct Line + { + Runs mRuns; + MyGUI::IntRect mRect; + }; + + typedef std::vector Lines; + + struct Section + { + Lines mLines; + MyGUI::IntRect mRect; + }; + + typedef std::vector
Sections; + + typedef std::pair Page; + typedef std::vector Pages; + + Pages mPages; + Sections mSections; + Contents mContents; + Styles mStyles; + MyGUI::IntRect mRect; + + virtual ~TypesetBookImpl () {} + + Range addContent (BookTypesetter::Utf8Span text) + { + Contents::iterator i = mContents.insert (mContents.end (), Content (text.first, text.second)); + + if (i->size () == 0) + return Range (Utf8Point (NULL), Utf8Point (NULL)); + + Utf8Point begin = &i->front (); + Utf8Point end = &i->front () + i->size (); + + return Range (begin, end); + } + + size_t pageCount () const { return mPages.size (); } + + std::pair getSize () const + { + return std::make_pair (mRect.width (), mRect.height ()); + } + + template + void visitRuns (int top, int bottom, MyGUI::IFont* Font, Visitor const & visitor) const + { + for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) + { + if (top >= mRect.bottom || bottom <= i->mRect.top) + continue; + + for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) + { + if (top >= j->mRect.bottom || bottom <= j->mRect.top) + continue; + + for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k) + if (!Font || k->mStyle->mFont == Font) + visitor (*i, *j, *k); + } + } + } + + template + void visitRuns (int top, int bottom, Visitor const & visitor) const + { + visitRuns (top, bottom, NULL, visitor); + } + + StyleImpl * hitTest (int left, int top) const + { + for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) + { + if (top < i->mRect.top || top >= i->mRect.bottom) + continue; + + int left1 = left - i->mRect.left; + + for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) + { + if (top < j->mRect.top || top >= j->mRect.bottom) + continue; + + int left2 = left1 - j->mRect.left; + + for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k) + { + if (left2 < k->mLeft || left2 >= k->mRight) + continue; + + return k->mStyle; + } + } + } + + return nullptr; + } + + MyGUI::IFont* affectedFont (StyleImpl* style) + { + for (Styles::iterator i = mStyles.begin (); i != mStyles.end (); ++i) + if (&*i == style) + return i->mFont; + return NULL; + } + + struct Typesetter; +}; + +struct TypesetBookImpl::Typesetter : BookTypesetter +{ + typedef TypesetBookImpl Book; + typedef boost::shared_ptr BookPtr; + + int mPageWidth; + int mPageHeight; + + BookPtr mBook; + Section * mSection; + Line * mLine; + Run * mRun; + + std::vector mSectionAlignment; + + Book::Content const * mCurrentContent; + Alignment mCurrentAlignment; + + Typesetter (size_t width, size_t height) : + mPageWidth (width), mPageHeight(height), + mSection (NULL), mLine (NULL), mRun (NULL), + mCurrentAlignment (AlignLeft), + mCurrentContent (NULL) + { + mBook = boost::make_shared (); + } + + virtual ~Typesetter () + { + } + + Style * createStyle (char const * fontName, Colour fontColour) + { + for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) + if (i->match (fontName, fontColour, fontColour, fontColour, 0)) + return &*i; + + StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); + + style.mFont = MyGUI::FontManager::getInstance().getByName(fontName); + style.mHotColour = fontColour; + style.mActiveColour = fontColour; + style.mNormalColour = fontColour; + style.mInteractiveId = 0; + + return &style; + } + + Style* createHotStyle (Style* baseStyle, Colour normalColour, Colour hoverColour, Colour activeColour, InteractiveId id, bool unique) + { + StyleImpl* BaseStyle = dynamic_cast (baseStyle); + + if (!unique) + for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) + if (i->match (BaseStyle->mFont, hoverColour, activeColour, normalColour, id)) + return &*i; + + StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); + + style.mFont = BaseStyle->mFont; + style.mHotColour = hoverColour; + style.mActiveColour = activeColour; + style.mNormalColour = normalColour; + style.mInteractiveId = id; + + return &style; + } + + void write (Style * style, Utf8Span text) + { + Range range = mBook->addContent (text); + + writeImpl (dynamic_cast (style), range.first, range.second); + } + + intptr_t addContent (Utf8Span text, bool select) + { + Contents::iterator i = mBook->mContents.insert (mBook->mContents.end (), Content (text.first, text.second)); + + if (select) + mCurrentContent = &(*i); + + return reinterpret_cast (&(*i)); + } + + void selectContent (intptr_t contentHandle) + { + mCurrentContent = reinterpret_cast (contentHandle); + } + + void write (Style * style, size_t begin, size_t end) + { + assert (mCurrentContent != NULL); + assert (end <= mCurrentContent->size ()); + assert (begin <= mCurrentContent->size ()); + + Utf8Point begin_ = &mCurrentContent->front () + begin; + Utf8Point end_ = &mCurrentContent->front () + end ; + + writeImpl (dynamic_cast (style), begin_, end_); + } + + void lineBreak (float margin) + { + assert (margin == 0); //TODO: figure out proper behavior here... + + mRun = NULL; + mLine = NULL; + } + + void sectionBreak (float margin) + { + if (mBook->mSections.size () > 0) + { + mRun = NULL; + mLine = NULL; + mSection = NULL; + + if (mBook->mRect.bottom < (mBook->mSections.back ().mRect.bottom + margin)) + mBook->mRect.bottom = (mBook->mSections.back ().mRect.bottom + margin); + } + } + + void setSectionAlignment (Alignment sectionAlignment) + { + if (mSection != NULL) + mSectionAlignment.back () = sectionAlignment; + mCurrentAlignment = sectionAlignment; + } + + TypesetBook::Ptr complete () + { + int curPageStart = 0; + int curPageStop = 0; + + std::vector ::iterator sa = mSectionAlignment.begin (); + for (Sections::iterator i = mBook->mSections.begin (); i != mBook->mSections.end (); ++i, ++sa) + { + // apply alignment to individual lines... + for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) + { + int width = j->mRect.width (); + int excess = mPageWidth - width; + + switch (*sa) + { + default: + case AlignLeft: j->mRect.left = 0; break; + case AlignCenter: j->mRect.left = excess/2; break; + case AlignRight: j->mRect.left = excess; break; + } + + j->mRect.right = j->mRect.left + width; + } + + if (curPageStop == curPageStart) + { + curPageStart = i->mRect.top; + curPageStop = i->mRect.top; + } + + int spaceLeft = mPageHeight - (curPageStop - curPageStart); + int sectionHeight = i->mRect.height (); + + if (sectionHeight <= mPageHeight) + { + if (sectionHeight > spaceLeft) + { + assert (curPageStart != curPageStop); + + mBook->mPages.push_back (Page (curPageStart, curPageStop)); + + curPageStart = i->mRect.top; + curPageStop = i->mRect.bottom; + } + else + curPageStop = i->mRect.bottom; + } + else + { + //split section + } + } + + if (curPageStart != curPageStop) + mBook->mPages.push_back (Page (curPageStart, curPageStop)); + + return mBook; + } + + void writeImpl (StyleImpl * style, Utf8Stream::Point _begin, Utf8Stream::Point _end) + { + int line_height = style->mFont->getDefaultHeight (); + + Utf8Stream stream (_begin, _end); + + while (!stream.eof ()) + { + if (ucsLineBreak (stream.peek ())) + { + stream.consume (); + mLine = NULL, mRun = NULL; + continue; + } + + int word_width = 0; + int word_height = 0; + int space_width = 0; + int character_count = 0; + + Utf8Stream::Point lead = stream.current (); + + while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ())) + { + MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ()); + space_width += gi->advance; + stream.consume (); + } + + Utf8Stream::Point origin = stream.current (); + + while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ())) + { + MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ()); + word_width += gi->advance + gi->bearingX; + word_height = line_height; + ++character_count; + stream.consume (); + } + + Utf8Stream::Point extent = stream.current (); + + if (lead == extent) + break; + + int left = mLine ? mLine->mRect.right : 0; + + if (left + space_width + word_width > mPageWidth) + { + mLine = NULL, mRun = NULL; + + append_run (style, origin, extent, extent - origin, word_width, mBook->mRect.bottom + word_height); + } + else + { + int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; + + append_run (style, lead, extent, extent - origin, left + space_width + word_width, top + word_height); + } + } + } + + void append_run (StyleImpl * style, Utf8Stream::Point begin, Utf8Stream::Point end, int pc, int right, int bottom) + { + if (mSection == NULL) + { + mBook->mSections.push_back (Section ()); + mSection = &mBook->mSections.back (); + mSection->mRect = MyGUI::IntRect (0, mBook->mRect.bottom, 0, mBook->mRect.bottom); + mSectionAlignment.push_back (mCurrentAlignment); + } + + if (mLine == NULL) + { + mSection->mLines.push_back (Line ()); + mLine = &mSection->mLines.back (); + mLine->mRect = MyGUI::IntRect (0, mSection->mRect.bottom, 0, mBook->mRect.bottom); + } + + if (mBook->mRect.right < right) + mBook->mRect.right = right; + + if (mBook->mRect.bottom < bottom) + mBook->mRect.bottom = bottom; + + if (mSection->mRect.right < right) + mSection->mRect.right = right; + + if (mSection->mRect.bottom < bottom) + mSection->mRect.bottom = bottom; + + if (mLine->mRect.right < right) + mLine->mRect.right = right; + + if (mLine->mRect.bottom < bottom) + mLine->mRect.bottom = bottom; + + if (mRun == NULL || mRun->mStyle != style || mRun->mRange.second != begin) + { + int left = mRun ? mRun->mRight : mLine->mRect.left; + + mLine->mRuns.push_back (Run ()); + mRun = &mLine->mRuns.back (); + mRun->mStyle = style; + mRun->mLeft = left; + mRun->mRight = right; + mRun->mRange.first = begin; + mRun->mRange.second = end; + mRun->mPrintableChars = pc; + //Run->Locale = Locale; + } + else + { + mRun->mRight = right; + mRun->mRange.second = end; + mRun->mPrintableChars += pc; + } + } +}; + +BookTypesetter::Ptr BookTypesetter::create (int pageWidth, int pageHeight) +{ + return boost::make_shared (pageWidth, pageHeight); +} + +namespace +{ + struct RenderXform + { + public: + + float clipTop; + float clipLeft; + float clipRight; + float clipBottom; + + float absoluteLeft; + float absoluteTop; + float leftOffset; + float topOffset; + + float pixScaleX; + float pixScaleY; + float hOffset; + float vOffset; + + RenderXform (MyGUI::ICroppedRectangle* croppedParent, MyGUI::RenderTargetInfo const & renderTargetInfo) + { + clipTop = croppedParent->_getMarginTop (); + clipLeft = croppedParent->_getMarginLeft (); + clipRight = croppedParent->getWidth () - croppedParent->_getMarginRight (); + clipBottom = croppedParent->getHeight () - croppedParent->_getMarginBottom (); + + absoluteLeft = croppedParent->getAbsoluteLeft(); + absoluteTop = croppedParent->getAbsoluteTop(); + leftOffset = renderTargetInfo.leftOffset; + topOffset = renderTargetInfo.topOffset; + + pixScaleX = renderTargetInfo.pixScaleX; + pixScaleY = renderTargetInfo.pixScaleY; + hOffset = renderTargetInfo.hOffset; + vOffset = renderTargetInfo.vOffset; + } + + bool clip (MyGUI::FloatRect & vr, MyGUI::FloatRect & tr) + { + if (vr.bottom <= clipTop || vr.right <= clipLeft || + vr.left >= clipRight || vr.top >= clipBottom ) + return false; + + if (vr.top < clipTop) + { + tr.top += tr.height () * (clipTop - vr.top) / vr.height (); + vr.top = clipTop; + } + + if (vr.left < clipLeft) + { + tr.left += tr.width () * (clipLeft - vr.left) / vr.width (); + vr.left = clipLeft; + } + + if (vr.right > clipRight) + { + tr.right -= tr.width () * (vr.right - clipRight) / vr.width (); + vr.right = clipRight; + } + + if (vr.bottom > clipBottom) + { + tr.bottom -= tr.height () * (vr.bottom - clipBottom) / vr.height (); + vr.bottom = clipBottom; + } + + return true; + } + + MyGUI::FloatPoint operator () (MyGUI::FloatPoint pt) + { + pt.left = absoluteLeft - leftOffset + pt.left; + pt.top = absoluteTop - topOffset + pt.top; + + pt.left = +(((pixScaleX * pt.left + hOffset) * 2.0f) - 1.0f); + pt.top = -(((pixScaleY * pt.top + vOffset) * 2.0f) - 1.0f); + + return pt; + } + }; + + struct GlyphStream + { + float mZ; + uint32_t mC; + MyGUI::IFont* mFont; + MyGUI::FloatPoint mOrigin; + MyGUI::FloatPoint mCursor; + MyGUI::Vertex* mVertices; + RenderXform mRenderXform; + MyGUI::VertexColourType mVertexColourType; + + GlyphStream (MyGUI::IFont* font, float left, float top, float Z, + MyGUI::Vertex* vertices, RenderXform const & renderXform) : + mZ(Z), mOrigin (left, top), + mFont (font), mVertices (vertices), + mRenderXform (renderXform) + { + mVertexColourType = MyGUI::RenderManager::getInstance().getVertexFormat(); + } + + ~GlyphStream () + { + } + + MyGUI::Vertex* end () const { return mVertices; } + + void reset (float left, float top, MyGUI::Colour colour) + { + mC = MyGUI::texture_utility::toColourARGB(colour) | 0xFF000000; + MyGUI::texture_utility::convertColour(mC, mVertexColourType); + + mCursor.left = mOrigin.left + left; + mCursor.top = mOrigin.top + top; + } + + void emitGlyph (wchar_t ch) + { + MyGUI::GlyphInfo* gi = mFont->getGlyphInfo (ch); + + MyGUI::FloatRect vr; + + vr.left = mCursor.left + gi->bearingX; + vr.top = mCursor.top + gi->bearingY; + vr.right = vr.left + gi->width; + vr.bottom = vr.top + gi->height; + + MyGUI::FloatRect tr = gi->uvRect; + + if (mRenderXform.clip (vr, tr)) + quad (vr, tr); + + mCursor.left += gi->bearingX + gi->advance; + } + + void emitSpace (wchar_t ch) + { + MyGUI::GlyphInfo* gi = mFont->getGlyphInfo (ch); + + mCursor.left += gi->bearingX + gi->advance; + } + + private: + + void quad (const MyGUI::FloatRect& vr, const MyGUI::FloatRect& tr) + { + vertex (vr.left, vr.top, tr.left, tr.top); + vertex (vr.right, vr.top, tr.right, tr.top); + vertex (vr.left, vr.bottom, tr.left, tr.bottom); + vertex (vr.right, vr.top, tr.right, tr.top); + vertex (vr.left, vr.bottom, tr.left, tr.bottom); + vertex (vr.right, vr.bottom, tr.right, tr.bottom); + } + + void vertex (float x, float y, float u, float v) + { + MyGUI::FloatPoint pt = mRenderXform (MyGUI::FloatPoint (x, y)); + + mVertices->x = pt.left; + mVertices->y = pt.top ; + mVertices->z = mZ; + mVertices->u = u; + mVertices->v = v; + mVertices->colour = mC; + + ++mVertices; + } + }; +} + +class PageDisplay : public MyGUI::ISubWidgetText +{ + MYGUI_RTTI_DERIVED(PageDisplay) +protected: + + typedef TypesetBookImpl::Section Section; + typedef TypesetBookImpl::Line Line; + typedef TypesetBookImpl::Run Run; + + struct TextFormat : ISubWidget + { + typedef MyGUI::IFont* Id; + + Id mFont; + int mCountVertex; + MyGUI::ITexture* mTexture; + MyGUI::RenderItem* mRenderItem; + PageDisplay * mDisplay; + + TextFormat (MyGUI::IFont* id, PageDisplay * display) : + mFont (id), + mTexture (NULL), + mRenderItem (NULL), + mDisplay (display), + mCountVertex (0) + { + } + + void createDrawItem (MyGUI::ILayerNode* node) + { + assert (mRenderItem == NULL); + + if (mTexture != NULL) + { + mRenderItem = node->addToRenderItem(mTexture, false, false); + mRenderItem->addDrawItem(this, mCountVertex); + } + } + + void destroyDrawItem (MyGUI::ILayerNode* node) + { + assert (mTexture != NULL ? mRenderItem != NULL : mRenderItem == NULL); + + if (mTexture != NULL) + { + mRenderItem->removeDrawItem (this); + mRenderItem = NULL; + } + } + + void doRender() { mDisplay->doRender (*this); } + + // this isn't really a sub-widget, its just a "drawitem" which + // should have its own interface + void createDrawItem(MyGUI::ITexture* _texture, MyGUI::ILayerNode* _node) {} + void destroyDrawItem() {}; + }; + +public: + + typedef TypesetBookImpl::StyleImpl Style; + typedef std::map ActiveTextFormats; + + int mViewTop; + int mViewBottom; + + Style* mFocusItem; + bool mItemActive; + MyGUI::MouseButton mLastDown; + boost::function mLinkClicked; + + + boost::shared_ptr mBook; + size_t mPage; + + MyGUI::ILayerNode* mNode; + ActiveTextFormats mActiveTextFormats; + + PageDisplay () + { + mPage = -1; + } + + void dirtyFocusItem () + { + if (mFocusItem != 0) + { + MyGUI::IFont* Font = mBook->affectedFont (mFocusItem); + + ActiveTextFormats::iterator i = mActiveTextFormats.find (Font); + + mNode->outOfDate (i->second->mRenderItem); + } + } + + void onMouseLostFocus () + { + if (!mBook) + return; + + dirtyFocusItem (); + + mFocusItem = 0; + mItemActive = false; + } + + void onMouseMove (int left, int top) + { + if (!mBook) + return; + + left -= mCroppedParent->getAbsoluteLeft (); + top -= mCroppedParent->getAbsoluteTop (); + + Style * Hit = mBook->hitTest (left, mViewTop + top); + + if (mLastDown == MyGUI::MouseButton::None) + { + if (Hit != mFocusItem) + { + dirtyFocusItem (); + + mFocusItem = Hit; + mItemActive = false; + + dirtyFocusItem (); + } + } + else + if (mFocusItem != 0) + { + bool newItemActive = Hit == mFocusItem; + + if (newItemActive != mItemActive) + { + mItemActive = newItemActive; + + dirtyFocusItem (); + } + } + } + + void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) + { + if (!mBook) + return; + + left -= mCroppedParent->getAbsoluteLeft (); + top -= mCroppedParent->getAbsoluteTop (); + + if (mLastDown == MyGUI::MouseButton::None) + { + mFocusItem = mBook->hitTest (left, mViewTop + top); + mItemActive = true; + + dirtyFocusItem (); + + mLastDown = id; + } + } + + void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) + { + if (!mBook) + return; + + left -= mCroppedParent->getAbsoluteLeft (); + top -= mCroppedParent->getAbsoluteTop (); + + if (mLastDown == id) + { + Style * mItem = mBook->hitTest (left, mViewTop + top); + + bool clicked = mFocusItem == mItem; + + mItemActive = false; + + dirtyFocusItem (); + + mLastDown = MyGUI::MouseButton::None; + + if (clicked && mLinkClicked && mItem && mItem->mInteractiveId != 0) + mLinkClicked (mItem->mInteractiveId); + } + } + + void showPage (TypesetBook::Ptr book, size_t newPage) + { + boost::shared_ptr newBook = boost::dynamic_pointer_cast (book); + + if (mBook != newBook) + { + mFocusItem = nullptr; + mItemActive = 0; + + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + { + if (mNode != NULL) + i->second->destroyDrawItem (mNode); + delete i->second; + } + + mActiveTextFormats.clear (); + + if (newBook != NULL) + { + createActiveFormats (newBook); + + mBook = newBook; + mPage = newPage; + + if (newPage < mBook->mPages.size ()) + { + mViewTop = mBook->mPages [newPage].first; + mViewBottom = mBook->mPages [newPage].second; + } + else + { + mViewTop = 0; + mViewBottom = 0; + } + } + else + { + mBook.reset (); + mPage = -1; + mViewTop = 0; + mViewBottom = 0; + } + } + else + if (mBook && mPage != newPage) + { + if (mNode != NULL) + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + mNode->outOfDate(i->second->mRenderItem); + + mPage = newPage; + + if (newPage < mBook->mPages.size ()) + { + mViewTop = mBook->mPages [newPage].first; + mViewBottom = mBook->mPages [newPage].second; + } + else + { + mViewTop = 0; + mViewBottom = 0; + } + } + } + + struct CreateActiveFormat + { + PageDisplay * this_; + + CreateActiveFormat (PageDisplay * this_) : this_ (this_) {} + + void operator () (Section const & section, Line const & line, Run const & run) const + { + MyGUI::IFont* Font = run.mStyle->mFont; + + ActiveTextFormats::iterator j = this_->mActiveTextFormats.find (Font); + + if (j == this_->mActiveTextFormats.end ()) + { + TextFormat * textFormat = new TextFormat (Font, this_); + + textFormat->mTexture = Font->getTextureFont (); + + j = this_->mActiveTextFormats.insert (std::make_pair (Font, textFormat)).first; + } + + j->second->mCountVertex += run.mPrintableChars * 6; + } + }; + + void createActiveFormats (boost::shared_ptr newBook) + { + newBook->visitRuns (0, 0x7FFFFFFF, CreateActiveFormat (this)); + + if (mNode != NULL) + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + i->second->createDrawItem (mNode); + } + + void setVisible (bool newVisible) + { + if (mVisible == newVisible) + return; + + mVisible = newVisible; + + if (mVisible) + { + // reset input state + mLastDown = MyGUI::MouseButton::None; + mFocusItem = nullptr; + mItemActive = 0; + } + + if (nullptr != mNode) + { + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + mNode->outOfDate(i->second->mRenderItem); + } + } + + void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) + { + //test (); + + mNode = node; + + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + i->second->createDrawItem (node); + } + + struct RenderRun + { + PageDisplay * this_; + GlyphStream &glyphStream; + + RenderRun (PageDisplay * this_, GlyphStream &glyphStream) : + this_(this_), glyphStream (glyphStream) + { + } + + void operator () (Section const & section, Line const & line, Run const & run) const + { + bool isActive = run.mStyle->mInteractiveId && (run.mStyle == this_->mFocusItem); + + MyGUI::Colour colour = isActive ? (this_->mItemActive ? run.mStyle->mActiveColour: run.mStyle->mHotColour) : run.mStyle->mNormalColour; + + glyphStream.reset (section.mRect.left + line.mRect.left + run.mLeft, line.mRect.top, colour); + + Utf8Stream stream (run.mRange); + + while (!stream.eof ()) + { + Utf8Stream::UnicodeChar code_point = stream.consume (); + + if (!ucsSpace (code_point)) + glyphStream.emitGlyph (code_point); + else + glyphStream.emitSpace (code_point); + } + } + }; + + /* + queue up rendering operations for this text format + */ + void doRender(TextFormat & textFormat) + { + if (!mVisible) + return; + + MyGUI::Vertex* vertices = textFormat.mRenderItem->getCurrentVertexBuffer(); + + RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); + + GlyphStream glyphStream (textFormat.mFont, mCoord.left, mCoord.top-mViewTop, + -1 /*mNode->getNodeDepth()*/, vertices, renderXform); + + int visit_top = (std::max) (mViewTop, mViewTop + int (renderXform.clipTop )); + int visit_bottom = (std::min) (mViewBottom, mViewTop + int (renderXform.clipBottom)); + + mBook->visitRuns (visit_top, visit_bottom, textFormat.mFont, RenderRun (this, glyphStream)); + + textFormat.mRenderItem->setLastVertexCount(glyphStream.end () - vertices); + } + + // ISubWidget should not necessarily be a drawitem + // in this case, it is not... + void doRender() { } + + void _updateView () + { + } + + void _correctView() + { + _checkMargin (); + + if (mNode != NULL) + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + mNode->outOfDate (i->second->mRenderItem); + + } + + void destroyDrawItem() + { + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + i->second->destroyDrawItem (mNode); + + mNode = NULL; + } +}; + + +class BookPageImpl : public BookPage +{ +MYGUI_RTTI_DERIVED(BookPage) +public: + + + void showPage (TypesetBook::Ptr book, size_t page) + { + if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) + pd->showPage (book, page); + else + throw std::runtime_error ("The main sub-widget for a BookPage must be a PageDisplay."); + } + + void adviseLinkClicked (boost::function linkClicked) + { + if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) + { + pd->mLinkClicked = linkClicked; + } + } + + void unadviseLinkClicked () + { + if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) + { + pd->mLinkClicked = boost::function (); + } + } + +protected: + void onMouseLostFocus(Widget* _new) + { + if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) + { + pd->onMouseLostFocus (); + } + else + Widget::onMouseLostFocus (_new); + } + + void onMouseMove(int left, int top) + { + if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) + { + pd->onMouseMove (left, top); + } + else + Widget::onMouseMove (left, top); + } + + void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) + { + if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) + { + pd->onMouseButtonPressed (left, top, id); + } + else + Widget::onMouseButtonPressed (left, top, id); + } + + void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) + { + if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) + { + pd->onMouseButtonReleased (left, top, id); + } + else + Widget::onMouseButtonReleased (left, top, id); + } +}; + +void BookPage::registerMyGUIComponents () +{ + MyGUI::FactoryManager & factory = MyGUI::FactoryManager::getInstance(); + + factory.registerFactory("Widget"); + factory.registerFactory("BasisSkin"); +} + +static bool ucsLineBreak (int codePoint) +{ + return codePoint == '\n'; +} + +static bool ucsSpace (int codePoint) +{ + switch (codePoint) + { + case 0x0020: // SPACE + case 0x00A0: // NO-BREAK SPACE + case 0x1680: // OGHAM SPACE MARK + case 0x180E: // MONGOLIAN VOWEL SEPARATOR + case 0x2000: // EN QUAD + case 0x2001: // EM QUAD + case 0x2002: // EN SPACE + case 0x2003: // EM SPACE + case 0x2004: // THREE-PER-EM SPACE + case 0x2005: // FOUR-PER-EM SPACE + case 0x2006: // SIX-PER-EM SPACE + case 0x2007: // FIGURE SPACE + case 0x2008: // PUNCTUATION SPACE + case 0x2009: // THIN SPACE + case 0x200A: // HAIR SPACE + case 0x200B: // ZERO WIDTH SPACE + case 0x202F: // NARROW NO-BREAK SPACE + case 0x205F: // MEDIUM MATHEMATICAL SPACE + case 0x3000: // IDEOGRAPHIC SPACE + case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE + return true; + default: + return false; + } +} + +static bool ucsBreakingSpace (int codePoint) +{ + switch (codePoint) + { + case 0x0020: // SPACE + //case 0x00A0: // NO-BREAK SPACE + case 0x1680: // OGHAM SPACE MARK + case 0x180E: // MONGOLIAN VOWEL SEPARATOR + case 0x2000: // EN QUAD + case 0x2001: // EM QUAD + case 0x2002: // EN SPACE + case 0x2003: // EM SPACE + case 0x2004: // THREE-PER-EM SPACE + case 0x2005: // FOUR-PER-EM SPACE + case 0x2006: // SIX-PER-EM SPACE + case 0x2007: // FIGURE SPACE + case 0x2008: // PUNCTUATION SPACE + case 0x2009: // THIN SPACE + case 0x200A: // HAIR SPACE + case 0x200B: // ZERO WIDTH SPACE + case 0x202F: // NARROW NO-BREAK SPACE + case 0x205F: // MEDIUM MATHEMATICAL SPACE + case 0x3000: // IDEOGRAPHIC SPACE + //case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE + return true; + default: + return false; + } +} + +} diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp new file mode 100644 index 0000000000..28aa371cf3 --- /dev/null +++ b/apps/openmw/mwgui/bookpage.hpp @@ -0,0 +1,122 @@ +#ifndef MWGUI_BOOKPAGE_HPP +#define MWGUI_BOOKPAGE_HPP + +#include "MyGUI_Colour.h" +#include "MyGUI_Widget.h" + +#include +#include +#include +#include + +namespace MWGui +{ + /// A formatted and paginated document to be used with + /// the book page widget. + struct TypesetBook + { + typedef boost::shared_ptr Ptr; + typedef intptr_t InteractiveId; + + /// Returns the number of pages in the document. + virtual size_t pageCount () const = 0; + + /// Return the area covered by the document. The first + /// integer is the maximum with of any line. This is not + /// the largest coordinate of the right edge of any line, + /// 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; + }; + + /// A factory class for creating a typeset book instance. + struct BookTypesetter + { + typedef boost::shared_ptr Ptr; + typedef TypesetBook::InteractiveId InteractiveId; + typedef MyGUI::Colour Colour; + typedef uint8_t const * Utf8Point; + typedef std::pair Utf8Span; + + enum Alignment { + AlignLeft = -1, + AlignCenter = 0, + AlignRight = +1 + }; + + /// Styles are used to control the character level formatting + /// of text added to a typeset book. Their lifetime is equal + /// to the lifetime of the book-typesetter instance that created + /// them. + struct Style; + + /// A factory function for creating the default implementation of a book typesetter + static Ptr create (int pageWidth, int pageHeight); + + /// Create a simple text style consisting of a font and a text color. + virtual Style* createStyle (char const * Font, Colour Colour) = 0; + + /// Create a hyper-link style with a user-defined identifier based on an + /// existing style. The unique flag forces a new instance of this style + /// to be created even if an existing instance is present. + virtual Style* createHotStyle (Style * BaseStyle, Colour NormalColour, Colour HoverColour, Colour ActiveColour, InteractiveId Id, bool Unique = true) = 0; + + /// Insert a line break into the document. Newline characters in the input + /// text have the same affect. The margin parameter adds additional space + /// before the next line of text. + virtual void lineBreak (float margin = 0) = 0; + + /// Insert a section break into the document. This causes a new section + /// to begin when additional text is inserted. Pagination attempts to keep + /// sections together on a single page. The margin parameter adds additional space + /// before the next line of text. + virtual void sectionBreak (float margin = 0) = 0; + + /// Changes the alignment for the current section of text. + virtual void setSectionAlignment (Alignment sectionAlignment) = 0; + + // Layout a block of text with the specified style into the document. + virtual void write (Style * Style, Utf8Span Text) = 0; + + /// Adds a content block to the document without laying it out. An + /// identifier is returned that can be used to refer to it. If select + /// is true, the block is activated to be references by future writes. + virtual intptr_t addContent (Utf8Span Text, bool Select = true) = 0; + + /// Select a previously created content block for future writes. + virtual void selectContent (intptr_t contentHandle) = 0; + + /// Layout a span of the selected content block into the document + /// using the specified style. + virtual void write (Style * Style, size_t Begin, size_t End) = 0; + + /// Finalize the document layout, and return a pointer to it. + virtual TypesetBook::Ptr complete () = 0; + }; + + /// An interface to the BookPage widget. + class BookPage : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(BookPage) + public: + + typedef TypesetBook::InteractiveId InteractiveId; + typedef boost::function ClickCallback; + + /// Make the widget display the specified page from the specified book. + virtual void showPage (TypesetBook::Ptr Book, size_t Page) = 0; + + /// Set the callback for a clicking a hyper-link in the document. + virtual void adviseLinkClicked (ClickCallback callback) = 0; + + /// Clear the hyper-link click callback. + virtual void unadviseLinkClicked () = 0; + + /// Register the widget and associated sub-widget with MyGUI. Should be + /// called once near the beginning of the program. + static void registerMyGUIComponents (); + }; +} + +#endif // MWGUI_BOOKPAGE_HPP diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 6d44e9d2c5..12e5b466b3 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -76,7 +76,7 @@ namespace MWGui parent = mRightPage; MyGUI::Widget* pageWidget = parent->createWidgetReal("", MyGUI::FloatCoord(0.0,0.0,1.0,1.0), MyGUI::Align::Default, "BookPage" + boost::lexical_cast(i)); - parser.parse(*it, pageWidget, mLeftPage->getSize().width); + parser.parsePage(*it, pageWidget, mLeftPage->getSize().width); mPages.push_back(pageWidget); ++i; } @@ -157,6 +157,21 @@ namespace MWGui } ++i; } + + //If it is the last page, hide the button "Next Page" + if ( (mCurrentPage+1)*2 == mPages.size() + || (mCurrentPage+1)*2 == mPages.size() + 1) + { + mNextPageButton->setVisible(false); + } else { + mNextPageButton->setVisible(true); + } + //If it is the fist page, hide the button "Prev Page" + if (mCurrentPage == 0) { + mPrevPageButton->setVisible(false); + } else { + mPrevPageButton->setVisible(true); + } } } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index b596cef018..d5a17d3ccb 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -11,36 +11,24 @@ #include "../mwdialogue/dialoguemanagerimp.hpp" -#include "dialoguehistory.hpp" #include "widgets.hpp" #include "list.hpp" #include "tradewindow.hpp" #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,11 +104,144 @@ 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) const + { + BookTypesetter::Style* title = typesetter->createStyle("EB Garamond", MyGUI::Colour(223/255.f, 201/255.f, 159/255.f)); + typesetter->sectionBreak(9); + if (mTitle != "") + typesetter->write(title, to_utf8_span(mTitle.c_str())); + typesetter->sectionBreak(9); + + typedef std::pair Range; + std::map hyperLinks; + + // We need this copy for when @# hyperlinks are replaced + std::string text = mText; + + size_t pos_begin, pos_end; + for(;;) + { + pos_begin = text.find('@'); + if (pos_begin != std::string::npos) + pos_end = text.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) + { + std::string link = text.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; + while (displayName[displayName.size()-1] == '*') + displayName.erase(displayName.size()-1, 1); + + text.replace(pos_begin, pos_end+1-pos_begin, displayName); + + if (topicLinks.find(Misc::StringUtils::lowerCase(topicName)) != topicLinks.end()) + hyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = intptr_t(topicLinks[Misc::StringUtils::lowerCase(topicName)]); + } + else + break; + } + + typesetter->addContent(to_utf8_span(text.c_str())); + + if (hyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) + { + BookTypesetter::Style* style = typesetter->createStyle("EB Garamond", MyGUI::Colour(202/255.f, 165/255.f, 96/255.f)); + size_t formatted = 0; // points to the first character that is not laid out yet + for (std::map::iterator it = hyperLinks.begin(); it != hyperLinks.end(); ++it) + { + intptr_t topicId = it->second; + const MyGUI::Colour linkHot (143/255.f, 155/255.f, 218/255.f); + const MyGUI::Colour linkNormal (112/255.f, 126/255.f, 207/255.f); + const MyGUI::Colour linkActive (175/255.f, 184/255.f, 228/255.f); + BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); + if (formatted < it->first.first) + typesetter->write(style, formatted, it->first.first); + typesetter->write(hotStyle, it->first.first, it->first.second); + formatted = it->first.second; + } + if (formatted < text.size()) + typesetter->write(style, formatted, text.size()); + } + else + { + std::string::const_iterator i = text.begin (); + KeywordSearchT::Match match; + while (i != text.end () && keywordSearch->search (i, text.end (), match)) + { + if (i != match.mBeg) + addTopicLink (typesetter, 0, i - text.begin (), match.mBeg - text.begin ()); + + addTopicLink (typesetter, match.mValue, match.mBeg - text.begin (), match.mEnd - text.begin ()); + + i = match.mEnd; + } + + if (i != text.end ()) + addTopicLink (typesetter, 0, i - text.begin (), text.size ()); + } + } + + void Response::addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const + { + BookTypesetter::Style* style = typesetter->createStyle("EB Garamond", MyGUI::Colour(202/255.f, 165/255.f, 96/255.f)); + + const MyGUI::Colour linkHot (143/255.f, 155/255.f, 218/255.f); + const MyGUI::Colour linkNormal (112/255.f, 126/255.f, 207/255.f); + const MyGUI::Colour linkActive (175/255.f, 184/255.f, 228/255.f); + + if (topicId) + style = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); + typesetter->write (style, begin, end); + } + + Message::Message(const std::string& text) + { + mText = text; + } + + void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const + { + BookTypesetter::Style* title = typesetter->createStyle("EB Garamond", MyGUI::Colour(223/255.f, 201/255.f, 159/255.f)); + typesetter->sectionBreak(9); + typesetter->write(title, to_utf8_span(mText.c_str())); + } + + // -------------------------------------------------------------------------------------------------- + + void Choice::activated() + { + MWBase::Environment::get().getDialogueManager()->questionAnswered(mChoiceId); + } + + void Topic::activated() + { + MWBase::Environment::get().getDialogueManager()->keywordSelected(Misc::StringUtils::lowerCase(mTopicId)); + } + + void Goodbye::activated() + { + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + } + + // -------------------------------------------------------------------------------------------------- + DialogueWindow::DialogueWindow() : WindowBase("openmw_dialogue_window.layout") , mPersuasionDialog() , mEnabled(false) , mServices(0) + , mGoodbye(false) { // Centre dialog center(); @@ -129,15 +250,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,68 +261,30 @@ 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; - - const MyGUI::IntPoint& lastPressed = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); - - size_t cursorPosition = t->getCursorPosition(lastPressed); - MyGUI::UString color = mHistory->getColorAtPos(cursorPosition); - - if (!mEnabled && color == "#572D21") - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); - - if (!mEnabled) - return; - - if(color != "#B29154") - { - MyGUI::UString key = mHistory->getColorTextAt(cursorPosition); - - 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)); - } - } - 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 +305,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 = @@ -289,20 +363,32 @@ namespace MWGui void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) { + mGoodbye = false; mEnabled = true; mPtr = actor; mTopicsList->setEnabled(true); setTitle(npcName); 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,161 +432,138 @@ namespace MWGui for(std::list::iterator it = keyWords.begin(); it != keyWords.end(); ++it) { mTopicsList->addItem(*it); + + Topic* t = new Topic(*it); + mTopicLinks[Misc::StringUtils::lowerCase(*it)] = t; + + mKeywordSearch.seed(Misc::StringUtils::lowerCase(*it), intptr_t(t)); } mTopicsList->adjustSize(); + + updateHistory(); } - void DialogueWindow::removeKeyword(std::string keyWord) + void DialogueWindow::updateHistory(bool scrollbar) { - if(mTopicsList->hasItem(keyWord)) + if (!scrollbar && mScrollBar->getVisible()) { - mTopicsList->removeItem(keyWord); + 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 + const MyGUI::Colour linkHot (223/255.f, 201/255.f, 159/255.f); + const MyGUI::Colour linkNormal (150/255.f, 50/255.f, 30/255.f); + const MyGUI::Colour linkActive (243/255.f, 237/255.f, 221/255.f); + for (std::map::iterator it = mChoices.begin(); it != mChoices.end(); ++it) + { + Choice* link = new Choice(it->second); + mLinks.push_back(link); + + typesetter->lineBreak(); + BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, linkNormal, linkHot, linkActive, + TypesetBook::InteractiveId(link)); + typesetter->write(questionStyle, to_utf8_span(it->first.c_str())); + } + + if (mGoodbye) + { + std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->getString(); + BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, linkNormal, linkHot, linkActive, + TypesetBook::InteractiveId(mLinks.back())); + typesetter->lineBreak(); + typesetter->write(questionStyle, to_utf8_span(goodbye.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); + mScrollBar->setTrackSize(viewHeight / static_cast(book->getSize().second) * mScrollBar->getLineSize()); + onScrollbarMoved(mScrollBar, range-1); + } + else + { + // no scrollbar + onScrollbarMoved(mScrollBar, 0); } - mTopicsList->adjustSize(); } - void addColorInString(std::string& str, const std::string& keyword,std::string color1, std::string color2) + void DialogueWindow::notifyLinkClicked (TypesetBook::InteractiveId link) { - size_t pos = 0; - while((pos = find_str_ci(str,keyword, pos)) != std::string::npos) + reinterpret_cast(link)->activated(); + } + + 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 title through the topic list. + std::string realTitle = title; + if (realTitle != "") { - // do not add color if this portion of text is already colored. + for (size_t i=0; igetItemCount(); ++i) { - MyGUI::TextIterator iterator (str); - MyGUI::UString colour; - while(iterator.moveNext()) + std::string item = mTopicsList->getItemNameAt(i); + if (Misc::StringUtils::lowerCase(item) == title) { - size_t iteratorPos = iterator.getPosition(); - iterator.getTagColour(colour); - if (iteratorPos == pos) - break; + realTitle = item; + break; } - - if (colour == color1) - return; } - - str.insert(pos,color1); - pos += color1.length(); - pos += keyword.length(); - str.insert(pos,color2); - pos+= color2.length(); - } - } - - 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) - - std::vector topics; - - bool hasSeparator = false; - for (unsigned int i=0; igetItemCount(); ++i) - { - if (mTopicsList->getItemNameAt(i) == "") - hasSeparator = true; } - for(unsigned int i = 0;igetItemCount();i++) - { - std::string keyWord = mTopicsList->getItemNameAt(i); - if (separatorReached || !hasSeparator) - topics.push_back(keyWord); - else if (keyWord == "") - 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) - { - 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 = - MWBase::Environment::get().getWindowManager()-> - 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( !MWBase::Environment::get().getWindowManager()->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 result; - } - - void DialogueWindow::addText(std::string text) - { - mHistory->addDialogText("#B29154"+parseText(text)+"#B29154"); + mHistoryContents.push_back(new Response(text, realTitle)); + updateHistory(); } void DialogueWindow::addMessageBox(const std::string& text) { - mHistory->addDialogText("\n#FFFFFF"+text+"#B29154"); + mHistoryContents.push_back(new Message(text)); + updateHistory(); } - void DialogueWindow::addTitle(std::string text) + void DialogueWindow::addChoice(const std::string& choice, int id) { - // 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) - { - std::string item = mTopicsList->getItemNameAt(i); - if (lower_string(item) == text) - text = item; - } - - mHistory->addDialogHeading(text); + mChoices[choice] = id; + updateHistory(); } - void DialogueWindow::askQuestion(std::string question) + void DialogueWindow::clearChoices() { - mHistory->addDialogText("#572D21"+question+"#B29154"+" "); + mChoices.clear(); + updateHistory(); } void DialogueWindow::updateOptions() { //Clear the list of topics mTopicsList->clear(); - mHyperLinks.clear(); - mHistory->eraseText(0, mHistory->getTextLength()); if (mPtr.getTypeName() == typeid(ESM::NPC).name()) { @@ -513,9 +576,11 @@ namespace MWGui void DialogueWindow::goodbye() { - mHistory->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->getString()); + mLinks.push_back(new Goodbye()); + mGoodbye = true; mTopicsList->setEnabled(false); mEnabled = false; + updateHistory(); } void DialogueWindow::onReferenceUnavailable() diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 74fa8051c5..befbd6eeeb 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 (); + }; + + struct Goodbye : Link + { + virtual void activated (); + }; + + typedef KeywordSearch KeywordSearchT; + + struct DialogueText + { + virtual ~DialogueText() {} + virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const = 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) const; + void addTopicLink (BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const; + std::string mTitle; + }; + + struct Message : DialogueText + { + Message(const std::string& text); + virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const; + }; + class DialogueWindow: public WindowBase, public ReferenceInterface { public: @@ -52,19 +106,18 @@ 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(); @@ -85,37 +138,39 @@ namespace MWGui protected: void onSelectTopic(const std::string& topic, int id); void onByeClicked(MyGUI::Widget* _sender); - void onHistoryClicked(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); void onWindowResize(MyGUI::Window* _sender); - virtual void onReferenceUnavailable(); + void onScrollbarMoved (MyGUI::ScrollBar* sender, size_t pos); - struct HyperLink - { - size_t mLength; - std::string mTrueValue; - }; + void updateHistory(bool scrollbar=false); + + virtual void onReferenceUnavailable(); private: void updateOptions(); - /** - *Helper function that add topic keyword in blue in a text. - */ - std::string parseText(const std::string& text); int mServices; bool mEnabled; - DialogueHistory* mHistory; + bool mGoodbye; + + 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; PersuasionDialog mPersuasionDialog; - - std::map mHyperLinks; }; } #endif diff --git a/apps/openmw/mwgui/dialoguehistory.cpp b/apps/openmw/mwgui/dialoguehistory.cpp deleted file mode 100644 index d99d55bc12..0000000000 --- a/apps/openmw/mwgui/dialoguehistory.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#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"); - } - -} diff --git a/apps/openmw/mwgui/dialoguehistory.hpp b/apps/openmw/mwgui/dialoguehistory.hpp deleted file mode 100644 index 4cf6de621d..0000000000 --- a/apps/openmw/mwgui/dialoguehistory.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef MWGUI_DIALOGE_HISTORY_H -#define MWGUI_DIALOGE_HISTORY_H - -#include - -namespace MWGui -{ - class DialogueHistory : public MyGUI::EditBox - { - 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); - }; -} -#endif - diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 2d72f21740..6f1e16ff13 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -6,7 +6,10 @@ #include #include +#include #include +#include +#include #include namespace @@ -74,6 +77,12 @@ namespace Ogre::UTFString string(s); return string.getChar(0); } + + bool is_not_empty(const std::string s) { + std::string temp = s; + boost::algorithm::trim(temp); + return !temp.empty(); + } } namespace MWGui @@ -88,6 +97,7 @@ namespace MWGui utf8Text = Interpreter::fixDefinesBook(utf8Text, interpreterContext); boost::algorithm::replace_all(utf8Text, "\n", ""); + boost::algorithm::replace_all(utf8Text, "\r", ""); boost::algorithm::replace_all(utf8Text, "
", "\n"); boost::algorithm::replace_all(utf8Text, "

", "\n\n"); @@ -106,6 +116,14 @@ namespace MWGui size_t currentWordStart = 0; size_t index = 0; + + { + std::string texToTrim = text.asUTF8(); + boost::algorithm::trim( texToTrim ); + text = UTFString(texToTrim); + } + + while (currentHeight <= height - spacing && index < text.size()) { const UTFString::unicode_char ch = text.getChar(index); @@ -176,8 +194,10 @@ namespace MWGui result.push_back(text.substr(0, pageEnd).asUTF8()); text.erase(0, pageEnd); } - - return result; + + std::vector nonEmptyPages; + boost::copy(result | boost::adaptors::filtered(is_not_empty), std::back_inserter(nonEmptyPages)); + return nonEmptyPages; } float BookTextParser::widthForCharGlyph(unsigned unicodeChar) const @@ -193,7 +213,30 @@ namespace MWGui return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight(); } - MyGUI::IntSize BookTextParser::parse(std::string text, MyGUI::Widget* parent, const int width) + MyGUI::IntSize BookTextParser::parsePage(std::string text, MyGUI::Widget* parent, const int width) + { + MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor + text = Interpreter::fixDefinesBook(text, interpreterContext); + + mParent = parent; + mWidth = width; + mHeight = 0; + + assert(mParent); + while (mParent->getChildCount()) + { + MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0)); + } + + // remove trailing " + if (text[text.size()-1] == '\"') + text.erase(text.size()-1); + + parseSubText(text); + return MyGUI::IntSize(mWidth, mHeight); + } + + MyGUI::IntSize BookTextParser::parseScroll(std::string text, MyGUI::Widget* parent, const int width) { MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor text = Interpreter::fixDefinesBook(text, interpreterContext); @@ -209,12 +252,10 @@ namespace MWGui } boost::algorithm::replace_all(text, "\n", ""); + boost::algorithm::replace_all(text, "\r", ""); boost::algorithm::replace_all(text, "
", "\n"); boost::algorithm::replace_all(text, "

", "\n\n"); - - // remove leading newlines - // while (text[0] == '\n') - // text.erase(0); + boost::algorithm::trim_left(text); // remove trailing " if (text[text.size()-1] == '\"') @@ -223,6 +264,7 @@ namespace MWGui parseSubText(text); return MyGUI::IntSize(mWidth, mHeight); } + void BookTextParser::parseImage(std::string tag, bool createWidget) { @@ -309,7 +351,7 @@ namespace MWGui parseImage(tag); if (boost::algorithm::starts_with(tag, "FONT")) parseFont(tag); - if (boost::algorithm::starts_with(tag, "DOV")) + if (boost::algorithm::starts_with(tag, "DIV")) parseDiv(tag); text.erase(0, tagEnd + 1); diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index ab1ee3af4d..a32d98fe58 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -32,7 +32,16 @@ namespace MWGui * @param maximum width * @return size of the created widgets */ - MyGUI::IntSize parse(std::string text, MyGUI::Widget* parent, const int width); + MyGUI::IntSize parsePage(std::string text, MyGUI::Widget* parent, const int width); + + /** + * Parse markup as MyGUI widgets + * @param markup to parse + * @param parent for the created widgets + * @param maximum width + * @return size of the created widgets + */ + MyGUI::IntSize parseScroll(std::string text, MyGUI::Widget* parent, const int width); /** * Split the specified text into pieces that fit in the area specified by width and height parameters diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp new file mode 100644 index 0000000000..273985e3e7 --- /dev/null +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -0,0 +1,326 @@ +#include "journalbooks.hpp" + +namespace +{ + MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) + { + typedef MWGui::BookTypesetter::Utf8Point point; + + point begin = reinterpret_cast (text); + + return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text)); + } + + 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); + + struct AddContent + { + MWGui::BookTypesetter::Ptr mTypesetter; + MWGui::BookTypesetter::Style* mBodyStyle; + + AddContent (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : + mTypesetter (typesetter), mBodyStyle (body_style) + { + } + }; + + struct AddSpan : AddContent + { + AddSpan (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : + AddContent (typesetter, body_style) + { + } + + void operator () (intptr_t topicId, size_t begin, size_t end) + { + MWGui::BookTypesetter::Style* style = mBodyStyle; + + if (topicId) + style = mTypesetter->createHotStyle (mBodyStyle, linkNormal, linkHot, linkActive, topicId); + + mTypesetter->write (style, begin, end); + } + }; + + struct AddEntry + { + MWGui::BookTypesetter::Ptr mTypesetter; + MWGui::BookTypesetter::Style* mBodyStyle; + + AddEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : + mTypesetter (typesetter), mBodyStyle (body_style) + { + } + + void operator () (MWGui::JournalViewModel::Entry const & entry) + { + mTypesetter->addContent (entry.body ()); + + entry.visitSpans (AddSpan (mTypesetter, mBodyStyle)); + } + }; + + struct AddJournalEntry : AddEntry + { + bool mAddHeader; + MWGui::BookTypesetter::Style* mHeaderStyle; + + AddJournalEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style, + MWGui::BookTypesetter::Style* header_style, bool add_header) : + AddEntry (typesetter, body_style), + mHeaderStyle (header_style), + mAddHeader (add_header) + { + } + + void operator () (MWGui::JournalViewModel::JournalEntry const & entry) + { + if (mAddHeader) + { + mTypesetter->write (mHeaderStyle, entry.timestamp ()); + mTypesetter->lineBreak (); + } + + AddEntry::operator () (entry); + + mTypesetter->sectionBreak (10); + } + }; + + struct AddTopicEntry : AddEntry + { + intptr_t mContentId; + MWGui::BookTypesetter::Style* mHeaderStyle; + + AddTopicEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style, + MWGui::BookTypesetter::Style* header_style, intptr_t contentId) : + AddEntry (typesetter, body_style), mHeaderStyle (header_style), mContentId (contentId) + { + } + + void operator () (MWGui::JournalViewModel::TopicEntry const & entry) + { + mTypesetter->write (mBodyStyle, entry.source ()); + mTypesetter->write (mBodyStyle, 0, 3);// begin + + AddEntry::operator() (entry); + + mTypesetter->selectContent (mContentId); + mTypesetter->write (mBodyStyle, 2, 3);// end quote + + mTypesetter->sectionBreak (10); + } + }; + + struct AddTopicName : AddContent + { + AddTopicName (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : + AddContent (typesetter, style) + { + } + + void operator () (MWGui::JournalViewModel::Utf8Span topicName) + { + mTypesetter->write (mBodyStyle, topicName); + mTypesetter->sectionBreak (10); + } + }; + + struct AddQuestName : AddContent + { + AddQuestName (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : + AddContent (typesetter, style) + { + } + + void operator () (MWGui::JournalViewModel::Utf8Span topicName) + { + mTypesetter->write (mBodyStyle, topicName); + mTypesetter->sectionBreak (10); + } + }; + + struct AddTopicLink : AddContent + { + AddTopicLink (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : + AddContent (typesetter, style) + { + } + + void operator () (MWGui::JournalViewModel::TopicId topicId, MWGui::JournalViewModel::Utf8Span name) + { + MWGui::BookTypesetter::Style* link = mTypesetter->createHotStyle (mBodyStyle, MyGUI::Colour::Black, linkHot, linkActive, topicId); + + mTypesetter->write (link, name); + mTypesetter->lineBreak (); + } + }; + + struct AddQuestLink : AddContent + { + AddQuestLink (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : + AddContent (typesetter, style) + { + } + + void operator () (MWGui::JournalViewModel::QuestId id, MWGui::JournalViewModel::Utf8Span name) + { + MWGui::BookTypesetter::Style* style = mTypesetter->createHotStyle (mBodyStyle, MyGUI::Colour::Black, linkHot, linkActive, id); + + mTypesetter->write (style, name); + mTypesetter->lineBreak (); + } + }; +} + +namespace MWGui +{ + +typedef TypesetBook::Ptr book; + +JournalBooks::JournalBooks (JournalViewModel::Ptr model) : + mModel (model) +{ +} + +book JournalBooks::createEmptyJournalBook () +{ + BookTypesetter::Ptr typesetter = createTypesetter (); + + BookTypesetter::Style* header = typesetter->createStyle ("EB Garamond", MyGUI::Colour (0.60f, 0.00f, 0.00f)); + BookTypesetter::Style* body = typesetter->createStyle ("EB Garamond", MyGUI::Colour::Black); + + typesetter->write (header, to_utf8_span ("You have no journal entries!")); + typesetter->lineBreak (); + typesetter->write (body, to_utf8_span ("You should have gone though the starting quest and got an initial quest.")); + + BookTypesetter::Style* big = typesetter->createStyle ("EB Garamond 24", MyGUI::Colour::Black); + BookTypesetter::Style* test = typesetter->createStyle ("MonoFont", MyGUI::Colour::Blue); + + typesetter->sectionBreak (20); + typesetter->write (body, to_utf8_span ( + "The layout engine doesn't currently support aligning fonts to " + "their baseline within a single line so the following text looks " + "funny. In order to properly implement it, a stupidly simple " + "change is needed in MyGUI to report the where the baseline is for " + "a particular font" + )); + + typesetter->sectionBreak (20); + typesetter->write (big, to_utf8_span ("big text g")); + typesetter->write (body, to_utf8_span (" проверяем только в дебаге")); + typesetter->write (body, to_utf8_span (" normal g")); + typesetter->write (big, to_utf8_span (" done g")); + + typesetter->sectionBreak (20); + typesetter->write (test, to_utf8_span ( + "int main (int argc,\n" + " char ** argv)\n" + "{\n" + " print (\"hello world!\\n\");\n" + " return 0;\n" + "}\n" + )); + + return typesetter->complete (); +} + +book JournalBooks::createJournalBook () +{ + BookTypesetter::Ptr typesetter = createTypesetter (); + + BookTypesetter::Style* header = typesetter->createStyle ("EB Garamond", MyGUI::Colour (0.60f, 0.00f, 0.00f)); + BookTypesetter::Style* body = typesetter->createStyle ("EB Garamond", MyGUI::Colour::Black); + + mModel->visitJournalEntries (0, AddJournalEntry (typesetter, body, header, true)); + + return typesetter->complete (); +} + +book JournalBooks::createTopicBook (uintptr_t topicId) +{ + BookTypesetter::Ptr typesetter = createTypesetter (); + + BookTypesetter::Style* header = typesetter->createStyle ("EB Garamond", MyGUI::Colour (0.60f, 0.00f, 0.00f)); + BookTypesetter::Style* body = typesetter->createStyle ("EB Garamond", MyGUI::Colour::Black); + + mModel->visitTopicName (topicId, AddTopicName (typesetter, header)); + + intptr_t contentId = typesetter->addContent (to_utf8_span (", \"")); + + mModel->visitTopicEntries (topicId, AddTopicEntry (typesetter, body, header, contentId)); + + return typesetter->complete (); +} + +book JournalBooks::createQuestBook (uintptr_t questId) +{ + BookTypesetter::Ptr typesetter = createTypesetter (); + + BookTypesetter::Style* header = typesetter->createStyle ("EB Garamond", MyGUI::Colour (0.60f, 0.00f, 0.00f)); + BookTypesetter::Style* body = typesetter->createStyle ("EB Garamond", MyGUI::Colour::Black); + + mModel->visitQuestName (questId, AddQuestName (typesetter, header)); + + mModel->visitJournalEntries (questId, AddJournalEntry (typesetter, body, header, false)); + + return typesetter->complete (); +} + +book JournalBooks::createTopicIndexBook () +{ + BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250); + + typesetter->setSectionAlignment (BookTypesetter::AlignCenter); + + BookTypesetter::Style* body = typesetter->createStyle ("EB Garamond", MyGUI::Colour::Black); + + for (int i = 0; i < 26; ++i) + { + char ch = 'A' + i; + + char buffer [32]; + + sprintf (buffer, "( %c )", ch); + + BookTypesetter::Style* style = typesetter->createHotStyle (body, MyGUI::Colour::Black, linkHot, linkActive, ch); + + if (i == 13) + typesetter->sectionBreak (); + + typesetter->write (style, to_utf8_span (buffer)); + typesetter->lineBreak (); + } + + return typesetter->complete (); +} + +book JournalBooks::createTopicIndexBook (char character) +{ + BookTypesetter::Ptr typesetter = BookTypesetter::create (0x7FFFFFFF, 0x7FFFFFFF); + BookTypesetter::Style* style = typesetter->createStyle ("EB Garamond", MyGUI::Colour::Black); + + mModel->visitTopicNamesStartingWith (character, AddTopicLink (typesetter, style)); + + return typesetter->complete (); +} + +book JournalBooks::createQuestIndexBook (bool activeOnly) +{ + BookTypesetter::Ptr typesetter = BookTypesetter::create (0x7FFFFFFF, 0x7FFFFFFF); + BookTypesetter::Style* base = typesetter->createStyle ("EB Garamond", MyGUI::Colour::Black); + + mModel->visitQuestNames (activeOnly, AddQuestLink (typesetter, base)); + + return typesetter->complete (); +} + +BookTypesetter::Ptr JournalBooks::createTypesetter () +{ + //TODO: determine page size from layout... + return BookTypesetter::create (240, 300); +} + +} diff --git a/apps/openmw/mwgui/journalbooks.hpp b/apps/openmw/mwgui/journalbooks.hpp new file mode 100644 index 0000000000..09d3cf1a85 --- /dev/null +++ b/apps/openmw/mwgui/journalbooks.hpp @@ -0,0 +1,29 @@ +#ifndef MWGUI_JOURNALBOOKS_HPP +#define MWGUI_JOURNALBOOKS_HPP + +#include "bookpage.hpp" +#include "journalviewmodel.hpp" + +namespace MWGui +{ + struct JournalBooks + { + typedef TypesetBook::Ptr Book; + JournalViewModel::Ptr mModel; + + JournalBooks (JournalViewModel::Ptr model); + + Book createEmptyJournalBook (); + Book createJournalBook (); + Book createTopicBook (uintptr_t topicId); + Book createQuestBook (uintptr_t questId); + Book createTopicIndexBook (); + Book createTopicIndexBook (char character); + Book createQuestIndexBook (bool showAll); + + private: + BookTypesetter::Ptr createTypesetter (); + }; +} + +#endif // MWGUI_JOURNALBOOKS_HPP diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp new file mode 100644 index 0000000000..79a77070ae --- /dev/null +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -0,0 +1,389 @@ +#include "journalviewmodel.hpp" + +#include +#include +#include + +#include + +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/journal.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwdialogue/journalentry.hpp" + +#include "keywordsearch.hpp" + +namespace MWGui { + +struct JournalViewModelImpl; + +static void injectMonthName (std::ostream & os, int month); + +struct JournalViewModelImpl : JournalViewModel +{ + typedef KeywordSearch KeywordSearchT; + + mutable bool mKeywordSearchLoaded; + mutable KeywordSearchT mKeywordSearch; + + std::locale mLocale; + + JournalViewModelImpl () + { + mKeywordSearchLoaded = false; + } + + virtual ~JournalViewModelImpl () + { + } + + /// \todo replace this nasty BS + static Utf8Span toUtf8Span (std::string const & str) + { + if (str.size () == 0) + return Utf8Span (Utf8Point (NULL), Utf8Point (NULL)); + + Utf8Point point = reinterpret_cast (str.c_str ()); + + return Utf8Span (point, point + str.size ()); + } + + void load () + { + } + + void unload () + { + mKeywordSearch.clear (); + mKeywordSearchLoaded = false; + } + + void ensureKeyWordSearchLoaded () const + { + if (!mKeywordSearchLoaded) + { + MWBase::Journal * journal = MWBase::Environment::get().getJournal(); + + for(MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd (); ++i) + mKeywordSearch.seed (i->first, intptr_t (&i->second)); + + mKeywordSearchLoaded = true; + } + } + + wchar_t tolower (wchar_t ch) const { return std::tolower (ch, mLocale); } + + bool isEmpty () const + { + MWBase::Journal * journal = MWBase::Environment::get().getJournal(); + + return journal->begin () == journal->end (); + } + + template + struct BaseEntry : Interface + { + typedef t_iterator iterator_t; + + iterator_t itr; + JournalViewModelImpl const * mModel; + + BaseEntry (JournalViewModelImpl const * model, iterator_t itr) : + mModel (model), itr (itr), loaded (false) + {} + + virtual ~BaseEntry () {} + + mutable bool loaded; + mutable std::string utf8text; + + typedef std::pair Range; + + // hyperlinks in @link# notation + mutable std::map mHyperLinks; + + virtual std::string getText () const = 0; + + void ensureLoaded () const + { + if (!loaded) + { + mModel->ensureKeyWordSearchLoaded (); + + utf8text = getText (); + + size_t pos_begin, pos_end; + for(;;) + { + pos_begin = utf8text.find('@'); + if (pos_begin != std::string::npos) + pos_end = utf8text.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) + { + std::string link = utf8text.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; + while (displayName[displayName.size()-1] == '*') + displayName.erase(displayName.size()-1, 1); + + utf8text.replace(pos_begin, pos_end+1-pos_begin, displayName); + + intptr_t value; + if (mModel->mKeywordSearch.containsKeyword(topicName, value)) + mHyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = value; + } + else + break; + } + + loaded = true; + } + } + + Utf8Span body () const + { + ensureLoaded (); + + return toUtf8Span (utf8text); + } + + void visitSpans (boost::function < void (TopicId, size_t, size_t)> visitor) const + { + ensureLoaded (); + mModel->ensureKeyWordSearchLoaded (); + + if (mHyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) + { + size_t formatted = 0; // points to the first character that is not laid out yet + for (std::map::const_iterator it = mHyperLinks.begin(); it != mHyperLinks.end(); ++it) + { + intptr_t topicId = it->second; + if (formatted < it->first.first) + visitor (0, formatted, it->first.first); + visitor (topicId, it->first.first, it->first.second); + formatted = it->first.second; + } + if (formatted < utf8text.size()) + visitor (0, formatted, utf8text.size()); + } + else + { + std::string::const_iterator i = utf8text.begin (); + + KeywordSearchT::Match match; + + while (i != utf8text.end () && mModel->mKeywordSearch.search (i, utf8text.end (), match)) + { + if (i != match.mBeg) + visitor (0, i - utf8text.begin (), match.mBeg - utf8text.begin ()); + + visitor (match.mValue, match.mBeg - utf8text.begin (), match.mEnd - utf8text.begin ()); + + i = match.mEnd; + } + + if (i != utf8text.end ()) + visitor (0, i - utf8text.begin (), utf8text.size ()); + } + } + + }; + + void visitQuestNames (bool active_only, boost::function visitor) const + { + MWBase::Journal * journal = MWBase::Environment::get ().getJournal (); + + for (MWBase::Journal::TQuestIter i = journal->questBegin (); i != journal->questEnd (); ++i) + { + if (active_only && i->second.isFinished ()) + continue; + + /// \todo quest.getName() is broken? returns empty string + //const MWDialogue::Quest& quest = i->second; + + visitor (reinterpret_cast (&i->second), toUtf8Span (i->first)); + } + } + + void visitQuestName (QuestId questId, boost::function visitor) const + { + MWDialogue::Quest const * quest = reinterpret_cast (questId); + + std::string name = quest->getName (); + + visitor (toUtf8Span (name)); + } + + template + struct JournalEntryImpl : BaseEntry + { + using BaseEntry ::itr; + + mutable std::string timestamp_buffer; + + JournalEntryImpl (JournalViewModelImpl const * model, iterator_t itr) : + BaseEntry (model, itr) + {} + + std::string getText () const + { + return itr->getText(MWBase::Environment::get().getWorld()->getStore()); + } + + Utf8Span timestamp () const + { + if (timestamp_buffer.empty ()) + { + std::ostringstream os; + + os << itr->mDayOfMonth << ' '; + + injectMonthName (os, itr->mMonth); + + const std::string& dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}"); + os << " (" << dayStr << " " << (itr->mDay + 1) << ')'; + + timestamp_buffer = os.str (); + } + + return toUtf8Span (timestamp_buffer); + } + }; + + void visitJournalEntries (QuestId questId, boost::function visitor) const + { + MWBase::Journal * journal = MWBase::Environment::get().getJournal(); + + if (questId != 0) + { + MWDialogue::Quest const * quest = reinterpret_cast (questId); + + for(MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end (); ++i) + { + for (MWDialogue::Topic::TEntryIter j = quest->begin (); j != quest->end (); ++j) + { + if (i->mInfoId == *j) + visitor (JournalEntryImpl (this, i)); + } + } + } + else + { + for(MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end (); ++i) + visitor (JournalEntryImpl (this, i)); + } + } + + void visitTopics (boost::function visitor) const + { + throw std::runtime_error ("not implemented"); + } + + void visitTopicName (TopicId topicId, boost::function visitor) const + { + MWDialogue::Topic const & topic = * reinterpret_cast (topicId); + // This is to get the correct case for the topic + const std::string& name = MWBase::Environment::get().getWorld()->getStore().get().find(topic.getName())->mId; + visitor (toUtf8Span (name)); + } + + void visitTopicNamesStartingWith (char character, boost::function < void (TopicId , Utf8Span) > visitor) const + { + MWBase::Journal * journal = MWBase::Environment::get().getJournal(); + + for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) + { + if (i->first [0] != std::tolower (character, mLocale)) + continue; + + // This is to get the correct case for the topic + const std::string& name = MWBase::Environment::get().getWorld()->getStore().get().find(i->first)->mId; + + visitor (TopicId (&i->second), toUtf8Span (name)); + } + + } + + struct TopicEntryImpl : BaseEntry + { + MWDialogue::Topic const & mTopic; + + mutable std::string source_buffer; + + TopicEntryImpl (JournalViewModelImpl const * model, MWDialogue::Topic const & topic, iterator_t itr) : + BaseEntry (model, itr), mTopic (topic) + {} + + std::string getText () const + { + /// \todo defines are not replaced (%PCName etc). should probably be done elsewhere though since we need the actor + return mTopic.getEntry (*itr).getText(MWBase::Environment::get().getWorld()->getStore()); + + } + + Utf8Span source () const + { + if (source_buffer.empty ()) + source_buffer = "someone"; + return toUtf8Span (source_buffer); + } + + }; + + void visitTopicEntries (TopicId topicId, boost::function visitor) const + { + typedef MWDialogue::Topic::TEntryIter iterator_t; + + MWDialogue::Topic const & topic = * reinterpret_cast (topicId); + + for (iterator_t i = topic.begin (); i != topic.end (); ++i) + visitor (TopicEntryImpl (this, topic, i)); + } +}; + +static void injectMonthName (std::ostream & os, int month) +{ + MyGUI::LanguageManager& lm = MyGUI::LanguageManager::getInstance(); + + if (month == 0) + os << lm.replaceTags ("#{sMonthMorningstar}"); + else if (month == 1) + os << lm.replaceTags ("#{sMonthSunsdawn}"); + else if (month == 2) + os << lm.replaceTags ("#{sMonthFirstseed}"); + else if (month == 3) + os << lm.replaceTags ("#{sMonthRainshand}"); + else if (month == 4) + os << lm.replaceTags ("#{sMonthSecondseed}"); + else if (month == 5) + os << lm.replaceTags ("#{sMonthMidyear}"); + else if (month == 6) + os << lm.replaceTags ("#{sMonthSunsheight}"); + else if (month == 7) + os << lm.replaceTags ("#{sMonthLastseed}"); + else if (month == 8) + os << lm.replaceTags ("#{sMonthHeartfire}"); + else if (month == 9) + os << lm.replaceTags ("#{sMonthFrostfall}"); + else if (month == 10) + os << lm.replaceTags ("#{sMonthSunsdusk}"); + else if (month == 11) + os << lm.replaceTags ("#{sMonthEveningstar}"); + else + os << month; +} + +JournalViewModel::Ptr JournalViewModel::create () +{ + return boost::make_shared (); +} + +} diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp new file mode 100644 index 0000000000..21c3a11785 --- /dev/null +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -0,0 +1,93 @@ +#ifndef MWGUI_JOURNALVIEWMODEL_HPP +#define MWGUI_JOURNALVIEWMODEL_HPP + +#include +#include +#include +#include +#include +#include + +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 JournalViewModel + { + typedef boost::shared_ptr Ptr; + + typedef intptr_t QuestId; + typedef intptr_t TopicId; + typedef uint8_t const * Utf8Point; + typedef std::pair Utf8Span; + + /// The base interface for both journal entries and topics. + struct Entry + { + /// 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 Utf8Span 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 visitSpans (boost::function visitor) const = 0; + }; + + /// An interface to topic data. + struct TopicEntry : Entry + { + /// Returns a pre-formatted span of UTF8 encoded text representing + /// the name of the NPC this portion of dialog was heard from. + virtual Utf8Span source () const = 0; + }; + + /// An interface to journal data. + struct JournalEntry : Entry + { + /// Returns a pre-formatted span of UTF8 encoded text representing + /// the in-game date this entry was added to the journal. + virtual Utf8Span 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 isEmpty () const = 0; + + /// provides access to the name of the quest with the specified identifier + virtual void visitQuestName (TopicId topicId, boost::function visitor) const = 0; + + /// walks the active and optionally completed, quests providing the quest id and name + virtual void visitQuestNames (bool active_only, boost::function visitor) const = 0; + + /// walks over the journal entries related to the specified quest identified by its id + virtual void visitJournalEntries (QuestId questId, boost::function visitor) const = 0; + + /// provides the name of the topic specified by its id + virtual void visitTopicName (TopicId topicId, boost::function visitor) const = 0; + + /// walks over the topics whose names start with the specified character providing the topics id and name + virtual void visitTopicNamesStartingWith (char character, boost::function < void (TopicId , Utf8Span) > visitor) const = 0; + + /// walks over the topic entries for the topic specified by its identifier + virtual void visitTopicEntries (TopicId topicId, boost::function visitor) const = 0; + + // create an instance of the default journal view model implementation + static Ptr create (); + }; +} + +#endif // MWGUI_JOURNALVIEWMODEL_HPP diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 23588a4afa..d0332c9431 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -1,192 +1,433 @@ #include "journalwindow.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/journal.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "list.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include "boost/lexical_cast.hpp" + +#include "bookpage.hpp" +#include "windowbase.hpp" +#include "imagebutton.hpp" +#include "journalviewmodel.hpp" +#include "journalbooks.hpp" namespace { - struct book + static char const OptionsOverlay [] = "OptionsOverlay"; + static char const OptionsBTN [] = "OptionsBTN"; + static char const PrevPageBTN [] = "PrevPageBTN"; + static char const NextPageBTN [] = "NextPageBTN"; + static char const CloseBTN [] = "CloseBTN"; + static char const JournalBTN [] = "JournalBTN"; + static char const TopicsBTN [] = "TopicsBTN"; + static char const QuestsBTN [] = "QuestsBTN"; + static char const CancelBTN [] = "CancelBTN"; + static char const ShowAllBTN [] = "ShowAllBTN"; + static char const ShowActiveBTN [] = "ShowActiveBTN"; + static char const PageOneNum [] = "PageOneNum"; + static char const PageTwoNum [] = "PageTwoNum"; + static char const TopicsList [] = "TopicsList"; + static char const TopicsPage [] = "TopicsPage"; + static char const QuestsList [] = "QuestsList"; + static char const QuestsPage [] = "QuestsPage"; + static char const LeftBookPage [] = "LeftBookPage"; + static char const RightBookPage [] = "RightBookPage"; + static char const LeftTopicIndex [] = "LeftTopicIndex"; + static char const RightTopicIndex [] = "RightTopicIndex"; + + struct JournalWindowImpl : MWGui::WindowBase, MWGui::JournalBooks, MWGui::JournalWindow { - int endLine; - std::list pages; + struct DisplayState + { + unsigned int mPage; + Book mBook; + }; + + typedef std::stack DisplayStateStack; + + DisplayStateStack mStates; + Book mTopicIndexBook; + bool mQuestMode; + bool mAllQuests; + + template + T * getWidget (char const * name) + { + T * widget; + WindowBase::getWidget (widget, name); + return widget; + } + + template + void setText (char const * name, value_type const & value) + { + getWidget (name) -> + setCaption (boost::lexical_cast (value)); + } + + void setVisible (char const * name, bool visible) + { + getWidget (name) -> + setVisible (visible); + } + + void adviseButtonClick (char const * name, void (JournalWindowImpl::*Handler) (MyGUI::Widget* _sender)) + { + getWidget (name) -> + eventMouseButtonClick += newDelegate(this, Handler); + } + + MWGui::BookPage* getPage (char const * name) + { + return getWidget (name); + } + + JournalWindowImpl (MWGui::JournalViewModel::Ptr Model) + : WindowBase("openmw_journal.layout"), JournalBooks (Model) + { + mMainWidget->setVisible(false); + center(); + + adviseButtonClick (OptionsBTN, &JournalWindowImpl::notifyOptions ); + adviseButtonClick (PrevPageBTN, &JournalWindowImpl::notifyPrevPage ); + adviseButtonClick (NextPageBTN, &JournalWindowImpl::notifyNextPage ); + adviseButtonClick (CloseBTN, &JournalWindowImpl::notifyClose ); + adviseButtonClick (JournalBTN, &JournalWindowImpl::notifyJournal ); + + adviseButtonClick (TopicsBTN, &JournalWindowImpl::notifyTopics ); + adviseButtonClick (QuestsBTN, &JournalWindowImpl::notifyQuests ); + adviseButtonClick (CancelBTN, &JournalWindowImpl::notifyCancel ); + + adviseButtonClick (ShowAllBTN, &JournalWindowImpl::notifyShowAll ); + adviseButtonClick (ShowActiveBTN, &JournalWindowImpl::notifyShowActive); + + { + MWGui::BookPage::ClickCallback callback; + + callback = boost::bind (&JournalWindowImpl::notifyTopicClicked, this, _1); + + getPage (TopicsPage)->adviseLinkClicked (callback); + getPage (LeftBookPage)->adviseLinkClicked (callback); + getPage (RightBookPage)->adviseLinkClicked (callback); + } + + { + MWGui::BookPage::ClickCallback callback; + + callback = boost::bind (&JournalWindowImpl::notifyIndexLinkClicked, this, _1); + + getPage (LeftTopicIndex)->adviseLinkClicked (callback); + getPage (RightTopicIndex)->adviseLinkClicked (callback); + } + + { + MWGui::BookPage::ClickCallback callback; + + callback = boost::bind (&JournalWindowImpl::notifyQuestClicked, this, _1); + + getPage (QuestsPage)->adviseLinkClicked (callback); + } + + mQuestMode = false; + mAllQuests = false; + } + + void open() + { + mModel->load (); + + setBookMode (); + + /// \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 (); + else + journalBook = createJournalBook (); + + pushBook (journalBook, 0); + + // fast forward to the last page + if (!mStates.empty ()) + { + unsigned int & page = mStates.top ().mPage; + page = mStates.top().mBook->pageCount()-1; + if (page%2) + --page; + } + updateShowingPages(); + } + + void close() + { + mModel->unload (); + + getPage (LeftBookPage)->showPage (Book (), 0); + getPage (RightBookPage)->showPage (Book (), 0); + + while (!mStates.empty ()) + mStates.pop (); + + mTopicIndexBook.reset (); + } + + void setVisible (bool newValue) + { + WindowBase::setVisible (newValue); + } + + void setBookMode () + { + setVisible (OptionsBTN, true); + setVisible (OptionsOverlay, false); + + updateShowingPages (); + updateCloseJournalButton (); + } + + void setOptionsMode () + { + setVisible (OptionsBTN, false); + setVisible (OptionsOverlay, true); + + setVisible (PrevPageBTN, false); + setVisible (NextPageBTN, false); + setVisible (CloseBTN, false); + setVisible (JournalBTN, false); + + setVisible (TopicsList, false); + setVisible (QuestsList, mQuestMode); + setVisible (LeftTopicIndex, !mQuestMode); + setVisible (RightTopicIndex, !mQuestMode); + setVisible (ShowAllBTN, mQuestMode && !mAllQuests); + setVisible (ShowActiveBTN, mQuestMode && mAllQuests); + + //TODO: figure out how to make "options" page overlay book page + // correctly, so that text may show underneath + getPage (RightBookPage)->showPage (Book (), 0); + } + + void pushBook (Book book, unsigned int page) + { + DisplayState bs; + bs.mPage = page; + bs.mBook = book; + mStates.push (bs); + updateShowingPages (); + updateCloseJournalButton (); + } + + void replaceBook (Book book, unsigned int page) + { + assert (!mStates.empty ()); + mStates.top ().mBook = book; + mStates.top ().mPage = page; + updateShowingPages (); + } + + void popBook () + { + mStates.pop (); + updateShowingPages (); + updateCloseJournalButton (); + } + + void updateCloseJournalButton () + { + setVisible (CloseBTN, mStates.size () < 2); + setVisible (JournalBTN, mStates.size () >= 2); + } + + void updateShowingPages () + { + Book book; + unsigned int page; + unsigned int relPages; + + if (!mStates.empty ()) + { + book = mStates.top ().mBook; + page = mStates.top ().mPage; + relPages = book->pageCount () - page; + } + else + { + page = 0; + relPages = 0; + } + + setVisible (PrevPageBTN, page > 0); + setVisible (NextPageBTN, relPages > 2); + + setVisible (PageOneNum, relPages > 0); + setVisible (PageTwoNum, relPages > 1); + + getPage (LeftBookPage)->showPage ((relPages > 0) ? book : Book (), page+0); + getPage (RightBookPage)->showPage ((relPages > 0) ? book : Book (), page+1); + + setText (PageOneNum, page + 1); + setText (PageTwoNum, page + 2); + } + + void notifyTopicClicked (intptr_t linkId) + { + Book topicBook = createTopicBook (linkId); + + if (mStates.size () > 1) + replaceBook (topicBook, 0); + else + pushBook (topicBook, 0); + + setVisible (OptionsOverlay, false); + setVisible (OptionsBTN, true); + setVisible (JournalBTN, true); + } + + void notifyQuestClicked (intptr_t questId) + { + Book book = createQuestBook (questId); + + if (mStates.size () > 1) + replaceBook (book, 0); + else + pushBook (book, 0); + + setVisible (OptionsOverlay, false); + setVisible (OptionsBTN, true); + setVisible (JournalBTN, true); + } + + void notifyOptions(MyGUI::Widget* _sender) + { + setOptionsMode (); + + if (!mTopicIndexBook) + mTopicIndexBook = createTopicIndexBook (); + + getPage (LeftTopicIndex)->showPage (mTopicIndexBook, 0); + getPage (RightTopicIndex)->showPage (mTopicIndexBook, 1); + } + + void notifyJournal(MyGUI::Widget* _sender) + { + assert (mStates.size () > 1); + popBook (); + } + + void showList (char const * listId, char const * pageId, Book book) + { + std::pair size = book->getSize (); + + getPage (pageId)->showPage (book, 0); + + getWidget (listId)->setCanvasSize (size.first, size.second); + } + + void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId character) + { + setVisible (LeftTopicIndex, false); + setVisible (RightTopicIndex, false); + setVisible (TopicsList, true); + + showList (TopicsList, TopicsPage, createTopicIndexBook ((char)character)); + } + + void notifyTopics(MyGUI::Widget* _sender) + { + mQuestMode = false; + setVisible (LeftTopicIndex, true); + setVisible (RightTopicIndex, true); + setVisible (TopicsList, false); + setVisible (QuestsList, false); + setVisible (ShowAllBTN, false); + setVisible (ShowActiveBTN, false); + } + + void notifyQuests(MyGUI::Widget* _sender) + { + mQuestMode = true; + setVisible (LeftTopicIndex, false); + setVisible (RightTopicIndex, false); + setVisible (TopicsList, false); + setVisible (QuestsList, true); + setVisible (ShowAllBTN, !mAllQuests); + setVisible (ShowActiveBTN, mAllQuests); + + showList (QuestsList, QuestsPage, createQuestIndexBook (!mAllQuests)); + } + + void notifyShowAll(MyGUI::Widget* _sender) + { + mAllQuests = true; + setVisible (ShowAllBTN, !mAllQuests); + setVisible (ShowActiveBTN, mAllQuests); + showList (QuestsList, QuestsPage, createQuestIndexBook (!mAllQuests)); + } + + void notifyShowActive(MyGUI::Widget* _sender) + { + mAllQuests = false; + setVisible (ShowAllBTN, !mAllQuests); + setVisible (ShowActiveBTN, mAllQuests); + showList (QuestsList, QuestsPage, createQuestIndexBook (!mAllQuests)); + } + + void notifyCancel(MyGUI::Widget* _sender) + { + setBookMode (); + } + + void notifyClose(MyGUI::Widget* _sender) + { + + MWBase::Environment::get().getWindowManager ()->popGuiMode (); + } + + void notifyNextPage(MyGUI::Widget* _sender) + { + if (!mStates.empty ()) + { + unsigned int & page = mStates.top ().mPage; + Book book = mStates.top ().mBook; + + if (page < book->pageCount () - 2) + { + page += 2; + updateShowingPages (); + } + } + } + + void notifyPrevPage(MyGUI::Widget* _sender) + { + if (!mStates.empty ()) + { + unsigned int & page = mStates.top ().mPage; + + if(page > 0) + { + page -= 2; + updateShowingPages (); + } + } + } }; } -book formatText(std::string text,book mBook,int maxLine, int lineSize) +// glue the implementation to the interface +MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model) { - //stringList.push_back(""); - - int cLineSize = 0; - int cLine = mBook.endLine +1; - std::string cString; - - if(mBook.pages.empty()) - { - cString = ""; - cLine = 0; - } - else - { - cString = mBook.pages.back() + std::string("\n"); - mBook.pages.pop_back(); - } - - //std::string::iterator wordBegin = text.begin(); - //std::string::iterator wordEnd; - - std::string cText = text; - - while(cText.length() != 0) - { - size_t firstSpace = cText.find_first_of(' '); - if(firstSpace == std::string::npos) - { - cString = cString + cText; - mBook.pages.push_back(cString); - //TODO:finnish this - break; - } - if(static_cast (firstSpace) + cLineSize <= lineSize) - { - cLineSize = firstSpace + cLineSize; - cString = cString + cText.substr(0,firstSpace +1); - } - else - { - cLineSize = firstSpace; - if(cLine +1 <= maxLine) - { - cLine = cLine + 1; - cString = cString + std::string("\n") + cText.substr(0,firstSpace +1); - } - else - { - cLine = 0; - mBook.pages.push_back(cString); - cString = cText.substr(0,firstSpace +1); - } - } - //std::cout << cText << "\n"; - //std::cout << cText.length(); - cText = cText.substr(firstSpace +1,cText.length() - firstSpace -1); - } - mBook.endLine = cLine; - return mBook; - //std::string -} - - -MWGui::JournalWindow::JournalWindow () - : WindowBase("openmw_journal.layout") - , mPageNumber(0) -{ - mMainWidget->setVisible(false); - //setCoord(0,0,498, 342); - center(); - - getWidget(mLeftTextWidget, "LeftText"); - getWidget(mRightTextWidget, "RightText"); - getWidget(mPrevBtn, "PrevPageBTN"); - mPrevBtn->eventMouseButtonClick += MyGUI::newDelegate(this,&MWGui::JournalWindow::notifyPrevPage); - getWidget(mNextBtn, "NextPageBTN"); - mNextBtn->eventMouseButtonClick += MyGUI::newDelegate(this,&MWGui::JournalWindow::notifyNextPage); - //MyGUI::ItemBox* list = new MyGUI::ItemBox(); - //list->addItem("qaq","aqzazaz"); - //mScrollerWidget->addChildItem(list); - //mScrollerWidget->addItem("dserzt",MyGUI::UString("fedgdfg")); - //mEditWidget->addText("ljblsxdvdsfvgedfvdfvdkjfghldfjgn sdv,nhsxl;vvn lklksbvlksb lbsdflkbdSLKJGBLskdhbvlshow(); - //mEditWidget->setEditStatic(true); - mLeftTextWidget->addText("left texxxt "); - mLeftTextWidget->setEditReadOnly(true); - mRightTextWidget->setEditReadOnly(true); - mRightTextWidget->setEditStatic(true); - mLeftTextWidget->setEditStatic(true); - mRightTextWidget->addText("Right texxt "); - - //std::list list = formatText("OpenMW rgh dsfg sqef srg ZT uzql n ZLIEHRF LQSJH GLOIjf qjfmj hslkdgn jlkdjhg qlr isgli shli uhs fiuh qksf cg ksjnf lkqsnbf ksbf sbfkl zbf kuyzflkj sbgdfkj zlfh ozhjfmo hzmfh lizuf rty qzt ezy tkyEZT RYYJ DG fgh is an open-source implementation of the game engine found in the game Morrowind. This is a dumb test text msodjbg smojg smoig fiiinnn"); - //std::list list = formatText(); - //displayLeftText(list.front()); -} - -void MWGui::JournalWindow::open() -{ - mPageNumber = 0; - if(MWBase::Environment::get().getJournal()->begin()!=MWBase::Environment::get().getJournal()->end()) - { - book journal; - journal.endLine = 0; - - for(std::deque::const_iterator it = MWBase::Environment::get().getJournal()->begin();it!=MWBase::Environment::get().getJournal()->end();++it) - { - std::string a = it->getText(MWBase::Environment::get().getWorld()->getStore()); - journal = formatText(a,journal,10,17); - journal.endLine = journal.endLine +1; - journal.pages.back() = journal.pages.back() + std::string("\n"); - } - //std::string a = MWBase::Environment::get().getJournal()->begin()->getText(MWBase::Environment::get().getWorld()->getStore()); - //std::list journal = formatText(a,10,20,1); - bool left = true; - for(std::list::iterator it = journal.pages.begin(); it != journal.pages.end();++it) - { - if(left) - { - mLeftPages.push_back(*it); - } - else - { - mRightPages.push_back(*it); - } - left = !left; - } - if(!left) mRightPages.push_back(""); - - mPageNumber = mLeftPages.size()-1; - displayLeftText(mLeftPages[mPageNumber]); - displayRightText(mRightPages[mPageNumber]); - - } - else - { - //std::cout << MWBase::Environment::get().getJournal()->begin()->getText(MWBase::Environment::get().getWorld()->getStore()); - } -} - -void MWGui::JournalWindow::displayLeftText(std::string text) -{ - mLeftTextWidget->eraseText(0,mLeftTextWidget->getTextLength()); - mLeftTextWidget->addText(text); -} - -void MWGui::JournalWindow::displayRightText(std::string text) -{ - mRightTextWidget->eraseText(0,mRightTextWidget->getTextLength()); - mRightTextWidget->addText(text); -} - - -void MWGui::JournalWindow::notifyNextPage(MyGUI::Widget* _sender) -{ - if(mPageNumber < int(mLeftPages.size())-1) - { - std::string nextSound = "book page2"; - MWBase::Environment::get().getSoundManager()->playSound (nextSound, 1.0, 1.0); - mPageNumber = mPageNumber + 1; - displayLeftText(mLeftPages[mPageNumber]); - displayRightText(mRightPages[mPageNumber]); - } -} - -void MWGui::JournalWindow::notifyPrevPage(MyGUI::Widget* _sender) -{ - if(mPageNumber > 0) - { - std::string prevSound = "book page"; - MWBase::Environment::get().getSoundManager()->playSound (prevSound, 1.0, 1.0); - mPageNumber = mPageNumber - 1; - displayLeftText(mLeftPages[mPageNumber]); - displayRightText(mRightPages[mPageNumber]); - } + return new JournalWindowImpl (Model); } diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index 27bf608e18..63770ec1aa 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -1,39 +1,26 @@ #ifndef MWGUI_JOURNAL_H #define MWGUI_JOURNAL_H -#include +#include +#include -#include "windowbase.hpp" -#include "imagebutton.hpp" +namespace MWBase { class WindowManager; } namespace MWGui { - class JournalWindow : public WindowBase + struct JournalViewModel; + + struct JournalWindow { - public: - JournalWindow(); - virtual void open(); + /// construct a new instance of the one JournalWindow implementation + static JournalWindow * create (boost::shared_ptr Model); - private: - void displayLeftText(std::string text); - void displayRightText(std::string text); + /// destroy this instance of the JournalWindow implementation + virtual ~JournalWindow () {}; - - /** - *Called when next/prev button is used. - */ - void notifyNextPage(MyGUI::Widget* _sender); - void notifyPrevPage(MyGUI::Widget* _sender); - - MyGUI::EditBox* mLeftTextWidget; - MyGUI::EditBox* mRightTextWidget; - MWGui::ImageButton* mPrevBtn; - MWGui::ImageButton* mNextBtn; - std::vector mLeftPages; - std::vector mRightPages; - int mPageNumber; //store the number of the current left page + /// show/hide the journal window + virtual void setVisible (bool newValue) = 0; }; - } #endif diff --git a/apps/openmw/mwgui/keywordsearch.cpp b/apps/openmw/mwgui/keywordsearch.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/openmw/mwgui/keywordsearch.hpp b/apps/openmw/mwgui/keywordsearch.hpp new file mode 100644 index 0000000000..13d3318e59 --- /dev/null +++ b/apps/openmw/mwgui/keywordsearch.hpp @@ -0,0 +1,199 @@ +#ifndef MWGUI_KEYWORDSEARCH_H +#define MWGUI_KEYWORDSEARCH_H + +#include +#include +#include +#include +#include // std::reverse + +#include + +namespace MWGui +{ + +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 containsKeyword (string_t keyword, value_t& value) + { + typename Entry::childen_t::iterator current; + typename Entry::childen_t::iterator next; + + current = mRoot.mChildren.find (std::tolower (*keyword.begin(), mLocale)); + if (current == mRoot.mChildren.end()) + return false; + else if (current->second.mKeyword.size() && Misc::StringUtils::ciEqual(current->second.mKeyword, keyword)) + { + value = current->second.mValue; + return true; + } + + for (Point i = ++keyword.begin(); i != keyword.end(); ++i) + { + next = current->second.mChildren.find(std::tolower (*i, mLocale)); + if (next == current->second.mChildren.end()) + return false; + if (Misc::StringUtils::ciEqual(next->second.mKeyword, keyword)) + { + value = next->second.mValue; + return true; + } + current = next; + } + return false; + } + + 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; + + // some keywords might be longer variations of other keywords, so we definitely need a list of candidates + // the first element in the pair is length of the match, i.e. depth from the first character on + std::vector< typename std::pair > candidates; + + while ((j + 1) != end) + { + typename Entry::childen_t::iterator next = candidate->second.mChildren.find (std::tolower (*++j, mLocale)); + + if (next == candidate->second.mChildren.end ()) + { + if (candidate->second.mKeyword.size() > 0) + candidates.push_back(std::make_pair((j-i), candidate)); + break; + } + + candidate = next; + + if (candidate->second.mKeyword.size() > 0) + candidates.push_back(std::make_pair((j-i), candidate)); + } + + if (!candidates.size()) + continue; // didn't match enough to disambiguate, on to next character + + // shorter candidates will be added to the vector first. however, we want to check against longer candidates first + std::reverse(candidates.begin(), candidates.end()); + + for (typename std::vector< std::pair >::iterator it = candidates.begin(); + it != candidates.end(); ++it) + { + candidate = it->second; + // try to match the rest of the keyword + Point k = i + it->first; + typename string_t::const_iterator t = candidate->second.mKeyword.begin () + (k - i); + + + while (k != end && t != candidate->second.mKeyword.end ()) + { + if (std::tolower (*k, mLocale) != std::tolower (*t, mLocale)) + break; + + ++k, ++t; + } + + // didn't match full keyword, try the next candidate + if (t != candidate->second.mKeyword.end ()) + continue; + + // we did it, report the good news + match.mValue = candidate->second.mValue; + match.mBeg = i; + match.mEnd = k; + + 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); + + 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); + j->second.mKeyword.clear (); + } + } + if (depth+1 == keyword.size()) + j->second.mKeyword = value; + else // 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/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 3b341574d9..018f51feb8 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -22,6 +22,7 @@ namespace MWGui , mLastRenderTime(0.f) , mLastWallpaperChangeTime(0.f) , mFirstLoad(true) + , mTotalRefsLoading(0) { getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 876fb35220..2fc50f257c 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -13,6 +13,7 @@ namespace MWGui // defines mMessageBoxSpeed = 0.1; mInterMessageBoxe = NULL; + mStaticMessageBox = NULL; } void MessageBoxManager::onFrame (float frameDuration) @@ -68,11 +69,14 @@ namespace MWGui } } - void MessageBoxManager::createMessageBox (const std::string& message) + void MessageBoxManager::createMessageBox (const std::string& message, bool stat) { MessageBox *box = new MessageBox(*this, message); - removeMessageBox(message.length()*mMessageBoxSpeed, box); + if(stat) + mStaticMessageBox = box; + else + removeMessageBox(message.length()*mMessageBoxSpeed, box); mMessageBoxes.push_back(box); std::vector::iterator it; @@ -90,6 +94,12 @@ namespace MWGui } } + void MessageBoxManager::removeStaticMessageBox () + { + removeMessageBox(mStaticMessageBox); + mStaticMessageBox = NULL; + } + bool MessageBoxManager::createInteractiveMessageBox (const std::string& message, const std::vector& buttons) { if(mInterMessageBoxe != NULL) { diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 0df6f3544b..0e47b0323b 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -31,7 +31,8 @@ namespace MWGui public: MessageBoxManager (); void onFrame (float frameDuration); - void createMessageBox (const std::string& message); + void createMessageBox (const std::string& message, bool stat = false); + void removeStaticMessageBox (); bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); @@ -52,6 +53,7 @@ namespace MWGui private: std::vector mMessageBoxes; InteractiveMessageBox* mInterMessageBoxe; + MessageBox* mStaticMessageBox; std::vector mTimers; float mMessageBoxSpeed; }; diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 95bf17635f..b44c21c6eb 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -39,7 +39,7 @@ namespace MWGui MWWorld::LiveCellRef *ref = mScroll.get(); BookTextParser parser; - MyGUI::IntSize size = parser.parse(ref->mBase->mText, mTextView, 390); + MyGUI::IntSize size = parser.parseScroll(ref->mBase->mText, mTextView, 390); if (size.height > mTextView->getSize().height) mTextView->setCanvasSize(MyGUI::IntSize(410, size.height)); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 39ee4e01d0..413171dd4d 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -551,6 +551,8 @@ namespace MWGui while (mControlsBox->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mControlsBox->getChildAt(0)); + MWBase::Environment::get().getWindowManager ()->removeStaticMessageBox(); + std::vector actions = MWBase::Environment::get().getInputManager()->getActionSorting (); const int h = 18; @@ -585,7 +587,7 @@ namespace MWGui static_cast(_sender)->setCaptionWithReplacing("#{sNone}"); - MWBase::Environment::get().getWindowManager ()->messageBox ("#{sControlsMenu3}"); + MWBase::Environment::get().getWindowManager ()->staticMessageBox ("#{sControlsMenu3}"); MWBase::Environment::get().getWindowManager ()->disallowMouse(); MWBase::Environment::get().getInputManager ()->enableDetectingBindingMode (actionId); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index ff83f5f672..dc05b8a9b2 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -7,9 +7,9 @@ #include "console.hpp" #include "journalwindow.hpp" +#include "journalviewmodel.hpp" #include "charactercreation.hpp" #include "dialogue.hpp" -#include "dialoguehistory.hpp" #include "statswindow.hpp" #include "messagebox.hpp" #include "tooltips.hpp" @@ -38,6 +38,7 @@ #include "soulgemdialog.hpp" #include "companionwindow.hpp" #include "inventorywindow.hpp" +#include "bookpage.hpp" namespace MWGui { @@ -106,7 +107,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"); @@ -122,6 +122,7 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + BookPage::registerMyGUIComponents (); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); @@ -145,7 +146,7 @@ namespace MWGui mMap = new MapWindow(cacheDir); mStatsWindow = new StatsWindow(); mConsole = new Console(w,h, consoleOnlyScripts); - mJournal = new JournalWindow(); + mJournal = JournalWindow::create(JournalViewModel::create ()); mMessageBoxManager = new MessageBoxManager(); mInventoryWindow = new InventoryWindow(mDragAndDrop); mTradeWindow = new TradeWindow(); @@ -600,6 +601,16 @@ namespace MWGui } } + void WindowManager::staticMessageBox(const std::string& message) + { + mMessageBoxManager->createMessageBox(message, true); + } + + void WindowManager::removeStaticMessageBox() + { + mMessageBoxManager->removeStaticMessageBox(); + } + void WindowManager::enterPressed () { mMessageBoxManager->enterPressed(); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index acac77bf31..f3413ca2f6 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -192,6 +192,8 @@ namespace MWGui virtual void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted. virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector()); + virtual void staticMessageBox(const std::string& message); + virtual void removeStaticMessageBox(); virtual void enterPressed (); virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index cc1d83d9fc..b64b1d1f34 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -905,6 +905,10 @@ namespace MWInput void InputManager::keyBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control , OIS::KeyCode key, ICS::Control::ControlChangingDirection direction) { + //Disallow binding escape key, and unassigned keys + if(key==OIS::KC_ESCAPE || key==OIS::KC_UNASSIGNED) + return + clearAllBindings(control); ICS::DetectingBindingListener::keyBindingDetected (ICS, control, key, direction); MWBase::Environment::get().getWindowManager ()->notifyInputActionBound (); diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 13ae2a592e..b221856674 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -48,7 +48,7 @@ namespace MWMechanics { int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX); //check if actor is near the border of an inactive cell. If so, disable aitravel. - if(sideX*(pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX*(ESM::Land::REAL_SIZE/2. - 2000)) + if(sideX*(pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX*(ESM::Land::REAL_SIZE/2. - 200)) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; return true; @@ -58,7 +58,7 @@ namespace MWMechanics { int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY); //check if actor is near the border of an inactive cell. If so, disable aitravel. - if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY*(ESM::Land::REAL_SIZE/2. - 2000)) + if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY*(ESM::Land::REAL_SIZE/2. - 200)) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; return true; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5b31b04994..b3890464de 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -152,6 +152,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mMovingAnim(false) , mSecondsOfRunning(0) , mSecondsOfSwimming(0) + , mLooping(false) { if(!mAnimation) return; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 236e9c5df5..0811d87749 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -120,13 +121,11 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b MaterialManager::getSingleton().setDefaultTextureFiltering(tfo); MaterialManager::getSingleton().setDefaultAnisotropy( (filter == "anisotropic") ? Settings::Manager::getInt("anisotropy", "General") : 1 ); - //ResourceGroupManager::getSingleton ().declareResource ("GlobalMap.png", "Texture", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + Ogre::TextureManager::getSingleton().setMemoryBudget(126*1024*1024); + Ogre::MeshManager::getSingleton().setMemoryBudget(64*1024*1024); ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); - // causes light flicker in opengl when moving.. - //mRendering.getScene()->setCameraRelativeRendering(true); - // disable unsupported effects if (!Settings::Manager::getBool("shaders", "Objects")) Settings::Manager::setBool("enabled", "Shadows", false); @@ -236,6 +235,7 @@ void RenderingManager::toggleWater() void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) { + sh::Factory::getInstance().unloadUnreferencedMaterials(); mObjects.buildStaticGeometry (*store); mDebugging->cellAdded(store); if (store->mCell->isExterior()) @@ -306,7 +306,6 @@ void RenderingManager::update (float duration, bool paused) int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude; mRendering.getFader()->setFactor(1.f-(blind / 100.f)); - setAmbientMode(); // player position diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index b9c055acdf..892dab7cfc 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -49,6 +49,7 @@ namespace MWRender TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc) : Ogre::TerrainMaterialGenerator::Profile(parent, name, desc) , mGlobalColourMap(false) + , mMaterial(0) { } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 6c03236370..a21d03ad6a 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -41,7 +41,9 @@ namespace MWRender { { public: Reflection(Ogre::SceneManager* sceneManager) - : mSceneMgr(sceneManager) {} + : mSceneMgr(sceneManager) + , mIsUnderwater(false) + {} virtual ~Reflection() {} virtual void setHeight (float height) {} diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index cad143c469..bba5cb4ade 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -292,19 +292,25 @@ namespace MWScript std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; - std::map ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); - std::map::const_iterator it = ranks.begin(); - const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get().find(factionId); - if(it->second < 0 || it->second > 9) - return ""; + std::map ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); - if(it->second <= 8) // If player is at max rank, there is no next rank - return faction->mRanks[it->second + 1]; + if (ranks.size()) + { + std::map::const_iterator it = ranks.begin(); + + if(it->second < -1 || it->second > 9) + return ""; + + if(it->second <= 8) // If player is at max rank, there is no next rank + return faction->mRanks[it->second + 1]; + else + return faction->mRanks[it->second]; + } else - return faction->mRanks[it->second]; + return faction->mRanks[0]; } int InterpreterContext::getPCBounty() const diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 5a22309f94..40462c4881 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -141,7 +141,7 @@ namespace Compiler if (mState==SetMemberVarState) { - mMemberName = Misc::StringUtils::lowerCase (name); + mMemberName = name; char type = getContext().getMemberType (mMemberName, mName); if (type!=' ') diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 18e5f81c1f..52f892b420 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/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp new file mode 100644 index 0000000000..19a0688b26 --- /dev/null +++ b/components/misc/utf8stream.hpp @@ -0,0 +1,116 @@ +#ifndef MISC_UTF8ITER_HPP +#define MISC_UTF8ITER_HPP + +#include + +class Utf8Stream +{ +public: + + typedef uint32_t UnicodeChar; + typedef unsigned char const * Point; + + //static const unicode_char sBadChar = 0xFFFFFFFF; gcc can't handle this + static UnicodeChar sBadChar () { return UnicodeChar (0xFFFFFFFF); } + + Utf8Stream (Point begin, Point end) : + cur (begin), nxt (begin), end (end) + { + } + + Utf8Stream (std::pair range) : + cur (range.first), nxt (range.first), end (range.second) + { + } + + bool eof () const + { + return cur == end; + } + + Point current () const + { + return cur; + } + + UnicodeChar peek () + { + if (cur == nxt) + next (); + return val; + } + + UnicodeChar consume () + { + if (cur == nxt) + next (); + cur = nxt; + return val; + } + + static std::pair decode (Point cur, Point end) + { + if ((*cur & 0x80) == 0) + { + UnicodeChar chr = *cur++; + + return std::make_pair (chr, cur); + } + + int octets; + UnicodeChar chr; + + boost::tie (octets, chr) = octet_count (*cur++); + + if (octets > 5) + return std::make_pair (sBadChar(), cur); + + Point eoc = cur + octets; + + if (eoc > end) + return std::make_pair (sBadChar(), cur); + + while (cur != eoc) + { + if ((*cur & 0xC0) != 0x80) // check continuation mark + return std::make_pair (sBadChar(), cur);; + + chr = (chr << 6) | UnicodeChar ((*cur++) & 0x3F); + } + + return std::make_pair (chr, cur); + } + +private: + + static std::pair octet_count (unsigned char octet) + { + int octets; + + unsigned char mark = 0xC0; + unsigned char mask = 0xE0; + + for (octets = 1; octets <= 5; ++octets) + { + if ((octet & mask) == mark) + break; + + mark = (mark >> 1) | 0x80; + mask = (mask >> 1) | 0x80; + } + + return std::make_pair (octets, octet & ~mask); + } + + void next () + { + boost::tie (val, nxt) = decode (nxt, end); + } + + Point cur; + Point nxt; + Point end; + UnicodeChar val; +}; + +#endif diff --git a/credits.txt b/credits.txt index 6456e30a10..c7430d6841 100644 --- a/credits.txt +++ b/credits.txt @@ -52,6 +52,7 @@ Paul McElroy (Greendogo) Pieter van der Kloet (pvdk) Radu-Marius Popovici (rpopovici) Roman Melnik (Kromgart) +Roman Proskuryakov (humbug) Sandy Carter (bwrsandman) Sebastian Wick (swick) Sergey Shambir diff --git a/extern/oics/tinyxml.cpp b/extern/oics/tinyxml.cpp index 841a41cd39..29a4768aa0 100644 --- a/extern/oics/tinyxml.cpp +++ b/extern/oics/tinyxml.cpp @@ -706,9 +706,9 @@ void TiXmlElement::SetDoubleAttribute( const char * name, double val ) { char buf[256]; #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%f", val ); + TIXML_SNPRINTF( buf, sizeof(buf), "%f", val ); #else - sprintf( buf, "%f", val ); + sprintf( buf, "%f", val ); #endif SetAttribute( name, buf ); } @@ -1266,9 +1266,9 @@ void TiXmlAttribute::SetDoubleValue( double _value ) { char buf [256]; #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%lf", _value); + TIXML_SNPRINTF( buf, sizeof(buf), "%f", _value); #else - sprintf (buf, "%lf", _value); + sprintf (buf, "%f", _value); #endif SetValue (buf); } diff --git a/extern/shiny/CMakeLists.txt b/extern/shiny/CMakeLists.txt index 6eadcc1676..6b6068ba9e 100644 --- a/extern/shiny/CMakeLists.txt +++ b/extern/shiny/CMakeLists.txt @@ -9,8 +9,6 @@ set(SHINY_LIBRARY "shiny") set(SHINY_OGREPLATFORM_LIBRARY "shiny.OgrePlatform") # Sources -file(GLOB SOURCE_FILES Main/*.cpp ) - set(SOURCE_FILES Main/Factory.cpp Main/MaterialInstance.cpp @@ -57,12 +55,20 @@ file(GLOB OGRE_PLATFORM_SOURCE_FILES Platforms/Ogre/*.cpp) add_library(${SHINY_LIBRARY} STATIC ${SOURCE_FILES}) +set(SHINY_LIBRARIES ${SHINY_LIBRARY}) + if (SHINY_BUILD_OGRE_PLATFORM) add_library(${SHINY_OGREPLATFORM_LIBRARY} STATIC ${OGRE_PLATFORM_SOURCE_FILES}) + set(SHINY_LIBRARIES ${SHINY_LIBRARIES} ${SHINY_OGREPLATFORM_LIBRARY}) endif() +set(SHINY_LIBRARY ${SHINY_LIBRARY} PARENT_SCOPE) + +if (DEFINED SHINY_BUILD_MATERIAL_EDITOR) + add_subdirectory(Editor) + + set(SHINY_BUILD_EDITOR_FLAG ${SHINY_BUILD_EDITOR_FLAG} PARENT_SCOPE) +endif() link_directories(${CMAKE_CURRENT_BINARY_DIR}) - -set(SHINY_LIBRARY ${SHINY_LIBRARY} PARENT_SCOPE) -set(SHINY_OGREPLATFORM_LIBRARY ${SHINY_OGREPLATFORM_LIBRARY} PARENT_SCOPE) +set(SHINY_LIBRARIES ${SHINY_LIBRARIES} PARENT_SCOPE) diff --git a/extern/shiny/Docs/Configurations.dox b/extern/shiny/Docs/Configurations.dox index affd914237..570e81d4af 100644 --- a/extern/shiny/Docs/Configurations.dox +++ b/extern/shiny/Docs/Configurations.dox @@ -21,7 +21,7 @@ } \endcode - \note You may also create configurations using sh::Factory::registerConfiguration. + \note You may also create configurations using sh::Factory::createConfiguration. The active Configuration is controlled by the active material scheme in Ogre. So, in order to use the configuration "reflection_targets" for your reflection renders, simply call \code diff --git a/extern/shiny/Editor/Actions.cpp b/extern/shiny/Editor/Actions.cpp new file mode 100644 index 0000000000..135e819878 --- /dev/null +++ b/extern/shiny/Editor/Actions.cpp @@ -0,0 +1,195 @@ +#include "Actions.hpp" + +#include "../Main/Factory.hpp" + +namespace sh +{ + + void ActionDeleteMaterial::execute() + { + sh::Factory::getInstance().destroyMaterialInstance(mName); + } + + void ActionCloneMaterial::execute() + { + sh::MaterialInstance* sourceMaterial = sh::Factory::getInstance().getMaterialInstance(mSourceName); + std::string sourceMaterialParent = static_cast(sourceMaterial->getParent())->getName(); + sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance( + mDestName, sourceMaterialParent); + sourceMaterial->copyAll(material, sourceMaterial, false); + + material->setSourceFile(sourceMaterial->getSourceFile()); + } + + void ActionSaveAll::execute() + { + sh::Factory::getInstance().saveAll(); + } + + void ActionChangeGlobalSetting::execute() + { + sh::Factory::getInstance().setGlobalSetting(mName, mNewValue); + } + + void ActionCreateConfiguration::execute() + { + sh::Configuration newConfiguration; + sh::Factory::getInstance().createConfiguration(mName); + } + + void ActionDeleteConfiguration::execute() + { + sh::Factory::getInstance().destroyConfiguration(mName); + } + + void ActionChangeConfiguration::execute() + { + sh::Configuration* c = sh::Factory::getInstance().getConfiguration(mName); + c->setProperty(mKey, sh::makeProperty(new sh::StringValue(mValue))); + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionDeleteConfigurationProperty::execute() + { + sh::Configuration* c = sh::Factory::getInstance().getConfiguration(mName); + c->deleteProperty(mKey); + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionSetMaterialProperty::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + m->setProperty(mKey, sh::makeProperty(mValue)); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionDeleteMaterialProperty::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + m->deleteProperty(mKey); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionCreatePass::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + m->createPass(); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionDeletePass::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + m->deletePass(mPassIndex); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionSetPassProperty::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + m->getPasses()->at(mPassIndex).setProperty (mKey, sh::makeProperty(mValue)); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionDeletePassProperty::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + m->getPasses()->at(mPassIndex).deleteProperty(mKey); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionSetShaderProperty::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + m->getPasses()->at(mPassIndex).mShaderProperties.setProperty (mKey, sh::makeProperty(mValue)); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionDeleteShaderProperty::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + m->getPasses()->at(mPassIndex).mShaderProperties.deleteProperty (mKey); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionSetTextureProperty::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); + m->getPasses()->at(mPassIndex).mTexUnits.at(mTextureIndex).setProperty(mKey, sh::makeProperty(mValue)); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionDeleteTextureProperty::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); + m->getPasses()->at(mPassIndex).mTexUnits.at(mTextureIndex).deleteProperty(mKey); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionCreateTextureUnit::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + m->getPasses()->at(mPassIndex).createTextureUnit(mTexUnitName); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionDeleteTextureUnit::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); + + m->getPasses()->at(mPassIndex).mTexUnits.erase(m->getPasses()->at(mPassIndex).mTexUnits.begin() + mTextureIndex); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionMoveTextureUnit::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); + if (!mMoveUp) + assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex+1); + + std::vector textures = m->getPasses()->at(mPassIndex).mTexUnits; + if (mMoveUp) + std::swap(textures[mTextureIndex-1], textures[mTextureIndex]); + else + std::swap(textures[mTextureIndex+1], textures[mTextureIndex]); + m->getPasses()->at(mPassIndex).mTexUnits = textures; + + sh::Factory::getInstance().notifyConfigurationChanged(); + } + + void ActionChangeTextureUnitName::execute() + { + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + assert (m->getPasses()->size() > mPassIndex); + assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); + + m->getPasses()->at(mPassIndex).mTexUnits[mTextureIndex].setName(mTexUnitName); + + sh::Factory::getInstance().notifyConfigurationChanged(); + } +} diff --git a/extern/shiny/Editor/Actions.hpp b/extern/shiny/Editor/Actions.hpp new file mode 100644 index 0000000000..e5cb6df6a5 --- /dev/null +++ b/extern/shiny/Editor/Actions.hpp @@ -0,0 +1,307 @@ +#ifndef SH_ACTIONS_H +#define SH_ACTIONS_H + +#include + +namespace sh +{ + + class Action + { + public: + virtual void execute() = 0; + virtual ~Action(); + }; + + class ActionDeleteMaterial : public Action + { + public: + ActionDeleteMaterial(const std::string& name) + : mName(name) {} + + virtual void execute(); + private: + std::string mName; + }; + + class ActionCloneMaterial : public Action + { + public: + ActionCloneMaterial(const std::string& sourceName, const std::string& destName) + : mSourceName(sourceName), mDestName(destName) {} + + virtual void execute(); + private: + std::string mSourceName; + std::string mDestName; + }; + + class ActionSaveAll : public Action + { + public: + virtual void execute(); + }; + + class ActionChangeGlobalSetting : public Action + { + public: + ActionChangeGlobalSetting(const std::string& name, const std::string& newValue) + : mName(name), mNewValue(newValue) {} + + virtual void execute(); + private: + std::string mName; + std::string mNewValue; + }; + + // configuration + + class ActionCreateConfiguration : public Action + { + public: + ActionCreateConfiguration(const std::string& name) + : mName(name) {} + + virtual void execute(); + private: + std::string mName; + + }; + + class ActionDeleteConfiguration : public Action + { + public: + ActionDeleteConfiguration(const std::string& name) + : mName(name) {} + + virtual void execute(); + private: + std::string mName; + + }; + + class ActionChangeConfiguration : public Action + { + public: + ActionChangeConfiguration (const std::string& name, const std::string& key, const std::string& value) + : mName(name), mKey(key), mValue(value) {} + + virtual void execute(); + private: + std::string mName; + std::string mKey; + std::string mValue; + }; + + class ActionDeleteConfigurationProperty : public Action + { + public: + ActionDeleteConfigurationProperty (const std::string& name, const std::string& key) + : mName(name), mKey(key) {} + + virtual void execute(); + private: + std::string mName; + std::string mKey; + }; + + // material + + class ActionSetMaterialProperty : public Action + { + public: + ActionSetMaterialProperty (const std::string& name, const std::string& key, const std::string& value) + : mName(name), mKey(key), mValue(value) {} + + virtual void execute(); + private: + std::string mName; + std::string mKey; + std::string mValue; + }; + + class ActionDeleteMaterialProperty : public Action + { + public: + ActionDeleteMaterialProperty (const std::string& name, const std::string& key) + : mName(name), mKey(key) {} + + virtual void execute(); + private: + std::string mName; + std::string mKey; + }; + + // pass + + class ActionCreatePass : public Action + { + public: + ActionCreatePass (const std::string& name) + : mName(name) {} + + virtual void execute(); + private: + std::string mName; + }; + + class ActionDeletePass : public Action + { + public: + ActionDeletePass (const std::string& name, int passIndex) + : mName(name), mPassIndex(passIndex) {} + + virtual void execute(); + private: + std::string mName; + int mPassIndex; + }; + + class ActionSetPassProperty : public Action + { + public: + ActionSetPassProperty (const std::string& name, int passIndex, const std::string& key, const std::string& value) + : mName(name), mPassIndex(passIndex), mKey(key), mValue(value) {} + + virtual void execute(); + private: + std::string mName; + int mPassIndex; + std::string mKey; + std::string mValue; + }; + + class ActionDeletePassProperty : public Action + { + public: + ActionDeletePassProperty (const std::string& name, int passIndex, const std::string& key) + : mName(name), mPassIndex(passIndex), mKey(key) {} + + virtual void execute(); + private: + std::string mName; + int mPassIndex; + std::string mKey; + }; + + // shader + + class ActionSetShaderProperty : public Action + { + public: + ActionSetShaderProperty (const std::string& name, int passIndex, const std::string& key, const std::string& value) + : mName(name), mPassIndex(passIndex), mKey(key), mValue(value) {} + + virtual void execute(); + private: + std::string mName; + int mPassIndex; + std::string mKey; + std::string mValue; + }; + + class ActionDeleteShaderProperty : public Action + { + public: + ActionDeleteShaderProperty (const std::string& name, int passIndex, const std::string& key) + : mName(name), mPassIndex(passIndex), mKey(key) {} + + virtual void execute(); + private: + std::string mName; + int mPassIndex; + std::string mKey; + }; + + // texture unit + + class ActionChangeTextureUnitName : public Action + { + public: + ActionChangeTextureUnitName (const std::string& name, int passIndex, int textureIndex, const std::string& texUnitName) + : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mTexUnitName(texUnitName) {} + + virtual void execute(); + + private: + std::string mName; + int mPassIndex; + int mTextureIndex; + std::string mTexUnitName; + }; + + class ActionCreateTextureUnit : public Action + { + public: + ActionCreateTextureUnit (const std::string& name, int passIndex, const std::string& texUnitName) + : mName(name), mPassIndex(passIndex), mTexUnitName(texUnitName) {} + + virtual void execute(); + + private: + std::string mName; + int mPassIndex; + std::string mTexUnitName; + }; + + class ActionDeleteTextureUnit : public Action + { + public: + ActionDeleteTextureUnit (const std::string& name, int passIndex, int textureIndex) + : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex) {} + + virtual void execute(); + + private: + std::string mName; + int mPassIndex; + int mTextureIndex; + }; + + class ActionMoveTextureUnit : public Action + { + public: + ActionMoveTextureUnit (const std::string& name, int passIndex, int textureIndex, bool moveUp) + : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mMoveUp(moveUp) {} + + virtual void execute(); + + private: + std::string mName; + int mPassIndex; + int mTextureIndex; + bool mMoveUp; + }; + + class ActionSetTextureProperty : public Action + { + public: + ActionSetTextureProperty (const std::string& name, int passIndex, int textureIndex, const std::string& key, const std::string& value) + : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mKey(key), mValue(value) {} + + virtual void execute(); + private: + std::string mName; + int mPassIndex; + int mTextureIndex; + std::string mKey; + std::string mValue; + }; + + class ActionDeleteTextureProperty : public Action + { + public: + ActionDeleteTextureProperty (const std::string& name, int passIndex, int textureIndex, const std::string& key) + : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mKey(key) {} + + virtual void execute(); + private: + std::string mName; + int mPassIndex; + int mTextureIndex; + std::string mKey; + }; + +} + +#endif diff --git a/extern/shiny/Editor/AddPropertyDialog.cpp b/extern/shiny/Editor/AddPropertyDialog.cpp new file mode 100644 index 0000000000..71b47feb19 --- /dev/null +++ b/extern/shiny/Editor/AddPropertyDialog.cpp @@ -0,0 +1,31 @@ +#include "AddPropertyDialog.hpp" +#include "ui_addpropertydialog.h" + +AddPropertyDialog::AddPropertyDialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::AddPropertyDialog) + , mType(0) +{ + ui->setupUi(this); + + connect(ui->buttonBox, SIGNAL(accepted()), + this, SLOT(accepted())); + connect(ui->buttonBox, SIGNAL(rejected()), + this, SLOT(rejected())); +} + +void AddPropertyDialog::accepted() +{ + mName = ui->lineEdit->text(); + mType = ui->comboBox->currentIndex(); +} + +void AddPropertyDialog::rejected() +{ + mName = ""; +} + +AddPropertyDialog::~AddPropertyDialog() +{ + delete ui; +} diff --git a/extern/shiny/Editor/AddPropertyDialog.h b/extern/shiny/Editor/AddPropertyDialog.h new file mode 100644 index 0000000000..c1d2c960b5 --- /dev/null +++ b/extern/shiny/Editor/AddPropertyDialog.h @@ -0,0 +1,22 @@ +#ifndef ADDPROPERTYDIALOG_H +#define ADDPROPERTYDIALOG_H + +#include + +namespace Ui { +class AddPropertyDialog; +} + +class AddPropertyDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AddPropertyDialog(QWidget *parent = 0); + ~AddPropertyDialog(); + +private: + Ui::AddPropertyDialog *ui; +}; + +#endif // ADDPROPERTYDIALOG_H diff --git a/extern/shiny/Editor/AddPropertyDialog.hpp b/extern/shiny/Editor/AddPropertyDialog.hpp new file mode 100644 index 0000000000..b4e19b0870 --- /dev/null +++ b/extern/shiny/Editor/AddPropertyDialog.hpp @@ -0,0 +1,29 @@ +#ifndef ADDPROPERTYDIALOG_H +#define ADDPROPERTYDIALOG_H + +#include + +namespace Ui { +class AddPropertyDialog; +} + +class AddPropertyDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AddPropertyDialog(QWidget *parent = 0); + ~AddPropertyDialog(); + + int mType; + QString mName; + +public slots: + void accepted(); + void rejected(); + +private: + Ui::AddPropertyDialog *ui; +}; + +#endif // ADDPROPERTYDIALOG_H diff --git a/extern/shiny/Editor/CMakeLists.txt b/extern/shiny/Editor/CMakeLists.txt new file mode 100644 index 0000000000..eead159f08 --- /dev/null +++ b/extern/shiny/Editor/CMakeLists.txt @@ -0,0 +1,61 @@ +set(SHINY_EDITOR_LIBRARY "shiny.Editor") + +find_package(Qt4) + +if (QT_FOUND) + + add_definitions(-DSHINY_BUILD_MATERIAL_EDITOR=1) + set (SHINY_BUILD_EDITOR_FLAG -DSHINY_BUILD_MATERIAL_EDITOR=1 PARENT_SCOPE) + + set(QT_USE_QTGUI 1) + + # Headers that must be preprocessed + set(SHINY_EDITOR_HEADER_MOC + MainWindow.hpp + NewMaterialDialog.hpp + AddPropertyDialog.hpp + PropertySortModel.hpp + ) + + set(SHINY_EDITOR_UI + mainwindow.ui + newmaterialdialog.ui + addpropertydialog.ui + ) + + QT4_WRAP_CPP(MOC_SRCS ${SHINY_EDITOR_HEADER_MOC}) + QT4_WRAP_UI(UI_HDRS ${SHINY_EDITOR_UI}) + + set(SOURCE_FILES + NewMaterialDialog.cpp + AddPropertyDialog.cpp + ColoredTabWidget.hpp + MainWindow.cpp + Editor.cpp + Actions.cpp + Query.cpp + PropertySortModel.cpp + ${SHINY_EDITOR_UI} # Just to have them in the IDE's file explorer + ) + + include(${QT_USE_FILE}) + + set (CMAKE_INCLUDE_CURRENT_DIR "true") + + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + add_library(${SHINY_EDITOR_LIBRARY} STATIC ${SOURCE_FILES} ${MOC_SRCS} ${UI_HDRS}) + + set(SHINY_LIBRARIES ${SHINY_LIBRARIES} + ${SHINY_EDITOR_LIBRARY} + ${QT_LIBRARIES} + ) + set(SHINY_LIBRARIES ${SHINY_LIBRARIES} PARENT_SCOPE) + +else (QT_FOUND) + + add_definitions(-DSHINY_BUILD_MATERIAL_EDITOR=0) + set (SHINY_BUILD_EDITOR_FLAG -DSHINY_BUILD_MATERIAL_EDITOR=0 PARENT_SCOPE) + message(STATUS "QT4 was not found. You will not be able to use the material editor.") + +endif(QT_FOUND) diff --git a/extern/shiny/Editor/ColoredTabWidget.hpp b/extern/shiny/Editor/ColoredTabWidget.hpp new file mode 100644 index 0000000000..0bf30f6dda --- /dev/null +++ b/extern/shiny/Editor/ColoredTabWidget.hpp @@ -0,0 +1,24 @@ +#ifndef SHINY_EDITOR_COLOREDTABWIDGET_H +#define SHINY_EDITOR_COLOREDTABWIDGET_H + +#include + +namespace sh +{ + +/// Makes tabBar() public to allow changing tab title colors. +class ColoredTabWidget : public QTabWidget +{ +public: + ColoredTabWidget(QWidget* parent = 0) + : QTabWidget(parent) {} + + QTabBar* tabBar() + { + return QTabWidget::tabBar(); + } +}; + +} + +#endif diff --git a/extern/shiny/Editor/Editor.cpp b/extern/shiny/Editor/Editor.cpp new file mode 100644 index 0000000000..8c58d0e66d --- /dev/null +++ b/extern/shiny/Editor/Editor.cpp @@ -0,0 +1,117 @@ +#include "Editor.hpp" + + +#include +#include + +#include + +#include "../Main/Factory.hpp" + +#include "MainWindow.hpp" + +namespace sh +{ + + Editor::Editor() + : mMainWindow(NULL) + , mApplication(NULL) + , mInitialized(false) + , mThread(NULL) + { + } + + Editor::~Editor() + { + if (mMainWindow) + mMainWindow->mRequestExit = true; + + if (mThread) + mThread->join(); + delete mThread; + } + + void Editor::show() + { + if (!mInitialized) + { + mInitialized = true; + + mThread = new boost::thread(boost::bind(&Editor::runThread, this)); + } + else + { + if (mMainWindow) + mMainWindow->mRequestShowWindow = true; + } + } + + void Editor::runThread() + { + int argc = 0; + char** argv = NULL; + mApplication = new QApplication(argc, argv); + mApplication->setQuitOnLastWindowClosed(false); + mMainWindow = new MainWindow(); + mMainWindow->mSync = &mSync; + mMainWindow->show(); + + mApplication->exec(); + + delete mApplication; + } + + void Editor::update() + { + sh::Factory::getInstance().doMonitorShaderFiles(); + + if (!mMainWindow) + return; + + + { + boost::mutex::scoped_lock lock(mSync.mActionMutex); + + // execute pending actions + while (mMainWindow->mActionQueue.size()) + { + Action* action = mMainWindow->mActionQueue.front(); + action->execute(); + delete action; + mMainWindow->mActionQueue.pop(); + } + } + { + boost::mutex::scoped_lock lock(mSync.mQueryMutex); + + // execute pending queries + for (std::vector::iterator it = mMainWindow->mQueries.begin(); it != mMainWindow->mQueries.end(); ++it) + { + Query* query = *it; + if (!query->mDone) + query->execute(); + } + } + + boost::mutex::scoped_lock lock2(mSync.mUpdateMutex); + + // update the list of materials + mMainWindow->mState.mMaterialList.clear(); + sh::Factory::getInstance().listMaterials(mMainWindow->mState.mMaterialList); + + // update global settings + mMainWindow->mState.mGlobalSettingsMap.clear(); + sh::Factory::getInstance().listGlobalSettings(mMainWindow->mState.mGlobalSettingsMap); + + // update configuration list + mMainWindow->mState.mConfigurationList.clear(); + sh::Factory::getInstance().listConfigurationNames(mMainWindow->mState.mConfigurationList); + + // update shader list + mMainWindow->mState.mShaderSets.clear(); + sh::Factory::getInstance().listShaderSets(mMainWindow->mState.mShaderSets); + + mMainWindow->mState.mErrors += sh::Factory::getInstance().getErrorLog(); + } + +} diff --git a/extern/shiny/Editor/Editor.hpp b/extern/shiny/Editor/Editor.hpp new file mode 100644 index 0000000000..2b1e8040d0 --- /dev/null +++ b/extern/shiny/Editor/Editor.hpp @@ -0,0 +1,73 @@ +#ifndef SH_EDITOR_H +#define SH_EDITOR_H + +#if SHINY_BUILD_MATERIAL_EDITOR +class QApplication; + +#include +#include + +namespace boost +{ + class thread; +} + +namespace sh +{ + class MainWindow; + + struct SynchronizationState + { + boost::mutex mUpdateMutex; + boost::mutex mActionMutex; + boost::mutex mQueryMutex; + }; + + class Editor + { + public: + + Editor(); + ~Editor(); + + void show(); + void update(); + + + private: + bool mInitialized; + + MainWindow* mMainWindow; + QApplication* mApplication; + + SynchronizationState mSync; + + boost::thread* mThread; + + void runThread(); + + void processShowWindow(); + }; + +} + +#else + +// Dummy implementation, so that the user's code does not have to be polluted with #ifdefs +namespace sh +{ + + class Editor + { + public: + Editor() {} + ~Editor() {} + void show() {} + void update() {} + + }; +} + +#endif + +#endif diff --git a/extern/shiny/Editor/MainWindow.cpp b/extern/shiny/Editor/MainWindow.cpp new file mode 100644 index 0000000000..1d46719cea --- /dev/null +++ b/extern/shiny/Editor/MainWindow.cpp @@ -0,0 +1,950 @@ +#include "MainWindow.hpp" +#include "ui_mainwindow.h" + +#include +#include + +#include +#include + +#include "Editor.hpp" +#include "ColoredTabWidget.hpp" +#include "AddPropertyDialog.hpp" + +sh::MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , mRequestShowWindow(false) + , mRequestExit(false) + , mIgnoreGlobalSettingChange(false) + , mIgnoreConfigurationChange(false) + , mIgnoreMaterialChange(false) + , mIgnoreMaterialPropertyChange(false) +{ + ui->setupUi(this); + + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(onIdle())); + timer->start(50); + + QList sizes; + sizes << 250; + sizes << 550; + ui->splitter->setSizes(sizes); + + mMaterialModel = new QStringListModel(this); + + mMaterialProxyModel = new QSortFilterProxyModel(this); + mMaterialProxyModel->setSourceModel(mMaterialModel); + mMaterialProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + mMaterialProxyModel->setDynamicSortFilter(true); + mMaterialProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + + ui->materialList->setModel(mMaterialProxyModel); + ui->materialList->setSelectionMode(QAbstractItemView::SingleSelection); + ui->materialList->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->materialList->setAlternatingRowColors(true); + + connect(ui->materialList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(onMaterialSelectionChanged(QModelIndex,QModelIndex))); + + mMaterialPropertyModel = new QStandardItemModel(0, 2, this); + mMaterialPropertyModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name"))); + mMaterialPropertyModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value"))); + connect(mMaterialPropertyModel, SIGNAL(itemChanged(QStandardItem*)), + this, SLOT(onMaterialPropertyChanged(QStandardItem*))); + + mMaterialSortModel = new PropertySortModel(this); + mMaterialSortModel->setSourceModel(mMaterialPropertyModel); + mMaterialSortModel->setDynamicSortFilter(true); + mMaterialSortModel->setSortCaseSensitivity(Qt::CaseInsensitive); + + ui->materialView->setModel(mMaterialSortModel); + ui->materialView->setContextMenuPolicy(Qt::CustomContextMenu); + ui->materialView->setAlternatingRowColors(true); + ui->materialView->setSortingEnabled(true); + connect(ui->materialView, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(onContextMenuRequested(QPoint))); + + mGlobalSettingsModel = new QStandardItemModel(0, 2, this); + mGlobalSettingsModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name"))); + mGlobalSettingsModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value"))); + connect(mGlobalSettingsModel, SIGNAL(itemChanged(QStandardItem*)), + this, SLOT(onGlobalSettingChanged(QStandardItem*))); + + ui->globalSettingsView->setModel(mGlobalSettingsModel); + ui->globalSettingsView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents); + ui->globalSettingsView->verticalHeader()->hide(); + ui->globalSettingsView->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + ui->globalSettingsView->setSelectionMode(QAbstractItemView::SingleSelection); + + ui->configurationList->setSelectionMode(QAbstractItemView::SingleSelection); + ui->configurationList->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(ui->configurationList, SIGNAL(currentTextChanged(QString)), + this, SLOT(onConfigurationSelectionChanged(QString))); + + mConfigurationModel = new QStandardItemModel(0, 2, this); + mConfigurationModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name"))); + mConfigurationModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value"))); + connect(mConfigurationModel, SIGNAL(itemChanged(QStandardItem*)), + this, SLOT(onConfigurationChanged(QStandardItem*))); + + ui->configurationView->setModel(mConfigurationModel); + ui->configurationView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents); + ui->configurationView->verticalHeader()->hide(); + ui->configurationView->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + ui->configurationView->setSelectionMode(QAbstractItemView::SingleSelection); +} + +sh::MainWindow::~MainWindow() +{ + delete ui; +} + +void sh::MainWindow::closeEvent(QCloseEvent *event) +{ + this->hide(); + event->ignore(); +} + +void sh::MainWindow::onIdle() +{ + if (mRequestShowWindow) + { + mRequestShowWindow = false; + show(); + } + + if (mRequestExit) + { + QApplication::exit(); + return; + } + + boost::mutex::scoped_lock lock(mSync->mUpdateMutex); + + + mIgnoreMaterialChange = true; + QString selected; + + QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex(); + if (selectedIndex.isValid()) + selected = mMaterialModel->data(selectedIndex, Qt::DisplayRole).toString(); + + QStringList list; + + for (std::vector::const_iterator it = mState.mMaterialList.begin(); it != mState.mMaterialList.end(); ++it) + { + list.push_back(QString::fromStdString(*it)); + } + + if (mMaterialModel->stringList() != list) + { + mMaterialModel->setStringList(list); + + // quick hack to keep our selection when the model has changed + if (!selected.isEmpty()) + for (int i=0; irowCount(); ++i) + { + const QModelIndex& index = mMaterialModel->index(i,0); + if (mMaterialModel->data(index, Qt::DisplayRole).toString() == selected) + { + ui->materialList->setCurrentIndex(index); + break; + } + } + } + mIgnoreMaterialChange = false; + + mIgnoreGlobalSettingChange = true; + for (std::map::const_iterator it = mState.mGlobalSettingsMap.begin(); + it != mState.mGlobalSettingsMap.end(); ++it) + { + QList list = mGlobalSettingsModel->findItems(QString::fromStdString(it->first)); + if (!list.empty()) // item was already there + { + // if it changed, set the value column + if (mGlobalSettingsModel->data(mGlobalSettingsModel->index(list.front()->row(), 1)).toString() + != QString::fromStdString(it->second)) + { + mGlobalSettingsModel->setItem(list.front()->row(), 1, new QStandardItem(QString::fromStdString(it->second))); + } + } + else // item wasn't there; insert new row + { + QList toAdd; + QStandardItem* name = new QStandardItem(QString::fromStdString(it->first)); + name->setFlags(name->flags() &= ~Qt::ItemIsEditable); + QStandardItem* value = new QStandardItem(QString::fromStdString(it->second)); + toAdd.push_back(name); + toAdd.push_back(value); + mGlobalSettingsModel->appendRow(toAdd); + } + } + mIgnoreGlobalSettingChange = false; + + + mIgnoreConfigurationChange = true; + QList selected_ = ui->configurationList->selectedItems(); + QString selectedStr; + if (selected_.size()) + selectedStr = selected_.front()->text(); + + ui->configurationList->clear(); + + for (std::vector::const_iterator it = mState.mConfigurationList.begin(); it != mState.mConfigurationList.end(); ++it) + ui->configurationList->addItem(QString::fromStdString(*it)); + + if (!selectedStr.isEmpty()) + for (int i=0; iconfigurationList->count(); ++i) + { + if (ui->configurationList->item(i)->text() == selectedStr) + { + ui->configurationList->setCurrentItem(ui->configurationList->item(i), QItemSelectionModel::ClearAndSelect); + } + } + + mIgnoreConfigurationChange = false; + + if (!mState.mErrors.empty()) + { + ui->errorLog->append(QString::fromStdString(mState.mErrors)); + mState.mErrors = ""; + QColor color = ui->tabWidget->palette().color(QPalette::Normal, QPalette::Link); + ui->tabWidget->tabBar()->setTabTextColor(3, color); + } + + + // process query results + boost::mutex::scoped_lock lock2(mSync->mQueryMutex); + for (std::vector::iterator it = mQueries.begin(); it != mQueries.end();) + { + if ((*it)->mDone) + { + if (typeid(**it) == typeid(ConfigurationQuery)) + buildConfigurationModel(static_cast(*it)); + else if (typeid(**it) == typeid(MaterialQuery)) + buildMaterialModel(static_cast(*it)); + else if (typeid(**it) == typeid(MaterialPropertyQuery)) + { + MaterialPropertyQuery* q = static_cast(*it); + mIgnoreMaterialPropertyChange = true; + if (getSelectedMaterial().toStdString() == q->mName) + { + for (int i=0; irowCount(); ++i) + { + if (mMaterialPropertyModel->item(i,0)->text() == QString::fromStdString(q->mPropertyName)) + { + mMaterialPropertyModel->item(i,1)->setText(QString::fromStdString(q->mValue)); + if (mMaterialPropertyModel->item(i,1)->isCheckable()) + mMaterialPropertyModel->item(i,1)->setCheckState ((q->mValue == "true") + ? Qt::Checked : Qt::Unchecked); + } + } + } + mIgnoreMaterialPropertyChange = false; + } + + delete *it; + it = mQueries.erase(it); + } + else + ++it; + } +} + +void sh::MainWindow::onMaterialSelectionChanged (const QModelIndex & current, const QModelIndex & previous) +{ + if (mIgnoreMaterialChange) + return; + + QString name = getSelectedMaterial(); + if (!name.isEmpty()) + requestQuery(new sh::MaterialQuery(name.toStdString())); +} + +QString sh::MainWindow::getSelectedMaterial() +{ + QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex(); + if (!selectedIndex.isValid()) + return QString(""); + + return mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString(); +} + +void sh::MainWindow::onConfigurationSelectionChanged (const QString& current) +{ + if (mIgnoreConfigurationChange) + return; + requestQuery(new sh::ConfigurationQuery(current.toStdString())); +} + +void sh::MainWindow::onGlobalSettingChanged(QStandardItem *item) +{ + if (mIgnoreGlobalSettingChange) + return; // we are only interested in changes by the user, not by the backend. + + std::string name = mGlobalSettingsModel->data(mGlobalSettingsModel->index(item->row(), 0)).toString().toStdString(); + std::string value = mGlobalSettingsModel->data(mGlobalSettingsModel->index(item->row(), 1)).toString().toStdString(); + + queueAction(new sh::ActionChangeGlobalSetting(name, value)); +} + +void sh::MainWindow::onConfigurationChanged (QStandardItem* item) +{ + QList items = ui->configurationList->selectedItems(); + if (items.size()) + { + std::string name = items.front()->text().toStdString(); + std::string key = mConfigurationModel->data(mConfigurationModel->index(item->row(), 0)).toString().toStdString(); + std::string value = mConfigurationModel->data(mConfigurationModel->index(item->row(), 1)).toString().toStdString(); + + queueAction(new sh::ActionChangeConfiguration(name, key, value)); + + requestQuery(new sh::ConfigurationQuery(name)); + } +} + +void sh::MainWindow::on_lineEdit_textEdited(const QString &arg1) +{ + mMaterialProxyModel->setFilterFixedString(arg1); +} + +void sh::MainWindow::on_actionSave_triggered() +{ + queueAction (new sh::ActionSaveAll()); +} + +void sh::MainWindow::on_actionNewMaterial_triggered() +{ + +} + +void sh::MainWindow::on_actionDeleteMaterial_triggered() +{ + QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex(); + QString name = mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString(); + + queueAction (new sh::ActionDeleteMaterial(name.toStdString())); +} + +void sh::MainWindow::queueAction(Action* action) +{ + boost::mutex::scoped_lock lock(mSync->mActionMutex); + mActionQueue.push(action); +} + +void sh::MainWindow::requestQuery(Query *query) +{ + boost::mutex::scoped_lock lock(mSync->mActionMutex); + mQueries.push_back(query); +} + +void sh::MainWindow::on_actionQuit_triggered() +{ + hide(); +} + +void sh::MainWindow::on_actionNewConfiguration_triggered() +{ + QInputDialog dialog(this); + + QString text = QInputDialog::getText(this, tr("New Configuration"), + tr("Configuration name:")); + + if (!text.isEmpty()) + { + queueAction(new ActionCreateConfiguration(text.toStdString())); + } +} + +void sh::MainWindow::on_actionDeleteConfiguration_triggered() +{ + QList items = ui->configurationList->selectedItems(); + if (items.size()) + queueAction(new ActionDeleteConfiguration(items.front()->text().toStdString())); +} + +void sh::MainWindow::on_actionDeleteConfigurationProperty_triggered() +{ + QList items = ui->configurationList->selectedItems(); + if (items.empty()) + return; + std::string configurationName = items.front()->text().toStdString(); + + QModelIndex current = ui->configurationView->currentIndex(); + if (!current.isValid()) + return; + + std::string propertyName = mConfigurationModel->data(mConfigurationModel->index(current.row(), 0)).toString().toStdString(); + + queueAction(new sh::ActionDeleteConfigurationProperty(configurationName, propertyName)); + requestQuery(new sh::ConfigurationQuery(configurationName)); +} + +void sh::MainWindow::on_actionCloneMaterial_triggered() +{ + QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex(); + QString name = mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString(); + if (name.isEmpty()) + return; + + QInputDialog dialog(this); + + QString text = QInputDialog::getText(this, tr("Clone material"), + tr("Name:")); + + if (!text.isEmpty()) + { + queueAction(new ActionCloneMaterial(name.toStdString(), text.toStdString())); + } +} + +void sh::MainWindow::onContextMenuRequested(const QPoint &point) +{ + QPoint globalPos = ui->materialView->viewport()->mapToGlobal(point); + + QMenu menu; + + QList actions; + actions.push_back(ui->actionNewProperty); + actions.push_back(ui->actionDeleteProperty); + actions.push_back(ui->actionCreatePass); + actions.push_back(ui->actionCreateTextureUnit); + menu.addActions(actions); + + menu.exec(globalPos); +} + +void sh::MainWindow::getContext(QModelIndex index, int* passIndex, int* textureIndex, bool* isInPass, bool* isInTextureUnit) +{ + if (passIndex) + { + *passIndex = 0; + if (isInPass) + *isInPass = false; + QModelIndex passModelIndex = index; + // go up until we find the pass item. + while (getPropertyKey(passModelIndex) != "pass" && passModelIndex.isValid()) + passModelIndex = passModelIndex.parent(); + + if (passModelIndex.isValid()) + { + if (passModelIndex.column() != 0) + passModelIndex = passModelIndex.parent().child(passModelIndex.row(), 0); + for (int i=0; irowCount(); ++i) + { + if (mMaterialPropertyModel->data(mMaterialPropertyModel->index(i, 0)).toString() == QString("pass")) + { + if (mMaterialPropertyModel->index(i, 0) == passModelIndex) + { + if (isInPass) + *isInPass = true; + break; + } + ++(*passIndex); + } + } + } + } + if (textureIndex) + { + *textureIndex = 0; + if (isInTextureUnit) + *isInTextureUnit = false; + QModelIndex texModelIndex = index; + // go up until we find the texture_unit item. + while (getPropertyKey(texModelIndex) != "texture_unit" && texModelIndex.isValid()) + texModelIndex = texModelIndex.parent(); + if (texModelIndex.isValid()) + { + if (texModelIndex.column() != 0) + texModelIndex = texModelIndex.parent().child(texModelIndex.row(), 0); + for (int i=0; irowCount(texModelIndex.parent()); ++i) + { + if (texModelIndex.parent().child(i, 0).data().toString() == QString("texture_unit")) + { + if (texModelIndex.parent().child(i, 0) == texModelIndex) + { + if (isInTextureUnit) + *isInTextureUnit = true; + break; + } + ++(*textureIndex); + } + } + } + } +} + +std::string sh::MainWindow::getPropertyKey(QModelIndex index) +{ + if (!index.parent().isValid()) + return mMaterialPropertyModel->data(mMaterialPropertyModel->index(index.row(), 0)).toString().toStdString(); + else + return index.parent().child(index.row(), 0).data().toString().toStdString(); +} + +std::string sh::MainWindow::getPropertyValue(QModelIndex index) +{ + if (!index.parent().isValid()) + return mMaterialPropertyModel->data(mMaterialPropertyModel->index(index.row(), 1)).toString().toStdString(); + else + return index.parent().child(index.row(), 1).data().toString().toStdString(); +} + +void sh::MainWindow::onMaterialPropertyChanged(QStandardItem *item) +{ + if (mIgnoreMaterialPropertyChange) + return; + + QString material = getSelectedMaterial(); + if (material.isEmpty()) + return; + + // handle checkboxes being checked/unchecked + std::string value = getPropertyValue(item->index()); + if (item->data(Qt::UserRole).toInt() == MaterialProperty::Boolean) + { + if (item->checkState() == Qt::Checked && value != "true") + value = "true"; + else if (item->checkState() == Qt::Unchecked && value == "true") + value = "false"; + item->setText(QString::fromStdString(value)); + } + + // handle inherited properties being changed, i.e. overridden by the current (derived) material + if (item->data(Qt::UserRole+1).toInt() == MaterialProperty::Inherited_Unchanged) + { + QColor normalColor = ui->materialView->palette().color(QPalette::Normal, QPalette::WindowText); + mIgnoreMaterialPropertyChange = true; + mMaterialPropertyModel->item(item->index().row(), 0) + ->setData(QVariant(MaterialProperty::Inherited_Changed), Qt::UserRole+1); + mMaterialPropertyModel->item(item->index().row(), 0) + ->setData(normalColor, Qt::ForegroundRole); + mMaterialPropertyModel->item(item->index().row(), 1) + ->setData(QVariant(MaterialProperty::Inherited_Changed), Qt::UserRole+1); + mMaterialPropertyModel->item(item->index().row(), 1) + ->setData(normalColor, Qt::ForegroundRole); + mIgnoreMaterialPropertyChange = false; + + ui->materialView->scrollTo(mMaterialSortModel->mapFromSource(item->index())); + } + + if (!item->index().parent().isValid()) + { + // top level material property + queueAction(new ActionSetMaterialProperty( + material.toStdString(), getPropertyKey(item->index()), value)); + } + else if (getPropertyKey(item->index()) == "texture_unit") + { + // texture unit name changed + int passIndex, textureIndex; + getContext(item->index(), &passIndex, &textureIndex); + std::cout << "passIndex " << passIndex << " " << textureIndex << std::endl; + + queueAction(new ActionChangeTextureUnitName( + material.toStdString(), passIndex, textureIndex, value)); + + } + else if (item->index().parent().data().toString() == "pass") + { + // pass property + int passIndex; + getContext(item->index(), &passIndex, NULL); + /// \todo if shaders are changed, check that the material provides all properties needed by the shader + queueAction(new ActionSetPassProperty( + material.toStdString(), passIndex, getPropertyKey(item->index()), value)); + } + else if (item->index().parent().data().toString() == "shader_properties") + { + // shader property + int passIndex; + getContext(item->index(), &passIndex, NULL); + queueAction(new ActionSetShaderProperty( + material.toStdString(), passIndex, getPropertyKey(item->index()), value)); + } + else if (item->index().parent().data().toString() == "texture_unit") + { + // texture property + int passIndex, textureIndex; + getContext(item->index(), &passIndex, &textureIndex); + queueAction(new ActionSetTextureProperty( + material.toStdString(), passIndex, textureIndex, getPropertyKey(item->index()), value)); + } +} + +void sh::MainWindow::buildMaterialModel(MaterialQuery *data) +{ + mMaterialPropertyModel->clear(); + + mMaterialPropertyModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name"))); + mMaterialPropertyModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value"))); + + for (std::map::const_iterator it = data->mProperties.begin(); + it != data->mProperties.end(); ++it) + { + addProperty(mMaterialPropertyModel->invisibleRootItem(), it->first, it->second); + } + + for (std::vector::iterator it = data->mPasses.begin(); + it != data->mPasses.end(); ++it) + { + QStandardItem* passItem = new QStandardItem (QString("pass")); + passItem->setFlags(passItem->flags() &= ~Qt::ItemIsEditable); + passItem->setData(QVariant(static_cast(MaterialProperty::Object)), Qt::UserRole); + + if (it->mShaderProperties.size()) + { + QStandardItem* shaderPropertiesItem = new QStandardItem (QString("shader_properties")); + shaderPropertiesItem->setFlags(shaderPropertiesItem->flags() &= ~Qt::ItemIsEditable); + shaderPropertiesItem->setData(QVariant(static_cast(MaterialProperty::Object)), Qt::UserRole); + + for (std::map::iterator pit = it->mShaderProperties.begin(); + pit != it->mShaderProperties.end(); ++pit) + { + addProperty(shaderPropertiesItem, pit->first, pit->second); + } + passItem->appendRow(shaderPropertiesItem); + } + + for (std::map::iterator pit = it->mProperties.begin(); + pit != it->mProperties.end(); ++pit) + { + addProperty(passItem, pit->first, pit->second); + } + + for (std::vector::iterator tIt = it->mTextureUnits.begin(); + tIt != it->mTextureUnits.end(); ++tIt) + { + QStandardItem* unitItem = new QStandardItem (QString("texture_unit")); + unitItem->setFlags(unitItem->flags() &= ~Qt::ItemIsEditable); + unitItem->setData(QVariant(static_cast(MaterialProperty::Object)), Qt::UserRole); + QStandardItem* nameItem = new QStandardItem (QString::fromStdString(tIt->mName)); + nameItem->setData(QVariant(static_cast(MaterialProperty::Object)), Qt::UserRole); + + QList texUnit; + texUnit << unitItem << nameItem; + + for (std::map::iterator pit = tIt->mProperties.begin(); + pit != tIt->mProperties.end(); ++pit) + { + addProperty(unitItem, pit->first, pit->second); + } + + passItem->appendRow(texUnit); + } + + QList toAdd; + toAdd << passItem; + toAdd << new QStandardItem(QString("")); + mMaterialPropertyModel->appendRow(toAdd); + } + + ui->materialView->expandAll(); + ui->materialView->resizeColumnToContents(0); + ui->materialView->resizeColumnToContents(1); +} + +void sh::MainWindow::addProperty(QStandardItem *parent, const std::string &key, MaterialProperty value, bool scrollTo) +{ + QList toAdd; + QStandardItem* keyItem = new QStandardItem(QString::fromStdString(key)); + keyItem->setFlags(keyItem->flags() &= ~Qt::ItemIsEditable); + keyItem->setData(QVariant(value.mType), Qt::UserRole); + keyItem->setData(QVariant(value.mSource), Qt::UserRole+1); + toAdd.push_back(keyItem); + + QStandardItem* valueItem = NULL; + if (value.mSource != MaterialProperty::None) + { + valueItem = new QStandardItem(QString::fromStdString(value.mValue)); + valueItem->setData(QVariant(value.mType), Qt::UserRole); + valueItem->setData(QVariant(value.mSource), Qt::UserRole+1); + toAdd.push_back(valueItem); + } + + + if (value.mSource == MaterialProperty::Inherited_Unchanged) + { + QColor color = ui->configurationView->palette().color(QPalette::Disabled, QPalette::WindowText); + keyItem->setData(color, Qt::ForegroundRole); + if (valueItem) + valueItem->setData(color, Qt::ForegroundRole); + } + if (value.mType == MaterialProperty::Boolean && valueItem) + { + valueItem->setCheckable(true); + valueItem->setCheckState((value.mValue == "true") ? Qt::Checked : Qt::Unchecked); + } + + parent->appendRow(toAdd); + + if (scrollTo) + ui->materialView->scrollTo(mMaterialSortModel->mapFromSource(keyItem->index())); +} + +void sh::MainWindow::buildConfigurationModel(ConfigurationQuery *data) +{ + while (mConfigurationModel->rowCount()) + mConfigurationModel->removeRow(0); + for (std::map::iterator it = data->mProperties.begin(); + it != data->mProperties.end(); ++it) + { + QList toAdd; + QStandardItem* name = new QStandardItem(QString::fromStdString(it->first)); + name->setFlags(name->flags() &= ~Qt::ItemIsEditable); + QStandardItem* value = new QStandardItem(QString::fromStdString(it->second)); + toAdd.push_back(name); + toAdd.push_back(value); + mConfigurationModel->appendRow(toAdd); + } + + // add items that are in global settings, but not in this configuration (with a "inactive" color) + for (std::map::const_iterator it = mState.mGlobalSettingsMap.begin(); + it != mState.mGlobalSettingsMap.end(); ++it) + { + if (data->mProperties.find(it->first) == data->mProperties.end()) + { + QColor color = ui->configurationView->palette().color(QPalette::Disabled, QPalette::WindowText); + QList toAdd; + QStandardItem* name = new QStandardItem(QString::fromStdString(it->first)); + name->setFlags(name->flags() &= ~Qt::ItemIsEditable); + name->setData(color, Qt::ForegroundRole); + QStandardItem* value = new QStandardItem(QString::fromStdString(it->second)); + value->setData(color, Qt::ForegroundRole); + toAdd.push_back(name); + toAdd.push_back(value); + mConfigurationModel->appendRow(toAdd); + } + } +} + +void sh::MainWindow::on_actionCreatePass_triggered() +{ + QString material = getSelectedMaterial(); + if (!material.isEmpty()) + { + addProperty(mMaterialPropertyModel->invisibleRootItem(), + "pass", MaterialProperty("", MaterialProperty::Object, MaterialProperty::None), true); + + queueAction (new ActionCreatePass(material.toStdString())); + } +} + +void sh::MainWindow::on_actionDeleteProperty_triggered() +{ + QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex()); + QString material = getSelectedMaterial(); + if (material.isEmpty()) + return; + + mIgnoreMaterialPropertyChange = true; + + if (getPropertyKey(selectedIndex) == "pass") + { + // delete whole pass + int passIndex; + getContext(selectedIndex, &passIndex, NULL); + if (passIndex == 0) + { + QMessageBox msgBox; + msgBox.setText("The first pass can not be deleted."); + msgBox.exec(); + } + else + { + queueAction(new ActionDeletePass(material.toStdString(), passIndex)); + mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); + } + } + else if (getPropertyKey(selectedIndex) == "texture_unit") + { + // delete whole texture unit + int passIndex, textureIndex; + getContext(selectedIndex, &passIndex, &textureIndex); + queueAction(new ActionDeleteTextureUnit(material.toStdString(), passIndex, textureIndex)); + mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); + } + else if (!selectedIndex.parent().isValid()) + { + // top level material property + MaterialProperty::Source source = static_cast( + mMaterialPropertyModel->itemFromIndex(selectedIndex)->data(Qt::UserRole+1).toInt()); + if (source == MaterialProperty::Inherited_Unchanged) + { + QMessageBox msgBox; + msgBox.setText("Inherited properties can not be deleted."); + msgBox.exec(); + } + else + { + queueAction(new ActionDeleteMaterialProperty( + material.toStdString(), getPropertyKey(selectedIndex))); + std::cout << "source is " << source << std::endl; + if (source == MaterialProperty::Inherited_Changed) + { + QColor inactiveColor = ui->materialView->palette().color(QPalette::Disabled, QPalette::WindowText); + mMaterialPropertyModel->item(selectedIndex.row(), 0) + ->setData(QVariant(MaterialProperty::Inherited_Unchanged), Qt::UserRole+1); + mMaterialPropertyModel->item(selectedIndex.row(), 0) + ->setData(inactiveColor, Qt::ForegroundRole); + mMaterialPropertyModel->item(selectedIndex.row(), 1) + ->setData(QVariant(MaterialProperty::Inherited_Unchanged), Qt::UserRole+1); + mMaterialPropertyModel->item(selectedIndex.row(), 1) + ->setData(inactiveColor, Qt::ForegroundRole); + + // make sure to update the property's value + requestQuery(new sh::MaterialPropertyQuery(material.toStdString(), getPropertyKey(selectedIndex))); + } + else + mMaterialPropertyModel->removeRow(selectedIndex.row()); + } + } + else if (selectedIndex.parent().data().toString() == "pass") + { + // pass property + int passIndex; + getContext(selectedIndex, &passIndex, NULL); + queueAction(new ActionDeletePassProperty( + material.toStdString(), passIndex, getPropertyKey(selectedIndex))); + mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); + } + else if (selectedIndex.parent().data().toString() == "shader_properties") + { + // shader property + int passIndex; + getContext(selectedIndex, &passIndex, NULL); + queueAction(new ActionDeleteShaderProperty( + material.toStdString(), passIndex, getPropertyKey(selectedIndex))); + mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); + } + else if (selectedIndex.parent().data().toString() == "texture_unit") + { + // texture property + int passIndex, textureIndex; + getContext(selectedIndex, &passIndex, &textureIndex); + queueAction(new ActionDeleteTextureProperty( + material.toStdString(), passIndex, textureIndex, getPropertyKey(selectedIndex))); + mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); + } + mIgnoreMaterialPropertyChange = false; +} + +void sh::MainWindow::on_actionNewProperty_triggered() +{ + QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex()); + QString material = getSelectedMaterial(); + if (material.isEmpty()) + return; + + AddPropertyDialog* dialog = new AddPropertyDialog(this); + dialog->exec(); + QString propertyName = dialog->mName; + QString defaultValue = ""; + + /// \todo check if this property name exists already + + if (!propertyName.isEmpty()) + { + int passIndex, textureIndex; + bool isInPass, isInTextureUnit; + getContext(selectedIndex, &passIndex, &textureIndex, &isInPass, &isInTextureUnit); + + QList items; + QStandardItem* keyItem = new QStandardItem(propertyName); + keyItem->setFlags(keyItem->flags() &= ~Qt::ItemIsEditable); + items << keyItem; + items << new QStandardItem(defaultValue); + + // figure out which item the new property should be a child of + QModelIndex parentIndex = selectedIndex; + if (selectedIndex.data(Qt::UserRole) != MaterialProperty::Object) + parentIndex = selectedIndex.parent(); + QStandardItem* parentItem; + if (!parentIndex.isValid()) + parentItem = mMaterialPropertyModel->invisibleRootItem(); + else + parentItem = mMaterialPropertyModel->itemFromIndex(parentIndex); + + if (isInTextureUnit) + { + queueAction(new ActionSetTextureProperty( + material.toStdString(), passIndex, textureIndex, propertyName.toStdString(), defaultValue.toStdString())); + } + else if (isInPass) + { + if (selectedIndex.parent().child(selectedIndex.row(),0).data().toString() == "shader_properties" + || selectedIndex.parent().data().toString() == "shader_properties") + { + queueAction(new ActionSetShaderProperty( + material.toStdString(), passIndex, propertyName.toStdString(), defaultValue.toStdString())); + } + else + { + queueAction(new ActionSetPassProperty( + material.toStdString(), passIndex, propertyName.toStdString(), defaultValue.toStdString())); + } + } + else + { + queueAction(new ActionSetMaterialProperty( + material.toStdString(), propertyName.toStdString(), defaultValue.toStdString())); + } + + addProperty(parentItem, propertyName.toStdString(), + MaterialProperty (defaultValue.toStdString(), MaterialProperty::Misc, MaterialProperty::Normal), true); + + /// \todo scroll to newly added property + } +} + +void sh::MainWindow::on_actionCreateTextureUnit_triggered() +{ + QString material = getSelectedMaterial(); + if (material.isEmpty()) + return; + + QInputDialog dialog(this); + + QString text = QInputDialog::getText(this, tr("New texture unit"), + tr("Texture unit name (for referencing in shaders):")); + if (!text.isEmpty()) + { + QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex()); + int passIndex; + getContext(selectedIndex, &passIndex, NULL); + queueAction(new ActionCreateTextureUnit(material.toStdString(), passIndex, text.toStdString())); + + // add to model + int index = 0; + for (int i=0; irowCount(); ++i) + { + if (mMaterialPropertyModel->data(mMaterialPropertyModel->index(i, 0)).toString() == QString("pass")) + { + if (index == passIndex) + { + addProperty(mMaterialPropertyModel->itemFromIndex(mMaterialPropertyModel->index(i, 0)), + "texture_unit", MaterialProperty(text.toStdString(), MaterialProperty::Object), true); + break; + } + + ++index; + } + } + } +} + +void sh::MainWindow::on_clearButton_clicked() +{ + ui->errorLog->clear(); +} + +void sh::MainWindow::on_tabWidget_currentChanged(int index) +{ + QColor color = ui->tabWidget->palette().color(QPalette::Normal, QPalette::WindowText); + + if (index == 3) + ui->tabWidget->tabBar()->setTabTextColor(3, color); +} diff --git a/extern/shiny/Editor/MainWindow.hpp b/extern/shiny/Editor/MainWindow.hpp new file mode 100644 index 0000000000..3f0dc295c0 --- /dev/null +++ b/extern/shiny/Editor/MainWindow.hpp @@ -0,0 +1,139 @@ +#ifndef SHINY_EDITOR_MAINWINDOW_HPP +#define SHINY_EDITOR_MAINWINDOW_HPP + +#include + +#include +#include +#include + +#include + +#include "Actions.hpp" +#include "Query.hpp" + +#include "PropertySortModel.hpp" + +namespace Ui { +class MainWindow; +} + +namespace sh +{ + +struct SynchronizationState; + + +/** + * @brief A snapshot of the material system's state. Lock the mUpdateMutex before accessing. + */ +struct MaterialSystemState +{ + std::vector mMaterialList; + + std::map mGlobalSettingsMap; + + std::vector mConfigurationList; + + std::vector mMaterialFiles; + std::vector mConfigurationFiles; + + std::vector mShaderSets; + + std::string mErrors; +}; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + + // really should be an std::atomic + volatile bool mRequestShowWindow; + // dito + volatile bool mRequestExit; + + SynchronizationState* mSync; + + /// \todo Is there a better way to ignore manual model changes? + bool mIgnoreGlobalSettingChange; + bool mIgnoreConfigurationChange; + bool mIgnoreMaterialChange; + bool mIgnoreMaterialPropertyChange; + + std::queue mActionQueue; + std::vector mQueries; + + MaterialSystemState mState; + +private: + Ui::MainWindow *ui; + + // material tab + QStringListModel* mMaterialModel; + QSortFilterProxyModel* mMaterialProxyModel; + + QStandardItemModel* mMaterialPropertyModel; + PropertySortModel* mMaterialSortModel; + + // global settings tab + QStandardItemModel* mGlobalSettingsModel; + + // configuration tab + QStandardItemModel* mConfigurationModel; + + void queueAction(Action* action); + void requestQuery(Query* query); + + void buildMaterialModel (MaterialQuery* data); + void buildConfigurationModel (ConfigurationQuery* data); + + QString getSelectedMaterial(); + + /// get the context of an index in the material property model + void getContext(QModelIndex index, int* passIndex, int* textureIndex, bool* isInPass=NULL, bool* isInTextureUnit=NULL); + + std::string getPropertyKey(QModelIndex index); + std::string getPropertyValue(QModelIndex index); + + void addProperty (QStandardItem* parent, const std::string& key, MaterialProperty value, bool scrollTo=false); + +protected: + void closeEvent(QCloseEvent *event); + +public slots: + void onIdle(); + + void onMaterialSelectionChanged (const QModelIndex & current, const QModelIndex & previous); + void onConfigurationSelectionChanged (const QString& current); + + void onGlobalSettingChanged (QStandardItem* item); + void onConfigurationChanged (QStandardItem* item); + void onMaterialPropertyChanged (QStandardItem* item); + + void onContextMenuRequested(const QPoint& point); + +private slots: + void on_lineEdit_textEdited(const QString &arg1); + void on_actionSave_triggered(); + void on_actionNewMaterial_triggered(); + void on_actionDeleteMaterial_triggered(); + void on_actionQuit_triggered(); + void on_actionNewConfiguration_triggered(); + void on_actionDeleteConfiguration_triggered(); + void on_actionDeleteConfigurationProperty_triggered(); + void on_actionCloneMaterial_triggered(); + void on_actionCreatePass_triggered(); + void on_actionDeleteProperty_triggered(); + void on_actionNewProperty_triggered(); + void on_actionCreateTextureUnit_triggered(); + void on_clearButton_clicked(); + void on_tabWidget_currentChanged(int index); +}; + +} + +#endif // MAINWINDOW_HPP diff --git a/extern/shiny/Editor/NewMaterialDialog.cpp b/extern/shiny/Editor/NewMaterialDialog.cpp new file mode 100644 index 0000000000..f1a716a9f0 --- /dev/null +++ b/extern/shiny/Editor/NewMaterialDialog.cpp @@ -0,0 +1,14 @@ +#include "NewMaterialDialog.hpp" +#include "ui_newmaterialdialog.h" + +NewMaterialDialog::NewMaterialDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewMaterialDialog) +{ + ui->setupUi(this); +} + +NewMaterialDialog::~NewMaterialDialog() +{ + delete ui; +} diff --git a/extern/shiny/Editor/NewMaterialDialog.hpp b/extern/shiny/Editor/NewMaterialDialog.hpp new file mode 100644 index 0000000000..2a20bbb395 --- /dev/null +++ b/extern/shiny/Editor/NewMaterialDialog.hpp @@ -0,0 +1,22 @@ +#ifndef NEWMATERIALDIALOG_HPP +#define NEWMATERIALDIALOG_HPP + +#include + +namespace Ui { +class NewMaterialDialog; +} + +class NewMaterialDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewMaterialDialog(QWidget *parent = 0); + ~NewMaterialDialog(); + +private: + Ui::NewMaterialDialog *ui; +}; + +#endif // NEWMATERIALDIALOG_HPP diff --git a/extern/shiny/Editor/PropertySortModel.cpp b/extern/shiny/Editor/PropertySortModel.cpp new file mode 100644 index 0000000000..637fe11b02 --- /dev/null +++ b/extern/shiny/Editor/PropertySortModel.cpp @@ -0,0 +1,35 @@ +#include "PropertySortModel.hpp" + +#include "Query.hpp" + +#include +sh::PropertySortModel::PropertySortModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +bool sh::PropertySortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + if (left.data(Qt::UserRole+1).toInt() != 0 && right.data(Qt::UserRole+1).toInt() != 0) + { + int sourceL = left.data(Qt::UserRole+1).toInt(); + int sourceR = right.data(Qt::UserRole+1).toInt(); + + if (sourceL > sourceR) + return true; + else if (sourceR > sourceL) + return false; + } + + int typeL = left.data(Qt::UserRole).toInt(); + int typeR = right.data(Qt::UserRole).toInt(); + + if (typeL > typeR) + return true; + else if (typeR > typeL) + return false; + + QString nameL = left.data().toString(); + QString nameR = right.data().toString(); + return nameL > nameR; +} diff --git a/extern/shiny/Editor/PropertySortModel.hpp b/extern/shiny/Editor/PropertySortModel.hpp new file mode 100644 index 0000000000..f96927764e --- /dev/null +++ b/extern/shiny/Editor/PropertySortModel.hpp @@ -0,0 +1,21 @@ +#ifndef SHINY_EDITOR_PROPERTYSORTMODEL_H +#define SHINY_EDITOR_PROPERTYSORTMODEL_H + +#include + +namespace sh +{ + + class PropertySortModel : public QSortFilterProxyModel + { + Q_OBJECT + + public: + PropertySortModel(QObject* parent); + protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + }; + +} + +#endif diff --git a/extern/shiny/Editor/Query.cpp b/extern/shiny/Editor/Query.cpp new file mode 100644 index 0000000000..ccf07b62bc --- /dev/null +++ b/extern/shiny/Editor/Query.cpp @@ -0,0 +1,134 @@ +#include "Query.hpp" + +#include "../Main/Factory.hpp" + +namespace sh +{ + +void Query::execute() +{ + executeImpl(); + mDone = true; +} + +ConfigurationQuery::ConfigurationQuery(const std::string &name) + : mName(name) +{ +} + +void ConfigurationQuery::executeImpl() +{ + sh::Factory::getInstance().listConfigurationSettings(mName, mProperties); +} + +void MaterialQuery::executeImpl() +{ + sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(mName); + + if (instance->getParent()) + mParent = static_cast(instance->getParent())->getName(); + + // add the inherited properties + sh::PropertySetGet* parent = instance; + std::vector inheritedPropertiesVector; + while (parent->getParent()) + { + parent = parent->getParent(); + const sh::PropertyMap& parentProperties = parent->listProperties(); + + for (PropertyMap::const_iterator it = parentProperties.begin(); it != parentProperties.end(); ++it) + { + MaterialProperty::Source source = MaterialProperty::Inherited_Unchanged; + MaterialProperty::Type type = getType(it->first, parent->getProperty(it->first)); + mProperties[it->first] = MaterialProperty ( + retrieveValue(parent->getProperty(it->first), NULL).get(), + type, source); + inheritedPropertiesVector.push_back(it->first); + } + } + + // add our properties + const sh::PropertyMap& ourProperties = instance->listProperties(); + for (PropertyMap::const_iterator it = ourProperties.begin(); it != ourProperties.end(); ++it) + { + MaterialProperty::Source source = + (std::find(inheritedPropertiesVector.begin(), inheritedPropertiesVector.end(), it->first) + != inheritedPropertiesVector.end()) ? + MaterialProperty::Inherited_Changed : MaterialProperty::Normal; + MaterialProperty::Type type = getType(it->first, instance->getProperty(it->first)); + mProperties[it->first] = MaterialProperty ( + retrieveValue(instance->getProperty(it->first), NULL).get(), + type, source); + } + + std::vector* passes = instance->getPasses(); + for (std::vector::iterator it = passes->begin(); it != passes->end(); ++it) + { + mPasses.push_back(PassInfo()); + + const sh::PropertyMap& passProperties = it->listProperties(); + for (PropertyMap::const_iterator pit = passProperties.begin(); pit != passProperties.end(); ++pit) + { + PropertyValuePtr property = it->getProperty(pit->first); + MaterialProperty::Type type = getType(pit->first, property); + if (typeid(*property).name() == typeid(sh::LinkedValue).name()) + mPasses.back().mProperties[pit->first] = MaterialProperty("$" + property->_getStringValue(), type); + else + mPasses.back().mProperties[pit->first] = MaterialProperty( + retrieveValue(property, NULL).get(), type); + } + + const sh::PropertyMap& shaderProperties = it->mShaderProperties.listProperties(); + for (PropertyMap::const_iterator pit = shaderProperties.begin(); pit != shaderProperties.end(); ++pit) + { + PropertyValuePtr property = it->mShaderProperties.getProperty(pit->first); + MaterialProperty::Type type = getType(pit->first, property); + if (typeid(*property).name() == typeid(sh::LinkedValue).name()) + mPasses.back().mShaderProperties[pit->first] = MaterialProperty("$" + property->_getStringValue(), type); + else + mPasses.back().mShaderProperties[pit->first] = MaterialProperty( + retrieveValue(property, NULL).get(), type); + } + + std::vector* texUnits = &it->mTexUnits; + for (std::vector::iterator tIt = texUnits->begin(); tIt != texUnits->end(); ++tIt) + { + mPasses.back().mTextureUnits.push_back(TextureUnitInfo()); + mPasses.back().mTextureUnits.back().mName = tIt->getName(); + const sh::PropertyMap& unitProperties = tIt->listProperties(); + for (PropertyMap::const_iterator pit = unitProperties.begin(); pit != unitProperties.end(); ++pit) + { + PropertyValuePtr property = tIt->getProperty(pit->first); + MaterialProperty::Type type = getType(pit->first, property); + if (typeid(*property).name() == typeid(sh::LinkedValue).name()) + mPasses.back().mTextureUnits.back().mProperties[pit->first] = MaterialProperty( + "$" + property->_getStringValue(), MaterialProperty::Linked); + else + mPasses.back().mTextureUnits.back().mProperties[pit->first] = MaterialProperty( + retrieveValue(property, NULL).get(), type); + } + } + } +} + +MaterialProperty::Type MaterialQuery::getType(const std::string &key, PropertyValuePtr value) +{ + if (typeid(*value).name() == typeid(sh::LinkedValue).name()) + return MaterialProperty::Linked; + + if (key == "vertex_program" || key == "fragment_program") + return MaterialProperty::Shader; + + std::string valueStr = retrieveValue(value, NULL).get(); + + if (valueStr == "false" || valueStr == "true") + return MaterialProperty::Boolean; +} + +void MaterialPropertyQuery::executeImpl() +{ + sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); + mValue = retrieveValue(m->getProperty(mPropertyName), m).get(); +} + +} diff --git a/extern/shiny/Editor/Query.hpp b/extern/shiny/Editor/Query.hpp new file mode 100644 index 0000000000..7aec684880 --- /dev/null +++ b/extern/shiny/Editor/Query.hpp @@ -0,0 +1,121 @@ +#ifndef SH_QUERY_H +#define SH_QUERY_H + +#include +#include +#include + +#include "../Main/PropertyBase.hpp" + +namespace sh +{ + +class Query +{ +public: + Query() + : mDone(false) {} + virtual ~Query(); + + void execute(); + + bool mDone; + +protected: + virtual void executeImpl() = 0; +}; + +class ConfigurationQuery : public Query +{ +public: + ConfigurationQuery(const std::string& name); + + std::map mProperties; +protected: + std::string mName; + virtual void executeImpl(); +}; + + +struct MaterialProperty +{ + + enum Type + { + Texture, + Color, + Boolean, + Shader, + Misc, + Linked, + Object // child object, i.e. pass, texture unit, shader properties + }; + + enum Source + { + Normal, + Inherited_Changed, + Inherited_Unchanged, + None // there is no property source (e.g. a pass, which does not have a name) + }; + + MaterialProperty() {} + MaterialProperty (const std::string& value, Type type, Source source=Normal) + : mValue(value), mType(type), mSource(source) {} + + std::string mValue; + Type mType; + Source mSource; +}; + + +struct TextureUnitInfo +{ + std::string mName; + std::map mProperties; +}; + +struct PassInfo +{ + std::map mShaderProperties; + + std::map mProperties; + std::vector mTextureUnits; +}; + +class MaterialQuery : public Query +{ +public: + MaterialQuery(const std::string& name) + : mName(name) {} + + std::string mParent; + std::vector mPasses; + std::map mProperties; + +protected: + std::string mName; + virtual void executeImpl(); + + MaterialProperty::Type getType (const std::string& key, PropertyValuePtr value); +}; + +class MaterialPropertyQuery : public Query +{ +public: + MaterialPropertyQuery(const std::string& name, const std::string& propertyName) + : mName(name), mPropertyName(propertyName) + { + } + + std::string mValue; + + std::string mName; + std::string mPropertyName; +protected: + virtual void executeImpl(); +}; + +} + +#endif diff --git a/extern/shiny/Editor/addpropertydialog.ui b/extern/shiny/Editor/addpropertydialog.ui new file mode 100644 index 0000000000..63de7d1415 --- /dev/null +++ b/extern/shiny/Editor/addpropertydialog.ui @@ -0,0 +1,118 @@ + + + AddPropertyDialog + + + + 0 + 0 + 257 + 133 + + + + Dialog + + + + + + + + + + + + + Property name + + + + + + + Editing widget + + + + + + + + Checkbox + + + + + Shader + + + + + Color + + + + + Texture + + + + + Other + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + AddPropertyDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AddPropertyDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/extern/shiny/Editor/mainwindow.ui b/extern/shiny/Editor/mainwindow.ui new file mode 100644 index 0000000000..b27c8357de --- /dev/null +++ b/extern/shiny/Editor/mainwindow.ui @@ -0,0 +1,420 @@ + + + MainWindow + + + + 0 + 0 + 647 + 512 + + + + MainWindow + + + + + + + 0 + + + + + + + Materials + + + + + + Qt::Horizontal + + + + + + + + + + Search + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 16 + 16 + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + 1 + 0 + + + + + 16 + 16 + + + + Qt::ToolButtonIconOnly + + + + + + + + + + + + + + Global settings + + + + + + + + + + Configurations + + + + + + Qt::Horizontal + + + + + + + + + + + 16 + 16 + + + + + + + + + + + + + + + + + + 16 + 16 + + + + + + + + + + + + + + Errors + + + + + + + + true + + + + + + + + 0 + 0 + + + + Clear + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 647 + 25 + + + + false + + + + File + + + + + + + Material + + + + + + + + + History + + + + + + + + + + + + + + Quit + + + + + + + + + + Save + + + Save all + + + + + + + + + + Delete + + + Delete selected material + + + + + Change parent... + + + + + + + + + + New + + + Create a new material + + + + + + + + + + Clone + + + Clone selected material + + + + + + + + + + Delete + + + Delete selected configuration + + + Del + + + + + + + + + + New + + + Create a new configuration + + + + + + + + + + Delete + + + Delete property + + + + + + + + + + Delete + + + Delete item + + + + + + + + + + New property + + + + + + + + + + Create pass + + + + + + + + + + Create texture unit + + + + + + sh::ColoredTabWidget + QTabWidget +

ColoredTabWidget.hpp
+ 1 + + + + + diff --git a/extern/shiny/Editor/newmaterialdialog.ui b/extern/shiny/Editor/newmaterialdialog.ui new file mode 100644 index 0000000000..f24561cf79 --- /dev/null +++ b/extern/shiny/Editor/newmaterialdialog.ui @@ -0,0 +1,98 @@ + + + NewMaterialDialog + + + + 0 + 0 + 385 + 198 + + + + Dialog + + + + + + + + Name + + + + + + + Parent material + + + + + + + + + + + + + File + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + NewMaterialDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NewMaterialDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/extern/shiny/Main/Factory.cpp b/extern/shiny/Main/Factory.cpp index 40c695fd4a..780d5eece3 100644 --- a/extern/shiny/Main/Factory.cpp +++ b/extern/shiny/Main/Factory.cpp @@ -51,8 +51,6 @@ namespace sh { assert(mCurrentLanguage != Language_None); - bool removeBinaryCache = false; - if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt")) { std::ifstream file; @@ -86,8 +84,9 @@ namespace sh break; } - PropertySetGet newConfiguration; + Configuration newConfiguration; newConfiguration.setParent(&mGlobalSettings); + newConfiguration.setSourceFile (it->second->mFileName); std::vector props = it->second->getChildren(); for (std::vector::const_iterator propIt = props.begin(); propIt != props.end(); ++propIt) @@ -137,82 +136,7 @@ namespace sh } // load shader sets - { - ScriptLoader shaderSetLoader(".shaderset"); - ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath()); - std::map nodes = shaderSetLoader.getAllConfigScripts(); - for (std::map ::const_iterator it = nodes.begin(); - it != nodes.end(); ++it) - { - if (!(it->second->getName() == "shader_set")) - { - std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .shaderset" << std::endl; - break; - } - - if (!it->second->findChild("profiles_cg")) - throw std::runtime_error ("missing \"profiles_cg\" field for \"" + it->first + "\""); - if (!it->second->findChild("profiles_hlsl")) - throw std::runtime_error ("missing \"profiles_hlsl\" field for \"" + it->first + "\""); - if (!it->second->findChild("source")) - throw std::runtime_error ("missing \"source\" field for \"" + it->first + "\""); - if (!it->second->findChild("type")) - throw std::runtime_error ("missing \"type\" field for \"" + it->first + "\""); - - std::vector profiles_cg; - boost::split (profiles_cg, it->second->findChild("profiles_cg")->getValue(), boost::is_any_of(" ")); - std::string cg_profile; - for (std::vector::iterator it2 = profiles_cg.begin(); it2 != profiles_cg.end(); ++it2) - { - if (mPlatform->isProfileSupported(*it2)) - { - cg_profile = *it2; - break; - } - } - - std::vector profiles_hlsl; - boost::split (profiles_hlsl, it->second->findChild("profiles_hlsl")->getValue(), boost::is_any_of(" ")); - std::string hlsl_profile; - for (std::vector::iterator it2 = profiles_hlsl.begin(); it2 != profiles_hlsl.end(); ++it2) - { - if (mPlatform->isProfileSupported(*it2)) - { - hlsl_profile = *it2; - break; - } - } - - std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue(); - std::string sourceRelative = it->second->findChild("source")->getValue(); - - ShaderSet newSet (it->second->findChild("type")->getValue(), cg_profile, hlsl_profile, - sourceAbsolute, - mPlatform->getBasePath(), - it->first, - &mGlobalSettings); - - int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute)); - mShadersLastModifiedNew[sourceRelative] = lastModified; - if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end()) - { - if (mShadersLastModified[sourceRelative] != lastModified) - { - // delete any outdated shaders based on this shader set - if (removeCache (it->first)) - removeBinaryCache = true; - } - } - else - { - // if we get here, this is either the first run or a new shader file was added - // in both cases we can safely delete - if (removeCache (it->first)) - removeBinaryCache = true; - } - mShaderSets.insert(std::make_pair(it->first, newSet)); - } - } + bool removeBinaryCache = reloadShaders(); // load materials { @@ -315,6 +239,8 @@ namespace sh Factory::~Factory () { + mShaderSets.clear(); + if (mPlatform->supportsShaderSerialization () && mWriteMicrocodeCache) { std::string file = mPlatform->getCacheFolder () + "/" + mBinaryCacheName; @@ -367,15 +293,16 @@ namespace sh while (i>0) { --i; - m->createForConfiguration (configuration, i); - - if (mListener) + if (m->createForConfiguration (configuration, i) && mListener) mListener->materialCreated (m, configuration, i); + else + return NULL; } - m->createForConfiguration (configuration, lodIndex); - if (mListener) + if (m->createForConfiguration (configuration, lodIndex) && mListener) mListener->materialCreated (m, configuration, lodIndex); + else + return NULL; } return m; } @@ -439,6 +366,12 @@ namespace sh ShaderSet* Factory::getShaderSet (const std::string& name) { + if (mShaderSets.find(name) == mShaderSets.end()) + { + std::stringstream msg; + msg << "Shader '" << name << "' not found"; + throw std::runtime_error(msg.str()); + } return &mShaderSets.find(name)->second; } @@ -466,6 +399,14 @@ namespace sh } } + void Factory::notifyConfigurationChanged() + { + for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) + { + it->second.destroyAll(); + } + } + MaterialInstance* Factory::getMaterialInstance (const std::string& name) { return findInstance(name); @@ -493,17 +434,21 @@ namespace sh return ""; } - PropertySetGet* Factory::getConfiguration (const std::string& name) + Configuration* Factory::getConfiguration (const std::string& name) { return &mConfigurations[name]; } - void Factory::registerConfiguration (const std::string& name, PropertySetGet configuration) + void Factory::createConfiguration (const std::string& name) { - mConfigurations[name] = configuration; mConfigurations[name].setParent (&mGlobalSettings); } + void Factory::destroyConfiguration(const std::string &name) + { + mConfigurations.erase(name); + } + void Factory::registerLodConfiguration (int index, PropertySetGet configuration) { mLodConfigurations[index] = configuration; @@ -571,17 +516,93 @@ namespace sh return p; } - void Factory::saveMaterials (const std::string& filename) + void Factory::saveAll () { - std::ofstream file; - file.open (filename.c_str ()); - + std::map files; for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) { - it->second.save(file); + if (it->second.getSourceFile().empty()) + continue; + if (files.find(it->second.getSourceFile()) == files.end()) + { + /// \todo check if this is actually the same file, since there can be different paths to the same file + std::ofstream* stream = new std::ofstream(); + stream->open (it->second.getSourceFile().c_str()); + + files[it->second.getSourceFile()] = stream; + } + it->second.save (*files[it->second.getSourceFile()]); } - file.close(); + for (std::map::iterator it = files.begin(); it != files.end(); ++it) + { + delete it->second; + } + files.clear(); + + for (ConfigurationMap::iterator it = mConfigurations.begin(); it != mConfigurations.end(); ++it) + { + if (it->second.getSourceFile().empty()) + continue; + if (files.find(it->second.getSourceFile()) == files.end()) + { + /// \todo check if this is actually the same file, since there can be different paths to the same file + std::ofstream* stream = new std::ofstream(); + stream->open (it->second.getSourceFile().c_str()); + + files[it->second.getSourceFile()] = stream; + } + it->second.save (it->first, *files[it->second.getSourceFile()]); + } + + for (std::map::iterator it = files.begin(); it != files.end(); ++it) + { + delete it->second; + } + } + + void Factory::listMaterials(std::vector &out) + { + for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) + { + out.push_back(it->first); + } + } + + void Factory::listGlobalSettings(std::map &out) + { + const PropertyMap& properties = mGlobalSettings.listProperties(); + + for (PropertyMap::const_iterator it = properties.begin(); it != properties.end(); ++it) + { + out[it->first] = retrieveValue(mGlobalSettings.getProperty(it->first), NULL).get(); + } + } + + void Factory::listConfigurationSettings(const std::string& name, std::map &out) + { + const PropertyMap& properties = mConfigurations[name].listProperties(); + + for (PropertyMap::const_iterator it = properties.begin(); it != properties.end(); ++it) + { + out[it->first] = retrieveValue(mConfigurations[name].getProperty(it->first), NULL).get(); + } + } + + void Factory::listConfigurationNames(std::vector &out) + { + for (ConfigurationMap::const_iterator it = mConfigurations.begin(); it != mConfigurations.end(); ++it) + { + out.push_back(it->first); + } + } + + void Factory::listShaderSets(std::vector &out) + { + for (ShaderSetMap::const_iterator it = mShaderSets.begin(); it != mShaderSets.end(); ++it) + { + out.push_back(it->first); + } } void Factory::_ensureMaterial(const std::string& name, const std::string& configuration) @@ -630,4 +651,146 @@ namespace sh } return ret; } + + bool Factory::reloadShaders() + { + mShaderSets.clear(); + notifyConfigurationChanged(); + + bool removeBinaryCache = false; + ScriptLoader shaderSetLoader(".shaderset"); + ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath()); + std::map nodes = shaderSetLoader.getAllConfigScripts(); + for (std::map ::const_iterator it = nodes.begin(); + it != nodes.end(); ++it) + { + if (!(it->second->getName() == "shader_set")) + { + std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .shaderset" << std::endl; + break; + } + + if (!it->second->findChild("profiles_cg")) + throw std::runtime_error ("missing \"profiles_cg\" field for \"" + it->first + "\""); + if (!it->second->findChild("profiles_hlsl")) + throw std::runtime_error ("missing \"profiles_hlsl\" field for \"" + it->first + "\""); + if (!it->second->findChild("source")) + throw std::runtime_error ("missing \"source\" field for \"" + it->first + "\""); + if (!it->second->findChild("type")) + throw std::runtime_error ("missing \"type\" field for \"" + it->first + "\""); + + std::vector profiles_cg; + boost::split (profiles_cg, it->second->findChild("profiles_cg")->getValue(), boost::is_any_of(" ")); + std::string cg_profile; + for (std::vector::iterator it2 = profiles_cg.begin(); it2 != profiles_cg.end(); ++it2) + { + if (mPlatform->isProfileSupported(*it2)) + { + cg_profile = *it2; + break; + } + } + + std::vector profiles_hlsl; + boost::split (profiles_hlsl, it->second->findChild("profiles_hlsl")->getValue(), boost::is_any_of(" ")); + std::string hlsl_profile; + for (std::vector::iterator it2 = profiles_hlsl.begin(); it2 != profiles_hlsl.end(); ++it2) + { + if (mPlatform->isProfileSupported(*it2)) + { + hlsl_profile = *it2; + break; + } + } + + std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue(); + std::string sourceRelative = it->second->findChild("source")->getValue(); + + ShaderSet newSet (it->second->findChild("type")->getValue(), cg_profile, hlsl_profile, + sourceAbsolute, + mPlatform->getBasePath(), + it->first, + &mGlobalSettings); + + int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute)); + mShadersLastModifiedNew[sourceRelative] = lastModified; + if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end()) + { + if (mShadersLastModified[sourceRelative] != lastModified) + { + // delete any outdated shaders based on this shader set + if (removeCache (it->first)) + removeBinaryCache = true; + mShadersLastModified[sourceRelative] = lastModified; + } + } + else + { + // if we get here, this is either the first run or a new shader file was added + // in both cases we can safely delete + if (removeCache (it->first)) + removeBinaryCache = true; + mShadersLastModified[sourceRelative] = lastModified; + } + mShaderSets.insert(std::make_pair(it->first, newSet)); + } + + return removeBinaryCache; + } + + void Factory::doMonitorShaderFiles() + { + bool reload=false; + ScriptLoader shaderSetLoader(".shaderset"); + ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath()); + std::map nodes = shaderSetLoader.getAllConfigScripts(); + for (std::map ::const_iterator it = nodes.begin(); + it != nodes.end(); ++it) + { + + std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue(); + std::string sourceRelative = it->second->findChild("source")->getValue(); + + int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute)); + if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end()) + { + if (mShadersLastModified[sourceRelative] != lastModified) + { + reload=true; + break; + } + } + } + if (reload) + reloadShaders(); + } + + void Factory::logError(const std::string &msg) + { + mErrorLog << msg << '\n'; + } + + std::string Factory::getErrorLog() + { + std::string errors = mErrorLog.str(); + mErrorLog.str(""); + return errors; + } + + void Factory::unloadUnreferencedMaterials() + { + for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) + { + if (it->second.getMaterial()->isUnreferenced()) + it->second.destroyAll(); + } + } + + void Configuration::save(const std::string& name, std::ofstream &stream) + { + stream << "configuration " << name << '\n'; + stream << "{\n"; + PropertySetGet::save(stream, "\t"); + stream << "}\n"; + } } diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index 846860b89a..97984609ea 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "MaterialInstance.hpp" #include "ShaderSet.hpp" @@ -12,9 +13,21 @@ namespace sh { class Platform; + class Configuration : public PropertySetGet + { + public: + void setSourceFile (const std::string& file) { mSourceFile = file ; } + std::string getSourceFile () { return mSourceFile; } + + void save(const std::string& name, std::ofstream &stream); + + private: + std::string mSourceFile; + }; + typedef std::map MaterialMap; typedef std::map ShaderSetMap; - typedef std::map ConfigurationMap; + typedef std::map ConfigurationMap; typedef std::map LodConfigurationMap; typedef std::map LastModifiedMap; @@ -81,8 +94,8 @@ namespace sh /// Get a MaterialInstance by name MaterialInstance* getMaterialInstance (const std::string& name); - /// Register a configuration, which can then be used by switching the active material scheme - void registerConfiguration (const std::string& name, PropertySetGet configuration); + /// Create a configuration, which can then be altered by using Factory::getConfiguration + void createConfiguration (const std::string& name); /// Register a lod configuration, which can then be used by setting up lod distance values for the material \n /// 0 refers to highest lod, so use 1 or higher as index parameter @@ -125,8 +138,48 @@ namespace sh /// \note The default is off (no cache reading) void setReadMicrocodeCache(bool read) { mReadMicrocodeCache = read; } - /// Saves all the materials that were initially loaded from the file with this name - void saveMaterials (const std::string& filename); + /// Lists all materials currently registered with the factory. Whether they are + /// loaded or not does not matter. + void listMaterials (std::vector& out); + + /// Lists current name & value of all global settings. + void listGlobalSettings (std::map& out); + + /// Lists configuration names. + void listConfigurationNames (std::vector& out); + + /// Lists current name & value of settings for a given configuration. + void listConfigurationSettings (const std::string& name, std::map& out); + + /// Lists shader sets. + void listShaderSets (std::vector& out); + + /// \note This only works if microcode caching is disabled, as there is currently no way to remove the cache + /// through the Ogre API. Luckily, this is already fixed in Ogre 1.9. + bool reloadShaders(); + + /// Calls reloadShaders() if shader files have been modified since the last reload. + /// \note This only works if microcode caching is disabled, as there is currently no way to remove the cache + /// through the Ogre API. Luckily, this is already fixed in Ogre 1.9. + void doMonitorShaderFiles(); + + /// Unloads all materials that are currently not referenced. This will not unload the textures themselves, + /// but it will let go of the SharedPtr's to the textures, so that you may unload them if you so desire. \n + /// A good time to call this would be after a new level has been loaded, but just calling it occasionally after a period + /// of time should work just fine too. + void unloadUnreferencedMaterials(); + + void destroyConfiguration (const std::string& name); + + void notifyConfigurationChanged(); + + /// Saves all materials and configurations, by default to the file they were loaded from. + /// If you wish to save them elsewhere, use setSourceFile first. + void saveAll (); + + /// Returns the error log as a string, then clears it. + /// Note: Errors are also written to the standard error output, or thrown if they are fatal. + std::string getErrorLog (); static Factory& getInstance(); ///< Return instance of this class. @@ -137,11 +190,13 @@ namespace sh /// You will probably never have to use this. void _ensureMaterial(const std::string& name, const std::string& configuration); + + Configuration* getConfiguration (const std::string& name); + private: MaterialInstance* requestMaterial (const std::string& name, const std::string& configuration, unsigned short lodIndex); ShaderSet* getShaderSet (const std::string& name); - PropertySetGet* getConfiguration (const std::string& name); Platform* getPlatform (); PropertySetGet* getCurrentGlobalSettings(); @@ -163,6 +218,8 @@ namespace sh std::map mTextureAliasInstances; + void logError (const std::string& msg); + friend class Platform; friend class MaterialInstance; friend class ShaderInstance; @@ -179,6 +236,7 @@ namespace sh bool mWriteMicrocodeCache; bool mReadSourceCache; bool mWriteSourceCache; + std::stringstream mErrorLog; MaterialMap mMaterials; ShaderSetMap mShaderSets; diff --git a/extern/shiny/Main/MaterialInstance.cpp b/extern/shiny/Main/MaterialInstance.cpp index 0f8bcdda78..b8032d681e 100644 --- a/extern/shiny/Main/MaterialInstance.cpp +++ b/extern/shiny/Main/MaterialInstance.cpp @@ -1,6 +1,7 @@ #include "MaterialInstance.hpp" #include +#include #include "Factory.hpp" #include "ShaderSet.hpp" @@ -12,6 +13,7 @@ namespace sh , mShadersEnabled(true) , mFactory(f) , mListener(NULL) + , mFailedToCreate(false) { } @@ -46,6 +48,7 @@ namespace sh return; mMaterial->removeAll(); mTexUnits.clear(); + mFailedToCreate = false; } void MaterialInstance::setProperty (const std::string& name, PropertyValuePtr value) @@ -54,118 +57,136 @@ namespace sh destroyAll(); // trigger updates } - void MaterialInstance::createForConfiguration (const std::string& configuration, unsigned short lodIndex) + bool MaterialInstance::createForConfiguration (const std::string& configuration, unsigned short lodIndex) { - bool res = mMaterial->createConfiguration(configuration, lodIndex); - if (!res) - return; // listener was false positive + if (mFailedToCreate) + return false; + try{ + mMaterial->ensureLoaded(); + bool res = mMaterial->createConfiguration(configuration, lodIndex); + if (!res) + return false; // listener was false positive - if (mListener) - mListener->requestedConfiguration (this, configuration); + if (mListener) + mListener->requestedConfiguration (this, configuration); - mFactory->setActiveConfiguration (configuration); - mFactory->setActiveLodLevel (lodIndex); + mFactory->setActiveConfiguration (configuration); + mFactory->setActiveLodLevel (lodIndex); - bool allowFixedFunction = true; - if (!mShadersEnabled && hasProperty("allow_fixed_function")) - { - allowFixedFunction = retrieveValue(getProperty("allow_fixed_function"), NULL).get(); - } - - bool useShaders = mShadersEnabled || !allowFixedFunction; - - // get passes of the top-most parent - PassVector passes = getPasses(); - if (passes.size() == 0) - throw std::runtime_error ("material \"" + mName + "\" does not have any passes"); - - for (PassVector::iterator it = passes.begin(); it != passes.end(); ++it) - { - boost::shared_ptr pass = mMaterial->createPass (configuration, lodIndex); - it->copyAll (pass.get(), this); - - // texture samplers used in the shaders - std::vector usedTextureSamplersVertex; - std::vector usedTextureSamplersFragment; - - PropertySetGet* context = this; - - // create or retrieve shaders - bool hasVertex = it->hasProperty("vertex_program"); - bool hasFragment = it->hasProperty("fragment_program"); - if (useShaders) + bool allowFixedFunction = true; + if (!mShadersEnabled && hasProperty("allow_fixed_function")) { - it->setContext(context); - it->mShaderProperties.setContext(context); - if (hasVertex) + allowFixedFunction = retrieveValue(getProperty("allow_fixed_function"), NULL).get(); + } + + bool useShaders = mShadersEnabled || !allowFixedFunction; + + // get passes of the top-most parent + PassVector* passes = getParentPasses(); + if (passes->empty()) + throw std::runtime_error ("material \"" + mName + "\" does not have any passes"); + + for (PassVector::iterator it = passes->begin(); it != passes->end(); ++it) + { + boost::shared_ptr pass = mMaterial->createPass (configuration, lodIndex); + it->copyAll (pass.get(), this); + + // texture samplers used in the shaders + std::vector usedTextureSamplersVertex; + std::vector usedTextureSamplersFragment; + + PropertySetGet* context = this; + + // create or retrieve shaders + bool hasVertex = it->hasProperty("vertex_program") + && !retrieveValue(it->getProperty("vertex_program"), context).get().empty(); + bool hasFragment = it->hasProperty("fragment_program") + && !retrieveValue(it->getProperty("fragment_program"), context).get().empty(); + if (useShaders) { - ShaderSet* vertex = mFactory->getShaderSet(retrieveValue(it->getProperty("vertex_program"), context).get()); - ShaderInstance* v = vertex->getInstance(&it->mShaderProperties); - if (v) + it->setContext(context); + it->mShaderProperties.setContext(context); + if (hasVertex) { - pass->assignProgram (GPT_Vertex, v->getName()); - v->setUniformParameters (pass, &it->mShaderProperties); - - std::vector sharedParams = v->getSharedParameters (); - for (std::vector::iterator it = sharedParams.begin(); it != sharedParams.end(); ++it) + ShaderSet* vertex = mFactory->getShaderSet(retrieveValue(it->getProperty("vertex_program"), context).get()); + ShaderInstance* v = vertex->getInstance(&it->mShaderProperties); + if (v) { - pass->addSharedParameter (GPT_Vertex, *it); - } + pass->assignProgram (GPT_Vertex, v->getName()); + v->setUniformParameters (pass, &it->mShaderProperties); - std::vector vector = v->getUsedSamplers (); - usedTextureSamplersVertex.insert(usedTextureSamplersVertex.end(), vector.begin(), vector.end()); + std::vector sharedParams = v->getSharedParameters (); + for (std::vector::iterator it2 = sharedParams.begin(); it2 != sharedParams.end(); ++it2) + { + pass->addSharedParameter (GPT_Vertex, *it2); + } + + std::vector vector = v->getUsedSamplers (); + usedTextureSamplersVertex.insert(usedTextureSamplersVertex.end(), vector.begin(), vector.end()); + } + } + if (hasFragment) + { + ShaderSet* fragment = mFactory->getShaderSet(retrieveValue(it->getProperty("fragment_program"), context).get()); + ShaderInstance* f = fragment->getInstance(&it->mShaderProperties); + if (f) + { + pass->assignProgram (GPT_Fragment, f->getName()); + f->setUniformParameters (pass, &it->mShaderProperties); + + std::vector sharedParams = f->getSharedParameters (); + for (std::vector::iterator it2 = sharedParams.begin(); it2 != sharedParams.end(); ++it2) + { + pass->addSharedParameter (GPT_Fragment, *it2); + } + + std::vector vector = f->getUsedSamplers (); + usedTextureSamplersFragment.insert(usedTextureSamplersFragment.end(), vector.begin(), vector.end()); + } } } - if (hasFragment) + + // create texture units + std::vector* texUnits = &it->mTexUnits; + int i=0; + for (std::vector::iterator texIt = texUnits->begin(); texIt != texUnits->end(); ++texIt ) { - ShaderSet* fragment = mFactory->getShaderSet(retrieveValue(it->getProperty("fragment_program"), context).get()); - ShaderInstance* f = fragment->getInstance(&it->mShaderProperties); - if (f) + // only create those that are needed by the shader, OR those marked to be created in fixed function pipeline if shaders are disabled + bool foundVertex = std::find(usedTextureSamplersVertex.begin(), usedTextureSamplersVertex.end(), texIt->getName()) != usedTextureSamplersVertex.end(); + bool foundFragment = std::find(usedTextureSamplersFragment.begin(), usedTextureSamplersFragment.end(), texIt->getName()) != usedTextureSamplersFragment.end(); + if ( (foundVertex || foundFragment) + || (((!useShaders || (!hasVertex || !hasFragment)) && allowFixedFunction) && texIt->hasProperty("create_in_ffp") && retrieveValue(texIt->getProperty("create_in_ffp"), this).get())) { - pass->assignProgram (GPT_Fragment, f->getName()); - f->setUniformParameters (pass, &it->mShaderProperties); + boost::shared_ptr texUnit = pass->createTextureUnitState (texIt->getName()); + texIt->copyAll (texUnit.get(), context); - std::vector sharedParams = f->getSharedParameters (); - for (std::vector::iterator it = sharedParams.begin(); it != sharedParams.end(); ++it) + mTexUnits.push_back(texUnit); + + // set texture unit indices (required by GLSL) + if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && mFactory->getCurrentLanguage () == Language_GLSL) { - pass->addSharedParameter (GPT_Fragment, *it); - } + pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i); - std::vector vector = f->getUsedSamplers (); - usedTextureSamplersFragment.insert(usedTextureSamplersFragment.end(), vector.begin(), vector.end()); + ++i; + } } } } - // create texture units - std::vector texUnits = it->getTexUnits(); - int i=0; - for (std::vector::iterator texIt = texUnits.begin(); texIt != texUnits.end(); ++texIt ) - { - // only create those that are needed by the shader, OR those marked to be created in fixed function pipeline if shaders are disabled - bool foundVertex = std::find(usedTextureSamplersVertex.begin(), usedTextureSamplersVertex.end(), texIt->getName()) != usedTextureSamplersVertex.end(); - bool foundFragment = std::find(usedTextureSamplersFragment.begin(), usedTextureSamplersFragment.end(), texIt->getName()) != usedTextureSamplersFragment.end(); - if ( (foundVertex || foundFragment) - || (((!useShaders || (!hasVertex || !hasFragment)) && allowFixedFunction) && texIt->hasProperty("create_in_ffp") && retrieveValue(texIt->getProperty("create_in_ffp"), this).get())) - { - boost::shared_ptr texUnit = pass->createTextureUnitState (); - texIt->copyAll (texUnit.get(), context); + if (mListener) + mListener->createdConfiguration (this, configuration); + return true; - mTexUnits.push_back(texUnit); - - // set texture unit indices (required by GLSL) - if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && mFactory->getCurrentLanguage () == Language_GLSL) - { - pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i); - - ++i; - } - } - } + } catch (std::runtime_error& e) + { + destroyAll(); + mFailedToCreate = true; + std::stringstream msg; + msg << "Error while creating material " << mName << ": " << e.what(); + std::cerr << msg.str() << std::endl; + mFactory->logError(msg.str()); + return false; } - - if (mListener) - mListener->createdConfiguration (this, configuration); } Material* MaterialInstance::getMaterial () @@ -180,12 +201,23 @@ namespace sh return &mPasses.back(); } - PassVector MaterialInstance::getPasses() + void MaterialInstance::deletePass(unsigned int index) + { + assert(mPasses.size() > index); + mPasses.erase(mPasses.begin()+index); + } + + PassVector* MaterialInstance::getParentPasses() { if (mParent) - return static_cast(mParent)->getPasses(); + return static_cast(mParent)->getParentPasses(); else - return mPasses; + return &mPasses; + } + + PassVector* MaterialInstance::getPasses() + { + return &mPasses; } void MaterialInstance::setShadersEnabled (bool enabled) @@ -206,7 +238,7 @@ namespace sh if (mParent) { - stream << "\t" << static_cast(mParent)->getName() << "\n"; + stream << "\t" << "parent " << static_cast(mParent)->getName() << "\n"; } const PropertyMap& properties = listProperties (); @@ -215,6 +247,14 @@ namespace sh stream << "\t" << it->first << " " << retrieveValue(getProperty(it->first), NULL).get() << "\n"; } + for (PassVector::iterator it = mPasses.begin(); it != mPasses.end(); ++it) + { + stream << "\tpass" << '\n'; + stream << "\t{" << '\n'; + it->save(stream); + stream << "\t}" << '\n'; + } + stream << "}\n"; } } diff --git a/extern/shiny/Main/MaterialInstance.hpp b/extern/shiny/Main/MaterialInstance.hpp index 36ba37ddba..72c78c7b7c 100644 --- a/extern/shiny/Main/MaterialInstance.hpp +++ b/extern/shiny/Main/MaterialInstance.hpp @@ -41,8 +41,12 @@ namespace sh MaterialInstance (const std::string& name, Factory* f); virtual ~MaterialInstance (); + PassVector* getParentPasses(); ///< gets the passes of the top-most parent + + PassVector* getPasses(); ///< get our passes (for derived materials, none) + MaterialInstancePass* createPass (); - PassVector getPasses(); ///< gets the passes of the top-most parent + void deletePass (unsigned int index); /// @attention Because the backend material passes are created on demand, the returned material here might not contain anything yet! /// The only place where you should use this method, is for the MaterialInstance given by the MaterialListener::materialCreated event! @@ -55,25 +59,25 @@ namespace sh virtual void setProperty (const std::string& name, PropertyValuePtr value); - private: - void setParentInstance (const std::string& name); - std::string getParentInstance (); - - void create (Platform* platform); - void createForConfiguration (const std::string& configuration, unsigned short lodIndex); - - void destroyAll (); - - void setShadersEnabled (bool enabled); - void setSourceFile(const std::string& sourceFile) { mSourceFile = sourceFile; } std::string getSourceFile() { return mSourceFile; } ///< get the name of the file this material was read from, or empty if it was created dynamically by code + private: + void setParentInstance (const std::string& name); + std::string getParentInstance (); + + void create (Platform* platform); + bool createForConfiguration (const std::string& configuration, unsigned short lodIndex); + + void destroyAll (); + + void setShadersEnabled (bool enabled); + void save (std::ofstream& stream); - ///< this will only save the properties, not the passes and texture units, and as such - /// is only intended to be used for derived materials + + bool mFailedToCreate; friend class Factory; diff --git a/extern/shiny/Main/MaterialInstancePass.cpp b/extern/shiny/Main/MaterialInstancePass.cpp index b14476f4e7..a628cd64c7 100644 --- a/extern/shiny/Main/MaterialInstancePass.cpp +++ b/extern/shiny/Main/MaterialInstancePass.cpp @@ -1,5 +1,7 @@ #include "MaterialInstancePass.hpp" +#include + namespace sh { @@ -9,8 +11,25 @@ namespace sh return &mTexUnits.back(); } - std::vector MaterialInstancePass::getTexUnits () + void MaterialInstancePass::save(std::ofstream &stream) { - return mTexUnits; + if (mShaderProperties.listProperties().size()) + { + stream << "\t\t" << "shader_properties" << '\n'; + stream << "\t\t{\n"; + mShaderProperties.save(stream, "\t\t\t"); + stream << "\t\t}\n"; + } + + PropertySetGet::save(stream, "\t\t"); + + for (std::vector ::iterator it = mTexUnits.begin(); + it != mTexUnits.end(); ++it) + { + stream << "\t\ttexture_unit " << it->getName() << '\n'; + stream << "\t\t{\n"; + it->save(stream, "\t\t\t"); + stream << "\t\t}\n"; + } } } diff --git a/extern/shiny/Main/MaterialInstancePass.hpp b/extern/shiny/Main/MaterialInstancePass.hpp index 7d7330f705..3d83d8fa35 100644 --- a/extern/shiny/Main/MaterialInstancePass.hpp +++ b/extern/shiny/Main/MaterialInstancePass.hpp @@ -18,10 +18,10 @@ namespace sh public: MaterialInstanceTextureUnit* createTextureUnit (const std::string& name); + void save (std::ofstream& stream); + PropertySetGet mShaderProperties; - std::vector getTexUnits (); - private: std::vector mTexUnits; }; } diff --git a/extern/shiny/Main/MaterialInstanceTextureUnit.hpp b/extern/shiny/Main/MaterialInstanceTextureUnit.hpp index ae9f54fd2d..5ca400fd49 100644 --- a/extern/shiny/Main/MaterialInstanceTextureUnit.hpp +++ b/extern/shiny/Main/MaterialInstanceTextureUnit.hpp @@ -18,6 +18,7 @@ namespace sh public: MaterialInstanceTextureUnit (const std::string& name); std::string getName() const; + void setName (const std::string& name) { mName = name; } private: std::string mName; }; diff --git a/extern/shiny/Main/Platform.cpp b/extern/shiny/Main/Platform.cpp index 94b4f872ae..3eb7f4ad34 100644 --- a/extern/shiny/Main/Platform.cpp +++ b/extern/shiny/Main/Platform.cpp @@ -9,7 +9,7 @@ namespace sh Platform::Platform (const std::string& basePath) : mBasePath(basePath) , mCacheFolder("./") - , mShaderCachingEnabled(false) + , mFactory(NULL) { } @@ -57,11 +57,6 @@ namespace sh mCacheFolder = folder; } - void Platform::setShaderCachingEnabled (bool enabled) - { - mShaderCachingEnabled = enabled; - } - std::string Platform::getCacheFolder() const { return mCacheFolder; diff --git a/extern/shiny/Main/Platform.hpp b/extern/shiny/Main/Platform.hpp index 1b095e9576..24afea03ba 100644 --- a/extern/shiny/Main/Platform.hpp +++ b/extern/shiny/Main/Platform.hpp @@ -24,6 +24,7 @@ namespace sh class GpuProgram { public: + virtual ~GpuProgram() {} virtual bool getSupported () = 0; ///< @return true if the compilation was successful /// @param name name of the uniform in the shader @@ -35,8 +36,7 @@ namespace sh class TextureUnitState : public PropertySet { public: - virtual ~TextureUnitState(); - + virtual ~TextureUnitState(); virtual void setTextureName (const std::string& textureName) = 0; protected: @@ -46,7 +46,7 @@ namespace sh class Pass : public PropertySet { public: - virtual boost::shared_ptr createTextureUnitState () = 0; + virtual boost::shared_ptr createTextureUnitState (const std::string& name) = 0; virtual void assignProgram (GpuProgramType type, const std::string& name) = 0; /// @param type gpu program type @@ -68,6 +68,9 @@ namespace sh virtual bool createConfiguration (const std::string& name, unsigned short lodIndex) = 0; ///< @return false if already exists virtual void removeAll () = 0; ///< remove all configurations + virtual bool isUnreferenced() = 0; + virtual void ensureLoaded() = 0; + virtual void setLodLevels (const std::string& lodLevels) = 0; virtual void setShadowCasterMaterial (const std::string& name) = 0; @@ -79,8 +82,6 @@ namespace sh Platform (const std::string& basePath); virtual ~Platform (); - void setShaderCachingEnabled (bool enabled); - /// set the folder to use for shader caching void setCacheFolder (const std::string& folder); @@ -93,6 +94,8 @@ namespace sh const std::string& name, const std::string& profile, const std::string& source, Language lang) = 0; + virtual void destroyGpuProgram (const std::string& name) = 0; + virtual void setSharedParameter (const std::string& name, PropertyValuePtr value) = 0; virtual bool isProfileSupported (const std::string& profile) = 0; @@ -105,6 +108,7 @@ namespace sh friend class Factory; friend class MaterialInstance; friend class ShaderInstance; + friend class ShaderSet; protected: /** @@ -131,9 +135,6 @@ namespace sh std::string mCacheFolder; Factory* mFactory; - protected: - bool mShaderCachingEnabled; - private: void setFactory (Factory* factory); diff --git a/extern/shiny/Main/PropertyBase.cpp b/extern/shiny/Main/PropertyBase.cpp index 0c39e5c1e7..8592712fa9 100644 --- a/extern/shiny/Main/PropertyBase.cpp +++ b/extern/shiny/Main/PropertyBase.cpp @@ -6,6 +6,8 @@ #include #include +#include + namespace sh { @@ -39,8 +41,9 @@ namespace sh mValue = false; else { - std::cerr << "sh::BooleanValue: Warning: Unrecognized value \"" << in << "\" for property value of type BooleanValue" << std::endl; - mValue = false; + std::stringstream msg; + msg << "sh::BooleanValue: Warning: Unrecognized value \"" << in << "\" for property value of type BooleanValue"; + throw std::runtime_error(msg.str()); } } @@ -183,12 +186,16 @@ namespace sh void PropertySet::setProperty (const std::string& name, PropertyValuePtr &value, PropertySetGet* context) { if (!setPropertyOverride (name, value, context)) - std::cerr << "sh::PropertySet: Warning: No match for property with name '" << name << "'" << std::endl; + { + std::stringstream msg; + msg << "sh::PropertySet: Warning: No match for property with name '" << name << "'"; + throw std::runtime_error(msg.str()); + } } bool PropertySet::setPropertyOverride (const std::string& name, PropertyValuePtr &value, PropertySetGet* context) { - // if we got here, none of the sub-classes was able to make use of the property + // if we got here, none of the sub-classes were able to make use of the property return false; } @@ -226,6 +233,11 @@ namespace sh mProperties [name] = value; } + void PropertySetGet::deleteProperty(const std::string &name) + { + mProperties.erase(name); + } + PropertyValuePtr& PropertySetGet::getProperty (const std::string& name) { bool found = (mProperties.find(name) != mProperties.end()); @@ -241,7 +253,7 @@ namespace sh return mProperties[name]; } - bool PropertySetGet::hasProperty (const std::string& name) + bool PropertySetGet::hasProperty (const std::string& name) const { bool found = (mProperties.find(name) != mProperties.end()); @@ -256,13 +268,35 @@ namespace sh return true; } - void PropertySetGet::copyAll (PropertySet* target, PropertySetGet* context) + void PropertySetGet::copyAll (PropertySet* target, PropertySetGet* context, bool copyParent) { - if (mParent) + if (mParent && copyParent) mParent->copyAll (target, context); for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it) { target->setProperty(it->first, it->second, context); } } + + void PropertySetGet::copyAll (PropertySetGet* target, PropertySetGet* context, bool copyParent) + { + if (mParent && copyParent) + mParent->copyAll (target, context); + for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it) + { + std::string val = retrieveValue(it->second, this).get(); + target->setProperty(it->first, sh::makeProperty(new sh::StringValue(val))); + } + } + + void PropertySetGet::save(std::ofstream &stream, const std::string& indentation) + { + for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it) + { + if (typeid( *(it->second) ) == typeid(LinkedValue)) + stream << indentation << it->first << " " << "$" + static_cast(&*(it->second))->_getStringValue() << '\n'; + else + stream << indentation << it->first << " " << retrieveValue(it->second, this).get() << '\n'; + } + } } diff --git a/extern/shiny/Main/PropertyBase.hpp b/extern/shiny/Main/PropertyBase.hpp index 240acce81b..13809443e2 100644 --- a/extern/shiny/Main/PropertyBase.hpp +++ b/extern/shiny/Main/PropertyBase.hpp @@ -133,6 +133,7 @@ namespace sh class PropertySet { public: + virtual ~PropertySet() {} void setProperty (const std::string& name, PropertyValuePtr& value, PropertySetGet* context); protected: @@ -151,18 +152,26 @@ namespace sh virtual ~PropertySetGet() {} - void copyAll (PropertySet* target, PropertySetGet* context); ///< call setProperty for each property/value pair stored in \a this + void save (std::ofstream& stream, const std::string& indentation); + + void copyAll (PropertySet* target, PropertySetGet* context, bool copyParent=true); + ///< call setProperty for each property/value pair stored in \a this + void copyAll (PropertySetGet* target, PropertySetGet* context, bool copyParent=true); + ///< call setProperty for each property/value pair stored in \a this void setParent (PropertySetGet* parent); + PropertySetGet* getParent () { return mParent; } void setContext (PropertySetGet* context); PropertySetGet* getContext(); virtual void setProperty (const std::string& name, PropertyValuePtr value); PropertyValuePtr& getProperty (const std::string& name); + void deleteProperty (const std::string& name); + const PropertyMap& listProperties() { return mProperties; } - bool hasProperty (const std::string& name); + bool hasProperty (const std::string& name) const; private: PropertyMap mProperties; @@ -225,7 +234,7 @@ namespace sh template /// Create a property of any type - /// Example: sh::makeProperty\ (new sh::Vector4(1, 1, 1, 1)) + /// Example: sh::makeProperty (new sh::Vector4(1, 1, 1, 1)) inline PropertyValuePtr makeProperty (T* p) { return PropertyValuePtr ( static_cast(p) ); diff --git a/extern/shiny/Main/ScriptLoader.cpp b/extern/shiny/Main/ScriptLoader.cpp index 93d728b023..bafe07fc8e 100644 --- a/extern/shiny/Main/ScriptLoader.cpp +++ b/extern/shiny/Main/ScriptLoader.cpp @@ -24,6 +24,10 @@ namespace sh } ScriptLoader::ScriptLoader(const std::string& fileEnding) + : mLoadOrder(0) + , mToken(TOKEN_NewLine) + , mLastToken(TOKEN_NewLine) + { mFileEnding = fileEnding; } @@ -36,7 +40,7 @@ namespace sh void ScriptLoader::clearScriptList() { std::map ::iterator i; - for (i = m_scriptList.begin(); i != m_scriptList.end(); i++) + for (i = m_scriptList.begin(); i != m_scriptList.end(); ++i) { delete i->second; } @@ -293,7 +297,7 @@ namespace sh { //Delete all children std::vector::iterator i; - for (i = mChildren.begin(); i != mChildren.end(); i++) + for (i = mChildren.begin(); i != mChildren.end(); ++i) { ScriptNode *node = *i; node->mRemoveSelf = false; @@ -323,15 +327,24 @@ namespace sh ScriptNode *ScriptNode::findChild(const std::string &name, bool recursive) { - int indx, prevC, nextC; + int indx; int childCount = (int)mChildren.size(); if (mLastChildFound != -1) { //If possible, try checking the nodes neighboring the last successful search //(often nodes searched for in sequence, so this will boost search speeds). - prevC = mLastChildFound-1; if (prevC < 0) prevC = 0; else if (prevC >= childCount) prevC = childCount-1; - nextC = mLastChildFound+1; if (nextC < 0) nextC = 0; else if (nextC >= childCount) nextC = childCount-1; + int prevC = mLastChildFound-1; + if (prevC < 0) + prevC = 0; + else if (prevC >= childCount) + prevC = childCount-1; + int nextC = mLastChildFound+1; + if (nextC < 0) + nextC = 0; + else if (nextC >= childCount) + nextC = childCount-1; + for (indx = prevC; indx <= nextC; ++indx) { ScriptNode *node = mChildren[indx]; diff --git a/extern/shiny/Main/ShaderSet.cpp b/extern/shiny/Main/ShaderSet.cpp index 628e0acee7..8fb530d394 100644 --- a/extern/shiny/Main/ShaderSet.cpp +++ b/extern/shiny/Main/ShaderSet.cpp @@ -37,6 +37,14 @@ namespace sh parse(); } + ShaderSet::~ShaderSet() + { + for (ShaderInstanceMap::iterator it = mInstances.begin(); it != mInstances.end(); ++it) + { + sh::Factory::getInstance().getPlatform()->destroyGpuProgram(it->second.getName()); + } + } + void ShaderSet::parse() { std::string currentToken; diff --git a/extern/shiny/Main/ShaderSet.hpp b/extern/shiny/Main/ShaderSet.hpp index b21278ac99..988c6769fa 100644 --- a/extern/shiny/Main/ShaderSet.hpp +++ b/extern/shiny/Main/ShaderSet.hpp @@ -21,6 +21,7 @@ namespace sh public: ShaderSet (const std::string& type, const std::string& cgProfile, const std::string& hlslProfile, const std::string& sourceFile, const std::string& basePath, const std::string& name, PropertySetGet* globalSettingsPtr); + ~ShaderSet(); /// Retrieve a shader instance for the given properties. \n /// If a \a ShaderInstance with the same properties exists already, simply returns this instance. \n diff --git a/extern/shiny/Platforms/Ogre/OgreMaterial.cpp b/extern/shiny/Platforms/Ogre/OgreMaterial.cpp index 4a550b8bff..77091fe03d 100644 --- a/extern/shiny/Platforms/Ogre/OgreMaterial.cpp +++ b/extern/shiny/Platforms/Ogre/OgreMaterial.cpp @@ -15,6 +15,7 @@ namespace sh OgreMaterial::OgreMaterial (const std::string& name, const std::string& resourceGroup) : Material() { + mName = name; assert (Ogre::MaterialManager::getSingleton().getByName(name).isNull() && "Material already exists"); mMaterial = Ogre::MaterialManager::getSingleton().create (name, resourceGroup); mMaterial->removeAllTechniques(); @@ -22,9 +23,22 @@ namespace sh mMaterial->compile(); } + void OgreMaterial::ensureLoaded() + { + if (mMaterial.isNull()) + mMaterial = Ogre::MaterialManager::getSingleton().getByName(mName); + } + + bool OgreMaterial::isUnreferenced() + { + // Resource system internals hold 3 shared pointers, we hold one, so usecount of 4 means unused + return (!mMaterial.isNull() && mMaterial.useCount() <= Ogre::ResourceGroupManager::RESOURCE_SYSTEM_NUM_REFERENCE_COUNTS+1); + } + OgreMaterial::~OgreMaterial() { - Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); + if (!mMaterial.isNull()) + Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); } boost::shared_ptr OgreMaterial::createPass (const std::string& configuration, unsigned short lodIndex) @@ -34,6 +48,8 @@ namespace sh void OgreMaterial::removeAll () { + if (mMaterial.isNull()) + return; mMaterial->removeAllTechniques(); mMaterial->createTechnique()->setSchemeName (sDefaultTechniqueName); mMaterial->compile(); diff --git a/extern/shiny/Platforms/Ogre/OgreMaterial.hpp b/extern/shiny/Platforms/Ogre/OgreMaterial.hpp index bec23f0b6f..bfa6e2c6f8 100644 --- a/extern/shiny/Platforms/Ogre/OgreMaterial.hpp +++ b/extern/shiny/Platforms/Ogre/OgreMaterial.hpp @@ -18,6 +18,9 @@ namespace sh virtual boost::shared_ptr createPass (const std::string& configuration, unsigned short lodIndex); virtual bool createConfiguration (const std::string& name, unsigned short lodIndex); + virtual bool isUnreferenced(); + virtual void ensureLoaded(); + virtual void removeAll (); Ogre::MaterialPtr getOgreMaterial(); @@ -30,6 +33,7 @@ namespace sh private: Ogre::MaterialPtr mMaterial; + std::string mName; std::string mShadowCasterMaterial; }; diff --git a/extern/shiny/Platforms/Ogre/OgrePass.cpp b/extern/shiny/Platforms/Ogre/OgrePass.cpp index 3ed48b96f8..3a25c5c740 100644 --- a/extern/shiny/Platforms/Ogre/OgrePass.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePass.cpp @@ -20,9 +20,9 @@ namespace sh mPass = t->createPass(); } - boost::shared_ptr OgrePass::createTextureUnitState () + boost::shared_ptr OgrePass::createTextureUnitState (const std::string& name) { - return boost::shared_ptr (new OgreTextureUnitState (this)); + return boost::shared_ptr (new OgreTextureUnitState (this, name)); } void OgrePass::assignProgram (GpuProgramType type, const std::string& name) @@ -105,7 +105,17 @@ namespace sh else if (type == GPT_Fragment) params = mPass->getFragmentProgramParameters(); - params->addSharedParameters (name); + try + { + params->addSharedParameters (name); + } + catch (Ogre::Exception& e) + { + std::stringstream msg; + msg << "Could not create a shared parameter instance for '" + << name << "'. Make sure this shared parameter has a value set (via Factory::setSharedParameter)!"; + throw std::runtime_error(msg.str()); + } } void OgrePass::setTextureUnitIndex (int programType, const std::string& name, int index) diff --git a/extern/shiny/Platforms/Ogre/OgrePass.hpp b/extern/shiny/Platforms/Ogre/OgrePass.hpp index da67a1a2a5..e7cfd4e337 100644 --- a/extern/shiny/Platforms/Ogre/OgrePass.hpp +++ b/extern/shiny/Platforms/Ogre/OgrePass.hpp @@ -14,7 +14,7 @@ namespace sh public: OgrePass (OgreMaterial* parent, const std::string& configuration, unsigned short lodIndex); - virtual boost::shared_ptr createTextureUnitState (); + virtual boost::shared_ptr createTextureUnitState (const std::string& name); virtual void assignProgram (GpuProgramType type, const std::string& name); Ogre::Pass* getOgrePass(); diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp index 357949402f..2749c22ce6 100644 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "OgreMaterial.hpp" @@ -76,6 +77,11 @@ namespace sh return boost::shared_ptr (material); } + void OgrePlatform::destroyGpuProgram(const std::string &name) + { + Ogre::HighLevelGpuProgramManager::getSingleton().remove(name); + } + boost::shared_ptr OgrePlatform::createGpuProgram ( GpuProgramType type, const std::string& compileArguments, @@ -122,6 +128,7 @@ namespace sh if (mSharedParameters.find(name) == mSharedParameters.end()) { params = Ogre::GpuProgramManager::getSingleton().createSharedParameters(name); + Ogre::GpuConstantType type; if (typeid(*value) == typeid(Vector4)) type = Ogre::GCT_FLOAT4; diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.hpp b/extern/shiny/Platforms/Ogre/OgrePlatform.hpp index d115c46bb4..d3a1a83608 100644 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.hpp +++ b/extern/shiny/Platforms/Ogre/OgrePlatform.hpp @@ -47,6 +47,8 @@ namespace sh const std::string& name, const std::string& profile, const std::string& source, Language lang); + virtual void destroyGpuProgram (const std::string& name); + virtual void setSharedParameter (const std::string& name, PropertyValuePtr value); friend class ShaderInstance; diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp index 0938cf667d..54dda3523f 100644 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp +++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp @@ -6,10 +6,11 @@ namespace sh { - OgreTextureUnitState::OgreTextureUnitState (OgrePass* parent) + OgreTextureUnitState::OgreTextureUnitState (OgrePass* parent, const std::string& name) : TextureUnitState() { mTextureUnitState = parent->getOgrePass()->createTextureUnitState(""); + mTextureUnitState->setName(name); } bool OgreTextureUnitState::setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context) diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp index d36f4b945a..0f1914f62e 100644 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp +++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp @@ -12,7 +12,7 @@ namespace sh class OgreTextureUnitState : public TextureUnitState { public: - OgreTextureUnitState (OgrePass* parent); + OgreTextureUnitState (OgrePass* parent, const std::string& name); virtual void setTextureName (const std::string& textureName); diff --git a/files/materials/objects.shader b/files/materials/objects.shader index acabde10e6..0c869d2cb8 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -415,7 +415,7 @@ float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel); #endif -#if SHADOWS +#if SHADOWS || SHADOWS_PSSM shOutputColour(0) *= (lightResult - float4(directionalResult * (1.0-shadow),0)); #else shOutputColour(0) *= lightResult; diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 9a9da72d4c..46090d0008 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -5,14 +5,13 @@ - - - - - - - - + + + + + + + diff --git a/files/mygui/openmw_font.xml b/files/mygui/openmw_font.xml index f1be51e165..ef43003264 100644 --- a/files/mygui/openmw_font.xml +++ b/files/mygui/openmw_font.xml @@ -23,6 +23,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout index fdf82e4dee..1b440b8337 100644 --- a/files/mygui/openmw_journal.layout +++ b/files/mygui/openmw_journal.layout @@ -2,24 +2,111 @@ - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_journal_skin.xml b/files/mygui/openmw_journal_skin.xml index 9f82e907ff..44448225c0 100644 --- a/files/mygui/openmw_journal_skin.xml +++ b/files/mygui/openmw_journal_skin.xml @@ -10,8 +10,6 @@ - - - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 693c5a9cb5..77d14d0f6d 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -16,7 +16,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -65,35 +65,35 @@ - + - + - + - + - + @@ -118,7 +118,7 @@ - + @@ -134,7 +134,7 @@ - + @@ -199,7 +199,7 @@ - + @@ -224,7 +224,7 @@ - + @@ -232,7 +232,7 @@ - +