From 2aae65d6f6c1ab3309e54437a339d1644088c5c8 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 10 Jan 2016 20:19:56 +0100 Subject: made instance-switching usable this includes tons and tons of changes to how paths are determined. the profiles-dir can now also be configured --- src/CMakeLists.txt | 22 +-- src/installationmanager.cpp | 2 +- src/instancemanager.cpp | 206 ++++++++++++++++++++++++++++ src/instancemanager.h | 62 +++++++++ src/logbuffer.cpp | 120 ++++++++-------- src/main.cpp | 323 ++++++++++++++++++++++---------------------- src/mainwindow.cpp | 26 ++-- src/mainwindow.h | 7 +- src/organizercore.cpp | 22 ++- src/organizercore.h | 2 + src/profile.cpp | 7 +- src/profilesdialog.cpp | 6 +- src/selectiondialog.cpp | 5 + src/selectiondialog.h | 2 + src/selfupdater.cpp | 2 +- src/settings.cpp | 274 +++++++++++++++++++++---------------- src/settings.h | 20 ++- src/settingsdialog.cpp | 24 +++- src/settingsdialog.h | 3 + src/settingsdialog.ui | 242 +++++++++++++++++++-------------- 20 files changed, 897 insertions(+), 480 deletions(-) create mode 100644 src/instancemanager.cpp create mode 100644 src/instancemanager.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7cd66739..fda85b76 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,11 +35,11 @@ SET(organizer_SRCS modlist.cpp modinfodialog.cpp modinfo.cpp - modinfobackup.cpp - modinfoforeign.cpp - modinfooverwrite.cpp - modinforegular.cpp - modinfowithconflictinfo.cpp + modinfobackup.cpp + modinfoforeign.cpp + modinfooverwrite.cpp + modinforegular.cpp + modinfowithconflictinfo.cpp messagedialog.cpp mainwindow.cpp main.cpp @@ -87,6 +87,7 @@ SET(organizer_SRCS viewmarkingscrollbar.cpp plugincontainer.cpp organizercore.cpp + instancemanager.cpp usvfsconnector.cpp shared/inject.cpp @@ -131,11 +132,11 @@ SET(organizer_HDRS modlist.h modinfodialog.h modinfo.h - modinfobackup.h - modinfoforeign.h - modinfooverwrite.h - modinforegular.h - modinfowithconflictinfo.h + modinfobackup.h + modinfoforeign.h + modinfooverwrite.h + modinforegular.h + modinfowithconflictinfo.h messagedialog.h mainwindow.h loghighlighter.h @@ -183,6 +184,7 @@ SET(organizer_HDRS plugincontainer.h organizercore.h iuserinterface.h + instancemanager.h usvfsconnector.h shared/inject.h diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 739948cd..58565489 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -78,7 +78,7 @@ InstallationManager::InstallationManager() : m_ParentWidget(nullptr) , m_SupportedExtensions({ "zip", "rar", "7z", "fomod", "001" }) { - QLibrary archiveLib("dlls\\archive.dll"); + QLibrary archiveLib(QCoreApplication::applicationDirPath() + "\\dlls\\archive.dll"); if (!archiveLib.load()) { throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); } diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp new file mode 100644 index 00000000..48c12d42 --- /dev/null +++ b/src/instancemanager.cpp @@ -0,0 +1,206 @@ +/* +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 . +*/ + + +#include "instancemanager.h" +#include "selectiondialog.h" +#include +#include +#include +#include +#include +#include +#include + + +static const char COMPANY_NAME[] = "Tannin"; +static const char APPLICATION_NAME[] = "Mod Organizer"; +static const char INSTANCE_KEY[] = "CurrentInstance"; + + + +InstanceManager::InstanceManager() + : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) +{ +} + + +QString InstanceManager::currentInstance() const +{ + return m_AppSettings.value(INSTANCE_KEY, "").toString(); +} + +void InstanceManager::clearCurrentInstance() +{ + setCurrentInstance(""); +} + +void InstanceManager::setCurrentInstance(const QString &name) +{ + m_AppSettings.setValue(INSTANCE_KEY, name); +} + + +QString InstanceManager::queryInstanceName() const +{ + QString instanceId; + while (instanceId.isEmpty()) { + QInputDialog dialog; + // 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({ "Oblivion", "Skyrim", "Fallout 3", + "Fallout NV", "Fallout 4" }); + dialog.setComboBoxEditable(true); + dialog.setWindowTitle(QObject::tr("Enter Instance Name")); + dialog.setLabelText(QObject::tr("Name")); + if (dialog.exec() == QDialog::Rejected) { + throw MOBase::MyException(QObject::tr("Canceled")); + } + instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), ""); + } + return instanceId; +} + + +QString InstanceManager::chooseInstance(const QStringList &instanceList) const +{ + SelectionDialog selection(QObject::tr("Choose Instance"), nullptr); + selection.disableCancel(); + for (const QString &instance : instanceList) { + selection.addChoice(instance, "", instance); + } + + selection.addChoice(QObject::tr("New"), + QObject::tr("Create a new instance."), + ""); + if (selection.exec() == QDialog::Rejected) { + qDebug("rejected"); + throw MOBase::MyException(QObject::tr("Canceled")); + } + + QString choice = selection.getChoiceData().toString(); + + if (choice.isEmpty()) { + return queryInstanceName(); + } else { + return choice; + } +} + + +QString InstanceManager::instancePath() const +{ + return QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation)); +} + + +QStringList InstanceManager::instances() const +{ + return QDir(instancePath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot); +} + + +bool InstanceManager::portableInstall() const +{ + return QFile::exists(qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::iniFileName())); +} + + +InstanceManager::InstallationMode InstanceManager::queryInstallMode() const +{ + SelectionDialog selection(QObject::tr("Installation Mode"), nullptr); + selection.disableCancel(); + selection.addChoice(QObject::tr("Portable"), + QObject::tr("Everything in one directory, only one game per installation."), + 0); + selection.addChoice(QObject::tr("Regular"), + QObject::tr("Data in separate directory, multiple games supported."), + 1); + if (selection.exec() == QDialog::Rejected) { + throw MOBase::MyException(QObject::tr("Canceled")); + } + + switch (selection.getChoiceData().toInt()) { + case 0: return InstallationMode::PORTABLE; + default: return InstallationMode::REGULAR; + } +} + + +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 (instanceId.isEmpty() && !portableInstall()) { + // no portable install and no selected instance + + QStringList instanceList = instances(); + + qDebug("%d - %s", instanceList.size(), qPrintable(instanceList.join(";"))); + + if (instanceList.size() == 0) { + switch (queryInstallMode()) { + case InstallationMode::PORTABLE: { + instanceId = QString(); + } break; + case InstallationMode::REGULAR: { + instanceId = queryInstanceName(); + } break; + } + } else { + instanceId = chooseInstance(instanceList); + } + } + + if (instanceId.isEmpty()) { + qDebug("portable mode"); + return qApp->applicationDirPath(); + } else { + setCurrentInstance(instanceId); + + QString dataPath = QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation) + + "/" + instanceId); + + createDataPath(dataPath); + + return dataPath; + } +} + diff --git a/src/instancemanager.h b/src/instancemanager.h new file mode 100644 index 00000000..2e17d1c0 --- /dev/null +++ b/src/instancemanager.h @@ -0,0 +1,62 @@ +/* +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 + + +#include +#include + + +class InstanceManager { + + enum class InstallationMode { + PORTABLE, + REGULAR + }; + +public: + + InstanceManager(); + + QString determineDataPath(); + QStringList instances() const; + void clearCurrentInstance(); + +private: + + QString currentInstance() const; + QString instancePath() const; + + bool portableInstall() const; + + void setCurrentInstance(const QString &name); + + QString queryInstanceName() const; + QString chooseInstance(const QStringList &instanceList) const; + + void createDataPath(const QString &dataPath) const; + InstallationMode queryInstallMode() const; + +private: + + QSettings m_AppSettings; + +}; diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index 2c3fc101..01ff2b87 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -26,37 +26,33 @@ along with Mod Organizer. If not, see . #include #include - using MOBase::reportError; QScopedPointer LogBuffer::s_Instance; QMutex LogBuffer::s_Mutex; - -LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName) - : QAbstractItemModel(nullptr), m_OutFileName(outputFileName), m_ShutDown(false), - m_MinMsgType(minMsgType), m_NumMessages(0) +LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, + const QString &outputFileName) + : QAbstractItemModel(nullptr) + , m_OutFileName(outputFileName) + , m_ShutDown(false) + , m_MinMsgType(minMsgType) + , m_NumMessages(0) { m_Messages.resize(messageCount); } LogBuffer::~LogBuffer() { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + qDebug("closing log buffer"); qInstallMessageHandler(0); -#else - qInstallMsgHandler(0); -#endif -// if (!m_ShutDown) { - write(); -// } + write(); } - void LogBuffer::logMessage(QtMsgType type, const QString &message) { if (type >= m_MinMsgType) { - Message msg = { type, QTime::currentTime(), message }; + Message msg = {type, QTime::currentTime(), message}; if (m_NumMessages < m_Messages.size()) { beginInsertRows(QModelIndex(), m_NumMessages, m_NumMessages + 1); } @@ -73,7 +69,6 @@ void LogBuffer::logMessage(QtMsgType type, const QString &message) } } - void LogBuffer::write() const { if (m_NumMessages == 0) { @@ -84,12 +79,15 @@ void LogBuffer::write() const QFile file(m_OutFileName); if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write log to %1: %2").arg(m_OutFileName).arg(file.errorString())); + reportError(tr("failed to write log to %1: %2") + .arg(m_OutFileName) + .arg(file.errorString())); return; } - unsigned int i = (m_NumMessages > m_Messages.size()) ? m_NumMessages - m_Messages.size() - : 0U; + unsigned int i = (m_NumMessages > m_Messages.size()) + ? m_NumMessages - m_Messages.size() + : 0U; for (; i < m_NumMessages; ++i) { file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8()); file.write("\r\n"); @@ -97,68 +95,68 @@ void LogBuffer::write() const ::SetLastError(lastError); } - -void LogBuffer::init(int messageCount, QtMsgType minMsgType, const QString &outputFileName) +void LogBuffer::init(int messageCount, QtMsgType minMsgType, + const QString &outputFileName) { QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance.reset(); - } s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) qInstallMessageHandler(LogBuffer::log); -#else - qInstallMsgHandler(LogBuffer::log); -#endif } char LogBuffer::msgTypeID(QtMsgType type) { switch (type) { - case QtDebugMsg: return 'D'; - case QtWarningMsg: return 'W'; - case QtCriticalMsg: return 'C'; - case QtFatalMsg: return 'F'; - default: return '?'; + case QtDebugMsg: + return 'D'; + case QtWarningMsg: + return 'W'; + case QtCriticalMsg: + return 'C'; + case QtFatalMsg: + return 'F'; + default: + return '?'; } } -void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, const QString &message) +void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, + const QString &message) { // QMutexLocker doesn't support timeout... if (!s_Mutex.tryLock(100)) { fprintf(stderr, "failed to log: %s", qPrintable(message)); return; } - ON_BLOCK_EXIT([] () { - s_Mutex.unlock(); - }); + ON_BLOCK_EXIT([]() { s_Mutex.unlock(); }); if (!s_Instance.isNull()) { s_Instance->logMessage(type, message); } -// fprintf(stdout, "(%s:%u) %s\n", context.file, context.line, qPrintable(message)); if (type == QtDebugMsg) { - fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message)); + fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), + msgTypeID(type), qPrintable(message)); } else { if (context.line != 0) { - fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), + fprintf(stdout, "%s [%c] (%s:%u) %s\n", + qPrintable(QTime::currentTime().toString()), msgTypeID(type), context.file, context.line, qPrintable(message)); } else { - fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message)); + fprintf(stdout, "%s [%c] %s\n", + qPrintable(QTime::currentTime().toString()), msgTypeID(type), + qPrintable(message)); } } fflush(stdout); } -QModelIndex LogBuffer::index(int row, int column, const QModelIndex&) const +QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const { return createIndex(row, column, row); } -QModelIndex LogBuffer::parent(const QModelIndex&) const +QModelIndex LogBuffer::parent(const QModelIndex &) const { return QModelIndex(); } @@ -171,16 +169,16 @@ int LogBuffer::rowCount(const QModelIndex &parent) const return std::min(m_NumMessages, m_Messages.size()); } -int LogBuffer::columnCount(const QModelIndex&) const +int LogBuffer::columnCount(const QModelIndex &) const { return 2; } - QVariant LogBuffer::data(const QModelIndex &index, int role) const { - unsigned offset = m_NumMessages < m_Messages.size() ? 0 - : m_NumMessages - m_Messages.size(); + unsigned offset = m_NumMessages < m_Messages.size() + ? 0 + : m_NumMessages - m_Messages.size(); unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size(); switch (role) { case Qt::DisplayRole: { @@ -198,20 +196,28 @@ QVariant LogBuffer::data(const QModelIndex &index, int role) const case Qt::DecorationRole: { if (index.column() == 1) { switch (m_Messages[msgIndex].type) { - case QtDebugMsg: return QIcon(":/MO/gui/information"); - case QtWarningMsg: return QIcon(":/MO/gui/warning"); - case QtCriticalMsg: return QIcon(":/MO/gui/important"); - case QtFatalMsg: return QIcon(":/MO/gui/problem"); + case QtDebugMsg: + return QIcon(":/MO/gui/information"); + case QtWarningMsg: + return QIcon(":/MO/gui/warning"); + case QtCriticalMsg: + return QIcon(":/MO/gui/important"); + case QtFatalMsg: + return QIcon(":/MO/gui/problem"); } } } break; case Qt::UserRole: { if (index.column() == 1) { switch (m_Messages[msgIndex].type) { - case QtDebugMsg: return "D"; - case QtWarningMsg: return "W"; - case QtCriticalMsg: return "C"; - case QtFatalMsg: return "F"; + case QtDebugMsg: + return "D"; + case QtWarningMsg: + return "W"; + case QtCriticalMsg: + return "C"; + case QtFatalMsg: + return "F"; } } } break; @@ -227,7 +233,6 @@ void LogBuffer::writeNow() } } - void LogBuffer::cleanQuit() { QMutexLocker guard(&s_Mutex); @@ -255,5 +260,8 @@ void log(const char *format, ...) QString LogBuffer::Message::toString() const { - return QString("%1 [%2] %3").arg(time.toString()).arg(msgTypeID(type)).arg(message); + return QString("%1 [%2] %3") + .arg(time.toString()) + .arg(msgTypeID(type)) + .arg(message); } diff --git a/src/main.cpp b/src/main.cpp index 30938485..a262d031 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,6 +45,8 @@ along with Mod Organizer. If not, see . #include "moapplication.h" #include "tutorialmanager.h" #include "nxmaccessmanager.h" +#include "instancemanager.h" + #include #include @@ -84,6 +86,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; + bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); @@ -135,43 +138,6 @@ bool bootstrap() return true; } -void cleanupDir() -{ - // files from previous versions of MO that are no longer - // required (in that location) - QStringList fileNames { - "imageformats/", - "loot/resources/", - "plugins/previewDDS.dll", - "dlls/boost_python-vc100-mt-1_55.dll", - "dlls/QtCore4.dll", - "dlls/QtDeclarative4.dll", - "dlls/QtGui4.dll", - "dlls/QtNetwork4.dll", - "dlls/QtOpenGL4.dll", - "dlls/QtScript4.dll", - "dlls/QtSql4.dll", - "dlls/QtSvg4.dll", - "dlls/QtWebKit4.dll", - "dlls/QtXml4.dll", - "dlls/QtXmlPatterns4.dll", - "msvcp100.dll", - "msvcr100.dll", - "proxy.dll" - }; - - for (const QString &fileName : fileNames) { - QString fullPath = qApp->applicationDirPath() + "/" + fileName; - if (QFile::exists(fullPath)) { - if (shellDelete(QStringList(fullPath), true)) { - qDebug("removed obsolete file %s", qPrintable(fullPath)); - } else { - qDebug("failed to remove obsolete %s", qPrintable(fullPath)); - } - } - } -} - bool isNxmLink(const QString &link) { @@ -195,6 +161,10 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except if (dbgDLL) { FuncMiniDumpWriteDump funcDump = (FuncMiniDumpWriteDump)::GetProcAddress(dbgDLL, "MiniDumpWriteDump"); if (funcDump) { + wchar_t exeNameBuffer[MAX_PATH]; + ::GetModuleFileNameW(nullptr, exeNameBuffer, MAX_PATH); + QString dumpName = QString::fromWCharArray(exeNameBuffer) + ".dmp"; + if (QMessageBox::question(nullptr, QObject::tr("Woops"), QObject::tr("ModOrganizer has crashed! " "Should a diagnostic file be created? " @@ -202,12 +172,10 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except "the bug is a lot more likely to be fixed. " "Please include a short description of what you were " "doing when the crash happened" - ).arg(qApp->applicationFilePath().append(".dmp")), + ).arg(dumpName), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - std::wstring dumpName = ToWString(qApp->applicationFilePath().append(".dmp")); - - HANDLE dumpFile = ::CreateFile(dumpName.c_str(), + HANDLE dumpFile = ::CreateFile(dumpName.toStdWString().c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (dumpFile != INVALID_HANDLE_VALUE) { @@ -225,10 +193,10 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except return EXCEPTION_EXECUTE_HANDLER; } _snprintf(errorBuffer, errorLen, "failed to save minidump to %ls (error %lu)", - dumpName.c_str(), ::GetLastError()); + dumpName.toStdWString().c_str(), ::GetLastError()); } else { _snprintf(errorBuffer, errorLen, "failed to create %ls (error %lu)", - dumpName.c_str(), ::GetLastError()); + dumpName.toStdWString().c_str(), ::GetLastError()); } } else { return result; @@ -435,139 +403,95 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett return nullptr; } -int main(int argc, char *argv[]) + +// 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() { - MOApplication application(argc, argv); + static const int BUFSIZE = 4096; - qDebug("application name: %s", qPrintable(application.applicationName())); - - QString instanceID; - QFile instanceFile(application.applicationDirPath() + "/INSTANCE"); - if (instanceFile.open(QIODevice::ReadOnly)) { - instanceID = instanceFile.readAll().trimmed(); - } - - QString const dataPath = - instanceID.isEmpty() ? application.applicationDirPath() - : QDir::fromNativeSeparators( - QStandardPaths::writableLocation(QStandardPaths::DataLocation) - + "/" + instanceID - ); - application.setProperty("dataPath", dataPath); - - if (!QDir(dataPath).exists()) { - if (!QDir().mkpath(dataPath)) { - qCritical("failed to create %s", qPrintable(dataPath)); - return 1; - } + qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators( + QCoreApplication::applicationDirPath()))); + + 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); } - SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); + std::wstring newPath(oldPath.get()); + newPath += L";"; + newPath += ToWString(QDir::toNativeSeparators( + QCoreApplication::applicationDirPath())) + .c_str(); + newPath += L"\\dlls"; - if (!HaveWriteAccess(ToWString(application.applicationDirPath()))) { - QStringList arguments = application.arguments(); - arguments.pop_front(); - ::ShellExecuteW( nullptr - , L"runas" - , ToWString(QString("\"%1\"").arg(QCoreApplication::applicationFilePath())).c_str() - , ToWString(arguments.join(" ")).c_str() - , ToWString(QDir::currentPath()).c_str(), SW_SHOWNORMAL); - return 1; - } + ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); +} + +int runApplication(MOApplication &application, SingleInstance &instance) +{ + qDebug("start main application"); QPixmap pixmap(":/MO/gui/splash"); QSplashScreen splash(pixmap); - try { - if (!bootstrap()) { - return -1; - } + QString dataPath = application.property("dataPath").toString(); + qDebug("data path: %s", qPrintable(dataPath)); - LogBuffer::init(100, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + if (!bootstrap()) { + reportError("failed to set up data path"); + return 1; + } -#if QT_VERSION >= 0x050000 && !defined(QT_NO_SSL) - qDebug("ssl support: %d", QSslSocket::supportsSsl()); -#endif + QStringList arguments = application.arguments(); + try { qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); - qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath()))); splash.show(); - - cleanupDir(); } catch (const std::exception &e) { reportError(e.what()); return 1; } - { // 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) - static const int BUFSIZE = 4096; - - 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(oldPath.get()); - newPath += L";"; - newPath += ToWString(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())).c_str(); - newPath += L"\\dlls"; - - ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); - } - - QStringList arguments = application.arguments(); - - bool forcePrimary = false; - if (arguments.contains("update")) { - arguments.removeAll("update"); - forcePrimary = true; - } - try { - SingleInstance instance(forcePrimary); - if (!instance.primaryInstance()) { - if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) { - qDebug("not primary instance, sending download message"); - instance.sendMessage(arguments.at(1)); - 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 has been called with parameters - - QSettings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); + QSettings settings(dataPath + "/" + + QString::fromStdWString(AppConfig::iniFileName()), + QSettings::IniFormat); qDebug("initializing core"); OrganizerCore organizer(settings); qDebug("initialize plugins"); PluginContainer pluginContainer(&organizer); pluginContainer.loadPlugins(); - MOBase::IPluginGame *game = determineCurrentGame(application.applicationDirPath(), settings, pluginContainer); + MOBase::IPluginGame *game = determineCurrentGame( + application.applicationDirPath(), settings, pluginContainer); if (game == nullptr) { return 1; } organizer.setManagedGame(game); - organizer.createDefaultProfile(); - //See the pragma - we apparently don't use this so not sure why we check it if (!settings.contains("game_edition")) { 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); + SelectionDialog selection( + QObject::tr("Please select the game edition you have (MO can't " + "start the game correctly if this is set " + "incorrectly!)"), + nullptr); int index = 0; for (const QString &edition : editions) { selection.addChoice(edition, "", index++); } if (selection.exec() == QDialog::Rejected) { - return -1; + return 1; } else { settings.setValue("game_edition", selection.getChoiceString()); } @@ -575,8 +499,8 @@ int main(int argc, char *argv[]) } game->setGameVariant(settings.value("game_edition").toString()); -#pragma message("edition isn't used?") - qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(game->gameDirectory().absolutePath()))); + qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators( + game->gameDirectory().absolutePath()))); organizer.updateExecutablesList(settings); @@ -585,27 +509,35 @@ int main(int argc, char *argv[]) // if we have a command line parameter, it is either a nxm link or // a binary to start - if ((arguments.size() > 1) - && !isNxmLink(arguments.at(1))) { - QString exeName = arguments.at(1); - qDebug("starting %s from command line", qPrintable(exeName)); - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.startApplication(exeName, arguments, QString(), QString()); - return 0; - } catch (const std::exception &e) { - reportError(QObject::tr("failed to start application: %1").arg(e.what())); - return 1; + if (arguments.size() > 1) { + if (isNxmLink(arguments.at(1))) { + qDebug("starting download from command line: %s", + qPrintable(arguments.at(1))); + organizer.externalMessage(arguments.at(1)); + } else { + QString exeName = arguments.at(1); + qDebug("starting %s from command line", qPrintable(exeName)); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) + arguments.removeFirst(); // remove binary name + // pass the remaining parameters to the binary + try { + organizer.startApplication(exeName, arguments, QString(), QString()); + } catch (const std::exception &e) { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } } + return 0; } NexusInterface::instance()->getAccessManager()->startLoginCheck(); qDebug("initializing tutorials"); - TutorialManager::init(qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", - &organizer); + TutorialManager::init( + qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", + &organizer); if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { // disable invalid stylesheet @@ -615,27 +547,94 @@ int main(int argc, char *argv[]) int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(argv[0], settings, organizer, pluginContainer); + MainWindow mainWindow(settings, organizer, pluginContainer); - QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); - QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, + SLOT(setStyleFile(QString))); + QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, + SLOT(externalMessage(QString))); mainWindow.readSettings(); qDebug("displaying main window"); mainWindow.show(); - if ((arguments.size() > 1) - && isNxmLink(arguments.at(1))) { - qDebug("starting download from command line: %s", qPrintable(arguments.at(1))); - organizer.externalMessage(arguments.at(1)); - } splash.finish(&mainWindow); - res = application.exec(); + return application.exec(); } - return res; } catch (const std::exception &e) { reportError(e.what()); return 1; } } + + +int main(int argc, char *argv[]) +{ + SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); + + MOApplication application(argc, argv); + QStringList arguments = application.arguments(); + + if ((arguments.length() >= 4) && (arguments.at(1) == "launch")) { + // all we're supposed to do is launch another process + QProcess process; + process.setWorkingDirectory(QDir::fromNativeSeparators(arguments.at(2))); + process.setProgram(QDir::fromNativeSeparators(arguments.at(3))); + process.setArguments(arguments.mid(4)); + process.start(); + process.waitForFinished(-1); + return process.exitCode(); + } + + setupPath(); + + #if !defined(QT_NO_SSL) + qDebug("ssl support: %d", QSslSocket::supportsSsl()); + #else + qDebug("non-ssl build"); + #endif + + bool forcePrimary = false; + if (arguments.contains("update")) { + arguments.removeAll("update"); + forcePrimary = true; + } + + SingleInstance instance(forcePrimary); + if (!instance.primaryInstance()) { + if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) { + qDebug("not primary instance, sending download message"); + instance.sendMessage(arguments.at(1)); + 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 + + do { + QString dataPath; + + try { + dataPath = InstanceManager().determineDataPath(); + } catch (const std::exception &e) { + QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), + e.what()); + return 1; + } + + application.setProperty("dataPath", dataPath); + + LogBuffer::init(100, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + + int result = runApplication(application, instance); + + if (result != INT_MAX) { + return result; + } + argc = 1; + } while (true); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0c54fc8d..80e3490c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -74,6 +74,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include @@ -146,8 +147,7 @@ using namespace MOBase; using namespace MOShared; -MainWindow::MainWindow(const QString &exeName - , QSettings &initSettings +MainWindow::MainWindow(QSettings &initSettings , OrganizerCore &organizerCore , PluginContainer &pluginContainer , QWidget *parent) @@ -155,7 +155,6 @@ MainWindow::MainWindow(const QString &exeName , ui(new Ui::MainWindow) , m_WasVisible(false) , m_Tutorial(this, "MainWindow") - , m_ExeName(exeName) , m_OldProfileIndex(-1) , m_ModListGroupingProxy(nullptr) , m_ModListSortProxy(nullptr) @@ -301,6 +300,7 @@ MainWindow::MainWindow(const QString &exeName connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); + connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), &m_OrganizerCore, SLOT(requestDownload(QUrl,QNetworkReply*))); @@ -347,6 +347,8 @@ MainWindow::MainWindow(const QString &exeName MainWindow::~MainWindow() { + cleanup(); + m_PluginContainer.setUserInterface(nullptr, nullptr); m_OrganizerCore.setUserInterface(nullptr, nullptr); m_IntegratedBrowser.close(); @@ -810,6 +812,14 @@ void MainWindow::closeEvent(QCloseEvent* event) } setCursor(Qt::WaitCursor); +} + +void MainWindow::cleanup() +{ + if (ui->logList->model() != nullptr) { + disconnect(ui->logList->model(), nullptr, nullptr, nullptr); + ui->logList->setModel(nullptr); + } m_IntegratedBrowser.close(); } @@ -1175,7 +1185,7 @@ bool MainWindow::refreshProfiles(bool selectProfile) profileBox->clear(); profileBox->addItem(QObject::tr("")); - QDir profilesDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath())); + QDir profilesDir(Settings::instance().getProfileDirectory()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); @@ -3166,7 +3176,7 @@ QString getStartMenuLinkfile(const Executable &exec) void MainWindow::addWindowsLink(const ShortcutType mapping) { - Executable const &selectedExecutable(getSelectedExecutable()); + const Executable &selectedExecutable(getSelectedExecutable()); QString const linkName = getLinkfile(mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(), selectedExecutable); @@ -4217,6 +4227,7 @@ void MainWindow::on_bossButton_clicked() HANDLE stdOutRead = INVALID_HANDLE_VALUE; createStdoutPipe(&stdOutRead, &stdOutWrite); m_OrganizerCore.prepareVFS(); + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), parameters.join(" "), qApp->applicationDirPath() + "/loot", @@ -4313,6 +4324,7 @@ void MainWindow::on_bossButton_clicked() } catch (const std::exception &e) { reportError(tr("failed to run loot: %1").arg(e.what())); } + if (errorMessages.length() > 0) { QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this); warn->setModal(false); @@ -4326,11 +4338,7 @@ void MainWindow::on_bossButton_clicked() QStringList temp = report.split("?"); QUrl url = QUrl::fromLocalFile(temp.at(0)); if (temp.size() > 1) { -#if QT_VERSION >= 0x050000 url.setQuery(temp.at(1).toUtf8()); -#else - url.setEncodedQuery(temp.at(1).toUtf8()); -#endif } m_IntegratedBrowser.openUrl(url); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 49476bf3..530097cf 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -26,7 +26,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -75,7 +74,7 @@ class MainWindow : public QMainWindow, public IUserInterface public: - explicit MainWindow(const QString &exeName, QSettings &initSettings, + explicit MainWindow(QSettings &initSettings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent = 0); ~MainWindow(); @@ -155,6 +154,8 @@ protected: private: + void cleanup(); + void actionToToolButton(QAction *&sourceAction); void updateToolBar(); @@ -263,8 +264,6 @@ private: MOBase::TutorialControl m_Tutorial; - QString m_ExeName; - int m_OldProfileIndex; std::vector m_ModNameList; // the mod-list to go with the directory structure diff --git a/src/organizercore.cpp b/src/organizercore.cpp index c84b603d..a73e6845 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -535,7 +535,7 @@ InstallationManager *OrganizerCore::installationManager() void OrganizerCore::createDefaultProfile() { - QString profilesPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()); + QString profilesPath = settings().getProfileDirectory(); if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { Profile newProf("Default", managedGame(), false); } @@ -552,7 +552,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) (profileName == m_CurrentProfile->name())) { return; } - QString profileDir = qApp->property("dataPath").toString() + "/" + ToQString(AppConfig::profilesPath()) + "/" + profileName; + QString profileDir = settings().getProfileDirectory() + "/" + profileName; Profile *newProfile = new Profile(QDir(profileDir), managedGame()); delete m_CurrentProfile; @@ -994,10 +994,9 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString & m_CurrentProfile->modlistWriter().writeImmediately(true); } - // TODO: should also pass arguments if (m_AboutToRun(binary.absoluteFilePath())) { m_USVFS.updateMapping(fileMapping()); - QString modsPath(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::modsPath())); + QString modsPath = settings().getModDirectory(); QString binPath = binary.absoluteFilePath(); @@ -1008,15 +1007,14 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString & QString cwdPath = currentDirectory.absolutePath(); int binOffset = binPath.indexOf('/', modsPath.length() + 1); - int cwdOffset = cwdPath.indexOf('/', cwdPath.length() + 1); - QString binPath = m_GamePlugin->dataDirectory().absolutePath() + "/" + binPath.mid(binOffset); - QString cwd = m_GamePlugin->dataDirectory().absolutePath() + "/" + cwdPath.mid(cwdOffset); + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString dataBinPath = m_GamePlugin->dataDirectory().absolutePath() + binPath.mid(binOffset, -1); + QString dataCwd = m_GamePlugin->dataDirectory().absolutePath() + cwdPath.mid(cwdOffset, -1); QString cmdline = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwd), - QDir::toNativeSeparators(binPath), + .arg(QDir::toNativeSeparators(dataCwd), + QDir::toNativeSeparators(dataBinPath), arguments); - - return startBinary(QFileInfo(QCoreApplication::applicationDirPath() + "/helper.exe"), + return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), cmdline, QCoreApplication::applicationDirPath(), true); } else { @@ -1600,7 +1598,7 @@ std::vector OrganizerCore::fileMapping() int overwriteId = m_DirectoryStructure->getOriginByName(L"Overwrite").getID(); - const IPluginGame *game = qApp->property("managed_game").value(); + IPluginGame *game = qApp->property("managed_game").value(); MappingType result = fileMapping( QDir::toNativeSeparators(game->dataDirectory().absolutePath()), "\\", diff --git a/src/organizercore.h b/src/organizercore.h index d86033eb..bf6c5a8b 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -201,6 +201,8 @@ signals: void managedGameChanged(MOBase::IPluginGame const *gamePlugin); + void close(); + private: void storeSettings(); diff --git a/src/profile.cpp b/src/profile.cpp index 4b916a2f..99a6bd90 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "windows_error.h" #include "modinfo.h" #include "safewritefile.h" +#include "settings.h" #include #include #include @@ -58,7 +59,7 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef : m_ModListWriter(std::bind(&Profile::writeModlistNow, this)) , m_GamePlugin(gamePlugin) { - QString profilesDir = qApp->property("dataPath").toString() + "/" + ToQString(AppConfig::profilesPath()); + QString profilesDir = Settings::instance().getProfileDirectory(); QDir profileBase(profilesDir); QString fixedName = name; @@ -487,7 +488,7 @@ void Profile::setModPriority(unsigned int index, int &newPriority) Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) { - QString profileDirectory = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()) + "/" + name; + QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name; reference.copyFilesTo(profileDirectory); return new Profile(QDir(profileDirectory), gamePlugin); } @@ -691,7 +692,7 @@ QString Profile::absolutePath() const void Profile::rename(const QString &newName) { - QDir profileDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath())); + QDir profileDir(Settings::instance().getProfileDirectory()); profileDir.rename(name(), newName); m_Directory = profileDir.absoluteFilePath(newName); } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index b21aee53..49fbda4e 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "profileinputdialog.h" #include "mainwindow.h" #include "aboutdialog.h" +#include "settings.h" #include #include #include @@ -50,7 +51,7 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c { ui->setupUi(this); - QDir profilesDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath())); + QDir profilesDir(Settings::instance().getProfileDirectory()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); m_ProfilesList = findChild("profilesList"); @@ -187,8 +188,7 @@ void ProfilesDialog::on_removeProfileButton_clicked() Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); QString profilePath; if (currentProfile.get() == nullptr) { - profilePath = qApp->property("dataPath").toString() - + "/" + QString::fromStdWString(AppConfig::profilesPath()) + profilePath = Settings::instance().getProfileDirectory() + "/" + profilesList->currentItem()->text(); if (QMessageBox::question(this, tr("Profile broken"), tr("This profile you're about to delete seems to be broken or the path is invalid. " diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index aae95f56..55728751 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -79,6 +79,11 @@ QString SelectionDialog::getChoiceString() } } +void SelectionDialog::disableCancel() +{ + ui->cancelButton->setEnabled(false); + ui->cancelButton->setHidden(true); +} void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) { diff --git a/src/selectiondialog.h b/src/selectiondialog.h index 43ba9767..3c4b25df 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -53,6 +53,8 @@ public: QVariant getChoiceData(); QString getChoiceString(); + void disableCancel(); + private slots: void on_buttonBox_clicked(QAbstractButton *button); diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 0cfd7f08..ae75b9cb 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -65,7 +65,7 @@ SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) , m_Attempts(3) , m_NexusDownload(nullptr) { - QLibrary archiveLib("dlls\\archive.dll"); + QLibrary archiveLib(QCoreApplication::applicationDirPath() + "\\dlls\\archive.dll"); if (!archiveLib.load()) { throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); } diff --git a/src/settings.cpp b/src/settings.cpp index 7b812759..c99b434d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -125,7 +125,7 @@ void Settings::registerPlugin(IPlugin *plugin) m_Plugins.push_back(plugin); m_PluginSettings.insert(plugin->name(), QMap()); m_PluginDescriptions.insert(plugin->name(), QMap()); - foreach (const PluginSetting &setting, plugin->settings()) { + for (const PluginSetting &setting : plugin->settings()) { QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); if (!temp.convert(setting.defaultValue.type())) { qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", @@ -183,7 +183,7 @@ void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) { m_Settings.beginGroup("Servers"); - foreach (const QString &serverKey, m_Settings.childKeys()) { + for (const QString &serverKey : m_Settings.childKeys()) { QVariantMap data = m_Settings.value(serverKey).toMap(); if (serverKey == serverName) { data["downloadCount"] = data["downloadCount"].toInt() + 1; @@ -201,7 +201,7 @@ std::map Settings::getPreferredServers() std::map result; m_Settings.beginGroup("Servers"); - foreach (const QString &serverKey, m_Settings.childKeys()) { + for (const QString &serverKey : m_Settings.childKeys()) { QVariantMap data = m_Settings.value(serverKey).toMap(); int preference = data["preferred"].toInt(); if (preference > 0) { @@ -233,6 +233,11 @@ QString Settings::getModDirectory() const return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath())); } +QString Settings::getProfileDirectory() const +{ + return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath())); +} + QString Settings::getNMMVersion() const { static const QString MIN_NMM_VERSION = "0.52.3"; @@ -411,7 +416,7 @@ void Settings::updateServers(const QList &servers) m_Settings.beginGroup("Servers"); QStringList oldServerKeys = m_Settings.childKeys(); - foreach (const ServerInfo &server, servers) { + for (const ServerInfo &server : servers) { if (!oldServerKeys.contains(server.name)) { // not yet known server QVariantMap newVal; @@ -433,7 +438,7 @@ void Settings::updateServers(const QList &servers) // clean up unavailable servers QDate now = QDate::currentDate(); - foreach (const QString &key, m_Settings.childKeys()) { + for (const QString &key : m_Settings.childKeys()) { QVariantMap val = m_Settings.value(key).toMap(); QDate lastSeen = val["lastSeen"].toDate(); if (lastSeen.daysTo(now) > 30) { @@ -457,7 +462,7 @@ void Settings::writePluginBlacklist() { m_Settings.beginWriteArray("pluginBlacklist"); int idx = 0; - foreach (const QString &plugin, m_PluginBlacklist) { + for (const QString &plugin : m_PluginBlacklist) { m_Settings.setArrayIndex(idx++); m_Settings.setValue("name", plugin); } @@ -522,7 +527,7 @@ void Settings::resetDialogs() { m_Settings.beginGroup("DialogChoices"); QStringList keys = m_Settings.childKeys(); - foreach (QString key, keys) { + for (QString key : keys) { m_Settings.remove(key); } @@ -539,6 +544,7 @@ void Settings::query(QWidget *parent) std::vector> tabs; tabs.push_back(std::unique_ptr(new GeneralTab(this, dialog))); + tabs.push_back(std::unique_ptr(new PathsTab(this, dialog))); tabs.push_back(std::unique_ptr(new NexusTab(this, dialog))); tabs.push_back(std::unique_ptr(new SteamTab(this, dialog))); tabs.push_back(std::unique_ptr(new PluginsTab(this, dialog))); @@ -552,56 +558,54 @@ void Settings::query(QWidget *parent) } } -Settings::SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) : - m_parent(m_parent), - m_Settings(m_parent->m_Settings), - m_dialog(m_dialog) -{} +Settings::SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : m_parent(m_parent) + , m_Settings(m_parent->m_Settings) + , m_dialog(m_dialog) +{ +} Settings::SettingsTab::~SettingsTab() {} -Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_languageBox(m_dialog.findChild("languageBox")), - m_styleBox(m_dialog.findChild("styleBox")), - m_logLevelBox(m_dialog.findChild("logLevelBox")), - m_downloadDirEdit(m_dialog.findChild("downloadDirEdit")), - m_modDirEdit(m_dialog.findChild("modDirEdit")), - m_cacheDirEdit(m_dialog.findChild("cacheDirEdit")), - m_compactBox(m_dialog.findChild("compactBox")), - m_showMetaBox(m_dialog.findChild("showMetaBox")) +Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_languageBox(m_dialog.findChild("languageBox")) + , m_styleBox(m_dialog.findChild("styleBox")) + , m_logLevelBox(m_dialog.findChild("logLevelBox")) + , m_compactBox(m_dialog.findChild("compactBox")) + , m_showMetaBox(m_dialog.findChild("showMetaBox")) +{ + // FIXME I think 'addLanguages' lives in here not in parent + m_parent->addLanguages(m_languageBox); { - //FIXME I think 'addLanguages' lives in here not in parent - m_parent->addLanguages(m_languageBox); - { - QString languageCode = m_parent->language(); - int currentID = m_languageBox->findData(languageCode); - // I made a mess. :( Most languages are stored with only the iso country code (2 characters like "de") but chinese - // with the exact language variant (zh_TW) so I have to search for both variants - if (currentID == -1) { - currentID = m_languageBox->findData(languageCode.mid(0, 2)); - } - if (currentID != -1) { - m_languageBox->setCurrentIndex(currentID); - } + QString languageCode = m_parent->language(); + int currentID = m_languageBox->findData(languageCode); + // I made a mess. :( Most languages are stored with only the iso country + // code (2 characters like "de") but chinese + // with the exact language variant (zh_TW) so I have to search for both + // variants + if (currentID == -1) { + currentID = m_languageBox->findData(languageCode.mid(0, 2)); + } + if (currentID != -1) { + m_languageBox->setCurrentIndex(currentID); } + } - //FIXME I think addStyles lives in here not in parent - m_parent->addStyles(m_styleBox); - { - int currentID = m_styleBox->findData(m_Settings.value("Settings/style", "").toString()); - if (currentID != -1) { - m_styleBox->setCurrentIndex(currentID); - } + // FIXME I think addStyles lives in here not in parent + m_parent->addStyles(m_styleBox); + { + int currentID = m_styleBox->findData( + m_Settings.value("Settings/style", "").toString()); + if (currentID != -1) { + m_styleBox->setCurrentIndex(currentID); } + } - m_logLevelBox->setCurrentIndex(m_parent->logLevel()); - m_downloadDirEdit->setText(m_parent->getDownloadDirectory()); - m_modDirEdit->setText(m_parent->getModDirectory()); - m_cacheDirEdit->setText(m_parent->getCacheDirectory()); - m_compactBox->setChecked(m_parent->compactDownloads()); - m_showMetaBox->setChecked(m_parent->metaDownloads()); + m_logLevelBox->setCurrentIndex(m_parent->logLevel()); + m_compactBox->setChecked(m_parent->compactDownloads()); + m_showMetaBox->setChecked(m_parent->metaDownloads()); } void Settings::GeneralTab::update() @@ -622,68 +626,108 @@ void Settings::GeneralTab::update() m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex()); - { // advanced settings - if ((QDir::fromNativeSeparators(m_modDirEdit->text()) != QDir::fromNativeSeparators(m_parent->getModDirectory())) && - (QMessageBox::question(nullptr, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " - "Mods not present (or named differently) in the new location will be disabled in all profiles. " - "There is no way to undo this unless you backed up your profiles manually. Proceed?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { - m_modDirEdit->setText(m_parent->getModDirectory()); - } - if (!QDir(m_downloadDirEdit->text()).exists()) { - QDir().mkpath(m_downloadDirEdit->text()); - } - if (QFileInfo(m_downloadDirEdit->text()) != - QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::downloadPath()))) { - m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(m_downloadDirEdit->text())); - } else { - m_Settings.remove("Settings/download_directory"); - } + m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked()); + m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked()); +} - if (!QDir(m_modDirEdit->text()).exists()) { - QDir().mkpath(m_modDirEdit->text()); - } - if (QFileInfo(m_modDirEdit->text()) != - QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::modsPath()))) { - m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(m_modDirEdit->text())); - } else { - m_Settings.remove("Settings/mod_directory"); - } +Settings::PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) + : SettingsTab(parent, dialog) + , m_downloadDirEdit(m_dialog.findChild("downloadDirEdit")) + , m_modDirEdit(m_dialog.findChild("modDirEdit")) + , m_cacheDirEdit(m_dialog.findChild("cacheDirEdit")) + , m_profilesDirEdit(m_dialog.findChild("profilesDirEdit")) +{ + m_downloadDirEdit->setText(m_parent->getDownloadDirectory()); + m_modDirEdit->setText(m_parent->getModDirectory()); + m_cacheDirEdit->setText(m_parent->getCacheDirectory()); + m_profilesDirEdit->setText(m_parent->getProfileDirectory()); +} + +void Settings::PathsTab::update() +{ + if ((QDir::fromNativeSeparators(m_modDirEdit->text()) + != QDir::fromNativeSeparators(m_parent->getModDirectory())) + && (QMessageBox::question( + nullptr, tr("Confirm"), + tr("Changing the mod directory affects all your profiles! " + "Mods not present (or named differently) in the new location " + "will be disabled in all profiles. " + "There is no way to undo this unless you backed up your " + "profiles manually. Proceed?"), + QMessageBox::Yes | QMessageBox::No) + == QMessageBox::No)) { + m_modDirEdit->setText(m_parent->getModDirectory()); + } - if (!QDir(m_cacheDirEdit->text()).exists()) { - QDir().mkpath(m_cacheDirEdit->text()); - } - if (QFileInfo(m_cacheDirEdit->text()) != - QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::cachePath()))) { - m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(m_cacheDirEdit->text())); - } else { - m_Settings.remove("Settings/cache_directory"); - } + if (!QDir(m_downloadDirEdit->text()).exists()) { + QDir().mkpath(m_downloadDirEdit->text()); + } + if (QFileInfo(m_downloadDirEdit->text()) + != QFileInfo(qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::downloadPath()))) { + m_Settings.setValue("Settings/download_directory", + QDir::toNativeSeparators(m_downloadDirEdit->text())); + } else { + m_Settings.remove("Settings/download_directory"); } - m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked()); + if (!QDir(m_modDirEdit->text()).exists()) { + QDir().mkpath(m_modDirEdit->text()); + } + if (QFileInfo(m_modDirEdit->text()) + != QFileInfo(qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::modsPath()))) { + m_Settings.setValue("Settings/mod_directory", + QDir::toNativeSeparators(m_modDirEdit->text())); + } else { + m_Settings.remove("Settings/mod_directory"); + } + + if (!QDir(m_cacheDirEdit->text()).exists()) { + QDir().mkpath(m_cacheDirEdit->text()); + } + if (QFileInfo(m_cacheDirEdit->text()) + != QFileInfo(qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::cachePath()))) { + m_Settings.setValue("Settings/cache_directory", + QDir::toNativeSeparators(m_cacheDirEdit->text())); + } else { + m_Settings.remove("Settings/cache_directory"); + } + + if (!QDir(m_profilesDirEdit->text()).exists()) { + QDir().mkpath(m_profilesDirEdit->text()); + } + if (QFileInfo(m_profilesDirEdit->text()) + != QFileInfo(qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::profilesPath()))) { + m_Settings.setValue("Settings/profiles_directory", + QDir::toNativeSeparators(m_profilesDirEdit->text())); + } else { + m_Settings.remove("Settings/profiles_directory"); + } } -Settings::NexusTab::NexusTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_loginCheckBox(m_dialog.findChild("loginCheckBox")), - m_usernameEdit(m_dialog.findChild("usernameEdit")), - m_passwordEdit(m_dialog.findChild("passwordEdit")), - m_offlineBox(m_dialog.findChild("offlineBox")), - m_proxyBox(m_dialog.findChild("proxyBox")), - m_knownServersList(m_dialog.findChild("knownServersList")), - m_preferredServersList(m_dialog.findChild("preferredServersList")) +Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) + : Settings::SettingsTab(parent, dialog) + , m_loginCheckBox(dialog.findChild("loginCheckBox")) + , m_usernameEdit(dialog.findChild("usernameEdit")) + , m_passwordEdit(dialog.findChild("passwordEdit")) + , m_offlineBox(dialog.findChild("offlineBox")) + , m_proxyBox(dialog.findChild("proxyBox")) + , m_knownServersList(dialog.findChild("knownServersList")) + , m_preferredServersList( + dialog.findChild("preferredServersList")) { - if (m_parent->automaticLoginEnabled()) { + if (parent->automaticLoginEnabled()) { m_loginCheckBox->setChecked(true); m_usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); m_passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); } - m_offlineBox->setChecked(m_parent->offlineMode()); - m_proxyBox->setChecked(m_parent->useProxy()); + m_offlineBox->setChecked(parent->offlineMode()); + m_proxyBox->setChecked(parent->useProxy()); // display server preferences m_Settings.beginGroup("Servers"); @@ -743,11 +787,10 @@ void Settings::NexusTab::update() m_Settings.endGroup(); } - -Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_steamUserEdit(m_dialog.findChild("steamUserEdit")), - m_steamPassEdit(m_dialog.findChild("steamPassEdit")) +Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_steamUserEdit(m_dialog.findChild("steamUserEdit")) + , m_steamPassEdit(m_dialog.findChild("steamPassEdit")) { if (m_Settings.contains("Settings/steam_username")) { m_steamUserEdit->setText(m_Settings.value("Settings/steam_username", "").toString()); @@ -763,10 +806,10 @@ void Settings::SteamTab::update() m_parent->setSteamLogin(m_steamUserEdit->text(), m_steamPassEdit->text()); } -Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_pluginsList(m_dialog.findChild("pluginsList")), - m_pluginBlacklistList(m_dialog.findChild("pluginBlacklist")) +Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_pluginsList(m_dialog.findChild("pluginsList")) + , m_pluginBlacklistList(m_dialog.findChild("pluginBlacklist")) { // display plugin settings for (IPlugin *plugin : m_parent->m_Plugins) { @@ -799,20 +842,21 @@ void Settings::PluginsTab::update() // store plugin blacklist m_parent->m_PluginBlacklist.clear(); - foreach (QListWidgetItem *item, m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { + for (QListWidgetItem *item : m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { m_parent->m_PluginBlacklist.insert(item->text()); } m_parent->writePluginBlacklist(); } -Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_appIDEdit(m_dialog.findChild("appIDEdit")), - m_mechanismBox(m_dialog.findChild("mechanismBox")), - m_nmmVersionEdit(m_dialog.findChild("nmmVersionEdit")), - m_hideUncheckedBox(m_dialog.findChild("hideUncheckedBox")), - m_forceEnableBox(m_dialog.findChild("forceEnableBox")), - m_displayForeignBox(m_dialog.findChild("displayForeignBox")) +Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, + SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_appIDEdit(m_dialog.findChild("appIDEdit")) + , m_mechanismBox(m_dialog.findChild("mechanismBox")) + , m_nmmVersionEdit(m_dialog.findChild("nmmVersionEdit")) + , m_hideUncheckedBox(m_dialog.findChild("hideUncheckedBox")) + , m_forceEnableBox(m_dialog.findChild("forceEnableBox")) + , m_displayForeignBox(m_dialog.findChild("displayForeignBox")) { m_appIDEdit->setText(m_parent->getSteamAppID()); diff --git a/src/settings.h b/src/settings.h index 1ee16e76..85a3dac0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -130,6 +130,11 @@ public: **/ QString getCacheDirectory() const; + /** + * retrieve the directory where profiles stored (with native separators) + **/ + QString getProfileDirectory() const; + /** * @return true if the user has set up automatic login to nexus **/ @@ -339,11 +344,22 @@ private: QComboBox *m_languageBox; QComboBox *m_styleBox; QComboBox *m_logLevelBox; + QCheckBox *m_compactBox; + QCheckBox *m_showMetaBox; + }; + + class PathsTab : public SettingsTab + { + public: + PathsTab(Settings *parent, SettingsDialog &dialog); + + void update(); + + private: QLineEdit *m_downloadDirEdit; QLineEdit *m_modDirEdit; QLineEdit *m_cacheDirEdit; - QCheckBox *m_compactBox; - QCheckBox *m_showMetaBox; + QLineEdit *m_profilesDirEdit; }; /** Display/store the configuration in the 'nexus' tab of the settings dialogue */ diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 4e2f89a1..341fa19d 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "noeditdelegate.h" #include "iplugingame.h" #include "settings.h" +#include "instancemanager.h" #include #include @@ -39,11 +40,13 @@ using namespace MOBase; SettingsDialog::SettingsDialog(QWidget *parent) - : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) + : TutorableDialog("SettingsDialog", parent) + , ui(new Ui::SettingsDialog) { ui->setupUi(this); - QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + QShortcut *delShortcut + = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); } @@ -54,7 +57,7 @@ SettingsDialog::~SettingsDialog() void SettingsDialog::addPlugins(const std::vector &plugins) { - foreach (IPlugin *plugin, plugins) { + for (IPlugin *plugin : plugins) { ui->pluginsList->addItem(plugin->name()); } } @@ -89,7 +92,8 @@ void SettingsDialog::on_categoriesBtn_clicked() void SettingsDialog::on_bsaDateBtn_clicked() { - IPluginGame const *game = qApp->property("managed_game").value(); + IPluginGame const *game + = qApp->property("managed_game").value(); QDir dir = game->dataDirectory(); Helper::backdateBSAs(qApp->property("dataPath").toString().toStdWString(), @@ -186,3 +190,15 @@ void SettingsDialog::on_associateButton_clicked() { Settings::instance().registerAsNXMHandler(true); } + +void SettingsDialog::on_changeInstanceButton_clicked() +{ + if (QMessageBox::question(this, tr("Are you sure?"), + tr("This will restart MO, continue?"), + QMessageBox::Yes | QMessageBox::Cancel) + == QMessageBox::Yes) { + InstanceManager().clearCurrentInstance(); + this->reject(); + qApp->exit(INT_MAX); + } +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index e99fb3e3..8bd080ab 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -52,6 +52,9 @@ signals: void resetDialogs(); +private slots: + void on_changeInstanceButton_clicked(); + private: void storeSettings(QListWidgetItem *pluginItem); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index b88885dc..805a5060 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -108,114 +108,60 @@ p, li { white-space: pre-wrap; } - + - Advanced - - - true - - - false + User interface - - - + + + - Directory where downloads are stored. + If checked, the download interface will be more compact. - - Directory where downloads are stored. + + Compact Download Interface - - - - - 0 - 0 - + + + + If checked, the download list will display meta information instead of file names. - ... + Download Meta Information - - - - Mod Directory + + + + + 16777215 + 16777215 + - - - - - Directory where mods are stored. + Reset stored information from dialogs. - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - - - - - - - ... - - - - - - - Download Directory - - - - - - - Cache Directory + This will make all dialogs show up again where you checked the "Remember selection"-box. - - - - - - - - ... + Reset Dialogs - - - - - - - User interface - - - + - If checked, the download interface will be more compact. + Modify the categories available to arrange your mods. - - Compact Download Interface - - - - - - - If checked, the download list will display meta information instead of file names. + + Modify the categories available to arrange your mods. - Download Meta Information + Configure Mod Categories @@ -223,26 +169,132 @@ p, li { white-space: pre-wrap; } - - + + + Qt::Vertical + + - 16777215 - 16777215 + 20 + 40 + + + + - Reset stored information from dialogs. + Restart MO and choose a different set of data paths. - This will make all dialogs show up again where you checked the "Remember selection"-box. + This will restart MO and give you opportunity to switch to a different set of data paths. - Reset Dialogs + Change Instance + + + + + Paths + + - + + + + + Downloads + + + + + + + Caches + + + + + + + ... + + + + + + + + + + Directory where downloads are stored. + + + Directory where downloads are stored. + + + + + + + Mods + + + + + + + ... + + + + + + + Directory where mods are stored. + + + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + + + + + + + + 0 + 0 + + + + ... + + + + + + + Profiles + + + + + + + + + + ... + + + + + + + Qt::Vertical @@ -255,15 +307,9 @@ p, li { white-space: pre-wrap; } - - - Modify the categories available to arrange your mods. - - - Modify the categories available to arrange your mods. - + - Configure Mod Categories + Important: All directories have to be writeable! -- cgit v1.3.1