diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2020-11-08 23:55:17 -0500 |
|---|---|---|
| committer | isanae <14251494+isanae@users.noreply.github.com> | 2020-11-08 23:55:17 -0500 |
| commit | 3cc568a852e9b64bb6e0f34f74869965d04f6a0c (patch) | |
| tree | 16e4c247803ffb84e58e4e06f4e7696364ab822b /src | |
| parent | d7ae42d3775f88d5cd63fa2f8860bd31e18f981e (diff) | |
keyboard nav for create instance dialog
change the skip intro setting on click instead of on completion
ctrl+f in game page
Diffstat (limited to 'src')
| -rw-r--r-- | src/createinstancedialog.cpp | 62 | ||||
| -rw-r--r-- | src/createinstancedialog.h | 23 | ||||
| -rw-r--r-- | src/createinstancedialog.ui | 6 | ||||
| -rw-r--r-- | src/createinstancedialogpages.cpp | 117 | ||||
| -rw-r--r-- | src/createinstancedialogpages.h | 66 |
5 files changed, 218 insertions, 56 deletions
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<void ()> 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<std::size_t>(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<void ()> 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 @@ <property name="lineWrapMode"> <enum>QTextEdit::NoWrap</enum> </property> + <property name="readOnly"> + <bool>true</bool> + </property> </widget> </item> <item> @@ -1112,6 +1115,9 @@ <property name="textInteractionFlags"> <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> </property> + <property name="placeholderText"> + <string>Instance creation log</string> + </property> </widget> </item> <item> 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; + 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); }); + }; - 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(); }); + setEdit(ui->location); + setEdit(ui->base); + setEdit(ui->downloads); + setEdit(ui->mods); + setEdit(ui->profiles); + setEdit(ui->overwrite); - 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); }); + 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 // |
