From b7cb63ddb1e2b263d5e485c97faea527c7c0af44 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 11:37:12 -0500 Subject: removed some redundant functions in InstanceManager, made them all non-static documentation --- src/instancemanagerdialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/instancemanagerdialog.cpp') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 3b6daeb8..c2108f9d 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -48,7 +48,7 @@ public: void setDir(const QDir& dir) { m_dir = dir; - m_settings.reset(new Settings(InstanceManager::iniPath(dir))); + m_settings.reset(new Settings(InstanceManager::singleton().iniPath(dir))); } QString name() const @@ -96,7 +96,7 @@ public: QString iniFile() const { - return InstanceManager::iniPath(m_dir); + return InstanceManager::singleton().iniPath(m_dir); } QIcon icon(const PluginContainer& plugins) const @@ -351,7 +351,7 @@ void InstanceManagerDialog::updateInstances() m_instances.clear(); - for (auto&& d : m.instancePaths()) { + for (auto&& d : m.globalInstancePaths()) { m_instances.push_back(std::make_unique(d, false)); } -- cgit v1.3.1 From dc2ada2a4249917f538938298deb193ed1dd6bb9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 13:56:04 -0500 Subject: added ... to the "Delete Instance" button to make it less scary merged InstanceInfo into Instance, most of it was redundant added logging before deleting instance added Instance::readFromIni(), contains stuff that used to be in setup(), used by instance dialog replaced QDir with QString in a few places, I hate QDir --- src/instancemanager.cpp | 278 +++++++++++++++++++++++++++++---- src/instancemanager.h | 109 ++++++++++--- src/instancemanagerdialog.cpp | 347 +++++------------------------------------- src/instancemanagerdialog.h | 80 +++++++++- src/instancemanagerdialog.ui | 2 +- src/main.cpp | 8 +- 6 files changed, 456 insertions(+), 368 deletions(-) (limited to 'src/instancemanagerdialog.cpp') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index e7c2a611..fbafc6e8 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -38,7 +38,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; -Instance::Instance(QDir dir, bool portable, QString profileName) : +Instance::Instance(QString dir, bool portable, QString profileName) : m_dir(std::move(dir)), m_portable(portable), m_plugin(nullptr), m_profile(std::move(profileName)) { @@ -49,7 +49,7 @@ QString Instance::name() const if (isPortable()) return QObject::tr("Portable"); else - return m_dir.dirName(); + return QDir(m_dir).dirName(); } QString Instance::gameName() const @@ -62,11 +62,16 @@ QString Instance::gameDirectory() const return m_gameDir; } -QDir Instance::directory() const +QString Instance::directory() const { return m_dir; } +QString Instance::baseDirectory() const +{ + return m_baseDir; +} + MOBase::IPluginGame* Instance::gamePlugin() const { return m_plugin; @@ -87,13 +92,29 @@ bool Instance::isPortable() const return m_portable; } -Instance::SetupResults Instance::setup(PluginContainer& plugins) +bool Instance::isActive() const +{ + auto& m = InstanceManager::singleton(); + + if (auto i=m.currentInstance()) + { + if (m_portable) { + return i->isPortable(); + } else { + return (i->name() == name()); + } + } + + return false; +} + +bool Instance::readFromIni() { Settings s(iniPath()); if (s.iniStatus() != QSettings::NoError) { log::error("can't read ini {}", iniPath()); - return SetupResults::BadIni; + return false; } // game name and directory are from ini unless overridden by setGame() @@ -107,27 +128,61 @@ Instance::SetupResults Instance::setup(PluginContainer& plugins) m_gameDir = *v; } - // getting game plugin - const auto r = getGamePlugin(plugins); - if (r != SetupResults::Ok) { - return r; - } - - // getting game variant, error if it's missing and required by the plugin if (m_gameVariant.isEmpty()) { if (auto v=s.game().edition()) { m_gameVariant = *v; } } + if (m_baseDir.isEmpty()) { + m_baseDir = s.paths().base(); + } + + // figuring out profile from ini if it's missing + getProfile(s); + + return true; +} + +Instance::SetupResults Instance::setup(PluginContainer& plugins) +{ + // read initial values from the ini + if (!readFromIni()) { + return SetupResults::BadIni; + } + + // getting game plugin + const auto r = getGamePlugin(plugins); + if (r != SetupResults::Ok) { + return r; + } + + // error if the variant missing and required by the plugin if (m_gameVariant.isEmpty() && m_plugin->gameVariants().size() > 1) { return SetupResults::MissingVariant; } else { m_plugin->setGameVariant(m_gameVariant); } - // figuring out profile from ini if it's missing - getProfile(s); + // update the ini in case anything was missing + updateIni(); + + // the game directory may be different than what the plugin detected, the user + // can change it in the settings and might have multiple versions of the game + // installed + m_plugin->setGamePath(m_gameDir); + + return SetupResults::Ok; +} + +void Instance::updateIni() +{ + Settings s(iniPath()); + + if (s.iniStatus() != QSettings::NoError) { + log::error("can't open ini {}", iniPath()); + return; + } // updating the settings since some of these values might have been missing s.game().setName(m_gameName); @@ -138,13 +193,6 @@ Instance::SetupResults Instance::setup(PluginContainer& plugins) // don't write a variant to the ini if the plugin doesn't require one s.game().setEdition(m_gameVariant); } - - // the game directory may be different than what the plugin detected, the user - // can change it in the settings and might have multiple versions of the game - // installed - m_plugin->setGamePath(m_gameDir); - - return SetupResults::Ok; } void Instance::setGame(const QString& name, const QString& dir) @@ -272,7 +320,6 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) } } - void Instance::getProfile(const Settings& s) { if (!m_profile.isEmpty()) { @@ -294,6 +341,176 @@ void Instance::getProfile(const Settings& s) iniPath(), m_profile); } +// returns a list of files and folders that must be deleted when deleting +// this instance +// +std::vector Instance::objectsForDeletion() 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 = [](QString 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 = [&](const QString& root, const QString& dir) { + return canonicalDir(dir).startsWith(canonicalDir(root)); + }; + + // whether the given file is contained in the root + auto fileInRoot = [&](const QString& root, const QString& file) { + return canonicalFile(file).startsWith(canonicalDir(root)); + }; + + + Settings settings(iniPath()); + + if (settings.iniStatus() != QSettings::NoError) { + log::error("can't read ini {}", iniPath()); + return {}; + } + + + + const QString loc = directory(); + const QString base = settings.paths().base(); + + + // directories that might contain the individual files and directories + // set in the path settings + std::vector roots; + + // a portable instance has its location in the installation directory, + // don't delete that + if (!isPortable()) { + if (QDir(loc).exists()) { + roots.push_back({loc, true}); + } + } + + // the base directory is the location directory by default, don't add it + // if it's the same + if (canonicalDir(base) != canonicalDir(loc)) { + if (QDir(base).exists()) { + roots.push_back({base, false}); + } + } + + + // all the directories that are part of an instance; none of them are + // mandatory for deletion + const std::vector dirs = { + settings.paths().downloads(), + settings.paths().mods(), + settings.paths().cache(), + settings.paths().profiles(), + settings.paths().overwrite(), + QDir(m_dir).filePath(QString::fromStdWString(AppConfig::dumpsDir())), + QDir(m_dir).filePath(QString::fromStdWString(AppConfig::logPath())), + }; + + // all the files that are part of an instance + const std::vector files = { + {iniPath(), true}, // the ini file must be deleted + }; + + + // this will contain the root directories, plus all the individual + // directories that are not inside these roots + std::vector cleanDirs; + + for (const auto& f : dirs) { + bool inRoots = false; + + for (const auto& root : roots) { + if (dirInRoot(root.path, f.path)) { + inRoots = true; + break; + } + } + + if (!inRoots) { + // not in roots, this is a path that was changed by the user; make + // sure it exists + if (QDir(f.path).exists()) { + cleanDirs.push_back({prettyDir(f.path), f.mandatoryDelete}); + } + } + } + + // prepending the roots + for (auto itor=roots.rbegin(); itor!=roots.rend(); ++itor) { + cleanDirs.insert( + cleanDirs.begin(), + {prettyDir(itor->path), itor->mandatoryDelete}); + } + + + + // 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 + std::vector cleanFiles; + + for (const auto& f : files) { + bool inRoots = false; + + for (const auto& root : roots) { + if (fileInRoot(root.path, f.path)) { + inRoots = true; + break; + } + } + + if (!inRoots) { + // not in roots, this is a path that was changed by the user; make + // sure it exists + if (QFileInfo(f.path).exists()) { + cleanFiles.push_back({prettyFile(f.path), f.mandatoryDelete}); + } + } + } + + + // contains all the directories and files to be deleted + std::vector all; + all.insert(all.end(), cleanDirs.begin(), cleanDirs.end()); + all.insert(all.end(), cleanFiles.begin(), cleanFiles.end()); + + // mandatory on top + std::stable_sort(all.begin(), all.end()); + + return all; +} + + InstanceManager::InstanceManager() { @@ -327,20 +544,20 @@ std::optional InstanceManager::currentInstance() const if (!allowedToChangeInstance()) { // force portable instance - return Instance(QDir(portablePath()), true, profile); + return Instance(portablePath(), true, profile); } if (name.isEmpty()) { if (portableInstanceExists()) { // use portable - return Instance(QDir(portablePath()), true, profile); + return Instance(portablePath(), true, profile); } else { // no instance set return {}; } } - return Instance(QDir(instancePath(name)), false, profile); + return Instance(instancePath(name), false, profile); } void InstanceManager::clearCurrentInstance() @@ -365,12 +582,13 @@ QString InstanceManager::globalInstancesRootPath() const QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } -QString InstanceManager::iniPath(const QDir& instanceDir) const +QString InstanceManager::iniPath(const QString& instanceDir) const { - return instanceDir.filePath(QString::fromStdWString(AppConfig::iniFileName())); + return QDir(instanceDir).filePath( + QString::fromStdWString(AppConfig::iniFileName())); } -std::vector InstanceManager::globalInstancePaths() const +std::vector InstanceManager::globalInstancePaths() const { const std::set ignore = { "cache", "qtwebengine", @@ -379,7 +597,7 @@ std::vector InstanceManager::globalInstancePaths() const const QDir root(globalInstancesRootPath()); const auto dirs = root.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - std::vector list; + std::vector list; for (auto&& d : dirs) { if (!ignore.contains(QFileInfo(d).fileName().toLower())) { @@ -416,7 +634,7 @@ bool InstanceManager::allowedToChangeInstance() const } const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( - const QDir& instanceDir, const PluginContainer& plugins) const + const QString& instanceDir, const PluginContainer& plugins) const { const QString ini = iniPath(instanceDir); diff --git a/src/instancemanager.h b/src/instancemanager.h index e2ceb743..161df739 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -55,15 +55,64 @@ public: }; + // a file or directory owned by this instance, used by objectsForDeletion() + // + struct Object + { + // path to the file or directory + QString path; + + // whether this object must be deleted to properly delete the instance; + // typically true only for the instance directory itself, but not for the + // base directory, etc. + bool mandatoryDelete; + + + Object(QString p, bool d=false) + : path(std::move(p)), mandatoryDelete(d) + { + } + + // puts mandatory delete on top + // + bool operator<(const Object& o) const + { + if (mandatoryDelete && !o.mandatoryDelete) { + return true; + } else if (!mandatoryDelete && o.mandatoryDelete) { + return false; + } + + return false; + } + }; + + + // an instance that lives in the given directory; `portable` must be `true` // if this is a portable instance // // `profileName` can be given to override what's in the INI; this typically // happens when the profile is overriden on the command line // - Instance(QDir dir, bool portable, QString profileName={}); + Instance(QString dir, bool portable, QString profileName={}); + + + // reads in values from the INI if they were not given yet: + // - game name + // - game directory + // - game variant + // - profile name + // + // note that setup() already calls this + // + // returns false if the ini couldn't be read from + // + bool readFromIni(); + - // finds the appropriate game plugin and sets it up so MO can use it + // finds the appropriate game plugin and sets it up so MO can use it; this + // calls readFromIni() first // // setup() tries to recover from some errors, but can fail for a variety of // reasons, see SetupResults @@ -88,22 +137,32 @@ public: QString name() const; // returns either: - // 1) the game name from the INI, - // 2) gameName() from the game plugin if it was missing, or - // 3) whatever was given in setGame() + // 1) the game name from the INI, if readFromIni() was called; + // 2) gameName() from the game plugin if it was missing and setup() was + // called; or + // 3) whatever was given in setGame() // QString gameName() const; // returns either: - // 1) the game directory from the INI, - // 2) gameDirectory() from the game plugin if it was missing, or - // 3) whatever was given in setGame() + // 1) the game directory from the INI, if readFromIni() was called; + // 2) gameDirectory() from the game plugin if it was missing and setup() + // was called; or + // 3) whatever was given in setGame() // QString gameDirectory() const; - // returns the instance directory; can be called without setup() + // returns the instance directory as given in the constructor // - QDir directory() const; + QString directory() const; + + // returns the base directory; empty if readFromIni() hasn't been called + // + QString baseDirectory() const; + + // returns whether this is a portable instance, as given in the constructor + // + bool isPortable() const; // returns the selected game plugin; will return null if setup() hasn't been // called, or if it failed @@ -111,8 +170,8 @@ public: MOBase::IPluginGame* gamePlugin() const; // returns either: - // 1) the profile name given in the constructor, - // 2) the profile name from the INI, or + // 1) the profile name given in the constructor if not empty; + // 2) the profile name from the INI if readFromIni() was called, or // 3) the default profile name if it's missing (see // AppConfig::defaultProfileName()) // @@ -123,15 +182,21 @@ public: // QString iniPath() const; - // returns whether this is a portable instance; this is the flag given in the - // constructor + // whether this is the currently active instance in MO // - bool isPortable() const; + bool isActive() const; + + // returns a list of files and directories that must be deleted when deleting + // this instance; this will read the INI and fail if it's not accessible + // + // returns an empty list on failure + // + std::vector objectsForDeletion() const; private: - QDir m_dir; + QString m_dir; bool m_portable; - QString m_gameName, m_gameDir, m_gameVariant; + QString m_gameName, m_gameDir, m_gameVariant, m_baseDir; MOBase::IPluginGame* m_plugin; QString m_profile; @@ -142,6 +207,10 @@ private: // figures out the profile name for this instance // void getProfile(const Settings& s); + + // updates the ini with the given values and the ones found by setup() + // + void updateIni(); }; @@ -175,7 +244,7 @@ public: // returns null if all of this fails // const MOBase::IPluginGame* gamePluginForDirectory( - const QDir& dir, const PluginContainer& plugins) const; + const QString& dir, const PluginContainer& plugins) const; // clears the instance name from the registry; on restart, this will make MO // either select the portable instance if it exists, or display the instance @@ -221,7 +290,7 @@ public: // returns the list of absolute path to all existing global instances; this // does not include the portable instance // - std::vector globalInstancePaths() const; + std::vector globalInstancePaths() const; // returns `name` modified so that it is a valid instance name // @@ -253,7 +322,7 @@ public: // returns the absolute path to the INI file for the given instance directory; // the file may not exist // - QString iniPath(const QDir& instanceDir) const; + QString iniPath(const QString& instanceDir) const; private: InstanceManager(); diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index c2108f9d..c199fa7b 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -13,292 +13,19 @@ using namespace MOBase; -class InstanceInfo -{ -public: - struct Object - { - QString path; - bool mandatoryDelete; - - Object(QString p, bool d=false) - : path(std::move(p)), mandatoryDelete(d) - { - } - - bool operator<(const Object& o) const - { - if (mandatoryDelete && !o.mandatoryDelete) { - return true; - } else if (!mandatoryDelete && o.mandatoryDelete) { - return false; - } - - return false; - } - }; - - - InstanceInfo(QDir dir, bool isPortable) - : m_portable(isPortable) - { - setDir(dir); - } - - void setDir(const QDir& dir) - { - m_dir = dir; - m_settings.reset(new Settings(InstanceManager::singleton().iniPath(dir))); - } - - QString name() const - { - if (m_portable) { - return QObject::tr("Portable"); - } else { - 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 QDir::toNativeSeparators(*n); - } else { - return {}; - } - } - - QString location() const - { - return QDir::toNativeSeparators(m_dir.path()); - } - - QString baseDirectory() const - { - return QDir::toNativeSeparators(m_settings->paths().base()); - } - - QString iniFile() const - { - return InstanceManager::singleton().iniPath(m_dir); - } - - QIcon icon(const PluginContainer& plugins) const - { - const auto* game = InstanceManager::singleton().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; - } - - bool isActive() const - { - auto& m = InstanceManager::singleton(); - - if (auto i=m.currentInstance()) - { - if (m_portable) { - return i->isPortable(); - } else { - return (i->name() == name()); - } - } - - return false; - } - - // returns a list of files and folders that must be deleted when deleting - // this instance - // - std::vector objectsForDeletion() 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)); - }; - +QIcon instanceIcon(const PluginContainer& pc, const Instance& i) +{ + const auto* game = InstanceManager::singleton() + .gamePluginForDirectory(i.directory(), pc); + if (game) + return game->gameIcon(); - 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 - std::vector roots; - - // a portable instance has its location in the installation directory, - // don't delete that - if (!isPortable()) { - if (QDir(loc).exists()) { - roots.push_back({loc, true}); - } - } - - // the base directory is the location directory by default, don't add it - // if it's the same - if (canonicalDir(base) != canonicalDir(loc)) { - if (QDir(base).exists()) { - roots.push_back({base, false}); - } - } - - - // all the directories that are part of an instance; none of them are - // mandatory for deletion - const std::vector dirs = { - m_settings->paths().downloads(), - m_settings->paths().mods(), - m_settings->paths().cache(), - m_settings->paths().profiles(), - m_settings->paths().overwrite(), - m_dir.filePath(QString::fromStdWString(AppConfig::dumpsDir())), - m_dir.filePath(QString::fromStdWString(AppConfig::logPath())), - }; - - // all the files that are part of an instance - const std::vector files = { - {iniFile(), true}, // the ini file must be deleted - }; - - - // this will contain the root directories, plus all the individual - // directories that are not inside these roots - std::vector cleanDirs; - - for (const auto& f : dirs) { - bool inRoots = false; - - for (const auto& root : roots) { - if (dirInRoot(root.path, f.path)) { - inRoots = true; - break; - } - } - - if (!inRoots) { - // not in roots, this is a path that was changed by the user; make - // sure it exists - if (QDir(f.path).exists()) { - cleanDirs.push_back({prettyDir(f.path), f.mandatoryDelete}); - } - } - } - - // prepending the roots - for (auto itor=roots.rbegin(); itor!=roots.rend(); ++itor) { - cleanDirs.insert( - cleanDirs.begin(), - {prettyDir(itor->path), itor->mandatoryDelete}); - } - - - - // 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 - std::vector cleanFiles; - - for (const auto& f : files) { - bool inRoots = false; - - for (const auto& root : roots) { - if (fileInRoot(root.path, f.path)) { - inRoots = true; - break; - } - } - - if (!inRoots) { - // not in roots, this is a path that was changed by the user; make - // sure it exists - if (QFileInfo(f.path).exists()) { - cleanFiles.push_back({prettyFile(f.path), f.mandatoryDelete}); - } - } - } - - - // contains all the directories and files to be deleted - std::vector all; - all.insert(all.end(), cleanDirs.begin(), cleanDirs.end()); - all.insert(all.end(), cleanFiles.begin(), cleanFiles.end()); - - // mandatory on top - std::stable_sort(all.begin(), all.end()); - - return all; - } - -private: - const bool m_portable; - QDir m_dir; - std::unique_ptr m_settings; -}; + QPixmap empty(32, 32); + empty.fill(QColor(0, 0, 0, 0)); + return QIcon(empty); +} InstanceManagerDialog::~InstanceManagerDialog() = default; @@ -352,7 +79,7 @@ void InstanceManagerDialog::updateInstances() m_instances.clear(); for (auto&& d : m.globalInstancePaths()) { - m_instances.push_back(std::make_unique(d, false)); + m_instances.push_back(std::make_unique(d, false)); } // sort first, prepend portable after so it's always on top @@ -363,7 +90,12 @@ void InstanceManagerDialog::updateInstances() if (m.portableInstanceExists()) { m_instances.insert( m_instances.begin(), - std::make_unique(m.portablePath(), true)); + std::make_unique(m.portablePath(), true)); + } + + // read all inis, ignore errors + for (auto&& i : m_instances) { + i->readFromIni(); } } @@ -381,7 +113,7 @@ void InstanceManagerDialog::updateList() const auto& ii = *m_instances[i]; auto* item = new QStandardItem(ii.name()); - item->setIcon(ii.icon(m_pc)); + item->setIcon(instanceIcon(m_pc, ii)); m_model->appendRow(item); @@ -568,9 +300,9 @@ void InstanceManagerDialog::rename() return; } - const QString src = i->location(); + const QString src = i->directory(); const QString dest = QDir::toNativeSeparators( - QFileInfo(i->location()).dir().path() + "/" + newName); + QFileInfo(src).dir().path() + "/" + newName); const auto r = shell::Rename(src, dest, false); if (!r) { @@ -582,15 +314,19 @@ void InstanceManagerDialog::rename() return; } + auto newInstance = std::make_unique(dest, false); + i = newInstance.get(); + m_model->item(selIndex)->setText(newName); - i->setDir(dest); + m_instances[selIndex] = std::move(newInstance); + fillData(*i); } void InstanceManagerDialog::exploreLocation() { if (const auto* i=singleSelection()) { - shell::Explore(i->location()); + shell::Explore(i->directory()); } } @@ -604,14 +340,14 @@ void InstanceManagerDialog::exploreBaseDirectory() void InstanceManagerDialog::exploreGame() { if (const auto* i=singleSelection()) { - shell::Explore(i->gamePath()); + shell::Explore(i->gameDirectory()); } } void InstanceManagerDialog::openINI() { if (const auto* i=singleSelection()) { - shell::Open(i->iniFile()); + shell::Open(i->iniPath()); } } @@ -707,6 +443,15 @@ void InstanceManagerDialog::setRestartOnSelect(bool b) bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) { + // logging + for (auto&& f : files) { + if (recycle) { + log::info("will recycle {}", f); + } else { + log::info("will delete {}", f); + } + } + if (MOBase::shellDelete(files, recycle, this)) { return true; } @@ -771,17 +516,7 @@ std::size_t InstanceManagerDialog::singleSelectionIndex() const return static_cast(sel.indexes()[0].row()); } -InstanceInfo* InstanceManagerDialog::singleSelection() -{ - const auto i = singleSelectionIndex(); - if (i == NoSelection) { - return nullptr; - } - - return m_instances[i].get(); -} - -const InstanceInfo* InstanceManagerDialog::singleSelection() const +const Instance* InstanceManagerDialog::singleSelection() const { const auto i = singleSelectionIndex(); if (i == NoSelection) { @@ -791,13 +526,13 @@ const InstanceInfo* InstanceManagerDialog::singleSelection() const return m_instances[i].get(); } -void InstanceManagerDialog::fillData(const InstanceInfo& ii) +void InstanceManagerDialog::fillData(const Instance& ii) { ui->name->setText(ii.name()); - ui->location->setText(ii.location()); + ui->location->setText(ii.directory()); ui->baseDirectory->setText(ii.baseDirectory()); ui->gameName->setText(ii.gameName()); - ui->gameDir->setText(ii.gamePath()); + ui->gameDir->setText(ii.gameDirectory()); setButtonsEnabled(true); const auto& m = InstanceManager::singleton(); diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 5b08ffc2..94f379ca 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -6,9 +6,11 @@ namespace Ui { class InstanceManagerDialog; }; -class InstanceInfo; +class Instance; class PluginContainer; +// a dialog to manage existing instances +// class InstanceManagerDialog : public QDialog { Q_OBJECT @@ -19,21 +21,61 @@ public: ~InstanceManagerDialog(); + // selects the instance having the given index in the list + // void select(std::size_t i); + + // selects the instance by name + // void select(const QString& name); + + // select the instance that is currently in use in MO + // void selectActiveInstance(); + + // switches to the selected instance; restarts MO, unless + // was called setRestartOnSelect(false) + // void openSelectedInstance(); + + // renames the currently selected instance + // void rename(); + + // explores the directory of the selected instance + // void exploreLocation(); + + // explores the base directory of the selected instance + // void exploreBaseDirectory(); + + // explores the game directory of the selected instance + // void exploreGame(); + + // converts the selected, portable instance to a global one; not implemented + // void convertToGlobal(); + + // converts the selected, global instance to a portable one; not implemented + // void convertToPortable(); + + // opens the ini of the selected instance in the shell + // void openINI(); + + // deletes the selected instance + // void deleteInstance(); + + // sets whether the dialog should restart MO when selecting an instance; this + // is false on startup when no instances exist + // void setRestartOnSelect(bool b); private: @@ -41,27 +83,51 @@ private: std::unique_ptr ui; const PluginContainer& m_pc; - std::vector> m_instances; + std::vector> m_instances; MOBase::FilterWidget m_filter; QStandardItemModel* m_model; bool m_restartOnSelect; + // refreshes the list instances from disk + // void updateInstances(); + // updates the ui for the selected instance + // void onSelection(); + + // opens the create instance dialog + // void createNew(); + + // returns the index of selected instance, NoSelection if none + // std::size_t singleSelectionIndex() const; - InstanceInfo* singleSelection(); - const InstanceInfo* singleSelection() const; + // returns the InstanceInfo associated with the selected instance, null if + // none + // + const Instance* singleSelection() const; + + // fills the instance list on the ui + // void updateList(); - void fillData(const InstanceInfo& ii); + + // fills the ui for the selected instance + // + void fillData(const Instance& ii); + + // clears the ui when there's no selection + // void clearData(); + + // enables/disables buttons like rename, explore... + // void setButtonsEnabled(bool b); - bool deletePortable(const InstanceInfo& ii); - bool deleteGlobal(const InstanceInfo& ii); + // deletes the given files, returns false on error + // bool doDelete(const QStringList& files, bool recycle); }; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 410b58c1..45b99e21 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -330,7 +330,7 @@ - Delete instance + Delete instance... diff --git a/src/main.cpp b/src/main.cpp index 7f0886f6..a2653685 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -662,17 +662,17 @@ int doOneRun( } else { - if (!currentInstance->directory().exists()) { + if (!QDir(currentInstance->directory()).exists()) { // the previously used instance doesn't exist anymore if (m.hasAnyInstances()) { criticalOnTop(QObject::tr( "Instance at '%1' not found. Select another instance.") - .arg(currentInstance->directory().absolutePath())); + .arg(currentInstance->directory())); } else { criticalOnTop(QObject::tr( "Instance at '%1' not found. You must create a new instance") - .arg(currentInstance->directory().absolutePath())); + .arg(currentInstance->directory())); } currentInstance = selectInstance(); @@ -682,7 +682,7 @@ int doOneRun( } } - const QString dataPath = currentInstance->directory().path(); + const QString dataPath = currentInstance->directory(); application.setProperty("dataPath", dataPath); setExceptionHandlers(); -- cgit v1.3.1 From bc157095a56596e697d40371688926953b2c5533 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 14:43:33 -0500 Subject: moved getInstanceName() up removed individual getters in CreateInstanceDialog, they were duplicating the work in creationInfo() only VariantsPage needs access to the game plugin during creationInfo(), so pass it instead selecting an undetected game, choosing a valid path and going back to the games page would not display the selected path in the list --- src/createinstancedialog.cpp | 65 +++++--------- src/createinstancedialog.h | 99 ++++++++++++++++++---- src/createinstancedialogpages.cpp | 76 +++++++++-------- src/createinstancedialogpages.h | 4 +- src/instancemanagerdialog.cpp | 172 +++++++++++++++++++++----------------- 5 files changed, 243 insertions(+), 173 deletions(-) (limited to 'src/instancemanagerdialog.cpp') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 13dd200c..c2cdde49 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -413,49 +413,6 @@ bool CreateInstanceDialog::canBack() const return false; } -CreateInstanceDialog::Types CreateInstanceDialog::instanceType() const -{ - return getSelected(&cid::Page::selectedInstanceType); -} - -MOBase::IPluginGame* CreateInstanceDialog::game() const -{ - return getSelected(&cid::Page::selectedGame); -} - -QString CreateInstanceDialog::gameLocation() const -{ - return getSelected(&cid::Page::selectedGameLocation); -} - -QString CreateInstanceDialog::gameVariant() const -{ - return getSelected(&cid::Page::selectedGameVariant); -} - -QString CreateInstanceDialog::instanceName() const -{ - return getSelected(&cid::Page::selectedInstanceName); -} - -QString CreateInstanceDialog::dataPath() const -{ - QString s; - - if (instanceType() == Portable) { - s = QDir(InstanceManager::singleton().portablePath()).absolutePath(); - } else { - s = InstanceManager::singleton().instancePath(instanceName()); - } - - return QDir::toNativeSeparators(s); -} - -CreateInstanceDialog::Paths CreateInstanceDialog::paths() const -{ - return getSelected(&cid::Page::selectedPaths); -} - bool CreateInstanceDialog::switching() const { return m_switching; @@ -482,7 +439,8 @@ void fixFilePath(QString& path) path = QDir::toNativeSeparators(QFileInfo(path).absolutePath()); } -CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const +CreateInstanceDialog::CreationInfo +CreateInstanceDialog::rawCreationInfo() const { const auto iniFilename = QString::fromStdWString(AppConfig::iniFileName()); @@ -491,12 +449,27 @@ CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const ci.type = getSelected(&cid::Page::selectedInstanceType); ci.game = getSelected(&cid::Page::selectedGame); ci.gameLocation = getSelected(&cid::Page::selectedGameLocation); - ci.gameVariant = getSelected(&cid::Page::selectedGameVariant); + ci.gameVariant = getSelected(&cid::Page::selectedGameVariant, ci.game); ci.instanceName = getSelected(&cid::Page::selectedInstanceName); ci.paths = getSelected(&cid::Page::selectedPaths); - ci.dataPath = dataPath(); + + if (ci.type == Portable) { + ci.dataPath = QDir(InstanceManager::singleton().portablePath()).absolutePath(); + } else { + ci.dataPath = InstanceManager::singleton().instancePath(ci.instanceName); + } + + ci.dataPath = QDir::toNativeSeparators(ci.dataPath); ci.iniPath = ci.dataPath + "/" + iniFilename; + return ci; +} + +CreateInstanceDialog::CreationInfo +CreateInstanceDialog::creationInfo() const +{ + auto ci = rawCreationInfo(); + fixDirPath(ci.paths.base); fixFilePath(ci.paths.ini); diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 25e383eb..d541f45e 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -10,11 +10,25 @@ namespace cid { class Page; } class PluginContainer; class Settings; +// this is a wizard for creating a new instance, it is made out of Page objects, +// see createinstancedialogpages.h +// +// each page can give back one or more pieces of information that is collected +// in creationInfo() and used by finish() to do the actual creation +// +// pages can be disabled if they return true in skip(), which happens globally +// for some (IntroPage has a setting in the registry), depending on context +// (NexusPage is skipped if the API key already exists) or explicitly (when +// only some info about the instance is missing on startup, such as a game +// variant) +// class CreateInstanceDialog : public QDialog { Q_OBJECT public: + // instance type + // enum Types { NoType = 0, @@ -22,6 +36,10 @@ public: Portable }; + // all the paths required by the instance, some may be empty, such as + // basically all of them except for `base` when the user doesn't use the + // "Advanced" part of the paths page + // struct Paths { QString base; @@ -34,6 +52,8 @@ public: auto operator<=>(const Paths&) const = default; }; + // all the info filled in the various pages + // struct CreationInfo { Types type; @@ -53,10 +73,11 @@ public: ~CreateInstanceDialog(); Ui::CreateInstanceDialog* getUI(); - const PluginContainer& pluginContainer(); Settings* settings(); + // disables all the pages except for the given one, used on startup when some + // specific info is missing template void setSinglePage(const QString& instanceName) { @@ -71,6 +92,8 @@ public: setSinglePageImpl(instanceName); } + // returns the page having the give path, or null + // template Page* getPage() { @@ -83,24 +106,59 @@ public: return nullptr; } + + // moves to the next page calls finish() if on the last one + // void next(); + + // moves to the previous page, if any + // void back(); + + // whether the current page reports that it is ready; if this is the last + // page, next() would call finish() + // + bool canNext() const; + + // whether the current page is not the first one and there is an enabled page + // prior + // + bool canBack() const; + + // selects the given page by index; this doesn't check if the page should be + // skipped + // void selectPage(std::size_t i); + + // moves by `d` pages, can be negative to move back + // void changePage(int d); + + // creates the instance and closes the dialog + // void finish(); + + // updates the navigation buttons based on the current page + // void updateNavigation(); + + // whether this is the last enabled page + // bool isOnLastPage() const; - Types instanceType() const; - MOBase::IPluginGame* game() const; - QString gameLocation() const; - QString gameVariant() const; - QString instanceName() const; - QString dataPath() const; - Paths paths() const; + // returns whether the user has requested to switch to the new instance + // bool switching() const; + // gathers the info from all the pages as it appears, paths are not fixed; + // see creationInfo() + // + CreationInfo rawCreationInfo() const; + + // gathers the info from all the pages: paths are converted to absolute and + // the base dir variable is expanded everywhere; see rawCreationInfo() + // CreationInfo creationInfo() const; private: @@ -113,13 +171,26 @@ private: bool m_singlePage; + // called from setSinglePage(), does whatever doesn't need the T + // void setSinglePageImpl(const QString& instanceName); - template - T getSelected(T (cid::Page::*mf)() const) const + // adds a line to the creation log + // + void logCreation(const QString& s); + void logCreation(const std::wstring& s); + + // calls the given member function on all pages until one returns an object + // that's not empty; used by gatherInfo() + // + template + auto getSelected(MF mf, Args&&... args) const { + // return type + using T = decltype((std::declval().*mf)(std::forward(args)...)); + for (auto&& p : m_pages) { - const auto t = (p.get()->*mf)(); + const auto t = (p.get()->*mf)(std::forward(args)...); if (t != T()) { return t; } @@ -127,12 +198,6 @@ private: return T(); } - - void logCreation(const QString& s); - void logCreation(const std::wstring& s); - - bool canNext() const; - bool canBack() const; }; #endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index f2a7ab36..a08bf5a1 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -101,7 +101,7 @@ QString Page::selectedGameLocation() const return {}; } -QString Page::selectedGameVariant() const +QString Page::selectedGameVariant(MOBase::IPluginGame*) const { // no-op return {}; @@ -246,6 +246,12 @@ void GamePage::select(IPluginGame* game, const QString& dir) checked = nullptr; } else { checked = checkInstallation(path, checked); + + if (checked) { + // remember this path + checked->dir = path; + checked->installed = true; + } } } else { checked->dir = dir; @@ -255,7 +261,9 @@ void GamePage::select(IPluginGame* game, const QString& dir) } m_selection = checked; + selectButton(checked); + updateButton(checked); updateNavigation(); if (checked) { @@ -611,7 +619,7 @@ bool VariantsPage::ready() const bool VariantsPage::doSkip() const { - auto* g = m_dlg.game(); + auto* g = m_dlg.rawCreationInfo().game; if (!g) { // shouldn't happen return true; @@ -623,7 +631,7 @@ bool VariantsPage::doSkip() const void VariantsPage::activated() { - auto* g = m_dlg.game(); + auto* g = m_dlg.rawCreationInfo().game; if (m_previousGame != g) { m_previousGame = g; @@ -650,15 +658,13 @@ void VariantsPage::select(const QString& variant) } } -QString VariantsPage::selectedGameVariant() const +QString VariantsPage::selectedGameVariant(MOBase::IPluginGame* game) const { - auto* g = m_dlg.game(); - if (!g) { - // shouldn't happen + if (!game) { return {}; } - const auto variants = g->gameVariants(); + const auto variants = game->gameVariants(); if (variants.size() < 2) { return {}; } else { @@ -671,7 +677,7 @@ void VariantsPage::fillList() ui->editions->clear(); m_buttons.clear(); - auto* g = m_dlg.game(); + auto* g = m_dlg.rawCreationInfo().game; if (!g) { // shouldn't happen return; @@ -708,12 +714,12 @@ bool NamePage::ready() const bool NamePage::doSkip() const { - return (m_dlg.instanceType() == CreateInstanceDialog::Portable); + return (m_dlg.rawCreationInfo().type == CreateInstanceDialog::Portable); } void NamePage::activated() { - auto* g = m_dlg.game(); + auto* g = m_dlg.rawCreationInfo().game; if (!g) { // shouldn't happen, next should be disabled return; @@ -820,8 +826,8 @@ bool PathsPage::ready() const void PathsPage::activated() { - const auto name = m_dlg.instanceName(); - const auto type = m_dlg.instanceType(); + const auto name = m_dlg.rawCreationInfo().instanceName; + const auto type = m_dlg.rawCreationInfo().type; const bool changed = (m_lastInstanceName != name) || (m_lastType != type); @@ -829,7 +835,7 @@ void PathsPage::activated() checkPaths(); updateNavigation(); - m_label.setText(m_dlg.game()->gameName()); + m_label.setText(m_dlg.rawCreationInfo().game->gameName()); m_lastInstanceName = name; m_lastType = type; } @@ -898,7 +904,7 @@ void PathsPage::setPaths(const QString& name, bool force) { QString path; - if (m_dlg.instanceType() == CreateInstanceDialog::Portable) { + if (m_dlg.rawCreationInfo().type == CreateInstanceDialog::Portable) { path = InstanceManager::singleton().portablePath(); } else { const auto root = InstanceManager::singleton().globalInstancesRootPath(); @@ -939,7 +945,7 @@ bool PathsPage::checkPath( const QDir d(path); if (InstanceManager::singleton().validInstanceName(d.dirName())) { - if (m_dlg.instanceType() == CreateInstanceDialog::Portable) { + if (m_dlg.rawCreationInfo().type == CreateInstanceDialog::Portable) { // the default data path for a portable instance is the application // directory, so it's not an error if it exists if (QDir(path) != InstanceManager::singleton().portablePath()) { @@ -1041,37 +1047,41 @@ QString ConfirmationPage::toLocalizedString(CreateInstanceDialog::Types t) const QString ConfirmationPage::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())); + const auto ci = m_dlg.rawCreationInfo(); + + lines.push_back(QObject::tr("Instance type: %1") + .arg(toLocalizedString(ci.type))); + + lines.push_back(QObject::tr("Instance location: %1") + .arg(ci.dataPath)); - if (m_dlg.instanceType() != CreateInstanceDialog::Portable) { - lines.push_back(QObject::tr("Instance name: %1").arg(m_dlg.instanceName())); + if (ci.type != CreateInstanceDialog::Portable) { + lines.push_back(QObject::tr("Instance name: %1").arg(ci.instanceName)); } - if (paths.downloads.isEmpty()) { + if (ci.paths.downloads.isEmpty()) { // simple settings - if (paths.base != m_dlg.dataPath()) { - lines.push_back(QObject::tr("Base directory: %1").arg(paths.base)); + if (ci.paths.base != ci.dataPath) { + lines.push_back(QObject::tr("Base directory: %1").arg(ci.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)); + lines.push_back(QObject::tr("Base directory: %1").arg(ci.paths.base)); + lines.push_back(dirLine(QObject::tr("Downloads"), ci.paths.downloads)); + lines.push_back(dirLine(QObject::tr("Mods"), ci.paths.mods)); + lines.push_back(dirLine(QObject::tr("Profiles"), ci.paths.profiles)); + lines.push_back(dirLine(QObject::tr("Overwrite"), ci.paths.overwrite)); } // game - QString name = m_dlg.game()->gameName(); - if (!m_dlg.gameVariant().isEmpty()) { - name += " (" + m_dlg.gameVariant() + ")"; + QString name = ci.game->gameName(); + if (!ci.gameVariant.isEmpty()) { + name += " (" + ci.gameVariant + ")"; } lines.push_back(QObject::tr("Game: %1").arg(name)); - lines.push_back(QObject::tr("Game location: %1").arg(m_dlg.gameLocation())); + lines.push_back(QObject::tr("Game location: %1").arg(ci.gameLocation)); return lines.join("\n"); } diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index e239c196..099071a2 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -48,7 +48,7 @@ public: virtual CreateInstanceDialog::Types selectedInstanceType() const; virtual MOBase::IPluginGame* selectedGame() const; virtual QString selectedGameLocation() const; - virtual QString selectedGameVariant() const; + virtual QString selectedGameVariant(MOBase::IPluginGame* game) const; virtual QString selectedInstanceName() const; virtual CreateInstanceDialog::Paths selectedPaths() const; @@ -146,7 +146,7 @@ public: bool ready() const override; void activated() override; - QString selectedGameVariant() const override; + QString selectedGameVariant(MOBase::IPluginGame* game) const override; void select(const QString& variant); diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index c199fa7b..71fed78d 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -13,7 +13,9 @@ using namespace MOBase; - +// returns the icon for the given instance or an empty 32x32 icon if the game +// plugin couldn't be found +// QIcon instanceIcon(const PluginContainer& pc, const Instance& i) { const auto* game = InstanceManager::singleton() @@ -27,6 +29,78 @@ QIcon instanceIcon(const PluginContainer& pc, const Instance& i) return QIcon(empty); } +// pops up a dialog to ask for an instance name when renaming +// +QString getInstanceName( + QWidget* parent, const QString& title, const QString& moreText, + const QString& label, const QString& oldName={}) +{ + auto& m = InstanceManager::singleton(); + + QDialog dlg(parent); + dlg.setWindowTitle(title); + + auto* ly = new QVBoxLayout(&dlg); + + auto* bb = new QDialogButtonBox( + QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + + auto* text = new QLineEdit(oldName); + text->selectAll(); + + auto* error = new QLabel; + + 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); + + auto check = [&] { + bool okay = false; + + 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 != oldName) && m.instanceExists(text->text())) { + error->setText(QObject::tr("An instance with this name already exists.")); + } else { + okay = true; + } + } + + 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(); }); + + check(); + + dlg.resize({400, 120}); + if (dlg.exec() != QDialog::Accepted) { + return {}; + } + + return m.sanitizeInstanceName(text->text()); +} + InstanceManagerDialog::~InstanceManagerDialog() = default; @@ -106,9 +180,9 @@ void InstanceManagerDialog::updateList() m_model->clear(); - const std::size_t NoSel = -1; - std::size_t sel = NoSel; + std::size_t sel = NoSelection; + // creating items for instances for (std::size_t i=0; i= m_instances.size()) { sel = m_instances.size() - 1; } else { @@ -207,76 +281,6 @@ void InstanceManagerDialog::openSelectedInstance() accept(); } -QString getInstanceName( - QWidget* parent, const QString& title, const QString& moreText, - const QString& label, const QString& oldName={}) -{ - auto& m = InstanceManager::singleton(); - - QDialog dlg(parent); - dlg.setWindowTitle(title); - - auto* ly = new QVBoxLayout(&dlg); - - auto* bb = new QDialogButtonBox( - QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - - auto* text = new QLineEdit(oldName); - text->selectAll(); - - auto* error = new QLabel; - - 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); - - auto check = [&] { - bool okay = false; - - 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 != oldName) && m.instanceExists(text->text())) { - error->setText(QObject::tr("An instance with this name already exists.")); - } else { - okay = true; - } - } - - 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(); }); - - check(); - - dlg.resize({400, 120}); - if (dlg.exec() != QDialog::Accepted) { - return {}; - } - - return m.sanitizeInstanceName(text->text()); -} - void InstanceManagerDialog::rename() { auto* i = singleSelection(); @@ -293,6 +297,8 @@ void InstanceManagerDialog::rename() return; } + + // getting new name const auto newName = getInstanceName( this, tr("Rename instance"), "", tr("Instance name"), i->name()); @@ -300,11 +306,16 @@ void InstanceManagerDialog::rename() return; } + + // renaming const QString src = i->directory(); const QString dest = QDir::toNativeSeparators( QFileInfo(src).dir().path() + "/" + newName); + log::info("renaming {} to {}", src, dest); + const auto r = shell::Rename(src, dest, false); + if (!r) { QMessageBox::critical( this, tr("Error"), @@ -314,6 +325,8 @@ void InstanceManagerDialog::rename() return; } + + // updating ui auto newInstance = std::make_unique(dest, false); i = newInstance.get(); @@ -365,6 +378,8 @@ void InstanceManagerDialog::deleteInstance() return; } + // creating dialog + const auto Recycle = QMessageBox::Save; const auto Delete = QMessageBox::Yes; const auto Cancel = QMessageBox::Cancel; @@ -388,6 +403,8 @@ void InstanceManagerDialog::deleteInstance() list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); list->setMaximumHeight(160); + + // filling the list for (const auto& f : files) { auto* item = new QListWidgetItem(f.path); @@ -412,6 +429,7 @@ void InstanceManagerDialog::deleteInstance() } + // gathering all the selected items QStringList selected; for (int i=0; icount(); ++i) { @@ -427,10 +445,14 @@ void InstanceManagerDialog::deleteInstance() return; } + + // deleting if (!doDelete(selected, (r == Recycle))) { return; } + + // updating ui updateInstances(); updateList(); } @@ -501,7 +523,7 @@ void InstanceManagerDialog::createNew() updateInstances(); updateList(); - select(dlg.instanceName()); + select(dlg.creationInfo().instanceName); } std::size_t InstanceManagerDialog::singleSelectionIndex() const -- cgit v1.3.1 From 0b8fbfdc57ad22c996fa88f6d974269afec004fd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 19:43:11 -0500 Subject: larger initial size for the instance dialog save geometry if settings are available --- src/instancemanagerdialog.cpp | 28 +++++++++++++++++++++++++++- src/instancemanagerdialog.h | 10 ++++++++++ src/instancemanagerdialog.ui | 4 ++-- src/settings.cpp | 8 +++++++- src/settings.h | 10 +++++++++- 5 files changed, 55 insertions(+), 5 deletions(-) (limited to 'src/instancemanagerdialog.cpp') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 71fed78d..d579ea9c 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -111,7 +111,7 @@ InstanceManagerDialog::InstanceManagerDialog( { ui->setupUi(this); - ui->splitter->setSizes({200, 1}); + ui->splitter->setSizes({250, 1}); ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); @@ -146,6 +146,32 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->close, &QPushButton::clicked, [&]{ close(); }); } +void InstanceManagerDialog::showEvent(QShowEvent* e) +{ + // there might not be a global Settings object if this is called on startup + // when there's no current instance + const auto* s = Settings::maybeInstance(); + + if (s) { + s->geometry().restoreGeometry(this); + } + + QDialog::showEvent(e); +} + +void InstanceManagerDialog::done(int r) +{ + // there might not be a global Settings object if this is called on startup + // when there's no current instance + auto* s = Settings::maybeInstance(); + + if (s) { + s->geometry().saveGeometry(this); + } + + QDialog::done(r); +} + void InstanceManagerDialog::updateInstances() { auto& m = InstanceManager::singleton(); diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 94f379ca..2641f716 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -78,6 +78,16 @@ public: // void setRestartOnSelect(bool b); + + // saves geometry + // + void done(int r) override; + +protected: + // restores geometry + // + void showEvent(QShowEvent* e) override; + private: static const std::size_t NoSelection = -1; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 45b99e21..4137d1b6 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -6,8 +6,8 @@ 0 0 - 689 - 413 + 884 + 539 diff --git a/src/settings.cpp b/src/settings.cpp index a8ada6ba..54d32786 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -86,14 +86,20 @@ Settings::~Settings() } } -Settings &Settings::instance() +Settings& Settings::instance() { if (s_Instance == nullptr) { throw std::runtime_error("no instance of \"Settings\""); } + return *s_Instance; } +Settings* Settings::maybeInstance() +{ + return s_Instance; +} + void Settings::processUpdates( const QVersionNumber& currentVersion, const QVersionNumber& lastVersion) { diff --git a/src/settings.h b/src/settings.h index df081c5c..689067d7 100644 --- a/src/settings.h +++ b/src/settings.h @@ -693,7 +693,15 @@ public: Settings(const QString& path, bool globalInstance=false); ~Settings(); - static Settings &instance(); + + // throws if there is no global Settings instance + // + static Settings& instance(); + + // returns null if there is no global Settings instance + // + static Settings* maybeInstance(); + // name of the ini file // -- cgit v1.3.1 From f9feb5fc5b4dc2834827862131035c2c8894f879 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 20:09:16 -0500 Subject: show a confirmation before switching instances --- src/instancemanagerdialog.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- src/instancemanagerdialog.h | 5 ++++- 2 files changed, 44 insertions(+), 3 deletions(-) (limited to 'src/instancemanagerdialog.cpp') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index d579ea9c..b461d39d 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -294,10 +294,16 @@ void InstanceManagerDialog::openSelectedInstance() return; } - if (m_instances[i]->isPortable()) { + const auto& to = *m_instances[i]; + + if (!confirmSwitch(to)) { + return; + } + + if (to.isPortable()) { InstanceManager::singleton().setCurrentInstance(""); } else { - InstanceManager::singleton().setCurrentInstance(m_instances[i]->name()); + InstanceManager::singleton().setCurrentInstance(to.name()); } if (m_restartOnSelect) { @@ -307,6 +313,38 @@ void InstanceManagerDialog::openSelectedInstance() accept(); } +bool InstanceManagerDialog::confirmSwitch(const Instance& to) +{ + // there might not be a global Settings object if this is called on startup + // when there's no current instance + const auto* s = Settings::maybeInstance(); + + // if there is are no settings, no instances are loaded and the confirmation + // wouldn't make sense + if (!s) { + return true; + } + + if (!s->interface().showChangeGameConfirmation()) { + // user disabled confirmation + return true; + } + + MOBase::TaskDialog dlg(this); + + const auto r = dlg + .title(tr("Switching instances")) + .main(tr("Mod Organizer must restart to manage the instance '%1'.") + .arg(to.name())) + .content(tr("This confirmation can be disabled in the settings.")) + .icon(QMessageBox::Question) + .button({tr("Restart Mod Organizer"), QMessageBox::Ok}) + .button({tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + return (r == QMessageBox::Ok); +} + void InstanceManagerDialog::rename() { auto* i = singleSelection(); diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 2641f716..86fe0dac 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -38,7 +38,6 @@ public: // void openSelectedInstance(); - // renames the currently selected instance // void rename(); @@ -110,6 +109,10 @@ private: // void createNew(); + // shows a confirmation to the user before switching + // + bool confirmSwitch(const Instance& to); + // returns the index of selected instance, NoSelection if none // -- cgit v1.3.1