From b23f9c1a002b366dcc430a7586d216e73e2dc3ee Mon Sep 17 00:00:00 2001 From: codereader Date: Wed, 8 Nov 2017 16:43:45 +0100 Subject: [PATCH] WIP commit, trying to move all the path setup logic to the idTech setup page. --- radiant/settings/GameManager.cpp | 58 ++++----- radiant/ui/prefdialog/GameSetupDialog.cpp | 97 +++++++++++--- radiant/ui/prefdialog/GameSetupDialog.h | 18 ++- radiant/ui/prefdialog/GameSetupPage.h | 13 +- radiant/ui/prefdialog/GameSetupPageIdTech.cpp | 121 +++++++++++++++++- radiant/ui/prefdialog/GameSetupPageIdTech.h | 22 +++- 6 files changed, 267 insertions(+), 62 deletions(-) diff --git a/radiant/settings/GameManager.cpp b/radiant/settings/GameManager.cpp index 2d3c25bcbe..b66e32a07f 100644 --- a/radiant/settings/GameManager.cpp +++ b/radiant/settings/GameManager.cpp @@ -59,20 +59,38 @@ const StringSet& Manager::getDependencies() const { void Manager::initialiseModule(const ApplicationContext& ctx) { + // Read command line parameters, these override any existing preference setting + const ApplicationContext::ArgumentList& args = ctx.getCmdLineArgs(); + + for (const std::string& arg : args) + { + if (string::istarts_with(arg, "fs_game=")) + { + GlobalRegistry().set(RKEY_FS_GAME, arg.substr(8)); + } + else if (string::istarts_with(arg, "fs_game_base=")) + { + GlobalRegistry().set(RKEY_FS_GAME_BASE, arg.substr(13)); + } + } + initialise(ctx.getRuntimeDataPath()); initEnginePath(); } -const std::string& Manager::getFSGame() const { - return _fsGame; +const std::string& Manager::getFSGame() const +{ + return GlobalRegistry().get(RKEY_FS_GAME); } -const std::string& Manager::getFSGameBase() const { - return _fsGameBase; +const std::string& Manager::getFSGameBase() const +{ + return GlobalRegistry().get(RKEY_FS_GAME_BASE); } -const std::string& Manager::getModPath() const { +const std::string& Manager::getModPath() const +{ // Return the fs_game path if available return (!_modPath.empty()) ? _modPath : _modBasePath; } @@ -125,7 +143,7 @@ void Manager::initialise(const std::string& appPath) // No game types available, bail out, the program would crash anyway on // module load wxutil::Messagebox::ShowFatalError( - _("GameManager: No valid game files found, can't continue."), NULL + _("GameManager: No valid game files found, can't continue."), nullptr ); } @@ -159,10 +177,10 @@ void Manager::initialise(const std::string& appPath) // The game type should be selected now if (!_currentGameName.empty()) { - rMessage() << "GameManager: Selected game type: " - << _currentGameName << std::endl; + rMessage() << "GameManager: Selected game type: " << _currentGameName << std::endl; } - else { + else + { // No game type selected, bail out, the program would crash anyway on module load wxutil::Messagebox::ShowFatalError(_("No game type selected.")); } @@ -199,28 +217,6 @@ void Manager::constructPaths() // Make sure it's a well formatted path _enginePath = os::standardPathWithSlash(_enginePath); - // Read command line parameters, these override any existing preference setting - const ApplicationContext::ArgumentList& args( - module::ModuleRegistry::Instance().getApplicationContext().getCmdLineArgs() - ); - - for (ApplicationContext::ArgumentList::const_iterator i = args.begin(); - i != args.end(); - ++i) - { - // get the argument and investigate it - std::string arg = *i; - - if (string::istarts_with(arg, "fs_game=")) - { - GlobalRegistry().set(RKEY_FS_GAME, arg.substr(8)); - } - else if (string::istarts_with(arg, "fs_game_base=")) - { - GlobalRegistry().set(RKEY_FS_GAME_BASE, arg.substr(13)); - } - } - // Load the fsGame and fsGameBase from the registry _fsGame = GlobalRegistry().get(RKEY_FS_GAME); _fsGameBase = GlobalRegistry().get(RKEY_FS_GAME_BASE); diff --git a/radiant/ui/prefdialog/GameSetupDialog.cpp b/radiant/ui/prefdialog/GameSetupDialog.cpp index c224bc0a01..2b07abf19a 100644 --- a/radiant/ui/prefdialog/GameSetupDialog.cpp +++ b/radiant/ui/prefdialog/GameSetupDialog.cpp @@ -5,6 +5,7 @@ #include "igame.h" #include "modulesystem/ModuleRegistry.h" +#include "wxutil/dialog/MessageBox.h" #include "registry/registry.h" #include "GameSetupPageIdTech.h" #include @@ -31,7 +32,20 @@ GameSetupDialog::GameSetupDialog(wxWindow* parent) : mainVbox->Add(label); mainVbox->Add(_book, 1, wxEXPAND); - mainVbox->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_RIGHT); + wxBoxSizer* buttonHBox = new wxBoxSizer(wxHORIZONTAL); + + // Create the Save button + wxButton* saveButton = new wxButton(this, wxID_SAVE); + saveButton->Connect(wxEVT_BUTTON, wxCommandEventHandler(GameSetupDialog::onSave), nullptr, this); + + // Create the assign shortcut button + wxButton* cancelButton = new wxButton(this, wxID_CANCEL); + cancelButton->Connect(wxEVT_BUTTON, wxCommandEventHandler(GameSetupDialog::onCancel), NULL, this); + + buttonHBox->Add(saveButton, 0, wxRIGHT, 6); + buttonHBox->Add(cancelButton, 0, wxRIGHT, 6); + + mainVbox->Add(buttonHBox, 0, wxALIGN_RIGHT | wxTOP | wxBOTTOM, 12); initialiseControls(); @@ -76,29 +90,66 @@ void GameSetupDialog::initialiseControls() } } -void GameSetupDialog::save() +GameSetupPage* GameSetupDialog::getSelectedPage() +{ + // Extract the game type value from the current page and save it to the registry + wxWindow* container = _book->GetPage(_book->GetSelection()); + return dynamic_cast(wxWindow::FindWindowByName("GameSetupPage", container)); +} + +std::string GameSetupDialog::getSelectedGameType() { - if (_book->GetSelection() == wxNOT_FOUND) + // Extract the game type value from the current page and save it to the registry + GameSetupPage* page = getSelectedPage(); + + if (page == nullptr) return std::string(); + + wxStringClientData* data = static_cast(page->GetClientData()); + + return data->GetData().ToStdString(); +} + +void GameSetupDialog::onSave(wxCommandEvent& ev) +{ + if (getSelectedGameType().empty()) { - rError() << "Cannot save game type, nothing selected" << std::endl; + // Ask the user to select a game type + wxutil::Messagebox::Show(_("Invalid Settings"), + _("Please select a game type"), ui::IDialog::MESSAGE_CONFIRM, nullptr); return; } - // Extract the game type value from the current page and save it to the registry - wxWindow* container = _book->GetPage(_book->GetSelection()); - GameSetupPage* page = dynamic_cast(wxWindow::FindWindowByName("GameSetupPage", container)); + GameSetupPage* page = GameSetupDialog::getSelectedPage(); + assert(page != nullptr); - wxStringClientData* data = static_cast(page->GetClientData()); + try + { + page->validateSettings(); + } + catch (GameSettingsInvalidException& ex) + { + std::string msg = fmt::format(_("Warning:\n{0}\nDo you want to correct these settings?"), ex.what()); - std::string selectedGame = data->GetData().ToStdString(); - registry::setValue(RKEY_GAME_TYPE, selectedGame); + if (wxutil::Messagebox::Show(_("Invalid Settings"), + msg, ui::IDialog::MESSAGE_ASK, nullptr) == wxutil::Messagebox::RESULT_YES) + { + // User wants to correct the settings, don't exit + return; + } + } + + EndModal(wxID_OK); +} - // Ask the current page to set the paths to the registry - page->saveSettings(); +void GameSetupDialog::onCancel(wxCommandEvent& ev) +{ + EndModal(wxID_CANCEL); } -void GameSetupDialog::Show(const cmd::ArgumentList& args) +GameSetupDialog::Result GameSetupDialog::Show(const cmd::ArgumentList& args) { + GameSetupDialog::Result result; + // greebo: Check if the mainframe module is already "existing". It might be // uninitialised if this dialog is shown during DarkRadiant startup wxWindow* parent = module::GlobalModuleRegistry().moduleExists(MODULE_MAINFRAME) ? @@ -106,14 +157,26 @@ void GameSetupDialog::Show(const cmd::ArgumentList& args) GameSetupDialog* dialog = new GameSetupDialog(parent); - int result = dialog->ShowModal(); - - if (result == wxID_OK) + if (dialog->ShowModal() == wxID_OK) { - dialog->save(); + result.gameType = dialog->getSelectedGameType(); + + if (result.gameType.empty()) + { + rError() << "Cannot save game paths, nothing selected" << std::endl; + return result; + } + + GameSetupPage* page = dialog->getSelectedPage(); + + result.enginePath = page->getEnginePath(); + result.modPath = page->getModPath(); + result.modBasePath = page->getModBasePath(); } dialog->Destroy(); + + return result; } } diff --git a/radiant/ui/prefdialog/GameSetupDialog.h b/radiant/ui/prefdialog/GameSetupDialog.h index e24547386c..8e972d0ce8 100644 --- a/radiant/ui/prefdialog/GameSetupDialog.h +++ b/radiant/ui/prefdialog/GameSetupDialog.h @@ -30,14 +30,28 @@ class GameSetupDialog : GameSetupDialog(wxWindow* parent); public: + std::string getSelectedGameType(); + + // The result after the user is done with the dialog + struct Result + { + std::string gameType; // Display name of the selected game + std::string enginePath; // selected engine path + std::string modPath; // selected mod path + std::string modBasePath; // selected mod base path + }; + /** greebo: The command target to show the Game settings preferences. */ - static void Show(const cmd::ArgumentList& args); + static Result Show(const cmd::ArgumentList& args); private: + GameSetupPage* getSelectedPage(); + void initialiseControls(); - void save(); + void onSave(wxCommandEvent& ev); + void onCancel(wxCommandEvent& ev); }; } diff --git a/radiant/ui/prefdialog/GameSetupPage.h b/radiant/ui/prefdialog/GameSetupPage.h index 0d39d5f8ef..778e96e5a2 100644 --- a/radiant/ui/prefdialog/GameSetupPage.h +++ b/radiant/ui/prefdialog/GameSetupPage.h @@ -39,8 +39,17 @@ class GameSetupPage : // GameSettingsInvalidException in case something is not correct. virtual void validateSettings() = 0; - // Saves the settings to the registry - virtual void saveSettings() = 0; + // The following three path accessors are needed by the owning GameManager + // to continue setting up the VFS search order, map paths, etc. + + // Returns the engine path as derived from the user's input + virtual std::string getEnginePath() = 0; + + // Returns the mod base path as derived from the user's input + virtual std::string getModBasePath() = 0; + + // Returns the mod path as derived from the user's input + virtual std::string getModPath() = 0; public: typedef std::function CreateInstanceFunc; diff --git a/radiant/ui/prefdialog/GameSetupPageIdTech.cpp b/radiant/ui/prefdialog/GameSetupPageIdTech.cpp index 1040803d9b..a497bf39a0 100644 --- a/radiant/ui/prefdialog/GameSetupPageIdTech.cpp +++ b/radiant/ui/prefdialog/GameSetupPageIdTech.cpp @@ -4,30 +4,42 @@ #include "imodule.h" #include "igame.h" +#include "wxutil/PathEntry.h" + +#include #include #include #include #include +#include "os/file.h" #include "registry/Widgets.h" namespace ui { GameSetupPageIdTech::GameSetupPageIdTech(wxWindow* parent) : - GameSetupPage(parent) + GameSetupPage(parent), + _fsGameEntry(nullptr), + _fsGameBaseEntry(nullptr), + _enginePathEntry(nullptr) { wxFlexGridSizer* table = new wxFlexGridSizer(3, 2, wxSize(6, 6)); this->SetSizer(table); + _enginePathEntry = createPathEntry(RKEY_ENGINE_PATH); table->Add(new wxStaticText(this, wxID_ANY, _("Engine Path")), 0, wxALIGN_CENTRE_VERTICAL); - table->Add(createEntry(RKEY_ENGINE_PATH), 0); + table->Add(_enginePathEntry, 0); + _fsGameEntry = createEntry(RKEY_FS_GAME); table->Add(new wxStaticText(this, wxID_ANY, _("Mod (fs_game)")), 0, wxALIGN_CENTRE_VERTICAL); - table->Add(createEntry(RKEY_FS_GAME), 0); + table->Add(_fsGameEntry, 0); + _fsGameBaseEntry = createEntry(RKEY_FS_GAME_BASE); table->Add(new wxStaticText(this, wxID_ANY, _("Mod Base (fs_game_base, optional)")), 0, wxALIGN_CENTRE_VERTICAL); - table->Add(createEntry(RKEY_FS_GAME_BASE), 0); + table->Add(_fsGameBaseEntry, 0); + + // TODO: Derive some default values for the paths unless they're already set } const char* GameSetupPageIdTech::TYPE() @@ -42,15 +54,92 @@ const char* GameSetupPageIdTech::getType() void GameSetupPageIdTech::validateSettings() { + constructPaths(); + + std::string errorMsg; + + if (!os::fileOrDirExists(_enginePath)) + { + // Engine path doesn't exist + errorMsg += fmt::format(_("Engine path \"{0}\" does not exist.\n"), _enginePath); + } + + // Check the mod base path, if not empty + if (!_modBasePath.empty() && !os::fileOrDirExists(_modBasePath)) + { + // Mod base name is not empty, but folder doesnt' exist + errorMsg += fmt::format(_("The mod base path \"{0}\" does not exist.\n"), _modBasePath); + } + + // Check the mod path, if not empty + if (!_modPath.empty() && !os::fileOrDirExists(_modPath)) + { + // Mod name is not empty, but mod folder doesnt' exist + errorMsg += fmt::format(_("The mod path \"{0}\" does not exist.\n"), _modPath); + } + + if (!errorMsg.empty()) + { + throw GameSettingsInvalidException(errorMsg); + } +} + +std::string GameSetupPageIdTech::getEnginePath() +{ + return _enginePath; +} +std::string GameSetupPageIdTech::getModBasePath() +{ + return _modBasePath; +} + +std::string GameSetupPageIdTech::getModPath() +{ + return _modPath; } -void GameSetupPageIdTech::saveSettings() +void GameSetupPageIdTech::constructPaths() { - _registryBuffer.commitChanges(); + _enginePath = _enginePathEntry->getEntryWidget()->GetValue().ToStdString(); + + // Make sure it's a well formatted path + _enginePath = os::standardPathWithSlash(_enginePath); + + // Load the fsGame and fsGameBase from the registry + std::string fsGame = _fsGameEntry->GetValue().ToStdString(); + std::string fsGameBase = _fsGameBaseEntry->GetValue().ToStdString(); + + if (!fsGameBase.empty()) + { + // greebo: #3480 check if the mod base path is absolute. If not, append it to the engine path + _modBasePath = fs::path(fsGameBase).is_absolute() ? fsGameBase : _enginePath + fsGameBase; + + // Normalise the path as last step + _modBasePath = os::standardPathWithSlash(_modBasePath); + } + else + { + // No fs_game_base, no mod base path + _modBasePath = ""; + } + + if (!fsGame.empty()) + { + // greebo: #3480 check if the mod path is absolute. If not, append it to the engine path + _modPath = fs::path(fsGame).is_absolute() ? fsGame : _enginePath + fsGame; + + // Normalise the path as last step + _modPath = os::standardPathWithSlash(_modPath); + } + else + { + // No fs_game, no modpath + _modPath = ""; + } } -wxWindow* GameSetupPageIdTech::createEntry(const std::string& registryKey) +wxTextCtrl* GameSetupPageIdTech::createEntry(const std::string& registryKey) { wxTextCtrl* entryWidget = new wxTextCtrl(this, wxID_ANY); @@ -63,4 +152,22 @@ wxWindow* GameSetupPageIdTech::createEntry(const std::string& registryKey) return entryWidget; } +wxutil::PathEntry* GameSetupPageIdTech::createPathEntry(const std::string& registryKey) +{ + wxutil::PathEntry* entry = new wxutil::PathEntry(this, true); + + // Connect the registry key to the newly created input field + registry::bindWidgetToBufferedKey(entry->getEntryWidget(), registryKey, _registryBuffer, _resetValuesSignal); + + int minChars = static_cast(std::max(GlobalRegistry().get(registryKey).size(), std::size_t(30))); + + entry->getEntryWidget()->SetMinClientSize( + wxSize(entry->getEntryWidget()->GetCharWidth() * minChars, -1)); + + // Initialize entry + entry->setValue(registry::getValue(registryKey)); + + return entry; +} + } diff --git a/radiant/ui/prefdialog/GameSetupPageIdTech.h b/radiant/ui/prefdialog/GameSetupPageIdTech.h index f2620b7c14..1be93ae0a0 100644 --- a/radiant/ui/prefdialog/GameSetupPageIdTech.h +++ b/radiant/ui/prefdialog/GameSetupPageIdTech.h @@ -3,6 +3,9 @@ #include "GameSetupPage.h" #include "registry/buffer.h" +class wxTextCtrl; +namespace wxutil { class PathEntry; } + namespace ui { @@ -21,18 +24,31 @@ class GameSetupPageIdTech : // when emitted, the widgets reload the values from the registry. sigc::signal _resetValuesSignal; + wxTextCtrl* _fsGameEntry; + wxTextCtrl* _fsGameBaseEntry; + wxutil::PathEntry* _enginePathEntry; + + std::string _enginePath; + std::string _modBasePath; + std::string _modPath; + public: GameSetupPageIdTech(wxWindow* parent); static const char* TYPE(); const char* getType() override; - void validateSettings() override; - void saveSettings() override; + + std::string getEnginePath() override; + std::string getModBasePath() override; + std::string getModPath() override; private: - wxWindow* createEntry(const std::string& registryKey); + void constructPaths(); + + wxTextCtrl* createEntry(const std::string& registryKey); + wxutil::PathEntry* createPathEntry(const std::string& registryKey); }; }