diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2020-07-24 15:43:13 -0400 |
|---|---|---|
| committer | isanae <14251494+isanae@users.noreply.github.com> | 2020-11-03 11:39:05 -0500 |
| commit | 048e3ac0d9b4e4258c9c2ac4d39d332f340e73a0 (patch) | |
| tree | 52e14f6e9d6dd6044ecf7935e6e8a560360143aa /src | |
| parent | 4ac8ab98563e223075c1c93852d4112ab6e4579c (diff) | |
split create instance dialog pages
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/createinstancedialog.cpp | 1053 | ||||
| -rw-r--r-- | src/createinstancedialogpages.cpp | 976 | ||||
| -rw-r--r-- | src/createinstancedialogpages.h | 219 |
4 files changed, 1199 insertions, 1050 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index af350584..844f5e19 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -85,6 +85,7 @@ add_filter(NAME src/executables GROUPS add_filter(NAME src/instances GROUPS createinstancedialog + createinstancedialogpages instancemanager instancemanagerdialog ) diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 5c85aa68..63be7346 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -1,1063 +1,16 @@ #include "createinstancedialog.h" #include "ui_createinstancedialog.h" +#include "createinstancedialogpages.h" #include "instancemanager.h" -#include "plugincontainer.h" +//#include "plugincontainer.h" #include "settings.h" #include "shared/appconfig.h" -#include <report.h> +//#include <report.h> #include <iplugingame.h> #include <utility.h> using namespace MOBase; -namespace cid -{ - -using MOBase::TaskDialog; - -class PathChecker -{ -public: - PathChecker(QLabel* existsLabel, QLabel* invalidLabel) - : m_exists(existsLabel), m_invalid(invalidLabel) - { - m_existsOriginal = m_exists->text(); - m_invalidOriginal = m_invalid->text(); - } - - QString sanitizeFileName(const QString& name) const - { - QString new_name = name; - - // Restrict the allowed characters - new_name = new_name.remove(QRegExp("[^A-Za-z0-9 _=+;!@#$%^'\\-\\.\\[\\]\\{\\}\\(\\)]")); - - // Don't end in spaces and periods - new_name = new_name.remove(QRegExp("\\.*$")); - new_name = new_name.remove(QRegExp(" *$")); - - // Recurse until stuff stops changing - if (new_name != name) { - return sanitizeFileName(new_name); - } - - return new_name; - } - - // same thing as above, but allows path separators and colons - // - QString sanitizePath(const QString& path) const - { - QString new_name = path; - - // Restrict the allowed characters - new_name = new_name.remove(QRegExp("[^\\\\\\/A-Za-z0-9 _=+;!@#$%^:'\\-\\.\\[\\]\\{\\}\\(\\)]")); - - // Don't end in spaces and periods - new_name = new_name.remove(QRegExp("\\.*$")); - new_name = new_name.remove(QRegExp(" *$")); - - // Recurse until stuff stops changing - if (new_name != path) { - return sanitizeFileName(new_name); - } - - return new_name; - } - - bool checkName(QString parentDir, QString name) const - { - bool exists = false; - bool invalid = false; - bool empty = false; - - name = name.trimmed(); - - if (name.isEmpty()) { - empty = true; - } else { - const QString sanitized = sanitizeFileName(name); - - if (name != sanitized) { - invalid = true; - } else { - exists = QDir(parentDir).exists(name); - } - } - - bool okay = false; - - if (exists) { - m_exists->setVisible(true); - setPossiblePlaceholder(m_exists, m_existsOriginal, QDir(parentDir).filePath(name)); - m_invalid->setVisible(false); - } else if (invalid) { - m_exists->setVisible(false); - m_invalid->setVisible(true); - setPossiblePlaceholder(m_invalid, m_invalidOriginal, name); - } else { - okay = !empty; - m_exists->setVisible(false); - m_invalid->setVisible(false); - } - - return okay; - } - - bool checkPath(QString path) const - { - bool exists = false; - bool invalid = false; - bool empty = false; - - path = path.trimmed(); - - if (path.isEmpty()) { - empty = true; - } else { - const QString sanitized = sanitizePath(path); - - if (path != sanitized) { - invalid = true; - } else { - exists = QDir(path).exists(); - } - } - - bool okay = false; - - if (exists) { - m_exists->setVisible(true); - setPossiblePlaceholder(m_exists, m_existsOriginal, path); - m_invalid->setVisible(false); - } else if (invalid) { - m_exists->setVisible(false); - m_invalid->setVisible(true); - setPossiblePlaceholder(m_invalid, m_invalidOriginal, path); - } else { - okay = !empty; - m_exists->setVisible(false); - m_invalid->setVisible(false); - } - - return okay; - } - -private: - QLabel* m_exists; - QString m_existsOriginal; - - QLabel* m_invalid; - QString m_invalidOriginal; - - void setPossiblePlaceholder( - QLabel* label, const QString& s, const QString& arg) const - { - if (label->text().contains("%1")) { - label->setText(s.arg(arg)); - } - } -}; - -QString makeDefaultPath(const std::wstring& dir) -{ - return QDir::toNativeSeparators( - PathSettings::makeDefaultPath(QString::fromStdWString(dir))); -} - - -class Page -{ -public: - Page(CreateInstanceDialog& dlg) - : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()) - { - } - - virtual bool ready() const - { - return true; - } - - virtual bool skip() const - { - // no-op - return false; - } - - virtual void activated() - { - // no-op - } - - void updateNavigation() - { - m_dlg.updateNavigation(); - } - - void next() - { - m_dlg.next(); - } - - - virtual CreateInstanceDialog::Types selectedInstanceType() const - { - // no-op - return CreateInstanceDialog::NoType; - } - - virtual IPluginGame* selectedGame() const - { - // no-op - return nullptr; - } - - virtual QString selectedGameLocation() const - { - // no-op - return {}; - } - - virtual QString selectedGameEdition() const - { - // no-op - return {}; - } - - virtual QString selectedInstanceName() const - { - // no-op - return {}; - } - - virtual CreateInstanceDialog::Paths selectedPaths() const - { - // no-op - return {}; - } - -protected: - Ui::CreateInstanceDialog* ui; - CreateInstanceDialog& m_dlg; - const PluginContainer& m_pc; -}; - - -class InfoPage : public Page -{ -public: - InfoPage(CreateInstanceDialog& dlg) - : Page(dlg) - { - } -}; - - -class TypePage : public Page -{ -public: - TypePage(CreateInstanceDialog& dlg) - : Page(dlg), m_type(CreateInstanceDialog::NoType) - { - ui->createGlobal->setDescription( - ui->createGlobal->description() - .arg(InstanceManager::instance().instancesPath())); - - ui->createPortable->setDescription( - ui->createPortable->description() - .arg(qApp->applicationDirPath())); - - QObject::connect( - ui->createGlobal, &QAbstractButton::clicked, [&]{ global(); }); - - QObject::connect( - ui->createPortable, &QAbstractButton::clicked, [&]{ portable(); }); - } - - bool ready() const override - { - return (m_type != CreateInstanceDialog::NoType); - } - - CreateInstanceDialog::Types selectedInstanceType() const override - { - return m_type; - } - - void global() - { - m_type = CreateInstanceDialog::Global; - - ui->createGlobal->setChecked(true); - ui->createPortable->setChecked(false); - - next(); - } - - void portable() - { - m_type = CreateInstanceDialog::Portable; - - ui->createGlobal->setChecked(false); - ui->createPortable->setChecked(true); - - next(); - } - -private: - CreateInstanceDialog::Types m_type; -}; - - -class GamePage : public Page -{ -public: - GamePage(CreateInstanceDialog& dlg) - : Page(dlg), m_selection(nullptr) - { - createGames(); - fillList(); - - QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); }); - } - - bool ready() const override - { - return (m_selection != nullptr); - } - - IPluginGame* selectedGame() const override - { - if (!m_selection) { - return nullptr; - } - - return m_selection->game; - } - - QString selectedGameLocation() const override - { - if (!m_selection) { - return {}; - } - - return QDir::toNativeSeparators(m_selection->dir); - } - - void select(IPluginGame* game) - { - Game* checked = findGame(game); - - if (checked) { - if (!checked->installed) { - const auto path = QFileDialog::getExistingDirectory( - &m_dlg, QObject::tr("Find game installation")); - - if (path.isEmpty()) { - checked = nullptr; - } else { - checked = checkInstallation(path, checked); - } - } - } - - m_selection = checked; - selectButton(checked); - updateNavigation(); - } - - void selectCustom() - { - const auto path = QFileDialog::getExistingDirectory( - &m_dlg, QObject::tr("Find game installation")); - - if (path.isEmpty()) { - selectButton(m_selection); - return; - } - - for (auto& g : m_games) { - if (g->game->looksValid(path)) { - g->dir = path; - g->installed = true; - select(g->game); - updateButton(g.get()); - return; - } - } - - warnUnrecognized(path); - selectButton(m_selection); - } - - void warnUnrecognized(const QString& path) - { - QString supportedGames; - for (auto* game : sortedGamePlugins()) { - supportedGames += "<li>" + game->gameName() + "</li>"; - } - - QMessageBox::warning(&m_dlg, - QObject::tr("Unrecognized game"), - QObject::tr( - "The folder %1 does not seem to contain a game Mod Organizer can " - "manage.<br><br><b>These are the games that can be managed:</b>" - "<ul>%2</ul>").arg(path).arg(supportedGames)); - } - -private: - struct Game - { - IPluginGame* game = nullptr; - QCommandLinkButton* button = nullptr; - QString dir; - bool installed = false; - - Game(IPluginGame* g) - : game(g), installed(g->isInstalled()) - { - if (installed) { - dir = game->gameDirectory().path(); - } - } - - Game(const Game&) = delete; - Game& operator=(const Game&) = delete; - }; - - std::vector<std::unique_ptr<Game>> m_games; - Game* m_selection; - - - std::vector<IPluginGame*> sortedGamePlugins() const - { - std::vector<IPluginGame*> v; - - for (auto* game : m_pc.plugins<IPluginGame>()) { - v.push_back(game); - } - - std::sort(v.begin(), v.end(), [](auto* a, auto* b) { - return (a->gameName() < b->gameName()); - }); - - return v; - } - - Game* findGame(IPluginGame* game) - { - for (auto& g : m_games) { - if (g->game == game) { - return g.get(); - } - } - - return nullptr; - } - - void createGames() - { - m_games.clear(); - - for (auto* game : sortedGamePlugins()) { - m_games.push_back(std::make_unique<Game>(game)); - } - } - - void updateButton(Game* g) - { - if (!g || !g->button) { - return; - } - - g->button->setText(g->game->gameName()); - - if (g->installed) { - g->button->setDescription(g->dir); - } else { - g->button->setDescription(QObject::tr("No installation found")); - } - } - - void selectButton(Game* g) - { - // go through each game, set the button that is for game `g` as active; - // some button might not exist, which happens when selecting a custom - // folder for a game that was considered uninstalled - - for (const auto& gg : m_games) { - if (!g) { - // nothing should be selected - if (gg->button) { - gg->button->setChecked(false); - } - - continue; - } - - if (gg->game == g->game) { - // this is the button that should be selected - - if (!gg->button) { - // this happens when the button wasn't visible because the game - // was not installed; create it and show it - // and it has a button, just check it - createGameButton(gg.get()); - ui->games->addButton(gg->button, QDialogButtonBox::AcceptRole); - } - - gg->button->setChecked(true); - gg->button->setFocus(); - } else { - // this is not the button you're looking for - if (gg->button) { - gg->button->setChecked(false); - } - } - } - } - - QCommandLinkButton* createCustomButton() - { - auto* b = new QCommandLinkButton; - - b->setText(QObject::tr("Browse...")); - b->setDescription( - QObject::tr("The folder must contain a valid game installation")); - - QObject::connect(b, &QAbstractButton::clicked, [&] { - selectCustom(); - }); - - return b; - } - - void 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 fillList() - { - const bool showAll = ui->showAllGames->isChecked(); - - ui->games->clear(); - - ui->games->addButton(createCustomButton(), QDialogButtonBox::AcceptRole); - - for (auto& g : m_games) { - g->button = nullptr; - - if (!showAll && !g->installed) { - // not installed - continue; - } - - createGameButton(g.get()); - ui->games->addButton(g->button, QDialogButtonBox::AcceptRole); - } - } - - Game* checkInstallation(const QString& path, Game* g) - { - if (g->game->looksValid(path)) { - // okay - return g; - } - - // the selected game can't use that folder, find another one - auto* otherGame = findAnotherGame(path); - if (otherGame == g->game) { - // shouldn't happen, but okay - return g; - } - - if (otherGame) { - auto* confirmedGame = confirmOtherGame(path, g->game, otherGame); - - if (!confirmedGame) { - // cancelled - return nullptr; - } - - // make it look like the user clicked that button instead - g = findGame(confirmedGame); - if (!g) { - return nullptr; - } - } else { - // nothing can manage this, but the user can override - if (!confirmUnknown(path, g->game)) { - // cancelled - return nullptr; - } - } - - // remember this path - g->dir = path; - g->installed = true; - - updateButton(g); - - return g; - } - - IPluginGame* findAnotherGame(const QString& path) - { - for (auto* otherGame : m_pc.plugins<IPluginGame>()) { - if (otherGame->looksValid(path)) { - return otherGame; - } - } - - return nullptr; - } - - bool confirmUnknown(const QString& path, IPluginGame* game) - { - const auto r = TaskDialog(&m_dlg) - .title(QObject::tr("Unrecognized game")) - .main(QObject::tr("Unrecognized game")) - .content(QObject::tr( - "The folder %1 does not seem to contain an installation for " - "<span style=\"white-space: nowrap; font-weight: bold;\">%2</span> or " - "for any other game Mod Organizer can manage.") - .arg(path) - .arg(game->gameName())) - .button({ - QObject::tr("Use this folder for %1").arg(game->gameName()), - QObject::tr("I know what I'm doing"), - QMessageBox::Ignore}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); - - return (r == QMessageBox::Ignore); - } - - IPluginGame* confirmOtherGame( - const QString& path, - IPluginGame* selectedGame, IPluginGame* guessedGame) - { - const auto r = TaskDialog(&m_dlg) - .title(QObject::tr("Incorrect game")) - .main(QObject::tr("Incorrect game")) - .content(QObject::tr( - "The folder %1 seems to contain an installation for " - "<span style=\"white-space: nowrap; font-weight: bold;\">%2</span>, " - "not " - "<span style=\"white-space: nowrap; font-weight: bold;\">%3</span>.") - .arg(path) - .arg(guessedGame->gameName()) - .arg(selectedGame->gameName())) - .button({ - QObject::tr("Manage %1 instead").arg(guessedGame->gameName()), - QMessageBox::Ok}) - .button({ - QObject::tr("Use this folder for %1").arg(selectedGame->gameName()), - QObject::tr("I know what I'm doing"), - QMessageBox::Ignore}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); - - switch (r) - { - case QMessageBox::Ok: - return guessedGame; - - case QMessageBox::Ignore: - return selectedGame; - - case QMessageBox::Cancel: - default: - return nullptr; - } - } -}; - - -class EditionsPage : public Page -{ -public: - EditionsPage(CreateInstanceDialog& dlg) - : Page(dlg), m_previousGame(nullptr) - { - } - - bool ready() const override - { - return !m_selection.isEmpty(); - } - - bool skip() const override - { - auto* g = m_dlg.game(); - if (!g) { - // shouldn't happen - return true; - } - - const auto variants = g->gameVariants(); - return (variants.size() < 2); - } - - void activated() override - { - auto* g = m_dlg.game(); - - if (m_previousGame != g) { - m_previousGame = g; - m_selection = ""; - fillList(); - } - } - - void select(const QString& variant) - { - for (auto* b : m_buttons) { - if (b->text() == variant) { - m_selection = variant; - b->setChecked(true); - } else { - b->setChecked(false); - } - } - - updateNavigation(); - } - - QString selectedGameEdition() const override - { - auto* g = m_dlg.game(); - if (!g) { - // shouldn't happen - return {}; - } - - const auto variants = g->gameVariants(); - if (variants.size() < 2) { - return {}; - } else { - return m_selection; - } - } - -private: - IPluginGame* m_previousGame; - std::vector<QCommandLinkButton*> m_buttons; - QString m_selection; - - void fillList() - { - ui->editions->clear(); - m_buttons.clear(); - - auto* g = m_dlg.game(); - if (!g) { - // shouldn't happen - return; - } - - const auto variants = g->gameVariants(); - for (auto& v : variants) { - auto* b = new QCommandLinkButton(v); - b->setCheckable(true); - - QObject::connect(b, &QAbstractButton::clicked, [v, this] { - select(v); - }); - - ui->editions->addButton(b, QDialogButtonBox::AcceptRole); - m_buttons.push_back(b); - } - } -}; - - -class NamePage : public Page -{ -public: - NamePage(CreateInstanceDialog& dlg) : - Page(dlg), m_modified(false), m_okay(false), - m_checker(ui->instanceNameExists, ui->instanceNameInvalid) - { - m_originalLabel = ui->instanceNameLabel->text(); - - QObject::connect( - ui->instanceName, &QLineEdit::textEdited, [&]{ onChanged(); }); - } - - bool ready() const override - { - return m_okay; - } - - bool skip() const override - { - return (m_dlg.instanceType() == CreateInstanceDialog::Portable); - } - - void activated() override - { - auto* g = m_dlg.game(); - if (!g) { - // shouldn't happen, next should be disabled - return; - } - - ui->instanceNameLabel->setText(m_originalLabel.arg(g->gameName())); - - if (!m_modified || ui->instanceName->text().isEmpty()) { - const auto n = InstanceManager::instance().makeUniqueName(g->gameName()); - ui->instanceName->setText(n); - m_modified = false; - } - - updateWarnings(); - } - - QString selectedInstanceName() const override - { - if (!m_okay) { - return {}; - } - - const auto text = ui->instanceName->text().trimmed(); - return m_checker.sanitizeFileName(text); - } - -private: - PathChecker m_checker; - QString m_originalLabel; - bool m_modified; - bool m_okay; - - void onChanged() - { - m_modified = true; - updateWarnings(); - } - - void updateWarnings() - { - const auto root = InstanceManager::instance().instancesPath(); - - m_okay = m_checker.checkName(root, ui->instanceName->text()); - updateNavigation(); - } -}; - - -class PathsPage : public Page -{ -public: - PathsPage(CreateInstanceDialog& dlg) : - Page(dlg), - m_checker(ui->locationExists, ui->locationInvalid), - m_advancedChecker(ui->advancedDirExists, ui->advancedDirInvalid) - { - 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(); }); - - QObject::connect( - ui->advancedPathOptions, &QCheckBox::clicked, [&]{ onAdvanced(); }); - - ui->pathPages->setCurrentIndex(0); - } - - - bool ready() const override - { - return checkPaths(); - } - - void activated() override - { - const auto name = m_dlg.instanceName(); - - setPaths(name, (m_lastInstanceName != name)); - checkPaths(); - updateNavigation(); - - m_lastInstanceName = name; - } - - CreateInstanceDialog::Paths selectedPaths() const override - { - CreateInstanceDialog::Paths p; - - if (ui->advancedPathOptions->isChecked()) { - p.base = ui->base->text(); - p.downloads = ui->downloads->text(); - p.mods = ui->mods->text(); - p.profiles = ui->profiles->text(); - p.overwrite = ui->overwrite->text(); - } else { - p.base = ui->location->text(); - } - - return p; - } - -private: - PathChecker m_checker, m_advancedChecker; - QString m_lastInstanceName; - - void onChanged() - { - checkPaths(); - updateNavigation(); - } - - bool checkPaths() const - { - if (ui->advancedPathOptions->isChecked()) { - return - checkAdvancedPath(ui->base->text()) && - checkVarPath(ui->downloads->text()); - } else { - return m_checker.checkPath(ui->location->text()); - } - } - - bool checkAdvancedPath(const QString& path) const - { - return m_advancedChecker.checkPath(path); - } - - bool checkVarPath(QString path) const - { - path = PathSettings::resolve(path, ui->base->text()); - return checkAdvancedPath(path); - } - - void onAdvanced() - { - if (ui->advancedPathOptions->isChecked()) { - ui->base->setText(ui->location->text()); - ui->pathPages->setCurrentIndex(1); - } else { - ui->location->setText(ui->base->text()); - ui->pathPages->setCurrentIndex(0); - } - - checkPaths(); - } - - void setPaths(const QString& name, bool force) - { - const auto root = InstanceManager::instance().instancesPath(); - const auto path = QDir::toNativeSeparators(root + "/" + name); - - setIfEmpty(ui->location, path, force); - - setIfEmpty(ui->base, path, force); - setIfEmpty(ui->downloads, makeDefaultPath(AppConfig::downloadPath()), force); - setIfEmpty(ui->mods, makeDefaultPath(AppConfig::modsPath()), force); - setIfEmpty(ui->profiles, makeDefaultPath(AppConfig::profilesPath()), force); - setIfEmpty(ui->overwrite, makeDefaultPath(AppConfig::overwritePath()), force); - } - - void setIfEmpty(QLineEdit* e, const QString& path, bool force) - { - if (e->text().isEmpty() || force) { - e->setText(path); - } - } -}; - - -class ConfirmationPage : public Page -{ -public: - ConfirmationPage(CreateInstanceDialog& dlg) - : Page(dlg) - { - } - - void activated() override - { - ui->review->setPlainText(makeReview()); - ui->creationLog->clear(); - } - - QString 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 makeReview() const - { - QStringList lines; - const auto paths = m_dlg.paths(); - - lines.push_back(QObject::tr("Instance type: %1").arg(toLocalizedString(m_dlg.instanceType()))); - lines.push_back(QObject::tr("Instance location: %1").arg(m_dlg.dataPath())); - - if (m_dlg.instanceType() != CreateInstanceDialog::Portable) { - lines.push_back(QObject::tr("Instance name: %1").arg(m_dlg.instanceName())); - } - - if (paths.downloads.isEmpty()) { - // simple settings - if (paths.base != m_dlg.dataPath()) { - lines.push_back(QObject::tr("Base directory: %1").arg(paths.base)); - } - } else { - // advanced settings - lines.push_back(QObject::tr("Base directory: %1").arg(paths.base)); - lines.push_back(dirLine(QObject::tr("Downloads"), paths.downloads)); - lines.push_back(dirLine(QObject::tr("Mods"), paths.mods)); - lines.push_back(dirLine(QObject::tr("Profiles"), paths.profiles)); - lines.push_back(dirLine(QObject::tr("Overwrite"), paths.overwrite)); - } - - // game - QString name = m_dlg.game()->gameName(); - if (!m_dlg.gameEdition().isEmpty()) { - name += " (" + m_dlg.gameEdition() + ")"; - } - - lines.push_back(QObject::tr("Game: %1").arg(name)); - lines.push_back(QObject::tr("Game location: %1").arg(m_dlg.gameLocation())); - - return lines.join("\n"); - } - - QString dirLine(const QString& caption, const QString& path) const - { - return QString(" - %1: %2").arg(caption).arg(path); - } -}; - -} // namespace - - CreateInstanceDialog::CreateInstanceDialog( const PluginContainer& pc, QWidget *parent) : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc) diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp new file mode 100644 index 00000000..ab020c66 --- /dev/null +++ b/src/createinstancedialogpages.cpp @@ -0,0 +1,976 @@ +#include "createinstancedialogpages.h" +#include "ui_createinstancedialog.h" +#include "instancemanager.h" +#include "settings.h" +#include "plugincontainer.h" +#include "shared/appconfig.h" +#include <iplugingame.h> +#include <report.h> + +namespace cid +{ + +using MOBase::IPluginGame; +using MOBase::TaskDialog; + +QString makeDefaultPath(const std::wstring& dir) +{ + return QDir::toNativeSeparators( + PathSettings::makeDefaultPath(QString::fromStdWString(dir))); +} + + +PathChecker::PathChecker(QLabel* existsLabel, QLabel* invalidLabel) + : m_exists(existsLabel), m_invalid(invalidLabel) +{ + m_existsOriginal = m_exists->text(); + m_invalidOriginal = m_invalid->text(); +} + +QString PathChecker::sanitizeFileName(const QString& name) const +{ + QString new_name = name; + + // Restrict the allowed characters + new_name = new_name.remove(QRegExp("[^A-Za-z0-9 _=+;!@#$%^'\\-\\.\\[\\]\\{\\}\\(\\)]")); + + // Don't end in spaces and periods + new_name = new_name.remove(QRegExp("\\.*$")); + new_name = new_name.remove(QRegExp(" *$")); + + // Recurse until stuff stops changing + if (new_name != name) { + return sanitizeFileName(new_name); + } + + return new_name; +} + +// same thing as above, but allows path separators and colons +// +QString PathChecker::sanitizePath(const QString& path) const +{ + QString new_name = path; + + // Restrict the allowed characters + new_name = new_name.remove(QRegExp("[^\\\\\\/A-Za-z0-9 _=+;!@#$%^:'\\-\\.\\[\\]\\{\\}\\(\\)]")); + + // Don't end in spaces and periods + new_name = new_name.remove(QRegExp("\\.*$")); + new_name = new_name.remove(QRegExp(" *$")); + + // Recurse until stuff stops changing + if (new_name != path) { + return sanitizeFileName(new_name); + } + + return new_name; +} + +bool PathChecker::checkName(QString parentDir, QString name) const +{ + bool exists = false; + bool invalid = false; + bool empty = false; + + name = name.trimmed(); + + if (name.isEmpty()) { + empty = true; + } else { + const QString sanitized = sanitizeFileName(name); + + if (name != sanitized) { + invalid = true; + } else { + exists = QDir(parentDir).exists(name); + } + } + + bool okay = false; + + if (exists) { + m_exists->setVisible(true); + setPossiblePlaceholder(m_exists, m_existsOriginal, QDir(parentDir).filePath(name)); + m_invalid->setVisible(false); + } else if (invalid) { + m_exists->setVisible(false); + m_invalid->setVisible(true); + setPossiblePlaceholder(m_invalid, m_invalidOriginal, name); + } else { + okay = !empty; + m_exists->setVisible(false); + m_invalid->setVisible(false); + } + + return okay; +} + +bool PathChecker::checkPath(QString path) const +{ + bool exists = false; + bool invalid = false; + bool empty = false; + + path = path.trimmed(); + + if (path.isEmpty()) { + empty = true; + } else { + const QString sanitized = sanitizePath(path); + + if (path != sanitized) { + invalid = true; + } else { + exists = QDir(path).exists(); + } + } + + bool okay = false; + + if (exists) { + m_exists->setVisible(true); + setPossiblePlaceholder(m_exists, m_existsOriginal, path); + m_invalid->setVisible(false); + } else if (invalid) { + m_exists->setVisible(false); + m_invalid->setVisible(true); + setPossiblePlaceholder(m_invalid, m_invalidOriginal, path); + } else { + okay = !empty; + m_exists->setVisible(false); + m_invalid->setVisible(false); + } + + return okay; +} + +void PathChecker::setPossiblePlaceholder( + QLabel* label, const QString& s, const QString& arg) const +{ + if (label->text().contains("%1")) { + label->setText(s.arg(arg)); + } +} + + +Page::Page(CreateInstanceDialog& dlg) + : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()) +{ +} + +bool Page::ready() const +{ + return true; +} + +bool Page::skip() const +{ + // no-op + return false; +} + +void Page::activated() +{ + // no-op +} + +void Page::updateNavigation() +{ + m_dlg.updateNavigation(); +} + +void Page::next() +{ + m_dlg.next(); +} + + +CreateInstanceDialog::Types Page::selectedInstanceType() const +{ + // no-op + return CreateInstanceDialog::NoType; +} + +IPluginGame* Page::selectedGame() const +{ + // no-op + return nullptr; +} + +QString Page::selectedGameLocation() const +{ + // no-op + return {}; +} + +QString Page::selectedGameEdition() const +{ + // no-op + return {}; +} + +QString Page::selectedInstanceName() const +{ + // no-op + return {}; +} + +CreateInstanceDialog::Paths Page::selectedPaths() const +{ + // no-op + return {}; +} + + +InfoPage::InfoPage(CreateInstanceDialog& dlg) + : Page(dlg) +{ +} + + +TypePage::TypePage(CreateInstanceDialog& dlg) + : Page(dlg), m_type(CreateInstanceDialog::NoType) +{ + ui->createGlobal->setDescription( + ui->createGlobal->description() + .arg(InstanceManager::instance().instancesPath())); + + ui->createPortable->setDescription( + ui->createPortable->description() + .arg(qApp->applicationDirPath())); + + QObject::connect( + ui->createGlobal, &QAbstractButton::clicked, [&]{ global(); }); + + QObject::connect( + ui->createPortable, &QAbstractButton::clicked, [&]{ portable(); }); +} + +bool TypePage::ready() const +{ + return (m_type != CreateInstanceDialog::NoType); +} + +CreateInstanceDialog::Types TypePage::selectedInstanceType() const +{ + return m_type; +} + +void TypePage::global() +{ + m_type = CreateInstanceDialog::Global; + + ui->createGlobal->setChecked(true); + ui->createPortable->setChecked(false); + + next(); +} + +void TypePage::portable() +{ + m_type = CreateInstanceDialog::Portable; + + ui->createGlobal->setChecked(false); + ui->createPortable->setChecked(true); + + next(); +} + + +GamePage::Game::Game(IPluginGame* g) + : game(g), installed(g->isInstalled()) +{ + if (installed) { + dir = game->gameDirectory().path(); + } +} + + +GamePage::GamePage(CreateInstanceDialog& dlg) + : Page(dlg), m_selection(nullptr) +{ + createGames(); + fillList(); + + QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); }); +} + +bool GamePage::ready() const +{ + return (m_selection != nullptr); +} + +IPluginGame* GamePage::selectedGame() const +{ + if (!m_selection) { + return nullptr; + } + + return m_selection->game; +} + +QString GamePage::selectedGameLocation() const +{ + if (!m_selection) { + return {}; + } + + return QDir::toNativeSeparators(m_selection->dir); +} + +void GamePage::select(IPluginGame* game) +{ + Game* checked = findGame(game); + + if (checked) { + if (!checked->installed) { + const auto path = QFileDialog::getExistingDirectory( + &m_dlg, QObject::tr("Find game installation")); + + if (path.isEmpty()) { + checked = nullptr; + } else { + checked = checkInstallation(path, checked); + } + } + } + + m_selection = checked; + selectButton(checked); + updateNavigation(); +} + +void GamePage::selectCustom() +{ + const auto path = QFileDialog::getExistingDirectory( + &m_dlg, QObject::tr("Find game installation")); + + if (path.isEmpty()) { + selectButton(m_selection); + return; + } + + for (auto& g : m_games) { + if (g->game->looksValid(path)) { + g->dir = path; + g->installed = true; + select(g->game); + updateButton(g.get()); + return; + } + } + + warnUnrecognized(path); + selectButton(m_selection); +} + +void GamePage::warnUnrecognized(const QString& path) +{ + QString supportedGames; + for (auto* game : sortedGamePlugins()) { + supportedGames += "<li>" + game->gameName() + "</li>"; + } + + QMessageBox::warning(&m_dlg, + QObject::tr("Unrecognized game"), + QObject::tr( + "The folder %1 does not seem to contain a game Mod Organizer can " + "manage.<br><br><b>These are the games that can be managed:</b>" + "<ul>%2</ul>").arg(path).arg(supportedGames)); +} + +std::vector<IPluginGame*> GamePage::sortedGamePlugins() const +{ + std::vector<IPluginGame*> v; + + for (auto* game : m_pc.plugins<IPluginGame>()) { + v.push_back(game); + } + + std::sort(v.begin(), v.end(), [](auto* a, auto* b) { + return (a->gameName() < b->gameName()); + }); + + return v; +} + +GamePage::Game* GamePage::findGame(IPluginGame* game) +{ + for (auto& g : m_games) { + if (g->game == game) { + return g.get(); + } + } + + return nullptr; +} + +void GamePage::createGames() +{ + m_games.clear(); + + for (auto* game : sortedGamePlugins()) { + m_games.push_back(std::make_unique<Game>(game)); + } +} + +void GamePage::updateButton(Game* g) +{ + if (!g || !g->button) { + return; + } + + g->button->setText(g->game->gameName()); + + if (g->installed) { + g->button->setDescription(g->dir); + } else { + g->button->setDescription(QObject::tr("No installation found")); + } +} + +void GamePage::selectButton(Game* g) +{ + // go through each game, set the button that is for game `g` as active; + // some button might not exist, which happens when selecting a custom + // folder for a game that was considered uninstalled + + for (const auto& gg : m_games) { + if (!g) { + // nothing should be selected + if (gg->button) { + gg->button->setChecked(false); + } + + continue; + } + + if (gg->game == g->game) { + // this is the button that should be selected + + if (!gg->button) { + // this happens when the button wasn't visible because the game + // was not installed; create it and show it + // and it has a button, just check it + createGameButton(gg.get()); + ui->games->addButton(gg->button, QDialogButtonBox::AcceptRole); + } + + gg->button->setChecked(true); + gg->button->setFocus(); + } else { + // this is not the button you're looking for + if (gg->button) { + gg->button->setChecked(false); + } + } + } +} + +QCommandLinkButton* GamePage::createCustomButton() +{ + auto* b = new QCommandLinkButton; + + b->setText(QObject::tr("Browse...")); + b->setDescription( + QObject::tr("The folder must contain a valid game installation")); + + QObject::connect(b, &QAbstractButton::clicked, [&] { + selectCustom(); + }); + + 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(); + + ui->games->clear(); + + ui->games->addButton(createCustomButton(), QDialogButtonBox::AcceptRole); + + for (auto& g : m_games) { + g->button = nullptr; + + if (!showAll && !g->installed) { + // not installed + continue; + } + + createGameButton(g.get()); + ui->games->addButton(g->button, QDialogButtonBox::AcceptRole); + } +} + +GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g) +{ + if (g->game->looksValid(path)) { + // okay + return g; + } + + // the selected game can't use that folder, find another one + auto* otherGame = findAnotherGame(path); + if (otherGame == g->game) { + // shouldn't happen, but okay + return g; + } + + if (otherGame) { + auto* confirmedGame = confirmOtherGame(path, g->game, otherGame); + + if (!confirmedGame) { + // cancelled + return nullptr; + } + + // make it look like the user clicked that button instead + g = findGame(confirmedGame); + if (!g) { + return nullptr; + } + } else { + // nothing can manage this, but the user can override + if (!confirmUnknown(path, g->game)) { + // cancelled + return nullptr; + } + } + + // remember this path + g->dir = path; + g->installed = true; + + updateButton(g); + + return g; +} + +IPluginGame* GamePage::findAnotherGame(const QString& path) +{ + for (auto* otherGame : m_pc.plugins<IPluginGame>()) { + if (otherGame->looksValid(path)) { + return otherGame; + } + } + + return nullptr; +} + +bool GamePage::confirmUnknown(const QString& path, IPluginGame* game) +{ + const auto r = TaskDialog(&m_dlg) + .title(QObject::tr("Unrecognized game")) + .main(QObject::tr("Unrecognized game")) + .content(QObject::tr( + "The folder %1 does not seem to contain an installation for " + "<span style=\"white-space: nowrap; font-weight: bold;\">%2</span> or " + "for any other game Mod Organizer can manage.") + .arg(path) + .arg(game->gameName())) + .button({ + QObject::tr("Use this folder for %1").arg(game->gameName()), + QObject::tr("I know what I'm doing"), + QMessageBox::Ignore}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); + + return (r == QMessageBox::Ignore); +} + +IPluginGame* GamePage::confirmOtherGame( + const QString& path, + IPluginGame* selectedGame, IPluginGame* guessedGame) +{ + const auto r = TaskDialog(&m_dlg) + .title(QObject::tr("Incorrect game")) + .main(QObject::tr("Incorrect game")) + .content(QObject::tr( + "The folder %1 seems to contain an installation for " + "<span style=\"white-space: nowrap; font-weight: bold;\">%2</span>, " + "not " + "<span style=\"white-space: nowrap; font-weight: bold;\">%3</span>.") + .arg(path) + .arg(guessedGame->gameName()) + .arg(selectedGame->gameName())) + .button({ + QObject::tr("Manage %1 instead").arg(guessedGame->gameName()), + QMessageBox::Ok}) + .button({ + QObject::tr("Use this folder for %1").arg(selectedGame->gameName()), + QObject::tr("I know what I'm doing"), + QMessageBox::Ignore}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); + + switch (r) + { + case QMessageBox::Ok: + return guessedGame; + + case QMessageBox::Ignore: + return selectedGame; + + case QMessageBox::Cancel: + default: + return nullptr; + } +} + + +EditionsPage::EditionsPage(CreateInstanceDialog& dlg) + : Page(dlg), m_previousGame(nullptr) +{ +} + +bool EditionsPage::ready() const +{ + return !m_selection.isEmpty(); +} + +bool EditionsPage::skip() const +{ + auto* g = m_dlg.game(); + if (!g) { + // shouldn't happen + return true; + } + + const auto variants = g->gameVariants(); + return (variants.size() < 2); +} + +void EditionsPage::activated() +{ + auto* g = m_dlg.game(); + + if (m_previousGame != g) { + m_previousGame = g; + m_selection = ""; + fillList(); + } +} + +void EditionsPage::select(const QString& variant) +{ + for (auto* b : m_buttons) { + if (b->text() == variant) { + m_selection = variant; + b->setChecked(true); + } else { + b->setChecked(false); + } + } + + updateNavigation(); +} + +QString EditionsPage::selectedGameEdition() const +{ + auto* g = m_dlg.game(); + if (!g) { + // shouldn't happen + return {}; + } + + const auto variants = g->gameVariants(); + if (variants.size() < 2) { + return {}; + } else { + return m_selection; + } +} + +void EditionsPage::fillList() +{ + ui->editions->clear(); + m_buttons.clear(); + + auto* g = m_dlg.game(); + if (!g) { + // shouldn't happen + return; + } + + const auto variants = g->gameVariants(); + for (auto& v : variants) { + auto* b = new QCommandLinkButton(v); + b->setCheckable(true); + + QObject::connect(b, &QAbstractButton::clicked, [v, this] { + select(v); + }); + + ui->editions->addButton(b, QDialogButtonBox::AcceptRole); + m_buttons.push_back(b); + } +} + + +NamePage::NamePage(CreateInstanceDialog& dlg) : + Page(dlg), m_modified(false), m_okay(false), + m_checker(ui->instanceNameExists, ui->instanceNameInvalid) +{ + m_originalLabel = ui->instanceNameLabel->text(); + + QObject::connect( + ui->instanceName, &QLineEdit::textEdited, [&]{ onChanged(); }); +} + +bool NamePage::ready() const +{ + return m_okay; +} + +bool NamePage::skip() const +{ + return (m_dlg.instanceType() == CreateInstanceDialog::Portable); +} + +void NamePage::activated() +{ + auto* g = m_dlg.game(); + if (!g) { + // shouldn't happen, next should be disabled + return; + } + + ui->instanceNameLabel->setText(m_originalLabel.arg(g->gameName())); + + if (!m_modified || ui->instanceName->text().isEmpty()) { + const auto n = InstanceManager::instance().makeUniqueName(g->gameName()); + ui->instanceName->setText(n); + m_modified = false; + } + + updateWarnings(); +} + +QString NamePage::selectedInstanceName() const +{ + if (!m_okay) { + return {}; + } + + const auto text = ui->instanceName->text().trimmed(); + return m_checker.sanitizeFileName(text); +} + +void NamePage::onChanged() +{ + m_modified = true; + updateWarnings(); +} + +void NamePage::updateWarnings() +{ + const auto root = InstanceManager::instance().instancesPath(); + + m_okay = m_checker.checkName(root, ui->instanceName->text()); + updateNavigation(); +} + + +PathsPage::PathsPage(CreateInstanceDialog& dlg) : + Page(dlg), + m_checker(ui->locationExists, ui->locationInvalid), + m_advancedChecker(ui->advancedDirExists, ui->advancedDirInvalid) +{ + 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(); }); + + QObject::connect( + ui->advancedPathOptions, &QCheckBox::clicked, [&]{ onAdvanced(); }); + + ui->pathPages->setCurrentIndex(0); +} + + +bool PathsPage::ready() const +{ + return checkPaths(); +} + +void PathsPage::activated() +{ + const auto name = m_dlg.instanceName(); + + setPaths(name, (m_lastInstanceName != name)); + checkPaths(); + updateNavigation(); + + m_lastInstanceName = name; +} + +CreateInstanceDialog::Paths PathsPage::selectedPaths() const +{ + CreateInstanceDialog::Paths p; + + if (ui->advancedPathOptions->isChecked()) { + p.base = ui->base->text(); + p.downloads = ui->downloads->text(); + p.mods = ui->mods->text(); + p.profiles = ui->profiles->text(); + p.overwrite = ui->overwrite->text(); + } else { + p.base = ui->location->text(); + } + + return p; +} + +void PathsPage::onChanged() +{ + checkPaths(); + updateNavigation(); +} + +bool PathsPage::checkPaths() const +{ + if (ui->advancedPathOptions->isChecked()) { + return + checkAdvancedPath(ui->base->text()) && + checkVarPath(ui->downloads->text()); + } else { + return m_checker.checkPath(ui->location->text()); + } +} + +bool PathsPage::checkAdvancedPath(const QString& path) const +{ + return m_advancedChecker.checkPath(path); +} + +bool PathsPage::checkVarPath(QString path) const +{ + path = PathSettings::resolve(path, ui->base->text()); + return checkAdvancedPath(path); +} + +void PathsPage::onAdvanced() +{ + if (ui->advancedPathOptions->isChecked()) { + ui->base->setText(ui->location->text()); + ui->pathPages->setCurrentIndex(1); + } else { + ui->location->setText(ui->base->text()); + ui->pathPages->setCurrentIndex(0); + } + + checkPaths(); +} + +void PathsPage::setPaths(const QString& name, bool force) +{ + const auto root = InstanceManager::instance().instancesPath(); + const auto path = QDir::toNativeSeparators(root + "/" + name); + + setIfEmpty(ui->location, path, force); + + setIfEmpty(ui->base, path, force); + setIfEmpty(ui->downloads, makeDefaultPath(AppConfig::downloadPath()), force); + setIfEmpty(ui->mods, makeDefaultPath(AppConfig::modsPath()), force); + setIfEmpty(ui->profiles, makeDefaultPath(AppConfig::profilesPath()), force); + setIfEmpty(ui->overwrite, makeDefaultPath(AppConfig::overwritePath()), force); +} + +void PathsPage::setIfEmpty(QLineEdit* e, const QString& path, bool force) +{ + if (e->text().isEmpty() || force) { + e->setText(path); + } +} + + +ConfirmationPage::ConfirmationPage(CreateInstanceDialog& dlg) + : Page(dlg) +{ +} + +void ConfirmationPage::activated() +{ + ui->review->setPlainText(makeReview()); + 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; + const auto paths = m_dlg.paths(); + + lines.push_back(QObject::tr("Instance type: %1").arg(toLocalizedString(m_dlg.instanceType()))); + lines.push_back(QObject::tr("Instance location: %1").arg(m_dlg.dataPath())); + + if (m_dlg.instanceType() != CreateInstanceDialog::Portable) { + lines.push_back(QObject::tr("Instance name: %1").arg(m_dlg.instanceName())); + } + + if (paths.downloads.isEmpty()) { + // simple settings + if (paths.base != m_dlg.dataPath()) { + lines.push_back(QObject::tr("Base directory: %1").arg(paths.base)); + } + } else { + // advanced settings + lines.push_back(QObject::tr("Base directory: %1").arg(paths.base)); + lines.push_back(dirLine(QObject::tr("Downloads"), paths.downloads)); + lines.push_back(dirLine(QObject::tr("Mods"), paths.mods)); + lines.push_back(dirLine(QObject::tr("Profiles"), paths.profiles)); + lines.push_back(dirLine(QObject::tr("Overwrite"), paths.overwrite)); + } + + // game + QString name = m_dlg.game()->gameName(); + if (!m_dlg.gameEdition().isEmpty()) { + name += " (" + m_dlg.gameEdition() + ")"; + } + + lines.push_back(QObject::tr("Game: %1").arg(name)); + lines.push_back(QObject::tr("Game location: %1").arg(m_dlg.gameLocation())); + + return lines.join("\n"); +} + +QString ConfirmationPage::dirLine(const QString& caption, const QString& path) const +{ + return QString(" - %1: %2").arg(caption).arg(path); +} + +} // namespace diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h new file mode 100644 index 00000000..0baff666 --- /dev/null +++ b/src/createinstancedialogpages.h @@ -0,0 +1,219 @@ +#ifndef MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED +#define MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED + +#include <QLabel> +#include <QLineEdit> +#include <QCommandLinkButton> +#include "createinstancedialog.h" + +namespace MOBase { class IPluginGame; } + +namespace cid +{ + +QString makeDefaultPath(const std::wstring& dir); + +class PathChecker +{ +public: + PathChecker(QLabel* existsLabel, QLabel* invalidLabel); + + QString sanitizeFileName(const QString& name) const; + + // same thing as above, but allows path separators and colons + // + QString sanitizePath(const QString& path) const; + + bool checkName(QString parentDir, QString name) const; + bool checkPath(QString path) const; + +private: + QLabel* m_exists; + QString m_existsOriginal; + + QLabel* m_invalid; + QString m_invalidOriginal; + + void setPossiblePlaceholder( + QLabel* label, const QString& s, const QString& arg) const; +}; + + +class Page +{ +public: + Page(CreateInstanceDialog& dlg); + + virtual bool ready() const; + virtual bool skip() const; + virtual void activated(); + + void updateNavigation(); + void next(); + + virtual CreateInstanceDialog::Types selectedInstanceType() const; + virtual MOBase::IPluginGame* selectedGame() const; + virtual QString selectedGameLocation() const; + virtual QString selectedGameEdition() const; + virtual QString selectedInstanceName() const; + virtual CreateInstanceDialog::Paths selectedPaths() const; + +protected: + Ui::CreateInstanceDialog* ui; + CreateInstanceDialog& m_dlg; + const PluginContainer& m_pc; +}; + + +class InfoPage : public Page +{ +public: + InfoPage(CreateInstanceDialog& dlg); +}; + + +class TypePage : public Page +{ +public: + TypePage(CreateInstanceDialog& dlg); + + bool ready() const override; + CreateInstanceDialog::Types selectedInstanceType() const override; + + void global(); + void portable(); + +private: + CreateInstanceDialog::Types m_type; +}; + + +class GamePage : public Page +{ +public: + GamePage(CreateInstanceDialog& dlg); + + bool ready() const override; + MOBase::IPluginGame* selectedGame() const override; + QString selectedGameLocation() const override; + + void select(MOBase::IPluginGame* game); + void selectCustom(); + + void warnUnrecognized(const QString& path); + +private: + struct Game + { + MOBase::IPluginGame* game = nullptr; + QCommandLinkButton* button = nullptr; + QString dir; + bool installed = false; + + Game(MOBase::IPluginGame* g); + Game(const Game&) = delete; + Game& operator=(const Game&) = delete; + }; + + std::vector<std::unique_ptr<Game>> m_games; + Game* m_selection; + + + std::vector<MOBase::IPluginGame*> sortedGamePlugins() const; + Game* findGame(MOBase::IPluginGame* game); + void createGames(); + void updateButton(Game* g); + void selectButton(Game* g); + QCommandLinkButton* createCustomButton(); + void createGameButton(Game* g); + void fillList(); + Game* checkInstallation(const QString& path, Game* g); + MOBase::IPluginGame* findAnotherGame(const QString& path); + bool confirmUnknown(const QString& path, MOBase::IPluginGame* game); + MOBase::IPluginGame* confirmOtherGame( + const QString& path, + MOBase::IPluginGame* selectedGame, MOBase::IPluginGame* guessedGame); +}; + + +class EditionsPage : public Page +{ +public: + EditionsPage(CreateInstanceDialog& dlg); + + bool ready() const override; + bool skip() const override; + void activated() override; + QString selectedGameEdition() const override; + + void select(const QString& variant); + +private: + MOBase::IPluginGame* m_previousGame; + std::vector<QCommandLinkButton*> m_buttons; + QString m_selection; + + void fillList(); +}; + + +class NamePage : public Page +{ +public: + NamePage(CreateInstanceDialog& dlg); + + bool ready() const override; + bool skip() const override; + void activated() override; + QString selectedInstanceName() const override; + +private: + PathChecker m_checker; + QString m_originalLabel; + bool m_modified; + bool m_okay; + + void onChanged(); + void updateWarnings(); +}; + + +class PathsPage : public Page +{ +public: + PathsPage(CreateInstanceDialog& dlg); + + bool ready() const override; + void activated() override; + + CreateInstanceDialog::Paths selectedPaths() const override; + +private: + PathChecker m_checker, m_advancedChecker; + QString m_lastInstanceName; + + void onChanged(); + bool checkPaths() const; + bool checkAdvancedPath(const QString& path) const; + bool checkVarPath(QString path) const; + void onAdvanced(); + void setPaths(const QString& name, bool force); + void setIfEmpty(QLineEdit* e, const QString& path, bool force); +}; + + +class ConfirmationPage : public Page +{ +public: + ConfirmationPage(CreateInstanceDialog& dlg); + + void activated() override; + + QString toLocalizedString(CreateInstanceDialog::Types t) const; + QString makeReview() const; + QString dirLine(const QString& caption, const QString& path) const; +}; + +} // namespace + +#endif // MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED |
