From ae118153fbedc0240ea183488834a32abe202839 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 00:38:03 -0400 Subject: more refactoring: - moved splash stuff together - moved stuff in the main loop into doOneRun() --- src/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/settings.cpp') diff --git a/src/settings.cpp b/src/settings.cpp index 534e67c8..d37f0d99 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -876,7 +876,7 @@ void GeometrySettings::setCenterDialogs(bool b) set(m_Settings, "Settings", "center_dialogs", b); } -void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) +void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) const { const auto monitor = getOptional( m_Settings, "Geometry", "MainWindow_monitor").value_or(-1); -- 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/settings.cpp') 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 2cb5c21b8e15b02a09144ff6c1ab11e87879a420 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 Jul 2020 09:25:02 -0400 Subject: fixed settings doing weird stuff with multiple instances sort games by name added intro and confirmation pages --- src/createinstancedialog.cpp | 55 ++++++++++++++++++++++++++---- src/createinstancedialog.h | 1 + src/createinstancedialog.ui | 79 +++++++++++++++++++++++++++++++++++++++++++ src/instancemanagerdialog.cpp | 6 ++++ src/main.cpp | 10 +++--- src/mainwindow.cpp | 26 ++++++++------ src/settings.cpp | 40 +++++++++++----------- src/settings.h | 5 ++- 8 files changed, 178 insertions(+), 44 deletions(-) (limited to 'src/settings.cpp') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 4308969e..5db6d685 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -214,6 +214,16 @@ protected: }; +class InfoPage : public Page +{ +public: + InfoPage(CreateInstanceDialog& dlg) + : Page(dlg) + { + } +}; + + class TypePage : public Page { public: @@ -345,7 +355,7 @@ public: void warnUnrecognized(const QString& path) { QString supportedGames; - for (auto* game : m_pc.plugins()) { + for (auto* game : sortedGamePlugins()) { supportedGames += "
  • " + game->gameName() + "
  • "; } @@ -381,6 +391,21 @@ private: Game* m_selection; + std::vector sortedGamePlugins() const + { + std::vector v; + + for (auto* game : m_pc.plugins()) { + v.push_back(game); + } + + std::sort(v.begin(), v.end(), [](auto* a, auto* b) { + return (a->gameName() < b->gameName()); + }); + + return v; + } + Game* findGame(MOBase::IPluginGame* game) { for (auto& g : m_games) { @@ -396,7 +421,7 @@ private: { m_games.clear(); - for (auto* game : m_pc.plugins()) { + for (auto* game : sortedGamePlugins()) { m_games.push_back(std::make_unique(game)); } } @@ -563,9 +588,9 @@ private: .title(QObject::tr("Unrecognized game")) .main(QObject::tr("Unrecognized game")) .content(QObject::tr( - "The folder %1 does not seem to contain installation for " + "The folder %1 does not seem to contain an installation for " "%2 or " - "any other game Mod Organizer can manage.") + "for any other game Mod Organizer can manage.") .arg(path) .arg(game->gameName())) .button({ @@ -800,6 +825,11 @@ public: ui->pathPages->setCurrentIndex(0); } + bool skip() const override + { + return (m_dlg.selectedType() == CreateInstanceDialog::Portable); + } + bool ready() const override { return checkPaths(); @@ -900,11 +930,13 @@ CreateInstanceDialog::CreateInstanceDialog( ui->setupUi(this); m_originalNext = ui->next->text(); + 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)); ui->pages->setCurrentIndex(0); @@ -926,10 +958,21 @@ const PluginContainer& CreateInstanceDialog::pluginContainer() return m_pc; } +bool CreateInstanceDialog::isOnLastPage() const +{ + for (int i=ui->pages->currentIndex() + 1; i < ui->pages->count(); ++i) { + if (!m_pages[i]->skip()) { + return false; + } + } + + return true; +} + void CreateInstanceDialog::next() { const auto i = ui->pages->currentIndex(); - const auto last = (i == (ui->pages->count() - 1)); + const auto last = isOnLastPage(); if (last) { finish(); @@ -997,7 +1040,7 @@ void CreateInstanceDialog::selectPage(std::size_t i) void CreateInstanceDialog::updateNavigation() { const auto i = ui->pages->currentIndex(); - const auto last = (i == (ui->pages->count() - 1)); + const auto last = isOnLastPage(); ui->next->setEnabled(m_pages[i]->ready()); ui->back->setEnabled(i > 0); diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index ac1c30b6..02608c8d 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -36,6 +36,7 @@ public: void finish(); void updateNavigation(); + bool isOnLastPage() const; Types selectedType() const; MOBase::IPluginGame* selectedGame() const; diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 98cbe96e..72d73020 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -60,6 +60,24 @@ 0 + + + + + + <h3>What is an instance?</h3> +<p>An instance is a full set of mods, downloads, profiles and configuration for a game. Each game must be managed in its own instance. Mod Organizer can freely switch between instances.</p> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + @@ -823,6 +841,67 @@ + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Confirmation</h3> + + + + + + + The instance is about to be created. Review the information below and press 'Finish'. + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 566f1aad..26e8eae1 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -8,6 +8,12 @@ #include "shared/appconfig.h" #include +void openInstanceManager(PluginContainer& pc, QWidget* parent) +{ + InstanceManagerDialog dlg(pc, parent); + dlg.exec(); +} + class InstanceInfo { public: diff --git a/src/main.cpp b/src/main.cpp index 71fb0bbd..58e22466 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -270,6 +270,8 @@ std::optional handleCommandLine( return {}; } +void openInstanceManager(PluginContainer& pc, QWidget* parent); + int runApplication( MOApplication &application, const cl::CommandLine& cl, SingleInstance &instance, const QString &dataPath) @@ -298,8 +300,9 @@ int runApplication( try { - Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); - settings.setGlobalInstance(); + Settings settings( + dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), + true); log::getDefault().setLevel(settings.diagnostics().logLevel()); @@ -434,8 +437,7 @@ int runApplication( QTimer::singleShot(std::chrono::milliseconds(1), [&] { - InstanceManagerDialog dlg(*pluginContainer, &mainWindow); - dlg.exec(); + openInstanceManager(*pluginContainer, &mainWindow); }); res = application.exec(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 65c1d65c..c2d83e36 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5952,20 +5952,24 @@ void MainWindow::on_actionNotifications_triggered() scheduleCheckForProblems(); } +void openInstanceManager(PluginContainer& pc, QWidget* parent); + void MainWindow::on_actionChange_Game_triggered() { - if (m_OrganizerCore.settings().interface().showChangeGameConfirmation()) { - const auto r = QMessageBox::question( - this, tr("Are you sure?"), tr("This will restart MO, continue?"), - QMessageBox::Yes | QMessageBox::Cancel); + openInstanceManager(m_PluginContainer, this); - if (r != QMessageBox::Yes) { - return; - } - } - - InstanceManager::instance().clearCurrentInstance(); - ExitModOrganizer(Exit::Restart); + //if (m_OrganizerCore.settings().interface().showChangeGameConfirmation()) { + // const auto r = QMessageBox::question( + // this, tr("Are you sure?"), tr("This will restart MO, continue?"), + // QMessageBox::Yes | QMessageBox::Cancel); + // + // if (r != QMessageBox::Yes) { + // return; + // } + //} + // + //InstanceManager::instance().clearCurrentInstance(); + //ExitModOrganizer(Exit::Restart); } void MainWindow::setCategoryListVisible(bool visible) diff --git a/src/settings.cpp b/src/settings.cpp index 43e7a4fa..8db9c623 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -61,33 +61,31 @@ QString toString(EndorsementState s) Settings *Settings::s_Instance = nullptr; -Settings::Settings(const QString& path) : +Settings::Settings(const QString& path, bool globalInstance) : m_Settings(path, QSettings::IniFormat), - m_Game(m_Settings), m_Geometry(m_Settings), m_Widgets(m_Settings), - m_Colors(m_Settings), m_Plugins(m_Settings), m_Paths(m_Settings), - m_Network(m_Settings), m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), + m_Game(m_Settings), m_Geometry(m_Settings), + m_Widgets(m_Settings, globalInstance), m_Colors(m_Settings), + m_Plugins(m_Settings), m_Paths(m_Settings), m_Network(m_Settings), + m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), m_Interface(m_Settings), m_Diagnostics(m_Settings) { + if (globalInstance) { + if (s_Instance != nullptr) { + throw std::runtime_error("second instance of \"Settings\" created"); + } else { + s_Instance = this; + } + } } Settings::~Settings() { - MOBase::QuestionBoxMemory::setCallbacks({}, {}, {}); - if (s_Instance == this) { + MOBase::QuestionBoxMemory::setCallbacks({}, {}, {}); 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() { if (s_Instance == nullptr) { @@ -1011,13 +1009,15 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const } -WidgetSettings::WidgetSettings(QSettings& s) +WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance) : m_Settings(s) { - MOBase::QuestionBoxMemory::setCallbacks( - [this](auto&& w, auto&& f){ return questionButton(w, f); }, - [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, - [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); + if (globalInstance) { + MOBase::QuestionBoxMemory::setCallbacks( + [this](auto&& w, auto&& f){ return questionButton(w, f); }, + [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, + [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); + } } std::optional WidgetSettings::index(const QComboBox* cb) const diff --git a/src/settings.h b/src/settings.h index 84102c72..a8c527d6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -195,7 +195,7 @@ private: class WidgetSettings { public: - WidgetSettings(QSettings& s); + WidgetSettings(QSettings& s, bool globalInstance); // selected index for a combobox // @@ -677,11 +677,10 @@ class Settings : public QObject Q_OBJECT; public: - Settings(const QString& path); + Settings(const QString& path, bool globalInstance=false); ~Settings(); static Settings &instance(); - void setGlobalInstance(); // name of the ini file // -- cgit v1.3.1 From 4ac8ab98563e223075c1c93852d4112ab6e4579c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 Jul 2020 15:29:28 -0400 Subject: don't skip paths for portable instance, what am I doing implemented actual creation removed mentions of %BASE_DIR% everywhere, use constant and functions from PathSettings --- modorganizer.natvis | 97 +++++++++++ src/createinstancedialog.cpp | 379 ++++++++++++++++++++++++++++++++++++------- src/createinstancedialog.h | 20 +++ src/createinstancedialog.ui | 16 +- src/instancemanager.h | 3 +- src/settings.cpp | 18 +- src/settings.h | 12 ++ src/settingsdialog.cpp | 2 +- src/settingsdialogpaths.cpp | 12 +- 9 files changed, 484 insertions(+), 75 deletions(-) (limited to 'src/settings.cpp') diff --git a/modorganizer.natvis b/modorganizer.natvis index fe4a7ce2..6e21e099 100644 --- a/modorganizer.natvis +++ b/modorganizer.natvis @@ -8,5 +8,102 @@ file={m_wsFile} loaded={m_loaded} expanded={m_expanded} }} + + <null> + {fileEntry} + fileEntry + + *((Qt5Core.dll!QSharedData *) this) + fileEntry + + + + + + {*((Qt5Core.dll!QFileInfoPrivate *) d_ptr.d)} + *((Qt5Core.dll!QFileInfoPrivate *) d_ptr.d) + + *((Qt5Core.dll!QFileInfoPrivate *) d_ptr.d) + + + + + + <null> + {m_filePath} + m_filePath + + + + + + <null> + {dirEntry} + dirEntry + + *((Qt5Core.dll!QSharedData *) this) + dirEntry + nameFilters + absoluteDirEntry + + + + + + + {*((Qt5Core.dll!QDirPrivate *) d_ptr.d)} + *((Qt5Core.dll!QDirPrivate *) d_ptr.d) + + *((Qt5Core.dll!QDirPrivate *) d_ptr.d) + + + + + + <null> + {fileName} + fileName + + *((Qt5Core.dll!QFileDevice *) this) + fileName + + + + + + {*((Qt5Core.dll!QFilePrivate *) d_ptr.d)} + *((Qt5Core.dll!QFilePrivate *) d_ptr.d) + + *((Qt5Core.dll!QFilePrivate *) d_ptr.d) + + diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 8c6605ed..5c85aa68 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -2,13 +2,19 @@ #include "ui_createinstancedialog.h" #include "instancemanager.h" #include "plugincontainer.h" +#include "settings.h" #include "shared/appconfig.h" #include #include +#include + +using namespace MOBase; namespace cid { +using MOBase::TaskDialog; + class PathChecker { public: @@ -153,6 +159,12 @@ private: } }; +QString makeDefaultPath(const std::wstring& dir) +{ + return QDir::toNativeSeparators( + PathSettings::makeDefaultPath(QString::fromStdWString(dir))); +} + class Page { @@ -195,7 +207,7 @@ public: return CreateInstanceDialog::NoType; } - virtual MOBase::IPluginGame* selectedGame() const + virtual IPluginGame* selectedGame() const { // no-op return nullptr; @@ -315,7 +327,7 @@ public: return (m_selection != nullptr); } - MOBase::IPluginGame* selectedGame() const override + IPluginGame* selectedGame() const override { if (!m_selection) { return nullptr; @@ -330,10 +342,10 @@ public: return {}; } - return m_selection->dir; + return QDir::toNativeSeparators(m_selection->dir); } - void select(MOBase::IPluginGame* game) + void select(IPluginGame* game) { Game* checked = findGame(game); @@ -397,12 +409,12 @@ public: private: struct Game { - MOBase::IPluginGame* game = nullptr; + IPluginGame* game = nullptr; QCommandLinkButton* button = nullptr; QString dir; bool installed = false; - Game(MOBase::IPluginGame* g) + Game(IPluginGame* g) : game(g), installed(g->isInstalled()) { if (installed) { @@ -418,11 +430,11 @@ private: Game* m_selection; - std::vector sortedGamePlugins() const + std::vector sortedGamePlugins() const { - std::vector v; + std::vector v; - for (auto* game : m_pc.plugins()) { + for (auto* game : m_pc.plugins()) { v.push_back(game); } @@ -433,7 +445,7 @@ private: return v; } - Game* findGame(MOBase::IPluginGame* game) + Game* findGame(IPluginGame* game) { for (auto& g : m_games) { if (g->game == game) { @@ -598,9 +610,9 @@ private: return g; } - MOBase::IPluginGame* findAnotherGame(const QString& path) + IPluginGame* findAnotherGame(const QString& path) { - for (auto* otherGame : m_pc.plugins()) { + for (auto* otherGame : m_pc.plugins()) { if (otherGame->looksValid(path)) { return otherGame; } @@ -609,9 +621,9 @@ private: return nullptr; } - bool confirmUnknown(const QString& path, MOBase::IPluginGame* game) + bool confirmUnknown(const QString& path, IPluginGame* game) { - const auto r = MOBase::TaskDialog(&m_dlg) + const auto r = TaskDialog(&m_dlg) .title(QObject::tr("Unrecognized game")) .main(QObject::tr("Unrecognized game")) .content(QObject::tr( @@ -632,11 +644,11 @@ private: return (r == QMessageBox::Ignore); } - MOBase::IPluginGame* confirmOtherGame( + IPluginGame* confirmOtherGame( const QString& path, - MOBase::IPluginGame* selectedGame, MOBase::IPluginGame* guessedGame) + IPluginGame* selectedGame, IPluginGame* guessedGame) { - const auto r = MOBase::TaskDialog(&m_dlg) + const auto r = TaskDialog(&m_dlg) .title(QObject::tr("Incorrect game")) .main(QObject::tr("Incorrect game")) .content(QObject::tr( @@ -742,7 +754,7 @@ public: } private: - MOBase::IPluginGame* m_previousGame; + IPluginGame* m_previousGame; std::vector m_buttons; QString m_selection; @@ -868,10 +880,6 @@ public: ui->pathPages->setCurrentIndex(0); } - bool skip() const override - { - return (m_dlg.instanceType() == CreateInstanceDialog::Portable); - } bool ready() const override { @@ -934,7 +942,7 @@ private: bool checkVarPath(QString path) const { - path.replace("%BASE_DIR%", ui->base->text()); + path = PathSettings::resolve(path, ui->base->text()); return checkAdvancedPath(path); } @@ -971,11 +979,6 @@ private: e->setText(path); } } - - QString makeDefaultPath(const std::wstring& dir) - { - return "%BASE_DIR%\\" + QString::fromStdWString(dir); - } }; @@ -990,54 +993,52 @@ public: void activated() override { ui->review->setPlainText(makeReview()); + ui->creationLog->clear(); } - QString makeReview() const + QString toLocalizedString(CreateInstanceDialog::Types t) const { - QStringList lines; - - const auto paths = m_dlg.paths(); - - // type - switch (m_dlg.instanceType()) + switch (t) { case CreateInstanceDialog::Global: - { - lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("Global"))); - lines.push_back(QObject::tr("Instance name: %1").arg(m_dlg.instanceName())); - - if (paths.downloads.isEmpty()) { - // simple settings - lines.push_back(QObject::tr("Instance location: %1").arg(paths.base)); - } else { - // advanced settings - lines.push_back(QObject::tr("Instance base folder: %1").arg(paths.base)); - lines.push_back(dirLine(QObject::tr("Downloads"), paths.downloads)); - lines.push_back(dirLine(QObject::tr("Mods"), paths.mods)); - lines.push_back(dirLine(QObject::tr("Profiles"), paths.profiles)); - lines.push_back(dirLine(QObject::tr("Overwrite"), paths.overwrite)); - } - - break; - } + return QObject::tr("Global"); case CreateInstanceDialog::Portable: - { - lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("Portable"))); - lines.push_back(QObject::tr("Instance location: %1").arg(qApp->applicationDirPath())); - break; - } + return QObject::tr("Portable"); default: - { - lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("?"))); + return QObject::tr("Instance type: %1").arg(QObject::tr("?")); + } + } + + QString makeReview() const + { + QStringList lines; + const auto paths = m_dlg.paths(); + + lines.push_back(QObject::tr("Instance type: %1").arg(toLocalizedString(m_dlg.instanceType()))); + lines.push_back(QObject::tr("Instance location: %1").arg(m_dlg.dataPath())); + + if (m_dlg.instanceType() != CreateInstanceDialog::Portable) { + lines.push_back(QObject::tr("Instance name: %1").arg(m_dlg.instanceName())); + } + + if (paths.downloads.isEmpty()) { + // simple settings + if (paths.base != m_dlg.dataPath()) { + lines.push_back(QObject::tr("Base directory: %1").arg(paths.base)); } + } else { + // advanced settings + lines.push_back(QObject::tr("Base directory: %1").arg(paths.base)); + lines.push_back(dirLine(QObject::tr("Downloads"), paths.downloads)); + lines.push_back(dirLine(QObject::tr("Mods"), paths.mods)); + lines.push_back(dirLine(QObject::tr("Profiles"), paths.profiles)); + lines.push_back(dirLine(QObject::tr("Overwrite"), paths.overwrite)); } // game - MOBase::IPluginGame* game = m_dlg.game(); - - QString name = game->gameName(); + QString name = m_dlg.game()->gameName(); if (!m_dlg.gameEdition().isEmpty()) { name += " (" + m_dlg.gameEdition() + ")"; } @@ -1157,8 +1158,195 @@ void CreateInstanceDialog::changePage(int d) } } + +class Failed {}; + +class DirectoryCreator +{ +public: + DirectoryCreator(const DirectoryCreator&) = delete; + DirectoryCreator& operator=(const DirectoryCreator&) = delete; + + static std::unique_ptr create( + const QDir& target, std::function log) + { + return std::unique_ptr(new DirectoryCreator(target, log)); + } + + ~DirectoryCreator() + { + rollback(); + } + + void commit() + { + m_created.clear(); + } + + void rollback() noexcept + { + try + { + for (auto itor=m_created.rbegin(); itor!=m_created.rend(); ++itor) { + const auto r = shell::DeleteDirectoryRecursive(*itor); + if (!r) { + m_logger(r.toString()); + } + } + + m_created.clear(); + } + catch(...) + { + // eat it + } + } + +private: + std::function m_logger; + + DirectoryCreator(const QDir& target, std::function log) + : m_logger(log) + { + try + { + const QString s = QDir::toNativeSeparators(target.absolutePath()); + const QStringList cs = s.split("\\"); + + if (cs.empty()) { + return; + } + + QDir d(cs[0]); + + for (int i=1; i m_created; +}; + void CreateInstanceDialog::finish() { + ui->creationLog->clear(); + logCreation(tr("Creating instance...")); + + const auto& m = InstanceManager::instance(); + const auto ci = creationInfo(); + + auto logger = [&](QString s) { + logCreation(s); + }; + + auto createDir = [&](QString path) { + return DirectoryCreator::create(path, logger); + }; + + + try + { + std::vector> dirs; + + dirs.push_back(createDir(ci.dataPath)); + dirs.push_back(createDir(ci.paths.base)); + dirs.push_back(createDir(PathSettings::resolve(ci.paths.downloads, ci.paths.base))); + dirs.push_back(createDir(PathSettings::resolve(ci.paths.mods, ci.paths.base))); + dirs.push_back(createDir(PathSettings::resolve(ci.paths.profiles, ci.paths.base))); + dirs.push_back(createDir(PathSettings::resolve(ci.paths.overwrite, ci.paths.base))); + + + Settings s(ci.iniPath); + s.game().setName(ci.game->gameName()); + s.game().setDirectory(ci.gameLocation); + + if (!ci.gameEdition.isEmpty()) { + s.game().setEdition(ci.gameEdition); + } + + if (ci.type == Global) { + if (ci.paths.base != ci.dataPath) { + s.paths().setBase(ci.paths.base); + } + + if (ci.paths.downloads != cid::makeDefaultPath(AppConfig::downloadPath())) { + s.paths().setDownloads(ci.paths.downloads); + } + + if (ci.paths.mods != cid::makeDefaultPath(AppConfig::modsPath())) { + s.paths().setMods(ci.paths.mods); + } + + if (ci.paths.profiles != cid::makeDefaultPath(AppConfig::profilesPath())) { + s.paths().setProfiles(ci.paths.profiles); + } + + if (ci.paths.overwrite != cid::makeDefaultPath(AppConfig::overwritePath())) { + s.paths().setOverwrite(ci.paths.overwrite); + } + } + + + logCreation(tr("Writing %1...").arg(ci.iniPath)); + + const auto r = s.sync(); + if (r != QSettings::NoError) { + switch (r) + { + case QSettings::AccessError: + logCreation(formatSystemMessage(ERROR_ACCESS_DENIED)); + break; + + case QSettings::FormatError: + logCreation(tr("Format error.")); + break; + + default: + logCreation(tr("Error %1.").arg(static_cast(r))); + break; + } + + throw Failed(); + } + + for (auto& d : dirs) { + d->commit(); + } + + logCreation(tr("Done.")); + } + catch(Failed&) + { + } +} + +void CreateInstanceDialog::logCreation(const QString& s) +{ + ui->creationLog->insertPlainText(s + "\n"); +} + +void CreateInstanceDialog::logCreation(const std::wstring& s) +{ + logCreation(QString::fromStdWString(s)); } void CreateInstanceDialog::selectPage(std::size_t i) @@ -1213,7 +1401,72 @@ QString CreateInstanceDialog::instanceName() const return getSelected(&cid::Page::selectedInstanceName); } +QString CreateInstanceDialog::dataPath() const +{ + QString s; + + if (instanceType() == Portable) { + s = QDir(qApp->applicationDirPath()).absolutePath(); + } else { + s = InstanceManager::instance().instancePath(instanceName()); + } + + return QDir::toNativeSeparators(s); +} + CreateInstanceDialog::Paths CreateInstanceDialog::paths() const { return getSelected(&cid::Page::selectedPaths); } + +CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const +{ + CreationInfo ci; + + 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.instanceName = getSelected(&cid::Page::selectedInstanceName); + ci.paths = getSelected(&cid::Page::selectedPaths); + ci.dataPath = dataPath(); + + ci.paths.base = QDir(ci.paths.base).absolutePath(); + + if (ci.paths.downloads.isEmpty()) { + ci.paths.downloads = cid::makeDefaultPath(AppConfig::downloadPath()); + } else if (!ci.paths.downloads.contains(PathSettings::BaseDirVariable)) { + ci.paths.downloads = QDir(ci.paths.downloads).absolutePath(); + } + + if (ci.paths.mods.isEmpty()) { + ci.paths.mods = cid::makeDefaultPath(AppConfig::modsPath()); + } else if (!ci.paths.mods.contains(PathSettings::BaseDirVariable)) { + ci.paths.mods = QDir(ci.paths.mods).absolutePath(); + } + + if (ci.paths.profiles.isEmpty()) { + ci.paths.profiles = cid::makeDefaultPath(AppConfig::profilesPath()); + } else if (!ci.paths.profiles.contains(PathSettings::BaseDirVariable)) { + ci.paths.profiles = QDir(ci.paths.profiles).absolutePath(); + } + + if (ci.paths.overwrite.isEmpty()) { + ci.paths.overwrite = cid::makeDefaultPath(AppConfig::overwritePath()); + } else if (!ci.paths.overwrite.contains(PathSettings::BaseDirVariable)) { + ci.paths.overwrite = QDir(ci.paths.overwrite).absolutePath(); + } + + ci.iniPath = QFileInfo( + ci.dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())) + .absoluteFilePath(); + + ci.paths.base = QDir::toNativeSeparators(ci.paths.base); + ci.paths.downloads = QDir::toNativeSeparators(ci.paths.downloads); + ci.paths.mods = QDir::toNativeSeparators(ci.paths.mods); + ci.paths.profiles = QDir::toNativeSeparators(ci.paths.profiles); + ci.paths.overwrite = QDir::toNativeSeparators(ci.paths.overwrite); + ci.paths.ini = QDir::toNativeSeparators(ci.paths.ini); + + return ci; +} diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index d9c392ca..2f5774ae 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -28,10 +28,24 @@ public: QString mods; QString profiles; QString overwrite; + QString ini; auto operator<=>(const Paths&) const = default; }; + struct CreationInfo + { + Types type; + MOBase::IPluginGame* game; + QString gameLocation; + QString gameEdition; + QString instanceName; + QString dataPath; + QString iniPath; + Paths paths; + }; + + explicit CreateInstanceDialog( const PluginContainer& pc, QWidget *parent = nullptr); @@ -54,8 +68,11 @@ public: QString gameLocation() const; QString gameEdition() const; QString instanceName() const; + QString dataPath() const; Paths paths() const; + CreationInfo creationInfo() const; + private: std::unique_ptr ui; const PluginContainer& m_pc; @@ -74,6 +91,9 @@ private: return T(); } + + void logCreation(const QString& s); + void logCreation(const std::wstring& s); }; #endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index c899a5b7..cfb343fa 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -891,7 +891,21 @@ 0 - + + + QTextEdit::NoWrap + + + + + + + QTextEdit::NoWrap + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + diff --git a/src/instancemanager.h b/src/instancemanager.h index 0cd5ef4c..6a6b52ac 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -56,13 +56,12 @@ public: QString sanitizeInstanceName(const QString &name) const; QString makeUniqueName(const QString& instanceName) const; bool instanceExists(const QString& instanceName) const; + QString instancePath(const QString& instanceName) const; private: InstanceManager(); - QString instancePath(const QString& instanceName) const; - bool deleteLocalInstance(const QString &instanceId) const; QString manageInstances(const QStringList &instanceList) const; diff --git a/src/settings.cpp b/src/settings.cpp index 8db9c623..661cf429 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1464,6 +1464,8 @@ QSet PluginSettings::readBlacklist() const } +const QString PathSettings::BaseDirVariable = "%BASE_DIR%"; + PathSettings::PathSettings(QSettings& settings) : m_Settings(settings) { @@ -1511,10 +1513,10 @@ QString PathSettings::getConfigurablePath(const QString &key, bool resolve) const { QString result = QDir::fromNativeSeparators( - get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); + get(m_Settings, "Settings", key, makeDefaultPath(def))); if (resolve) { - result.replace("%BASE_DIR%", base()); + result = PathSettings::resolve(result, base()); } return result; @@ -1529,6 +1531,18 @@ void PathSettings::setConfigurablePath(const QString &key, const QString& path) } } +QString PathSettings::resolve(const QString& path, const QString& baseDir) +{ + QString s = path; + s.replace(BaseDirVariable, baseDir); + return s; +} + +QString PathSettings::makeDefaultPath(const QString dirName) +{ + return BaseDirVariable + "/" + dirName; +} + QString PathSettings::base() const { return QDir::fromNativeSeparators(get(m_Settings, diff --git a/src/settings.h b/src/settings.h index a8c527d6..c723faec 100644 --- a/src/settings.h +++ b/src/settings.h @@ -391,6 +391,9 @@ private: class PathSettings { public: + // %BASE_DIR% + static const QString BaseDirVariable; + PathSettings(QSettings& settings); QString base() const; @@ -418,6 +421,15 @@ public: std::map recent() const; void setRecent(const std::map& map); + + // resolves %BASE_DIR% + // + static QString resolve(const QString& path, const QString& baseDir); + + // returns %BASE_DIR%/dirName + // + static QString makeDefaultPath(const QString dirName); + private: QSettings& m_Settings; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 87c7201d..c4a53fcd 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -114,7 +114,7 @@ QString SettingsDialog::getColoredButtonStyleSheet() const void SettingsDialog::accept() { QString newModPath = ui->modDirEdit->text(); - newModPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + newModPath = PathSettings::resolve(newModPath, ui->baseDirEdit->text()); if ((QDir::fromNativeSeparators(newModPath) != QDir::fromNativeSeparators( diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 74ba4f25..eb334541 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -64,7 +64,7 @@ void PathsSettingsTab::update() std::tie(path, setter, defaultName) = dir; QString realPath = path; - realPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + realPath = PathSettings::resolve(realPath, ui->baseDirEdit->text()); if (!QDir(realPath).exists()) { if (!QDir().mkpath(realPath)) { @@ -113,7 +113,7 @@ void PathsSettingsTab::on_browseBaseDirBtn_clicked() void PathsSettingsTab::on_browseDownloadDirBtn_clicked() { QString searchPath = ui->downloadDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select download directory"), searchPath); if (!temp.isEmpty()) { @@ -124,7 +124,7 @@ void PathsSettingsTab::on_browseDownloadDirBtn_clicked() void PathsSettingsTab::on_browseModDirBtn_clicked() { QString searchPath = ui->modDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select mod directory"), searchPath); if (!temp.isEmpty()) { @@ -135,7 +135,7 @@ void PathsSettingsTab::on_browseModDirBtn_clicked() void PathsSettingsTab::on_browseCacheDirBtn_clicked() { QString searchPath = ui->cacheDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select cache directory"), searchPath); if (!temp.isEmpty()) { @@ -146,7 +146,7 @@ void PathsSettingsTab::on_browseCacheDirBtn_clicked() void PathsSettingsTab::on_browseProfilesDirBtn_clicked() { QString searchPath = ui->profilesDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select profiles directory"), searchPath); if (!temp.isEmpty()) { @@ -157,7 +157,7 @@ void PathsSettingsTab::on_browseProfilesDirBtn_clicked() void PathsSettingsTab::on_browseOverwriteDirBtn_clicked() { QString searchPath = ui->overwriteDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select overwrite directory"), searchPath); if (!temp.isEmpty()) { -- cgit v1.3.1 From 0f0313874aa90c66acaac9082f5ba6fbd29300ef Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 22:47:34 -0400 Subject: new GlobalSettings class to manage the registry close the create instance dialog when launch is unchecked --- src/createinstancedialog.cpp | 7 +------ src/createinstancedialog.ui | 3 +++ src/instancemanager.cpp | 30 +++--------------------------- src/instancemanager.h | 4 ---- src/settings.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/settings.h | 16 ++++++++++++++++ 6 files changed, 59 insertions(+), 37 deletions(-) (limited to 'src/settings.cpp') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 63df9cd7..5a38aa50 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -294,8 +294,7 @@ void CreateInstanceDialog::finish() InstanceManager::instance().setCurrentInstance(ci.instanceName); ExitModOrganizer(Exit::Restart); } else { - ui->next->setEnabled(false); - ui->cancel->setText(tr("Close")); + close(); } } catch(Failed&) @@ -338,10 +337,6 @@ void CreateInstanceDialog::updateNavigation() } else { ui->next->setText(m_originalNext); } - - // this may have been changed by finish() if the launch checkbox wasn't - // checked - ui->cancel->setText(tr("Cancel")); } CreateInstanceDialog::Types CreateInstanceDialog::instanceType() const diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index bcd2eb03..1f9a8ce1 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -943,6 +943,9 @@ Launch the new instance + + true + diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 849cdb5e..bd35cb47 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "shared/appconfig.h" #include "plugincontainer.h" -#include "env.h" #include #include #include @@ -38,15 +37,9 @@ along with Mod Organizer. If not, see . using namespace MOBase; -const QString Organization = "Mod Organizer Team"; -const QString Application = "Mod Organizer"; -const QString InstanceValue = "CurrentInstance"; - - InstanceManager::InstanceManager() - : m_AppSettings(Organization, Application) { - updateRegistryKey(); + GlobalSettings::updateRegistryKey(); } InstanceManager &InstanceManager::instance() @@ -55,23 +48,6 @@ InstanceManager &InstanceManager::instance() return s_Instance; } -void InstanceManager::updateRegistryKey() -{ - const QString OldOrganization = "Tannin"; - const QString OldApplication = "Mod Organizer"; - const QString OldInstanceValue = "CurrentInstance"; - - const QString OldRootKey = "Software\\" + OldOrganization; - - if (env::registryValueExists(OldRootKey + "\\" + OldApplication, OldInstanceValue)) { - QSettings old(OldOrganization, OldApplication); - setCurrentInstance(old.value(OldInstanceValue).toString()); - old.remove(OldInstanceValue); - } - - env::deleteRegistryKeyIfEmpty(OldRootKey); -} - void InstanceManager::overrideInstance(const QString& instanceName) { m_overrideInstanceName = instanceName; @@ -89,7 +65,7 @@ QString InstanceManager::currentInstance() const if (m_overrideInstance) return m_overrideInstanceName; else - return m_AppSettings.value(InstanceValue, "").toString(); + return GlobalSettings::currentInstance(); } void InstanceManager::clearCurrentInstance() @@ -101,7 +77,7 @@ void InstanceManager::clearCurrentInstance() void InstanceManager::setCurrentInstance(const QString &name) { - m_AppSettings.setValue(InstanceValue, name); + GlobalSettings::setCurrentInstance(name); } bool InstanceManager::deleteLocalInstance(const QString& instanceId) const diff --git a/src/instancemanager.h b/src/instancemanager.h index 7467e2fa..f53543df 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -75,11 +75,7 @@ private: bool portableInstall() const; bool portableInstallIsLocked() const; - void updateRegistryKey(); - private: - - QSettings m_AppSettings; bool m_Reset {false}; bool m_overrideInstance{false}; QString m_overrideInstanceName; diff --git a/src/settings.cpp b/src/settings.cpp index 661cf429..76378af9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2165,3 +2165,39 @@ void DiagnosticsSettings::setSpawnDelay(std::chrono::seconds t) { set(m_Settings, "Settings", "spawn_delay", t.count()); } + + +void GlobalSettings::updateRegistryKey() +{ + const QString OldOrganization = "Tannin"; + const QString OldApplication = "Mod Organizer"; + const QString OldInstanceValue = "CurrentInstance"; + + const QString OldRootKey = "Software\\" + OldOrganization; + + if (env::registryValueExists(OldRootKey + "\\" + OldApplication, OldInstanceValue)) { + QSettings old(OldOrganization, OldApplication); + setCurrentInstance(old.value(OldInstanceValue).toString()); + old.remove(OldInstanceValue); + } + + env::deleteRegistryKeyIfEmpty(OldRootKey); +} + +QString GlobalSettings::currentInstance() +{ + return settings().value("CurrentInstance", "").toString(); +} + +void GlobalSettings::setCurrentInstance(const QString& s) +{ + settings().setValue("CurrentInstance", s); +} + +QSettings GlobalSettings::settings() +{ + const QString Organization = "Mod Organizer Team"; + const QString Application = "Mod Organizer"; + + return QSettings(Organization, Application); +} diff --git a/src/settings.h b/src/settings.h index c723faec..54f1bb5b 100644 --- a/src/settings.h +++ b/src/settings.h @@ -827,6 +827,22 @@ private: }; +// manages global settings in the registry +// +class GlobalSettings +{ +public: + // migrates the old settings from the Tannin key to the new one + static void updateRegistryKey(); + + static QString currentInstance(); + static void setCurrentInstance(const QString& s); + +private: + static QSettings settings(); +}; + + // helper class that calls restoreGeometry() in the constructor and // saveGeometry() in the destructor // -- cgit v1.3.1 From 8987693dcf7dc116361869f128fd443716511f0c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 23:00:16 -0400 Subject: added hide option for intro page in create instance dialog --- src/createinstancedialog.cpp | 11 ++++++++++- src/createinstancedialog.ui | 26 +++++++++++++++++++++++--- src/createinstancedialogpages.cpp | 7 ++++++- src/createinstancedialogpages.h | 6 ++++-- src/settings.cpp | 15 +++++++++++++++ src/settings.h | 6 ++++++ src/settingsdialoggeneral.cpp | 1 + 7 files changed, 65 insertions(+), 7 deletions(-) (limited to 'src/settings.cpp') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 5a38aa50..2edfee88 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -19,7 +19,7 @@ CreateInstanceDialog::CreateInstanceDialog( ui->setupUi(this); m_originalNext = ui->next->text(); - 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)); @@ -30,10 +30,15 @@ CreateInstanceDialog::CreateInstanceDialog( ui->pages->setCurrentIndex(0); ui->launch->setChecked(true); + if (m_pages[0]->skip()) { + next(); + } + updateNavigation(); connect(ui->next, &QPushButton::clicked, [&]{ next(); }); connect(ui->back, &QPushButton::clicked, [&]{ back(); }); + connect(ui->cancel, &QPushButton::clicked, [&]{ reject(); }); } CreateInstanceDialog::~CreateInstanceDialog() = default; @@ -290,6 +295,10 @@ void CreateInstanceDialog::finish() logCreation(tr("Done.")); + if (ui->hideIntro->isChecked()) { + GlobalSettings::setHideCreateInstanceIntro(true); + } + if (ui->launch->isChecked()) { InstanceManager::instance().setCurrentInstance(ci.instanceName); ExitModOrganizer(Exit::Restart); diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 1f9a8ce1..cafccb36 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -58,7 +58,7 @@ - 5 + 0 @@ -76,6 +76,26 @@ + + + + Never show this again + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -258,7 +278,7 @@ 0 0 455 - 287 + 308 @@ -368,7 +388,7 @@ 0 0 455 - 257 + 278 diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index e5213d33..3f35b4fe 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -153,11 +153,16 @@ CreateInstanceDialog::Paths Page::selectedPaths() const } -InfoPage::InfoPage(CreateInstanceDialog& dlg) +IntroPage::IntroPage(CreateInstanceDialog& dlg) : Page(dlg) { } +bool IntroPage::skip() const +{ + return GlobalSettings::hideCreateInstanceIntro(); +} + TypePage::TypePage(CreateInstanceDialog& dlg) : Page(dlg), m_type(CreateInstanceDialog::NoType) diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 15e63a8f..b8366234 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -53,10 +53,12 @@ protected: }; -class InfoPage : public Page +class IntroPage : public Page { public: - InfoPage(CreateInstanceDialog& dlg); + IntroPage(CreateInstanceDialog& dlg); + + bool skip() const override; }; diff --git a/src/settings.cpp b/src/settings.cpp index 76378af9..4ee4cd81 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2201,3 +2201,18 @@ QSettings GlobalSettings::settings() return QSettings(Organization, Application); } + +bool GlobalSettings::hideCreateInstanceIntro() +{ + return settings().value("HideCreateInstanceIntro", false).toBool(); +} + +void GlobalSettings::setHideCreateInstanceIntro(bool b) +{ + settings().setValue("HideCreateInstanceIntro", b); +} + +void GlobalSettings::resetDialogs() +{ + setHideCreateInstanceIntro(false); +} diff --git a/src/settings.h b/src/settings.h index 54f1bb5b..dd8abf3f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -838,6 +838,12 @@ public: static QString currentInstance(); static void setCurrentInstance(const QString& s); + static bool hideCreateInstanceIntro(); + static void setHideCreateInstanceIntro(bool b); + + // resets anything that the user can disable + static void resetDialogs(); + private: static QSettings settings(); }; diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 3d14d521..f29e1d24 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -177,6 +177,7 @@ void GeneralSettingsTab::selectStyle() void GeneralSettingsTab::resetDialogs() { settings().widgets().resetQuestionButtons(); + GlobalSettings::resetDialogs(); } void GeneralSettingsTab::onExploreStyles() -- cgit v1.3.1 From b1e681e129d87cb2f8aab89a734ab7b185975bcd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 23:18:47 -0400 Subject: hide tutorial question option --- src/mainwindow.cpp | 30 +++++++++++++++++++++++++----- src/mainwindow.h | 1 + src/settings.cpp | 11 +++++++++++ src/settings.h | 3 +++ 4 files changed, 40 insertions(+), 5 deletions(-) (limited to 'src/settings.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c2d83e36..60ad77a0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1334,6 +1334,30 @@ void MainWindow::hookUpWindowTutorials() } } +bool MainWindow::shouldStartTutorial() const +{ + if (GlobalSettings::hideTutorialQuestion()) { + return false; + } + + QMessageBox dlg( + QMessageBox::Question, tr("Show tutorial?"), + tr("You are starting Mod Organizer for the first time. " + "Do you want to show a tutorial of its basic features? If you choose " + "no you can always start the tutorial from the \"Help\"-menu."), + QMessageBox::Yes | QMessageBox::No); + + dlg.setCheckBox(new QCheckBox(tr("Never ask to show tutorials"))); + + const auto r = dlg.exec(); + + if (dlg.checkBox()->isChecked()) { + GlobalSettings::setHideTutorialQuestion(true); + } + + return (r == QMessageBox::Yes); +} + void MainWindow::showEvent(QShowEvent *event) { QMainWindow::showEvent(event); @@ -1360,11 +1384,7 @@ void MainWindow::showEvent(QShowEvent *event) if (m_OrganizerCore.settings().firstStart()) { QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { - if (QMessageBox::question(this, tr("Show tutorial?"), - tr("You are starting Mod Organizer for the first time. " - "Do you want to show a tutorial of its basic features? If you choose " - "no you can always start the tutorial from the \"Help\"-menu."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (shouldStartTutorial()) { TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); } } else { diff --git a/src/mainwindow.h b/src/mainwindow.h index 814e0363..8b2188c8 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -506,6 +506,7 @@ private slots: void hideSaveGameInfo(); void hookUpWindowTutorials(); + bool shouldStartTutorial() const; void resumeDownload(int downloadIndex); void endorseMod(ModInfo::Ptr mod); diff --git a/src/settings.cpp b/src/settings.cpp index 4ee4cd81..a0758bd4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2212,7 +2212,18 @@ void GlobalSettings::setHideCreateInstanceIntro(bool b) settings().setValue("HideCreateInstanceIntro", b); } +bool GlobalSettings::hideTutorialQuestion() +{ + return settings().value("HideTutorialQuestion", false).toBool(); +} + +void GlobalSettings::setHideTutorialQuestion(bool b) +{ + settings().setValue("HideTutorialQuestion", b); +} + void GlobalSettings::resetDialogs() { setHideCreateInstanceIntro(false); + setHideTutorialQuestion(false); } diff --git a/src/settings.h b/src/settings.h index dd8abf3f..a9501b9d 100644 --- a/src/settings.h +++ b/src/settings.h @@ -841,6 +841,9 @@ public: static bool hideCreateInstanceIntro(); static void setHideCreateInstanceIntro(bool b); + static bool hideTutorialQuestion(); + static void setHideTutorialQuestion(bool b); + // resets anything that the user can disable static void resetDialogs(); -- 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/settings.cpp') 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 4c5e3da2334a1d0c474148be8881b46d6ca6d3fa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Aug 2020 05:06:26 -0400 Subject: moved nexus api stuff to GlobalSettings pass a pointer to Settings around for things that can be called without settings, when creating the first instance added dummy plugin list, mod list and iorganizer to initialize plugins without an instance moved PluginContainer into the core filter, had nothing to do with the plugins list NexusInterface is now created manually instead of being a static singleton because it needs to know if the settings are available --- src/CMakeLists.txt | 2 +- src/createinstancedialog.cpp | 9 +- src/createinstancedialog.h | 6 +- src/createinstancedialogpages.cpp | 5 +- src/instancemanagerdialog.cpp | 2 +- src/main.cpp | 27 +++-- src/mainwindow.cpp | 4 +- src/modlist.cpp | 41 ++++++++ src/modlist.h | 14 +++ src/nexusinterface.cpp | 28 +++-- src/nexusinterface.h | 6 +- src/nxmaccessmanager.cpp | 37 +++++-- src/nxmaccessmanager.h | 11 +- src/organizercore.cpp | 4 +- src/organizerproxy.cpp | 212 ++++++++++++++++++++++++++++++++++++++ src/organizerproxy.h | 59 +++++++++++ src/plugincontainer.cpp | 88 ++++++++++------ src/pluginlist.cpp | 60 +++++++++++ src/pluginlist.h | 18 ++++ src/settings.cpp | 62 +++++------ src/settings.h | 36 +++---- src/settingsdialognexus.cpp | 18 ++-- src/settingsdialognexus.h | 4 +- 23 files changed, 623 insertions(+), 130 deletions(-) (limited to 'src/settings.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 844f5e19..eec33460 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,7 @@ add_filter(NAME src/core GROUPS nexusinterface nxmaccessmanager organizercore + plugincontainer apiuseraccount processrunner qdirfiletree @@ -136,7 +137,6 @@ add_filter(NAME src/modlist GROUPS ) add_filter(NAME src/plugins GROUPS - plugincontainer pluginlist pluginlistsortproxy pluginlistview diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index f4140e01..0f62fcf6 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -11,8 +11,8 @@ using namespace MOBase; CreateInstanceDialog::CreateInstanceDialog( - const PluginContainer& pc, QWidget *parent) : - QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), + const PluginContainer& pc, Settings* s, QWidget *parent) : + QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s), m_switching(false) { using namespace cid; @@ -55,6 +55,11 @@ const PluginContainer& CreateInstanceDialog::pluginContainer() return m_pc; } +Settings* CreateInstanceDialog::settings() +{ + return m_settings; +} + bool CreateInstanceDialog::isOnLastPage() const { for (int i=ui->pages->currentIndex() + 1; i < ui->pages->count(); ++i) { diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 95d4fa68..0841ef29 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -8,6 +8,7 @@ namespace Ui { class CreateInstanceDialog; }; namespace cid { class Page; } class PluginContainer; +class Settings; class CreateInstanceDialog : public QDialog { @@ -47,12 +48,14 @@ public: explicit CreateInstanceDialog( - const PluginContainer& pc, QWidget *parent = nullptr); + const PluginContainer& pc, Settings* s, QWidget *parent = nullptr); ~CreateInstanceDialog(); Ui::CreateInstanceDialog* getUI(); + const PluginContainer& pluginContainer(); + Settings* settings(); void next(); void back(); @@ -77,6 +80,7 @@ public: private: std::unique_ptr ui; const PluginContainer& m_pc; + Settings* m_settings; std::vector> m_pages; QString m_originalNext; bool m_switching; diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 73b265c8..d809079d 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -964,7 +964,8 @@ NexusPage::NexusPage(CreateInstanceDialog& dlg) : Page(dlg), m_skip(false) { m_connectionUI.reset(new NexusConnectionUI( - Settings::instance(), &m_dlg, + &m_dlg, + dlg.settings(), ui->nexusConnect, nullptr, ui->nexusManual, @@ -972,7 +973,7 @@ NexusPage::NexusPage(CreateInstanceDialog& dlg) // just check it once, or connecting and then going back and forth would skip // the page, which would be unexpected - m_skip = Settings::instance().nexus().hasApiKey(); + m_skip = GlobalSettings::hasNexusApiKey(); } NexusPage::~NexusPage() = default; diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 3b9dd344..7b8522ee 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -718,7 +718,7 @@ void InstanceManagerDialog::onSelection() void InstanceManagerDialog::createNew() { - CreateInstanceDialog dlg(m_pc, this); + CreateInstanceDialog dlg(m_pc, &Settings::instance(), this); if (dlg.exec() != QDialog::Accepted) { return; } diff --git a/src/main.cpp b/src/main.cpp index 93580931..fd5a47c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include "nxmaccessmanager.h" #include "instancemanager.h" #include "instancemanagerdialog.h" +#include "createinstancedialog.h" #include "organizercore.h" #include "env.h" #include "envmodule.h" @@ -329,6 +330,9 @@ int runApplication( // this must outlive `organizer` std::unique_ptr pluginContainer; + log::debug("initializing nexus interface"); + NexusInterface ni(&settings); + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { @@ -394,8 +398,8 @@ int runApplication( auto splash = createSplash(settings, dataPath, game); QString apiKey; - if (settings.nexus().apiKey(apiKey)) { - NexusInterface::instance().getAccessManager()->apiCheck(apiKey); + if (GlobalSettings::nexusApiKey(apiKey)) { + ni.getAccessManager()->apiCheck(apiKey); } log::debug("initializing tutorials"); @@ -415,8 +419,7 @@ int runApplication( // set up main window and its data structures MainWindow mainWindow(settings, organizer, *pluginContainer); - NexusInterface::instance() - .getAccessManager()->setTopLevelWidget(&mainWindow); + ni.getAccessManager()->setTopLevelWidget(&mainWindow); QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); @@ -443,8 +446,7 @@ int runApplication( res = application.exec(); mainWindow.close(); - NexusInterface::instance() - .getAccessManager()->setTopLevelWidget(nullptr); + ni.getAccessManager()->setTopLevelWidget(nullptr); } settings.geometry().resetIfNeeded(); @@ -507,6 +509,7 @@ QString determineDataPath(const cl::CommandLine& cl) } } + int doOneRun( cl::CommandLine& cl, MOApplication& application, SingleInstance& instance) { @@ -515,6 +518,18 @@ int doOneRun( // resets things when MO is "restarted" resetForRestart(cl); + + //{ + // NexusInterface ni(nullptr); + // + // PluginContainer pc(nullptr); + // pc.loadPlugins(); + // + // CreateInstanceDialog dlg(pc, nullptr); + // dlg.exec(); + //} + + const QString dataPath = determineDataPath(cl); if (dataPath.isEmpty()) { return 1; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3ef34e93..9ad51510 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4226,7 +4226,7 @@ void MainWindow::checkModsForUpdates() NexusInterface::instance().requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; - if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { + if (GlobalSettings::nexusApiKey(apiKey)) { m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else { @@ -5434,7 +5434,7 @@ void MainWindow::modUpdateCheck(std::multimap IDs) ModInfo::manualUpdateCheck(this, IDs); } else { QString apiKey; - if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { + if (GlobalSettings::nexusApiKey(apiKey)) { m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else diff --git a/src/modlist.cpp b/src/modlist.cpp index 1f845999..bf9aef83 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1528,3 +1528,44 @@ void ModList::disableSelected(const QItemSelectionModel *selectionModel) m_Profile->setModsEnabled(QList(), modsToDisable); } } + + +QString DummyModList::displayName(const QString &internalName) const +{ + return {}; +} + +QStringList DummyModList::allMods() const +{ + return {}; +} + +IModList::ModStates DummyModList::state(const QString &name) const +{ + return 0; +} + +bool DummyModList::setActive(const QString &name, bool active) +{ + return true; +} + +int DummyModList::priority(const QString &name) const +{ + return -1; +} + +bool DummyModList::setPriority(const QString &name, int newPriority) +{ + return true; +} + +bool DummyModList::onModStateChanged(const std::function &func) +{ + return true; +} + +bool DummyModList::onModMoved(const std::function &func) +{ + return true; +} diff --git a/src/modlist.h b/src/modlist.h index 385ca04c..3ab486c9 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -397,5 +397,19 @@ private: }; + +class DummyModList : public MOBase::IModList +{ +public: + QString displayName(const QString &internalName) const override; + QStringList allMods() const override; + ModStates state(const QString &name) const override; + bool setActive(const QString &name, bool active) override; + int priority(const QString &name) const override; + bool setPriority(const QString &name, int newPriority) override; + bool onModStateChanged(const std::function &func) override; + bool onModMoved(const std::function &func) override; +}; + #endif // MODLIST_H diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 396cec11..1364d7e1 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "nxmaccessmanager.h" #include "selectiondialog.h" #include "bbcode.h" +#include "settings.h" #include #include "shared/util.h" #include @@ -235,31 +236,42 @@ APILimits NexusInterface::parseLimits( } -NexusInterface::NexusInterface() +static NexusInterface* g_instance = nullptr; + +NexusInterface::NexusInterface(Settings* s) : m_PluginContainer(nullptr) { + MO_ASSERT(!g_instance); + g_instance = this; + m_User.limits(defaultAPILimits()); m_MOVersion = createVersionInfo(); - m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString(3)); + m_AccessManager = new NXMAccessManager( + this, s, m_MOVersion.displayString(3)); + m_DiskCache = new QNetworkDiskCache(this); + connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); } -NXMAccessManager *NexusInterface::getAccessManager() +NexusInterface::~NexusInterface() { - return m_AccessManager; + cleanup(); + + MO_ASSERT(g_instance == this); + g_instance = nullptr; } -NexusInterface::~NexusInterface() +NXMAccessManager *NexusInterface::getAccessManager() { - cleanup(); + return m_AccessManager; } NexusInterface& NexusInterface::instance() { - static NexusInterface ni; - return ni; + MO_ASSERT(g_instance); + return *g_instance; } void NexusInterface::setCacheDirectory(const QString &directory) diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 72f30a38..cf365b2e 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -40,7 +40,7 @@ namespace MOBase { class IPluginGame; } class NexusInterface; class NXMAccessManager; - +class Settings; /** * @brief convenience class to make nxm requests easier @@ -153,7 +153,9 @@ public: static APILimits parseLimits(const QNetworkReply* reply); static APILimits parseLimits(const QList& headers); + NexusInterface(Settings* s); ~NexusInterface(); + static NexusInterface& instance(); /** @@ -533,8 +535,6 @@ private: static const int MAX_ACTIVE_DOWNLOADS = 6; private: - - NexusInterface(); void nextRequest(); void requestFinished(std::list::iterator iter); MOBase::IPluginGame *getGame(QString gameName) const; diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 2fe676ba..6c8b9054 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -48,8 +48,8 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(NexusKeyValidator& v) - : m_validator(v), m_updateTimer(nullptr), m_first(true) +ValidationProgressDialog::ValidationProgressDialog(Settings* s, NexusKeyValidator& v) + : m_settings(s), m_validator(v), m_updateTimer(nullptr), m_first(true) { ui.reset(new Ui::ValidationProgressDialog); ui->setupUi(this); @@ -98,7 +98,10 @@ void ValidationProgressDialog::stop() void ValidationProgressDialog::showEvent(QShowEvent* e) { if (m_first) { - Settings::instance().geometry().centerOnMainWindowMonitor(this); + if (m_settings) { + m_settings->geometry().centerOnMainWindowMonitor(this); + } + m_first = false; } } @@ -592,8 +595,8 @@ void ValidationAttempt::cleanup() } -NexusKeyValidator::NexusKeyValidator(NXMAccessManager& am) - : m_manager(am) +NexusKeyValidator::NexusKeyValidator(Settings* s, NXMAccessManager& am) + : m_settings(s), m_manager(am) { } @@ -602,6 +605,15 @@ NexusKeyValidator::~NexusKeyValidator() cancel(); } +std::vector NexusKeyValidator::getTimeouts() const +{ + if (m_settings) { + return m_settings->nexus().validationTimeouts(); + } else { + return {10s, 15s, 20s}; + } +} + void NexusKeyValidator::start(const QString& key, Behaviour b) { if (isActive()) { @@ -611,7 +623,7 @@ void NexusKeyValidator::start(const QString& key, Behaviour b) m_key = key; - const auto timeouts = Settings::instance().nexus().validationTimeouts(); + const auto timeouts = getTimeouts(); switch (b) { @@ -755,10 +767,11 @@ void NexusKeyValidator::setFinished( } -NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) +NXMAccessManager::NXMAccessManager(QObject *parent, Settings* s, const QString &moVersion) : QNetworkAccessManager(parent) + , m_Settings(s) , m_MOVersion(moVersion) - , m_validator(*this) + , m_validator(s, *this) , m_validationState(NotChecked) { m_validator.finished = [&](auto&& r, auto&& m, auto&& u) { @@ -769,8 +782,10 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) onValidatorAttemptFinished(a); }; - setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( - Settings::instance().paths().cache() + "/nexus_cookies.dat"))); + if (m_Settings) { + setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( + m_Settings->paths().cache() + "/nexus_cookies.dat"))); + } if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { // why is this necessary all of a sudden? @@ -963,7 +978,7 @@ void NXMAccessManager::clearApiKey() void NXMAccessManager::startProgress() { if (!m_ProgressDialog) { - m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + m_ProgressDialog.reset(new ValidationProgressDialog(m_Settings, m_validator)); } m_ProgressDialog->start(); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 6a45d880..60100467 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -33,6 +33,7 @@ along with Mod Organizer. If not, see . namespace MOBase { class IPluginGame; } namespace Ui { class ValidationProgressDialog; } class NXMAccessManager; +class Settings; class NexusSSOLogin { @@ -146,7 +147,7 @@ public: std::function finished; std::function attemptFinished; - NexusKeyValidator(NXMAccessManager& am); + NexusKeyValidator(Settings* s, NXMAccessManager& am); ~NexusKeyValidator(); void start(const QString& key, Behaviour b); @@ -157,11 +158,13 @@ public: const ValidationAttempt* currentAttempt() const; private: + Settings* m_settings; NXMAccessManager& m_manager; QString m_key; std::vector> m_attempts; void createAttempts(const std::vector& timeouts); + std::vector getTimeouts() const; bool nextTry(); void onAttemptSuccess(const ValidationAttempt& a, const APIUserAccount& u); @@ -178,7 +181,7 @@ class ValidationProgressDialog : public QDialog Q_OBJECT; public: - ValidationProgressDialog(NexusKeyValidator& v); + ValidationProgressDialog(Settings* s, NexusKeyValidator& v); void setParentWidget(QWidget* w); @@ -191,6 +194,7 @@ protected: private: std::unique_ptr ui; + Settings* m_settings; NexusKeyValidator& m_validator; QTimer* m_updateTimer; bool m_first; @@ -209,7 +213,7 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: - NXMAccessManager(QObject *parent, const QString &moVersion); + NXMAccessManager(QObject *parent, Settings* s, const QString &moVersion); void setTopLevelWidget(QWidget* w); @@ -264,6 +268,7 @@ private: }; QWidget* m_TopLevel; + Settings* m_Settings; mutable std::unique_ptr m_ProgressDialog; QString m_MOVersion; NexusKeyValidator m_validator; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 7fd8c33b..0d561581 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -339,7 +339,7 @@ bool OrganizerCore::nexusApi(bool retry) return false; } else { QString apiKey; - if (m_Settings.nexus().apiKey(apiKey)) { + if (GlobalSettings::nexusApiKey(apiKey)) { // credentials stored or user entered them manually log::debug("attempt to verify nexus api key"); accessManager->apiCheck(apiKey); @@ -1426,7 +1426,7 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) f(); } else { QString apiKey; - if (settings().nexus().apiKey(apiKey)) { + if (GlobalSettings::nexusApiKey(apiKey)) { doAfterLogin([f]{ f(); }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else { diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 45efc00c..8cc95a8b 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -287,3 +287,215 @@ bool OrganizerProxy::onPluginSettingChanged(std::functiononPluginSettingChanged(func); } + + + +DummyOrganizerProxy::DummyOrganizerProxy(const QString &pluginName) : + m_PluginName(pluginName), + m_mods(new DummyModList), m_plugins(new DummyPluginList) +{ +} + +DummyOrganizerProxy::~DummyOrganizerProxy() = default; + +IModRepositoryBridge *DummyOrganizerProxy::createNexusBridge() const +{ + return nullptr; +} + +QString DummyOrganizerProxy::profileName() const +{ + return {}; +} + +QString DummyOrganizerProxy::profilePath() const +{ + return {}; +} + +QString DummyOrganizerProxy::downloadsPath() const +{ + return {}; +} + +QString DummyOrganizerProxy::overwritePath() const +{ + return {}; +} + +QString DummyOrganizerProxy::basePath() const +{ + return {}; +} + +QString DummyOrganizerProxy::modsPath() const +{ + return {}; +} + +VersionInfo DummyOrganizerProxy::appVersion() const +{ + return {}; +} + +IModInterface *DummyOrganizerProxy::getMod(const QString &name) const +{ + return nullptr; +} + +IPluginGame *DummyOrganizerProxy::getGame(const QString &gameName) const +{ + return nullptr; +} + +IModInterface *DummyOrganizerProxy::createMod(MOBase::GuessedValue &name) +{ + return nullptr; +} + +bool DummyOrganizerProxy::removeMod(IModInterface *mod) +{ + return true; +} + +void DummyOrganizerProxy::modDataChanged(IModInterface *mod) +{ +} + +QVariant DummyOrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const +{ + if (key == "enabled") { + return true; + } + + return {}; +} + +void DummyOrganizerProxy::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ +} + +QVariant DummyOrganizerProxy::persistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + return {}; +} + +void DummyOrganizerProxy::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ +} + +QString DummyOrganizerProxy::pluginDataPath() const +{ + return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + "/data"; +} + +HANDLE DummyOrganizerProxy::startApplication( + const QString& exe, const QStringList& args, const QString &cwd, + const QString& profile, const QString &overwrite, bool ignoreOverwrite) +{ + return INVALID_HANDLE_VALUE; +} + +bool DummyOrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const +{ + return true; +} + +bool DummyOrganizerProxy::onAboutToRun(const std::function &func) +{ + return true; +} + +bool DummyOrganizerProxy::onFinishedRun(const std::function &func) +{ + return true; +} + +bool DummyOrganizerProxy::onModInstalled(const std::function &func) +{ + return true; +} + +bool DummyOrganizerProxy::onUserInterfaceInitialized(std::function const& func) +{ + return true; +} + +bool DummyOrganizerProxy::onProfileChanged(std::function const& func) +{ + return true; +} + +bool DummyOrganizerProxy::onPluginSettingChanged(std::function const& func) +{ + return true; +} + +void DummyOrganizerProxy::refreshModList(bool saveChanges) +{ +} + +IModInterface *DummyOrganizerProxy::installMod(const QString &fileName, const QString &nameSuggestion) +{ + return nullptr; +} + +QString DummyOrganizerProxy::resolvePath(const QString &fileName) const +{ + return {}; +} + +QStringList DummyOrganizerProxy::listDirectories(const QString &directoryName) const +{ + return {}; +} + +QStringList DummyOrganizerProxy::findFiles(const QString &path, const std::function &filter) const +{ + return {}; +} + +QStringList DummyOrganizerProxy::findFiles(const QString& path, const QStringList& globFilters) const +{ + return {}; +} + +QStringList DummyOrganizerProxy::getFileOrigins(const QString &fileName) const +{ + return {}; +} + +QList DummyOrganizerProxy::findFileInfos(const QString &path, const std::function &filter) const +{ + return {}; +} + +MOBase::IDownloadManager *DummyOrganizerProxy::downloadManager() const +{ + return nullptr; +} + +MOBase::IPluginList *DummyOrganizerProxy::pluginList() const +{ + return m_plugins.get(); +} + +MOBase::IModList *DummyOrganizerProxy::modList() const +{ + return m_mods.get(); +} + +MOBase::IProfile *DummyOrganizerProxy::profile() const +{ + return nullptr; +} + +MOBase::IPluginGame const *DummyOrganizerProxy::managedGame() const +{ + return nullptr; +} + +QStringList DummyOrganizerProxy::modsSortedByProfilePriority() const +{ + return {}; +} diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 6690d612..3bd70113 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -81,4 +81,63 @@ private: }; + +class DummyOrganizerProxy : public MOBase::IOrganizer +{ +public: + DummyOrganizerProxy(const QString &pluginName); + ~DummyOrganizerProxy(); + + virtual MOBase::IModRepositoryBridge *createNexusBridge() const; + virtual QString profileName() const; + virtual QString profilePath() const; + virtual QString downloadsPath() const; + virtual QString overwritePath() const; + virtual QString basePath() const; + virtual QString modsPath() const; + virtual MOBase::VersionInfo appVersion() const; + virtual MOBase::IModInterface *getMod(const QString &name) const; + virtual MOBase::IPluginGame *getGame(const QString &gameName) const; + virtual MOBase::IModInterface *createMod(MOBase::GuessedValue &name); + virtual bool removeMod(MOBase::IModInterface *mod); + virtual void modDataChanged(MOBase::IModInterface *mod); + virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const; + virtual void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + virtual QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const; + virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true); + virtual QString pluginDataPath() const; + virtual MOBase::IModInterface *installMod(const QString &fileName, const QString &nameSuggestion = QString()); + virtual QString resolvePath(const QString &fileName) const; + virtual QStringList listDirectories(const QString &directoryName) const; + virtual QStringList findFiles(const QString &path, const std::function &filter) const override; + virtual QStringList findFiles(const QString &path, const QStringList &globFilters) const override; + virtual QStringList getFileOrigins(const QString &fileName) const; + virtual QList findFileInfos(const QString &path, const std::function &filter) const; + + virtual MOBase::IDownloadManager *downloadManager() const; + virtual MOBase::IPluginList *pluginList() const; + virtual MOBase::IModList *modList() const; + virtual MOBase::IProfile *profile() const override; + virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", + const QString &profile = "", const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); + virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode = nullptr) const; + virtual void refreshModList(bool saveChanges); + + virtual bool onAboutToRun(const std::function &func); + virtual bool onFinishedRun(const std::function &func); + virtual bool onModInstalled(const std::function &func); + virtual bool onUserInterfaceInitialized(std::function const& func); + virtual bool onProfileChanged(std::function const& func); + virtual bool onPluginSettingChanged(std::function const& func); + + virtual MOBase::IPluginGame const *managedGame() const; + + virtual QStringList modsSortedByProfilePriority() const; + +private: + const QString &m_PluginName; + std::unique_ptr m_plugins; + std::unique_ptr m_mods; +}; + #endif // ORGANIZERPROXY_H diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 4771359d..6f2670dd 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -173,10 +173,21 @@ bool PluginContainer::verifyPlugin(IPlugin *plugin) { if (plugin == nullptr) { return false; - } else if (!plugin->init(new OrganizerProxy(m_Organizer, this, plugin))) { + } + + IOrganizer* proxy = nullptr; + + if (m_Organizer) { + proxy = new OrganizerProxy(m_Organizer, this, plugin); + } else { + proxy = new DummyOrganizerProxy(plugin); + } + + if (!plugin->init(proxy)) { log::warn("plugin failed to initialize"); return false; } + return true; } @@ -200,7 +211,9 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) return false; } plugin->setProperty("filename", fileName); - m_Organizer->settings().plugins().registerPlugin(pluginObj); + if (m_Organizer) { + m_Organizer->settings().plugins().registerPlugin(pluginObj); + } } { // diagnosis plugin @@ -244,7 +257,9 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) IPluginInstaller *installer = qobject_cast(plugin); if (verifyPlugin(installer)) { bf::at_key(m_Plugins).push_back(installer); - m_Organizer->installationManager()->registerInstaller(installer); + if (m_Organizer) { + m_Organizer->installationManager()->registerInstaller(installer); + } return true; } } @@ -317,7 +332,7 @@ void PluginContainer::unloadPlugins() } // disconnect all slots before unloading plugins so plugins don't have to take care of that - if (m_Organizer != nullptr) { + if (m_Organizer) { m_Organizer->disconnectPlugins(); } @@ -363,24 +378,28 @@ void PluginContainer::loadPlugins() registerPlugin(plugin, ""); } - QFile loadCheck(qApp->property("dataPath").toString() + "/plugin_loadcheck.tmp"); - if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { - // oh, there was a failed plugin load last time. Find out which plugin was loaded last - QString fileName; - while (!loadCheck.atEnd()) { - fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); - } - if (QMessageBox::question(nullptr, QObject::tr("Plugin error"), - QObject::tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" - "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " - "The plugin may be able to recover from the problem)").arg(fileName), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_Organizer->settings().plugins().addBlacklist(fileName); + QFile loadCheck; + + if (m_Organizer) { + loadCheck.setFileName(qApp->property("dataPath").toString() + "/plugin_loadcheck.tmp"); + if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { + // oh, there was a failed plugin load last time. Find out which plugin was loaded last + QString fileName; + while (!loadCheck.atEnd()) { + fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); + } + if (QMessageBox::question(nullptr, QObject::tr("Plugin error"), + QObject::tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" + "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " + "The plugin may be able to recover from the problem)").arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + m_Organizer->settings().plugins().addBlacklist(fileName); + } + loadCheck.close(); } - loadCheck.close(); - } - loadCheck.open(QIODevice::WriteOnly); + loadCheck.open(QIODevice::WriteOnly); + } QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath)); @@ -388,13 +407,20 @@ void PluginContainer::loadPlugins() while (iter.hasNext()) { iter.next(); - if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { - log::debug("plugin \"{}\" blacklisted", iter.fileName()); - continue; + + if (m_Organizer) { + if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { + log::debug("plugin \"{}\" blacklisted", iter.fileName()); + continue; + } } - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); + + if (loadCheck.isOpen()) { + loadCheck.write(iter.fileName().toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); + } + QString pluginName = iter.filePath(); if (QLibrary::isLibrary(pluginName)) { std::unique_ptr pluginLoader(new QPluginLoader(pluginName, this)); @@ -416,12 +442,16 @@ void PluginContainer::loadPlugins() } // remove the load check file on success - loadCheck.remove(); + if (loadCheck.isOpen()) { + loadCheck.remove(); + } - bf::at_key(m_Plugins).push_back(m_Organizer); bf::at_key(m_Plugins).push_back(this); - m_Organizer->connectPlugins(this); + if (m_Organizer) { + bf::at_key(m_Plugins).push_back(m_Organizer); + m_Organizer->connectPlugins(this); + } } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index a4c1ec6d..7134d246 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1671,3 +1671,63 @@ void PluginList::managedGameChanged(const IPluginGame *gamePlugin) { m_GamePlugin = gamePlugin; } + + + +QStringList DummyPluginList::pluginNames() const +{ + return {}; +} + +IPluginList::PluginStates DummyPluginList::state(const QString &name) const +{ + return 0; +} + +void DummyPluginList::setState(const QString &name, PluginStates state) +{ +} + +int DummyPluginList::priority(const QString &name) const +{ + return -1; +} + +int DummyPluginList::loadOrder(const QString &name) const +{ + return -1; +} + +void DummyPluginList::setLoadOrder(const QStringList &pluginList) +{ +} + +bool DummyPluginList::isMaster(const QString &name) const +{ + return false; +} + +QStringList DummyPluginList::masters(const QString &name) const +{ + return {}; +} + +QString DummyPluginList::origin(const QString &name) const +{ + return {}; +} + +bool DummyPluginList::onRefreshed(const std::function &callback) +{ + return true; +} + +bool DummyPluginList::onPluginMoved(const std::function &func) +{ + return true; +} + +bool DummyPluginList::onPluginStateChanged(const std::function &func) +{ + return true; +} diff --git a/src/pluginlist.h b/src/pluginlist.h index 0b49b86f..bfadaf4f 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -410,6 +410,24 @@ private: bool hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const; }; + +class DummyPluginList : public MOBase::IPluginList +{ +public: + QStringList pluginNames() const override; + PluginStates state(const QString &name) const override; + void setState(const QString &name, PluginStates state) override; + int priority(const QString &name) const override; + int loadOrder(const QString &name) const override; + void setLoadOrder(const QStringList &pluginList) override; + bool isMaster(const QString &name) const override; + QStringList masters(const QString &name) const override; + QString origin(const QString &name) const override; + bool onRefreshed(const std::function &callback) override; + bool onPluginMoved(const std::function &func) override; + bool onPluginStateChanged(const std::function &func) override; +}; + #pragma warning(pop) #endif // PLUGINLIST_H diff --git a/src/settings.cpp b/src/settings.cpp index b286510d..593d66bf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1812,37 +1812,6 @@ NexusSettings::NexusSettings(Settings& parent, QSettings& settings) { } -bool NexusSettings::apiKey(QString& apiKey) const -{ - QString tempKey = getWindowsCredential("APIKEY"); - if (tempKey.isEmpty()) - return false; - - apiKey = tempKey; - return true; -} - -bool NexusSettings::setApiKey(const QString& apiKey) -{ - if (!setWindowsCredential("APIKEY", apiKey)) { - const auto e = GetLastError(); - log::error("Storing API key failed: {}", formatSystemMessage(e)); - return false; - } - - return true; -} - -bool NexusSettings::clearApiKey() -{ - return setApiKey(""); -} - -bool NexusSettings::hasApiKey() const -{ - return !getWindowsCredential("APIKEY").isEmpty(); -} - bool NexusSettings::endorsementIntegration() const { return get(m_Settings, "Settings", "endorsement_integration", true); @@ -2224,6 +2193,37 @@ void GlobalSettings::setHideTutorialQuestion(bool b) settings().setValue("HideTutorialQuestion", b); } +bool GlobalSettings::nexusApiKey(QString& apiKey) +{ + QString tempKey = getWindowsCredential("APIKEY"); + if (tempKey.isEmpty()) + return false; + + apiKey = tempKey; + return true; +} + +bool GlobalSettings::setNexusApiKey(const QString& apiKey) +{ + if (!setWindowsCredential("APIKEY", apiKey)) { + const auto e = GetLastError(); + log::error("Storing API key failed: {}", formatSystemMessage(e)); + return false; + } + + return true; +} + +bool GlobalSettings::clearNexusApiKey() +{ + return setNexusApiKey(""); +} + +bool GlobalSettings::hasNexusApiKey() +{ + return !getWindowsCredential("APIKEY").isEmpty(); +} + void GlobalSettings::resetDialogs() { setHideCreateInstanceIntro(false); diff --git a/src/settings.h b/src/settings.h index a9501b9d..f71949d2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -498,24 +498,6 @@ class NexusSettings public: NexusSettings(Settings& parent, QSettings& settings); - // if the key exists from the credentials store, puts it in `apiKey` and - // returns true; otherwise, returns false and leaves `apiKey` untouched - // - bool apiKey(QString& apiKey) const; - - // sets the api key in the credentials store, removes it if empty; returns - // false on errors - // - bool setApiKey(const QString& apiKey); - - // removes the api key from the credentials store; returns false on errors - // - bool clearApiKey(); - - // returns whether an API key is currently stored - // - bool hasApiKey() const; - // returns whether endorsement integration is enabled // bool endorsementIntegration() const; @@ -844,6 +826,24 @@ public: static bool hideTutorialQuestion(); static void setHideTutorialQuestion(bool b); + // if the key exists from the credentials store, puts it in `apiKey` and + // returns true; otherwise, returns false and leaves `apiKey` untouched + // + static bool nexusApiKey(QString& apiKey); + + // sets the api key in the credentials store, removes it if empty; returns + // false on errors + // + static bool setNexusApiKey(const QString& apiKey); + + // removes the api key from the credentials store; returns false on errors + // + static bool clearNexusApiKey(); + + // returns whether an API key is currently stored + // + static bool hasNexusApiKey(); + // resets anything that the user can disable static void resetDialogs(); diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 54af9b59..1ed4c58f 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -72,13 +72,14 @@ private: NexusConnectionUI::NexusConnectionUI( - Settings& s, QWidget* parent, + Settings* s, QAbstractButton* connectButton, QAbstractButton* disconnectButton, QAbstractButton* manualButton, QListWidget* logList) : - m_parent(parent), m_settings(s), + m_parent(parent), + m_settings(s), m_connect(connectButton), m_disconnect(disconnectButton), m_manual(manualButton), @@ -96,7 +97,7 @@ NexusConnectionUI::NexusConnectionUI( QObject::connect(manualButton, &QPushButton::clicked, [&]{ manual(); }); } - if (m_settings.nexus().hasApiKey()) { + if (GlobalSettings::hasNexusApiKey()) { addLog(tr("Connected.")); } else { addLog(tr("Not connected.")); @@ -162,7 +163,7 @@ void NexusConnectionUI::validateKey(const QString& key) { if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( - *NexusInterface::instance().getAccessManager())); + m_settings, *NexusInterface::instance().getAccessManager())); m_nexusValidator->finished = [&](auto&& r, auto&& m, auto&& u) { onValidatorFinished(r, m, u); @@ -231,7 +232,7 @@ void NexusConnectionUI::addLog(const QString& s) bool NexusConnectionUI::setKey(const QString& key) { - const bool ret = m_settings.nexus().setApiKey(key); + const bool ret = GlobalSettings::setNexusApiKey(key); updateState(); emit keyChanged(); @@ -241,7 +242,7 @@ bool NexusConnectionUI::setKey(const QString& key) bool NexusConnectionUI::clearKey() { - const auto ret = m_settings.nexus().clearApiKey(); + const auto ret = GlobalSettings::clearNexusApiKey(); NexusInterface::instance().getAccessManager()->clearApiKey(); updateState(); @@ -274,7 +275,7 @@ void NexusConnectionUI::updateState() setButton(m_disconnect, false); setButton(m_manual, true, QObject::tr("Cancel")); } - else if (m_settings.nexus().hasApiKey()) { + else if (GlobalSettings::hasNexusApiKey()) { // api key is present setButton(m_connect, false, QObject::tr("Connect to Nexus")); setButton(m_disconnect, true); @@ -327,7 +328,8 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) } m_connectionUI.reset(new NexusConnectionUI( - settings(), &dialog(), + &dialog(), + &settings(), ui->nexusConnect, ui->nexusDisconnect, ui->nexusManualKey, diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index a2d6a4f8..c915accf 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -13,8 +13,8 @@ class NexusConnectionUI : public QObject public: NexusConnectionUI( - Settings& s, QWidget* parent, + Settings* s, QAbstractButton* connectButton, QAbstractButton* disconnectButton, QAbstractButton* manualButton, @@ -30,7 +30,7 @@ signals: private: QWidget* m_parent; - Settings& m_settings; + Settings* m_settings; QAbstractButton* m_connect; QAbstractButton* m_disconnect; QAbstractButton* m_manual; -- cgit v1.3.1 From b10436d60f7db3eedd838cbfad93cb41ddf1fd86 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 31 Oct 2020 17:44:25 -0400 Subject: game icons in the instance list --- src/instancemanager.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++ src/instancemanager.h | 3 +++ src/instancemanagerdialog.cpp | 19 +++++++++++++- src/settings.cpp | 5 ++++ src/settings.h | 4 +++ 5 files changed, 89 insertions(+), 1 deletion(-) (limited to 'src/settings.cpp') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 111f948b..4ad099ed 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -637,6 +637,65 @@ MOBase::IPluginGame* InstanceManager::determineCurrentGame( return nullptr; } +const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( + const QDir& instanceDir, const PluginContainer& plugins) const +{ + const QString ini = + QDir(instanceDir).filePath(QString::fromStdWString(AppConfig::iniFileName())); + + + Settings s(ini); + + if (s.iniStatus() != QSettings::NoError) + { + log::error("failed to load settings from {}", ini); + return nullptr; + } + + const auto instanceGameName = s.game().name(); + + if (instanceGameName && !instanceGameName->isEmpty()) + { + for (const IPluginGame* game : plugins.plugins()) { + if (instanceGameName->compare(game->gameName(), Qt::CaseInsensitive) == 0) { + return game; + } + } + + log::error( + "no plugin reports game name '{}' found in ini {}", + *instanceGameName, ini); + } + else + { + log::error("no game name found in ini {}", ini); + } + + + log::error("falling back on looksValid check"); + + const auto gameDir = s.game().directory(); + + if (gameDir && !gameDir->isEmpty()) + { + for (const IPluginGame* game : plugins.plugins()) { + if (game->looksValid(*gameDir)) { + return game; + } + } + + log::error( + "no plugins appear to support game directory '{}' from ini {}", + *gameDir, ini); + } + else + { + log::error("no game directory found in ini {}", ini); + } + + return nullptr; +} + QString InstanceManager::makeUniqueName(const QString& instanceName) const { const QString sanitized = sanitizeInstanceName(instanceName); diff --git a/src/instancemanager.h b/src/instancemanager.h index 33e115a7..c2d1e0f4 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -47,6 +47,9 @@ public: 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; void setCurrentInstance(const QString &name); diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 7b8522ee..545b5c71 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -112,6 +112,19 @@ public: return makeIniFile(m_dir); } + QIcon icon(const PluginContainer& plugins) const + { + const auto* game = InstanceManager::instance().gamePluginForDirectory( + m_dir, plugins); + + if (game) + return game->gameIcon(); + + QPixmap empty(32, 32); + empty.fill(QColor(0, 0, 0, 0)); + return QIcon(empty); + } + bool isPortable() const { return m_portable; @@ -376,7 +389,11 @@ void InstanceManagerDialog::updateList() for (std::size_t i=0; iappendRow(new QStandardItem(ii.name())); + + auto* item = new QStandardItem(ii.name()); + item->setIcon(ii.icon(m_pc)); + + m_model->appendRow(item); if (&ii == prevSel) { sel = i; diff --git a/src/settings.cpp b/src/settings.cpp index 593d66bf..99b41e1f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -465,6 +465,11 @@ QSettings::Status Settings::sync() const return m_Settings.status(); } +QSettings::Status Settings::iniStatus() const +{ + return m_Settings.status(); +} + void Settings::dump() const { static const QStringList ignore({ diff --git a/src/settings.h b/src/settings.h index f71949d2..c8325ba2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -778,6 +778,10 @@ public: // QSettings::Status sync() const; + // last status of the ini file + // + QSettings::Status iniStatus() const; + void dump() const; public slots: -- cgit v1.3.1