From f88b99201aceec7e6ea15e734795b8e002f5b5b4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 16 Nov 2023 11:41:19 +0400 Subject: [PATCH 1/3] Display missing plugins upon savegame loading (feature 7608) --- CHANGELOG.md | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 72 +++++++++++++++++++------ apps/openmw/mwstate/statemanagerimp.hpp | 2 +- components/esm3/savedgame.cpp | 14 +++++ components/esm3/savedgame.hpp | 2 + files/data/l10n/Interface/de.yaml | 1 + files/data/l10n/Interface/en.yaml | 1 + files/data/l10n/Interface/fr.yaml | 1 + files/data/l10n/Interface/ru.yaml | 1 + files/data/l10n/Interface/sv.yaml | 1 + files/data/l10n/OMWEngine/de.yaml | 14 ++++- files/data/l10n/OMWEngine/en.yaml | 14 ++++- files/data/l10n/OMWEngine/fr.yaml | 14 ++++- files/data/l10n/OMWEngine/ru.yaml | 14 ++++- files/data/l10n/OMWEngine/sv.yaml | 14 ++++- 15 files changed, 143 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1afca19c6c..21ddd7df26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,7 @@ Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7546: Start the game on Fredas Feature #7568: Uninterruptable scripted music + Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index fb3590a3f0..5ee5b21aa2 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include @@ -440,7 +442,9 @@ void MWState::StateManager::loadGame(const Character* character, const std::file { ESM::SavedGame profile; profile.load(reader); - if (!verifyProfile(profile)) + const auto& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); + auto missingFiles = profile.getMissingContentFiles(selectedContentFiles); + if (!missingFiles.empty() && !confirmLoading(missingFiles)) { cleanup(true); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); @@ -668,30 +672,64 @@ void MWState::StateManager::update(float duration) } } -bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const +bool MWState::StateManager::confirmLoading(const std::vector& missingFiles) const { - const std::vector& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); - bool notFound = false; - for (const std::string& contentFile : profile.mContentFiles) + std::ostringstream stream; + for (auto& contentFile : missingFiles) { - if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), contentFile) - == selectedContentFiles.end()) + Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing."; + stream << contentFile << "\n"; + } + + auto fullList = stream.str(); + if (!fullList.empty()) + fullList.pop_back(); + + constexpr size_t missingPluginsDisplayLimit = 12; + + std::vector buttons; + buttons.emplace_back("#{Interface:Yes}"); + buttons.emplace_back("#{Interface:Copy}"); + buttons.emplace_back("#{Interface:No}"); + std::string message = "#{OMWEngine:MissingContentFilesConfirmation}"; + + auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); + message += l10n->formatMessage("MissingContentFilesList", { "files" }, { static_cast(missingFiles.size()) }); + auto cappedSize = std::min(missingFiles.size(), missingPluginsDisplayLimit); + if (cappedSize == missingFiles.size()) + { + message += fullList; + } + else + { + for (size_t i = 0; i < cappedSize - 1; ++i) { - Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing."; - notFound = true; + message += missingFiles[i]; + message += "\n"; } + + message += "..."; } - if (notFound) + + message + += l10n->formatMessage("MissingContentFilesListCopy", { "files" }, { static_cast(missingFiles.size()) }); + + while (true) { - std::vector buttons; - buttons.emplace_back("#{Interface:Yes}"); - buttons.emplace_back("#{Interface:No}"); - MWBase::Environment::get().getWindowManager()->interactiveMessageBox( - "#{OMWEngine:MissingContentFilesConfirmation}", buttons, true); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true); int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - if (selectedButton == 1 || selectedButton == -1) - return false; + if (selectedButton == 0) + break; + + if (selectedButton == 1) + { + SDL_SetClipboardText(fullList.c_str()); + continue; + } + + return false; } + return true; } diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index df62ca7ebf..c293209f34 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -21,7 +21,7 @@ namespace MWState private: void cleanup(bool force = false); - bool verifyProfile(const ESM::SavedGame& profile) const; + bool confirmLoading(const std::vector& missingFiles) const; void writeScreenshot(std::vector& imageData) const; diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index e84cb27ad8..cec2b5e189 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -61,4 +61,18 @@ namespace ESM esm.writeHNT("MHLT", mMaximumHealth); } + std::vector SavedGame::getMissingContentFiles( + const std::vector& allContentFiles) const + { + std::vector missingFiles; + for (const std::string& contentFile : mContentFiles) + { + if (std::find(allContentFiles.begin(), allContentFiles.end(), contentFile) == allContentFiles.end()) + { + missingFiles.emplace_back(contentFile); + } + } + + return missingFiles; + } } diff --git a/components/esm3/savedgame.hpp b/components/esm3/savedgame.hpp index 4632e98927..f174340203 100644 --- a/components/esm3/savedgame.hpp +++ b/components/esm3/savedgame.hpp @@ -40,6 +40,8 @@ namespace ESM void load(ESMReader& esm); void save(ESMWriter& esm) const; + + std::vector getMissingContentFiles(const std::vector& allContentFiles) const; }; } diff --git a/files/data/l10n/Interface/de.yaml b/files/data/l10n/Interface/de.yaml index 1cabad01a9..ac1a95a0ea 100644 --- a/files/data/l10n/Interface/de.yaml +++ b/files/data/l10n/Interface/de.yaml @@ -25,3 +25,4 @@ Yes: "Ja" #OK: "OK" #Off: "Off" #On: "On" +#Copy: "Copy" diff --git a/files/data/l10n/Interface/en.yaml b/files/data/l10n/Interface/en.yaml index df450b5c38..82c1aeba1a 100644 --- a/files/data/l10n/Interface/en.yaml +++ b/files/data/l10n/Interface/en.yaml @@ -22,3 +22,4 @@ None: "None" OK: "OK" Cancel: "Cancel" Close: "Close" +Copy: "Copy" diff --git a/files/data/l10n/Interface/fr.yaml b/files/data/l10n/Interface/fr.yaml index 5aa0260680..bac4346364 100644 --- a/files/data/l10n/Interface/fr.yaml +++ b/files/data/l10n/Interface/fr.yaml @@ -22,3 +22,4 @@ None: "Aucun" OK: "Valider" Cancel: "Annuler" Close: "Fermer" +#Copy: "Copy" diff --git a/files/data/l10n/Interface/ru.yaml b/files/data/l10n/Interface/ru.yaml index 6d81dd7797..44b38a77b8 100644 --- a/files/data/l10n/Interface/ru.yaml +++ b/files/data/l10n/Interface/ru.yaml @@ -1,5 +1,6 @@ Cancel: "Отмена" Close: "Закрыть" +Copy: "Скопировать" DurationDay: "{days} д " DurationHour: "{hours} ч " DurationMinute: "{minutes} мин " diff --git a/files/data/l10n/Interface/sv.yaml b/files/data/l10n/Interface/sv.yaml index 5e9260cf97..aae63a1941 100644 --- a/files/data/l10n/Interface/sv.yaml +++ b/files/data/l10n/Interface/sv.yaml @@ -14,3 +14,4 @@ Off: "Av" On: "På" Reset: "Återställ" Yes: "Ja" +#Copy: "Copy" diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index f2b4ee7e5a..26838bd93c 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -41,10 +41,22 @@ TimePlayed: "Spielzeit" #DeleteGameConfirmation: "Are you sure you want to delete this saved game?" #EmptySaveNameError: "Game can not be saved without a name!" #LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" -#MissingContentFilesConfirmation: | +#MissingContentFilesConfirmation: |- # The currently selected content files do not match the ones used by this save game. # Errors may occur during load or game play. # Do you wish to continue? +#MissingContentFilesList: |- +# {files, plural, +# one{\n\nFound missing file: } +# few{\n\nFound {files} missing files:\n} +# other{\n\nFound {files} missing files:\n} +# } +#MissingContentFilesListCopy: |- +# {files, plural, +# one{\n\nPress Copy to place its name to the clipboard.} +# few{\n\nPress Copy to place their names to the clipboard.} +# other{\n\nPress Copy to place their names to the clipboard.} +# } #OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index 08df886f18..ee2a33ee71 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -34,10 +34,22 @@ DeleteGame: "Delete Game" DeleteGameConfirmation: "Are you sure you want to delete this saved game?" EmptySaveNameError: "Game can not be saved without a name!" LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" -MissingContentFilesConfirmation: | +MissingContentFilesConfirmation: |- The currently selected content files do not match the ones used by this save game. Errors may occur during load or game play. Do you wish to continue? +MissingContentFilesList: |- + {files, plural, + one{\n\nFound missing file: } + few{\n\nFound {files} missing files:\n} + other{\n\nFound {files} missing files:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nPress Copy to place its name to the clipboard.} + few{\n\nPress Copy to place their names to the clipboard.} + other{\n\nPress Copy to place their names to the clipboard.} + } OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" SelectCharacter: "Select Character..." TimePlayed: "Time played" diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index 5a6209b44c..689ccc59a5 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -37,10 +37,22 @@ DeleteGame: "Supprimer la partie" DeleteGameConfirmation: "Voulez-vous réellement supprimer cette partie sauvegardée ?" EmptySaveNameError: "Impossible de sauvegarder une partie lui donner un nom !" LoadGameConfirmation: "Voulez-vous charger cette autre partie ? Toute progression non sauvegardée sera perdue." -MissingContentFilesConfirmation: | +MissingContentFilesConfirmation: |- Les données de jeu actuellement sélectionnées ne correspondent pas à celle indiquée dans cette sauvegarde. Cela peut entraîner des erreurs lors du chargement, mais aussi lors de votre partie. Voulez-vous continuer ? +#MissingContentFilesList: |- +# {files, plural, +# one{\n\nFound missing file: } +# few{\n\nFound {files} missing files:\n} +# other{\n\nFound {files} missing files:\n} +# } +#MissingContentFilesListCopy: |- +# {files, plural, +# one{\n\nPress Copy to place its name to the clipboard.} +# few{\n\nPress Copy to place their names to the clipboard.} +# other{\n\nPress Copy to place their names to the clipboard.} +# } OverwriteGameConfirmation: "Écraser la sauvegarde précédente ?" diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index cbc71f91e4..b645b681b1 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -34,10 +34,22 @@ DeleteGame: "Удалить игру" DeleteGameConfirmation: "Вы уверены, что хотите удалить это сохранение?" EmptySaveNameError: "Имя сохранения не может быть пустым!" LoadGameConfirmation: "Вы хотите загрузить сохранение? Текущая игра будет потеряна." -MissingContentFilesConfirmation: | +MissingContentFilesConfirmation: |- Выбранные ESM/ESP файлы не соответствуют тем, которые использовались для этого сохранения. Во время загрузки или в процессе игры могут возникнуть ошибки. Вы хотите продолжить? +MissingContentFilesList: |- + {files, plural, + one{\n\nОтсутствует файл } + few{\n\nОтсутствуют {files} файла:\n} + other{\n\nОтсутствуют {files} файлов:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nНажмите Скопировать, чтобы поместить его название в буфер обмена.} + few{\n\nНажмите Скопировать, чтобы поместить их названия в буфер обмена.} + other{\n\nНажмите Скопировать, чтобы поместить их названия в буфер обмена.} + } OverwriteGameConfirmation: "Вы уверены, что хотите перезаписать это сохранение?" SelectCharacter: "Выберите персонажа..." TimePlayed: "Время в игре" diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index 1ee8bdc707..b9d2d44076 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -35,10 +35,22 @@ DeleteGame: "Radera spel" DeleteGameConfirmation: "Är du säker på att du vill radera sparfilen?" EmptySaveNameError: "Spelet kan inte sparas utan ett namn!" LoadGameConfirmation: "Vill du ladda ett sparat spel och förlora det pågående spelet?" -MissingContentFilesConfirmation: | +MissingContentFilesConfirmation: |- De valda innehållsfilerna matchar inte filerna som används av denna sparfil. Fel kan uppstå vid laddning eller under spel. Vill du fortsätta? +#MissingContentFilesList: |- +# {files, plural, +# one{\n\nFound missing file: } +# few{\n\nFound {files} missing files:\n} +# other{\n\nFound {files} missing files:\n} +# } +#MissingContentFilesListCopy: |- +# {files, plural, +# one{\n\nPress Copy to place its name to the clipboard.} +# few{\n\nPress Copy to place their names to the clipboard.} +# other{\n\nPress Copy to place their names to the clipboard.} +# } OverwriteGameConfirmation: "Är du säker på att du vill skriva över det här sparade spelet?" SelectCharacter: "Välj spelfigur..." From ff418f16f21c3e2ab8477fbd6428dde53d94eb13 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 18 Nov 2023 16:47:06 +0400 Subject: [PATCH 2/3] Do not wait one frame for blocking messageboxes --- apps/openmw/mwgui/messagebox.cpp | 29 +++++++++++++++++++++---- apps/openmw/mwgui/messagebox.hpp | 10 +++++++-- apps/openmw/mwgui/windowmanagerimp.cpp | 4 +++- apps/openmw/mwstate/statemanagerimp.cpp | 5 +++-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 49d474c826..9d1d98aa4e 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -46,6 +46,20 @@ namespace MWGui mLastButtonPressed = -1; } + void MessageBoxManager::resetInteractiveMessageBox() + { + if (mInterMessageBoxe) + { + mInterMessageBoxe->setVisible(false); + mInterMessageBoxe.reset(); + } + } + + void MessageBoxManager::setLastButtonPressed(int index) + { + mLastButtonPressed = index; + } + void MessageBoxManager::onFrame(float frameDuration) { for (auto it = mMessageBoxes.begin(); it != mMessageBoxes.end();) @@ -112,7 +126,7 @@ namespace MWGui } bool MessageBoxManager::createInteractiveMessageBox( - std::string_view message, const std::vector& buttons) + std::string_view message, const std::vector& buttons, bool immediate) { if (mInterMessageBoxe != nullptr) { @@ -120,7 +134,7 @@ namespace MWGui mInterMessageBoxe->setVisible(false); } - mInterMessageBoxe = std::make_unique(*this, std::string{ message }, buttons); + mInterMessageBoxe = std::make_unique(*this, std::string{ message }, buttons, immediate); mLastButtonPressed = -1; return true; @@ -200,13 +214,14 @@ namespace MWGui mMainWidget->setVisible(value); } - InteractiveMessageBox::InteractiveMessageBox( - MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons) + InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, + const std::vector& buttons, bool immediate) : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) + , mImmediate(immediate) { int textPadding = 10; // padding between text-widget and main-widget int textButtonPadding = 10; // padding between the text-widget und the button-widget @@ -393,6 +408,12 @@ namespace MWGui { mButtonPressed = index; mMessageBoxManager.onButtonPressed(mButtonPressed); + if (!mImmediate) + return; + + mMessageBoxManager.setLastButtonPressed(mButtonPressed); + MWBase::Environment::get().getInputManager()->changeInputMode( + MWBase::Environment::get().getWindowManager()->isGuiMode()); return; } index++; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index b10586549f..742d355c60 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -25,7 +25,8 @@ namespace MWGui void onFrame(float frameDuration); void createMessageBox(std::string_view message, bool stat = false); void removeStaticMessageBox(); - bool createInteractiveMessageBox(std::string_view message, const std::vector& buttons); + bool createInteractiveMessageBox( + std::string_view message, const std::vector& buttons, bool immediate = false); bool isInteractiveMessageBox(); int getMessagesCount(); @@ -40,6 +41,10 @@ namespace MWGui /// @param reset Reset the pressed button to -1 after reading it. int readPressedButton(bool reset = true); + void resetInteractiveMessageBox(); + + void setLastButtonPressed(int index); + typedef MyGUI::delegates::MultiDelegate EventHandle_Int; // Note: this delegate unassigns itself after it was fired, i.e. works once. @@ -88,7 +93,7 @@ namespace MWGui { public: InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, - const std::vector& buttons); + const std::vector& buttons, bool immediate); void mousePressed(MyGUI::Widget* _widget); int readPressedButton(); @@ -107,6 +112,7 @@ namespace MWGui std::vector mButtons; int mButtonPressed; + bool mImmediate; }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1d41bab34f..71c28230a4 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -746,7 +746,7 @@ namespace MWGui void WindowManager::interactiveMessageBox( std::string_view message, const std::vector& buttons, bool block) { - mMessageBoxManager->createInteractiveMessageBox(message, buttons); + mMessageBoxManager->createInteractiveMessageBox(message, buttons, block); updateVisible(); if (block) @@ -779,6 +779,8 @@ namespace MWGui frameRateLimiter.limit(); } + + mMessageBoxManager->resetInteractiveMessageBox(); } } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 5ee5b21aa2..757021805f 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -716,8 +716,9 @@ bool MWState::StateManager::confirmLoading(const std::vector& while (true) { - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true); - int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); + auto windowManager = MWBase::Environment::get().getWindowManager(); + windowManager->interactiveMessageBox(message, buttons, true); + int selectedButton = windowManager->readPressedButton(); if (selectedButton == 0) break; From 9bbb89e2682c76b994ffada36dfe24656f2fda8e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 18 Nov 2023 17:57:15 +0400 Subject: [PATCH 3/3] Allow to set default focus for interactive messagebox --- apps/openmw/mwbase/windowmanager.hpp | 4 ++-- apps/openmw/mwgui/messagebox.cpp | 11 ++++++++--- apps/openmw/mwgui/messagebox.hpp | 7 ++++--- apps/openmw/mwgui/windowmanagerimp.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.hpp | 4 ++-- apps/openmw/mwstate/statemanagerimp.cpp | 5 +++-- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index f225ebf24e..0c60fe9778 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -254,8 +254,8 @@ namespace MWBase = 0; virtual void staticMessageBox(std::string_view message) = 0; virtual void removeStaticMessageBox() = 0; - virtual void interactiveMessageBox( - std::string_view message, const std::vector& buttons = {}, bool block = false) + virtual void interactiveMessageBox(std::string_view message, const std::vector& buttons = {}, + bool block = false, int defaultFocus = -1) = 0; /// returns the index of the pressed button or -1 if no button was pressed diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 9d1d98aa4e..b22fb873fa 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -126,7 +126,7 @@ namespace MWGui } bool MessageBoxManager::createInteractiveMessageBox( - std::string_view message, const std::vector& buttons, bool immediate) + std::string_view message, const std::vector& buttons, bool immediate, int defaultFocus) { if (mInterMessageBoxe != nullptr) { @@ -134,7 +134,8 @@ namespace MWGui mInterMessageBoxe->setVisible(false); } - mInterMessageBoxe = std::make_unique(*this, std::string{ message }, buttons, immediate); + mInterMessageBoxe + = std::make_unique(*this, std::string{ message }, buttons, immediate, defaultFocus); mLastButtonPressed = -1; return true; @@ -215,12 +216,13 @@ namespace MWGui } InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, - const std::vector& buttons, bool immediate) + const std::vector& buttons, bool immediate, int defaultFocus) : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) + , mDefaultFocus(defaultFocus) , mImmediate(immediate) { int textPadding = 10; // padding between text-widget and main-widget @@ -378,6 +380,9 @@ namespace MWGui MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus() { std::vector keywords{ "sOk", "sYes" }; + if (mDefaultFocus >= 0 && mDefaultFocus < static_cast(mButtons.size())) + return mButtons[mDefaultFocus]; + for (MyGUI::Button* button : mButtons) { for (const std::string& keyword : keywords) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 742d355c60..bb61bd6bd9 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -25,8 +25,8 @@ namespace MWGui void onFrame(float frameDuration); void createMessageBox(std::string_view message, bool stat = false); void removeStaticMessageBox(); - bool createInteractiveMessageBox( - std::string_view message, const std::vector& buttons, bool immediate = false); + bool createInteractiveMessageBox(std::string_view message, const std::vector& buttons, + bool immediate = false, int defaultFocus = -1); bool isInteractiveMessageBox(); int getMessagesCount(); @@ -93,7 +93,7 @@ namespace MWGui { public: InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, - const std::vector& buttons, bool immediate); + const std::vector& buttons, bool immediate, int defaultFocus); void mousePressed(MyGUI::Widget* _widget); int readPressedButton(); @@ -112,6 +112,7 @@ namespace MWGui std::vector mButtons; int mButtonPressed; + int mDefaultFocus; bool mImmediate; }; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 71c28230a4..c6a69af2c0 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -744,9 +744,9 @@ namespace MWGui } void WindowManager::interactiveMessageBox( - std::string_view message, const std::vector& buttons, bool block) + std::string_view message, const std::vector& buttons, bool block, int defaultFocus) { - mMessageBoxManager->createInteractiveMessageBox(message, buttons, block); + mMessageBoxManager->createInteractiveMessageBox(message, buttons, block, defaultFocus); updateVisible(); if (block) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 5f6b12b7e5..7ee0554a26 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -268,8 +268,8 @@ namespace MWGui enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override; void staticMessageBox(std::string_view message) override; void removeStaticMessageBox() override; - void interactiveMessageBox( - std::string_view message, const std::vector& buttons = {}, bool block = false) override; + void interactiveMessageBox(std::string_view message, const std::vector& buttons = {}, + bool block = false, int defaultFocus = -1) override; int readPressedButton() override; ///< returns the index of the pressed button or -1 if no button was pressed ///< (->MessageBoxmanager->InteractiveMessageBox) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 757021805f..826c0dbba6 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -714,11 +714,12 @@ bool MWState::StateManager::confirmLoading(const std::vector& message += l10n->formatMessage("MissingContentFilesListCopy", { "files" }, { static_cast(missingFiles.size()) }); + int selectedButton = -1; while (true) { auto windowManager = MWBase::Environment::get().getWindowManager(); - windowManager->interactiveMessageBox(message, buttons, true); - int selectedButton = windowManager->readPressedButton(); + windowManager->interactiveMessageBox(message, buttons, true, selectedButton); + selectedButton = windowManager->readPressedButton(); if (selectedButton == 0) break;