From b103f297752b170ee4ade6e0e9085c6d31c020ca Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 Jul 2020 00:35:26 -0400 Subject: added --multiple to allow launching multiple instances --- src/main.cpp | 47 ++++++++++++++++++++++---------------- src/singleinstance.cpp | 23 +++++++++++-------- src/singleinstance.h | 62 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 85 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index a81ae962..7130d15c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -112,19 +112,19 @@ bool createAndMakeWritable(const std::wstring &subPath) { } } -bool bootstrap() +void purgeOldFiles() { - // remove the temporary backup directory in case we're restarting after an update + // remove the temporary backup directory in case we're restarting after an + // update QString backupDirectory = qApp->applicationDirPath() + "/update_backup"; if (QDir(backupDirectory).exists()) { shellDelete(QStringList(backupDirectory)); } // cycle log file - removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), - "usvfs*.log", 5, QDir::Name); - - return true; + removeOldFiles( + qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), + "usvfs*.log", 5, QDir::Name); } thread_local LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr; @@ -533,8 +533,9 @@ static QString getVersionDisplayString() } -int runApplication(MOApplication &application, SingleInstance &instance, - const QString &splashPath) +int runApplication( + MOApplication &application, QStringList& arguments, + SingleInstance &instance, const QString &splashPath) { TimeThis tt("runApplication() to exec()"); @@ -555,17 +556,13 @@ int runApplication(MOApplication &application, SingleInstance &instance, log::debug("this is a portable instance"); } - if (!bootstrap()) { - reportError("failed to set up data paths"); - InstanceManager::instance().clearCurrentInstance(); - return 1; + if (!instance.secondary()) { + purgeOldFiles(); } QWindowsWindowFunctions::setWindowActivationBehavior( QWindowsWindowFunctions::AlwaysActivateWindow); - QStringList arguments = application.arguments(); - try { log::info("working directory: {}", QDir::currentPath()); @@ -574,6 +571,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, log::debug("using ini at '{}'", settings.filename()); + if (instance.secondary()) { + log::debug("another instance of MO is running but --multiple was given"); + } + + // global crashDumpType sits in OrganizerCore to make a bit less ugly to // update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); @@ -929,16 +931,23 @@ int main(int argc, char *argv[]) setupPath(); - bool forcePrimary = false; + + SingleInstance::Flags siFlags = SingleInstance::NoFlags; + if (arguments.contains("update")) { arguments.removeAll("update"); - forcePrimary = true; + siFlags |= SingleInstance::ForcePrimary; + } + + if (arguments.contains("--multiple")) { + arguments.removeAll("--multiple"); + siFlags |= SingleInstance::AllowMultiple; } MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" }; - SingleInstance instance(forcePrimary); - if (!instance.primaryInstance()) { + SingleInstance instance(siFlags); + if (instance.ephemeral()) { if (moshortcut || arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) { @@ -998,7 +1007,7 @@ int main(int argc, char *argv[]) tt.stop(); - const int result = runApplication(application, instance, splash); + const int result = runApplication(application, arguments, instance, splash); if (result != RestartExitCode) { return result; } diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index aa62d40a..d38095df 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -28,33 +28,38 @@ static const int s_Timeout = 5000; using MOBase::reportError; -SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) : - QObject(parent), m_PrimaryInstance(false) +SingleInstance::SingleInstance(Flags flags, QObject *parent) : + QObject(parent), m_Ephemeral(false), m_OwnsSM(false) { m_SharedMem.setKey(s_Key); + if (!m_SharedMem.create(1)) { - if (forcePrimary) { + if (flags.testFlag(ForcePrimary)) { while (m_SharedMem.error() == QSharedMemory::AlreadyExists) { Sleep(500); if (m_SharedMem.create(1)) { - m_PrimaryInstance = true; + m_OwnsSM = true; break; } } } if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - m_SharedMem.attach(); - m_PrimaryInstance = false; + if (!flags.testFlag(AllowMultiple)) { + m_SharedMem.attach(); + m_Ephemeral = true; + } } + if ((m_SharedMem.error() != QSharedMemory::NoError) && (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); } } else { - m_PrimaryInstance = true; + m_OwnsSM = true; } - if (m_PrimaryInstance) { + + if (m_OwnsSM) { connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()), Qt::QueuedConnection); // has to be called before listen m_Server.setSocketOptions(QLocalServer::WorldAccessOption); @@ -65,7 +70,7 @@ SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) : void SingleInstance::sendMessage(const QString &message) { - if (m_PrimaryInstance) { + if (m_OwnsSM) { // nobody there to receive the message return; } diff --git a/src/singleinstance.h b/src/singleinstance.h index 14d49d6d..5c7cdf85 100644 --- a/src/singleinstance.h +++ b/src/singleinstance.h @@ -27,8 +27,8 @@ along with Mod Organizer. If not, see . /** * used to ensure only a single instance of Mod Organizer is started and to - * allow secondary instances to send messages to the primary (visible) one. This way, - * secondary instances can start a download in the primary one + * allow ephemeral instances to send messages to the primary (visible) one. + * This way, other instances can start a download in the primary one **/ class SingleInstance : public QObject { @@ -36,24 +36,46 @@ class SingleInstance : public QObject Q_OBJECT public: + enum Flag + { + NoFlags = 0x00, - /** - * @brief constructor - * - * @param forcePrimary if true, this will be treated as the primary instance even - * if another instance is running. This is used after an update since - * the other instance is assumed to be in the process of quitting - * @param parent parent object - * @todo the forcePrimary parameter makes no sense. The second instance after an update - * needs to delete the files from before the update so the first instance needs to quit - * first anyway - **/ - explicit SingleInstance(bool forcePrimary, QObject *parent = 0); + + // when set, this will be treated as the primary instance even if + // another instance is running. This is used after an update since the + // other instance is assumed to be in the process of quitting + // + // todo: this makes no sense. The second instance after an update needs + // to delete the files from before the update so the first instance + // needs to quit first anyway + ForcePrimary = 0x01, + + // if another instance is running, run this one disconnected from the + // shared memory + AllowMultiple = 0x02 + }; + + using Flags = QFlags; + + + explicit SingleInstance(Flags flags, QObject *parent = 0); /** - * @return true if this is the primary instance (the one that gets to display a UI) + * @return true if this instance's job is to forward data to the primary + * instance through shared memory **/ - bool primaryInstance() const { return m_PrimaryInstance; } + bool ephemeral() const + { + return m_Ephemeral; + } + + // returns true if this is not the primary instance, but was allowed because + // of the AllowMultiple flag + // + bool secondary() const + { + return !m_Ephemeral && !m_OwnsSM; + } /** * send a message to the primary instance. This can be used to transmit download urls @@ -65,7 +87,7 @@ public: signals: /** - * @brief emitted when a secondary instance has sent a message (to us) + * @brief emitted when an ephemeral instance has sent a message (to us) * * @param message the message we received **/ @@ -78,11 +100,13 @@ private slots: void receiveMessage(); private: - - bool m_PrimaryInstance; + bool m_Ephemeral; + bool m_OwnsSM; QSharedMemory m_SharedMem; QLocalServer m_Server; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(SingleInstance::Flags); + #endif // SINGLEINSTANCE_H -- cgit v1.3.1 From 5e528fb4cf16ae208944d15d568c9140e3d741e4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 Jul 2020 02:24:44 -0400 Subject: new CommandLine class implemented crashdump as a command, fixed dump_running_process.bat to use it attach to console if present instead of always create one --- dump_running_process.bat | 2 +- src/CMakeLists.txt | 1 + src/commandline.cpp | 205 +++++++++++++++++++++++++++++++++++++++++++++++ src/commandline.h | 62 ++++++++++++++ src/env.cpp | 42 +++++++++- src/env.h | 4 + src/main.cpp | 31 ++----- src/pch.h | 1 + 8 files changed, 318 insertions(+), 30 deletions(-) create mode 100644 src/commandline.cpp create mode 100644 src/commandline.h (limited to 'src') diff --git a/dump_running_process.bat b/dump_running_process.bat index 4697fe5e..1245c75e 100644 --- a/dump_running_process.bat +++ b/dump_running_process.bat @@ -1,2 +1,2 @@ pushd "%~dp0" -start ModOrganizer.exe --crashdump +start ModOrganizer.exe crashdump diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e95179b8..73254574 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ endif() add_filter(NAME src/application GROUPS iuserinterface + commandline main moapplication moshortcut diff --git a/src/commandline.cpp b/src/commandline.cpp new file mode 100644 index 00000000..f0bdadf9 --- /dev/null +++ b/src/commandline.cpp @@ -0,0 +1,205 @@ +#include "commandline.h" +#include "env.h" + +namespace cl +{ + +CommandLine::CommandLine() +{ + createOptions(); + m_commands.push_back(std::make_unique()); +} + +int CommandLine::run(int argc, char** argv) +{ + try + { + po::variables_map vm; + + auto parsed = po::command_line_parser(argc, argv) + .options(m_allOptions) + .positional(m_positional) + .allow_unregistered() + .run(); + + po::store(parsed, vm); + + if (vm.count("command")) { + const auto cmd = vm["command"].as(); + + for (auto&& c : m_commands) { + if (c->name() == cmd) { + auto co = c->options(); + co.add_options() + ("help", "shows this message"); + + auto opts = po::collect_unrecognized( + parsed.options, po::include_positional); + + // remove the command name itself + opts.erase(opts.begin()); + + parsed = po::command_line_parser(opts) + .options(co) + .run(); + + po::store(parsed, vm); + + if (vm.count("help")) { + env::Console console; + std::cout << usage(c.get()) << "\n"; + return 0; + } + + return c->run(vm); + } + } + } + + if (vm.count("help")) { + env::Console console; + std::cout << usage() << "\n"; + return 0; + } + + return -1; + } + catch(po::error& e) + { + env::Console console; + std::cerr << e.what() << "\n"; + std::cerr << usage() << "\n"; + return 1; + } +} + +void CommandLine::createOptions() +{ + m_visibleOptions.add_options() + ("help", "shows this message"); + + po::options_description options; + options.add_options() + ("command", po::value(), "command to execute"); + + m_positional + .add("command", 1) + .add("subargs", -1); + + m_allOptions.add(m_visibleOptions); + m_allOptions.add(options); +} + +std::string CommandLine::usage(const Command* c) const +{ + std::ostringstream oss; + + oss + << "\n" + << "Usage:\n"; + + if (c) { + oss + << " ModOrganizer.exe [options] " << c->name() << " [command-options]\n" + << "\n" + << "Command options:\n" + << c->options() << "\n"; + } else { + oss + << " ModOrganizer.exe [options] [[command] [command-options]]\n" + << "\n" + << "Commands:\n"; + + for (auto&& c : m_commands) { + oss << " " << c->name() << " " << c->description() << "\n"; + } + + oss << "\n"; + } + + oss + << "Global options:\n" + << m_visibleOptions << "\n"; + + return oss.str(); +} + + +std::string Command::name() const +{ + return meta().name; +} + +std::string Command::description() const +{ + return meta().description; +} + +po::options_description Command::options() const +{ + return doOptions(); +} + +po::options_description Command::doOptions() const +{ + // no-op + return {}; +} + +std::string Command::usage() const +{ + std::ostringstream oss; + + oss + << "\n" + << "Usage:\n" + << " ModOrganizer.exe [options] [[command] [command-options]]\n" + << "\n" + << "Options:\n" + << options() << "\n"; + + return oss.str(); +} + +int Command::run(po::variables_map& vm) +{ + return doRun(vm); +} + + + +po::options_description CrashDumpCommand::doOptions() const +{ + po::options_description d; + + d.add_options() + ("type", po::value()->default_value("mini"), "mini|data|full"); + + return d; +} + +Command::Meta CrashDumpCommand::meta() const +{ + return {"crashdump", "writes a crashdump for a running process of MO"}; +} + +int CrashDumpCommand::doRun(po::variables_map& vm) +{ + env::Console console; + + const auto typeString = vm["type"].as(); + const auto type = env::coreDumpTypeFromString(typeString); + + // dump + const auto b = env::coredumpOther(type); + if (!b) { + std::wcerr << L"\n>>>> a minidump file was not written\n\n"; + } + + std::wcerr << L"Press enter to continue..."; + std::wcin.get(); + + return (b ? 0 : 1); +} + +} // namespace diff --git a/src/commandline.h b/src/commandline.h new file mode 100644 index 00000000..d5ad5a32 --- /dev/null +++ b/src/commandline.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +namespace cl +{ + +namespace po = boost::program_options; + + +class Command +{ +public: + virtual ~Command() = default; + + std::string name() const; + std::string description() const; + + po::options_description options() const; + std::string usage() const; + + int run(po::variables_map& vm); + +protected: + struct Meta + { + std::string name, description; + }; + + virtual po::options_description doOptions() const; + virtual Meta meta() const = 0; + virtual int doRun(po::variables_map& vm) = 0; +}; + + +class CrashDumpCommand : public Command +{ +protected: + po::options_description doOptions() const; + Meta meta() const override; + int doRun(po::variables_map& vm) override; +}; + + +class CommandLine +{ +public: + CommandLine(); + + int run(int argc, char** argv); + std::string usage(const Command* c=nullptr) const; + +private: + po::options_description m_visibleOptions, m_allOptions; + po::positional_options_description m_positional; + std::vector> m_commands; + + void createOptions(); +}; + +} // namespace diff --git a/src/env.cpp b/src/env.cpp index 2862aa95..bf75c9fa 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -16,9 +16,15 @@ using namespace MOBase; Console::Console() : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) { - // open a console - if (!AllocConsole()) { - // failed, ignore + // try to attach to parent + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + if (GetLastError() != ERROR_ACCESS_DENIED) { + // parent has no console, create one + if (!AllocConsole()) { + // failed, ignore + return; + } + } } m_hasConsole = true; @@ -1032,6 +1038,36 @@ HandlePtr dumpFile() return {}; } + +CoreDumpTypes coreDumpTypeFromString(const std::string& s) +{ + if (s == "data") + return env::CoreDumpTypes::Data; + else if (s == "full") + return env::CoreDumpTypes::Full; + else + return env::CoreDumpTypes::Mini; +} + +std::string toString(CoreDumpTypes type) +{ + switch (type) + { + case CoreDumpTypes::Mini: + return "mini"; + + case CoreDumpTypes::Data: + return "data"; + + case CoreDumpTypes::Full: + return "full"; + + default: + return "?"; + } +} + + bool createMiniDump(HANDLE process, CoreDumpTypes type) { const DWORD pid = GetProcessId(process); diff --git a/src/env.h b/src/env.h index 5c7492c6..9bec1713 100644 --- a/src/env.h +++ b/src/env.h @@ -307,6 +307,10 @@ enum class CoreDumpTypes Full }; + +CoreDumpTypes coreDumpTypeFromString(const std::string& s); +std::string toString(CoreDumpTypes type); + // creates a minidump file for this process // bool coredump(CoreDumpTypes type); diff --git a/src/main.cpp b/src/main.cpp index 7130d15c..8a006907 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,6 +49,7 @@ along with Mod Organizer. If not, see . #include "env.h" #include "envmodule.h" #include "shared/util.h" +#include "commandline.h" #include #include "shared/windows_error.h" @@ -811,22 +812,6 @@ int runApplication( return 1; } -int doCoreDump(env::CoreDumpTypes type) -{ - env::Console c; - - // dump - const auto b = env::coredumpOther(type); - if (!b) { - std::wcerr << L"\n>>>> a minidump file was not written\n\n"; - } - - std::wcerr << L"Press enter to continue..."; - std::wcin.get(); - - return (b ? 0 : 1); -} - log::Levels convertQtLevel(QtMsgType t) { switch (t) @@ -895,17 +880,11 @@ void initLogging() int main(int argc, char *argv[]) { TimeThis tt("main to runApplication()"); + cl::CommandLine cl; - // handle --crashdump first - for (int i=1; i= 0) + return r; initLogging(); diff --git a/src/pch.h b/src/pch.h index c66550be..02b6b1a2 100644 --- a/src/pch.h +++ b/src/pch.h @@ -64,6 +64,7 @@ #include #include #include +#include // openssl #include -- cgit v1.3.1 From f121d92602772110b80ce8ee89fef82c475190d3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 Jul 2020 03:38:11 -0400 Subject: implemented `launch` as a command --- src/commandline.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++++++------ src/commandline.h | 51 +++++++++++++++-- src/main.cpp | 69 ++--------------------- 3 files changed, 190 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/src/commandline.cpp b/src/commandline.cpp index f0bdadf9..588b5085 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -8,15 +8,22 @@ CommandLine::CommandLine() { createOptions(); m_commands.push_back(std::make_unique()); + m_commands.push_back(std::make_unique()); } -int CommandLine::run(int argc, char** argv) +std::optional CommandLine::run(const std::wstring& line) { try { po::variables_map vm; - auto parsed = po::command_line_parser(argc, argv) + auto args = po::split_winmain(line); + if (!args.empty()) { + // remove program name + args.erase(args.begin()); + } + + auto parsed = po::wcommand_line_parser(args) .options(m_allOptions) .positional(m_positional) .allow_unregistered() @@ -25,10 +32,10 @@ int CommandLine::run(int argc, char** argv) po::store(parsed, vm); if (vm.count("command")) { - const auto cmd = vm["command"].as(); + const auto commandName = vm["command"].as(); for (auto&& c : m_commands) { - if (c->name() == cmd) { + if (c->name() == commandName) { auto co = c->options(); co.add_options() ("help", "shows this message"); @@ -39,9 +46,14 @@ int CommandLine::run(int argc, char** argv) // remove the command name itself opts.erase(opts.begin()); - parsed = po::command_line_parser(opts) - .options(co) - .run(); + po::wcommand_line_parser parser(opts); + parser.options(co); + + if (c->allow_unregistered()) { + parser.allow_unregistered(); + } + + parsed = parser.run(); po::store(parsed, vm); @@ -51,7 +63,7 @@ int CommandLine::run(int argc, char** argv) return 0; } - return c->run(vm); + return c->run(line, vm, opts); } } } @@ -62,13 +74,16 @@ int CommandLine::run(int argc, char** argv) return 0; } - return -1; + return {}; } catch(po::error& e) { env::Console console; - std::cerr << e.what() << "\n"; - std::cerr << usage() << "\n"; + + std::cerr + << e.what() << "\n" + << usage() << "\n"; + return 1; } } @@ -80,7 +95,8 @@ void CommandLine::createOptions() po::options_description options; options.add_options() - ("command", po::value(), "command to execute"); + ("command", po::value(), "command") + ("subargs", po::value >(), "args"); m_positional .add("command", 1) @@ -135,6 +151,11 @@ std::string Command::description() const return meta().description; } +bool Command::allow_unregistered() const +{ + return false; +} + po::options_description Command::options() const { return doOptions(); @@ -161,11 +182,32 @@ std::string Command::usage() const return oss.str(); } -int Command::run(po::variables_map& vm) +std::optional Command::run( + const std::wstring& originalLine, + po::variables_map vm, + std::vector untouched) +{ + m_original = originalLine; + m_vm = vm; + m_untouched = untouched; + + return doRun(); +} + +const std::wstring& Command::originalCmd() const { - return doRun(vm); + return m_original; } +const po::variables_map& Command::vm() const +{ + return m_vm; +} + +const std::vector& Command::untouched() const +{ + return m_untouched; +} po::options_description CrashDumpCommand::doOptions() const @@ -183,11 +225,11 @@ Command::Meta CrashDumpCommand::meta() const return {"crashdump", "writes a crashdump for a running process of MO"}; } -int CrashDumpCommand::doRun(po::variables_map& vm) +std::optional CrashDumpCommand::doRun() { env::Console console; - const auto typeString = vm["type"].as(); + const auto typeString = vm()["type"].as(); const auto type = env::coreDumpTypeFromString(typeString); // dump @@ -202,4 +244,85 @@ int CrashDumpCommand::doRun(po::variables_map& vm) return (b ? 0 : 1); } + +bool LaunchCommand::allow_unregistered() const +{ + return true; +} + +po::options_description LaunchCommand::doOptions() const +{ + return {}; +} + +Command::Meta LaunchCommand::meta() const +{ + return {"launch", ""}; +} + +std::optional LaunchCommand::doRun() +{ + // needs at least the working directory and process name + if (untouched().size() < 2) { + return 1; + } + + std::vector arg; + auto args = UntouchedCommandLineArguments(2, arg); + + return SpawnWaitProcess(arg[1].c_str(), args); +} + +int LaunchCommand::SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) +{ + PROCESS_INFORMATION pi{ 0 }; + STARTUPINFO si{ 0 }; + si.cb = sizeof(si); + std::wstring commandLineCopy = commandLine; + + if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) { + // A bit of a problem where to log the error message here, at least this way you can get the message + // using a either DebugView or a live debugger: + std::wostringstream ost; + ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError(); + OutputDebugStringW(ost.str().c_str()); + return -1; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD exitCode = (DWORD)-1; + ::GetExitCodeProcess(pi.hProcess, &exitCode); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return static_cast(exitCode); +} + +// Parses the first parseArgCount arguments of the current process command line and returns +// them in parsedArgs, the rest of the command line is returned untouched. +LPCWSTR LaunchCommand::UntouchedCommandLineArguments( + int parseArgCount, std::vector& parsedArgs) +{ + LPCWSTR cmd = GetCommandLineW(); + LPCWSTR arg = nullptr; // to skip executable name + for (; parseArgCount >= 0 && *cmd; ++cmd) + { + if (*cmd == '"') { + int escaped = 0; + for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd) + escaped = *cmd == '\\' ? escaped + 1 : 0; + } + if (*cmd == ' ') { + if (arg) + if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"') + parsedArgs.push_back(std::wstring(arg+1, cmd-1)); + else + parsedArgs.push_back(std::wstring(arg, cmd)); + arg = cmd + 1; + --parseArgCount; + } + } + return cmd; +} + } // namespace diff --git a/src/commandline.h b/src/commandline.h index d5ad5a32..b45a86c6 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -20,7 +20,12 @@ public: po::options_description options() const; std::string usage() const; - int run(po::variables_map& vm); + virtual bool allow_unregistered() const; + + std::optional run( + const std::wstring& originalLine, + po::variables_map vm, + std::vector untouched); protected: struct Meta @@ -30,7 +35,16 @@ protected: virtual po::options_description doOptions() const; virtual Meta meta() const = 0; - virtual int doRun(po::variables_map& vm) = 0; + virtual std::optional doRun() = 0; + + const std::wstring& originalCmd() const; + const po::variables_map& vm() const; + const std::vector& untouched() const; + +private: + std::wstring m_original; + po::variables_map m_vm; + std::vector m_untouched; }; @@ -39,7 +53,36 @@ class CrashDumpCommand : public Command protected: po::options_description doOptions() const; Meta meta() const override; - int doRun(po::variables_map& vm) override; + std::optional doRun() override; +}; + + +// this is the `launch` command used when starting a process from within the +// virtualized directory, see processrunner.cpp +// +// it has its own parsing of the command line to extract the argument after +// `launch` and use it as the cwd of the process, but pass the remaining +// arguments verbatim +// +// this is very old code that should probably never be changed +// +// note that it's actually buggy; in particular, it doesn't handle multiple +// whitespace between arguments +// +class LaunchCommand : public Command +{ +public: + bool allow_unregistered() const override; + +protected: + po::options_description doOptions() const; + Meta meta() const override; + std::optional doRun() override; + + int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine); + + LPCWSTR UntouchedCommandLineArguments( + int parseArgCount, std::vector& parsedArgs); }; @@ -48,7 +91,7 @@ class CommandLine public: CommandLine(); - int run(int argc, char** argv); + std::optional run(const std::wstring& line); std::string usage(const Command* c=nullptr) const; private: diff --git a/src/main.cpp b/src/main.cpp index 8a006907..68dcfe11 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -174,60 +174,6 @@ void setUnhandledExceptionHandler() prevTerminateHandler = std::set_terminate(terminateHandler); } -// Parses the first parseArgCount arguments of the current process command line and returns -// them in parsedArgs, the rest of the command line is returned untouched. -LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector& parsedArgs) -{ - LPCWSTR cmd = GetCommandLineW(); - LPCWSTR arg = nullptr; // to skip executable name - for (; parseArgCount >= 0 && *cmd; ++cmd) - { - if (*cmd == '"') { - int escaped = 0; - for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd) - escaped = *cmd == '\\' ? escaped + 1 : 0; - } - if (*cmd == ' ') { - if (arg) - if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"') - parsedArgs.push_back(std::wstring(arg+1, cmd-1)); - else - parsedArgs.push_back(std::wstring(arg, cmd)); - arg = cmd + 1; - --parseArgCount; - } - } - return cmd; -} - - -static int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) { - PROCESS_INFORMATION pi{ 0 }; - STARTUPINFO si{ 0 }; - si.cb = sizeof(si); - std::wstring commandLineCopy = commandLine; - - if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) { - // A bit of a problem where to log the error message here, at least this way you can get the message - // using a either DebugView or a live debugger: - std::wostringstream ost; - ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError(); - OutputDebugStringW(ost.str().c_str()); - return -1; - } - - WaitForSingleObject(pi.hProcess, INFINITE); - - DWORD exitCode = (DWORD)-1; - ::GetExitCodeProcess(pi.hProcess, &exitCode); - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - return static_cast(exitCode); -} - -static DWORD WaitForProcess() { - -} static bool HaveWriteAccess(const std::wstring &path) { @@ -879,13 +825,13 @@ void initLogging() int main(int argc, char *argv[]) { - TimeThis tt("main to runApplication()"); cl::CommandLine cl; - const auto r = cl.run(argc, argv); - if (r >= 0) - return r; + const auto r = cl.run(GetCommandLineW()); + if (r) + return *r; + TimeThis tt("main to runApplication()"); initLogging(); //Make sure the configured temp folder exists @@ -896,13 +842,6 @@ int main(int argc, char *argv[]) //Should allow for better scaling of ui with higher resolution displays QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - if (argc >= 4) { - std::vector arg; - auto args = UntouchedCommandLineArguments(2, arg); - if (arg[0] == L"launch") - return SpawnWaitProcess(arg[1].c_str(), args); - } - MOApplication application(argc, argv); QStringList arguments = application.arguments(); -- cgit v1.3.1 From 75cc2ffead148ab2409cd1ef469613d2e9b80e17 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 Jul 2020 04:06:18 -0400 Subject: removed unused `update` parameter moved --multiple to CommandLine --- src/commandline.cpp | 44 ++++++++++++++++++++++++++++++++++---------- src/commandline.h | 4 ++++ src/main.cpp | 7 +------ src/singleinstance.cpp | 10 ---------- src/singleinstance.h | 12 +----------- 5 files changed, 40 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/commandline.cpp b/src/commandline.cpp index 588b5085..f84906e9 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -15,8 +15,6 @@ std::optional CommandLine::run(const std::wstring& line) { try { - po::variables_map vm; - auto args = po::split_winmain(line); if (!args.empty()) { // remove program name @@ -29,10 +27,10 @@ std::optional CommandLine::run(const std::wstring& line) .allow_unregistered() .run(); - po::store(parsed, vm); + po::store(parsed, m_vm); - if (vm.count("command")) { - const auto commandName = vm["command"].as(); + if (m_vm.count("command")) { + const auto commandName = m_vm["command"].as(); for (auto&& c : m_commands) { if (c->name() == commandName) { @@ -55,20 +53,20 @@ std::optional CommandLine::run(const std::wstring& line) parsed = parser.run(); - po::store(parsed, vm); + po::store(parsed, m_vm); - if (vm.count("help")) { + if (m_vm.count("help")) { env::Console console; std::cout << usage(c.get()) << "\n"; return 0; } - return c->run(line, vm, opts); + return c->run(line, m_vm, opts); } } } - if (vm.count("help")) { + if (m_vm.count("help")) { env::Console console; std::cout << usage() << "\n"; return 0; @@ -91,7 +89,8 @@ std::optional CommandLine::run(const std::wstring& line) void CommandLine::createOptions() { m_visibleOptions.add_options() - ("help", "shows this message"); + ("help", "shows this message") + ("multiple", "allows multiple instances of MO to run; see below"); po::options_description options; options.add_options() @@ -137,9 +136,34 @@ std::string CommandLine::usage(const Command* c) const << "Global options:\n" << m_visibleOptions << "\n"; + if (!c) { + oss << "\n" << more() << "\n"; + } + return oss.str(); } +bool CommandLine::multiple() const +{ + return (m_vm.count("multiple") > 0); +} + +std::string CommandLine::more() const +{ + return + "--multiple can be used to allow multiple instances of MO to run\n" + "simultaneously. This is unsupported and can create all sorts of weird\n" + "problems. To minimize the problems:\n" + "\n" + " 1) Never have multiple MO instances opened that manage the same game\n" + " instance.\n" + " 2) If an executable is launched from an instance, only this instance\n" + " may launch executables until all instances are closed.\n" + "\n" + "It is recommended to close _all_ instances of MO as soon as multiple\n" + "instances become unnecessary."; +} + std::string Command::name() const { diff --git a/src/commandline.h b/src/commandline.h index b45a86c6..deeb9923 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -94,12 +94,16 @@ public: std::optional run(const std::wstring& line); std::string usage(const Command* c=nullptr) const; + bool multiple() const; + private: po::options_description m_visibleOptions, m_allOptions; po::positional_options_description m_positional; std::vector> m_commands; + po::variables_map m_vm; void createOptions(); + std::string more() const; }; } // namespace diff --git a/src/main.cpp b/src/main.cpp index 68dcfe11..a1a4be01 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -852,12 +852,7 @@ int main(int argc, char *argv[]) SingleInstance::Flags siFlags = SingleInstance::NoFlags; - if (arguments.contains("update")) { - arguments.removeAll("update"); - siFlags |= SingleInstance::ForcePrimary; - } - - if (arguments.contains("--multiple")) { + if (cl.multiple()) { arguments.removeAll("--multiple"); siFlags |= SingleInstance::AllowMultiple; } diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index d38095df..e32c8de1 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -34,16 +34,6 @@ SingleInstance::SingleInstance(Flags flags, QObject *parent) : m_SharedMem.setKey(s_Key); if (!m_SharedMem.create(1)) { - if (flags.testFlag(ForcePrimary)) { - while (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - Sleep(500); - if (m_SharedMem.create(1)) { - m_OwnsSM = true; - break; - } - } - } - if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { if (!flags.testFlag(AllowMultiple)) { m_SharedMem.attach(); diff --git a/src/singleinstance.h b/src/singleinstance.h index 5c7cdf85..d500a056 100644 --- a/src/singleinstance.h +++ b/src/singleinstance.h @@ -40,19 +40,9 @@ public: { NoFlags = 0x00, - - // when set, this will be treated as the primary instance even if - // another instance is running. This is used after an update since the - // other instance is assumed to be in the process of quitting - // - // todo: this makes no sense. The second instance after an update needs - // to delete the files from before the update so the first instance - // needs to quit first anyway - ForcePrimary = 0x01, - // if another instance is running, run this one disconnected from the // shared memory - AllowMultiple = 0x02 + AllowMultiple = 0x01 }; using Flags = QFlags; -- cgit v1.3.1 From ccab9eae8df3cf5367ce5cf164c98d1534ac13cb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 Jul 2020 05:07:32 -0400 Subject: add warning when profile given with -p doesn't exist implemented moshortcut, nxm links and executable names as command line options --- src/commandline.cpp | 98 +++++++++++++++++++++++++++++++++-------- src/commandline.h | 13 ++++++ src/main.cpp | 119 ++++++++++++++++++++++---------------------------- src/moshortcut.cpp | 9 ++++ src/moshortcut.h | 8 ++-- src/organizercore.cpp | 9 +++- src/organizercore.h | 3 -- src/shared/util.cpp | 6 +++ src/shared/util.h | 2 + 9 files changed, 175 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/commandline.cpp b/src/commandline.cpp index f84906e9..f5c4df60 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -1,5 +1,6 @@ #include "commandline.h" #include "env.h" +#include "shared/util.h" namespace cl { @@ -29,21 +30,22 @@ std::optional CommandLine::run(const std::wstring& line) po::store(parsed, m_vm); + auto opts = po::collect_unrecognized( + parsed.options, po::include_positional); + + if (m_vm.count("command")) { const auto commandName = m_vm["command"].as(); for (auto&& c : m_commands) { if (c->name() == commandName) { + // remove the command name itself + opts.erase(opts.begin()); + auto co = c->options(); co.add_options() ("help", "shows this message"); - auto opts = po::collect_unrecognized( - parsed.options, po::include_positional); - - // remove the command name itself - opts.erase(opts.begin()); - po::wcommand_line_parser parser(opts); parser.options(co); @@ -72,6 +74,26 @@ std::optional CommandLine::run(const std::wstring& line) return 0; } + if (!opts.empty()) { + const auto qs = QString::fromStdWString(opts[0]); + m_shortcut = qs; + + if (!m_shortcut.isValid()) { + if (isNxmLink(qs)) { + m_nxmLink = qs; + } else { + m_executable = qs; + } + } + + // remove the shortcut/nxm/executable + opts.erase(opts.begin()); + + for (auto&& o : opts) { + m_untouched.push_back(QString::fromStdWString(o)); + } + } + return {}; } catch(po::error& e) @@ -86,11 +108,19 @@ std::optional CommandLine::run(const std::wstring& line) } } +void CommandLine::clear() +{ + m_vm.clear(); + m_shortcut = {}; + m_nxmLink = {}; +} + void CommandLine::createOptions() { m_visibleOptions.add_options() - ("help", "shows this message") - ("multiple", "allows multiple instances of MO to run; see below"); + ("help", "show this message") + ("multiple", "allow multiple instances of MO to run; see below") + ("profile,p", po::value(), "use the given profile (defaults to last used)"); po::options_description options; options.add_options() @@ -148,20 +178,50 @@ bool CommandLine::multiple() const return (m_vm.count("multiple") > 0); } +std::optional CommandLine::profile() const +{ + if (m_vm.count("profile")) { + return QString::fromStdString(m_vm["profile"].as()); + } + + return {}; +} + +const MOShortcut& CommandLine::shortcut() const +{ + return m_shortcut; +} + +std::optional CommandLine::nxmLink() const +{ + return m_nxmLink; +} + +std::optional CommandLine::executable() const +{ + return m_executable; +} + +const QStringList& CommandLine::untouched() const +{ + return m_untouched; +} + std::string CommandLine::more() const { return - "--multiple can be used to allow multiple instances of MO to run\n" - "simultaneously. This is unsupported and can create all sorts of weird\n" - "problems. To minimize the problems:\n" - "\n" - " 1) Never have multiple MO instances opened that manage the same game\n" - " instance.\n" - " 2) If an executable is launched from an instance, only this instance\n" - " may launch executables until all instances are closed.\n" - "\n" - "It is recommended to close _all_ instances of MO as soon as multiple\n" - "instances become unnecessary."; + "Multiple instances\n" + " --multiple can be used to allow multiple instances of MO to run\n" + " simultaneously. This is unsupported and can create all sorts of weird\n" + " problems. To minimize the problems:\n" + " \n" + " 1) Never have multiple MO instances opened that manage the same\n" + " game instance.\n" + " 2) If an executable is launched from an instance, only this\n" + " instance may launch executables until all instances are closed.\n" + " \n" + " It is recommended to close _all_ instances of MO as soon as multiple\n" + " instances become unnecessary."; } diff --git a/src/commandline.h b/src/commandline.h index deeb9923..cdd1c917 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -1,5 +1,6 @@ #pragma once +#include "moshortcut.h" #include #include @@ -92,15 +93,27 @@ public: CommandLine(); std::optional run(const std::wstring& line); + void clear(); + std::string usage(const Command* c=nullptr) const; bool multiple() const; + std::optional profile() const; + const MOShortcut& shortcut() const; + std::optional nxmLink() const; + std::optional executable() const; + + const QStringList& untouched() const; private: po::options_description m_visibleOptions, m_allOptions; po::positional_options_description m_positional; std::vector> m_commands; po::variables_map m_vm; + MOShortcut m_shortcut; + std::optional m_nxmLink; + std::optional m_executable; + QStringList m_untouched; void createOptions(); std::string more() const; diff --git a/src/main.cpp b/src/main.cpp index a1a4be01..3c5bc92d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -225,18 +225,13 @@ static bool HaveWriteAccess(const std::wstring &path) } -QString determineProfile(QStringList &arguments, const Settings &settings) +QString determineProfile(const cl::CommandLine& cl, const Settings &settings) { auto selectedProfileName = settings.game().selectedProfileName(); - { // see if there is a profile on the command line - int profileIndex = arguments.indexOf("-p", 1); - if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { - log::debug("profile overwritten on command line"); - selectedProfileName = arguments.at(profileIndex + 1); - } - arguments.removeAt(profileIndex); - arguments.removeAt(profileIndex); + if (cl.profile()) { + log::debug("profile overwritten on command line"); + selectedProfileName = *cl.profile(); } if (!selectedProfileName) { @@ -481,7 +476,7 @@ static QString getVersionDisplayString() int runApplication( - MOApplication &application, QStringList& arguments, + MOApplication &application, const cl::CommandLine& cl, SingleInstance &instance, const QString &splashPath) { TimeThis tt("runApplication() to exec()"); @@ -635,57 +630,51 @@ int runApplication( organizer.updateExecutablesList(); organizer.updateModInfoFromDisc(); - QString selectedProfileName = determineProfile(arguments, settings); + QString selectedProfileName = determineProfile(cl, settings); organizer.setCurrentProfile(selectedProfileName); // if we have a command line parameter, it is either a nxm link or // a binary to start - if (arguments.size() > 1) { - if (MOShortcut shortcut{ arguments.at(1) }) { - if (shortcut.hasExecutable()) { - try { - organizer.processRunner() - .setFromShortcut(shortcut) - .setWaitForCompletion() - .run(); - - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } - } - else if (OrganizerCore::isNxmLink(arguments.at(1))) { - log::debug("starting download from command line: {}", arguments.at(1)); - organizer.externalMessage(arguments.at(1)); - } - else { - QString exeName = arguments.at(1); - log::debug("starting {} from command line", exeName); - - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - - try - { - // pass the remaining parameters to the binary - organizer.processRunner() - .setFromFileOrExecutable(exeName, arguments) + if (cl.shortcut().isValid()) { + if (cl.shortcut().hasExecutable()) { + try { + organizer.processRunner() + .setFromShortcut(cl.shortcut()) .setWaitForCompletion() .run(); - return 0; - } - catch (const std::exception &e) - { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } - } - } + + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; + } + } + } else if (cl.nxmLink()) { + log::debug("starting download from command line: {}", *cl.nxmLink()); + organizer.externalMessage(*cl.nxmLink()); + } else if (cl.executable()) { + const QString exeName = *cl.executable(); + log::debug("starting {} from command line", exeName); + + try + { + // pass the remaining parameters to the binary + organizer.processRunner() + .setFromFileOrExecutable(exeName, cl.untouched()) + .setWaitForCompletion() + .run(); + + return 0; + } + catch (const std::exception &e) + { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } + } QPixmap pixmap; @@ -857,15 +846,13 @@ int main(int argc, char *argv[]) siFlags |= SingleInstance::AllowMultiple; } - MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" }; - SingleInstance instance(siFlags); if (instance.ephemeral()) { - if (moshortcut || - arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) - { - log::debug("not primary instance, sending shortcut/download message"); - instance.sendMessage(arguments.at(1)); + if (cl.shortcut().isValid()) { + instance.sendMessage(cl.shortcut().toString()); + return 0; + } else if (cl.nxmLink()) { + instance.sendMessage(*cl.nxmLink()); return 0; } else if (arguments.size() == 1) { QMessageBox::information( @@ -887,8 +874,8 @@ int main(int argc, char *argv[]) try { InstanceManager& instanceManager = InstanceManager::instance(); - if (moshortcut && moshortcut.hasInstance()) - instanceManager.overrideInstance(moshortcut.instance()); + if (cl.shortcut().isValid() && cl.shortcut().hasInstance()) + instanceManager.overrideInstance(cl.shortcut().instance()); dataPath = instanceManager.determineDataPath(); } catch (const std::exception &e) { if (strcmp(e.what(),"Canceled")) @@ -920,12 +907,12 @@ int main(int argc, char *argv[]) tt.stop(); - const int result = runApplication(application, arguments, instance, splash); + const int result = runApplication(application, cl, instance, splash); if (result != RestartExitCode) { return result; } argc = 1; - moshortcut = MOShortcut(""); + cl.clear(); } while (true); } diff --git a/src/moshortcut.cpp b/src/moshortcut.cpp index aad7380e..4efedbdb 100644 --- a/src/moshortcut.cpp +++ b/src/moshortcut.cpp @@ -40,3 +40,12 @@ MOShortcut::MOShortcut(const QString& link) m_hasExecutable=true; } } + +QString MOShortcut::toString() const +{ + if (m_hasInstance) { + return "moshortcut://" + m_instance + ":" + m_executable; + } else { + return "moshortcut://" + m_executable; + } +} diff --git a/src/moshortcut.h b/src/moshortcut.h index 2ce54910..0067b3bc 100644 --- a/src/moshortcut.h +++ b/src/moshortcut.h @@ -27,19 +27,21 @@ along with Mod Organizer. If not, see . class MOShortcut { public: - MOShortcut(const QString& link); + MOShortcut(const QString& link={}); /// true iff intialized using a valid moshortcut link - operator bool() const { return m_valid; } + bool isValid() const { return m_valid; } bool hasInstance() const { return m_hasInstance; } - + bool hasExecutable() const { return m_hasExecutable; } const QString& instance() const { return m_instance; } const QString& executable() const { return m_executable; } + QString toString() const; + private: QString m_instance; QString m_executable; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1c35720b..f9cafc95 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -38,6 +38,7 @@ #include "shared/directoryentry.h" #include "shared/filesorigin.h" #include "shared/fileentry.h" +#include "shared/util.h" #include #include @@ -395,7 +396,9 @@ void OrganizerCore::profileRemoved(QString const& profileName) void OrganizerCore::externalMessage(const QString &message) { - if (MOShortcut moshortcut{ message } ) { + MOShortcut moshortcut(message); + + if (moshortcut.isValid()) { if(moshortcut.hasExecutable()) { processRunner() .setFromShortcut(moshortcut) @@ -555,12 +558,16 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) QString profileDir = profileBaseDir.absoluteFilePath(profileName); if (!QDir(profileDir).exists()) { + log::error("profile '{}' does not exist", profileName); + // selected profile doesn't exist. Ensure there is at least one profile, // then pick any one createDefaultProfile(); profileDir = profileBaseDir.absoluteFilePath( profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot).at(0)); + + log::error("picked profile '{}' instead", QDir(profileDir).dirName()); } // Keep the old profile to emit signal-changed: diff --git a/src/organizercore.h b/src/organizercore.h index 70ce94f5..1452bf08 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -191,9 +191,6 @@ public: }; public: - - static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } - OrganizerCore(Settings &settings); ~OrganizerCore(); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index ba500da9..f316549e 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -435,3 +435,9 @@ void ResetExitFlag() { g_exiting = false; } + + +bool isNxmLink(const QString& link) +{ + return link.startsWith("nxm://", Qt::CaseInsensitive); +} diff --git a/src/shared/util.h b/src/shared/util.h index 2761b64f..1688a931 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -83,4 +83,6 @@ bool ModOrganizerExiting(); bool ModOrganizerCanCloseNow(); void ResetExitFlag(); +bool isNxmLink(const QString& link); + #endif // UTIL_H -- cgit v1.3.1 From f4ca82f798fa7e456bb904ed301ddc17db5410c8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 Jul 2020 05:30:57 -0400 Subject: fixed handling of profile names with different casing than on the filesystem added --instance --- src/commandline.cpp | 12 ++++++++++++ src/commandline.h | 2 ++ src/main.cpp | 6 ++++-- src/organizercore.cpp | 19 +++++++++++++++++-- 4 files changed, 35 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/commandline.cpp b/src/commandline.cpp index f5c4df60..11f43dea 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -120,6 +120,7 @@ void CommandLine::createOptions() m_visibleOptions.add_options() ("help", "show this message") ("multiple", "allow multiple instances of MO to run; see below") + ("instance,i", po::value(), "use the given instance (defaults to last used)") ("profile,p", po::value(), "use the given profile (defaults to last used)"); po::options_description options; @@ -187,6 +188,17 @@ std::optional CommandLine::profile() const return {}; } +std::optional CommandLine::instance() const +{ + if (m_shortcut.isValid() && m_shortcut.hasInstance()) { + return m_shortcut.instance(); + } else if (m_vm.count("instance")) { + return QString::fromStdString(m_vm["instance"].as()); + } + + return {}; +} + const MOShortcut& CommandLine::shortcut() const { return m_shortcut; diff --git a/src/commandline.h b/src/commandline.h index cdd1c917..ecc19c89 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -99,6 +99,8 @@ public: bool multiple() const; std::optional profile() const; + std::optional instance() const; + const MOShortcut& shortcut() const; std::optional nxmLink() const; std::optional executable() const; diff --git a/src/main.cpp b/src/main.cpp index 3c5bc92d..5ae8999c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -874,8 +874,10 @@ int main(int argc, char *argv[]) try { InstanceManager& instanceManager = InstanceManager::instance(); - if (cl.shortcut().isValid() && cl.shortcut().hasInstance()) - instanceManager.overrideInstance(cl.shortcut().instance()); + + if (cl.instance()) + instanceManager.overrideInstance(*cl.instance()); + dataPath = instanceManager.determineDataPath(); } catch (const std::exception &e) { if (strcmp(e.what(),"Canceled")) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index f9cafc95..a9469e6e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -555,9 +555,24 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) log::debug("selecting profile '{}'", profileName); QDir profileBaseDir(settings().paths().profiles()); - QString profileDir = profileBaseDir.absoluteFilePath(profileName); - if (!QDir(profileDir).exists()) { + const auto subdirs = profileBaseDir.entryList( + QDir::AllDirs | QDir::NoDotAndDotDot); + + QString profileDir; + + // the profile name may not have the correct case, which breaks other parts + // of the ui like the profile combobox, which walks directories on its own + // + // find the real name with the correct case by walking the directories + for (auto&& dirName : subdirs) { + if (QString::compare(dirName, profileName, Qt::CaseInsensitive) == 0) { + profileDir = profileBaseDir.absoluteFilePath(dirName); + break; + } + } + + if (profileDir.isEmpty()) { log::error("profile '{}' does not exist", profileName); // selected profile doesn't exist. Ensure there is at least one profile, -- cgit v1.3.1 From a8b6f227302f2264eea38ce95d82ddc5aebf1100 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 Jul 2020 13:17:31 -0400 Subject: formatting for command list added empty exe and run commands --- src/commandline.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/commandline.h | 24 +++++++++++++++ 2 files changed, 110 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/commandline.cpp b/src/commandline.cpp index 11f43dea..819ac1c6 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -5,9 +5,47 @@ namespace cl { +std::string pad_right(std::string s, std::size_t n, char c=' ') +{ + if (s.size() < n) + s.append(n - s.size() , c); + + return s; +} + +std::string table( + const std::vector>& v, + std::size_t indent, std::size_t spacing) +{ + std::size_t longest = 0; + + for (auto&& p : v) + longest = std::max(longest, p.first.size()); + + std::string s; + + for (auto&& p : v) + { + if (!s.empty()) + s += "\n"; + + s += + std::string(indent, ' ') + + pad_right(p.first, longest) + " " + + std::string(spacing, ' ') + + p.second; + } + + return s; + +} + + CommandLine::CommandLine() { createOptions(); + m_commands.push_back(std::make_unique()); + m_commands.push_back(std::make_unique()); m_commands.push_back(std::make_unique()); m_commands.push_back(std::make_unique()); } @@ -156,11 +194,14 @@ std::string CommandLine::usage(const Command* c) const << "\n" << "Commands:\n"; + std::vector> v; for (auto&& c : m_commands) { - oss << " " << c->name() << " " << c->description() << "\n"; + v.push_back({c->name(), c->description()}); } - oss << "\n"; + oss + << table(v, 2, 4) << "\n" + << "\n"; } oss @@ -353,7 +394,7 @@ po::options_description LaunchCommand::doOptions() const Command::Meta LaunchCommand::meta() const { - return {"launch", ""}; + return {"launch", "(internal, do not use)"}; } std::optional LaunchCommand::doRun() @@ -421,4 +462,46 @@ LPCWSTR LaunchCommand::UntouchedCommandLineArguments( return cmd; } + +bool ExeCommand::allow_unregistered() const +{ + return true; +} + +po::options_description ExeCommand::doOptions() const +{ + return {}; +} + +Command::Meta ExeCommand::meta() const +{ + return {"exe", "launches a configured executable"}; +} + +std::optional ExeCommand::doRun() +{ + return {}; +} + + +bool RunCommand::allow_unregistered() const +{ + return true; +} + +po::options_description RunCommand::doOptions() const +{ + return {}; +} + +Command::Meta RunCommand::meta() const +{ + return {"run", "launches an arbitrary program"}; +} + +std::optional RunCommand::doRun() +{ + return {}; +} + } // namespace diff --git a/src/commandline.h b/src/commandline.h index ecc19c89..f633518b 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -87,6 +87,30 @@ protected: }; +class ExeCommand : public Command +{ +public: + bool allow_unregistered() const override; + +protected: + po::options_description doOptions() const; + Meta meta() const override; + std::optional doRun() override; +}; + + +class RunCommand : public Command +{ +public: + bool allow_unregistered() const override; + +protected: + po::options_description doOptions() const; + Meta meta() const override; + std::optional doRun() override; +}; + + class CommandLine { public: -- cgit v1.3.1 From cf289d7f95f6b5730cceaa8e6e22b4b33cf050c8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jul 2020 22:17:58 -0400 Subject: command usage lines, exe options --- src/commandline.cpp | 178 ++++++++++++++++++++++++++++++++++++++-------------- src/commandline.h | 27 ++++---- 2 files changed, 147 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/commandline.cpp b/src/commandline.cpp index 819ac1c6..3f8a6b1a 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -67,6 +67,7 @@ std::optional CommandLine::run(const std::wstring& line) .run(); po::store(parsed, m_vm); + po::notify(m_vm); auto opts = po::collect_unrecognized( parsed.options, po::include_positional); @@ -80,28 +81,43 @@ std::optional CommandLine::run(const std::wstring& line) // remove the command name itself opts.erase(opts.begin()); - auto co = c->options(); - co.add_options() - ("help", "shows this message"); + try + { + po::wcommand_line_parser parser(opts); - po::wcommand_line_parser parser(opts); - parser.options(co); + auto co = c->allOptions(); + parser.options(co); - if (c->allow_unregistered()) { - parser.allow_unregistered(); - } + if (c->allow_unregistered()) { + parser.allow_unregistered(); + } - parsed = parser.run(); + auto pos = c->positional(); + parser.positional(pos); - po::store(parsed, m_vm); + parsed = parser.run(); - if (m_vm.count("help")) { - env::Console console; - std::cout << usage(c.get()) << "\n"; - return 0; + po::store(parsed, m_vm); + po::notify(m_vm); + + if (m_vm.count("help")) { + env::Console console; + std::cout << usage(c.get()) << "\n"; + return 0; + } + + return c->run(line, m_vm, opts); } + catch(po::error& e) + { + env::Console console; - return c->run(line, m_vm, opts); + std::cerr + << e.what() << "\n" + << usage(c.get()) << "\n"; + + return 1; + } } } } @@ -157,7 +173,7 @@ void CommandLine::createOptions() { m_visibleOptions.add_options() ("help", "show this message") - ("multiple", "allow multiple instances of MO to run; see below") + ("multiple", "allow multiple MO processes to run; see below") ("instance,i", po::value(), "use the given instance (defaults to last used)") ("profile,p", po::value(), "use the given profile (defaults to last used)"); @@ -184,10 +200,10 @@ std::string CommandLine::usage(const Command* c) const if (c) { oss - << " ModOrganizer.exe [options] " << c->name() << " [command-options]\n" + << " ModOrganizer.exe [global-options] " << c->usageLine() << "\n" << "\n" << "Command options:\n" - << c->options() << "\n"; + << c->visibleOptions() << "\n"; } else { oss << " ModOrganizer.exe [options] [[command] [command-options]]\n" @@ -263,18 +279,24 @@ const QStringList& CommandLine::untouched() const std::string CommandLine::more() const { return - "Multiple instances\n" - " --multiple can be used to allow multiple instances of MO to run\n" + "Multiple processes\n" + " A note on terminology: 'instance' can either mean an MO process\n" + " that's running on the system, or a set of mods and profiles managed\n" + " by MO. To avoid confusion, the term 'process' is used below for the\n" + " former.\n" + " \n" + " --multiple can be used to allow multiple MO processes to run\n" " simultaneously. This is unsupported and can create all sorts of weird\n" - " problems. To minimize the problems:\n" + " problems. To minimize these:\n" " \n" - " 1) Never have multiple MO instances opened that manage the same\n" + " 1) Never have multiple MO processes running that manage the same\n" " game instance.\n" - " 2) If an executable is launched from an instance, only this\n" - " instance may launch executables until all instances are closed.\n" + " 2) If an executable is launched from an MO process, only this\n" + " process may launch executables until all processes are \n" + " terminated.\n" " \n" - " It is recommended to close _all_ instances of MO as soon as multiple\n" - " instances become unnecessary."; + " It is recommended to close _all_ MO processes as soon as multiple\n" + " processes become unnecessary."; } @@ -288,22 +310,65 @@ std::string Command::description() const return meta().description; } +std::string Command::usageLine() const +{ + return name() + " " + getUsageLine(); +} + bool Command::allow_unregistered() const { return false; } -po::options_description Command::options() const +po::options_description Command::allOptions() const +{ + po::options_description d; + + d.add(visibleOptions()); + d.add(getInternalOptions()); + + return d; +} + +po::options_description Command::visibleOptions() const +{ + po::options_description d(getVisibleOptions()); + + d.add_options() + ("help", "shows this message"); + + return d; +} + +po::positional_options_description Command::positional() const +{ + return getPositional(); +} + +std::string Command::getUsageLine() const +{ + return "[options]"; +} + +po::options_description Command::getVisibleOptions() const +{ + // no-op + return {}; +} + +po::options_description Command::getInternalOptions() const { - return doOptions(); + // no-op + return {}; } -po::options_description Command::doOptions() const +po::positional_options_description Command::getPositional() const { // no-op return {}; } + std::string Command::usage() const { std::ostringstream oss; @@ -314,7 +379,7 @@ std::string Command::usage() const << " ModOrganizer.exe [options] [[command] [command-options]]\n" << "\n" << "Options:\n" - << options() << "\n"; + << visibleOptions() << "\n"; return oss.str(); } @@ -347,7 +412,7 @@ const std::vector& Command::untouched() const } -po::options_description CrashDumpCommand::doOptions() const +po::options_description CrashDumpCommand::getVisibleOptions() const { po::options_description d; @@ -387,11 +452,6 @@ bool LaunchCommand::allow_unregistered() const return true; } -po::options_description LaunchCommand::doOptions() const -{ - return {}; -} - Command::Meta LaunchCommand::meta() const { return {"launch", "(internal, do not use)"}; @@ -463,14 +523,39 @@ LPCWSTR LaunchCommand::UntouchedCommandLineArguments( } -bool ExeCommand::allow_unregistered() const +std::string ExeCommand::getUsageLine() const { - return true; + return "[options] exe-name"; } -po::options_description ExeCommand::doOptions() const +po::options_description ExeCommand::getVisibleOptions() const { - return {}; + po::options_description d; + + d.add_options() + ("arguments,a", po::value()->default_value(""), "override arguments") + ("cwd,c", po::value()->default_value(""), "override working directory"); + + return d; +} + +po::options_description ExeCommand::getInternalOptions() const +{ + po::options_description d; + + d.add_options() + ("exe-name", po::value()->required(), "executable name"); + + return d; +} + +po::positional_options_description ExeCommand::getPositional() const +{ + po::positional_options_description d; + + d.add("exe-name", 1); + + return d; } Command::Meta ExeCommand::meta() const @@ -480,16 +565,17 @@ Command::Meta ExeCommand::meta() const std::optional ExeCommand::doRun() { - return {}; -} + const auto exe = vm()["exe-name"].as(); + const auto args = vm()["arguments"].as(); + const auto cwd = vm()["cwd"].as(); -bool RunCommand::allow_unregistered() const -{ - return true; + + return 0; } -po::options_description RunCommand::doOptions() const + +po::options_description RunCommand::getOptions() const { return {}; } diff --git a/src/commandline.h b/src/commandline.h index f633518b..0e300327 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -17,8 +17,11 @@ public: std::string name() const; std::string description() const; + std::string usageLine() const; - po::options_description options() const; + po::options_description allOptions() const; + po::options_description visibleOptions() const; + po::positional_options_description positional() const; std::string usage() const; virtual bool allow_unregistered() const; @@ -34,7 +37,11 @@ protected: std::string name, description; }; - virtual po::options_description doOptions() const; + virtual std::string getUsageLine() const; + virtual po::options_description getVisibleOptions() const; + virtual po::options_description getInternalOptions() const; + virtual po::positional_options_description getPositional() const; + virtual Meta meta() const = 0; virtual std::optional doRun() = 0; @@ -52,7 +59,7 @@ private: class CrashDumpCommand : public Command { protected: - po::options_description doOptions() const; + po::options_description getVisibleOptions() const override; Meta meta() const override; std::optional doRun() override; }; @@ -76,7 +83,6 @@ public: bool allow_unregistered() const override; protected: - po::options_description doOptions() const; Meta meta() const override; std::optional doRun() override; @@ -89,11 +95,11 @@ protected: class ExeCommand : public Command { -public: - bool allow_unregistered() const override; - protected: - po::options_description doOptions() const; + std::string getUsageLine() const override; + po::options_description getVisibleOptions() const override; + po::options_description getInternalOptions() const override; + po::positional_options_description getPositional() const override; Meta meta() const override; std::optional doRun() override; }; @@ -101,11 +107,8 @@ protected: class RunCommand : public Command { -public: - bool allow_unregistered() const override; - protected: - po::options_description doOptions() const; + po::options_description getOptions() const; Meta meta() const override; std::optional doRun() override; }; -- cgit v1.3.1 From 93d1c97fdaff456943ca398562ac04d6c7f4ed6a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jul 2020 22:30:24 -0400 Subject: cleaned up includes --- src/main.cpp | 61 +++++------------------------------------------------------- 1 file changed, 5 insertions(+), 56 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 5ae8999c..23ec91da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,83 +17,32 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ - -#ifdef LEAK_CHECK_WITH_VLD -#include -#include -#endif // LEAK_CHECK_WITH_VLD - -#define WIN32_LEAN_AND_MEAN -#include -#include - -#include "shared/appconfig.h" -#include -#include #include "mainwindow.h" -#include -#include "modlist.h" -#include "profile.h" -#include "spawn.h" -#include "executableslist.h" #include "singleinstance.h" -#include "utility.h" #include "loglist.h" #include "selectiondialog.h" #include "moapplication.h" #include "tutorialmanager.h" #include "nxmaccessmanager.h" #include "instancemanager.h" -#include "moshortcut.h" #include "organizercore.h" #include "env.h" #include "envmodule.h" -#include "shared/util.h" #include "commandline.h" -#include -#include "shared/windows_error.h" +#include "shared/util.h" +#include "shared/appconfig.h" + +#include #include #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - +#include #pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") - using namespace MOBase; using namespace MOShared; - void sanityChecks(const env::Environment& env); int checkIncompatibleModule(const env::Module& m); int checkPathsForSanity(MOBase::IPluginGame& game, const Settings& s); -- cgit v1.3.1 From 3d1d9f111025879983147fbbe78f8af04693d066 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jul 2020 22:32:12 -0400 Subject: removed unused HaveWriteAccess() stop useless preloading of ssl dlls --- src/main.cpp | 101 ++--------------------------------------------------------- 1 file changed, 2 insertions(+), 99 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 23ec91da..a335a37a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -123,57 +123,6 @@ void setUnhandledExceptionHandler() prevTerminateHandler = std::set_terminate(terminateHandler); } - -static bool HaveWriteAccess(const std::wstring &path) -{ - bool writable = false; - - const static SECURITY_INFORMATION requestedFileInformation = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; - - DWORD length = 0; - if (!::GetFileSecurityW(path.c_str(), requestedFileInformation, nullptr, 0UL, &length) - && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { - std::string tempBuffer; - tempBuffer.reserve(length); - PSECURITY_DESCRIPTOR security = (PSECURITY_DESCRIPTOR)tempBuffer.data(); - if (security - && ::GetFileSecurity(path.c_str(), requestedFileInformation, security, length, &length)) { - HANDLE token = nullptr; - const static DWORD tokenDesiredAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ; - if (!::OpenThreadToken(::GetCurrentThread(), tokenDesiredAccess, TRUE, &token)) { - if (!::OpenProcessToken(::GetCurrentProcess(), tokenDesiredAccess, &token)) { - throw std::runtime_error("Unable to get any thread or process token"); - } - } - - HANDLE impersonatedToken = nullptr; - if (::DuplicateToken(token, SecurityImpersonation, &impersonatedToken)) { - GENERIC_MAPPING mapping = { 0xFFFFFFFF }; - mapping.GenericRead = FILE_GENERIC_READ; - mapping.GenericWrite = FILE_GENERIC_WRITE; - mapping.GenericExecute = FILE_GENERIC_EXECUTE; - mapping.GenericAll = FILE_ALL_ACCESS; - - DWORD genericAccessRights = FILE_GENERIC_WRITE; - ::MapGenericMask(&genericAccessRights, &mapping); - - PRIVILEGE_SET privileges = { 0 }; - DWORD grantedAccess = 0; - DWORD privilegesLength = sizeof(privileges); - BOOL result = 0; - if (::AccessCheck(security, impersonatedToken, genericAccessRights, &mapping, &privileges, &privilegesLength, &grantedAccess, &result)) { - writable = result != 0; - } - ::CloseHandle(impersonatedToken); - } - - ::CloseHandle(token); - } - } - return writable; -} - - QString determineProfile(const cl::CommandLine& cl, const Settings &settings) { auto selectedProfileName = settings.game().selectedProfileName(); @@ -383,47 +332,6 @@ void setupPath() ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); } -void preloadDll(const QString& filename) -{ - if (GetModuleHandleW(filename.toStdWString().c_str())) { - // already loaded, this can happen when "restarting" MO by switching - // instances, for example - return; - } - - const auto appPath = QDir::toNativeSeparators( - QCoreApplication::applicationDirPath()); - - const auto dllPath = appPath + "\\" + filename; - - if (!QFile::exists(dllPath)) { - log::warn("{} not found", dllPath); - return; - } - - if (!LoadLibraryW(dllPath.toStdWString().c_str())) { - const auto e = GetLastError(); - log::warn("failed to load {}: {}", dllPath, formatSystemMessage(e)); - } -} - -void preloadSsl() -{ -#if Q_PROCESSOR_WORDSIZE == 8 - preloadDll("libcrypto-1_1-x64.dll"); - preloadDll("libssl-1_1-x64.dll"); -#elif Q_PROCESSOR_WORDSIZE == 4 - preloadDll("libcrypto-1_1.dll"); - preloadDll("libssl-1_1.dll"); -#endif -} - -static QString getVersionDisplayString() -{ - return createVersionInfo().displayString(3); -} - - int runApplication( MOApplication &application, const cl::CommandLine& cl, SingleInstance &instance, const QString &splashPath) @@ -432,13 +340,8 @@ int runApplication( log::info( "starting Mod Organizer version {} revision {} in {}, usvfs: {}", - getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath(), - MOShared::getUsvfsVersionString()); - - preloadSsl(); - if (!QSslSocket::supportsSsl()) { - log::warn("no ssl support"); - } + createVersionInfo().displayString(3), GITID, + QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString()); const QString dataPath = application.property("dataPath").toString(); log::info("data path: {}", dataPath); -- cgit v1.3.1 From f97e40f127441828eb4ffe11841ebf79191ac8a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jul 2020 22:35:40 -0400 Subject: moved initLogging() to loglist.cpp --- src/loglist.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/loglist.h | 3 +++ src/main.cpp | 67 ++------------------------------------------------------- 3 files changed, 70 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/loglist.cpp b/src/loglist.cpp index a4868ed4..7a64ecad 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -262,3 +262,68 @@ void LogList::onContextMenu(const QPoint& pos) auto* menu = createMenu(this); menu->popup(viewport()->mapToGlobal(pos)); } + + +log::Levels convertQtLevel(QtMsgType t) +{ + switch (t) + { + case QtDebugMsg: + return log::Debug; + + case QtWarningMsg: + return log::Warning; + + case QtCriticalMsg: // fall-through + case QtFatalMsg: + return log::Error; + + case QtInfoMsg: // fall-through + default: + return log::Info; + } +} + +void qtLogCallback( + QtMsgType type, const QMessageLogContext& context, const QString& message) +{ + std::string_view file = ""; + + if (type != QtDebugMsg) { + if (context.file) { + file = context.file; + + const auto lastSep = file.find_last_of("/\\"); + if (lastSep != std::string_view::npos) { + file = {context.file + lastSep + 1}; + } + } + } + + if (file.empty()) { + log::log( + convertQtLevel(type), "{}", + message.toStdString()); + } else { + log::log( + convertQtLevel(type), "[{}:{}] {}", + file, context.line, message.toStdString()); + } +} + +void initLogging() +{ + LogModel::create(); + + log::LoggerConfiguration conf; + conf.maxLevel = MOBase::log::Debug; + conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$"; + conf.utc = true; + + log::createDefault(conf); + + log::getDefault().setCallback( + [](log::Entry e){ LogModel::instance().add(e); }); + + qInstallMessageHandler(qtLogCallback); +} diff --git a/src/loglist.h b/src/loglist.h index 6b3aa4d5..7387eb50 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -82,4 +82,7 @@ private: void onNewEntry(); }; + +void initLogging(); + #endif // LOGBUFFER_H diff --git a/src/main.cpp b/src/main.cpp index a335a37a..e52b252b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -599,71 +599,6 @@ int runApplication( return 1; } -log::Levels convertQtLevel(QtMsgType t) -{ - switch (t) - { - case QtDebugMsg: - return log::Debug; - - case QtWarningMsg: - return log::Warning; - - case QtCriticalMsg: // fall-through - case QtFatalMsg: - return log::Error; - - case QtInfoMsg: // fall-through - default: - return log::Info; - } -} - -void qtLogCallback( - QtMsgType type, const QMessageLogContext& context, const QString& message) -{ - std::string_view file = ""; - - if (type != QtDebugMsg) { - if (context.file) { - file = context.file; - - const auto lastSep = file.find_last_of("/\\"); - if (lastSep != std::string_view::npos) { - file = {context.file + lastSep + 1}; - } - } - } - - if (file.empty()) { - log::log( - convertQtLevel(type), "{}", - message.toStdString()); - } else { - log::log( - convertQtLevel(type), "[{}:{}] {}", - file, context.line, message.toStdString()); - } -} - -void initLogging() -{ - LogModel::create(); - - log::LoggerConfiguration conf; - conf.maxLevel = MOBase::log::Debug; - conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$"; - conf.utc = true; - - log::createDefault(conf); - - log::getDefault().setCallback( - [](log::Entry e){ LogModel::instance().add(e); }); - - qInstallMessageHandler(qtLogCallback); -} - - int main(int argc, char *argv[]) { cl::CommandLine cl; @@ -673,6 +608,8 @@ int main(int argc, char *argv[]) return *r; TimeThis tt("main to runApplication()"); + + // in loglist.cpp initLogging(); //Make sure the configured temp folder exists -- cgit v1.3.1 From 9435202034cafb05ffc11aed48ff57536bce73f7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jul 2020 23:27:01 -0400 Subject: removed flags from SingleInstance because there's only one left refactoring in main.cpp: - moved stuff to loglist.cpp and moapplication.cpp - split main() into a few functions --- src/env.cpp | 13 ++- src/env.h | 3 +- src/loglist.cpp | 26 ++++++ src/loglist.h | 1 + src/main.cpp | 225 ++++++++++++++++++++++++------------------------- src/moapplication.cpp | 19 ++--- src/moapplication.h | 20 ++--- src/singleinstance.cpp | 4 +- src/singleinstance.h | 18 +--- src/spawn.cpp | 2 +- 10 files changed, 174 insertions(+), 157 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index bf75c9fa..9f0acbbd 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -400,11 +400,18 @@ QString path() return get("PATH"); } -QString addPath(const QString& s) +QString appendToPath(const QString& s) { auto old = path(); - set("PATH", get("PATH") + ";" + s); - return old; + set("PATH", old + ";" + s); + return old; +} + +QString prependToPath(const QString& s) +{ + auto old = path(); + set("PATH", s + ";" + old); + return old; } QString setPath(const QString& s) diff --git a/src/env.h b/src/env.h index 9bec1713..a563f2c3 100644 --- a/src/env.h +++ b/src/env.h @@ -236,7 +236,8 @@ QString get(const QString& name); QString set(const QString& name, const QString& value); QString path(); -QString addPath(const QString& s); +QString appendToPath(const QString& s); +QString prependToPath(const QString& s); QString setPath(const QString& s); diff --git a/src/loglist.cpp b/src/loglist.cpp index 7a64ecad..167b61ef 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -327,3 +327,29 @@ void initLogging() qInstallMessageHandler(qtLogCallback); } + +bool createAndMakeWritable(const std::wstring &subPath) { + QString const dataPath = qApp->property("dataPath").toString(); + QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); + + if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("Failed to create \"%1\". Your user " + "account probably lacks permission.") + .arg(fullPath)); + return false; + } else { + return true; + } +} + +bool setLogDirectory(const QString& dir) +{ + const auto logFile = dir + "/logs/mo_interface.log"; + + if (!createAndMakeWritable(AppConfig::logPath())) { + return false; + } + + log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString())); +} diff --git a/src/loglist.h b/src/loglist.h index 7387eb50..0745ed3e 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -84,5 +84,6 @@ private: void initLogging(); +bool setLogDirectory(const QString& dir); #endif // LOGBUFFER_H diff --git a/src/main.cpp b/src/main.cpp index e52b252b..864d26a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,7 +38,13 @@ along with Mod Organizer. If not, see . #include #include -#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") +// see addDllsToPath() below +#pragma comment(linker, "/manifestDependency:\"" \ + "name='dlls' " \ + "processorArchitecture='x86' " \ + "version='1.0.0.0' " \ + "type='win32' \"") + using namespace MOBase; using namespace MOShared; @@ -47,21 +53,6 @@ void sanityChecks(const env::Environment& env); int checkIncompatibleModule(const env::Module& m); int checkPathsForSanity(MOBase::IPluginGame& game, const Settings& s); -bool createAndMakeWritable(const std::wstring &subPath) { - QString const dataPath = qApp->property("dataPath").toString(); - QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); - - if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) { - QMessageBox::critical(nullptr, QObject::tr("Error"), - QObject::tr("Failed to create \"%1\". Your user " - "account probably lacks permission.") - .arg(fullPath)); - return false; - } else { - return true; - } -} - void purgeOldFiles() { // remove the temporary backup directory in case we're restarting after an @@ -305,36 +296,47 @@ MOBase::IPluginGame *determineCurrentGame( } -// extend path to include dll directory so plugins don't need a manifest -// (using AddDllDirectory would be an alternative to this but it seems fairly -// complicated esp. -// since it isn't easily accessible on Windows < 8 -// SetDllDirectory replaces other search directories and this seems to -// propagate to child processes) -void setupPath() +// This adds the `dlls` directory to the path so the dlls can be found. How +// MO is able to find dlls in there is a bit convoluted: +// +// Dependencies on DLLs can be baked into an executable by passing a +// `manifestdependency` option to the linker. This can be done on the command +// line or with a pragma. Typically, the dependency will not be a hardcoded +// filename, but an assembly name, such as Microsoft.Windows.Common-Controls. +// +// When Windows loads the exe, it will look for this assembly in a variety of +// places, such as in the WinSxS folder, but also in the program's folder. It +// will look for `assemblyname.dll` or `assemblyname/assemblyname.dll` and try +// to load that. +// +// If these files don't exist, then the loader gets creative and looks for +// `assemblyname.manifest` and `assemblyname/assemblyname.manifest`. A manifest +// file is just an XML file that can contain a list of DLLs to load for this +// assembly. +// +// In MO's case, there's a `pragma` at the beginning of this file which adds +// `dlls` as an "assembly" dependency. This is a bit of a hack to just force +// the loader to eventually find `dlls/dlls.manifest`, which contains the list +// of all the DLLs MO requires to load. +// +// This file was handwritten in `modorganizer/src/dlls.manifest.qt5` and +// is copied and renamed in CMakeLists.txt into `bin/dlls/dlls.manifest`. Note +// that the useless and incorrect .qt5 extension is removed. +// +void addDllsToPath() { - static const int BUFSIZE = 4096; - - QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); + const auto dllsPath = QDir::toNativeSeparators( + QCoreApplication::applicationDirPath() + "/dlls"); - boost::scoped_array oldPath(new TCHAR[BUFSIZE]); - DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); - if (offset > BUFSIZE) { - oldPath.reset(new TCHAR[offset]); - ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); - } - - std::wstring newPath(ToWString(QDir::toNativeSeparators( - QCoreApplication::applicationDirPath())) + L"\\dlls"); - newPath += L";"; - newPath += oldPath.get(); + QCoreApplication::setLibraryPaths( + QStringList(dllsPath) + QCoreApplication::libraryPaths()); - ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); + env::prependToPath(dllsPath); } int runApplication( MOApplication &application, const cl::CommandLine& cl, - SingleInstance &instance, const QString &splashPath) + SingleInstance &instance, const QString &dataPath) { TimeThis tt("runApplication() to exec()"); @@ -343,7 +345,6 @@ int runApplication( createVersionInfo().displayString(3), GITID, QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString()); - const QString dataPath = application.property("dataPath").toString(); log::info("data path: {}", dataPath); if (InstanceManager::isPortablePath(dataPath)) { @@ -429,8 +430,14 @@ int runApplication( checkPathsForSanity(*game, settings); bool useSplash = settings.useSplash(); + QString splashPath; if (useSplash) { + splashPath = dataPath + "/splash.png"; + if (!QFile::exists(dataPath + "/splash.png")) { + splashPath = ":/MO/gui/splash"; + } + if (splashPath.startsWith(':')) { // currently using MO splash, see if the plugin contains one QString pluginSplash @@ -599,111 +606,101 @@ int runApplication( return 1; } -int main(int argc, char *argv[]) +int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl) { - cl::CommandLine cl; - - const auto r = cl.run(GetCommandLineW()); - if (r) - return *r; - - TimeThis tt("main to runApplication()"); - - // in loglist.cpp - initLogging(); + if (cl.shortcut().isValid()) { + instance.sendMessage(cl.shortcut().toString()); + } else if (cl.nxmLink()) { + instance.sendMessage(*cl.nxmLink()); + } else { + QMessageBox::information( + nullptr, QObject::tr("Mod Organizer"), + QObject::tr("An instance of Mod Organizer is already running")); + } - //Make sure the configured temp folder exists - QDir tempDir = QDir::temp(); - if (!tempDir.exists()) - tempDir.root().mkpath(tempDir.canonicalPath()); + return 0; +} - //Should allow for better scaling of ui with higher resolution displays - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +void resetForRestart(cl::CommandLine& cl) +{ + LogModel::instance().clear(); + ResetExitFlag(); - MOApplication application(argc, argv); - QStringList arguments = application.arguments(); + // make sure the log file isn't locked in case MO was restarted and + // the previous instance gets deleted + log::getDefault().setFile({}); - SetThisThreadName("main"); + // don't reprocess command line + cl.clear(); +} - setupPath(); +QString determineDataPath(const cl::CommandLine& cl) +{ + try + { + InstanceManager& instanceManager = InstanceManager::instance(); + if (cl.instance()) + instanceManager.overrideInstance(*cl.instance()); - SingleInstance::Flags siFlags = SingleInstance::NoFlags; + return instanceManager.determineDataPath(); + } + catch (const std::exception &e) + { + if (strcmp(e.what(),"Canceled")) { + QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what()); + } - if (cl.multiple()) { - arguments.removeAll("--multiple"); - siFlags |= SingleInstance::AllowMultiple; + return {}; } +} - SingleInstance instance(siFlags); - if (instance.ephemeral()) { - if (cl.shortcut().isValid()) { - instance.sendMessage(cl.shortcut().toString()); - return 0; - } else if (cl.nxmLink()) { - instance.sendMessage(*cl.nxmLink()); - return 0; - } else if (arguments.size() == 1) { - QMessageBox::information( - nullptr, QObject::tr("Mod Organizer"), - QObject::tr("An instance of Mod Organizer is already running")); - return 0; - } - } // we continue for the primary instance OR if MO was called with parameters +int main(int argc, char *argv[]) +{ + cl::CommandLine cl; - do { - LogModel::instance().clear(); - ResetExitFlag(); + if (auto r=cl.run(GetCommandLineW())) { + return *r; + } - // make sure the log file isn't locked in case MO was restarted and - // the previous instance gets deleted - log::getDefault().setFile({}); + TimeThis tt("main to runApplication()"); + SetThisThreadName("main"); - QString dataPath; + initLogging(); + auto application = MOApplication::create(argc, argv); + addDllsToPath(); - try { - InstanceManager& instanceManager = InstanceManager::instance(); + SingleInstance instance(cl.multiple()); + if (instance.ephemeral()) { + return forwardToPrimary(instance, cl); + } - if (cl.instance()) - instanceManager.overrideInstance(*cl.instance()); + for (;;) + { + // resets things when MO is "restarted" + resetForRestart(cl); - dataPath = instanceManager.determineDataPath(); - } catch (const std::exception &e) { - if (strcmp(e.what(),"Canceled")) - QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what()); + const QString dataPath = determineDataPath(cl); + if (dataPath.isEmpty()) { return 1; } - application.setProperty("dataPath", dataPath); - - // initialize dump collection only after "dataPath" since the crashes are stored under it - setUnhandledExceptionHandler(); - const auto logFile = - qApp->property("dataPath").toString() + "/logs/mo_interface.log"; + application.setProperty("dataPath", dataPath); + setExceptionHandler(); - if (!createAndMakeWritable(AppConfig::logPath())) { + if (!setLogDirectory(dataPath)) { reportError("Failed to create log folder"); InstanceManager::instance().clearCurrentInstance(); return 1; } - log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString())); - log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW())); - QString splash = dataPath + "/splash.png"; - if (!QFile::exists(dataPath + "/splash.png")) { - splash = ":/MO/gui/splash"; - } - tt.stop(); - const int result = runApplication(application, cl, instance, splash); + const int result = runApplication(application, cl, instance, dataPath); if (result != RestartExitCode) { return result; } - - argc = 1; - cl.clear(); - } while (true); + } } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index dd49bf53..d95d544a 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -24,16 +24,10 @@ along with Mod Organizer. If not, see . #include "shared/appconfig.h" #include #include -#if QT_VERSION < QT_VERSION_CHECK(5,0,0) -#include -#include -#endif #include #include #include #include - - #include @@ -77,7 +71,7 @@ public: }; -MOApplication::MOApplication(int &argc, char **argv) +MOApplication::MOApplication(int argc, char** argv) : QApplication(argc, argv) { connect(&m_StyleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){ @@ -89,8 +83,13 @@ MOApplication::MOApplication(int &argc, char **argv) setStyle(new ProxyStyle(style())); } +MOApplication MOApplication::create(int argc, char** argv) +{ + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + return MOApplication(argc, argv); +} -bool MOApplication::setStyleFile(const QString &styleName) +bool MOApplication::setStyleFile(const QString& styleName) { // remove all files from watch QStringList currentWatch = m_StyleWatcher.files(); @@ -114,7 +113,7 @@ bool MOApplication::setStyleFile(const QString &styleName) } -bool MOApplication::notify(QObject *receiver, QEvent *event) +bool MOApplication::notify(QObject* receiver, QEvent* event) { try { return QApplication::notify(receiver, event); @@ -134,7 +133,7 @@ bool MOApplication::notify(QObject *receiver, QEvent *event) } -void MOApplication::updateStyle(const QString &fileName) +void MOApplication::updateStyle(const QString& fileName) { if (QStyleFactory::keys().contains(fileName)) { setStyle(QStyleFactory::create(fileName)); diff --git a/src/moapplication.h b/src/moapplication.h index 9db130af..3ed71fb6 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -24,27 +24,25 @@ along with Mod Organizer. If not, see . #include -class MOApplication : public QApplication { -Q_OBJECT -public: - - MOApplication(int &argc, char **argv); +class MOApplication : public QApplication +{ + Q_OBJECT - virtual bool notify (QObject *receiver, QEvent *event); +public: + static MOApplication create(int argc, char** argv); + virtual bool notify (QObject* receiver, QEvent* event); public slots: - - bool setStyleFile(const QString &style); + bool setStyleFile(const QString& style); private slots: - - void updateStyle(const QString &fileName); + void updateStyle(const QString& fileName); private: - QFileSystemWatcher m_StyleWatcher; QString m_DefaultStyle; + MOApplication(int argc, char** argv); }; diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index e32c8de1..bd7ccc43 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -28,14 +28,14 @@ static const int s_Timeout = 5000; using MOBase::reportError; -SingleInstance::SingleInstance(Flags flags, QObject *parent) : +SingleInstance::SingleInstance(bool allowMultiple, QObject *parent) : QObject(parent), m_Ephemeral(false), m_OwnsSM(false) { m_SharedMem.setKey(s_Key); if (!m_SharedMem.create(1)) { if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - if (!flags.testFlag(AllowMultiple)) { + if (!allowMultiple) { m_SharedMem.attach(); m_Ephemeral = true; } diff --git a/src/singleinstance.h b/src/singleinstance.h index d500a056..5f6c3633 100644 --- a/src/singleinstance.h +++ b/src/singleinstance.h @@ -36,19 +36,9 @@ class SingleInstance : public QObject Q_OBJECT public: - enum Flag - { - NoFlags = 0x00, - - // if another instance is running, run this one disconnected from the - // shared memory - AllowMultiple = 0x01 - }; - - using Flags = QFlags; - - - explicit SingleInstance(Flags flags, QObject *parent = 0); + // `allowMultiple`: if another instance is running, run this one + // disconnected from the shared memory + explicit SingleInstance(bool allowMultiple, QObject *parent = 0); /** * @return true if this instance's job is to forward data to the primary @@ -97,6 +87,4 @@ private: }; -Q_DECLARE_OPERATORS_FOR_FLAGS(SingleInstance::Flags); - #endif // SINGLEINSTANCE_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 2016bb23..a9ecb61e 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -488,7 +488,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) } const QString moPath = QCoreApplication::applicationDirPath(); - const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath)); + const auto oldPath = env::appendToPath(QDir::toNativeSeparators(moPath)); PROCESS_INFORMATION pi = {}; BOOL success = FALSE; -- cgit v1.3.1 From ae118153fbedc0240ea183488834a32abe202839 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 00:38:03 -0400 Subject: more refactoring: - moved splash stuff together - moved stuff in the main loop into doOneRun() --- src/loglist.cpp | 2 + src/main.cpp | 213 ++++++++++++++++++++++++++++---------------------- src/moapplication.cpp | 4 +- src/moapplication.h | 4 +- src/organizercore.cpp | 44 +++++++++-- src/settings.cpp | 2 +- src/settings.h | 2 +- 7 files changed, 164 insertions(+), 107 deletions(-) (limited to 'src') diff --git a/src/loglist.cpp b/src/loglist.cpp index 167b61ef..fad4678c 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -352,4 +352,6 @@ bool setLogDirectory(const QString& dir) } log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString())); + + return true; } diff --git a/src/main.cpp b/src/main.cpp index 864d26a1..52011ef4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,26 +68,31 @@ void purgeOldFiles() "usvfs*.log", 5, QDir::Name); } -thread_local LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr; -thread_local std::terminate_handler prevTerminateHandler = nullptr; +thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr; +thread_local std::terminate_handler g_prevTerminateHandler = nullptr; -LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs) +LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs) { const std::wstring& dumpPath = OrganizerCore::crashDumpsPath(); - int dumpRes = - CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); - if (!dumpRes) + + const int r = CreateMiniDump( + ptrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); + + if (r == 0) { log::error("ModOrganizer has crashed, crash dump created."); - else - log::error("ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", dumpRes, GetLastError()); + } else { + log::error( + "ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", + r, GetLastError()); + } - if (prevUnhandledExceptionFilter && exceptionPtrs) - return prevUnhandledExceptionFilter(exceptionPtrs); + if (g_prevExceptionFilter && ptrs) + return g_prevExceptionFilter(ptrs); else return EXCEPTION_CONTINUE_SEARCH; } -void terminateHandler() noexcept +void onTerminate() noexcept { __try { @@ -96,22 +101,22 @@ void terminateHandler() noexcept } __except ( - MyUnhandledExceptionFilter(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER + onUnhandledException(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER ) { } - if (prevTerminateHandler) { - prevTerminateHandler(); + if (g_prevTerminateHandler) { + g_prevTerminateHandler(); } else { std::abort(); } } -void setUnhandledExceptionHandler() +void setExceptionHandlers() { - prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - prevTerminateHandler = std::set_terminate(terminateHandler); + g_prevExceptionFilter = SetUnhandledExceptionFilter(onUnhandledException); + g_prevTerminateHandler = std::set_terminate(onTerminate); } QString determineProfile(const cl::CommandLine& cl, const Settings &settings) @@ -334,6 +339,55 @@ void addDllsToPath() env::prependToPath(dllsPath); } +QString getSplashPath( + const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game) +{ + if (!settings.useSplash()) { + return {}; + } + + const QString splashPath = dataPath + "/splash.png"; + if (QFile::exists(dataPath + "/splash.png")) { + return splashPath; + } + + // currently using MO splash, see if the plugin contains one + QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName()); + QImage image(pluginSplash); + + if (image.isNull()) { + return {}; + } + + image.save(splashPath); + return splashPath; +} + +std::unique_ptr createSplash( + const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game) +{ + const auto splashPath = getSplashPath(settings, dataPath, game); + if (splashPath.isEmpty()) { + return {}; + } + + QPixmap image(splashPath); + if (image.isNull()) { + log::error("failed to load splash from {}", splashPath); + return {}; + } + + auto splash = std::make_unique(image); + settings.geometry().centerOnMainWindowMonitor(splash.get()); + + splash->show(); + splash->activateWindow(); + + return splash; +} + int runApplication( MOApplication &application, const cl::CommandLine& cl, SingleInstance &instance, const QString &dataPath) @@ -351,6 +405,8 @@ int runApplication( log::debug("this is a portable instance"); } + log::info("working directory: {}", QDir::currentPath()); + if (!instance.secondary()) { purgeOldFiles(); } @@ -358,9 +414,8 @@ int runApplication( QWindowsWindowFunctions::setWindowActivationBehavior( QWindowsWindowFunctions::AlwaysActivateWindow); - try { - log::info("working directory: {}", QDir::currentPath()); - + try + { Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); log::getDefault().setLevel(settings.diagnostics().logLevel()); @@ -370,7 +425,6 @@ int runApplication( log::debug("another instance of MO is running but --multiple was given"); } - // global crashDumpType sits in OrganizerCore to make a bit less ugly to // update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); @@ -385,8 +439,10 @@ int runApplication( checkIncompatibleModule(m); }); - log::debug("initializing core"); + // this must outlive `organizer` std::unique_ptr pluginContainer; + + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { reportError("failed to set up data paths"); @@ -394,25 +450,11 @@ int runApplication( return 1; } - { - // log if there are any dmp files - const auto hasCrashDumps = - !QDir(QString::fromStdWString(organizer.crashDumpsPath())) - .entryList({"*.dmp"}, QDir::Files) - .empty(); - - if (hasCrashDumps) { - log::debug( - "there are crash dumps in '{}'", - QString::fromStdWString(organizer.crashDumpsPath())); - } - } - log::debug("initializing plugins"); pluginContainer = std::make_unique(&organizer); pluginContainer->loadPlugins(); - MOBase::IPluginGame *game = determineCurrentGame( + MOBase::IPluginGame* game = determineCurrentGame( application.applicationDirPath(), settings, *pluginContainer); if (game == nullptr) { @@ -429,25 +471,6 @@ int runApplication( checkPathsForSanity(*game, settings); - bool useSplash = settings.useSplash(); - QString splashPath; - - if (useSplash) { - splashPath = dataPath + "/splash.png"; - if (!QFile::exists(dataPath + "/splash.png")) { - splashPath = ":/MO/gui/splash"; - } - - if (splashPath.startsWith(':')) { - // currently using MO splash, see if the plugin contains one - QString pluginSplash - = QString(":/%1/splash").arg(game->gameShortName()); - QImage image(pluginSplash); - if (!image.isNull()) { - image.save(dataPath + "/splash.png"); - } - } - } organizer.setManagedGame(game); organizer.createDefaultProfile(); @@ -535,18 +558,7 @@ int runApplication( } } - QPixmap pixmap; - - QSplashScreen splash; - - if (useSplash) { - pixmap = QPixmap(splashPath); - splash.setPixmap(pixmap); - - settings.geometry().centerOnMainWindowMonitor(&splash); - splash.show(); - splash.activateWindow(); - } + auto splash = createSplash(settings, dataPath, game); QString apiKey; if (settings.nexus().apiKey(apiKey)) { @@ -582,10 +594,10 @@ int runApplication( mainWindow.show(); mainWindow.activateWindow(); - if (useSplash) { + if (splash) { // don't pass mainwindow as it just waits half a second for it // instead of proceding - splash.finish(nullptr); + splash->finish(nullptr); } tt.stop(); @@ -655,6 +667,35 @@ QString determineDataPath(const cl::CommandLine& cl) } } +int doOneRun( + cl::CommandLine& cl, MOApplication& application, SingleInstance& instance) +{ + TimeThis tt("doOneRun() to runApplication()"); + + // resets things when MO is "restarted" + resetForRestart(cl); + + const QString dataPath = determineDataPath(cl); + if (dataPath.isEmpty()) { + return 1; + } + + application.setProperty("dataPath", dataPath); + setExceptionHandlers(); + + if (!setLogDirectory(dataPath)) { + reportError("Failed to create log folder"); + InstanceManager::instance().clearCurrentInstance(); + return 1; + } + + log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW())); + + tt.stop(); + + return runApplication(application, cl, instance, dataPath); +} + int main(int argc, char *argv[]) { cl::CommandLine cl; @@ -663,7 +704,8 @@ int main(int argc, char *argv[]) return *r; } - TimeThis tt("main to runApplication()"); + TimeThis tt("main() to doOneRun()"); + SetThisThreadName("main"); initLogging(); @@ -675,32 +717,15 @@ int main(int argc, char *argv[]) return forwardToPrimary(instance, cl); } + tt.stop(); + for (;;) { - // resets things when MO is "restarted" - resetForRestart(cl); - - const QString dataPath = determineDataPath(cl); - if (dataPath.isEmpty()) { - return 1; - } - - application.setProperty("dataPath", dataPath); - setExceptionHandler(); - - if (!setLogDirectory(dataPath)) { - reportError("Failed to create log folder"); - InstanceManager::instance().clearCurrentInstance(); - return 1; + const auto r = doOneRun(cl, application, instance); + if (r == RestartExitCode) { + continue; } - log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW())); - - tt.stop(); - - const int result = runApplication(application, cl, instance, dataPath); - if (result != RestartExitCode) { - return result; - } + return r; } } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index d95d544a..290666af 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -71,7 +71,7 @@ public: }; -MOApplication::MOApplication(int argc, char** argv) +MOApplication::MOApplication(int& argc, char** argv) : QApplication(argc, argv) { connect(&m_StyleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){ @@ -83,7 +83,7 @@ MOApplication::MOApplication(int argc, char** argv) setStyle(new ProxyStyle(style())); } -MOApplication MOApplication::create(int argc, char** argv) +MOApplication MOApplication::create(int& argc, char** argv) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); return MOApplication(argc, argv); diff --git a/src/moapplication.h b/src/moapplication.h index 3ed71fb6..c67c4622 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -29,7 +29,7 @@ class MOApplication : public QApplication Q_OBJECT public: - static MOApplication create(int argc, char** argv); + static MOApplication create(int& argc, char** argv); virtual bool notify (QObject* receiver, QEvent* event); public slots: @@ -42,7 +42,7 @@ private: QFileSystemWatcher m_StyleWatcher; QString m_DefaultStyle; - MOApplication(int argc, char** argv); + MOApplication(int& argc, char** argv); }; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a9469e6e..0849d756 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -473,13 +473,43 @@ bool OrganizerCore::checkPathSymlinks() { return true; } -bool OrganizerCore::bootstrap() { - return createDirectory(m_Settings.paths().profiles()) && - createDirectory(m_Settings.paths().mods()) && - createDirectory(m_Settings.paths().downloads()) && - createDirectory(m_Settings.paths().overwrite()) && - createDirectory(QString::fromStdWString(crashDumpsPath())) && - checkPathSymlinks() && cycleDiagnostics(); +bool OrganizerCore::bootstrap() +{ + const auto dirs = { + m_Settings.paths().profiles(), + m_Settings.paths().mods(), + m_Settings.paths().downloads(), + m_Settings.paths().overwrite(), + QString::fromStdWString(crashDumpsPath()) + }; + + for (auto&& dir : dirs) { + if (!createDirectory(dir)) { + return false; + } + } + + if (!checkPathSymlinks()) { + return false; + } + + if (!cycleDiagnostics()) { + return false; + } + + // log if there are any dmp files + const auto hasCrashDumps = + !QDir(QString::fromStdWString(crashDumpsPath())) + .entryList({"*.dmp"}, QDir::Files) + .empty(); + + if (hasCrashDumps) { + log::debug( + "there are crash dumps in '{}'", + QString::fromStdWString(crashDumpsPath())); + } + + return true; } void OrganizerCore::createDefaultProfile() diff --git a/src/settings.cpp b/src/settings.cpp index 534e67c8..d37f0d99 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -876,7 +876,7 @@ void GeometrySettings::setCenterDialogs(bool b) set(m_Settings, "Settings", "center_dialogs", b); } -void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) +void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) const { const auto monitor = getOptional( m_Settings, "Geometry", "MainWindow_monitor").value_or(-1); diff --git a/src/settings.h b/src/settings.h index 636719bb..a1b30462 100644 --- a/src/settings.h +++ b/src/settings.h @@ -169,7 +169,7 @@ public: // assumes the given widget is a top-level // - void centerOnMainWindowMonitor(QWidget* w); + void centerOnMainWindowMonitor(QWidget* w) const; // saves the monitor number of the given window // -- cgit v1.3.1 From 2c915429d84a9df0e9c512db248fb5c8ec6663f7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 01:16:47 -0400 Subject: refactored stuff into determineGameEdition() and handleCommandLine() --- src/main.cpp | 171 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 100 insertions(+), 71 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 52011ef4..49f3c6c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -388,6 +388,94 @@ std::unique_ptr createSplash( return splash; } +bool determineGameEdition(Settings& settings, IPluginGame* game) +{ + QString edition; + + if (auto v=settings.game().edition()) { + edition = *v; + } else { + QStringList editions = game->gameVariants(); + if (editions.size() < 2) { + edition = ""; + return true; + } + + SelectionDialog selection( + QObject::tr("Please select the game edition you have (MO can't " + "start the game correctly if this is set " + "incorrectly!)"), + nullptr); + + selection.setWindowFlag(Qt::WindowStaysOnTopHint, true); + + int index = 0; + for (const QString &edition : editions) { + selection.addChoice(edition, "", index++); + } + + if (selection.exec() == QDialog::Rejected) { + return false; + } + + edition = selection.getChoiceString(); + settings.game().setEdition(edition); + } + + game->setGameVariant(edition); + + return true; +} + +std::optional handleCommandLine( + const cl::CommandLine& cl, OrganizerCore& organizer) +{ + // if we have a command line parameter, it is either a nxm link or + // a binary to start + if (cl.shortcut().isValid()) { + if (cl.shortcut().hasExecutable()) { + try { + organizer.processRunner() + .setFromShortcut(cl.shortcut()) + .setWaitForCompletion() + .run(); + + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; + } + } + } else if (cl.nxmLink()) { + log::debug("starting download from command line: {}", *cl.nxmLink()); + organizer.externalMessage(*cl.nxmLink()); + } else if (cl.executable()) { + const QString exeName = *cl.executable(); + log::debug("starting {} from command line", exeName); + + try + { + // pass the remaining parameters to the binary + organizer.processRunner() + .setFromFileOrExecutable(exeName, cl.untouched()) + .setWaitForCompletion() + .run(); + + return 0; + } + catch (const std::exception &e) + { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } + } + + return {}; +} + int runApplication( MOApplication &application, const cl::CommandLine& cl, SingleInstance &instance, const QString &dataPath) @@ -475,38 +563,16 @@ int runApplication( organizer.setManagedGame(game); organizer.createDefaultProfile(); - QString edition; - - if (auto v=settings.game().edition()) { - edition = *v; - } else { - QStringList editions = game->gameVariants(); - if (editions.size() > 1) { - SelectionDialog selection( - QObject::tr("Please select the game edition you have (MO can't " - "start the game correctly if this is set " - "incorrectly!)"), - nullptr); - selection.setWindowFlag(Qt::WindowStaysOnTopHint, true); - int index = 0; - for (const QString &edition : editions) { - selection.addChoice(edition, "", index++); - } - if (selection.exec() == QDialog::Rejected) { - return 1; - } else { - edition = selection.getChoiceString(); - settings.game().setEdition(edition); - } - } + if (!determineGameEdition(settings, game)) { + return 1; } - game->setGameVariant(edition); - log::info( - "using game plugin '{}' ('{}', steam id '{}') at {}", - game->gameName(), game->gameShortName(), game->steamAPPId(), - game->gameDirectory().absolutePath()); + "using game plugin '{}' ('{}', variant {}, steam id '{}') at {}", + game->gameName(), game->gameShortName(), + (settings.game().edition().value_or("").isEmpty() ? + "(none)" : *settings.game().edition()), + game->steamAPPId(), game->gameDirectory().absolutePath()); CategoryFactory::instance().loadCategories(); organizer.updateExecutablesList(); @@ -515,47 +581,8 @@ int runApplication( QString selectedProfileName = determineProfile(cl, settings); organizer.setCurrentProfile(selectedProfileName); - // if we have a command line parameter, it is either a nxm link or - // a binary to start - if (cl.shortcut().isValid()) { - if (cl.shortcut().hasExecutable()) { - try { - organizer.processRunner() - .setFromShortcut(cl.shortcut()) - .setWaitForCompletion() - .run(); - - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } - } else if (cl.nxmLink()) { - log::debug("starting download from command line: {}", *cl.nxmLink()); - organizer.externalMessage(*cl.nxmLink()); - } else if (cl.executable()) { - const QString exeName = *cl.executable(); - log::debug("starting {} from command line", exeName); - - try - { - // pass the remaining parameters to the binary - organizer.processRunner() - .setFromFileOrExecutable(exeName, cl.untouched()) - .setWaitForCompletion() - .run(); - - return 0; - } - catch (const std::exception &e) - { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } + if (auto r=handleCommandLine(cl, organizer)) { + return *r; } auto splash = createSplash(settings, dataPath, game); @@ -611,7 +638,9 @@ int runApplication( settings.geometry().resetIfNeeded(); return res; - } catch (const std::exception &e) { + } + catch (const std::exception &e) + { reportError(e.what()); } -- cgit v1.3.1 From 5b13704275fc3cf02f6c3f4d4bd77d32425ad2c9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 01:32:53 -0400 Subject: moved profile, current game and game edition checks to InstanceManager trying to centralize all of this stuff --- src/instancemanager.cpp | 232 ++++++++++++++++++++++++++++++++++++++++++++++- src/instancemanager.h | 18 +++- src/main.cpp | 234 +++--------------------------------------------- 3 files changed, 255 insertions(+), 229 deletions(-) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 6c7f797e..9bcd0f3c 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -20,9 +20,14 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "selectiondialog.h" +#include "settings.h" +#include "shared/appconfig.h" +#include "plugincontainer.h" +#include +#include #include #include -#include "shared/appconfig.h" + #include #include #include @@ -54,6 +59,11 @@ void InstanceManager::overrideInstance(const QString& instanceName) m_overrideInstance = true; } +void InstanceManager::overrideProfile(const QString& profileName) +{ + m_overrideProfileName = profileName; + m_overrideProfile = true; +} QString InstanceManager::currentInstance() const { @@ -357,6 +367,226 @@ QString InstanceManager::determineDataPath() } } +QString InstanceManager::determineProfile(const Settings &settings) +{ + auto selectedProfileName = settings.game().selectedProfileName(); + + if (m_overrideProfile) { + log::debug("profile overwritten on command line"); + selectedProfileName = m_overrideProfileName; + } + + if (!selectedProfileName) { + log::debug("no configured profile"); + selectedProfileName = "Default"; + } + + return *selectedProfileName; +} + +bool InstanceManager::determineGameEdition( + Settings& settings, IPluginGame* game) +{ + QString edition; + + if (auto v=settings.game().edition()) { + edition = *v; + } else { + QStringList editions = game->gameVariants(); + if (editions.size() < 2) { + edition = ""; + return true; + } + + SelectionDialog selection( + QObject::tr("Please select the game edition you have (MO can't " + "start the game correctly if this is set " + "incorrectly!)"), + nullptr); + + selection.setWindowFlag(Qt::WindowStaysOnTopHint, true); + + int index = 0; + for (const QString &edition : editions) { + selection.addChoice(edition, "", index++); + } + + if (selection.exec() == QDialog::Rejected) { + return false; + } + + edition = selection.getChoiceString(); + settings.game().setEdition(edition); + } + + game->setGameVariant(edition); + + return true; +} + +MOBase::IPluginGame *selectGame( + Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) +{ + settings.game().setName(game->gameName()); + + QString gameDir = gamePath.absolutePath(); + game->setGamePath(gameDir); + + settings.game().setDirectory(gameDir); + + return game; +} + +MOBase::IPluginGame* InstanceManager::determineCurrentGame( + const QString& moPath, Settings& settings, const PluginContainer &plugins) +{ + //Determine what game we are running where. Be very paranoid in case the + //user has done something odd. + + //If the game name has been set up, try to use that. + const auto gameName = settings.game().name(); + const bool gameConfigured = (gameName.has_value() && *gameName != ""); + + if (gameConfigured) { + MOBase::IPluginGame *game = plugins.managedGame(*gameName); + if (game == nullptr) { + reportError( + QObject::tr("Plugin to handle %1 no longer installed. An antivirus might have deleted files.") + .arg(*gameName)); + + return nullptr; + } + + auto gamePath = settings.game().directory(); + if (!gamePath || *gamePath == "") { + gamePath = game->gameDirectory().absolutePath(); + } + + QDir gameDir(*gamePath); + QFileInfo directoryInfo(gameDir.path()); + + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath)); + } + + if (game->looksValid(gameDir)) { + return selectGame(settings, gameDir, game); + } + } + + //If we've made it this far and the instance is already configured for a game, something has gone wrong. + //Tell the user about it. + if (gameConfigured) { + const auto gamePath = settings.game().directory(); + + reportError( + QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") + .arg(*gameName).arg(gamePath ? *gamePath : "")); + } + + SelectionDialog selection(gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); + + for (IPluginGame *game : plugins.plugins()) { + //If a game is already configured, skip any plugins that are not for that game + if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) + continue; + + //Only add games that are installed + if (game->isInstalled()) { + QString path = game->gameDirectory().absolutePath(); + selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); + } + } + + selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr))); + + while (selection.exec() != QDialog::Rejected) { + IPluginGame * game = selection.getChoiceData().value(); + QString gamePath = selection.getChoiceDescription(); + QFileInfo directoryInfo(gamePath); + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + } + if (game != nullptr) { + return selectGame(settings, game->gameDirectory(), game); + } + + gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), + QString(), QFileDialog::ShowDirsOnly); + + if (!gamePath.isEmpty()) { + QDir gameDir(gamePath); + QFileInfo directoryInfo(gamePath); + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + } + QList possibleGames; + for (IPluginGame * const game : plugins.plugins()) { + //If a game is already configured, skip any plugins that are not for that game + if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) + continue; + + //Only try plugins that look valid for this directory + if (game->looksValid(gameDir)) { + possibleGames.append(game); + } + } + + if (possibleGames.count() > 1) { + SelectionDialog browseSelection(gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), + nullptr, QSize(32, 32)); + + for (IPluginGame *game : possibleGames) { + browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game)); + } + + if (browseSelection.exec() == QDialog::Accepted) { + return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); + } else { + reportError(gameConfigured ? + QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) : + QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); + } + } else if(possibleGames.count() == 1) { + return selectGame(settings, gameDir, possibleGames[0]); + } else { + if (gameConfigured) { + reportError( + QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.") + .arg(*gameName).arg(gamePath)); + } else { + QString supportedGames; + + for (IPluginGame * const game : plugins.plugins()) { + supportedGames += "
  • " + game->gameName() + "
  • "; + } + + QString text = QObject::tr( + "No game identified in \"%1\". The directory is required to " + "contain the game binary.

    " + "These are the games supported by Mod Organizer:" + "
      %2
    ") + .arg(gamePath) + .arg(supportedGames); + + reportError(text); + } + } + } + } + + return nullptr; +} + QString InstanceManager::sanitizeInstanceName(const QString &name) const { diff --git a/src/instancemanager.h b/src/instancemanager.h index 649c2195..2331ebfd 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -24,18 +24,26 @@ along with Mod Organizer. If not, see . #include #include +namespace MOBase { class IPluginGame; } + +class Settings; +class PluginContainer; class InstanceManager { public: - static InstanceManager &instance(); - QString determineDataPath(); - void clearCurrentInstance(); - void overrideInstance(const QString& instanceName); + void overrideProfile(const QString& profileName); + + QString determineDataPath(); + QString determineProfile(const Settings &settings); + bool determineGameEdition(Settings& settings, MOBase::IPluginGame* game); + MOBase::IPluginGame* determineCurrentGame( + const QString& moPath, Settings& settings, const PluginContainer &plugins); + void clearCurrentInstance(); QString currentInstance() const; bool allowedToChangeInstance() const; @@ -69,4 +77,6 @@ private: bool m_Reset {false}; bool m_overrideInstance{false}; QString m_overrideInstanceName; + bool m_overrideProfile{false}; + QString m_overrideProfileName; }; diff --git a/src/main.cpp b/src/main.cpp index 49f3c6c0..5479d5a3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -119,188 +119,6 @@ void setExceptionHandlers() g_prevTerminateHandler = std::set_terminate(onTerminate); } -QString determineProfile(const cl::CommandLine& cl, const Settings &settings) -{ - auto selectedProfileName = settings.game().selectedProfileName(); - - if (cl.profile()) { - log::debug("profile overwritten on command line"); - selectedProfileName = *cl.profile(); - } - - if (!selectedProfileName) { - log::debug("no configured profile"); - selectedProfileName = "Default"; - } - - return *selectedProfileName; -} - -MOBase::IPluginGame *selectGame( - Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) -{ - settings.game().setName(game->gameName()); - - QString gameDir = gamePath.absolutePath(); - game->setGamePath(gameDir); - - settings.game().setDirectory(gameDir); - - return game; -} - - -MOBase::IPluginGame *determineCurrentGame( - QString const &moPath, Settings &settings, PluginContainer const &plugins) -{ - //Determine what game we are running where. Be very paranoid in case the - //user has done something odd. - - //If the game name has been set up, try to use that. - const auto gameName = settings.game().name(); - const bool gameConfigured = (gameName.has_value() && *gameName != ""); - - if (gameConfigured) { - MOBase::IPluginGame *game = plugins.managedGame(*gameName); - if (game == nullptr) { - reportError( - QObject::tr("Plugin to handle %1 no longer installed. An antivirus might have deleted files.") - .arg(*gameName)); - - return nullptr; - } - - auto gamePath = settings.game().directory(); - if (!gamePath || *gamePath == "") { - gamePath = game->gameDirectory().absolutePath(); - } - - QDir gameDir(*gamePath); - QFileInfo directoryInfo(gameDir.path()); - - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath)); - } - - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - - //If we've made it this far and the instance is already configured for a game, something has gone wrong. - //Tell the user about it. - if (gameConfigured) { - const auto gamePath = settings.game().directory(); - - reportError( - QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") - .arg(*gameName).arg(gamePath ? *gamePath : "")); - } - - SelectionDialog selection(gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); - - for (IPluginGame *game : plugins.plugins()) { - //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) - continue; - - //Only add games that are installed - if (game->isInstalled()) { - QString path = game->gameDirectory().absolutePath(); - selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); - } - } - - selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr))); - - while (selection.exec() != QDialog::Rejected) { - IPluginGame * game = selection.getChoiceData().value(); - QString gamePath = selection.getChoiceDescription(); - QFileInfo directoryInfo(gamePath); - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); - } - if (game != nullptr) { - return selectGame(settings, game->gameDirectory(), game); - } - - gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), - QString(), QFileDialog::ShowDirsOnly); - - if (!gamePath.isEmpty()) { - QDir gameDir(gamePath); - QFileInfo directoryInfo(gamePath); - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); - } - QList possibleGames; - for (IPluginGame * const game : plugins.plugins()) { - //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) - continue; - - //Only try plugins that look valid for this directory - if (game->looksValid(gameDir)) { - possibleGames.append(game); - } - } - - if (possibleGames.count() > 1) { - SelectionDialog browseSelection(gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), - nullptr, QSize(32, 32)); - - for (IPluginGame *game : possibleGames) { - browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game)); - } - - if (browseSelection.exec() == QDialog::Accepted) { - return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); - } else { - reportError(gameConfigured ? - QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) : - QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); - } - } else if(possibleGames.count() == 1) { - return selectGame(settings, gameDir, possibleGames[0]); - } else { - if (gameConfigured) { - reportError( - QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.") - .arg(*gameName).arg(gamePath)); - } else { - QString supportedGames; - - for (IPluginGame * const game : plugins.plugins()) { - supportedGames += "
  • " + game->gameName() + "
  • "; - } - - QString text = QObject::tr( - "No game identified in \"%1\". The directory is required to " - "contain the game binary.

    " - "These are the games supported by Mod Organizer:" - "
      %2
    ") - .arg(gamePath) - .arg(supportedGames); - - reportError(text); - } - } - } - } - - return nullptr; -} - - // This adds the `dlls` directory to the path so the dlls can be found. How // MO is able to find dlls in there is a bit convoluted: // @@ -388,45 +206,6 @@ std::unique_ptr createSplash( return splash; } -bool determineGameEdition(Settings& settings, IPluginGame* game) -{ - QString edition; - - if (auto v=settings.game().edition()) { - edition = *v; - } else { - QStringList editions = game->gameVariants(); - if (editions.size() < 2) { - edition = ""; - return true; - } - - SelectionDialog selection( - QObject::tr("Please select the game edition you have (MO can't " - "start the game correctly if this is set " - "incorrectly!)"), - nullptr); - - selection.setWindowFlag(Qt::WindowStaysOnTopHint, true); - - int index = 0; - for (const QString &edition : editions) { - selection.addChoice(edition, "", index++); - } - - if (selection.exec() == QDialog::Rejected) { - return false; - } - - edition = selection.getChoiceString(); - settings.game().setEdition(edition); - } - - game->setGameVariant(edition); - - return true; -} - std::optional handleCommandLine( const cl::CommandLine& cl, OrganizerCore& organizer) { @@ -542,7 +321,8 @@ int runApplication( pluginContainer = std::make_unique(&organizer); pluginContainer->loadPlugins(); - MOBase::IPluginGame* game = determineCurrentGame( + MOBase::IPluginGame* game = InstanceManager::instance() + .determineCurrentGame( application.applicationDirPath(), settings, *pluginContainer); if (game == nullptr) { @@ -563,7 +343,7 @@ int runApplication( organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (!determineGameEdition(settings, game)) { + if (!InstanceManager::instance().determineGameEdition(settings, game)) { return 1; } @@ -578,7 +358,13 @@ int runApplication( organizer.updateExecutablesList(); organizer.updateModInfoFromDisc(); - QString selectedProfileName = determineProfile(cl, settings); + if (cl.profile()) { + InstanceManager::instance().overrideProfile(*cl.profile()); + } + + QString selectedProfileName = InstanceManager::instance() + .determineProfile(settings); + organizer.setCurrentProfile(selectedProfileName); if (auto r=handleCommandLine(cl, organizer)) { -- cgit v1.3.1 From 3c791b092054192fcff27b599728d3482688aec8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 03:01:00 -0400 Subject: moved registry key from "Tannin" to "Mod Organizer Team" --- src/env.cpp | 160 ++++++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 9 +++ src/instancemanager.cpp | 31 ++++++++-- src/instancemanager.h | 2 + 4 files changed, 196 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 9f0acbbd..5bed84c1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -846,6 +846,166 @@ Association getAssociation(const QFileInfo& targetInfo) } +struct RegistryKeyCloser +{ + using pointer = HKEY; + + void operator()(HKEY key) + { + if (key != 0) { + ::RegCloseKey(key); + } + } +}; + +using RegistryKeyPtr = std::unique_ptr; + +RegistryKeyPtr openRegistryKey(HKEY parent, const wchar_t* name) +{ + HKEY subkey = 0; + + auto r = ::RegOpenKeyExW( + parent, name, + 0, KEY_SET_VALUE|KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE, + &subkey); + + if (r != ERROR_SUCCESS) { + return {}; + } + + return RegistryKeyPtr(subkey); +} + +bool keyHasValues(HKEY key) +{ + auto name = std::make_unique(1000 + 1); + DWORD nameSize = 1000; + + // note that RegEnumValueW() also enumerates the default value if it exists + auto r = ::RegEnumValueW( + key, 0, name.get(), &nameSize, nullptr, nullptr, nullptr, nullptr); + + if (r != ERROR_NO_MORE_ITEMS) { + return true; + } + + // no values, no default, it's empty + return false; +} + +bool forEachSubKey(HKEY key, std::function f) +{ + auto name = std::make_unique(1000 + 1); + DWORD nameSize = 1000; + + DWORD i = 0; + + // something would be really wrong if it had more than 100 keys + while (i < 100) + { + auto r = ::RegEnumKeyExW( + key, i, name.get(), &nameSize, nullptr, nullptr, nullptr, nullptr); + + if (r == ERROR_NO_MORE_ITEMS) { + // no more subkeys + break; + } + + if (r == ERROR_SUCCESS) { + // a subkey exists + auto subkey = openRegistryKey(key, name.get()); + + if (!subkey) { + // can't open it, stop + return false; + } + + // fire callback + if (!f(name.get())) { + return false; + } + } else { + // something went wrong, stop + return false; + } + + ++i; + } + + return true; +} + +bool isKeyEmpty(HKEY key) +{ + // check for any values + if (keyHasValues(key)) { + return false; + } + + auto r = forEachSubKey(key, [&](const wchar_t* name) { + // a subkey exists, recursively check if it's empty + auto subkey = openRegistryKey(key, name); + + if (!subkey) { + // can't open, stop + return false; + } + + if (!isKeyEmpty(subkey.get())) { + // not empty, stop + return false; + } + + // empty, go on + return true; + }); + + if (!r) { + // something went wrong or some subkey has values + return false; + } + + // key has no values and has either no subkeys or all subkeys are empty + return true; +} + +void deleteRegistryKeyIfEmpty(const QString& name) +{ + if (name.isEmpty()) { + return; + } + + auto key = openRegistryKey(HKEY_CURRENT_USER, name.toStdWString().c_str()); + if (!key) { + return; + } + + if (!isKeyEmpty(key.get())) { + return; + } + + ::RegDeleteTreeW(HKEY_CURRENT_USER, name.toStdWString().c_str()); +} + +bool registryValueExists(const QString& keyName, const QString& valueName) +{ + auto key = openRegistryKey( + HKEY_CURRENT_USER, keyName.toStdWString().c_str()); + + if (!key) { + return false; + } + + DWORD type = 0; + + auto r = ::RegQueryValueExW( + key.get(), valueName.toStdWString().c_str(), + nullptr, &type, nullptr, nullptr); + + return (r == ERROR_SUCCESS); +} + + // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index a563f2c3..dbbd5cf4 100644 --- a/src/env.h +++ b/src/env.h @@ -301,6 +301,15 @@ struct Association Association getAssociation(const QFileInfo& file); +// returns whether the given value exists +// +bool registryValueExists(const QString& key, const QString& value); + +// deletes a registry key if it's empty or only contains empty keys +// +void deleteRegistryKeyIfEmpty(const QString& name); + + enum class CoreDumpTypes { Mini = 1, diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 9bcd0f3c..aa8f1d8c 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "shared/appconfig.h" #include "plugincontainer.h" +#include "env.h" #include #include #include @@ -37,14 +38,15 @@ along with Mod Organizer. If not, see . using namespace MOBase; -static const char COMPANY_NAME[] = "Tannin"; -static const char APPLICATION_NAME[] = "Mod Organizer"; -static const char INSTANCE_KEY[] = "CurrentInstance"; +const QString Organization = "Mod Organizer Team"; +const QString Application = "Mod Organizer"; +const QString InstanceValue = "CurrentInstance"; InstanceManager::InstanceManager() - : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) + : m_AppSettings(Organization, Application) { + updateRegistryKey(); } InstanceManager &InstanceManager::instance() @@ -53,6 +55,23 @@ InstanceManager &InstanceManager::instance() return s_Instance; } +void InstanceManager::updateRegistryKey() +{ + const QString OldOrganization = "Tannin"; + const QString OldApplication = "Mod Organizer"; + const QString OldInstanceValue = "CurrentInstance"; + + const QString OldRootKey = "Software\\" + OldOrganization; + + if (env::registryValueExists(OldRootKey + "\\" + OldApplication, OldInstanceValue)) { + QSettings old(OldOrganization, OldApplication); + setCurrentInstance(old.value(OldInstanceValue).toString()); + old.remove(OldInstanceValue); + } + + env::deleteRegistryKeyIfEmpty(OldRootKey); +} + void InstanceManager::overrideInstance(const QString& instanceName) { m_overrideInstanceName = instanceName; @@ -70,7 +89,7 @@ QString InstanceManager::currentInstance() const if (m_overrideInstance) return m_overrideInstanceName; else - return m_AppSettings.value(INSTANCE_KEY, "").toString(); + return m_AppSettings.value(InstanceValue, "").toString(); } void InstanceManager::clearCurrentInstance() @@ -82,7 +101,7 @@ void InstanceManager::clearCurrentInstance() void InstanceManager::setCurrentInstance(const QString &name) { - m_AppSettings.setValue(INSTANCE_KEY, name); + m_AppSettings.setValue(InstanceValue, name); } bool InstanceManager::deleteLocalInstance(const QString &instanceId) const diff --git a/src/instancemanager.h b/src/instancemanager.h index 2331ebfd..7f87045d 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -71,6 +71,8 @@ private: bool portableInstall() const; bool portableInstallIsLocked() const; + void updateRegistryKey(); + private: QSettings m_AppSettings; -- cgit v1.3.1 From 3761ac84b586669b08657d67a6d170009a96795f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 03:31:09 -0400 Subject: deleteLocalInstance() now uses TaskDialog --- src/instancemanager.cpp | 87 +++++++++++++++++++++++++++++++++---------------- src/instancemanager.h | 3 +- 2 files changed, 61 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index aa8f1d8c..3821495b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -104,30 +104,67 @@ void InstanceManager::setCurrentInstance(const QString &name) m_AppSettings.setValue(InstanceValue, name); } -bool InstanceManager::deleteLocalInstance(const QString &instanceId) const +bool InstanceManager::deleteLocalInstance(const QString& instanceId) const { - bool result = true; - QString instancePath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId); + QString dir = instancePath(instanceId); - if (QMessageBox::warning(nullptr, QObject::tr("Deleting folder"), - QObject::tr("I'm about to delete the following folder: \"%1\". Proceed?").arg(instancePath), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::No ){ - return false; - } + const auto Recycle = QMessageBox::Save; + const auto Delete = QMessageBox::Yes; + const auto Cancel = QMessageBox::Cancel; + + const auto r = MOBase::TaskDialog() + .title(QObject::tr("Deleting instance folder")) + .main(QObject::tr("This will delete the instance folder.")) + .content(dir) + .icon(QMessageBox::Warning) + .button({QObject::tr("Move the folder to the recycle bin"), Recycle}) + .button({QObject::tr("Delete the folder permanently"), Delete}) + .button({QObject::tr("Cancel"), Cancel}) + .exec(); + + std::wstring error; - if (!MOBase::shellDelete(QStringList(instancePath),true)) + switch (r) { - log::warn( - "Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", - instancePath, ::GetLastError()); + case Recycle: + { + if (MOBase::shellDelete(QStringList(dir), true)) { + return true; + } + + const auto e = GetLastError(); + error = formatSystemMessage(e); + log::warn("failed to move to trash '{}', {}", dir, error); + + break; + } + + case Delete: + { + if (MOBase::shellDelete(QStringList(dir), false)) { + return true; + } + + const auto e = GetLastError(); + error = formatSystemMessage(e); + log::warn("failed to delete '{}', {}", dir, error); + + break; + } - if (!MOBase::removeDir(instancePath)) + default: { - log::warn("regular delete failed too"); - result = false; + return true; } } - return result; + QMessageBox::critical( + nullptr, QObject::tr("Error"), QObject::tr( + "Could not delete instance folder \"%1\".\n\n%2") + .arg(dir).arg(error), + QMessageBox::Ok); + + return false; } QString InstanceManager::manageInstances(const QStringList &instanceList) const @@ -147,17 +184,7 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const } else { QString choice = selection.getChoiceData().toString(); - { - if (QMessageBox::warning(nullptr, QObject::tr("Are you sure?"), - QObject::tr("Are you really sure you want to delete the Instance \"%1\" with all its files?").arg(choice), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) - { - if (!deleteLocalInstance(choice)) - { - QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"), - QObject::tr("Could not delete Instance \"%1\". \nIf the folder was still in use, restart MO and try again.").arg(choice), QMessageBox::Ok); - } - } - } + deleteLocalInstance(choice); } return(manageInstances(instances())); } @@ -278,8 +305,12 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const } } +QString InstanceManager::instancePath(const QString& instanceName) const +{ + return QDir::fromNativeSeparators(instancesPath() + "/" + instanceName); +} -QString InstanceManager::instancePath() const +QString InstanceManager::instancesPath() const { return QDir::fromNativeSeparators( QStandardPaths::writableLocation(QStandardPaths::DataLocation)); @@ -292,7 +323,7 @@ QStringList InstanceManager::instances() const "cache", "qtwebengine", }; - const auto dirs = QDir(instancePath()) + const auto dirs = QDir(instancesPath()) .entryList(QDir::Dirs | QDir::NoDotAndDotDot); QStringList list; diff --git a/src/instancemanager.h b/src/instancemanager.h index 7f87045d..13754f89 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -53,7 +53,8 @@ private: InstanceManager(); - QString instancePath() const; + QString instancesPath() const; + QString instancePath(const QString& instanceName) const; QStringList instances() const; -- cgit v1.3.1 From 555b2508a5a34798059af7e128f6f32fc34a4947 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 03:39:52 -0400 Subject: rewrote the delete instance dialog text --- src/instancemanager.cpp | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 3821495b..46936444 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -169,24 +169,27 @@ bool InstanceManager::deleteLocalInstance(const QString& instanceId) const QString InstanceManager::manageInstances(const QStringList &instanceList) const { - SelectionDialog selection( - QString("

    %1


    %2") - .arg(QObject::tr("Choose Instance to Delete")) - .arg(QObject::tr("Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")), - nullptr); - for (const QString &instance : instanceList) - { - selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance); - } - - if (selection.exec() == QDialog::Rejected) { - return(chooseInstance(instances())); - } - else { - QString choice = selection.getChoiceData().toString(); - deleteLocalInstance(choice); - } - return(manageInstances(instances())); + SelectionDialog selection(QString("

    %1


    %2") + .arg(QObject::tr("Select an instance to delete")) + .arg(QObject::tr( + "Deleting an instance will delete all the mods, downloads, profiles " + "(including profile-specific saves) and anything in the overwrite " + "folder.

    " + "Custom paths outside of the instance folder will not be deleted."))); + + for (const QString &instance : instanceList) { + selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance); + } + + if (selection.exec() == QDialog::Rejected) { + return (chooseInstance(instances())); + } + else { + QString choice = selection.getChoiceData().toString(); + deleteLocalInstance(choice); + } + + return(manageInstances(instances())); } QString InstanceManager::queryInstanceName(const QStringList &instanceList) const -- cgit v1.3.1 From e29f41565fce21cd72968ac188cd319e746b10e6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 04:47:52 -0400 Subject: fixed splash not trying the default one --- src/main.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 5479d5a3..08d70dd9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -165,20 +165,34 @@ QString getSplashPath( return {}; } + // try splash from instance directory const QString splashPath = dataPath + "/splash.png"; if (QFile::exists(dataPath + "/splash.png")) { - return splashPath; + QImage image(splashPath); + if (!image.isNull()) { + return splashPath; + } } - // currently using MO splash, see if the plugin contains one + // try splash from plugin QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName()); - QImage image(pluginSplash); + if (QFile::exists(pluginSplash)) { + QImage image(pluginSplash); + if (!image.isNull()) { + image.save(splashPath); + return pluginSplash; + } + } - if (image.isNull()) { - return {}; + // try default splash from resource + QString defaultSplash = ":/MO/gui/splash"; + if (QFile::exists(defaultSplash)) { + QImage image(defaultSplash); + if (!image.isNull()) { + return defaultSplash; + } } - image.save(splashPath); return splashPath; } -- cgit v1.3.1 From 5513b273c679b9edda951f418ef013a762ca587a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 04:53:20 -0400 Subject: added empty instance manager dialog --- src/CMakeLists.txt | 6 +- src/instancemanager.cpp | 30 +- src/instancemanager.h | 9 +- src/instancemanagerdialog.cpp | 36 ++ src/instancemanagerdialog.h | 20 + src/instancemanagerdialog.ui | 378 ++++++++++++++++++ src/organizer_en.ts | 885 +++++++++++++++++++++++------------------- 7 files changed, 940 insertions(+), 424 deletions(-) create mode 100644 src/instancemanagerdialog.cpp create mode 100644 src/instancemanagerdialog.h create mode 100644 src/instancemanagerdialog.ui (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 73254574..1f048be8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,7 +29,6 @@ add_filter(NAME src/core GROUPS categories archivefiletree installationmanager - instancemanager loadmechanism nexusinterface nxmaccessmanager @@ -84,6 +83,11 @@ add_filter(NAME src/executables GROUPS editexecutablesdialog ) +add_filter(NAME src/instances GROUPS + instancemanager + instancemanagerdialog +) + add_filter(NAME src/loot GROUPS loot lootdialog diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 46936444..98edba47 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -182,14 +182,14 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const } if (selection.exec() == QDialog::Rejected) { - return (chooseInstance(instances())); + return (chooseInstance(instanceNames())); } else { QString choice = selection.getChoiceData().toString(); deleteLocalInstance(choice); } - return(manageInstances(instances())); + return(manageInstances(instanceNames())); } QString InstanceManager::queryInstanceName(const QStringList &instanceList) const @@ -301,7 +301,7 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const case Special::Portable: return QString(); case Special::Manage: { - return(manageInstances(instances())); + return(manageInstances(instanceNames())); } default: throw std::runtime_error("invalid selection"); } @@ -319,27 +319,37 @@ QString InstanceManager::instancesPath() const QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } - -QStringList InstanceManager::instances() const +std::vector InstanceManager::instancePaths() const { const std::set ignore = { "cache", "qtwebengine", }; - const auto dirs = QDir(instancesPath()) - .entryList(QDir::Dirs | QDir::NoDotAndDotDot); + const QDir root(instancesPath()); + const auto dirs = root.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - QStringList list; + std::vector list; for (auto&& d : dirs) { if (!ignore.contains(QFileInfo(d).fileName().toLower())) { - list.push_back(d); + list.push_back(root.filePath(d)); } } return list; } +QStringList InstanceManager::instanceNames() const +{ + QStringList list; + + for (auto&& d : instancePaths()) { + list.push_back(d.dirName()); + } + + return list; +} + bool InstanceManager::isPortablePath(const QString& dataPath) { @@ -402,7 +412,7 @@ QString InstanceManager::determineDataPath() if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) { - instanceId = chooseInstance(instances()); + instanceId = chooseInstance(instanceNames()); setCurrentInstance(instanceId); if (!instanceId.isEmpty()) { dataPath = QDir::fromNativeSeparators( diff --git a/src/instancemanager.h b/src/instancemanager.h index 13754f89..8bbbbee9 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -29,8 +29,8 @@ namespace MOBase { class IPluginGame; } class Settings; class PluginContainer; -class InstanceManager { - +class InstanceManager +{ public: static InstanceManager &instance(); @@ -49,6 +49,9 @@ public: bool allowedToChangeInstance() const; static bool isPortablePath(const QString& dataPath); + QStringList instanceNames() const; + std::vector instancePaths() const; + private: InstanceManager(); @@ -56,8 +59,6 @@ private: QString instancesPath() const; QString instancePath(const QString& instanceName) const; - QStringList instances() const; - bool deleteLocalInstance(const QString &instanceId) const; QString manageInstances(const QStringList &instanceList) const; diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp new file mode 100644 index 00000000..1b482099 --- /dev/null +++ b/src/instancemanagerdialog.cpp @@ -0,0 +1,36 @@ +#include "instancemanagerdialog.h" +#include "ui_instancemanagerdialog.h" +#include "instancemanager.h" + +class InstanceInfo +{ +public: + InstanceInfo(QDir dir) + : m_dir(std::move(dir)) + { + } + + QString name() const + { + return m_dir.dirName(); + } + +private: + QDir m_dir; +}; + + +InstanceManagerDialog::InstanceManagerDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::InstanceManagerDialog) +{ + ui->setupUi(this); + + auto& m = InstanceManager::instance(); + + for (auto&& d : m.instancePaths()) { + InstanceInfo i(d); + ui->list->addItem(i.name()); + } +} + +InstanceManagerDialog::~InstanceManagerDialog() = default; diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h new file mode 100644 index 00000000..ea1cfc48 --- /dev/null +++ b/src/instancemanagerdialog.h @@ -0,0 +1,20 @@ +#ifndef MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED +#define MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED + +#include + +namespace Ui { class InstanceManagerDialog; }; + +class InstanceManagerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit InstanceManagerDialog(QWidget *parent = nullptr); + ~InstanceManagerDialog(); + +private: + std::unique_ptr ui; +}; + +#endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui new file mode 100644 index 00000000..b35d2e58 --- /dev/null +++ b/src/instancemanagerdialog.ui @@ -0,0 +1,378 @@ + + + InstanceManagerDialog + + + + 0 + 0 + 531 + 413 + + + + Instance manager + + + + + + + 0 + + + 0 + + + 0 + + + 5 + + + + + Create new instance + + + + :/MO/gui/add:/MO/gui/add + + + + + + + Create portable instance + + + + :/MO/gui/package:/MO/gui/package + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance"><span style=" text-decoration: underline; color:#0000ff;">What is an instance?</span></a></p></body></html> + + + true + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + instance name + + + Qt::TextBrowserInteraction + + + + + + + Game: + + + + + + + Name: + + + + + + + Location: + + + + + + + Rename + + + + + + + location path + + + Qt::TextBrowserInteraction + + + + + + + Base folder: + + + + + + + base folder path + + + Qt::TextBrowserInteraction + + + + + + + Explore + + + + + + + Explore + + + + + + + game name + + + Qt::TextBrowserInteraction + + + + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Convert to portable + + + + + + + Convert to global + + + + + + + Delete instance + + + + :/MO/gui/remove:/MO/gui/remove + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Switch to this instance + + + + :/MO/gui/next:/MO/gui/next + + + + + + + Cancel + + + true + + + + + + + + + + + + + diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 1ad4650f..c65c946c 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -110,22 +110,22 @@ - + ...and all other contributors! - + Other Supporters && Contributors - + Tannin (Original Creator) - + Close @@ -178,17 +178,17 @@ p, li { white-space: pre-wrap; } AdvancedConflictListModel - + Overwrites - + File - + Overwritten By @@ -1462,54 +1462,54 @@ Right now the only case I know of where this needs to be overwritten is for the FileTreeModel - + Name - + Mod - + Type - + Size - + Date modified - + Directory - - + + Virtual path - + Real path - + From - - + + Also in @@ -1868,6 +1868,85 @@ This is likely due to a corrupted or incompatible download or unrecognized archi + + InstanceManagerDialog + + + Instance manager + + + + + Create new instance + + + + + Create portable instance + + + + + <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance"><span style=" text-decoration: underline; color:#0000ff;">What is an instance?</span></a></p></body></html> + + + + + Game: + + + + + Name: + + + + + Location: + + + + + Rename + + + + + Base folder: + + + + + + Explore + + + + + Convert to portable + + + + + Convert to global + + + + + Delete instance + + + + + Switch to this instance + + + + + Cancel + + + ListDialog @@ -1999,12 +2078,12 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MOApplication - + an error occurred: %1 - + an error occurred @@ -2199,7 +2278,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2353,7 +2432,7 @@ p, li { white-space: pre-wrap; } - + Refresh @@ -2677,7 +2756,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2755,829 +2834,833 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - + failed to rename "%1" to "%2" - - - - - + + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Restore all hidden files in the following mods?<br><ul>%1</ul> - - - + + Are you sure? - + About to restore all hidden files in: - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - - + + Opening Web Pages - - + + You are trying to open %1 Web Pages. Are you sure you want to do this? - + + No valid Web Page for this mod + + + + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - - + + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Ignore missing data - - + + Mark as converted/working - - + + Visit on Nexus - - + + Visit on %1 - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - - + + Select Color... - - + + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Restore hidden files - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3585,12 +3668,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3598,242 +3681,227 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - - This will restart MO, continue? - - - - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - - Sorting plugins - - - - - Are you sure you want to sort your plugins list? - - - - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4701,7 +4769,7 @@ p, li { white-space: pre-wrap; } NoConflictListModel - + File @@ -4709,211 +4777,211 @@ p, li { white-space: pre-wrap; } OrganizerCore - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + Failed to write settings - + An error occurred trying to write back MO settings to %1: %2 - - + + Download started - + Download failed - - + + Installation cancelled - - + + Another installation is currently in progress. - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod not found: %1 - - + + Extraction cancelled - - + + The installation was cancelled while extracting files. If this was prior to a FOMOD setup, this warning may be ignored. However, if this was during installation, the mod will likely be missing files. - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + Error - + The designated write target "%1" is not enabled. @@ -4921,12 +4989,12 @@ Continue? OverwriteConflictListModel - + File - + Overwritten Mods @@ -5026,12 +5094,12 @@ Continue? OverwrittenConflictListModel - + File - + Providing Mod @@ -5653,9 +5721,10 @@ p, li { white-space: pre-wrap; } + + - - + @@ -5972,137 +6041,134 @@ Destination: - - Deleting folder - - - - - I'm about to delete the following folder: "%1". Proceed? + + Deleting instance folder - - Choose Instance to Delete + + This will delete the instance folder. + The folder will be deleted: "%1" - - Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched. + + Move the folder to the recycle bin - - Are you sure? + + Delete the folder permanently - - Are you really sure you want to delete the Instance "%1" with all its files? + + Could not delete instance folder "%1". + +%2 - - Failed to delete Instance + + Select an instance to delete - - Could not delete Instance "%1". -If the folder was still in use, restart MO and try again. + + Deleting an instance will delete all the mods, downloads, profiles (including profile-specific saves) and anything in the overwrite folder.<br><br>Custom paths outside of the instance folder will not be deleted. - + Enter a Name for the new Instance - + Enter a new name or select one from the suggested list: (This is just a name for the Instance and can be whatever you wish, the actual game selection will happen on the next screen regardless of chosen name) - - + + Canceled - + Invalid instance name - + The instance name "%1" is invalid. Use the name "%2" instead? - + The instance "%1" already exists. - + Please choose a different instance name, like: "%1 1" . - + Choose Instance - + Each Instance is a full set of MO data files (mods, downloads, profiles, configuration, ...). You can use multiple instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. If your MO folder is writable, you can also store a single instance locally (called a Portable install, and all the MO data files will be inside the installation folder). - + New - + Create a new instance. - + Portable - + Use MO folder for data. - + Manage Instances - + Delete an Instance. - + failed to create %1 - + Data directory created - + New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings. @@ -6194,112 +6260,112 @@ If the folder was still in use, restart MO and try again. - - + + Failed to create "%1". Your user account probably lacks permission. - + Plugin to handle %1 no longer installed. An antivirus might have deleted files. Plugin to handle %1 no longer installed - - - + + + The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly. - + Could not use configuration settings for game "%1", path "%2". - - - + + + Please select the installation of %1 to manage - - - + + + Please select the game to manage - + Canceled finding %1 in "%2". - + Canceled finding game in "%1". - + %1 not identified in "%2". The directory is required to contain the game binary. - + No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul> - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + failed to start shortcut: %1 - + failed to start application: %1 - + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + <Unmanaged> - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -6427,7 +6493,7 @@ If the folder was still in use, restart MO and try again. - + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is likely to be incompatible with MO2's virtual filesystem. @@ -6590,6 +6656,7 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl + @@ -7903,22 +7970,22 @@ programs you are intentionally running. SingleInstance - + SHM error: %1 - + failed to connect to running instance: %1 - + failed to communicate with running instance: %1 - + failed to receive data from secondary instance: %1 -- cgit v1.3.1 From 60b59ddf097fffa846a4d28e0d9256630da5149c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 07:32:06 -0400 Subject: started create instance dialog allow for multiple instances of Settings fill in information in instance manager --- src/CMakeLists.txt | 1 + src/createinstancedialog.cpp | 176 ++++++++++++ src/createinstancedialog.h | 30 ++ src/createinstancedialog.ui | 655 ++++++++++++++++++++++++++++++++++++++++++ src/instancemanager.h | 2 +- src/instancemanagerdialog.cpp | 102 ++++++- src/instancemanagerdialog.h | 16 +- src/instancemanagerdialog.ui | 116 ++++---- src/main.cpp | 9 + src/settings.cpp | 19 +- src/settings.h | 1 + 11 files changed, 1064 insertions(+), 63 deletions(-) create mode 100644 src/createinstancedialog.cpp create mode 100644 src/createinstancedialog.h create mode 100644 src/createinstancedialog.ui (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1f048be8..af350584 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -84,6 +84,7 @@ add_filter(NAME src/executables GROUPS ) add_filter(NAME src/instances GROUPS + createinstancedialog instancemanager instancemanagerdialog ) diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp new file mode 100644 index 00000000..d43fba1c --- /dev/null +++ b/src/createinstancedialog.cpp @@ -0,0 +1,176 @@ +#include "createinstancedialog.h" +#include "ui_createinstancedialog.h" +#include "instancemanager.h" + +namespace cid +{ + +class Page +{ +public: + Page(CreateInstanceDialog& dlg, std::size_t i) + : ui(dlg.getUI()), m_dlg(dlg), m_index(i) + { + } + + virtual bool ready() const + { + return true; + } + + void next() + { + m_dlg.next(); + } + +protected: + Ui::CreateInstanceDialog* ui; + +private: + CreateInstanceDialog& m_dlg; + std::size_t m_index; +}; + +class TypePage : public Page +{ +public: + TypePage(CreateInstanceDialog& dlg, std::size_t i) + : Page(dlg, i) + { + 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_global.has_value(); + } + + void global() + { + m_global = true; + + ui->createGlobal->setChecked(true); + ui->createPortable->setChecked(false); + + next(); + } + + void portable() + { + m_global = false; + + ui->createGlobal->setChecked(false); + ui->createPortable->setChecked(true); + + next(); + } + +private: + std::optional m_global; +}; + + +class GamePage : public Page +{ +public: + GamePage(CreateInstanceDialog& dlg, std::size_t i) + : Page(dlg, i) + { + } +}; + + +class NamePage : public Page +{ +public: + NamePage(CreateInstanceDialog& dlg, std::size_t i) + : Page(dlg, i) + { + } +}; + + +class PathsPage : public Page +{ +public: + PathsPage(CreateInstanceDialog& dlg, std::size_t i) + : Page(dlg, i) + { + } +}; + +} // namespace + + +CreateInstanceDialog::CreateInstanceDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::CreateInstanceDialog) +{ + using namespace cid; + + ui->setupUi(this); + + m_pages.push_back(std::make_unique(*this, 0)); + m_pages.push_back(std::make_unique(*this, 1)); + m_pages.push_back(std::make_unique(*this, 2)); + m_pages.push_back(std::make_unique(*this, 3)); + + ui->pages->setCurrentIndex(0); + + updateNavigationButtons(); + + connect(ui->next, &QPushButton::clicked, [&]{ next(); }); + connect(ui->back, &QPushButton::clicked, [&]{ back(); }); + + // + //SelectionDialog games(tr("Select a game to manage.")); + // + //for (auto* game : m_pc.plugins()) { + // if (game->isInstalled()) { + // games.addChoice(game->gameName(), game->gameDirectory().path(), {}); + // } else { + // games.addChoice(game->gameName(), "", {}); + // } + //} + // + //games.exec(); +} + +CreateInstanceDialog::~CreateInstanceDialog() = default; + +Ui::CreateInstanceDialog* CreateInstanceDialog::getUI() +{ + return ui.get(); +} + +void CreateInstanceDialog::next() +{ + ui->pages->setCurrentIndex(ui->pages->currentIndex() + 1); + updateNavigationButtons(); +} + +void CreateInstanceDialog::back() +{ + ui->pages->setCurrentIndex(ui->pages->currentIndex() - 1); + updateNavigationButtons(); +} + +void CreateInstanceDialog::updateNavigationButtons() +{ + const auto i = ui->pages->currentIndex(); + const auto last = (i == (ui->pages->count() - 1)); + + ui->next->setEnabled(m_pages[i]->ready() && !last); + ui->back->setEnabled(i > 0); +} diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h new file mode 100644 index 00000000..03e9de01 --- /dev/null +++ b/src/createinstancedialog.h @@ -0,0 +1,30 @@ +#ifndef MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED +#define MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED + +#include + +namespace Ui { class CreateInstanceDialog; }; +namespace cid { class Page; } + +class CreateInstanceDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CreateInstanceDialog(QWidget *parent = nullptr); + + ~CreateInstanceDialog(); + + Ui::CreateInstanceDialog* getUI(); + + void next(); + void back(); + +private: + std::unique_ptr ui; + std::vector> m_pages; + + void updateNavigationButtons(); +}; + +#endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui new file mode 100644 index 00000000..bcfddb20 --- /dev/null +++ b/src/createinstancedialog.ui @@ -0,0 +1,655 @@ + + + CreateInstanceDialog + + + + 0 + 0 + 493 + 423 + + + + Creating an instance + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h2>Creating a new instance</h2> + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + 0 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Select the type of instance to create.</h3> + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Create a global instance + + + true + + + Global instances are stored in %1, but some paths can be changed to be on a different drive if necessary. A single installation of Mod Organizer can manage multiple global instances. + + + + + + + Create a portable instance + + + true + + + A portable instance stores everything in Mod Organizer's installation folder, currently %1. There can only be one portable instance per installation of Mod Organizer. + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Select the game to manage.</h3> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Show all supported games + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Pick a name for this instance.</h3> + + + + + + + + + + + 0 + + + + + Instance name + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Select a folder where the data should be stored.</h3> + + + + + + + This includes downloads, mods, profiles and overwrite. If there is enough space on this drive, you should use the default folder. + + + true + + + + + + + + + + + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + ... + + + + + + + Location + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Overwrite + + + + + + + Mods + + + + + + + Base directory + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Profiles + + + + + + + + + + Downloads + + + + + + + + + + + + + + + + + + + Use <code>%BASE_DIR%</code> to refer to the Base Directory. + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 292 + 20 + + + + + + + + Show advanced options + + + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + < Back + + + + + + + Next > + + + true + + + + + + + Cancel + + + + + + + + + + + diff --git a/src/instancemanager.h b/src/instancemanager.h index 8bbbbee9..d53f4391 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -49,6 +49,7 @@ public: bool allowedToChangeInstance() const; static bool isPortablePath(const QString& dataPath); + QString instancesPath() const; QStringList instanceNames() const; std::vector instancePaths() const; @@ -56,7 +57,6 @@ private: InstanceManager(); - QString instancesPath() const; QString instancePath(const QString& instanceName) const; bool deleteLocalInstance(const QString &instanceId) const; diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 1b482099..c7042aa8 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -1,12 +1,19 @@ #include "instancemanagerdialog.h" #include "ui_instancemanagerdialog.h" #include "instancemanager.h" +#include "createinstancedialog.h" +#include "settings.h" +#include "selectiondialog.h" +#include "plugincontainer.h" +#include "shared/appconfig.h" +#include class InstanceInfo { public: - InstanceInfo(QDir dir) - : m_dir(std::move(dir)) + InstanceInfo(QDir dir) : + m_dir(std::move(dir)), + m_settings(dir.filePath(QString::fromStdWString(AppConfig::iniFileName()))) { } @@ -15,22 +22,105 @@ public: return m_dir.dirName(); } + QString gameName() const + { + if (auto n=m_settings.game().name()) { + if (auto e=m_settings.game().edition()) { + if (!e->isEmpty()) { + return *n + " (" + *e + ")"; + } + } + + return *n; + } else { + return {}; + } + } + + QString gamePath() const + { + if (auto n=m_settings.game().directory()) { + return *n; + } else { + return {}; + } + } + + QString location() const + { + return m_dir.path(); + } + + QString baseDirectory() const + { + return m_settings.paths().base(); + } + private: QDir m_dir; + Settings m_settings; }; -InstanceManagerDialog::InstanceManagerDialog(QWidget *parent) - : QDialog(parent), ui(new Ui::InstanceManagerDialog) +InstanceManagerDialog::InstanceManagerDialog( + const PluginContainer& pc, QWidget *parent) + : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc) { ui->setupUi(this); + ui->splitter->setSizes({200, 1}); + ui->splitter->setStretchFactor(0, 0); + ui->splitter->setStretchFactor(1, 1); auto& m = InstanceManager::instance(); for (auto&& d : m.instancePaths()) { - InstanceInfo i(d); - ui->list->addItem(i.name()); + auto ii = std::make_unique(d); + + ui->list->addItem(ii->name()); + m_instances.push_back(std::move(ii)); + } + + if (!m_instances.empty()) { + select(0); } + + connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); + connect(ui->list, &QListWidget::itemSelectionChanged, [&]{ onSelection(); }); } InstanceManagerDialog::~InstanceManagerDialog() = default; + +void InstanceManagerDialog::select(std::size_t i) +{ + if (i >= m_instances.size()) { + return; + } + + const auto& ii = m_instances[i]; + fill(*ii); +} + +void InstanceManagerDialog::onSelection() +{ + const auto sel = ui->list->selectionModel()->selectedIndexes(); + if (sel.size() != 1) { + return; + } + + select(static_cast(sel[0].row())); +} + +void InstanceManagerDialog::createNew() +{ + CreateInstanceDialog dlg(this); + dlg.exec(); +} + +void InstanceManagerDialog::fill(const InstanceInfo& ii) +{ + ui->name->setText(ii.name()); + ui->location->setText(ii.location()); + ui->baseDirectory->setText(ii.baseDirectory()); + ui->gameName->setText(ii.gameName()); + ui->gameDir->setText(ii.gamePath()); +} diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index ea1cfc48..c3e947dd 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -5,16 +5,30 @@ namespace Ui { class InstanceManagerDialog; }; +class InstanceInfo; +class PluginContainer; + class InstanceManagerDialog : public QDialog { Q_OBJECT public: - explicit InstanceManagerDialog(QWidget *parent = nullptr); + explicit InstanceManagerDialog( + const PluginContainer& pc, QWidget *parent = nullptr); + ~InstanceManagerDialog(); + void select(std::size_t i); + private: std::unique_ptr ui; + const PluginContainer& m_pc; + std::vector> m_instances; + + void onSelection(); + void createNew(); + + void fill(const InstanceInfo& ii); }; #endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index b35d2e58..146b5fb5 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -6,7 +6,7 @@ 0 0 - 531 + 689 413 @@ -40,17 +40,6 @@ - - - - Create portable instance - - - - :/MO/gui/package:/MO/gui/package - - - @@ -65,9 +54,9 @@ - + - <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance"><span style=" text-decoration: underline; color:#0000ff;">What is an instance?</span></a></p></body></html> + <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance">What is an instance?</a></p></body></html> true @@ -134,7 +123,7 @@ - + 0 @@ -148,33 +137,51 @@ 0 - + + + true + + + + + - instance name + Name - - Qt::TextBrowserInteraction + + + + + + Explore - + - Game: + Game - - - - Name: + + + + true + + + + + + + true - Location: + Location @@ -185,30 +192,24 @@ - - + + - location path - - - Qt::TextBrowserInteraction + Game location - Base folder: + Base folder - - - base folder path - - - Qt::TextBrowserInteraction + + + true @@ -219,20 +220,17 @@ - - - - Explore + + + + true - - + + - game name - - - Qt::TextBrowserInteraction + Explore @@ -292,6 +290,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -371,6 +382,13 @@ + + + LinkLabel + QLabel +
    linklabel.h
    +
    +
    diff --git a/src/main.cpp b/src/main.cpp index 08d70dd9..71fb0bbd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "tutorialmanager.h" #include "nxmaccessmanager.h" #include "instancemanager.h" +#include "instancemanagerdialog.h" #include "organizercore.h" #include "env.h" #include "envmodule.h" @@ -298,6 +299,8 @@ int runApplication( try { Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); + settings.setGlobalInstance(); + log::getDefault().setLevel(settings.diagnostics().logLevel()); log::debug("using ini at '{}'", settings.filename()); @@ -429,6 +432,12 @@ int runApplication( tt.stop(); + QTimer::singleShot(std::chrono::milliseconds(1), [&] + { + InstanceManagerDialog dlg(*pluginContainer, &mainWindow); + dlg.exec(); + }); + res = application.exec(); mainWindow.close(); diff --git a/src/settings.cpp b/src/settings.cpp index d37f0d99..43e7a4fa 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -68,17 +68,24 @@ Settings::Settings(const QString& path) : m_Network(m_Settings), m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), m_Interface(m_Settings), m_Diagnostics(m_Settings) { - if (s_Instance != nullptr) { - throw std::runtime_error("second instance of \"Settings\" created"); - } else { - s_Instance = this; - } } Settings::~Settings() { MOBase::QuestionBoxMemory::setCallbacks({}, {}, {}); - s_Instance = nullptr; + + if (s_Instance == this) { + s_Instance = nullptr; + } +} + +void Settings::setGlobalInstance() +{ + if (s_Instance != nullptr) { + throw std::runtime_error("second instance of \"Settings\" created"); + } else { + s_Instance = this; + } } Settings &Settings::instance() diff --git a/src/settings.h b/src/settings.h index a1b30462..84102c72 100644 --- a/src/settings.h +++ b/src/settings.h @@ -681,6 +681,7 @@ public: ~Settings(); static Settings &instance(); + void setGlobalInstance(); // name of the ini file // -- cgit v1.3.1 From 717be0c0483839ef4da22b366ed5bf8f07e379e2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 09:17:43 -0400 Subject: game page --- src/createinstancedialog.cpp | 280 +++++++++++++++++++++++++++++++++++++++--- src/createinstancedialog.h | 7 +- src/createinstancedialog.ui | 55 ++++++++- src/instancemanagerdialog.cpp | 2 +- 4 files changed, 324 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index d43fba1c..21dcd8ec 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -1,6 +1,9 @@ #include "createinstancedialog.h" #include "ui_createinstancedialog.h" #include "instancemanager.h" +#include "plugincontainer.h" +#include +#include namespace cid { @@ -9,7 +12,7 @@ class Page { public: Page(CreateInstanceDialog& dlg, std::size_t i) - : ui(dlg.getUI()), m_dlg(dlg), m_index(i) + : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_index(i) { } @@ -25,12 +28,14 @@ public: protected: Ui::CreateInstanceDialog* ui; + CreateInstanceDialog& m_dlg; + const PluginContainer& m_pc; private: - CreateInstanceDialog& m_dlg; std::size_t m_index; }; + class TypePage : public Page { public: @@ -88,6 +93,254 @@ public: GamePage(CreateInstanceDialog& dlg, std::size_t i) : Page(dlg, i) { + createGames(); + fillList(); + + QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); }); + } + + void select(MOBase::IPluginGame* game) + { + Game* checked = findGame(game); + if (!checked) { + return; + } + + 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); + } + } + + selectButton(checked); + } + +private: + struct Game + { + MOBase::IPluginGame* game = nullptr; + QCommandLinkButton* button = nullptr; + QString dir; + bool installed = false; + + Game(MOBase::IPluginGame* g) + : game(g), installed(g->isInstalled()) + { + if (installed) { + dir = game->gameDirectory().path(); + } + } + + Game(const Game&) = delete; + Game& operator=(const Game&) = delete; + }; + + std::vector> m_games; + + + Game* findGame(MOBase::IPluginGame* game) + { + for (auto& g : m_games) { + if (g->game == game) { + return g.get(); + } + } + + return nullptr; + } + + void createGames() + { + m_games.clear(); + + for (auto* game : m_pc.plugins()) { + m_games.push_back(std::make_unique(game)); + } + } + + void createButton(Game* g) + { + g->button = new QCommandLinkButton; + g->button->setCheckable(true); + + updateButton(g); + + QObject::connect(g->button, &QAbstractButton::clicked, [g, this] { + select(g->game); + }); + } + + void updateButton(Game* g) + { + if (!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) + { + for (const auto& gg : m_games) { + if (!gg->button) { + continue; + } + + if (g) { + gg->button->setChecked(gg->game == g->game); + } else { + gg->button->setChecked(false); + } + } + } + + void fillList() + { + const bool showAll = ui->showAllGames->isChecked(); + + ui->games->clear(); + + for (auto& g : m_games) { + g->button = nullptr; + + if (!showAll && !g->installed) { + // not installed + continue; + } + + createButton(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; + } + + MOBase::IPluginGame* findAnotherGame(const QString& path) + { + for (auto* otherGame : m_pc.plugins()) { + if (otherGame->looksValid(path)) { + return otherGame; + } + } + + return nullptr; + } + + bool confirmUnknown(const QString& path, MOBase::IPluginGame* game) + { + const auto r = MOBase::TaskDialog(&m_dlg) + .title(QObject::tr("Unrecognized game")) + .main(QObject::tr("Unrecognized game")) + .content(QObject::tr( + "The folder %1 does not seem to contain installation for " + "%2 or " + "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); + } + + MOBase::IPluginGame* confirmOtherGame( + const QString& path, + MOBase::IPluginGame* selectedGame, MOBase::IPluginGame* guessedGame) + { + const auto r = MOBase::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 " + "%2, " + "not " + "%3.") + .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; + } } }; @@ -114,8 +367,9 @@ public: } // namespace -CreateInstanceDialog::CreateInstanceDialog(QWidget *parent) - : QDialog(parent), ui(new Ui::CreateInstanceDialog) +CreateInstanceDialog::CreateInstanceDialog( + const PluginContainer& pc, QWidget *parent) + : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc) { using namespace cid; @@ -132,19 +386,6 @@ CreateInstanceDialog::CreateInstanceDialog(QWidget *parent) connect(ui->next, &QPushButton::clicked, [&]{ next(); }); connect(ui->back, &QPushButton::clicked, [&]{ back(); }); - - // - //SelectionDialog games(tr("Select a game to manage.")); - // - //for (auto* game : m_pc.plugins()) { - // if (game->isInstalled()) { - // games.addChoice(game->gameName(), game->gameDirectory().path(), {}); - // } else { - // games.addChoice(game->gameName(), "", {}); - // } - //} - // - //games.exec(); } CreateInstanceDialog::~CreateInstanceDialog() = default; @@ -154,6 +395,11 @@ Ui::CreateInstanceDialog* CreateInstanceDialog::getUI() return ui.get(); } +const PluginContainer& CreateInstanceDialog::pluginContainer() +{ + return m_pc; +} + void CreateInstanceDialog::next() { ui->pages->setCurrentIndex(ui->pages->currentIndex() + 1); diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 03e9de01..df056f85 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -6,22 +6,27 @@ namespace Ui { class CreateInstanceDialog; }; namespace cid { class Page; } +class PluginContainer; + class CreateInstanceDialog : public QDialog { Q_OBJECT public: - explicit CreateInstanceDialog(QWidget *parent = nullptr); + explicit CreateInstanceDialog( + const PluginContainer& pc, QWidget *parent = nullptr); ~CreateInstanceDialog(); Ui::CreateInstanceDialog* getUI(); + const PluginContainer& pluginContainer(); void next(); void back(); private: std::unique_ptr ui; + const PluginContainer& m_pc; std::vector> m_pages; void updateNavigationButtons(); diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index bcfddb20..8cad959d 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -227,7 +227,60 @@ 0 - + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 455 + 287 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::Vertical + + + QDialogButtonBox::NoButton + + + + + + diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index c7042aa8..566f1aad 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -112,7 +112,7 @@ void InstanceManagerDialog::onSelection() void InstanceManagerDialog::createNew() { - CreateInstanceDialog dlg(this); + CreateInstanceDialog dlg(m_pc, this); dlg.exec(); } -- cgit v1.3.1 From e0ed1c2153be0a9f922a619c527b3646b40377eb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 09:45:59 -0400 Subject: select custom game directory --- src/createinstancedialog.cpp | 119 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 100 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 21dcd8ec..79eaa79a 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -91,7 +91,7 @@ class GamePage : public Page { public: GamePage(CreateInstanceDialog& dlg, std::size_t i) - : Page(dlg, i) + : Page(dlg, i), m_selection(nullptr) { createGames(); fillList(); @@ -117,9 +117,49 @@ public: } } + m_selection = checked; selectButton(checked); } + 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 : m_pc.plugins()) { + supportedGames += "
  • " + game->gameName() + "
  • "; + } + + QMessageBox::warning(&m_dlg, + QObject::tr("Unrecognized game"), + QObject::tr( + "The folder %1 does not seem to contain a game Mod Organizer can " + "manage.

    These are the games that can be managed:" + "
      %2
    ").arg(path).arg(supportedGames)); + } + private: struct Game { @@ -141,6 +181,7 @@ private: }; std::vector> m_games; + Game* m_selection; Game* findGame(MOBase::IPluginGame* game) @@ -163,21 +204,9 @@ private: } } - void createButton(Game* g) - { - g->button = new QCommandLinkButton; - g->button->setCheckable(true); - - updateButton(g); - - QObject::connect(g->button, &QAbstractButton::clicked, [g, this] { - select(g->game); - }); - } - void updateButton(Game* g) { - if (!g->button) { + if (!g || !g->button) { return; } @@ -192,25 +221,77 @@ private: 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 (!gg->button) { + if (!g) { + // nothing should be selected + if (gg->button) { + gg->button->setChecked(false); + } + continue; } - if (g) { - gg->button->setChecked(gg->game == g->game); + 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 { - gg->button->setChecked(false); + // 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; @@ -219,7 +300,7 @@ private: continue; } - createButton(g.get()); + createGameButton(g.get()); ui->games->addButton(g->button, QDialogButtonBox::AcceptRole); } } -- cgit v1.3.1 From b10bcf4cb72bf3db06735a6b893c93319cead965 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 10:27:58 -0400 Subject: instance name page --- src/createinstancedialog.cpp | 215 ++++++++++++++++++++++++++++++++++++++----- src/createinstancedialog.h | 16 +++- src/createinstancedialog.ui | 23 ++++- src/instancemanager.cpp | 21 +++++ src/instancemanager.h | 5 +- 5 files changed, 252 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 79eaa79a..908c20d6 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -21,11 +21,40 @@ public: 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 selectedType() const + { + // no-op + return CreateInstanceDialog::NoType; + } + + virtual MOBase::IPluginGame* selectedGame() const + { + // no-op + return nullptr; + } + protected: Ui::CreateInstanceDialog* ui; CreateInstanceDialog& m_dlg; @@ -40,7 +69,7 @@ class TypePage : public Page { public: TypePage(CreateInstanceDialog& dlg, std::size_t i) - : Page(dlg, i) + : Page(dlg, i), m_type(CreateInstanceDialog::NoType) { ui->createGlobal->setDescription( ui->createGlobal->description() @@ -59,12 +88,17 @@ public: bool ready() const override { - return m_global.has_value(); + return (m_type != CreateInstanceDialog::NoType); + } + + CreateInstanceDialog::Types selectedType() const + { + return m_type; } void global() { - m_global = true; + m_type = CreateInstanceDialog::Global; ui->createGlobal->setChecked(true); ui->createPortable->setChecked(false); @@ -74,7 +108,7 @@ public: void portable() { - m_global = false; + m_type = CreateInstanceDialog::Portable; ui->createGlobal->setChecked(false); ui->createPortable->setChecked(true); @@ -83,7 +117,7 @@ public: } private: - std::optional m_global; + CreateInstanceDialog::Types m_type; }; @@ -99,26 +133,40 @@ public: QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); }); } + bool ready() const override + { + return (m_selection != nullptr); + } + + MOBase::IPluginGame* selectedGame() const override + { + if (!m_selection) { + return nullptr; + } + + return m_selection->game; + } + void select(MOBase::IPluginGame* game) { Game* checked = findGame(game); - if (!checked) { - return; - } - if (!checked->installed) { - const auto path = QFileDialog::getExistingDirectory( - &m_dlg, QObject::tr("Find game installation")); + 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); + if (path.isEmpty()) { + checked = nullptr; + } else { + checked = checkInstallation(path, checked); + } } } m_selection = checked; selectButton(checked); + updateNavigation(); } void selectCustom() @@ -430,8 +478,90 @@ class NamePage : public Page { public: NamePage(CreateInstanceDialog& dlg, std::size_t i) - : Page(dlg, i) + : Page(dlg, i), m_modified(false), m_okay(false) + { + 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.selectedType() == CreateInstanceDialog::Portable); + } + + void activated() override + { + auto* g = m_dlg.selectedGame(); + 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(); + } + +private: + QString m_originalLabel; + bool m_modified; + bool m_okay; + + void onChanged() + { + m_modified = true; + updateWarnings(); + } + + void updateWarnings() + { + bool exists = false; + bool invalid = false; + bool empty = false; + + auto& m = InstanceManager::instance(); + + const auto name = ui->instanceName->text().trimmed(); + + if (name.isEmpty()) { + empty = true; + } else { + const auto sanitized = m.sanitizeInstanceName(name); + if (name != sanitized) { + invalid = true; + } else { + exists = m.instanceExists(name); + } + } + + if (exists) { + m_okay = false; + ui->instanceNameExists->setVisible(true); + ui->instanceNameInvalid->setVisible(false); + } else if (invalid) { + m_okay = false; + ui->instanceNameExists->setVisible(false); + ui->instanceNameInvalid->setVisible(true); + } else { + m_okay = !empty; + ui->instanceNameExists->setVisible(false); + ui->instanceNameInvalid->setVisible(false); + } + + updateNavigation(); } }; @@ -463,7 +593,7 @@ CreateInstanceDialog::CreateInstanceDialog( ui->pages->setCurrentIndex(0); - updateNavigationButtons(); + updateNavigation(); connect(ui->next, &QPushButton::clicked, [&]{ next(); }); connect(ui->back, &QPushButton::clicked, [&]{ back(); }); @@ -483,17 +613,35 @@ const PluginContainer& CreateInstanceDialog::pluginContainer() void CreateInstanceDialog::next() { - ui->pages->setCurrentIndex(ui->pages->currentIndex() + 1); - updateNavigationButtons(); + selectPage(ui->pages->currentIndex() + 1); } void CreateInstanceDialog::back() { - ui->pages->setCurrentIndex(ui->pages->currentIndex() - 1); - updateNavigationButtons(); + selectPage(ui->pages->currentIndex() - 1); } -void CreateInstanceDialog::updateNavigationButtons() +void CreateInstanceDialog::selectPage(std::size_t i) +{ + while (i < m_pages.size()) { + if (!m_pages[i]->skip()) { + break; + } + + ++i; + } + + if (i >= m_pages.size()) { + return; + } + + ui->pages->setCurrentIndex(static_cast(i)); + m_pages[i]->activated(); + + updateNavigation(); +} + +void CreateInstanceDialog::updateNavigation() { const auto i = ui->pages->currentIndex(); const auto last = (i == (ui->pages->count() - 1)); @@ -501,3 +649,26 @@ void CreateInstanceDialog::updateNavigationButtons() ui->next->setEnabled(m_pages[i]->ready() && !last); ui->back->setEnabled(i > 0); } + +CreateInstanceDialog::Types CreateInstanceDialog::selectedType() const +{ + for (auto&& p : m_pages) { + const auto t = p->selectedType(); + if (t != NoType) { + return t; + } + } + + return NoType; +} + +MOBase::IPluginGame* CreateInstanceDialog::selectedGame() const +{ + for (auto&& p : m_pages) { + if (auto* g=p->selectedGame()) { + return g; + } + } + + return nullptr; +} diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index df056f85..3fd19d55 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -3,6 +3,7 @@ #include +namespace MOBase { class IPluginGame; } namespace Ui { class CreateInstanceDialog; }; namespace cid { class Page; } @@ -13,6 +14,13 @@ class CreateInstanceDialog : public QDialog Q_OBJECT public: + enum Types + { + NoType = 0, + Global, + Portable + }; + explicit CreateInstanceDialog( const PluginContainer& pc, QWidget *parent = nullptr); @@ -23,13 +31,17 @@ public: void next(); void back(); + void selectPage(std::size_t i); + + void updateNavigation(); + + Types selectedType() const; + MOBase::IPluginGame* selectedGame() const; private: std::unique_ptr ui; const PluginContainer& m_pc; std::vector> m_pages; - - void updateNavigationButtons(); }; #endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 8cad959d..6f215539 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -305,9 +305,12 @@ 0 - + - <h3>Pick a name for this instance.</h3> + <h3>Customize the name for this <span style="white-space: nowrap;">%1</span> instance.</h3> + + + true @@ -318,7 +321,7 @@ - 0 + 9 @@ -327,6 +330,20 @@ + + + + There is already an instance with this name. + + + + + + + The name contains invalid characters. It must be a valid folder name. + + + diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 98edba47..b135cac1 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -650,6 +650,27 @@ MOBase::IPluginGame* InstanceManager::determineCurrentGame( return nullptr; } +QString InstanceManager::makeUniqueName(const QString& instanceName) const +{ + const QString sanitized = sanitizeInstanceName(instanceName); + + QString name = sanitized; + for (int i=2; i<100; ++i) { + if (!instanceExists(name)) { + return name; + } + + name = QString("%1 (%2)").arg(sanitized).arg(i); + } + + return {}; +} + +bool InstanceManager::instanceExists(const QString& instanceName) const +{ + const QDir root = instancesPath(); + return root.exists(instanceName); +} QString InstanceManager::sanitizeInstanceName(const QString &name) const { diff --git a/src/instancemanager.h b/src/instancemanager.h index d53f4391..0cd5ef4c 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -53,6 +53,10 @@ public: QStringList instanceNames() const; std::vector instancePaths() const; + QString sanitizeInstanceName(const QString &name) const; + QString makeUniqueName(const QString& instanceName) const; + bool instanceExists(const QString& instanceName) const; + private: InstanceManager(); @@ -63,7 +67,6 @@ private: QString manageInstances(const QStringList &instanceList) const; - QString sanitizeInstanceName(const QString &name) const; void setCurrentInstance(const QString &name); QString queryInstanceName(const QStringList &instanceList) const; -- cgit v1.3.1 From 46d81056d1eda2fd74dac200cd7b8c36d9cbc4eb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 10:50:31 -0400 Subject: editions page --- src/createinstancedialog.cpp | 174 +++++++++++++++++++++++++++++++++++++------ src/createinstancedialog.h | 1 + src/createinstancedialog.ui | 106 +++++++++++++++++++++++++- 3 files changed, 258 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 908c20d6..262deb20 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -11,8 +11,8 @@ namespace cid class Page { public: - Page(CreateInstanceDialog& dlg, std::size_t i) - : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_index(i) + Page(CreateInstanceDialog& dlg) + : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()) { } @@ -55,21 +55,24 @@ public: return nullptr; } + virtual QString instanceName() const + { + // no-op + return {}; + } + protected: Ui::CreateInstanceDialog* ui; CreateInstanceDialog& m_dlg; const PluginContainer& m_pc; - -private: - std::size_t m_index; }; class TypePage : public Page { public: - TypePage(CreateInstanceDialog& dlg, std::size_t i) - : Page(dlg, i), m_type(CreateInstanceDialog::NoType) + TypePage(CreateInstanceDialog& dlg) + : Page(dlg), m_type(CreateInstanceDialog::NoType) { ui->createGlobal->setDescription( ui->createGlobal->description() @@ -124,8 +127,8 @@ private: class GamePage : public Page { public: - GamePage(CreateInstanceDialog& dlg, std::size_t i) - : Page(dlg, i), m_selection(nullptr) + GamePage(CreateInstanceDialog& dlg) + : Page(dlg), m_selection(nullptr) { createGames(); fillList(); @@ -474,11 +477,93 @@ private: }; +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.selectedGame(); + if (!g) { + // shouldn't happen + return true; + } + + const auto variants = g->gameVariants(); + return (variants.size() < 2); + } + + void activated() override + { + auto* g = m_dlg.selectedGame(); + + 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(); + } + +private: + MOBase::IPluginGame* m_previousGame; + std::vector m_buttons; + QString m_selection; + + void fillList() + { + ui->editions->clear(); + m_buttons.clear(); + + auto* g = m_dlg.selectedGame(); + 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, std::size_t i) - : Page(dlg, i), m_modified(false), m_okay(false) + NamePage(CreateInstanceDialog& dlg) + : Page(dlg), m_modified(false), m_okay(false) { m_originalLabel = ui->instanceNameLabel->text(); @@ -515,6 +600,16 @@ public: updateWarnings(); } + QString instanceName() const override + { + if (!m_okay) { + return {}; + } + + const auto text = ui->instanceName->text().trimmed(); + return InstanceManager::instance().sanitizeInstanceName(text); + } + private: QString m_originalLabel; bool m_modified; @@ -534,16 +629,16 @@ private: auto& m = InstanceManager::instance(); - const auto name = ui->instanceName->text().trimmed(); + const auto text = ui->instanceName->text().trimmed(); - if (name.isEmpty()) { + if (text.isEmpty()) { empty = true; } else { - const auto sanitized = m.sanitizeInstanceName(name); - if (name != sanitized) { + const auto sanitized = m.sanitizeInstanceName(text); + if (text != sanitized) { invalid = true; } else { - exists = m.instanceExists(name); + exists = m.instanceExists(text); } } @@ -569,9 +664,31 @@ private: class PathsPage : public Page { public: - PathsPage(CreateInstanceDialog& dlg, std::size_t i) - : Page(dlg, i) + PathsPage(CreateInstanceDialog& dlg) + : Page(dlg) { + QObject::connect( + ui->advancedPathOptions, &QCheckBox::clicked, [&]{ onAdvanced(); }); + + ui->pathPages->setCurrentIndex(0); + } + + void activated() override + { + const auto root = InstanceManager::instance().instancesPath(); + const auto path = QDir::toNativeSeparators(root + "/" + m_dlg.instanceName()); + + ui->location->setText(path); + } + +private: + void onAdvanced() + { + if (ui->advancedPathOptions->isChecked()) { + ui->pathPages->setCurrentIndex(1); + } else { + ui->pathPages->setCurrentIndex(0); + } } }; @@ -586,10 +703,11 @@ CreateInstanceDialog::CreateInstanceDialog( ui->setupUi(this); - m_pages.push_back(std::make_unique(*this, 0)); - m_pages.push_back(std::make_unique(*this, 1)); - m_pages.push_back(std::make_unique(*this, 2)); - m_pages.push_back(std::make_unique(*this, 3)); + m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); ui->pages->setCurrentIndex(0); @@ -672,3 +790,15 @@ MOBase::IPluginGame* CreateInstanceDialog::selectedGame() const return nullptr; } + +QString CreateInstanceDialog::instanceName() const +{ + for (auto&& p : m_pages) { + const auto s = p->instanceName(); + if (!s.isEmpty()) { + return s; + } + } + + return {}; +} diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 3fd19d55..14ed9f8e 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -37,6 +37,7 @@ public: Types selectedType() const; MOBase::IPluginGame* selectedGame() const; + QString instanceName() const; private: std::unique_ptr ui; diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 6f215539..751c5427 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -287,6 +287,110 @@ + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Select the game edition.</h3> + + + + + + + This game has multiple variants. The correct one must be selected or Mod Organizer will not be able to launch the game properly. + + + true + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + 0 + 0 + 455 + 257 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::Vertical + + + QDialogButtonBox::NoButton + + + + + + + + + + + + @@ -405,7 +509,7 @@ - 1 + 0 -- cgit v1.3.1 From ebbc8ed60c606b1b76fcea36eb2bd5db6373f7f0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 23 Jul 2020 12:20:55 -0400 Subject: paths page --- src/createinstancedialog.cpp | 337 +++++++++++++++++++++++++++++++++++++------ src/createinstancedialog.h | 3 + src/createinstancedialog.ui | 148 +++++++++++++------ 3 files changed, 395 insertions(+), 93 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 262deb20..4308969e 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -2,12 +2,158 @@ #include "ui_createinstancedialog.h" #include "instancemanager.h" #include "plugincontainer.h" +#include "shared/appconfig.h" #include #include namespace cid { +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)); + } + } +}; + + class Page { public: @@ -562,8 +708,9 @@ private: class NamePage : public Page { public: - NamePage(CreateInstanceDialog& dlg) - : Page(dlg), m_modified(false), m_okay(false) + NamePage(CreateInstanceDialog& dlg) : + Page(dlg), m_modified(false), m_okay(false), + m_checker(ui->instanceNameExists, ui->instanceNameInvalid) { m_originalLabel = ui->instanceNameLabel->text(); @@ -607,10 +754,11 @@ public: } const auto text = ui->instanceName->text().trimmed(); - return InstanceManager::instance().sanitizeInstanceName(text); + return m_checker.sanitizeFileName(text); } private: + PathChecker m_checker; QString m_originalLabel; bool m_modified; bool m_okay; @@ -623,39 +771,9 @@ private: void updateWarnings() { - bool exists = false; - bool invalid = false; - bool empty = false; - - auto& m = InstanceManager::instance(); - - const auto text = ui->instanceName->text().trimmed(); - - if (text.isEmpty()) { - empty = true; - } else { - const auto sanitized = m.sanitizeInstanceName(text); - if (text != sanitized) { - invalid = true; - } else { - exists = m.instanceExists(text); - } - } - - if (exists) { - m_okay = false; - ui->instanceNameExists->setVisible(true); - ui->instanceNameInvalid->setVisible(false); - } else if (invalid) { - m_okay = false; - ui->instanceNameExists->setVisible(false); - ui->instanceNameInvalid->setVisible(true); - } else { - m_okay = !empty; - ui->instanceNameExists->setVisible(false); - ui->instanceNameInvalid->setVisible(false); - } + const auto root = InstanceManager::instance().instancesPath(); + m_okay = m_checker.checkName(root, ui->instanceName->text()); updateNavigation(); } }; @@ -664,31 +782,109 @@ private: class PathsPage : public Page { public: - PathsPage(CreateInstanceDialog& dlg) - : Page(dlg) + 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 root = InstanceManager::instance().instancesPath(); - const auto path = QDir::toNativeSeparators(root + "/" + m_dlg.instanceName()); + const auto name = m_dlg.instanceName(); + + setPaths(name, (m_lastInstanceName != name)); + checkPaths(); + updateNavigation(); - ui->location->setText(path); + m_lastInstanceName = name; } 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.replace("%BASE_DIR%", 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); + } + } + + QString makeDefaultPath(const std::wstring& dir) + { + return "%BASE_DIR%\\" + QString::fromStdWString(dir); } }; @@ -702,6 +898,7 @@ CreateInstanceDialog::CreateInstanceDialog( using namespace cid; ui->setupUi(this); + m_originalNext = ui->next->text(); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); @@ -731,24 +928,62 @@ const PluginContainer& CreateInstanceDialog::pluginContainer() void CreateInstanceDialog::next() { - selectPage(ui->pages->currentIndex() + 1); + const auto i = ui->pages->currentIndex(); + const auto last = (i == (ui->pages->count() - 1)); + + if (last) { + finish(); + } else { + changePage(+1); + } } void CreateInstanceDialog::back() { - selectPage(ui->pages->currentIndex() - 1); + changePage(-1); } -void CreateInstanceDialog::selectPage(std::size_t i) +void CreateInstanceDialog::changePage(int d) { - while (i < m_pages.size()) { - if (!m_pages[i]->skip()) { - break; + std::size_t i = static_cast(ui->pages->currentIndex()); + + if (d > 0) { + for (;;) { + ++i; + + if (i >= m_pages.size()) { + break; + } + + if (!m_pages[i]->skip()) { + break; + } } + } else { + for (;;) { + if (i == 0) { + break; + } + + --i; - ++i; + if (!m_pages[i]->skip()) { + break; + } + } } + if (i < m_pages.size()) { + selectPage(i); + } +} + +void CreateInstanceDialog::finish() +{ +} + +void CreateInstanceDialog::selectPage(std::size_t i) +{ if (i >= m_pages.size()) { return; } @@ -764,8 +999,14 @@ void CreateInstanceDialog::updateNavigation() const auto i = ui->pages->currentIndex(); const auto last = (i == (ui->pages->count() - 1)); - ui->next->setEnabled(m_pages[i]->ready() && !last); + ui->next->setEnabled(m_pages[i]->ready()); ui->back->setEnabled(i > 0); + + if (last) { + ui->next->setText(tr("Finish")); + } else { + ui->next->setText(m_originalNext); + } } CreateInstanceDialog::Types CreateInstanceDialog::selectedType() const diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 14ed9f8e..ac1c30b6 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -32,6 +32,8 @@ public: void next(); void back(); void selectPage(std::size_t i); + void changePage(int d); + void finish(); void updateNavigation(); @@ -43,6 +45,7 @@ private: std::unique_ptr ui; const PluginContainer& m_pc; std::vector> m_pages; + QString m_originalNext; }; #endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 751c5427..98cbe96e 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -554,7 +554,7 @@ - + Qt::Vertical @@ -567,8 +567,26 @@ + + + + This folder already exists. + + + - + + + Folder + + + + + + + The folder contains invalid characters. + + @@ -604,10 +622,17 @@ 0 - - + + - Overwrite + ... + + + + + + + ... @@ -618,6 +643,27 @@ + + + + Folder + + + + + + + Folder + + + + + + + Overwrite + + + @@ -625,29 +671,30 @@ - - - - Qt::Vertical + + + + ... - - - 20 - 40 - + + + + + + Folder - + - - + + - Profiles + The folder %1 already exists. + + + true - - - @@ -656,40 +703,35 @@ - - - - - - - + + + Folder + + - - - - - - Use <code>%BASE_DIR%</code> to refer to the Base Directory. + + + Folder - - + + - ... + Profiles - - + + - ... + Use <code>%BASE_DIR%</code> to refer to the Base Directory. - - + + ... @@ -702,10 +744,26 @@ - - + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - ... + The folder %1 contains invalid characters. + + + true -- cgit v1.3.1 From 2cb5c21b8e15b02a09144ff6c1ab11e87879a420 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 Jul 2020 09:25:02 -0400 Subject: fixed settings doing weird stuff with multiple instances sort games by name added intro and confirmation pages --- src/createinstancedialog.cpp | 55 ++++++++++++++++++++++++++---- src/createinstancedialog.h | 1 + src/createinstancedialog.ui | 79 +++++++++++++++++++++++++++++++++++++++++++ src/instancemanagerdialog.cpp | 6 ++++ src/main.cpp | 10 +++--- src/mainwindow.cpp | 26 ++++++++------ src/settings.cpp | 40 +++++++++++----------- src/settings.h | 5 ++- 8 files changed, 178 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 4308969e..5db6d685 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -214,6 +214,16 @@ protected: }; +class InfoPage : public Page +{ +public: + InfoPage(CreateInstanceDialog& dlg) + : Page(dlg) + { + } +}; + + class TypePage : public Page { public: @@ -345,7 +355,7 @@ public: void warnUnrecognized(const QString& path) { QString supportedGames; - for (auto* game : m_pc.plugins()) { + for (auto* game : sortedGamePlugins()) { supportedGames += "
  • " + game->gameName() + "
  • "; } @@ -381,6 +391,21 @@ private: Game* m_selection; + std::vector sortedGamePlugins() const + { + std::vector v; + + for (auto* game : m_pc.plugins()) { + v.push_back(game); + } + + std::sort(v.begin(), v.end(), [](auto* a, auto* b) { + return (a->gameName() < b->gameName()); + }); + + return v; + } + Game* findGame(MOBase::IPluginGame* game) { for (auto& g : m_games) { @@ -396,7 +421,7 @@ private: { m_games.clear(); - for (auto* game : m_pc.plugins()) { + for (auto* game : sortedGamePlugins()) { m_games.push_back(std::make_unique(game)); } } @@ -563,9 +588,9 @@ private: .title(QObject::tr("Unrecognized game")) .main(QObject::tr("Unrecognized game")) .content(QObject::tr( - "The folder %1 does not seem to contain installation for " + "The folder %1 does not seem to contain an installation for " "%2 or " - "any other game Mod Organizer can manage.") + "for any other game Mod Organizer can manage.") .arg(path) .arg(game->gameName())) .button({ @@ -800,6 +825,11 @@ public: ui->pathPages->setCurrentIndex(0); } + bool skip() const override + { + return (m_dlg.selectedType() == CreateInstanceDialog::Portable); + } + bool ready() const override { return checkPaths(); @@ -900,11 +930,13 @@ CreateInstanceDialog::CreateInstanceDialog( ui->setupUi(this); m_originalNext = ui->next->text(); + m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); ui->pages->setCurrentIndex(0); @@ -926,10 +958,21 @@ const PluginContainer& CreateInstanceDialog::pluginContainer() return m_pc; } +bool CreateInstanceDialog::isOnLastPage() const +{ + for (int i=ui->pages->currentIndex() + 1; i < ui->pages->count(); ++i) { + if (!m_pages[i]->skip()) { + return false; + } + } + + return true; +} + void CreateInstanceDialog::next() { const auto i = ui->pages->currentIndex(); - const auto last = (i == (ui->pages->count() - 1)); + const auto last = isOnLastPage(); if (last) { finish(); @@ -997,7 +1040,7 @@ void CreateInstanceDialog::selectPage(std::size_t i) void CreateInstanceDialog::updateNavigation() { const auto i = ui->pages->currentIndex(); - const auto last = (i == (ui->pages->count() - 1)); + const auto last = isOnLastPage(); ui->next->setEnabled(m_pages[i]->ready()); ui->back->setEnabled(i > 0); diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index ac1c30b6..02608c8d 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -36,6 +36,7 @@ public: void finish(); void updateNavigation(); + bool isOnLastPage() const; Types selectedType() const; MOBase::IPluginGame* selectedGame() const; diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 98cbe96e..72d73020 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -60,6 +60,24 @@ 0 + + + + + + <h3>What is an instance?</h3> +<p>An instance is a full set of mods, downloads, profiles and configuration for a game. Each game must be managed in its own instance. Mod Organizer can freely switch between instances.</p> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + @@ -823,6 +841,67 @@ + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Confirmation</h3> + + + + + + + The instance is about to be created. Review the information below and press 'Finish'. + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + +
    diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 566f1aad..26e8eae1 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -8,6 +8,12 @@ #include "shared/appconfig.h" #include +void openInstanceManager(PluginContainer& pc, QWidget* parent) +{ + InstanceManagerDialog dlg(pc, parent); + dlg.exec(); +} + class InstanceInfo { public: diff --git a/src/main.cpp b/src/main.cpp index 71fb0bbd..58e22466 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -270,6 +270,8 @@ std::optional handleCommandLine( return {}; } +void openInstanceManager(PluginContainer& pc, QWidget* parent); + int runApplication( MOApplication &application, const cl::CommandLine& cl, SingleInstance &instance, const QString &dataPath) @@ -298,8 +300,9 @@ int runApplication( try { - Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); - settings.setGlobalInstance(); + Settings settings( + dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), + true); log::getDefault().setLevel(settings.diagnostics().logLevel()); @@ -434,8 +437,7 @@ int runApplication( QTimer::singleShot(std::chrono::milliseconds(1), [&] { - InstanceManagerDialog dlg(*pluginContainer, &mainWindow); - dlg.exec(); + openInstanceManager(*pluginContainer, &mainWindow); }); res = application.exec(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 65c1d65c..c2d83e36 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5952,20 +5952,24 @@ void MainWindow::on_actionNotifications_triggered() scheduleCheckForProblems(); } +void openInstanceManager(PluginContainer& pc, QWidget* parent); + void MainWindow::on_actionChange_Game_triggered() { - if (m_OrganizerCore.settings().interface().showChangeGameConfirmation()) { - const auto r = QMessageBox::question( - this, tr("Are you sure?"), tr("This will restart MO, continue?"), - QMessageBox::Yes | QMessageBox::Cancel); + openInstanceManager(m_PluginContainer, this); - if (r != QMessageBox::Yes) { - return; - } - } - - InstanceManager::instance().clearCurrentInstance(); - ExitModOrganizer(Exit::Restart); + //if (m_OrganizerCore.settings().interface().showChangeGameConfirmation()) { + // const auto r = QMessageBox::question( + // this, tr("Are you sure?"), tr("This will restart MO, continue?"), + // QMessageBox::Yes | QMessageBox::Cancel); + // + // if (r != QMessageBox::Yes) { + // return; + // } + //} + // + //InstanceManager::instance().clearCurrentInstance(); + //ExitModOrganizer(Exit::Restart); } void MainWindow::setCategoryListVisible(bool visible) diff --git a/src/settings.cpp b/src/settings.cpp index 43e7a4fa..8db9c623 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -61,33 +61,31 @@ QString toString(EndorsementState s) Settings *Settings::s_Instance = nullptr; -Settings::Settings(const QString& path) : +Settings::Settings(const QString& path, bool globalInstance) : m_Settings(path, QSettings::IniFormat), - m_Game(m_Settings), m_Geometry(m_Settings), m_Widgets(m_Settings), - m_Colors(m_Settings), m_Plugins(m_Settings), m_Paths(m_Settings), - m_Network(m_Settings), m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), + m_Game(m_Settings), m_Geometry(m_Settings), + m_Widgets(m_Settings, globalInstance), m_Colors(m_Settings), + m_Plugins(m_Settings), m_Paths(m_Settings), m_Network(m_Settings), + m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), m_Interface(m_Settings), m_Diagnostics(m_Settings) { + if (globalInstance) { + if (s_Instance != nullptr) { + throw std::runtime_error("second instance of \"Settings\" created"); + } else { + s_Instance = this; + } + } } Settings::~Settings() { - MOBase::QuestionBoxMemory::setCallbacks({}, {}, {}); - if (s_Instance == this) { + MOBase::QuestionBoxMemory::setCallbacks({}, {}, {}); s_Instance = nullptr; } } -void Settings::setGlobalInstance() -{ - if (s_Instance != nullptr) { - throw std::runtime_error("second instance of \"Settings\" created"); - } else { - s_Instance = this; - } -} - Settings &Settings::instance() { if (s_Instance == nullptr) { @@ -1011,13 +1009,15 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const } -WidgetSettings::WidgetSettings(QSettings& s) +WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance) : m_Settings(s) { - MOBase::QuestionBoxMemory::setCallbacks( - [this](auto&& w, auto&& f){ return questionButton(w, f); }, - [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, - [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); + if (globalInstance) { + MOBase::QuestionBoxMemory::setCallbacks( + [this](auto&& w, auto&& f){ return questionButton(w, f); }, + [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, + [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); + } } std::optional WidgetSettings::index(const QComboBox* cb) const diff --git a/src/settings.h b/src/settings.h index 84102c72..a8c527d6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -195,7 +195,7 @@ private: class WidgetSettings { public: - WidgetSettings(QSettings& s); + WidgetSettings(QSettings& s, bool globalInstance); // selected index for a combobox // @@ -677,11 +677,10 @@ class Settings : public QObject Q_OBJECT; public: - Settings(const QString& path); + Settings(const QString& path, bool globalInstance=false); ~Settings(); static Settings &instance(); - void setGlobalInstance(); // name of the ini file // -- cgit v1.3.1 From 7b02c77a3961d859aae270922db26bbc999677cb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 Jul 2020 10:10:19 -0400 Subject: review page --- src/createinstancedialog.cpp | 197 +++++++++++++++++++++++++++++++++++-------- src/createinstancedialog.h | 31 ++++++- src/createinstancedialog.ui | 6 +- 3 files changed, 194 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 5db6d685..8c6605ed 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -189,7 +189,7 @@ public: } - virtual CreateInstanceDialog::Types selectedType() const + virtual CreateInstanceDialog::Types selectedInstanceType() const { // no-op return CreateInstanceDialog::NoType; @@ -201,7 +201,25 @@ public: return nullptr; } - virtual QString instanceName() const + 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 {}; @@ -250,7 +268,7 @@ public: return (m_type != CreateInstanceDialog::NoType); } - CreateInstanceDialog::Types selectedType() const + CreateInstanceDialog::Types selectedInstanceType() const override { return m_type; } @@ -306,6 +324,15 @@ public: return m_selection->game; } + QString selectedGameLocation() const override + { + if (!m_selection) { + return {}; + } + + return m_selection->dir; + } + void select(MOBase::IPluginGame* game) { Game* checked = findGame(game); @@ -663,7 +690,7 @@ public: bool skip() const override { - auto* g = m_dlg.selectedGame(); + auto* g = m_dlg.game(); if (!g) { // shouldn't happen return true; @@ -675,7 +702,7 @@ public: void activated() override { - auto* g = m_dlg.selectedGame(); + auto* g = m_dlg.game(); if (m_previousGame != g) { m_previousGame = g; @@ -698,6 +725,22 @@ public: 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: MOBase::IPluginGame* m_previousGame; std::vector m_buttons; @@ -708,7 +751,7 @@ private: ui->editions->clear(); m_buttons.clear(); - auto* g = m_dlg.selectedGame(); + auto* g = m_dlg.game(); if (!g) { // shouldn't happen return; @@ -750,12 +793,12 @@ public: bool skip() const override { - return (m_dlg.selectedType() == CreateInstanceDialog::Portable); + return (m_dlg.instanceType() == CreateInstanceDialog::Portable); } void activated() override { - auto* g = m_dlg.selectedGame(); + auto* g = m_dlg.game(); if (!g) { // shouldn't happen, next should be disabled return; @@ -772,7 +815,7 @@ public: updateWarnings(); } - QString instanceName() const override + QString selectedInstanceName() const override { if (!m_okay) { return {}; @@ -827,7 +870,7 @@ public: bool skip() const override { - return (m_dlg.selectedType() == CreateInstanceDialog::Portable); + return (m_dlg.instanceType() == CreateInstanceDialog::Portable); } bool ready() const override @@ -846,6 +889,23 @@ public: 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; @@ -918,6 +978,82 @@ private: } }; + +class ConfirmationPage : public Page +{ +public: + ConfirmationPage(CreateInstanceDialog& dlg) + : Page(dlg) + { + } + + void activated() override + { + ui->review->setPlainText(makeReview()); + } + + QString makeReview() const + { + QStringList lines; + + const auto paths = m_dlg.paths(); + + // type + switch (m_dlg.instanceType()) + { + case CreateInstanceDialog::Global: + { + lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("Global"))); + lines.push_back(QObject::tr("Instance name: %1").arg(m_dlg.instanceName())); + + if (paths.downloads.isEmpty()) { + // simple settings + lines.push_back(QObject::tr("Instance location: %1").arg(paths.base)); + } else { + // advanced settings + lines.push_back(QObject::tr("Instance base folder: %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)); + } + + break; + } + + case CreateInstanceDialog::Portable: + { + lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("Portable"))); + lines.push_back(QObject::tr("Instance location: %1").arg(qApp->applicationDirPath())); + break; + } + + default: + { + lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("?"))); + } + } + + // game + MOBase::IPluginGame* game = m_dlg.game(); + + QString name = 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 @@ -936,7 +1072,7 @@ CreateInstanceDialog::CreateInstanceDialog( m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); - m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); ui->pages->setCurrentIndex(0); @@ -1052,37 +1188,32 @@ void CreateInstanceDialog::updateNavigation() } } -CreateInstanceDialog::Types CreateInstanceDialog::selectedType() const +CreateInstanceDialog::Types CreateInstanceDialog::instanceType() const { - for (auto&& p : m_pages) { - const auto t = p->selectedType(); - if (t != NoType) { - return t; - } - } + return getSelected(&cid::Page::selectedInstanceType); +} - return NoType; +MOBase::IPluginGame* CreateInstanceDialog::game() const +{ + return getSelected(&cid::Page::selectedGame); } -MOBase::IPluginGame* CreateInstanceDialog::selectedGame() const +QString CreateInstanceDialog::gameLocation() const { - for (auto&& p : m_pages) { - if (auto* g=p->selectedGame()) { - return g; - } - } + return getSelected(&cid::Page::selectedGameLocation); +} - return nullptr; +QString CreateInstanceDialog::gameEdition() const +{ + return getSelected(&cid::Page::selectedGameEdition); } QString CreateInstanceDialog::instanceName() const { - for (auto&& p : m_pages) { - const auto s = p->instanceName(); - if (!s.isEmpty()) { - return s; - } - } + return getSelected(&cid::Page::selectedInstanceName); +} - return {}; +CreateInstanceDialog::Paths CreateInstanceDialog::paths() const +{ + return getSelected(&cid::Page::selectedPaths); } diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 02608c8d..d9c392ca 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -21,6 +21,17 @@ public: Portable }; + struct Paths + { + QString base; + QString downloads; + QString mods; + QString profiles; + QString overwrite; + + auto operator<=>(const Paths&) const = default; + }; + explicit CreateInstanceDialog( const PluginContainer& pc, QWidget *parent = nullptr); @@ -38,15 +49,31 @@ public: void updateNavigation(); bool isOnLastPage() const; - Types selectedType() const; - MOBase::IPluginGame* selectedGame() const; + Types instanceType() const; + MOBase::IPluginGame* game() const; + QString gameLocation() const; + QString gameEdition() const; QString instanceName() const; + Paths paths() const; private: std::unique_ptr ui; const PluginContainer& m_pc; std::vector> m_pages; QString m_originalNext; + + template + T getSelected(T (cid::Page::*mf)() const) const + { + for (auto&& p : m_pages) { + const auto t = (p.get()->*mf)(); + if (t != T()) { + return t; + } + } + + return T(); + } }; #endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 72d73020..c899a5b7 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -891,11 +891,7 @@ 0 - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - +
    -- cgit v1.3.1 From 4ac8ab98563e223075c1c93852d4112ab6e4579c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 Jul 2020 15:29:28 -0400 Subject: don't skip paths for portable instance, what am I doing implemented actual creation removed mentions of %BASE_DIR% everywhere, use constant and functions from PathSettings --- modorganizer.natvis | 97 +++++++++++ src/createinstancedialog.cpp | 379 ++++++++++++++++++++++++++++++++++++------- src/createinstancedialog.h | 20 +++ src/createinstancedialog.ui | 16 +- src/instancemanager.h | 3 +- src/settings.cpp | 18 +- src/settings.h | 12 ++ src/settingsdialog.cpp | 2 +- src/settingsdialogpaths.cpp | 12 +- 9 files changed, 484 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/modorganizer.natvis b/modorganizer.natvis index fe4a7ce2..6e21e099 100644 --- a/modorganizer.natvis +++ b/modorganizer.natvis @@ -8,5 +8,102 @@ file={m_wsFile} loaded={m_loaded} expanded={m_expanded} }} + + <null> + {fileEntry} + fileEntry + + *((Qt5Core.dll!QSharedData *) this) + fileEntry + + + + + + {*((Qt5Core.dll!QFileInfoPrivate *) d_ptr.d)} + *((Qt5Core.dll!QFileInfoPrivate *) d_ptr.d) + + *((Qt5Core.dll!QFileInfoPrivate *) d_ptr.d) + + + + + + <null> + {m_filePath} + m_filePath + + + + + + <null> + {dirEntry} + dirEntry + + *((Qt5Core.dll!QSharedData *) this) + dirEntry + nameFilters + absoluteDirEntry + + + + + + + {*((Qt5Core.dll!QDirPrivate *) d_ptr.d)} + *((Qt5Core.dll!QDirPrivate *) d_ptr.d) + + *((Qt5Core.dll!QDirPrivate *) d_ptr.d) + + + + + + <null> + {fileName} + fileName + + *((Qt5Core.dll!QFileDevice *) this) + fileName + + + + + + {*((Qt5Core.dll!QFilePrivate *) d_ptr.d)} + *((Qt5Core.dll!QFilePrivate *) d_ptr.d) + + *((Qt5Core.dll!QFilePrivate *) d_ptr.d) + + diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 8c6605ed..5c85aa68 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -2,13 +2,19 @@ #include "ui_createinstancedialog.h" #include "instancemanager.h" #include "plugincontainer.h" +#include "settings.h" #include "shared/appconfig.h" #include #include +#include + +using namespace MOBase; namespace cid { +using MOBase::TaskDialog; + class PathChecker { public: @@ -153,6 +159,12 @@ private: } }; +QString makeDefaultPath(const std::wstring& dir) +{ + return QDir::toNativeSeparators( + PathSettings::makeDefaultPath(QString::fromStdWString(dir))); +} + class Page { @@ -195,7 +207,7 @@ public: return CreateInstanceDialog::NoType; } - virtual MOBase::IPluginGame* selectedGame() const + virtual IPluginGame* selectedGame() const { // no-op return nullptr; @@ -315,7 +327,7 @@ public: return (m_selection != nullptr); } - MOBase::IPluginGame* selectedGame() const override + IPluginGame* selectedGame() const override { if (!m_selection) { return nullptr; @@ -330,10 +342,10 @@ public: return {}; } - return m_selection->dir; + return QDir::toNativeSeparators(m_selection->dir); } - void select(MOBase::IPluginGame* game) + void select(IPluginGame* game) { Game* checked = findGame(game); @@ -397,12 +409,12 @@ public: private: struct Game { - MOBase::IPluginGame* game = nullptr; + IPluginGame* game = nullptr; QCommandLinkButton* button = nullptr; QString dir; bool installed = false; - Game(MOBase::IPluginGame* g) + Game(IPluginGame* g) : game(g), installed(g->isInstalled()) { if (installed) { @@ -418,11 +430,11 @@ private: Game* m_selection; - std::vector sortedGamePlugins() const + std::vector sortedGamePlugins() const { - std::vector v; + std::vector v; - for (auto* game : m_pc.plugins()) { + for (auto* game : m_pc.plugins()) { v.push_back(game); } @@ -433,7 +445,7 @@ private: return v; } - Game* findGame(MOBase::IPluginGame* game) + Game* findGame(IPluginGame* game) { for (auto& g : m_games) { if (g->game == game) { @@ -598,9 +610,9 @@ private: return g; } - MOBase::IPluginGame* findAnotherGame(const QString& path) + IPluginGame* findAnotherGame(const QString& path) { - for (auto* otherGame : m_pc.plugins()) { + for (auto* otherGame : m_pc.plugins()) { if (otherGame->looksValid(path)) { return otherGame; } @@ -609,9 +621,9 @@ private: return nullptr; } - bool confirmUnknown(const QString& path, MOBase::IPluginGame* game) + bool confirmUnknown(const QString& path, IPluginGame* game) { - const auto r = MOBase::TaskDialog(&m_dlg) + const auto r = TaskDialog(&m_dlg) .title(QObject::tr("Unrecognized game")) .main(QObject::tr("Unrecognized game")) .content(QObject::tr( @@ -632,11 +644,11 @@ private: return (r == QMessageBox::Ignore); } - MOBase::IPluginGame* confirmOtherGame( + IPluginGame* confirmOtherGame( const QString& path, - MOBase::IPluginGame* selectedGame, MOBase::IPluginGame* guessedGame) + IPluginGame* selectedGame, IPluginGame* guessedGame) { - const auto r = MOBase::TaskDialog(&m_dlg) + const auto r = TaskDialog(&m_dlg) .title(QObject::tr("Incorrect game")) .main(QObject::tr("Incorrect game")) .content(QObject::tr( @@ -742,7 +754,7 @@ public: } private: - MOBase::IPluginGame* m_previousGame; + IPluginGame* m_previousGame; std::vector m_buttons; QString m_selection; @@ -868,10 +880,6 @@ public: ui->pathPages->setCurrentIndex(0); } - bool skip() const override - { - return (m_dlg.instanceType() == CreateInstanceDialog::Portable); - } bool ready() const override { @@ -934,7 +942,7 @@ private: bool checkVarPath(QString path) const { - path.replace("%BASE_DIR%", ui->base->text()); + path = PathSettings::resolve(path, ui->base->text()); return checkAdvancedPath(path); } @@ -971,11 +979,6 @@ private: e->setText(path); } } - - QString makeDefaultPath(const std::wstring& dir) - { - return "%BASE_DIR%\\" + QString::fromStdWString(dir); - } }; @@ -990,54 +993,52 @@ public: void activated() override { ui->review->setPlainText(makeReview()); + ui->creationLog->clear(); } - QString makeReview() const + QString toLocalizedString(CreateInstanceDialog::Types t) const { - QStringList lines; - - const auto paths = m_dlg.paths(); - - // type - switch (m_dlg.instanceType()) + switch (t) { case CreateInstanceDialog::Global: - { - lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("Global"))); - lines.push_back(QObject::tr("Instance name: %1").arg(m_dlg.instanceName())); - - if (paths.downloads.isEmpty()) { - // simple settings - lines.push_back(QObject::tr("Instance location: %1").arg(paths.base)); - } else { - // advanced settings - lines.push_back(QObject::tr("Instance base folder: %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)); - } - - break; - } + return QObject::tr("Global"); case CreateInstanceDialog::Portable: - { - lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("Portable"))); - lines.push_back(QObject::tr("Instance location: %1").arg(qApp->applicationDirPath())); - break; - } + return QObject::tr("Portable"); default: - { - lines.push_back(QObject::tr("Instance type: %1").arg(QObject::tr("?"))); + 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 - MOBase::IPluginGame* game = m_dlg.game(); - - QString name = game->gameName(); + QString name = m_dlg.game()->gameName(); if (!m_dlg.gameEdition().isEmpty()) { name += " (" + m_dlg.gameEdition() + ")"; } @@ -1157,8 +1158,195 @@ void CreateInstanceDialog::changePage(int d) } } + +class Failed {}; + +class DirectoryCreator +{ +public: + DirectoryCreator(const DirectoryCreator&) = delete; + DirectoryCreator& operator=(const DirectoryCreator&) = delete; + + static std::unique_ptr create( + const QDir& target, std::function log) + { + return std::unique_ptr(new DirectoryCreator(target, log)); + } + + ~DirectoryCreator() + { + rollback(); + } + + void commit() + { + m_created.clear(); + } + + void rollback() noexcept + { + try + { + for (auto itor=m_created.rbegin(); itor!=m_created.rend(); ++itor) { + const auto r = shell::DeleteDirectoryRecursive(*itor); + if (!r) { + m_logger(r.toString()); + } + } + + m_created.clear(); + } + catch(...) + { + // eat it + } + } + +private: + std::function m_logger; + + DirectoryCreator(const QDir& target, std::function log) + : m_logger(log) + { + try + { + const QString s = QDir::toNativeSeparators(target.absolutePath()); + const QStringList cs = s.split("\\"); + + if (cs.empty()) { + return; + } + + QDir d(cs[0]); + + for (int i=1; i m_created; +}; + void CreateInstanceDialog::finish() { + ui->creationLog->clear(); + logCreation(tr("Creating instance...")); + + const auto& m = InstanceManager::instance(); + const auto ci = creationInfo(); + + auto logger = [&](QString s) { + logCreation(s); + }; + + auto createDir = [&](QString path) { + return DirectoryCreator::create(path, logger); + }; + + + try + { + std::vector> dirs; + + dirs.push_back(createDir(ci.dataPath)); + dirs.push_back(createDir(ci.paths.base)); + dirs.push_back(createDir(PathSettings::resolve(ci.paths.downloads, ci.paths.base))); + dirs.push_back(createDir(PathSettings::resolve(ci.paths.mods, ci.paths.base))); + dirs.push_back(createDir(PathSettings::resolve(ci.paths.profiles, ci.paths.base))); + dirs.push_back(createDir(PathSettings::resolve(ci.paths.overwrite, ci.paths.base))); + + + Settings s(ci.iniPath); + s.game().setName(ci.game->gameName()); + s.game().setDirectory(ci.gameLocation); + + if (!ci.gameEdition.isEmpty()) { + s.game().setEdition(ci.gameEdition); + } + + if (ci.type == Global) { + if (ci.paths.base != ci.dataPath) { + s.paths().setBase(ci.paths.base); + } + + if (ci.paths.downloads != cid::makeDefaultPath(AppConfig::downloadPath())) { + s.paths().setDownloads(ci.paths.downloads); + } + + if (ci.paths.mods != cid::makeDefaultPath(AppConfig::modsPath())) { + s.paths().setMods(ci.paths.mods); + } + + if (ci.paths.profiles != cid::makeDefaultPath(AppConfig::profilesPath())) { + s.paths().setProfiles(ci.paths.profiles); + } + + if (ci.paths.overwrite != cid::makeDefaultPath(AppConfig::overwritePath())) { + s.paths().setOverwrite(ci.paths.overwrite); + } + } + + + logCreation(tr("Writing %1...").arg(ci.iniPath)); + + const auto r = s.sync(); + if (r != QSettings::NoError) { + switch (r) + { + case QSettings::AccessError: + logCreation(formatSystemMessage(ERROR_ACCESS_DENIED)); + break; + + case QSettings::FormatError: + logCreation(tr("Format error.")); + break; + + default: + logCreation(tr("Error %1.").arg(static_cast(r))); + break; + } + + throw Failed(); + } + + for (auto& d : dirs) { + d->commit(); + } + + logCreation(tr("Done.")); + } + catch(Failed&) + { + } +} + +void CreateInstanceDialog::logCreation(const QString& s) +{ + ui->creationLog->insertPlainText(s + "\n"); +} + +void CreateInstanceDialog::logCreation(const std::wstring& s) +{ + logCreation(QString::fromStdWString(s)); } void CreateInstanceDialog::selectPage(std::size_t i) @@ -1213,7 +1401,72 @@ QString CreateInstanceDialog::instanceName() const return getSelected(&cid::Page::selectedInstanceName); } +QString CreateInstanceDialog::dataPath() const +{ + QString s; + + if (instanceType() == Portable) { + s = QDir(qApp->applicationDirPath()).absolutePath(); + } else { + s = InstanceManager::instance().instancePath(instanceName()); + } + + return QDir::toNativeSeparators(s); +} + CreateInstanceDialog::Paths CreateInstanceDialog::paths() const { return getSelected(&cid::Page::selectedPaths); } + +CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const +{ + CreationInfo ci; + + ci.type = getSelected(&cid::Page::selectedInstanceType); + ci.game = getSelected(&cid::Page::selectedGame); + ci.gameLocation = getSelected(&cid::Page::selectedGameLocation); + ci.gameEdition = getSelected(&cid::Page::selectedGameEdition); + ci.instanceName = getSelected(&cid::Page::selectedInstanceName); + ci.paths = getSelected(&cid::Page::selectedPaths); + ci.dataPath = dataPath(); + + ci.paths.base = QDir(ci.paths.base).absolutePath(); + + if (ci.paths.downloads.isEmpty()) { + ci.paths.downloads = cid::makeDefaultPath(AppConfig::downloadPath()); + } else if (!ci.paths.downloads.contains(PathSettings::BaseDirVariable)) { + ci.paths.downloads = QDir(ci.paths.downloads).absolutePath(); + } + + if (ci.paths.mods.isEmpty()) { + ci.paths.mods = cid::makeDefaultPath(AppConfig::modsPath()); + } else if (!ci.paths.mods.contains(PathSettings::BaseDirVariable)) { + ci.paths.mods = QDir(ci.paths.mods).absolutePath(); + } + + if (ci.paths.profiles.isEmpty()) { + ci.paths.profiles = cid::makeDefaultPath(AppConfig::profilesPath()); + } else if (!ci.paths.profiles.contains(PathSettings::BaseDirVariable)) { + ci.paths.profiles = QDir(ci.paths.profiles).absolutePath(); + } + + if (ci.paths.overwrite.isEmpty()) { + ci.paths.overwrite = cid::makeDefaultPath(AppConfig::overwritePath()); + } else if (!ci.paths.overwrite.contains(PathSettings::BaseDirVariable)) { + ci.paths.overwrite = QDir(ci.paths.overwrite).absolutePath(); + } + + ci.iniPath = QFileInfo( + ci.dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())) + .absoluteFilePath(); + + ci.paths.base = QDir::toNativeSeparators(ci.paths.base); + ci.paths.downloads = QDir::toNativeSeparators(ci.paths.downloads); + ci.paths.mods = QDir::toNativeSeparators(ci.paths.mods); + ci.paths.profiles = QDir::toNativeSeparators(ci.paths.profiles); + ci.paths.overwrite = QDir::toNativeSeparators(ci.paths.overwrite); + ci.paths.ini = QDir::toNativeSeparators(ci.paths.ini); + + return ci; +} diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index d9c392ca..2f5774ae 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -28,10 +28,24 @@ public: QString mods; QString profiles; QString overwrite; + QString ini; auto operator<=>(const Paths&) const = default; }; + struct CreationInfo + { + Types type; + MOBase::IPluginGame* game; + QString gameLocation; + QString gameEdition; + QString instanceName; + QString dataPath; + QString iniPath; + Paths paths; + }; + + explicit CreateInstanceDialog( const PluginContainer& pc, QWidget *parent = nullptr); @@ -54,8 +68,11 @@ public: QString gameLocation() const; QString gameEdition() const; QString instanceName() const; + QString dataPath() const; Paths paths() const; + CreationInfo creationInfo() const; + private: std::unique_ptr ui; const PluginContainer& m_pc; @@ -74,6 +91,9 @@ private: return T(); } + + void logCreation(const QString& s); + void logCreation(const std::wstring& s); }; #endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index c899a5b7..cfb343fa 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -891,7 +891,21 @@ 0 - + + + QTextEdit::NoWrap + + + + + + + QTextEdit::NoWrap + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + +
    diff --git a/src/instancemanager.h b/src/instancemanager.h index 0cd5ef4c..6a6b52ac 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -56,13 +56,12 @@ public: QString sanitizeInstanceName(const QString &name) const; QString makeUniqueName(const QString& instanceName) const; bool instanceExists(const QString& instanceName) const; + QString instancePath(const QString& instanceName) const; private: InstanceManager(); - QString instancePath(const QString& instanceName) const; - bool deleteLocalInstance(const QString &instanceId) const; QString manageInstances(const QStringList &instanceList) const; diff --git a/src/settings.cpp b/src/settings.cpp index 8db9c623..661cf429 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1464,6 +1464,8 @@ QSet PluginSettings::readBlacklist() const } +const QString PathSettings::BaseDirVariable = "%BASE_DIR%"; + PathSettings::PathSettings(QSettings& settings) : m_Settings(settings) { @@ -1511,10 +1513,10 @@ QString PathSettings::getConfigurablePath(const QString &key, bool resolve) const { QString result = QDir::fromNativeSeparators( - get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); + get(m_Settings, "Settings", key, makeDefaultPath(def))); if (resolve) { - result.replace("%BASE_DIR%", base()); + result = PathSettings::resolve(result, base()); } return result; @@ -1529,6 +1531,18 @@ void PathSettings::setConfigurablePath(const QString &key, const QString& path) } } +QString PathSettings::resolve(const QString& path, const QString& baseDir) +{ + QString s = path; + s.replace(BaseDirVariable, baseDir); + return s; +} + +QString PathSettings::makeDefaultPath(const QString dirName) +{ + return BaseDirVariable + "/" + dirName; +} + QString PathSettings::base() const { return QDir::fromNativeSeparators(get(m_Settings, diff --git a/src/settings.h b/src/settings.h index a8c527d6..c723faec 100644 --- a/src/settings.h +++ b/src/settings.h @@ -391,6 +391,9 @@ private: class PathSettings { public: + // %BASE_DIR% + static const QString BaseDirVariable; + PathSettings(QSettings& settings); QString base() const; @@ -418,6 +421,15 @@ public: std::map recent() const; void setRecent(const std::map& map); + + // resolves %BASE_DIR% + // + static QString resolve(const QString& path, const QString& baseDir); + + // returns %BASE_DIR%/dirName + // + static QString makeDefaultPath(const QString dirName); + private: QSettings& m_Settings; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 87c7201d..c4a53fcd 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -114,7 +114,7 @@ QString SettingsDialog::getColoredButtonStyleSheet() const void SettingsDialog::accept() { QString newModPath = ui->modDirEdit->text(); - newModPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + newModPath = PathSettings::resolve(newModPath, ui->baseDirEdit->text()); if ((QDir::fromNativeSeparators(newModPath) != QDir::fromNativeSeparators( diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 74ba4f25..eb334541 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -64,7 +64,7 @@ void PathsSettingsTab::update() std::tie(path, setter, defaultName) = dir; QString realPath = path; - realPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + realPath = PathSettings::resolve(realPath, ui->baseDirEdit->text()); if (!QDir(realPath).exists()) { if (!QDir().mkpath(realPath)) { @@ -113,7 +113,7 @@ void PathsSettingsTab::on_browseBaseDirBtn_clicked() void PathsSettingsTab::on_browseDownloadDirBtn_clicked() { QString searchPath = ui->downloadDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select download directory"), searchPath); if (!temp.isEmpty()) { @@ -124,7 +124,7 @@ void PathsSettingsTab::on_browseDownloadDirBtn_clicked() void PathsSettingsTab::on_browseModDirBtn_clicked() { QString searchPath = ui->modDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select mod directory"), searchPath); if (!temp.isEmpty()) { @@ -135,7 +135,7 @@ void PathsSettingsTab::on_browseModDirBtn_clicked() void PathsSettingsTab::on_browseCacheDirBtn_clicked() { QString searchPath = ui->cacheDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select cache directory"), searchPath); if (!temp.isEmpty()) { @@ -146,7 +146,7 @@ void PathsSettingsTab::on_browseCacheDirBtn_clicked() void PathsSettingsTab::on_browseProfilesDirBtn_clicked() { QString searchPath = ui->profilesDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select profiles directory"), searchPath); if (!temp.isEmpty()) { @@ -157,7 +157,7 @@ void PathsSettingsTab::on_browseProfilesDirBtn_clicked() void PathsSettingsTab::on_browseOverwriteDirBtn_clicked() { QString searchPath = ui->overwriteDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select overwrite directory"), searchPath); if (!temp.isEmpty()) { -- cgit v1.3.1 From 048e3ac0d9b4e4258c9c2ac4d39d332f340e73a0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 Jul 2020 15:43:13 -0400 Subject: split create instance dialog pages --- src/CMakeLists.txt | 1 + src/createinstancedialog.cpp | 1053 +------------------------------------ src/createinstancedialogpages.cpp | 976 ++++++++++++++++++++++++++++++++++ src/createinstancedialogpages.h | 219 ++++++++ 4 files changed, 1199 insertions(+), 1050 deletions(-) create mode 100644 src/createinstancedialogpages.cpp create mode 100644 src/createinstancedialogpages.h (limited to 'src') 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 +//#include #include #include 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 += "
  • " + game->gameName() + "
  • "; - } - - QMessageBox::warning(&m_dlg, - QObject::tr("Unrecognized game"), - QObject::tr( - "The folder %1 does not seem to contain a game Mod Organizer can " - "manage.

    These are the games that can be managed:" - "
      %2
    ").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> m_games; - Game* m_selection; - - - std::vector sortedGamePlugins() const - { - std::vector v; - - for (auto* game : m_pc.plugins()) { - 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)); - } - } - - 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()) { - 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 " - "%2 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 " - "%2, " - "not " - "%3.") - .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 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 +#include + +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 += "
  • " + game->gameName() + "
  • "; + } + + QMessageBox::warning(&m_dlg, + QObject::tr("Unrecognized game"), + QObject::tr( + "The folder %1 does not seem to contain a game Mod Organizer can " + "manage.

    These are the games that can be managed:" + "
      %2
    ").arg(path).arg(supportedGames)); +} + +std::vector GamePage::sortedGamePlugins() const +{ + std::vector v; + + for (auto* game : m_pc.plugins()) { + 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)); + } +} + +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()) { + 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 " + "%2 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 " + "%2, " + "not " + "%3.") + .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 +#include +#include +#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> m_games; + Game* m_selection; + + + std::vector 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 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 -- cgit v1.3.1 From cfdfa9d40e09d509396f57fbf70b48fa2136c230 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 21:17:24 -0400 Subject: PathChecker is unnecessary, refactored its guts into NamePage and PathsPage made existing paths just a warning, don't check for portable instances --- src/createinstancedialog.cpp | 72 ++++++----- src/createinstancedialog.ui | 8 +- src/createinstancedialogpages.cpp | 250 ++++++++++++++++++++++---------------- src/createinstancedialogpages.h | 39 +++--- src/instancemanager.cpp | 7 +- src/instancemanager.h | 1 + 6 files changed, 205 insertions(+), 172 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 63be7346..4b9f24ae 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -2,10 +2,8 @@ #include "ui_createinstancedialog.h" #include "createinstancedialogpages.h" #include "instancemanager.h" -//#include "plugincontainer.h" #include "settings.h" #include "shared/appconfig.h" -//#include #include #include @@ -80,6 +78,9 @@ void CreateInstanceDialog::changePage(int d) { std::size_t i = static_cast(ui->pages->currentIndex()); + // goes back or forwards until an unskippable page is reached, or the + // first/last page + if (d > 0) { for (;;) { ++i; @@ -359,7 +360,7 @@ QString CreateInstanceDialog::dataPath() const QString s; if (instanceType() == Portable) { - s = QDir(qApp->applicationDirPath()).absolutePath(); + s = QDir(InstanceManager::portablePath()).absolutePath(); } else { s = InstanceManager::instance().instancePath(instanceName()); } @@ -372,8 +373,31 @@ CreateInstanceDialog::Paths CreateInstanceDialog::paths() const return getSelected(&cid::Page::selectedPaths); } +void fixVarDir(QString& path, const std::wstring& defaultDir) +{ + if (path.isEmpty()) { + path = cid::makeDefaultPath(defaultDir); + } else if (!path.contains(PathSettings::BaseDirVariable)) { + path = QDir(path).absolutePath(); + } + + path = QDir::toNativeSeparators(path); +} + +void fixDirPath(QString& path) +{ + path = QDir::toNativeSeparators(QDir(path).absolutePath()); +} + +void fixFilePath(QString& path) +{ + path = QDir::toNativeSeparators(QFileInfo(path).absolutePath()); +} + CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const { + const auto iniFilename = QString::fromStdWString(AppConfig::iniFileName()); + CreationInfo ci; ci.type = getSelected(&cid::Page::selectedInstanceType); @@ -383,43 +407,15 @@ CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const ci.instanceName = getSelected(&cid::Page::selectedInstanceName); ci.paths = getSelected(&cid::Page::selectedPaths); ci.dataPath = dataPath(); + ci.iniPath = ci.dataPath + "/" + iniFilename; - ci.paths.base = QDir(ci.paths.base).absolutePath(); - - if (ci.paths.downloads.isEmpty()) { - ci.paths.downloads = cid::makeDefaultPath(AppConfig::downloadPath()); - } else if (!ci.paths.downloads.contains(PathSettings::BaseDirVariable)) { - ci.paths.downloads = QDir(ci.paths.downloads).absolutePath(); - } - - if (ci.paths.mods.isEmpty()) { - ci.paths.mods = cid::makeDefaultPath(AppConfig::modsPath()); - } else if (!ci.paths.mods.contains(PathSettings::BaseDirVariable)) { - ci.paths.mods = QDir(ci.paths.mods).absolutePath(); - } - - if (ci.paths.profiles.isEmpty()) { - ci.paths.profiles = cid::makeDefaultPath(AppConfig::profilesPath()); - } else if (!ci.paths.profiles.contains(PathSettings::BaseDirVariable)) { - ci.paths.profiles = QDir(ci.paths.profiles).absolutePath(); - } - - if (ci.paths.overwrite.isEmpty()) { - ci.paths.overwrite = cid::makeDefaultPath(AppConfig::overwritePath()); - } else if (!ci.paths.overwrite.contains(PathSettings::BaseDirVariable)) { - ci.paths.overwrite = QDir(ci.paths.overwrite).absolutePath(); - } - - ci.iniPath = QFileInfo( - ci.dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())) - .absoluteFilePath(); + fixDirPath(ci.paths.base); + fixFilePath(ci.paths.ini); - ci.paths.base = QDir::toNativeSeparators(ci.paths.base); - ci.paths.downloads = QDir::toNativeSeparators(ci.paths.downloads); - ci.paths.mods = QDir::toNativeSeparators(ci.paths.mods); - ci.paths.profiles = QDir::toNativeSeparators(ci.paths.profiles); - ci.paths.overwrite = QDir::toNativeSeparators(ci.paths.overwrite); - ci.paths.ini = QDir::toNativeSeparators(ci.paths.ini); + fixVarDir(ci.paths.downloads, AppConfig::downloadPath()); + fixVarDir(ci.paths.mods, AppConfig::modsPath()); + fixVarDir(ci.paths.profiles, AppConfig::profilesPath()); + fixVarDir(ci.paths.overwrite, AppConfig::overwritePath()); return ci; } diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index cfb343fa..4d9d8b06 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -588,7 +588,7 @@ - This folder already exists. + Warning: This folder already exists. @@ -602,7 +602,7 @@ - The folder contains invalid characters. + Warning: The folder contains invalid characters. @@ -706,7 +706,7 @@ - The folder %1 already exists. + Warning: The folder %1 already exists. true @@ -778,7 +778,7 @@ - The folder %1 contains invalid characters. + Warning: The folder %1 contains invalid characters. true diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index ab020c66..4d2c5b8b 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -15,19 +15,11 @@ using MOBase::TaskDialog; QString makeDefaultPath(const std::wstring& dir) { - return QDir::toNativeSeparators( - PathSettings::makeDefaultPath(QString::fromStdWString(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 sanitizeFileName(const QString& name) { QString new_name = name; @@ -48,7 +40,7 @@ QString PathChecker::sanitizeFileName(const QString& name) const // same thing as above, but allows path separators and colons // -QString PathChecker::sanitizePath(const QString& path) const +QString sanitizePath(const QString& path) { QString new_name = path; @@ -67,93 +59,31 @@ QString PathChecker::sanitizePath(const QString& path) const return new_name; } -bool PathChecker::checkName(QString parentDir, QString name) const +void setPossiblePlaceholder( + QLabel* label, const QString& s, const QString& arg) { - 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; +PlaceholderLabel::PlaceholderLabel(QLabel* label) + : m_label(label), m_original(label->text()) +{ } -bool PathChecker::checkPath(QString path) const +void PlaceholderLabel::setText(const QString& arg) { - 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); + if (m_original.contains("%1")) { + m_label->setText(m_original.arg(arg)); } - - return okay; } -void PathChecker::setPossiblePlaceholder( - QLabel* label, const QString& s, const QString& arg) const +void PlaceholderLabel::setVisible(bool b) { - if (label->text().contains("%1")) { - label->setText(s.arg(arg)); - } + m_label->setVisible(b); } + Page::Page(CreateInstanceDialog& dlg) : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()) { @@ -238,7 +168,7 @@ TypePage::TypePage(CreateInstanceDialog& dlg) ui->createPortable->setDescription( ui->createPortable->description() - .arg(qApp->applicationDirPath())); + .arg(InstanceManager::portablePath())); QObject::connect( ui->createGlobal, &QAbstractButton::clicked, [&]{ global(); }); @@ -727,10 +657,9 @@ void EditionsPage::fillList() NamePage::NamePage(CreateInstanceDialog& dlg) : Page(dlg), m_modified(false), m_okay(false), - m_checker(ui->instanceNameExists, ui->instanceNameInvalid) + m_label(ui->instanceNameLabel), m_exists(ui->instanceNameExists), + m_invalid(ui->instanceNameInvalid) { - m_originalLabel = ui->instanceNameLabel->text(); - QObject::connect( ui->instanceName, &QLineEdit::textEdited, [&]{ onChanged(); }); } @@ -753,7 +682,7 @@ void NamePage::activated() return; } - ui->instanceNameLabel->setText(m_originalLabel.arg(g->gameName())); + m_label.setText(g->gameName()); if (!m_modified || ui->instanceName->text().isEmpty()) { const auto n = InstanceManager::instance().makeUniqueName(g->gameName()); @@ -771,7 +700,7 @@ QString NamePage::selectedInstanceName() const } const auto text = ui->instanceName->text().trimmed(); - return m_checker.sanitizeFileName(text); + return sanitizeFileName(text); } void NamePage::onChanged() @@ -784,15 +713,55 @@ void NamePage::updateWarnings() { const auto root = InstanceManager::instance().instancesPath(); - m_okay = m_checker.checkName(root, ui->instanceName->text()); + m_okay = checkName(root, ui->instanceName->text()); updateNavigation(); } +bool NamePage::checkName(QString parentDir, QString name) +{ + 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); + m_exists.setText(QDir(parentDir).filePath(name)); + m_invalid.setVisible(false); + } else if (invalid) { + m_exists.setVisible(false); + m_invalid.setVisible(true); + m_invalid.setText(name); + } else { + okay = !empty; + m_exists.setVisible(false); + m_invalid.setVisible(false); + } + + return okay; +} + PathsPage::PathsPage(CreateInstanceDialog& dlg) : - Page(dlg), - m_checker(ui->locationExists, ui->locationInvalid), - m_advancedChecker(ui->advancedDirExists, ui->advancedDirInvalid) + Page(dlg), m_lastType(CreateInstanceDialog::NoType), + m_simpleExists(ui->locationExists), m_simpleInvalid(ui->locationInvalid), + m_advancedExists(ui->advancedDirExists), + m_advancedInvalid(ui->advancedDirInvalid) { QObject::connect(ui->location, &QLineEdit::textEdited, [&]{ onChanged(); }); QObject::connect(ui->base, &QLineEdit::textEdited, [&]{ onChanged(); }); @@ -816,12 +785,16 @@ bool PathsPage::ready() const void PathsPage::activated() { const auto name = m_dlg.instanceName(); + const auto type = m_dlg.instanceType(); - setPaths(name, (m_lastInstanceName != name)); + const bool changed = (m_lastInstanceName != name) || (m_lastType != type); + + setPaths(name, changed); checkPaths(); updateNavigation(); m_lastInstanceName = name; + m_lastType = type; } CreateInstanceDialog::Paths PathsPage::selectedPaths() const @@ -852,21 +825,23 @@ bool PathsPage::checkPaths() const if (ui->advancedPathOptions->isChecked()) { return checkAdvancedPath(ui->base->text()) && - checkVarPath(ui->downloads->text()); + checkAdvancedPath(resolve(ui->downloads->text())) && + checkAdvancedPath(resolve(ui->mods->text())) && + checkAdvancedPath(resolve(ui->profiles->text())) && + checkAdvancedPath(resolve(ui->overwrite->text())); } else { - return m_checker.checkPath(ui->location->text()); + return checkPath(ui->location->text(), m_simpleExists, m_simpleInvalid); } } bool PathsPage::checkAdvancedPath(const QString& path) const { - return m_advancedChecker.checkPath(path); + return checkPath(path, m_advancedExists, m_advancedInvalid); } -bool PathsPage::checkVarPath(QString path) const +QString PathsPage::resolve(const QString& path) const { - path = PathSettings::resolve(path, ui->base->text()); - return checkAdvancedPath(path); + return PathSettings::resolve(path, ui->base->text()); } void PathsPage::onAdvanced() @@ -884,12 +859,20 @@ void PathsPage::onAdvanced() void PathsPage::setPaths(const QString& name, bool force) { - const auto root = InstanceManager::instance().instancesPath(); - const auto path = QDir::toNativeSeparators(root + "/" + name); + QString path; - setIfEmpty(ui->location, path, force); + if (m_dlg.instanceType() == CreateInstanceDialog::Portable) { + path = InstanceManager::portablePath(); + } else { + const auto root = InstanceManager::instance().instancesPath(); + path = root + "/" + name; + } + path = QDir::toNativeSeparators(QDir(path).canonicalPath()); + + 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); @@ -903,6 +886,61 @@ void PathsPage::setIfEmpty(QLineEdit* e, const QString& path, bool force) } } +bool PathsPage::checkPath( + QString path, + PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel) const +{ + bool exists = false; + bool invalid = false; + bool empty = false; + + path = QDir::toNativeSeparators(path.trimmed()); + + if (path.isEmpty()) { + empty = true; + } else { + const QString sanitized = sanitizePath(path); + + if (path != sanitized) { + invalid = true; + } else { + if (m_dlg.instanceType() == 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::instance().portablePath()) { + exists = QDir(path).exists(); + } + } else { + exists = QDir(path).exists(); + } + } + } + + bool okay = true; + + if (invalid) { + okay = false; + existsLabel.setVisible(false); + invalidLabel.setVisible(true); + invalidLabel.setText(path); + } else if (empty) { + okay = false; + existsLabel.setVisible(false); + invalidLabel.setVisible(false); + } else if (exists) { + // this is just a warning + existsLabel.setVisible(true); + existsLabel.setText(path); + invalidLabel.setVisible(false); + } else { + okay = true; + existsLabel.setVisible(false); + invalidLabel.setVisible(false); + } + + return okay; +} + ConfirmationPage::ConfirmationPage(CreateInstanceDialog& dlg) : Page(dlg) diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 0baff666..0d210493 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -13,29 +13,17 @@ namespace cid QString makeDefaultPath(const std::wstring& dir); -class PathChecker + +class PlaceholderLabel { 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; + PlaceholderLabel(QLabel* label); + void setText(const QString& arg); + void setVisible(bool b); private: - QLabel* m_exists; - QString m_existsOriginal; - - QLabel* m_invalid; - QString m_invalidOriginal; - - void setPossiblePlaceholder( - QLabel* label, const QString& s, const QString& arg) const; + QLabel* m_label; + QString m_original; }; @@ -168,13 +156,13 @@ public: QString selectedInstanceName() const override; private: - PathChecker m_checker; - QString m_originalLabel; + mutable PlaceholderLabel m_label, m_exists, m_invalid; bool m_modified; bool m_okay; void onChanged(); void updateWarnings(); + bool checkName(QString parentDir, QString name); }; @@ -189,16 +177,21 @@ public: CreateInstanceDialog::Paths selectedPaths() const override; private: - PathChecker m_checker, m_advancedChecker; QString m_lastInstanceName; + CreateInstanceDialog::Types m_lastType; + mutable PlaceholderLabel m_simpleExists, m_simpleInvalid; + mutable PlaceholderLabel m_advancedExists, m_advancedInvalid; void onChanged(); bool checkPaths() const; bool checkAdvancedPath(const QString& path) const; - bool checkVarPath(QString path) const; + QString resolve(const QString& path) const; void onAdvanced(); void setPaths(const QString& name, bool force); void setIfEmpty(QLineEdit* e, const QString& path, bool force); + bool checkPath( + QString path, + PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel) const; }; diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index b135cac1..849cdb5e 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -353,7 +353,12 @@ QStringList InstanceManager::instanceNames() const bool InstanceManager::isPortablePath(const QString& dataPath) { - return (dataPath == qApp->applicationDirPath()); + return (dataPath == portablePath()); +} + +QString InstanceManager::portablePath() +{ + return qApp->applicationDirPath(); } bool InstanceManager::portableInstall() const diff --git a/src/instancemanager.h b/src/instancemanager.h index 6a6b52ac..33a751c2 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -48,6 +48,7 @@ public: bool allowedToChangeInstance() const; static bool isPortablePath(const QString& dataPath); + static QString portablePath(); QString instancesPath() const; QStringList instanceNames() const; -- cgit v1.3.1 From 0389688f3b30b9888bb94a08991bc1de00b89c59 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 21:52:30 -0400 Subject: move to next page when clicking a game add game name in the label in the paths page can't use canonicalPath(), path might not exist --- src/createinstancedialog.ui | 13 ++++++++----- src/createinstancedialogpages.cpp | 13 ++++++++++--- src/createinstancedialogpages.h | 1 + 3 files changed, 19 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 4d9d8b06..41703f5e 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -7,7 +7,7 @@ 0 0 493 - 423 + 444 @@ -58,7 +58,7 @@ - 0 + 5 @@ -506,12 +506,15 @@ <h3>Select a folder where the data should be stored.</h3> + + true + - + - This includes downloads, mods, profiles and overwrite. If there is enough space on this drive, you should use the default folder. + This includes downloads, mods, profiles and overwrite for your <b>%1</b> instance. If there is enough space on this drive, you should use the default folder. true @@ -744,7 +747,7 @@ - Use <code>%BASE_DIR%</code> to refer to the Base Directory. + Use %BASE_DIR% to refer to the Base Directory. diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 4d2c5b8b..e5213d33 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -269,6 +269,10 @@ void GamePage::select(IPluginGame* game) m_selection = checked; selectButton(checked); updateNavigation(); + + if (checked) { + next(); + } } void GamePage::selectCustom() @@ -277,6 +281,7 @@ void GamePage::selectCustom() &m_dlg, QObject::tr("Find game installation")); if (path.isEmpty()) { + // reselect the previous button selectButton(m_selection); return; } @@ -408,7 +413,7 @@ QCommandLinkButton* GamePage::createCustomButton() QObject::connect(b, &QAbstractButton::clicked, [&] { selectCustom(); - }); + }); return b; } @@ -422,7 +427,7 @@ void GamePage::createGameButton(Game* g) QObject::connect(g->button, &QAbstractButton::clicked, [g, this] { select(g->game); - }); + }); } void GamePage::fillList() @@ -759,6 +764,7 @@ bool NamePage::checkName(QString parentDir, QString name) PathsPage::PathsPage(CreateInstanceDialog& dlg) : Page(dlg), m_lastType(CreateInstanceDialog::NoType), + m_label(ui->pathsLabel), m_simpleExists(ui->locationExists), m_simpleInvalid(ui->locationInvalid), m_advancedExists(ui->advancedDirExists), m_advancedInvalid(ui->advancedDirInvalid) @@ -793,6 +799,7 @@ void PathsPage::activated() checkPaths(); updateNavigation(); + m_label.setText(m_dlg.game()->gameName()); m_lastInstanceName = name; m_lastType = type; } @@ -868,7 +875,7 @@ void PathsPage::setPaths(const QString& name, bool force) path = root + "/" + name; } - path = QDir::toNativeSeparators(QDir(path).canonicalPath()); + path = QDir::toNativeSeparators(QDir::cleanPath(path)); setIfEmpty(ui->location, path, force); setIfEmpty(ui->base, path, force); diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 0d210493..15e63a8f 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -179,6 +179,7 @@ public: private: QString m_lastInstanceName; CreateInstanceDialog::Types m_lastType; + PlaceholderLabel m_label; mutable PlaceholderLabel m_simpleExists, m_simpleInvalid; mutable PlaceholderLabel m_advancedExists, m_advancedInvalid; -- cgit v1.3.1 From dfbcf8ec4c6da4d2d098403a01e7ec19b587e836 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 22:08:43 -0400 Subject: restart when finished, added launch instance checkbox --- src/createinstancedialog.cpp | 14 ++++++++++++++ src/createinstancedialog.ui | 38 ++++++++++++++++++++++++++++++++++++++ src/instancemanager.h | 3 +-- 3 files changed, 53 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 4b9f24ae..63df9cd7 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -3,6 +3,7 @@ #include "createinstancedialogpages.h" #include "instancemanager.h" #include "settings.h" +#include "shared/util.h" #include "shared/appconfig.h" #include #include @@ -27,6 +28,7 @@ CreateInstanceDialog::CreateInstanceDialog( m_pages.push_back(std::make_unique(*this)); ui->pages->setCurrentIndex(0); + ui->launch->setChecked(true); updateNavigation(); @@ -287,6 +289,14 @@ void CreateInstanceDialog::finish() } logCreation(tr("Done.")); + + if (ui->launch->isChecked()) { + InstanceManager::instance().setCurrentInstance(ci.instanceName); + ExitModOrganizer(Exit::Restart); + } else { + ui->next->setEnabled(false); + ui->cancel->setText(tr("Close")); + } } catch(Failed&) { @@ -328,6 +338,10 @@ void CreateInstanceDialog::updateNavigation() } else { ui->next->setText(m_originalNext); } + + // this may have been changed by finish() if the launch checkbox wasn't + // checked + ui->cancel->setText(tr("Cancel")); } CreateInstanceDialog::Types CreateInstanceDialog::instanceType() const diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 41703f5e..bcd2eb03 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -910,6 +910,44 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 304 + 20 + + + + + + + + Launch the new instance + + + + + + diff --git a/src/instancemanager.h b/src/instancemanager.h index 33a751c2..7467e2fa 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -45,6 +45,7 @@ public: void clearCurrentInstance(); QString currentInstance() const; + void setCurrentInstance(const QString &name); bool allowedToChangeInstance() const; static bool isPortablePath(const QString& dataPath); @@ -67,8 +68,6 @@ private: QString manageInstances(const QStringList &instanceList) const; - void setCurrentInstance(const QString &name); - QString queryInstanceName(const QStringList &instanceList) const; QString chooseInstance(const QStringList &instanceList) const; -- cgit v1.3.1 From 0f0313874aa90c66acaac9082f5ba6fbd29300ef Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 22:47:34 -0400 Subject: new GlobalSettings class to manage the registry close the create instance dialog when launch is unchecked --- src/createinstancedialog.cpp | 7 +------ src/createinstancedialog.ui | 3 +++ src/instancemanager.cpp | 30 +++--------------------------- src/instancemanager.h | 4 ---- src/settings.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/settings.h | 16 ++++++++++++++++ 6 files changed, 59 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 63df9cd7..5a38aa50 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -294,8 +294,7 @@ void CreateInstanceDialog::finish() InstanceManager::instance().setCurrentInstance(ci.instanceName); ExitModOrganizer(Exit::Restart); } else { - ui->next->setEnabled(false); - ui->cancel->setText(tr("Close")); + close(); } } catch(Failed&) @@ -338,10 +337,6 @@ void CreateInstanceDialog::updateNavigation() } else { ui->next->setText(m_originalNext); } - - // this may have been changed by finish() if the launch checkbox wasn't - // checked - ui->cancel->setText(tr("Cancel")); } CreateInstanceDialog::Types CreateInstanceDialog::instanceType() const diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index bcd2eb03..1f9a8ce1 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -943,6 +943,9 @@ Launch the new instance + + true + diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 849cdb5e..bd35cb47 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "shared/appconfig.h" #include "plugincontainer.h" -#include "env.h" #include #include #include @@ -38,15 +37,9 @@ along with Mod Organizer. If not, see . using namespace MOBase; -const QString Organization = "Mod Organizer Team"; -const QString Application = "Mod Organizer"; -const QString InstanceValue = "CurrentInstance"; - - InstanceManager::InstanceManager() - : m_AppSettings(Organization, Application) { - updateRegistryKey(); + GlobalSettings::updateRegistryKey(); } InstanceManager &InstanceManager::instance() @@ -55,23 +48,6 @@ InstanceManager &InstanceManager::instance() return s_Instance; } -void InstanceManager::updateRegistryKey() -{ - const QString OldOrganization = "Tannin"; - const QString OldApplication = "Mod Organizer"; - const QString OldInstanceValue = "CurrentInstance"; - - const QString OldRootKey = "Software\\" + OldOrganization; - - if (env::registryValueExists(OldRootKey + "\\" + OldApplication, OldInstanceValue)) { - QSettings old(OldOrganization, OldApplication); - setCurrentInstance(old.value(OldInstanceValue).toString()); - old.remove(OldInstanceValue); - } - - env::deleteRegistryKeyIfEmpty(OldRootKey); -} - void InstanceManager::overrideInstance(const QString& instanceName) { m_overrideInstanceName = instanceName; @@ -89,7 +65,7 @@ QString InstanceManager::currentInstance() const if (m_overrideInstance) return m_overrideInstanceName; else - return m_AppSettings.value(InstanceValue, "").toString(); + return GlobalSettings::currentInstance(); } void InstanceManager::clearCurrentInstance() @@ -101,7 +77,7 @@ void InstanceManager::clearCurrentInstance() void InstanceManager::setCurrentInstance(const QString &name) { - m_AppSettings.setValue(InstanceValue, name); + GlobalSettings::setCurrentInstance(name); } bool InstanceManager::deleteLocalInstance(const QString& instanceId) const diff --git a/src/instancemanager.h b/src/instancemanager.h index 7467e2fa..f53543df 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -75,11 +75,7 @@ private: bool portableInstall() const; bool portableInstallIsLocked() const; - void updateRegistryKey(); - private: - - QSettings m_AppSettings; bool m_Reset {false}; bool m_overrideInstance{false}; QString m_overrideInstanceName; diff --git a/src/settings.cpp b/src/settings.cpp index 661cf429..76378af9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2165,3 +2165,39 @@ void DiagnosticsSettings::setSpawnDelay(std::chrono::seconds t) { set(m_Settings, "Settings", "spawn_delay", t.count()); } + + +void GlobalSettings::updateRegistryKey() +{ + const QString OldOrganization = "Tannin"; + const QString OldApplication = "Mod Organizer"; + const QString OldInstanceValue = "CurrentInstance"; + + const QString OldRootKey = "Software\\" + OldOrganization; + + if (env::registryValueExists(OldRootKey + "\\" + OldApplication, OldInstanceValue)) { + QSettings old(OldOrganization, OldApplication); + setCurrentInstance(old.value(OldInstanceValue).toString()); + old.remove(OldInstanceValue); + } + + env::deleteRegistryKeyIfEmpty(OldRootKey); +} + +QString GlobalSettings::currentInstance() +{ + return settings().value("CurrentInstance", "").toString(); +} + +void GlobalSettings::setCurrentInstance(const QString& s) +{ + settings().setValue("CurrentInstance", s); +} + +QSettings GlobalSettings::settings() +{ + const QString Organization = "Mod Organizer Team"; + const QString Application = "Mod Organizer"; + + return QSettings(Organization, Application); +} diff --git a/src/settings.h b/src/settings.h index c723faec..54f1bb5b 100644 --- a/src/settings.h +++ b/src/settings.h @@ -827,6 +827,22 @@ private: }; +// manages global settings in the registry +// +class GlobalSettings +{ +public: + // migrates the old settings from the Tannin key to the new one + static void updateRegistryKey(); + + static QString currentInstance(); + static void setCurrentInstance(const QString& s); + +private: + static QSettings settings(); +}; + + // helper class that calls restoreGeometry() in the constructor and // saveGeometry() in the destructor // -- cgit v1.3.1 From 8987693dcf7dc116361869f128fd443716511f0c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 23:00:16 -0400 Subject: added hide option for intro page in create instance dialog --- src/createinstancedialog.cpp | 11 ++++++++++- src/createinstancedialog.ui | 26 +++++++++++++++++++++++--- src/createinstancedialogpages.cpp | 7 ++++++- src/createinstancedialogpages.h | 6 ++++-- src/settings.cpp | 15 +++++++++++++++ src/settings.h | 6 ++++++ src/settingsdialoggeneral.cpp | 1 + 7 files changed, 65 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 5a38aa50..2edfee88 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -19,7 +19,7 @@ CreateInstanceDialog::CreateInstanceDialog( ui->setupUi(this); m_originalNext = ui->next->text(); - m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); @@ -30,10 +30,15 @@ CreateInstanceDialog::CreateInstanceDialog( ui->pages->setCurrentIndex(0); ui->launch->setChecked(true); + if (m_pages[0]->skip()) { + next(); + } + updateNavigation(); connect(ui->next, &QPushButton::clicked, [&]{ next(); }); connect(ui->back, &QPushButton::clicked, [&]{ back(); }); + connect(ui->cancel, &QPushButton::clicked, [&]{ reject(); }); } CreateInstanceDialog::~CreateInstanceDialog() = default; @@ -290,6 +295,10 @@ void CreateInstanceDialog::finish() logCreation(tr("Done.")); + if (ui->hideIntro->isChecked()) { + GlobalSettings::setHideCreateInstanceIntro(true); + } + if (ui->launch->isChecked()) { InstanceManager::instance().setCurrentInstance(ci.instanceName); ExitModOrganizer(Exit::Restart); diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index 1f9a8ce1..cafccb36 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -58,7 +58,7 @@ - 5 + 0 @@ -76,6 +76,26 @@ + + + + Never show this again + + + + + + + Qt::Vertical + + + + 20 + 40 + + + +
    @@ -258,7 +278,7 @@ 0 0 455 - 287 + 308 @@ -368,7 +388,7 @@ 0 0 455 - 257 + 278 diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index e5213d33..3f35b4fe 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -153,11 +153,16 @@ CreateInstanceDialog::Paths Page::selectedPaths() const } -InfoPage::InfoPage(CreateInstanceDialog& dlg) +IntroPage::IntroPage(CreateInstanceDialog& dlg) : Page(dlg) { } +bool IntroPage::skip() const +{ + return GlobalSettings::hideCreateInstanceIntro(); +} + TypePage::TypePage(CreateInstanceDialog& dlg) : Page(dlg), m_type(CreateInstanceDialog::NoType) diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 15e63a8f..b8366234 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -53,10 +53,12 @@ protected: }; -class InfoPage : public Page +class IntroPage : public Page { public: - InfoPage(CreateInstanceDialog& dlg); + IntroPage(CreateInstanceDialog& dlg); + + bool skip() const override; }; diff --git a/src/settings.cpp b/src/settings.cpp index 76378af9..4ee4cd81 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2201,3 +2201,18 @@ QSettings GlobalSettings::settings() return QSettings(Organization, Application); } + +bool GlobalSettings::hideCreateInstanceIntro() +{ + return settings().value("HideCreateInstanceIntro", false).toBool(); +} + +void GlobalSettings::setHideCreateInstanceIntro(bool b) +{ + settings().setValue("HideCreateInstanceIntro", b); +} + +void GlobalSettings::resetDialogs() +{ + setHideCreateInstanceIntro(false); +} diff --git a/src/settings.h b/src/settings.h index 54f1bb5b..dd8abf3f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -838,6 +838,12 @@ public: static QString currentInstance(); static void setCurrentInstance(const QString& s); + static bool hideCreateInstanceIntro(); + static void setHideCreateInstanceIntro(bool b); + + // resets anything that the user can disable + static void resetDialogs(); + private: static QSettings settings(); }; diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 3d14d521..f29e1d24 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -177,6 +177,7 @@ void GeneralSettingsTab::selectStyle() void GeneralSettingsTab::resetDialogs() { settings().widgets().resetQuestionButtons(); + GlobalSettings::resetDialogs(); } void GeneralSettingsTab::onExploreStyles() -- cgit v1.3.1 From b1e681e129d87cb2f8aab89a734ab7b185975bcd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 Jul 2020 23:18:47 -0400 Subject: hide tutorial question option --- src/mainwindow.cpp | 30 +++++++++++++++++++++++++----- src/mainwindow.h | 1 + src/settings.cpp | 11 +++++++++++ src/settings.h | 3 +++ 4 files changed, 40 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c2d83e36..60ad77a0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1334,6 +1334,30 @@ void MainWindow::hookUpWindowTutorials() } } +bool MainWindow::shouldStartTutorial() const +{ + if (GlobalSettings::hideTutorialQuestion()) { + return false; + } + + QMessageBox dlg( + QMessageBox::Question, tr("Show tutorial?"), + tr("You are starting Mod Organizer for the first time. " + "Do you want to show a tutorial of its basic features? If you choose " + "no you can always start the tutorial from the \"Help\"-menu."), + QMessageBox::Yes | QMessageBox::No); + + dlg.setCheckBox(new QCheckBox(tr("Never ask to show tutorials"))); + + const auto r = dlg.exec(); + + if (dlg.checkBox()->isChecked()) { + GlobalSettings::setHideTutorialQuestion(true); + } + + return (r == QMessageBox::Yes); +} + void MainWindow::showEvent(QShowEvent *event) { QMainWindow::showEvent(event); @@ -1360,11 +1384,7 @@ void MainWindow::showEvent(QShowEvent *event) if (m_OrganizerCore.settings().firstStart()) { QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { - if (QMessageBox::question(this, tr("Show tutorial?"), - tr("You are starting Mod Organizer for the first time. " - "Do you want to show a tutorial of its basic features? If you choose " - "no you can always start the tutorial from the \"Help\"-menu."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (shouldStartTutorial()) { TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); } } else { diff --git a/src/mainwindow.h b/src/mainwindow.h index 814e0363..8b2188c8 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -506,6 +506,7 @@ private slots: void hideSaveGameInfo(); void hookUpWindowTutorials(); + bool shouldStartTutorial() const; void resumeDownload(int downloadIndex); void endorseMod(ModInfo::Ptr mod); diff --git a/src/settings.cpp b/src/settings.cpp index 4ee4cd81..a0758bd4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2212,7 +2212,18 @@ void GlobalSettings::setHideCreateInstanceIntro(bool b) settings().setValue("HideCreateInstanceIntro", b); } +bool GlobalSettings::hideTutorialQuestion() +{ + return settings().value("HideTutorialQuestion", false).toBool(); +} + +void GlobalSettings::setHideTutorialQuestion(bool b) +{ + settings().setValue("HideTutorialQuestion", b); +} + void GlobalSettings::resetDialogs() { setHideCreateInstanceIntro(false); + setHideTutorialQuestion(false); } diff --git a/src/settings.h b/src/settings.h index dd8abf3f..a9501b9d 100644 --- a/src/settings.h +++ b/src/settings.h @@ -841,6 +841,9 @@ public: static bool hideCreateInstanceIntro(); static void setHideCreateInstanceIntro(bool b); + static bool hideTutorialQuestion(); + static void setHideTutorialQuestion(bool b); + // resets anything that the user can disable static void resetDialogs(); -- cgit v1.3.1 From a97638249de0a9a4c17dc28805c35df489a64e26 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 Jul 2020 21:07:13 -0400 Subject: moved switch to instance to InstanceManager double-click an instance to switch to it (disabled for now, not sure I want that) --- src/createinstancedialog.cpp | 3 +-- src/instancemanager.cpp | 7 +++++++ src/instancemanager.h | 4 ++++ src/instancemanagerdialog.cpp | 27 ++++++++++++++++++++++++--- src/instancemanagerdialog.h | 4 ++++ 5 files changed, 40 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 2edfee88..75ccc774 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -300,8 +300,7 @@ void CreateInstanceDialog::finish() } if (ui->launch->isChecked()) { - InstanceManager::instance().setCurrentInstance(ci.instanceName); - ExitModOrganizer(Exit::Restart); + InstanceManager::instance().switchToInstance(ci.instanceName); } else { close(); } diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index bd35cb47..2c8d8fb3 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "shared/appconfig.h" #include "plugincontainer.h" +#include "shared/util.h" #include #include #include @@ -75,6 +76,12 @@ void InstanceManager::clearCurrentInstance() m_overrideInstance = false; } +void InstanceManager::switchToInstance(const QString& instanceName) +{ + setCurrentInstance(instanceName); + ExitModOrganizer(Exit::Restart); +} + void InstanceManager::setCurrentInstance(const QString &name) { GlobalSettings::setCurrentInstance(name); diff --git a/src/instancemanager.h b/src/instancemanager.h index f53543df..f038678a 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -34,6 +34,10 @@ class InstanceManager public: static InstanceManager &instance(); + // restarts MO + // + void switchToInstance(const QString& instanceName); + void overrideInstance(const QString& instanceName); void overrideProfile(const QString& profileName); diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 26e8eae1..a038d3c2 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -92,6 +92,7 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); connect(ui->list, &QListWidget::itemSelectionChanged, [&]{ onSelection(); }); + //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); } InstanceManagerDialog::~InstanceManagerDialog() = default; @@ -106,14 +107,24 @@ void InstanceManagerDialog::select(std::size_t i) fill(*ii); } +void InstanceManagerDialog::openSelectedInstance() +{ + const auto i = singleSelection(); + if (i == NoSelection) { + return; + } + + InstanceManager::instance().switchToInstance(m_instances[i]->name()); +} + void InstanceManagerDialog::onSelection() { - const auto sel = ui->list->selectionModel()->selectedIndexes(); - if (sel.size() != 1) { + const auto i = singleSelection(); + if (i == NoSelection) { return; } - select(static_cast(sel[0].row())); + select(i); } void InstanceManagerDialog::createNew() @@ -122,6 +133,16 @@ void InstanceManagerDialog::createNew() dlg.exec(); } +std::size_t InstanceManagerDialog::singleSelection() const +{ + const auto sel = ui->list->selectionModel()->selectedIndexes(); + if (sel.size() != 1) { + return NoSelection; + } + + return static_cast(sel[0].row()); +} + void InstanceManagerDialog::fill(const InstanceInfo& ii) { ui->name->setText(ii.name()); diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index c3e947dd..6dbf015f 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -19,8 +19,11 @@ public: ~InstanceManagerDialog(); void select(std::size_t i); + void openSelectedInstance(); private: + static const std::size_t NoSelection = -1; + std::unique_ptr ui; const PluginContainer& m_pc; std::vector> m_instances; @@ -28,6 +31,7 @@ private: void onSelection(); void createNew(); + std::size_t singleSelection() const; void fill(const InstanceInfo& ii); }; -- cgit v1.3.1 From 58479e4b00d399e3025558104e50c75ef609dda0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 29 Jul 2020 22:14:43 -0400 Subject: filter for games replaced the button box with a regular widget with vertical layout it was doing weird things to buttons with focus when removing and adding buttons on the fly because of filtering --- src/createinstancedialog.ui | 204 +++++++++++++++++++++----------------- src/createinstancedialogpages.cpp | 46 ++++++++- src/createinstancedialogpages.h | 9 +- 3 files changed, 161 insertions(+), 98 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index cafccb36..a8f21c51 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -127,7 +127,7 @@
    - + 0 @@ -141,11 +141,8 @@ 0 - - - - 0 - + + 0 @@ -184,22 +181,22 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + - - - - Qt::Vertical - - - - 20 - 40 - - - -
    @@ -226,32 +223,12 @@
    - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Show all supported games - - - - + 0 @@ -265,59 +242,104 @@ 0 - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 455 - 308 - + + + + 0 - - - 0 - 0 - + + 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Qt::Vertical - - - QDialogButtonBox::NoButton + + 0 + + + 0 + + + + + true + + + + + 0 + 0 + 98 + 28 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + PushButton + + + + - - - + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Show all supported games + + + + + + + Qt::Horizontal + + + + 175 + 20 + + + + + + + + Filter + + + + @@ -387,8 +409,8 @@ 0 0 - 455 - 278 + 98 + 28 diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 3f35b4fe..2aac93a0 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -12,6 +12,7 @@ namespace cid using MOBase::IPluginGame; using MOBase::TaskDialog; +using MOBase::FilterWidget; QString makeDefaultPath(const std::wstring& dir) { @@ -228,6 +229,9 @@ GamePage::GamePage(CreateInstanceDialog& dlg) createGames(); fillList(); + m_filter.setEdit(ui->gamesFilter); + + QObject::connect(&m_filter, &FilterWidget::changed, [&]{ fillList(); }); QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); }); } @@ -362,6 +366,7 @@ void GamePage::updateButton(Game* g) } g->button->setText(g->game->gameName()); + g->button->setIcon(g->game->gameIcon()); if (g->installed) { g->button->setDescription(g->dir); @@ -394,7 +399,7 @@ void GamePage::selectButton(Game* g) // 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); + addButton(gg->button); } gg->button->setChecked(true); @@ -439,9 +444,8 @@ void GamePage::fillList() { const bool showAll = ui->showAllGames->isChecked(); - ui->games->clear(); - - ui->games->addButton(createCustomButton(), QDialogButtonBox::AcceptRole); + clearButtons(); + addButton(createCustomButton()); for (auto& g : m_games) { g->button = nullptr; @@ -451,11 +455,43 @@ void GamePage::fillList() continue; } + if (!m_filter.matches(g->game->gameName())) { + continue; + } + createGameButton(g.get()); - ui->games->addButton(g->button, QDialogButtonBox::AcceptRole); + addButton(g->button); } } +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)) { diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index b8366234..f2f4216d 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -1,10 +1,12 @@ #ifndef MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED #define MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED +#include "createinstancedialog.h" +#include + #include #include #include -#include "createinstancedialog.h" namespace MOBase { class IPluginGame; } @@ -107,16 +109,19 @@ private: std::vector> m_games; Game* m_selection; - + MOBase::FilterWidget m_filter; std::vector sortedGamePlugins() const; Game* findGame(MOBase::IPluginGame* game); void createGames(); void updateButton(Game* g); void selectButton(Game* g); + void clearButtons(); + void addButton(QAbstractButton* b); QCommandLinkButton* createCustomButton(); void createGameButton(Game* g); void fillList(); + void onFilter(); Game* checkInstallation(const QString& path, Game* g); MOBase::IPluginGame* findAnotherGame(const QString& path); bool confirmUnknown(const QString& path, MOBase::IPluginGame* game); -- cgit v1.3.1 From c699aeb6d862f2c0960de126538931e4f13368b7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 29 Jul 2020 22:28:06 -0400 Subject: moved list of supported games to details having too many plugins made the dialog too tall --- src/createinstancedialogpages.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 2aac93a0..f37853f0 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -313,15 +313,22 @@ void GamePage::warnUnrecognized(const QString& path) { QString supportedGames; for (auto* game : sortedGamePlugins()) { - supportedGames += "
  • " + game->gameName() + "
  • "; + supportedGames += game->gameName() + "\n"; } - QMessageBox::warning(&m_dlg, - QObject::tr("Unrecognized game"), - QObject::tr( - "The folder %1 does not seem to contain a game Mod Organizer can " - "manage.

    These are the games that can be managed:" - "
      %2
    ").arg(path).arg(supportedGames)); + QMessageBox dlg(&m_dlg); + + dlg.setWindowTitle(QObject::tr("Unrecognized game")); + dlg.setText(QObject::tr( + "The folder %1 does not seem to contain a game Mod Organizer can " + "manage.").arg(path)); + dlg.setInformativeText( + QObject::tr("See details for the list of supported games.")); + dlg.setDetailedText(supportedGames); + dlg.setIcon(QMessageBox::Warning); + dlg.setStandardButtons(QMessageBox::Ok); + + dlg.exec(); } std::vector GamePage::sortedGamePlugins() const @@ -334,7 +341,7 @@ std::vector GamePage::sortedGamePlugins() const std::sort(v.begin(), v.end(), [](auto* a, auto* b) { return (a->gameName() < b->gameName()); - }); + }); return v; } -- cgit v1.3.1 From c4df6b8ec98feb34cc9b9785e6d4d0eac6b3f571 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 30 Jul 2020 00:48:46 -0400 Subject: split nexus connection stuff into NexusConnectionUI so it can be reused in the instance creation dialog removed the PluginContainer* parameter from NexusInterface::instance() - it's a singleton, so it only needs to be given once, not every time - it doesn't even matter because the first time instance() is called, it creates the singleton, but the plugin container is always null - and setPluginContainer() is called much later from OrganizerCore with the correct value added non functional nexus page to instance creation dialog --- src/createinstancedialog.cpp | 1 + src/createinstancedialog.ui | 161 ++++++++++++++-- src/createinstancedialogpages.cpp | 20 ++ src/createinstancedialogpages.h | 14 ++ src/main.cpp | 10 +- src/mainwindow.cpp | 62 +++---- src/modinfo.cpp | 18 +- src/modinfo.h | 5 +- src/modinfodialognexus.cpp | 8 +- src/nexusinterface.cpp | 15 +- src/nexusinterface.h | 5 +- src/organizercore.cpp | 22 +-- src/settingsdialognexus.cpp | 382 +++++++++++++++++++++----------------- src/settingsdialognexus.h | 55 ++++-- 14 files changed, 510 insertions(+), 268 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 75ccc774..935b9ee9 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -25,6 +25,7 @@ CreateInstanceDialog::CreateInstanceDialog( m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); ui->pages->setCurrentIndex(0); diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index a8f21c51..ebaef84d 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -266,8 +266,8 @@ 0 0 - 98 - 28 + 63 + 16 @@ -283,13 +283,6 @@ 0 - - - - PushButton - - - @@ -409,8 +402,8 @@ 0 0 - 98 - 28 + 63 + 16 @@ -886,6 +879,148 @@ + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Link Mod Organizer with your Nexus account</h3> + + + + + + + Linking with Nexus allows you to download mods directly from Mod Organizer and automatically check for updates. This is optional. + + + true + + + + + + + + + + + 0 + 100 + + + + + 0 + + + 9 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Connect to Nexus + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Enter API Key Manually + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + @@ -1057,6 +1192,8 @@ - + + + diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index f37853f0..37d50ee1 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -997,6 +997,26 @@ bool PathsPage::checkPath( } +NexusPage::NexusPage(CreateInstanceDialog& dlg) + : Page(dlg) +{ +} + +bool NexusPage::ready() const +{ + return true; +} + +bool NexusPage::skip() const +{ + return Settings::instance().nexus().hasApiKey(); +} + +void NexusPage::activated() +{ +} + + ConfirmationPage::ConfirmationPage(CreateInstanceDialog& dlg) : Page(dlg) { diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index f2f4216d..88e49628 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -203,6 +203,20 @@ private: }; +class NexusPage : public Page +{ +public: + NexusPage(CreateInstanceDialog& dlg); + + bool ready() const override; + bool skip() const override; + void activated() override; + +private: + +}; + + class ConfirmationPage : public Page { public: diff --git a/src/main.cpp b/src/main.cpp index 58e22466..93580931 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -395,7 +395,7 @@ int runApplication( QString apiKey; if (settings.nexus().apiKey(apiKey)) { - NexusInterface::instance(pluginContainer.get())->getAccessManager()->apiCheck(apiKey); + NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } log::debug("initializing tutorials"); @@ -415,8 +415,8 @@ int runApplication( // set up main window and its data structures MainWindow mainWindow(settings, organizer, *pluginContainer); - NexusInterface::instance(pluginContainer.get()) - ->getAccessManager()->setTopLevelWidget(&mainWindow); + NexusInterface::instance() + .getAccessManager()->setTopLevelWidget(&mainWindow); QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); @@ -443,8 +443,8 @@ int runApplication( res = application.exec(); mainWindow.close(); - NexusInterface::instance(pluginContainer.get()) - ->getAccessManager()->setTopLevelWidget(nullptr); + NexusInterface::instance() + .getAccessManager()->setTopLevelWidget(nullptr); } settings.geometry().resetIfNeeded(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 60ad77a0..9144210c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -291,7 +291,7 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->setup(ui, settings); { - auto* ni = NexusInterface::instance(&m_PluginContainer); + auto* ni = &NexusInterface::instance(); // there are two ways to get here: // 1) the user just started MO, and @@ -439,24 +439,24 @@ MainWindow::MainWindow(Settings &settings connect(m_OrganizerCore.updater(), SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); connect(m_OrganizerCore.updater(), SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); - connect(NexusInterface::instance(&pluginContainer), SIGNAL(requestNXMDownload(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); - connect(NexusInterface::instance(&pluginContainer), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int))); - connect(NexusInterface::instance(&pluginContainer), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); + connect(&NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); + connect(&NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int))); + connect(&NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); connect( - NexusInterface::instance(&pluginContainer)->getAccessManager(), + NexusInterface::instance().getAccessManager(), SIGNAL(credentialsReceived(const APIUserAccount&)), this, SLOT(updateWindowTitle(const APIUserAccount&))); connect( - NexusInterface::instance(&pluginContainer)->getAccessManager(), + NexusInterface::instance().getAccessManager(), SIGNAL(credentialsReceived(const APIUserAccount&)), - NexusInterface::instance(&m_PluginContainer), + &NexusInterface::instance(), SLOT(setUserAccount(const APIUserAccount&))); connect( - NexusInterface::instance(&pluginContainer), + &NexusInterface::instance(), SIGNAL(requestsChanged(const APIStats&, const APIUserAccount&)), this, SLOT(onRequestsChanged(const APIStats&, const APIUserAccount&))); @@ -1911,9 +1911,9 @@ QDir MainWindow::currentSavesDir() const wchar_t path[MAX_PATH]; if (::GetPrivateProfileStringW( - L"General", L"SLocalSavePath", L"", - path, MAX_PATH, - iniPath.toStdWString().c_str() + L"General", L"SLocalSavePath", L"", + path, MAX_PATH, + iniPath.toStdWString().c_str() )) { savesDir.setPath(m_OrganizerCore.managedGame()->documentsDirectory().absoluteFilePath(QString::fromWCharArray(path))); } @@ -3288,7 +3288,7 @@ void MainWindow::visitOnNexus_clicked() int modID = info->nexusId(); gameName = info->gameName(); if (modID > 0) { - linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); + linkClicked(NexusInterface::instance().getModURL(modID, gameName)); } else { log::error("mod '{}' has no nexus id", info->name()); } @@ -3298,7 +3298,7 @@ void MainWindow::visitOnNexus_clicked() int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt(); QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole + 4).toString(); if (modID > 0) { - linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); + linkClicked(NexusInterface::instance().getModURL(modID, gameName)); } else { MessageDialog::showMessage(tr("Nexus ID for this mod is unknown"), this); } @@ -3355,7 +3355,7 @@ void MainWindow::visitNexusOrWebPage(const QModelIndex& idx) const auto url = info->parseCustomURL(); if (modID > 0) { - linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); + linkClicked(NexusInterface::instance().getModURL(modID, gameName)); } else if (url.isValid()) { linkClicked(url.toString()); } else { @@ -4220,15 +4220,15 @@ void MainWindow::saveArchiveList() void MainWindow::checkModsForUpdates() { bool checkingModsForUpdate = false; - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this); - NexusInterface::instance(&m_PluginContainer)->requestEndorsementInfo(this, QVariant(), QString()); - NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); + if (NexusInterface::instance().getAccessManager()->validated()) { + checkingModsForUpdate = ModInfo::checkAllForUpdate(this); + NexusInterface::instance().requestEndorsementInfo(this, QVariant(), QString()); + NexusInterface::instance().requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else { log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); } @@ -4603,7 +4603,7 @@ void MainWindow::exportModListCSV() if (nexus_ID->isChecked()) builder.setRowField("#Nexus_ID", info->nexusId()); if (mod_Nexus_URL->isChecked()) - builder.setRowField("#Mod_Nexus_URL",(info->nexusId()>0)? NexusInterface::instance(&m_PluginContainer)->getModURL(info->nexusId(), info->gameName()) : ""); + builder.setRowField("#Mod_Nexus_URL",(info->nexusId()>0)? NexusInterface::instance().getModURL(info->nexusId(), info->gameName()) : ""); if (mod_Version->isChecked()) builder.setRowField("#Mod_Version", info->version().canonicalString()); if (install_Date->isChecked()) @@ -5157,7 +5157,7 @@ void MainWindow::on_actionSettings_triggered() } if (settings.paths().cache() != oldCacheDirectory) { - NexusInterface::instance(&m_PluginContainer)->setCacheDirectory( + NexusInterface::instance().setCacheDirectory( settings.paths().cache()); } @@ -5191,7 +5191,7 @@ void MainWindow::on_actionNexus_triggered() QString gameName = game->gameShortName(); if (game->gameNexusName().isEmpty() && game->primarySources().count()) gameName = game->primarySources()[0]; - QDesktopServices::openUrl(QUrl(NexusInterface::instance(&m_PluginContainer)->getGameURL(gameName))); + QDesktopServices::openUrl(QUrl(NexusInterface::instance().getGameURL(gameName))); } @@ -5350,9 +5350,9 @@ void MainWindow::actionEndorseMO() if (QMessageBox::question(this, tr("Endorse Mod Organizer"), tr("Do you want to endorse Mod Organizer on %1 now?").arg( - NexusInterface::instance(&m_PluginContainer)->getGameURL(game->gameShortName())), + NexusInterface::instance().getGameURL(game->gameShortName())), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - NexusInterface::instance(&m_PluginContainer)->requestToggleEndorsement( + NexusInterface::instance().requestToggleEndorsement( game->gameShortName(), game->nexusModOrganizerID(), m_OrganizerCore.getVersion().canonicalString(), true, this, QVariant(), QString()); } } @@ -5366,9 +5366,9 @@ void MainWindow::actionWontEndorseMO() if (QMessageBox::question(this, tr("Abstain from Endorsing Mod Organizer"), tr("Are you sure you want to abstain from endorsing Mod Organizer 2?\n" "You will have to visit the mod page on the %1 Nexus site to change your mind.").arg( - NexusInterface::instance(&m_PluginContainer)->getGameURL(game->gameShortName())), + NexusInterface::instance().getGameURL(game->gameShortName())), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - NexusInterface::instance(&m_PluginContainer)->requestToggleEndorsement( + NexusInterface::instance().requestToggleEndorsement( game->gameShortName(), game->nexusModOrganizerID(), m_OrganizerCore.getVersion().canonicalString(), false, this, QVariant(), QString()); } } @@ -5430,13 +5430,13 @@ void MainWindow::updateDownloadView() void MainWindow::modUpdateCheck(std::multimap IDs) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - ModInfo::manualUpdateCheck(&m_PluginContainer, this, IDs); + if (NexusInterface::instance().getAccessManager()->validated()) { + ModInfo::manualUpdateCheck(this, IDs); } else { QString apiKey; if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); } @@ -5588,7 +5588,7 @@ void MainWindow::finishUpdateInfo() log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.")); for (auto game : organizedGames) - NexusInterface::instance(&m_PluginContainer)->requestUpdates(game.second, this, QVariant(), game.first, QString()); + NexusInterface::instance().requestUpdates(game.second, this, QVariant(), game.first, QString()); disconnect(sender()); delete sender(); @@ -5675,7 +5675,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD } if (requiresInfo) - NexusInterface::instance(&m_PluginContainer)->requestModInfo(gameNameReal, modID, this, QVariant(), QString()); + NexusInterface::instance().requestModInfo(gameNameReal, modID, this, QVariant(), QString()); } void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) diff --git a/src/modinfo.cpp b/src/modinfo.cpp index bbeefb12..04cb18ce 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -266,7 +266,7 @@ void ModInfo::updateFromDisc(const QString &modDirectory, createFromOverwrite(pluginContainer, game, directoryStructure); std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); - + parallelMap(std::begin(s_Collection), std::end(s_Collection), &ModInfo::prefetch, refreshThreadCount); updateIndices(); @@ -295,7 +295,7 @@ ModInfo::ModInfo(PluginContainer *pluginContainer) } -bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver) +bool ModInfo::checkAllForUpdate(QObject *receiver) { bool updatesAvailable = true; @@ -348,19 +348,19 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei } for (auto game : organizedGames) - NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); + NexusInterface::instance().requestUpdates(game.second, receiver, QVariant(), game.first, QString()); } else if (earliest < QDateTime::currentDateTimeUtc().addMonths(-1)) { for (auto gameName : games) - NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(true), QString()); + NexusInterface::instance().requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(true), QString()); } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-7)) { for (auto gameName : games) - NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(false), QString()); + NexusInterface::instance().requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(false), QString()); } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-1)) { for (auto gameName : games) - NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::WEEK, receiver, QVariant(false), QString()); + NexusInterface::instance().requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::WEEK, receiver, QVariant(false), QString()); } else { for (auto gameName : games) - NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::DAY, receiver, QVariant(false), QString()); + NexusInterface::instance().requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::DAY, receiver, QVariant(false), QString()); } return updatesAvailable; @@ -400,7 +400,7 @@ std::set> ModInfo::filteredMods(QString gameName, QVaria return finalMods; } -void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs) +void ModInfo::manualUpdateCheck(QObject *receiver, std::multimap IDs) { std::vector> mods; std::set> organizedGames; @@ -438,7 +438,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei } for (auto game : organizedGames) { - NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); + NexusInterface::instance().requestUpdates(game.second, receiver, QVariant(), game.first, QString()); } } else { log::info("None of the selected mods can be updated."); diff --git a/src/modinfo.h b/src/modinfo.h index 7223cece..480fe013 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -190,15 +190,14 @@ public: // Static functions: * @brief Run a limited batch of mod update checks for "newest version" information. * */ - static void manualUpdateCheck( - PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs); + static void manualUpdateCheck(QObject *receiver, std::multimap IDs); /** * @brief Query nexus information for every mod and update the "newest version" information. * * @return true if any mods are checked for update. */ - static bool checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); + static bool checkAllForUpdate(QObject *receiver); /** * diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index dd95cdaa..b4099a8d 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -141,8 +141,8 @@ void NexusTab::updateWebpage() const int modID = mod().nexusId(); if (isValidModID(modID)) { - const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod().gameName()); + const QString nexusLink = NexusInterface::instance() + .getModURL(modID, mod().gameName()); ui->visitNexus->setToolTip(nexusLink); refreshData(modID); @@ -360,8 +360,8 @@ void NexusTab::onVisitNexus() const int modID = mod().nexusId(); if (isValidModID(modID)) { - const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod().gameName()); + const QString nexusLink = NexusInterface::instance() + .getModURL(modID, mod().gameName()); shell::Open(QUrl(nexusLink)); } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 9eb1540a..396cec11 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include #include "shared/util.h" #include +#include #include #include @@ -50,7 +51,7 @@ void throttledWarning(const APIUserAccount& user) NexusBridge::NexusBridge(PluginContainer *pluginContainer, const QString &subModule) - : m_Interface(NexusInterface::instance(pluginContainer)) + : m_Interface(&NexusInterface::instance()) , m_SubModule(subModule) { } @@ -234,8 +235,8 @@ APILimits NexusInterface::parseLimits( } -NexusInterface::NexusInterface(PluginContainer *pluginContainer) - : m_PluginContainer(pluginContainer) +NexusInterface::NexusInterface() + : m_PluginContainer(nullptr) { m_User.limits(defaultAPILimits()); m_MOVersion = createVersionInfo(); @@ -255,10 +256,10 @@ NexusInterface::~NexusInterface() cleanup(); } -NexusInterface *NexusInterface::instance(PluginContainer *pluginContainer) +NexusInterface& NexusInterface::instance() { - static NexusInterface s_Instance(pluginContainer); - return &s_Instance; + static NexusInterface ni; + return ni; } void NexusInterface::setCacheDirectory(const QString &directory) @@ -284,7 +285,7 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo static const QRegularExpression complex(R"(^([a-zA-Z0-9_'"\-.() ]*?)([-_ ][VvRr]+[0-9]+(?:(?:[\.][0-9]+){0,2}|(?:[_][0-9]+){0,2}|(?:[-.][0-9]+){0,2})?[ab]?)??-([1-9][0-9]+)?-.*?\.(zip|rar|7z))"); //complex regex explanation: //group 1: modname. - //group 2: optional version, + //group 2: optional version, // assumed to start with v (empty most of the time). //group 3: NexusId, // assumed wrapped in "-", will miss single digit IDs for better accuracy. diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 35362bdf..72f30a38 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -154,8 +154,7 @@ public: static APILimits parseLimits(const QList& headers); ~NexusInterface(); - - static NexusInterface *instance(PluginContainer *pluginContainer); + static NexusInterface& instance(); /** * @return the access manager object used to connect to nexus @@ -535,7 +534,7 @@ private: private: - NexusInterface(PluginContainer *pluginContainer); + NexusInterface(); void nextRequest(); void requestFinished(std::list::iterator iter); MOBase::IPluginGame *getGame(QString gameName) const; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0849d756..7fd8c33b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -95,12 +95,12 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_PluginContainer(nullptr) , m_CurrentProfile(nullptr) , m_Settings(settings) - , m_Updater(NexusInterface::instance(m_PluginContainer)) + , m_Updater(&NexusInterface::instance()) , m_ModList(m_PluginContainer, this) , m_PluginList(this) , m_DirectoryRefresher(new DirectoryRefresher(settings.refreshThreadCount())) , m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)) - , m_DownloadManager(NexusInterface::instance(m_PluginContainer), this) + , m_DownloadManager(&NexusInterface::instance(), this) , m_DirectoryUpdate(false) , m_ArchivesInit(false) , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) @@ -108,8 +108,7 @@ OrganizerCore::OrganizerCore(Settings &settings) env::setHandleCloserThreadCount(settings.refreshThreadCount()); m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads(), false); - NexusInterface::instance(m_PluginContainer)->setCacheDirectory( - m_Settings.paths().cache()); + NexusInterface::instance().setCacheDirectory(m_Settings.paths().cache()); m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); m_InstallationManager.setDownloadDirectory(m_Settings.paths().downloads()); @@ -122,9 +121,9 @@ OrganizerCore::OrganizerCore(Settings &settings) connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); - connect(NexusInterface::instance(m_PluginContainer)->getAccessManager(), + connect(NexusInterface::instance().getAccessManager(), SIGNAL(validateSuccessful(bool)), this, SLOT(loginSuccessful(bool))); - connect(NexusInterface::instance(m_PluginContainer)->getAccessManager(), + connect(NexusInterface::instance().getAccessManager(), SIGNAL(validateFailed(QString)), this, SLOT(loginFailed(QString))); // This seems awfully imperative @@ -332,8 +331,7 @@ Settings &OrganizerCore::settings() bool OrganizerCore::nexusApi(bool retry) { - NXMAccessManager *accessManager - = NexusInterface::instance(m_PluginContainer)->getAccessManager(); + auto* accessManager = NexusInterface::instance().getAccessManager(); if ((accessManager->validateAttempted() || accessManager->validated()) && !retry) { @@ -1424,13 +1422,13 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap f) { - if (NexusInterface::instance(m_PluginContainer)->getAccessManager()->validated()) { + if (NexusInterface::instance().getAccessManager()->validated()) { f(); } else { QString apiKey; if (settings().nexus().apiKey(apiKey)) { doAfterLogin([f]{ f(); }); - NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus"), parent); } @@ -1704,7 +1702,7 @@ void OrganizerCore::loginSuccessful(bool necessary) } m_PostLoginTasks.clear(); - NexusInterface::instance(m_PluginContainer)->loginCompleted(); + NexusInterface::instance().loginCompleted(); } void OrganizerCore::loginSuccessfulUpdate(bool necessary) @@ -1742,7 +1740,7 @@ void OrganizerCore::loginFailed(const QString &message) qApp->activeWindow()); m_PostLoginTasks.clear(); } - NexusInterface::instance(m_PluginContainer)->loginCompleted(); + NexusInterface::instance().loginCompleted(); } void OrganizerCore::loginFailedUpdate(const QString &message) diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index d49e0a33..a76b9ad7 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -71,112 +71,45 @@ private: }; -NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d) +NexusConnectionUI::NexusConnectionUI(Settings& s, QWidget* parent) : + m_parent(parent), m_settings(s), + m_connect(nullptr), m_disconnect(nullptr), m_manual(nullptr), m_log(nullptr) { - ui->offlineBox->setChecked(settings().network().offlineMode()); - ui->proxyBox->setChecked(settings().network().useProxy()); - ui->endorsementBox->setChecked(settings().nexus().endorsementIntegration()); - ui->hideAPICounterBox->setChecked(settings().interface().hideAPICounter()); - - // display server preferences - for (const auto& server : s.network().servers()) { - QString descriptor = server.name(); - - if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { - descriptor += QStringLiteral(" (automatic)"); - } - - const auto averageSpeed = server.averageSpeed(); - if (averageSpeed > 0) { - descriptor += QString(" (%1)").arg(MOBase::localizedByteSpeed(averageSpeed)); - } - - QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); - - newItem->setData(Qt::UserRole, server.name()); - newItem->setData(Qt::UserRole + 1, server.preferred()); - - if (server.preferred() > 0) { - ui->preferredServersList->addItem(newItem); - } else { - ui->knownServersList->addItem(newItem); - } - - ui->preferredServersList->sortItems(Qt::DescendingOrder); - } - - QObject::connect(ui->nexusConnect, &QPushButton::clicked, [&]{ on_nexusConnect_clicked(); }); - QObject::connect(ui->nexusManualKey, &QPushButton::clicked, [&]{ on_nexusManualKey_clicked(); }); - QObject::connect(ui->nexusDisconnect, &QPushButton::clicked, [&]{ on_nexusDisconnect_clicked(); }); - QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&]{ on_clearCacheButton_clicked(); }); - QObject::connect(ui->associateButton, &QPushButton::clicked, [&]{ on_associateButton_clicked(); }); - - if (settings().nexus().hasApiKey()) { - addNexusLog(QObject::tr("Connected.")); - } else { - addNexusLog(QObject::tr("Not connected.")); - } - - updateNexusState(); } -void NexusSettingsTab::update() +void NexusConnectionUI::set( + QAbstractButton* connectButton, + QAbstractButton* disconnectButton, + QAbstractButton* manualButton, + QListWidget* logList) { - settings().network().setOfflineMode(ui->offlineBox->isChecked()); - settings().network().setUseProxy(ui->proxyBox->isChecked()); - settings().nexus().setEndorsementIntegration(ui->endorsementBox->isChecked()); - settings().interface().setHideAPICounter(ui->hideAPICounterBox->isChecked()); - - auto servers = settings().network().servers(); - - // store server preference - for (int i = 0; i < ui->knownServersList->count(); ++i) { - const QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); - - bool found = false; - - for (auto& server : servers) { - if (server.name() == key) { - server.setPreferred(0); - found = true; - break; - } - } - - if (!found) { - log::error("while setting preferred to 0, server '{}' not found", key); - } + m_connect = connectButton; + if (m_connect) { + QObject::connect(m_connect, &QPushButton::clicked, [&]{ connect(); }); } - const int count = ui->preferredServersList->count(); - - for (int i = 0; i < count; ++i) { - const QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); - const int newPreferred = count - i; + m_disconnect = disconnectButton; + if (m_disconnect) { + QObject::connect(m_disconnect, &QPushButton::clicked, [&]{ disconnect(); }); + } - bool found = false; + m_manual = manualButton; + if (m_manual) { + QObject::connect(manualButton, &QPushButton::clicked, [&]{ manual(); }); + } - for (auto& server : servers) { + m_log = logList; - if (server.name() == key) { - server.setPreferred(newPreferred); - found = true; - break; - } - } - - if (!found) { - log::error( - "while setting preference to {}, server '{}' not found", - newPreferred, key); - } + if (m_settings.nexus().hasApiKey()) { + addLog(tr("Connected.")); + } else { + addLog(tr("Not connected.")); } - settings().network().updateServers(servers); + updateState(); } -void NexusSettingsTab::on_nexusConnect_clicked() +void NexusConnectionUI::connect() { if (m_nexusLogin && m_nexusLogin->isActive()) { m_nexusLogin->cancel(); @@ -195,19 +128,19 @@ void NexusSettingsTab::on_nexusConnect_clicked() }; } - ui->nexusLog->clear(); + m_log->clear(); m_nexusLogin->start(); - updateNexusState(); + updateState(); } -void NexusSettingsTab::on_nexusManualKey_clicked() +void NexusConnectionUI::manual() { if (m_nexusValidator && m_nexusValidator->isActive()) { m_nexusValidator->cancel(); return; } - NexusManualKeyDialog d(&dialog()); + NexusManualKeyDialog d(m_parent); if (d.exec() != QDialog::Accepted) { return; } @@ -218,162 +151,273 @@ void NexusSettingsTab::on_nexusManualKey_clicked() return; } - ui->nexusLog->clear(); + m_log->clear(); validateKey(key); } -void NexusSettingsTab::on_nexusDisconnect_clicked() +void NexusConnectionUI::disconnect() { clearKey(); - ui->nexusLog->clear(); - addNexusLog(QObject::tr("Disconnected.")); -} - -void NexusSettingsTab::on_clearCacheButton_clicked() -{ - QDir(Settings::instance().paths().cache()).removeRecursively(); - NexusInterface::instance(dialog().pluginContainer())->clearCache(); -} - -void NexusSettingsTab::on_associateButton_clicked() -{ - Settings::instance().nexus().registerAsNXMHandler(true); + m_log->clear(); + addLog(tr("Disconnected.")); } -void NexusSettingsTab::validateKey(const QString& key) +void NexusConnectionUI::validateKey(const QString& key) { if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( - *NexusInterface::instance(dialog().pluginContainer())->getAccessManager())); + *NexusInterface::instance().getAccessManager())); m_nexusValidator->finished = [&](auto&& r, auto&& m, auto&& u) { onValidatorFinished(r, m, u); }; } - addNexusLog(QObject::tr("Checking API key...")); + addLog(tr("Checking API key...")); m_nexusValidator->start(key, NexusKeyValidator::OneShot); } -void NexusSettingsTab::onSSOKeyChanged(const QString& key) +void NexusConnectionUI::onSSOKeyChanged(const QString& key) { if (key.isEmpty()) { clearKey(); } else { - addNexusLog(QObject::tr("Received API key.")); + addLog(tr("Received API key.")); validateKey(key); } } -void NexusSettingsTab::onSSOStateChanged(NexusSSOLogin::States s, const QString& e) +void NexusConnectionUI::onSSOStateChanged( + NexusSSOLogin::States s, const QString& e) { if (s != NexusSSOLogin::Finished) { // finished state is handled in onSSOKeyChanged() const auto log = NexusSSOLogin::stateToString(s, e); for (auto&& line : log.split("\n")) { - addNexusLog(line); + addLog(line); } } - updateNexusState(); + updateState(); } -void NexusSettingsTab::onValidatorFinished( +void NexusConnectionUI::onValidatorFinished( ValidationAttempt::Result r, const QString& message, std::optional user) { if (user) { - NexusInterface::instance(dialog().pluginContainer())->setUserAccount(*user); - addNexusLog(QObject::tr("Received user acount information")); + NexusInterface::instance().setUserAccount(*user); + addLog(tr("Received user account information")); if (setKey(user->apiKey())) { - addNexusLog(QObject::tr("Linked with Nexus successfully.")); + addLog(tr("Linked with Nexus successfully.")); } else { - addNexusLog(QObject::tr("Failed to set API key")); + addLog(tr("Failed to set API key")); } } else { if (message.isEmpty()) { // shouldn't happen - addNexusLog("Unknown error"); + addLog("Unknown error"); } else { - addNexusLog(message); + addLog(message); } } - updateNexusState(); + updateState(); } -void NexusSettingsTab::addNexusLog(const QString& s) +void NexusConnectionUI::addLog(const QString& s) { - ui->nexusLog->addItem(s); - ui->nexusLog->scrollToBottom(); + m_log->addItem(s); + m_log->scrollToBottom(); } -bool NexusSettingsTab::setKey(const QString& key) +bool NexusConnectionUI::setKey(const QString& key) { - dialog().setExitNeeded(Exit::Restart); - const bool ret = settings().nexus().setApiKey(key); - updateNexusState(); + const bool ret = m_settings.nexus().setApiKey(key); + updateState(); + + emit keyChanged(); + return ret; } -bool NexusSettingsTab::clearKey() +bool NexusConnectionUI::clearKey() { - dialog().setExitNeeded(Exit::Restart); - const auto ret = settings().nexus().clearApiKey(); + const auto ret = m_settings.nexus().clearApiKey(); + + NexusInterface::instance().getAccessManager()->clearApiKey(); + updateState(); - NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey(); - updateNexusState(); + emit keyChanged(); return ret; } -void NexusSettingsTab::updateNexusState() +void NexusConnectionUI::updateState() { - updateNexusButtons(); - updateNexusData(); -} + auto setButton = [&](QAbstractButton* b, bool enabled, QString caption={}) { + if (b) { + b->setEnabled(enabled); + if (!caption.isEmpty()) { + b->setText(caption); + } + } + }; -void NexusSettingsTab::updateNexusButtons() -{ if (m_nexusLogin && m_nexusLogin->isActive()) { // api key is in the process of being retrieved - ui->nexusConnect->setText(QObject::tr("Cancel")); - ui->nexusConnect->setEnabled(true); - ui->nexusDisconnect->setEnabled(false); - ui->nexusManualKey->setText(QObject::tr("Enter API Key Manually")); - ui->nexusManualKey->setEnabled(false); + setButton(m_connect, true, QObject::tr("Cancel")); + setButton(m_disconnect, false); + setButton(m_manual, false,QObject::tr("Enter API Key Manually")); } else if (m_nexusValidator && m_nexusValidator->isActive()) { // api key is in the process of being tested - ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); - ui->nexusConnect->setEnabled(false); - ui->nexusDisconnect->setEnabled(false); - ui->nexusManualKey->setText(QObject::tr("Cancel")); - ui->nexusManualKey->setEnabled(true); + setButton(m_connect, false, QObject::tr("Connect to Nexus")); + setButton(m_disconnect, false); + setButton(m_manual, true, QObject::tr("Cancel")); } - else if (settings().nexus().hasApiKey()) { + else if (m_settings.nexus().hasApiKey()) { // api key is present - ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); - ui->nexusConnect->setEnabled(false); - ui->nexusDisconnect->setEnabled(true); - ui->nexusManualKey->setText(QObject::tr("Enter API Key Manually")); - ui->nexusManualKey->setEnabled(false); + setButton(m_connect, false, QObject::tr("Connect to Nexus")); + setButton(m_disconnect, true); + setButton(m_manual, false, QObject::tr("Enter API Key Manually")); } else { // api key not present - ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); - ui->nexusConnect->setEnabled(true); - ui->nexusDisconnect->setEnabled(false); - ui->nexusManualKey->setText(QObject::tr("Enter API Key Manually")); - ui->nexusManualKey->setEnabled(true); + setButton(m_connect, true, QObject::tr("Connect to Nexus")); + setButton(m_disconnect, false); + setButton(m_manual, true, QObject::tr("Enter API Key Manually")); + } + + emit stateChanged(); +} + + + +NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d), m_connectionUI(s, &d) +{ + ui->offlineBox->setChecked(settings().network().offlineMode()); + ui->proxyBox->setChecked(settings().network().useProxy()); + ui->endorsementBox->setChecked(settings().nexus().endorsementIntegration()); + ui->hideAPICounterBox->setChecked(settings().interface().hideAPICounter()); + + // display server preferences + for (const auto& server : s.network().servers()) { + QString descriptor = server.name(); + + if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { + descriptor += QStringLiteral(" (automatic)"); + } + + const auto averageSpeed = server.averageSpeed(); + if (averageSpeed > 0) { + descriptor += QString(" (%1)").arg(MOBase::localizedByteSpeed(averageSpeed)); + } + + QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); + + newItem->setData(Qt::UserRole, server.name()); + newItem->setData(Qt::UserRole + 1, server.preferred()); + + if (server.preferred() > 0) { + ui->preferredServersList->addItem(newItem); + } else { + ui->knownServersList->addItem(newItem); + } + + ui->preferredServersList->sortItems(Qt::DescendingOrder); } + + m_connectionUI.set( + ui->nexusConnect, + ui->nexusDisconnect, + ui->nexusManualKey, + ui->nexusLog); + + QObject::connect( + &m_connectionUI, &NexusConnectionUI::stateChanged, &d, + [&]{ updateNexusData(); }, Qt::QueuedConnection); + + QObject::connect( + &m_connectionUI, &NexusConnectionUI::keyChanged, &d, + [&]{ dialog().setExitNeeded(Exit::Restart); }); + + + QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&]{ on_clearCacheButton_clicked(); }); + QObject::connect(ui->associateButton, &QPushButton::clicked, [&]{ on_associateButton_clicked(); }); +} + +void NexusSettingsTab::update() +{ + settings().network().setOfflineMode(ui->offlineBox->isChecked()); + settings().network().setUseProxy(ui->proxyBox->isChecked()); + settings().nexus().setEndorsementIntegration(ui->endorsementBox->isChecked()); + settings().interface().setHideAPICounter(ui->hideAPICounterBox->isChecked()); + + auto servers = settings().network().servers(); + + // store server preference + for (int i = 0; i < ui->knownServersList->count(); ++i) { + const QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); + + bool found = false; + + for (auto& server : servers) { + if (server.name() == key) { + server.setPreferred(0); + found = true; + break; + } + } + + if (!found) { + log::error("while setting preferred to 0, server '{}' not found", key); + } + } + + const int count = ui->preferredServersList->count(); + + for (int i = 0; i < count; ++i) { + const QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); + const int newPreferred = count - i; + + bool found = false; + + for (auto& server : servers) { + + if (server.name() == key) { + server.setPreferred(newPreferred); + found = true; + break; + } + } + + if (!found) { + log::error( + "while setting preference to {}, server '{}' not found", + newPreferred, key); + } + } + + settings().network().updateServers(servers); +} + +void NexusSettingsTab::on_clearCacheButton_clicked() +{ + QDir(Settings::instance().paths().cache()).removeRecursively(); + NexusInterface::instance().clearCache(); +} + +void NexusSettingsTab::on_associateButton_clicked() +{ + Settings::instance().nexus().registerAsNXMHandler(true); } void NexusSettingsTab::updateNexusData() { - const auto user = NexusInterface::instance(dialog().pluginContainer()) - ->getAPIUserAccount(); + const auto user = NexusInterface::instance().getAPIUserAccount(); if (user.isValid()) { ui->nexusUserID->setText(user.id()); diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index 2cb1cc1e..0adf67c7 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -5,38 +5,67 @@ #include "settingsdialog.h" #include "nxmaccessmanager.h" -class NexusSettingsTab : public SettingsTab +class NexusConnectionUI : public QObject { + Q_OBJECT; + public: - NexusSettingsTab(Settings& settings, SettingsDialog& dialog); - void update(); + NexusConnectionUI(Settings& s, QWidget* parent=nullptr); + + void set( + QAbstractButton* connectButton, + QAbstractButton* disconnectButton, + QAbstractButton* manualButton, + QListWidget* logList); + + void connect(); + void manual(); + void disconnect(); + +signals: + void stateChanged(); + void keyChanged(); private: + QWidget* m_parent; + Settings& m_settings; + QAbstractButton* m_connect; + QAbstractButton* m_disconnect; + QAbstractButton* m_manual; + QListWidget* m_log; + std::unique_ptr m_nexusLogin; std::unique_ptr m_nexusValidator; - void on_nexusConnect_clicked(); - void on_nexusManualKey_clicked(); - void on_nexusDisconnect_clicked(); - void on_clearCacheButton_clicked(); - void on_associateButton_clicked(); + void addLog(const QString& s); + + void updateState(); void validateKey(const QString& key); bool setKey(const QString& key); bool clearKey(); - void updateNexusState(); - void updateNexusButtons(); - void updateNexusData(); - void onSSOKeyChanged(const QString& key); void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); void onValidatorFinished( ValidationAttempt::Result r, const QString& message, std::optional useR); +}; + - void addNexusLog(const QString& s); +class NexusSettingsTab : public SettingsTab +{ +public: + NexusSettingsTab(Settings& settings, SettingsDialog& dialog); + void update(); + +private: + NexusConnectionUI m_connectionUI; + + void on_clearCacheButton_clicked(); + void on_associateButton_clicked(); + void updateNexusData(); }; #endif // SETTINGSDIALOGNEXUS_H -- cgit v1.3.1 From 44d9ebaf076ff5be5235b65fe68a8b0699c8cf6f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 30 Jul 2020 00:58:03 -0400 Subject: implemented nexus page --- src/createinstancedialogpages.cpp | 17 +++++++++++++++-- src/createinstancedialogpages.h | 6 +++++- src/settingsdialognexus.cpp | 33 +++++++++++++++------------------ src/settingsdialognexus.h | 10 ++++++---- 4 files changed, 41 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 37d50ee1..b2e3612f 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -3,6 +3,7 @@ #include "instancemanager.h" #include "settings.h" #include "plugincontainer.h" +#include "settingsdialognexus.h" #include "shared/appconfig.h" #include #include @@ -998,10 +999,22 @@ bool PathsPage::checkPath( NexusPage::NexusPage(CreateInstanceDialog& dlg) - : Page(dlg) + : Page(dlg), m_skip(false) { + m_connectionUI.reset(new NexusConnectionUI( + Settings::instance(), &m_dlg, + ui->nexusConnect, + nullptr, + ui->nexusManual, + ui->nexusLog)); + + // just check it once, or connecting and then going back and forth would skip + // the page, which would be unexpected + m_skip = Settings::instance().nexus().hasApiKey(); } +NexusPage::~NexusPage() = default; + bool NexusPage::ready() const { return true; @@ -1009,7 +1022,7 @@ bool NexusPage::ready() const bool NexusPage::skip() const { - return Settings::instance().nexus().hasApiKey(); + return m_skip; } void NexusPage::activated() diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 88e49628..412f2d84 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -9,6 +9,8 @@ #include namespace MOBase { class IPluginGame; } +class NexusConnectionUI; + namespace cid { @@ -207,13 +209,15 @@ class NexusPage : public Page { public: NexusPage(CreateInstanceDialog& dlg); + ~NexusPage(); bool ready() const override; bool skip() const override; void activated() override; private: - + std::unique_ptr m_connectionUI; + bool m_skip; }; diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index a76b9ad7..54af9b59 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -71,35 +71,31 @@ private: }; -NexusConnectionUI::NexusConnectionUI(Settings& s, QWidget* parent) : - m_parent(parent), m_settings(s), - m_connect(nullptr), m_disconnect(nullptr), m_manual(nullptr), m_log(nullptr) -{ -} - -void NexusConnectionUI::set( +NexusConnectionUI::NexusConnectionUI( + Settings& s, + QWidget* parent, QAbstractButton* connectButton, QAbstractButton* disconnectButton, QAbstractButton* manualButton, - QListWidget* logList) + QListWidget* logList) : + m_parent(parent), m_settings(s), + m_connect(connectButton), + m_disconnect(disconnectButton), + m_manual(manualButton), + m_log(logList) { - m_connect = connectButton; if (m_connect) { QObject::connect(m_connect, &QPushButton::clicked, [&]{ connect(); }); } - m_disconnect = disconnectButton; if (m_disconnect) { QObject::connect(m_disconnect, &QPushButton::clicked, [&]{ disconnect(); }); } - m_manual = manualButton; if (m_manual) { QObject::connect(manualButton, &QPushButton::clicked, [&]{ manual(); }); } - m_log = logList; - if (m_settings.nexus().hasApiKey()) { addLog(tr("Connected.")); } else { @@ -296,7 +292,7 @@ void NexusConnectionUI::updateState() NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d), m_connectionUI(s, &d) + : SettingsTab(s, d) { ui->offlineBox->setChecked(settings().network().offlineMode()); ui->proxyBox->setChecked(settings().network().useProxy()); @@ -330,18 +326,19 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) ui->preferredServersList->sortItems(Qt::DescendingOrder); } - m_connectionUI.set( + m_connectionUI.reset(new NexusConnectionUI( + settings(), &dialog(), ui->nexusConnect, ui->nexusDisconnect, ui->nexusManualKey, - ui->nexusLog); + ui->nexusLog)); QObject::connect( - &m_connectionUI, &NexusConnectionUI::stateChanged, &d, + m_connectionUI.get(), &NexusConnectionUI::stateChanged, &d, [&]{ updateNexusData(); }, Qt::QueuedConnection); QObject::connect( - &m_connectionUI, &NexusConnectionUI::keyChanged, &d, + m_connectionUI.get(), &NexusConnectionUI::keyChanged, &d, [&]{ dialog().setExitNeeded(Exit::Restart); }); diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index 0adf67c7..a2d6a4f8 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -5,14 +5,16 @@ #include "settingsdialog.h" #include "nxmaccessmanager.h" +// used by the settings dialog and the create instance dialog +// class NexusConnectionUI : public QObject { Q_OBJECT; public: - NexusConnectionUI(Settings& s, QWidget* parent=nullptr); - - void set( + NexusConnectionUI( + Settings& s, + QWidget* parent, QAbstractButton* connectButton, QAbstractButton* disconnectButton, QAbstractButton* manualButton, @@ -61,7 +63,7 @@ public: void update(); private: - NexusConnectionUI m_connectionUI; + std::unique_ptr m_connectionUI; void on_clearCacheButton_clicked(); void on_associateButton_clicked(); -- cgit v1.3.1 From 6f21f3a19fbd82bc43c97dcab877959220ce2f22 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 30 Jul 2020 01:17:30 -0400 Subject: disable portable instance button if one exists --- src/createinstancedialog.ui | 18 ++++++++++++++---- src/createinstancedialogpages.cpp | 12 ++++++++++-- src/instancemanager.cpp | 5 ++--- src/instancemanager.h | 2 +- 4 files changed, 27 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index ebaef84d..a3c3db92 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -181,6 +181,16 @@
    + + + + A portable instance already exists. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -266,8 +276,8 @@ 0 0 - 63 - 16 + 455 + 286 @@ -402,8 +412,8 @@ 0 0 - 63 - 16 + 455 + 278 diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index b2e3612f..127243c6 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -171,11 +171,19 @@ TypePage::TypePage(CreateInstanceDialog& dlg) { ui->createGlobal->setDescription( ui->createGlobal->description() - .arg(InstanceManager::instance().instancesPath())); + .arg(InstanceManager::instance().instancesPath())); ui->createPortable->setDescription( ui->createPortable->description() - .arg(InstanceManager::portablePath())); + .arg(InstanceManager::portablePath())); + + if (InstanceManager::instance().portableInstanceExists()) { + ui->createPortable->setEnabled(false); + ui->portableExistsLabel->setVisible(true); + } else { + ui->createPortable->setEnabled(true); + ui->portableExistsLabel->setVisible(false); + } QObject::connect( ui->createGlobal, &QAbstractButton::clicked, [&]{ global(); }); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 2c8d8fb3..b6798fa9 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -344,7 +344,7 @@ QString InstanceManager::portablePath() return qApp->applicationDirPath(); } -bool InstanceManager::portableInstall() const +bool InstanceManager::portableInstanceExists() const { return QFile::exists(qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::iniFileName())); @@ -357,7 +357,6 @@ bool InstanceManager::portableInstallIsLocked() const QString::fromStdWString(AppConfig::portableLockFileName())); } - bool InstanceManager::allowedToChangeInstance() const { return !portableInstallIsLocked(); @@ -388,7 +387,7 @@ QString InstanceManager::determineDataPath() { instanceId.clear(); } - if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstall())) + if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstanceExists())) { // startup, apparently using portable mode before return qApp->applicationDirPath(); diff --git a/src/instancemanager.h b/src/instancemanager.h index f038678a..9250ffe9 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -54,6 +54,7 @@ public: bool allowedToChangeInstance() const; static bool isPortablePath(const QString& dataPath); static QString portablePath(); + bool portableInstanceExists() const; QString instancesPath() const; QStringList instanceNames() const; @@ -76,7 +77,6 @@ private: QString chooseInstance(const QStringList &instanceList) const; void createDataPath(const QString &dataPath) const; - bool portableInstall() const; bool portableInstallIsLocked() const; private: -- cgit v1.3.1 From ea4485857c09fd3ab6879966e7377d3c56951a85 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 30 Jul 2020 04:36:12 -0400 Subject: filter, explore buttons, instance rename --- src/createinstancedialog.ui | 21 +++++- src/createinstancedialogpages.cpp | 62 ++------------- src/instancemanager.cpp | 9 +++ src/instancemanager.h | 1 + src/instancemanagerdialog.cpp | 154 ++++++++++++++++++++++++++++++++++++-- src/instancemanagerdialog.h | 12 ++- src/instancemanagerdialog.ui | 27 ++++++- 7 files changed, 221 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index a3c3db92..b7f4f502 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -77,9 +77,12 @@ - + - Never show this again + <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance">More information</a></p></body></html> + + + true @@ -96,6 +99,13 @@ + + + + Never show this page again + + + @@ -1202,6 +1212,13 @@ + + + LinkLabel + QLabel +
    linklabel.h
    +
    +
    diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 127243c6..8d304635 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -21,50 +21,6 @@ QString makeDefaultPath(const std::wstring& dir) QString::fromStdWString(dir))); } -QString sanitizeFileName(const QString& name) -{ - 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) -{ - 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; -} - -void setPossiblePlaceholder( - QLabel* label, const QString& s, const QString& arg) -{ -} PlaceholderLabel::PlaceholderLabel(QLabel* label) @@ -762,7 +718,7 @@ QString NamePage::selectedInstanceName() const } const auto text = ui->instanceName->text().trimmed(); - return sanitizeFileName(text); + return InstanceManager::instance().sanitizeInstanceName(text); } void NamePage::onChanged() @@ -790,12 +746,10 @@ bool NamePage::checkName(QString parentDir, QString name) if (name.isEmpty()) { empty = true; } else { - const QString sanitized = sanitizeFileName(name); - - if (name != sanitized) { - invalid = true; - } else { + if (InstanceManager::instance().validInstanceName(name)) { exists = QDir(parentDir).exists(name); + } else { + invalid = true; } } @@ -963,11 +917,9 @@ bool PathsPage::checkPath( if (path.isEmpty()) { empty = true; } else { - const QString sanitized = sanitizePath(path); + const QDir d(path); - if (path != sanitized) { - invalid = true; - } else { + if (InstanceManager::instance().validInstanceName(d.dirName())) { if (m_dlg.instanceType() == CreateInstanceDialog::Portable) { // the default data path for a portable instance is the application // directory, so it's not an error if it exists @@ -977,6 +929,8 @@ bool PathsPage::checkPath( } else { exists = QDir(path).exists(); } + } else { + invalid = true; } } diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index b6798fa9..111f948b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -676,3 +676,12 @@ QString InstanceManager::sanitizeInstanceName(const QString &name) const } return new_name; } + +bool InstanceManager::validInstanceName(const QString& instanceName) const +{ + if (instanceName.isEmpty()) { + return false; + } + + return (instanceName == sanitizeInstanceName(instanceName)); +} diff --git a/src/instancemanager.h b/src/instancemanager.h index 9250ffe9..33e115a7 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -63,6 +63,7 @@ public: QString sanitizeInstanceName(const QString &name) const; QString makeUniqueName(const QString& instanceName) const; bool instanceExists(const QString& instanceName) const; + bool validInstanceName(const QString& instanceName) const; QString instancePath(const QString& instanceName) const; private: diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index a038d3c2..aa6bdb04 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -8,8 +8,12 @@ #include "shared/appconfig.h" #include +namespace shell = MOBase::shell; + void openInstanceManager(PluginContainer& pc, QWidget* parent) { + //CreateInstanceDialog dlg(pc, parent); + //dlg.exec(); InstanceManagerDialog dlg(pc, parent); dlg.exec(); } @@ -77,12 +81,20 @@ InstanceManagerDialog::InstanceManagerDialog( ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); + auto* model = new QStandardItemModel; + ui->list->setModel(model); + + m_filter.setEdit(ui->filter); + m_filter.setList(ui->list); + m_filter.setUpdateDelay(false); + m_filter.setFilteredBorder(false); + auto& m = InstanceManager::instance(); for (auto&& d : m.instancePaths()) { auto ii = std::make_unique(d); - ui->list->addItem(ii->name()); + model->appendRow(new QStandardItem(ii->name())); m_instances.push_back(std::move(ii)); } @@ -91,8 +103,12 @@ InstanceManagerDialog::InstanceManagerDialog( } connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); - connect(ui->list, &QListWidget::itemSelectionChanged, [&]{ onSelection(); }); + connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&]{ onSelection(); }); //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); + connect(ui->rename, &QPushButton::clicked, [&]{ rename(); }); + connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); + connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); + connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); } InstanceManagerDialog::~InstanceManagerDialog() = default; @@ -105,11 +121,15 @@ void InstanceManagerDialog::select(std::size_t i) const auto& ii = m_instances[i]; fill(*ii); + + ui->list->selectionModel()->select( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), + QItemSelectionModel::ClearAndSelect); } void InstanceManagerDialog::openSelectedInstance() { - const auto i = singleSelection(); + const auto i = singleSelectionIndex(); if (i == NoSelection) { return; } @@ -117,9 +137,107 @@ void InstanceManagerDialog::openSelectedInstance() InstanceManager::instance().switchToInstance(m_instances[i]->name()); } +void InstanceManagerDialog::rename() +{ + auto* i = singleSelection(); + if (!i) { + return; + } + + auto& m = InstanceManager::instance(); + if (m.currentInstance() == i->name()) { + QMessageBox::information(this, + tr("Rename instance"), tr("The active instance cannot be renamed")); + return; + } + + QDialog dlg(this); + dlg.setWindowTitle(tr("Rename instance")); + + auto* ly = new QVBoxLayout(&dlg); + + auto* bb = new QDialogButtonBox( + QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + + auto* text = new QLineEdit(i->name()); + text->selectAll(); + + auto* error = new QLabel; + + ly->addWidget(new QLabel(tr("Instance name"))); + ly->addWidget(text); + ly->addWidget(error); + ly->addStretch(); + ly->addWidget(bb); + + connect(text, &QLineEdit::textChanged, [&] { + bool okay = false; + + if (!m.validInstanceName(text->text())) { + error->setText(tr("The instance name must be a valid folder name.")); + } else { + const auto name = m.sanitizeInstanceName(text->text()); + + if ((name != i->name()) && m.instanceExists(text->text())) { + error->setText(tr("An instance with this name already exists.")); + } else { + okay = true; + } + } + + error->setVisible(!okay); + bb->button(QDialogButtonBox::Ok)->setEnabled(okay); + }); + + connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); }); + connect(bb, &QDialogButtonBox::rejected, [&]{ dlg.reject(); }); + + dlg.resize({400, 120}); + if (dlg.exec() != QDialog::Accepted) { + return; + } + + + const QString newName = m.sanitizeInstanceName(text->text()); + const QString src = QDir::toNativeSeparators(i->location()); + const QString dest = QDir::toNativeSeparators( + QFileInfo(i->location()).dir().path() + "/" + newName); + + const auto r = shell::Rename(src, dest, false); + if (!r) { + QMessageBox::critical( + this, tr("Error"), + tr("Failed to rename \"%1\" to \"%2\": %3") + .arg(src).arg(dest).arg(r.toString())); + + return; + } +} + +void InstanceManagerDialog::exploreLocation() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->location()); + } +} + +void InstanceManagerDialog::exploreBaseDirectory() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->baseDirectory()); + } +} + +void InstanceManagerDialog::exploreGame() +{ + if (const auto* i=singleSelection()) { + shell::Explore(i->gamePath()); + } +} + void InstanceManagerDialog::onSelection() { - const auto i = singleSelection(); + const auto i = singleSelectionIndex(); if (i == NoSelection) { return; } @@ -133,14 +251,36 @@ void InstanceManagerDialog::createNew() dlg.exec(); } -std::size_t InstanceManagerDialog::singleSelection() const +std::size_t InstanceManagerDialog::singleSelectionIndex() const { - const auto sel = ui->list->selectionModel()->selectedIndexes(); + const auto sel = m_filter.mapSelectionToSource( + ui->list->selectionModel()->selection()); + if (sel.size() != 1) { return NoSelection; } - return static_cast(sel[0].row()); + return static_cast(sel.indexes()[0].row()); +} + +InstanceInfo* InstanceManagerDialog::singleSelection() +{ + const auto i = singleSelectionIndex(); + if (i == NoSelection) { + return nullptr; + } + + return m_instances[i].get(); +} + +const InstanceInfo* InstanceManagerDialog::singleSelection() const +{ + const auto i = singleSelectionIndex(); + if (i == NoSelection) { + return nullptr; + } + + return m_instances[i].get(); } void InstanceManagerDialog::fill(const InstanceInfo& ii) diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 6dbf015f..529a99b8 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -1,6 +1,7 @@ #ifndef MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED #define MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED +#include #include namespace Ui { class InstanceManagerDialog; }; @@ -21,17 +22,26 @@ public: void select(std::size_t i); void openSelectedInstance(); + void rename(); + void exploreLocation(); + void exploreBaseDirectory(); + void exploreGame(); + private: static const std::size_t NoSelection = -1; std::unique_ptr ui; const PluginContainer& m_pc; std::vector> m_instances; + MOBase::FilterWidget m_filter; void onSelection(); void createNew(); - std::size_t singleSelection() const; + std::size_t singleSelectionIndex() const; + InstanceInfo* singleSelection(); + const InstanceInfo* singleSelection() const; + void fill(const InstanceInfo& ii); }; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 146b5fb5..08894ec1 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -103,7 +103,32 @@ 0 - + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter + + + + + -- cgit v1.3.1 From 5fc6b0cd142b18ea574381580752ef6647102c9f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 Jul 2020 09:07:40 -0400 Subject: renamed cancel to close add portable instance to the list, handle some of the buttons --- src/instancemanagerdialog.cpp | 102 +++++++++++++++++++++++++++++++++--------- src/instancemanagerdialog.h | 6 ++- src/instancemanagerdialog.ui | 6 +-- 3 files changed, 88 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index aa6bdb04..99ade7ad 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -21,15 +21,19 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent) class InstanceInfo { public: - InstanceInfo(QDir dir) : - m_dir(std::move(dir)), + InstanceInfo(QDir dir, bool isPortable) : + m_dir(std::move(dir)), m_portable(isPortable), m_settings(dir.filePath(QString::fromStdWString(AppConfig::iniFileName()))) { } QString name() const { - return m_dir.dirName(); + if (m_portable) { + return QObject::tr("Portable"); + } else { + return m_dir.dirName(); + } } QString gameName() const @@ -66,52 +70,83 @@ public: return m_settings.paths().base(); } + bool isPortable() const + { + return m_portable; + } + private: QDir m_dir; + bool m_portable; Settings m_settings; }; +InstanceManagerDialog::~InstanceManagerDialog() = default; + InstanceManagerDialog::InstanceManagerDialog( - const PluginContainer& pc, QWidget *parent) - : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc) + const PluginContainer& pc, QWidget *parent) : + QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc), + m_model(nullptr) { ui->setupUi(this); ui->splitter->setSizes({200, 1}); ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); - auto* model = new QStandardItemModel; - ui->list->setModel(model); + m_model = new QStandardItemModel; + ui->list->setModel(m_model); m_filter.setEdit(ui->filter); m_filter.setList(ui->list); m_filter.setUpdateDelay(false); m_filter.setFilteredBorder(false); - auto& m = InstanceManager::instance(); - - for (auto&& d : m.instancePaths()) { - auto ii = std::make_unique(d); - - model->appendRow(new QStandardItem(ii->name())); - m_instances.push_back(std::move(ii)); - } - - if (!m_instances.empty()) { - select(0); - } + updateInstances(); + updateList(); connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); + connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&]{ onSelection(); }); //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); + connect(ui->rename, &QPushButton::clicked, [&]{ rename(); }); connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); + + connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); + connect(ui->close, &QPushButton::clicked, [&]{ close(); }); } -InstanceManagerDialog::~InstanceManagerDialog() = default; +void InstanceManagerDialog::updateInstances() +{ + auto& m = InstanceManager::instance(); + + m_instances.clear(); + + if (m.portableInstanceExists()) { + m_instances.push_back(std::make_unique( + m.portablePath(), true)); + } + + for (auto&& d : m.instancePaths()) { + m_instances.push_back(std::make_unique(d, false)); + } +} + +void InstanceManagerDialog::updateList() +{ + m_model->clear(); + + for (auto&& ii : m_instances) { + m_model->appendRow(new QStandardItem(ii->name())); + } + + if (!m_instances.empty()) { + select(0); + } +} void InstanceManagerDialog::select(std::size_t i) { @@ -120,7 +155,7 @@ void InstanceManagerDialog::select(std::size_t i) } const auto& ii = m_instances[i]; - fill(*ii); + fillData(*ii); ui->list->selectionModel()->select( m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), @@ -212,6 +247,8 @@ void InstanceManagerDialog::rename() return; } + + } void InstanceManagerDialog::exploreLocation() @@ -283,11 +320,32 @@ const InstanceInfo* InstanceManagerDialog::singleSelection() const return m_instances[i].get(); } -void InstanceManagerDialog::fill(const InstanceInfo& ii) +void InstanceManagerDialog::fillData(const InstanceInfo& ii) { ui->name->setText(ii.name()); ui->location->setText(ii.location()); ui->baseDirectory->setText(ii.baseDirectory()); ui->gameName->setText(ii.gameName()); ui->gameDir->setText(ii.gamePath()); + + const auto& m = InstanceManager::instance(); + + ui->rename->setEnabled(!ii.isPortable()); + + if (ii.isPortable()) { + ui->convertToPortable->setVisible(false); + ui->convertToGlobal->setVisible(true); + ui->convertToGlobal->setEnabled(true); + } else { + ui->convertToPortable->setVisible(true); + ui->convertToGlobal->setVisible(false); + + if (m.portableInstanceExists()) { + ui->convertToPortable->setEnabled(false); + ui->convertToPortable->setToolTip(tr("A portable instance already exists.")); + } else { + ui->convertToPortable->setEnabled(false); + ui->convertToPortable->setToolTip(""); + } + } } diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 529a99b8..d102facb 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -34,6 +34,9 @@ private: const PluginContainer& m_pc; std::vector> m_instances; MOBase::FilterWidget m_filter; + QStandardItemModel* m_model; + + void updateInstances(); void onSelection(); void createNew(); @@ -42,7 +45,8 @@ private: InstanceInfo* singleSelection(); const InstanceInfo* singleSelection() const; - void fill(const InstanceInfo& ii); + void updateList(); + void fillData(const InstanceInfo& ii); }; #endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 08894ec1..8bf28ed6 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -382,7 +382,7 @@ - + Switch to this instance @@ -393,9 +393,9 @@ - + - Cancel + Close true -- cgit v1.3.1 From f67dc91aa1f00eb2005d8e576c24739df91802ce Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Aug 2020 16:13:24 -0400 Subject: fixed boost bind warnings --- src/downloadmanager.cpp | 4 +++- src/mainwindow.cpp | 4 +++- src/pch.h | 2 +- src/selfupdater.cpp | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 5db0cbdf..2927a972 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -42,7 +42,7 @@ along with Mod Organizer. If not, see . #include #include -#include +#include #include @@ -1841,6 +1841,8 @@ int DownloadManager::indexByInfo(const DownloadInfo* info) const void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID) { + using namespace boost::placeholders; + std::set::iterator idIter = m_RequestIDs.find(requestID); if (idIter == m_RequestIDs.end()) { return; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9144210c..3ef34e93 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -168,7 +168,7 @@ along with Mod Organizer. If not, see . #ifndef Q_MOC_RUN #include #include -#include +#include #include #include #endif @@ -5903,6 +5903,8 @@ bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std: void MainWindow::extractBSATriggered() { + using namespace boost::placeholders; + QTreeWidgetItem *item = m_ContextItem; QString origin; diff --git a/src/pch.h b/src/pch.h index 02b6b1a2..2a2246ba 100644 --- a/src/pch.h +++ b/src/pch.h @@ -48,7 +48,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 904dbd83..c0f3b005 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -56,7 +56,7 @@ along with Mod Organizer. If not, see . #include #include -#include +#include #include //for VS_FIXEDFILEINFO, GetLastError -- cgit v1.3.1 From f7e9724eb1a817bdef372f87ebf96e9692db2bc9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Aug 2020 19:44:31 -0400 Subject: delete instance --- src/instancemanagerdialog.cpp | 268 ++++++++++++++++++++++++++++++++++++++---- src/instancemanagerdialog.h | 7 ++ src/instancemanagerdialog.ui | 24 ++-- 3 files changed, 265 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 99ade7ad..5f42f409 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -6,9 +6,11 @@ #include "selectiondialog.h" #include "plugincontainer.h" #include "shared/appconfig.h" +#include +#include #include -namespace shell = MOBase::shell; +using namespace MOBase; void openInstanceManager(PluginContainer& pc, QWidget* parent) { @@ -54,7 +56,7 @@ public: QString gamePath() const { if (auto n=m_settings.game().directory()) { - return *n; + return QDir::toNativeSeparators(*n); } else { return {}; } @@ -62,12 +64,12 @@ public: QString location() const { - return m_dir.path(); + return QDir::toNativeSeparators(m_dir.path()); } QString baseDirectory() const { - return m_settings.paths().base(); + return QDir::toNativeSeparators(m_settings.paths().base()); } bool isPortable() const @@ -75,6 +77,19 @@ public: return m_portable; } + bool isActive() const + { + auto& m = InstanceManager::instance(); + + if (m_portable && m.currentInstance() == "") { + return true; + } else if (m.currentInstance() == name()) { + return true; + } + + return false; + } + private: QDir m_dir; bool m_portable; @@ -90,6 +105,7 @@ InstanceManagerDialog::InstanceManagerDialog( m_model(nullptr) { ui->setupUi(this); + ui->splitter->setSizes({200, 1}); ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); @@ -114,6 +130,7 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); + connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); connect(ui->close, &QPushButton::clicked, [&]{ close(); }); @@ -137,29 +154,51 @@ void InstanceManagerDialog::updateInstances() void InstanceManagerDialog::updateList() { + const auto prevSelIndex = singleSelectionIndex(); + const auto* prevSel = singleSelection(); + m_model->clear(); - for (auto&& ii : m_instances) { - m_model->appendRow(new QStandardItem(ii->name())); + const std::size_t NoSel = -1; + std::size_t sel = NoSel; + + for (std::size_t i=0; iappendRow(new QStandardItem(ii.name())); + + if (&ii == prevSel) { + sel = i; + } } - if (!m_instances.empty()) { - select(0); + + if (m_instances.empty()) { + select(-1); + } else { + if (sel == NoSel) { + if (prevSelIndex >= m_instances.size()) { + sel = m_instances.size() - 1; + } else { + sel = prevSelIndex; + } + } + + select(sel); } } void InstanceManagerDialog::select(std::size_t i) { - if (i >= m_instances.size()) { - return; - } - - const auto& ii = m_instances[i]; - fillData(*ii); + if (i < m_instances.size()) { + const auto& ii = m_instances[i]; + fillData(*ii); - ui->list->selectionModel()->select( - m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), - QItemSelectionModel::ClearAndSelect); + ui->list->selectionModel()->select( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), + QItemSelectionModel::ClearAndSelect); + } else { + clearData(); + } } void InstanceManagerDialog::openSelectedInstance() @@ -180,9 +219,9 @@ void InstanceManagerDialog::rename() } auto& m = InstanceManager::instance(); - if (m.currentInstance() == i->name()) { + if (i->isActive()) { QMessageBox::information(this, - tr("Rename instance"), tr("The active instance cannot be renamed")); + tr("Rename instance"), tr("The active instance cannot be renamed.")); return; } @@ -234,7 +273,7 @@ void InstanceManagerDialog::rename() const QString newName = m.sanitizeInstanceName(text->text()); - const QString src = QDir::toNativeSeparators(i->location()); + const QString src = i->location(); const QString dest = QDir::toNativeSeparators( QFileInfo(i->location()).dir().path() + "/" + newName); @@ -247,8 +286,6 @@ void InstanceManagerDialog::rename() return; } - - } void InstanceManagerDialog::exploreLocation() @@ -272,6 +309,160 @@ void InstanceManagerDialog::exploreGame() } } +void InstanceManagerDialog::deleteInstance() +{ + const auto* i = singleSelection(); + if (!i) { + return; + } + + auto& m = InstanceManager::instance(); + if (i->isActive()) { + QMessageBox::information(this, + tr("Deleting instance"), tr("The active instance cannot be deleted.")); + return; + } + + if (i->isPortable()) { + deletePortable(*i); + } else { + deleteGlobal(*i); + } + + updateInstances(); + updateList(); +} + +bool InstanceManagerDialog::deletePortable(const InstanceInfo& i) +{ + const auto Recycle = QMessageBox::Save; + const auto Delete = QMessageBox::Yes; + const auto Cancel = QMessageBox::Cancel; + + const std::vector fileNames = { + AppConfig::iniFileName(), + }; + + const std::vector dirNames = { + AppConfig::dumpsDir(), + AppConfig::downloadPath(), + AppConfig::logPath(), + AppConfig::modsPath(), + AppConfig::overwritePath(), + AppConfig::profilesPath(), + AppConfig::cachePath() + }; + + QStringList files; + for (const auto& n : fileNames) { + files.push_back(QDir::toNativeSeparators( + i.location() + "/" + QString::fromStdWString(n))); + } + + QStringList dirs; + for (const auto& n : dirNames) { + dirs.push_back(QDir::toNativeSeparators( + i.location() + "/" + QString::fromStdWString(n))); + } + + QString details = QObject::tr("These files will be deleted:"); + for (const auto& f : files) { + details += "\n - " + f; + } + + details += "\n\n" + QObject::tr("These folders will be deleted:"); + for (const auto& d : dirs) { + details += "\n - " + d; + } + + + QStringList all; + all.append(files); + all.append(dirs); + + + const auto r = MOBase::TaskDialog(this) + .title(("Deleting portable instance")) + .main(tr("This will delete the data of the portable instance.")) + .content(tr( + "The data is in %1. Only the relevant files and folders will be " + "deleted. The Mod Organizer installation itself will be untouched.") + .arg(i.location())) + .details(details) + .icon(QMessageBox::Warning) + .button({tr("Move the data to the recycle bin"), Recycle}) + .button({tr("Delete the data permanently"), Delete}) + .button({tr("Cancel"), Cancel}) + .exec(); + + switch (r) + { + case Recycle: + return doDelete(all, true); + + case Delete: + return doDelete(all, false); + + case Cancel: // fall-through + default: + { + return false; + } + } + + return true; +} + +bool InstanceManagerDialog::deleteGlobal(const InstanceInfo& i) +{ + const auto Recycle = QMessageBox::Save; + const auto Delete = QMessageBox::Yes; + const auto Cancel = QMessageBox::Cancel; + + const auto r = MOBase::TaskDialog(this) + .title(tr("Deleting instance")) + .main(tr("The instance folder will be deleted.")) + .content(i.location()) + .icon(QMessageBox::Warning) + .button({tr("Move the folder to the recycle bin"), Recycle}) + .button({tr("Delete the folder permanently"), Delete}) + .button({tr("Cancel"), Cancel}) + .exec(); + + switch (r) + { + case Recycle: + return doDelete(QStringList(i.location()), true); + + case Delete: + return doDelete(QStringList(i.location()), false); + + case Cancel: // fall-through + default: + { + return false; + } + } + + return true; +} + +bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) +{ + if (MOBase::shellDelete(files, recycle, this)) { + return true; + } + + const auto e = GetLastError(); + if (e == ERROR_CANCELLED) { + log::debug("deletion cancelled by user"); + } else { + log::error("failed to delete, {}", formatSystemMessage(e)); + } + + return false; +} + void InstanceManagerDialog::onSelection() { const auto i = singleSelectionIndex(); @@ -327,6 +518,7 @@ void InstanceManagerDialog::fillData(const InstanceInfo& ii) ui->baseDirectory->setText(ii.baseDirectory()); ui->gameName->setText(ii.gameName()); ui->gameDir->setText(ii.gamePath()); + setButtonsEnabled(true); const auto& m = InstanceManager::instance(); @@ -348,4 +540,36 @@ void InstanceManagerDialog::fillData(const InstanceInfo& ii) ui->convertToPortable->setToolTip(""); } } + + + // these are not currently implemented; the ui sets them correctly above, + // but force them hidden for now + ui->convertToPortable->setVisible(false); + ui->convertToGlobal->setVisible(false); +} + +void InstanceManagerDialog::clearData() +{ + ui->name->clear(); + ui->location->clear(); + ui->baseDirectory->clear(); + ui->gameName->clear(); + ui->gameDir->clear(); + + setButtonsEnabled(false); + + ui->convertToPortable->setVisible(false); + ui->convertToGlobal->setVisible(false); +} + +void InstanceManagerDialog::setButtonsEnabled(bool b) +{ + ui->rename->setEnabled(b); + ui->exploreLocation->setEnabled(b); + ui->exploreBaseDirectory->setEnabled(b); + ui->exploreGame->setEnabled(b); + ui->convertToPortable->setEnabled(b); + ui->convertToGlobal->setEnabled(b); + ui->deleteInstance->setEnabled(b); + ui->switchToInstance->setEnabled(b); } diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index d102facb..3daa0800 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -26,6 +26,7 @@ public: void exploreLocation(); void exploreBaseDirectory(); void exploreGame(); + void deleteInstance(); private: static const std::size_t NoSelection = -1; @@ -47,6 +48,12 @@ private: void updateList(); void fillData(const InstanceInfo& ii); + void clearData(); + void setButtonsEnabled(bool b); + + bool deletePortable(const InstanceInfo& ii); + bool deleteGlobal(const InstanceInfo& ii); + bool doDelete(const QStringList& files, bool recycle); }; #endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 8bf28ed6..a8e5e2b7 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -305,18 +305,7 @@ - - - Delete instance - - - - :/MO/gui/remove:/MO/gui/remove - - - - - + Qt::Horizontal @@ -328,6 +317,17 @@ + + + + Delete instance + + + + :/MO/gui/remove:/MO/gui/remove + + + -- cgit v1.3.1 From 003ef66d2ff8e83e864d362f152a6f87ae05cbc4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Aug 2020 20:53:42 -0400 Subject: - changed PathSettings::base() to use the ini's parent directory instead of the global "dataPath" property; this is always the same thing, unless another Settings object is created for a different instance than the active one, which happens in the instance manager dialog - instance manager dialog: - select the active instance when opening - instance deletion now handles custom paths from the ini - started on instance conversions, not functional --- src/instancemanagerdialog.cpp | 455 +++++++++++++++++++++++++++++------------- src/instancemanagerdialog.h | 3 + src/instancemanagerdialog.ui | 6 +- src/settings.cpp | 4 +- 4 files changed, 328 insertions(+), 140 deletions(-) (limited to 'src') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 5f42f409..a91e3d92 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -20,12 +20,17 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent) dlg.exec(); } +QString iniFile(const QDir& dir) +{ + return dir.filePath(QString::fromStdWString(AppConfig::iniFileName())); +} + + class InstanceInfo { public: InstanceInfo(QDir dir, bool isPortable) : - m_dir(std::move(dir)), m_portable(isPortable), - m_settings(dir.filePath(QString::fromStdWString(AppConfig::iniFileName()))) + m_dir(std::move(dir)), m_portable(isPortable), m_settings(iniFile(dir)) { } @@ -90,6 +95,154 @@ public: return false; } + // returns a list of files and folders that must be deleted when deleting + // this instance + // + QStringList filesForDeletion() const + { + // native separators and ending slash + auto prettyDir = [](auto s) { + if (!s.endsWith("/") || !s.endsWith("\\")) { + s += "/"; + } + + return QDir::toNativeSeparators(s); + }; + + // native separators + auto prettyFile = [](auto s) { + return QDir::toNativeSeparators(s); + }; + + + // lowercase, native separators and ending slash + auto canonicalDir = [](auto s) { + s = s.toLower(); + if (!s.endsWith("/") || !s.endsWith("\\")) { + s += "/"; + } + + return QDir::toNativeSeparators(s); + }; + + // lower and native separators + auto canonicalFile = [](auto s) { + return QDir::toNativeSeparators(s.toLower()); + }; + + + + // whether the given directory is contained in the root + auto dirInRoot = [&](auto root, auto dir) { + return canonicalDir(dir).startsWith(canonicalDir(root)); + }; + + // whether the given file is contained in the root + auto fileInRoot = [&](auto root, auto file) { + return canonicalFile(file).startsWith(canonicalDir(root)); + }; + + + + + const auto loc = location(); + const auto base = m_settings.paths().base(); + + + // directories that might contain the individual files and directories + // set in the path settings + QStringList roots; + + // a portable instance has its location in the installation directory, + // don't delete that + if (!isPortable()) { + roots.append(loc); + } + + // the base directory is the location directory by default, don't add it + // if it's the same + if (canonicalDir(base) != canonicalDir(loc)) { + roots.append(base); + } + + + // all the directories that are part of an instance + const QStringList dirs = { + m_settings.paths().downloads(), + m_settings.paths().mods(), + m_settings.paths().overwrite(), + m_settings.paths().profiles(), + m_settings.paths().cache(), + m_dir.filePath(QString::fromStdWString(AppConfig::dumpsDir())), + m_dir.filePath(QString::fromStdWString(AppConfig::logPath())), + }; + + // all the files that are part of an instance + const QStringList files = { + iniFile(m_dir), + }; + + + // this will contain the root directories, plus all the individual + // directories that are not inside these roots + QStringList cleanDirs; + + for (const auto& f : dirs) { + bool inRoots = false; + + for (const auto& root : roots) { + if (dirInRoot(root, f)) { + inRoots = true; + break; + } + } + + if (!inRoots) { + // not in roots, this is a path that was changed by the user + cleanDirs.append(prettyDir(f)); + } + } + + // adding the roots + for (const auto& root : roots) { + cleanDirs.append(prettyDir(root)); + } + + cleanDirs.sort(Qt::CaseInsensitive); + + + // this will contain the individual files that are not inside the roots; + // not that this only contains the INI file for now, so most of this is + // useless + QStringList cleanFiles; + + for (const auto& f : files) { + bool inRoots = false; + + for (const auto& root : roots) { + if (fileInRoot(root, f)) { + inRoots = true; + break; + } + } + + if (!inRoots) { + // not in roots, this is a path that was changed by the user + cleanFiles.append(prettyFile(f)); + } + } + + cleanFiles.sort(Qt::CaseInsensitive); + + + // contains all the directories and files to be deleted + QStringList all; + all.append(cleanDirs); + all.append(cleanFiles); + + return all; + } + private: QDir m_dir; bool m_portable; @@ -120,11 +273,12 @@ InstanceManagerDialog::InstanceManagerDialog( updateInstances(); updateList(); + selectActiveInstance(); connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&]{ onSelection(); }); - //connect(ui->list, &QListWidget::itemActivated, [&]{ openSelectedInstance(); }); + connect(ui->list, &QListView::activated, [&]{ openSelectedInstance(); }); connect(ui->rename, &QPushButton::clicked, [&]{ rename(); }); connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); @@ -132,6 +286,9 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); + connect(ui->convertToGlobal, &QPushButton::clicked, [&]{ convertToGlobal(); }); + connect(ui->convertToPortable, &QPushButton::clicked, [&]{ convertToPortable(); }); + connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); connect(ui->close, &QPushButton::clicked, [&]{ close(); }); } @@ -171,19 +328,22 @@ void InstanceManagerDialog::updateList() } } - - if (m_instances.empty()) { - select(-1); - } else { - if (sel == NoSel) { - if (prevSelIndex >= m_instances.size()) { - sel = m_instances.size() - 1; - } else { - sel = prevSelIndex; + // keep current selection or select the next one if there was a selection; + // there's no selection when opening the dialog, that's handled in the ctor + if (prevSel) { + if (m_instances.empty()) { + select(-1); + } else { + if (sel == NoSel) { + if (prevSelIndex >= m_instances.size()) { + sel = m_instances.size() - 1; + } else { + sel = prevSelIndex; + } } - } - select(sel); + select(sel); + } } } @@ -201,6 +361,24 @@ void InstanceManagerDialog::select(std::size_t i) } } +void InstanceManagerDialog::selectActiveInstance() +{ + const auto active = InstanceManager::instance().currentInstance(); + + for (std::size_t i=0; iname() == active) { + select(i); + + ui->list->scrollTo( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); + + return; + } + } + + select(0); +} + void InstanceManagerDialog::openSelectedInstance() { const auto i = singleSelectionIndex(); @@ -211,49 +389,53 @@ void InstanceManagerDialog::openSelectedInstance() InstanceManager::instance().switchToInstance(m_instances[i]->name()); } -void InstanceManagerDialog::rename() +QString getInstanceName( + QWidget* parent, const QString& title, const QString& moreText, + const QString& label, const QString& oldName={}) { - auto* i = singleSelection(); - if (!i) { - return; - } - auto& m = InstanceManager::instance(); - if (i->isActive()) { - QMessageBox::information(this, - tr("Rename instance"), tr("The active instance cannot be renamed.")); - return; - } - QDialog dlg(this); - dlg.setWindowTitle(tr("Rename instance")); + QDialog dlg(parent); + dlg.setWindowTitle(title); auto* ly = new QVBoxLayout(&dlg); auto* bb = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - auto* text = new QLineEdit(i->name()); + auto* text = new QLineEdit(oldName); text->selectAll(); auto* error = new QLabel; - ly->addWidget(new QLabel(tr("Instance name"))); + 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); - connect(text, &QLineEdit::textChanged, [&] { + auto check = [&] { bool okay = false; - if (!m.validInstanceName(text->text())) { - error->setText(tr("The instance name must be a valid folder name.")); + 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 != i->name()) && m.instanceExists(text->text())) { - error->setText(tr("An instance with this name already exists.")); + if ((name != oldName) && m.instanceExists(text->text())) { + error->setText(QObject::tr("An instance with this name already exists.")); } else { okay = true; } @@ -261,18 +443,43 @@ void InstanceManagerDialog::rename() 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(); }); - connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); }); - 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(); + if (!i) { return; } + auto& m = InstanceManager::instance(); + if (i->isActive()) { + QMessageBox::information(this, + tr("Rename instance"), tr("The active instance cannot be renamed.")); + return; + } + + const auto newName = getInstanceName( + this, tr("Rename instance"), "", tr("Instance name"), i->name()); + + if (newName.isEmpty()) { + return; + } - const QString newName = m.sanitizeInstanceName(text->text()); const QString src = i->location(); const QString dest = QDir::toNativeSeparators( QFileInfo(i->location()).dir().path() + "/" + newName); @@ -323,130 +530,71 @@ void InstanceManagerDialog::deleteInstance() return; } - if (i->isPortable()) { - deletePortable(*i); - } else { - deleteGlobal(*i); - } - - updateInstances(); - updateList(); -} - -bool InstanceManagerDialog::deletePortable(const InstanceInfo& i) -{ const auto Recycle = QMessageBox::Save; const auto Delete = QMessageBox::Yes; const auto Cancel = QMessageBox::Cancel; - const std::vector fileNames = { - AppConfig::iniFileName(), - }; + const auto files = i->filesForDeletion(); - const std::vector dirNames = { - AppConfig::dumpsDir(), - AppConfig::downloadPath(), - AppConfig::logPath(), - AppConfig::modsPath(), - AppConfig::overwritePath(), - AppConfig::profilesPath(), - AppConfig::cachePath() - }; + MOBase::TaskDialog dlg(this); - QStringList files; - for (const auto& n : fileNames) { - files.push_back(QDir::toNativeSeparators( - i.location() + "/" + QString::fromStdWString(n))); - } + dlg + .title(("Deleting instance")) + .main(QObject::tr("These files and folders will be deleted")) + .icon(QMessageBox::Warning) + .button({tr("Move to the recycle bin"), Recycle}) + .button({tr("Delete permanently"), Delete}) + .button({tr("Cancel"), Cancel}); - QStringList dirs; - for (const auto& n : dirNames) { - dirs.push_back(QDir::toNativeSeparators( - i.location() + "/" + QString::fromStdWString(n))); - } + auto* list = new QPlainTextEdit(); + list->setReadOnly(true); + list->setWordWrapMode(QTextOption::NoWrap); + list->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + list->setMaximumHeight(150); - QString details = QObject::tr("These files will be deleted:"); for (const auto& f : files) { - details += "\n - " + f; - } - - details += "\n\n" + QObject::tr("These folders will be deleted:"); - for (const auto& d : dirs) { - details += "\n - " + d; + list->appendPlainText(f); } + list->moveCursor(QTextCursor::MoveOperation::Start); - QStringList all; - all.append(files); - all.append(dirs); + dlg.addContent(list); - - const auto r = MOBase::TaskDialog(this) - .title(("Deleting portable instance")) - .main(tr("This will delete the data of the portable instance.")) - .content(tr( - "The data is in %1. Only the relevant files and folders will be " - "deleted. The Mod Organizer installation itself will be untouched.") - .arg(i.location())) - .details(details) - .icon(QMessageBox::Warning) - .button({tr("Move the data to the recycle bin"), Recycle}) - .button({tr("Delete the data permanently"), Delete}) - .button({tr("Cancel"), Cancel}) - .exec(); + const auto r = dlg.exec(); switch (r) { case Recycle: - return doDelete(all, true); - - case Delete: - return doDelete(all, false); - - case Cancel: // fall-through - default: { - return false; - } - } - - return true; -} - -bool InstanceManagerDialog::deleteGlobal(const InstanceInfo& i) -{ - const auto Recycle = QMessageBox::Save; - const auto Delete = QMessageBox::Yes; - const auto Cancel = QMessageBox::Cancel; - - const auto r = MOBase::TaskDialog(this) - .title(tr("Deleting instance")) - .main(tr("The instance folder will be deleted.")) - .content(i.location()) - .icon(QMessageBox::Warning) - .button({tr("Move the folder to the recycle bin"), Recycle}) - .button({tr("Delete the folder permanently"), Delete}) - .button({tr("Cancel"), Cancel}) - .exec(); + if (!doDelete(files, true)) { + return; + } - switch (r) - { - case Recycle: - return doDelete(QStringList(i.location()), true); + break; + } case Delete: - return doDelete(QStringList(i.location()), false); + { + if (!doDelete(files, false)) { + return; + } + + break; + } case Cancel: // fall-through default: { - return false; + return; } } - return true; + updateInstances(); + updateList(); } + bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) { if (MOBase::shellDelete(files, recycle, this)) { @@ -463,6 +611,43 @@ bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) return false; } +void InstanceManagerDialog::convertToGlobal() +{ + const auto* i = singleSelection(); + if (!i) { + return; + } + + if (!i->isPortable()) { + log::error("can't convert to global, this is not a portable instance"); + return; + } + + const auto& m = InstanceManager::instance(); + + const auto name = getInstanceName( + this, + tr("Convert to global instance"), + tr( + "This will move all the instance data currently in Mod Organizer's " + "installation folder into a global instance. If the operation fails or " + "is cancelled, no data should be lost, but the move will need to be " + "completed or cleaned up manually.

    " + "Source: %1
    " + "Destination: %2") + .arg(i->location()) + .arg(QDir::toNativeSeparators(m.instancesPath())), + tr("Name of the new instance")); + + if (name.isEmpty()) { + return; + } +} + +void InstanceManagerDialog::convertToPortable() +{ +} + void InstanceManagerDialog::onSelection() { const auto i = singleSelectionIndex(); @@ -540,12 +725,6 @@ void InstanceManagerDialog::fillData(const InstanceInfo& ii) ui->convertToPortable->setToolTip(""); } } - - - // these are not currently implemented; the ui sets them correctly above, - // but force them hidden for now - ui->convertToPortable->setVisible(false); - ui->convertToGlobal->setVisible(false); } void InstanceManagerDialog::clearData() diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 3daa0800..659710fe 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -20,6 +20,7 @@ public: ~InstanceManagerDialog(); void select(std::size_t i); + void selectActiveInstance(); void openSelectedInstance(); void rename(); @@ -27,6 +28,8 @@ public: void exploreBaseDirectory(); void exploreGame(); void deleteInstance(); + void convertToGlobal(); + void convertToPortable(); private: static const std::size_t NoSelection = -1; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index a8e5e2b7..b2abfa4b 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -103,7 +103,11 @@ 0 - + + + QAbstractItemView::NoEditTriggers + + diff --git a/src/settings.cpp b/src/settings.cpp index a0758bd4..b286510d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1545,8 +1545,10 @@ QString PathSettings::makeDefaultPath(const QString dirName) QString PathSettings::base() const { + const QString dataPath = QFileInfo(m_Settings.fileName()).dir().path(); + return QDir::fromNativeSeparators(get(m_Settings, - "Settings", "base_directory", qApp->property("dataPath").toString())); + "Settings", "base_directory", dataPath)); } QString PathSettings::downloads(bool resolve) const -- cgit v1.3.1 From 14d15070c8b24e73297a6f37132b8ad5c6e966ef Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Aug 2020 21:57:08 -0400 Subject: added checkboxes for deleting instances disabled instance conversion buttons again, not implemented --- src/instancemanagerdialog.cpp | 178 ++++++++++++++++++++++-------------------- src/instancemanagerdialog.ui | 22 +++--- 2 files changed, 104 insertions(+), 96 deletions(-) (limited to 'src') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index a91e3d92..5810a255 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -29,6 +29,29 @@ QString iniFile(const QDir& dir) class InstanceInfo { public: + struct Object + { + QString path; + bool mandatoryDelete; + + Object(QString p, bool d=false) + : path(std::move(p)), mandatoryDelete(d) + { + } + + bool operator<(const Object& o) const + { + if (mandatoryDelete && !o.mandatoryDelete) { + return true; + } else if (!mandatoryDelete && o.mandatoryDelete) { + return false; + } + + return false; + } + }; + + InstanceInfo(QDir dir, bool isPortable) : m_dir(std::move(dir)), m_portable(isPortable), m_settings(iniFile(dir)) { @@ -98,7 +121,7 @@ public: // returns a list of files and folders that must be deleted when deleting // this instance // - QStringList filesForDeletion() const + std::vector objectsForDeletion() const { // native separators and ending slash auto prettyDir = [](auto s) { @@ -151,47 +174,48 @@ public: // directories that might contain the individual files and directories // set in the path settings - QStringList roots; + std::vector roots; // a portable instance has its location in the installation directory, // don't delete that if (!isPortable()) { - roots.append(loc); + roots.push_back({loc, true}); } // the base directory is the location directory by default, don't add it // if it's the same if (canonicalDir(base) != canonicalDir(loc)) { - roots.append(base); + roots.push_back({base, false}); } - // all the directories that are part of an instance - const QStringList dirs = { + // all the directories that are part of an instance; none of them are + // mandatory for deletion + const std::vector dirs = { m_settings.paths().downloads(), m_settings.paths().mods(), - m_settings.paths().overwrite(), - m_settings.paths().profiles(), m_settings.paths().cache(), + m_settings.paths().profiles(), + m_settings.paths().overwrite(), m_dir.filePath(QString::fromStdWString(AppConfig::dumpsDir())), m_dir.filePath(QString::fromStdWString(AppConfig::logPath())), }; // all the files that are part of an instance - const QStringList files = { - iniFile(m_dir), + const std::vector files = { + {iniFile(m_dir), true}, // the ini file must be deleted }; // this will contain the root directories, plus all the individual // directories that are not inside these roots - QStringList cleanDirs; + std::vector cleanDirs; for (const auto& f : dirs) { bool inRoots = false; for (const auto& root : roots) { - if (dirInRoot(root, f)) { + if (dirInRoot(root.path, f.path)) { inRoots = true; break; } @@ -199,28 +223,29 @@ public: if (!inRoots) { // not in roots, this is a path that was changed by the user - cleanDirs.append(prettyDir(f)); + cleanDirs.push_back({prettyDir(f.path), f.mandatoryDelete}); } } - // adding the roots - for (const auto& root : roots) { - cleanDirs.append(prettyDir(root)); + // prepending the roots + for (auto itor=roots.rbegin(); itor!=roots.rend(); ++itor) { + cleanDirs.insert( + cleanDirs.begin(), + {prettyDir(itor->path), itor->mandatoryDelete}); } - cleanDirs.sort(Qt::CaseInsensitive); // this will contain the individual files that are not inside the roots; // not that this only contains the INI file for now, so most of this is // useless - QStringList cleanFiles; + std::vector cleanFiles; for (const auto& f : files) { bool inRoots = false; for (const auto& root : roots) { - if (fileInRoot(root, f)) { + if (fileInRoot(root.path, f.path)) { inRoots = true; break; } @@ -228,17 +253,18 @@ public: if (!inRoots) { // not in roots, this is a path that was changed by the user - cleanFiles.append(prettyFile(f)); + cleanFiles.push_back({prettyFile(f.path), f.mandatoryDelete}); } } - cleanFiles.sort(Qt::CaseInsensitive); - // contains all the directories and files to be deleted - QStringList all; - all.append(cleanDirs); - all.append(cleanFiles); + std::vector all; + all.insert(all.end(), cleanDirs.begin(), cleanDirs.end()); + all.insert(all.end(), cleanFiles.begin(), cleanFiles.end()); + + // mandatory on top + std::stable_sort(all.begin(), all.end()); return all; } @@ -534,60 +560,65 @@ void InstanceManagerDialog::deleteInstance() const auto Delete = QMessageBox::Yes; const auto Cancel = QMessageBox::Cancel; - const auto files = i->filesForDeletion(); + const auto files = i->objectsForDeletion(); MOBase::TaskDialog dlg(this); dlg - .title(("Deleting instance")) - .main(QObject::tr("These files and folders will be deleted")) + .title(tr("Deleting instance")) + .main(tr("These files and folders will be deleted")) + .content(tr("All checked items will be deleted.")) .icon(QMessageBox::Warning) .button({tr("Move to the recycle bin"), Recycle}) .button({tr("Delete permanently"), Delete}) .button({tr("Cancel"), Cancel}); - auto* list = new QPlainTextEdit(); - list->setReadOnly(true); - list->setWordWrapMode(QTextOption::NoWrap); - list->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + auto* list = new QListWidget(); + list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - list->setMaximumHeight(150); + list->setMaximumHeight(160); for (const auto& f : files) { - list->appendPlainText(f); - } + auto* item = new QListWidgetItem(f.path); + + if (f.mandatoryDelete) { + item->setFlags(item->flags() & (~Qt::ItemIsEnabled)); + } else { + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + } - list->moveCursor(QTextCursor::MoveOperation::Start); + item->setCheckState(Qt::Checked); + list->addItem(item); + } dlg.addContent(list); + const auto r = dlg.exec(); - switch (r) - { - case Recycle: - { - if (!doDelete(files, true)) { - return; - } + if (r != Recycle && r != Delete) { + return; + } - break; - } - case Delete: - { - if (!doDelete(files, false)) { - return; - } + QStringList selected; - break; + for (int i=0; icount(); ++i) { + if (list->item(i)->checkState() == Qt::Checked) { + selected.append(list->item(i)->text()); } + } - case Cancel: // fall-through - default: - { - return; - } + if (selected.isEmpty()) { + QMessageBox::information( + this, tr("Deleting instance"), tr("Nothing to delete.")); + + return; + } + + if (!doDelete(selected, (r == Recycle))) { + return; } updateInstances(); @@ -613,39 +644,12 @@ bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) void InstanceManagerDialog::convertToGlobal() { - const auto* i = singleSelection(); - if (!i) { - return; - } - - if (!i->isPortable()) { - log::error("can't convert to global, this is not a portable instance"); - return; - } - - const auto& m = InstanceManager::instance(); - - const auto name = getInstanceName( - this, - tr("Convert to global instance"), - tr( - "This will move all the instance data currently in Mod Organizer's " - "installation folder into a global instance. If the operation fails or " - "is cancelled, no data should be lost, but the move will need to be " - "completed or cleaned up manually.

    " - "Source: %1
    " - "Destination: %2") - .arg(i->location()) - .arg(QDir::toNativeSeparators(m.instancesPath())), - tr("Name of the new instance")); - - if (name.isEmpty()) { - return; - } + // not implemented } void InstanceManagerDialog::convertToPortable() { + // not implemented } void InstanceManagerDialog::onSelection() @@ -725,6 +729,10 @@ void InstanceManagerDialog::fillData(const InstanceInfo& ii) ui->convertToPortable->setToolTip(""); } } + + // not implemented, hide the buttons + ui->convertToPortable->setVisible(false); + ui->convertToGlobal->setVisible(false); } void InstanceManagerDialog::clearData() diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index b2abfa4b..414d4982 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -308,6 +308,17 @@ + + + + Delete instance + + + + :/MO/gui/remove:/MO/gui/remove + + + @@ -321,17 +332,6 @@ - - - - Delete instance - - - - :/MO/gui/remove:/MO/gui/remove - - - -- cgit v1.3.1 From a9772873d69b875f5617c0121c73a271d432a29c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Aug 2020 22:35:19 -0400 Subject: open ini button update when renaming instance update and select when creating instance --- src/createinstancedialog.cpp | 13 +++++-- src/createinstancedialog.h | 2 + src/instancemanagerdialog.cpp | 86 ++++++++++++++++++++++++++++++++++--------- src/instancemanagerdialog.h | 5 ++- src/instancemanagerdialog.ui | 19 ++++++++++ 5 files changed, 103 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 935b9ee9..f4140e01 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -11,8 +11,9 @@ using namespace MOBase; CreateInstanceDialog::CreateInstanceDialog( - const PluginContainer& pc, QWidget *parent) - : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc) + const PluginContainer& pc, QWidget *parent) : + QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), + m_switching(false) { using namespace cid; @@ -302,8 +303,9 @@ void CreateInstanceDialog::finish() if (ui->launch->isChecked()) { InstanceManager::instance().switchToInstance(ci.instanceName); + m_switching = true; } else { - close(); + accept(); } } catch(Failed&) @@ -391,6 +393,11 @@ CreateInstanceDialog::Paths CreateInstanceDialog::paths() const return getSelected(&cid::Page::selectedPaths); } +bool CreateInstanceDialog::switching() const +{ + return m_switching; +} + void fixVarDir(QString& path, const std::wstring& defaultDir) { if (path.isEmpty()) { diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 2f5774ae..95d4fa68 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -70,6 +70,7 @@ public: QString instanceName() const; QString dataPath() const; Paths paths() const; + bool switching() const; CreationInfo creationInfo() const; @@ -78,6 +79,7 @@ private: const PluginContainer& m_pc; std::vector> m_pages; QString m_originalNext; + bool m_switching; template T getSelected(T (cid::Page::*mf)() const) const diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 5810a255..37bdea7b 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -20,7 +20,7 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent) dlg.exec(); } -QString iniFile(const QDir& dir) +QString makeIniFile(const QDir& dir) { return dir.filePath(QString::fromStdWString(AppConfig::iniFileName())); } @@ -52,9 +52,16 @@ public: }; - InstanceInfo(QDir dir, bool isPortable) : - m_dir(std::move(dir)), m_portable(isPortable), m_settings(iniFile(dir)) + InstanceInfo(QDir dir, bool isPortable) + : m_portable(isPortable) { + setDir(dir); + } + + void setDir(const QDir& dir) + { + m_dir = dir; + m_settings.reset(new Settings(makeIniFile(dir))); } QString name() const @@ -68,8 +75,8 @@ public: QString gameName() const { - if (auto n=m_settings.game().name()) { - if (auto e=m_settings.game().edition()) { + if (auto n=m_settings->game().name()) { + if (auto e=m_settings->game().edition()) { if (!e->isEmpty()) { return *n + " (" + *e + ")"; } @@ -83,7 +90,7 @@ public: QString gamePath() const { - if (auto n=m_settings.game().directory()) { + if (auto n=m_settings->game().directory()) { return QDir::toNativeSeparators(*n); } else { return {}; @@ -97,7 +104,12 @@ public: QString baseDirectory() const { - return QDir::toNativeSeparators(m_settings.paths().base()); + return QDir::toNativeSeparators(m_settings->paths().base()); + } + + QString iniFile() const + { + return makeIniFile(m_dir); } bool isPortable() const @@ -169,7 +181,7 @@ public: const auto loc = location(); - const auto base = m_settings.paths().base(); + const auto base = m_settings->paths().base(); // directories that might contain the individual files and directories @@ -192,18 +204,18 @@ public: // all the directories that are part of an instance; none of them are // mandatory for deletion const std::vector dirs = { - m_settings.paths().downloads(), - m_settings.paths().mods(), - m_settings.paths().cache(), - m_settings.paths().profiles(), - m_settings.paths().overwrite(), + m_settings->paths().downloads(), + m_settings->paths().mods(), + m_settings->paths().cache(), + m_settings->paths().profiles(), + m_settings->paths().overwrite(), m_dir.filePath(QString::fromStdWString(AppConfig::dumpsDir())), m_dir.filePath(QString::fromStdWString(AppConfig::logPath())), }; // all the files that are part of an instance const std::vector files = { - {iniFile(m_dir), true}, // the ini file must be deleted + {iniFile(), true}, // the ini file must be deleted }; @@ -270,9 +282,9 @@ public: } private: + const bool m_portable; QDir m_dir; - bool m_portable; - Settings m_settings; + std::unique_ptr m_settings; }; @@ -310,10 +322,11 @@ InstanceManagerDialog::InstanceManagerDialog( connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); - connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); connect(ui->convertToGlobal, &QPushButton::clicked, [&]{ convertToGlobal(); }); connect(ui->convertToPortable, &QPushButton::clicked, [&]{ convertToPortable(); }); + connect(ui->openINI, &QPushButton::clicked, [&]{ openINI(); }); + connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); connect(ui->close, &QPushButton::clicked, [&]{ close(); }); @@ -387,6 +400,18 @@ void InstanceManagerDialog::select(std::size_t i) } } +void InstanceManagerDialog::select(const QString& name) +{ + for (std::size_t i=0; iname() == name) { + select(i); + return; + } + } + + log::error("can't select instance {}, not in list", name); +} + void InstanceManagerDialog::selectActiveInstance() { const auto active = InstanceManager::instance().currentInstance(); @@ -492,6 +517,8 @@ void InstanceManagerDialog::rename() return; } + const auto selIndex = singleSelectionIndex(); + auto& m = InstanceManager::instance(); if (i->isActive()) { QMessageBox::information(this, @@ -519,6 +546,10 @@ void InstanceManagerDialog::rename() return; } + + m_model->item(selIndex)->setText(newName); + i->setDir(dest); + fillData(*i); } void InstanceManagerDialog::exploreLocation() @@ -542,6 +573,13 @@ void InstanceManagerDialog::exploreGame() } } +void InstanceManagerDialog::openINI() +{ + if (const auto* i=singleSelection()) { + shell::Open(i->iniFile()); + } +} + void InstanceManagerDialog::deleteInstance() { const auto* i = singleSelection(); @@ -665,7 +703,19 @@ void InstanceManagerDialog::onSelection() void InstanceManagerDialog::createNew() { CreateInstanceDialog dlg(m_pc, this); - dlg.exec(); + if (dlg.exec() != QDialog::Accepted) { + return; + } + + if (dlg.switching()) { + // restarting MO + return; + } + + updateInstances(); + updateList(); + + select(dlg.instanceName()); } std::size_t InstanceManagerDialog::singleSelectionIndex() const diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 659710fe..477a7d01 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -20,6 +20,7 @@ public: ~InstanceManagerDialog(); void select(std::size_t i); + void select(const QString& name); void selectActiveInstance(); void openSelectedInstance(); @@ -27,9 +28,11 @@ public: void exploreLocation(); void exploreBaseDirectory(); void exploreGame(); - void deleteInstance(); + void convertToGlobal(); void convertToPortable(); + void openINI(); + void deleteInstance(); private: static const std::size_t NoSelection = -1; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 414d4982..410b58c1 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -107,6 +107,18 @@ QAbstractItemView::NoEditTriggers + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + QListView::Adjust + + + true + @@ -308,6 +320,13 @@ + + + + Open INI + + + -- cgit v1.3.1 From ee53312652af10fbddb5d1d16db6212bca2b9665 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Aug 2020 02:41:57 -0400 Subject: moved naturalCompare() to uibase don't try to delete paths that don't exist natsort for instance names and games --- src/createinstancedialogpages.cpp | 6 +++--- src/instancemanagerdialog.cpp | 38 +++++++++++++++++++++++++----------- src/modinfodialog.cpp | 24 ----------------------- src/modinfodialogconflictsmodels.cpp | 3 +++ src/modinfodialogfwd.h | 3 --- 5 files changed, 33 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 8d304635..73b265c8 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -7,13 +7,13 @@ #include "shared/appconfig.h" #include #include +#include namespace cid { -using MOBase::IPluginGame; +using namespace MOBase; using MOBase::TaskDialog; -using MOBase::FilterWidget; QString makeDefaultPath(const std::wstring& dir) { @@ -305,7 +305,7 @@ std::vector GamePage::sortedGamePlugins() const } std::sort(v.begin(), v.end(), [](auto* a, auto* b) { - return (a->gameName() < b->gameName()); + return (naturalCompare(a->gameName(), b->gameName()) < 0); }); return v; diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 37bdea7b..3b9dd344 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -191,13 +191,17 @@ public: // a portable instance has its location in the installation directory, // don't delete that if (!isPortable()) { - roots.push_back({loc, true}); + if (QDir(loc).exists()) { + roots.push_back({loc, true}); + } } // the base directory is the location directory by default, don't add it // if it's the same if (canonicalDir(base) != canonicalDir(loc)) { - roots.push_back({base, false}); + if (QDir(base).exists()) { + roots.push_back({base, false}); + } } @@ -234,8 +238,11 @@ public: } if (!inRoots) { - // not in roots, this is a path that was changed by the user - cleanDirs.push_back({prettyDir(f.path), f.mandatoryDelete}); + // not in roots, this is a path that was changed by the user; make + // sure it exists + if (QDir(f.path).exists()) { + cleanDirs.push_back({prettyDir(f.path), f.mandatoryDelete}); + } } } @@ -264,8 +271,11 @@ public: } if (!inRoots) { - // not in roots, this is a path that was changed by the user - cleanFiles.push_back({prettyFile(f.path), f.mandatoryDelete}); + // not in roots, this is a path that was changed by the user; make + // sure it exists + if (QFileInfo(f.path).exists()) { + cleanFiles.push_back({prettyFile(f.path), f.mandatoryDelete}); + } } } @@ -338,14 +348,20 @@ void InstanceManagerDialog::updateInstances() m_instances.clear(); - if (m.portableInstanceExists()) { - m_instances.push_back(std::make_unique( - m.portablePath(), true)); - } - for (auto&& d : m.instancePaths()) { m_instances.push_back(std::make_unique(d, false)); } + + // sort first, prepend portable after so it's always on top + std::sort(m_instances.begin(), m_instances.end(), [](auto&& a, auto&& b) { + return (MOBase::naturalCompare(a->name(), b->name()) < 0); + }); + + if (m.portableInstanceExists()) { + m_instances.insert( + m_instances.begin(), + std::make_unique(m.portablePath(), true)); + } } void InstanceManagerDialog::updateList() diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c3239e0d..cc50d446 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -40,30 +40,6 @@ namespace fs = std::filesystem; const int max_scan_for_context_menu = 50; -int naturalCompare(const QString& a, const QString& b) -{ - static QCollator c = []{ - QCollator c; - c.setNumericMode(true); - c.setCaseSensitivity(Qt::CaseInsensitive); - return c; - }(); - - - // todo: remove this once the fix is released - // see https://bugreports.qt.io/projects/QTBUG/issues/QTBUG-81673 - // and https://codereview.qt-project.org/c/qt/qtbase/+/287966/5/src/corelib/text/qcollator_win.cpp - if (!a.size()) { - return b.size() ? -1 : 0; - } - if (!b.size()) { - return +1; - } - - - return c.compare(a, b); -} - bool canPreviewFile( const PluginContainer& pluginContainer, bool isArchive, const QString& filename) diff --git a/src/modinfodialogconflictsmodels.cpp b/src/modinfodialogconflictsmodels.cpp index 17fdef90..464abbd3 100644 --- a/src/modinfodialogconflictsmodels.cpp +++ b/src/modinfodialogconflictsmodels.cpp @@ -1,5 +1,8 @@ #include "modinfodialogconflictsmodels.h" #include "modinfodialog.h" +#include + +using MOBase::naturalCompare; ConflictItem::ConflictItem( QString before, QString relativeName, QString after, diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index c758573c..805e8a06 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -35,9 +35,6 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString& oldNa FileRenamer::RenameResults restoreHiddenFilesRecursive(FileRenamer& renamer, const QString &targetDir); -int naturalCompare(const QString& a, const QString& b); - - class ElideLeftDelegate : public QStyledItemDelegate { public: -- cgit v1.3.1 From 4c5e3da2334a1d0c474148be8881b46d6ca6d3fa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Aug 2020 05:06:26 -0400 Subject: moved nexus api stuff to GlobalSettings pass a pointer to Settings around for things that can be called without settings, when creating the first instance added dummy plugin list, mod list and iorganizer to initialize plugins without an instance moved PluginContainer into the core filter, had nothing to do with the plugins list NexusInterface is now created manually instead of being a static singleton because it needs to know if the settings are available --- src/CMakeLists.txt | 2 +- src/createinstancedialog.cpp | 9 +- src/createinstancedialog.h | 6 +- src/createinstancedialogpages.cpp | 5 +- src/instancemanagerdialog.cpp | 2 +- src/main.cpp | 27 +++-- src/mainwindow.cpp | 4 +- src/modlist.cpp | 41 ++++++++ src/modlist.h | 14 +++ src/nexusinterface.cpp | 28 +++-- src/nexusinterface.h | 6 +- src/nxmaccessmanager.cpp | 37 +++++-- src/nxmaccessmanager.h | 11 +- src/organizercore.cpp | 4 +- src/organizerproxy.cpp | 212 ++++++++++++++++++++++++++++++++++++++ src/organizerproxy.h | 59 +++++++++++ src/plugincontainer.cpp | 88 ++++++++++------ src/pluginlist.cpp | 60 +++++++++++ src/pluginlist.h | 18 ++++ src/settings.cpp | 62 +++++------ src/settings.h | 36 +++---- src/settingsdialognexus.cpp | 18 ++-- src/settingsdialognexus.h | 4 +- 23 files changed, 623 insertions(+), 130 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 844f5e19..eec33460 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,7 @@ add_filter(NAME src/core GROUPS nexusinterface nxmaccessmanager organizercore + plugincontainer apiuseraccount processrunner qdirfiletree @@ -136,7 +137,6 @@ add_filter(NAME src/modlist GROUPS ) add_filter(NAME src/plugins GROUPS - plugincontainer pluginlist pluginlistsortproxy pluginlistview diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index f4140e01..0f62fcf6 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -11,8 +11,8 @@ using namespace MOBase; CreateInstanceDialog::CreateInstanceDialog( - const PluginContainer& pc, QWidget *parent) : - QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), + const PluginContainer& pc, Settings* s, QWidget *parent) : + QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s), m_switching(false) { using namespace cid; @@ -55,6 +55,11 @@ const PluginContainer& CreateInstanceDialog::pluginContainer() return m_pc; } +Settings* CreateInstanceDialog::settings() +{ + return m_settings; +} + bool CreateInstanceDialog::isOnLastPage() const { for (int i=ui->pages->currentIndex() + 1; i < ui->pages->count(); ++i) { diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 95d4fa68..0841ef29 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -8,6 +8,7 @@ namespace Ui { class CreateInstanceDialog; }; namespace cid { class Page; } class PluginContainer; +class Settings; class CreateInstanceDialog : public QDialog { @@ -47,12 +48,14 @@ public: explicit CreateInstanceDialog( - const PluginContainer& pc, QWidget *parent = nullptr); + const PluginContainer& pc, Settings* s, QWidget *parent = nullptr); ~CreateInstanceDialog(); Ui::CreateInstanceDialog* getUI(); + const PluginContainer& pluginContainer(); + Settings* settings(); void next(); void back(); @@ -77,6 +80,7 @@ public: private: std::unique_ptr ui; const PluginContainer& m_pc; + Settings* m_settings; std::vector> m_pages; QString m_originalNext; bool m_switching; diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 73b265c8..d809079d 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -964,7 +964,8 @@ NexusPage::NexusPage(CreateInstanceDialog& dlg) : Page(dlg), m_skip(false) { m_connectionUI.reset(new NexusConnectionUI( - Settings::instance(), &m_dlg, + &m_dlg, + dlg.settings(), ui->nexusConnect, nullptr, ui->nexusManual, @@ -972,7 +973,7 @@ NexusPage::NexusPage(CreateInstanceDialog& dlg) // just check it once, or connecting and then going back and forth would skip // the page, which would be unexpected - m_skip = Settings::instance().nexus().hasApiKey(); + m_skip = GlobalSettings::hasNexusApiKey(); } NexusPage::~NexusPage() = default; diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 3b9dd344..7b8522ee 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -718,7 +718,7 @@ void InstanceManagerDialog::onSelection() void InstanceManagerDialog::createNew() { - CreateInstanceDialog dlg(m_pc, this); + CreateInstanceDialog dlg(m_pc, &Settings::instance(), this); if (dlg.exec() != QDialog::Accepted) { return; } diff --git a/src/main.cpp b/src/main.cpp index 93580931..fd5a47c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include "nxmaccessmanager.h" #include "instancemanager.h" #include "instancemanagerdialog.h" +#include "createinstancedialog.h" #include "organizercore.h" #include "env.h" #include "envmodule.h" @@ -329,6 +330,9 @@ int runApplication( // this must outlive `organizer` std::unique_ptr pluginContainer; + log::debug("initializing nexus interface"); + NexusInterface ni(&settings); + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { @@ -394,8 +398,8 @@ int runApplication( auto splash = createSplash(settings, dataPath, game); QString apiKey; - if (settings.nexus().apiKey(apiKey)) { - NexusInterface::instance().getAccessManager()->apiCheck(apiKey); + if (GlobalSettings::nexusApiKey(apiKey)) { + ni.getAccessManager()->apiCheck(apiKey); } log::debug("initializing tutorials"); @@ -415,8 +419,7 @@ int runApplication( // set up main window and its data structures MainWindow mainWindow(settings, organizer, *pluginContainer); - NexusInterface::instance() - .getAccessManager()->setTopLevelWidget(&mainWindow); + ni.getAccessManager()->setTopLevelWidget(&mainWindow); QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); @@ -443,8 +446,7 @@ int runApplication( res = application.exec(); mainWindow.close(); - NexusInterface::instance() - .getAccessManager()->setTopLevelWidget(nullptr); + ni.getAccessManager()->setTopLevelWidget(nullptr); } settings.geometry().resetIfNeeded(); @@ -507,6 +509,7 @@ QString determineDataPath(const cl::CommandLine& cl) } } + int doOneRun( cl::CommandLine& cl, MOApplication& application, SingleInstance& instance) { @@ -515,6 +518,18 @@ int doOneRun( // resets things when MO is "restarted" resetForRestart(cl); + + //{ + // NexusInterface ni(nullptr); + // + // PluginContainer pc(nullptr); + // pc.loadPlugins(); + // + // CreateInstanceDialog dlg(pc, nullptr); + // dlg.exec(); + //} + + const QString dataPath = determineDataPath(cl); if (dataPath.isEmpty()) { return 1; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3ef34e93..9ad51510 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4226,7 +4226,7 @@ void MainWindow::checkModsForUpdates() NexusInterface::instance().requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; - if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { + if (GlobalSettings::nexusApiKey(apiKey)) { m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else { @@ -5434,7 +5434,7 @@ void MainWindow::modUpdateCheck(std::multimap IDs) ModInfo::manualUpdateCheck(this, IDs); } else { QString apiKey; - if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { + if (GlobalSettings::nexusApiKey(apiKey)) { m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else diff --git a/src/modlist.cpp b/src/modlist.cpp index 1f845999..bf9aef83 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1528,3 +1528,44 @@ void ModList::disableSelected(const QItemSelectionModel *selectionModel) m_Profile->setModsEnabled(QList(), modsToDisable); } } + + +QString DummyModList::displayName(const QString &internalName) const +{ + return {}; +} + +QStringList DummyModList::allMods() const +{ + return {}; +} + +IModList::ModStates DummyModList::state(const QString &name) const +{ + return 0; +} + +bool DummyModList::setActive(const QString &name, bool active) +{ + return true; +} + +int DummyModList::priority(const QString &name) const +{ + return -1; +} + +bool DummyModList::setPriority(const QString &name, int newPriority) +{ + return true; +} + +bool DummyModList::onModStateChanged(const std::function &func) +{ + return true; +} + +bool DummyModList::onModMoved(const std::function &func) +{ + return true; +} diff --git a/src/modlist.h b/src/modlist.h index 385ca04c..3ab486c9 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -397,5 +397,19 @@ private: }; + +class DummyModList : public MOBase::IModList +{ +public: + QString displayName(const QString &internalName) const override; + QStringList allMods() const override; + ModStates state(const QString &name) const override; + bool setActive(const QString &name, bool active) override; + int priority(const QString &name) const override; + bool setPriority(const QString &name, int newPriority) override; + bool onModStateChanged(const std::function &func) override; + bool onModMoved(const std::function &func) override; +}; + #endif // MODLIST_H diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 396cec11..1364d7e1 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "nxmaccessmanager.h" #include "selectiondialog.h" #include "bbcode.h" +#include "settings.h" #include #include "shared/util.h" #include @@ -235,31 +236,42 @@ APILimits NexusInterface::parseLimits( } -NexusInterface::NexusInterface() +static NexusInterface* g_instance = nullptr; + +NexusInterface::NexusInterface(Settings* s) : m_PluginContainer(nullptr) { + MO_ASSERT(!g_instance); + g_instance = this; + m_User.limits(defaultAPILimits()); m_MOVersion = createVersionInfo(); - m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString(3)); + m_AccessManager = new NXMAccessManager( + this, s, m_MOVersion.displayString(3)); + m_DiskCache = new QNetworkDiskCache(this); + connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); } -NXMAccessManager *NexusInterface::getAccessManager() +NexusInterface::~NexusInterface() { - return m_AccessManager; + cleanup(); + + MO_ASSERT(g_instance == this); + g_instance = nullptr; } -NexusInterface::~NexusInterface() +NXMAccessManager *NexusInterface::getAccessManager() { - cleanup(); + return m_AccessManager; } NexusInterface& NexusInterface::instance() { - static NexusInterface ni; - return ni; + MO_ASSERT(g_instance); + return *g_instance; } void NexusInterface::setCacheDirectory(const QString &directory) diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 72f30a38..cf365b2e 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -40,7 +40,7 @@ namespace MOBase { class IPluginGame; } class NexusInterface; class NXMAccessManager; - +class Settings; /** * @brief convenience class to make nxm requests easier @@ -153,7 +153,9 @@ public: static APILimits parseLimits(const QNetworkReply* reply); static APILimits parseLimits(const QList& headers); + NexusInterface(Settings* s); ~NexusInterface(); + static NexusInterface& instance(); /** @@ -533,8 +535,6 @@ private: static const int MAX_ACTIVE_DOWNLOADS = 6; private: - - NexusInterface(); void nextRequest(); void requestFinished(std::list::iterator iter); MOBase::IPluginGame *getGame(QString gameName) const; diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 2fe676ba..6c8b9054 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -48,8 +48,8 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(NexusKeyValidator& v) - : m_validator(v), m_updateTimer(nullptr), m_first(true) +ValidationProgressDialog::ValidationProgressDialog(Settings* s, NexusKeyValidator& v) + : m_settings(s), m_validator(v), m_updateTimer(nullptr), m_first(true) { ui.reset(new Ui::ValidationProgressDialog); ui->setupUi(this); @@ -98,7 +98,10 @@ void ValidationProgressDialog::stop() void ValidationProgressDialog::showEvent(QShowEvent* e) { if (m_first) { - Settings::instance().geometry().centerOnMainWindowMonitor(this); + if (m_settings) { + m_settings->geometry().centerOnMainWindowMonitor(this); + } + m_first = false; } } @@ -592,8 +595,8 @@ void ValidationAttempt::cleanup() } -NexusKeyValidator::NexusKeyValidator(NXMAccessManager& am) - : m_manager(am) +NexusKeyValidator::NexusKeyValidator(Settings* s, NXMAccessManager& am) + : m_settings(s), m_manager(am) { } @@ -602,6 +605,15 @@ NexusKeyValidator::~NexusKeyValidator() cancel(); } +std::vector NexusKeyValidator::getTimeouts() const +{ + if (m_settings) { + return m_settings->nexus().validationTimeouts(); + } else { + return {10s, 15s, 20s}; + } +} + void NexusKeyValidator::start(const QString& key, Behaviour b) { if (isActive()) { @@ -611,7 +623,7 @@ void NexusKeyValidator::start(const QString& key, Behaviour b) m_key = key; - const auto timeouts = Settings::instance().nexus().validationTimeouts(); + const auto timeouts = getTimeouts(); switch (b) { @@ -755,10 +767,11 @@ void NexusKeyValidator::setFinished( } -NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) +NXMAccessManager::NXMAccessManager(QObject *parent, Settings* s, const QString &moVersion) : QNetworkAccessManager(parent) + , m_Settings(s) , m_MOVersion(moVersion) - , m_validator(*this) + , m_validator(s, *this) , m_validationState(NotChecked) { m_validator.finished = [&](auto&& r, auto&& m, auto&& u) { @@ -769,8 +782,10 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) onValidatorAttemptFinished(a); }; - setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( - Settings::instance().paths().cache() + "/nexus_cookies.dat"))); + if (m_Settings) { + setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( + m_Settings->paths().cache() + "/nexus_cookies.dat"))); + } if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { // why is this necessary all of a sudden? @@ -963,7 +978,7 @@ void NXMAccessManager::clearApiKey() void NXMAccessManager::startProgress() { if (!m_ProgressDialog) { - m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + m_ProgressDialog.reset(new ValidationProgressDialog(m_Settings, m_validator)); } m_ProgressDialog->start(); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 6a45d880..60100467 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -33,6 +33,7 @@ along with Mod Organizer. If not, see . namespace MOBase { class IPluginGame; } namespace Ui { class ValidationProgressDialog; } class NXMAccessManager; +class Settings; class NexusSSOLogin { @@ -146,7 +147,7 @@ public: std::function finished; std::function attemptFinished; - NexusKeyValidator(NXMAccessManager& am); + NexusKeyValidator(Settings* s, NXMAccessManager& am); ~NexusKeyValidator(); void start(const QString& key, Behaviour b); @@ -157,11 +158,13 @@ public: const ValidationAttempt* currentAttempt() const; private: + Settings* m_settings; NXMAccessManager& m_manager; QString m_key; std::vector> m_attempts; void createAttempts(const std::vector& timeouts); + std::vector getTimeouts() const; bool nextTry(); void onAttemptSuccess(const ValidationAttempt& a, const APIUserAccount& u); @@ -178,7 +181,7 @@ class ValidationProgressDialog : public QDialog Q_OBJECT; public: - ValidationProgressDialog(NexusKeyValidator& v); + ValidationProgressDialog(Settings* s, NexusKeyValidator& v); void setParentWidget(QWidget* w); @@ -191,6 +194,7 @@ protected: private: std::unique_ptr ui; + Settings* m_settings; NexusKeyValidator& m_validator; QTimer* m_updateTimer; bool m_first; @@ -209,7 +213,7 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: - NXMAccessManager(QObject *parent, const QString &moVersion); + NXMAccessManager(QObject *parent, Settings* s, const QString &moVersion); void setTopLevelWidget(QWidget* w); @@ -264,6 +268,7 @@ private: }; QWidget* m_TopLevel; + Settings* m_Settings; mutable std::unique_ptr m_ProgressDialog; QString m_MOVersion; NexusKeyValidator m_validator; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 7fd8c33b..0d561581 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -339,7 +339,7 @@ bool OrganizerCore::nexusApi(bool retry) return false; } else { QString apiKey; - if (m_Settings.nexus().apiKey(apiKey)) { + if (GlobalSettings::nexusApiKey(apiKey)) { // credentials stored or user entered them manually log::debug("attempt to verify nexus api key"); accessManager->apiCheck(apiKey); @@ -1426,7 +1426,7 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) f(); } else { QString apiKey; - if (settings().nexus().apiKey(apiKey)) { + if (GlobalSettings::nexusApiKey(apiKey)) { doAfterLogin([f]{ f(); }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else { diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 45efc00c..8cc95a8b 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -287,3 +287,215 @@ bool OrganizerProxy::onPluginSettingChanged(std::functiononPluginSettingChanged(func); } + + + +DummyOrganizerProxy::DummyOrganizerProxy(const QString &pluginName) : + m_PluginName(pluginName), + m_mods(new DummyModList), m_plugins(new DummyPluginList) +{ +} + +DummyOrganizerProxy::~DummyOrganizerProxy() = default; + +IModRepositoryBridge *DummyOrganizerProxy::createNexusBridge() const +{ + return nullptr; +} + +QString DummyOrganizerProxy::profileName() const +{ + return {}; +} + +QString DummyOrganizerProxy::profilePath() const +{ + return {}; +} + +QString DummyOrganizerProxy::downloadsPath() const +{ + return {}; +} + +QString DummyOrganizerProxy::overwritePath() const +{ + return {}; +} + +QString DummyOrganizerProxy::basePath() const +{ + return {}; +} + +QString DummyOrganizerProxy::modsPath() const +{ + return {}; +} + +VersionInfo DummyOrganizerProxy::appVersion() const +{ + return {}; +} + +IModInterface *DummyOrganizerProxy::getMod(const QString &name) const +{ + return nullptr; +} + +IPluginGame *DummyOrganizerProxy::getGame(const QString &gameName) const +{ + return nullptr; +} + +IModInterface *DummyOrganizerProxy::createMod(MOBase::GuessedValue &name) +{ + return nullptr; +} + +bool DummyOrganizerProxy::removeMod(IModInterface *mod) +{ + return true; +} + +void DummyOrganizerProxy::modDataChanged(IModInterface *mod) +{ +} + +QVariant DummyOrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const +{ + if (key == "enabled") { + return true; + } + + return {}; +} + +void DummyOrganizerProxy::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ +} + +QVariant DummyOrganizerProxy::persistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + return {}; +} + +void DummyOrganizerProxy::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ +} + +QString DummyOrganizerProxy::pluginDataPath() const +{ + return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + "/data"; +} + +HANDLE DummyOrganizerProxy::startApplication( + const QString& exe, const QStringList& args, const QString &cwd, + const QString& profile, const QString &overwrite, bool ignoreOverwrite) +{ + return INVALID_HANDLE_VALUE; +} + +bool DummyOrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const +{ + return true; +} + +bool DummyOrganizerProxy::onAboutToRun(const std::function &func) +{ + return true; +} + +bool DummyOrganizerProxy::onFinishedRun(const std::function &func) +{ + return true; +} + +bool DummyOrganizerProxy::onModInstalled(const std::function &func) +{ + return true; +} + +bool DummyOrganizerProxy::onUserInterfaceInitialized(std::function const& func) +{ + return true; +} + +bool DummyOrganizerProxy::onProfileChanged(std::function const& func) +{ + return true; +} + +bool DummyOrganizerProxy::onPluginSettingChanged(std::function const& func) +{ + return true; +} + +void DummyOrganizerProxy::refreshModList(bool saveChanges) +{ +} + +IModInterface *DummyOrganizerProxy::installMod(const QString &fileName, const QString &nameSuggestion) +{ + return nullptr; +} + +QString DummyOrganizerProxy::resolvePath(const QString &fileName) const +{ + return {}; +} + +QStringList DummyOrganizerProxy::listDirectories(const QString &directoryName) const +{ + return {}; +} + +QStringList DummyOrganizerProxy::findFiles(const QString &path, const std::function &filter) const +{ + return {}; +} + +QStringList DummyOrganizerProxy::findFiles(const QString& path, const QStringList& globFilters) const +{ + return {}; +} + +QStringList DummyOrganizerProxy::getFileOrigins(const QString &fileName) const +{ + return {}; +} + +QList DummyOrganizerProxy::findFileInfos(const QString &path, const std::function &filter) const +{ + return {}; +} + +MOBase::IDownloadManager *DummyOrganizerProxy::downloadManager() const +{ + return nullptr; +} + +MOBase::IPluginList *DummyOrganizerProxy::pluginList() const +{ + return m_plugins.get(); +} + +MOBase::IModList *DummyOrganizerProxy::modList() const +{ + return m_mods.get(); +} + +MOBase::IProfile *DummyOrganizerProxy::profile() const +{ + return nullptr; +} + +MOBase::IPluginGame const *DummyOrganizerProxy::managedGame() const +{ + return nullptr; +} + +QStringList DummyOrganizerProxy::modsSortedByProfilePriority() const +{ + return {}; +} diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 6690d612..3bd70113 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -81,4 +81,63 @@ private: }; + +class DummyOrganizerProxy : public MOBase::IOrganizer +{ +public: + DummyOrganizerProxy(const QString &pluginName); + ~DummyOrganizerProxy(); + + virtual MOBase::IModRepositoryBridge *createNexusBridge() const; + virtual QString profileName() const; + virtual QString profilePath() const; + virtual QString downloadsPath() const; + virtual QString overwritePath() const; + virtual QString basePath() const; + virtual QString modsPath() const; + virtual MOBase::VersionInfo appVersion() const; + virtual MOBase::IModInterface *getMod(const QString &name) const; + virtual MOBase::IPluginGame *getGame(const QString &gameName) const; + virtual MOBase::IModInterface *createMod(MOBase::GuessedValue &name); + virtual bool removeMod(MOBase::IModInterface *mod); + virtual void modDataChanged(MOBase::IModInterface *mod); + virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const; + virtual void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + virtual QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const; + virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true); + virtual QString pluginDataPath() const; + virtual MOBase::IModInterface *installMod(const QString &fileName, const QString &nameSuggestion = QString()); + virtual QString resolvePath(const QString &fileName) const; + virtual QStringList listDirectories(const QString &directoryName) const; + virtual QStringList findFiles(const QString &path, const std::function &filter) const override; + virtual QStringList findFiles(const QString &path, const QStringList &globFilters) const override; + virtual QStringList getFileOrigins(const QString &fileName) const; + virtual QList findFileInfos(const QString &path, const std::function &filter) const; + + virtual MOBase::IDownloadManager *downloadManager() const; + virtual MOBase::IPluginList *pluginList() const; + virtual MOBase::IModList *modList() const; + virtual MOBase::IProfile *profile() const override; + virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", + const QString &profile = "", const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); + virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode = nullptr) const; + virtual void refreshModList(bool saveChanges); + + virtual bool onAboutToRun(const std::function &func); + virtual bool onFinishedRun(const std::function &func); + virtual bool onModInstalled(const std::function &func); + virtual bool onUserInterfaceInitialized(std::function const& func); + virtual bool onProfileChanged(std::function const& func); + virtual bool onPluginSettingChanged(std::function const& func); + + virtual MOBase::IPluginGame const *managedGame() const; + + virtual QStringList modsSortedByProfilePriority() const; + +private: + const QString &m_PluginName; + std::unique_ptr m_plugins; + std::unique_ptr m_mods; +}; + #endif // ORGANIZERPROXY_H diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 4771359d..6f2670dd 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -173,10 +173,21 @@ bool PluginContainer::verifyPlugin(IPlugin *plugin) { if (plugin == nullptr) { return false; - } else if (!plugin->init(new OrganizerProxy(m_Organizer, this, plugin))) { + } + + IOrganizer* proxy = nullptr; + + if (m_Organizer) { + proxy = new OrganizerProxy(m_Organizer, this, plugin); + } else { + proxy = new DummyOrganizerProxy(plugin); + } + + if (!plugin->init(proxy)) { log::warn("plugin failed to initialize"); return false; } + return true; } @@ -200,7 +211,9 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) return false; } plugin->setProperty("filename", fileName); - m_Organizer->settings().plugins().registerPlugin(pluginObj); + if (m_Organizer) { + m_Organizer->settings().plugins().registerPlugin(pluginObj); + } } { // diagnosis plugin @@ -244,7 +257,9 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) IPluginInstaller *installer = qobject_cast(plugin); if (verifyPlugin(installer)) { bf::at_key(m_Plugins).push_back(installer); - m_Organizer->installationManager()->registerInstaller(installer); + if (m_Organizer) { + m_Organizer->installationManager()->registerInstaller(installer); + } return true; } } @@ -317,7 +332,7 @@ void PluginContainer::unloadPlugins() } // disconnect all slots before unloading plugins so plugins don't have to take care of that - if (m_Organizer != nullptr) { + if (m_Organizer) { m_Organizer->disconnectPlugins(); } @@ -363,24 +378,28 @@ void PluginContainer::loadPlugins() registerPlugin(plugin, ""); } - QFile loadCheck(qApp->property("dataPath").toString() + "/plugin_loadcheck.tmp"); - if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { - // oh, there was a failed plugin load last time. Find out which plugin was loaded last - QString fileName; - while (!loadCheck.atEnd()) { - fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); - } - if (QMessageBox::question(nullptr, QObject::tr("Plugin error"), - QObject::tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" - "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " - "The plugin may be able to recover from the problem)").arg(fileName), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_Organizer->settings().plugins().addBlacklist(fileName); + QFile loadCheck; + + if (m_Organizer) { + loadCheck.setFileName(qApp->property("dataPath").toString() + "/plugin_loadcheck.tmp"); + if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { + // oh, there was a failed plugin load last time. Find out which plugin was loaded last + QString fileName; + while (!loadCheck.atEnd()) { + fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); + } + if (QMessageBox::question(nullptr, QObject::tr("Plugin error"), + QObject::tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" + "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " + "The plugin may be able to recover from the problem)").arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + m_Organizer->settings().plugins().addBlacklist(fileName); + } + loadCheck.close(); } - loadCheck.close(); - } - loadCheck.open(QIODevice::WriteOnly); + loadCheck.open(QIODevice::WriteOnly); + } QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath)); @@ -388,13 +407,20 @@ void PluginContainer::loadPlugins() while (iter.hasNext()) { iter.next(); - if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { - log::debug("plugin \"{}\" blacklisted", iter.fileName()); - continue; + + if (m_Organizer) { + if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { + log::debug("plugin \"{}\" blacklisted", iter.fileName()); + continue; + } } - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); + + if (loadCheck.isOpen()) { + loadCheck.write(iter.fileName().toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); + } + QString pluginName = iter.filePath(); if (QLibrary::isLibrary(pluginName)) { std::unique_ptr pluginLoader(new QPluginLoader(pluginName, this)); @@ -416,12 +442,16 @@ void PluginContainer::loadPlugins() } // remove the load check file on success - loadCheck.remove(); + if (loadCheck.isOpen()) { + loadCheck.remove(); + } - bf::at_key(m_Plugins).push_back(m_Organizer); bf::at_key(m_Plugins).push_back(this); - m_Organizer->connectPlugins(this); + if (m_Organizer) { + bf::at_key(m_Plugins).push_back(m_Organizer); + m_Organizer->connectPlugins(this); + } } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index a4c1ec6d..7134d246 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1671,3 +1671,63 @@ void PluginList::managedGameChanged(const IPluginGame *gamePlugin) { m_GamePlugin = gamePlugin; } + + + +QStringList DummyPluginList::pluginNames() const +{ + return {}; +} + +IPluginList::PluginStates DummyPluginList::state(const QString &name) const +{ + return 0; +} + +void DummyPluginList::setState(const QString &name, PluginStates state) +{ +} + +int DummyPluginList::priority(const QString &name) const +{ + return -1; +} + +int DummyPluginList::loadOrder(const QString &name) const +{ + return -1; +} + +void DummyPluginList::setLoadOrder(const QStringList &pluginList) +{ +} + +bool DummyPluginList::isMaster(const QString &name) const +{ + return false; +} + +QStringList DummyPluginList::masters(const QString &name) const +{ + return {}; +} + +QString DummyPluginList::origin(const QString &name) const +{ + return {}; +} + +bool DummyPluginList::onRefreshed(const std::function &callback) +{ + return true; +} + +bool DummyPluginList::onPluginMoved(const std::function &func) +{ + return true; +} + +bool DummyPluginList::onPluginStateChanged(const std::function &func) +{ + return true; +} diff --git a/src/pluginlist.h b/src/pluginlist.h index 0b49b86f..bfadaf4f 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -410,6 +410,24 @@ private: bool hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const; }; + +class DummyPluginList : public MOBase::IPluginList +{ +public: + QStringList pluginNames() const override; + PluginStates state(const QString &name) const override; + void setState(const QString &name, PluginStates state) override; + int priority(const QString &name) const override; + int loadOrder(const QString &name) const override; + void setLoadOrder(const QStringList &pluginList) override; + bool isMaster(const QString &name) const override; + QStringList masters(const QString &name) const override; + QString origin(const QString &name) const override; + bool onRefreshed(const std::function &callback) override; + bool onPluginMoved(const std::function &func) override; + bool onPluginStateChanged(const std::function &func) override; +}; + #pragma warning(pop) #endif // PLUGINLIST_H diff --git a/src/settings.cpp b/src/settings.cpp index b286510d..593d66bf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1812,37 +1812,6 @@ NexusSettings::NexusSettings(Settings& parent, QSettings& settings) { } -bool NexusSettings::apiKey(QString& apiKey) const -{ - QString tempKey = getWindowsCredential("APIKEY"); - if (tempKey.isEmpty()) - return false; - - apiKey = tempKey; - return true; -} - -bool NexusSettings::setApiKey(const QString& apiKey) -{ - if (!setWindowsCredential("APIKEY", apiKey)) { - const auto e = GetLastError(); - log::error("Storing API key failed: {}", formatSystemMessage(e)); - return false; - } - - return true; -} - -bool NexusSettings::clearApiKey() -{ - return setApiKey(""); -} - -bool NexusSettings::hasApiKey() const -{ - return !getWindowsCredential("APIKEY").isEmpty(); -} - bool NexusSettings::endorsementIntegration() const { return get(m_Settings, "Settings", "endorsement_integration", true); @@ -2224,6 +2193,37 @@ void GlobalSettings::setHideTutorialQuestion(bool b) settings().setValue("HideTutorialQuestion", b); } +bool GlobalSettings::nexusApiKey(QString& apiKey) +{ + QString tempKey = getWindowsCredential("APIKEY"); + if (tempKey.isEmpty()) + return false; + + apiKey = tempKey; + return true; +} + +bool GlobalSettings::setNexusApiKey(const QString& apiKey) +{ + if (!setWindowsCredential("APIKEY", apiKey)) { + const auto e = GetLastError(); + log::error("Storing API key failed: {}", formatSystemMessage(e)); + return false; + } + + return true; +} + +bool GlobalSettings::clearNexusApiKey() +{ + return setNexusApiKey(""); +} + +bool GlobalSettings::hasNexusApiKey() +{ + return !getWindowsCredential("APIKEY").isEmpty(); +} + void GlobalSettings::resetDialogs() { setHideCreateInstanceIntro(false); diff --git a/src/settings.h b/src/settings.h index a9501b9d..f71949d2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -498,24 +498,6 @@ class NexusSettings public: NexusSettings(Settings& parent, QSettings& settings); - // if the key exists from the credentials store, puts it in `apiKey` and - // returns true; otherwise, returns false and leaves `apiKey` untouched - // - bool apiKey(QString& apiKey) const; - - // sets the api key in the credentials store, removes it if empty; returns - // false on errors - // - bool setApiKey(const QString& apiKey); - - // removes the api key from the credentials store; returns false on errors - // - bool clearApiKey(); - - // returns whether an API key is currently stored - // - bool hasApiKey() const; - // returns whether endorsement integration is enabled // bool endorsementIntegration() const; @@ -844,6 +826,24 @@ public: static bool hideTutorialQuestion(); static void setHideTutorialQuestion(bool b); + // if the key exists from the credentials store, puts it in `apiKey` and + // returns true; otherwise, returns false and leaves `apiKey` untouched + // + static bool nexusApiKey(QString& apiKey); + + // sets the api key in the credentials store, removes it if empty; returns + // false on errors + // + static bool setNexusApiKey(const QString& apiKey); + + // removes the api key from the credentials store; returns false on errors + // + static bool clearNexusApiKey(); + + // returns whether an API key is currently stored + // + static bool hasNexusApiKey(); + // resets anything that the user can disable static void resetDialogs(); diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 54af9b59..1ed4c58f 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -72,13 +72,14 @@ private: NexusConnectionUI::NexusConnectionUI( - Settings& s, QWidget* parent, + Settings* s, QAbstractButton* connectButton, QAbstractButton* disconnectButton, QAbstractButton* manualButton, QListWidget* logList) : - m_parent(parent), m_settings(s), + m_parent(parent), + m_settings(s), m_connect(connectButton), m_disconnect(disconnectButton), m_manual(manualButton), @@ -96,7 +97,7 @@ NexusConnectionUI::NexusConnectionUI( QObject::connect(manualButton, &QPushButton::clicked, [&]{ manual(); }); } - if (m_settings.nexus().hasApiKey()) { + if (GlobalSettings::hasNexusApiKey()) { addLog(tr("Connected.")); } else { addLog(tr("Not connected.")); @@ -162,7 +163,7 @@ void NexusConnectionUI::validateKey(const QString& key) { if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( - *NexusInterface::instance().getAccessManager())); + m_settings, *NexusInterface::instance().getAccessManager())); m_nexusValidator->finished = [&](auto&& r, auto&& m, auto&& u) { onValidatorFinished(r, m, u); @@ -231,7 +232,7 @@ void NexusConnectionUI::addLog(const QString& s) bool NexusConnectionUI::setKey(const QString& key) { - const bool ret = m_settings.nexus().setApiKey(key); + const bool ret = GlobalSettings::setNexusApiKey(key); updateState(); emit keyChanged(); @@ -241,7 +242,7 @@ bool NexusConnectionUI::setKey(const QString& key) bool NexusConnectionUI::clearKey() { - const auto ret = m_settings.nexus().clearApiKey(); + const auto ret = GlobalSettings::clearNexusApiKey(); NexusInterface::instance().getAccessManager()->clearApiKey(); updateState(); @@ -274,7 +275,7 @@ void NexusConnectionUI::updateState() setButton(m_disconnect, false); setButton(m_manual, true, QObject::tr("Cancel")); } - else if (m_settings.nexus().hasApiKey()) { + else if (GlobalSettings::hasNexusApiKey()) { // api key is present setButton(m_connect, false, QObject::tr("Connect to Nexus")); setButton(m_disconnect, true); @@ -327,7 +328,8 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) } m_connectionUI.reset(new NexusConnectionUI( - settings(), &dialog(), + &dialog(), + &settings(), ui->nexusConnect, ui->nexusDisconnect, ui->nexusManualKey, diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index a2d6a4f8..c915accf 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -13,8 +13,8 @@ class NexusConnectionUI : public QObject public: NexusConnectionUI( - Settings& s, QWidget* parent, + Settings* s, QAbstractButton* connectButton, QAbstractButton* disconnectButton, QAbstractButton* manualButton, @@ -30,7 +30,7 @@ signals: private: QWidget* m_parent; - Settings& m_settings; + Settings* m_settings; QAbstractButton* m_connect; QAbstractButton* m_disconnect; QAbstractButton* m_manual; -- cgit v1.3.1 From 356d17b2ea70d9bfdc36d8199e12eb21cd3d4669 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 31 Oct 2020 16:08:05 -0400 Subject: fixes after rebasing --- src/mainwindow.cpp | 2 +- src/modinfo.cpp | 2 +- src/modinfo.h | 2 +- src/modinfodialogconflicts.cpp | 14 +++---- src/modlist.cpp | 48 +++++++++++++++++----- src/modlist.h | 8 +++- src/organizerproxy.cpp | 36 +++++++---------- src/organizerproxy.h | 91 ++++++++++++++++++++---------------------- src/pluginlist.cpp | 9 ++++- src/pluginlist.h | 3 +- src/thread_utils.h | 5 +-- 11 files changed, 126 insertions(+), 94 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9ad51510..6a648512 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4221,7 +4221,7 @@ void MainWindow::checkModsForUpdates() { bool checkingModsForUpdate = false; if (NexusInterface::instance().getAccessManager()->validated()) { - checkingModsForUpdate = ModInfo::checkAllForUpdate(this); + checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this); NexusInterface::instance().requestEndorsementInfo(this, QVariant(), QString()); NexusInterface::instance().requestTrackingInfo(this, QVariant(), QString()); } else { diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 04cb18ce..a0382fe8 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -295,7 +295,7 @@ ModInfo::ModInfo(PluginContainer *pluginContainer) } -bool ModInfo::checkAllForUpdate(QObject *receiver) +bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver) { bool updatesAvailable = true; diff --git a/src/modinfo.h b/src/modinfo.h index 480fe013..e5e46741 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -197,7 +197,7 @@ public: // Static functions: * * @return true if any mods are checked for update. */ - static bool checkAllForUpdate(QObject *receiver); + static bool checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); /** * diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 0103b58a..cf7f6340 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -1001,7 +1001,7 @@ std::optional AdvancedConflictsTab::createItem( if (currOrigin->getID() == fileOrigin) { // current origin is the active winner, all alternatives go in 'before' - + if (showAllAlts) { for (const auto& alt : alternatives) { @@ -1023,7 +1023,7 @@ std::optional AdvancedConflictsTab::createItem( } else { // current mod is one of the alternatives, find its position - + auto currOrgId = currOrigin->getID(); auto currModIter = std::find_if(alternatives.begin(), alternatives.end(), @@ -1037,14 +1037,14 @@ std::optional AdvancedConflictsTab::createItem( } isCurrOrigArchive = currModIter->isFromArchive(); - + if (showAllAlts) { // fills 'before' and 'after' with all the alternatives that come - // before and after the current mod, trusting the alternatives vector to be + // before and after the current mod, trusting the alternatives vector to be // already sorted correctly - + for (auto iter = alternatives.begin(); iter != alternatives.end(); iter++) { - + const auto& altOrigin = ds.getOriginByID(iter->originID()); if (iter < currModIter) { @@ -1094,7 +1094,7 @@ std::optional AdvancedConflictsTab::createItem( after += ds.getOriginByID(fileOrigin).getName(); } - + } } } diff --git a/src/modlist.cpp b/src/modlist.cpp index bf9aef83..c98464a9 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -977,7 +977,7 @@ int ModList::setActive(const QStringList& names, bool active) { indices.append(modIndex); } else { - log::debug("Trying to {} mod {} which does not exist.", + log::debug("Trying to {} mod {} which does not exist.", active ? "enable" : "disable", name); } } @@ -1043,7 +1043,7 @@ void ModList::notifyModRemoved(QString const& modName) const m_ModRemoved(modName); } -void ModList::notifyModStateChanged(QList modIndices) const +void ModList::notifyModStateChanged(QList modIndices) const { std::map mods; for (auto modIndex : modIndices) { @@ -1530,7 +1530,7 @@ void ModList::disableSelected(const QItemSelectionModel *selectionModel) } -QString DummyModList::displayName(const QString &internalName) const +QString DummyModList::displayName(const QString&) const { return {}; } @@ -1540,32 +1540,62 @@ QStringList DummyModList::allMods() const return {}; } -IModList::ModStates DummyModList::state(const QString &name) const +QStringList DummyModList::allModsByProfilePriority(MOBase::IProfile*) const +{ + return {}; +} + +IModInterface* DummyModList::getMod(const QString&) const +{ + return nullptr; +} + +bool DummyModList::removeMod(MOBase::IModInterface*) +{ + return true; +} + +IModList::ModStates DummyModList::state(const QString&) const { return 0; } -bool DummyModList::setActive(const QString &name, bool active) +bool DummyModList::setActive(const QString&, bool) { return true; } -int DummyModList::priority(const QString &name) const +int DummyModList::setActive(const QStringList&, bool) +{ + return 0; +} + +int DummyModList::priority(const QString&) const { return -1; } -bool DummyModList::setPriority(const QString &name, int newPriority) +bool DummyModList::setPriority(const QString&, int) +{ + return true; +} + +bool DummyModList::onModInstalled(const std::function&) +{ + return true; +} + +bool DummyModList::onModRemoved(const std::function&) { return true; } -bool DummyModList::onModStateChanged(const std::function &func) +bool DummyModList::onModStateChanged(const std::function&)>&) { return true; } -bool DummyModList::onModMoved(const std::function &func) +bool DummyModList::onModMoved(const std::function&) { return true; } diff --git a/src/modlist.h b/src/modlist.h index 3ab486c9..1a469ee7 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -403,11 +403,17 @@ class DummyModList : public MOBase::IModList public: QString displayName(const QString &internalName) const override; QStringList allMods() const override; + QStringList allModsByProfilePriority(MOBase::IProfile *profile = nullptr) const override; + MOBase::IModInterface* getMod(const QString& name) const override; + bool removeMod(MOBase::IModInterface *mod) override; ModStates state(const QString &name) const override; bool setActive(const QString &name, bool active) override; + int setActive(const QStringList& names, bool active) override; int priority(const QString &name) const override; bool setPriority(const QString &name, int newPriority) override; - bool onModStateChanged(const std::function &func) override; + bool onModInstalled(const std::function& func) override; + bool onModRemoved(const std::function& func) override; + bool onModStateChanged(const std::function&)> &func) override; bool onModMoved(const std::function &func) override; }; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 8cc95a8b..b2a4f791 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -290,9 +290,8 @@ bool OrganizerProxy::onPluginSettingChanged(std::function &nam return nullptr; } -bool DummyOrganizerProxy::removeMod(IModInterface *mod) -{ - return true; -} - void DummyOrganizerProxy::modDataChanged(IModInterface *mod) { } @@ -411,12 +400,22 @@ bool DummyOrganizerProxy::onFinishedRun(const std::function &func) +bool DummyOrganizerProxy::onUserInterfaceInitialized(std::function const& func) { return true; } -bool DummyOrganizerProxy::onUserInterfaceInitialized(std::function const& func) +bool DummyOrganizerProxy::onProfileCreated(std::function const& func) +{ + return true; +} + +bool DummyOrganizerProxy::onProfileRenamed(std::function const& func) +{ + return true; +} + +bool DummyOrganizerProxy::onProfileRemoved(std::function const& func) { return true; } @@ -431,7 +430,7 @@ bool DummyOrganizerProxy::onPluginSettingChanged(std::function &name); - virtual bool removeMod(MOBase::IModInterface *mod); - virtual void modDataChanged(MOBase::IModInterface *mod); - virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const; - virtual void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); - virtual QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const; - virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true); - virtual QString pluginDataPath() const; - virtual MOBase::IModInterface *installMod(const QString &fileName, const QString &nameSuggestion = QString()); - virtual QString resolvePath(const QString &fileName) const; - virtual QStringList listDirectories(const QString &directoryName) const; - virtual QStringList findFiles(const QString &path, const std::function &filter) const override; - virtual QStringList findFiles(const QString &path, const QStringList &globFilters) const override; - virtual QStringList getFileOrigins(const QString &fileName) const; - virtual QList findFileInfos(const QString &path, const std::function &filter) const; - - virtual MOBase::IDownloadManager *downloadManager() const; - virtual MOBase::IPluginList *pluginList() const; - virtual MOBase::IModList *modList() const; - virtual MOBase::IProfile *profile() const override; - virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", - const QString &profile = "", const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); - virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode = nullptr) const; - virtual void refreshModList(bool saveChanges); - - virtual bool onAboutToRun(const std::function &func); - virtual bool onFinishedRun(const std::function &func); - virtual bool onModInstalled(const std::function &func); - virtual bool onUserInterfaceInitialized(std::function const& func); - virtual bool onProfileChanged(std::function const& func); - virtual bool onPluginSettingChanged(std::function const& func); - - virtual MOBase::IPluginGame const *managedGame() const; - - virtual QStringList modsSortedByProfilePriority() const; + MOBase::IModRepositoryBridge *createNexusBridge() const override; + QString profileName() const override; + QString profilePath() const override; + QString downloadsPath() const override; + QString overwritePath() const override; + QString basePath() const override; + QString modsPath() const override; + MOBase::VersionInfo appVersion() const override; + MOBase::IPluginGame *getGame(const QString &gameName) const override; + MOBase::IModInterface *createMod(MOBase::GuessedValue &name) override; + void modDataChanged(MOBase::IModInterface *mod) override; + QVariant pluginSetting(const QString &pluginName, const QString &key) const override; + void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) override; + QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const override; + void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true) override; + QString pluginDataPath() const override; + MOBase::IModInterface *installMod(const QString &fileName, const QString &nameSuggestion = QString()) override; + QString resolvePath(const QString &fileName) const override; + QStringList listDirectories(const QString &directoryName) const override; + QStringList findFiles(const QString &path, const std::function &filter) const override; + QStringList findFiles(const QString &path, const QStringList &globFilters) const override; + QStringList getFileOrigins(const QString &fileName) const override; + QList findFileInfos(const QString &path, const std::function &filter) const override; + + MOBase::IDownloadManager *downloadManager() const override; + MOBase::IPluginList *pluginList() const override; + MOBase::IModList *modList() const override; + MOBase::IProfile *profile() const override; + HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", + const QString &profile = "", const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false) override; + bool waitForApplication(HANDLE handle, LPDWORD exitCode = nullptr) const override; + void refresh(bool saveChanges = true) override; + + bool onAboutToRun(const std::function &func) override; + bool onFinishedRun(const std::function &func) override; + bool onUserInterfaceInitialized(std::function const& func) override; + bool onProfileCreated(std::function const& func) override; + bool onProfileRenamed(std::function const& func) override; + bool onProfileRemoved(std::function const& func) override; + bool onProfileChanged(std::function const& func) override; + bool onPluginSettingChanged(std::function const& func) override; + + MOBase::IPluginGame const *managedGame() const override; private: - const QString &m_PluginName; std::unique_ptr m_plugins; std::unique_ptr m_mods; }; diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 7134d246..bda360ba 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -801,7 +801,7 @@ bool PluginList::setPriority(const QString& name, int newPriority) { int rowIndex = findPluginByPriority(oldPriority); - // We need to increment newPriority if its above the old one, otherwise the + // We need to increment newPriority if its above the old one, otherwise the // plugin is place right below the new priority. if (oldPriority < newPriority) { newPriority += 1; @@ -1702,6 +1702,11 @@ void DummyPluginList::setLoadOrder(const QStringList &pluginList) { } +bool DummyPluginList::setPriority(const QString&, int) +{ + return true; +} + bool DummyPluginList::isMaster(const QString &name) const { return false; @@ -1727,7 +1732,7 @@ bool DummyPluginList::onPluginMoved(const std::function &func) +bool DummyPluginList::onPluginStateChanged(const std::function&)> &func) { return true; } diff --git a/src/pluginlist.h b/src/pluginlist.h index bfadaf4f..27c15056 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -420,12 +420,13 @@ public: int priority(const QString &name) const override; int loadOrder(const QString &name) const override; void setLoadOrder(const QStringList &pluginList) override; + bool setPriority(const QString& name, int newPriority) override; bool isMaster(const QString &name) const override; QStringList masters(const QString &name) const override; QString origin(const QString &name) const override; bool onRefreshed(const std::function &callback) override; bool onPluginMoved(const std::function &func) override; - bool onPluginStateChanged(const std::function &func) override; + bool onPluginStateChanged(const std::function&)> &func) override; }; #pragma warning(pop) diff --git a/src/thread_utils.h b/src/thread_utils.h index 607d73a9..f64dd601 100644 --- a/src/thread_utils.h +++ b/src/thread_utils.h @@ -7,8 +7,7 @@ #include // in main.cpp -void setUnhandledExceptionHandler(); -LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs); +void setExceptionHandlers(); namespace MOShared { @@ -20,7 +19,7 @@ template std::thread startSafeThread(F&& f) { return std::thread([f=std::forward(f)] { - setUnhandledExceptionHandler(); + setExceptionHandlers(); f(); }); } -- cgit v1.3.1 From b10436d60f7db3eedd838cbfad93cb41ddf1fd86 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 31 Oct 2020 17:44:25 -0400 Subject: game icons in the instance list --- src/instancemanager.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++ src/instancemanager.h | 3 +++ src/instancemanagerdialog.cpp | 19 +++++++++++++- src/settings.cpp | 5 ++++ src/settings.h | 4 +++ 5 files changed, 89 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 111f948b..4ad099ed 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -637,6 +637,65 @@ MOBase::IPluginGame* InstanceManager::determineCurrentGame( return nullptr; } +const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( + const QDir& instanceDir, const PluginContainer& plugins) const +{ + const QString ini = + QDir(instanceDir).filePath(QString::fromStdWString(AppConfig::iniFileName())); + + + Settings s(ini); + + if (s.iniStatus() != QSettings::NoError) + { + log::error("failed to load settings from {}", ini); + return nullptr; + } + + const auto instanceGameName = s.game().name(); + + if (instanceGameName && !instanceGameName->isEmpty()) + { + for (const IPluginGame* game : plugins.plugins()) { + if (instanceGameName->compare(game->gameName(), Qt::CaseInsensitive) == 0) { + return game; + } + } + + log::error( + "no plugin reports game name '{}' found in ini {}", + *instanceGameName, ini); + } + else + { + log::error("no game name found in ini {}", ini); + } + + + log::error("falling back on looksValid check"); + + const auto gameDir = s.game().directory(); + + if (gameDir && !gameDir->isEmpty()) + { + for (const IPluginGame* game : plugins.plugins()) { + if (game->looksValid(*gameDir)) { + return game; + } + } + + log::error( + "no plugins appear to support game directory '{}' from ini {}", + *gameDir, ini); + } + else + { + log::error("no game directory found in ini {}", ini); + } + + return nullptr; +} + QString InstanceManager::makeUniqueName(const QString& instanceName) const { const QString sanitized = sanitizeInstanceName(instanceName); diff --git a/src/instancemanager.h b/src/instancemanager.h index 33e115a7..c2d1e0f4 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -47,6 +47,9 @@ public: MOBase::IPluginGame* determineCurrentGame( const QString& moPath, Settings& settings, const PluginContainer &plugins); + const MOBase::IPluginGame* gamePluginForDirectory( + const QDir& dir, const PluginContainer& plugins) const; + void clearCurrentInstance(); QString currentInstance() const; void setCurrentInstance(const QString &name); diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 7b8522ee..545b5c71 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -112,6 +112,19 @@ public: return makeIniFile(m_dir); } + QIcon icon(const PluginContainer& plugins) const + { + const auto* game = InstanceManager::instance().gamePluginForDirectory( + m_dir, plugins); + + if (game) + return game->gameIcon(); + + QPixmap empty(32, 32); + empty.fill(QColor(0, 0, 0, 0)); + return QIcon(empty); + } + bool isPortable() const { return m_portable; @@ -376,7 +389,11 @@ void InstanceManagerDialog::updateList() for (std::size_t i=0; iappendRow(new QStandardItem(ii.name())); + + auto* item = new QStandardItem(ii.name()); + item->setIcon(ii.icon(m_pc)); + + m_model->appendRow(item); if (&ii == prevSel) { sel = i; diff --git a/src/settings.cpp b/src/settings.cpp index 593d66bf..99b41e1f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -465,6 +465,11 @@ QSettings::Status Settings::sync() const return m_Settings.status(); } +QSettings::Status Settings::iniStatus() const +{ + return m_Settings.status(); +} + void Settings::dump() const { static const QStringList ignore({ diff --git a/src/settings.h b/src/settings.h index f71949d2..c8325ba2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -778,6 +778,10 @@ public: // QSettings::Status sync() const; + // last status of the ini file + // + QSettings::Status iniStatus() const; + void dump() const; public slots: -- cgit v1.3.1 From 1d97d914f1de0404b5b4db1bfdeff35ed94ea422 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 3 Nov 2020 10:57:26 -0500 Subject: InstanceManager now returns new Instance struct instead of instance name moved most of the figuring out of instance parameters from InstanceManager to Instance, separated all the ui from it and put it in main.cpp added ways to show single pages in the create instance dialog so they can be used when info is missing --- src/createinstancedialog.cpp | 69 +++- src/createinstancedialog.h | 37 +- src/createinstancedialogpages.cpp | 67 ++-- src/createinstancedialogpages.h | 32 +- src/envshortcut.cpp | 2 +- src/instancemanager.cpp | 707 ++++++++++++++------------------------ src/instancemanager.h | 66 ++-- src/instancemanagerdialog.cpp | 50 ++- src/instancemanagerdialog.h | 3 + src/main.cpp | 265 ++++++++++---- src/organizercore.cpp | 8 +- src/processrunner.cpp | 13 +- src/shared/appconfig.inc | 1 + src/statusbar.cpp | 7 +- 14 files changed, 697 insertions(+), 630 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index 0f62fcf6..d67e3451 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -13,7 +13,7 @@ using namespace MOBase; CreateInstanceDialog::CreateInstanceDialog( const PluginContainer& pc, Settings* s, QWidget *parent) : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s), - m_switching(false) + m_switching(false), m_singlePage(false) { using namespace cid; @@ -23,7 +23,7 @@ CreateInstanceDialog::CreateInstanceDialog( m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); - m_pages.push_back(std::make_unique(*this)); + m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); m_pages.push_back(std::make_unique(*this)); @@ -32,6 +32,12 @@ CreateInstanceDialog::CreateInstanceDialog( ui->pages->setCurrentIndex(0); ui->launch->setChecked(true); + if (!m_settings) + { + // first run of MO, there are no instances yet, force launch + ui->launch->setEnabled(false); + } + if (m_pages[0]->skip()) { next(); } @@ -77,7 +83,12 @@ void CreateInstanceDialog::next() const auto last = isOnLastPage(); if (last) { - finish(); + if (m_singlePage) { + // just close the dialog + accept(); + } else { + finish(); + } } else { changePage(+1); } @@ -88,6 +99,15 @@ void CreateInstanceDialog::back() changePage(-1); } +void CreateInstanceDialog::setSinglePageImpl() +{ + m_singlePage = true; + + if (m_pages[ui->pages->currentIndex()]->skip()) { + next(); + } +} + void CreateInstanceDialog::changePage(int d) { std::size_t i = static_cast(ui->pages->currentIndex()); @@ -247,8 +267,8 @@ void CreateInstanceDialog::finish() s.game().setName(ci.game->gameName()); s.game().setDirectory(ci.gameLocation); - if (!ci.gameEdition.isEmpty()) { - s.game().setEdition(ci.gameEdition); + if (!ci.gameVariant.isEmpty()) { + s.game().setEdition(ci.gameVariant); } if (ci.type == Global) { @@ -307,7 +327,8 @@ void CreateInstanceDialog::finish() } if (ui->launch->isChecked()) { - InstanceManager::instance().switchToInstance(ci.instanceName); + InstanceManager::instance().setCurrentInstance(ci.instanceName); + ExitModOrganizer(Exit::Restart); m_switching = true; } else { accept(); @@ -345,8 +366,8 @@ void CreateInstanceDialog::updateNavigation() const auto i = ui->pages->currentIndex(); const auto last = isOnLastPage(); - ui->next->setEnabled(m_pages[i]->ready()); - ui->back->setEnabled(i > 0); + ui->next->setEnabled(canNext()); + ui->back->setEnabled(canBack()); if (last) { ui->next->setText(tr("Finish")); @@ -355,6 +376,32 @@ void CreateInstanceDialog::updateNavigation() } } +bool CreateInstanceDialog::canNext() const +{ + const auto i = ui->pages->currentIndex(); + return m_pages[i]->ready(); +} + +bool CreateInstanceDialog::canBack() const +{ + auto i = ui->pages->currentIndex(); + + for (;;) + { + if (i == 0) { + break; + } + + --i; + + if (!m_pages[i]->skip()) { + return true; + } + } + + return false; +} + CreateInstanceDialog::Types CreateInstanceDialog::instanceType() const { return getSelected(&cid::Page::selectedInstanceType); @@ -370,9 +417,9 @@ QString CreateInstanceDialog::gameLocation() const return getSelected(&cid::Page::selectedGameLocation); } -QString CreateInstanceDialog::gameEdition() const +QString CreateInstanceDialog::gameVariant() const { - return getSelected(&cid::Page::selectedGameEdition); + return getSelected(&cid::Page::selectedGameVariant); } QString CreateInstanceDialog::instanceName() const @@ -433,7 +480,7 @@ CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const ci.type = getSelected(&cid::Page::selectedInstanceType); ci.game = getSelected(&cid::Page::selectedGame); ci.gameLocation = getSelected(&cid::Page::selectedGameLocation); - ci.gameEdition = getSelected(&cid::Page::selectedGameEdition); + ci.gameVariant = getSelected(&cid::Page::selectedGameVariant); ci.instanceName = getSelected(&cid::Page::selectedInstanceName); ci.paths = getSelected(&cid::Page::selectedPaths); ci.dataPath = dataPath(); diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 0841ef29..6947f2e2 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -39,7 +39,7 @@ public: Types type; MOBase::IPluginGame* game; QString gameLocation; - QString gameEdition; + QString gameVariant; QString instanceName; QString dataPath; QString iniPath; @@ -57,6 +57,32 @@ public: const PluginContainer& pluginContainer(); Settings* settings(); + template + void setSinglePage() + { + for (auto&& p : m_pages) { + if (auto* tp=dynamic_cast(p.get())) { + tp->setSkip(false); + } else { + p->setSkip(true); + } + } + + setSinglePageImpl(); + } + + template + Page* getPage() + { + for (auto&& p : m_pages) { + if (auto* tp=dynamic_cast(p.get())) { + return tp; + } + } + + return nullptr; + } + void next(); void back(); void selectPage(std::size_t i); @@ -69,7 +95,7 @@ public: Types instanceType() const; MOBase::IPluginGame* game() const; QString gameLocation() const; - QString gameEdition() const; + QString gameVariant() const; QString instanceName() const; QString dataPath() const; Paths paths() const; @@ -84,6 +110,10 @@ private: std::vector> m_pages; QString m_originalNext; bool m_switching; + bool m_singlePage; + + + void setSinglePageImpl(); template T getSelected(T (cid::Page::*mf)() const) const @@ -100,6 +130,9 @@ private: 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 d809079d..00d49b99 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -43,7 +43,7 @@ void PlaceholderLabel::setVisible(bool b) Page::Page(CreateInstanceDialog& dlg) - : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()) + : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_skip(false) { } @@ -54,7 +54,11 @@ bool Page::ready() const bool Page::skip() const { - // no-op + return m_skip || doSkip(); +} + +bool Page::doSkip() const +{ return false; } @@ -63,6 +67,11 @@ void Page::activated() // no-op } +void Page::setSkip(bool b) +{ + m_skip = b; +} + void Page::updateNavigation() { m_dlg.updateNavigation(); @@ -92,7 +101,7 @@ QString Page::selectedGameLocation() const return {}; } -QString Page::selectedGameEdition() const +QString Page::selectedGameVariant() const { // no-op return {}; @@ -116,7 +125,7 @@ IntroPage::IntroPage(CreateInstanceDialog& dlg) { } -bool IntroPage::skip() const +bool IntroPage::doSkip() const { return GlobalSettings::hideCreateInstanceIntro(); } @@ -223,19 +232,24 @@ QString GamePage::selectedGameLocation() const return QDir::toNativeSeparators(m_selection->dir); } -void GamePage::select(IPluginGame* game) +void GamePage::select(IPluginGame* game, const QString& dir) { 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; + if (dir.isEmpty()) { + const auto path = QFileDialog::getExistingDirectory( + &m_dlg, QObject::tr("Find game installation")); + + if (path.isEmpty()) { + checked = nullptr; + } else { + checked = checkInstallation(path, checked); + } } else { - checked = checkInstallation(path, checked); + checked->dir = dir; + checked->installed = true; } } } @@ -417,7 +431,6 @@ void GamePage::fillList() const bool showAll = ui->showAllGames->isChecked(); clearButtons(); - addButton(createCustomButton()); for (auto& g : m_games) { g->button = nullptr; @@ -434,6 +447,8 @@ void GamePage::fillList() createGameButton(g.get()); addButton(g->button); } + + addButton(createCustomButton()); } void GamePage::clearButtons() @@ -584,17 +599,17 @@ IPluginGame* GamePage::confirmOtherGame( } -EditionsPage::EditionsPage(CreateInstanceDialog& dlg) +VariantsPage::VariantsPage(CreateInstanceDialog& dlg) : Page(dlg), m_previousGame(nullptr) { } -bool EditionsPage::ready() const +bool VariantsPage::ready() const { return !m_selection.isEmpty(); } -bool EditionsPage::skip() const +bool VariantsPage::doSkip() const { auto* g = m_dlg.game(); if (!g) { @@ -606,7 +621,7 @@ bool EditionsPage::skip() const return (variants.size() < 2); } -void EditionsPage::activated() +void VariantsPage::activated() { auto* g = m_dlg.game(); @@ -617,7 +632,7 @@ void EditionsPage::activated() } } -void EditionsPage::select(const QString& variant) +void VariantsPage::select(const QString& variant) { for (auto* b : m_buttons) { if (b->text() == variant) { @@ -629,9 +644,13 @@ void EditionsPage::select(const QString& variant) } updateNavigation(); + + if (!m_selection.isEmpty()) { + next(); + } } -QString EditionsPage::selectedGameEdition() const +QString VariantsPage::selectedGameVariant() const { auto* g = m_dlg.game(); if (!g) { @@ -647,7 +666,7 @@ QString EditionsPage::selectedGameEdition() const } } -void EditionsPage::fillList() +void VariantsPage::fillList() { ui->editions->clear(); m_buttons.clear(); @@ -665,7 +684,7 @@ void EditionsPage::fillList() QObject::connect(b, &QAbstractButton::clicked, [v, this] { select(v); - }); + }); ui->editions->addButton(b, QDialogButtonBox::AcceptRole); m_buttons.push_back(b); @@ -687,7 +706,7 @@ bool NamePage::ready() const return m_okay; } -bool NamePage::skip() const +bool NamePage::doSkip() const { return (m_dlg.instanceType() == CreateInstanceDialog::Portable); } @@ -983,7 +1002,7 @@ bool NexusPage::ready() const return true; } -bool NexusPage::skip() const +bool NexusPage::doSkip() const { return m_skip; } @@ -1047,8 +1066,8 @@ QString ConfirmationPage::makeReview() const // game QString name = m_dlg.game()->gameName(); - if (!m_dlg.gameEdition().isEmpty()) { - name += " (" + m_dlg.gameEdition() + ")"; + if (!m_dlg.gameVariant().isEmpty()) { + name += " (" + m_dlg.gameVariant() + ")"; } lines.push_back(QObject::tr("Game: %1").arg(name)); diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 412f2d84..e239c196 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -37,16 +37,18 @@ public: Page(CreateInstanceDialog& dlg); virtual bool ready() const; - virtual bool skip() const; virtual void activated(); + void setSkip(bool b); + bool skip() const; + void updateNavigation(); void next(); virtual CreateInstanceDialog::Types selectedInstanceType() const; virtual MOBase::IPluginGame* selectedGame() const; virtual QString selectedGameLocation() const; - virtual QString selectedGameEdition() const; + virtual QString selectedGameVariant() const; virtual QString selectedInstanceName() const; virtual CreateInstanceDialog::Paths selectedPaths() const; @@ -54,6 +56,9 @@ protected: Ui::CreateInstanceDialog* ui; CreateInstanceDialog& m_dlg; const PluginContainer& m_pc; + bool m_skip; + + virtual bool doSkip() const; }; @@ -62,7 +67,8 @@ class IntroPage : public Page public: IntroPage(CreateInstanceDialog& dlg); - bool skip() const override; +protected: + bool doSkip() const override; }; @@ -91,7 +97,7 @@ public: MOBase::IPluginGame* selectedGame() const override; QString selectedGameLocation() const override; - void select(MOBase::IPluginGame* game); + void select(MOBase::IPluginGame* game, const QString& dir={}); void selectCustom(); void warnUnrecognized(const QString& path); @@ -133,18 +139,20 @@ private: }; -class EditionsPage : public Page +class VariantsPage : public Page { public: - EditionsPage(CreateInstanceDialog& dlg); + VariantsPage(CreateInstanceDialog& dlg); bool ready() const override; - bool skip() const override; void activated() override; - QString selectedGameEdition() const override; + QString selectedGameVariant() const override; void select(const QString& variant); +protected: + bool doSkip() const override; + private: MOBase::IPluginGame* m_previousGame; std::vector m_buttons; @@ -160,10 +168,12 @@ public: NamePage(CreateInstanceDialog& dlg); bool ready() const override; - bool skip() const override; void activated() override; QString selectedInstanceName() const override; +protected: + bool doSkip() const override; + private: mutable PlaceholderLabel m_label, m_exists, m_invalid; bool m_modified; @@ -212,9 +222,11 @@ public: ~NexusPage(); bool ready() const override; - bool skip() const override; void activated() override; +protected: + bool doSkip() const override; + private: std::unique_ptr m_connectionUI; bool m_skip; diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 99495c39..5222665b 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -149,7 +149,7 @@ Shortcut::Shortcut(const Executable& exe) m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); m_arguments = QString("\"moshortcut://%1:%2\"") - .arg(InstanceManager::instance().currentInstance()) + .arg(InstanceManager::instance().currentInstance()->name()) .arg(exe.title()); m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 4ad099ed..c79c5254 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -38,257 +38,320 @@ along with Mod Organizer. If not, see . using namespace MOBase; -InstanceManager::InstanceManager() +Instance::Instance(QDir dir, bool portable, QString profileName) : + m_dir(std::move(dir)), m_portable(portable), m_plugin(nullptr), + m_profile(std::move(profileName)) { - GlobalSettings::updateRegistryKey(); } -InstanceManager &InstanceManager::instance() +QString Instance::name() const { - static InstanceManager s_Instance; - return s_Instance; + if (isPortable()) + return QObject::tr("Portable"); + else + return m_dir.dirName(); } -void InstanceManager::overrideInstance(const QString& instanceName) +QString Instance::gameName() const { - m_overrideInstanceName = instanceName; - m_overrideInstance = true; + return m_gameName; } -void InstanceManager::overrideProfile(const QString& profileName) +QString Instance::gameDirectory() const { - m_overrideProfileName = profileName; - m_overrideProfile = true; + return m_gameDir; } -QString InstanceManager::currentInstance() const +QDir Instance::directory() const { - if (m_overrideInstance) - return m_overrideInstanceName; - else - return GlobalSettings::currentInstance(); + return m_dir; } -void InstanceManager::clearCurrentInstance() +MOBase::IPluginGame* Instance::gamePlugin() const { - setCurrentInstance(""); - m_Reset = true; - m_overrideInstance = false; + return m_plugin; } -void InstanceManager::switchToInstance(const QString& instanceName) +QString Instance::profileName() const { - setCurrentInstance(instanceName); - ExitModOrganizer(Exit::Restart); + return m_profile; } -void InstanceManager::setCurrentInstance(const QString &name) +QString Instance::iniPath() const { - GlobalSettings::setCurrentInstance(name); + return InstanceManager::iniPath(m_dir); } -bool InstanceManager::deleteLocalInstance(const QString& instanceId) const +bool Instance::isPortable() const { - QString dir = instancePath(instanceId); + return m_portable; +} - const auto Recycle = QMessageBox::Save; - const auto Delete = QMessageBox::Yes; - const auto Cancel = QMessageBox::Cancel; +Instance::SetupResults Instance::setup(PluginContainer& plugins) +{ + Settings s(iniPath()); - const auto r = MOBase::TaskDialog() - .title(QObject::tr("Deleting instance folder")) - .main(QObject::tr("This will delete the instance folder.")) - .content(dir) - .icon(QMessageBox::Warning) - .button({QObject::tr("Move the folder to the recycle bin"), Recycle}) - .button({QObject::tr("Delete the folder permanently"), Delete}) - .button({QObject::tr("Cancel"), Cancel}) - .exec(); + if (s.iniStatus() != QSettings::NoError) { + log::error("can't read ini {}", iniPath()); + return SetupResults::BadIni; + } - std::wstring error; + if (m_gameName.isEmpty()) { + if (auto v=s.game().name()) + m_gameName = *v; + } - switch (r) - { - case Recycle: - { - if (MOBase::shellDelete(QStringList(dir), true)) { - return true; - } + if (m_gameDir.isEmpty()) { + if (auto v=s.game().directory()) + m_gameDir = *v; + } - const auto e = GetLastError(); - error = formatSystemMessage(e); - log::warn("failed to move to trash '{}', {}", dir, error); + const auto r = getGamePlugin(plugins); + if (r != SetupResults::Ok) { + return r; + } - break; + if (m_gameVariant.isEmpty()) { + if (auto v=s.game().edition()) { + m_gameVariant = *v; } + } - case Delete: - { - if (MOBase::shellDelete(QStringList(dir), false)) { - return true; - } + if (m_gameVariant.isEmpty() && m_plugin->gameVariants().size() > 1) { + return SetupResults::MissingVariant; + } else { + m_plugin->setGameVariant(m_gameVariant); + } - const auto e = GetLastError(); - error = formatSystemMessage(e); - log::warn("failed to delete '{}', {}", dir, error); + getProfile(s); - break; - } + s.game().setName(m_gameName); + s.game().setDirectory(m_gameDir); + s.game().setSelectedProfileName(m_profile); - default: - { - return true; - } - } + if (!m_gameVariant.isEmpty()) + s.game().setEdition(m_gameVariant); - QMessageBox::critical( - nullptr, QObject::tr("Error"), QObject::tr( - "Could not delete instance folder \"%1\".\n\n%2") - .arg(dir).arg(error), - QMessageBox::Ok); + m_plugin->setGamePath(m_gameDir); - return false; + return SetupResults::Ok; } -QString InstanceManager::manageInstances(const QStringList &instanceList) const +void Instance::setGame(const QString& name, const QString& dir) { - SelectionDialog selection(QString("

    %1


    %2") - .arg(QObject::tr("Select an instance to delete")) - .arg(QObject::tr( - "Deleting an instance will delete all the mods, downloads, profiles " - "(including profile-specific saves) and anything in the overwrite " - "folder.

    " - "Custom paths outside of the instance folder will not be deleted."))); + m_gameName = name; + m_gameDir = dir; +} - for (const QString &instance : instanceList) { - selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance); - } +void Instance::setVariant(const QString& name) +{ + m_gameVariant = name; +} - if (selection.exec() == QDialog::Rejected) { - return (chooseInstance(instanceNames())); - } - else { - QString choice = selection.getChoiceData().toString(); - deleteLocalInstance(choice); +Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) +{ + if (!m_gameName.isEmpty() && !m_gameDir.isEmpty()) + { + // normal case: both the name and dir are in the ini + + // find the plugin by name + for (IPluginGame* game : plugins.plugins()) { + if (m_gameName.compare(game->gameName(), Qt::CaseInsensitive) == 0) { + // plugin found, check if the game directory is valid + + if (!game->looksValid(m_gameDir)) { + // the directory from the ini is not valid anymore + log::warn( + "game plugin {} says dir {} from ini {} is not valid", + game->gameName(), m_gameDir, iniPath()); + + // note that some plugins return true for isInstalled() if a path + // is found in the registry, but without actually checking if it's + // valid + + if (game->isInstalled() && game->looksValid(game->gameDirectory())) { + // bad game directory but the plugin reports there's a valid one + // somewhere; take it instead + log::warn( + "game plugin {} found a game at {}, taking it", + game->gameName(), game->gameDirectory().absolutePath()); + + m_gameDir = game->gameDirectory().absolutePath(); + } else { + // game seems to be gone completely + log::warn("game plugin {} found no game installation at all", game->gameName()); + return SetupResults::GameGone; + } + } + + m_plugin = game; + return SetupResults::Ok; + } + } + + log::warn("game plugin {} not found", m_gameName); + return SetupResults::PluginGone; } + else if (m_gameName.isEmpty() && !m_gameDir.isEmpty()) + { + // the name is missing, but there's a directory; find a plugin that can + // handle it - return(manageInstances(instanceNames())); -} + log::warn( + "game name is missing from ini {} but dir {} is available", + iniPath(), m_gameDir); -QString InstanceManager::queryInstanceName(const QStringList &instanceList) const -{ - QString instanceId; - QString dialogText; - while (instanceId.isEmpty()) { - QInputDialog dialog; + for (IPluginGame* game : plugins.plugins()) { + if (game->looksValid(m_gameDir)) { + // take it + log::warn("found plugin {} that can use dir {}", game->gameName(), m_gameDir); - dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance")); - dialog.setLabelText(QObject::tr("Enter a new name or select one from the suggested list: \n" - "(This is just a name for the Instance and can be whatever you wish,\n" - " the actual game selection will happen on the next screen regardless of chosen name)")); - // would be neat if we could take the names from the game plugins but - // the required initialization order requires the ini file to be - // available *before* we load plugins - dialog.setComboBoxItems({ "NewName", "Fallout 4", "SkyrimSE", "Skyrim", "SkyrimVR", "Fallout 3", - "Fallout NV", "TTW", "FO4VR", "Oblivion", "Morrowind", "Enderal" }); - dialog.setComboBoxEditable(true); + m_plugin = game; + m_gameName = game->gameName(); - if (dialog.exec() == QDialog::Rejected) { - throw MOBase::MyException(QObject::tr("Canceled")); + return SetupResults::Ok; + } } - dialogText = dialog.textValue(); - instanceId = sanitizeInstanceName(dialogText); - if (instanceId != dialogText) { - if (QMessageBox::question( nullptr, - QObject::tr("Invalid instance name"), - QObject::tr("The instance name \"%1\" is invalid. Use the name \"%2\" instead?").arg(dialogText,instanceId), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - instanceId=""; - continue; + + log::error("no plugins can use dir {}", m_gameDir); + return SetupResults::GameGone; + } + else if (!m_gameName.isEmpty() && m_gameDir.isEmpty()) + { + // dir is missing, find a plugin with the correct name and use the install + // dir it detected + + log::warn( + "game dir is missing from ini {} but name {} is available", + iniPath(), m_gameName); + + for (IPluginGame* game : plugins.plugins()) { + if (m_gameName.compare(game->gameName(), Qt::CaseInsensitive) == 0) { + // plugin found, use its detected installation dir + + if (game->isInstalled()) { + log::warn( + "found plugin {} that matches name in ini {}, using auto detected " + "game dir {}", + game->gameName(), iniPath(), game->gameDirectory().absolutePath()); + + m_plugin = game; + m_gameDir = game->gameDirectory().absolutePath(); + + return SetupResults::Ok; + } else { + log::warn( + "found plugin {} that matches name in ini {}, but no game install " + "detected by plugin", + game->gameName(), iniPath()); + + return SetupResults::GameGone; } + } } - bool alreadyExists=false; - for (const QString &instance : instanceList) { - if(instanceId==instance) - alreadyExists=true; - } - if(alreadyExists) - { - QMessageBox msgBox; - msgBox.setText( QObject::tr("The instance \"%1\" already exists.").arg(instanceId) ); - msgBox.setInformativeText(QObject::tr("Please choose a different instance name, like: \"%1 1\" .").arg(instanceId)); - msgBox.exec(); - instanceId=""; - } + // plugin seems to be gone + log::error("no plugin matches name {}", m_gameName); + return SetupResults::PluginGone; + } + else + { + // can't do anything with these two missing + log::error("both game name and dir are missing from ini {}", iniPath()); + return SetupResults::IniMissingGame; } - return instanceId; } -QString InstanceManager::chooseInstance(const QStringList &instanceList) const +void Instance::getProfile(const Settings& s) { - if (portableInstallIsLocked()) { - return QString(); + if (!m_profile.isEmpty()) { + // there's already a profile set up, probably an override + return; } - enum class Special : uint8_t { - NewInstance, - Portable, - Manage - }; - - SelectionDialog selection( - QString("

    %1


    %2") - .arg(QObject::tr("Choose Instance")) - .arg(QObject::tr( - "Each Instance is a full set of MO data files (mods, " - "downloads, profiles, configuration, ...). You can use multiple " - "instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. " - "If your MO folder is writable, you can also store a single instance locally (called " - "a Portable install, and all the MO data files will be inside the installation folder).")), - nullptr); - selection.disableCancel(); - for (const QString &instance : instanceList) { - selection.addChoice(instance, "", instance); + if (auto name=s.game().selectedProfileName()) { + // use last profile + m_profile = *name; + return; } - selection.addChoice(QIcon(":/MO/gui/add"), QObject::tr("New"), - QObject::tr("Create a new instance."), - static_cast(Special::NewInstance)); + // profile missing from ini, use the default + m_profile = QString::fromStdWString(AppConfig::defaultProfileName()); - if (QFileInfo(qApp->applicationDirPath()).isWritable()) { - selection.addChoice(QIcon(":/MO/gui/package"), QObject::tr("Portable"), - QObject::tr("Use MO folder for data."), - static_cast(Special::Portable)); - } + log::warn( + "no profile found in ini {}, using default '{}'", + iniPath(), m_profile); +} - selection.addChoice(QIcon(":/MO/gui/remove"), QObject::tr("Manage Instances"), - QObject::tr("Delete an Instance."), - static_cast(Special::Manage)); - selection.setWindowFlags(selection.windowFlags() | Qt::WindowStaysOnTopHint); +InstanceManager::InstanceManager() +{ + GlobalSettings::updateRegistryKey(); +} + +InstanceManager &InstanceManager::instance() +{ + static InstanceManager s_Instance; + return s_Instance; +} - if (selection.exec() == QDialog::Rejected) { - log::debug("rejected"); - throw MOBase::MyException(QObject::tr("Canceled")); +void InstanceManager::overrideInstance(const QString& instanceName) +{ + m_overrideInstanceName = instanceName; + m_overrideInstance = true; +} + +void InstanceManager::overrideProfile(const QString& profileName) +{ + m_overrideProfileName = profileName; + m_overrideProfile = true; +} + +std::optional InstanceManager::currentInstance() const +{ + const QString profile = m_overrideProfile ? m_overrideProfileName : ""; + + if (portableInstallIsLocked()) { + // force portable instance + return Instance(QDir(portablePath()), true, profile); } - QVariant choice = selection.getChoiceData(); + QString name; - if (choice.type() == QVariant::String) { - return choice.toString(); - } else { - switch (static_cast(choice.value())) { - case Special::NewInstance: return queryInstanceName(instanceList); - case Special::Portable: return QString(); - case Special::Manage: { + if (m_overrideInstance) + name = m_overrideInstanceName; + else + name = GlobalSettings::currentInstance(); - return(manageInstances(instanceNames())); - } - default: throw std::runtime_error("invalid selection"); + if (name.isEmpty()) { + if (portableInstanceExists()) { + // use portable + return Instance(QDir(portablePath()), true, profile); + } else { + // no instance set + return {}; } } + + QString path = instancePath(name); + if (!QFileInfo::exists(path)) { + // the previously used instance doesn't exist anymore + return {}; + } + + return Instance(QDir(path), false, profile); +} + +void InstanceManager::clearCurrentInstance() +{ + setCurrentInstance(""); + m_overrideInstance = false; +} + +void InstanceManager::setCurrentInstance(const QString &name) +{ + GlobalSettings::setCurrentInstance(name); } QString InstanceManager::instancePath(const QString& instanceName) const @@ -302,6 +365,11 @@ QString InstanceManager::instancesPath() const QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } +QString InstanceManager::iniPath(const QDir& instanceDir) +{ + return instanceDir.filePath(QString::fromStdWString(AppConfig::iniFileName())); +} + std::vector InstanceManager::instancePaths() const { const std::set ignore = { @@ -362,287 +430,10 @@ bool InstanceManager::allowedToChangeInstance() const return !portableInstallIsLocked(); } - -void InstanceManager::createDataPath(const QString &dataPath) const -{ - if (!QDir(dataPath).exists()) { - if (!QDir().mkpath(dataPath)) { - throw MOBase::MyException( - QObject::tr("failed to create %1").arg(dataPath)); - } else { - QMessageBox::information( - nullptr, QObject::tr("Data directory created"), - QObject::tr("New data directory created at %1. If you don't want to " - "store a lot of data there, reconfigure the storage " - "directories via settings.").arg(dataPath)); - } - } -} - - -QString InstanceManager::determineDataPath() -{ - QString instanceId = currentInstance(); - if (portableInstallIsLocked()) - { - instanceId.clear(); - } - if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstanceExists())) - { - // startup, apparently using portable mode before - return qApp->applicationDirPath(); - } - - QString dataPath = QDir::fromNativeSeparators( - QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + "/" + instanceId); - - - if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) { - instanceId = chooseInstance(instanceNames()); - setCurrentInstance(instanceId); - if (!instanceId.isEmpty()) { - dataPath = QDir::fromNativeSeparators( - QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + "/" + instanceId); - } - } - - if (instanceId.isEmpty()) { - return qApp->applicationDirPath(); - } else { - createDataPath(dataPath); - - return dataPath; - } -} - -QString InstanceManager::determineProfile(const Settings &settings) -{ - auto selectedProfileName = settings.game().selectedProfileName(); - - if (m_overrideProfile) { - log::debug("profile overwritten on command line"); - selectedProfileName = m_overrideProfileName; - } - - if (!selectedProfileName) { - log::debug("no configured profile"); - selectedProfileName = "Default"; - } - - return *selectedProfileName; -} - -bool InstanceManager::determineGameEdition( - Settings& settings, IPluginGame* game) -{ - QString edition; - - if (auto v=settings.game().edition()) { - edition = *v; - } else { - QStringList editions = game->gameVariants(); - if (editions.size() < 2) { - edition = ""; - return true; - } - - SelectionDialog selection( - QObject::tr("Please select the game edition you have (MO can't " - "start the game correctly if this is set " - "incorrectly!)"), - nullptr); - - selection.setWindowFlag(Qt::WindowStaysOnTopHint, true); - - int index = 0; - for (const QString &edition : editions) { - selection.addChoice(edition, "", index++); - } - - if (selection.exec() == QDialog::Rejected) { - return false; - } - - edition = selection.getChoiceString(); - settings.game().setEdition(edition); - } - - game->setGameVariant(edition); - - return true; -} - -MOBase::IPluginGame *selectGame( - Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) -{ - settings.game().setName(game->gameName()); - - QString gameDir = gamePath.absolutePath(); - game->setGamePath(gameDir); - - settings.game().setDirectory(gameDir); - - return game; -} - -MOBase::IPluginGame* InstanceManager::determineCurrentGame( - const QString& moPath, Settings& settings, const PluginContainer &plugins) -{ - //Determine what game we are running where. Be very paranoid in case the - //user has done something odd. - - //If the game name has been set up, try to use that. - const auto gameName = settings.game().name(); - const bool gameConfigured = (gameName.has_value() && *gameName != ""); - - if (gameConfigured) { - MOBase::IPluginGame *game = plugins.managedGame(*gameName); - if (game == nullptr) { - reportError( - QObject::tr("Plugin to handle %1 no longer installed. An antivirus might have deleted files.") - .arg(*gameName)); - - return nullptr; - } - - auto gamePath = settings.game().directory(); - if (!gamePath || *gamePath == "") { - gamePath = game->gameDirectory().absolutePath(); - } - - QDir gameDir(*gamePath); - QFileInfo directoryInfo(gameDir.path()); - - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath)); - } - - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - - //If we've made it this far and the instance is already configured for a game, something has gone wrong. - //Tell the user about it. - if (gameConfigured) { - const auto gamePath = settings.game().directory(); - - reportError( - QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") - .arg(*gameName).arg(gamePath ? *gamePath : "")); - } - - SelectionDialog selection(gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); - - for (IPluginGame *game : plugins.plugins()) { - //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) - continue; - - //Only add games that are installed - if (game->isInstalled()) { - QString path = game->gameDirectory().absolutePath(); - selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); - } - } - - selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr))); - - while (selection.exec() != QDialog::Rejected) { - IPluginGame * game = selection.getChoiceData().value(); - QString gamePath = selection.getChoiceDescription(); - QFileInfo directoryInfo(gamePath); - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); - } - if (game != nullptr) { - return selectGame(settings, game->gameDirectory(), game); - } - - gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), - QString(), QFileDialog::ShowDirsOnly); - - if (!gamePath.isEmpty()) { - QDir gameDir(gamePath); - QFileInfo directoryInfo(gamePath); - if (directoryInfo.isSymLink()) { - reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); - } - QList possibleGames; - for (IPluginGame * const game : plugins.plugins()) { - //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) - continue; - - //Only try plugins that look valid for this directory - if (game->looksValid(gameDir)) { - possibleGames.append(game); - } - } - - if (possibleGames.count() > 1) { - SelectionDialog browseSelection(gameConfigured ? - QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : - QObject::tr("Please select the game to manage"), - nullptr, QSize(32, 32)); - - for (IPluginGame *game : possibleGames) { - browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game)); - } - - if (browseSelection.exec() == QDialog::Accepted) { - return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); - } else { - reportError(gameConfigured ? - QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) : - QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); - } - } else if(possibleGames.count() == 1) { - return selectGame(settings, gameDir, possibleGames[0]); - } else { - if (gameConfigured) { - reportError( - QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.") - .arg(*gameName).arg(gamePath)); - } else { - QString supportedGames; - - for (IPluginGame * const game : plugins.plugins()) { - supportedGames += "
  • " + game->gameName() + "
  • "; - } - - QString text = QObject::tr( - "No game identified in \"%1\". The directory is required to " - "contain the game binary.

    " - "These are the games supported by Mod Organizer:" - "
      %2
    ") - .arg(gamePath) - .arg(supportedGames); - - reportError(text); - } - } - } - } - - return nullptr; -} - const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( const QDir& instanceDir, const PluginContainer& plugins) const { - const QString ini = - QDir(instanceDir).filePath(QString::fromStdWString(AppConfig::iniFileName())); - + const QString ini = iniPath(instanceDir); Settings s(ini); diff --git a/src/instancemanager.h b/src/instancemanager.h index c2d1e0f4..ddab4a2e 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -29,29 +29,61 @@ namespace MOBase { class IPluginGame; } class Settings; class PluginContainer; + +class Instance +{ +public: + enum class SetupResults + { + Ok, + BadIni, + IniMissingGame, + PluginGone, + GameGone, + MissingVariant + }; + + Instance(QDir dir, bool portable, QString profileName={}); + + SetupResults setup(PluginContainer& plugins); + + void setGame(const QString& name, const QString& dir); + void setVariant(const QString& name); + + QString name() const; + QString gameName() const; + QString gameDirectory() const; + QDir directory() const; + MOBase::IPluginGame* gamePlugin() const; + QString profileName() const; + QString iniPath() const; + bool isPortable() const; + +private: + QDir m_dir; + bool m_portable; + QString m_gameName, m_gameDir, m_gameVariant; + MOBase::IPluginGame* m_plugin; + QString m_profile; + + SetupResults getGamePlugin(PluginContainer& plugins); + void getProfile(const Settings& s); +}; + + class InstanceManager { public: static InstanceManager &instance(); - // restarts MO - // - void switchToInstance(const QString& instanceName); - void overrideInstance(const QString& instanceName); void overrideProfile(const QString& profileName); - QString determineDataPath(); - QString determineProfile(const Settings &settings); - bool determineGameEdition(Settings& settings, MOBase::IPluginGame* game); - MOBase::IPluginGame* determineCurrentGame( - const QString& moPath, Settings& settings, const PluginContainer &plugins); - const MOBase::IPluginGame* gamePluginForDirectory( const QDir& dir, const PluginContainer& plugins) const; void clearCurrentInstance(); - QString currentInstance() const; + std::optional currentInstance() const; void setCurrentInstance(const QString &name); bool allowedToChangeInstance() const; @@ -68,23 +100,13 @@ public: bool instanceExists(const QString& instanceName) const; bool validInstanceName(const QString& instanceName) const; QString instancePath(const QString& instanceName) const; + static QString iniPath(const QDir& instanceDir); private: - InstanceManager(); - - bool deleteLocalInstance(const QString &instanceId) const; - - QString manageInstances(const QStringList &instanceList) const; - - QString queryInstanceName(const QStringList &instanceList) const; - QString chooseInstance(const QStringList &instanceList) const; - - void createDataPath(const QString &dataPath) const; bool portableInstallIsLocked() const; private: - bool m_Reset {false}; bool m_overrideInstance{false}; QString m_overrideInstanceName; bool m_overrideProfile{false}; diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 545b5c71..f662082f 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -6,6 +6,7 @@ #include "selectiondialog.h" #include "plugincontainer.h" #include "shared/appconfig.h" +#include "shared/util.h" #include #include #include @@ -20,11 +21,6 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent) dlg.exec(); } -QString makeIniFile(const QDir& dir) -{ - return dir.filePath(QString::fromStdWString(AppConfig::iniFileName())); -} - class InstanceInfo { @@ -61,7 +57,7 @@ public: void setDir(const QDir& dir) { m_dir = dir; - m_settings.reset(new Settings(makeIniFile(dir))); + m_settings.reset(new Settings(InstanceManager::iniPath(dir))); } QString name() const @@ -109,7 +105,7 @@ public: QString iniFile() const { - return makeIniFile(m_dir); + return InstanceManager::iniPath(m_dir); } QIcon icon(const PluginContainer& plugins) const @@ -134,10 +130,13 @@ public: { auto& m = InstanceManager::instance(); - if (m_portable && m.currentInstance() == "") { - return true; - } else if (m.currentInstance() == name()) { - return true; + if (auto i=m.currentInstance()) + { + if (m_portable) { + return i->isPortable(); + } else { + return (i->name() == name()); + } } return false; @@ -316,7 +315,7 @@ InstanceManagerDialog::~InstanceManagerDialog() = default; InstanceManagerDialog::InstanceManagerDialog( const PluginContainer& pc, QWidget *parent) : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc), - m_model(nullptr) + m_model(nullptr), m_restartOnSelect(true) { ui->setupUi(this); @@ -449,14 +448,16 @@ void InstanceManagerDialog::selectActiveInstance() { const auto active = InstanceManager::instance().currentInstance(); - for (std::size_t i=0; iname() == active) { - select(i); + if (active) { + for (std::size_t i=0; iname() == active->name()) { + select(i); - ui->list->scrollTo( - m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); + ui->list->scrollTo( + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); - return; + return; + } } } @@ -470,7 +471,13 @@ void InstanceManagerDialog::openSelectedInstance() return; } - InstanceManager::instance().switchToInstance(m_instances[i]->name()); + InstanceManager::instance().setCurrentInstance(m_instances[i]->name()); + + if (m_restartOnSelect) { + ExitModOrganizer(Exit::Restart); + } + + accept(); } QString getInstanceName( @@ -697,6 +704,11 @@ void InstanceManagerDialog::deleteInstance() } +void InstanceManagerDialog::setRestartOnSelect(bool b) +{ + m_restartOnSelect = b; +} + bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) { if (MOBase::shellDelete(files, recycle, this)) { diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 477a7d01..5b08ffc2 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -34,6 +34,8 @@ public: void openINI(); void deleteInstance(); + void setRestartOnSelect(bool b); + private: static const std::size_t NoSelection = -1; @@ -42,6 +44,7 @@ private: std::vector> m_instances; MOBase::FilterWidget m_filter; QStandardItemModel* m_model; + bool m_restartOnSelect; void updateInstances(); diff --git a/src/main.cpp b/src/main.cpp index fd5a47c9..2a5a3e81 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "instancemanagerdialog.h" #include "createinstancedialog.h" +#include "createinstancedialogpages.h" #include "organizercore.h" #include "env.h" #include "envmodule.h" @@ -273,9 +274,166 @@ std::optional handleCommandLine( void openInstanceManager(PluginContainer& pc, QWidget* parent); +std::optional selectInstance() +{ + NexusInterface ni(nullptr); + + PluginContainer pc(nullptr); + pc.loadPlugins(); + + InstanceManagerDialog dlg(pc); + dlg.setRestartOnSelect(false); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return {}; + } + + return InstanceManager::instance().currentInstance(); +} + +enum class SetupInstanceResults +{ + Ok, + TryAgain, + SelectAnother, + Exit +}; + + +void criticalOnTop(const QString& message) +{ + QMessageBox mb(QMessageBox::Critical, QObject::tr("Mod Organizer"), message); + + mb.show(); + mb.activateWindow(); + mb.raise(); + mb.exec(); +} + + +SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) +{ + const auto setupResult = instance.setup(pc); + + switch (setupResult) + { + case Instance::SetupResults::Ok: + { + return SetupInstanceResults::Ok; + } + + case Instance::SetupResults::BadIni: + { + criticalOnTop( + QObject::tr("Cannot open instance '%1', failed to read INI file %2.") + .arg(instance.name()).arg(instance.iniPath())); + + return SetupInstanceResults::SelectAnother; + } + + case Instance::SetupResults::IniMissingGame: + { + criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the managed game was not found in the INI " + "file %2. Select the game managed by this instance.") + .arg(instance.name()).arg(instance.iniPath())); + + CreateInstanceDialog dlg(pc, nullptr); + dlg.setSinglePage(); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setGame( + dlg.creationInfo().game->gameName(), + dlg.creationInfo().game->gameDirectory().absolutePath()); + + return SetupInstanceResults::TryAgain; + } + + case Instance::SetupResults::PluginGone: + { + criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the game plugin '%2' doesn't exist. It " + "may have been deleted by an antivirus. Select another instance.") + .arg(instance.name()).arg(instance.gameName())); + + return SetupInstanceResults::SelectAnother; + } + + case Instance::SetupResults::GameGone: + { + criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the game directory '%2' doesn't exist or " + "the game plugin '%3' doesn't recognize it. Select the game managed " + "by this instance.") + .arg(instance.name()) + .arg(instance.gameDirectory()) + .arg(instance.gameName())); + + CreateInstanceDialog dlg(pc, nullptr); + dlg.setSinglePage(); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setGame( + dlg.creationInfo().game->gameName(), + dlg.creationInfo().game->gameDirectory().absolutePath()); + + return SetupInstanceResults::TryAgain; + } + + case Instance::SetupResults::MissingVariant: + { + CreateInstanceDialog dlg(pc, nullptr); + + dlg.getPage()->select( + instance.gamePlugin(), instance.gameDirectory()); + + dlg.setSinglePage(); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setVariant(dlg.creationInfo().gameVariant); + + return SetupInstanceResults::TryAgain; + } + + default: + { + return SetupInstanceResults::Exit; + } + } +} + int runApplication( MOApplication &application, const cl::CommandLine& cl, - SingleInstance &instance, const QString &dataPath) + SingleInstance &instance, const QString &dataPath, + Instance& currentInstance) { TimeThis tt("runApplication() to exec()"); @@ -345,57 +503,48 @@ int runApplication( pluginContainer = std::make_unique(&organizer); pluginContainer->loadPlugins(); - MOBase::IPluginGame* game = InstanceManager::instance() - .determineCurrentGame( - application.applicationDirPath(), settings, *pluginContainer); - - if (game == nullptr) { - InstanceManager &instance = InstanceManager::instance(); - QString instanceName = instance.currentInstance(); - - if (instanceName.compare("Portable", Qt::CaseInsensitive) != 0) { - instance.clearCurrentInstance(); + for (;;) + { + const auto setupResult = setupInstance(currentInstance, *pluginContainer); + + if (setupResult == SetupInstanceResults::Ok) { + break; + } else if (setupResult == SetupInstanceResults::TryAgain) { + continue; + } else if (setupResult == SetupInstanceResults::SelectAnother) { + InstanceManager::instance().clearCurrentInstance(); return RestartExitCode; + } else { + return 1; } - - return 1; } - checkPathsForSanity(*game, settings); - + checkPathsForSanity(*currentInstance.gamePlugin(), settings); - organizer.setManagedGame(game); + organizer.setManagedGame(currentInstance.gamePlugin()); organizer.createDefaultProfile(); - if (!InstanceManager::instance().determineGameEdition(settings, game)) { - return 1; - } - log::info( "using game plugin '{}' ('{}', variant {}, steam id '{}') at {}", - game->gameName(), game->gameShortName(), + currentInstance.gamePlugin()->gameName(), + currentInstance.gamePlugin()->gameShortName(), (settings.game().edition().value_or("").isEmpty() ? "(none)" : *settings.game().edition()), - game->steamAPPId(), game->gameDirectory().absolutePath()); + currentInstance.gamePlugin()->steamAPPId(), + currentInstance.gamePlugin()->gameDirectory().absolutePath()); + CategoryFactory::instance().loadCategories(); organizer.updateExecutablesList(); organizer.updateModInfoFromDisc(); - if (cl.profile()) { - InstanceManager::instance().overrideProfile(*cl.profile()); - } - - QString selectedProfileName = InstanceManager::instance() - .determineProfile(settings); - - organizer.setCurrentProfile(selectedProfileName); + organizer.setCurrentProfile(currentInstance.profileName()); if (auto r=handleCommandLine(cl, organizer)) { return *r; } - auto splash = createSplash(settings, dataPath, game); + auto splash = createSplash(settings, dataPath, currentInstance.gamePlugin()); QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { @@ -438,11 +587,6 @@ int runApplication( tt.stop(); - QTimer::singleShot(std::chrono::milliseconds(1), [&] - { - openInstanceManager(*pluginContainer, &mainWindow); - }); - res = application.exec(); mainWindow.close(); @@ -488,28 +632,6 @@ void resetForRestart(cl::CommandLine& cl) cl.clear(); } -QString determineDataPath(const cl::CommandLine& cl) -{ - try - { - InstanceManager& instanceManager = InstanceManager::instance(); - - if (cl.instance()) - instanceManager.overrideInstance(*cl.instance()); - - return instanceManager.determineDataPath(); - } - catch (const std::exception &e) - { - if (strcmp(e.what(),"Canceled")) { - QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what()); - } - - return {}; - } -} - - int doOneRun( cl::CommandLine& cl, MOApplication& application, SingleInstance& instance) { @@ -518,24 +640,25 @@ int doOneRun( // resets things when MO is "restarted" resetForRestart(cl); + if (cl.instance()) + InstanceManager::instance().overrideInstance(*cl.instance()); - //{ - // NexusInterface ni(nullptr); - // - // PluginContainer pc(nullptr); - // pc.loadPlugins(); - // - // CreateInstanceDialog dlg(pc, nullptr); - // dlg.exec(); - //} + if (cl.profile()) { + InstanceManager::instance().overrideProfile(*cl.profile()); + } + auto currentInstance = InstanceManager::instance().currentInstance(); - const QString dataPath = determineDataPath(cl); - if (dataPath.isEmpty()) { - return 1; + if (!currentInstance) + { + currentInstance = selectInstance(); + if (!currentInstance) + return 1; } + const QString dataPath = currentInstance->directory().path(); application.setProperty("dataPath", dataPath); + setExceptionHandlers(); if (!setLogDirectory(dataPath)) { @@ -548,7 +671,7 @@ int doOneRun( tt.stop(); - return runApplication(application, cl, instance, dataPath); + return runApplication(application, cl, instance, dataPath, *currentInstance); } int main(int argc, char *argv[]) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0d561581..550f61d4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -513,9 +513,11 @@ bool OrganizerCore::bootstrap() void OrganizerCore::createDefaultProfile() { QString profilesPath = settings().paths().profiles(); - if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() - == 0) { - Profile newProf("Default", managedGame(), false); + if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { + Profile newProf( + QString::fromStdWString(AppConfig::defaultProfileName()), + managedGame(), false); + m_ProfileCreated(&newProf); } } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index a0e74f47..8ee0914b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -588,11 +588,14 @@ ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut) { const auto currentInstance = InstanceManager::instance().currentInstance(); - if (shortcut.hasInstance() && shortcut.instance() != currentInstance) { - throw std::runtime_error( - QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(),shortcut.executable()) - .toLocal8Bit().constData()); + if (currentInstance) + { + if (shortcut.hasInstance() && shortcut.instance() != currentInstance->name()) { + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + } } const Executable& exe = m_core.executablesList()->get(shortcut.executable()); diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc index 709c845d..807f1d69 100644 --- a/src/shared/appconfig.inc +++ b/src/shared/appconfig.inc @@ -9,6 +9,7 @@ APPPARAM(std::wstring, cachePath, L"webcache") APPPARAM(std::wstring, tutorialsPath, L"tutorials") APPPARAM(std::wstring, logPath, L"logs") APPPARAM(std::wstring, dumpsDir, L"crashDumps") +APPPARAM(std::wstring, defaultProfileName, L"Default") APPPARAM(std::wstring, profileTweakIni, L"profile_tweaks.ini") APPPARAM(std::wstring, logFileName, L"ModOrganizer.log") APPPARAM(std::wstring, iniFileName, L"ModOrganizer.ini") diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 5897b6bb..aefabc73 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -153,10 +153,9 @@ void StatusBar::updateNormalMessage(OrganizerCore& core) game = tr("Unknown game"); } - QString instance = InstanceManager::instance().currentInstance(); - if (instance.isEmpty()) { - instance = tr("Portable"); - } + QString instance = "?"; + if (auto i=InstanceManager::instance().currentInstance()) + instance = i->name(); QString profile = core.profileName(); -- cgit v1.3.1 From fb2cddd92a1f09fef22dc66c737aa608408aaa18 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 3 Nov 2020 11:13:28 -0500 Subject: replace "create instance" title by "setting up instance" fixed bad game location for custom paths --- src/createinstancedialog.cpp | 7 ++++++- src/createinstancedialog.h | 6 +++--- src/createinstancedialog.ui | 11 +++++++++-- src/main.cpp | 10 +++++----- 4 files changed, 23 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index d67e3451..d2846367 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -99,13 +99,18 @@ void CreateInstanceDialog::back() changePage(-1); } -void CreateInstanceDialog::setSinglePageImpl() +void CreateInstanceDialog::setSinglePageImpl(const QString& instanceName) { m_singlePage = true; if (m_pages[ui->pages->currentIndex()]->skip()) { next(); } + + // don't show the "create a new instance" title for single pages, this is + // when the instance already exists but some info is missing + ui->title->setText(tr("Setting up instance %1").arg(instanceName)); + setWindowTitle(tr("Setting up an instance %1").arg(instanceName)); } void CreateInstanceDialog::changePage(int d) diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 6947f2e2..f05495c6 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -58,7 +58,7 @@ public: Settings* settings(); template - void setSinglePage() + void setSinglePage(const QString& instanceName) { for (auto&& p : m_pages) { if (auto* tp=dynamic_cast(p.get())) { @@ -68,7 +68,7 @@ public: } } - setSinglePageImpl(); + setSinglePageImpl(instanceName); } template @@ -113,7 +113,7 @@ private: bool m_singlePage; - void setSinglePageImpl(); + void setSinglePageImpl(const QString& instanceName); template T getSelected(T (cid::Page::*mf)() const) const diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui index b7f4f502..b10d74a7 100644 --- a/src/createinstancedialog.ui +++ b/src/createinstancedialog.ui @@ -30,9 +30,16 @@ 0
    - + + + + 14 + 75 + true + + - <h2>Creating a new instance</h2> + Creating a new instance diff --git a/src/main.cpp b/src/main.cpp index 2a5a3e81..bfc074ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -344,7 +344,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) .arg(instance.name()).arg(instance.iniPath())); CreateInstanceDialog dlg(pc, nullptr); - dlg.setSinglePage(); + dlg.setSinglePage(instance.name()); dlg.show(); dlg.activateWindow(); @@ -356,7 +356,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) instance.setGame( dlg.creationInfo().game->gameName(), - dlg.creationInfo().game->gameDirectory().absolutePath()); + dlg.creationInfo().gameLocation); return SetupInstanceResults::TryAgain; } @@ -384,7 +384,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) .arg(instance.gameName())); CreateInstanceDialog dlg(pc, nullptr); - dlg.setSinglePage(); + dlg.setSinglePage(instance.name()); dlg.show(); dlg.activateWindow(); @@ -396,7 +396,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) instance.setGame( dlg.creationInfo().game->gameName(), - dlg.creationInfo().game->gameDirectory().absolutePath()); + dlg.creationInfo().gameLocation); return SetupInstanceResults::TryAgain; } @@ -408,7 +408,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) dlg.getPage()->select( instance.gamePlugin(), instance.gameDirectory()); - dlg.setSinglePage(); + dlg.setSinglePage(instance.name()); dlg.show(); dlg.activateWindow(); -- cgit v1.3.1 From 7b1e30b4649bc9ed8844cbc0d330aaf150957373 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 3 Nov 2020 11:31:17 -0500 Subject: larged delete instance dialog --- src/instancemanagerdialog.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index f662082f..f2e9a928 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -671,6 +671,7 @@ void InstanceManagerDialog::deleteInstance() } dlg.addContent(list); + dlg.setWidth(600); const auto r = dlg.exec(); -- cgit v1.3.1 From a4747a1ecc9f4fe56700c64933cc3c6cebd04734 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 3 Nov 2020 12:10:41 -0500 Subject: show create instance dialog on startup if there are no instances --- src/createinstancedialog.cpp | 12 +++++++++--- src/main.cpp | 12 +++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index d2846367..c976ee13 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -333,11 +333,17 @@ void CreateInstanceDialog::finish() if (ui->launch->isChecked()) { InstanceManager::instance().setCurrentInstance(ci.instanceName); - ExitModOrganizer(Exit::Restart); + + if (m_settings) { + // don't restart without settings, it happens on startup when there are + // no instances + ExitModOrganizer(Exit::Restart); + } + m_switching = true; - } else { - accept(); } + + accept(); } catch(Failed&) { diff --git a/src/main.cpp b/src/main.cpp index bfc074ec..bd7f8303 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -277,10 +277,20 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent); std::optional selectInstance() { NexusInterface ni(nullptr); - PluginContainer pc(nullptr); pc.loadPlugins(); + if (InstanceManager::instance().instancePaths().empty()) { + // no instances configured + CreateInstanceDialog dlg(pc, nullptr); + if (dlg.exec() != QDialog::Accepted) { + return {}; + } + + return InstanceManager::instance().currentInstance(); + } + + InstanceManagerDialog dlg(pc); dlg.setRestartOnSelect(false); -- cgit v1.3.1 From 38d2f87b31ba4af8f6ecb73e0432460778e26f82 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 3 Nov 2020 13:35:02 -0500 Subject: replaced #pragma once by ifdefs changed pointer to ref to NexusInterface on_actionChange_Game_triggered() now creates the dialog itself instead of calling test code fixed broken command line options, they'd be reset before they were used removed useless explicit --- src/commandline.h | 6 ++++-- src/createinstancedialog.h | 2 +- src/instancemanager.h | 26 ++++---------------------- src/instancemanagerdialog.cpp | 9 --------- src/main.cpp | 14 +++++++------- src/mainwindow.cpp | 31 +++++++++---------------------- src/moshortcut.h | 31 ++++++------------------------- src/pluginlistview.cpp | 2 -- src/shared/error_report.h | 8 ++++++-- src/uilocker.h | 5 ++++- 10 files changed, 41 insertions(+), 93 deletions(-) (limited to 'src') diff --git a/src/commandline.h b/src/commandline.h index 0e300327..72018ba3 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -1,5 +1,5 @@ -#pragma once - +#ifndef MODORGANIZER_COMMANDLINE_INCLUDED +#define MODORGANIZER_COMMANDLINE_INCLUDED #include "moshortcut.h" #include #include @@ -149,3 +149,5 @@ private: }; } // namespace + +#endif // MODORGANIZER_COMMANDLINE_INCLUDED diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index f05495c6..25e383eb 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -47,7 +47,7 @@ public: }; - explicit CreateInstanceDialog( + CreateInstanceDialog( const PluginContainer& pc, Settings* s, QWidget *parent = nullptr); ~CreateInstanceDialog(); diff --git a/src/instancemanager.h b/src/instancemanager.h index ddab4a2e..69536650 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -1,25 +1,5 @@ -/* -Copyright (C) 2016 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - - -#pragma once - +#ifndef MODORGANIZER_INSTANCEMANAGER_INCLUDED +#define MODORGANIZER_INSTANCEMANAGER_INCLUDED #include #include @@ -112,3 +92,5 @@ private: bool m_overrideProfile{false}; QString m_overrideProfileName; }; + +#endif // MODORGANIZER_INSTANCEMANAGER_INCLUDED diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index f2e9a928..231835ba 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -13,15 +13,6 @@ using namespace MOBase; -void openInstanceManager(PluginContainer& pc, QWidget* parent) -{ - //CreateInstanceDialog dlg(pc, parent); - //dlg.exec(); - InstanceManagerDialog dlg(pc, parent); - dlg.exec(); -} - - class InstanceInfo { public: diff --git a/src/main.cpp b/src/main.cpp index bd7f8303..8f7af77d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -650,13 +650,6 @@ int doOneRun( // resets things when MO is "restarted" resetForRestart(cl); - if (cl.instance()) - InstanceManager::instance().overrideInstance(*cl.instance()); - - if (cl.profile()) { - InstanceManager::instance().overrideProfile(*cl.profile()); - } - auto currentInstance = InstanceManager::instance().currentInstance(); if (!currentInstance) @@ -707,6 +700,13 @@ int main(int argc, char *argv[]) tt.stop(); + if (cl.instance()) + InstanceManager::instance().overrideInstance(*cl.instance()); + + if (cl.profile()) { + InstanceManager::instance().overrideProfile(*cl.profile()); + } + for (;;) { const auto r = doOneRun(cl, application, instance); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6a648512..02900571 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -76,6 +76,7 @@ along with Mod Organizer. If not, see . #include "statusbar.h" #include "filterlist.h" #include "datatab.h" +#include "instancemanagerdialog.h" #include #include #include @@ -291,7 +292,7 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->setup(ui, settings); { - auto* ni = &NexusInterface::instance(); + auto& ni = NexusInterface::instance(); // there are two ways to get here: // 1) the user just started MO, and @@ -311,8 +312,8 @@ MainWindow::MainWindow(Settings &settings // // in the rare case where the user restarts MO through the settings, this // will correctly pick up the previous values - updateWindowTitle(ni->getAPIUserAccount()); - ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); + updateWindowTitle(ni.getAPIUserAccount()); + ui->statusBar->setAPI(ni.getAPIStats(), ni.getAPIUserAccount()); } m_Filters.reset(new FilterList(ui, &m_OrganizerCore, m_CategoryFactory)); @@ -1961,8 +1962,8 @@ void MainWindow::refreshSaveList() it.next(); files.append(it.fileInfo()); } - std::sort(files.begin(), files.end(), [](auto const& lhs, auto const& rhs) { - return lhs.fileTime(QFileDevice::FileModificationTime) < rhs.fileTime(QFileDevice::FileModificationTime); + std::sort(files.begin(), files.end(), [](auto const& lhs, auto const& rhs) { + return lhs.fileTime(QFileDevice::FileModificationTime) < rhs.fileTime(QFileDevice::FileModificationTime); }); for (const QFileInfo &file : files) { @@ -5569,7 +5570,7 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa void MainWindow::finishUpdateInfo() { QFutureWatcher>>> *watcher = static_cast>>> *>(sender()); - + QString game = watcher->result().first; auto finalMods = watcher->result().second; @@ -5974,24 +5975,10 @@ void MainWindow::on_actionNotifications_triggered() scheduleCheckForProblems(); } -void openInstanceManager(PluginContainer& pc, QWidget* parent); - void MainWindow::on_actionChange_Game_triggered() { - openInstanceManager(m_PluginContainer, this); - - //if (m_OrganizerCore.settings().interface().showChangeGameConfirmation()) { - // const auto r = QMessageBox::question( - // this, tr("Are you sure?"), tr("This will restart MO, continue?"), - // QMessageBox::Yes | QMessageBox::Cancel); - // - // if (r != QMessageBox::Yes) { - // return; - // } - //} - // - //InstanceManager::instance().clearCurrentInstance(); - //ExitModOrganizer(Exit::Restart); + InstanceManagerDialog dlg(m_PluginContainer, this); + dlg.exec(); } void MainWindow::setCategoryListVisible(bool visible) diff --git a/src/moshortcut.h b/src/moshortcut.h index 0067b3bc..33346bb9 100644 --- a/src/moshortcut.h +++ b/src/moshortcut.h @@ -1,31 +1,10 @@ -/* -Copyright (C) 2016 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - - -#pragma once - +#ifndef MODORGANIZER_MOSHORTCUT_INCLUDED +#define MODORGANIZER_MOSHORTCUT_INCLUDED #include - -class MOShortcut { - +class MOShortcut +{ public: MOShortcut(const QString& link={}); @@ -49,3 +28,5 @@ private: bool m_hasInstance; bool m_hasExecutable; }; + +#endif // MODORGANIZER_MOSHORTCUT_INCLUDED diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index 4217971d..a265d5d4 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -56,5 +56,3 @@ void PluginListView::setModel(QAbstractItemModel *model) QTreeView::setModel(model); setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); } - -#pragma once diff --git a/src/shared/error_report.h b/src/shared/error_report.h index 17b25645..da07c728 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -17,16 +17,20 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#pragma once +#ifndef MODORGANIZER_SHARED_ERROR_REPORT_INCLUDED +#define MODORGANIZER_SHARED_ERROR_REPORT_INCLUDED #include #define WIN32_LEAN_AND_MEAN #include #include -namespace MOShared { +namespace MOShared +{ void reportError(LPCSTR format, ...); void reportError(LPCWSTR format, ...); } // namespace MOShared + +#endif // MODORGANIZER_SHARED_ERROR_REPORT_INCLUDED diff --git a/src/uilocker.h b/src/uilocker.h index cc467184..44d9d8a2 100644 --- a/src/uilocker.h +++ b/src/uilocker.h @@ -1,4 +1,5 @@ -#pragma once +#ifndef MODORGANIZER_UILOCKER_INCLUDED +#define MODORGANIZER_UILOCKER_INCLUDED #include #include @@ -94,3 +95,5 @@ private: void enableAll(); void disable(QWidget* w); }; + +#endif // MODORGANIZER_UILOCKER_INCLUDED -- cgit v1.3.1 From f7b097e6ba3160b424204c719b615954601f76fe Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 3 Nov 2020 13:38:08 -0500 Subject: stop using deprecated map() --- src/modinfodialogtextfiles.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 4bdc8ee1..26ca3eb8 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -203,7 +203,7 @@ void GenericFilesTab::select(const QModelIndex& index) } m_editor->setEnabled(true); - m_editor->load(m_model->fullPath(m_filter.map(index))); + m_editor->load(m_model->fullPath(m_filter.mapToSource(index))); } -- cgit v1.3.1 From 780cc2a35217e887c9bba0573be1146505114521 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 4 Nov 2020 21:46:39 +0100 Subject: Add IModInterface::fileTree(). --- src/modinfo.h | 11 +++++++++++ src/modinforegular.cpp | 2 +- src/modinfowithconflictinfo.cpp | 6 +++--- src/modinfowithconflictinfo.h | 24 ++++++++++++------------ 4 files changed, 27 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/modinfo.h b/src/modinfo.h index 7223cece..ad917c17 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -368,6 +368,17 @@ public: // IModInterface implementations / Re-declaration */ virtual MOBase::EndorsedState endorsedState() const override { return MOBase::EndorsedState::ENDORSED_NEVER; } + /** + * @brief Retrieve a file tree corresponding to the underlying disk content + * of this mod. + * + * The file tree should not be cached since it is already cached and updated when + * required. + * + * @return a file tree representing the content of this mod. + */ + virtual std::shared_ptr fileTree() const = 0; + public: // Mutable operations: /** diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 78c04f2d..3f5fc7fc 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -682,7 +682,7 @@ std::set ModInfoRegular::doGetContents() const ModDataContent* contentFeature = m_GamePlugin->feature(); if (contentFeature) { - auto result = contentFeature->getContentsFor(contentFileTree()); + auto result = contentFeature->getContentsFor(fileTree()); return std::set(std::begin(result), std::end(result)); } diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 70f9b3f1..dd8618e3 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -310,21 +310,21 @@ void ModInfoWithConflictInfo::diskContentModified() { void ModInfoWithConflictInfo::prefetch() { // Populating the tree to 1-depth (IFileTree is lazy, so size() forces the // tree to populate the first level): - contentFileTree()->size(); + fileTree()->size(); } bool ModInfoWithConflictInfo::doIsValid() const { auto mdc = m_GamePlugin->feature(); if (mdc) { - auto qdirfiletree = contentFileTree(); + auto qdirfiletree = fileTree(); return mdc->dataLooksValid(qdirfiletree) == ModDataChecker::CheckReturn::VALID; } return true; } -std::shared_ptr ModInfoWithConflictInfo::contentFileTree() const { +std::shared_ptr ModInfoWithConflictInfo::fileTree() const { return m_FileTree.value(); } diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h index 7109b83d..feded99b 100644 --- a/src/modinfowithconflictinfo.h +++ b/src/modinfowithconflictinfo.h @@ -34,7 +34,18 @@ public: * * @return true if the content is there, false otherwise. */ - virtual bool hasContent(int content) const override; + virtual bool hasContent(int content) const override; /** + * @brief Retrieve a file tree corresponding to the underlying disk content + * of this mod. + * + * The file tree should not be cached since it is already cached and updated when + * required. + * + * @return a file tree representing the content of this mod. + */ + std::shared_ptr fileTree() const override; + +public: /** * @brief clear all caches held for this mod @@ -78,17 +89,6 @@ protected: **/ virtual std::set doGetContents() const { return {}; } - /** - * @brief Retrieve a file tree corresponding to the underlying disk content - * of this mod. - * - * The file tree should not be cached since it is already cached and updated when - * required. - * - * @return a file tree representing the content of this mod. - */ - std::shared_ptr contentFileTree() const; - ModInfoWithConflictInfo( PluginContainer* pluginContainer, const MOBase::IPluginGame* gamePlugin, -- cgit v1.3.1 From e6a87a17987de5d8e462634777df31e2cae5c1a6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Nov 2020 20:57:50 -0500 Subject: added IPlugin::registered() removed useless dummy interfaces because init() isn't called anymore python plugins currently broken because init() isn't called on them fixed create instance dialog being shown on startup even if portable instance existed display a message when the last instance can't be found fixed instance manager dialog failing to open the portable instance --- src/instancemanager.cpp | 8 +- src/instancemanagerdialog.cpp | 6 +- src/main.cpp | 37 ++++++-- src/modlist.cpp | 71 --------------- src/modlist.h | 20 ---- src/organizerproxy.cpp | 206 ------------------------------------------ src/organizerproxy.h | 56 ------------ src/plugincontainer.cpp | 35 ++++--- src/plugincontainer.h | 2 +- src/pluginlist.cpp | 65 ------------- src/pluginlist.h | 19 ---- 11 files changed, 54 insertions(+), 471 deletions(-) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index c79c5254..61c442be 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -334,13 +334,7 @@ std::optional InstanceManager::currentInstance() const } } - QString path = instancePath(name); - if (!QFileInfo::exists(path)) { - // the previously used instance doesn't exist anymore - return {}; - } - - return Instance(QDir(path), false, profile); + return Instance(QDir(instancePath(name)), false, profile); } void InstanceManager::clearCurrentInstance() diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 231835ba..282329a5 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -462,7 +462,11 @@ void InstanceManagerDialog::openSelectedInstance() return; } - InstanceManager::instance().setCurrentInstance(m_instances[i]->name()); + if (m_instances[i]->isPortable()) { + InstanceManager::instance().setCurrentInstance(""); + } else { + InstanceManager::instance().setCurrentInstance(m_instances[i]->name()); + } if (m_restartOnSelect) { ExitModOrganizer(Exit::Restart); diff --git a/src/main.cpp b/src/main.cpp index 8f7af77d..7b489aee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -272,22 +272,22 @@ std::optional handleCommandLine( return {}; } -void openInstanceManager(PluginContainer& pc, QWidget* parent); - std::optional selectInstance() { + auto& m = InstanceManager::instance(); + NexusInterface ni(nullptr); PluginContainer pc(nullptr); pc.loadPlugins(); - if (InstanceManager::instance().instancePaths().empty()) { + if (m.instancePaths().empty() && !m.portableInstanceExists()) { // no instances configured CreateInstanceDialog dlg(pc, nullptr); if (dlg.exec() != QDialog::Accepted) { return {}; } - return InstanceManager::instance().currentInstance(); + return m.currentInstance(); } @@ -302,7 +302,7 @@ std::optional selectInstance() return {}; } - return InstanceManager::instance().currentInstance(); + return m.currentInstance(); } enum class SetupInstanceResults @@ -650,13 +650,36 @@ int doOneRun( // resets things when MO is "restarted" resetForRestart(cl); - auto currentInstance = InstanceManager::instance().currentInstance(); + auto& m = InstanceManager::instance(); + auto currentInstance = m.currentInstance(); if (!currentInstance) { currentInstance = selectInstance(); - if (!currentInstance) + if (!currentInstance) { return 1; + } + } + else + { + if (!currentInstance->directory().exists()) { + // the previously used instance doesn't exist anymore + + if (m.instanceNames().empty() && !m.portableInstanceExists()) { + criticalOnTop(QObject::tr( + "Instance at '%1' not found. You must create a new instance") + .arg(currentInstance->directory().absolutePath())); + } else { + criticalOnTop(QObject::tr( + "Instance at '%1' not found. Select another instance.") + .arg(currentInstance->directory().absolutePath())); + } + + currentInstance = selectInstance(); + if (!currentInstance) { + return 1; + } + } } const QString dataPath = currentInstance->directory().path(); diff --git a/src/modlist.cpp b/src/modlist.cpp index c98464a9..04abfb01 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1528,74 +1528,3 @@ void ModList::disableSelected(const QItemSelectionModel *selectionModel) m_Profile->setModsEnabled(QList(), modsToDisable); } } - - -QString DummyModList::displayName(const QString&) const -{ - return {}; -} - -QStringList DummyModList::allMods() const -{ - return {}; -} - -QStringList DummyModList::allModsByProfilePriority(MOBase::IProfile*) const -{ - return {}; -} - -IModInterface* DummyModList::getMod(const QString&) const -{ - return nullptr; -} - -bool DummyModList::removeMod(MOBase::IModInterface*) -{ - return true; -} - -IModList::ModStates DummyModList::state(const QString&) const -{ - return 0; -} - -bool DummyModList::setActive(const QString&, bool) -{ - return true; -} - -int DummyModList::setActive(const QStringList&, bool) -{ - return 0; -} - -int DummyModList::priority(const QString&) const -{ - return -1; -} - -bool DummyModList::setPriority(const QString&, int) -{ - return true; -} - -bool DummyModList::onModInstalled(const std::function&) -{ - return true; -} - -bool DummyModList::onModRemoved(const std::function&) -{ - return true; -} - -bool DummyModList::onModStateChanged(const std::function&)>&) -{ - return true; -} - -bool DummyModList::onModMoved(const std::function&) -{ - return true; -} diff --git a/src/modlist.h b/src/modlist.h index 1a469ee7..385ca04c 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -397,25 +397,5 @@ private: }; - -class DummyModList : public MOBase::IModList -{ -public: - QString displayName(const QString &internalName) const override; - QStringList allMods() const override; - QStringList allModsByProfilePriority(MOBase::IProfile *profile = nullptr) const override; - MOBase::IModInterface* getMod(const QString& name) const override; - bool removeMod(MOBase::IModInterface *mod) override; - ModStates state(const QString &name) const override; - bool setActive(const QString &name, bool active) override; - int setActive(const QStringList& names, bool active) override; - int priority(const QString &name) const override; - bool setPriority(const QString &name, int newPriority) override; - bool onModInstalled(const std::function& func) override; - bool onModRemoved(const std::function& func) override; - bool onModStateChanged(const std::function&)> &func) override; - bool onModMoved(const std::function &func) override; -}; - #endif // MODLIST_H diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index b2a4f791..45efc00c 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -287,209 +287,3 @@ bool OrganizerProxy::onPluginSettingChanged(std::functiononPluginSettingChanged(func); } - - - -DummyOrganizerProxy::DummyOrganizerProxy(MOBase::IPlugin* plugin) - : m_mods(new DummyModList), m_plugins(new DummyPluginList) -{ -} - -DummyOrganizerProxy::~DummyOrganizerProxy() = default; - -IModRepositoryBridge *DummyOrganizerProxy::createNexusBridge() const -{ - return nullptr; -} - -QString DummyOrganizerProxy::profileName() const -{ - return {}; -} - -QString DummyOrganizerProxy::profilePath() const -{ - return {}; -} - -QString DummyOrganizerProxy::downloadsPath() const -{ - return {}; -} - -QString DummyOrganizerProxy::overwritePath() const -{ - return {}; -} - -QString DummyOrganizerProxy::basePath() const -{ - return {}; -} - -QString DummyOrganizerProxy::modsPath() const -{ - return {}; -} - -VersionInfo DummyOrganizerProxy::appVersion() const -{ - return {}; -} - -IPluginGame *DummyOrganizerProxy::getGame(const QString &gameName) const -{ - return nullptr; -} - -IModInterface *DummyOrganizerProxy::createMod(MOBase::GuessedValue &name) -{ - return nullptr; -} - -void DummyOrganizerProxy::modDataChanged(IModInterface *mod) -{ -} - -QVariant DummyOrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const -{ - if (key == "enabled") { - return true; - } - - return {}; -} - -void DummyOrganizerProxy::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) -{ -} - -QVariant DummyOrganizerProxy::persistent(const QString &pluginName, const QString &key, const QVariant &def) const -{ - return {}; -} - -void DummyOrganizerProxy::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) -{ -} - -QString DummyOrganizerProxy::pluginDataPath() const -{ - return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + "/data"; -} - -HANDLE DummyOrganizerProxy::startApplication( - const QString& exe, const QStringList& args, const QString &cwd, - const QString& profile, const QString &overwrite, bool ignoreOverwrite) -{ - return INVALID_HANDLE_VALUE; -} - -bool DummyOrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const -{ - return true; -} - -bool DummyOrganizerProxy::onAboutToRun(const std::function &func) -{ - return true; -} - -bool DummyOrganizerProxy::onFinishedRun(const std::function &func) -{ - return true; -} - -bool DummyOrganizerProxy::onUserInterfaceInitialized(std::function const& func) -{ - return true; -} - -bool DummyOrganizerProxy::onProfileCreated(std::function const& func) -{ - return true; -} - -bool DummyOrganizerProxy::onProfileRenamed(std::function const& func) -{ - return true; -} - -bool DummyOrganizerProxy::onProfileRemoved(std::function const& func) -{ - return true; -} - -bool DummyOrganizerProxy::onProfileChanged(std::function const& func) -{ - return true; -} - -bool DummyOrganizerProxy::onPluginSettingChanged(std::function const& func) -{ - return true; -} - -void DummyOrganizerProxy::refresh(bool saveChanges) -{ -} - -IModInterface *DummyOrganizerProxy::installMod(const QString &fileName, const QString &nameSuggestion) -{ - return nullptr; -} - -QString DummyOrganizerProxy::resolvePath(const QString &fileName) const -{ - return {}; -} - -QStringList DummyOrganizerProxy::listDirectories(const QString &directoryName) const -{ - return {}; -} - -QStringList DummyOrganizerProxy::findFiles(const QString &path, const std::function &filter) const -{ - return {}; -} - -QStringList DummyOrganizerProxy::findFiles(const QString& path, const QStringList& globFilters) const -{ - return {}; -} - -QStringList DummyOrganizerProxy::getFileOrigins(const QString &fileName) const -{ - return {}; -} - -QList DummyOrganizerProxy::findFileInfos(const QString &path, const std::function &filter) const -{ - return {}; -} - -MOBase::IDownloadManager *DummyOrganizerProxy::downloadManager() const -{ - return nullptr; -} - -MOBase::IPluginList *DummyOrganizerProxy::pluginList() const -{ - return m_plugins.get(); -} - -MOBase::IModList *DummyOrganizerProxy::modList() const -{ - return m_mods.get(); -} - -MOBase::IProfile *DummyOrganizerProxy::profile() const -{ - return nullptr; -} - -MOBase::IPluginGame const *DummyOrganizerProxy::managedGame() const -{ - return nullptr; -} diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 144a7732..6690d612 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -81,60 +81,4 @@ private: }; - -class DummyOrganizerProxy : public MOBase::IOrganizer -{ -public: - DummyOrganizerProxy(MOBase::IPlugin* plugin); - ~DummyOrganizerProxy(); - - MOBase::IModRepositoryBridge *createNexusBridge() const override; - QString profileName() const override; - QString profilePath() const override; - QString downloadsPath() const override; - QString overwritePath() const override; - QString basePath() const override; - QString modsPath() const override; - MOBase::VersionInfo appVersion() const override; - MOBase::IPluginGame *getGame(const QString &gameName) const override; - MOBase::IModInterface *createMod(MOBase::GuessedValue &name) override; - void modDataChanged(MOBase::IModInterface *mod) override; - QVariant pluginSetting(const QString &pluginName, const QString &key) const override; - void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) override; - QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const override; - void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true) override; - QString pluginDataPath() const override; - MOBase::IModInterface *installMod(const QString &fileName, const QString &nameSuggestion = QString()) override; - QString resolvePath(const QString &fileName) const override; - QStringList listDirectories(const QString &directoryName) const override; - QStringList findFiles(const QString &path, const std::function &filter) const override; - QStringList findFiles(const QString &path, const QStringList &globFilters) const override; - QStringList getFileOrigins(const QString &fileName) const override; - QList findFileInfos(const QString &path, const std::function &filter) const override; - - MOBase::IDownloadManager *downloadManager() const override; - MOBase::IPluginList *pluginList() const override; - MOBase::IModList *modList() const override; - MOBase::IProfile *profile() const override; - HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", - const QString &profile = "", const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false) override; - bool waitForApplication(HANDLE handle, LPDWORD exitCode = nullptr) const override; - void refresh(bool saveChanges = true) override; - - bool onAboutToRun(const std::function &func) override; - bool onFinishedRun(const std::function &func) override; - bool onUserInterfaceInitialized(std::function const& func) override; - bool onProfileCreated(std::function const& func) override; - bool onProfileRenamed(std::function const& func) override; - bool onProfileRemoved(std::function const& func) override; - bool onProfileChanged(std::function const& func) override; - bool onPluginSettingChanged(std::function const& func) override; - - MOBase::IPluginGame const *managedGame() const override; - -private: - std::unique_ptr m_plugins; - std::unique_ptr m_mods; -}; - #endif // ORGANIZERPROXY_H diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 6f2670dd..0c71e491 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -169,23 +169,19 @@ QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const return *it; } -bool PluginContainer::verifyPlugin(IPlugin *plugin) +bool PluginContainer::initPlugin(IPlugin *plugin) { if (plugin == nullptr) { return false; } - IOrganizer* proxy = nullptr; - if (m_Organizer) { - proxy = new OrganizerProxy(m_Organizer, this, plugin); - } else { - proxy = new DummyOrganizerProxy(plugin); - } + auto* proxy = new OrganizerProxy(m_Organizer, this, plugin); - if (!plugin->init(proxy)) { - log::warn("plugin failed to initialize"); - return false; + if (!plugin->init(proxy)) { + log::warn("plugin failed to initialize"); + return false; + } } return true; @@ -197,7 +193,6 @@ void PluginContainer::registerGame(IPluginGame *game) m_SupportedGames.insert({ game->gameName(), game }); } - bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) { // Storing the original QObject* is a bit of a hack as I couldn't figure out any @@ -210,10 +205,14 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) log::debug("not an IPlugin"); return false; } + plugin->setProperty("filename", fileName); + if (m_Organizer) { m_Organizer->settings().plugins().registerPlugin(pluginObj); } + + pluginObj->registered(); } { // diagnosis plugin @@ -233,14 +232,14 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } { // mod page plugin IPluginModPage *modPage = qobject_cast(plugin); - if (verifyPlugin(modPage)) { + if (initPlugin(modPage)) { bf::at_key(m_Plugins).push_back(modPage); return true; } } { // game plugin IPluginGame *game = qobject_cast(plugin); - if (verifyPlugin(game)) { + if (initPlugin(game)) { bf::at_key(m_Plugins).push_back(game); registerGame(game); return true; @@ -248,14 +247,14 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } { // tool plugins IPluginTool *tool = qobject_cast(plugin); - if (verifyPlugin(tool)) { + if (initPlugin(tool)) { bf::at_key(m_Plugins).push_back(tool); return true; } } { // installer plugins IPluginInstaller *installer = qobject_cast(plugin); - if (verifyPlugin(installer)) { + if (initPlugin(installer)) { bf::at_key(m_Plugins).push_back(installer); if (m_Organizer) { m_Organizer->installationManager()->registerInstaller(installer); @@ -265,7 +264,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } { // preview plugins IPluginPreview *preview = qobject_cast(plugin); - if (verifyPlugin(preview)) { + if (initPlugin(preview)) { bf::at_key(m_Plugins).push_back(preview); m_PreviewGenerator.registerPlugin(preview); return true; @@ -273,7 +272,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } { // proxy plugins IPluginProxy *proxy = qobject_cast(plugin); - if (verifyPlugin(proxy)) { + if (initPlugin(proxy)) { bf::at_key(m_Plugins).push_back(proxy); QStringList pluginNames = proxy->pluginList( QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); @@ -305,7 +304,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) { // dummy plugins // only initialize these, no processing otherwise IPlugin *dummy = qobject_cast(plugin); - if (verifyPlugin(dummy)) { + if (initPlugin(dummy)) { bf::at_key(m_Plugins).push_back(dummy); return true; } diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 07363ee7..bfbf1fa8 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -168,7 +168,7 @@ private: QObject* as_qobject(MOBase::IPlugin* plugin) const; - bool verifyPlugin(MOBase::IPlugin *plugin); + bool initPlugin(MOBase::IPlugin *plugin); void registerGame(MOBase::IPluginGame *game); bool registerPlugin(QObject *pluginObj, const QString &fileName); bool unregisterPlugin(QObject *pluginObj, const QString &fileName); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index bda360ba..8d04b592 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1671,68 +1671,3 @@ void PluginList::managedGameChanged(const IPluginGame *gamePlugin) { m_GamePlugin = gamePlugin; } - - - -QStringList DummyPluginList::pluginNames() const -{ - return {}; -} - -IPluginList::PluginStates DummyPluginList::state(const QString &name) const -{ - return 0; -} - -void DummyPluginList::setState(const QString &name, PluginStates state) -{ -} - -int DummyPluginList::priority(const QString &name) const -{ - return -1; -} - -int DummyPluginList::loadOrder(const QString &name) const -{ - return -1; -} - -void DummyPluginList::setLoadOrder(const QStringList &pluginList) -{ -} - -bool DummyPluginList::setPriority(const QString&, int) -{ - return true; -} - -bool DummyPluginList::isMaster(const QString &name) const -{ - return false; -} - -QStringList DummyPluginList::masters(const QString &name) const -{ - return {}; -} - -QString DummyPluginList::origin(const QString &name) const -{ - return {}; -} - -bool DummyPluginList::onRefreshed(const std::function &callback) -{ - return true; -} - -bool DummyPluginList::onPluginMoved(const std::function &func) -{ - return true; -} - -bool DummyPluginList::onPluginStateChanged(const std::function&)> &func) -{ - return true; -} diff --git a/src/pluginlist.h b/src/pluginlist.h index 27c15056..0b49b86f 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -410,25 +410,6 @@ private: bool hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const; }; - -class DummyPluginList : public MOBase::IPluginList -{ -public: - QStringList pluginNames() const override; - PluginStates state(const QString &name) const override; - void setState(const QString &name, PluginStates state) override; - int priority(const QString &name) const override; - int loadOrder(const QString &name) const override; - void setLoadOrder(const QStringList &pluginList) override; - bool setPriority(const QString& name, int newPriority) override; - bool isMaster(const QString &name) const override; - QStringList masters(const QString &name) const override; - QString origin(const QString &name) const override; - bool onRefreshed(const std::function &callback) override; - bool onPluginMoved(const std::function &func) override; - bool onPluginStateChanged(const std::function&)> &func) override; -}; - #pragma warning(pop) #endif // PLUGINLIST_H -- cgit v1.3.1 From dc9de3696519fab6403c386a48c84cd6781ab99c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Nov 2020 22:50:51 -0500 Subject: call init() on proxy plugins OrganizerProxy now handles a null OrganizerCore, used for proxy plugins changed appVersion() and pluginDataPath() so they don't need OrganizerCore --- src/organizercore.cpp | 2 +- src/organizercore.h | 2 +- src/organizerproxy.cpp | 206 +++++++++++++++++++++++++++++++++++++++--------- src/plugincontainer.cpp | 34 +++++++- src/plugincontainer.h | 2 +- 5 files changed, 205 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 550f61d4..d01aab96 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -758,7 +758,7 @@ void OrganizerCore::setPersistent(const QString &pluginName, const QString &key, m_Settings.plugins().setPersistent(pluginName, key, value, sync); } -QString OrganizerCore::pluginDataPath() const +QString OrganizerCore::pluginDataPath() { return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + "/data"; diff --git a/src/organizercore.h b/src/organizercore.h index 1452bf08..80b89a43 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -314,7 +314,7 @@ public: void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def) const; void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); - QString pluginDataPath() const; + static QString pluginDataPath(); virtual MOBase::IModInterface *installMod(const QString &fileName, bool reinstallation, ModInfo::Ptr currentMod, const QString &initModName); QString resolvePath(const QString &fileName) const; QStringList listDirectories(const QString &directoryName) const; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 45efc00c..5fa1f4ef 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -9,6 +9,7 @@ #include "modlistproxy.h" #include "pluginlistproxy.h" #include "proxyutils.h" +#include "shared/util.h" #include #include @@ -21,10 +22,17 @@ OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, PluginContainer* plugin : m_Proxied(organizer) , m_PluginContainer(pluginContainer) , m_Plugin(plugin) - , m_DownloadManagerProxy(std::make_unique(this, organizer->downloadManager())) - , m_ModListProxy(std::make_unique(this, organizer->modList())) - , m_PluginListProxy(std::make_unique(this, organizer->pluginList())) { + if (m_Proxied) { + m_DownloadManagerProxy = std::make_unique( + this, m_Proxied->downloadManager()); + + m_ModListProxy = std::make_unique( + this, m_Proxied->modList()); + + m_PluginListProxy = std::make_unique( + this, m_Proxied->pluginList()); + } } IModRepositoryBridge *OrganizerProxy::createNexusBridge() const @@ -34,83 +42,133 @@ IModRepositoryBridge *OrganizerProxy::createNexusBridge() const QString OrganizerProxy::profileName() const { - return m_Proxied->profileName(); + if (m_Proxied) { + return m_Proxied->profileName(); + } else { + return {}; + } } QString OrganizerProxy::profilePath() const { - return m_Proxied->profilePath(); + if (m_Proxied) { + return m_Proxied->profilePath(); + } else { + return {}; + } } QString OrganizerProxy::downloadsPath() const { - return m_Proxied->downloadsPath(); + if (m_Proxied) { + return m_Proxied->downloadsPath(); + } else { + return {}; + } } QString OrganizerProxy::overwritePath() const { - return m_Proxied->overwritePath(); + if (m_Proxied) { + return m_Proxied->overwritePath(); + } else { + return {}; + } } QString OrganizerProxy::basePath() const { - return m_Proxied->basePath(); + if (m_Proxied) { + return m_Proxied->basePath(); + } else { + return {}; + } } QString OrganizerProxy::modsPath() const { - return m_Proxied->modsPath(); + if (m_Proxied) { + return m_Proxied->modsPath(); + } else { + return {}; + } } VersionInfo OrganizerProxy::appVersion() const { - return m_Proxied->appVersion(); + return createVersionInfo(); } IPluginGame *OrganizerProxy::getGame(const QString &gameName) const { - return m_Proxied->getGame(gameName); + if (m_Proxied) { + return m_Proxied->getGame(gameName); + } else { + return nullptr; + } } IModInterface *OrganizerProxy::createMod(MOBase::GuessedValue &name) { - return m_Proxied->createMod(name); + if (m_Proxied) { + return m_Proxied->createMod(name); + } else { + return nullptr; + } } void OrganizerProxy::modDataChanged(IModInterface *mod) { - m_Proxied->modDataChanged(mod); + if (m_Proxied) { + m_Proxied->modDataChanged(mod); + } } QVariant OrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const { - return m_Proxied->pluginSetting(pluginName, key); + if (m_Proxied) { + return m_Proxied->pluginSetting(pluginName, key); + } else { + return {}; + } } void OrganizerProxy::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { - m_Proxied->setPluginSetting(pluginName, key, value); + if (m_Proxied) { + m_Proxied->setPluginSetting(pluginName, key, value); + } } QVariant OrganizerProxy::persistent(const QString &pluginName, const QString &key, const QVariant &def) const { - return m_Proxied->persistent(pluginName, key, def); + if (m_Proxied) { + return m_Proxied->persistent(pluginName, key, def); + } else { + return {}; + } } void OrganizerProxy::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) { - m_Proxied->setPersistent(pluginName, key, value, sync); + if (m_Proxied) { + m_Proxied->setPersistent(pluginName, key, value, sync); + } } QString OrganizerProxy::pluginDataPath() const { - return m_Proxied->pluginDataPath(); + return OrganizerCore::pluginDataPath(); } HANDLE OrganizerProxy::startApplication( const QString& exe, const QStringList& args, const QString &cwd, const QString& profile, const QString &overwrite, bool ignoreOverwrite) { + if (!m_Proxied) { + return INVALID_HANDLE_VALUE; + } + log::debug( "a plugin has requested to start an application:\n" " . executable: '{}'\n" @@ -135,6 +193,10 @@ HANDLE OrganizerProxy::startApplication( bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { + if (!m_Proxied) { + return false; + } + const auto pid = ::GetProcessId(handle); log::debug( @@ -170,31 +232,53 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const void OrganizerProxy::refresh(bool saveChanges) { - m_Proxied->refresh(saveChanges); + if (m_Proxied) { + m_Proxied->refresh(saveChanges); + } } IModInterface *OrganizerProxy::installMod(const QString &fileName, const QString &nameSuggestion) { - return m_Proxied->installMod(fileName, false, nullptr, nameSuggestion); + if (m_Proxied) { + return m_Proxied->installMod(fileName, false, nullptr, nameSuggestion); + } else { + return nullptr; + } } QString OrganizerProxy::resolvePath(const QString &fileName) const { - return m_Proxied->resolvePath(fileName); + if (m_Proxied) { + return m_Proxied->resolvePath(fileName); + } else { + return {}; + } } QStringList OrganizerProxy::listDirectories(const QString &directoryName) const { - return m_Proxied->listDirectories(directoryName); + if (m_Proxied) { + return m_Proxied->listDirectories(directoryName); + } else { + return {}; + } } QStringList OrganizerProxy::findFiles(const QString &path, const std::function &filter) const { - return m_Proxied->findFiles(path, filter); + if (m_Proxied) { + return m_Proxied->findFiles(path, filter); + } else { + return {}; + } } QStringList OrganizerProxy::findFiles(const QString& path, const QStringList& globFilters) const { + if (!m_Proxied) { + return {}; + } + QList> patterns; for (auto& gfilter : globFilters) { patterns.append(GlobPattern(gfilter)); @@ -211,12 +295,20 @@ QStringList OrganizerProxy::findFiles(const QString& path, const QStringList& gl QStringList OrganizerProxy::getFileOrigins(const QString &fileName) const { - return m_Proxied->getFileOrigins(fileName); + if (m_Proxied) { + return m_Proxied->getFileOrigins(fileName); + } else { + return {}; + } } QList OrganizerProxy::findFileInfos(const QString &path, const std::function &filter) const { - return m_Proxied->findFileInfos(path, filter); + if (m_Proxied) { + return m_Proxied->findFileInfos(path, filter); + } else { + return {}; + } } MOBase::IDownloadManager *OrganizerProxy::downloadManager() const @@ -236,54 +328,94 @@ MOBase::IModList *OrganizerProxy::modList() const MOBase::IProfile *OrganizerProxy::profile() const { - return m_Proxied->currentProfile(); + if (m_Proxied) { + return m_Proxied->currentProfile(); + } else { + return nullptr; + } } MOBase::IPluginGame const *OrganizerProxy::managedGame() const { - return m_Proxied->managedGame(); + if (m_Proxied) { + return m_Proxied->managedGame(); + } else { + return nullptr; + } } // CALLBACKS bool OrganizerProxy::onAboutToRun(const std::function& func) { - return m_Proxied->onAboutToRun(MOShared::callIfPluginActive(this, func, true)); + if (m_Proxied) { + return m_Proxied->onAboutToRun(MOShared::callIfPluginActive(this, func, true)); + } else { + return false; + } } bool OrganizerProxy::onFinishedRun(const std::function& func) { - return m_Proxied->onFinishedRun(MOShared::callIfPluginActive(this, func)); + if (m_Proxied) { + return m_Proxied->onFinishedRun(MOShared::callIfPluginActive(this, func)); + } else { + return false; + } } bool OrganizerProxy::onUserInterfaceInitialized(std::function const& func) { - // Always call this one to allow plugin to initialize themselves even when not active: - return m_Proxied->onUserInterfaceInitialized(func); + if (m_Proxied) { + // Always call this one to allow plugin to initialize themselves even when not active: + return m_Proxied->onUserInterfaceInitialized(func); + } else { + return false; + } } bool OrganizerProxy::onProfileCreated(std::function const& func) { - return m_Proxied->onProfileCreated(MOShared::callIfPluginActive(this, func)); + if (m_Proxied) { + return m_Proxied->onProfileCreated(MOShared::callIfPluginActive(this, func)); + } else { + return false; + } } bool OrganizerProxy::onProfileRenamed(std::function const& func) { - return m_Proxied->onProfileRenamed(MOShared::callIfPluginActive(this, func)); + if (m_Proxied) { + return m_Proxied->onProfileRenamed(MOShared::callIfPluginActive(this, func)); + } else { + return false; + } } bool OrganizerProxy::onProfileRemoved(std::function const& func) { - return m_Proxied->onProfileRemoved(MOShared::callIfPluginActive(this, func)); + if (m_Proxied) { + return m_Proxied->onProfileRemoved(MOShared::callIfPluginActive(this, func)); + } else { + return false; + } } bool OrganizerProxy::onProfileChanged(std::function const& func) { - return m_Proxied->onProfileChanged(MOShared::callIfPluginActive(this, func)); + if (m_Proxied) { + return m_Proxied->onProfileChanged(MOShared::callIfPluginActive(this, func)); + } else { + return false; + } } bool OrganizerProxy::onPluginSettingChanged(std::function const& func) { - // Always call this one, otherwise plugin cannot detect they are being enabled / disabled: - return m_Proxied->onPluginSettingChanged(func); + if (m_Proxied) { + // Always call this one, otherwise plugin cannot detect they are being enabled / disabled: + return m_Proxied->onPluginSettingChanged(func); + } else { + return false; + } } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 0c71e491..3ad3da21 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -171,6 +171,22 @@ QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const bool PluginContainer::initPlugin(IPlugin *plugin) { + // when MO has no instance loaded, `init()` is not called on plugins, except + // for proxy plugins + // + // proxy plugins are given an OrganizerProxy that has a null OrganizerCore, + // so there's not a lot it can do, but it can return some information; the + // majority of its functions are no-ops + // + // so initPlugin() (this function) is called for all plugins except proxies, + // and does not call init() if m_Organizer is null; initProxyPlugin() below + // is called only for proxy plugins and will call init() with the crippled + // OrganizerProxy + // + // note that after proxies are initialized, instantiate() is called for all + // the plugins they've discovered, but as for regular plugins, init() won't be + // called on them if m_OrganizerCore is null + if (plugin == nullptr) { return false; } @@ -187,6 +203,22 @@ bool PluginContainer::initPlugin(IPlugin *plugin) return true; } +bool PluginContainer::initProxyPlugin(IPlugin *plugin) +{ + // see initPlugin() above for info + + if (plugin == nullptr) { + return false; + } + + if (!plugin->init(new OrganizerProxy(m_Organizer, this, plugin))) { + log::warn("proxy plugin failed to initialize"); + return false; + } + + return true; +} + void PluginContainer::registerGame(IPluginGame *game) { @@ -272,7 +304,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } { // proxy plugins IPluginProxy *proxy = qobject_cast(plugin); - if (initPlugin(proxy)) { + if (initProxyPlugin(proxy)) { bf::at_key(m_Plugins).push_back(proxy); QStringList pluginNames = proxy->pluginList( QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); diff --git a/src/plugincontainer.h b/src/plugincontainer.h index bfbf1fa8..2b39726b 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -169,9 +169,9 @@ private: bool initPlugin(MOBase::IPlugin *plugin); + bool initProxyPlugin(MOBase::IPlugin *plugin); void registerGame(MOBase::IPluginGame *game); bool registerPlugin(QObject *pluginObj, const QString &fileName); - bool unregisterPlugin(QObject *pluginObj, const QString &fileName); OrganizerCore *m_Organizer; -- cgit v1.3.1 From be9c39a3b47a8f93388eaf624b6d007eac22b68e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 09:05:45 -0500 Subject: pass null IOrganizer to proxy plugins instead of one with a null OrganizerCore removed now useless null checks in OrganizerProxy call setPluginDataPath() early so plugins can have it, since it's now a static function in IOrganizer --- src/main.cpp | 6 +- src/organizerproxy.cpp | 203 +++++++++--------------------------------------- src/plugincontainer.cpp | 24 +++--- 3 files changed, 51 insertions(+), 182 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 7b489aee..7005d374 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,10 +32,10 @@ along with Mod Organizer. If not, see . #include "env.h" #include "envmodule.h" #include "commandline.h" - #include "shared/util.h" #include "shared/appconfig.h" +#include #include #include #include @@ -730,6 +730,10 @@ int main(int argc, char *argv[]) InstanceManager::instance().overrideProfile(*cl.profile()); } + // makes plugin data path available to plugins, see + // IOrganizer::getPluginDataPath() + MOBase::details::setPluginDataPath(OrganizerCore::pluginDataPath()); + for (;;) { const auto r = doOneRun(cl, application, instance); diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 5fa1f4ef..a988ba9f 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -22,17 +22,10 @@ OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, PluginContainer* plugin : m_Proxied(organizer) , m_PluginContainer(pluginContainer) , m_Plugin(plugin) + , m_DownloadManagerProxy(std::make_unique(this, organizer->downloadManager())) + , m_ModListProxy(std::make_unique(this, organizer->modList())) + , m_PluginListProxy(std::make_unique(this, organizer->pluginList())) { - if (m_Proxied) { - m_DownloadManagerProxy = std::make_unique( - this, m_Proxied->downloadManager()); - - m_ModListProxy = std::make_unique( - this, m_Proxied->modList()); - - m_PluginListProxy = std::make_unique( - this, m_Proxied->pluginList()); - } } IModRepositoryBridge *OrganizerProxy::createNexusBridge() const @@ -42,118 +35,72 @@ IModRepositoryBridge *OrganizerProxy::createNexusBridge() const QString OrganizerProxy::profileName() const { - if (m_Proxied) { - return m_Proxied->profileName(); - } else { - return {}; - } + return m_Proxied->profileName(); } QString OrganizerProxy::profilePath() const { - if (m_Proxied) { - return m_Proxied->profilePath(); - } else { - return {}; - } + return m_Proxied->profilePath(); } QString OrganizerProxy::downloadsPath() const { - if (m_Proxied) { - return m_Proxied->downloadsPath(); - } else { - return {}; - } + return m_Proxied->downloadsPath(); } QString OrganizerProxy::overwritePath() const { - if (m_Proxied) { - return m_Proxied->overwritePath(); - } else { - return {}; - } + return m_Proxied->overwritePath(); } QString OrganizerProxy::basePath() const { - if (m_Proxied) { - return m_Proxied->basePath(); - } else { - return {}; - } + return m_Proxied->basePath(); } QString OrganizerProxy::modsPath() const { - if (m_Proxied) { - return m_Proxied->modsPath(); - } else { - return {}; - } + return m_Proxied->modsPath(); } VersionInfo OrganizerProxy::appVersion() const { - return createVersionInfo(); + return m_Proxied->appVersion(); } IPluginGame *OrganizerProxy::getGame(const QString &gameName) const { - if (m_Proxied) { - return m_Proxied->getGame(gameName); - } else { - return nullptr; - } + return m_Proxied->getGame(gameName); } IModInterface *OrganizerProxy::createMod(MOBase::GuessedValue &name) { - if (m_Proxied) { - return m_Proxied->createMod(name); - } else { - return nullptr; - } + return m_Proxied->createMod(name); } void OrganizerProxy::modDataChanged(IModInterface *mod) { - if (m_Proxied) { - m_Proxied->modDataChanged(mod); - } + m_Proxied->modDataChanged(mod); } QVariant OrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const { - if (m_Proxied) { - return m_Proxied->pluginSetting(pluginName, key); - } else { - return {}; - } + return m_Proxied->pluginSetting(pluginName, key); } void OrganizerProxy::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { - if (m_Proxied) { - m_Proxied->setPluginSetting(pluginName, key, value); - } + m_Proxied->setPluginSetting(pluginName, key, value); } QVariant OrganizerProxy::persistent(const QString &pluginName, const QString &key, const QVariant &def) const { - if (m_Proxied) { - return m_Proxied->persistent(pluginName, key, def); - } else { - return {}; - } + return m_Proxied->persistent(pluginName, key, def); } void OrganizerProxy::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) { - if (m_Proxied) { - m_Proxied->setPersistent(pluginName, key, value, sync); - } + m_Proxied->setPersistent(pluginName, key, value, sync); } QString OrganizerProxy::pluginDataPath() const @@ -165,10 +112,6 @@ HANDLE OrganizerProxy::startApplication( const QString& exe, const QStringList& args, const QString &cwd, const QString& profile, const QString &overwrite, bool ignoreOverwrite) { - if (!m_Proxied) { - return INVALID_HANDLE_VALUE; - } - log::debug( "a plugin has requested to start an application:\n" " . executable: '{}'\n" @@ -193,10 +136,6 @@ HANDLE OrganizerProxy::startApplication( bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - if (!m_Proxied) { - return false; - } - const auto pid = ::GetProcessId(handle); log::debug( @@ -232,53 +171,31 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const void OrganizerProxy::refresh(bool saveChanges) { - if (m_Proxied) { - m_Proxied->refresh(saveChanges); - } + m_Proxied->refresh(saveChanges); } IModInterface *OrganizerProxy::installMod(const QString &fileName, const QString &nameSuggestion) { - if (m_Proxied) { - return m_Proxied->installMod(fileName, false, nullptr, nameSuggestion); - } else { - return nullptr; - } + return m_Proxied->installMod(fileName, false, nullptr, nameSuggestion); } QString OrganizerProxy::resolvePath(const QString &fileName) const { - if (m_Proxied) { - return m_Proxied->resolvePath(fileName); - } else { - return {}; - } + return m_Proxied->resolvePath(fileName); } QStringList OrganizerProxy::listDirectories(const QString &directoryName) const { - if (m_Proxied) { - return m_Proxied->listDirectories(directoryName); - } else { - return {}; - } + return m_Proxied->listDirectories(directoryName); } QStringList OrganizerProxy::findFiles(const QString &path, const std::function &filter) const { - if (m_Proxied) { - return m_Proxied->findFiles(path, filter); - } else { - return {}; - } + return m_Proxied->findFiles(path, filter); } QStringList OrganizerProxy::findFiles(const QString& path, const QStringList& globFilters) const { - if (!m_Proxied) { - return {}; - } - QList> patterns; for (auto& gfilter : globFilters) { patterns.append(GlobPattern(gfilter)); @@ -295,20 +212,12 @@ QStringList OrganizerProxy::findFiles(const QString& path, const QStringList& gl QStringList OrganizerProxy::getFileOrigins(const QString &fileName) const { - if (m_Proxied) { - return m_Proxied->getFileOrigins(fileName); - } else { - return {}; - } + return m_Proxied->getFileOrigins(fileName); } QList OrganizerProxy::findFileInfos(const QString &path, const std::function &filter) const { - if (m_Proxied) { - return m_Proxied->findFileInfos(path, filter); - } else { - return {}; - } + return m_Proxied->findFileInfos(path, filter); } MOBase::IDownloadManager *OrganizerProxy::downloadManager() const @@ -328,94 +237,54 @@ MOBase::IModList *OrganizerProxy::modList() const MOBase::IProfile *OrganizerProxy::profile() const { - if (m_Proxied) { - return m_Proxied->currentProfile(); - } else { - return nullptr; - } + return m_Proxied->currentProfile(); } MOBase::IPluginGame const *OrganizerProxy::managedGame() const { - if (m_Proxied) { - return m_Proxied->managedGame(); - } else { - return nullptr; - } + return m_Proxied->managedGame(); } // CALLBACKS bool OrganizerProxy::onAboutToRun(const std::function& func) { - if (m_Proxied) { - return m_Proxied->onAboutToRun(MOShared::callIfPluginActive(this, func, true)); - } else { - return false; - } + return m_Proxied->onAboutToRun(MOShared::callIfPluginActive(this, func, true)); } bool OrganizerProxy::onFinishedRun(const std::function& func) { - if (m_Proxied) { - return m_Proxied->onFinishedRun(MOShared::callIfPluginActive(this, func)); - } else { - return false; - } + return m_Proxied->onFinishedRun(MOShared::callIfPluginActive(this, func)); } bool OrganizerProxy::onUserInterfaceInitialized(std::function const& func) { - if (m_Proxied) { - // Always call this one to allow plugin to initialize themselves even when not active: - return m_Proxied->onUserInterfaceInitialized(func); - } else { - return false; - } + // Always call this one to allow plugin to initialize themselves even when not active: + return m_Proxied->onUserInterfaceInitialized(func); } bool OrganizerProxy::onProfileCreated(std::function const& func) { - if (m_Proxied) { - return m_Proxied->onProfileCreated(MOShared::callIfPluginActive(this, func)); - } else { - return false; - } + return m_Proxied->onProfileCreated(MOShared::callIfPluginActive(this, func)); } bool OrganizerProxy::onProfileRenamed(std::function const& func) { - if (m_Proxied) { - return m_Proxied->onProfileRenamed(MOShared::callIfPluginActive(this, func)); - } else { - return false; - } + return m_Proxied->onProfileRenamed(MOShared::callIfPluginActive(this, func)); } bool OrganizerProxy::onProfileRemoved(std::function const& func) { - if (m_Proxied) { - return m_Proxied->onProfileRemoved(MOShared::callIfPluginActive(this, func)); - } else { - return false; - } + return m_Proxied->onProfileRemoved(MOShared::callIfPluginActive(this, func)); } bool OrganizerProxy::onProfileChanged(std::function const& func) { - if (m_Proxied) { - return m_Proxied->onProfileChanged(MOShared::callIfPluginActive(this, func)); - } else { - return false; - } + return m_Proxied->onProfileChanged(MOShared::callIfPluginActive(this, func)); } bool OrganizerProxy::onPluginSettingChanged(std::function const& func) { - if (m_Proxied) { - // Always call this one, otherwise plugin cannot detect they are being enabled / disabled: - return m_Proxied->onPluginSettingChanged(func); - } else { - return false; - } + // Always call this one, otherwise plugin cannot detect they are being enabled / disabled: + return m_Proxied->onPluginSettingChanged(func); } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 3ad3da21..a571a0aa 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -171,20 +171,11 @@ QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const bool PluginContainer::initPlugin(IPlugin *plugin) { - // when MO has no instance loaded, `init()` is not called on plugins, except - // for proxy plugins + // when MO has no instance loaded, init() is not called on plugins, except + // for proxy plugins, where init() is called with a null IOrganizer // - // proxy plugins are given an OrganizerProxy that has a null OrganizerCore, - // so there's not a lot it can do, but it can return some information; the - // majority of its functions are no-ops - // - // so initPlugin() (this function) is called for all plugins except proxies, - // and does not call init() if m_Organizer is null; initProxyPlugin() below - // is called only for proxy plugins and will call init() with the crippled - // OrganizerProxy - // - // note that after proxies are initialized, instantiate() is called for all - // the plugins they've discovered, but as for regular plugins, init() won't be + // after proxies are initialized, instantiate() is called for all the plugins + // they've discovered, but as for regular plugins, init() won't be // called on them if m_OrganizerCore is null if (plugin == nullptr) { @@ -211,7 +202,12 @@ bool PluginContainer::initProxyPlugin(IPlugin *plugin) return false; } - if (!plugin->init(new OrganizerProxy(m_Organizer, this, plugin))) { + IOrganizer* proxy = nullptr; + if (m_Organizer) { + proxy = new OrganizerProxy(m_Organizer, this, plugin); + } + + if (!plugin->init(proxy)) { log::warn("proxy plugin failed to initialize"); return false; } -- cgit v1.3.1 From ad8e9d99b30578676c15d10a74f010439e132406 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 09:27:15 -0500 Subject: renamed InstanceManager::instance() to singleton() to avoid confusion --- src/createinstancedialog.cpp | 6 +++--- src/createinstancedialogpages.cpp | 18 +++++++++--------- src/envshortcut.cpp | 2 +- src/instancemanager.cpp | 2 +- src/instancemanager.h | 2 +- src/instancemanagerdialog.cpp | 20 ++++++++++---------- src/main.cpp | 14 +++++++------- src/mainwindow.cpp | 2 +- src/processrunner.cpp | 2 +- src/statusbar.cpp | 2 +- 10 files changed, 35 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index c976ee13..2ff8adf3 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -244,7 +244,7 @@ void CreateInstanceDialog::finish() ui->creationLog->clear(); logCreation(tr("Creating instance...")); - const auto& m = InstanceManager::instance(); + const auto& m = InstanceManager::singleton(); const auto ci = creationInfo(); auto logger = [&](QString s) { @@ -332,7 +332,7 @@ void CreateInstanceDialog::finish() } if (ui->launch->isChecked()) { - InstanceManager::instance().setCurrentInstance(ci.instanceName); + InstanceManager::singleton().setCurrentInstance(ci.instanceName); if (m_settings) { // don't restart without settings, it happens on startup when there are @@ -445,7 +445,7 @@ QString CreateInstanceDialog::dataPath() const if (instanceType() == Portable) { s = QDir(InstanceManager::portablePath()).absolutePath(); } else { - s = InstanceManager::instance().instancePath(instanceName()); + s = InstanceManager::singleton().instancePath(instanceName()); } return QDir::toNativeSeparators(s); diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 00d49b99..26f2f61d 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -136,13 +136,13 @@ TypePage::TypePage(CreateInstanceDialog& dlg) { ui->createGlobal->setDescription( ui->createGlobal->description() - .arg(InstanceManager::instance().instancesPath())); + .arg(InstanceManager::singleton().instancesPath())); ui->createPortable->setDescription( ui->createPortable->description() .arg(InstanceManager::portablePath())); - if (InstanceManager::instance().portableInstanceExists()) { + if (InstanceManager::singleton().portableInstanceExists()) { ui->createPortable->setEnabled(false); ui->portableExistsLabel->setVisible(true); } else { @@ -722,7 +722,7 @@ void NamePage::activated() m_label.setText(g->gameName()); if (!m_modified || ui->instanceName->text().isEmpty()) { - const auto n = InstanceManager::instance().makeUniqueName(g->gameName()); + const auto n = InstanceManager::singleton().makeUniqueName(g->gameName()); ui->instanceName->setText(n); m_modified = false; } @@ -737,7 +737,7 @@ QString NamePage::selectedInstanceName() const } const auto text = ui->instanceName->text().trimmed(); - return InstanceManager::instance().sanitizeInstanceName(text); + return InstanceManager::singleton().sanitizeInstanceName(text); } void NamePage::onChanged() @@ -748,7 +748,7 @@ void NamePage::onChanged() void NamePage::updateWarnings() { - const auto root = InstanceManager::instance().instancesPath(); + const auto root = InstanceManager::singleton().instancesPath(); m_okay = checkName(root, ui->instanceName->text()); updateNavigation(); @@ -765,7 +765,7 @@ bool NamePage::checkName(QString parentDir, QString name) if (name.isEmpty()) { empty = true; } else { - if (InstanceManager::instance().validInstanceName(name)) { + if (InstanceManager::singleton().validInstanceName(name)) { exists = QDir(parentDir).exists(name); } else { invalid = true; @@ -901,7 +901,7 @@ void PathsPage::setPaths(const QString& name, bool force) if (m_dlg.instanceType() == CreateInstanceDialog::Portable) { path = InstanceManager::portablePath(); } else { - const auto root = InstanceManager::instance().instancesPath(); + const auto root = InstanceManager::singleton().instancesPath(); path = root + "/" + name; } @@ -938,11 +938,11 @@ bool PathsPage::checkPath( } else { const QDir d(path); - if (InstanceManager::instance().validInstanceName(d.dirName())) { + if (InstanceManager::singleton().validInstanceName(d.dirName())) { if (m_dlg.instanceType() == 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::instance().portablePath()) { + if (QDir(path) != InstanceManager::singleton().portablePath()) { exists = QDir(path).exists(); } } else { diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 5222665b..b13a7d9f 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -149,7 +149,7 @@ Shortcut::Shortcut(const Executable& exe) m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); m_arguments = QString("\"moshortcut://%1:%2\"") - .arg(InstanceManager::instance().currentInstance()->name()) + .arg(InstanceManager::singleton().currentInstance()->name()) .arg(exe.title()); m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 61c442be..8163149b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -290,7 +290,7 @@ InstanceManager::InstanceManager() GlobalSettings::updateRegistryKey(); } -InstanceManager &InstanceManager::instance() +InstanceManager &InstanceManager::singleton() { static InstanceManager s_Instance; return s_Instance; diff --git a/src/instancemanager.h b/src/instancemanager.h index 69536650..79f1f30b 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -54,7 +54,7 @@ private: class InstanceManager { public: - static InstanceManager &instance(); + static InstanceManager& singleton(); void overrideInstance(const QString& instanceName); void overrideProfile(const QString& profileName); diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 282329a5..3b6daeb8 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -101,7 +101,7 @@ public: QIcon icon(const PluginContainer& plugins) const { - const auto* game = InstanceManager::instance().gamePluginForDirectory( + const auto* game = InstanceManager::singleton().gamePluginForDirectory( m_dir, plugins); if (game) @@ -119,7 +119,7 @@ public: bool isActive() const { - auto& m = InstanceManager::instance(); + auto& m = InstanceManager::singleton(); if (auto i=m.currentInstance()) { @@ -347,7 +347,7 @@ InstanceManagerDialog::InstanceManagerDialog( void InstanceManagerDialog::updateInstances() { - auto& m = InstanceManager::instance(); + auto& m = InstanceManager::singleton(); m_instances.clear(); @@ -437,7 +437,7 @@ void InstanceManagerDialog::select(const QString& name) void InstanceManagerDialog::selectActiveInstance() { - const auto active = InstanceManager::instance().currentInstance(); + const auto active = InstanceManager::singleton().currentInstance(); if (active) { for (std::size_t i=0; iisPortable()) { - InstanceManager::instance().setCurrentInstance(""); + InstanceManager::singleton().setCurrentInstance(""); } else { - InstanceManager::instance().setCurrentInstance(m_instances[i]->name()); + InstanceManager::singleton().setCurrentInstance(m_instances[i]->name()); } if (m_restartOnSelect) { @@ -479,7 +479,7 @@ QString getInstanceName( QWidget* parent, const QString& title, const QString& moreText, const QString& label, const QString& oldName={}) { - auto& m = InstanceManager::instance(); + auto& m = InstanceManager::singleton(); QDialog dlg(parent); dlg.setWindowTitle(title); @@ -554,7 +554,7 @@ void InstanceManagerDialog::rename() const auto selIndex = singleSelectionIndex(); - auto& m = InstanceManager::instance(); + auto& m = InstanceManager::singleton(); if (i->isActive()) { QMessageBox::information(this, tr("Rename instance"), tr("The active instance cannot be renamed.")); @@ -622,7 +622,7 @@ void InstanceManagerDialog::deleteInstance() return; } - auto& m = InstanceManager::instance(); + auto& m = InstanceManager::singleton(); if (i->isActive()) { QMessageBox::information(this, tr("Deleting instance"), tr("The active instance cannot be deleted.")); @@ -800,7 +800,7 @@ void InstanceManagerDialog::fillData(const InstanceInfo& ii) ui->gameDir->setText(ii.gamePath()); setButtonsEnabled(true); - const auto& m = InstanceManager::instance(); + const auto& m = InstanceManager::singleton(); ui->rename->setEnabled(!ii.isPortable()); diff --git a/src/main.cpp b/src/main.cpp index 7005d374..e56197c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -274,7 +274,7 @@ std::optional handleCommandLine( std::optional selectInstance() { - auto& m = InstanceManager::instance(); + auto& m = InstanceManager::singleton(); NexusInterface ni(nullptr); PluginContainer pc(nullptr); @@ -505,7 +505,7 @@ int runApplication( OrganizerCore organizer(settings); if (!organizer.bootstrap()) { reportError("failed to set up data paths"); - InstanceManager::instance().clearCurrentInstance(); + InstanceManager::singleton().clearCurrentInstance(); return 1; } @@ -522,7 +522,7 @@ int runApplication( } else if (setupResult == SetupInstanceResults::TryAgain) { continue; } else if (setupResult == SetupInstanceResults::SelectAnother) { - InstanceManager::instance().clearCurrentInstance(); + InstanceManager::singleton().clearCurrentInstance(); return RestartExitCode; } else { return 1; @@ -650,7 +650,7 @@ int doOneRun( // resets things when MO is "restarted" resetForRestart(cl); - auto& m = InstanceManager::instance(); + auto& m = InstanceManager::singleton(); auto currentInstance = m.currentInstance(); if (!currentInstance) @@ -689,7 +689,7 @@ int doOneRun( if (!setLogDirectory(dataPath)) { reportError("Failed to create log folder"); - InstanceManager::instance().clearCurrentInstance(); + InstanceManager::singleton().clearCurrentInstance(); return 1; } @@ -724,10 +724,10 @@ int main(int argc, char *argv[]) tt.stop(); if (cl.instance()) - InstanceManager::instance().overrideInstance(*cl.instance()); + InstanceManager::singleton().overrideInstance(*cl.instance()); if (cl.profile()) { - InstanceManager::instance().overrideProfile(*cl.profile()); + InstanceManager::singleton().overrideProfile(*cl.profile()); } // makes plugin data path available to plugins, see diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 02900571..ea8a0efe 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -837,7 +837,7 @@ void MainWindow::setupToolbar() log::warn("no separator found on the toolbar, icons won't be right-aligned"); } - if (!InstanceManager::instance().allowedToChangeInstance()) { + if (!InstanceManager::singleton().allowedToChangeInstance()) { ui->actionChange_Game->setVisible(false); } } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 8ee0914b..bc4e6227 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -586,7 +586,7 @@ ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe) ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut) { - const auto currentInstance = InstanceManager::instance().currentInstance(); + const auto currentInstance = InstanceManager::singleton().currentInstance(); if (currentInstance) { diff --git a/src/statusbar.cpp b/src/statusbar.cpp index aefabc73..8fa43d5b 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -154,7 +154,7 @@ void StatusBar::updateNormalMessage(OrganizerCore& core) } QString instance = "?"; - if (auto i=InstanceManager::instance().currentInstance()) + if (auto i=InstanceManager::singleton().currentInstance()) instance = i->name(); QString profile = core.profileName(); -- cgit v1.3.1