diff options
| -rw-r--r-- | src/createinstancedialog.cpp | 65 | ||||
| -rw-r--r-- | src/createinstancedialog.h | 99 | ||||
| -rw-r--r-- | src/createinstancedialogpages.cpp | 76 | ||||
| -rw-r--r-- | src/createinstancedialogpages.h | 4 | ||||
| -rw-r--r-- | src/instancemanagerdialog.cpp | 172 |
5 files changed, 243 insertions, 173 deletions
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 <class Page> void setSinglePage(const QString& instanceName) { @@ -71,6 +92,8 @@ public: setSinglePageImpl(instanceName); } + // returns the page having the give path, or null + // template <class Page> 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 <class T> - 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 <class MF, class... Args> + auto getSelected(MF mf, Args&&... args) const { + // return type + using T = decltype((std::declval<cid::Page>().*mf)(std::forward<Args>(args)...)); + for (auto&& p : m_pages) { - const auto t = (p.get()->*mf)(); + const auto t = (p.get()->*mf)(std::forward<Args>(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(); ++i) { const auto& ii = *m_instances[i]; @@ -128,7 +202,7 @@ void InstanceManagerDialog::updateList() if (m_instances.empty()) { select(-1); } else { - if (sel == NoSel) { + if (sel == NoSelection) { if (prevSelIndex >= 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<Instance>(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; i<list->count(); ++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 |
