#include "journalwindow.hpp" #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/windowmanager.hpp" #include "bookpage.hpp" #include "journalbooks.hpp" #include "journalviewmodel.hpp" #include "windowbase.hpp" namespace { 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 QuestsList[] = "QuestsList"; static char const LeftBookPage[] = "LeftBookPage"; static char const RightBookPage[] = "RightBookPage"; static char const LeftTopicIndex[] = "LeftTopicIndex"; static char const CenterTopicIndex[] = "CenterTopicIndex"; static char const RightTopicIndex[] = "RightTopicIndex"; struct JournalWindowImpl : MWGui::JournalBooks, MWGui::JournalWindow { struct DisplayState { unsigned int mPage; Book mBook; }; typedef std::stack DisplayStateStack; DisplayStateStack mStates; Book mTopicIndexBook; bool mQuestMode; bool mOptionsMode; bool mTopicsMode; 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(MyGUI::utility::toString(value)); } void setVisible(char const* name, bool visible) { getWidget(name)->setVisible(visible); } void adviseButtonClick(char const* name, void (JournalWindowImpl::*handler)(MyGUI::Widget*)) { getWidget(name)->eventMouseButtonClick += newDelegate(this, handler); } void adviseKeyPress( char const* name, void (JournalWindowImpl::*handler)(MyGUI::Widget*, MyGUI::KeyCode, MyGUI::Char)) { getWidget(name)->eventKeyButtonPressed += newDelegate(this, handler); } MWGui::BookPage* getPage(char const* name) { return getWidget(name); } JournalWindowImpl(MWGui::JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) : JournalBooks(Model, encoding) , JournalWindow() { 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); adviseKeyPress(OptionsBTN, &JournalWindowImpl::notifyKeyPress); adviseKeyPress(PrevPageBTN, &JournalWindowImpl::notifyKeyPress); adviseKeyPress(NextPageBTN, &JournalWindowImpl::notifyKeyPress); adviseKeyPress(CloseBTN, &JournalWindowImpl::notifyKeyPress); adviseKeyPress(JournalBTN, &JournalWindowImpl::notifyKeyPress); Gui::MWList* list = getWidget(QuestsList); list->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyQuestClicked); Gui::MWList* topicsList = getWidget(TopicsList); topicsList->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyTopicSelected); { MWGui::BookPage::ClickCallback callback; callback = std::bind(&JournalWindowImpl::notifyTopicClicked, this, std::placeholders::_1); getPage(LeftBookPage)->adviseLinkClicked(callback); getPage(RightBookPage)->adviseLinkClicked(callback); getPage(LeftBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); getPage(RightBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); } { MWGui::BookPage::ClickCallback callback; callback = std::bind(&JournalWindowImpl::notifyIndexLinkClicked, this, std::placeholders::_1); getPage(LeftTopicIndex)->adviseLinkClicked(callback); getPage(CenterTopicIndex)->adviseLinkClicked(callback); getPage(RightTopicIndex)->adviseLinkClicked(callback); } adjustButton(PrevPageBTN); float nextButtonScale = adjustButton(NextPageBTN); adjustButton(CloseBTN); adjustButton(CancelBTN); adjustButton(JournalBTN); Gui::ImageButton* optionsButton = getWidget(OptionsBTN); Gui::ImageButton* showActiveButton = getWidget(ShowActiveBTN); Gui::ImageButton* showAllButton = getWidget(ShowAllBTN); Gui::ImageButton* questsButton = getWidget(QuestsBTN); Gui::ImageButton* nextButton = getWidget(NextPageBTN); if (nextButton->getSize().width == 64) { // english button has a 7 pixel wide strip of garbage on its right edge nextButton->setSize(64 - 7, nextButton->getSize().height); nextButton->setImageCoord( MyGUI::IntCoord(0, 0, (64 - 7) * nextButtonScale, nextButton->getSize().height * nextButtonScale)); } if (!questList) { // If tribunal is not installed (-> no options button), we still want the Topics button available, // so place it where the options button would have been Gui::ImageButton* topicsButton = getWidget(TopicsBTN); topicsButton->detachFromWidget(); topicsButton->attachToWidget(optionsButton->getParent()); topicsButton->setPosition(optionsButton->getPosition()); topicsButton->eventMouseButtonClick.clear(); topicsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &JournalWindowImpl::notifyOptions); optionsButton->setVisible(false); showActiveButton->setVisible(false); showAllButton->setVisible(false); questsButton->setVisible(false); adjustButton(TopicsBTN); } else { optionsButton->setImage("textures/tx_menubook_options.dds"); showActiveButton->setImage("textures/tx_menubook_quests_active.dds"); showAllButton->setImage("textures/tx_menubook_quests_all.dds"); questsButton->setImage("textures/tx_menubook_quests.dds"); adjustButton(ShowAllBTN); adjustButton(ShowActiveBTN); adjustButton(OptionsBTN); adjustButton(QuestsBTN); adjustButton(TopicsBTN); int topicsWidth = getWidget(TopicsBTN)->getSize().width; int cancelLeft = getWidget(CancelBTN)->getPosition().left; int cancelRight = getWidget(CancelBTN)->getPosition().left + getWidget(CancelBTN)->getSize().width; getWidget(QuestsBTN)->setPosition( cancelRight, getWidget(QuestsBTN)->getPosition().top); // Usually Topics, Quests, and Cancel buttons have the 64px width, so we can place the Topics left-up // from the Cancel button, and the Quests right-up from the Cancel button. But in some installations, // e.g. German one, the Topics button has the 128px width, so we should place it exactly left from the // Quests button. if (topicsWidth == 64) { getWidget(TopicsBTN)->setPosition( cancelLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); } else { int questLeft = getWidget(QuestsBTN)->getPosition().left; getWidget(TopicsBTN)->setPosition( questLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); } } mQuestMode = false; mAllQuests = false; mOptionsMode = false; mTopicsMode = false; } void onOpen() override { if (!MWBase::Environment::get().getWindowManager()->getJournalAllowed()) { MWBase::Environment::get().getWindowManager()->popGuiMode(); } mModel->load(); setBookMode(); 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(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(getWidget(CloseBTN)); } void onClose() override { mModel->unload(); getPage(LeftBookPage)->showPage(Book(), 0); getPage(RightBookPage)->showPage(Book(), 0); while (!mStates.empty()) mStates.pop(); mTopicIndexBook.reset(); } void setVisible(bool newValue) override { WindowBase::setVisible(newValue); } void setBookMode() { mOptionsMode = false; mTopicsMode = false; setVisible(OptionsBTN, true); setVisible(OptionsOverlay, false); updateShowingPages(); updateCloseJournalButton(); } void setOptionsMode() { mOptionsMode = true; mTopicsMode = false; 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(CenterTopicIndex, !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); // If in quest mode, ensure the quest list is updated if (mQuestMode) notifyQuests(getWidget(QuestsList)); else notifyTopics(getWidget(TopicsList)); } 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; } MyGUI::Widget* nextPageBtn = getWidget(NextPageBTN); MyGUI::Widget* prevPageBtn = getWidget(PrevPageBTN); MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); bool nextPageVisible = relPages > 2; nextPageBtn->setVisible(nextPageVisible); bool prevPageVisible = page > 0; prevPageBtn->setVisible(prevPageVisible); if (focus == nextPageBtn && !nextPageVisible && prevPageVisible) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(prevPageBtn); else if (focus == prevPageBtn && !prevPageVisible && nextPageVisible) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nextPageBtn); 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 notifyKeyPress(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character) { if (key == MyGUI::KeyCode::ArrowUp) notifyPrevPage(sender); else if (key == MyGUI::KeyCode::ArrowDown) notifyNextPage(sender); } 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); mOptionsMode = false; mTopicsMode = false; MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } void notifyTopicSelected(const std::string& topicIdString, int id) { ESM::RefId topic = ESM::RefId::stringRefId(topicIdString); const MWBase::Journal* journal = MWBase::Environment::get().getJournal(); intptr_t topicId = 0; /// \todo get rid of intptr ids for (MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd(); ++i) { if (i->first == topic) topicId = intptr_t(&i->second); } notifyTopicClicked(topicId); } void notifyQuestClicked(const std::string& name, int id) { Book book = createQuestBook(name); if (mStates.size() > 1) replaceBook(book, 0); else pushBook(book, 0); setVisible(OptionsOverlay, false); setVisible(OptionsBTN, true); setVisible(JournalBTN, true); mOptionsMode = false; MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } void notifyOptions(MyGUI::Widget* _sender) { setOptionsMode(); if (!mTopicIndexBook) mTopicIndexBook = createTopicIndexBook(); if (mIndexPagesCount == 3) { getPage(LeftTopicIndex)->showPage(mTopicIndexBook, 0); getPage(CenterTopicIndex)->showPage(mTopicIndexBook, 1); getPage(RightTopicIndex)->showPage(mTopicIndexBook, 2); } else { getPage(LeftTopicIndex)->showPage(mTopicIndexBook, 0); getPage(RightTopicIndex)->showPage(mTopicIndexBook, 1); } } void notifyJournal(MyGUI::Widget* _sender) { assert(mStates.size() > 1); popBook(); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } void notifyIndexLinkClicked(MWGui::TypesetBook::InteractiveId index) { setVisible(LeftTopicIndex, false); setVisible(CenterTopicIndex, false); setVisible(RightTopicIndex, false); setVisible(TopicsList, true); mTopicsMode = true; Gui::MWList* list = getWidget(TopicsList); list->clear(); AddNamesToList add(list); mModel->visitTopicNamesStartingWith(index, add); list->adjustSize(); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } void notifyTopics(MyGUI::Widget* _sender) { mQuestMode = false; mTopicsMode = false; setVisible(LeftTopicIndex, true); setVisible(CenterTopicIndex, true); setVisible(RightTopicIndex, true); setVisible(TopicsList, false); setVisible(QuestsList, false); setVisible(ShowAllBTN, false); setVisible(ShowActiveBTN, false); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } struct AddNamesToList { AddNamesToList(Gui::MWList* list) : mList(list) { } Gui::MWList* mList; void operator()(std::string_view name, bool finished = false) { mList->addItem(name); } }; struct SetNamesInactive { SetNamesInactive(Gui::MWList* list) : mList(list) { } Gui::MWList* mList; void operator()(std::string_view name, bool finished) { if (finished) { mList->getItemWidget(name)->setStateSelected(true); } } }; void notifyQuests(MyGUI::Widget* _sender) { mQuestMode = true; setVisible(LeftTopicIndex, false); setVisible(CenterTopicIndex, false); setVisible(RightTopicIndex, false); setVisible(TopicsList, false); setVisible(QuestsList, true); setVisible(ShowAllBTN, !mAllQuests); setVisible(ShowActiveBTN, mAllQuests); Gui::MWList* list = getWidget(QuestsList); list->clear(); AddNamesToList add(list); mModel->visitQuestNames(!mAllQuests, add); list->sort(); list->adjustSize(); if (mAllQuests) { SetNamesInactive setInactive(list); mModel->visitQuestNames(false, setInactive); } MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } void notifyShowAll(MyGUI::Widget* _sender) { mAllQuests = true; notifyQuests(_sender); } void notifyShowActive(MyGUI::Widget* _sender) { mAllQuests = false; notifyQuests(_sender); } void notifyCancel(MyGUI::Widget* _sender) { if (mTopicsMode) { notifyTopics(_sender); } else { setBookMode(); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } } void notifyClose(MyGUI::Widget* _sender) { MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); winMgr->playSound(ESM::RefId::stringRefId("book close")); winMgr->popGuiMode(); } void notifyMouseWheel(MyGUI::Widget* sender, int rel) { if (rel < 0) notifyNextPage(sender); else notifyPrevPage(sender); } void notifyNextPage(MyGUI::Widget* _sender) { if (mOptionsMode) return; if (!mStates.empty()) { unsigned int& page = mStates.top().mPage; Book book = mStates.top().mBook; if (page + 2 < book->pageCount()) { MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); page += 2; updateShowingPages(); } } } void notifyPrevPage(MyGUI::Widget* _sender) { if (mOptionsMode) return; if (!mStates.empty()) { unsigned int& page = mStates.top().mPage; if (page >= 2) { MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); page -= 2; updateShowingPages(); } } } }; } // glue the implementation to the interface std::unique_ptr MWGui::JournalWindow::create( JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) { return std::make_unique(Model, questList, encoding); } MWGui::JournalWindow::JournalWindow() : BookWindowBase("openmw_journal.layout") { }