From 5513b273c679b9edda951f418ef013a762ca587a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 04:53:20 -0400 Subject: added empty instance manager dialog --- src/instancemanagerdialog.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/instancemanagerdialog.h (limited to 'src/instancemanagerdialog.h') diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h new file mode 100644 index 00000000..ea1cfc48 --- /dev/null +++ b/src/instancemanagerdialog.h @@ -0,0 +1,20 @@ +#ifndef MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED +#define MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED + +#include + +namespace Ui { class InstanceManagerDialog; }; + +class InstanceManagerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit InstanceManagerDialog(QWidget *parent = nullptr); + ~InstanceManagerDialog(); + +private: + std::unique_ptr ui; +}; + +#endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED -- cgit v1.3.1 From 60b59ddf097fffa846a4d28e0d9256630da5149c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 07:32:06 -0400 Subject: started create instance dialog allow for multiple instances of Settings fill in information in instance manager --- src/CMakeLists.txt | 1 + src/createinstancedialog.cpp | 176 ++++++++++++ src/createinstancedialog.h | 30 ++ src/createinstancedialog.ui | 655 ++++++++++++++++++++++++++++++++++++++++++ src/instancemanager.h | 2 +- src/instancemanagerdialog.cpp | 102 ++++++- src/instancemanagerdialog.h | 16 +- src/instancemanagerdialog.ui | 116 ++++---- src/main.cpp | 9 + src/settings.cpp | 19 +- src/settings.h | 1 + 11 files changed, 1064 insertions(+), 63 deletions(-) create mode 100644 src/createinstancedialog.cpp create mode 100644 src/createinstancedialog.h create mode 100644 src/createinstancedialog.ui (limited to 'src/instancemanagerdialog.h') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1f048be8..af350584 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -84,6 +84,7 @@ add_filter(NAME src/executables GROUPS ) add_filter(NAME src/instances GROUPS + createinstancedialog instancemanager instancemanagerdialog ) diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp new file mode 100644 index 00000000..d43fba1c --- /dev/null +++ b/src/createinstancedialog.cpp @@ -0,0 +1,176 @@ +#include "createinstancedialog.h" +#include "ui_createinstancedialog.h" +#include "instancemanager.h" + +namespace cid +{ + +class Page +{ +public: + Page(CreateInstanceDialog& dlg, std::size_t i) + : ui(dlg.getUI()), m_dlg(dlg), m_index(i) + { + } + + virtual bool ready() const + { + return true; + } + + void next() + { + m_dlg.next(); + } + +protected: + Ui::CreateInstanceDialog* ui; + +private: + CreateInstanceDialog& m_dlg; + std::size_t m_index; +}; + +class TypePage : public Page +{ +public: + TypePage(CreateInstanceDialog& dlg, std::size_t i) + : Page(dlg, i) + { + ui->createGlobal->setDescription( + ui->createGlobal->description() + .arg(InstanceManager::instance().instancesPath())); + + ui->createPortable->setDescription( + ui->createPortable->description() + .arg(qApp->applicationDirPath())); + + QObject::connect( + ui->createGlobal, &QAbstractButton::clicked, [&]{ global(); }); + + QObject::connect( + ui->createPortable, &QAbstractButton::clicked, [&]{ portable(); }); + } + + bool ready() const override + { + return m_global.has_value(); + } + + void global() + { + m_global = true; + + ui->createGlobal->setChecked(true); + ui->createPortable->setChecked(false); + + next(); + } + + void portable() + { + m_global = false; + + ui->createGlobal->setChecked(false); + ui->createPortable->setChecked(true); + + next(); + } + +private: + std::optional m_global; +}; + + +class GamePage : public Page +{ +public: + GamePage(CreateInstanceDialog& dlg, std::size_t i) + : Page(dlg, i) + { + } +}; + + +class NamePage : public Page +{ +public: + NamePage(CreateInstanceDialog& dlg, std::size_t i) + : Page(dlg, i) + { + } +}; + + +class PathsPage : public Page +{ +public: + PathsPage(CreateInstanceDialog& dlg, std::size_t i) + : Page(dlg, i) + { + } +}; + +} // namespace + + +CreateInstanceDialog::CreateInstanceDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::CreateInstanceDialog) +{ + using namespace cid; + + ui->setupUi(this); + + m_pages.push_back(std::make_unique(*this, 0)); + m_pages.push_back(std::make_unique(*this, 1)); + m_pages.push_back(std::make_unique(*this, 2)); + m_pages.push_back(std::make_unique(*this, 3)); + + ui->pages->setCurrentIndex(0); + + updateNavigationButtons(); + + connect(ui->next, &QPushButton::clicked, [&]{ next(); }); + connect(ui->back, &QPushButton::clicked, [&]{ back(); }); + + // + //SelectionDialog games(tr("Select a game to manage.")); + // + //for (auto* game : m_pc.plugins()) { + // if (game->isInstalled()) { + // games.addChoice(game->gameName(), game->gameDirectory().path(), {}); + // } else { + // games.addChoice(game->gameName(), "", {}); + // } + //} + // + //games.exec(); +} + +CreateInstanceDialog::~CreateInstanceDialog() = default; + +Ui::CreateInstanceDialog* CreateInstanceDialog::getUI() +{ + return ui.get(); +} + +void CreateInstanceDialog::next() +{ + ui->pages->setCurrentIndex(ui->pages->currentIndex() + 1); + updateNavigationButtons(); +} + +void CreateInstanceDialog::back() +{ + ui->pages->setCurrentIndex(ui->pages->currentIndex() - 1); + updateNavigationButtons(); +} + +void CreateInstanceDialog::updateNavigationButtons() +{ + const auto i = ui->pages->currentIndex(); + const auto last = (i == (ui->pages->count() - 1)); + + ui->next->setEnabled(m_pages[i]->ready() && !last); + ui->back->setEnabled(i > 0); +} diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h new file mode 100644 index 00000000..03e9de01 --- /dev/null +++ b/src/createinstancedialog.h @@ -0,0 +1,30 @@ +#ifndef MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED +#define MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED + +#include + +namespace Ui { class CreateInstanceDialog; }; +namespace cid { class Page; } + +class CreateInstanceDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CreateInstanceDialog(QWidget *parent = nullptr); + + ~CreateInstanceDialog(); + + Ui::CreateInstanceDialog* getUI(); + + void next(); + void back(); + +private: + std::unique_ptr ui; + std::vector> m_pages; + + void updateNavigationButtons(); +}; + +#endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui new file mode 100644 index 00000000..bcfddb20 --- /dev/null +++ b/src/createinstancedialog.ui @@ -0,0 +1,655 @@ + + + CreateInstanceDialog + + + + 0 + 0 + 493 + 423 + + + + Creating an instance + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h2>Creating a new instance</h2> + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + 0 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Select the type of instance to create.</h3> + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Create a global instance + + + true + + + Global instances are stored in %1, but some paths can be changed to be on a different drive if necessary. A single installation of Mod Organizer can manage multiple global instances. + + + + + + + Create a portable instance + + + true + + + A portable instance stores everything in Mod Organizer's installation folder, currently %1. There can only be one portable instance per installation of Mod Organizer. + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Select the game to manage.</h3> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Show all supported games + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Pick a name for this instance.</h3> + + + + + + + + + + + 0 + + + + + Instance name + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Select a folder where the data should be stored.</h3> + + + + + + + This includes downloads, mods, profiles and overwrite. If there is enough space on this drive, you should use the default folder. + + + true + + + + + + + + + + + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + ... + + + + + + + Location + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Overwrite + + + + + + + Mods + + + + + + + Base directory + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Profiles + + + + + + + + + + Downloads + + + + + + + + + + + + + + + + + + + Use <code>%BASE_DIR%</code> to refer to the Base Directory. + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 292 + 20 + + + + + + + + Show advanced options + + + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + < Back + + + + + + + Next > + + + true + + + + + + + Cancel + + + + + + + + + + + diff --git a/src/instancemanager.h b/src/instancemanager.h index 8bbbbee9..d53f4391 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -49,6 +49,7 @@ public: bool allowedToChangeInstance() const; static bool isPortablePath(const QString& dataPath); + QString instancesPath() const; QStringList instanceNames() const; std::vector instancePaths() const; @@ -56,7 +57,6 @@ private: InstanceManager(); - QString instancesPath() const; QString instancePath(const QString& instanceName) const; bool deleteLocalInstance(const QString &instanceId) const; diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 1b482099..c7042aa8 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -1,12 +1,19 @@ #include "instancemanagerdialog.h" #include "ui_instancemanagerdialog.h" #include "instancemanager.h" +#include "createinstancedialog.h" +#include "settings.h" +#include "selectiondialog.h" +#include "plugincontainer.h" +#include "shared/appconfig.h" +#include class InstanceInfo { public: - InstanceInfo(QDir dir) - : m_dir(std::move(dir)) + InstanceInfo(QDir dir) : + m_dir(std::move(dir)), + m_settings(dir.filePath(QString::fromStdWString(AppConfig::iniFileName()))) { } @@ -15,22 +22,105 @@ public: return m_dir.dirName(); } + QString gameName() const + { + if (auto n=m_settings.game().name()) { + if (auto e=m_settings.game().edition()) { + if (!e->isEmpty()) { + return *n + " (" + *e + ")"; + } + } + + return *n; + } else { + return {}; + } + } + + QString gamePath() const + { + if (auto n=m_settings.game().directory()) { + return *n; + } else { + return {}; + } + } + + QString location() const + { + return m_dir.path(); + } + + QString baseDirectory() const + { + return m_settings.paths().base(); + } + private: QDir m_dir; + Settings m_settings; }; -InstanceManagerDialog::InstanceManagerDialog(QWidget *parent) - : QDialog(parent), ui(new Ui::InstanceManagerDialog) +InstanceManagerDialog::InstanceManagerDialog( + const PluginContainer& pc, QWidget *parent) + : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc) { ui->setupUi(this); + ui->splitter->setSizes({200, 1}); + ui->splitter->setStretchFactor(0, 0); + ui->splitter->setStretchFactor(1, 1); auto& m = InstanceManager::instance(); for (auto&& d : m.instancePaths()) { - InstanceInfo i(d); - ui->list->addItem(i.name()); + auto ii = std::make_unique(d); + + ui->list->addItem(ii->name()); + m_instances.push_back(std::move(ii)); + } + + if (!m_instances.empty()) { + select(0); } + + connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); + connect(ui->list, &QListWidget::itemSelectionChanged, [&]{ onSelection(); }); } InstanceManagerDialog::~InstanceManagerDialog() = default; + +void InstanceManagerDialog::select(std::size_t i) +{ + if (i >= m_instances.size()) { + return; + } + + const auto& ii = m_instances[i]; + fill(*ii); +} + +void InstanceManagerDialog::onSelection() +{ + const auto sel = ui->list->selectionModel()->selectedIndexes(); + if (sel.size() != 1) { + return; + } + + select(static_cast(sel[0].row())); +} + +void InstanceManagerDialog::createNew() +{ + CreateInstanceDialog dlg(this); + dlg.exec(); +} + +void InstanceManagerDialog::fill(const InstanceInfo& ii) +{ + ui->name->setText(ii.name()); + ui->location->setText(ii.location()); + ui->baseDirectory->setText(ii.baseDirectory()); + ui->gameName->setText(ii.gameName()); + ui->gameDir->setText(ii.gamePath()); +} diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index ea1cfc48..c3e947dd 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -5,16 +5,30 @@ namespace Ui { class InstanceManagerDialog; }; +class InstanceInfo; +class PluginContainer; + class InstanceManagerDialog : public QDialog { Q_OBJECT public: - explicit InstanceManagerDialog(QWidget *parent = nullptr); + explicit InstanceManagerDialog( + const PluginContainer& pc, QWidget *parent = nullptr); + ~InstanceManagerDialog(); + void select(std::size_t i); + private: std::unique_ptr ui; + const PluginContainer& m_pc; + std::vector> m_instances; + + void onSelection(); + void createNew(); + + void fill(const InstanceInfo& ii); }; #endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index b35d2e58..146b5fb5 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -6,7 +6,7 @@ 0 0 - 531 + 689 413 @@ -40,17 +40,6 @@ - - - - Create portable instance - - - - :/MO/gui/package:/MO/gui/package - - - @@ -65,9 +54,9 @@ - + - <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance"><span style=" text-decoration: underline; color:#0000ff;">What is an instance?</span></a></p></body></html> + <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance">What is an instance?</a></p></body></html> true @@ -134,7 +123,7 @@ - + 0 @@ -148,33 +137,51 @@ 0 - + + + true + + + + + - instance name + Name - - Qt::TextBrowserInteraction + + + + + + Explore - + - Game: + Game - - - - Name: + + + + true + + + + + + + true - Location: + Location @@ -185,30 +192,24 @@ - - + + - location path - - - Qt::TextBrowserInteraction + Game location - Base folder: + Base folder - - - base folder path - - - Qt::TextBrowserInteraction + + + true @@ -219,20 +220,17 @@ - - - - Explore + + + + true - - + + - game name - - - Qt::TextBrowserInteraction + Explore @@ -292,6 +290,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -371,6 +382,13 @@ + + + LinkLabel + QLabel +
linklabel.h
+
+
diff --git a/src/main.cpp b/src/main.cpp index 08d70dd9..71fb0bbd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "tutorialmanager.h" #include "nxmaccessmanager.h" #include "instancemanager.h" +#include "instancemanagerdialog.h" #include "organizercore.h" #include "env.h" #include "envmodule.h" @@ -298,6 +299,8 @@ int runApplication( try { Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); + settings.setGlobalInstance(); + log::getDefault().setLevel(settings.diagnostics().logLevel()); log::debug("using ini at '{}'", settings.filename()); @@ -429,6 +432,12 @@ int runApplication( tt.stop(); + QTimer::singleShot(std::chrono::milliseconds(1), [&] + { + InstanceManagerDialog dlg(*pluginContainer, &mainWindow); + dlg.exec(); + }); + res = application.exec(); mainWindow.close(); diff --git a/src/settings.cpp b/src/settings.cpp index d37f0d99..43e7a4fa 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -68,17 +68,24 @@ Settings::Settings(const QString& path) : m_Network(m_Settings), m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), m_Interface(m_Settings), m_Diagnostics(m_Settings) { - if (s_Instance != nullptr) { - throw std::runtime_error("second instance of \"Settings\" created"); - } else { - s_Instance = this; - } } Settings::~Settings() { MOBase::QuestionBoxMemory::setCallbacks({}, {}, {}); - s_Instance = nullptr; + + if (s_Instance == this) { + s_Instance = nullptr; + } +} + +void Settings::setGlobalInstance() +{ + if (s_Instance != nullptr) { + throw std::runtime_error("second instance of \"Settings\" created"); + } else { + s_Instance = this; + } } Settings &Settings::instance() diff --git a/src/settings.h b/src/settings.h index a1b30462..84102c72 100644 --- a/src/settings.h +++ b/src/settings.h @@ -681,6 +681,7 @@ public: ~Settings(); static Settings &instance(); + void setGlobalInstance(); // name of the ini file // -- cgit v1.3.1 From a97638249de0a9a4c17dc28805c35df489a64e26 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 Jul 2020 21:07:13 -0400 Subject: moved switch to instance to InstanceManager double-click an instance to switch to it (disabled for now, not sure I want that) --- src/createinstancedialog.cpp | 3 +-- src/instancemanager.cpp | 7 +++++++ src/instancemanager.h | 4 ++++ src/instancemanagerdialog.cpp | 27 ++++++++++++++++++++++++--- src/instancemanagerdialog.h | 4 ++++ 5 files changed, 40 insertions(+), 5 deletions(-) (limited to 'src/instancemanagerdialog.h') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 2edfee88..75ccc774 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -300,8 +300,7 @@ void CreateInstanceDialog::finish() } if (ui->launch->isChecked()) { - InstanceManager::instance().setCurrentInstance(ci.instanceName); - ExitModOrganizer(Exit::Restart); + InstanceManager::instance().switchToInstance(ci.instanceName); } else { close(); } diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index bd35cb47..2c8d8fb3 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "shared/appconfig.h" #include "plugincontainer.h" +#include "shared/util.h" #include #include #include @@ -75,6 +76,12 @@ void InstanceManager::clearCurrentInstance() m_overrideInstance = false; } +void InstanceManager::switchToInstance(const QString& instanceName) +{ + setCurrentInstance(instanceName); + ExitModOrganizer(Exit::Restart); +} + void InstanceManager::setCurrentInstance(const QString &name) { GlobalSettings::setCurrentInstance(name); diff --git a/src/instancemanager.h b/src/instancemanager.h index f53543df..f038678a 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -34,6 +34,10 @@ class InstanceManager public: static InstanceManager &instance(); + // restarts MO + // + void switchToInstance(const QString& instanceName); + void overrideInstance(const QString& instanceName); void overrideProfile(const QString& profileName); diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 26e8eae1..a038d3c2 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -92,6 +92,7 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); connect(ui->list, &QListWidget::itemSelectionChanged, [&]{ onSelection(); }); + //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); } InstanceManagerDialog::~InstanceManagerDialog() = default; @@ -106,14 +107,24 @@ void InstanceManagerDialog::select(std::size_t i) fill(*ii); } +void InstanceManagerDialog::openSelectedInstance() +{ + const auto i = singleSelection(); + if (i == NoSelection) { + return; + } + + InstanceManager::instance().switchToInstance(m_instances[i]->name()); +} + void InstanceManagerDialog::onSelection() { - const auto sel = ui->list->selectionModel()->selectedIndexes(); - if (sel.size() != 1) { + const auto i = singleSelection(); + if (i == NoSelection) { return; } - select(static_cast(sel[0].row())); + select(i); } void InstanceManagerDialog::createNew() @@ -122,6 +133,16 @@ void InstanceManagerDialog::createNew() dlg.exec(); } +std::size_t InstanceManagerDialog::singleSelection() const +{ + const auto sel = ui->list->selectionModel()->selectedIndexes(); + if (sel.size() != 1) { + return NoSelection; + } + + return static_cast(sel[0].row()); +} + void InstanceManagerDialog::fill(const InstanceInfo& ii) { ui->name->setText(ii.name()); diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index c3e947dd..6dbf015f 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -19,8 +19,11 @@ public: ~InstanceManagerDialog(); void select(std::size_t i); + void openSelectedInstance(); private: + static const std::size_t NoSelection = -1; + std::unique_ptr ui; const PluginContainer& m_pc; std::vector> m_instances; @@ -28,6 +31,7 @@ private: void onSelection(); void createNew(); + std::size_t singleSelection() const; void fill(const InstanceInfo& ii); }; -- cgit v1.3.1 From ea4485857c09fd3ab6879966e7377d3c56951a85 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 30 Jul 2020 04:36:12 -0400 Subject: filter, explore buttons, instance rename --- src/createinstancedialog.ui | 21 +++++- src/createinstancedialogpages.cpp | 62 ++------------- src/instancemanager.cpp | 9 +++ src/instancemanager.h | 1 + src/instancemanagerdialog.cpp | 154 ++++++++++++++++++++++++++++++++++++-- src/instancemanagerdialog.h | 12 ++- src/instancemanagerdialog.ui | 27 ++++++- 7 files changed, 221 insertions(+), 65 deletions(-) (limited to 'src/instancemanagerdialog.h') diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index a3c3db92..b7f4f502 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -77,9 +77,12 @@ - + - Never show this again + <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance">More information</a></p></body></html> + + + true @@ -96,6 +99,13 @@ + + + + Never show this page again + + + @@ -1202,6 +1212,13 @@ + + + LinkLabel + QLabel +
linklabel.h
+
+
diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 127243c6..8d304635 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -21,50 +21,6 @@ QString makeDefaultPath(const std::wstring& dir) QString::fromStdWString(dir))); } -QString sanitizeFileName(const QString& name) -{ - QString new_name = name; - - // Restrict the allowed characters - new_name = new_name.remove(QRegExp("[^A-Za-z0-9 _=+;!@#$%^'\\-\\.\\[\\]\\{\\}\\(\\)]")); - - // Don't end in spaces and periods - new_name = new_name.remove(QRegExp("\\.*$")); - new_name = new_name.remove(QRegExp(" *$")); - - // Recurse until stuff stops changing - if (new_name != name) { - return sanitizeFileName(new_name); - } - - return new_name; -} - -// same thing as above, but allows path separators and colons -// -QString sanitizePath(const QString& path) -{ - QString new_name = path; - - // Restrict the allowed characters - new_name = new_name.remove(QRegExp("[^\\\\\\/A-Za-z0-9 _=+;!@#$%^:'\\-\\.\\[\\]\\{\\}\\(\\)]")); - - // Don't end in spaces and periods - new_name = new_name.remove(QRegExp("\\.*$")); - new_name = new_name.remove(QRegExp(" *$")); - - // Recurse until stuff stops changing - if (new_name != path) { - return sanitizeFileName(new_name); - } - - return new_name; -} - -void setPossiblePlaceholder( - QLabel* label, const QString& s, const QString& arg) -{ -} PlaceholderLabel::PlaceholderLabel(QLabel* label) @@ -762,7 +718,7 @@ QString NamePage::selectedInstanceName() const } const auto text = ui->instanceName->text().trimmed(); - return sanitizeFileName(text); + return InstanceManager::instance().sanitizeInstanceName(text); } void NamePage::onChanged() @@ -790,12 +746,10 @@ bool NamePage::checkName(QString parentDir, QString name) if (name.isEmpty()) { empty = true; } else { - const QString sanitized = sanitizeFileName(name); - - if (name != sanitized) { - invalid = true; - } else { + if (InstanceManager::instance().validInstanceName(name)) { exists = QDir(parentDir).exists(name); + } else { + invalid = true; } } @@ -963,11 +917,9 @@ bool PathsPage::checkPath( if (path.isEmpty()) { empty = true; } else { - const QString sanitized = sanitizePath(path); + const QDir d(path); - if (path != sanitized) { - invalid = true; - } else { + if (InstanceManager::instance().validInstanceName(d.dirName())) { if (m_dlg.instanceType() == CreateInstanceDialog::Portable) { // the default data path for a portable instance is the application // directory, so it's not an error if it exists @@ -977,6 +929,8 @@ bool PathsPage::checkPath( } else { exists = QDir(path).exists(); } + } else { + invalid = true; } } diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index b6798fa9..111f948b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -676,3 +676,12 @@ QString InstanceManager::sanitizeInstanceName(const QString &name) const } return new_name; } + +bool InstanceManager::validInstanceName(const QString& instanceName) const +{ + if (instanceName.isEmpty()) { + return false; + } + + return (instanceName == sanitizeInstanceName(instanceName)); +} diff --git a/src/instancemanager.h b/src/instancemanager.h index 9250ffe9..33e115a7 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -63,6 +63,7 @@ public: QString sanitizeInstanceName(const QString &name) const; QString makeUniqueName(const QString& instanceName) const; bool instanceExists(const QString& instanceName) const; + bool validInstanceName(const QString& instanceName) const; QString instancePath(const QString& instanceName) const; private: diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index a038d3c2..aa6bdb04 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -8,8 +8,12 @@ #include "shared/appconfig.h" #include +namespace shell = MOBase::shell; + void openInstanceManager(PluginContainer& pc, QWidget* parent) { + //CreateInstanceDialog dlg(pc, parent); + //dlg.exec(); InstanceManagerDialog dlg(pc, parent); dlg.exec(); } @@ -77,12 +81,20 @@ InstanceManagerDialog::InstanceManagerDialog( ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); + auto* model = new QStandardItemModel; + ui->list->setModel(model); + + m_filter.setEdit(ui->filter); + m_filter.setList(ui->list); + m_filter.setUpdateDelay(false); + m_filter.setFilteredBorder(false); + auto& m = InstanceManager::instance(); for (auto&& d : m.instancePaths()) { auto ii = std::make_unique(d); - ui->list->addItem(ii->name()); + model->appendRow(new QStandardItem(ii->name())); m_instances.push_back(std::move(ii)); } @@ -91,8 +103,12 @@ InstanceManagerDialog::InstanceManagerDialog( } connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); - connect(ui->list, &QListWidget::itemSelectionChanged, [&]{ onSelection(); }); + connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&]{ onSelection(); }); //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); + connect(ui->rename, &QPushButton::clicked, [&]{ rename(); }); + connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); + connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); + connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); } InstanceManagerDialog::~InstanceManagerDialog() = default; @@ -105,11 +121,15 @@ void InstanceManagerDialog::select(std::size_t i) const auto& ii = m_instances[i]; fill(*ii); + + ui->list->selectionModel()->select( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), + QItemSelectionModel::ClearAndSelect); } void InstanceManagerDialog::openSelectedInstance() { - const auto i = singleSelection(); + const auto i = singleSelectionIndex(); if (i == NoSelection) { return; } @@ -117,9 +137,107 @@ void InstanceManagerDialog::openSelectedInstance() InstanceManager::instance().switchToInstance(m_instances[i]->name()); } +void InstanceManagerDialog::rename() +{ + auto* i = singleSelection(); + if (!i) { + return; + } + + auto& m = InstanceManager::instance(); + if (m.currentInstance() == i->name()) { + QMessageBox::information(this, + tr("Rename instance"), tr("The active instance cannot be renamed")); + return; + } + + QDialog dlg(this); + dlg.setWindowTitle(tr("Rename instance")); + + auto* ly = new QVBoxLayout(&dlg); + + auto* bb = new QDialogButtonBox( + QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + + auto* text = new QLineEdit(i->name()); + text->selectAll(); + + auto* error = new QLabel; + + ly->addWidget(new QLabel(tr("Instance name"))); + ly->addWidget(text); + ly->addWidget(error); + ly->addStretch(); + ly->addWidget(bb); + + connect(text, &QLineEdit::textChanged, [&] { + bool okay = false; + + if (!m.validInstanceName(text->text())) { + error->setText(tr("The instance name must be a valid folder name.")); + } else { + const auto name = m.sanitizeInstanceName(text->text()); + + if ((name != i->name()) && m.instanceExists(text->text())) { + error->setText(tr("An instance with this name already exists.")); + } else { + okay = true; + } + } + + error->setVisible(!okay); + bb->button(QDialogButtonBox::Ok)->setEnabled(okay); + }); + + connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); }); + connect(bb, &QDialogButtonBox::rejected, [&]{ dlg.reject(); }); + + dlg.resize({400, 120}); + if (dlg.exec() != QDialog::Accepted) { + return; + } + + + const QString newName = m.sanitizeInstanceName(text->text()); + const QString src = QDir::toNativeSeparators(i->location()); + const QString dest = QDir::toNativeSeparators( + QFileInfo(i->location()).dir().path() + "/" + newName); + + const auto r = shell::Rename(src, dest, false); + if (!r) { + QMessageBox::critical( + this, tr("Error"), + tr("Failed to rename \"%1\" to \"%2\": %3") + .arg(src).arg(dest).arg(r.toString())); + + return; + } +} + +void InstanceManagerDialog::exploreLocation() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->location()); + } +} + +void InstanceManagerDialog::exploreBaseDirectory() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->baseDirectory()); + } +} + +void InstanceManagerDialog::exploreGame() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->gamePath()); + } +} + void InstanceManagerDialog::onSelection() { - const auto i = singleSelection(); + const auto i = singleSelectionIndex(); if (i == NoSelection) { return; } @@ -133,14 +251,36 @@ void InstanceManagerDialog::createNew() dlg.exec(); } -std::size_t InstanceManagerDialog::singleSelection() const +std::size_t InstanceManagerDialog::singleSelectionIndex() const { - const auto sel = ui->list->selectionModel()->selectedIndexes(); + const auto sel = m_filter.mapSelectionToSource( + ui->list->selectionModel()->selection()); + if (sel.size() != 1) { return NoSelection; } - return static_cast(sel[0].row()); + return static_cast(sel.indexes()[0].row()); +} + +InstanceInfo* InstanceManagerDialog::singleSelection() +{ + const auto i = singleSelectionIndex(); + if (i == NoSelection) { + return nullptr; + } + + return m_instances[i].get(); +} + +const InstanceInfo* InstanceManagerDialog::singleSelection() const +{ + const auto i = singleSelectionIndex(); + if (i == NoSelection) { + return nullptr; + } + + return m_instances[i].get(); } void InstanceManagerDialog::fill(const InstanceInfo& ii) diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 6dbf015f..529a99b8 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -1,6 +1,7 @@ #ifndef MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED #define MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED +#include #include namespace Ui { class InstanceManagerDialog; }; @@ -21,17 +22,26 @@ public: void select(std::size_t i); void openSelectedInstance(); + void rename(); + void exploreLocation(); + void exploreBaseDirectory(); + void exploreGame(); + private: static const std::size_t NoSelection = -1; std::unique_ptr ui; const PluginContainer& m_pc; std::vector> m_instances; + MOBase::FilterWidget m_filter; void onSelection(); void createNew(); - std::size_t singleSelection() const; + std::size_t singleSelectionIndex() const; + InstanceInfo* singleSelection(); + const InstanceInfo* singleSelection() const; + void fill(const InstanceInfo& ii); }; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 146b5fb5..08894ec1 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -103,7 +103,32 @@ 0 - + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter + + + + + -- cgit v1.3.1 From 5fc6b0cd142b18ea574381580752ef6647102c9f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 Jul 2020 09:07:40 -0400 Subject: renamed cancel to close add portable instance to the list, handle some of the buttons --- src/instancemanagerdialog.cpp | 102 +++++++++++++++++++++++++++++++++--------- src/instancemanagerdialog.h | 6 ++- src/instancemanagerdialog.ui | 6 +-- 3 files changed, 88 insertions(+), 26 deletions(-) (limited to 'src/instancemanagerdialog.h') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index aa6bdb04..99ade7ad 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -21,15 +21,19 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent) class InstanceInfo { public: - InstanceInfo(QDir dir) : - m_dir(std::move(dir)), + InstanceInfo(QDir dir, bool isPortable) : + m_dir(std::move(dir)), m_portable(isPortable), m_settings(dir.filePath(QString::fromStdWString(AppConfig::iniFileName()))) { } QString name() const { - return m_dir.dirName(); + if (m_portable) { + return QObject::tr("Portable"); + } else { + return m_dir.dirName(); + } } QString gameName() const @@ -66,52 +70,83 @@ public: return m_settings.paths().base(); } + bool isPortable() const + { + return m_portable; + } + private: QDir m_dir; + bool m_portable; Settings m_settings; }; +InstanceManagerDialog::~InstanceManagerDialog() = default; + InstanceManagerDialog::InstanceManagerDialog( - const PluginContainer& pc, QWidget *parent) - : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc) + const PluginContainer& pc, QWidget *parent) : + QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc), + m_model(nullptr) { ui->setupUi(this); ui->splitter->setSizes({200, 1}); ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); - auto* model = new QStandardItemModel; - ui->list->setModel(model); + m_model = new QStandardItemModel; + ui->list->setModel(m_model); m_filter.setEdit(ui->filter); m_filter.setList(ui->list); m_filter.setUpdateDelay(false); m_filter.setFilteredBorder(false); - auto& m = InstanceManager::instance(); - - for (auto&& d : m.instancePaths()) { - auto ii = std::make_unique(d); - - model->appendRow(new QStandardItem(ii->name())); - m_instances.push_back(std::move(ii)); - } - - if (!m_instances.empty()) { - select(0); - } + updateInstances(); + updateList(); connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); + connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&]{ onSelection(); }); //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); + connect(ui->rename, &QPushButton::clicked, [&]{ rename(); }); connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); + + connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); + connect(ui->close, &QPushButton::clicked, [&]{ close(); }); } -InstanceManagerDialog::~InstanceManagerDialog() = default; +void InstanceManagerDialog::updateInstances() +{ + auto& m = InstanceManager::instance(); + + m_instances.clear(); + + if (m.portableInstanceExists()) { + m_instances.push_back(std::make_unique( + m.portablePath(), true)); + } + + for (auto&& d : m.instancePaths()) { + m_instances.push_back(std::make_unique(d, false)); + } +} + +void InstanceManagerDialog::updateList() +{ + m_model->clear(); + + for (auto&& ii : m_instances) { + m_model->appendRow(new QStandardItem(ii->name())); + } + + if (!m_instances.empty()) { + select(0); + } +} void InstanceManagerDialog::select(std::size_t i) { @@ -120,7 +155,7 @@ void InstanceManagerDialog::select(std::size_t i) } const auto& ii = m_instances[i]; - fill(*ii); + fillData(*ii); ui->list->selectionModel()->select( m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), @@ -212,6 +247,8 @@ void InstanceManagerDialog::rename() return; } + + } void InstanceManagerDialog::exploreLocation() @@ -283,11 +320,32 @@ const InstanceInfo* InstanceManagerDialog::singleSelection() const return m_instances[i].get(); } -void InstanceManagerDialog::fill(const InstanceInfo& ii) +void InstanceManagerDialog::fillData(const InstanceInfo& ii) { ui->name->setText(ii.name()); ui->location->setText(ii.location()); ui->baseDirectory->setText(ii.baseDirectory()); ui->gameName->setText(ii.gameName()); ui->gameDir->setText(ii.gamePath()); + + const auto& m = InstanceManager::instance(); + + ui->rename->setEnabled(!ii.isPortable()); + + if (ii.isPortable()) { + ui->convertToPortable->setVisible(false); + ui->convertToGlobal->setVisible(true); + ui->convertToGlobal->setEnabled(true); + } else { + ui->convertToPortable->setVisible(true); + ui->convertToGlobal->setVisible(false); + + if (m.portableInstanceExists()) { + ui->convertToPortable->setEnabled(false); + ui->convertToPortable->setToolTip(tr("A portable instance already exists.")); + } else { + ui->convertToPortable->setEnabled(false); + ui->convertToPortable->setToolTip(""); + } + } } diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 529a99b8..d102facb 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -34,6 +34,9 @@ private: const PluginContainer& m_pc; std::vector> m_instances; MOBase::FilterWidget m_filter; + QStandardItemModel* m_model; + + void updateInstances(); void onSelection(); void createNew(); @@ -42,7 +45,8 @@ private: InstanceInfo* singleSelection(); const InstanceInfo* singleSelection() const; - void fill(const InstanceInfo& ii); + void updateList(); + void fillData(const InstanceInfo& ii); }; #endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 08894ec1..8bf28ed6 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -382,7 +382,7 @@ - + Switch to this instance @@ -393,9 +393,9 @@ - + - Cancel + Close true -- cgit v1.3.1 From f7e9724eb1a817bdef372f87ebf96e9692db2bc9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Aug 2020 19:44:31 -0400 Subject: delete instance --- src/instancemanagerdialog.cpp | 268 ++++++++++++++++++++++++++++++++++++++---- src/instancemanagerdialog.h | 7 ++ src/instancemanagerdialog.ui | 24 ++-- 3 files changed, 265 insertions(+), 34 deletions(-) (limited to 'src/instancemanagerdialog.h') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 99ade7ad..5f42f409 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -6,9 +6,11 @@ #include "selectiondialog.h" #include "plugincontainer.h" #include "shared/appconfig.h" +#include +#include #include -namespace shell = MOBase::shell; +using namespace MOBase; void openInstanceManager(PluginContainer& pc, QWidget* parent) { @@ -54,7 +56,7 @@ public: QString gamePath() const { if (auto n=m_settings.game().directory()) { - return *n; + return QDir::toNativeSeparators(*n); } else { return {}; } @@ -62,12 +64,12 @@ public: QString location() const { - return m_dir.path(); + return QDir::toNativeSeparators(m_dir.path()); } QString baseDirectory() const { - return m_settings.paths().base(); + return QDir::toNativeSeparators(m_settings.paths().base()); } bool isPortable() const @@ -75,6 +77,19 @@ public: return m_portable; } + bool isActive() const + { + auto& m = InstanceManager::instance(); + + if (m_portable && m.currentInstance() == "") { + return true; + } else if (m.currentInstance() == name()) { + return true; + } + + return false; + } + private: QDir m_dir; bool m_portable; @@ -90,6 +105,7 @@ InstanceManagerDialog::InstanceManagerDialog( m_model(nullptr) { ui->setupUi(this); + ui->splitter->setSizes({200, 1}); ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); @@ -114,6 +130,7 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); + connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); connect(ui->close, &QPushButton::clicked, [&]{ close(); }); @@ -137,29 +154,51 @@ void InstanceManagerDialog::updateInstances() void InstanceManagerDialog::updateList() { + const auto prevSelIndex = singleSelectionIndex(); + const auto* prevSel = singleSelection(); + m_model->clear(); - for (auto&& ii : m_instances) { - m_model->appendRow(new QStandardItem(ii->name())); + const std::size_t NoSel = -1; + std::size_t sel = NoSel; + + for (std::size_t i=0; iappendRow(new QStandardItem(ii.name())); + + if (&ii == prevSel) { + sel = i; + } } - if (!m_instances.empty()) { - select(0); + + if (m_instances.empty()) { + select(-1); + } else { + if (sel == NoSel) { + if (prevSelIndex >= m_instances.size()) { + sel = m_instances.size() - 1; + } else { + sel = prevSelIndex; + } + } + + select(sel); } } void InstanceManagerDialog::select(std::size_t i) { - if (i >= m_instances.size()) { - return; - } - - const auto& ii = m_instances[i]; - fillData(*ii); + if (i < m_instances.size()) { + const auto& ii = m_instances[i]; + fillData(*ii); - ui->list->selectionModel()->select( - m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), - QItemSelectionModel::ClearAndSelect); + ui->list->selectionModel()->select( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), + QItemSelectionModel::ClearAndSelect); + } else { + clearData(); + } } void InstanceManagerDialog::openSelectedInstance() @@ -180,9 +219,9 @@ void InstanceManagerDialog::rename() } auto& m = InstanceManager::instance(); - if (m.currentInstance() == i->name()) { + if (i->isActive()) { QMessageBox::information(this, - tr("Rename instance"), tr("The active instance cannot be renamed")); + tr("Rename instance"), tr("The active instance cannot be renamed.")); return; } @@ -234,7 +273,7 @@ void InstanceManagerDialog::rename() const QString newName = m.sanitizeInstanceName(text->text()); - const QString src = QDir::toNativeSeparators(i->location()); + const QString src = i->location(); const QString dest = QDir::toNativeSeparators( QFileInfo(i->location()).dir().path() + "/" + newName); @@ -247,8 +286,6 @@ void InstanceManagerDialog::rename() return; } - - } void InstanceManagerDialog::exploreLocation() @@ -272,6 +309,160 @@ void InstanceManagerDialog::exploreGame() } } +void InstanceManagerDialog::deleteInstance() +{ + const auto* i = singleSelection(); + if (!i) { + return; + } + + auto& m = InstanceManager::instance(); + if (i->isActive()) { + QMessageBox::information(this, + tr("Deleting instance"), tr("The active instance cannot be deleted.")); + return; + } + + if (i->isPortable()) { + deletePortable(*i); + } else { + deleteGlobal(*i); + } + + updateInstances(); + updateList(); +} + +bool InstanceManagerDialog::deletePortable(const InstanceInfo& i) +{ + const auto Recycle = QMessageBox::Save; + const auto Delete = QMessageBox::Yes; + const auto Cancel = QMessageBox::Cancel; + + const std::vector fileNames = { + AppConfig::iniFileName(), + }; + + const std::vector dirNames = { + AppConfig::dumpsDir(), + AppConfig::downloadPath(), + AppConfig::logPath(), + AppConfig::modsPath(), + AppConfig::overwritePath(), + AppConfig::profilesPath(), + AppConfig::cachePath() + }; + + QStringList files; + for (const auto& n : fileNames) { + files.push_back(QDir::toNativeSeparators( + i.location() + "/" + QString::fromStdWString(n))); + } + + QStringList dirs; + for (const auto& n : dirNames) { + dirs.push_back(QDir::toNativeSeparators( + i.location() + "/" + QString::fromStdWString(n))); + } + + QString details = QObject::tr("These files will be deleted:"); + for (const auto& f : files) { + details += "\n - " + f; + } + + details += "\n\n" + QObject::tr("These folders will be deleted:"); + for (const auto& d : dirs) { + details += "\n - " + d; + } + + + QStringList all; + all.append(files); + all.append(dirs); + + + const auto r = MOBase::TaskDialog(this) + .title(("Deleting portable instance")) + .main(tr("This will delete the data of the portable instance.")) + .content(tr( + "The data is in %1. Only the relevant files and folders will be " + "deleted. The Mod Organizer installation itself will be untouched.") + .arg(i.location())) + .details(details) + .icon(QMessageBox::Warning) + .button({tr("Move the data to the recycle bin"), Recycle}) + .button({tr("Delete the data permanently"), Delete}) + .button({tr("Cancel"), Cancel}) + .exec(); + + switch (r) + { + case Recycle: + return doDelete(all, true); + + case Delete: + return doDelete(all, false); + + case Cancel: // fall-through + default: + { + return false; + } + } + + return true; +} + +bool InstanceManagerDialog::deleteGlobal(const InstanceInfo& i) +{ + const auto Recycle = QMessageBox::Save; + const auto Delete = QMessageBox::Yes; + const auto Cancel = QMessageBox::Cancel; + + const auto r = MOBase::TaskDialog(this) + .title(tr("Deleting instance")) + .main(tr("The instance folder will be deleted.")) + .content(i.location()) + .icon(QMessageBox::Warning) + .button({tr("Move the folder to the recycle bin"), Recycle}) + .button({tr("Delete the folder permanently"), Delete}) + .button({tr("Cancel"), Cancel}) + .exec(); + + switch (r) + { + case Recycle: + return doDelete(QStringList(i.location()), true); + + case Delete: + return doDelete(QStringList(i.location()), false); + + case Cancel: // fall-through + default: + { + return false; + } + } + + return true; +} + +bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) +{ + if (MOBase::shellDelete(files, recycle, this)) { + return true; + } + + const auto e = GetLastError(); + if (e == ERROR_CANCELLED) { + log::debug("deletion cancelled by user"); + } else { + log::error("failed to delete, {}", formatSystemMessage(e)); + } + + return false; +} + void InstanceManagerDialog::onSelection() { const auto i = singleSelectionIndex(); @@ -327,6 +518,7 @@ void InstanceManagerDialog::fillData(const InstanceInfo& ii) ui->baseDirectory->setText(ii.baseDirectory()); ui->gameName->setText(ii.gameName()); ui->gameDir->setText(ii.gamePath()); + setButtonsEnabled(true); const auto& m = InstanceManager::instance(); @@ -348,4 +540,36 @@ void InstanceManagerDialog::fillData(const InstanceInfo& ii) ui->convertToPortable->setToolTip(""); } } + + + // these are not currently implemented; the ui sets them correctly above, + // but force them hidden for now + ui->convertToPortable->setVisible(false); + ui->convertToGlobal->setVisible(false); +} + +void InstanceManagerDialog::clearData() +{ + ui->name->clear(); + ui->location->clear(); + ui->baseDirectory->clear(); + ui->gameName->clear(); + ui->gameDir->clear(); + + setButtonsEnabled(false); + + ui->convertToPortable->setVisible(false); + ui->convertToGlobal->setVisible(false); +} + +void InstanceManagerDialog::setButtonsEnabled(bool b) +{ + ui->rename->setEnabled(b); + ui->exploreLocation->setEnabled(b); + ui->exploreBaseDirectory->setEnabled(b); + ui->exploreGame->setEnabled(b); + ui->convertToPortable->setEnabled(b); + ui->convertToGlobal->setEnabled(b); + ui->deleteInstance->setEnabled(b); + ui->switchToInstance->setEnabled(b); } diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index d102facb..3daa0800 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -26,6 +26,7 @@ public: void exploreLocation(); void exploreBaseDirectory(); void exploreGame(); + void deleteInstance(); private: static const std::size_t NoSelection = -1; @@ -47,6 +48,12 @@ private: void updateList(); void fillData(const InstanceInfo& ii); + void clearData(); + void setButtonsEnabled(bool b); + + bool deletePortable(const InstanceInfo& ii); + bool deleteGlobal(const InstanceInfo& ii); + bool doDelete(const QStringList& files, bool recycle); }; #endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 8bf28ed6..a8e5e2b7 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -305,18 +305,7 @@ - - - Delete instance - - - - :/MO/gui/remove:/MO/gui/remove - - - - - + Qt::Horizontal @@ -328,6 +317,17 @@ + + + + Delete instance + + + + :/MO/gui/remove:/MO/gui/remove + + + -- cgit v1.3.1 From 003ef66d2ff8e83e864d362f152a6f87ae05cbc4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Aug 2020 20:53:42 -0400 Subject: - changed PathSettings::base() to use the ini's parent directory instead of the global "dataPath" property; this is always the same thing, unless another Settings object is created for a different instance than the active one, which happens in the instance manager dialog - instance manager dialog: - select the active instance when opening - instance deletion now handles custom paths from the ini - started on instance conversions, not functional --- src/instancemanagerdialog.cpp | 455 +++++++++++++++++++++++++++++------------- src/instancemanagerdialog.h | 3 + src/instancemanagerdialog.ui | 6 +- src/settings.cpp | 4 +- 4 files changed, 328 insertions(+), 140 deletions(-) (limited to 'src/instancemanagerdialog.h') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 5f42f409..a91e3d92 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -20,12 +20,17 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent) dlg.exec(); } +QString iniFile(const QDir& dir) +{ + return dir.filePath(QString::fromStdWString(AppConfig::iniFileName())); +} + + class InstanceInfo { public: InstanceInfo(QDir dir, bool isPortable) : - m_dir(std::move(dir)), m_portable(isPortable), - m_settings(dir.filePath(QString::fromStdWString(AppConfig::iniFileName()))) + m_dir(std::move(dir)), m_portable(isPortable), m_settings(iniFile(dir)) { } @@ -90,6 +95,154 @@ public: return false; } + // returns a list of files and folders that must be deleted when deleting + // this instance + // + QStringList filesForDeletion() const + { + // native separators and ending slash + auto prettyDir = [](auto s) { + if (!s.endsWith("/") || !s.endsWith("\\")) { + s += "/"; + } + + return QDir::toNativeSeparators(s); + }; + + // native separators + auto prettyFile = [](auto s) { + return QDir::toNativeSeparators(s); + }; + + + // lowercase, native separators and ending slash + auto canonicalDir = [](auto s) { + s = s.toLower(); + if (!s.endsWith("/") || !s.endsWith("\\")) { + s += "/"; + } + + return QDir::toNativeSeparators(s); + }; + + // lower and native separators + auto canonicalFile = [](auto s) { + return QDir::toNativeSeparators(s.toLower()); + }; + + + + // whether the given directory is contained in the root + auto dirInRoot = [&](auto root, auto dir) { + return canonicalDir(dir).startsWith(canonicalDir(root)); + }; + + // whether the given file is contained in the root + auto fileInRoot = [&](auto root, auto file) { + return canonicalFile(file).startsWith(canonicalDir(root)); + }; + + + + + const auto loc = location(); + const auto base = m_settings.paths().base(); + + + // directories that might contain the individual files and directories + // set in the path settings + QStringList roots; + + // a portable instance has its location in the installation directory, + // don't delete that + if (!isPortable()) { + roots.append(loc); + } + + // the base directory is the location directory by default, don't add it + // if it's the same + if (canonicalDir(base) != canonicalDir(loc)) { + roots.append(base); + } + + + // all the directories that are part of an instance + const QStringList dirs = { + m_settings.paths().downloads(), + m_settings.paths().mods(), + m_settings.paths().overwrite(), + m_settings.paths().profiles(), + m_settings.paths().cache(), + m_dir.filePath(QString::fromStdWString(AppConfig::dumpsDir())), + m_dir.filePath(QString::fromStdWString(AppConfig::logPath())), + }; + + // all the files that are part of an instance + const QStringList files = { + iniFile(m_dir), + }; + + + // this will contain the root directories, plus all the individual + // directories that are not inside these roots + QStringList cleanDirs; + + for (const auto& f : dirs) { + bool inRoots = false; + + for (const auto& root : roots) { + if (dirInRoot(root, f)) { + inRoots = true; + break; + } + } + + if (!inRoots) { + // not in roots, this is a path that was changed by the user + cleanDirs.append(prettyDir(f)); + } + } + + // adding the roots + for (const auto& root : roots) { + cleanDirs.append(prettyDir(root)); + } + + cleanDirs.sort(Qt::CaseInsensitive); + + + // this will contain the individual files that are not inside the roots; + // not that this only contains the INI file for now, so most of this is + // useless + QStringList cleanFiles; + + for (const auto& f : files) { + bool inRoots = false; + + for (const auto& root : roots) { + if (fileInRoot(root, f)) { + inRoots = true; + break; + } + } + + if (!inRoots) { + // not in roots, this is a path that was changed by the user + cleanFiles.append(prettyFile(f)); + } + } + + cleanFiles.sort(Qt::CaseInsensitive); + + + // contains all the directories and files to be deleted + QStringList all; + all.append(cleanDirs); + all.append(cleanFiles); + + return all; + } + private: QDir m_dir; bool m_portable; @@ -120,11 +273,12 @@ InstanceManagerDialog::InstanceManagerDialog( updateInstances(); updateList(); + selectActiveInstance(); connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&]{ onSelection(); }); - //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); + connect(ui->list, &QListView::activated, [&]{ openSelectedInstance(); }); connect(ui->rename, &QPushButton::clicked, [&]{ rename(); }); connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); @@ -132,6 +286,9 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); + connect(ui->convertToGlobal, &QPushButton::clicked, [&]{ convertToGlobal(); }); + connect(ui->convertToPortable, &QPushButton::clicked, [&]{ convertToPortable(); }); + connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); connect(ui->close, &QPushButton::clicked, [&]{ close(); }); } @@ -171,19 +328,22 @@ void InstanceManagerDialog::updateList() } } - - if (m_instances.empty()) { - select(-1); - } else { - if (sel == NoSel) { - if (prevSelIndex >= m_instances.size()) { - sel = m_instances.size() - 1; - } else { - sel = prevSelIndex; + // keep current selection or select the next one if there was a selection; + // there's no selection when opening the dialog, that's handled in the ctor + if (prevSel) { + if (m_instances.empty()) { + select(-1); + } else { + if (sel == NoSel) { + if (prevSelIndex >= m_instances.size()) { + sel = m_instances.size() - 1; + } else { + sel = prevSelIndex; + } } - } - select(sel); + select(sel); + } } } @@ -201,6 +361,24 @@ void InstanceManagerDialog::select(std::size_t i) } } +void InstanceManagerDialog::selectActiveInstance() +{ + const auto active = InstanceManager::instance().currentInstance(); + + for (std::size_t i=0; iname() == active) { + select(i); + + ui->list->scrollTo( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); + + return; + } + } + + select(0); +} + void InstanceManagerDialog::openSelectedInstance() { const auto i = singleSelectionIndex(); @@ -211,49 +389,53 @@ void InstanceManagerDialog::openSelectedInstance() InstanceManager::instance().switchToInstance(m_instances[i]->name()); } -void InstanceManagerDialog::rename() +QString getInstanceName( + QWidget* parent, const QString& title, const QString& moreText, + const QString& label, const QString& oldName={}) { - auto* i = singleSelection(); - if (!i) { - return; - } - auto& m = InstanceManager::instance(); - if (i->isActive()) { - QMessageBox::information(this, - tr("Rename instance"), tr("The active instance cannot be renamed.")); - return; - } - QDialog dlg(this); - dlg.setWindowTitle(tr("Rename instance")); + QDialog dlg(parent); + dlg.setWindowTitle(title); auto* ly = new QVBoxLayout(&dlg); auto* bb = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - auto* text = new QLineEdit(i->name()); + auto* text = new QLineEdit(oldName); text->selectAll(); auto* error = new QLabel; - ly->addWidget(new QLabel(tr("Instance name"))); + if (!moreText.isEmpty()) { + auto* lb = new QLabel(moreText); + lb->setWordWrap(true); + ly->addWidget(lb); + ly->addSpacing(10); + } + + auto* lb = new QLabel(label); + lb->setWordWrap(true); + ly->addWidget(lb); + ly->addWidget(text); ly->addWidget(error); ly->addStretch(); ly->addWidget(bb); - connect(text, &QLineEdit::textChanged, [&] { + auto check = [&] { bool okay = false; - if (!m.validInstanceName(text->text())) { - error->setText(tr("The instance name must be a valid folder name.")); + if (text->text().isEmpty()) { + error->setText(""); + } else if (!m.validInstanceName(text->text())) { + error->setText(QObject::tr("The instance name must be a valid folder name.")); } else { const auto name = m.sanitizeInstanceName(text->text()); - if ((name != i->name()) && m.instanceExists(text->text())) { - error->setText(tr("An instance with this name already exists.")); + if ((name != oldName) && m.instanceExists(text->text())) { + error->setText(QObject::tr("An instance with this name already exists.")); } else { okay = true; } @@ -261,18 +443,43 @@ void InstanceManagerDialog::rename() error->setVisible(!okay); bb->button(QDialogButtonBox::Ok)->setEnabled(okay); - }); + }; + + QObject::connect(text, &QLineEdit::textChanged, [&] { check(); }); + QObject::connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); }); + QObject::connect(bb, &QDialogButtonBox::rejected, [&]{ dlg.reject(); }); - connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); }); - connect(bb, &QDialogButtonBox::rejected, [&]{ dlg.reject(); }); + check(); dlg.resize({400, 120}); if (dlg.exec() != QDialog::Accepted) { + return {}; + } + + return m.sanitizeInstanceName(text->text()); +} + +void InstanceManagerDialog::rename() +{ + auto* i = singleSelection(); + if (!i) { return; } + auto& m = InstanceManager::instance(); + if (i->isActive()) { + QMessageBox::information(this, + tr("Rename instance"), tr("The active instance cannot be renamed.")); + return; + } + + const auto newName = getInstanceName( + this, tr("Rename instance"), "", tr("Instance name"), i->name()); + + if (newName.isEmpty()) { + return; + } - const QString newName = m.sanitizeInstanceName(text->text()); const QString src = i->location(); const QString dest = QDir::toNativeSeparators( QFileInfo(i->location()).dir().path() + "/" + newName); @@ -323,130 +530,71 @@ void InstanceManagerDialog::deleteInstance() return; } - if (i->isPortable()) { - deletePortable(*i); - } else { - deleteGlobal(*i); - } - - updateInstances(); - updateList(); -} - -bool InstanceManagerDialog::deletePortable(const InstanceInfo& i) -{ const auto Recycle = QMessageBox::Save; const auto Delete = QMessageBox::Yes; const auto Cancel = QMessageBox::Cancel; - const std::vector fileNames = { - AppConfig::iniFileName(), - }; + const auto files = i->filesForDeletion(); - const std::vector dirNames = { - AppConfig::dumpsDir(), - AppConfig::downloadPath(), - AppConfig::logPath(), - AppConfig::modsPath(), - AppConfig::overwritePath(), - AppConfig::profilesPath(), - AppConfig::cachePath() - }; + MOBase::TaskDialog dlg(this); - QStringList files; - for (const auto& n : fileNames) { - files.push_back(QDir::toNativeSeparators( - i.location() + "/" + QString::fromStdWString(n))); - } + dlg + .title(("Deleting instance")) + .main(QObject::tr("These files and folders will be deleted")) + .icon(QMessageBox::Warning) + .button({tr("Move to the recycle bin"), Recycle}) + .button({tr("Delete permanently"), Delete}) + .button({tr("Cancel"), Cancel}); - QStringList dirs; - for (const auto& n : dirNames) { - dirs.push_back(QDir::toNativeSeparators( - i.location() + "/" + QString::fromStdWString(n))); - } + auto* list = new QPlainTextEdit(); + list->setReadOnly(true); + list->setWordWrapMode(QTextOption::NoWrap); + list->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + list->setMaximumHeight(150); - QString details = QObject::tr("These files will be deleted:"); for (const auto& f : files) { - details += "\n - " + f; - } - - details += "\n\n" + QObject::tr("These folders will be deleted:"); - for (const auto& d : dirs) { - details += "\n - " + d; + list->appendPlainText(f); } + list->moveCursor(QTextCursor::MoveOperation::Start); - QStringList all; - all.append(files); - all.append(dirs); + dlg.addContent(list); - - const auto r = MOBase::TaskDialog(this) - .title(("Deleting portable instance")) - .main(tr("This will delete the data of the portable instance.")) - .content(tr( - "The data is in %1. Only the relevant files and folders will be " - "deleted. The Mod Organizer installation itself will be untouched.") - .arg(i.location())) - .details(details) - .icon(QMessageBox::Warning) - .button({tr("Move the data to the recycle bin"), Recycle}) - .button({tr("Delete the data permanently"), Delete}) - .button({tr("Cancel"), Cancel}) - .exec(); + const auto r = dlg.exec(); switch (r) { case Recycle: - return doDelete(all, true); - - case Delete: - return doDelete(all, false); - - case Cancel: // fall-through - default: { - return false; - } - } - - return true; -} - -bool InstanceManagerDialog::deleteGlobal(const InstanceInfo& i) -{ - const auto Recycle = QMessageBox::Save; - const auto Delete = QMessageBox::Yes; - const auto Cancel = QMessageBox::Cancel; - - const auto r = MOBase::TaskDialog(this) - .title(tr("Deleting instance")) - .main(tr("The instance folder will be deleted.")) - .content(i.location()) - .icon(QMessageBox::Warning) - .button({tr("Move the folder to the recycle bin"), Recycle}) - .button({tr("Delete the folder permanently"), Delete}) - .button({tr("Cancel"), Cancel}) - .exec(); + if (!doDelete(files, true)) { + return; + } - switch (r) - { - case Recycle: - return doDelete(QStringList(i.location()), true); + break; + } case Delete: - return doDelete(QStringList(i.location()), false); + { + if (!doDelete(files, false)) { + return; + } + + break; + } case Cancel: // fall-through default: { - return false; + return; } } - return true; + updateInstances(); + updateList(); } + bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) { if (MOBase::shellDelete(files, recycle, this)) { @@ -463,6 +611,43 @@ bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) return false; } +void InstanceManagerDialog::convertToGlobal() +{ + const auto* i = singleSelection(); + if (!i) { + return; + } + + if (!i->isPortable()) { + log::error("can't convert to global, this is not a portable instance"); + return; + } + + const auto& m = InstanceManager::instance(); + + const auto name = getInstanceName( + this, + tr("Convert to global instance"), + tr( + "This will move all the instance data currently in Mod Organizer's " + "installation folder into a global instance. If the operation fails or " + "is cancelled, no data should be lost, but the move will need to be " + "completed or cleaned up manually.

" + "Source: %1
" + "Destination: %2") + .arg(i->location()) + .arg(QDir::toNativeSeparators(m.instancesPath())), + tr("Name of the new instance")); + + if (name.isEmpty()) { + return; + } +} + +void InstanceManagerDialog::convertToPortable() +{ +} + void InstanceManagerDialog::onSelection() { const auto i = singleSelectionIndex(); @@ -540,12 +725,6 @@ void InstanceManagerDialog::fillData(const InstanceInfo& ii) ui->convertToPortable->setToolTip(""); } } - - - // these are not currently implemented; the ui sets them correctly above, - // but force them hidden for now - ui->convertToPortable->setVisible(false); - ui->convertToGlobal->setVisible(false); } void InstanceManagerDialog::clearData() diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 3daa0800..659710fe 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -20,6 +20,7 @@ public: ~InstanceManagerDialog(); void select(std::size_t i); + void selectActiveInstance(); void openSelectedInstance(); void rename(); @@ -27,6 +28,8 @@ public: void exploreBaseDirectory(); void exploreGame(); void deleteInstance(); + void convertToGlobal(); + void convertToPortable(); private: static const std::size_t NoSelection = -1; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index a8e5e2b7..b2abfa4b 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -103,7 +103,11 @@ 0 - + + + QAbstractItemView::NoEditTriggers + + diff --git a/src/settings.cpp b/src/settings.cpp index a0758bd4..b286510d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1545,8 +1545,10 @@ QString PathSettings::makeDefaultPath(const QString dirName) QString PathSettings::base() const { + const QString dataPath = QFileInfo(m_Settings.fileName()).dir().path(); + return QDir::fromNativeSeparators(get(m_Settings, - "Settings", "base_directory", qApp->property("dataPath").toString())); + "Settings", "base_directory", dataPath)); } QString PathSettings::downloads(bool resolve) const -- cgit v1.3.1 From a9772873d69b875f5617c0121c73a271d432a29c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Aug 2020 22:35:19 -0400 Subject: open ini button update when renaming instance update and select when creating instance --- src/createinstancedialog.cpp | 13 +++++-- src/createinstancedialog.h | 2 + src/instancemanagerdialog.cpp | 86 ++++++++++++++++++++++++++++++++++--------- src/instancemanagerdialog.h | 5 ++- src/instancemanagerdialog.ui | 19 ++++++++++ 5 files changed, 103 insertions(+), 22 deletions(-) (limited to 'src/instancemanagerdialog.h') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 935b9ee9..f4140e01 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -11,8 +11,9 @@ using namespace MOBase; CreateInstanceDialog::CreateInstanceDialog( - const PluginContainer& pc, QWidget *parent) - : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc) + const PluginContainer& pc, QWidget *parent) : + QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), + m_switching(false) { using namespace cid; @@ -302,8 +303,9 @@ void CreateInstanceDialog::finish() if (ui->launch->isChecked()) { InstanceManager::instance().switchToInstance(ci.instanceName); + m_switching = true; } else { - close(); + accept(); } } catch(Failed&) @@ -391,6 +393,11 @@ CreateInstanceDialog::Paths CreateInstanceDialog::paths() const return getSelected(&cid::Page::selectedPaths); } +bool CreateInstanceDialog::switching() const +{ + return m_switching; +} + void fixVarDir(QString& path, const std::wstring& defaultDir) { if (path.isEmpty()) { diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 2f5774ae..95d4fa68 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -70,6 +70,7 @@ public: QString instanceName() const; QString dataPath() const; Paths paths() const; + bool switching() const; CreationInfo creationInfo() const; @@ -78,6 +79,7 @@ private: const PluginContainer& m_pc; std::vector> m_pages; QString m_originalNext; + bool m_switching; template T getSelected(T (cid::Page::*mf)() const) const diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 5810a255..37bdea7b 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -20,7 +20,7 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent) dlg.exec(); } -QString iniFile(const QDir& dir) +QString makeIniFile(const QDir& dir) { return dir.filePath(QString::fromStdWString(AppConfig::iniFileName())); } @@ -52,9 +52,16 @@ public: }; - InstanceInfo(QDir dir, bool isPortable) : - m_dir(std::move(dir)), m_portable(isPortable), m_settings(iniFile(dir)) + InstanceInfo(QDir dir, bool isPortable) + : m_portable(isPortable) { + setDir(dir); + } + + void setDir(const QDir& dir) + { + m_dir = dir; + m_settings.reset(new Settings(makeIniFile(dir))); } QString name() const @@ -68,8 +75,8 @@ public: QString gameName() const { - if (auto n=m_settings.game().name()) { - if (auto e=m_settings.game().edition()) { + if (auto n=m_settings->game().name()) { + if (auto e=m_settings->game().edition()) { if (!e->isEmpty()) { return *n + " (" + *e + ")"; } @@ -83,7 +90,7 @@ public: QString gamePath() const { - if (auto n=m_settings.game().directory()) { + if (auto n=m_settings->game().directory()) { return QDir::toNativeSeparators(*n); } else { return {}; @@ -97,7 +104,12 @@ public: QString baseDirectory() const { - return QDir::toNativeSeparators(m_settings.paths().base()); + return QDir::toNativeSeparators(m_settings->paths().base()); + } + + QString iniFile() const + { + return makeIniFile(m_dir); } bool isPortable() const @@ -169,7 +181,7 @@ public: const auto loc = location(); - const auto base = m_settings.paths().base(); + const auto base = m_settings->paths().base(); // directories that might contain the individual files and directories @@ -192,18 +204,18 @@ public: // all the directories that are part of an instance; none of them are // mandatory for deletion const std::vector dirs = { - m_settings.paths().downloads(), - m_settings.paths().mods(), - m_settings.paths().cache(), - m_settings.paths().profiles(), - m_settings.paths().overwrite(), + m_settings->paths().downloads(), + m_settings->paths().mods(), + m_settings->paths().cache(), + m_settings->paths().profiles(), + m_settings->paths().overwrite(), m_dir.filePath(QString::fromStdWString(AppConfig::dumpsDir())), m_dir.filePath(QString::fromStdWString(AppConfig::logPath())), }; // all the files that are part of an instance const std::vector files = { - {iniFile(m_dir), true}, // the ini file must be deleted + {iniFile(), true}, // the ini file must be deleted }; @@ -270,9 +282,9 @@ public: } private: + const bool m_portable; QDir m_dir; - bool m_portable; - Settings m_settings; + std::unique_ptr m_settings; }; @@ -310,10 +322,11 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); - connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); connect(ui->convertToGlobal, &QPushButton::clicked, [&]{ convertToGlobal(); }); connect(ui->convertToPortable, &QPushButton::clicked, [&]{ convertToPortable(); }); + connect(ui->openINI, &QPushButton::clicked, [&]{ openINI(); }); + connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); connect(ui->close, &QPushButton::clicked, [&]{ close(); }); @@ -387,6 +400,18 @@ void InstanceManagerDialog::select(std::size_t i) } } +void InstanceManagerDialog::select(const QString& name) +{ + for (std::size_t i=0; iname() == name) { + select(i); + return; + } + } + + log::error("can't select instance {}, not in list", name); +} + void InstanceManagerDialog::selectActiveInstance() { const auto active = InstanceManager::instance().currentInstance(); @@ -492,6 +517,8 @@ void InstanceManagerDialog::rename() return; } + const auto selIndex = singleSelectionIndex(); + auto& m = InstanceManager::instance(); if (i->isActive()) { QMessageBox::information(this, @@ -519,6 +546,10 @@ void InstanceManagerDialog::rename() return; } + + m_model->item(selIndex)->setText(newName); + i->setDir(dest); + fillData(*i); } void InstanceManagerDialog::exploreLocation() @@ -542,6 +573,13 @@ void InstanceManagerDialog::exploreGame() } } +void InstanceManagerDialog::openINI() +{ + if (const auto* i=singleSelection()) { + shell::Open(i->iniFile()); + } +} + void InstanceManagerDialog::deleteInstance() { const auto* i = singleSelection(); @@ -665,7 +703,19 @@ void InstanceManagerDialog::onSelection() void InstanceManagerDialog::createNew() { CreateInstanceDialog dlg(m_pc, this); - dlg.exec(); + if (dlg.exec() != QDialog::Accepted) { + return; + } + + if (dlg.switching()) { + // restarting MO + return; + } + + updateInstances(); + updateList(); + + select(dlg.instanceName()); } std::size_t InstanceManagerDialog::singleSelectionIndex() const diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 659710fe..477a7d01 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -20,6 +20,7 @@ public: ~InstanceManagerDialog(); void select(std::size_t i); + void select(const QString& name); void selectActiveInstance(); void openSelectedInstance(); @@ -27,9 +28,11 @@ public: void exploreLocation(); void exploreBaseDirectory(); void exploreGame(); - void deleteInstance(); + void convertToGlobal(); void convertToPortable(); + void openINI(); + void deleteInstance(); private: static const std::size_t NoSelection = -1; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 414d4982..410b58c1 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -107,6 +107,18 @@ QAbstractItemView::NoEditTriggers + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + QListView::Adjust + + + true + @@ -308,6 +320,13 @@ + + + + Open INI + + + -- cgit v1.3.1 From 1d97d914f1de0404b5b4db1bfdeff35ed94ea422 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 3 Nov 2020 10:57:26 -0500 Subject: InstanceManager now returns new Instance struct instead of instance name moved most of the figuring out of instance parameters from InstanceManager to Instance, separated all the ui from it and put it in main.cpp added ways to show single pages in the create instance dialog so they can be used when info is missing --- src/createinstancedialog.cpp | 69 +++- src/createinstancedialog.h | 37 +- src/createinstancedialogpages.cpp | 67 ++-- src/createinstancedialogpages.h | 32 +- src/envshortcut.cpp | 2 +- src/instancemanager.cpp | 707 ++++++++++++++------------------------ src/instancemanager.h | 66 ++-- src/instancemanagerdialog.cpp | 50 ++- src/instancemanagerdialog.h | 3 + src/main.cpp | 265 ++++++++++---- src/organizercore.cpp | 8 +- src/processrunner.cpp | 13 +- src/shared/appconfig.inc | 1 + src/statusbar.cpp | 7 +- 14 files changed, 697 insertions(+), 630 deletions(-) (limited to 'src/instancemanagerdialog.h') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 0f62fcf6..d67e3451 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -13,7 +13,7 @@ using namespace MOBase; CreateInstanceDialog::CreateInstanceDialog( const PluginContainer& pc, Settings* s, QWidget *parent) : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s), - m_switching(false) + m_switching(false), m_singlePage(false) { using namespace cid; @@ -23,7 +23,7 @@ CreateInstanceDialog::CreateInstanceDialog( m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); - m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); @@ -32,6 +32,12 @@ CreateInstanceDialog::CreateInstanceDialog( ui->pages->setCurrentIndex(0); ui->launch->setChecked(true); + if (!m_settings) + { + // first run of MO, there are no instances yet, force launch + ui->launch->setEnabled(false); + } + if (m_pages[0]->skip()) { next(); } @@ -77,7 +83,12 @@ void CreateInstanceDialog::next() const auto last = isOnLastPage(); if (last) { - finish(); + if (m_singlePage) { + // just close the dialog + accept(); + } else { + finish(); + } } else { changePage(+1); } @@ -88,6 +99,15 @@ void CreateInstanceDialog::back() changePage(-1); } +void CreateInstanceDialog::setSinglePageImpl() +{ + m_singlePage = true; + + if (m_pages[ui->pages->currentIndex()]->skip()) { + next(); + } +} + void CreateInstanceDialog::changePage(int d) { std::size_t i = static_cast(ui->pages->currentIndex()); @@ -247,8 +267,8 @@ void CreateInstanceDialog::finish() s.game().setName(ci.game->gameName()); s.game().setDirectory(ci.gameLocation); - if (!ci.gameEdition.isEmpty()) { - s.game().setEdition(ci.gameEdition); + if (!ci.gameVariant.isEmpty()) { + s.game().setEdition(ci.gameVariant); } if (ci.type == Global) { @@ -307,7 +327,8 @@ void CreateInstanceDialog::finish() } if (ui->launch->isChecked()) { - InstanceManager::instance().switchToInstance(ci.instanceName); + InstanceManager::instance().setCurrentInstance(ci.instanceName); + ExitModOrganizer(Exit::Restart); m_switching = true; } else { accept(); @@ -345,8 +366,8 @@ void CreateInstanceDialog::updateNavigation() const auto i = ui->pages->currentIndex(); const auto last = isOnLastPage(); - ui->next->setEnabled(m_pages[i]->ready()); - ui->back->setEnabled(i > 0); + ui->next->setEnabled(canNext()); + ui->back->setEnabled(canBack()); if (last) { ui->next->setText(tr("Finish")); @@ -355,6 +376,32 @@ void CreateInstanceDialog::updateNavigation() } } +bool CreateInstanceDialog::canNext() const +{ + const auto i = ui->pages->currentIndex(); + return m_pages[i]->ready(); +} + +bool CreateInstanceDialog::canBack() const +{ + auto i = ui->pages->currentIndex(); + + for (;;) + { + if (i == 0) { + break; + } + + --i; + + if (!m_pages[i]->skip()) { + return true; + } + } + + return false; +} + CreateInstanceDialog::Types CreateInstanceDialog::instanceType() const { return getSelected(&cid::Page::selectedInstanceType); @@ -370,9 +417,9 @@ QString CreateInstanceDialog::gameLocation() const return getSelected(&cid::Page::selectedGameLocation); } -QString CreateInstanceDialog::gameEdition() const +QString CreateInstanceDialog::gameVariant() const { - return getSelected(&cid::Page::selectedGameEdition); + return getSelected(&cid::Page::selectedGameVariant); } QString CreateInstanceDialog::instanceName() const @@ -433,7 +480,7 @@ CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const ci.type = getSelected(&cid::Page::selectedInstanceType); ci.game = getSelected(&cid::Page::selectedGame); ci.gameLocation = getSelected(&cid::Page::selectedGameLocation); - ci.gameEdition = getSelected(&cid::Page::selectedGameEdition); + ci.gameVariant = getSelected(&cid::Page::selectedGameVariant); ci.instanceName = getSelected(&cid::Page::selectedInstanceName); ci.paths = getSelected(&cid::Page::selectedPaths); ci.dataPath = dataPath(); diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 0841ef29..6947f2e2 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -39,7 +39,7 @@ public: Types type; MOBase::IPluginGame* game; QString gameLocation; - QString gameEdition; + QString gameVariant; QString instanceName; QString dataPath; QString iniPath; @@ -57,6 +57,32 @@ public: const PluginContainer& pluginContainer(); Settings* settings(); + template + void setSinglePage() + { + for (auto&& p : m_pages) { + if (auto* tp=dynamic_cast(p.get())) { + tp->setSkip(false); + } else { + p->setSkip(true); + } + } + + setSinglePageImpl(); + } + + template + Page* getPage() + { + for (auto&& p : m_pages) { + if (auto* tp=dynamic_cast(p.get())) { + return tp; + } + } + + return nullptr; + } + void next(); void back(); void selectPage(std::size_t i); @@ -69,7 +95,7 @@ public: Types instanceType() const; MOBase::IPluginGame* game() const; QString gameLocation() const; - QString gameEdition() const; + QString gameVariant() const; QString instanceName() const; QString dataPath() const; Paths paths() const; @@ -84,6 +110,10 @@ private: std::vector> m_pages; QString m_originalNext; bool m_switching; + bool m_singlePage; + + + void setSinglePageImpl(); template T getSelected(T (cid::Page::*mf)() const) const @@ -100,6 +130,9 @@ private: void logCreation(const QString& s); void logCreation(const std::wstring& s); + + bool canNext() const; + bool canBack() const; }; #endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index d809079d..00d49b99 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -43,7 +43,7 @@ void PlaceholderLabel::setVisible(bool b) Page::Page(CreateInstanceDialog& dlg) - : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()) + : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_skip(false) { } @@ -54,7 +54,11 @@ bool Page::ready() const bool Page::skip() const { - // no-op + return m_skip || doSkip(); +} + +bool Page::doSkip() const +{ return false; } @@ -63,6 +67,11 @@ void Page::activated() // no-op } +void Page::setSkip(bool b) +{ + m_skip = b; +} + void Page::updateNavigation() { m_dlg.updateNavigation(); @@ -92,7 +101,7 @@ QString Page::selectedGameLocation() const return {}; } -QString Page::selectedGameEdition() const +QString Page::selectedGameVariant() const { // no-op return {}; @@ -116,7 +125,7 @@ IntroPage::IntroPage(CreateInstanceDialog& dlg) { } -bool IntroPage::skip() const +bool IntroPage::doSkip() const { return GlobalSettings::hideCreateInstanceIntro(); } @@ -223,19 +232,24 @@ QString GamePage::selectedGameLocation() const return QDir::toNativeSeparators(m_selection->dir); } -void GamePage::select(IPluginGame* game) +void GamePage::select(IPluginGame* game, const QString& dir) { Game* checked = findGame(game); if (checked) { if (!checked->installed) { - const auto path = QFileDialog::getExistingDirectory( - &m_dlg, QObject::tr("Find game installation")); - - if (path.isEmpty()) { - checked = nullptr; + if (dir.isEmpty()) { + const auto path = QFileDialog::getExistingDirectory( + &m_dlg, QObject::tr("Find game installation")); + + if (path.isEmpty()) { + checked = nullptr; + } else { + checked = checkInstallation(path, checked); + } } else { - checked = checkInstallation(path, checked); + checked->dir = dir; + checked->installed = true; } } } @@ -417,7 +431,6 @@ void GamePage::fillList() const bool showAll = ui->showAllGames->isChecked(); clearButtons(); - addButton(createCustomButton()); for (auto& g : m_games) { g->button = nullptr; @@ -434,6 +447,8 @@ void GamePage::fillList() createGameButton(g.get()); addButton(g->button); } + + addButton(createCustomButton()); } void GamePage::clearButtons() @@ -584,17 +599,17 @@ IPluginGame* GamePage::confirmOtherGame( } -EditionsPage::EditionsPage(CreateInstanceDialog& dlg) +VariantsPage::VariantsPage(CreateInstanceDialog& dlg) : Page(dlg), m_previousGame(nullptr) { } -bool EditionsPage::ready() const +bool VariantsPage::ready() const { return !m_selection.isEmpty(); } -bool EditionsPage::skip() const +bool VariantsPage::doSkip() const { auto* g = m_dlg.game(); if (!g) { @@ -606,7 +621,7 @@ bool EditionsPage::skip() const return (variants.size() < 2); } -void EditionsPage::activated() +void VariantsPage::activated() { auto* g = m_dlg.game(); @@ -617,7 +632,7 @@ void EditionsPage::activated() } } -void EditionsPage::select(const QString& variant) +void VariantsPage::select(const QString& variant) { for (auto* b : m_buttons) { if (b->text() == variant) { @@ -629,9 +644,13 @@ void EditionsPage::select(const QString& variant) } updateNavigation(); + + if (!m_selection.isEmpty()) { + next(); + } } -QString EditionsPage::selectedGameEdition() const +QString VariantsPage::selectedGameVariant() const { auto* g = m_dlg.game(); if (!g) { @@ -647,7 +666,7 @@ QString EditionsPage::selectedGameEdition() const } } -void EditionsPage::fillList() +void VariantsPage::fillList() { ui->editions->clear(); m_buttons.clear(); @@ -665,7 +684,7 @@ void EditionsPage::fillList() QObject::connect(b, &QAbstractButton::clicked, [v, this] { select(v); - }); + }); ui->editions->addButton(b, QDialogButtonBox::AcceptRole); m_buttons.push_back(b); @@ -687,7 +706,7 @@ bool NamePage::ready() const return m_okay; } -bool NamePage::skip() const +bool NamePage::doSkip() const { return (m_dlg.instanceType() == CreateInstanceDialog::Portable); } @@ -983,7 +1002,7 @@ bool NexusPage::ready() const return true; } -bool NexusPage::skip() const +bool NexusPage::doSkip() const { return m_skip; } @@ -1047,8 +1066,8 @@ QString ConfirmationPage::makeReview() const // game QString name = m_dlg.game()->gameName(); - if (!m_dlg.gameEdition().isEmpty()) { - name += " (" + m_dlg.gameEdition() + ")"; + if (!m_dlg.gameVariant().isEmpty()) { + name += " (" + m_dlg.gameVariant() + ")"; } lines.push_back(QObject::tr("Game: %1").arg(name)); diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 412f2d84..e239c196 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -37,16 +37,18 @@ public: Page(CreateInstanceDialog& dlg); virtual bool ready() const; - virtual bool skip() const; virtual void activated(); + void setSkip(bool b); + bool skip() const; + void updateNavigation(); void next(); virtual CreateInstanceDialog::Types selectedInstanceType() const; virtual MOBase::IPluginGame* selectedGame() const; virtual QString selectedGameLocation() const; - virtual QString selectedGameEdition() const; + virtual QString selectedGameVariant() const; virtual QString selectedInstanceName() const; virtual CreateInstanceDialog::Paths selectedPaths() const; @@ -54,6 +56,9 @@ protected: Ui::CreateInstanceDialog* ui; CreateInstanceDialog& m_dlg; const PluginContainer& m_pc; + bool m_skip; + + virtual bool doSkip() const; }; @@ -62,7 +67,8 @@ class IntroPage : public Page public: IntroPage(CreateInstanceDialog& dlg); - bool skip() const override; +protected: + bool doSkip() const override; }; @@ -91,7 +97,7 @@ public: MOBase::IPluginGame* selectedGame() const override; QString selectedGameLocation() const override; - void select(MOBase::IPluginGame* game); + void select(MOBase::IPluginGame* game, const QString& dir={}); void selectCustom(); void warnUnrecognized(const QString& path); @@ -133,18 +139,20 @@ private: }; -class EditionsPage : public Page +class VariantsPage : public Page { public: - EditionsPage(CreateInstanceDialog& dlg); + VariantsPage(CreateInstanceDialog& dlg); bool ready() const override; - bool skip() const override; void activated() override; - QString selectedGameEdition() const override; + QString selectedGameVariant() const override; void select(const QString& variant); +protected: + bool doSkip() const override; + private: MOBase::IPluginGame* m_previousGame; std::vector m_buttons; @@ -160,10 +168,12 @@ public: NamePage(CreateInstanceDialog& dlg); bool ready() const override; - bool skip() const override; void activated() override; QString selectedInstanceName() const override; +protected: + bool doSkip() const override; + private: mutable PlaceholderLabel m_label, m_exists, m_invalid; bool m_modified; @@ -212,9 +222,11 @@ public: ~NexusPage(); bool ready() const override; - bool skip() const override; void activated() override; +protected: + bool doSkip() const override; + private: std::unique_ptr m_connectionUI; bool m_skip; diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 99495c39..5222665b 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -149,7 +149,7 @@ Shortcut::Shortcut(const Executable& exe) m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); m_arguments = QString("\"moshortcut://%1:%2\"") - .arg(InstanceManager::instance().currentInstance()) + .arg(InstanceManager::instance().currentInstance()->name()) .arg(exe.title()); m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 4ad099ed..c79c5254 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -38,257 +38,320 @@ along with Mod Organizer. If not, see . using namespace MOBase; -InstanceManager::InstanceManager() +Instance::Instance(QDir dir, bool portable, QString profileName) : + m_dir(std::move(dir)), m_portable(portable), m_plugin(nullptr), + m_profile(std::move(profileName)) { - GlobalSettings::updateRegistryKey(); } -InstanceManager &InstanceManager::instance() +QString Instance::name() const { - static InstanceManager s_Instance; - return s_Instance; + if (isPortable()) + return QObject::tr("Portable"); + else + return m_dir.dirName(); } -void InstanceManager::overrideInstance(const QString& instanceName) +QString Instance::gameName() const { - m_overrideInstanceName = instanceName; - m_overrideInstance = true; + return m_gameName; } -void InstanceManager::overrideProfile(const QString& profileName) +QString Instance::gameDirectory() const { - m_overrideProfileName = profileName; - m_overrideProfile = true; + return m_gameDir; } -QString InstanceManager::currentInstance() const +QDir Instance::directory() const { - if (m_overrideInstance) - return m_overrideInstanceName; - else - return GlobalSettings::currentInstance(); + return m_dir; } -void InstanceManager::clearCurrentInstance() +MOBase::IPluginGame* Instance::gamePlugin() const { - setCurrentInstance(""); - m_Reset = true; - m_overrideInstance = false; + return m_plugin; } -void InstanceManager::switchToInstance(const QString& instanceName) +QString Instance::profileName() const { - setCurrentInstance(instanceName); - ExitModOrganizer(Exit::Restart); + return m_profile; } -void InstanceManager::setCurrentInstance(const QString &name) +QString Instance::iniPath() const { - GlobalSettings::setCurrentInstance(name); + return InstanceManager::iniPath(m_dir); } -bool InstanceManager::deleteLocalInstance(const QString& instanceId) const +bool Instance::isPortable() const { - QString dir = instancePath(instanceId); + return m_portable; +} - const auto Recycle = QMessageBox::Save; - const auto Delete = QMessageBox::Yes; - const auto Cancel = QMessageBox::Cancel; +Instance::SetupResults Instance::setup(PluginContainer& plugins) +{ + Settings s(iniPath()); - const auto r = MOBase::TaskDialog() - .title(QObject::tr("Deleting instance folder")) - .main(QObject::tr("This will delete the instance folder.")) - .content(dir) - .icon(QMessageBox::Warning) - .button({QObject::tr("Move the folder to the recycle bin"), Recycle}) - .button({QObject::tr("Delete the folder permanently"), Delete}) - .button({QObject::tr("Cancel"), Cancel}) - .exec(); + if (s.iniStatus() != QSettings::NoError) { + log::error("can't read ini {}", iniPath()); + return SetupResults::BadIni; + } - std::wstring error; + if (m_gameName.isEmpty()) { + if (auto v=s.game().name()) + m_gameName = *v; + } - switch (r) - { - case Recycle: - { - if (MOBase::shellDelete(QStringList(dir), true)) { - return true; - } + if (m_gameDir.isEmpty()) { + if (auto v=s.game().directory()) + m_gameDir = *v; + } - const auto e = GetLastError(); - error = formatSystemMessage(e); - log::warn("failed to move to trash '{}', {}", dir, error); + const auto r = getGamePlugin(plugins); + if (r != SetupResults::Ok) { + return r; + } - break; + if (m_gameVariant.isEmpty()) { + if (auto v=s.game().edition()) { + m_gameVariant = *v; } + } - case Delete: - { - if (MOBase::shellDelete(QStringList(dir), false)) { - return true; - } + if (m_gameVariant.isEmpty() && m_plugin->gameVariants().size() > 1) { + return SetupResults::MissingVariant; + } else { + m_plugin->setGameVariant(m_gameVariant); + } - const auto e = GetLastError(); - error = formatSystemMessage(e); - log::warn("failed to delete '{}', {}", dir, error); + getProfile(s); - break; - } + s.game().setName(m_gameName); + s.game().setDirectory(m_gameDir); + s.game().setSelectedProfileName(m_profile); - default: - { - return true; - } - } + if (!m_gameVariant.isEmpty()) + s.game().setEdition(m_gameVariant); - QMessageBox::critical( - nullptr, QObject::tr("Error"), QObject::tr( - "Could not delete instance folder \"%1\".\n\n%2") - .arg(dir).arg(error), - QMessageBox::Ok); + m_plugin->setGamePath(m_gameDir); - return false; + return SetupResults::Ok; } -QString InstanceManager::manageInstances(const QStringList &instanceList) const +void Instance::setGame(const QString& name, const QString& dir) { - SelectionDialog selection(QString("

%1


%2") - .arg(QObject::tr("Select an instance to delete")) - .arg(QObject::tr( - "Deleting an instance will delete all the mods, downloads, profiles " - "(including profile-specific saves) and anything in the overwrite " - "folder.

" - "Custom paths outside of the instance folder will not be deleted."))); + m_gameName = name; + m_gameDir = dir; +} - for (const QString &instance : instanceList) { - selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance); - } +void Instance::setVariant(const QString& name) +{ + m_gameVariant = name; +} - if (selection.exec() == QDialog::Rejected) { - return (chooseInstance(instanceNames())); - } - else { - QString choice = selection.getChoiceData().toString(); - deleteLocalInstance(choice); +Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) +{ + if (!m_gameName.isEmpty() && !m_gameDir.isEmpty()) + { + // normal case: both the name and dir are in the ini + + // find the plugin by name + for (IPluginGame* game : plugins.plugins()) { + if (m_gameName.compare(game->gameName(), Qt::CaseInsensitive) == 0) { + // plugin found, check if the game directory is valid + + if (!game->looksValid(m_gameDir)) { + // the directory from the ini is not valid anymore + log::warn( + "game plugin {} says dir {} from ini {} is not valid", + game->gameName(), m_gameDir, iniPath()); + + // note that some plugins return true for isInstalled() if a path + // is found in the registry, but without actually checking if it's + // valid + + if (game->isInstalled() && game->looksValid(game->gameDirectory())) { + // bad game directory but the plugin reports there's a valid one + // somewhere; take it instead + log::warn( + "game plugin {} found a game at {}, taking it", + game->gameName(), game->gameDirectory().absolutePath()); + + m_gameDir = game->gameDirectory().absolutePath(); + } else { + // game seems to be gone completely + log::warn("game plugin {} found no game installation at all", game->gameName()); + return SetupResults::GameGone; + } + } + + m_plugin = game; + return SetupResults::Ok; + } + } + + log::warn("game plugin {} not found", m_gameName); + return SetupResults::PluginGone; } + else if (m_gameName.isEmpty() && !m_gameDir.isEmpty()) + { + // the name is missing, but there's a directory; find a plugin that can + // handle it - return(manageInstances(instanceNames())); -} + log::warn( + "game name is missing from ini {} but dir {} is available", + iniPath(), m_gameDir); -QString InstanceManager::queryInstanceName(const QStringList &instanceList) const -{ - QString instanceId; - QString dialogText; - while (instanceId.isEmpty()) { - QInputDialog dialog; + for (IPluginGame* game : plugins.plugins()) { + if (game->looksValid(m_gameDir)) { + // take it + log::warn("found plugin {} that can use dir {}", game->gameName(), m_gameDir); - dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance")); - dialog.setLabelText(QObject::tr("Enter a new name or select one from the suggested list: \n" - "(This is just a name for the Instance and can be whatever you wish,\n" - " the actual game selection will happen on the next screen regardless of chosen name)")); - // would be neat if we could take the names from the game plugins but - // the required initialization order requires the ini file to be - // available *before* we load plugins - dialog.setComboBoxItems({ "NewName", "Fallout 4", "SkyrimSE", "Skyrim", "SkyrimVR", "Fallout 3", - "Fallout NV", "TTW", "FO4VR", "Oblivion", "Morrowind", "Enderal" }); - dialog.setComboBoxEditable(true); + m_plugin = game; + m_gameName = game->gameName(); - if (dialog.exec() == QDialog::Rejected) { - throw MOBase::MyException(QObject::tr("Canceled")); + return SetupResults::Ok; + } } - dialogText = dialog.textValue(); - instanceId = sanitizeInstanceName(dialogText); - if (instanceId != dialogText) { - if (QMessageBox::question( nullptr, - QObject::tr("Invalid instance name"), - QObject::tr("The instance name \"%1\" is invalid. Use the name \"%2\" instead?").arg(dialogText,instanceId), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - instanceId=""; - continue; + + log::error("no plugins can use dir {}", m_gameDir); + return SetupResults::GameGone; + } + else if (!m_gameName.isEmpty() && m_gameDir.isEmpty()) + { + // dir is missing, find a plugin with the correct name and use the install + // dir it detected + + log::warn( + "game dir is missing from ini {} but name {} is available", + iniPath(), m_gameName); + + for (IPluginGame* game : plugins.plugins()) { + if (m_gameName.compare(game->gameName(), Qt::CaseInsensitive) == 0) { + // plugin found, use its detected installation dir + + if (game->isInstalled()) { + log::warn( + "found plugin {} that matches name in ini {}, using auto detected " + "game dir {}", + game->gameName(), iniPath(), game->gameDirectory().absolutePath()); + + m_plugin = game; + m_gameDir = game->gameDirectory().absolutePath(); + + return SetupResults::Ok; + } else { + log::warn( + "found plugin {} that matches name in ini {}, but no game install " + "detected by plugin", + game->gameName(), iniPath()); + + return SetupResults::GameGone; } + } } - bool alreadyExists=false; - for (const QString &instance : instanceList) { - if(instanceId==instance) - alreadyExists=true; - } - if(alreadyExists) - { - QMessageBox msgBox; - msgBox.setText( QObject::tr("The instance \"%1\" already exists.").arg(instanceId) ); - msgBox.setInformativeText(QObject::tr("Please choose a different instance name, like: \"%1 1\" .").arg(instanceId)); - msgBox.exec(); - instanceId=""; - } + // plugin seems to be gone + log::error("no plugin matches name {}", m_gameName); + return SetupResults::PluginGone; + } + else + { + // can't do anything with these two missing + log::error("both game name and dir are missing from ini {}", iniPath()); + return SetupResults::IniMissingGame; } - return instanceId; } -QString InstanceManager::chooseInstance(const QStringList &instanceList) const +void Instance::getProfile(const Settings& s) { - if (portableInstallIsLocked()) { - return QString(); + if (!m_profile.isEmpty()) { + // there's already a profile set up, probably an override + return; } - enum class Special : uint8_t { - NewInstance, - Portable, - Manage - }; - - SelectionDialog selection( - QString("

%1


%2") - .arg(QObject::tr("Choose Instance")) - .arg(QObject::tr( - "Each Instance is a full set of MO data files (mods, " - "downloads, profiles, configuration, ...). You can use multiple " - "instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. " - "If your MO folder is writable, you can also store a single instance locally (called " - "a Portable install, and all the MO data files will be inside the installation folder).")), - nullptr); - selection.disableCancel(); - for (const QString &instance : instanceList) { - selection.addChoice(instance, "", instance); + if (auto name=s.game().selectedProfileName()) { + // use last profile + m_profile = *name; + return; } - selection.addChoice(QIcon(":/MO/gui/add"), QObject::tr("New"), - QObject::tr("Create a new instance."), - static_cast(Special::NewInstance)); + // profile missing from ini, use the default + m_profile = QString::fromStdWString(AppConfig::defaultProfileName()); - if (QFileInfo(qApp->applicationDirPath()).isWritable()) { - selection.addChoice(QIcon(":/MO/gui/package"), QObject::tr("Portable"), - QObject::tr("Use MO folder for data."), - static_cast(Special::Portable)); - } + log::warn( + "no profile found in ini {}, using default '{}'", + iniPath(), m_profile); +} - selection.addChoice(QIcon(":/MO/gui/remove"), QObject::tr("Manage Instances"), - QObject::tr("Delete an Instance."), - static_cast(Special::Manage)); - selection.setWindowFlags(selection.windowFlags() | Qt::WindowStaysOnTopHint); +InstanceManager::InstanceManager() +{ + GlobalSettings::updateRegistryKey(); +} + +InstanceManager &InstanceManager::instance() +{ + static InstanceManager s_Instance; + return s_Instance; +} - if (selection.exec() == QDialog::Rejected) { - log::debug("rejected"); - throw MOBase::MyException(QObject::tr("Canceled")); +void InstanceManager::overrideInstance(const QString& instanceName) +{ + m_overrideInstanceName = instanceName; + m_overrideInstance = true; +} + +void InstanceManager::overrideProfile(const QString& profileName) +{ + m_overrideProfileName = profileName; + m_overrideProfile = true; +} + +std::optional InstanceManager::currentInstance() const +{ + const QString profile = m_overrideProfile ? m_overrideProfileName : ""; + + if (portableInstallIsLocked()) { + // force portable instance + return Instance(QDir(portablePath()), true, profile); } - QVariant choice = selection.getChoiceData(); + QString name; - if (choice.type() == QVariant::String) { - return choice.toString(); - } else { - switch (static_cast(choice.value())) { - case Special::NewInstance: return queryInstanceName(instanceList); - case Special::Portable: return QString(); - case Special::Manage: { + if (m_overrideInstance) + name = m_overrideInstanceName; + else + name = GlobalSettings::currentInstance(); - return(manageInstances(instanceNames())); - } - default: throw std::runtime_error("invalid selection"); + if (name.isEmpty()) { + if (portableInstanceExists()) { + // use portable + return Instance(QDir(portablePath()), true, profile); + } else { + // no instance set + return {}; } } + + QString path = instancePath(name); + if (!QFileInfo::exists(path)) { + // the previously used instance doesn't exist anymore + return {}; + } + + return Instance(QDir(path), false, profile); +} + +void InstanceManager::clearCurrentInstance() +{ + setCurrentInstance(""); + m_overrideInstance = false; +} + +void InstanceManager::setCurrentInstance(const QString &name) +{ + GlobalSettings::setCurrentInstance(name); } QString InstanceManager::instancePath(const QString& instanceName) const @@ -302,6 +365,11 @@ QString InstanceManager::instancesPath() const QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } +QString InstanceManager::iniPath(const QDir& instanceDir) +{ + return instanceDir.filePath(QString::fromStdWString(AppConfig::iniFileName())); +} + std::vector InstanceManager::instancePaths() const { const std::set ignore = { @@ -362,287 +430,10 @@ bool InstanceManager::allowedToChangeInstance() const return !portableInstallIsLocked(); } - -void InstanceManager::createDataPath(const QString &dataPath) const -{ - if (!QDir(dataPath).exists()) { - if (!QDir().mkpath(dataPath)) { - throw MOBase::MyException( - QObject::tr("failed to create %1").arg(dataPath)); - } else { - QMessageBox::information( - nullptr, QObject::tr("Data directory created"), - QObject::tr("New data directory created at %1. If you don't want to " - "store a lot of data there, reconfigure the storage " - "directories via settings.").arg(dataPath)); - } - } -} - - -QString InstanceManager::determineDataPath() -{ - QString instanceId = currentInstance(); - if (portableInstallIsLocked()) - { - instanceId.clear(); - } - if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstanceExists())) - { - // startup, apparently using portable mode before - return qApp->applicationDirPath(); - } - - QString dataPath = QDir::fromNativeSeparators( - QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + "/" + instanceId); - - - if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) { - instanceId = chooseInstance(instanceNames()); - setCurrentInstance(instanceId); - if (!instanceId.isEmpty()) { - dataPath = QDir::fromNativeSeparators( - QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + "/" + instanceId); - } - } - - if (instanceId.isEmpty()) { - return qApp->applicationDirPath(); - } else { - createDataPath(dataPath); - - return dataPath; - } -} - -QString InstanceManager::determineProfile(const Settings &settings) -{ - auto selectedProfileName = settings.game().selectedProfileName(); - - if (m_overrideProfile) { - log::debug("profile overwritten on command line"); - selectedProfileName = m_overrideProfileName; - } - - if (!selectedProfileName) { - log::debug("no configured profile"); - selectedProfileName = "Default"; - } - - return *selectedProfileName; -} - -bool InstanceManager::determineGameEdition( - Settings& settings, IPluginGame* game) -{ - QString edition; - - if (auto v=settings.game().edition()) { - edition = *v; - } else { - QStringList editions = game->gameVariants(); - if (editions.size() < 2) { - edition = ""; - return true; - } - - SelectionDialog selection( - QObject::tr("Please select the game edition you have (MO can't " - "start the game correctly if this is set " - "incorrectly!)"), - nullptr); - - selection.setWindowFlag(Qt::WindowStaysOnTopHint, true); - - int index = 0; - for (const QString &edition : editions) { - selection.addChoice(edition, "", index++); - } - - if (selection.exec() == QDialog::Rejected) { - return false; - } - - edition = selection.getChoiceString(); - settings.game().setEdition(edition); - } - - game->setGameVariant(edition); - - return true; -} - -MOBase::IPluginGame *selectGame( - Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) -{ - settings.game().setName(game->gameName()); - - QString gameDir = gamePath.absolutePath(); - game->setGamePath(gameDir); - - settings.game().setDirectory(gameDir); - - return game; -} - -MOBase::IPluginGame* InstanceManager::determineCurrentGame( - const QString& moPath, Settings& settings, const PluginContainer &plugins) -{ - //Determine what game we are running where. Be very paranoid in case the - //user has done something odd. - - //If the game name has been set up, try to use that. - const auto gameName = settings.game().name(); - const bool gameConfigured = (gameName.has_value() && *gameName != ""); - - if (gameConfigured) { - MOBase::IPluginGame *game = plugins.managedGame(*gameName); - if (game == nullptr) { - reportError( - QObject::tr("Plugin to handle %1 no longer installed. An antivirus might have deleted files.") - .arg(*gameName)); - - return nullptr; - } - - auto gamePath = settings.game().directory(); - if (!gamePath || *gamePath == "") { - gamePath = game->gameDirectory().absolutePath(); - } - - QDir gameDir(*gamePath); - QFileInfo directoryInfo(gameDir.path()); - - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath)); - } - - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - - //If we've made it this far and the instance is already configured for a game, something has gone wrong. - //Tell the user about it. - if (gameConfigured) { - const auto gamePath = settings.game().directory(); - - reportError( - QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") - .arg(*gameName).arg(gamePath ? *gamePath : "")); - } - - SelectionDialog selection(gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); - - for (IPluginGame *game : plugins.plugins()) { - //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) - continue; - - //Only add games that are installed - if (game->isInstalled()) { - QString path = game->gameDirectory().absolutePath(); - selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); - } - } - - selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr))); - - while (selection.exec() != QDialog::Rejected) { - IPluginGame * game = selection.getChoiceData().value(); - QString gamePath = selection.getChoiceDescription(); - QFileInfo directoryInfo(gamePath); - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); - } - if (game != nullptr) { - return selectGame(settings, game->gameDirectory(), game); - } - - gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), - QString(), QFileDialog::ShowDirsOnly); - - if (!gamePath.isEmpty()) { - QDir gameDir(gamePath); - QFileInfo directoryInfo(gamePath); - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); - } - QList possibleGames; - for (IPluginGame * const game : plugins.plugins()) { - //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) - continue; - - //Only try plugins that look valid for this directory - if (game->looksValid(gameDir)) { - possibleGames.append(game); - } - } - - if (possibleGames.count() > 1) { - SelectionDialog browseSelection(gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), - nullptr, QSize(32, 32)); - - for (IPluginGame *game : possibleGames) { - browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game)); - } - - if (browseSelection.exec() == QDialog::Accepted) { - return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); - } else { - reportError(gameConfigured ? - QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) : - QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); - } - } else if(possibleGames.count() == 1) { - return selectGame(settings, gameDir, possibleGames[0]); - } else { - if (gameConfigured) { - reportError( - QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.") - .arg(*gameName).arg(gamePath)); - } else { - QString supportedGames; - - for (IPluginGame * const game : plugins.plugins()) { - supportedGames += "
  • " + game->gameName() + "
  • "; - } - - QString text = QObject::tr( - "No game identified in \"%1\". The directory is required to " - "contain the game binary.

    " - "These are the games supported by Mod Organizer:" - "
      %2
    ") - .arg(gamePath) - .arg(supportedGames); - - reportError(text); - } - } - } - } - - return nullptr; -} - const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( const QDir& instanceDir, const PluginContainer& plugins) const { - const QString ini = - QDir(instanceDir).filePath(QString::fromStdWString(AppConfig::iniFileName())); - + const QString ini = iniPath(instanceDir); Settings s(ini); diff --git a/src/instancemanager.h b/src/instancemanager.h index c2d1e0f4..ddab4a2e 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -29,29 +29,61 @@ namespace MOBase { class IPluginGame; } class Settings; class PluginContainer; + +class Instance +{ +public: + enum class SetupResults + { + Ok, + BadIni, + IniMissingGame, + PluginGone, + GameGone, + MissingVariant + }; + + Instance(QDir dir, bool portable, QString profileName={}); + + SetupResults setup(PluginContainer& plugins); + + void setGame(const QString& name, const QString& dir); + void setVariant(const QString& name); + + QString name() const; + QString gameName() const; + QString gameDirectory() const; + QDir directory() const; + MOBase::IPluginGame* gamePlugin() const; + QString profileName() const; + QString iniPath() const; + bool isPortable() const; + +private: + QDir m_dir; + bool m_portable; + QString m_gameName, m_gameDir, m_gameVariant; + MOBase::IPluginGame* m_plugin; + QString m_profile; + + SetupResults getGamePlugin(PluginContainer& plugins); + void getProfile(const Settings& s); +}; + + class InstanceManager { public: static InstanceManager &instance(); - // restarts MO - // - void switchToInstance(const QString& instanceName); - void overrideInstance(const QString& instanceName); void overrideProfile(const QString& profileName); - QString determineDataPath(); - QString determineProfile(const Settings &settings); - bool determineGameEdition(Settings& settings, MOBase::IPluginGame* game); - MOBase::IPluginGame* determineCurrentGame( - const QString& moPath, Settings& settings, const PluginContainer &plugins); - const MOBase::IPluginGame* gamePluginForDirectory( const QDir& dir, const PluginContainer& plugins) const; void clearCurrentInstance(); - QString currentInstance() const; + std::optional currentInstance() const; void setCurrentInstance(const QString &name); bool allowedToChangeInstance() const; @@ -68,23 +100,13 @@ public: bool instanceExists(const QString& instanceName) const; bool validInstanceName(const QString& instanceName) const; QString instancePath(const QString& instanceName) const; + static QString iniPath(const QDir& instanceDir); private: - InstanceManager(); - - bool deleteLocalInstance(const QString &instanceId) const; - - QString manageInstances(const QStringList &instanceList) const; - - QString queryInstanceName(const QStringList &instanceList) const; - QString chooseInstance(const QStringList &instanceList) const; - - void createDataPath(const QString &dataPath) const; bool portableInstallIsLocked() const; private: - bool m_Reset {false}; bool m_overrideInstance{false}; QString m_overrideInstanceName; bool m_overrideProfile{false}; diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 545b5c71..f662082f 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -6,6 +6,7 @@ #include "selectiondialog.h" #include "plugincontainer.h" #include "shared/appconfig.h" +#include "shared/util.h" #include #include #include @@ -20,11 +21,6 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent) dlg.exec(); } -QString makeIniFile(const QDir& dir) -{ - return dir.filePath(QString::fromStdWString(AppConfig::iniFileName())); -} - class InstanceInfo { @@ -61,7 +57,7 @@ public: void setDir(const QDir& dir) { m_dir = dir; - m_settings.reset(new Settings(makeIniFile(dir))); + m_settings.reset(new Settings(InstanceManager::iniPath(dir))); } QString name() const @@ -109,7 +105,7 @@ public: QString iniFile() const { - return makeIniFile(m_dir); + return InstanceManager::iniPath(m_dir); } QIcon icon(const PluginContainer& plugins) const @@ -134,10 +130,13 @@ public: { auto& m = InstanceManager::instance(); - if (m_portable && m.currentInstance() == "") { - return true; - } else if (m.currentInstance() == name()) { - return true; + if (auto i=m.currentInstance()) + { + if (m_portable) { + return i->isPortable(); + } else { + return (i->name() == name()); + } } return false; @@ -316,7 +315,7 @@ InstanceManagerDialog::~InstanceManagerDialog() = default; InstanceManagerDialog::InstanceManagerDialog( const PluginContainer& pc, QWidget *parent) : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc), - m_model(nullptr) + m_model(nullptr), m_restartOnSelect(true) { ui->setupUi(this); @@ -449,14 +448,16 @@ void InstanceManagerDialog::selectActiveInstance() { const auto active = InstanceManager::instance().currentInstance(); - for (std::size_t i=0; iname() == active) { - select(i); + if (active) { + for (std::size_t i=0; iname() == active->name()) { + select(i); - ui->list->scrollTo( - m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); + ui->list->scrollTo( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); - return; + return; + } } } @@ -470,7 +471,13 @@ void InstanceManagerDialog::openSelectedInstance() return; } - InstanceManager::instance().switchToInstance(m_instances[i]->name()); + InstanceManager::instance().setCurrentInstance(m_instances[i]->name()); + + if (m_restartOnSelect) { + ExitModOrganizer(Exit::Restart); + } + + accept(); } QString getInstanceName( @@ -697,6 +704,11 @@ void InstanceManagerDialog::deleteInstance() } +void InstanceManagerDialog::setRestartOnSelect(bool b) +{ + m_restartOnSelect = b; +} + bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) { if (MOBase::shellDelete(files, recycle, this)) { diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 477a7d01..5b08ffc2 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -34,6 +34,8 @@ public: void openINI(); void deleteInstance(); + void setRestartOnSelect(bool b); + private: static const std::size_t NoSelection = -1; @@ -42,6 +44,7 @@ private: std::vector> m_instances; MOBase::FilterWidget m_filter; QStandardItemModel* m_model; + bool m_restartOnSelect; void updateInstances(); diff --git a/src/main.cpp b/src/main.cpp index fd5a47c9..2a5a3e81 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "instancemanagerdialog.h" #include "createinstancedialog.h" +#include "createinstancedialogpages.h" #include "organizercore.h" #include "env.h" #include "envmodule.h" @@ -273,9 +274,166 @@ std::optional handleCommandLine( void openInstanceManager(PluginContainer& pc, QWidget* parent); +std::optional selectInstance() +{ + NexusInterface ni(nullptr); + + PluginContainer pc(nullptr); + pc.loadPlugins(); + + InstanceManagerDialog dlg(pc); + dlg.setRestartOnSelect(false); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return {}; + } + + return InstanceManager::instance().currentInstance(); +} + +enum class SetupInstanceResults +{ + Ok, + TryAgain, + SelectAnother, + Exit +}; + + +void criticalOnTop(const QString& message) +{ + QMessageBox mb(QMessageBox::Critical, QObject::tr("Mod Organizer"), message); + + mb.show(); + mb.activateWindow(); + mb.raise(); + mb.exec(); +} + + +SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) +{ + const auto setupResult = instance.setup(pc); + + switch (setupResult) + { + case Instance::SetupResults::Ok: + { + return SetupInstanceResults::Ok; + } + + case Instance::SetupResults::BadIni: + { + criticalOnTop( + QObject::tr("Cannot open instance '%1', failed to read INI file %2.") + .arg(instance.name()).arg(instance.iniPath())); + + return SetupInstanceResults::SelectAnother; + } + + case Instance::SetupResults::IniMissingGame: + { + criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the managed game was not found in the INI " + "file %2. Select the game managed by this instance.") + .arg(instance.name()).arg(instance.iniPath())); + + CreateInstanceDialog dlg(pc, nullptr); + dlg.setSinglePage(); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setGame( + dlg.creationInfo().game->gameName(), + dlg.creationInfo().game->gameDirectory().absolutePath()); + + return SetupInstanceResults::TryAgain; + } + + case Instance::SetupResults::PluginGone: + { + criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the game plugin '%2' doesn't exist. It " + "may have been deleted by an antivirus. Select another instance.") + .arg(instance.name()).arg(instance.gameName())); + + return SetupInstanceResults::SelectAnother; + } + + case Instance::SetupResults::GameGone: + { + criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the game directory '%2' doesn't exist or " + "the game plugin '%3' doesn't recognize it. Select the game managed " + "by this instance.") + .arg(instance.name()) + .arg(instance.gameDirectory()) + .arg(instance.gameName())); + + CreateInstanceDialog dlg(pc, nullptr); + dlg.setSinglePage(); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setGame( + dlg.creationInfo().game->gameName(), + dlg.creationInfo().game->gameDirectory().absolutePath()); + + return SetupInstanceResults::TryAgain; + } + + case Instance::SetupResults::MissingVariant: + { + CreateInstanceDialog dlg(pc, nullptr); + + dlg.getPage()->select( + instance.gamePlugin(), instance.gameDirectory()); + + dlg.setSinglePage(); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setVariant(dlg.creationInfo().gameVariant); + + return SetupInstanceResults::TryAgain; + } + + default: + { + return SetupInstanceResults::Exit; + } + } +} + int runApplication( MOApplication &application, const cl::CommandLine& cl, - SingleInstance &instance, const QString &dataPath) + SingleInstance &instance, const QString &dataPath, + Instance& currentInstance) { TimeThis tt("runApplication() to exec()"); @@ -345,57 +503,48 @@ int runApplication( pluginContainer = std::make_unique(&organizer); pluginContainer->loadPlugins(); - MOBase::IPluginGame* game = InstanceManager::instance() - .determineCurrentGame( - application.applicationDirPath(), settings, *pluginContainer); - - if (game == nullptr) { - InstanceManager &instance = InstanceManager::instance(); - QString instanceName = instance.currentInstance(); - - if (instanceName.compare("Portable", Qt::CaseInsensitive) != 0) { - instance.clearCurrentInstance(); + for (;;) + { + const auto setupResult = setupInstance(currentInstance, *pluginContainer); + + if (setupResult == SetupInstanceResults::Ok) { + break; + } else if (setupResult == SetupInstanceResults::TryAgain) { + continue; + } else if (setupResult == SetupInstanceResults::SelectAnother) { + InstanceManager::instance().clearCurrentInstance(); return RestartExitCode; + } else { + return 1; } - - return 1; } - checkPathsForSanity(*game, settings); - + checkPathsForSanity(*currentInstance.gamePlugin(), settings); - organizer.setManagedGame(game); + organizer.setManagedGame(currentInstance.gamePlugin()); organizer.createDefaultProfile(); - if (!InstanceManager::instance().determineGameEdition(settings, game)) { - return 1; - } - log::info( "using game plugin '{}' ('{}', variant {}, steam id '{}') at {}", - game->gameName(), game->gameShortName(), + currentInstance.gamePlugin()->gameName(), + currentInstance.gamePlugin()->gameShortName(), (settings.game().edition().value_or("").isEmpty() ? "(none)" : *settings.game().edition()), - game->steamAPPId(), game->gameDirectory().absolutePath()); + currentInstance.gamePlugin()->steamAPPId(), + currentInstance.gamePlugin()->gameDirectory().absolutePath()); + CategoryFactory::instance().loadCategories(); organizer.updateExecutablesList(); organizer.updateModInfoFromDisc(); - if (cl.profile()) { - InstanceManager::instance().overrideProfile(*cl.profile()); - } - - QString selectedProfileName = InstanceManager::instance() - .determineProfile(settings); - - organizer.setCurrentProfile(selectedProfileName); + organizer.setCurrentProfile(currentInstance.profileName()); if (auto r=handleCommandLine(cl, organizer)) { return *r; } - auto splash = createSplash(settings, dataPath, game); + auto splash = createSplash(settings, dataPath, currentInstance.gamePlugin()); QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { @@ -438,11 +587,6 @@ int runApplication( tt.stop(); - QTimer::singleShot(std::chrono::milliseconds(1), [&] - { - openInstanceManager(*pluginContainer, &mainWindow); - }); - res = application.exec(); mainWindow.close(); @@ -488,28 +632,6 @@ void resetForRestart(cl::CommandLine& cl) cl.clear(); } -QString determineDataPath(const cl::CommandLine& cl) -{ - try - { - InstanceManager& instanceManager = InstanceManager::instance(); - - if (cl.instance()) - instanceManager.overrideInstance(*cl.instance()); - - return instanceManager.determineDataPath(); - } - catch (const std::exception &e) - { - if (strcmp(e.what(),"Canceled")) { - QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what()); - } - - return {}; - } -} - - int doOneRun( cl::CommandLine& cl, MOApplication& application, SingleInstance& instance) { @@ -518,24 +640,25 @@ int doOneRun( // resets things when MO is "restarted" resetForRestart(cl); + if (cl.instance()) + InstanceManager::instance().overrideInstance(*cl.instance()); - //{ - // NexusInterface ni(nullptr); - // - // PluginContainer pc(nullptr); - // pc.loadPlugins(); - // - // CreateInstanceDialog dlg(pc, nullptr); - // dlg.exec(); - //} + if (cl.profile()) { + InstanceManager::instance().overrideProfile(*cl.profile()); + } + auto currentInstance = InstanceManager::instance().currentInstance(); - const QString dataPath = determineDataPath(cl); - if (dataPath.isEmpty()) { - return 1; + if (!currentInstance) + { + currentInstance = selectInstance(); + if (!currentInstance) + return 1; } + const QString dataPath = currentInstance->directory().path(); application.setProperty("dataPath", dataPath); + setExceptionHandlers(); if (!setLogDirectory(dataPath)) { @@ -548,7 +671,7 @@ int doOneRun( tt.stop(); - return runApplication(application, cl, instance, dataPath); + return runApplication(application, cl, instance, dataPath, *currentInstance); } int main(int argc, char *argv[]) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0d561581..550f61d4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -513,9 +513,11 @@ bool OrganizerCore::bootstrap() void OrganizerCore::createDefaultProfile() { QString profilesPath = settings().paths().profiles(); - if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() - == 0) { - Profile newProf("Default", managedGame(), false); + if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { + Profile newProf( + QString::fromStdWString(AppConfig::defaultProfileName()), + managedGame(), false); + m_ProfileCreated(&newProf); } } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index a0e74f47..8ee0914b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -588,11 +588,14 @@ ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut) { const auto currentInstance = InstanceManager::instance().currentInstance(); - if (shortcut.hasInstance() && shortcut.instance() != currentInstance) { - throw std::runtime_error( - QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(),shortcut.executable()) - .toLocal8Bit().constData()); + if (currentInstance) + { + if (shortcut.hasInstance() && shortcut.instance() != currentInstance->name()) { + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + } } const Executable& exe = m_core.executablesList()->get(shortcut.executable()); diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc index 709c845d..807f1d69 100644 --- a/src/shared/appconfig.inc +++ b/src/shared/appconfig.inc @@ -9,6 +9,7 @@ APPPARAM(std::wstring, cachePath, L"webcache") APPPARAM(std::wstring, tutorialsPath, L"tutorials") APPPARAM(std::wstring, logPath, L"logs") APPPARAM(std::wstring, dumpsDir, L"crashDumps") +APPPARAM(std::wstring, defaultProfileName, L"Default") APPPARAM(std::wstring, profileTweakIni, L"profile_tweaks.ini") APPPARAM(std::wstring, logFileName, L"ModOrganizer.log") APPPARAM(std::wstring, iniFileName, L"ModOrganizer.ini") diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 5897b6bb..aefabc73 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -153,10 +153,9 @@ void StatusBar::updateNormalMessage(OrganizerCore& core) game = tr("Unknown game"); } - QString instance = InstanceManager::instance().currentInstance(); - if (instance.isEmpty()) { - instance = tr("Portable"); - } + QString instance = "?"; + if (auto i=InstanceManager::instance().currentInstance()) + instance = i->name(); QString profile = core.profileName(); -- cgit v1.3.1