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/createinstancedialogpages.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/createinstancedialogpages.cpp') diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 26f2f61d..f2a7ab36 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -136,11 +136,11 @@ TypePage::TypePage(CreateInstanceDialog& dlg) { ui->createGlobal->setDescription( ui->createGlobal->description() - .arg(InstanceManager::singleton().instancesPath())); + .arg(InstanceManager::singleton().globalInstancesRootPath())); ui->createPortable->setDescription( ui->createPortable->description() - .arg(InstanceManager::portablePath())); + .arg(InstanceManager::singleton().portablePath())); if (InstanceManager::singleton().portableInstanceExists()) { ui->createPortable->setEnabled(false); @@ -748,7 +748,7 @@ void NamePage::onChanged() void NamePage::updateWarnings() { - const auto root = InstanceManager::singleton().instancesPath(); + const auto root = InstanceManager::singleton().globalInstancesRootPath(); m_okay = checkName(root, ui->instanceName->text()); updateNavigation(); @@ -899,9 +899,9 @@ void PathsPage::setPaths(const QString& name, bool force) QString path; if (m_dlg.instanceType() == CreateInstanceDialog::Portable) { - path = InstanceManager::portablePath(); + path = InstanceManager::singleton().portablePath(); } else { - const auto root = InstanceManager::singleton().instancesPath(); + const auto root = InstanceManager::singleton().globalInstancesRootPath(); path = root + "/" + name; } -- 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/createinstancedialogpages.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 baa5c3abee4960046e4fe1e2240dd1e73253c43c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 16:13:06 -0500 Subject: documentation use an okay flag in PathsPage to avoid calling checkPaths() in ready() and make stuff mutable moved a few things around --- src/createinstancedialog.cpp | 10 ++ src/createinstancedialogpages.cpp | 173 +++++++++--------- src/createinstancedialogpages.h | 368 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 448 insertions(+), 103 deletions(-) (limited to 'src/createinstancedialogpages.cpp') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index f52d3ebd..1c1b62d0 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -13,6 +13,12 @@ using namespace MOBase; class Failed {}; +// create() will create all the directories in `target`; if any path component +// fails to create, it will throw Failed +// +// unless commit() is called, all the created directories will be deleted in +// the destructor +// class DirectoryCreator { public: @@ -39,6 +45,7 @@ public: { try { + // delete each directory starting from the end for (auto itor=m_created.rbegin(); itor!=m_created.rend(); ++itor) { const auto r = shell::DeleteDirectoryRecursive(*itor); if (!r) { @@ -62,6 +69,7 @@ private: { try { + // split on separators const QString s = QDir::toNativeSeparators(target.absolutePath()); const QStringList cs = s.split("\\"); @@ -69,8 +77,10 @@ private: return; } + // root directory QDir d(cs[0]); + // for each directory after the root for (int i=1; i GamePage::sortedGamePlugins() const return v; } +void GamePage::createGames() +{ + m_games.clear(); + + for (auto* game : sortedGamePlugins()) { + m_games.push_back(std::make_unique(game)); + } +} + GamePage::Game* GamePage::findGame(IPluginGame* game) { for (auto& g : m_games) { @@ -344,13 +368,24 @@ GamePage::Game* GamePage::findGame(IPluginGame* game) return nullptr; } -void GamePage::createGames() +void GamePage::createGameButton(Game* g) { - m_games.clear(); + g->button = new QCommandLinkButton; + g->button->setCheckable(true); - for (auto* game : sortedGamePlugins()) { - m_games.push_back(std::make_unique(game)); - } + updateButton(g); + + QObject::connect(g->button, &QAbstractButton::clicked, [g, this] { + select(g->game); + }); +} + +void GamePage::addButton(QAbstractButton* b) +{ + auto* ly = static_cast(ui->games->layout()); + + // insert before the stretch + ly->insertWidget(ly->count() - 1, b); } void GamePage::updateButton(Game* g) @@ -407,6 +442,26 @@ void GamePage::selectButton(Game* g) } } +void GamePage::clearButtons() +{ + auto* ly = static_cast(ui->games->layout()); + + ui->games->setUpdatesEnabled(false); + + // delete all children + qDeleteAll(ui->games->findChildren("", Qt::FindDirectChildrenOnly)); + + // stretch widgets added with addStretch() are not in the parent widget, + // they have to be deleted from the layout itself + while (auto* child=ly->takeAt(0)) + delete child; + + // add a stretch, buttons will be added before + ly->addStretch(); + + ui->games->setUpdatesEnabled(true); +} + QCommandLinkButton* GamePage::createCustomButton() { auto* b = new QCommandLinkButton; @@ -422,18 +477,6 @@ QCommandLinkButton* GamePage::createCustomButton() return b; } -void GamePage::createGameButton(Game* g) -{ - g->button = new QCommandLinkButton; - g->button->setCheckable(true); - - updateButton(g); - - QObject::connect(g->button, &QAbstractButton::clicked, [g, this] { - select(g->game); - }); -} - void GamePage::fillList() { const bool showAll = ui->showAllGames->isChecked(); @@ -459,34 +502,6 @@ void GamePage::fillList() addButton(createCustomButton()); } -void GamePage::clearButtons() -{ - auto* ly = static_cast(ui->games->layout()); - - ui->games->setUpdatesEnabled(false); - - // delete all children - qDeleteAll(ui->games->findChildren("", Qt::FindDirectChildrenOnly)); - - // stretch widgets added with addStretch() are not in the parent widget, - // they have to be deleted from the layout itself - while (auto* child=ly->takeAt(0)) - delete child; - - // add a stretch, buttons will be added before - ly->addStretch(); - - ui->games->setUpdatesEnabled(true); -} - -void GamePage::addButton(QAbstractButton* b) -{ - auto* ly = static_cast(ui->games->layout()); - - // insert before the stretch - ly->insertWidget(ly->count() - 1, b); -} - GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g) { if (g->game->looksValid(path)) { @@ -495,7 +510,15 @@ GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g) } // the selected game can't use that folder, find another one - auto* otherGame = findAnotherGame(path); + IPluginGame* otherGame = nullptr; + + for (auto* gg : m_pc.plugins()) { + if (gg->looksValid(path)) { + otherGame = gg; + break; + } + } + if (otherGame == g->game) { // shouldn't happen, but okay return g; @@ -531,17 +554,6 @@ GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g) return g; } -IPluginGame* GamePage::findAnotherGame(const QString& path) -{ - for (auto* otherGame : m_pc.plugins()) { - if (otherGame->looksValid(path)) { - return otherGame; - } - } - - return nullptr; -} - bool GamePage::confirmUnknown(const QString& path, IPluginGame* game) { const auto r = TaskDialog(&m_dlg) @@ -733,7 +745,7 @@ void NamePage::activated() m_modified = false; } - updateWarnings(); + verify(); } QString NamePage::selectedInstanceName() const @@ -749,13 +761,12 @@ QString NamePage::selectedInstanceName() const void NamePage::onChanged() { m_modified = true; - updateWarnings(); + verify(); } -void NamePage::updateWarnings() +void NamePage::verify() { const auto root = InstanceManager::singleton().globalInstancesRootPath(); - m_okay = checkName(root, ui->instanceName->text()); updateNavigation(); } @@ -803,7 +814,8 @@ PathsPage::PathsPage(CreateInstanceDialog& dlg) : m_label(ui->pathsLabel), m_simpleExists(ui->locationExists), m_simpleInvalid(ui->locationInvalid), m_advancedExists(ui->advancedDirExists), - m_advancedInvalid(ui->advancedDirInvalid) + m_advancedInvalid(ui->advancedDirInvalid), + m_okay(false) { QObject::connect(ui->location, &QLineEdit::textEdited, [&]{ onChanged(); }); QObject::connect(ui->base, &QLineEdit::textEdited, [&]{ onChanged(); }); @@ -821,7 +833,7 @@ PathsPage::PathsPage(CreateInstanceDialog& dlg) : bool PathsPage::ready() const { - return checkPaths(); + return m_okay; } void PathsPage::activated() @@ -863,21 +875,27 @@ void PathsPage::onChanged() updateNavigation(); } -bool PathsPage::checkPaths() const +void PathsPage::checkPaths() { if (ui->advancedPathOptions->isChecked()) { - return + m_okay = checkAdvancedPath(ui->base->text()) && checkAdvancedPath(resolve(ui->downloads->text())) && checkAdvancedPath(resolve(ui->mods->text())) && checkAdvancedPath(resolve(ui->profiles->text())) && checkAdvancedPath(resolve(ui->overwrite->text())); } else { - return checkPath(ui->location->text(), m_simpleExists, m_simpleInvalid); + m_okay = + checkSimplePath(ui->location->text()); } } -bool PathsPage::checkAdvancedPath(const QString& path) const +bool PathsPage::checkSimplePath(const QString& path) +{ + return checkPath(path, m_simpleExists, m_simpleInvalid); +} + +bool PathsPage::checkAdvancedPath(const QString& path) { return checkPath(path, m_advancedExists, m_advancedInvalid); } @@ -931,7 +949,7 @@ void PathsPage::setIfEmpty(QLineEdit* e, const QString& path, bool force) bool PathsPage::checkPath( QString path, - PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel) const + PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel) { bool exists = false; bool invalid = false; @@ -1013,10 +1031,6 @@ bool NexusPage::doSkip() const return m_skip; } -void NexusPage::activated() -{ -} - ConfirmationPage::ConfirmationPage(CreateInstanceDialog& dlg) : Page(dlg) @@ -1029,21 +1043,6 @@ void ConfirmationPage::activated() ui->creationLog->clear(); } -QString ConfirmationPage::toLocalizedString(CreateInstanceDialog::Types t) const -{ - switch (t) - { - case CreateInstanceDialog::Global: - return QObject::tr("Global"); - - case CreateInstanceDialog::Portable: - return QObject::tr("Portable"); - - default: - return QObject::tr("Instance type: %1").arg(QObject::tr("?")); - } -} - QString ConfirmationPage::makeReview() const { QStringList lines; diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 099071a2..e2eaf0fb 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -15,14 +15,26 @@ class NexusConnectionUI; namespace cid { +// returns "%base_dir%/dir" +// QString makeDefaultPath(const std::wstring& dir); +// remembers the original text of the given label and, if it contains a %1, +// sets it in setText() +// class PlaceholderLabel { public: PlaceholderLabel(QLabel* label); + + // if the original label text contained a %1, replaces it by the arg and + // sets that as the new label text + // void setText(const QString& arg); + + // whether the label is visible + // void setVisible(bool b); private: @@ -31,25 +43,67 @@ private: }; +// one page in the wizard +// +// each page can implement one or more selected*() below; those are called +// by CreateInstanceDialog to gather data from all pages +// class Page { public: Page(CreateInstanceDialog& dlg); + // whether this page has been filled and is valid; used by the dialog to + // determine if it can move to the next page + // virtual bool ready() const; + + // called every time a page is shown in the screen + // virtual void activated(); + // overrides whether this page should be skipped; this is used by + // CreateInstanceDialog::setSinglePage() to disable all other pages + // void setSkip(bool b); + + // whether this page should be skipped + // bool skip() const; + // asks the dialog to update its navigation buttons, typically used when a + // page changes its ready state without moving to a different page + // void updateNavigation(); + + // asks the dialog to move to the next page; some pages will automatically + // advance once the user has made the proper selection + // void next(); + + // returns the instance type + // virtual CreateInstanceDialog::Types selectedInstanceType() const; + + // returns the game plugin + // virtual MOBase::IPluginGame* selectedGame() const; + + // returns the game directory + // virtual QString selectedGameLocation() const; + + // returns the game variant + // virtual QString selectedGameVariant(MOBase::IPluginGame* game) const; + + // returns the instance name + // virtual QString selectedInstanceName() const; + + // returns the various paths + // virtual CreateInstanceDialog::Paths selectedPaths() const; protected: @@ -58,10 +112,15 @@ protected: const PluginContainer& m_pc; bool m_skip; + + // implemented by derived classes, overridden by setSkip(true) + // virtual bool doSkip() const; }; +// introduction page, can be disabled by a global setting +// class IntroPage : public Page { public: @@ -72,15 +131,27 @@ protected: }; +// instance type page +// class TypePage : public Page { public: TypePage(CreateInstanceDialog& dlg); + // whether a type has been been selected + // bool ready() const override; + + // returns the selected type + // CreateInstanceDialog::Types selectedInstanceType() const override; + // selects a global instance + // void global(); + + // selects a portable instance + // void portable(); private: @@ -88,160 +159,425 @@ private: }; +// game plugin page, displays a list of command buttons for each game, along +// with a "browse" button for custom directories and filtering stuff +// +// the game list initially only shows plugins that report isInstalled(), and the +// user has two ways of specifying paths for games that were not found: +// +// 1) by clicking the "Browse..." button and selecting an arbitrary directory +// +// all plugins are checked until one returns true for looksValid(); if none +// of them do, this is an error +// +// 2) by checking the "Show all supported games" checkbox and clicking one +// of the games on the list +// +// if the selected plugin doesn't recognize the directory, the user is +// warned, but is allowed to continue; there's also some logic to try to +// find another plugin that can manage this directory and suggest it +// instead +// class GamePage : public Page { public: GamePage(CreateInstanceDialog& dlg); + // whether a game has been selected + // bool ready() const override; + + // returns the selected game + // MOBase::IPluginGame* selectedGame() const override; + + // returns the selected game directory QString selectedGameLocation() const override; + + // selects the given game and toggles its associated button; the game + // directory can be overridden + // + // pops up a directory selection dialog if `dir` is empty and the plugin + // hasn't detected the game + // void select(MOBase::IPluginGame* game, const QString& dir={}); + + // pops up a directory selection dialog and looks for a plugin to manage + // it + // void selectCustom(); + // pops up a warning dialog that the game at the given path is not supported + // by any plugin, includes a list of all game plugins in the details section + // of the dialog + // void warnUnrecognized(const QString& path); private: + // a single game, with its button and custom directory, if any + // struct Game { + // game plugin MOBase::IPluginGame* game = nullptr; + + // button on the ui QCommandLinkButton* button = nullptr; + + // game directory; set in ctor if the plugin has detected the game, or + // set later when the user selects a directory QString dir; + + // whether a directory has been set for this game, either auto detected + // or by the user bool installed = false; + Game(MOBase::IPluginGame* g); Game(const Game&) = delete; Game& operator=(const Game&) = delete; }; + // list of all game plugins, even if they're not installed; those are filtered + // from the ui if the checkbox isn't checked std::vector> m_games; + + // current selection Game* m_selection; + + // filter MOBase::FilterWidget m_filter; + + // returns a list of all the game plugins sorted with natsort + // std::vector sortedGamePlugins() const; - Game* findGame(MOBase::IPluginGame* game); + + // creates the m_games list + // void createGames(); + + // finds the game struct associated with the given game + // + Game* findGame(MOBase::IPluginGame* game); + + + // creates the ui for the given game button + // + void createGameButton(Game* g); + + // adds the given button to the ui + // + void addButton(QAbstractButton* b); + + // updates the given button on the ui, sets the text, icon, etc. + // void updateButton(Game* g); + + // called when a button has been clicked; selects the game or asks the user + // for directory, depending + // void selectButton(Game* g); + + // removes all buttons from the ui + // void clearButtons(); - void addButton(QAbstractButton* b); + + // creates the "Browse" button + // QCommandLinkButton* createCustomButton(); - void createGameButton(Game* g); + + + // clears the button list and adds all the buttons to it, depending on + // filtering and stuff + // void fillList(); - void onFilter(); + + // checks whether the given path looks valid to the given game plugin + // + // if the plugin doesn't like the path, allows the user to override and + // accept, but also attempts to find another plugin that wants it and + // propose that as an alternative, if there's one + // + // returns: + // - if the user selects the alternative plugin, returns that plugin + // instead; + // - if the path is bad but the user overrides, returns the given plugin + // - if the user cancels or if no plugins can manage the directory, returns + // null + // Game* checkInstallation(const QString& path, Game* g); - MOBase::IPluginGame* findAnotherGame(const QString& path); + + // tells the user that the path cannot be handled by any game plugin, returns + // true if the user decides to accept anyway + // bool confirmUnknown(const QString& path, MOBase::IPluginGame* game); + + // tells the user that the path can be handled by a different plugin than the + // selected one and allows them to either + // 1) use the alternative, guessedGame is returned; + // 2) use the selection anyway, selectedGame is returned; or + // 3) cancel, null is returned + // MOBase::IPluginGame* confirmOtherGame( const QString& path, MOBase::IPluginGame* selectedGame, MOBase::IPluginGame* guessedGame); }; +// game variants page; displays a list of command buttons for game variants, as +// reported by the game plugin +// +// this page is always skipped if the game plugin reports no variants +// class VariantsPage : public Page { public: VariantsPage(CreateInstanceDialog& dlg); + // whether a variant has been selected or the game plugin reports no variants + // bool ready() const override; + + // uses the game selected in the previous page to fill the list, this must be + // called every time because the user may go back in forth in the wizard + // void activated() override; + + // returns the selected variant, if any + // QString selectedGameVariant(MOBase::IPluginGame* game) const override; + // selects the given variant + // void select(const QString& variant); protected: + // returns true if the game has no variants + // bool doSkip() const override; private: + // game that was selected the last time this page was active MOBase::IPluginGame* m_previousGame; + + // buttons std::vector m_buttons; + + // selected variant QString m_selection; + + // fills the list with buttons void fillList(); }; +// instance name page; displays a textbox where the user can enter a name and +// does basic checks to make sure the name is valid and not a duplicate +// +// skipped for portable instances +// class NamePage : public Page { public: NamePage(CreateInstanceDialog& dlg); + // whether a valid name has been entered + // bool ready() const override; + + // uses the selected game to generate an instance name + // + // as long as the user hasn't modified the textbox, this will regenerate a new + // instance name every time the selected game changes + // void activated() override; + + // returns the instance name + // QString selectedInstanceName() const override; protected: + // returns true for portable instances + // bool doSkip() const override; private: - mutable PlaceholderLabel m_label, m_exists, m_invalid; + // game label, replaces %1 with the game name + PlaceholderLabel m_label; + + // "instance already exists" label, replaces %1 with instance name + PlaceholderLabel m_exists; + + // "instance name invalid" label, replaces %1 with instance name + PlaceholderLabel m_invalid; + + // whether the user has modified the text, prevents auto generation when the + // selected game changes bool m_modified; + + // whether the instance name is valid bool m_okay; + + // called when the user modifies the textbox, remember that it has changed and + // calls verify() + // void onChanged(); - void updateWarnings(); + + // check if the entered name is valid, sets m_okay and calls checkName() + // + void verify(); + + // updates the ui depending on whether the given instance name is valid in + // the given directory; returns false if the name is invalid + // bool checkName(QString parentDir, QString name); }; +// instance paths page; shows a single textbox for the base directory, or a +// series of textboxes for all the configurable paths if the advanced checkbox +// is checked +// class PathsPage : public Page { public: PathsPage(CreateInstanceDialog& dlg); + // whether all paths make sense + // bool ready() const override; + + // resets all the paths if the instance type or instance name have changed, + // the current values are kept as long as these don't change; also updates the + // game name in the ui + // void activated() override; + // returns the selected paths + // CreateInstanceDialog::Paths selectedPaths() const override; private: + // instance name the last time this page was active QString m_lastInstanceName; + + // instance type the last time this page was active CreateInstanceDialog::Types m_lastType; + + // help label, replaces %1 by the game name PlaceholderLabel m_label; - mutable PlaceholderLabel m_simpleExists, m_simpleInvalid; - mutable PlaceholderLabel m_advancedExists, m_advancedInvalid; + // path exists/is invalid labels for the simple page, replaces %1 with the + // path + PlaceholderLabel m_simpleExists, m_simpleInvalid; + + // path exists/is invalid labels for the advanced page, replaces %1 with the + // path + PlaceholderLabel m_advancedExists, m_advancedInvalid; + + // whether the paths are valid + bool m_okay; + + + // called when the user changes any textbox, checks the path and updates nav + // void onChanged(); - bool checkPaths() const; - bool checkAdvancedPath(const QString& path) const; + + // checks the simple or advanced paths, sets m_okay + // + void checkPaths(); + + // checks a simple path, forwards to checkPath() with the simple labels + // + bool checkSimplePath(const QString& path); + + // checks an advanced path, forwards to checkPath() with the advanced labels + // + bool checkAdvancedPath(const QString& path); + + // returns false if the path is invalid or already exists, sets the given + // labels accordingly + // + bool checkPath( + QString path, + PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel); + + // replaces %base_dir% in the given path by whatever's in the base path + // textbox + // QString resolve(const QString& path) const; + + // called when the advanced checkbox is toggled, switches the active page + // and checks the paths + // void onAdvanced(); + + // called whenever the page becomes active + // + // this normally doesn't change the textboxes unless they're empty, but if the + // instance name or type have changed, `force` is true, which forces all paths + // to reset + // void setPaths(const QString& name, bool force); + + // sets the given textbox to the path if it's empty or if `force` is true + // void setIfEmpty(QLineEdit* e, const QString& path, bool force); - bool checkPath( - QString path, - PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel) const; }; +// nexus connection page; this reuses the ui found in the settings dialog and +// is skipped if there's already an api key in the credentials manager +// class NexusPage : public Page { public: NexusPage(CreateInstanceDialog& dlg); ~NexusPage(); + // always returns true, this is an optional page + // bool ready() const override; - void activated() override; protected: + // returns true if the api key was already detected + // bool doSkip() const override; private: + // connection ui std::unique_ptr m_connectionUI; + + // set to true only if the api key was detected when opening the dialog, or + // going back and forth would skip the page after the process is completed, + // which would be unexpected bool m_skip; }; +// shows a text log of all the creation parameters +// class ConfirmationPage : public Page { public: ConfirmationPage(CreateInstanceDialog& dlg); + // recreates the log with the latest settings + // void activated() override; - QString toLocalizedString(CreateInstanceDialog::Types t) const; + // returns the text for the log + // QString makeReview() const; + +private: + // returns a log line with the given caption and path, something like + // " - caption: path" + // QString dirLine(const QString& caption, const QString& path) const; }; -- cgit v1.3.1 From 7614a147495d631c376aac2da5049b5ae4c9cc48 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 16:40:01 -0500 Subject: documentation --- src/createinstancedialogpages.cpp | 101 +++++++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 19 deletions(-) (limited to 'src/createinstancedialogpages.cpp') diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 8431abd1..0f39f6c1 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -15,6 +15,8 @@ namespace cid using namespace MOBase; using MOBase::TaskDialog; +// returns %base_dir%/dir +// QString makeDefaultPath(const std::wstring& dir) { return QDir::toNativeSeparators(PathSettings::makeDefaultPath( @@ -69,6 +71,7 @@ bool Page::ready() const bool Page::skip() const { + // setSkip() overrides this if it's true return m_skip || doSkip(); } @@ -149,6 +152,7 @@ bool IntroPage::doSkip() const TypePage::TypePage(CreateInstanceDialog& dlg) : Page(dlg), m_type(CreateInstanceDialog::NoType) { + // replace placeholders with actual paths ui->createGlobal->setDescription( ui->createGlobal->description() .arg(InstanceManager::singleton().globalInstancesRootPath())); @@ -157,6 +161,7 @@ TypePage::TypePage(CreateInstanceDialog& dlg) ui->createPortable->description() .arg(InstanceManager::singleton().portablePath())); + // disable portable button if it already exists if (InstanceManager::singleton().portableInstanceExists()) { ui->createPortable->setEnabled(false); ui->portableExistsLabel->setVisible(true); @@ -254,34 +259,47 @@ void GamePage::select(IPluginGame* game, const QString& dir) if (checked) { if (!checked->installed) { if (dir.isEmpty()) { + // the selected game has no installation directory and none was given, + // ask the user + const auto path = QFileDialog::getExistingDirectory( &m_dlg, QObject::tr("Find game installation")); if (path.isEmpty()) { + // cancelled checked = nullptr; } else { + // check whether a plugin supports the given directory; this can + // return the same plugin, a different one, or null checked = checkInstallation(path, checked); if (checked) { - // remember this path + // plugin was found, remember this path checked->dir = path; checked->installed = true; } } } else { + // the selected game didn't detect anything, but a directory was given, + // so use that checked->dir = dir; checked->installed = true; } } } - m_selection = checked; + // select this plugin, if any + m_selection = checked; selectButton(checked); + + // update the button associated with it in case the paths have changed updateButton(checked); + updateNavigation(); if (checked) { + // automatically move to the next page when a game is selected next(); } } @@ -297,22 +315,33 @@ void GamePage::selectCustom() return; } + // try to find a plugin that likes this directory for (auto& g : m_games) { if (g->game->looksValid(path)) { + // found one g->dir = path; g->installed = true; + + // select it select(g->game); + + // update the button because the path has changed updateButton(g.get()); + return; } } + // warning to the user warnUnrecognized(path); + + // reselect the previous button selectButton(m_selection); } void GamePage::warnUnrecognized(const QString& path) { + // put the list of supported games in the details textbox QString supportedGames; for (auto* game : sortedGamePlugins()) { supportedGames += game->gameName() + "\n"; @@ -337,10 +366,12 @@ std::vector GamePage::sortedGamePlugins() const { std::vector v; + // all game plugins for (auto* game : m_pc.plugins()) { v.push_back(game); } + // natsort std::sort(v.begin(), v.end(), [](auto* a, auto* b) { return (naturalCompare(a->gameName(), b->gameName()) < 0); }); @@ -460,6 +491,11 @@ void GamePage::clearButtons() ly->addStretch(); ui->games->setUpdatesEnabled(true); + + for (auto& g : m_games) { + // all buttons have been deleted + g->button = nullptr; + } } QCommandLinkButton* GamePage::createCustomButton() @@ -484,14 +520,13 @@ void GamePage::fillList() clearButtons(); for (auto& g : m_games) { - g->button = nullptr; - if (!showAll && !g->installed) { // not installed continue; } if (!m_filter.matches(g->game->gameName())) { + // filtered out continue; } @@ -499,6 +534,7 @@ void GamePage::fillList() addButton(g->button); } + // browse button addButton(createCustomButton()); } @@ -525,6 +561,7 @@ GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g) } if (otherGame) { + // an alternative was found, ask the user about it auto* confirmedGame = confirmOtherGame(path, g->game, otherGame); if (!confirmedGame) { @@ -626,6 +663,9 @@ VariantsPage::VariantsPage(CreateInstanceDialog& dlg) bool VariantsPage::ready() const { + // note that this isn't called when doSkip() is true, which happens when + // the game has no variants + return !m_selection.isEmpty(); } @@ -637,8 +677,7 @@ bool VariantsPage::doSkip() const return true; } - const auto variants = g->gameVariants(); - return (variants.size() < 2); + return (g->gameVariants().size() < 2); } void VariantsPage::activated() @@ -646,6 +685,7 @@ void VariantsPage::activated() auto* g = m_dlg.rawCreationInfo().game; if (m_previousGame != g) { + // recreate the list, the game has changed m_previousGame = g; m_selection = ""; fillList(); @@ -654,9 +694,11 @@ void VariantsPage::activated() void VariantsPage::select(const QString& variant) { + m_selection = variant; + + // find the button, set it checked for (auto* b : m_buttons) { if (b->text() == variant) { - m_selection = variant; b->setChecked(true); } else { b->setChecked(false); @@ -666,6 +708,7 @@ void VariantsPage::select(const QString& variant) updateNavigation(); if (!m_selection.isEmpty()) { + // automatically move to the next page when a variant is selected next(); } } @@ -676,8 +719,7 @@ QString VariantsPage::selectedGameVariant(MOBase::IPluginGame* game) const return {}; } - const auto variants = game->gameVariants(); - if (variants.size() < 2) { + if (game->gameVariants().size() < 2) { return {}; } else { return m_selection; @@ -695,8 +737,8 @@ void VariantsPage::fillList() return; } - const auto variants = g->gameVariants(); - for (auto& v : variants) { + // for each variant, create a checkable button and add it + for (auto& v : g->gameVariants()) { auto* b = new QCommandLinkButton(v); b->setCheckable(true); @@ -721,11 +763,13 @@ NamePage::NamePage(CreateInstanceDialog& dlg) : bool NamePage::ready() const { + // checked when textboxes change or when the page is activated return m_okay; } bool NamePage::doSkip() const { + // portable instances have no name return (m_dlg.rawCreationInfo().type == CreateInstanceDialog::Portable); } @@ -739,6 +783,8 @@ void NamePage::activated() m_label.setText(g->gameName()); + // generate a name if the user hasn't changed the text in case the game + // changed, or if it's empty if (!m_modified || ui->instanceName->text().isEmpty()) { const auto n = InstanceManager::singleton().makeUniqueName(g->gameName()); ui->instanceName->setText(n); @@ -833,6 +879,8 @@ PathsPage::PathsPage(CreateInstanceDialog& dlg) : bool PathsPage::ready() const { + // set when the page is activated, textboxes are changed or the advanced + // checkbox is toggled return m_okay; } @@ -841,10 +889,15 @@ void PathsPage::activated() const auto name = m_dlg.rawCreationInfo().instanceName; const auto type = m_dlg.rawCreationInfo().type; + // if the instance name or type have changed, all the paths must be + // regenerated const bool changed = (m_lastInstanceName != name) || (m_lastType != type); + + // generating and paths setPaths(name, changed); checkPaths(); + updateNavigation(); m_label.setText(m_dlg.rawCreationInfo().game->gameName()); @@ -878,6 +931,7 @@ void PathsPage::onChanged() void PathsPage::checkPaths() { if (ui->advancedPathOptions->isChecked()) { + // checking advanced paths m_okay = checkAdvancedPath(ui->base->text()) && checkAdvancedPath(resolve(ui->downloads->text())) && @@ -885,6 +939,7 @@ void PathsPage::checkPaths() checkAdvancedPath(resolve(ui->profiles->text())) && checkAdvancedPath(resolve(ui->overwrite->text())); } else { + // checking simple path m_okay = checkSimplePath(ui->location->text()); } @@ -907,6 +962,9 @@ QString PathsPage::resolve(const QString& path) const void PathsPage::onAdvanced() { + // the base/location textboxes are different widgets but they represent the + // same base path value, so they're synced between pages + if (ui->advancedPathOptions->isChecked()) { ui->base->setText(ui->location->text()); ui->pathPages->setCurrentIndex(1); @@ -920,19 +978,21 @@ void PathsPage::onAdvanced() void PathsPage::setPaths(const QString& name, bool force) { - QString path; + QString basePath; if (m_dlg.rawCreationInfo().type == CreateInstanceDialog::Portable) { - path = InstanceManager::singleton().portablePath(); + basePath = InstanceManager::singleton().portablePath(); } else { const auto root = InstanceManager::singleton().globalInstancesRootPath(); - path = root + "/" + name; + basePath = root + "/" + name; } - path = QDir::toNativeSeparators(QDir::cleanPath(path)); + basePath = QDir::toNativeSeparators(QDir::cleanPath(basePath)); - setIfEmpty(ui->location, path, force); - setIfEmpty(ui->base, path, force); + // all paths are set regardless of advanced checkbox + + setIfEmpty(ui->location, basePath, force); + setIfEmpty(ui->base, basePath, force); setIfEmpty(ui->downloads, makeDefaultPath(AppConfig::downloadPath()), force); setIfEmpty(ui->mods, makeDefaultPath(AppConfig::modsPath()), force); @@ -951,6 +1011,8 @@ bool PathsPage::checkPath( QString path, PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel) { + auto& m = InstanceManager::singleton(); + bool exists = false; bool invalid = false; bool empty = false; @@ -962,11 +1024,11 @@ bool PathsPage::checkPath( } else { const QDir d(path); - if (InstanceManager::singleton().validInstanceName(d.dirName())) { + if (m.validInstanceName(d.dirName())) { 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()) { + if (QDir(path) != m.portablePath()) { exists = QDir(path).exists(); } } else { @@ -1023,6 +1085,7 @@ NexusPage::~NexusPage() = default; bool NexusPage::ready() const { + // this page is optional return true; } -- cgit v1.3.1 From a28aeb2b1871b5626dedfaedafcdb3102eaeea5d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 19:53:13 -0500 Subject: implemented browse buttons --- src/createinstancedialogpages.cpp | 34 +++++++++++++++++++++++++++------- src/createinstancedialogpages.h | 4 ++++ 2 files changed, 31 insertions(+), 7 deletions(-) (limited to 'src/createinstancedialogpages.cpp') diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 0f39f6c1..49b118f7 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -863,12 +863,23 @@ PathsPage::PathsPage(CreateInstanceDialog& dlg) : m_advancedInvalid(ui->advancedDirInvalid), m_okay(false) { - QObject::connect(ui->location, &QLineEdit::textEdited, [&]{ onChanged(); }); - QObject::connect(ui->base, &QLineEdit::textEdited, [&]{ onChanged(); }); - QObject::connect(ui->downloads, &QLineEdit::textEdited, [&]{ onChanged(); }); - QObject::connect(ui->mods, &QLineEdit::textEdited, [&]{ onChanged(); }); - QObject::connect(ui->profiles, &QLineEdit::textEdited, [&]{ onChanged(); }); - QObject::connect(ui->overwrite, &QLineEdit::textEdited, [&]{ onChanged(); }); + using O = QObject; + using E = QLineEdit; + using B = QAbstractButton; + + O::connect(ui->location, &E::textEdited, [&]{ onChanged(); }); + O::connect(ui->base, &E::textEdited, [&]{ onChanged(); }); + O::connect(ui->downloads, &E::textEdited, [&]{ onChanged(); }); + O::connect(ui->mods, &E::textEdited, [&]{ onChanged(); }); + O::connect(ui->profiles, &E::textEdited, [&]{ onChanged(); }); + O::connect(ui->overwrite, &E::textEdited, [&]{ onChanged(); }); + + O::connect(ui->browseLocation, &B::clicked, [&]{ browse(ui->location); }); + O::connect(ui->browseBase, &B::clicked, [&]{ browse(ui->base); }); + O::connect(ui->browseDownloads, &B::clicked, [&]{ browse(ui->downloads); }); + O::connect(ui->browseMods, &B::clicked, [&]{ browse(ui->mods); }); + O::connect(ui->browseProfiles, &B::clicked, [&]{ browse(ui->profiles); }); + O::connect(ui->browseOverwrite, &B::clicked, [&]{ browse(ui->overwrite); }); QObject::connect( ui->advancedPathOptions, &QCheckBox::clicked, [&]{ onAdvanced(); }); @@ -876,7 +887,6 @@ PathsPage::PathsPage(CreateInstanceDialog& dlg) : ui->pathPages->setCurrentIndex(0); } - bool PathsPage::ready() const { // set when the page is activated, textboxes are changed or the advanced @@ -928,6 +938,16 @@ void PathsPage::onChanged() updateNavigation(); } +void PathsPage::browse(QLineEdit* e) +{ + const auto s = QFileDialog::getExistingDirectory(&m_dlg, {}, e->text()); + if (s.isNull() || s.isEmpty()) { + return; + } + + e->setText(QDir::toNativeSeparators(s)); +} + void PathsPage::checkPaths() { if (ui->advancedPathOptions->isChecked()) { diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index e2eaf0fb..4fde089b 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -487,6 +487,10 @@ private: // void onChanged(); + // opens a browse directory dialog and sets the given textbox + // + void browse(QLineEdit* e); + // checks the simple or advanced paths, sets m_okay // void checkPaths(); -- cgit v1.3.1 From 3cc568a852e9b64bb6e0f34f74869965d04f6a0c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Nov 2020 23:55:17 -0500 Subject: keyboard nav for create instance dialog change the skip intro setting on click instead of on completion ctrl+f in game page --- src/createinstancedialog.cpp | 62 +++++++++++++++---- src/createinstancedialog.h | 23 +++++++- src/createinstancedialog.ui | 6 ++ src/createinstancedialogpages.cpp | 121 +++++++++++++++++++++++++++++--------- src/createinstancedialogpages.h | 66 +++++++++++++++------ 5 files changed, 220 insertions(+), 58 deletions(-) (limited to 'src/createinstancedialogpages.cpp') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 1c1b62d0..47cb9d5c 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -141,8 +141,16 @@ CreateInstanceDialog::CreateInstanceDialog( next(); } + ui->next->setFocus(); + updateNavigation(); + addShortcutAction(QKeySequence::Find, Actions::Find); + + addShortcut(Qt::ALT+Qt::Key_Left, [&]{ back(); }); + addShortcut(Qt::ALT+Qt::Key_Right, [&]{ next(false); }); + addShortcut(Qt::CTRL+Qt::Key_Return, [&]{ next(); }); + connect(ui->next, &QPushButton::clicked, [&]{ next(); }); connect(ui->back, &QPushButton::clicked, [&]{ back(); }); connect(ui->cancel, &QPushButton::clicked, [&]{ reject(); }); @@ -176,17 +184,23 @@ bool CreateInstanceDialog::isOnLastPage() const return true; } -void CreateInstanceDialog::next() +void CreateInstanceDialog::next(bool allowFinish) { + if (!canNext()) { + return; + } + const auto i = ui->pages->currentIndex(); const auto last = isOnLastPage(); if (last) { - if (m_singlePage) { - // just close the dialog - accept(); - } else { - finish(); + if (allowFinish) { + if (m_singlePage) { + // just close the dialog + accept(); + } else { + finish(); + } } } else { changePage(+1); @@ -195,9 +209,40 @@ void CreateInstanceDialog::next() void CreateInstanceDialog::back() { + if (!canBack()) { + return; + } + changePage(-1); } +void CreateInstanceDialog::addShortcut( + QKeySequence seq, std::function f) +{ + auto* sc = new QShortcut(seq, this); + + sc->setAutoRepeat(false); + sc->setContext(Qt::WidgetWithChildrenShortcut); + + QObject::connect(sc, &QShortcut::activated, f); +} + +void CreateInstanceDialog::addShortcutAction(QKeySequence seq, Actions a) +{ + addShortcut(seq, [this, a]{ doAction(a); }); +} + +void CreateInstanceDialog::doAction(Actions a) +{ + std::size_t i = static_cast(ui->pages->currentIndex()); + + if (i >= m_pages.size()) { + return; + } + + m_pages[i]->action(a); +} + void CreateInstanceDialog::setSinglePageImpl(const QString& instanceName) { m_singlePage = true; @@ -347,11 +392,6 @@ void CreateInstanceDialog::finish() logCreation(tr("Done.")); - // remember settings - if (ui->hideIntro->isChecked()) { - GlobalSettings::setHideCreateInstanceIntro(true); - } - // launch the new instance if (ui->launch->isChecked()) { InstanceManager::singleton().setCurrentInstance(ci.instanceName); diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index d541f45e..8e8fe517 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -27,6 +27,11 @@ class CreateInstanceDialog : public QDialog Q_OBJECT public: + enum class Actions + { + Find = 1 + }; + // instance type // enum Types @@ -107,9 +112,10 @@ public: } - // moves to the next page calls finish() if on the last one + // moves to the next page; if `allowFinish` is true, calls finish() if + // currently on the last page // - void next(); + void next(bool allowFinish=true); // moves to the previous page, if any // @@ -171,6 +177,19 @@ private: bool m_singlePage; + // creates a shortcut for the given sequence + // + void addShortcut(QKeySequence seq, std::function f); + + // creates a shortcut for the given sequence and executes the action when + // activated + // + void addShortcutAction(QKeySequence seq, Actions a); + + // calls action() with the given action on the selected page, if any + // + void doAction(Actions a); + // called from setSinglePage(), does whatever doesn't need the T // void setSinglePageImpl(const QString& instanceName); diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 4d1eda1f..86fa900b 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -1102,6 +1102,9 @@ QTextEdit::NoWrap + + true + @@ -1112,6 +1115,9 @@ Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + Instance creation log + diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 49b118f7..882776df 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -59,8 +59,9 @@ void PlaceholderLabel::setVisible(bool b) -Page::Page(CreateInstanceDialog& dlg) - : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_skip(false) +Page::Page(CreateInstanceDialog& dlg) : + ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), + m_skip(false), m_firstActivation(true) { } @@ -80,11 +81,17 @@ bool Page::doSkip() const return false; } -void Page::activated() +void Page::doActivated(bool) { // no-op } +void Page::activated() +{ + doActivated(m_firstActivation); + m_firstActivation = false; +} + void Page::setSkip(bool b) { m_skip = b; @@ -100,6 +107,12 @@ void Page::next() m_dlg.next(); } +bool Page::action(CreateInstanceDialog::Actions a) +{ + // no-op + return false; +} + CreateInstanceDialog::Types Page::selectedInstanceType() const { @@ -139,13 +152,16 @@ CreateInstanceDialog::Paths Page::selectedPaths() const IntroPage::IntroPage(CreateInstanceDialog& dlg) - : Page(dlg) + : Page(dlg), m_skip(GlobalSettings::hideCreateInstanceIntro()) { + QObject::connect(ui->hideIntro, &QCheckBox::toggled, [&] { + GlobalSettings::setHideCreateInstanceIntro(ui->hideIntro->isChecked()); + }); } bool IntroPage::doSkip() const { - return GlobalSettings::hideCreateInstanceIntro(); + return m_skip; } @@ -207,6 +223,13 @@ void TypePage::portable() next(); } +void TypePage::doActivated(bool firstTime) +{ + if (firstTime) { + ui->createGlobal->setFocus(); + } +} + GamePage::Game::Game(IPluginGame* g) : game(g), installed(g->isInstalled()) @@ -224,6 +247,7 @@ GamePage::GamePage(CreateInstanceDialog& dlg) fillList(); m_filter.setEdit(ui->gamesFilter); + m_filter.setUpdateDelay(0); QObject::connect(&m_filter, &FilterWidget::changed, [&]{ fillList(); }); QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); }); @@ -234,6 +258,18 @@ bool GamePage::ready() const return (m_selection != nullptr); } +bool GamePage::action(CreateInstanceDialog::Actions a) +{ + using Actions = CreateInstanceDialog::Actions; + + if (a == Actions::Find) { + ui->gamesFilter->setFocus(); + return true; + } + + return false; +} + IPluginGame* GamePage::selectedGame() const { if (!m_selection) { @@ -263,7 +299,8 @@ void GamePage::select(IPluginGame* game, const QString& dir) // ask the user const auto path = QFileDialog::getExistingDirectory( - &m_dlg, QObject::tr("Find game installation")); + &m_dlg, QObject::tr("Find game installation for %1") + .arg(game->gameName())); if (path.isEmpty()) { // cancelled @@ -291,11 +328,13 @@ void GamePage::select(IPluginGame* game, const QString& dir) // select this plugin, if any m_selection = checked; - selectButton(checked); // update the button associated with it in case the paths have changed updateButton(checked); + // toggle it on + selectButton(checked); + updateNavigation(); if (checked) { @@ -519,6 +558,8 @@ void GamePage::fillList() clearButtons(); + Game* firstButton = nullptr; + for (auto& g : m_games) { if (!showAll && !g->installed) { // not installed @@ -532,10 +573,18 @@ void GamePage::fillList() createGameButton(g.get()); addButton(g->button); + + if (!firstButton) { + firstButton = g.get(); + } } // browse button addButton(createCustomButton()); + + if (firstButton) { + firstButton->button->setDefault(true); + } } GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g) @@ -680,7 +729,7 @@ bool VariantsPage::doSkip() const return (g->gameVariants().size() < 2); } -void VariantsPage::activated() +void VariantsPage::doActivated(bool) { auto* g = m_dlg.rawCreationInfo().game; @@ -749,6 +798,10 @@ void VariantsPage::fillList() ui->editions->addButton(b, QDialogButtonBox::AcceptRole); m_buttons.push_back(b); } + + if (!m_buttons.empty()) { + m_buttons[0]->setDefault(true); + } } @@ -759,6 +812,9 @@ NamePage::NamePage(CreateInstanceDialog& dlg) : { QObject::connect( ui->instanceName, &QLineEdit::textEdited, [&]{ onChanged(); }); + + QObject::connect( + ui->instanceName, &QLineEdit::returnPressed, [&]{ next(); }); } bool NamePage::ready() const @@ -773,7 +829,7 @@ bool NamePage::doSkip() const return (m_dlg.rawCreationInfo().type == CreateInstanceDialog::Portable); } -void NamePage::activated() +void NamePage::doActivated(bool) { auto* g = m_dlg.rawCreationInfo().game; if (!g) { @@ -863,23 +919,28 @@ PathsPage::PathsPage(CreateInstanceDialog& dlg) : m_advancedInvalid(ui->advancedDirInvalid), m_okay(false) { - using O = QObject; - using E = QLineEdit; - using B = QAbstractButton; - - O::connect(ui->location, &E::textEdited, [&]{ onChanged(); }); - O::connect(ui->base, &E::textEdited, [&]{ onChanged(); }); - O::connect(ui->downloads, &E::textEdited, [&]{ onChanged(); }); - O::connect(ui->mods, &E::textEdited, [&]{ onChanged(); }); - O::connect(ui->profiles, &E::textEdited, [&]{ onChanged(); }); - O::connect(ui->overwrite, &E::textEdited, [&]{ onChanged(); }); - - O::connect(ui->browseLocation, &B::clicked, [&]{ browse(ui->location); }); - O::connect(ui->browseBase, &B::clicked, [&]{ browse(ui->base); }); - O::connect(ui->browseDownloads, &B::clicked, [&]{ browse(ui->downloads); }); - O::connect(ui->browseMods, &B::clicked, [&]{ browse(ui->mods); }); - O::connect(ui->browseProfiles, &B::clicked, [&]{ browse(ui->profiles); }); - O::connect(ui->browseOverwrite, &B::clicked, [&]{ browse(ui->overwrite); }); + auto setEdit = [&](QLineEdit* e) { + QObject::connect(e, &QLineEdit::textEdited, [&]{ onChanged(); }); + QObject::connect(e, &QLineEdit::returnPressed, [&]{ next(); }); + }; + + auto setBrowse = [&](QAbstractButton* b, QLineEdit* e) { + QObject::connect(b, &QAbstractButton::clicked, [&]{ browse(e); }); + }; + + setEdit(ui->location); + setEdit(ui->base); + setEdit(ui->downloads); + setEdit(ui->mods); + setEdit(ui->profiles); + setEdit(ui->overwrite); + + setBrowse(ui->browseLocation, ui->location); + setBrowse(ui->browseBase, ui->base); + setBrowse(ui->browseDownloads, ui->downloads); + setBrowse(ui->browseMods, ui->mods); + setBrowse(ui->browseProfiles, ui->profiles); + setBrowse(ui->browseOverwrite, ui->overwrite); QObject::connect( ui->advancedPathOptions, &QCheckBox::clicked, [&]{ onAdvanced(); }); @@ -894,7 +955,7 @@ bool PathsPage::ready() const return m_okay; } -void PathsPage::activated() +void PathsPage::doActivated(bool firstTime) { const auto name = m_dlg.rawCreationInfo().instanceName; const auto type = m_dlg.rawCreationInfo().type; @@ -913,6 +974,10 @@ void PathsPage::activated() m_label.setText(m_dlg.rawCreationInfo().game->gameName()); m_lastInstanceName = name; m_lastType = type; + + if (firstTime) { + ui->location->setFocus(); + } } CreateInstanceDialog::Paths PathsPage::selectedPaths() const @@ -1120,7 +1185,7 @@ ConfirmationPage::ConfirmationPage(CreateInstanceDialog& dlg) { } -void ConfirmationPage::activated() +void ConfirmationPage::doActivated(bool) { ui->review->setPlainText(makeReview()); ui->creationLog->clear(); diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 4fde089b..08953a74 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -60,7 +60,7 @@ public: // called every time a page is shown in the screen // - virtual void activated(); + void activated(); // overrides whether this page should be skipped; this is used by // CreateInstanceDialog::setSinglePage() to disable all other pages @@ -82,6 +82,12 @@ public: void next(); + // called from the dialog when an action is requested on the current page; + // returns true when handled + // + virtual bool action(CreateInstanceDialog::Actions a); + + // returns the instance type // virtual CreateInstanceDialog::Types selectedInstanceType() const; @@ -111,8 +117,14 @@ protected: CreateInstanceDialog& m_dlg; const PluginContainer& m_pc; bool m_skip; + bool m_firstActivation; + // called every time a page is shown in the screen; `firstTime` is true for + // first activation + // + virtual void doActivated(bool firstTime); + // implemented by derived classes, overridden by setSkip(true) // virtual bool doSkip() const; @@ -128,6 +140,12 @@ public: protected: bool doSkip() const override; + +private: + // the setting is only checked once when opening the dialog, or going forwards + // then back after checking the box wouldn't show the intro page any more, + // which would be unexpected + bool m_skip; }; @@ -154,6 +172,11 @@ public: // void portable(); +protected: + // focuses global instance button on first activation + // + void doActivated(bool firstTime) override; + private: CreateInstanceDialog::Types m_type; }; @@ -187,6 +210,10 @@ public: // bool ready() const override; + // handles find + // + bool action(CreateInstanceDialog::Actions a) override; + // returns the selected game // MOBase::IPluginGame* selectedGame() const override; @@ -262,7 +289,6 @@ private: // Game* findGame(MOBase::IPluginGame* game); - // creates the ui for the given game button // void createGameButton(Game* g); @@ -275,8 +301,13 @@ private: // void updateButton(Game* g); - // called when a button has been clicked; selects the game or asks the user - // for directory, depending + // game buttons are toggles, this creates the button for the given game if + // it doesn't exist and toggles it on + // + // the button might not exist if, for example: + // 1) this game is currently filtered out (not installed, doesn't match + // filter text, etc) and, + // 2) the user browses to a directory that a hidden plugin can use // void selectButton(Game* g); @@ -343,7 +374,7 @@ public: // uses the game selected in the previous page to fill the list, this must be // called every time because the user may go back in forth in the wizard // - void activated() override; + void doActivated(bool firstTime) override; // returns the selected variant, if any // @@ -388,18 +419,18 @@ public: // bool ready() const override; + // returns the instance name + // + QString selectedInstanceName() const override; + +protected: // uses the selected game to generate an instance name // // as long as the user hasn't modified the textbox, this will regenerate a new // instance name every time the selected game changes // - void activated() override; + void doActivated(bool firstTime) override; - // returns the instance name - // - QString selectedInstanceName() const override; - -protected: // returns true for portable instances // bool doSkip() const override; @@ -451,15 +482,16 @@ public: // bool ready() const override; + // returns the selected paths + // + CreateInstanceDialog::Paths selectedPaths() const override; + +protected: // resets all the paths if the instance type or instance name have changed, // the current values are kept as long as these don't change; also updates the // game name in the ui // - void activated() override; - - // returns the selected paths - // - CreateInstanceDialog::Paths selectedPaths() const override; + void doActivated(bool firstTime) override; private: // instance name the last time this page was active @@ -572,7 +604,7 @@ public: // recreates the log with the latest settings // - void activated() override; + void doActivated(bool firstTime) override; // returns the text for the log // -- cgit v1.3.1