#ifndef MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED #define MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED #include "createinstancedialog.h" #include #include #include #include namespace MOBase { class IPluginGame; } 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: QLabel* m_label; QString m_original; }; // 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 // 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(); // 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; // 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; // returns the profile settings // virtual CreateInstanceDialog::ProfileSettings profileSettings() const; protected: Ui::CreateInstanceDialog* ui; 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; }; // introduction page, can be disabled by a global setting // class IntroPage : public Page { public: IntroPage(CreateInstanceDialog& dlg); 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; }; // 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(); protected: // focuses global instance button on first activation // void doActivated(bool firstTime) override; private: CreateInstanceDialog::Types m_type; }; // 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; // handles find // bool action(CreateInstanceDialog::Actions a) 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; // 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); // 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); // removes all buttons from the ui // void clearButtons(); // creates the "Browse" button // QCommandLinkButton* createCustomButton(); // clears the button list and adds all the buttons to it, depending on // filtering and stuff // void fillList(); // 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); // 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); // detects if the given path likely contains a Microsoft Store game // bool detectMicrosoftStore(const QString& path); // tells the user that the path probably contains a Microsoft Store game that // is not supported, returns true if the user decides to accept anyway. // bool confirmMicrosoftStore(const QString& path, MOBase::IPluginGame* game); }; // 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 doActivated(bool firstTime) 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; // 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 doActivated(bool firstTime) override; // returns true for portable instances // bool doSkip() const override; private: // 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(); // 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; // 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 doActivated(bool firstTime) 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; // 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(); // 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(); // 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); }; // default settings for profiles page; allow the user to set their preferred // defaults for the profile options // class ProfilePage : public Page { public: ProfilePage(CreateInstanceDialog& dlg); // always returns true, options are boolean // bool ready() const override; CreateInstanceDialog::ProfileSettings profileSettings() const override; }; // 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; 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 doActivated(bool firstTime) override; // 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; }; } // namespace cid #endif // MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED