summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-09-09 01:25:01 -0400
committerGitHub <noreply@github.com>2019-09-09 01:25:01 -0400
commit99fdbb2539227cec4fab534efc7420cfc5ca1a92 (patch)
tree4fe70561a85d86b65cc4c6725541767684af09a7 /src
parent73626b297904d50507bb7d5d1342120040e655e8 (diff)
parentd863e62c6e2f3c0daa50e16a0206f59558a7bb7e (diff)
Merge pull request #830 from isanae/settings-interface
Settings interface
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt9
-rw-r--r--src/browserdialog.cpp5
-rw-r--r--src/categoriesdialog.cpp7
-rw-r--r--src/categoriesdialog.h6
-rw-r--r--src/downloadlist.cpp21
-rw-r--r--src/downloadlist.h2
-rw-r--r--src/downloadlistsortproxy.cpp2
-rw-r--r--src/downloadmanager.cpp64
-rw-r--r--src/downloadmanager.h16
-rw-r--r--src/editexecutablesdialog.cpp5
-rw-r--r--src/editexecutablesdialog.h4
-rw-r--r--src/executableslist.cpp55
-rw-r--r--src/executableslist.h5
-rw-r--r--src/expanderwidget.cpp27
-rw-r--r--src/expanderwidget.h5
-rw-r--r--src/filedialogmemory.cpp54
-rw-r--r--src/filedialogmemory.h14
-rw-r--r--src/installationmanager.cpp13
-rw-r--r--src/iuserinterface.h5
-rw-r--r--src/listdialog.cpp7
-rw-r--r--src/listdialog.h4
-rw-r--r--src/loadmechanism.cpp13
-rw-r--r--src/loadmechanism.h2
-rw-r--r--src/loglist.h2
-rw-r--r--src/main.cpp270
-rw-r--r--src/mainwindow.cpp865
-rw-r--r--src/mainwindow.h38
-rw-r--r--src/mainwindow.ui2106
-rw-r--r--src/modinfo.cpp1
-rw-r--r--src/modinfodialog.cpp77
-rw-r--r--src/modinfodialog.h26
-rw-r--r--src/modinfodialogconflicts.cpp102
-rw-r--r--src/modinfodialogesps.cpp4
-rw-r--r--src/modinfodialogimages.cpp12
-rw-r--r--src/modinfodialognexus.cpp2
-rw-r--r--src/modinfodialogtab.h34
-rw-r--r--src/modinfodialogtextfiles.cpp4
-rw-r--r--src/modinfooverwrite.cpp2
-rw-r--r--src/modinforegular.cpp2
-rw-r--r--src/modlist.cpp18
-rw-r--r--src/nxmaccessmanager.cpp15
-rw-r--r--src/nxmaccessmanager.h2
-rw-r--r--src/organizercore.cpp251
-rw-r--r--src/organizercore.h10
-rw-r--r--src/overwriteinfodialog.cpp12
-rw-r--r--src/overwriteinfodialog.h11
-rw-r--r--src/pch.h2
-rw-r--r--src/plugincontainer.cpp6
-rw-r--r--src/pluginlist.cpp4
-rw-r--r--src/previewdialog.cpp7
-rw-r--r--src/previewdialog.h4
-rw-r--r--src/problemsdialog.cpp6
-rw-r--r--src/problemsdialog.h4
-rw-r--r--src/profile.cpp8
-rw-r--r--src/profilesdialog.cpp10
-rw-r--r--src/profilesdialog.h4
-rw-r--r--src/serverinfo.cpp177
-rw-r--r--src/serverinfo.h62
-rw-r--r--src/settings.cpp1881
-rw-r--r--src/settings.h928
-rw-r--r--src/settingsdialog.cpp76
-rw-r--r--src/settingsdialog.h18
-rw-r--r--src/settingsdialog.ui23
-rw-r--r--src/settingsdialogdiagnostics.cpp47
-rw-r--r--src/settingsdialogdiagnostics.h3
-rw-r--r--src/settingsdialoggeneral.cpp102
-rw-r--r--src/settingsdialoggeneral.h2
-rw-r--r--src/settingsdialognexus.cpp129
-rw-r--r--src/settingsdialognexus.h2
-rw-r--r--src/settingsdialogpaths.cpp84
-rw-r--r--src/settingsdialogpaths.h3
-rw-r--r--src/settingsdialogplugins.cpp34
-rw-r--r--src/settingsdialogplugins.h2
-rw-r--r--src/settingsdialogsteam.cpp8
-rw-r--r--src/settingsdialogsteam.h5
-rw-r--r--src/settingsdialogworkarounds.cpp62
-rw-r--r--src/settingsdialogworkarounds.h3
-rw-r--r--src/settingsutilities.cpp267
-rw-r--r--src/settingsutilities.h274
-rw-r--r--src/statusbar.cpp67
-rw-r--r--src/statusbar.h14
-rw-r--r--src/usvfsconnector.cpp9
-rw-r--r--src/usvfsconnector.h2
83 files changed, 5345 insertions, 3205 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d8316e7e..ea27184b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -45,6 +45,7 @@ SET(organizer_SRCS
settingsdialogsteam.cpp
settingsdialogworkarounds.cpp
settings.cpp
+ settingsutilities.cpp
selfupdater.cpp
selectiondialog.cpp
queryoverwritedialog.cpp
@@ -166,6 +167,7 @@ SET(organizer_HDRS
settingsdialogsteam.h
settingsdialogworkarounds.h
settings.h
+ settingsutilities.h
selfupdater.h
selectiondialog.h
queryoverwritedialog.h
@@ -444,6 +446,10 @@ set(profiles
set(settings
settings
+ settingsutilities
+)
+
+set(settingsdialog
settingsdialog
settingsdialogdiagnostics
settingsdialoggeneral
@@ -490,7 +496,8 @@ set(widgets
set(src_filters
application core browser dialogs downloads env executables locking modinfo
- modinfo\\dialog modlist plugins previews profiles settings utilities widgets
+ modinfo\\dialog modlist plugins previews profiles settings settingsdialog
+ utilities widgets
)
foreach(filter in list ${src_filters})
diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp
index 73a6a2d0..72cb8862 100644
--- a/src/browserdialog.cpp
+++ b/src/browserdialog.cpp
@@ -49,7 +49,7 @@ BrowserDialog::BrowserDialog(QWidget *parent)
ui->setupUi(this);
m_AccessManager->setCookieJar(new PersistentCookieJar(
- QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/cookies.dat")));
+ QDir::fromNativeSeparators(Settings::instance().paths().cache() + "/cookies.dat")));
Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint;
Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint;
@@ -72,7 +72,7 @@ BrowserDialog::~BrowserDialog()
void BrowserDialog::closeEvent(QCloseEvent *event)
{
-// m_AccessManager->showCookies();
+ Settings::instance().geometry().saveGeometry(this);
QDialog::closeEvent(event);
}
@@ -126,6 +126,7 @@ void BrowserDialog::urlChanged(const QUrl &url)
void BrowserDialog::openUrl(const QUrl &url)
{
if (isHidden()) {
+ Settings::instance().geometry().restoreGeometry(this);
show();
}
openInNewTab(url);
diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp
index 881179a4..b5194bf0 100644
--- a/src/categoriesdialog.cpp
+++ b/src/categoriesdialog.cpp
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "ui_categoriesdialog.h"
#include "categories.h"
#include "utility.h"
+#include "settings.h"
#include <QItemDelegate>
#include <QRegExpValidator>
#include <QLineEdit>
@@ -109,6 +110,12 @@ CategoriesDialog::~CategoriesDialog()
delete ui;
}
+int CategoriesDialog::exec()
+{
+ GeometrySaver gs(Settings::instance(), this);
+ return QDialog::exec();
+}
+
void CategoriesDialog::cellChanged(int row, int)
{
diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h
index 72d2154d..c743c157 100644
--- a/src/categoriesdialog.h
+++ b/src/categoriesdialog.h
@@ -33,12 +33,16 @@ class CategoriesDialog;
class CategoriesDialog : public MOBase::TutorableDialog
{
Q_OBJECT
-
+
public:
explicit CategoriesDialog(QWidget *parent = 0);
~CategoriesDialog();
+ // also saves and restores geometry
+ //
+ int exec() override;
+
/**
* @brief store changes here to the global categories store (categories.h)
*
diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp
index 36bc2b7f..6957f270 100644
--- a/src/downloadlist.cpp
+++ b/src/downloadlist.cpp
@@ -19,6 +19,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "downloadlist.h"
#include "downloadmanager.h"
+#include <utility.h>
#include <log.h>
#include <QEvent>
#include <QColor>
@@ -121,7 +122,7 @@ QVariant DownloadList::data(const QModelIndex &index, int role) const
return QString("%1").arg(m_Manager->getModID(index.row()));
}
}
- case COL_SIZE: return sizeFormat(m_Manager->getFileSize(index.row()));
+ case COL_SIZE: return MOBase::localizedByteSize(m_Manager->getFileSize(index.row()));
case COL_FILETIME: return m_Manager->getFileTime(index.row());
case COL_STATUS:
switch (m_Manager->getState(index.row())) {
@@ -195,21 +196,3 @@ void DownloadList::update(int row)
else
log::error("invalid row {} in download list, update failed", row);
}
-
-QString DownloadList::sizeFormat(quint64 size) const
-{
- qreal calc = size;
- QStringList list;
- list << "MB" << "GB" << "TB";
-
- QStringListIterator i(list);
- QString unit("KB");
-
- calc /= 1024.0;
- while (calc >= 1024.0 && i.hasNext()) {
- unit = i.next();
- calc /= 1024.0;
- }
-
- return QString().setNum(calc, 'f', 2) + " " + unit;
-}
diff --git a/src/downloadlist.h b/src/downloadlist.h
index 6f63f0c8..51ab4541 100644
--- a/src/downloadlist.h
+++ b/src/downloadlist.h
@@ -99,8 +99,6 @@ private:
DownloadManager *m_Manager;
bool m_MetaDisplay;
-
- QString sizeFormat(quint64 size) const;
};
#endif // DOWNLOADLIST_H
diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp
index 7bda139b..a69993c0 100644
--- a/src/downloadlistsortproxy.cpp
+++ b/src/downloadlistsortproxy.cpp
@@ -110,7 +110,7 @@ bool DownloadListSortProxy::filterAcceptsRow(int sourceRow, const QModelIndex&)
if (m_CurrentFilter.length() == 0) {
return true;
} else if (sourceRow < m_Manager->numTotalDownloads()) {
- QString displayedName = Settings::instance().metaDownloads()
+ QString displayedName = Settings::instance().interface().metaDownloads()
? m_Manager->getDisplayName(sourceRow)
: m_Manager->getFileName(sourceRow);
return displayedName.contains(m_CurrentFilter, Qt::CaseInsensitive);
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp
index 3b084b83..56238ef3 100644
--- a/src/downloadmanager.cpp
+++ b/src/downloadmanager.cpp
@@ -288,12 +288,6 @@ void DownloadManager::setOutputDirectory(const QString &outputDirectory)
}
-void DownloadManager::setPreferredServers(const std::map<QString, int> &preferredServers)
-{
- m_PreferredServers = preferredServers;
-}
-
-
void DownloadManager::setSupportedExtensions(const QStringList &extensions)
{
m_SupportedExtensions = extensions;
@@ -1447,22 +1441,11 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
std::get<4>(info->m_SpeedDiff) = ((calc*0.5) + (std::get<4>(info->m_SpeedDiff)*1.5)) / 2;
// calculate the download speed
- double speed = (std::get<4>(info->m_SpeedDiff) * 1000.0) / (5 * 1000);
-
- QString unit;
- if (speed < 1000) {
- unit = "B/s";
- }
- else if (speed < 1000*1024) {
- speed /= 1024;
- unit = "KB/s";
- }
- else {
- speed /= 1024 * 1024;
- unit = "MB/s";
- }
+ const double speed = (std::get<4>(info->m_SpeedDiff) * 1000.0) / (5 * 1000);
- info->m_Progress.second = QString::fromLatin1("%1% - %2 %3").arg(info->m_Progress.first).arg(QString::number(speed, 'f', 1)).arg(unit);
+ info->m_Progress.second = QString::fromLatin1("%1% - %2")
+ .arg(info->m_Progress.first)
+ .arg(MOBase::localizedByteSpeed(speed));
TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal);
emit update(index);
@@ -1667,23 +1650,39 @@ void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int file
m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(info->gameName, info->modID, info->fileID, this, qVariantFromValue(test), QString()));
}
-static int evaluateFileInfoMap(const QVariantMap &map, const std::map<QString, int> &preferredServers)
+static int evaluateFileInfoMap(
+ const QVariantMap &map,
+ const ServerList::container& preferredServers)
{
- int result = 0;
+ int preference = 0;
+ bool found = false;
+ const auto name = map["short_name"].toString();
- auto preference = preferredServers.find(map["short_name"].toString());
+ for (const auto& server : preferredServers) {
+ if (server.name() == name) {
+ preference = server.preferred();
+ found = true;
+ break;
+ }
+ }
- if (preference != preferredServers.end()) {
- result += 100 + preference->second * 20;
+ if (!found) {
+ log::error("server '{}' not found while sorting by preference", name);
+ return 0;
}
- return result;
+ return 100 + preference * 20;
}
// sort function to sort by best download server
-bool DownloadManager::ServerByPreference(const std::map<QString, int> &preferredServers, const QVariant &LHS, const QVariant &RHS)
+//
+bool ServerByPreference(
+ const ServerList::container& preferredServers,
+ const QVariant &LHS, const QVariant &RHS)
{
- return evaluateFileInfoMap(LHS.toMap(), preferredServers) > evaluateFileInfoMap(RHS.toMap(), preferredServers);
+ const auto a = evaluateFileInfoMap(LHS.toMap(), preferredServers);
+ const auto b = evaluateFileInfoMap(RHS.toMap(), preferredServers);
+ return (a > b);
}
int DownloadManager::startDownloadURLs(const QStringList &urls)
@@ -1732,7 +1731,12 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int
return;
}
- std::sort(resultList.begin(), resultList.end(), boost::bind(&DownloadManager::ServerByPreference, m_PreferredServers, _1, _2));
+ const auto servers = m_OrganizerCore->settings().network().servers();
+
+ std::sort(
+ resultList.begin(),
+ resultList.end(),
+ boost::bind(&ServerByPreference, servers.getPreferred(), _1, _2));
info->userData["downloadMap"] = resultList;
diff --git a/src/downloadmanager.h b/src/downloadmanager.h
index feef0eaa..bed1b3cc 100644
--- a/src/downloadmanager.h
+++ b/src/downloadmanager.h
@@ -20,6 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef DOWNLOADMANAGER_H
#define DOWNLOADMANAGER_H
+#include "serverinfo.h"
#include <idownloadmanager.h>
#include <modrepositoryfileinfo.h>
#include <set>
@@ -174,11 +175,6 @@ public:
QString getOutputDirectory() const { return m_OutputDirectory; }
/**
- * @brief setPreferredServers set the list of preferred servers
- */
- void setPreferredServers(const std::map<QString, int> &preferredServers);
-
- /**
* @brief set the list of supported extensions
* @param extensions list of supported extensions
*/
@@ -360,15 +356,6 @@ public:
*/
void refreshList();
- /**
- * @brief Sort function for download servers
- * @param LHS
- * @param RHS
- * @return
- */
- static bool ServerByPreference(const std::map<QString, int> &preferredServers, const QVariant &LHS, const QVariant &RHS);
-
-
virtual int startDownloadURLs(const QStringList &urls);
virtual int startDownloadNexusFile(int modID, int fileID);
@@ -548,7 +535,6 @@ private:
QVector<DownloadInfo*> m_ActiveDownloads;
QString m_OutputDirectory;
- std::map<QString, int> m_PreferredServers;
QStringList m_SupportedExtensions;
std::set<int> m_RequestIDs;
QVector<int> m_AlphabeticalTranslation;
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp
index 3ec3d64f..7823fadc 100644
--- a/src/editexecutablesdialog.cpp
+++ b/src/editexecutablesdialog.cpp
@@ -65,6 +65,11 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent)
EditExecutablesDialog::~EditExecutablesDialog() = default;
+int EditExecutablesDialog::exec()
+{
+ GeometrySaver gs(Settings::instance(), this);
+ return QDialog::exec();
+}
void EditExecutablesDialog::loadCustomOverwrites()
{
diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h
index 9715489e..494f0651 100644
--- a/src/editexecutablesdialog.h
+++ b/src/editexecutablesdialog.h
@@ -151,6 +151,10 @@ public:
~EditExecutablesDialog();
+ // also saves and restores geometry
+ //
+ int exec() override;
+
ExecutablesList getExecutablesList() const;
const CustomOverwrites& getCustomOverwrites() const;
const ForcedLibraries& getForcedLibraries() const;
diff --git a/src/executableslist.cpp b/src/executableslist.cpp
index 3f76bb6f..dce9181b 100644
--- a/src/executableslist.cpp
+++ b/src/executableslist.cpp
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "iplugingame.h"
#include "utility.h"
+#include "settings.h"
#include <log.h>
#include <QFileInfo>
@@ -64,7 +65,7 @@ bool ExecutablesList::empty() const
return m_Executables.empty();
}
-void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings)
+void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s)
{
log::debug("loading executables");
@@ -74,32 +75,29 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings)
// executables from 2.2.0, see upgradeFromCustom()
bool needsUpgrade = false;
- int numCustomExecutables = settings.beginReadArray("customExecutables");
- for (int i = 0; i < numCustomExecutables; ++i) {
- settings.setArrayIndex(i);
-
+ for (auto& map : s.executables()) {
Executable::Flags flags;
- if (settings.value("toolbar", false).toBool())
+
+ if (map["toolbar"].toBool())
flags |= Executable::ShowInToolbar;
- if (settings.value("ownicon", false).toBool())
+
+ if (map["ownicon"].toBool())
flags |= Executable::UseApplicationIcon;
- if (settings.contains("custom")) {
+ if (map.contains("custom")) {
// the "custom" setting only exists in older versions
needsUpgrade = true;
}
setExecutable(Executable()
- .title(settings.value("title").toString())
- .binaryInfo(settings.value("binary").toString())
- .arguments(settings.value("arguments").toString())
- .steamAppID(settings.value("steamAppID", "").toString())
- .workingDirectory(settings.value("workingDirectory", "").toString())
+ .title(map["title"].toString())
+ .binaryInfo(map["binary"].toString())
+ .arguments(map["arguments"].toString())
+ .steamAppID(map["steamAppID"].toString())
+ .workingDirectory(map["workingDirectory"].toString())
.flags(flags));
}
- settings.endArray();
-
addFromPlugin(game, IgnoreExisting);
if (needsUpgrade)
@@ -108,26 +106,25 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings)
dump();
}
-void ExecutablesList::store(QSettings& settings)
+void ExecutablesList::store(Settings& s)
{
- settings.remove("customExecutables");
- settings.beginWriteArray("customExecutables");
-
- int count = 0;
+ std::vector<std::map<QString, QVariant>> v;
for (const auto& item : *this) {
- settings.setArrayIndex(count++);
+ std::map<QString, QVariant> map;
+
+ map["title"] = item.title();
+ map["toolbar"] = item.isShownOnToolbar();
+ map["ownicon"] = item.usesOwnIcon();
+ map["binary"] = item.binaryInfo().absoluteFilePath();
+ map["arguments"] = item.arguments();
+ map["workingDirectory"] = item.workingDirectory();
+ map["steamAppID"] = item.steamAppID();
- settings.setValue("title", item.title());
- settings.setValue("toolbar", item.isShownOnToolbar());
- settings.setValue("ownicon", item.usesOwnIcon());
- settings.setValue("binary", item.binaryInfo().absoluteFilePath());
- settings.setValue("arguments", item.arguments());
- settings.setValue("workingDirectory", item.workingDirectory());
- settings.setValue("steamAppID", item.steamAppID());
+ v.push_back(std::move(map));
}
- settings.endArray();
+ s.setExecutables(v);
}
std::vector<Executable> ExecutablesList::getPluginExecutables(
diff --git a/src/executableslist.h b/src/executableslist.h
index eda2034e..23cf3cfe 100644
--- a/src/executableslist.h
+++ b/src/executableslist.h
@@ -29,6 +29,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QMetaType>
namespace MOBase { class IPluginGame; class ExecutableInfo; }
+class Settings;
/*!
* @brief Information about an executable
@@ -103,7 +104,7 @@ public:
/**
* @brief initializes the list from the settings and the given plugin
**/
- void load(const MOBase::IPluginGame* game, QSettings& settings);
+ void load(const MOBase::IPluginGame* game, const Settings& settings);
/**
* @brief re-adds all the executables from the plugin and renames existing
@@ -114,7 +115,7 @@ public:
/**
* @brief writes the current list to the settings
*/
- void store(QSettings& settings);
+ void store(Settings& settings);
/**
* @brief get an executable by name
diff --git a/src/expanderwidget.cpp b/src/expanderwidget.cpp
index 2f47da5b..a9d045a5 100644
--- a/src/expanderwidget.cpp
+++ b/src/expanderwidget.cpp
@@ -52,3 +52,30 @@ bool ExpanderWidget::opened() const
{
return opened_;
}
+
+QByteArray ExpanderWidget::saveState() const
+{
+ QByteArray result;
+ QDataStream stream(&result, QIODevice::WriteOnly);
+
+ stream << opened();
+
+ return result;
+}
+
+void ExpanderWidget::restoreState(const QByteArray& a)
+{
+ QDataStream stream(a);
+
+ bool opened = false;
+ stream >> opened;
+
+ if (stream.status() == QDataStream::Ok) {
+ toggle(opened);
+ }
+}
+
+QToolButton* ExpanderWidget::button() const
+{
+ return m_button;
+}
diff --git a/src/expanderwidget.h b/src/expanderwidget.h
index da3eb9d6..99b2d303 100644
--- a/src/expanderwidget.h
+++ b/src/expanderwidget.h
@@ -37,6 +37,11 @@ public:
**/
bool opened() const;
+ QByteArray saveState() const;
+ void restoreState(const QByteArray& a);
+
+ QToolButton* button() const;
+
private:
QToolButton* m_button;
QWidget* m_content;
diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp
index 0e3e9793..8cfeb6b5 100644
--- a/src/filedialogmemory.cpp
+++ b/src/filedialogmemory.cpp
@@ -18,44 +18,21 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "filedialogmemory.h"
+#include "settings.h"
#include <QFileDialog>
+static std::map<QString, QString> g_Cache;
-FileDialogMemory::FileDialogMemory()
+void FileDialogMemory::save(Settings& s)
{
+ s.paths().setRecent(g_Cache);
}
-
-void FileDialogMemory::save(QSettings &settings)
+void FileDialogMemory::restore(const Settings& s)
{
- settings.remove("recentDirectories");
- settings.beginWriteArray("recentDirectories");
- int index = 0;
- for (std::map<QString, QString>::const_iterator iter = instance().m_Cache.begin();
- iter != instance().m_Cache.end(); ++iter) {
- settings.setArrayIndex(index++);
- settings.setValue("name", iter->first);
- settings.setValue("directory", iter->second);
- }
- settings.endArray();
+ g_Cache = s.paths().recent();
}
-
-void FileDialogMemory::restore(QSettings &settings)
-{
- int size = settings.beginReadArray("recentDirectories");
- for (int i = 0; i < size; ++i) {
- settings.setArrayIndex(i);
- QVariant name = settings.value("name");
- QVariant dir = settings.value("directory");
- if (name.isValid() && dir.isValid()) {
- instance().m_Cache.insert(std::make_pair(name.toString(), dir.toString()));
- }
- }
- settings.endArray();
-}
-
-
QString FileDialogMemory::getOpenFileName(
const QString &dirID, QWidget *parent, const QString &caption,
const QString &dir, const QString &filter, QString *selectedFilter,
@@ -64,8 +41,8 @@ QString FileDialogMemory::getOpenFileName(
QString currentDir = dir;
if (currentDir.isEmpty()) {
- auto itor = instance().m_Cache.find(dirID);
- if (itor != instance().m_Cache.end()) {
+ auto itor = g_Cache.find(dirID);
+ if (itor != g_Cache.end()) {
currentDir = itor->second;
}
}
@@ -74,7 +51,7 @@ QString FileDialogMemory::getOpenFileName(
parent, caption, currentDir, filter, selectedFilter, options);
if (!result.isNull()) {
- instance().m_Cache[dirID] = QFileInfo(result).path();
+ g_Cache[dirID] = QFileInfo(result).path();
}
return result;
@@ -88,8 +65,8 @@ QString FileDialogMemory::getExistingDirectory(
QString currentDir = dir;
if (currentDir.isEmpty()) {
- auto itor = instance().m_Cache.find(dirID);
- if (itor != instance().m_Cache.end()) {
+ auto itor = g_Cache.find(dirID);
+ if (itor != g_Cache.end()) {
currentDir = itor->second;
}
}
@@ -98,15 +75,8 @@ QString FileDialogMemory::getExistingDirectory(
parent, caption, currentDir, options);
if (!result.isNull()) {
- instance().m_Cache[dirID] = QFileInfo(result).path();
+ g_Cache[dirID] = result;
}
return result;
}
-
-
-FileDialogMemory &FileDialogMemory::instance()
-{
- static FileDialogMemory instance;
- return instance;
-}
diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h
index 81d7ba40..8b8a3b76 100644
--- a/src/filedialogmemory.h
+++ b/src/filedialogmemory.h
@@ -23,15 +23,17 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <map>
#include <QString>
-#include <QSettings>
#include <QFileDialog>
+class Settings;
class FileDialogMemory
{
public:
- static void save(QSettings &settings);
- static void restore(QSettings &settings);
+ FileDialogMemory() = delete;
+
+ static void save(Settings& settings);
+ static void restore(const Settings& settings);
static QString getOpenFileName(
const QString &dirID, QWidget *parent = 0, const QString &caption = QString(),
@@ -42,12 +44,6 @@ public:
const QString &dirID, QWidget *parent = 0, const QString &caption = QString(),
const QString &dir = QString(),
QFileDialog::Options options = QFileDialog::ShowDirsOnly);
-
-private:
- std::map<QString, QString> m_Cache;
-
- FileDialogMemory();
- static FileDialogMemory &instance();
};
#endif // FILEDIALOGMEMORY_H
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp
index 89d0079f..522489e4 100644
--- a/src/installationmanager.cpp
+++ b/src/installationmanager.cpp
@@ -476,13 +476,18 @@ QString InstallationManager::generateBackupName(const QString &directoryName) co
bool InstallationManager::testOverwrite(GuessedValue<QString> &modName, bool *merge) const
{
QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName);
+
while (QDir(targetDirectory).exists()) {
Settings &settings(Settings::instance());
- bool backup = settings.directInterface().value("backup_install", false).toBool();
- QueryOverwriteDialog overwriteDialog(m_ParentWidget,
- backup ? QueryOverwriteDialog::BACKUP_YES : QueryOverwriteDialog::BACKUP_NO);
+
+ const bool backup = settings.keepBackupOnInstall();
+ QueryOverwriteDialog overwriteDialog(
+ m_ParentWidget,
+ backup ? QueryOverwriteDialog::BACKUP_YES : QueryOverwriteDialog::BACKUP_NO);
+
if (overwriteDialog.exec()) {
- settings.directInterface().setValue("backup_install", overwriteDialog.backup());
+ settings.setKeepBackupOnInstall(overwriteDialog.backup());
+
if (overwriteDialog.backup()) {
QString backupDirectory = generateBackupName(targetDirectory);
if (!copyDir(targetDirectory, backupDirectory, false)) {
diff --git a/src/iuserinterface.h b/src/iuserinterface.h
index bba8de2b..a309ed9b 100644
--- a/src/iuserinterface.h
+++ b/src/iuserinterface.h
@@ -10,14 +10,9 @@
#include <QMenu>
-class QSettings;
-
class IUserInterface
{
public:
-
- virtual void storeSettings(QSettings &settings) = 0;
-
virtual void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr) = 0;
virtual void registerPluginTools(std::vector<MOBase::IPluginTool *> toolPlugins) = 0;
virtual void registerModPage(MOBase::IPluginModPage *modPage) = 0;
diff --git a/src/listdialog.cpp b/src/listdialog.cpp
index b9857070..2ad88408 100644
--- a/src/listdialog.cpp
+++ b/src/listdialog.cpp
@@ -17,6 +17,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "listdialog.h"
#include "ui_listdialog.h"
+#include "settings.h"
ListDialog::ListDialog(QWidget *parent)
: QDialog(parent)
@@ -32,6 +33,12 @@ ListDialog::~ListDialog()
delete ui;
}
+int ListDialog::exec()
+{
+ GeometrySaver gs(Settings::instance(), this);
+ return QDialog::exec();
+}
+
void ListDialog::setChoices(QStringList choices)
{
m_Choices = choices;
diff --git a/src/listdialog.h b/src/listdialog.h
index 7b5a5461..d0594bd7 100644
--- a/src/listdialog.h
+++ b/src/listdialog.h
@@ -15,6 +15,10 @@ public:
explicit ListDialog(QWidget *parent = nullptr);
~ListDialog();
+ // also saves and restores geometry
+ //
+ int exec() override;
+
void setChoices(QStringList choices);
QString getChoice() const;
diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp
index 06e9f201..0c81b7b2 100644
--- a/src/loadmechanism.cpp
+++ b/src/loadmechanism.cpp
@@ -49,3 +49,16 @@ void LoadMechanism::activate(EMechanism)
{
// no-op
}
+
+
+QString toString(LoadMechanism::EMechanism e)
+{
+ switch (e)
+ {
+ case LoadMechanism::LOAD_MODORGANIZER:
+ return "ModOrganizer";
+
+ default:
+ return QString("unknown (%1)").arg(static_cast<int>(e));
+ }
+}
diff --git a/src/loadmechanism.h b/src/loadmechanism.h
index 49eb0c52..151e804f 100644
--- a/src/loadmechanism.h
+++ b/src/loadmechanism.h
@@ -56,4 +56,6 @@ private:
};
+QString toString(LoadMechanism::EMechanism e);
+
#endif // LOADMECHANISM_H
diff --git a/src/loglist.h b/src/loglist.h
index 0b25dfd1..36671be4 100644
--- a/src/loglist.h
+++ b/src/loglist.h
@@ -61,6 +61,8 @@ private:
class LogList : public QTreeView
{
+ Q_OBJECT;
+
public:
LogList(QWidget* parent=nullptr);
diff --git a/src/main.cpp b/src/main.cpp
index 911f11c3..b5568fec 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -62,7 +62,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QCheckBox>
#include <QDir>
#include <QFileInfo>
-#include <QSettings>
#include <QWhatsThis>
#include <QToolBar>
#include <QFileDialog>
@@ -116,7 +115,7 @@ bool bootstrap()
shellDelete(QStringList(backupDirectory));
}
- // cycle logfile
+ // cycle log file
removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()),
"usvfs*.log", 5, QDir::Name);
@@ -245,9 +244,10 @@ static bool HaveWriteAccess(const std::wstring &path)
}
-QString determineProfile(QStringList &arguments, const QSettings &settings)
+QString determineProfile(QStringList &arguments, const Settings &settings)
{
- QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray());
+ 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)) {
@@ -257,56 +257,61 @@ QString determineProfile(QStringList &arguments, const QSettings &settings)
arguments.removeAt(profileIndex);
arguments.removeAt(profileIndex);
}
- if (selectedProfileName.isEmpty()) {
+
+ if (!selectedProfileName) {
log::debug("no configured profile");
selectedProfileName = "Default";
} else {
- log::debug("configured profile: {}", selectedProfileName);
+ log::debug("configured profile: {}", *selectedProfileName);
}
- return selectedProfileName;
+ return *selectedProfileName;
}
-MOBase::IPluginGame *selectGame(QSettings &settings, QDir const &gamePath, MOBase::IPluginGame *game)
+MOBase::IPluginGame *selectGame(
+ Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game)
{
- settings.setValue("gameName", game->gameName());
- //Sadly, hookdll needs gamePath in order to run. So following code block is
- //commented out
- /*if (gamePath == game->gameDirectory()) {
- settings.remove("gamePath");
- } else*/ {
- QString gameDir = gamePath.absolutePath();
- game->setGamePath(gameDir);
- settings.setValue("gamePath", QDir::toNativeSeparators(gameDir).toUtf8().constData());
- }
- return game; //Woot
+ settings.game().setName(game->gameName());
+
+ QString gameDir = gamePath.absolutePath();
+ game->setGamePath(gameDir);
+
+ settings.game().setDirectory(gameDir);
+
+ return game;
}
-MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &settings, PluginContainer const &plugins)
+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.
- QString gameName = settings.value("gameName", "").toString();
- bool gameConfigured = !gameName.isEmpty();
+ const auto gameName = settings.game().name();
+ const bool gameConfigured = (gameName.has_value() && *gameName != "");
+
if (gameConfigured) {
- MOBase::IPluginGame *game = plugins.managedGame(gameName);
+ MOBase::IPluginGame *game = plugins.managedGame(*gameName);
if (game == nullptr) {
- reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName));
+ reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(*gameName));
return nullptr;
}
- QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray());
- if (gamePath == "") {
+
+ auto gamePath = settings.game().directory();
+ if (!gamePath || *gamePath == "") {
gamePath = game->gameDirectory().absolutePath();
}
- QDir gameDir(gamePath);
+
+ 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));
+ "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath));
}
+
if (game->looksValid(gameDir)) {
return selectGame(settings, gameDir, game);
}
@@ -315,17 +320,20 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett
//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) {
- QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray());
- reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".").
- arg(gameName).arg(gamePath));
+ 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));
+ 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<IPluginGame>()) {
//If a game is already configured, skip any plugins that are not for that game
- if (gameConfigured && gameName.compare(game->gameName(), Qt::CaseInsensitive) != 0)
+ if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0)
continue;
//Only add games that are installed
@@ -349,9 +357,11 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett
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);
+ 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);
@@ -362,7 +372,7 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett
QList<IPluginGame *> possibleGames;
for (IPluginGame * const game : plugins.plugins<IPluginGame>()) {
//If a game is already configured, skip any plugins that are not for that game
- if (gameConfigured && gameName.compare(game->gameName(), Qt::CaseInsensitive) != 0)
+ if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0)
continue;
//Only try plugins that look valid for this directory
@@ -370,24 +380,31 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett
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));
+ 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<IPluginGame *>());
} else {
- reportError(gameConfigured ? QObject::tr("Canceled finding %1 in \"%2\".").arg(gameName).arg(gamePath)
- : QObject::tr("Canceled finding game in \"%1\".").arg(gamePath));
+ 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));
+ reportError(
+ QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.")
+ .arg(*gameName).arg(gamePath));
} else {
QString supportedGames;
@@ -480,27 +497,6 @@ static QString getVersionDisplayString()
return createVersionInfo().displayString(3);
}
-void dumpSettings(QSettings& settings)
-{
- static const QStringList ignore({
- "username", "password", "nexus_api_key"
- });
-
- log::debug("settings:");
-
- settings.beginGroup("Settings");
-
- for (auto k : settings.allKeys()) {
- if (ignore.contains(k, Qt::CaseInsensitive)) {
- continue;
- }
-
- log::debug(" . {}={}", k, settings.value(k).toString());
- }
-
- settings.endGroup();
-}
-
void checkMissingFiles()
{
// files that are likely to be eaten
@@ -557,7 +553,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
log::warn("no ssl support");
}
- QString dataPath = application.property("dataPath").toString();
+ const QString dataPath = application.property("dataPath").toString();
log::info("data path: {}", dataPath);
if (!bootstrap()) {
@@ -573,21 +569,17 @@ int runApplication(MOApplication &application, SingleInstance &instance,
try {
log::info("working directory: {}", QDir::currentPath());
- QSettings initSettings(
- dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()),
- QSettings::IniFormat);
-
- Settings settings(initSettings);
- log::getDefault().setLevel(settings.logLevel());
+ Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()));
+ log::getDefault().setLevel(settings.diagnostics().logLevel());
// global crashDumpType sits in OrganizerCore to make a bit less ugly to
// update it when the settings are changed during runtime
- OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType());
+ OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType());
env::Environment env;
env.dump();
- dumpSettings(initSettings);
+ settings.dump();
sanityChecks(env);
log::debug("initializing core");
@@ -602,7 +594,8 @@ int runApplication(MOApplication &application, SingleInstance &instance,
pluginContainer.loadPlugins();
MOBase::IPluginGame *game = determineCurrentGame(
- application.applicationDirPath(), initSettings, pluginContainer);
+ application.applicationDirPath(), settings, pluginContainer);
+
if (game == nullptr) {
InstanceManager &instance = InstanceManager::instance();
QString instanceName = instance.currentInstance();
@@ -612,6 +605,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
}
return 1;
}
+
if (splashPath.startsWith(':')) {
// currently using MO splash, see if the plugin contains one
QString pluginSplash
@@ -625,7 +619,11 @@ int runApplication(MOApplication &application, SingleInstance &instance,
organizer.setManagedGame(game);
organizer.createDefaultProfile();
- if (!initSettings.contains("game_edition")) {
+ QString edition;
+
+ if (auto v=settings.game().edition()) {
+ edition = *v;
+ } else {
QStringList editions = game->gameVariants();
if (editions.size() > 1) {
SelectionDialog selection(
@@ -641,78 +639,70 @@ int runApplication(MOApplication &application, SingleInstance &instance,
if (selection.exec() == QDialog::Rejected) {
return 1;
} else {
- initSettings.setValue("game_edition", selection.getChoiceString());
+ edition = selection.getChoiceString();
+ settings.game().setEdition(edition);
}
}
}
- game->setGameVariant(initSettings.value("game_edition").toString());
+
+ Q_ASSERT(!edition.isEmpty());
+
+ game->setGameVariant(edition);
log::info("managing game at {}", game->gameDirectory().absolutePath());
- organizer.updateExecutablesList(initSettings);
+ organizer.updateExecutablesList();
- QString selectedProfileName = determineProfile(arguments, initSettings);
+ QString selectedProfileName = determineProfile(arguments, 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.runShortcut(shortcut);
- 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
- // 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 (MOShortcut shortcut{ arguments.at(1) }) {
+ if (shortcut.hasExecutable()) {
+ try {
+ organizer.runShortcut(shortcut);
+ 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
+ // 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;
+ }
+ }
+ }
QPixmap pixmap(splashPath);
QSplashScreen splash(pixmap);
- if (initSettings.contains("window_monitor")) {
- const int monitor = initSettings.value("window_monitor").toInt();
-
- if (monitor != -1 && QGuiApplication::screens().size() > monitor) {
- QGuiApplication::screens().at(monitor)->geometry().center();
- const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center();
- splash.move(center - splash.rect().center());
- } else {
- const QPoint center = QGuiApplication::primaryScreen()->geometry().center();
- splash.move(center - splash.rect().center());
- }
- }
-
+ settings.geometry().centerOnMainWindowMonitor(&splash);
splash.show();
splash.activateWindow();
QString apiKey;
- if (organizer.settings().getNexusApiKey(apiKey)) {
+ if (settings.nexus().apiKey(apiKey)) {
NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey);
}
@@ -722,15 +712,16 @@ int runApplication(MOApplication &application, SingleInstance &instance,
+ QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
&organizer);
- if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) {
+ if (!application.setStyleFile(settings.interface().styleName().value_or(""))) {
// disable invalid stylesheet
- initSettings.setValue("Settings/style", "");
+ settings.interface().setStyleName("");
}
int res = 1;
+
{ // scope to control lifetime of mainwindow
// set up main window and its data structures
- MainWindow mainWindow(initSettings, organizer, pluginContainer);
+ MainWindow mainWindow(settings, organizer, pluginContainer);
NexusInterface::instance(&pluginContainer)
->getAccessManager()->setTopLevelWidget(&mainWindow);
@@ -740,30 +731,31 @@ int runApplication(MOApplication &application, SingleInstance &instance,
QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer,
SLOT(externalMessage(QString)));
- mainWindow.processUpdates();
+ mainWindow.processUpdates(settings);
// this must be before readSettings(), see DockFixer in mainwindow.cpp
splash.finish(&mainWindow);
- mainWindow.readSettings();
-
log::debug("displaying main window");
mainWindow.show();
mainWindow.activateWindow();
splash.finish(&mainWindow);
- const auto ret = application.exec();
+ res = application.exec();
NexusInterface::instance(&pluginContainer)
->getAccessManager()->setTopLevelWidget(nullptr);
-
- return ret;
}
+
+ settings.geometry().resetIfNeeded();
+ return res;
+
} catch (const std::exception &e) {
reportError(e.what());
- return 1;
}
+
+ return 1;
}
int doCoreDump(env::CoreDumpTypes type)
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 28405819..657c1a27 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -138,7 +138,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QRect>
#include <QRegExp>
#include <QResizeEvent>
-#include <QSettings>
#include <QScopedPointer>
#include <QSizePolicy>
#include <QSize>
@@ -195,115 +194,16 @@ const QSize MediumToolbarSize(32, 32);
const QSize LargeToolbarSize(42, 36);
-// this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock
-// sizes are not restored when the main window is maximized; it is used in
-// MainWindow::readSettings() and MainWindow::storeSettings()
-//
-// there's also https://stackoverflow.com/questions/44005852, which has what
-// seems to be a popular fix, but it breaks the restored size of the window
-// by setting it to the desktop's resolution, so that doesn't work
-//
-// the only fix I could find is to remember the sizes of the docks and manually
-// setting them back; saving is straightforward, but restoring is messy
-//
-// this also depends on the window being visible before the timer in restore()
-// is fired and the timer must be processed by application.exec(); therefore,
-// the splash screen _must_ be closed before readSettings() is called, because
-// it has its own event loop, which seems to interfere with this
-//
-// all of this should become unnecessary when QTBUG-46620 is fixed
-//
-class DockFixer
-{
-public:
- static void save(MainWindow* mw, QSettings& settings)
- {
- const auto docks = mw->findChildren<QDockWidget*>();
-
- // saves the size of each dock
- for (int i=0; i<docks.size(); ++i) {
- int size = 0;
-
- // save the width for horizontal docks, or the height for vertical
- if (orientation(mw, docks[i]) == Qt::Horizontal) {
- size = docks[i]->size().width();
- } else {
- size = docks[i]->size().height();
- }
-
- settings.setValue(settingName(docks[i]), size);
- }
- }
-
- static void restore(MainWindow* mw, const QSettings& settings)
- {
- struct DockInfo
- {
- QDockWidget* d;
- int size = 0;
- Qt::Orientation ori;
- };
-
- std::vector<DockInfo> dockInfos;
-
- const auto docks = mw->findChildren<QDockWidget*>();
-
- // for each dock
- for (int i=0; i<docks.size(); ++i) {
- const QString name = settingName(docks[i]);
-
- if (settings.contains(name)) {
- // remember this dock, its size and orientation
- const auto size = settings.value(name).toInt();
- dockInfos.push_back({docks[i], size, orientation(mw, docks[i])});
- }
- }
-
- // the main window must have had time to process the settings from
- // readSettings() or it seems to override whatever is set here
- //
- // some people said a single processEvents() call is enough, but it doesn't
- // look like it
- QTimer::singleShot(1, [=] {
- for (const auto& info : dockInfos) {
- mw->resizeDocks({info.d}, {info.size}, info.ori);
- }
- });
- }
-
- static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d)
- {
- // docks in these areas are horizontal
- const auto horizontalAreas =
- Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea;
-
- if (mw->dockWidgetArea(d) & horizontalAreas) {
- return Qt::Horizontal;
- } else {
- return Qt::Vertical;
- }
- }
-
- static QString settingName(QDockWidget* d)
- {
- return "geometry/" + d->objectName() + "_size";
- }
-};
-
-
-MainWindow::MainWindow(QSettings &initSettings
+MainWindow::MainWindow(Settings &settings
, OrganizerCore &organizerCore
, PluginContainer &pluginContainer
, QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_WasVisible(false)
- , m_menuBarVisible(true)
- , m_statusBarVisible(true)
, m_linksSeparator(nullptr)
, m_Tutorial(this, "MainWindow")
, m_OldProfileIndex(-1)
- , m_ModListGroupingProxy(nullptr)
, m_ModListSortProxy(nullptr)
, m_OldExecutableIndex(-1)
, m_CategoryFactory(CategoryFactory::instance())
@@ -322,11 +222,11 @@ MainWindow::MainWindow(QSettings &initSettings
{
QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800);
- QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory());
- QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory());
+ QWebEngineProfile::defaultProfile()->setCachePath(settings.paths().cache());
+ QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache());
ui->setupUi(this);
- m_statusBar.reset(new StatusBar(statusBar(), ui));
+ ui->statusBar->setup(ui);
{
auto* ni = NexusInterface::instance(&m_PluginContainer);
@@ -350,18 +250,15 @@ MainWindow::MainWindow(QSettings &initSettings
// in the rare case where the user restarts MO through the settings, this
// will correctly pick up the previous values
updateWindowTitle(ni->getAPIUserAccount());
- m_statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount());
+ ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount());
}
- languageChange(m_OrganizerCore.settings().language());
+ languageChange(settings.interface().language());
m_CategoryFactory.loadCategories();
ui->logList->setCore(m_OrganizerCore);
- int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value
- ui->topLevelSplitter->setSizes(QList<int>() << splitterSize - 100 << 100);
-
updateProblemsButton();
setupToolbar();
@@ -369,41 +266,7 @@ MainWindow::MainWindow(QSettings &initSettings
TaskProgressManager::instance().tryCreateTaskbar();
- // set up mod list
- m_ModListSortProxy = m_OrganizerCore.createModListProxyModel();
-
- ui->modList->setModel(m_ModListSortProxy);
-
- GenericIconDelegate *contentDelegate = new GenericIconDelegate(ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150);
- connect(ui->modList->header(), SIGNAL(sectionResized(int,int,int)), contentDelegate, SLOT(columnResized(int,int,int)));
- ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder);
- ModFlagIconDelegate *flagDelegate = new ModFlagIconDelegate(ui->modList, ModList::COL_FLAGS, 120);
- connect(ui->modList->header(), SIGNAL(sectionResized(int,int,int)), flagDelegate, SLOT(columnResized(int,int,int)));
- ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate);
- ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate);
- ui->modList->header()->installEventFilter(m_OrganizerCore.modList());
- connect(ui->modList->header(), SIGNAL(sectionResized(int, int, int)), this, SLOT(modListSectionResized(int, int, int)));
-
- bool modListAdjusted = registerWidgetState(ui->modList->objectName(), ui->modList->header(), "mod_list_state");
-
- if (modListAdjusted) {
- // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that
- for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) {
- int sectionSize = ui->modList->header()->sectionSize(column);
- ui->modList->header()->resizeSection(column, sectionSize + 1);
- ui->modList->header()->resizeSection(column, sectionSize);
- }
- } else {
- // hide these columns by default
- ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true);
- ui->modList->header()->setSectionHidden(ModList::COL_MODID, true);
- ui->modList->header()->setSectionHidden(ModList::COL_GAME, true);
- ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true);
- ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true);
- }
-
- ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); // prevent the name-column from being hidden
- ui->modList->installEventFilter(m_OrganizerCore.modList());
+ setupModList();
// set up plugin list
m_PluginListSortProxy = m_OrganizerCore.createPluginListProxyModel();
@@ -416,15 +279,17 @@ MainWindow::MainWindow(QSettings &initSettings
ui->bsaList->setLocalMoveOnly(true);
initDownloadView();
- bool pluginListAdjusted = registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state");
- registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header());
- registerWidgetState(ui->downloadView->objectName(),
- ui->downloadView->header());
+
+ const bool pluginListAdjusted =
+ settings.geometry().restoreState(ui->espList->header());
+
+ settings.geometry().restoreState(ui->dataTree->header());
+ settings.geometry().restoreState(ui->downloadView->header());
ui->splitter->setStretchFactor(0, 3);
ui->splitter->setStretchFactor(1, 2);
- resizeLists(modListAdjusted, pluginListAdjusted);
+ resizeLists(pluginListAdjusted);
QMenu *linkMenu = new QMenu(this);
m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar()));
@@ -461,8 +326,6 @@ MainWindow::MainWindow(QSettings &initSettings
connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*)));
- connect(ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool)));
-
connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool)));
connect(m_ModListSortProxy, SIGNAL(layoutChanged()), this, SLOT(updateModCount()));
connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString)));
@@ -509,7 +372,6 @@ MainWindow::MainWindow(QSettings &initSettings
connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString)));
connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int)));
- connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder)));
connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint)));
connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); });
connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); });
@@ -523,7 +385,6 @@ MainWindow::MainWindow(QSettings &initSettings
connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList()));
connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection)));
- connect(ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(modlistSelectionsChanged(QItemSelection)));
new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated()));
new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated()));
@@ -540,8 +401,7 @@ MainWindow::MainWindow(QSettings &initSettings
connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas()));
m_SaveMetaTimer.start(5000);
- setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool());
- FileDialogMemory::restore(initSettings);
+ FileDialogMemory::restore(settings);
fixCategories();
@@ -586,6 +446,80 @@ MainWindow::MainWindow(QSettings &initSettings
updateModCount();
}
+void MainWindow::setupModList()
+{
+ m_ModListSortProxy = m_OrganizerCore.createModListProxyModel();
+ ui->modList->setModel(m_ModListSortProxy);
+ ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder);
+
+
+ connect(
+ ui->modList, SIGNAL(dropModeUpdate(bool)),
+ m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool)));
+
+ connect(
+ ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)),
+ this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder)));
+
+ connect(
+ ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
+ this, SLOT(modlistSelectionsChanged(QItemSelection)));
+
+ connect(
+ ui->modList->header(), SIGNAL(sectionResized(int, int, int)),
+ this, SLOT(modListSectionResized(int, int, int)));
+
+
+ GenericIconDelegate *contentDelegate = new GenericIconDelegate(
+ ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150);
+
+ connect(
+ ui->modList->header(), SIGNAL(sectionResized(int,int,int)),
+ contentDelegate, SLOT(columnResized(int,int,int)));
+
+
+ ModFlagIconDelegate *flagDelegate = new ModFlagIconDelegate(
+ ui->modList, ModList::COL_FLAGS, 120);
+
+ connect(
+ ui->modList->header(), SIGNAL(sectionResized(int,int,int)),
+ flagDelegate, SLOT(columnResized(int,int,int)));
+
+
+ ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate);
+ ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate);
+ ui->modList->header()->installEventFilter(m_OrganizerCore.modList());
+
+
+ if (m_OrganizerCore.settings().geometry().restoreState(ui->modList->header())) {
+ // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that
+ for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) {
+ int sectionSize = ui->modList->header()->sectionSize(column);
+ ui->modList->header()->resizeSection(column, sectionSize + 1);
+ ui->modList->header()->resizeSection(column, sectionSize);
+ }
+ } else {
+ // hide these columns by default
+ ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true);
+ ui->modList->header()->setSectionHidden(ModList::COL_MODID, true);
+ ui->modList->header()->setSectionHidden(ModList::COL_GAME, true);
+ ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true);
+ ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true);
+
+ // resize mod list to fit content
+ for (int i = 0; i < ui->modList->header()->count(); ++i) {
+ ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+
+ ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch);
+ }
+
+ // prevent the name-column from being hidden
+ ui->modList->header()->setSectionHidden(ModList::COL_NAME, false);
+
+ ui->modList->installEventFilter(m_OrganizerCore.modList());
+}
+
void MainWindow::resetActionIcons()
{
// this is a bit of a hack
@@ -688,7 +622,7 @@ void MainWindow::updateWindowTitle(const APIUserAccount& user)
void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user)
{
- m_statusBar->setAPI(stats, user);
+ ui->statusBar->setAPI(stats, user);
}
@@ -700,16 +634,8 @@ void MainWindow::disconnectPlugins()
}
-void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom)
+void MainWindow::resizeLists(bool pluginListCustom)
{
- if (!modListCustom) {
- // resize mod list to fit content
- for (int i = 0; i < ui->modList->header()->count(); ++i) {
- ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
- }
- ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch);
- }
-
// ensure the columns aren't so small you can't see them any more
for (int i = 0; i < ui->modList->header()->count(); ++i) {
if (ui->modList->header()->sectionSize(i) < 10) {
@@ -890,7 +816,7 @@ QMenu* MainWindow::createPopupMenu()
void MainWindow::on_actionMainMenuToggle_triggered()
{
- showMenuBar(!ui->menuBar->isVisible());
+ ui->menuBar->setVisible(!ui->menuBar->isVisible());
}
void MainWindow::on_actionToolBarMainToggle_triggered()
@@ -900,7 +826,7 @@ void MainWindow::on_actionToolBarMainToggle_triggered()
void MainWindow::on_actionStatusBarToggle_triggered()
{
- showStatusBar(!ui->statusBar->isVisible());
+ ui->statusBar->setVisible(!ui->statusBar->isVisible());
}
void MainWindow::on_actionToolBarSmallIcons_triggered()
@@ -952,36 +878,6 @@ void MainWindow::setToolbarButtonStyle(Qt::ToolButtonStyle s)
}
}
-void MainWindow::showMenuBar(bool b)
-{
- ui->menuBar->setVisible(b);
- m_menuBarVisible = b;
-}
-
-void MainWindow::showStatusBar(bool b)
-{
- ui->statusBar->setVisible(b);
- m_statusBarVisible = b;
-
- // the central widget typically has no bottom padding because the status bar
- // is more than enough, but when it's hidden, the bottom widget (currently
- // the log) touches the bottom border of the window, which looks ugly
- //
- // when hiding the statusbar, the central widget is given the same border
- // margin as it has on the top (which is typically 6, as it's the default from
- // the qt designer)
-
- auto m = ui->centralWidget->layout()->contentsMargins();
-
- if (b) {
- m.setBottom(0);
- } else {
- m.setBottom(m.top());
- }
-
- ui->centralWidget->layout()->setContentsMargins(m);
-}
-
void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos)
{
// this allows for getting the context menu even if both the menubar and all
@@ -1063,8 +959,8 @@ void MainWindow::updateProblemsButton()
}
// updating the status bar, may be null very early when MO is starting
- if (m_statusBar) {
- m_statusBar->setNotifications(numProblems > 0);
+ if (ui->statusBar) {
+ ui->statusBar->setNotifications(numProblems > 0);
}
}
@@ -1298,7 +1194,7 @@ void MainWindow::hookUpWindowTutorials()
QString firstLine = QString::fromUtf8(file.readLine());
if (firstLine.startsWith("//WIN")) {
QString windowName = firstLine.mid(6).trimmed();
- if (!m_OrganizerCore.settings().directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) {
+ if (!m_OrganizerCore.settings().interface().isTutorialCompleted(windowName)) {
TutorialManager::instance().activateTutorial(windowName, fileName);
}
}
@@ -1307,6 +1203,8 @@ void MainWindow::hookUpWindowTutorials()
void MainWindow::showEvent(QShowEvent *event)
{
+ readSettings(m_OrganizerCore.settings());
+
refreshFilters();
QMainWindow::showEvent(event);
@@ -1327,7 +1225,7 @@ void MainWindow::showEvent(QShowEvent *event)
hookUpWindowTutorials();
- if (m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) {
+ if (m_OrganizerCore.settings().firstStart()) {
QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial());
if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) {
if (QMessageBox::question(this, tr("Show tutorial?"),
@@ -1346,16 +1244,14 @@ void MainWindow::showEvent(QShowEvent *event)
QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements"));
}
- m_OrganizerCore.settings().directInterface().setValue("first_start", false);
+ m_OrganizerCore.settings().setFirstStart(false);
}
- // this has no visible impact when called before the ui is visible
- int grouping = m_OrganizerCore.settings().directInterface().value("group_state").toInt();
- ui->groupCombo->setCurrentIndex(grouping);
+ m_OrganizerCore.settings().widgets().restoreIndex(ui->groupCombo);
allowListResize();
- m_OrganizerCore.settings().registerAsNXMHandler(false);
+ m_OrganizerCore.settings().nexus().registerAsNXMHandler(false);
m_WasVisible = true;
updateProblemsButton();
}
@@ -1366,7 +1262,10 @@ void MainWindow::closeEvent(QCloseEvent* event)
{
if (!confirmExit()) {
event->ignore();
+ return;
}
+
+ storeSettings(m_OrganizerCore.settings());
}
bool MainWindow::confirmExit()
@@ -1407,12 +1306,6 @@ void MainWindow::cleanup()
m_MetaSave.waitForFinished();
}
-
-void MainWindow::setBrowserGeometry(const QByteArray &geometry)
-{
- m_IntegratedBrowser.restoreGeometry(geometry);
-}
-
void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem)
{
// don't display the widget if the main window doesn't have focus
@@ -1642,18 +1535,6 @@ void MainWindow::startExeAction()
}
-
-void MainWindow::setExecutableIndex(int index)
-{
- QComboBox *executableBox = findChild<QComboBox*>("executablesListBox");
-
- if ((index != 0) && (executableBox->count() > index)) {
- executableBox->setCurrentIndex(index);
- } else {
- executableBox->setCurrentIndex(1);
- }
-}
-
void MainWindow::activateSelectedProfile()
{
m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText());
@@ -1870,7 +1751,7 @@ bool MainWindow::refreshProfiles(bool selectProfile)
profileBox->clear();
profileBox->addItem(QObject::tr("<Manage...>"));
- QDir profilesDir(Settings::instance().getProfileDirectory());
+ QDir profilesDir(Settings::instance().paths().profiles());
profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
QDirIterator profileIter(profilesDir);
@@ -1916,7 +1797,7 @@ void MainWindow::refreshExecutablesList()
++i;
}
- setExecutableIndex(1);
+ ui->executablesListBox->setCurrentIndex(1);
executablesList->setEnabled(true);
}
@@ -2109,7 +1990,7 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString
newItem->setFlags(newItem->flags() & ~(Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable));
newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked);
newItem->setData(0, Qt::UserRole, false);
- if (m_OrganizerCore.settings().forceEnableCoreFiles()
+ if (m_OrganizerCore.settings().game().forceEnableCoreFiles()
&& defaultArchives.contains(fileInfo.fileName())) {
newItem->setCheckState(0, Qt::Checked);
newItem->setDisabled(true);
@@ -2247,62 +2128,49 @@ void MainWindow::activateProxy(bool activate)
busyDialog.hide();
}
-void MainWindow::readSettings()
+void MainWindow::readSettings(const Settings& settings)
{
- QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat);
+ settings.geometry().restoreGeometry(this);
+ settings.geometry().restoreState(this);
+ settings.geometry().restoreDocks(this);
+ settings.geometry().restoreToolbars(this);
+ settings.geometry().restoreState(ui->splitter);
+ settings.geometry().restoreVisibility(ui->menuBar);
+ settings.geometry().restoreVisibility(ui->statusBar);
- if (settings.contains("window_geometry")) {
- restoreGeometry(settings.value("window_geometry").toByteArray());
- }
-
- if (settings.contains("window_state")) {
- restoreState(settings.value("window_state").toByteArray());
- }
-
- if (settings.contains("toolbar_size")) {
- setToolbarSize(settings.value("toolbar_size").toSize());
- }
-
- if (settings.contains("toolbar_button_style")) {
- setToolbarButtonStyle(static_cast<Qt::ToolButtonStyle>(
- settings.value("toolbar_button_style").toInt()));
- }
+ {
+ // special case in case someone puts 0 in the INI
+ auto v = settings.widgets().index(ui->executablesListBox);
+ if (!v || v == 0) {
+ v = 1;
+ }
- if (settings.contains("menubar_visible")) {
- showMenuBar(settings.value("menubar_visible").toBool());
+ ui->executablesListBox->setCurrentIndex(*v);
}
- if (settings.contains("statusbar_visible")) {
- showStatusBar(settings.value("statusbar_visible").toBool());
- }
+ settings.widgets().restoreIndex(ui->groupCombo);
- if (settings.contains("window_split")) {
- ui->splitter->restoreState(settings.value("window_split").toByteArray());
+ {
+ settings.geometry().restoreVisibility(ui->categoriesGroup, false);
+ const auto v = ui->categoriesGroup->isVisible();
+ setCategoryListVisible(v);
+ ui->displayCategoriesBtn->setChecked(v);
}
- if (settings.contains("log_split")) {
- ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray());
+ if (settings.network().useProxy()) {
+ activateProxy(true);
}
+}
- bool filtersVisible = settings.value("filters_visible", false).toBool();
- setCategoryListVisible(filtersVisible);
- ui->displayCategoriesBtn->setChecked(filtersVisible);
+void MainWindow::processUpdates(Settings& settings) {
+ const auto earliest = QVersionNumber::fromString("2.1.2").normalized();
- int selectedExecutable = settings.value("selected_executable").toInt();
- setExecutableIndex(selectedExecutable);
+ const auto lastVersion = settings.version().value_or(earliest);
+ const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber();
- if (settings.value("Settings/use_proxy", false).toBool()) {
- activateProxy(true);
- }
-
- DockFixer::restore(this, settings);
-}
+ settings.processUpdates(currentVersion, lastVersion);
-void MainWindow::processUpdates() {
- QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat);
- QVersionNumber lastVersion = QVersionNumber::fromString(settings.value("version", "2.1.2").toString()).normalized();
- QVersionNumber currentVersion = QVersionNumber::fromString(m_OrganizerCore.getVersion().displayString()).normalized();
- if (!m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) {
+ if (!settings.firstStart()) {
if (lastVersion < QVersionNumber(2, 1, 3)) {
bool lastHidden = true;
for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) {
@@ -2311,24 +2179,11 @@ void MainWindow::processUpdates() {
lastHidden = hidden;
}
}
+
if (lastVersion < QVersionNumber(2, 1, 6)) {
ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true);
}
- if (lastVersion < QVersionNumber(2, 2, 0)) {
- QSettings &instance = Settings::instance().directInterface();
- instance.beginGroup("Settings");
- instance.remove("steam_password");
- instance.remove("nexus_username");
- instance.remove("nexus_password");
- instance.remove("nexus_login");
- instance.remove("nexus_api_key");
- instance.remove("ask_for_nexuspw");
- instance.remove("nmm_version");
- instance.endGroup();
- instance.beginGroup("Servers");
- instance.remove("");
- instance.endGroup();
- }
+
if (lastVersion < QVersionNumber(2, 2, 1)) {
// hide new columns by default
for (int i=DownloadList::COL_MODNAME; i<DownloadList::COL_COUNT; ++i) {
@@ -2337,9 +2192,7 @@ void MainWindow::processUpdates() {
}
}
- if (currentVersion > lastVersion) {
- //NOP
- } else if (currentVersion < lastVersion) {
+ if (currentVersion < lastVersion) {
const auto text = tr(
"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. "
@@ -2349,51 +2202,28 @@ void MainWindow::processUpdates() {
log::warn("{}", text);
}
-
- //save version in all case
- settings.setValue("version", currentVersion.toString());
}
-void MainWindow::storeSettings(QSettings &settings) {
- settings.setValue("group_state", ui->groupCombo->currentIndex());
- settings.setValue("selected_executable",
- ui->executablesListBox->currentIndex());
+void MainWindow::storeSettings(Settings& s)
+{
+ s.geometry().saveState(this);
+ s.geometry().saveGeometry(this);
+ s.geometry().saveDocks(this);
- if (settings.value("reset_geometry", false).toBool()) {
- settings.remove("window_geometry");
- settings.remove("window_state");
- settings.remove("toolbar_size");
- settings.remove("toolbar_button_style");
- settings.remove("menubar_visible");
- settings.remove("window_split");
- settings.remove("window_monitor");
- settings.remove("log_split");
- settings.remove("filters_visible");
- settings.remove("browser_geometry");
- settings.remove("geometry");
- settings.remove("reset_geometry");
- } else {
- settings.setValue("window_geometry", saveGeometry());
- settings.setValue("window_state", saveState());
- settings.setValue("toolbar_size", ui->toolBar->iconSize());
- settings.setValue("toolbar_button_style", static_cast<int>(ui->toolBar->toolButtonStyle()));
- settings.setValue("menubar_visible", m_menuBarVisible);
- settings.setValue("statusbar_visible", m_statusBarVisible);
- settings.setValue("window_split", ui->splitter->saveState());
- QScreen *screen = this->window()->windowHandle()->screen();
- int screenId = QGuiApplication::screens().indexOf(screen);
- settings.setValue("window_monitor", screenId);
- settings.setValue("log_split", ui->topLevelSplitter->saveState());
- settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry());
- settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked());
+ s.geometry().saveVisibility(ui->menuBar);
+ s.geometry().saveVisibility(ui->statusBar);
+ s.geometry().saveToolbars(this);
+ s.geometry().saveState(ui->splitter);
+ s.geometry().saveMainWindowMonitor(this);
+ s.geometry().saveVisibility(ui->categoriesGroup);
- for (const std::pair<QString, QHeaderView*> kv : m_PersistedGeometry) {
- QString key = QString("geometry/") + kv.first;
- settings.setValue(key, kv.second->saveState());
- }
+ s.geometry().saveState(ui->espList->header());
+ s.geometry().saveState(ui->dataTree->header());
+ s.geometry().saveState(ui->downloadView->header());
+ s.geometry().saveState(ui->modList->header());
- DockFixer::save(this, settings);
- }
+ s.widgets().saveIndex(ui->groupCombo);
+ s.widgets().saveIndex(ui->executablesListBox);
}
ILockedWaitingForProcess* MainWindow::lock()
@@ -2508,44 +2338,33 @@ bool MainWindow::modifyExecutablesDialog()
bool result = false;
try {
- const auto oldExecutables = *m_OrganizerCore.executablesList();
-
EditExecutablesDialog dialog(m_OrganizerCore, this);
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(dialog.objectName());
-
- if (settings.contains(key)) {
- dialog.restoreGeometry(settings.value(key).toByteArray());
- }
-
result = (dialog.exec() == QDialog::Accepted);
- settings.setValue(key, dialog.saveGeometry());
refreshExecutablesList();
updatePinnedExecutables();
} catch (const std::exception &e) {
reportError(e.what());
}
+
return result;
}
void MainWindow::on_executablesListBox_currentIndexChanged(int index)
{
- QComboBox* executablesList = findChild<QComboBox*>("executablesListBox");
+ if (!ui->executablesListBox->isEnabled()) {
+ return;
+ }
+
+ const int previousIndex =
+ (m_OldExecutableIndex > 0 ? m_OldExecutableIndex : 1);
- int previousIndex = m_OldExecutableIndex;
m_OldExecutableIndex = index;
- if (executablesList->isEnabled()) {
- //I think the 2nd test is impossible
- if ((index == 0) || (index > static_cast<int>(m_OrganizerCore.executablesList()->size()))) {
- if (modifyExecutablesDialog()) {
- setExecutableIndex(previousIndex);
- }
- } else {
- setExecutableIndex(index);
- }
+ if (index == 0) {
+ modifyExecutablesDialog();
+ ui->executablesListBox->setCurrentIndex(previousIndex);
}
}
@@ -2593,17 +2412,13 @@ void MainWindow::on_actionAdd_Profile_triggered()
ProfilesDialog profilesDialog(m_OrganizerCore.currentProfile()->name(),
m_OrganizerCore.managedGame(),
this);
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(profilesDialog.objectName());
- if (settings.contains(key)) {
- profilesDialog.restoreGeometry(settings.value(key).toByteArray());
- }
+
// workaround: need to disable monitoring of the saves directory, otherwise the active
// profile directory is locked
stopMonitorSaves();
profilesDialog.exec();
- settings.setValue(key, profilesDialog.saveGeometry());
refreshSaveList(); // since the save list may now be outdated we have to refresh it completely
+
if (refreshProfiles() && !profilesDialog.failed()) {
break;
}
@@ -2625,7 +2440,7 @@ void MainWindow::on_actionAdd_Profile_triggered()
void MainWindow::on_actionModify_Executables_triggered()
{
if (modifyExecutablesDialog()) {
- setExecutableIndex(m_OldExecutableIndex);
+ ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex);
}
}
@@ -2659,7 +2474,7 @@ void MainWindow::setESPListSorting(int index)
void MainWindow::refresher_progress(int percent)
{
setEnabled(percent == 100);
- m_statusBar->setProgress(percent);
+ ui->statusBar->setProgress(percent);
}
void MainWindow::directory_refreshed()
@@ -2936,7 +2751,7 @@ void MainWindow::restoreBackup_clicked()
ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
if (backupRegEx.indexIn(modInfo->name()) != -1) {
QString regName = backupRegEx.cap(1);
- QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory()));
+ QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods()));
if (!modDir.exists(regName) ||
(QMessageBox::question(this, tr("Overwrite?"),
tr("This will replace the existing mod \"%1\". Continue?").arg(regName),
@@ -2944,7 +2759,7 @@ void MainWindow::restoreBackup_clicked()
if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) {
reportError(tr("failed to remove mod \"%1\"").arg(regName));
} else {
- QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory()) + "/" + regName;
+ QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods()) + "/" + regName;
if (!modDir.rename(modInfo->absolutePath(), destinationPath)) {
reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath));
}
@@ -3200,7 +3015,7 @@ void MainWindow::untrack_clicked()
void MainWindow::windowTutorialFinished(const QString &windowName)
{
- m_OrganizerCore.settings().directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true);
+ m_OrganizerCore.settings().interface().setTutorialCompleted(windowName);
}
void MainWindow::overwriteClosed(int)
@@ -3208,9 +3023,6 @@ void MainWindow::overwriteClosed(int)
OverwriteInfoDialog *dialog = this->findChild<OverwriteInfoDialog*>("__overwriteDialog");
if (dialog != nullptr) {
m_OrganizerCore.modList()->modInfoChanged(dialog->modInfo());
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(dialog->objectName());
- settings.setValue(key, dialog->saveGeometry());
dialog->deleteLater();
}
m_OrganizerCore.refreshDirectoryStructure();
@@ -3234,11 +3046,7 @@ void MainWindow::displayModInformation(
} else {
qobject_cast<OverwriteInfoDialog*>(dialog)->setModInfo(modInfo);
}
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(dialog->objectName());
- if (settings.contains(key)) {
- dialog->restoreGeometry(settings.value(key).toByteArray());
- }
+
dialog->show();
dialog->raise();
dialog->activateWindow();
@@ -3257,16 +3065,7 @@ void MainWindow::displayModInformation(
dialog.selectTab(tabID);
}
- dialog.restoreState(m_OrganizerCore.settings());
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(dialog.objectName());
- if (settings.contains(key)) {
- dialog.restoreGeometry(settings.value(key).toByteArray());
- }
-
dialog.exec();
- dialog.saveState(m_OrganizerCore.settings());
- settings.setValue(key, dialog.saveGeometry());
modInfo->saveMeta();
emit modInfoDisplayed();
@@ -3845,32 +3644,37 @@ void MainWindow::createSeparator_clicked()
{
m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority);
}
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QColor previousColor = settings.value("previousSeparatorColor", QColor()).value<QColor>();
- if (previousColor.isValid()) {
- ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(previousColor);
- }
+ if (auto c=m_OrganizerCore.settings().colors().previousSeparatorColor()) {
+ ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c);
+ }
}
void MainWindow::setColor_clicked()
{
- QSettings &settings = m_OrganizerCore.settings().directInterface();
+ auto& settings = m_OrganizerCore.settings();
ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
+
QColorDialog dialog(this);
dialog.setOption(QColorDialog::ShowAlphaChannel);
+
QColor currentColor = modInfo->getColor();
- QColor previousColor = settings.value("previousSeparatorColor", QColor()).value<QColor>();
- if (currentColor.isValid())
+ if (currentColor.isValid()) {
dialog.setCurrentColor(currentColor);
- else
- dialog.setCurrentColor(previousColor);
+ }
+ else if (auto c=settings.colors().previousSeparatorColor()) {
+ dialog.setCurrentColor(*c);
+ }
+
if (!dialog.exec())
return;
+
currentColor = dialog.currentColor();
if (!currentColor.isValid())
return;
- settings.setValue("previousSeparatorColor", currentColor);
+
+ settings.colors().setPreviousSeparatorColor(currentColor);
+
QItemSelectionModel *selection = ui->modList->selectionModel();
if (selection->hasSelection() && selection->selectedRows().count() > 1) {
for (QModelIndex idx : selection->selectedRows()) {
@@ -3905,7 +3709,8 @@ void MainWindow::resetColor_clicked()
else {
modInfo->setColor(color);
}
- Settings::instance().directInterface().remove("previousSeparatorColor");
+
+ m_OrganizerCore.settings().colors().removePreviousSeparatorColor();
}
void MainWindow::createModFromOverwrite()
@@ -3951,17 +3756,10 @@ void MainWindow::moveOverwriteContentToExistingMod()
}
ListDialog dialog(this);
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(dialog.objectName());
-
dialog.setWindowTitle("Select a mod...");
dialog.setChoices(mods);
- if (settings.contains(key)) {
- dialog.restoreGeometry(settings.value(key).toByteArray());
- }
if (dialog.exec() == QDialog::Accepted) {
-
QString result = dialog.getChoice();
if (!result.isEmpty()) {
@@ -3983,7 +3781,6 @@ void MainWindow::moveOverwriteContentToExistingMod()
doMoveOverwriteContentToMod(modAbsolutePath);
}
}
- settings.setValue(key, dialog.saveGeometry());
}
void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath)
@@ -4387,7 +4184,7 @@ void MainWindow::checkModsForUpdates()
NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString());
} else {
QString apiKey;
- if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) {
+ if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) {
m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); });
NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey);
} else {
@@ -4590,12 +4387,12 @@ void MainWindow::openIniFolder()
void MainWindow::openDownloadsFolder()
{
- shell::ExploreFile(m_OrganizerCore.settings().getDownloadDirectory());
+ shell::ExploreFile(m_OrganizerCore.settings().paths().downloads());
}
void MainWindow::openModsFolder()
{
- shell::ExploreFile(m_OrganizerCore.settings().getModDirectory());
+ shell::ExploreFile(m_OrganizerCore.settings().paths().mods());
}
void MainWindow::openGameFolder()
@@ -4961,7 +4758,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
menu.addSeparator();
- if (info->getNexusID() > 0 && Settings::instance().endorsementIntegration()) {
+ if (info->getNexusID() > 0 && Settings::instance().nexus().endorsementIntegration()) {
switch (info->endorsedState()) {
case ModInfo::ENDORSED_TRUE: {
menu.addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked()));
@@ -5210,19 +5007,19 @@ void MainWindow::on_actionSettings_triggered()
{
Settings &settings = m_OrganizerCore.settings();
- QString oldModDirectory(settings.getModDirectory());
- QString oldCacheDirectory(settings.getCacheDirectory());
- QString oldProfilesDirectory(settings.getProfileDirectory());
- QString oldManagedGameDirectory(settings.getManagedGameDirectory());
- bool oldDisplayForeign(settings.displayForeign());
- bool proxy = settings.useProxy();
+ QString oldModDirectory(settings.paths().mods());
+ QString oldCacheDirectory(settings.paths().cache());
+ QString oldProfilesDirectory(settings.paths().profiles());
+ QString oldManagedGameDirectory(settings.game().directory().value_or(""));
+ bool oldDisplayForeign(settings.interface().displayForeign());
+ bool proxy = settings.network().useProxy();
DownloadManager *dlManager = m_OrganizerCore.downloadManager();
- SettingsDialog dialog(&m_PluginContainer, &settings, this);
+ SettingsDialog dialog(&m_PluginContainer, settings, this);
dialog.exec();
- if (oldManagedGameDirectory != settings.getManagedGameDirectory()) {
+ if (oldManagedGameDirectory != settings.game().directory()) {
QMessageBox::about(this, tr("Restarting MO"),
tr("Changing the managed game directory requires restarting MO.\n"
"Any pending downloads will be paused.\n\n"
@@ -5232,29 +5029,28 @@ void MainWindow::on_actionSettings_triggered()
}
InstallationManager *instManager = m_OrganizerCore.installationManager();
- instManager->setModsDirectory(settings.getModDirectory());
- instManager->setDownloadDirectory(settings.getDownloadDirectory());
+ instManager->setModsDirectory(settings.paths().mods());
+ instManager->setDownloadDirectory(settings.paths().downloads());
fixCategories();
refreshFilters();
- if (settings.getProfileDirectory() != oldProfilesDirectory) {
+ if (settings.paths().profiles() != oldProfilesDirectory) {
refreshProfiles();
}
- if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) {
+ if (dlManager->getOutputDirectory() != settings.paths().downloads()) {
if (dlManager->downloadsInProgress()) {
MessageDialog::showMessage(tr("Can't change download directory while "
"downloads are in progress!"),
this);
} else {
- dlManager->setOutputDirectory(settings.getDownloadDirectory());
+ dlManager->setOutputDirectory(settings.paths().downloads());
}
}
- dlManager->setPreferredServers(settings.getPreferredServers());
- if ((settings.getModDirectory() != oldModDirectory)
- || (settings.displayForeign() != oldDisplayForeign)) {
+ if ((settings.paths().mods() != oldModDirectory)
+ || (settings.interface().displayForeign() != oldDisplayForeign)) {
m_OrganizerCore.profileRefresh();
}
@@ -5279,18 +5075,19 @@ void MainWindow::on_actionSettings_triggered()
m_OrganizerCore.refreshLists();
}
- if (settings.getCacheDirectory() != oldCacheDirectory) {
- NexusInterface::instance(&m_PluginContainer)->setCacheDirectory(settings.getCacheDirectory());
+ if (settings.paths().cache() != oldCacheDirectory) {
+ NexusInterface::instance(&m_PluginContainer)->setCacheDirectory(
+ settings.paths().cache());
}
- if (proxy != settings.useProxy()) {
- activateProxy(settings.useProxy());
+ if (proxy != settings.network().useProxy()) {
+ activateProxy(settings.network().useProxy());
}
- m_statusBar->checkSettings(m_OrganizerCore.settings());
+ ui->statusBar->checkSettings(m_OrganizerCore.settings());
updateDownloadView();
- m_OrganizerCore.setLogLevel(settings.logLevel());
+ m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel());
m_OrganizerCore.cycleDiagnostics();
toggleMO2EndorseState();
@@ -5596,7 +5393,7 @@ void MainWindow::updateAvailable()
{
ui->actionUpdate->setEnabled(true);
ui->actionUpdate->setToolTip(tr("Update available"));
- m_statusBar->setUpdateAvailable(true);
+ ui->statusBar->setUpdateAvailable(true);
}
@@ -5606,10 +5403,10 @@ void MainWindow::motdReceived(const QString &motd)
// internet connection is faster next time
if (m_StartTime.secsTo(QTime::currentTime()) < 5) {
uint hash = qHash(motd);
- if (hash != m_OrganizerCore.settings().getMotDHash()) {
+ if (hash != m_OrganizerCore.settings().motdHash()) {
MotDDialog dialog(motd);
dialog.exec();
- m_OrganizerCore.settings().setMotDHash(hash);
+ m_OrganizerCore.settings().setMotdHash(hash);
}
}
}
@@ -5732,7 +5529,7 @@ void MainWindow::initDownloadView()
void MainWindow::updateDownloadView()
{
// set the view attribute and default row sizes
- if (m_OrganizerCore.settings().compactDownloads()) {
+ if (m_OrganizerCore.settings().interface().compactDownloads()) {
ui->downloadView->setProperty("downloadView", "compact");
setStyleSheet("DownloadListWidget::item { padding: 4px 2px; }");
} else {
@@ -5745,7 +5542,7 @@ void MainWindow::updateDownloadView()
// reapply global stylesheet on the widget level (!) to override the defaults
//ui->downloadView->setStyleSheet(styleSheet());
- ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().metaDownloads());
+ ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().interface().metaDownloads());
ui->downloadView->style()->unpolish(ui->downloadView);
ui->downloadView->style()->polish(ui->downloadView);
qobject_cast<DownloadListHeader*>(ui->downloadView->header())->customResizeSections();
@@ -5758,7 +5555,7 @@ void MainWindow::modUpdateCheck(std::multimap<QString, int> IDs)
ModInfo::manualUpdateCheck(&m_PluginContainer, this, IDs);
} else {
QString apiKey;
- if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) {
+ if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) {
m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); });
NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey);
} else
@@ -5768,22 +5565,42 @@ void MainWindow::modUpdateCheck(std::multimap<QString, int> IDs)
void MainWindow::toggleMO2EndorseState()
{
- if (Settings::instance().endorsementIntegration()) {
- ui->actionEndorseMO->setVisible(true);
- if (Settings::instance().directInterface().contains("endorse_state")) {
- ui->actionEndorseMO->menu()->setEnabled(false);
- if (Settings::instance().directInterface().value("endorse_state").toString() == "Endorsed") {
- ui->actionEndorseMO->setToolTip(tr("Thank you for endorsing MO2! :)"));
- ui->actionEndorseMO->setStatusTip(tr("Thank you for endorsing MO2! :)"));
- } else if (Settings::instance().directInterface().value("endorse_state").toString() == "Abstained") {
- ui->actionEndorseMO->setToolTip(tr("Please reconsider endorsing MO2 on Nexus!"));
- ui->actionEndorseMO->setStatusTip(tr("Please reconsider endorsing MO2 on Nexus!"));
- }
- } else {
- ui->actionEndorseMO->menu()->setEnabled(true);
- }
- } else
+ const auto& s = m_OrganizerCore.settings();
+
+ if (!s.nexus().endorsementIntegration()) {
ui->actionEndorseMO->setVisible(false);
+ return;
+ }
+
+ ui->actionEndorseMO->setVisible(true);
+
+ bool enabled = false;
+ QString text;
+
+ switch (s.nexus().endorsementState())
+ {
+ case EndorsementState::Accepted:
+ {
+ text = tr("Thank you for endorsing MO2! :)");
+ break;
+ }
+
+ case EndorsementState::Refused:
+ {
+ text = tr("Please reconsider endorsing MO2 on Nexus!");
+ break;
+ }
+
+ case EndorsementState::NoDecision:
+ {
+ enabled = true;
+ break;
+ }
+ }
+
+ ui->actionEndorseMO->menu()->setEnabled(enabled);
+ ui->actionEndorseMO->setToolTip(text);
+ ui->actionEndorseMO->setStatusTip(text);
}
void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int)
@@ -5815,22 +5632,26 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData
mod->setIsEndorsed(false);
}
- if (Settings::instance().endorsementIntegration()) {
+ if (Settings::instance().nexus().endorsementIntegration()) {
if (result->first == "skyrimspecialedition" && result->second.first == gamePlugin->nexusModOrganizerID()) {
- Settings::instance().directInterface().setValue("endorse_state", result->second.second);
+ m_OrganizerCore.settings().nexus().setEndorsementState(
+ endorsementStateFromString(result->second.second));
+
toggleMO2EndorseState();
}
}
}
}
- if (!searchedMO2NexusGame && Settings::instance().endorsementIntegration()) {
+ if (!searchedMO2NexusGame && Settings::instance().nexus().endorsementIntegration()) {
auto gamePlugin = m_OrganizerCore.getGame("SkyrimSE");
if (gamePlugin) {
auto iter = sorted.equal_range(gamePlugin->gameNexusName());
for (auto result = iter.first; result != iter.second; ++result) {
if (result->second.first == gamePlugin->nexusModOrganizerID()) {
- Settings::instance().directInterface().setValue("endorse_state", result->second.second);
+ m_OrganizerCore.settings().nexus().setEndorsementState(
+ endorsementStateFromString(result->second.second));
+
toggleMO2EndorseState();
break;
}
@@ -6010,15 +5831,41 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD
void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int)
{
- QMap results = resultData.toMap();
- if (results["status"].toString().compare("Endorsed") == 0) {
- QMessageBox::information(this, tr("Thank you!"), tr("Thank you for your endorsement!"));
- Settings::instance().directInterface().setValue("endorse_state", "Endorsed");
- } else if (results["status"].toString().compare("Abstained") == 0) {
- QMessageBox::information(this, tr("Okay."), tr("This mod will not be endorsed and will no longer ask you to endorse."));
- Settings::instance().directInterface().setValue("endorse_state", "Abstained");
+ const QMap results = resultData.toMap();
+
+ auto itor = results.find("status");
+ if (itor == results.end()) {
+ log::error("endorsement response has no status");
+ return;
+ }
+
+ const auto s = endorsementStateFromString(itor->toString());
+
+ switch (s)
+ {
+ case EndorsementState::Accepted:
+ {
+ QMessageBox::information(this, tr("Thank you!"), tr("Thank you for your endorsement!"));
+ break;
+ }
+
+ case EndorsementState::Refused:
+ {
+ // don't spam message boxes if the user doesn't want to endorse
+ log::info("Mod Organizer will not be endorsed and will no longer ask you to endorse.");
+ break;
+ }
+
+ case EndorsementState::NoDecision:
+ {
+ log::error("bad status '{}' in endorsement response", itor->toString());
+ return;
+ }
}
+
+ m_OrganizerCore.settings().nexus().setEndorsementState(s);
toggleMO2EndorseState();
+
if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)),
this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) {
log::error("failed to disconnect endorsement slot");
@@ -6055,19 +5902,35 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData,
void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int)
{
- QVariantList serverList = resultData.toList();
+ auto servers = m_OrganizerCore.settings().network().servers();
+
+ for (const QVariant &var : resultData.toList()) {
+ const QVariantMap map = var.toMap();
+
+ const auto name = map["short_name"].toString();
+ const auto isPremium = map["name"].toString().contains("Premium", Qt::CaseInsensitive);
+ const auto isCDN = map["short_name"].toString().contains("CDN", Qt::CaseInsensitive);
+
+ bool found = false;
+
+ for (auto& server : servers) {
+ if (server.name() == name) {
+ // already exists, update
+ server.setPremium(isPremium);
+ server.updateLastSeen();
+ found = true;
+ break;
+ }
+ }
- QList<ServerInfo> servers;
- for (const QVariant &server : serverList) {
- QVariantMap serverInfo = server.toMap();
- ServerInfo info;
- info.name = serverInfo["short_name"].toString();
- info.premium = serverInfo["name"].toString().contains("Premium", Qt::CaseInsensitive);
- info.lastSeen = QDate::currentDate();
- info.preferred = serverInfo["short_name"].toString().contains("CDN", Qt::CaseInsensitive);
- servers.append(info);
+ if (!found) {
+ // new server
+ ServerInfo server(name, isPremium, QDate::currentDate(), isCDN ? 1 : 0, {});
+ servers.add(std::move(server));
+ }
}
- m_OrganizerCore.settings().updateServers(servers);
+
+ m_OrganizerCore.settings().network().updateServers(servers);
}
@@ -6225,24 +6088,20 @@ void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int)
void MainWindow::on_actionNotifications_triggered()
{
updateProblemsButton();
- ProblemsDialog problems(m_PluginContainer.plugins<QObject>(), this);
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(problems.objectName());
- if (settings.contains(key)) {
- problems.restoreGeometry(settings.value(key).toByteArray());
- }
+ ProblemsDialog problems(m_PluginContainer.plugins<QObject>(), this);
problems.exec();
- settings.setValue(key, problems.saveGeometry());
+
updateProblemsButton();
}
void MainWindow::on_actionChange_Game_triggered()
{
- if (QMessageBox::question(this, tr("Are you sure?"),
- tr("This will restart MO, continue?"),
- QMessageBox::Yes | QMessageBox::Cancel)
- == QMessageBox::Yes) {
+ const auto r = QMessageBox::question(
+ this, tr("Are you sure?"), tr("This will restart MO, continue?"),
+ QMessageBox::Yes | QMessageBox::Cancel);
+
+ if (r == QMessageBox::Yes) {
InstanceManager::instance().clearCurrentInstance();
qApp->exit(INT_MAX);
}
@@ -6267,16 +6126,10 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked)
void MainWindow::editCategories()
{
CategoriesDialog dialog(this);
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(dialog.objectName());
- if (settings.contains(key)) {
- dialog.restoreGeometry(settings.value(key).toByteArray());
- }
+
if (dialog.exec() == QDialog::Accepted) {
dialog.commitChanges();
}
- settings.setValue(key, dialog.saveGeometry());
-
}
void MainWindow::deselectFilters()
@@ -6550,7 +6403,6 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe
void MainWindow::on_bossButton_clicked()
{
- std::string reportURL;
std::string errorMessages;
//m_OrganizerCore.currentProfile()->writeModlistNow();
@@ -6698,16 +6550,6 @@ void MainWindow::on_bossButton_clicked()
if (success) {
m_DidUpdateMasterList = true;
- if (reportURL.length() > 0) {
- m_IntegratedBrowser.setWindowTitle("LOOT Report");
- QString report(reportURL.c_str());
- QStringList temp = report.split("?");
- QUrl url = QUrl::fromLocalFile(temp.at(0));
- if (temp.size() > 1) {
- url.setQuery(temp.at(1).toUtf8());
- }
- m_IntegratedBrowser.openUrl(url);
- }
m_OrganizerCore.refreshESPList(false);
m_OrganizerCore.savePluginList();
}
@@ -6927,31 +6769,6 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m
}
}
-bool MainWindow::registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName) {
- // register the view so it's geometry gets saved at exit
- m_PersistedGeometry.push_back(std::make_pair(name, view));
-
- // also, restore the geometry if it was saved before
- QSettings &settings = m_OrganizerCore.settings().directInterface();
-
- QString key = QString("geometry/%1").arg(name);
- QByteArray data;
-
- if ((oldSettingName != nullptr) && settings.contains(oldSettingName)) {
- data = settings.value(oldSettingName).toByteArray();
- settings.remove(oldSettingName);
- } else if (settings.contains(key)) {
- data = settings.value(key).toByteArray();
- }
-
- if (!data.isEmpty()) {
- view->restoreState(data);
- return true;
- } else {
- return false;
- }
-}
-
void MainWindow::dropEvent(QDropEvent *event)
{
Qt::DropAction action = event->proposedAction();
@@ -6975,7 +6792,7 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event)
// if the menubar is hidden, pressing Alt will make it visible
if (event->key() == Qt::Key_Alt) {
if (!ui->menuBar->isVisible()) {
- showMenuBar(true);
+ ui->menuBar->show();
}
}
@@ -7042,12 +6859,9 @@ void MainWindow::sendSelectedModsToSeparator_clicked()
}
ListDialog dialog(this);
- QSettings &settings = m_OrganizerCore.settings().directInterface();
- QString key = QString("geometry/%1").arg(dialog.objectName());
-
dialog.setWindowTitle("Select a separator...");
dialog.setChoices(separators);
- dialog.restoreGeometry(settings.value(key).toByteArray());
+
if (dialog.exec() == QDialog::Accepted) {
QString result = dialog.getChoice();
if (!result.isEmpty()) {
@@ -7081,7 +6895,6 @@ void MainWindow::sendSelectedModsToSeparator_clicked()
}
}
}
- settings.setValue(key, dialog.saveGeometry());
}
void MainWindow::on_showArchiveDataCheckBox_toggled(const bool checked)
diff --git a/src/mainwindow.h b/src/mainwindow.h
index aa49205d..6f06b9d5 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -40,7 +40,6 @@ class Executable;
class CategoryFactory;
class LockedDialogBase;
class OrganizerCore;
-class StatusBar;
class PluginListSortProxy;
namespace BSA { class Archive; }
@@ -83,7 +82,6 @@ class QProgressDialog;
class QTranslator;
class QTreeWidgetItem;
class QUrl;
-class QSettings;
class QWidget;
#ifndef Q_MOC_RUN
@@ -103,6 +101,7 @@ namespace Ui {
class MainWindow;
}
+class Settings;
class MainWindow : public QMainWindow, public IUserInterface
@@ -113,14 +112,12 @@ class MainWindow : public QMainWindow, public IUserInterface
public:
- explicit MainWindow(QSettings &initSettings,
+ explicit MainWindow(Settings &settings,
OrganizerCore &organizerCore, PluginContainer &pluginContainer,
QWidget *parent = 0);
~MainWindow();
- void storeSettings(QSettings &settings) override;
- void readSettings();
- void processUpdates();
+ void processUpdates(Settings& settings);
virtual ILockedWaitingForProcess* lock() override;
virtual void unlock() override;
@@ -229,8 +226,6 @@ private:
QMenu* createPopupMenu() override;
void activateSelectedProfile();
- void setExecutableIndex(int index);
-
void startSteam();
void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon);
@@ -293,7 +288,6 @@ private:
static void setupNetworkProxy(bool activate);
void activateProxy(bool activate);
- void setBrowserGeometry(const QByteArray &geometry);
bool createBackup(const QString &filePath, const QDateTime &time);
QString queryRestore(const QString &filePath);
@@ -315,8 +309,6 @@ private:
void dropLocalFile(const QUrl &url, const QString &outputDir, bool move);
- bool registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName = nullptr);
-
void sendSelectedModsToPriority(int newPriority);
void sendSelectedPluginsToPriority(int newPriority);
@@ -334,12 +326,6 @@ private:
bool m_WasVisible;
- // this has to be remembered because by the time storeSettings() is called,
- // the window is closed and the all bars are hidden
- bool m_menuBarVisible, m_statusBarVisible;
-
- std::unique_ptr<StatusBar> m_statusBar;
-
// last separator on the toolbar, used to add spacer for right-alignment and
// as an insert point for executables
QAction* m_linksSeparator;
@@ -349,11 +335,9 @@ private:
int m_OldProfileIndex;
std::vector<QString> m_ModNameList; // the mod-list to go with the directory structure
- bool m_Refreshing;
QStringList m_DefaultArchives;
- QAbstractItemModel *m_ModListGroupingProxy;
ModListSortProxy *m_ModListSortProxy;
PluginListSortProxy *m_PluginListSortProxy;
@@ -369,8 +353,6 @@ private:
CategoryFactory &m_CategoryFactory;
- bool m_LoginAttempted;
-
QTimer m_CheckBSATimer;
QTimer m_SaveMetaTimer;
QTimer m_UpdateProblemsTimer;
@@ -404,8 +386,6 @@ private:
bool m_showArchiveData{ true };
- std::vector<std::pair<QString, QHeaderView*>> m_PersistedGeometry;
-
MOBase::DelayedFileWriter m_ArchiveListWriter;
QAction* m_LinkToolbar;
@@ -603,10 +583,7 @@ private slots:
void expandModList(const QModelIndex &index);
- /**
- * @brief resize columns in mod list and plugin list to content
- */
- void resizeLists(bool modListCustom, bool pluginListCustom);
+ void resizeLists(bool pluginListCustom);
/**
* @brief allow columns in mod list and plugin list to be resized
@@ -697,10 +674,9 @@ private slots: // ui slots
void on_categoriesOrBtn_toggled(bool checked);
void on_managedArchiveLabel_linkHovered(const QString &link);
- void showMenuBar(bool b);
- void showStatusBar(bool b);
+ void storeSettings(Settings& settings);
+ void readSettings(const Settings& settings);
+ void setupModList();
};
-
-
#endif // MAINWINDOW_H
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index 6c6d0bca..02c6dec0 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -47,1246 +47,1241 @@
<number>0</number>
</property>
<item>
- <widget class="QSplitter" name="topLevelSplitter">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
<widget class="QWidget" name="horizontalLayoutWidget_2">
- <layout class="QHBoxLayout" name="horizontalLayout_10" stretch="0,2">
+ <layout class="QHBoxLayout" name="horizontalLayout_10" stretch="0,2">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_8">
- <item>
- <widget class="QGroupBox" name="categoriesGroup">
- <property name="title">
- <string>Categories</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_10" stretch="6,0,0">
- <property name="spacing">
+ <widget class="QGroupBox" name="categoriesGroup">
+ <property name="title">
+ <string>Categories</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10" stretch="6,0,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="categoriesList">
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>214</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="indentation">
<number>0</number>
</property>
- <property name="leftMargin">
- <number>3</number>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
</property>
- <property name="topMargin">
- <number>7</number>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="clickBlankButton">
+ <property name="enabled">
+ <bool>false</bool>
</property>
- <property name="rightMargin">
- <number>3</number>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="bottomMargin">
- <number>1</number>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
</property>
- <item>
- <widget class="QTreeWidget" name="categoriesList">
- <property name="minimumSize">
- <size>
- <width>120</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>214</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- <property name="indentation">
- <number>0</number>
- </property>
- <property name="uniformRowHeights">
- <bool>true</bool>
- </property>
- <attribute name="headerVisible">
- <bool>false</bool>
- </attribute>
- <column>
- <property name="text">
- <string notr="true">1</string>
- </property>
- </column>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="clickBlankButton">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>25</height>
- </size>
- </property>
- <property name="text">
- <string>Clear</string>
- </property>
- <property name="flat">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="widget" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_11">
- <item>
- <widget class="QRadioButton" name="categoriesAndBtn">
- <property name="toolTip">
- <string>If checked, only mods that match all selected categories are displayed.</string>
- </property>
- <property name="text">
- <string>And</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="categoriesOrBtn">
- <property name="toolTip">
- <string>If checked, all mods that match at least one of the selected categories are displayed.</string>
- </property>
- <property name="text">
- <string>Or</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QSplitter" name="splitter">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <widget class="QWidget" name="layoutWidget">
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="spacing">
- <number>2</number>
- </property>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,1,0,0,0,0,0,0,0">
- <item>
- <widget class="QLabel" name="label_3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Profile</string>
- </property>
- <property name="buddy">
- <cstring>profileBox</cstring>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="profileBox">
- <property name="toolTip">
- <string>Pick a module collection</string>
- </property>
- <property name="whatsThis">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.&lt;/span&gt;&lt;/p&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Please note that right now your esp load order is not kept separate for different profiles.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="listOptionsBtn">
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="toolTip">
- <string>Open list options...</string>
- </property>
- <property name="whatsThis">
- <string>Refresh list. This is usually not necessary unless you modified data outside the program.</string>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/settings</normaloff>:/MO/gui/settings</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="openFolderMenu">
- <property name="toolTip">
- <string>Show Open Folders menu...</string>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/open_folder</normaloff>:/MO/gui/open_folder</iconset>
- </property>
- </widget>
- </item>
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
- <widget class="QPushButton" name="restoreModsButton">
+ <widget class="QRadioButton" name="categoriesAndBtn">
<property name="toolTip">
- <string>Restore Backup...</string>
+ <string>If checked, only mods that match all selected categories are displayed.</string>
</property>
<property name="text">
- <string notr="true"/>
+ <string>And</string>
</property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/restore</normaloff>:/MO/gui/restore</iconset>
+ <property name="checked">
+ <bool>true</bool>
</property>
</widget>
</item>
<item>
- <widget class="QPushButton" name="saveModsButton">
+ <widget class="QRadioButton" name="categoriesOrBtn">
<property name="toolTip">
- <string>Create Backup</string>
- </property>
- <property name="text">
- <string notr="true"/>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/backup</normaloff>:/MO/gui/backup</iconset>
+ <string>If checked, all mods that match at least one of the selected categories are displayed.</string>
</property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="activeModslabel">
<property name="text">
- <string>Active:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="LCDNumber" name="activeModsCounter">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>26</height>
- </size>
- </property>
- <property name="whatsThis">
- <string>This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter.</string>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <property name="digitCount">
- <number>5</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
+ <string>Or</string>
</property>
</widget>
</item>
</layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="layoutWidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,1,0,0,0,0,0,0,0">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Profile</string>
+ </property>
+ <property name="buddy">
+ <cstring>profileBox</cstring>
+ </property>
+ </widget>
</item>
<item>
- <widget class="ModListView" name="modList">
- <property name="minimumSize">
+ <widget class="QComboBox" name="profileBox">
+ <property name="toolTip">
+ <string>Pick a module collection</string>
+ </property>
+ <property name="whatsThis">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Please note that right now your esp load order is not kept separate for different profiles.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
<size>
- <width>330</width>
- <height>400</height>
+ <width>40</width>
+ <height>20</height>
</size>
</property>
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="listOptionsBtn">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
</property>
<property name="toolTip">
- <string>List of available mods.</string>
+ <string>Open list options...</string>
</property>
<property name="whatsThis">
- <string>This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag &amp; drop mods to change their &quot;installation&quot; orders.</string>
+ <string>Refresh list. This is usually not necessary unless you modified data outside the program.</string>
</property>
- <property name="styleSheet">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/settings</normaloff>:/MO/gui/settings</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="openFolderMenu">
+ <property name="toolTip">
+ <string>Show Open Folders menu...</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/open_folder</normaloff>:/MO/gui/open_folder</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restoreModsButton">
+ <property name="toolTip">
+ <string>Restore Backup...</string>
+ </property>
+ <property name="text">
<string notr="true"/>
</property>
- <property name="editTriggers">
- <set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/restore</normaloff>:/MO/gui/restore</iconset>
</property>
- <property name="showDropIndicator" stdset="0">
- <bool>true</bool>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="saveModsButton">
+ <property name="toolTip">
+ <string>Create Backup</string>
</property>
- <property name="dragEnabled">
- <bool>true</bool>
+ <property name="text">
+ <string notr="true"/>
</property>
- <property name="dragDropMode">
- <enum>QAbstractItemView::DragDrop</enum>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/backup</normaloff>:/MO/gui/backup</iconset>
</property>
- <property name="defaultDropAction">
- <enum>Qt::MoveAction</enum>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="activeModslabel">
+ <property name="text">
+ <string>Active:</string>
</property>
- <property name="alternatingRowColors">
- <bool>true</bool>
+ </widget>
+ </item>
+ <item>
+ <widget class="LCDNumber" name="activeModsCounter">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>26</height>
+ </size>
</property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
+ <property name="whatsThis">
+ <string>This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter.</string>
</property>
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectRows</enum>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
</property>
- <property name="indentation">
- <number>20</number>
+ <property name="digitCount">
+ <number>5</number>
</property>
- <property name="uniformRowHeights">
- <bool>true</bool>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
</property>
- <property name="itemsExpandable">
- <bool>true</bool>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="ModListView" name="modList">
+ <property name="minimumSize">
+ <size>
+ <width>330</width>
+ <height>400</height>
+ </size>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="toolTip">
+ <string>List of available mods.</string>
+ </property>
+ <property name="whatsThis">
+ <string>This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag &amp; drop mods to change their &quot;installation&quot; orders.</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>true</bool>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DragDrop</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::MoveAction</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="indentation">
+ <number>20</number>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="expandsOnDoubleClick">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerDefaultSectionSize">
+ <number>35</number>
+ </attribute>
+ <attribute name="headerShowSortIndicator" stdset="0">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,0,1,1">
+ <item>
+ <widget class="QPushButton" name="displayCategoriesBtn">
+ <property name="maximumSize">
+ <size>
+ <width>20</width>
+ <height>16777215</height>
+ </size>
</property>
- <property name="sortingEnabled">
- <bool>true</bool>
+ <property name="text">
+ <string notr="true">x</string>
</property>
- <property name="expandsOnDoubleClick">
- <bool>false</bool>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
</property>
- <attribute name="headerDefaultSectionSize">
- <number>35</number>
- </attribute>
- <attribute name="headerShowSortIndicator" stdset="0">
+ <property name="checkable">
<bool>true</bool>
- </attribute>
- <attribute name="headerStretchLastSection">
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Filter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="currentCategoryLabel">
+ <property name="font">
+ <font>
+ <pointsize>8</pointsize>
+ <italic>true</italic>
+ </font>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignLeft">
+ <widget class="QPushButton" name="clearFiltersButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>95</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="visible">
<bool>false</bool>
- </attribute>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::RightToLeft</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">border:1px solid #ff0000;</string>
+ </property>
+ <property name="text">
+ <string>Clear all Filters</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>12</width>
+ <height>12</height>
+ </size>
+ </property>
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,0,1,1">
- <item>
- <widget class="QPushButton" name="displayCategoriesBtn">
- <property name="maximumSize">
- <size>
- <width>20</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string notr="true">x</string>
- </property>
- <property name="iconSize">
- <size>
- <width>20</width>
- <height>20</height>
- </size>
- </property>
- <property name="checkable">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Filter</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="currentCategoryLabel">
- <property name="font">
- <font>
- <pointsize>8</pointsize>
- <italic>true</italic>
- </font>
- </property>
- </widget>
- </item>
+ <widget class="QComboBox" name="groupCombo">
+ <property name="baseSize">
+ <size>
+ <width>220</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
<item>
- <spacer name="horizontalSpacer_5">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item alignment="Qt::AlignLeft">
- <widget class="QPushButton" name="clearFiltersButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>22</height>
- </size>
- </property>
- <property name="baseSize">
- <size>
- <width>95</width>
- <height>0</height>
- </size>
- </property>
- <property name="visible">
- <bool>false</bool>
- </property>
- <property name="layoutDirection">
- <enum>Qt::RightToLeft</enum>
- </property>
- <property name="styleSheet">
- <string notr="true">border:1px solid #ff0000;</string>
- </property>
- <property name="text">
- <string>Clear all Filters</string>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>12</width>
- <height>12</height>
- </size>
- </property>
- </widget>
+ <property name="text">
+ <string>No groups</string>
+ </property>
</item>
<item>
- <widget class="QComboBox" name="groupCombo">
- <property name="baseSize">
- <size>
- <width>220</width>
- <height>0</height>
- </size>
- </property>
- <property name="focusPolicy">
- <enum>Qt::ClickFocus</enum>
- </property>
- <item>
- <property name="text">
- <string>No groups</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Categories</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Nexus IDs</string>
- </property>
- </item>
- </widget>
+ <property name="text">
+ <string>Categories</string>
+ </property>
</item>
<item>
- <widget class="MOBase::LineEditClear" name="modFilterEdit">
- <property name="baseSize">
- <size>
- <width>220</width>
- <height>0</height>
- </size>
- </property>
- <property name="placeholderText">
- <string>Filter</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Nexus IDs</string>
+ </property>
</item>
- </layout>
+ </widget>
</item>
- </layout>
- </widget>
- <widget class="QWidget" name="layoutWidget_2">
- <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
- <widget class="QFrame" name="startGroup">
- <layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
+ <widget class="MOBase::LineEditClear" name="modFilterEdit">
+ <property name="baseSize">
+ <size>
+ <width>220</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="placeholderText">
+ <string>Filter</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="layoutWidget_2">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QFrame" name="startGroup">
+ <layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
+ <item>
+ <widget class="QComboBox" name="executablesListBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>40</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>9</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="toolTip">
+ <string>Pick a program to run.</string>
+ </property>
+ <property name="whatsThis">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;You can add new Tools to this list, but I can't promise tools I haven't tested will work.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="frame">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_12" stretch="0,0">
<item>
- <widget class="QComboBox" name="executablesListBox">
+ <widget class="QPushButton" name="startButton">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
- <width>0</width>
- <height>40</height>
+ <width>120</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
</size>
</property>
<property name="font">
<font>
- <pointsize>9</pointsize>
+ <pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="toolTip">
- <string>Pick a program to run.</string>
+ <string>Run program</string>
</property>
<property name="whatsThis">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.&lt;/span&gt;&lt;/p&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;You can add new Tools to this list, but I can't promise tools I haven't tested will work.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Run the selected program with ModOrganizer enabled.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="locale">
+ <locale language="English" country="UnitedStates"/>
+ </property>
+ <property name="text">
+ <string>Run</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/run</normaloff>:/MO/gui/run</iconset>
</property>
<property name="iconSize">
<size>
- <width>32</width>
- <height>32</height>
+ <width>36</width>
+ <height>36</height>
</size>
</property>
- <property name="frame">
- <bool>false</bool>
- </property>
</widget>
</item>
<item>
- <layout class="QVBoxLayout" name="verticalLayout_12" stretch="0,0">
- <item>
- <widget class="QPushButton" name="startButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>120</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="font">
- <font>
- <pointsize>10</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
- <property name="toolTip">
- <string>Run program</string>
- </property>
- <property name="whatsThis">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Run the selected program with ModOrganizer enabled.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="locale">
- <locale language="English" country="UnitedStates"/>
- </property>
- <property name="text">
- <string>Run</string>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/run</normaloff>:/MO/gui/run</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>36</width>
- <height>36</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="linkButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>140</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="baseSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="toolTip">
- <string>Create a shortcut in your start menu or on the desktop to the specified program</string>
- </property>
- <property name="whatsThis">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+ <widget class="QPushButton" name="linkButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>140</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Create a shortcut in your start menu or on the desktop to the specified program</string>
+ </property>
+ <property name="whatsThis">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This creates a start menu shortcut that directly starts the selected program with the MO active.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="text">
- <string>Shortcut</string>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/link</normaloff>:/MO/gui/link</iconset>
- </property>
- </widget>
- </item>
- </layout>
+ </property>
+ <property name="text">
+ <string>Shortcut</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/link</normaloff>:/MO/gui/link</iconset>
+ </property>
+ </widget>
</item>
</layout>
- </widget>
- </item>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="minimumSize">
- <size>
- <width>340</width>
- <height>250</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>16777215</height>
- </size>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="minimumSize">
+ <size>
+ <width>340</width>
+ <height>250</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::NoContextMenu</enum>
+ </property>
+ <property name="tabShape">
+ <enum>QTabWidget::Rounded</enum>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="espTab">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <attribute name="title">
+ <string>Plugins</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>6</number>
</property>
- <property name="contextMenuPolicy">
- <enum>Qt::NoContextMenu</enum>
+ <property name="topMargin">
+ <number>6</number>
</property>
- <property name="tabShape">
- <enum>QTabWidget::Rounded</enum>
+ <property name="rightMargin">
+ <number>6</number>
</property>
- <property name="currentIndex">
+ <property name="bottomMargin">
<number>0</number>
</property>
- <widget class="QWidget" name="espTab">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>16777215</height>
- </size>
- </property>
- <attribute name="title">
- <string>Plugins</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <property name="leftMargin">
- <number>6</number>
- </property>
- <property name="topMargin">
- <number>6</number>
- </property>
- <property name="rightMargin">
- <number>6</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <item>
- <widget class="QPushButton" name="bossButton">
- <property name="visible">
+ <widget class="QPushButton" name="bossButton">
+ <property name="visible">
<bool>true</bool>
- </property>
- <property name="text">
- <string>Sort</string>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/sort</normaloff>:/MO/gui/sort</iconset>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="restoreButton">
- <property name="toolTip">
- <string>Restore Backup...</string>
- </property>
- <property name="text">
- <string notr="true"/>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/restore</normaloff>:/MO/gui/restore</iconset>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="saveButton">
- <property name="toolTip">
- <string>Create Backup</string>
- </property>
- <property name="text">
- <string notr="true"/>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/backup</normaloff>:/MO/gui/backup</iconset>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="activePluginsLabel">
- <property name="text">
- <string>Active:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="LCDNumber" name="activePluginsCounter">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>26</height>
- </size>
- </property>
- <property name="whatsThis">
- <string>This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter.</string>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
- <property name="digitCount">
- <number>4</number>
- </property>
- <property name="segmentStyle">
- <enum>QLCDNumber::Flat</enum>
- </property>
- </widget>
- </item>
- </layout>
+ </property>
+ <property name="text">
+ <string>Sort</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/sort</normaloff>:/MO/gui/sort</iconset>
+ </property>
+ </widget>
</item>
<item>
- <widget class="PluginListView" name="espList">
- <property name="minimumSize">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
<size>
- <width>250</width>
- <height>250</height>
+ <width>40</width>
+ <height>20</height>
</size>
</property>
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restoreButton">
<property name="toolTip">
- <string>List of available esp/esm files</string>
- </property>
- <property name="whatsThis">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.&lt;br /&gt;There is a great tool named &amp;quot;BOSS&amp;quot; to automatically sort these files.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="editTriggers">
- <set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
+ <string>Restore Backup...</string>
</property>
- <property name="dragEnabled">
- <bool>true</bool>
+ <property name="text">
+ <string notr="true"/>
</property>
- <property name="dragDropOverwriteMode">
- <bool>false</bool>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/restore</normaloff>:/MO/gui/restore</iconset>
</property>
- <property name="dragDropMode">
- <enum>QAbstractItemView::InternalMove</enum>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
</property>
- <property name="defaultDropAction">
- <enum>Qt::MoveAction</enum>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="saveButton">
+ <property name="toolTip">
+ <string>Create Backup</string>
</property>
- <property name="alternatingRowColors">
- <bool>true</bool>
+ <property name="text">
+ <string notr="true"/>
</property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/backup</normaloff>:/MO/gui/backup</iconset>
</property>
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectRows</enum>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="activePluginsLabel">
+ <property name="text">
+ <string>Active:</string>
</property>
- <property name="indentation">
- <number>0</number>
+ </widget>
+ </item>
+ <item>
+ <widget class="LCDNumber" name="activePluginsCounter">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>26</height>
+ </size>
</property>
- <property name="uniformRowHeights">
- <bool>true</bool>
+ <property name="whatsThis">
+ <string>This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter.</string>
</property>
- <property name="itemsExpandable">
- <bool>false</bool>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
</property>
- <property name="sortingEnabled">
- <bool>true</bool>
+ <property name="digitCount">
+ <number>4</number>
</property>
- <property name="expandsOnDoubleClick">
- <bool>false</bool>
+ <property name="segmentStyle">
+ <enum>QLCDNumber::Flat</enum>
</property>
- <attribute name="headerStretchLastSection">
- <bool>false</bool>
- </attribute>
</widget>
</item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="MOBase::LineEditClear" name="espFilterEdit">
- <property name="text">
- <string/>
- </property>
- <property name="placeholderText">
- <string>Filter</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
- </widget>
- <widget class="QWidget" name="bsaTab">
- <property name="visible">
- <bool>false</bool>
- </property>
- <attribute name="title">
- <string>Archives</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_9">
- <property name="leftMargin">
- <number>6</number>
+ </item>
+ <item>
+ <widget class="PluginListView" name="espList">
+ <property name="minimumSize">
+ <size>
+ <width>250</width>
+ <height>250</height>
+ </size>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="toolTip">
+ <string>List of available esp/esm files</string>
+ </property>
+ <property name="whatsThis">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.&lt;br /&gt;There is a great tool named &amp;quot;BOSS&amp;quot; to automatically sort these files.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropOverwriteMode">
+ <bool>false</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::MoveAction</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
</property>
- <property name="topMargin">
- <number>6</number>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
</property>
- <property name="rightMargin">
- <number>6</number>
+ <property name="indentation">
+ <number>0</number>
</property>
- <property name="bottomMargin">
- <number>6</number>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
</property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="expandsOnDoubleClick">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0">
- <item>
- <widget class="QLabel" name="managedArchiveLabel">
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. &lt;p&gt;Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).&lt;/p&gt;&lt;p&gt;If there is a matching plugin, the game will load them no matter what.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Currently detected archives. (&lt;a href=&quot;#&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;What is an archive?&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="MOBase::LineEditClear" name="espFilterEdit">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="placeholderText">
+ <string>Filter</string>
+ </property>
+ </widget>
</item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="bsaTab">
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ <attribute name="title">
+ <string>Archives</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0">
<item>
- <widget class="MOBase::SortableTreeWidget" name="bsaList" native="true">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
+ <widget class="QLabel" name="managedArchiveLabel">
<property name="toolTip">
- <string>List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order.</string>
- </property>
- <property name="whatsThis">
- <string>BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they &quot;compete&quot; with loose files in your data directory over which is loaded.
- By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored!
-
- BSAs checked here are loaded in such a way that your installation order is obeyed properly.</string>
- </property>
- <property name="showDropIndicator" stdset="0">
- <bool>false</bool>
- </property>
- <property name="dragEnabled" stdset="0">
- <bool>false</bool>
- </property>
- <property name="dragDropOverwriteMode" stdset="0">
- <bool>false</bool>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. &lt;p&gt;Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).&lt;/p&gt;&lt;p&gt;If there is a matching plugin, the game will load them no matter what.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
- <property name="indentation" stdset="0">
- <number>20</number>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Currently detected archives. (&lt;a href=&quot;#&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;What is an archive?&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
- <property name="itemsExpandable" stdset="0">
+ <property name="wordWrap">
<bool>true</bool>
</property>
- <property name="columnCount" stdset="0">
- <number>1</number>
- </property>
</widget>
</item>
</layout>
- </widget>
- <widget class="QWidget" name="dataTab">
- <attribute name="title">
- <string>Data</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <property name="leftMargin">
- <number>6</number>
+ </item>
+ <item>
+ <widget class="MOBase::SortableTreeWidget" name="bsaList" native="true">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="toolTip">
+ <string>List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order.</string>
+ </property>
+ <property name="whatsThis">
+ <string>BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they &quot;compete&quot; with loose files in your data directory over which is loaded.
+ By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored!
+
+ BSAs checked here are loaded in such a way that your installation order is obeyed properly.</string>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="dragEnabled" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="dragDropOverwriteMode" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="indentation" stdset="0">
+ <number>20</number>
+ </property>
+ <property name="itemsExpandable" stdset="0">
+ <bool>true</bool>
+ </property>
+ <property name="columnCount" stdset="0">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataTab">
+ <attribute name="title">
+ <string>Data</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="btnRefreshData">
+ <property name="toolTip">
+ <string>refresh data-directory overview</string>
</property>
- <property name="topMargin">
- <number>6</number>
+ <property name="whatsThis">
+ <string>Refresh the overview. This may take a moment.</string>
</property>
- <property name="rightMargin">
- <number>6</number>
+ <property name="text">
+ <string>Refresh</string>
</property>
- <property name="bottomMargin">
- <number>6</number>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/resources/view-refresh.png</normaloff>:/MO/gui/resources/view-refresh.png</iconset>
</property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
- <widget class="QPushButton" name="btnRefreshData">
- <property name="toolTip">
- <string>refresh data-directory overview</string>
+ <widget class="QTreeWidget" name="dataTree">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
</property>
<property name="whatsThis">
- <string>Refresh the overview. This may take a moment.</string>
+ <string>This is an overview of your data directory as visible to the game (and tools). </string>
</property>
- <property name="text">
- <string>Refresh</string>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
</property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/resources/view-refresh.png</normaloff>:/MO/gui/resources/view-refresh.png</iconset>
+ <property name="animated">
+ <bool>true</bool>
</property>
+ <attribute name="headerDefaultSectionSize">
+ <number>400</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>File</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Mod</string>
+ </property>
+ </column>
</widget>
</item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QTreeWidget" name="dataTree">
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="whatsThis">
- <string>This is an overview of your data directory as visible to the game (and tools). </string>
- </property>
- <property name="uniformRowHeights">
- <bool>true</bool>
- </property>
- <property name="animated">
- <bool>true</bool>
- </property>
- <attribute name="headerDefaultSectionSize">
- <number>400</number>
- </attribute>
- <column>
- <property name="text">
- <string>File</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Mod</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
+ <widget class="QCheckBox" name="conflictsCheckBox">
+ <property name="toolTip">
+ <string>Filters the above list so that only conflicts are displayed.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Filters the above list so that only conflicts are displayed.</string>
+ </property>
+ <property name="text">
+ <string>Show only conflicts</string>
+ </property>
+ </widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_12">
- <item>
- <widget class="QCheckBox" name="conflictsCheckBox">
- <property name="toolTip">
- <string>Filters the above list so that only conflicts are displayed.</string>
- </property>
- <property name="whatsThis">
- <string>Filters the above list so that only conflicts are displayed.</string>
- </property>
- <property name="text">
- <string>Show only conflicts</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="showArchiveDataCheckBox">
- <property name="toolTip">
- <string>Filters the above list so that files from archives are not shown</string>
- </property>
- <property name="statusTip">
- <string/>
- </property>
- <property name="whatsThis">
- <string>Filters the above list so that files from archives are not shown</string>
- </property>
- <property name="text">
- <string>Show files from Archives</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QCheckBox" name="showArchiveDataCheckBox">
+ <property name="toolTip">
+ <string>Filters the above list so that files from archives are not shown</string>
+ </property>
+ <property name="statusTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string>Filters the above list so that files from archives are not shown</string>
+ </property>
+ <property name="text">
+ <string>Show files from Archives</string>
+ </property>
+ </widget>
</item>
</layout>
- </widget>
- <widget class="QWidget" name="savesTab">
- <attribute name="title">
- <string>Saves</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <property name="leftMargin">
- <number>6</number>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="savesTab">
+ <attribute name="title">
+ <string>Saves</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QListWidget" name="savegameList">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="toolTip">
+ <string notr="true"/>
+ </property>
+ <property name="whatsThis">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;If you click &amp;quot;Fix Mods...&amp;quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
</property>
- <property name="topMargin">
- <number>6</number>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="downloadTab">
+ <attribute name="title">
+ <string>Downloads</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="btnRefreshDownloads">
+ <property name="toolTip">
+ <string>Refresh downloads view</string>
</property>
- <property name="rightMargin">
- <number>6</number>
+ <property name="text">
+ <string>Refresh</string>
</property>
- <property name="bottomMargin">
- <number>6</number>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/resources/view-refresh.png</normaloff>:/MO/gui/resources/view-refresh.png</iconset>
</property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="downloadLayout">
<item>
- <widget class="QListWidget" name="savegameList">
+ <widget class="DownloadListWidget" name="downloadView">
+ <property name="minimumSize">
+ <size>
+ <width>320</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
<property name="toolTip">
- <string notr="true"/>
+ <string/>
</property>
<property name="whatsThis">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.&lt;/span&gt;&lt;/p&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;/p&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;If you click &amp;quot;Fix Mods...&amp;quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here.</string>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
</property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DragDrop</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::MoveAction</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="indentation">
+ <number>0</number>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
</property>
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectRows</enum>
+ <property name="sortingEnabled">
+ <bool>true</bool>
</property>
</widget>
</item>
</layout>
- </widget>
- <widget class="QWidget" name="downloadTab">
- <attribute name="title">
- <string>Downloads</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_7">
- <property name="leftMargin">
- <number>2</number>
- </property>
- <property name="topMargin">
- <number>2</number>
- </property>
- <property name="rightMargin">
- <number>2</number>
- </property>
- <property name="bottomMargin">
- <number>2</number>
- </property>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,2">
<item>
- <widget class="QPushButton" name="btnRefreshDownloads">
- <property name="toolTip">
- <string>Refresh downloads view</string>
- </property>
+ <widget class="QCheckBox" name="showHiddenBox">
<property name="text">
- <string>Refresh</string>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/resources/view-refresh.png</normaloff>:/MO/gui/resources/view-refresh.png</iconset>
+ <string>Show Hidden</string>
</property>
</widget>
</item>
<item>
- <layout class="QVBoxLayout" name="downloadLayout">
- <item>
- <widget class="DownloadListWidget" name="downloadView">
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>0</height>
- </size>
- </property>
- <property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
- </property>
- <property name="acceptDrops">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string/>
- </property>
- <property name="whatsThis">
- <string>This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here.</string>
- </property>
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOn</enum>
- </property>
- <property name="dragEnabled">
- <bool>true</bool>
- </property>
- <property name="dragDropMode">
- <enum>QAbstractItemView::DragDrop</enum>
- </property>
- <property name="defaultDropAction">
- <enum>Qt::MoveAction</enum>
- </property>
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <property name="verticalScrollMode">
- <enum>QAbstractItemView::ScrollPerPixel</enum>
- </property>
- <property name="indentation">
- <number>0</number>
- </property>
- <property name="itemsExpandable">
- <bool>false</bool>
- </property>
- <property name="sortingEnabled">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,2">
- <item>
- <widget class="QCheckBox" name="showHiddenBox">
- <property name="text">
- <string>Show Hidden</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="MOBase::LineEditClear" name="downloadFilterEdit">
- <property name="placeholderText">
- <string>Filter</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="MOBase::LineEditClear" name="downloadFilterEdit">
+ <property name="placeholderText">
+ <string>Filter</string>
+ </property>
+ </widget>
</item>
</layout>
- </widget>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
</widget>
- </widget>
</item>
</layout>
</widget>
@@ -1325,7 +1320,7 @@ p, li { white-space: pre-wrap; }
<addaction name="actionUpdate"/>
<addaction name="actionHelp"/>
</widget>
- <widget class="QStatusBar" name="statusBar"/>
+ <widget class="StatusBar" name="statusBar"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
@@ -1795,6 +1790,11 @@ p, li { white-space: pre-wrap; }
<extends>QTreeView</extends>
<header>loglist.h</header>
</customwidget>
+ <customwidget>
+ <class>StatusBar</class>
+ <extends>QStatusBar</extends>
+ <header>statusbar.h</header>
+ </customwidget>
</customwidgets>
<resources>
<include location="resources.qrc"/>
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index 5a05e7ca..e3daa4fd 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -42,7 +42,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QApplication>
#include <QDirIterator>
#include <QMutexLocker>
-#include <QSettings>
using namespace MOBase;
using namespace MOShared;
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index 4b1e2f76..c7e071ad 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -210,6 +210,9 @@ void ModInfoDialog::createTabs()
int ModInfoDialog::exec()
{
+ GeometrySaver gs(Settings::instance(), this);
+ restoreState();
+
// whether to select the first tab; if the main window requested a specific
// tab, it is selected when encountered in update()
const auto selectFirst = (m_initialTab == ModInfoTabIDs::None);
@@ -220,7 +223,10 @@ int ModInfoDialog::exec()
ui->tabWidget->setCurrentIndex(0);
}
- return TutorableDialog::exec();
+ const int r = TutorableDialog::exec();
+ saveState();
+
+ return r;
}
void ModInfoDialog::setMod(ModInfo::Ptr mod)
@@ -356,7 +362,7 @@ void ModInfoDialog::setTabsVisibility(bool firstTime)
if (!firstTime) {
// but don't do it the first time visibility is set because the tabs are
// in the default order, which will clobber the current settings
- saveTabOrder(Settings::instance());
+ saveTabOrder();
}
// remember selection, if any
@@ -375,11 +381,14 @@ void ModInfoDialog::reAddTabs(
Q_ASSERT(visibility.size() == m_tabs.size());
// ordered tab names from settings
- const auto orderedNames = getOrderedTabNames();
+ const auto orderedNames = m_core->settings().geometry().modInfoTabOrder();
- // whether the tabs can be sorted; if the object name of a tab widget is not
- // found in orderedNames, the list cannot be sorted safely
- bool canSort = true;
+ // whether the tabs can be sorted
+ //
+ // if the object name of a tab widget is not found in orderedNames, the list
+ // cannot be sorted safely; if the list is empty, it's probably a first run
+ // and there's nothing to sort
+ bool canSort = !orderedNames.empty();
// gathering visible tabs
std::vector<TabInfo*> visibleTabs;
@@ -575,37 +584,28 @@ MOShared::FilesOrigin* ModInfoDialog::getOrigin()
return origin;
}
-void ModInfoDialog::saveState(Settings& s) const
+void ModInfoDialog::saveState() const
{
- saveTabOrder(s);
-
- // remove 2.2.0 settings
- s.directInterface().remove("mod_info_tabs");
- s.directInterface().remove("mod_info_conflict_expanders");
- s.directInterface().remove("mod_info_conflicts");
- s.directInterface().remove("mod_info_advanced_conflicts");
- s.directInterface().remove("mod_info_conflicts_overwrite");
- s.directInterface().remove("mod_info_conflicts_noconflict");
- s.directInterface().remove("mod_info_conflicts_overwritten");
+ saveTabOrder();
// save state for each tab
for (const auto& tabInfo : m_tabs) {
- tabInfo.tab->saveState(s);
+ tabInfo.tab->saveState(m_core->settings());
}
}
-void ModInfoDialog::restoreState(const Settings& s)
+void ModInfoDialog::restoreState()
{
// tab order is not restored here, it will be picked up if tabs have to be
// removed and re-added
// restore state for each tab
for (const auto& tabInfo : m_tabs) {
- tabInfo.tab->restoreState(s);
+ tabInfo.tab->restoreState(m_core->settings());
}
}
-void ModInfoDialog::saveTabOrder(Settings& s) const
+void ModInfoDialog::saveTabOrder() const
{
if (static_cast<int>(m_tabs.size()) != ui->tabWidget->count()) {
// only save tab state when all tabs are visible
@@ -629,40 +629,7 @@ void ModInfoDialog::saveTabOrder(Settings& s) const
names += ui->tabWidget->widget(i)->objectName();
}
- s.directInterface().setValue("mod_info_tab_order", names);
-}
-
-std::vector<QString> ModInfoDialog::getOrderedTabNames() const
-{
- const auto& settings = Settings::instance().directInterface();
-
- std::vector<QString> v;
-
- if (settings.contains("mod_info_tabs")) {
- // old byte array from 2.2.0
- QDataStream stream(settings.value("mod_info_tabs").toByteArray());
-
- int count = 0;
- stream >> count;
-
- for (int i=0; i<count; ++i) {
- QString s;
- stream >> s;
- v.emplace_back(std::move(s));
- }
- } else {
- // string list
- QString string = settings.value("mod_info_tab_order").toString();
- QTextStream stream(&string);
-
- while (!stream.atEnd()) {
- QString s;
- stream >> s;
- v.emplace_back(std::move(s));
- }
- }
-
- return v;
+ m_core->settings().geometry().setModInfoTabOrder(names);
}
void ModInfoDialog::onOriginModified(int originID)
diff --git a/src/modinfodialog.h b/src/modinfodialog.h
index 34555b0c..48680ca4 100644
--- a/src/modinfodialog.h
+++ b/src/modinfodialog.h
@@ -61,18 +61,11 @@ public:
//
void selectTab(ModInfoTabIDs id);
- // updates all tabs, selects the initial tab and opens the dialog
+ // updates all tabs, selects the initial tab, opens the dialog and
+ // saves/restores geometry
//
int exec() override;
- // saves the dialog state and calls saveState() on all tabs
- //
- void saveState(Settings& s) const;
-
- // restores the dialog state and calls restoreState() on all tabs
- //
- void restoreState(const Settings& s);
-
signals:
// emitted when a tab changes the origin
//
@@ -146,6 +139,15 @@ private:
void createTabs();
+ // saves the dialog state and calls saveState() on all tabs
+ //
+ void saveState() const;
+
+ // restores the dialog state and calls restoreState() on all tabs
+ //
+ void restoreState();
+
+
// sets the currently selected mod; resets first activation, but doesn't
// update anything
//
@@ -213,11 +215,7 @@ private:
// setTabsVisibility() to make sure any changes to order are saved before
// re-adding tabs
//
- void saveTabOrder(Settings& s) const;
-
- // returns a list of tab names in the order they should appear on the widget
- //
- std::vector<QString> getOrderedTabNames() const;
+ void saveTabOrder() const;
// asks all the tabs if they accept closing the dialog, returns false if one
// objected
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
index 03b490a2..3a71b405 100644
--- a/src/modinfodialogconflicts.cpp
+++ b/src/modinfodialogconflicts.cpp
@@ -409,8 +409,7 @@ void ConflictsTab::clear()
void ConflictsTab::saveState(Settings& s)
{
- s.directInterface().setValue(
- "mod_info_conflicts_tab", ui->tabConflictsTabs->currentIndex());
+ s.widgets().saveIndex(ui->tabConflictsTabs);
m_general.saveState(s);
m_advanced.saveState(s);
@@ -418,8 +417,7 @@ void ConflictsTab::saveState(Settings& s)
void ConflictsTab::restoreState(const Settings& s)
{
- ui->tabConflictsTabs->setCurrentIndex(
- s.directInterface().value("mod_info_conflicts_tab", 0).toInt());
+ s.widgets().restoreIndex(ui->tabConflictsTabs, 0);
m_general.restoreState(s);
m_advanced.restoreState(s);
@@ -817,55 +815,22 @@ void GeneralConflictsTab::clear()
void GeneralConflictsTab::saveState(Settings& s)
{
- QByteArray result;
- QDataStream stream(&result, QIODevice::WriteOnly);
-
- stream
- << m_expanders.overwrite.opened()
- << m_expanders.overwritten.opened()
- << m_expanders.nonconflict.opened();
-
- s.directInterface().setValue(
- "mod_info_conflicts_general_expanders", result);
-
- s.directInterface().setValue(
- "mod_info_conflicts_general_overwrite",
- ui->overwriteTree->header()->saveState());
-
- s.directInterface().setValue(
- "mod_info_conflicts_general_noconflict",
- ui->noConflictTree->header()->saveState());
-
- s.directInterface().setValue(
- "mod_info_conflicts_general_overwritten",
- ui->overwrittenTree->header()->saveState());
+ s.geometry().saveState(&m_expanders.overwrite);
+ s.geometry().saveState(&m_expanders.overwritten);
+ s.geometry().saveState(&m_expanders.nonconflict);
+ s.geometry().saveState(ui->overwriteTree->header());
+ s.geometry().saveState(ui->noConflictTree->header());
+ s.geometry().saveState(ui->overwrittenTree->header());
}
void GeneralConflictsTab::restoreState(const Settings& s)
{
- QDataStream stream(s.directInterface()
- .value("mod_info_conflicts_general_expanders").toByteArray());
-
- bool overwriteExpanded = false;
- bool overwrittenExpanded = false;
- bool noConflictExpanded = false;
-
- stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded;
-
- if (stream.status() == QDataStream::Ok) {
- m_expanders.overwrite.toggle(overwriteExpanded);
- m_expanders.overwritten.toggle(overwrittenExpanded);
- m_expanders.nonconflict.toggle(noConflictExpanded);
- }
-
- ui->overwriteTree->header()->restoreState(s.directInterface()
- .value("mod_info_conflicts_general_overwrite").toByteArray());
-
- ui->noConflictTree->header()->restoreState(s.directInterface()
- .value("mod_info_conflicts_general_noconflict").toByteArray());
-
- ui->overwrittenTree->header()->restoreState(s.directInterface()
- .value("mod_info_conflicts_general_overwritten").toByteArray());
+ s.geometry().restoreState(&m_expanders.overwrite);
+ s.geometry().restoreState(&m_expanders.overwritten);
+ s.geometry().restoreState(&m_expanders.nonconflict);
+ s.geometry().restoreState(ui->overwriteTree->header());
+ s.geometry().restoreState(ui->noConflictTree->header());
+ s.geometry().restoreState(ui->overwrittenTree->header());
}
bool GeneralConflictsTab::update()
@@ -1048,41 +1013,18 @@ void AdvancedConflictsTab::clear()
void AdvancedConflictsTab::saveState(Settings& s)
{
- s.directInterface().setValue(
- "mod_info_conflicts_advanced_list",
- ui->conflictsAdvancedList->header()->saveState());
-
- QByteArray result;
- QDataStream stream(&result, QIODevice::WriteOnly);
-
- stream
- << ui->conflictsAdvancedShowNoConflict->isChecked()
- << ui->conflictsAdvancedShowAll->isChecked()
- << ui->conflictsAdvancedShowNearest->isChecked();
-
- s.directInterface().setValue(
- "mod_info_conflicts_advanced_options", result);
+ s.geometry().saveState(ui->conflictsAdvancedList->header());
+ s.widgets().saveChecked(ui->conflictsAdvancedShowNoConflict);
+ s.widgets().saveChecked(ui->conflictsAdvancedShowAll);
+ s.widgets().saveChecked(ui->conflictsAdvancedShowNearest);
}
void AdvancedConflictsTab::restoreState(const Settings& s)
{
- ui->conflictsAdvancedList->header()->restoreState(
- s.directInterface().value("mod_info_conflicts_advanced_list").toByteArray());
-
- QDataStream stream(s.directInterface()
- .value("mod_info_conflicts_advanced_options").toByteArray());
-
- bool noConflictChecked = false;
- bool showAllChecked = false;
- bool showNearestChecked = false;
-
- stream >> noConflictChecked >> showAllChecked >> showNearestChecked;
-
- if (stream.status() == QDataStream::Ok) {
- ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked);
- ui->conflictsAdvancedShowAll->setChecked(showAllChecked);
- ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked);
- }
+ s.geometry().restoreState(ui->conflictsAdvancedList->header());
+ s.widgets().restoreChecked(ui->conflictsAdvancedShowNoConflict);
+ s.widgets().restoreChecked(ui->conflictsAdvancedShowAll);
+ s.widgets().restoreChecked(ui->conflictsAdvancedShowNearest);
}
void AdvancedConflictsTab::update()
diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp
index 3130b4bd..e5f1637f 100644
--- a/src/modinfodialogesps.cpp
+++ b/src/modinfodialogesps.cpp
@@ -277,12 +277,12 @@ void ESPsTab::update()
void ESPsTab::saveState(Settings& s)
{
- saveWidgetState(s.directInterface(), ui->ESPsSplitter);
+ s.geometry().saveState(ui->ESPsSplitter);
}
void ESPsTab::restoreState(const Settings& s)
{
- restoreWidgetState(s.directInterface(), ui->ESPsSplitter);
+ s.geometry().restoreState(ui->ESPsSplitter);
}
void ESPsTab::onActivate()
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp
index 10362058..9d347f57 100644
--- a/src/modinfodialogimages.cpp
+++ b/src/modinfodialogimages.cpp
@@ -130,18 +130,14 @@ void ImagesTab::update()
void ImagesTab::saveState(Settings& s)
{
- s.directInterface().setValue(
- "mod_info_dialog_images_show_dds", m_ddsEnabled);
-
- saveWidgetState(s.directInterface(), ui->tabImagesSplitter);
+ s.widgets().saveChecked(ui->imagesShowDDS);
+ s.geometry().saveState(ui->tabImagesSplitter);
}
void ImagesTab::restoreState(const Settings& s)
{
- ui->imagesShowDDS->setChecked(s.directInterface()
- .value("mod_info_dialog_images_show_dds", false).toBool());
-
- restoreWidgetState(s.directInterface(), ui->tabImagesSplitter);
+ s.widgets().restoreChecked(ui->imagesShowDDS);
+ s.geometry().restoreState(ui->tabImagesSplitter);
}
void ImagesTab::checkFiltering()
diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp
index 6d28cbe3..95e62328 100644
--- a/src/modinfodialognexus.cpp
+++ b/src/modinfodialognexus.cpp
@@ -19,7 +19,7 @@ NexusTab::NexusTab(ModInfoDialogTabContext cx) :
ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false)
{
ui->modID->setValidator(new QIntValidator(ui->modID));
- ui->endorse->setVisible(core().settings().endorsementIntegration());
+ ui->endorse->setVisible(core().settings().nexus().endorsementIntegration());
connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); });
connect(
diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h
index c43fa076..d0a58574 100644
--- a/src/modinfodialogtab.h
+++ b/src/modinfodialogtab.h
@@ -253,32 +253,6 @@ protected:
//
void setFocus();
-
- // saves the sate of the given widget in geometry/modinfodialog_[objectname]
- //
- // this needs to be a template because saveState() and restoreState() are
- // not in QWidget, but they're in various widgets
- //
- template <class Widget>
- void saveWidgetState(QSettings& s, Widget* w)
- {
- s.setValue(settingName(w), w->saveState());
- }
-
- // restores the sate of the given widget from
- // geometry/modinfodialog_[objectname]
- //
- // this needs to be a template because saveState() and restoreState() are
- // not in QWidget, but they're in various widgets
- //
- template <class Widget>
- void restoreWidgetState(const QSettings& s, Widget* w)
- {
- if (s.contains(settingName(w))) {
- w->restoreState(s.value(settingName(w)).toByteArray());
- }
- }
-
private:
// core
OrganizerCore& m_core;
@@ -303,14 +277,6 @@ private:
// true if the tab has never been selected for the current mod
bool m_firstActivation;
-
-
- // used by saveWidgetState() and restoreWidgetState()
- //
- QString settingName(QWidget* w)
- {
- return "geometry/modinfodialog_" + w->objectName();
- }
};
diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp
index 7a09fa4e..fdf12568 100644
--- a/src/modinfodialogtextfiles.cpp
+++ b/src/modinfodialogtextfiles.cpp
@@ -176,12 +176,12 @@ void GenericFilesTab::update()
void GenericFilesTab::saveState(Settings& s)
{
- saveWidgetState(s.directInterface(), m_splitter);
+ s.geometry().saveState(m_splitter);
}
void GenericFilesTab::restoreState(const Settings& s)
{
- restoreWidgetState(s.directInterface(), m_splitter);
+ s.geometry().restoreState(m_splitter);
}
void GenericFilesTab::onSelection(
diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp
index 37c8c650..fb110abb 100644
--- a/src/modinfooverwrite.cpp
+++ b/src/modinfooverwrite.cpp
@@ -23,7 +23,7 @@ bool ModInfoOverwrite::isEmpty() const
QString ModInfoOverwrite::absolutePath() const
{
- return Settings::instance().getOverwriteDirectory();
+ return Settings::instance().paths().overwrite();
}
std::vector<ModInfo::EFlag> ModInfoOverwrite::getFlags() const
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp
index ce29e11e..3cff914a 100644
--- a/src/modinforegular.cpp
+++ b/src/modinforegular.cpp
@@ -631,7 +631,7 @@ std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const
std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags();
if ((m_NexusID > 0) &&
(endorsedState() == ENDORSED_FALSE) &&
- Settings::instance().endorsementIntegration()) {
+ Settings::instance().nexus().endorsementIntegration()) {
result.push_back(ModInfo::FLAG_NOTENDORSED);
}
if ((m_NexusID > 0) &&
diff --git a/src/modlist.cpp b/src/modlist.cpp
index c591c49b..6018d3d4 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -390,7 +390,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
return QVariant();
} else if (role == Qt::ForegroundRole) {
if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid()) {
- return Settings::getIdealTextColor(modInfo->getColor());
+ return ColorSettings::idealTextColor(modInfo->getColor());
} else if (column == COL_NAME) {
int highlight = modInfo->getHighlight();
if (highlight & ModInfo::HIGHLIGHT_IMPORTANT)
@@ -416,19 +416,19 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end();
bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end();
if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) {
- return Settings::instance().modlistContainsPluginColor();
+ return Settings::instance().colors().modlistContainsPlugin();
} else if (overwritten || archiveLooseOverwritten) {
- return Settings::instance().modlistOverwritingLooseColor();
+ return Settings::instance().colors().modlistOverwritingLoose();
} else if (overwrite || archiveLooseOverwrite) {
- return Settings::instance().modlistOverwrittenLooseColor();
+ return Settings::instance().colors().modlistOverwrittenLoose();
} else if (archiveOverwritten) {
- return Settings::instance().modlistOverwritingArchiveColor();
+ return Settings::instance().colors().modlistOverwritingArchive();
} else if (archiveOverwrite) {
- return Settings::instance().modlistOverwrittenArchiveColor();
+ return Settings::instance().colors().modlistOverwrittenArchive();
} else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)
&& modInfo->getColor().isValid()
&& ((role != ViewMarkingScrollBar::DEFAULT_ROLE)
- || Settings::instance().colorSeparatorScrollbar())) {
+ || Settings::instance().colors().colorSeparatorScrollbar())) {
return modInfo->getColor();
} else {
return QVariant();
@@ -999,8 +999,8 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa
ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
QDir modDir = QDir(modInfo->absolutePath());
- QDir allModsDir(Settings::instance().getModDirectory());
- QDir overwriteDir(Settings::instance().getOverwriteDirectory());
+ QDir allModsDir(Settings::instance().paths().mods());
+ QDir overwriteDir(Settings::instance().paths().overwrite());
QStringList sourceList;
QStringList targetList;
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp
index fd1dc0c1..c6ef7bc7 100644
--- a/src/nxmaccessmanager.cpp
+++ b/src/nxmaccessmanager.cpp
@@ -48,8 +48,9 @@ const QString NexusSSO("wss://sso.nexusmods.com");
const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2");
-ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t)
- : m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr)
+ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) :
+ m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr),
+ m_first(true)
{
m_bar = new QProgressBar;
m_bar->setTextVisible(false);
@@ -103,6 +104,14 @@ void ValidationProgressDialog::stop()
hide();
}
+void ValidationProgressDialog::showEvent(QShowEvent* e)
+{
+ if (m_first) {
+ Settings::instance().geometry().centerOnMainWindowMonitor(this);
+ m_first = false;
+ }
+}
+
void ValidationProgressDialog::closeEvent(QCloseEvent* e)
{
hide();
@@ -563,7 +572,7 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion)
m_validator.finished = [&](auto&& user){ onValidatorFinished(user); };
setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators(
- Settings::instance().getCacheDirectory() + "/nexus_cookies.dat")));
+ Settings::instance().paths().cache() + "/nexus_cookies.dat")));
if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) {
// why is this necessary all of a sudden?
diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h
index eed7c1c9..0c85153b 100644
--- a/src/nxmaccessmanager.h
+++ b/src/nxmaccessmanager.h
@@ -48,6 +48,7 @@ public:
using QDialog::show;
protected:
+ void showEvent(QShowEvent* e) override;
void closeEvent(QCloseEvent* e) override;
private:
@@ -56,6 +57,7 @@ private:
QDialogButtonBox* m_buttons;
QTimer* m_timer;
QElapsedTimer m_elapsed;
+ bool m_first;
void onButton(QAbstractButton* b);
void onTimer();
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 1e164525..91e16716 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -94,15 +94,6 @@ static bool isOnline()
return false;
}
-static bool renameFile(const QString &oldName, const QString &newName,
- bool overwrite = true)
-{
- if (overwrite && QFile::exists(newName)) {
- QFile::remove(newName);
- }
- return QFile::rename(oldName, newName);
-}
-
static std::wstring getProcessName(HANDLE process)
{
wchar_t buffer[MAX_PATH];
@@ -155,7 +146,7 @@ static void startSteam(QWidget *widget)
QStringList args;
QString username;
QString password;
- if (Settings::instance().getSteamLogin(username, password)) {
+ if (Settings::instance().steam().login(username, password)) {
args << "-login";
args << username;
if (password != "") {
@@ -284,15 +275,13 @@ OrganizerCore::OrganizerCore(Settings &settings)
, m_ArchivesInit(false)
, m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this))
{
- m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory());
- m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers());
-
- NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory());
+ m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads());
- MOBase::QuestionBoxMemory::init(m_Settings.directInterface().fileName());
+ NexusInterface::instance(m_PluginContainer)->setCacheDirectory(
+ m_Settings.paths().cache());
- m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
- m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory());
+ m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
+ m_InstallationManager.setDownloadDirectory(m_Settings.paths().downloads());
connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this,
SLOT(downloadSpeed(QString, int)));
@@ -342,80 +331,33 @@ OrganizerCore::~OrganizerCore()
delete m_DirectoryStructure;
}
-QString OrganizerCore::commitSettings(const QString &iniFile)
-{
- if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) {
- DWORD err = ::GetLastError();
- // make a second attempt using qt functions but if that fails print the
- // error from the first attempt
- if (!renameFile(iniFile + ".new", iniFile)) {
- return QString::fromStdWString(formatSystemMessage(err));
- }
- }
- return QString();
-}
-
-QSettings::Status OrganizerCore::storeSettings(const QString &fileName)
+void OrganizerCore::storeSettings()
{
- QSettings settings(fileName, QSettings::IniFormat);
-
- if (m_UserInterface != nullptr) {
- m_UserInterface->storeSettings(settings);
- }
-
if (m_CurrentProfile != nullptr) {
- settings.setValue("selected_profile",
- m_CurrentProfile->name().toUtf8().constData());
+ m_Settings.game().setSelectedProfileName(m_CurrentProfile->name());
}
- m_ExecutablesList.store(settings);
-
- FileDialogMemory::save(settings);
+ m_ExecutablesList.store(m_Settings);
- settings.sync();
- return settings.status();
-}
+ FileDialogMemory::save(m_Settings);
-void OrganizerCore::storeSettings()
-{
- QString iniFile = qApp->property("dataPath").toString() + "/"
- + QString::fromStdWString(AppConfig::iniFileName());
- if (QFileInfo(iniFile).exists()) {
- if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) {
- const auto e = GetLastError();
- QMessageBox::critical(
- qApp->activeWindow(), tr("Failed to write settings"),
- tr("An error occurred trying to update MO settings to %1: %2")
- .arg(iniFile)
- .arg(QString::fromStdWString(formatSystemMessage(e))));
- return;
- }
- }
+ const auto result = m_Settings.sync();
- QString writeTarget = iniFile + ".new";
-
- QSettings::Status result = storeSettings(writeTarget);
+ if (result != QSettings::NoError) {
+ QString reason;
- if (result == QSettings::NoError) {
- QString errMsg = commitSettings(iniFile);
- if (!errMsg.isEmpty()) {
- log::warn(
- "settings file not writable, may be locked by another "
- "application, trying direct write");
- writeTarget = iniFile;
- result = storeSettings(iniFile);
+ if (result == QSettings::AccessError) {
+ reason = tr("File is write protected");
+ } else if (result == QSettings::FormatError) {
+ reason = tr("Invalid file format (probably a bug)");
+ } else {
+ reason = tr("Unknown error %1").arg(result);
}
- }
- if (result != QSettings::NoError) {
- QString reason = result == QSettings::AccessError
- ? tr("File is write protected")
- : result == QSettings::FormatError
- ? tr("Invalid file format (probably a bug)")
- : tr("Unknown error %1").arg(result);
+
QMessageBox::critical(
- qApp->activeWindow(), tr("Failed to write settings"),
- tr("An error occurred trying to write back MO settings to %1: %2")
- .arg(writeTarget, reason));
+ qApp->activeWindow(), tr("Failed to write settings"),
+ tr("An error occurred trying to write back MO settings to %1: %2")
+ .arg(m_Settings.filename(), reason));
}
}
@@ -480,19 +422,20 @@ return true;
}
-void OrganizerCore::updateExecutablesList(QSettings &settings)
+void OrganizerCore::updateExecutablesList()
{
if (m_PluginContainer == nullptr) {
log::error("can't update executables list now");
return;
}
- m_ExecutablesList.load(managedGame(), settings);
+ m_ExecutablesList.load(managedGame(), m_Settings);
// TODO this has nothing to do with executables list move to an appropriate
// function!
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
- m_PluginContainer, m_Settings.displayForeign(), managedGame());
+ ModInfo::updateFromDisc(
+ m_Settings.paths().mods(), &m_DirectoryStructure,
+ m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
}
void OrganizerCore::setUserInterface(IUserInterface *userInterface,
@@ -537,7 +480,7 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface,
if (userInterface != nullptr) {
// this currently wouldn't work reliably if the ui isn't initialized yet to
// display the result
- if (isOnline() && !m_Settings.offlineMode()) {
+ if (isOnline() && !m_Settings.network().offlineMode()) {
m_Updater.testForUpdate();
} else {
log::debug("user doesn't seem to be connected to the internet");
@@ -571,7 +514,7 @@ void OrganizerCore::disconnectPlugins()
m_DownloadManager.setPluginContainer(nullptr);
m_ModList.setPluginContainer(nullptr);
- m_Settings.clearPlugins();
+ m_Settings.plugins().clearPlugins();
m_GamePlugin = nullptr;
m_PluginContainer = nullptr;
}
@@ -600,7 +543,7 @@ bool OrganizerCore::nexusApi(bool retry)
return false;
} else {
QString apiKey;
- if (m_Settings.getNexusApiKey(apiKey)) {
+ if (m_Settings.nexus().apiKey(apiKey)) {
// credentials stored or user entered them manually
log::debug("attempt to verify nexus api key");
accessManager->apiCheck(apiKey);
@@ -667,7 +610,7 @@ void OrganizerCore::removeOrigin(const QString &name)
void OrganizerCore::downloadSpeed(const QString &serverName, int bytesPerSecond)
{
- m_Settings.setDownloadSpeed(serverName, bytesPerSecond);
+ m_Settings.network().setDownloadSpeed(serverName, bytesPerSecond);
}
InstallationManager *OrganizerCore::installationManager()
@@ -688,9 +631,9 @@ bool OrganizerCore::createDirectory(const QString &path) {
}
bool OrganizerCore::checkPathSymlinks() {
- bool hasSymlink = (QFileInfo(m_Settings.getProfileDirectory()).isSymLink() ||
- QFileInfo(m_Settings.getModDirectory()).isSymLink() ||
- QFileInfo(m_Settings.getOverwriteDirectory()).isSymLink());
+ bool hasSymlink = (QFileInfo(m_Settings.paths().profiles()).isSymLink() ||
+ QFileInfo(m_Settings.paths().mods()).isSymLink() ||
+ QFileInfo(m_Settings.paths().overwrite()).isSymLink());
if (hasSymlink) {
QMessageBox::critical(nullptr, QObject::tr("Error"),
QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) "
@@ -702,17 +645,17 @@ bool OrganizerCore::checkPathSymlinks() {
}
bool OrganizerCore::bootstrap() {
- return createDirectory(m_Settings.getProfileDirectory()) &&
- createDirectory(m_Settings.getModDirectory()) &&
- createDirectory(m_Settings.getDownloadDirectory()) &&
- createDirectory(m_Settings.getOverwriteDirectory()) &&
+ 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();
}
void OrganizerCore::createDefaultProfile()
{
- QString profilesPath = settings().getProfileDirectory();
+ QString profilesPath = settings().paths().profiles();
if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size()
== 0) {
Profile newProf("Default", managedGame(), false);
@@ -725,7 +668,7 @@ void OrganizerCore::prepareVFS()
}
void OrganizerCore::updateVFSParams(
- log::Levels logLevel, int crashDumpsType, QString executableBlacklist)
+ log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist)
{
setGlobalCrashDumpsType(crashDumpsType);
m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist);
@@ -733,25 +676,25 @@ void OrganizerCore::updateVFSParams(
void OrganizerCore::setLogLevel(log::Levels level)
{
- m_Settings.setLogLevel(level);
+ m_Settings.diagnostics().setLogLevel(level);
updateVFSParams(
- m_Settings.logLevel(),
- m_Settings.crashDumpsType(),
+ m_Settings.diagnostics().logLevel(),
+ m_Settings.diagnostics().crashDumpsType(),
m_Settings.executablesBlacklist());
- log::getDefault().setLevel(m_Settings.logLevel());
+ log::getDefault().setLevel(m_Settings.diagnostics().logLevel());
}
bool OrganizerCore::cycleDiagnostics() {
- if (int maxDumps = settings().crashDumpsMax())
+ if (int maxDumps = settings().diagnostics().crashDumpsMax())
removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed);
return true;
}
//static
-void OrganizerCore::setGlobalCrashDumpsType(int crashDumpsType) {
- m_globalCrashDumpsType = ::crashDumpsType(crashDumpsType);
+void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type) {
+ m_globalCrashDumpsType = type;
}
//static
@@ -779,7 +722,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
return;
}
- QDir profileBaseDir(settings().getProfileDirectory());
+ QDir profileBaseDir(settings().paths().profiles());
QString profileDir = profileBaseDir.absoluteFilePath(profileName);
if (!QDir(profileDir).exists()) {
@@ -803,16 +746,11 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
m_CurrentProfile->deactivateInvalidation();
}
+ m_Settings.game().setSelectedProfileName(m_CurrentProfile->name());
+
connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint)));
connect(m_CurrentProfile, SIGNAL(modStatusChanged(QList<uint>)), this, SLOT(modStatusChanged(QList<uint>)));
refreshDirectoryStructure();
-
- //This line is not actually needed and was only added to allow some
- //outside detection of Mo2 profile change. (like BaobobMiller utility)
- if (m_CurrentProfile != nullptr) {
- settings().directInterface().setValue("selected_profile",
- m_CurrentProfile->name().toUtf8().constData());
- }
}
MOBase::IModRepositoryBridge *OrganizerCore::createNexusBridge() const
@@ -840,22 +778,22 @@ QString OrganizerCore::profilePath() const
QString OrganizerCore::downloadsPath() const
{
- return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory());
+ return QDir::fromNativeSeparators(m_Settings.paths().downloads());
}
QString OrganizerCore::overwritePath() const
{
- return QDir::fromNativeSeparators(m_Settings.getOverwriteDirectory());
+ return QDir::fromNativeSeparators(m_Settings.paths().overwrite());
}
QString OrganizerCore::basePath() const
{
- return QDir::fromNativeSeparators(m_Settings.getBaseDirectory());
+ return QDir::fromNativeSeparators(m_Settings.paths().base());
}
QString OrganizerCore::modsPath() const
{
- return QDir::fromNativeSeparators(m_Settings.getModDirectory());
+ return QDir::fromNativeSeparators(m_Settings.paths().mods());
}
MOBase::VersionInfo OrganizerCore::appVersion() const
@@ -885,10 +823,10 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name)
return nullptr;
}
- m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
+ m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
QString targetDirectory
- = QDir::fromNativeSeparators(m_Settings.getModDirectory())
+ = QDir::fromNativeSeparators(m_Settings.paths().mods())
.append("/")
.append(name);
@@ -928,26 +866,26 @@ void OrganizerCore::modDataChanged(MOBase::IModInterface *)
QVariant OrganizerCore::pluginSetting(const QString &pluginName,
const QString &key) const
{
- return m_Settings.pluginSetting(pluginName, key);
+ return m_Settings.plugins().setting(pluginName, key);
}
void OrganizerCore::setPluginSetting(const QString &pluginName,
const QString &key, const QVariant &value)
{
- m_Settings.setPluginSetting(pluginName, key, value);
+ m_Settings.plugins().setSetting(pluginName, key, value);
}
QVariant OrganizerCore::persistent(const QString &pluginName,
const QString &key,
const QVariant &def) const
{
- return m_Settings.pluginPersistent(pluginName, key, def);
+ return m_Settings.plugins().persistent(pluginName, key, def);
}
void OrganizerCore::setPersistent(const QString &pluginName, const QString &key,
const QVariant &value, bool sync)
{
- m_Settings.setPluginPersistent(pluginName, key, value, sync);
+ m_Settings.plugins().setPersistent(pluginName, key, value, sync);
}
QString OrganizerCore::pluginDataPath() const
@@ -976,7 +914,7 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
modName.update(initModName, GUESS_USER);
}
m_CurrentProfile->writeModlistNow();
- m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
+ m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
MessageDialog::showMessage(tr("Installation successful"),
qApp->activeWindow());
@@ -1041,7 +979,7 @@ void OrganizerCore::installDownload(int index)
m_CurrentProfile->writeModlistNow();
bool hasIniTweaks = false;
- m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
+ m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
MessageDialog::showMessage(tr("Installation successful"),
qApp->activeWindow());
@@ -1334,7 +1272,7 @@ bool OrganizerCore::previewFileWithAlternatives(
else {
// crude: we search for the next slash after the base mod directory to skip
// everything up to the data-relative directory
- int offset = settings().getModDirectory().size() + 1;
+ int offset = settings().paths().mods().size() + 1;
offset = fileName.indexOf("/", offset);
fileName = fileName.mid(offset + 1);
}
@@ -1349,7 +1287,8 @@ bool OrganizerCore::previewFileWithAlternatives(
}
// set up preview dialog
- PreviewDialog preview(fileName);
+ PreviewDialog preview(fileName, parent);
+
auto addFunc = [&](int originId) {
FilesOrigin &origin = directoryStructure()->getOriginByID(originId);
QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName;
@@ -1404,16 +1343,7 @@ bool OrganizerCore::previewFileWithAlternatives(
}
if (preview.numVariants() > 0) {
- QSettings &s = settings().directInterface();
- QString key = QString("geometry/%1").arg(preview.objectName());
- if (s.contains(key)) {
- preview.restoreGeometry(s.value(key).toByteArray());
- }
-
preview.exec();
-
- s.setValue(key, preview.saveGeometry());
-
return true;
}
else {
@@ -1433,7 +1363,7 @@ bool OrganizerCore::previewFile(
return false;
}
- PreviewDialog preview(path);
+ PreviewDialog preview(path, parent);
QWidget *wid = m_PluginContainer->previewGenerator().genPreview(path);
if (wid == nullptr) {
@@ -1442,17 +1372,8 @@ bool OrganizerCore::previewFile(
}
preview.addVariant(originName, wid);
-
- QSettings &s = settings().directInterface();
- QString key = QString("geometry/%1").arg(preview.objectName());
- if (s.contains(key)) {
- preview.restoreGeometry(s.value(key).toByteArray());
- }
-
preview.exec();
- s.setValue(key, preview.saveGeometry());
-
return true;
}
@@ -1493,7 +1414,7 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
LPDWORD exitCode)
{
HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries);
- if (Settings::instance().lockGUI() && processHandle != INVALID_HANDLE_VALUE) {
+ if (Settings::instance().interface().lockGUI() && processHandle != INVALID_HANDLE_VALUE) {
std::unique_ptr<LockedDialog> dlg;
ILockedWaitingForProcess* uilock = nullptr;
@@ -1542,7 +1463,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
} else {
::SetEnvironmentVariableW(L"SteamAPPId",
- ToWString(m_Settings.getSteamAppID()).c_str());
+ ToWString(m_Settings.steam().appID()).c_str());
}
QWidget *window = qApp->activeWindow();
@@ -1558,7 +1479,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
|| QFileInfo(managedGame()->gameDirectory().absoluteFilePath(
"steam_api64.dll"))
.exists())
- && (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) {
+ && (m_Settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) {
bool steamFound = true;
bool steamAccess = true;
@@ -1673,7 +1594,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
}
}
- QString modsPath = settings().getModDirectory();
+ QString modsPath = settings().paths().mods();
// Check if this a request with either an executable or a working directory under our mods folder
// then will start the process in a virtualized "environment" with the appropriate paths fixed:
@@ -1830,7 +1751,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable,
bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
{
- if (!Settings::instance().lockGUI())
+ if (!Settings::instance().interface().lockGUI())
return true;
ILockedWaitingForProcess* uilock = nullptr;
@@ -2041,8 +1962,10 @@ void OrganizerCore::refreshModList(bool saveChanges)
if (saveChanges) {
m_CurrentProfile->writeModlistNow(true);
}
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
- m_PluginContainer, m_Settings.displayForeign(), managedGame());
+
+ ModInfo::updateFromDisc(
+ m_Settings.paths().mods(), &m_DirectoryStructure,
+ m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
m_CurrentProfile->refreshModStatus();
@@ -2211,7 +2134,7 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap<unsigned int, ModInfo::P
updateModsActiveState(modInfo.keys(), true);
m_PluginList.blockSignals(false);
// now we need to refresh the bsa list and save it so there is no confusion
- // about what archives are avaiable and active
+ // about what archives are available and active
refreshBSAList();
if (m_UserInterface != nullptr) {
m_UserInterface->archivesWriter().writeImmediately(false);
@@ -2237,7 +2160,7 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function<void ()> f)
f();
} else {
QString apiKey;
- if (settings().getNexusApiKey(apiKey)) {
+ if (settings().nexus().apiKey(apiKey)) {
doAfterLogin([f]{ f(); });
NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(apiKey);
} else {
@@ -2376,8 +2299,10 @@ void OrganizerCore::profileRefresh()
{
// have to refresh mods twice (again in refreshModList), otherwise the refresh
// isn't complete. Not sure why
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
- m_PluginContainer, m_Settings.displayForeign(), managedGame());
+ ModInfo::updateFromDisc(
+ m_Settings.paths().mods(), &m_DirectoryStructure,
+ m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
+
m_CurrentProfile->refreshModStatus();
refreshModList();
@@ -2544,7 +2469,7 @@ void OrganizerCore::syncOverwrite()
SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure,
qApp->activeWindow());
if (syncDialog.exec() == QDialog::Accepted) {
- syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory()));
+ syncDialog.apply(QDir::fromNativeSeparators(m_Settings.paths().mods()));
modInfo->testValid();
refreshDirectoryStructure();
}
@@ -2567,7 +2492,7 @@ std::vector<unsigned int> OrganizerCore::activeProblems() const
const auto& hookdll = oldMO1HookDll();
if (!hookdll.isEmpty()) {
// This warning will now be shown every time the problems are checked, which is a bit
- // of a "log spam". But since this is a sevre error which will most likely make the
+ // of a "log spam". But since this is a sever error which will most likely make the
// game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it
// easier for the user to notice the warning.
log::warn("hook.dll found in game folder: {}", hookdll);
@@ -2643,7 +2568,7 @@ void OrganizerCore::savePluginList()
}
m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(),
m_CurrentProfile->getDeleterFileName(),
- m_Settings.hideUncheckedPlugins());
+ m_Settings.game().hideUncheckedPlugins());
m_PluginList.saveLoadOrder(*m_DirectoryStructure);
}
@@ -2655,7 +2580,7 @@ void OrganizerCore::prepareStart()
m_CurrentProfile->writeModlist();
m_CurrentProfile->createTweakedIniFile();
saveCurrentLists();
- m_Settings.setupLoadMechanism();
+ m_Settings.game().loadMechanism().activate(m_Settings.game().loadMechanismType());
storeSettings();
}
@@ -2669,7 +2594,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
}
IPluginGame *game = qApp->property("managed_game").value<IPluginGame *>();
- Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName),
+ Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName),
game);
MappingType result;
@@ -2715,7 +2640,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
}
result.insert(result.end(), {
- QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()),
+ QDir::toNativeSeparators(m_Settings.paths().overwrite()),
dataPath,
true,
customOverwrite.isEmpty()
diff --git a/src/organizercore.h b/src/organizercore.h
index 2aa7e707..a14d79a9 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -107,7 +107,7 @@ public:
void setManagedGame(MOBase::IPluginGame *game);
- void updateExecutablesList(QSettings &settings);
+ void updateExecutablesList();
void startMOUpdate();
@@ -193,7 +193,7 @@ public:
void prepareVFS();
void updateVFSParams(
- MOBase::log::Levels logLevel, int crashDumpsType,
+ MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType,
QString executableBlacklist);
void setLogLevel(MOBase::log::Levels level);
@@ -201,7 +201,7 @@ public:
bool cycleDiagnostics();
static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; }
- static void setGlobalCrashDumpsType(int crashDumpsType);
+ static void setGlobalCrashDumpsType(CrashDumpsType crashDumpsType);
static std::wstring crashDumpsPath();
public:
@@ -288,10 +288,6 @@ private:
void storeSettings();
- QSettings::Status storeSettings(const QString &fileName);
-
- QString commitSettings(const QString &iniFile);
-
bool queryApi(QString &apiKey);
void updateModActiveState(int index, bool active);
diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp
index 715e11e3..fe1d8825 100644
--- a/src/overwriteinfodialog.cpp
+++ b/src/overwriteinfodialog.cpp
@@ -104,6 +104,18 @@ OverwriteInfoDialog::~OverwriteInfoDialog()
delete ui;
}
+void OverwriteInfoDialog::showEvent(QShowEvent* e)
+{
+ Settings::instance().geometry().restoreGeometry(this);
+ QDialog::showEvent(e);
+}
+
+void OverwriteInfoDialog::done(int r)
+{
+ Settings::instance().geometry().saveGeometry(this);
+ QDialog::done(r);
+}
+
void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo)
{
m_ModInfo = modInfo;
diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h
index 4b731736..bedb779a 100644
--- a/src/overwriteinfodialog.h
+++ b/src/overwriteinfodialog.h
@@ -31,7 +31,7 @@ class OverwriteInfoDialog;
class OverwriteInfoDialog : public QDialog
{
Q_OBJECT
-
+
public:
explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0);
@@ -39,8 +39,17 @@ public:
ModInfo::Ptr modInfo() const { return m_ModInfo; }
+ // saves geometry
+ //
+ void done(int r) override;
+
void setModInfo(ModInfo::Ptr modInfo);
+protected:
+ // restores geometry
+ //
+ void showEvent(QShowEvent* e) override;
+
private:
void openFile(const QModelIndex &index);
diff --git a/src/pch.h b/src/pch.h
index 504ef8f1..af1a4ade 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -95,6 +95,7 @@
#include <QDialogButtonBox>
#include <QDir>
#include <QDirIterator>
+#include <QDockWidget>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QElapsedTimer>
@@ -189,6 +190,7 @@
#include <QSize>
#include <QSizePolicy>
#include <QSortFilterProxyModel>
+#include <QSplitter>
#include <QSpinBox>
#include <QSplashScreen>
#include <QSslSocket>
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp
index 62cdff1e..c0706ba8 100644
--- a/src/plugincontainer.cpp
+++ b/src/plugincontainer.cpp
@@ -95,7 +95,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName)
return false;
}
plugin->setProperty("filename", fileName);
- m_Organizer->settings().registerPlugin(pluginObj);
+ m_Organizer->settings().plugins().registerPlugin(pluginObj);
}
{ // diagnosis plugin
@@ -266,7 +266,7 @@ void PluginContainer::loadPlugins()
"(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().addBlacklistPlugin(fileName);
+ m_Organizer->settings().plugins().addBlacklist(fileName);
}
loadCheck.close();
}
@@ -279,7 +279,7 @@ void PluginContainer::loadPlugins()
while (iter.hasNext()) {
iter.next();
- if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) {
+ if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) {
log::debug("plugin \"{}\" blacklisted", iter.fileName());
continue;
}
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index 8637f546..33423225 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -191,7 +191,7 @@ void PluginList::refresh(const QString &profileName
continue;
}
- bool forceEnabled = Settings::instance().forceEnableCoreFiles() &&
+ bool forceEnabled = Settings::instance().game().forceEnableCoreFiles() &&
primaryPlugins.contains(filename, Qt::CaseInsensitive);
//(std::find(primaryPlugins.begin(), primaryPlugins.end(), filename.toLower()) != primaryPlugins.end());
@@ -931,7 +931,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
} else if (role == Qt::BackgroundRole
|| (role == ViewMarkingScrollBar::DEFAULT_ROLE)) {
if (m_ESPs[index].m_ModSelected) {
- return Settings::instance().pluginListContainedColor();
+ return Settings::instance().colors().pluginListContained();
} else {
return QVariant();
}
diff --git a/src/previewdialog.cpp b/src/previewdialog.cpp
index de33cdd0..91a5f13e 100644
--- a/src/previewdialog.cpp
+++ b/src/previewdialog.cpp
@@ -1,5 +1,6 @@
#include "previewdialog.h"
#include "ui_previewdialog.h"
+#include "settings.h"
#include <QFileInfo>
PreviewDialog::PreviewDialog(const QString &fileName, QWidget *parent) :
@@ -17,6 +18,12 @@ PreviewDialog::~PreviewDialog()
delete ui;
}
+int PreviewDialog::exec()
+{
+ GeometrySaver gs(Settings::instance(), this);
+ return QDialog::exec();
+}
+
void PreviewDialog::addVariant(const QString &modName, QWidget *widget)
{
widget->setProperty("modName", modName);
diff --git a/src/previewdialog.h b/src/previewdialog.h
index 0011bc50..9525f127 100644
--- a/src/previewdialog.h
+++ b/src/previewdialog.h
@@ -15,6 +15,10 @@ public:
explicit PreviewDialog(const QString &fileName, QWidget *parent = 0);
~PreviewDialog();
+ // also saves and restores geometry
+ //
+ int exec() override;
+
void addVariant(const QString &modName, QWidget *widget);
int numVariants() const;
diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp
index da09935b..63d58295 100644
--- a/src/problemsdialog.cpp
+++ b/src/problemsdialog.cpp
@@ -29,6 +29,12 @@ ProblemsDialog::~ProblemsDialog()
delete ui;
}
+int ProblemsDialog::exec()
+{
+ GeometrySaver gs(Settings::instance(), this);
+ return QDialog::exec();
+}
+
void ProblemsDialog::runDiagnosis()
{
m_hasProblems = false;
diff --git a/src/problemsdialog.h b/src/problemsdialog.h
index c211e4f5..a30c8d48 100644
--- a/src/problemsdialog.h
+++ b/src/problemsdialog.h
@@ -20,6 +20,10 @@ public:
explicit ProblemsDialog(std::vector<QObject*> pluginObjects, QWidget *parent = 0);
~ProblemsDialog();
+ // also saves and restores geometry
+ //
+ int exec() override;
+
bool hasProblems() const;
private:
diff --git a/src/profile.cpp b/src/profile.cpp
index 7f4ebcaa..e76060b9 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -73,7 +73,7 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef
: m_ModListWriter(std::bind(&Profile::doWriteModlist, this))
, m_GamePlugin(gamePlugin)
{
- QString profilesDir = Settings::instance().getProfileDirectory();
+ QString profilesDir = Settings::instance().paths().profiles();
QDir profileBase(profilesDir);
QString fixedName = name;
if (!fixDirectoryName(fixedName)) {
@@ -299,7 +299,7 @@ void Profile::createTweakedIniFile()
// static
void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName)
{
- QDir profilesDir(Settings::instance().getProfileDirectory());
+ QDir profilesDir(Settings::instance().paths().profiles());
profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
QDirIterator profileIter(profilesDir);
while (profileIter.hasNext()) {
@@ -655,7 +655,7 @@ void Profile::setModPriority(unsigned int index, int &newPriority)
Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin)
{
- QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name;
+ QString profileDirectory = Settings::instance().paths().profiles() + "/" + name;
reference.copyFilesTo(profileDirectory);
return new Profile(QDir(profileDirectory), gamePlugin);
}
@@ -906,7 +906,7 @@ QString Profile::savePath() const
void Profile::rename(const QString &newName)
{
- QDir profileDir(Settings::instance().getProfileDirectory());
+ QDir profileDir(Settings::instance().paths().profiles());
profileDir.rename(name(), newName);
m_Directory.setPath(profileDir.absoluteFilePath(newName));
}
diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp
index d7863fc8..c91f48f4 100644
--- a/src/profilesdialog.cpp
+++ b/src/profilesdialog.cpp
@@ -57,7 +57,7 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c
{
ui->setupUi(this);
- QDir profilesDir(Settings::instance().getProfileDirectory());
+ QDir profilesDir(Settings::instance().paths().profiles());
profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
QDirIterator profileIter(profilesDir);
@@ -84,6 +84,12 @@ ProfilesDialog::~ProfilesDialog()
delete ui;
}
+int ProfilesDialog::exec()
+{
+ GeometrySaver gs(Settings::instance(), this);
+ return QDialog::exec();
+}
+
void ProfilesDialog::showEvent(QShowEvent *event)
{
TutorableDialog::showEvent(event);
@@ -197,7 +203,7 @@ void ProfilesDialog::on_removeProfileButton_clicked()
if (confirmBox.exec() == QMessageBox::Yes) {
QString profilePath;
if (profileToDelete.get() == nullptr) {
- profilePath = Settings::instance().getProfileDirectory()
+ profilePath = Settings::instance().paths().profiles()
+ "/" + ui->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/profilesdialog.h b/src/profilesdialog.h
index a328ce40..a47367be 100644
--- a/src/profilesdialog.h
+++ b/src/profilesdialog.h
@@ -51,6 +51,10 @@ public:
explicit ProfilesDialog(const QString &profileName, MOBase::IPluginGame const *game, QWidget *parent = 0);
~ProfilesDialog();
+ // also saves and restores geometry
+ //
+ int exec() override;
+
/**
* @return true if creation of a new profile failed
* @todo the notion of a fail state makes little sense in the current dialog
diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp
index e96b69d2..aece61da 100644
--- a/src/serverinfo.cpp
+++ b/src/serverinfo.cpp
@@ -1 +1,178 @@
#include "serverinfo.h"
+#include "log.h"
+
+using namespace MOBase;
+
+const std::size_t MaxDownloadCount = 5;
+
+
+ServerInfo::ServerInfo()
+ : ServerInfo({}, false, {}, 0, {})
+{
+}
+
+ServerInfo::ServerInfo(
+ QString name, bool premium, QDate last, int preferred,
+ SpeedList lastDownloads) :
+ m_name(std::move(name)), m_premium(premium), m_lastSeen(std::move(last)),
+ m_preferred(preferred), m_lastDownloads(std::move(lastDownloads))
+{
+ if (m_lastDownloads.size() > MaxDownloadCount) {
+ m_lastDownloads.resize(MaxDownloadCount);
+ }
+}
+
+const QString& ServerInfo::name() const
+{
+ return m_name;
+}
+
+bool ServerInfo::isPremium() const
+{
+ return m_premium;
+}
+
+void ServerInfo::setPremium(bool b)
+{
+ m_premium = b;
+}
+
+const QDate& ServerInfo::lastSeen() const
+{
+ return m_lastSeen;
+}
+
+void ServerInfo::updateLastSeen()
+{
+ m_lastSeen = QDate::currentDate();
+}
+
+int ServerInfo::preferred() const
+{
+ return m_preferred;
+}
+
+void ServerInfo::setPreferred(int i)
+{
+ m_preferred = i;
+}
+
+const ServerInfo::SpeedList& ServerInfo::lastDownloads() const
+{
+ return m_lastDownloads;
+}
+
+int ServerInfo::averageSpeed() const
+{
+ int count = 0;
+ int total = 0;
+
+ for (const auto& s : m_lastDownloads) {
+ if (s > 0) {
+ ++count;
+ total += s;
+ }
+ }
+
+ if (count > 0) {
+ return static_cast<double>(total) / count;
+ }
+
+ return 0;
+}
+
+void ServerInfo::addDownload(int bytesPerSecond)
+{
+ if (bytesPerSecond <= 0) {
+ log::error(
+ "trying to add download with {} B/s to server '{}'; ignoring",
+ bytesPerSecond, m_name);
+
+ return;
+ }
+
+ if (m_lastDownloads.size() == MaxDownloadCount) {
+ std::rotate(
+ m_lastDownloads.begin(),
+ m_lastDownloads.begin() + 1,
+ m_lastDownloads.end());
+
+ m_lastDownloads.back() = bytesPerSecond;
+ } else {
+ m_lastDownloads.push_back(bytesPerSecond);
+ }
+
+ log::debug("added download at {} B/s to server '{}'", bytesPerSecond, m_name);
+}
+
+
+void ServerList::add(ServerInfo s)
+{
+ m_servers.push_back(std::move(s));
+
+ std::sort(m_servers.begin(), m_servers.end(), [](auto&& a, auto&& b){
+ return (a.preferred() < b.preferred());
+ });
+}
+
+ServerList::iterator ServerList::begin()
+{
+ return m_servers.begin();
+}
+
+ServerList::const_iterator ServerList::begin() const
+{
+ return m_servers.begin();
+}
+
+ServerList::iterator ServerList::end()
+{
+ return m_servers.end();
+}
+
+ServerList::const_iterator ServerList::end() const
+{
+ return m_servers.end();
+}
+
+std::size_t ServerList::size() const
+{
+ return m_servers.size();
+}
+
+bool ServerList::empty() const
+{
+ return m_servers.empty();
+}
+
+ServerList::container ServerList::getPreferred() const
+{
+ container v;
+
+ for (const auto& server : m_servers) {
+ if (server.preferred() > 0) {
+ v.push_back(server);
+ }
+ }
+
+ return v;
+}
+
+void ServerList::cleanup()
+{
+ QDate now = QDate::currentDate();
+
+ for (auto itor=m_servers.begin(); itor!=m_servers.end(); ) {
+ const QDate lastSeen = itor->lastSeen();
+
+ if (lastSeen.daysTo(now) > 30) {
+ log::debug(
+ "removing server {} since it hasn't been available for downloads "
+ "in over a month", itor->name());
+
+ itor = m_servers.erase(itor);
+ } else {
+ ++itor;
+ }
+ }
+}
diff --git a/src/serverinfo.h b/src/serverinfo.h
index 8e5e935a..af8f77c8 100644
--- a/src/serverinfo.h
+++ b/src/serverinfo.h
@@ -5,14 +5,66 @@
#include <QDate>
#include <QMetaType>
-struct ServerInfo
+class ServerInfo
{
- QString name;
- bool premium;
- QDate lastSeen;
- bool preferred;
+public:
+ using SpeedList = std::vector<int>;
+
+ ServerInfo();
+ ServerInfo(
+ QString name, bool premium, QDate lastSeen, int preferred,
+ SpeedList lastDownloads);
+
+ const QString& name() const;
+
+ bool isPremium() const;
+ void setPremium(bool b);
+
+ const QDate& lastSeen() const;
+ void updateLastSeen();
+
+ int preferred() const;
+ void setPreferred(int i);
+
+ const SpeedList& lastDownloads() const;
+ int averageSpeed() const;
+ void addDownload(int bytesPerSecond);
+
+private:
+ QString m_name;
+ bool m_premium;
+ QDate m_lastSeen;
+ int m_preferred;
+ SpeedList m_lastDownloads;
};
Q_DECLARE_METATYPE(ServerInfo)
+
+class ServerList
+{
+public:
+ using container = std::vector<ServerInfo>;
+ using iterator = container::iterator;
+ using const_iterator = container::const_iterator;
+
+ void add(ServerInfo s);
+
+ iterator begin();
+ const_iterator begin() const;
+ iterator end();
+ const_iterator end() const;
+ std::size_t size() const;
+ bool empty() const;
+
+ container getPreferred() const;
+
+ // removes servers that haven't been seen in a while
+ //
+ void cleanup();
+
+private:
+ container m_servers;
+};
+
#endif // SERVERINFO_H
diff --git a/src/settings.cpp b/src/settings.cpp
index 77db6918..1f066100 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -18,18 +18,53 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "settings.h"
+#include "settingsutilities.h"
#include "serverinfo.h"
+#include "executableslist.h"
#include "appconfig.h"
+#include "expanderwidget.h"
#include <utility.h>
#include <iplugingame.h>
-#include <usvfsparameters.h>
using namespace MOBase;
+
+EndorsementState endorsementStateFromString(const QString& s)
+{
+ if (s == "Endorsed") {
+ return EndorsementState::Accepted;
+ } else if (s == "Abstained") {
+ return EndorsementState::Refused;
+ } else {
+ return EndorsementState::NoDecision;
+ }
+}
+
+QString toString(EndorsementState s)
+{
+ switch (s)
+ {
+ case EndorsementState::Accepted:
+ return "Endorsed";
+
+ case EndorsementState::Refused:
+ return "Abstained";
+
+ case EndorsementState::NoDecision: // fall-through
+ default:
+ return {};
+ }
+}
+
+
Settings *Settings::s_Instance = nullptr;
-Settings::Settings(const QSettings &settingsSource)
- : m_Settings(settingsSource.fileName(), settingsSource.format())
+Settings::Settings(const QString& path) :
+ 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_Interface(m_Settings), m_Diagnostics(m_Settings)
{
if (s_Instance != nullptr) {
throw std::runtime_error("second instance of \"Settings\" created");
@@ -40,6 +75,7 @@ Settings::Settings(const QSettings &settingsSource)
Settings::~Settings()
{
+ MOBase::QuestionBoxMemory::setCallbacks({}, {}, {});
s_Instance = nullptr;
}
@@ -51,449 +87,1071 @@ Settings &Settings::instance()
return *s_Instance;
}
-void Settings::clearPlugins()
+void Settings::processUpdates(
+ const QVersionNumber& currentVersion, const QVersionNumber& lastVersion)
{
- m_Plugins.clear();
- m_PluginSettings.clear();
+ if (firstStart()) {
+ return;
+ }
- m_PluginBlacklist.clear();
- int count = m_Settings.beginReadArray("pluginBlacklist");
- for (int i = 0; i < count; ++i) {
- m_Settings.setArrayIndex(i);
- m_PluginBlacklist.insert(m_Settings.value("name").toString());
+ if (currentVersion == lastVersion) {
+ return;
}
- m_Settings.endArray();
+
+ log::info(
+ "updating from {} to {}",
+ lastVersion.toString(), currentVersion.toString());
+
+ auto version = [&](const QVersionNumber& v, auto&& f) {
+ if (lastVersion < v) {
+ log::debug("processing updates for {}", v.toString());
+ f();
+ }
+ };
+
+ version({2, 2, 0}, [&]{
+ remove(m_Settings, "Settings", "steam_password");
+ remove(m_Settings, "Settings", "nexus_username");
+ remove(m_Settings, "Settings", "nexus_password");
+ remove(m_Settings, "Settings", "nexus_login");
+ remove(m_Settings, "Settings", "nexus_api_key");
+ remove(m_Settings, "Settings", "ask_for_nexuspw");
+ remove(m_Settings, "Settings", "nmm_version");
+
+ removeSection(m_Settings, "Servers");
+ });
+
+ version({2, 2, 1}, [&]{
+ remove(m_Settings, "General", "mod_info_tabs");
+ remove(m_Settings, "General", "mod_info_conflict_expanders");
+ remove(m_Settings, "General", "mod_info_conflicts");
+ remove(m_Settings, "General", "mod_info_advanced_conflicts");
+ remove(m_Settings, "General", "mod_info_conflicts_overwrite");
+ remove(m_Settings, "General", "mod_info_conflicts_noconflict");
+ remove(m_Settings, "General", "mod_info_conflicts_overwritten");
+ });
+
+ version({2, 2, 2}, [&]{
+ // log splitter is gone, it's a dock now
+ remove(m_Settings, "General", "log_split");
+
+ // moved to widgets
+ remove(m_Settings, "General", "mod_info_conflicts_tab");
+ remove(m_Settings, "General", "mod_info_conflicts_general_expanders");
+ remove(m_Settings, "General", "mod_info_conflicts_general_overwrite");
+ remove(m_Settings, "General", "mod_info_conflicts_general_noconflict");
+ remove(m_Settings, "General", "mod_info_conflicts_general_overwritten");
+ remove(m_Settings, "General", "mod_info_conflicts_advanced_list");
+ remove(m_Settings, "General", "mod_info_conflicts_advanced_options");
+ remove(m_Settings, "General", "mod_info_tab_order");
+ remove(m_Settings, "General", "mod_info_dialog_images_show_dds");
+
+ // moved to geometry
+ remove(m_Settings, "General", "window_geometry");
+ remove(m_Settings, "General", "window_state");
+ remove(m_Settings, "General", "toolbar_size");
+ remove(m_Settings, "General", "toolbar_button_style");
+ remove(m_Settings, "General", "menubar_visible");
+ remove(m_Settings, "General", "statusbar_visible");
+ remove(m_Settings, "General", "window_split");
+ remove(m_Settings, "General", "window_monitor");
+ remove(m_Settings, "General", "browser_geometry");
+ remove(m_Settings, "General", "filters_visible");
+
+ // this was supposed to have been removed above when updating from 2.2.0,
+ // but it wasn't in Settings, it was in General
+ remove(m_Settings, "General", "ask_for_nexuspw");
+
+ m_Network.updateFromOldMap();
+ });
+
+ //save version in all case
+ set(m_Settings, "General", "version", currentVersion.toString());
+
+ log::debug("updating done");
}
-bool Settings::pluginBlacklisted(const QString &fileName) const
+QString Settings::filename() const
{
- return m_PluginBlacklist.contains(fileName);
+ return m_Settings.fileName();
}
-void Settings::registerAsNXMHandler(bool force)
+bool Settings::usePrereleases() const
{
- const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe";
- const auto executable = QCoreApplication::applicationFilePath();
+ return get<bool>(m_Settings, "Settings", "use_prereleases", false);
+}
- QString mode = force ? "forcereg" : "reg";
- QString parameters = mode + " " + m_GamePlugin->gameShortName();
- for (const QString& altGame : m_GamePlugin->validShortNames()) {
- parameters += "," + altGame;
- }
- parameters += " \"" + executable + "\"";
+void Settings::setUsePrereleases(bool b)
+{
+ set(m_Settings, "Settings", "use_prereleases", b);
+}
- if (!shell::Execute(nxmPath, parameters)) {
- QMessageBox::critical(
- nullptr, tr("Failed"), tr("Failed to start the helper application"));
+std::optional<QVersionNumber> Settings::version() const
+{
+ if (auto v=getOptional<QString>(m_Settings, "General", "version")) {
+ return QVersionNumber::fromString(*v).normalized();
}
+
+ return {};
}
-bool Settings::colorSeparatorScrollbar() const
+bool Settings::firstStart() const
{
- return m_Settings.value("Settings/colorSeparatorScrollbars", true).toBool();
+ return get<bool>(m_Settings, "General", "first_start", true);
}
-void Settings::managedGameChanged(IPluginGame const *gamePlugin)
+void Settings::setFirstStart(bool b)
{
- m_GamePlugin = gamePlugin;
+ set(m_Settings, "General", "first_start", b);
+}
+
+QString Settings::executablesBlacklist() const
+{
+ static const QString def = (QStringList()
+ << "Chrome.exe"
+ << "Firefox.exe"
+ << "TSVNCache.exe"
+ << "TGitCache.exe"
+ << "Steam.exe"
+ << "GameOverlayUI.exe"
+ << "Discord.exe"
+ << "GalaxyClient.exe"
+ << "Spotify.exe"
+ ).join(";");
+
+ return get<QString>(m_Settings, "Settings", "executable_blacklist", def);
}
-void Settings::registerPlugin(IPlugin *plugin)
+void Settings::setExecutablesBlacklist(const QString& s)
{
- m_Plugins.push_back(plugin);
- m_PluginSettings.insert(plugin->name(), QVariantMap());
- m_PluginDescriptions.insert(plugin->name(), QVariantMap());
- for (const PluginSetting &setting : plugin->settings()) {
- QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue);
- if (!temp.convert(setting.defaultValue.type())) {
- log::warn(
- "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default",
- temp.toString(), setting.key, plugin->name());
- temp = setting.defaultValue;
+ set(m_Settings, "Settings", "executable_blacklist", s);
+}
+
+void Settings::setMotdHash(uint hash)
+{
+ set(m_Settings, "General", "motd_hash", hash);
+}
+
+unsigned int Settings::motdHash() const
+{
+ return get<unsigned int>(m_Settings, "General", "motd_hash", 0);
+}
+
+bool Settings::archiveParsing() const
+{
+ return get<bool>(m_Settings, "Settings", "archive_parsing_experimental", false);
+}
+
+void Settings::setArchiveParsing(bool b)
+{
+ set(m_Settings, "Settings", "archive_parsing_experimental", b);
+}
+
+std::vector<std::map<QString, QVariant>> Settings::executables() const
+{
+ ScopedReadArray sra(m_Settings, "customExecutables");
+ std::vector<std::map<QString, QVariant>> v;
+
+ sra.for_each([&]{
+ std::map<QString, QVariant> map;
+
+ for (auto&& key : sra.keys()) {
+ map[key] = sra.get<QVariant>(key);
}
- m_PluginSettings[plugin->name()][setting.key] = temp;
- m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)").arg(setting.description).arg(setting.defaultValue.toString());
- }
+
+ v.push_back(map);
+ });
+
+ return v;
}
-bool Settings::obfuscate(const QString key, const QString data)
+void Settings::setExecutables(const std::vector<std::map<QString, QVariant>>& v)
{
- QString finalKey("ModOrganizer2_" + key);
- wchar_t* keyData = new wchar_t[finalKey.size()+1];
- finalKey.toWCharArray(keyData);
- keyData[finalKey.size()] = L'\0';
- bool result = false;
- if (data.isEmpty()) {
- result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0);
- if (!result)
- if (GetLastError() == ERROR_NOT_FOUND)
- result = true;
- } else {
- wchar_t* charData = new wchar_t[data.size()];
- data.toWCharArray(charData);
+ const auto current = executables();
+
+ if (current == v) {
+ // no change
+ return;
+ }
+
+ if (current.size() > v.size()) {
+ // Qt can't remove array elements, the section must be cleared
+ removeSection(m_Settings, "customExecutables");
+ }
- CREDENTIALW cred = {};
- cred.Flags = 0;
- cred.Type = CRED_TYPE_GENERIC;
- cred.TargetName = keyData;
- cred.CredentialBlob = (LPBYTE)charData;
- cred.CredentialBlobSize = sizeof(wchar_t) * data.size();
- cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
+ ScopedWriteArray swa(m_Settings, "customExecutables", v.size());
- result = CredWriteW(&cred, 0);
- delete[] charData;
+ for (const auto& map : v) {
+ swa.next();
+
+ for (auto&& p : map) {
+ swa.set(p.first, p.second);
+ }
}
- delete[] keyData;
- return result;
}
-QString Settings::deObfuscate(const QString key)
+bool Settings::keepBackupOnInstall() const
{
- QString result;
- QString finalKey("ModOrganizer2_" + key);
- wchar_t* keyData = new wchar_t[finalKey.size()+1];
- finalKey.toWCharArray(keyData);
- keyData[finalKey.size()] = L'\0';
- PCREDENTIALW creds;
- if (CredReadW(keyData, 1, 0, &creds)) {
- wchar_t *charData = (wchar_t *)creds->CredentialBlob;
- result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t));
- CredFree(creds);
- } else {
- const auto e = GetLastError();
- if (e != ERROR_NOT_FOUND) {
- log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e));
+ return get<bool>(m_Settings, "General", "backup_install", false);
+}
+
+void Settings::setKeepBackupOnInstall(bool b)
+{
+ set(m_Settings, "General", "backup_install", b);
+}
+
+GameSettings& Settings::game()
+{
+ return m_Game;
+}
+
+const GameSettings& Settings::game() const
+{
+ return m_Game;
+}
+
+GeometrySettings& Settings::geometry()
+{
+ return m_Geometry;
+}
+
+const GeometrySettings& Settings::geometry() const
+{
+ return m_Geometry;
+}
+
+WidgetSettings& Settings::widgets()
+{
+ return m_Widgets;
+}
+
+const WidgetSettings& Settings::widgets() const
+{
+ return m_Widgets;
+}
+
+ColorSettings& Settings::colors()
+{
+ return m_Colors;
+}
+
+const ColorSettings& Settings::colors() const
+{
+ return m_Colors;
+}
+
+PluginSettings& Settings::plugins()
+{
+ return m_Plugins;
+}
+
+const PluginSettings& Settings::plugins() const
+{
+ return m_Plugins;
+}
+
+PathSettings& Settings::paths()
+{
+ return m_Paths;
+}
+
+const PathSettings& Settings::paths() const
+{
+ return m_Paths;
+}
+
+NetworkSettings& Settings::network()
+{
+ return m_Network;
+}
+
+const NetworkSettings& Settings::network() const
+{
+ return m_Network;
+}
+
+NexusSettings& Settings::nexus()
+{
+ return m_Nexus;
+}
+
+const NexusSettings& Settings::nexus() const
+{
+ return m_Nexus;
+}
+
+SteamSettings& Settings::steam()
+{
+ return m_Steam;
+}
+
+const SteamSettings& Settings::steam() const
+{
+ return m_Steam;
+}
+
+InterfaceSettings& Settings::interface()
+{
+ return m_Interface;
+}
+
+const InterfaceSettings& Settings::interface() const
+{
+ return m_Interface;
+}
+
+DiagnosticsSettings& Settings::diagnostics()
+{
+ return m_Diagnostics;
+}
+
+const DiagnosticsSettings& Settings::diagnostics() const
+{
+ return m_Diagnostics;
+}
+
+QSettings::Status Settings::sync() const
+{
+ m_Settings.sync();
+ return m_Settings.status();
+}
+
+void Settings::dump() const
+{
+ static const QStringList ignore({
+ "username", "password", "nexus_api_key"
+ });
+
+ log::debug("settings:");
+
+ {
+ ScopedGroup sg(m_Settings, "Settings");
+
+ for (auto k : m_Settings.allKeys()) {
+ if (ignore.contains(k, Qt::CaseInsensitive)) {
+ continue;
+ }
+
+ log::debug(" . {}={}", k, m_Settings.value(k).toString());
}
}
- delete[] keyData;
- return result;
+
+ m_Network.dump();
}
-QColor Settings::getIdealTextColor(const QColor& rBackgroundColor)
+void Settings::managedGameChanged(IPluginGame const *gamePlugin)
{
- if (rBackgroundColor.alpha() == 0)
- return QColor(Qt::black);
+ m_Game.setPlugin(gamePlugin);
+}
- const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha();
- int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114);
- return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white);
+
+GameSettings::GameSettings(QSettings& settings)
+ : m_Settings(settings), m_GamePlugin(nullptr)
+{
}
+const MOBase::IPluginGame* GameSettings::plugin()
+{
+ return m_GamePlugin;
+}
-bool Settings::hideUncheckedPlugins() const
+void GameSettings::setPlugin(const MOBase::IPluginGame* gamePlugin)
{
- return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool();
+ m_GamePlugin = gamePlugin;
}
-bool Settings::forceEnableCoreFiles() const
+bool GameSettings::forceEnableCoreFiles() const
{
- return m_Settings.value("Settings/force_enable_core_files", true).toBool();
+ return get<bool>(m_Settings, "Settings", "force_enable_core_files", true);
}
-bool Settings::lockGUI() const
+void GameSettings::setForceEnableCoreFiles(bool b)
{
- return m_Settings.value("Settings/lock_gui", true).toBool();
+ set(m_Settings, "Settings", "force_enable_core_files", b);
}
-bool Settings::automaticLoginEnabled() const
+std::optional<QString> GameSettings::directory() const
{
- return m_Settings.value("Settings/nexus_login", false).toBool();
+ if (auto v=getOptional<QByteArray>(m_Settings, "General", "gamePath")) {
+ return QString::fromUtf8(*v);
+ }
+
+ return {};
}
-QString Settings::getSteamAppID() const
+void GameSettings::setDirectory(const QString& path)
{
- return m_Settings.value("Settings/app_id", m_GamePlugin->steamAPPId()).toString();
+ set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8());
}
-bool Settings::usePrereleases() const
+std::optional<QString> GameSettings::name() const
{
- return m_Settings.value("Settings/use_prereleases", false).toBool();
+ return getOptional<QString>(m_Settings, "General", "gameName");
}
-void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond)
+void GameSettings::setName(const QString& name)
{
- m_Settings.beginGroup("Servers");
+ set(m_Settings, "General", "gameName", name);
+}
- for (const QString &serverKey : m_Settings.childKeys()) {
- QVariantMap data = m_Settings.value(serverKey).toMap();
- if (serverKey == serverName) {
- data["downloadCount"] = data["downloadCount"].toInt() + 1;
- data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast<double>(bytesPerSecond);
- m_Settings.setValue(serverKey, data);
- }
+std::optional<QString> GameSettings::edition() const
+{
+ return getOptional<QString>(m_Settings, "General", "game_edition");
+}
+
+void GameSettings::setEdition(const QString& name)
+{
+ set(m_Settings, "General", "game_edition", name);
+}
+
+std::optional<QString> GameSettings::selectedProfileName() const
+{
+ if (auto v=getOptional<QByteArray>(m_Settings, "General", "selected_profile")) {
+ return QString::fromUtf8(*v);
}
- m_Settings.endGroup();
- m_Settings.sync();
+ return {};
}
-std::map<QString, int> Settings::getPreferredServers()
+void GameSettings::setSelectedProfileName(const QString& name)
{
- std::map<QString, int> result;
- m_Settings.beginGroup("Servers");
+ set(m_Settings, "General", "selected_profile", name.toUtf8());
+}
+
+LoadMechanism::EMechanism GameSettings::loadMechanismType() const
+{
+ const auto def = LoadMechanism::LOAD_MODORGANIZER;
- for (const QString &serverKey : m_Settings.childKeys()) {
- QVariantMap data = m_Settings.value(serverKey).toMap();
- int preference = data["preferred"].toInt();
- if (preference > 0) {
- result[serverKey] = preference;
+ const auto i = get<LoadMechanism::EMechanism>(m_Settings,
+ "Settings", "load_mechanism", def);
+
+ switch (i)
+ {
+ // ok
+ case LoadMechanism::LOAD_MODORGANIZER: // fall-through
+ {
+ break;
+ }
+
+ default:
+ {
+ log::error(
+ "invalid load mechanism {}, reverting to {}",
+ static_cast<int>(i), toString(def));
+
+ set(m_Settings, "Settings", "load_mechanism", def);
+
+ return def;
}
}
- m_Settings.endGroup();
- return result;
+ return i;
}
-QString Settings::getConfigurablePath(const QString &key,
- const QString &def,
- bool resolve) const
+void GameSettings::setLoadMechanism(LoadMechanism::EMechanism m)
{
- QString result = QDir::fromNativeSeparators(
- m_Settings.value(QString("settings/") + key, QString("%BASE_DIR%/") + def)
- .toString());
- if (resolve) {
- result.replace("%BASE_DIR%", getBaseDirectory());
+ set(m_Settings, "Settings", "load_mechanism", m);
+}
+
+const LoadMechanism& GameSettings::loadMechanism() const
+{
+ return m_LoadMechanism;
+}
+
+LoadMechanism& GameSettings::loadMechanism()
+{
+ return m_LoadMechanism;
+}
+
+bool GameSettings::hideUncheckedPlugins() const
+{
+ return get<bool>(m_Settings, "Settings", "hide_unchecked_plugins", false);
+}
+
+void GameSettings::setHideUncheckedPlugins(bool b)
+{
+ set(m_Settings, "Settings", "hide_unchecked_plugins", b);
+}
+
+
+GeometrySettings::GeometrySettings(QSettings& s)
+ : m_Settings(s), m_Reset(false)
+{
+}
+
+void GeometrySettings::requestReset()
+{
+ m_Reset = true;
+}
+
+void GeometrySettings::resetIfNeeded()
+{
+ if (!m_Reset) {
+ return;
}
- return result;
+
+ removeSection(m_Settings, "Geometry");
+}
+
+void GeometrySettings::saveGeometry(const QWidget* w)
+{
+ set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry());
}
-QString Settings::getBaseDirectory() const
+bool GeometrySettings::restoreGeometry(QWidget* w) const
{
- return QDir::fromNativeSeparators(m_Settings.value(
- "settings/base_directory", qApp->property("dataPath").toString()).toString());
+ if (auto v=getOptional<QByteArray>(m_Settings, "Geometry", geoSettingName(w))) {
+ w->restoreGeometry(*v);
+ return true;
+ }
+
+ return false;
}
-QString Settings::getDownloadDirectory(bool resolve) const
+void GeometrySettings::saveState(const QMainWindow* w)
{
- return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()), resolve);
+ set(m_Settings, "Geometry", stateSettingName(w), w->saveState());
}
-QString Settings::getCacheDirectory(bool resolve) const
+bool GeometrySettings::restoreState(QMainWindow* w) const
{
- return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()), resolve);
+ if (auto v=getOptional<QByteArray>(m_Settings, "Geometry", stateSettingName(w))) {
+ w->restoreState(*v);
+ return true;
+ }
+
+ return false;
}
-QString Settings::getModDirectory(bool resolve) const
+void GeometrySettings::saveState(const QHeaderView* w)
{
- return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve);
+ set(m_Settings, "Geometry", stateSettingName(w), w->saveState());
}
-QString Settings::getManagedGameDirectory() const
+bool GeometrySettings::restoreState(QHeaderView* w) const
{
- return m_Settings.value("gamePath", "").toString();
+ if (auto v=getOptional<QByteArray>(m_Settings, "Geometry", stateSettingName(w))) {
+ w->restoreState(*v);
+ return true;
+ }
+
+ return false;
}
-QString Settings::getProfileDirectory(bool resolve) const
+void GeometrySettings::saveState(const QSplitter* w)
{
- return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve);
+ set(m_Settings, "Geometry", stateSettingName(w), w->saveState());
}
-QString Settings::getOverwriteDirectory(bool resolve) const
+bool GeometrySettings::restoreState(QSplitter* w) const
{
- return getConfigurablePath("overwrite_directory",
- ToQString(AppConfig::overwritePath()), resolve);
+ if (auto v=getOptional<QByteArray>(m_Settings, "Geometry", stateSettingName(w))) {
+ w->restoreState(*v);
+ return true;
+ }
+
+ return false;
}
-bool Settings::getNexusApiKey(QString &apiKey) const
+void GeometrySettings::saveState(const ExpanderWidget* expander)
{
- QString tempKey = deObfuscate("APIKEY");
- if (tempKey.isEmpty())
- return false;
- apiKey = tempKey;
- return true;
+ set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState());
}
-bool Settings::setNexusApiKey(const QString& apiKey)
+bool GeometrySettings::restoreState(ExpanderWidget* expander) const
{
- if (!obfuscate("APIKEY", apiKey)) {
- const auto e = GetLastError();
- log::error("Storing API key failed: {}", formatSystemMessage(e));
- return false;
+ if (auto v=getOptional<QByteArray>(m_Settings, "Geometry", stateSettingName(expander))) {
+ expander->restoreState(*v);
+ return true;
}
- return true;
+ return false;
}
-bool Settings::clearNexusApiKey()
+void GeometrySettings::saveVisibility(const QWidget* w)
{
- return setNexusApiKey("");
+ set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible());
}
-bool Settings::hasNexusApiKey() const
+bool GeometrySettings::restoreVisibility(QWidget* w, std::optional<bool> def) const
{
- return !deObfuscate("APIKEY").isEmpty();
+ if (auto v=getOptional<bool>(m_Settings, "Geometry", visibilitySettingName(w), def)) {
+ w->setVisible(*v);
+ return true;
+ }
+
+ return false;
}
-bool Settings::getSteamLogin(QString &username, QString &password) const
+void GeometrySettings::restoreToolbars(QMainWindow* w) const
{
- username = m_Settings.value("Settings/steam_username", "").toString();
- password = deObfuscate("steam_password");
+ // all toolbars have the same size and button style settings
+ const auto size = getOptional<QSize>(m_Settings, "Geometry", "toolbar_size");
+ const auto style = getOptional<int>(m_Settings, "Geometry", "toolbar_button_style");
- return !username.isEmpty() && !password.isEmpty();
+ for (auto* tb : w->findChildren<QToolBar*>()) {
+ if (size) {
+ tb->setIconSize(*size);
+ }
+
+ if (style) {
+ tb->setToolButtonStyle(static_cast<Qt::ToolButtonStyle>(*style));
+ }
+
+ restoreVisibility(tb);
+ }
}
-bool Settings::compactDownloads() const
+
+void GeometrySettings::saveToolbars(const QMainWindow* w)
{
- return m_Settings.value("Settings/compact_downloads", false).toBool();
+ const auto tbs = w->findChildren<QToolBar*>();
+
+ // save visibility for all
+ for (auto* tb : tbs) {
+ saveVisibility(tb);
+ }
+
+ // all toolbars have the same size and button style settings, just save the
+ // first one
+ if (!tbs.isEmpty()) {
+ const auto* tb = tbs[0];
+
+ set(m_Settings, "Geometry", "toolbar_size", tb->iconSize());
+ set(m_Settings, "Geometry", "toolbar_button_style", static_cast<int>(tb->toolButtonStyle()));
+ }
}
-bool Settings::metaDownloads() const
+QStringList GeometrySettings::modInfoTabOrder() const
{
- return m_Settings.value("Settings/meta_downloads", false).toBool();
+ QStringList v;
+
+ if (m_Settings.contains("mod_info_tabs")) {
+ // old byte array from 2.2.0
+ QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray());
+
+ int count = 0;
+ stream >> count;
+
+ for (int i=0; i<count; ++i) {
+ QString s;
+ stream >> s;
+ v.push_back(s);
+ }
+ } else {
+ // string list since 2.2.1
+ QString string = get<QString>(m_Settings, "Widgets", "ModInfoTabOrder", "");
+ QTextStream stream(&string);
+
+ while (!stream.atEnd()) {
+ QString s;
+ stream >> s;
+ v.push_back(s);
+ }
+ }
+
+ return v;
}
-bool Settings::offlineMode() const
+void GeometrySettings::setModInfoTabOrder(const QString& names)
{
- return m_Settings.value("Settings/offline_mode", false).toBool();
+ set(m_Settings, "Widgets", "ModInfoTabOrder", names);
}
-log::Levels Settings::logLevel() const
+void GeometrySettings::centerOnMainWindowMonitor(QWidget* w)
{
- return static_cast<log::Levels>(m_Settings.value("Settings/log_level").toInt());
+ const auto monitor = getOptional<int>(
+ m_Settings, "Geometry", "MainWindow_monitor");
+
+ QPoint center;
+
+ if (monitor && QGuiApplication::screens().size() > *monitor) {
+ center = QGuiApplication::screens().at(*monitor)->geometry().center();
+ } else {
+ center = QGuiApplication::primaryScreen()->geometry().center();
+ }
+
+ w->move(center - w->rect().center());
}
-void Settings::setLogLevel(log::Levels level)
+void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w)
{
- m_Settings.setValue("Settings/log_level", static_cast<int>(level));
+ if (auto* handle=w->windowHandle()) {
+ if (auto* screen = handle->screen()) {
+ const int screenId = QGuiApplication::screens().indexOf(screen);
+ set(m_Settings, "Geometry", "MainWindow_monitor", screenId);
+ }
+ }
}
-int Settings::crashDumpsType() const
+Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d)
{
- return m_Settings.value("Settings/crash_dumps_type", static_cast<int>(CrashDumpsType::Mini)).toInt();
+ // docks in these areas are horizontal
+ const auto horizontalAreas =
+ Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea;
+
+ if (mw->dockWidgetArea(const_cast<QDockWidget*>(d)) & horizontalAreas) {
+ return Qt::Horizontal;
+ } else {
+ return Qt::Vertical;
+ }
}
-int Settings::crashDumpsMax() const
+void GeometrySettings::saveDocks(const QMainWindow* mw)
{
- return m_Settings.value("Settings/crash_dumps_max", 5).toInt();
+ // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock
+ // sizes are not restored when the main window is maximized; it is used in
+ // MainWindow::readSettings() and MainWindow::storeSettings()
+ //
+ // there's also https://stackoverflow.com/questions/44005852, which has what
+ // seems to be a popular fix, but it breaks the restored size of the window
+ // by setting it to the desktop's resolution, so that doesn't work
+ //
+ // the only fix I could find is to remember the sizes of the docks and manually
+ // setting them back; saving is straightforward, but restoring is messy
+ //
+ // this also depends on the window being visible before the timer in restore()
+ // is fired and the timer must be processed by application.exec(); therefore,
+ // the splash screen _must_ be closed before readSettings() is called, because
+ // it has its own event loop, which seems to interfere with this
+ //
+ // all of this should become unnecessary when QTBUG-46620 is fixed
+ //
+
+ // saves the size of each dock
+ for (const auto* dock : mw->findChildren<QDockWidget*>()) {
+ int size = 0;
+
+ // save the width for horizontal docks, or the height for vertical
+ if (dockOrientation(mw, dock) == Qt::Horizontal) {
+ size = dock->size().width();
+ } else {
+ size = dock->size().height();
+ }
+
+ set(m_Settings, "Geometry", dockSettingName(dock), size);
+ }
}
-QColor Settings::modlistOverwrittenLooseColor() const
+void GeometrySettings::restoreDocks(QMainWindow* mw) const
{
- return m_Settings.value("Settings/overwrittenLooseFilesColor", QColor(0, 255, 0, 64)).value<QColor>();
+ struct DockInfo
+ {
+ QDockWidget* d;
+ int size = 0;
+ Qt::Orientation ori;
+ };
+
+ std::vector<DockInfo> dockInfos;
+
+ // for each dock
+ for (auto* dock : mw->findChildren<QDockWidget*>()) {
+ if (auto size=getOptional<int>(m_Settings, "Geometry", dockSettingName(dock))) {
+ // remember this dock, its size and orientation
+ dockInfos.push_back({dock, *size, dockOrientation(mw, dock)});
+ }
+ }
+
+ // the main window must have had time to process the settings from
+ // readSettings() or it seems to override whatever is set here
+ //
+ // some people said a single processEvents() call is enough, but it doesn't
+ // look like it
+ QTimer::singleShot(5, [=] {
+ for (const auto& info : dockInfos) {
+ mw->resizeDocks({info.d}, {info.size}, info.ori);
+ }
+ });
}
-QColor Settings::modlistOverwritingLooseColor() const
+
+WidgetSettings::WidgetSettings(QSettings& s)
+ : m_Settings(s)
{
- return m_Settings.value("Settings/overwritingLooseFilesColor", QColor(255, 0, 0, 64)).value<QColor>();
+ 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); });
}
-QColor Settings::modlistOverwrittenArchiveColor() const
+std::optional<int> WidgetSettings::index(const QComboBox* cb) const
{
- return m_Settings.value("Settings/overwrittenArchiveFilesColor", QColor(0, 255, 255, 64)).value<QColor>();
+ return getOptional<int>(m_Settings, "Widgets", indexSettingName(cb));
}
-QColor Settings::modlistOverwritingArchiveColor() const
+void WidgetSettings::saveIndex(const QComboBox* cb)
{
- return m_Settings.value("Settings/overwritingArchiveFilesColor", QColor(255, 0, 255, 64)).value<QColor>();
+ set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex());
}
-QColor Settings::modlistContainsPluginColor() const
+void WidgetSettings::restoreIndex(QComboBox* cb, std::optional<int> def) const
{
- return m_Settings.value("Settings/containsPluginColor", QColor(0, 0, 255, 64)).value<QColor>();
+ if (auto v=getOptional<int>(m_Settings, "Widgets", indexSettingName(cb), def)) {
+ cb->setCurrentIndex(*v);
+ }
}
-QColor Settings::pluginListContainedColor() const
+std::optional<int> WidgetSettings::index(const QTabWidget* w) const
{
- return m_Settings.value("Settings/containedColor", QColor(0, 0, 255, 64)).value<QColor>();
+ return getOptional<int>(m_Settings, "Widgets", indexSettingName(w));
}
-QString Settings::executablesBlacklist() const
+void WidgetSettings::saveIndex(const QTabWidget* w)
{
- return m_Settings.value("Settings/executable_blacklist", (
- QStringList()
- << "Chrome.exe"
- << "Firefox.exe"
- << "TSVNCache.exe"
- << "TGitCache.exe"
- << "Steam.exe"
- << "GameOverlayUI.exe"
- << "Discord.exe"
- << "GalaxyClient.exe"
- << "Spotify.exe"
- ).join(";")
- ).toString();
+ set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex());
}
-void Settings::setSteamLogin(QString username, QString password)
+void WidgetSettings::restoreIndex(QTabWidget* w, std::optional<int> def) const
{
- if (username == "") {
- m_Settings.remove("Settings/steam_username");
- password = "";
- } else {
- m_Settings.setValue("Settings/steam_username", username);
+ if (auto v=getOptional<int>(m_Settings, "Widgets", indexSettingName(w), def)) {
+ w->setCurrentIndex(*v);
}
- if (!obfuscate("steam_password", password)) {
- const auto e = GetLastError();
- log::error("Storing or deleting password failed: {}", formatSystemMessage(e));
+}
+
+std::optional<bool> WidgetSettings::checked(const QAbstractButton* w) const
+{
+ warnIfNotCheckable(w);
+ return getOptional<bool>(m_Settings, "Widgets", checkedSettingName(w));
+}
+
+void WidgetSettings::saveChecked(const QAbstractButton* w)
+{
+ warnIfNotCheckable(w);
+ set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked());
+}
+
+void WidgetSettings::restoreChecked(QAbstractButton* w, std::optional<bool> def) const
+{
+ warnIfNotCheckable(w);
+
+ if (auto v=getOptional<bool>(m_Settings, "Widgets", checkedSettingName(w), def)) {
+ w->setChecked(*v);
}
}
-LoadMechanism::EMechanism Settings::getLoadMechanism() const
+QuestionBoxMemory::Button WidgetSettings::questionButton(
+ const QString& windowName, const QString& filename) const
{
- const auto i = m_Settings.value("Settings/load_mechanism").toInt();
+ const QString sectionName("DialogChoices");
- switch (i)
- {
- case LoadMechanism::LOAD_MODORGANIZER:
- return LoadMechanism::LOAD_MODORGANIZER;
+ if (!filename.isEmpty()) {
+ const auto fileSetting = windowName + "/" + filename;
+ if (auto v=getOptional<int>(m_Settings, sectionName, filename)) {
+ return static_cast<QuestionBoxMemory::Button>(*v);
+ }
+ }
- default:
- qCritical().nospace().noquote()
- << "invalid load mechanism " << i << ", reverting to modorganizer";
+ if (auto v=getOptional<int>(m_Settings, sectionName, windowName)) {
+ return static_cast<QuestionBoxMemory::Button>(*v);
+ }
+
+ return QuestionBoxMemory::NoButton;
+}
- m_Settings.setValue("Settings/load_mechanism", LoadMechanism::LOAD_MODORGANIZER);
+void WidgetSettings::setQuestionWindowButton(
+ const QString& windowName, QuestionBoxMemory::Button button)
+{
+ const QString sectionName("DialogChoices");
- return LoadMechanism::LOAD_MODORGANIZER;
+ if (button == QuestionBoxMemory::NoButton) {
+ remove(m_Settings, sectionName, windowName);
+ } else {
+ set(m_Settings, sectionName, windowName, button);
}
}
+void WidgetSettings::setQuestionFileButton(
+ const QString& windowName, const QString& filename,
+ QuestionBoxMemory::Button button)
+{
+ const QString sectionName("DialogChoices");
+ const QString settingName(windowName + "/" + filename);
+
+ if (button == QuestionBoxMemory::NoButton) {
+ remove(m_Settings, sectionName, settingName);
+ } else {
+ set(m_Settings, sectionName, settingName, button);
+ }
+}
-void Settings::setupLoadMechanism()
+void WidgetSettings::resetQuestionButtons()
{
- m_LoadMechanism.activate(getLoadMechanism());
+ removeSection(m_Settings, "DialogChoices");
}
-bool Settings::useProxy() const
+ColorSettings::ColorSettings(QSettings& s)
+ : m_Settings(s)
{
- return m_Settings.value("Settings/use_proxy", false).toBool();
}
-bool Settings::endorsementIntegration() const
+QColor ColorSettings::modlistOverwrittenLoose() const
{
- return m_Settings.value("Settings/endorsement_integration", true).toBool();
+ return get<QColor>(
+ m_Settings, "Settings", "overwrittenLooseFilesColor",
+ QColor(0, 255, 0, 64));
}
-bool Settings::hideAPICounter() const
+void ColorSettings::setModlistOverwrittenLoose(const QColor& c)
{
- return m_Settings.value("Settings/hide_api_counter", false).toBool();
+ set(m_Settings, "Settings", "overwrittenLooseFilesColor", c);
}
-bool Settings::displayForeign() const
+QColor ColorSettings::modlistOverwritingLoose() const
{
- return m_Settings.value("Settings/display_foreign", true).toBool();
+ return get<QColor>(
+ m_Settings, "Settings", "overwritingLooseFilesColor",
+ QColor(255, 0, 0, 64));
}
-void Settings::setMotDHash(uint hash)
+void ColorSettings::setModlistOverwritingLoose(const QColor& c)
{
- m_Settings.setValue("motd_hash", hash);
+ set(m_Settings, "Settings", "overwritingLooseFilesColor", c);
}
-uint Settings::getMotDHash() const
+QColor ColorSettings::modlistOverwrittenArchive() const
{
- return m_Settings.value("motd_hash", 0).toUInt();
+ return get<QColor>(
+ m_Settings, "Settings", "overwrittenArchiveFilesColor",
+ QColor(0, 255, 255, 64));
}
-bool Settings::archiveParsing() const
+void ColorSettings::setModlistOverwrittenArchive(const QColor& c)
+{
+ set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c);
+}
+
+QColor ColorSettings::modlistOverwritingArchive() const
{
- return m_Settings.value("Settings/archive_parsing_experimental", false).toBool();
+ return get<QColor>(
+ m_Settings, "Settings", "overwritingArchiveFilesColor",
+ QColor(255, 0, 255, 64));
}
-QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const
+void ColorSettings::setModlistOverwritingArchive(const QColor& c)
+{
+ set(m_Settings, "Settings", "overwritingArchiveFilesColor", c);
+}
+
+QColor ColorSettings::modlistContainsPlugin() const
+{
+ return get<QColor>(
+ m_Settings, "Settings", "containsPluginColor",
+ QColor(0, 0, 255, 64));
+}
+
+void ColorSettings::setModlistContainsPlugin(const QColor& c)
+{
+ set(m_Settings, "Settings", "containsPluginColor", c);
+}
+
+QColor ColorSettings::pluginListContained() const
+{
+ return get<QColor>(
+ m_Settings, "Settings", "containedColor",
+ QColor(0, 0, 255, 64));
+}
+
+void ColorSettings::setPluginListContained(const QColor& c)
+{
+ set(m_Settings, "Settings", "containedColor", c);
+}
+
+std::optional<QColor> ColorSettings::previousSeparatorColor() const
+{
+ const auto c = getOptional<QColor>(m_Settings, "General", "previousSeparatorColor");
+ if (c && c->isValid()) {
+ return c;
+ }
+
+ return {};
+}
+
+void ColorSettings::setPreviousSeparatorColor(const QColor& c) const
+{
+ set(m_Settings, "General", "previousSeparatorColor", c);
+}
+
+void ColorSettings::removePreviousSeparatorColor()
+{
+ remove(m_Settings, "General", "previousSeparatorColor");
+}
+
+bool ColorSettings::colorSeparatorScrollbar() const
+{
+ return get<bool>(m_Settings, "Settings", "colorSeparatorScrollbars", true);
+}
+
+void ColorSettings::setColorSeparatorScrollbar(bool b)
+{
+ set(m_Settings, "Settings", "colorSeparatorScrollbars", b);
+}
+
+QColor ColorSettings::idealTextColor(const QColor& rBackgroundColor)
+{
+ if (rBackgroundColor.alpha() == 0)
+ return QColor(Qt::black);
+
+ const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha();
+ int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114);
+ return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white);
+}
+
+
+
+PluginSettings::PluginSettings(QSettings& settings)
+ : m_Settings(settings)
+{
+}
+
+void PluginSettings::clearPlugins()
+{
+ m_Plugins.clear();
+ m_PluginSettings.clear();
+ m_PluginBlacklist.clear();
+
+ m_PluginBlacklist = readBlacklist();
+}
+
+void PluginSettings::registerPlugin(IPlugin *plugin)
+{
+ m_Plugins.push_back(plugin);
+ m_PluginSettings.insert(plugin->name(), QVariantMap());
+ m_PluginDescriptions.insert(plugin->name(), QVariantMap());
+
+ for (const PluginSetting &setting : plugin->settings()) {
+ const QString settingName = plugin->name() + "/" + setting.key;
+
+ QVariant temp = get<QVariant>(
+ m_Settings, "Plugins", settingName, setting.defaultValue);
+
+ if (!temp.convert(setting.defaultValue.type())) {
+ log::warn(
+ "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default",
+ temp.toString(), setting.key, plugin->name());
+
+ temp = setting.defaultValue;
+ }
+
+ m_PluginSettings[plugin->name()][setting.key] = temp;
+
+ m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)")
+ .arg(setting.description)
+ .arg(setting.defaultValue.toString());
+ }
+}
+
+std::vector<MOBase::IPlugin*> PluginSettings::plugins() const
+{
+ return m_Plugins;
+}
+
+QVariant PluginSettings::setting(const QString &pluginName, const QString &key) const
{
auto iterPlugin = m_PluginSettings.find(pluginName);
if (iterPlugin == m_PluginSettings.end()) {
return QVariant();
}
+
auto iterSetting = iterPlugin->find(key);
if (iterSetting == iterPlugin->end()) {
return QVariant();
@@ -502,42 +1160,658 @@ QVariant Settings::pluginSetting(const QString &pluginName, const QString &key)
return *iterSetting;
}
-void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value)
+void PluginSettings::setSetting(const QString &pluginName, const QString &key, const QVariant &value)
{
auto iterPlugin = m_PluginSettings.find(pluginName);
+
if (iterPlugin == m_PluginSettings.end()) {
- throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName));
+ throw MyException(
+ QObject::tr("attempt to store setting for unknown plugin \"%1\"")
+ .arg(pluginName));
}
// store the new setting both in memory and in the ini
m_PluginSettings[pluginName][key] = value;
- m_Settings.setValue("Plugins/" + pluginName + "/" + key, value);
+ set(m_Settings, "Plugins", pluginName + "/" + key, value);
+}
+
+QVariantMap PluginSettings::settings(const QString &pluginName) const
+{
+ return m_PluginSettings[pluginName];
+}
+
+void PluginSettings::setSettings(const QString &pluginName, const QVariantMap& map)
+{
+ m_PluginSettings[pluginName] = map;
}
-QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const
+QVariantMap PluginSettings::descriptions(const QString &pluginName) const
+{
+ return m_PluginDescriptions[pluginName];
+}
+
+void PluginSettings::setDescriptions(const QString &pluginName, const QVariantMap& map)
+{
+ m_PluginDescriptions[pluginName] = map;
+}
+
+QVariant PluginSettings::persistent(const QString &pluginName, const QString &key, const QVariant &def) const
{
if (!m_PluginSettings.contains(pluginName)) {
return def;
}
- return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def);
+
+ return get<QVariant>(m_Settings, "PluginPersistance", pluginName + "/" + key, def);
}
-void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync)
+void PluginSettings::setPersistent(
+ const QString &pluginName, const QString &key, const QVariant &value, bool sync)
{
if (!m_PluginSettings.contains(pluginName)) {
- throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName));
+ throw MyException(
+ QObject::tr("attempt to store setting for unknown plugin \"%1\"")
+ .arg(pluginName));
}
- m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value);
+
+ set(m_Settings, "PluginPersistance", pluginName + "/" + key, value);
+
if (sync) {
m_Settings.sync();
}
}
+void PluginSettings::addBlacklist(const QString &fileName)
+{
+ m_PluginBlacklist.insert(fileName);
+ writeBlacklist();
+}
+
+bool PluginSettings::blacklisted(const QString &fileName) const
+{
+ return m_PluginBlacklist.contains(fileName);
+}
+
+void PluginSettings::setBlacklist(const QStringList& pluginNames)
+{
+ m_PluginBlacklist.clear();
+
+ for (const auto& name : pluginNames) {
+ m_PluginBlacklist.insert(name);
+ }
+}
+
+const QSet<QString>& PluginSettings::blacklist() const
+{
+ return m_PluginBlacklist;
+}
-QString Settings::language()
+void PluginSettings::save()
{
- QString result = m_Settings.value("Settings/language", "").toString();
+ for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) {
+ for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) {
+ const auto key = iterPlugins.key() + "/" + iterSettings.key();
+ set(m_Settings, "Plugins", key, iterSettings.value());
+ }
+ }
+
+ writeBlacklist();
+}
+
+void PluginSettings::writeBlacklist()
+{
+ const auto current = readBlacklist();
+
+ if (current.size() > m_PluginBlacklist.size()) {
+ // Qt can't remove array elements, the section must be cleared
+ removeSection(m_Settings, "pluginBlacklist");
+ }
+
+ ScopedWriteArray swa(m_Settings, "pluginBlacklist", m_PluginBlacklist.size());
+
+ for (const QString &plugin : m_PluginBlacklist) {
+ swa.next();
+ swa.set("name", plugin);
+ }
+}
+
+QSet<QString> PluginSettings::readBlacklist() const
+{
+ QSet<QString> set;
+
+ ScopedReadArray sra(m_Settings, "pluginBlacklist");
+ sra.for_each([&]{
+ set.insert(sra.get<QString>("name"));
+ });
+
+ return set;
+}
+
+
+PathSettings::PathSettings(QSettings& settings)
+ : m_Settings(settings)
+{
+}
+
+std::map<QString, QString> PathSettings::recent() const
+{
+ std::map<QString, QString> map;
+
+ ScopedReadArray sra(m_Settings, "recentDirectories");
+
+ sra.for_each([&] {
+ const QVariant name = sra.get<QVariant>("name");
+ const QVariant dir = sra.get<QVariant>("directory");
+
+ if (name.isValid() && dir.isValid()) {
+ map.emplace(name.toString(), dir.toString());
+ }
+ });
+
+ return map;
+}
+
+void PathSettings::setRecent(const std::map<QString, QString>& map)
+{
+ const auto current = recent();
+
+ if (current.size() > map.size()) {
+ // Qt can't remove array elements, the section must be cleared
+ removeSection(m_Settings, "recentDirectories");
+ }
+
+ ScopedWriteArray swa(m_Settings, "recentDirectories", map.size());
+
+ for (auto&& p : map) {
+ swa.next();
+
+ swa.set("name", p.first);
+ swa.set("directory", p.second);
+ }
+}
+
+QString PathSettings::getConfigurablePath(const QString &key,
+ const QString &def,
+ bool resolve) const
+{
+ QString result = QDir::fromNativeSeparators(
+ get<QString>(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def));
+
+ if (resolve) {
+ result.replace("%BASE_DIR%", base());
+ }
+
+ return result;
+}
+
+void PathSettings::setConfigurablePath(const QString &key, const QString& path)
+{
+ if (path.isEmpty()) {
+ remove(m_Settings, "Settings", key);
+ } else {
+ set(m_Settings, "Settings", key, path);
+ }
+}
+
+QString PathSettings::base() const
+{
+ return QDir::fromNativeSeparators(get<QString>(m_Settings,
+ "Settings", "base_directory", qApp->property("dataPath").toString()));
+}
+
+QString PathSettings::downloads(bool resolve) const
+{
+ return getConfigurablePath(
+ "download_directory",
+ ToQString(AppConfig::downloadPath()),
+ resolve);
+}
+
+QString PathSettings::cache(bool resolve) const
+{
+ return getConfigurablePath(
+ "cache_directory",
+ ToQString(AppConfig::cachePath()),
+ resolve);
+}
+
+QString PathSettings::mods(bool resolve) const
+{
+ return getConfigurablePath(
+ "mod_directory",
+ ToQString(AppConfig::modsPath()),
+ resolve);
+}
+
+QString PathSettings::profiles(bool resolve) const
+{
+ return getConfigurablePath(
+ "profiles_directory",
+ ToQString(AppConfig::profilesPath()),
+ resolve);
+}
+
+QString PathSettings::overwrite(bool resolve) const
+{
+ return getConfigurablePath(
+ "overwrite_directory",
+ ToQString(AppConfig::overwritePath()),
+ resolve);
+}
+
+void PathSettings::setBase(const QString& path)
+{
+ if (path.isEmpty()) {
+ remove(m_Settings, "Settings", "base_directory");
+ } else {
+ set(m_Settings, "Settings", "base_directory", path);
+ }
+}
+
+void PathSettings::setDownloads(const QString& path)
+{
+ setConfigurablePath("download_directory", path);
+}
+
+void PathSettings::setMods(const QString& path)
+{
+ setConfigurablePath("mod_directory", path);
+}
+
+void PathSettings::setCache(const QString& path)
+{
+ setConfigurablePath("cache_directory", path);
+}
+
+void PathSettings::setProfiles(const QString& path)
+{
+ setConfigurablePath("profiles_directory", path);
+}
+
+void PathSettings::setOverwrite(const QString& path)
+{
+ setConfigurablePath("overwrite_directory", path);
+}
+
+
+NetworkSettings::NetworkSettings(QSettings& settings)
+ : m_Settings(settings)
+{
+}
+
+bool NetworkSettings::offlineMode() const
+{
+ return get<bool>(m_Settings, "Settings", "offline_mode", false);
+}
+
+void NetworkSettings::setOfflineMode(bool b)
+{
+ set(m_Settings, "Settings", "offline_mode", b);
+}
+
+bool NetworkSettings::useProxy() const
+{
+ return get<bool>(m_Settings, "Settings", "use_proxy", false);
+}
+
+void NetworkSettings::setUseProxy(bool b)
+{
+ set(m_Settings, "Settings", "use_proxy", b);
+}
+
+void NetworkSettings::setDownloadSpeed(const QString& name, int bytesPerSecond)
+{
+ auto current = servers();
+
+ for (auto& server : current) {
+ if (server.name() == name) {
+ server.addDownload(bytesPerSecond);
+ updateServers(current);
+ return;
+ }
+ }
+
+ log::error(
+ "server '{}' not found while trying to add a download with bps {}",
+ name, bytesPerSecond);
+}
+
+ServerList NetworkSettings::servers() const
+{
+ ServerList list;
+
+ {
+ ScopedReadArray sra(m_Settings, "Servers");
+
+ sra.for_each([&] {
+ ServerInfo::SpeedList lastDownloads;
+
+ const auto lastDownloadsString = sra.get<QString>("lastDownloads", "");
+
+ for (const auto& s : lastDownloadsString.split(" ")) {
+ const auto bytesPerSecond = s.toInt();
+ if (bytesPerSecond > 0) {
+ lastDownloads.push_back(bytesPerSecond);
+ }
+ }
+
+ ServerInfo server(
+ sra.get<QString>("name", ""),
+ sra.get<bool>("premium", false),
+ QDate::fromString(sra.get<QString>("lastSeen", ""), Qt::ISODate),
+ sra.get<int>("preferred", 0),
+ lastDownloads);
+
+ list.add(std::move(server));
+ });
+ }
+
+ return list;
+}
+
+void NetworkSettings::updateServers(ServerList newServers)
+{
+ // clean up unavailable servers
+ newServers.cleanup();
+
+ const auto current = servers();
+
+ if (current.size() > newServers.size()) {
+ // Qt can't remove array elements, the section must be cleared
+ removeSection(m_Settings, "Servers");
+ }
+
+
+ ScopedWriteArray swa(m_Settings, "Servers", newServers.size());
+
+ for (const auto& server : newServers) {
+ swa.next();
+
+ swa.set("name", server.name());
+ swa.set("premium", server.isPremium());
+ swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate));
+ swa.set("preferred", server.preferred());
+
+ QString lastDownloads;
+ for (const auto& speed : server.lastDownloads()) {
+ if (speed > 0) {
+ lastDownloads += QString("%1 ").arg(speed);
+ }
+ }
+
+ swa.set("lastDownloads", lastDownloads.trimmed());
+ }
+}
+
+void NetworkSettings::updateFromOldMap()
+{
+ // servers used to be a map of byte arrays until 2.2.1, it's now an array of
+ // individual values instead
+ //
+ // so post 2.2.1, only one key is returned: "size", the size of the arrays;
+ // in 2.2.1, one key per server is returned
+
+ // sanity check that this is really 2.2.1
+ {
+ const QStringList keys = ScopedGroup(m_Settings, "Servers").keys();
+
+ for (auto&& k : keys) {
+ if (k == "size") {
+ // this looks like an array, so the upgrade was probably already done
+ return;
+ }
+ }
+ }
+
+ const auto servers = serversFromOldMap();
+ removeSection(m_Settings, "Servers");
+ updateServers(servers);
+}
+
+ServerList NetworkSettings::serversFromOldMap() const
+{
+ // for 2.2.1 and before
+
+ ServerList list;
+ const ScopedGroup sg(m_Settings, "Servers");
+
+ sg.for_each([&](auto&& serverKey) {
+ QVariantMap data = sg.get<QVariantMap>(serverKey);
+
+ ServerInfo server(
+ serverKey,
+ data["premium"].toBool(),
+ data["lastSeen"].toDate(),
+ data["preferred"].toInt(),
+ {});
+
+ // ignoring download count and speed, it's now a list of values instead of
+ // a total
+
+ list.add(std::move(server));
+ });
+
+ return list;
+}
+
+void NetworkSettings::dump() const
+{
+ log::debug("servers:");
+
+ for (const auto& server : servers()) {
+ QString lastDownloads;
+ for (auto speed : server.lastDownloads()) {
+ lastDownloads += QString("%1 ").arg(speed);
+ }
+
+ log::debug(
+ " . {} premium={} lastSeen={} preferred={} lastDownloads={}",
+ server.name(),
+ server.isPremium() ? "yes" : "no",
+ server.lastSeen().toString(Qt::ISODate),
+ server.preferred(),
+ lastDownloads.trimmed());
+ }
+}
+
+
+NexusSettings::NexusSettings(Settings& parent, QSettings& settings)
+ : m_Parent(parent), m_Settings(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<bool>(m_Settings, "Settings", "endorsement_integration", true);
+}
+
+void NexusSettings::setEndorsementIntegration(bool b) const
+{
+ set(m_Settings, "Settings", "endorsement_integration", b);
+}
+
+EndorsementState NexusSettings::endorsementState() const
+{
+ return endorsementStateFromString(
+ get<QString>(m_Settings, "General", "endorse_state", ""));
+}
+
+void NexusSettings::setEndorsementState(EndorsementState s)
+{
+ const auto v = toString(s);
+
+ if (v.isEmpty()) {
+ remove(m_Settings, "General", "endorse_state");
+ } else {
+ set(m_Settings, "General", "endorse_state", v);
+ }
+}
+
+void NexusSettings::registerAsNXMHandler(bool force)
+{
+ const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe";
+ const auto executable = QCoreApplication::applicationFilePath();
+
+ QString mode = force ? "forcereg" : "reg";
+ QString parameters = mode + " " + m_Parent.game().plugin()->gameShortName();
+ for (const QString& altGame : m_Parent.game().plugin()->validShortNames()) {
+ parameters += "," + altGame;
+ }
+ parameters += " \"" + executable + "\"";
+
+ if (!shell::Execute(nxmPath, parameters)) {
+ QMessageBox::critical(
+ nullptr, QObject::tr("Failed"),
+ QObject::tr("Failed to start the helper application"));
+ }
+}
+
+
+SteamSettings::SteamSettings(Settings& parent, QSettings& settings)
+ : m_Parent(parent), m_Settings(settings)
+{
+}
+
+QString SteamSettings::appID() const
+{
+ return get<QString>(
+ m_Settings, "Settings", "app_id", m_Parent.game().plugin()->steamAPPId());
+}
+
+void SteamSettings::setAppID(const QString& id)
+{
+ if (id.isEmpty()) {
+ remove(m_Settings, "Settings", "app_id");
+ } else {
+ set(m_Settings, "Settings", "app_id", id);
+ }
+}
+
+bool SteamSettings::login(QString &username, QString &password) const
+{
+ username = get<QString>(m_Settings, "Settings", "steam_username", "");
+ password = getWindowsCredential("steam_password");
+
+ return !username.isEmpty() && !password.isEmpty();
+}
+
+void SteamSettings::setLogin(QString username, QString password)
+{
+ if (username == "") {
+ remove(m_Settings, "Settings", "steam_username");
+ password = "";
+ } else {
+ set(m_Settings, "Settings", "steam_username", username);
+ }
+
+ if (!setWindowsCredential("steam_password", password)) {
+ const auto e = GetLastError();
+ log::error("Storing or deleting password failed: {}", formatSystemMessage(e));
+ }
+}
+
+
+InterfaceSettings::InterfaceSettings(QSettings& settings)
+ : m_Settings(settings)
+{
+}
+
+bool InterfaceSettings::lockGUI() const
+{
+ return get<bool>(m_Settings, "Settings", "lock_gui", true);
+}
+
+void InterfaceSettings::setLockGUI(bool b)
+{
+ set(m_Settings, "Settings", "lock_gui", b);
+}
+
+std::optional<QString> InterfaceSettings::styleName() const
+{
+ return getOptional<QString>(m_Settings, "Settings", "style");
+}
+
+void InterfaceSettings::setStyleName(const QString& name)
+{
+ set(m_Settings, "Settings", "style", name);
+}
+
+bool InterfaceSettings::compactDownloads() const
+{
+ return get<bool>(m_Settings, "Settings", "compact_downloads", false);
+}
+
+void InterfaceSettings::setCompactDownloads(bool b)
+{
+ set(m_Settings, "Settings", "compact_downloads", b);
+}
+
+bool InterfaceSettings::metaDownloads() const
+{
+ return get<bool>(m_Settings, "Settings", "meta_downloads", false);
+}
+
+void InterfaceSettings::setMetaDownloads(bool b)
+{
+ set(m_Settings, "Settings", "meta_downloads", b);
+}
+
+bool InterfaceSettings::hideAPICounter() const
+{
+ return get<bool>(m_Settings, "Settings", "hide_api_counter", false);
+}
+
+void InterfaceSettings::setHideAPICounter(bool b)
+{
+ set(m_Settings, "Settings", "hide_api_counter", b);
+}
+
+bool InterfaceSettings::displayForeign() const
+{
+ return get<bool>(m_Settings, "Settings", "display_foreign", true);
+}
+
+void InterfaceSettings::setDisplayForeign(bool b)
+{
+ set(m_Settings, "Settings", "display_foreign", b);
+}
+
+QString InterfaceSettings::language()
+{
+ QString result = get<QString>(m_Settings, "Settings", "language", "");
+
if (result.isEmpty()) {
QStringList languagePreferences = QLocale::system().uiLanguages();
+
if (languagePreferences.length() > 0) {
// the users most favoritest language
result = languagePreferences.at(0);
@@ -546,65 +1820,70 @@ QString Settings::language()
result = QLocale::system().name();
}
}
+
return result;
}
-void Settings::updateServers(const QList<ServerInfo> &servers)
+void InterfaceSettings::setLanguage(const QString& name)
{
- m_Settings.beginGroup("Servers");
- QStringList oldServerKeys = m_Settings.childKeys();
+ set(m_Settings, "Settings", "language", name);
+}
- for (const ServerInfo &server : servers) {
- if (!oldServerKeys.contains(server.name)) {
- // not yet known server
- QVariantMap newVal;
- newVal["premium"] = server.premium;
- newVal["preferred"] = server.preferred ? 1 : 0;
- newVal["lastSeen"] = server.lastSeen;
- newVal["downloadCount"] = 0;
- newVal["downloadSpeed"] = 0.0;
+bool InterfaceSettings::isTutorialCompleted(const QString& windowName) const
+{
+ return get<bool>(m_Settings, "CompletedWindowTutorials", windowName, false);
+}
- m_Settings.setValue(server.name, newVal);
- } else {
- QVariantMap data = m_Settings.value(server.name).toMap();
- data["lastSeen"] = server.lastSeen;
- data["premium"] = server.premium;
+void InterfaceSettings::setTutorialCompleted(const QString& windowName, bool b)
+{
+ set(m_Settings, "CompletedWindowTutorials", windowName, b);
+}
- m_Settings.setValue(server.name, data);
- }
- }
- // clean up unavailable servers
- QDate now = QDate::currentDate();
- for (const QString &key : m_Settings.childKeys()) {
- QVariantMap val = m_Settings.value(key).toMap();
- QDate lastSeen = val["lastSeen"].toDate();
- if (lastSeen.daysTo(now) > 30) {
- log::debug("removing server {} since it hasn't been available for downloads in over a month", key);
- m_Settings.remove(key);
- }
- }
+DiagnosticsSettings::DiagnosticsSettings(QSettings& settings)
+ : m_Settings(settings)
+{
+}
- m_Settings.endGroup();
+log::Levels DiagnosticsSettings::logLevel() const
+{
+ return get<log::Levels>(m_Settings, "Settings", "log_level", log::Levels::Info);
+}
- m_Settings.sync();
+void DiagnosticsSettings::setLogLevel(log::Levels level)
+{
+ set(m_Settings, "Settings", "log_level", level);
}
-void Settings::addBlacklistPlugin(const QString &fileName)
+CrashDumpsType DiagnosticsSettings::crashDumpsType() const
{
- m_PluginBlacklist.insert(fileName);
- writePluginBlacklist();
+ return get<CrashDumpsType>(m_Settings,
+ "Settings", "crash_dumps_type", CrashDumpsType::Mini);
}
-void Settings::writePluginBlacklist()
+void DiagnosticsSettings::setCrashDumpsType(CrashDumpsType type)
{
- m_Settings.remove("pluginBlacklist");
- m_Settings.beginWriteArray("pluginBlacklist");
- int idx = 0;
- for (const QString &plugin : m_PluginBlacklist) {
- m_Settings.setArrayIndex(idx++);
- m_Settings.setValue("name", plugin);
- }
+ set(m_Settings, "Settings", "crash_dumps_type", type);
+}
+
+int DiagnosticsSettings::crashDumpsMax() const
+{
+ return get<int>(m_Settings, "Settings", "crash_dumps_max", 5);
+}
+
+void DiagnosticsSettings::setCrashDumpsMax(int n)
+{
+ set(m_Settings, "Settings", "crash_dumps_max", n);
+}
+
+
+GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog)
+ : m_settings(s), m_dialog(dialog)
+{
+ m_settings.geometry().restoreGeometry(m_dialog);
+}
- m_Settings.endArray();
+GeometrySaver::~GeometrySaver()
+{
+ m_settings.geometry().saveGeometry(m_dialog);
}
diff --git a/src/settings.h b/src/settings.h
index 63718089..91b87e29 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -21,380 +21,746 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#define SETTINGS_H
#include "loadmechanism.h"
+#include <questionboxmemory.h>
#include <log.h>
+#include <usvfsparameters.h>
+
+#ifdef interface
+ #undef interface
+#endif
namespace MOBase {
class IPlugin;
class IPluginGame;
}
-class PluginContainer;
-struct ServerInfo;
+class QSplitter;
-/**
- * manages the settings for Mod Organizer. The settings are not cached
- * inside the class but read/written directly from/to disc
- **/
-class Settings : public QObject
+class ServerList;
+class Settings;
+class ExpanderWidget;
+
+
+// helper class that calls restoreGeometry() in the constructor and
+// saveGeometry() in the destructor
+//
+class GeometrySaver
{
- Q_OBJECT
+public:
+ GeometrySaver(Settings& s, QDialog* dialog);
+ ~GeometrySaver();
+private:
+ Settings& m_settings;
+ QDialog* m_dialog;
+};
+
+
+// setting for the currently managed game
+//
+class GameSettings
+{
public:
- Settings(const QSettings &settingsSource);
- ~Settings();
+ GameSettings(QSettings& setting);
- static Settings &instance();
+ // game plugin
+ //
+ const MOBase::IPluginGame* plugin();
+ void setPlugin(const MOBase::IPluginGame* gamePlugin);
+
+ // whether files of the core game are forced-enabled so the user can't
+ // accidentally disable them
+ //
+ bool forceEnableCoreFiles() const;
+ void setForceEnableCoreFiles(bool b);
+
+ // the directory where the managed game is stored
+ //
+ std::optional<QString> directory() const;
+ void setDirectory(const QString& path);
+
+ // the name of the managed game
+ //
+ std::optional<QString> name() const;
+ void setName(const QString& name);
+
+ // the edition of the managed game
+ //
+ std::optional<QString> edition() const;
+ void setEdition(const QString& name);
+
+ // the current profile name
+ //
+ std::optional<QString> selectedProfileName() const;
+ void setSelectedProfileName(const QString& name);
+
+ // load mechanism type
+ //
+ LoadMechanism::EMechanism loadMechanismType() const;
+ void setLoadMechanism(LoadMechanism::EMechanism m);
+
+ // load mechanism object
+ //
+ const LoadMechanism& loadMechanism() const;
+ LoadMechanism& loadMechanism();
+
+ // whether the user wants unchecked plugins (esp, esm) to be hidden from
+ // the virtual data directory
+ //
+ bool hideUncheckedPlugins() const;
+ void setHideUncheckedPlugins(bool b);
+
+private:
+ QSettings& m_Settings;
+ const MOBase::IPluginGame* m_GamePlugin;
+ LoadMechanism m_LoadMechanism;
+};
+
+
+// geometry settings for various widgets; this should contain any setting that
+// can get invalid through UI changes or when users change display settings
+// (resolution, monitors, etc.); see WidgetSettings for the counterpart
+//
+// all these settings are stored under [Geometry] and get wiped when the
+// "reset geometry settings" button is clicked in the settings
+//
+// saveGeometry(), restoreGeometry(), saveState() and restoreState() call the
+// same functions on the given widget
+//
+class GeometrySettings
+{
+public:
+ GeometrySettings(QSettings& s);
+
+ // asks the settings to get reset
+ //
+ // this gets called from the settings dialog and gets picked up in
+ // resetIfNeeded(), called from runApplication() just before exiting
+ //
+ void requestReset();
+ void resetIfNeeded();
+
+
+ void saveGeometry(const QWidget* w);
+ bool restoreGeometry(QWidget* w) const;
+
+ void saveState(const QMainWindow* window);
+ bool restoreState(QMainWindow* window) const;
+
+ void saveState(const QHeaderView* header);
+ bool restoreState(QHeaderView* header) const;
+
+ void saveState(const QSplitter* splitter);
+ bool restoreState(QSplitter* splitter) const;
+
+ void saveState(const ExpanderWidget* expander);
+ bool restoreState(ExpanderWidget* expander) const;
+
+ void saveVisibility(const QWidget* w);
+ bool restoreVisibility(QWidget* w, std::optional<bool> def={}) const;
+
+ void saveToolbars(const QMainWindow* w);
+ void restoreToolbars(QMainWindow* w) const;
+
+ void saveDocks(const QMainWindow* w);
+ void restoreDocks(QMainWindow* w) const;
+
+ // this should be a generic "tab order" setting, but it only happens for the
+ // mod info dialog right now
+ //
+ QStringList modInfoTabOrder() const;
+ void setModInfoTabOrder(const QString& names);
+
+ // assumes the given widget is a top-level
+ //
+ void centerOnMainWindowMonitor(QWidget* w);
+
+ // saves the monitor number of the given window
+ //
+ void saveMainWindowMonitor(const QMainWindow* w);
+
+private:
+ QSettings& m_Settings;
+ bool m_Reset;
+};
+
+
+// widget settings that should stay valid regardless of UI changes or when users
+// change display settings (resolution, monitors, etc.); see GeometrySettings
+// for the counterpart
+//
+class WidgetSettings
+{
+public:
+ WidgetSettings(QSettings& s);
+
+ // selected index for a combobox
+ //
+ std::optional<int> index(const QComboBox* cb) const;
+ void saveIndex(const QComboBox* cb);
+ void restoreIndex(QComboBox* cb, std::optional<int> def={}) const;
- /**
- * unregister all plugins from settings
- */
+ // selected tab index for a tab widget
+ //
+ std::optional<int> index(const QTabWidget* w) const;
+ void saveIndex(const QTabWidget* w);
+ void restoreIndex(QTabWidget* w, std::optional<int> def={}) const;
+
+ // check state for a checkable button
+ //
+ std::optional<bool> checked(const QAbstractButton* w) const;
+ void saveChecked(const QAbstractButton* w);
+ void restoreChecked(QAbstractButton* w, std::optional<bool> def={}) const;
+
+ // returns the remembered button for a question dialog, or NoButton if the
+ // user hasn't saved the choice
+ //
+ MOBase::QuestionBoxMemory::Button questionButton(
+ const QString& windowName, const QString& filename) const;
+
+ // sets the button to be remembered for the given window
+ //
+ void setQuestionWindowButton(
+ const QString& windowName, MOBase::QuestionBoxMemory::Button button);
+
+ // sets the button to be remembered for the given file
+ //
+ void setQuestionFileButton(
+ const QString& windowName, const QString& filename,
+ MOBase::QuestionBoxMemory::Button choice);
+
+ // wipes all the remembered buttons
+ //
+ void resetQuestionButtons();
+
+private:
+ QSettings& m_Settings;
+};
+
+
+// various color settings
+//
+class ColorSettings
+{
+public:
+ ColorSettings(QSettings& s);
+
+ QColor modlistOverwrittenLoose() const;
+ void setModlistOverwrittenLoose(const QColor& c);
+
+ QColor modlistOverwritingLoose() const;
+ void setModlistOverwritingLoose(const QColor& c);
+
+ QColor modlistOverwrittenArchive() const;
+ void setModlistOverwrittenArchive(const QColor& c);
+
+ QColor modlistOverwritingArchive() const;
+ void setModlistOverwritingArchive(const QColor& c);
+
+ QColor modlistContainsPlugin() const;
+ void setModlistContainsPlugin(const QColor& c);
+
+ QColor pluginListContained() const;
+ void setPluginListContained(const QColor& c) ;
+
+ std::optional<QColor> previousSeparatorColor() const;
+ void setPreviousSeparatorColor(const QColor& c) const;
+ void removePreviousSeparatorColor();
+
+ // whether the scrollbar of the mod list should have colors for custom
+ // separator colors
+ //
+ bool colorSeparatorScrollbar() const;
+ void setColorSeparatorScrollbar(bool b);
+
+ // returns a color with a good contrast for the given background
+ //
+ static QColor idealTextColor(const QColor& rBackgroundColor);
+
+private:
+ QSettings& m_Settings;
+};
+
+
+// settings about plugins
+//
+class PluginSettings
+{
+public:
+ PluginSettings(QSettings& settings);
+
+
+ // forgets all the plugins
+ //
void clearPlugins();
- /**
- * @brief register plugin to be configurable
- * @param plugin the plugin to register
- * @return true if the plugin may be registered, false if it is blacklisted
- */
+ // adds the given plugin to the list and loads all of its settings
+ //
void registerPlugin(MOBase::IPlugin *plugin);
- /**
- * set up the settings for the specified plugins
- **/
- void addPluginSettings(const std::vector<MOBase::IPlugin*> &plugins);
+ // returns all the registered plugins
+ //
+ std::vector<MOBase::IPlugin*> plugins() const;
- /**
- * @return true if the user wants unchecked plugins (esp, esm) should be hidden from
- * the virtual dat adirectory
- **/
- bool hideUncheckedPlugins() const;
- /**
- * @return true if files of the core game are forced-enabled so the user can't accidentally disable them
- */
- bool forceEnableCoreFiles() const;
+ // returns the plugin setting for the given key
+ //
+ QVariant setting(const QString &pluginName, const QString &key) const;
- /**
- * @return true if the GUI should be locked when running executables
- */
- bool lockGUI() const;
+ // sets the plugin setting for the given key
+ //
+ void setSetting(const QString &pluginName, const QString &key, const QVariant &value);
- /**
- * @brief register download speed
- * @param url complete download url
- * @param bytesPerSecond download size in bytes per second
- */
- void setDownloadSpeed(const QString &serverName, int bytesPerSecond);
+ // returns all settings
+ //
+ QVariantMap settings(const QString &pluginName) const;
+
+ // overwrites all settings
+ //
+ void setSettings(const QString &pluginName, const QVariantMap& map);
+
+ // returns all descriptions
+ //
+ QVariantMap descriptions(const QString &pluginName) const;
+
+ // overwrites all descriptions
+ //
+ void setDescriptions(const QString &pluginName, const QVariantMap& map);
- /**
- * the steam appid is assigned by the steam platform to each product sold there.
- * The appid may differ between different versions of a game so it may be impossible
- * for Mod Organizer to automatically recognize it, though usually it does
- * @return the steam appid for the game
- **/
- QString getSteamAppID() const;
- /**
- * retrieves the base directory under which the other directories usually
- * reside
- */
- QString getBaseDirectory() const;
+ // ?
+ 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);
- /**
- * retrieve the directory where downloads are stored (with native separators)
- **/
- QString getDownloadDirectory(bool resolve = true) const;
- /**
- * retrieve a sorted list of preferred servers
- */
- std::map<QString, int> getPreferredServers();
+ // adds the given plugin to the blacklist
+ //
+ void addBlacklist(const QString &fileName);
- /**
- * retrieve the directory where mods are stored (with native separators)
- **/
- QString getModDirectory(bool resolve = true) const;
+ // returns whether the given plugin is blacklisted
+ //
+ bool blacklisted(const QString &fileName) const;
- /**
- * retrieve the directory where the web cache is stored (with native separators)
- **/
- QString getCacheDirectory(bool resolve = true) const;
+ // overwrites the whole blacklist
+ //
+ void setBlacklist(const QStringList& pluginNames);
- /**
- * retrieve the directory where the managed game is stored (with native separators)
- **/
- QString getManagedGameDirectory() const;
+ // returns the blacklist
+ //
+ const QSet<QString>& blacklist() const;
- /**
- * retrieve the directory where profiles stored (with native separators)
- **/
- QString getProfileDirectory(bool resolve = true) const;
- /**
- * retrieve the directory were new files are stored that can't be assigned
- * to a mod (with native separators)
- */
- QString getOverwriteDirectory(bool resolve = true) const;
+ // commits all the settings to the ini
+ //
+ void save();
- /**
- * @return true if the user has set up automatic login to nexus
- **/
- bool automaticLoginEnabled() const;
+private:
+ QSettings& m_Settings;
+ std::vector<MOBase::IPlugin*> m_Plugins;
+ QMap<QString, QVariantMap> m_PluginSettings;
+ QMap<QString, QVariantMap> m_PluginDescriptions;
+ QSet<QString> m_PluginBlacklist;
+
+ // commits the blacklist to the ini
+ //
+ void writeBlacklist();
+
+ // reads the blacklist from the ini
+ //
+ QSet<QString> readBlacklist() const;
+};
+
+
+// paths for the game and various components
+//
+// if the 'resolve' parameter is true, %BASE_DIR% is expanded; it's set to
+// false mostly in the settings dialog
+//
+class PathSettings
+{
+public:
+ PathSettings(QSettings& settings);
- /**
- * @brief retrieve the login information for nexus
- *
- * @param username (out) receives the user name for nexus
- * @param password (out) received the password for nexus
- * @return true if automatic login is active, false otherwise
- **/
- bool getNexusApiKey(QString &apiKey) const;
+ QString base() const;
+ void setBase(const QString& path);
- /**
- * @brief set the nexus login information
- *
- * @param username username
- * @param password password
- */
- bool setNexusApiKey(const QString& apiKey);
+ QString downloads(bool resolve = true) const;
+ void setDownloads(const QString& path);
- /**
- * @brief clears the nexus login information
- */
- bool clearNexusApiKey();
+ QString mods(bool resolve = true) const;
+ void setMods(const QString& path);
- /**
- * @brief returns whether an API key is currently stored
- */
- bool hasNexusApiKey() const;
+ QString cache(bool resolve = true) const;
+ void setCache(const QString& path);
- /**
- * @brief retrieve the login information for steam
- *
- * @param username (out) receives the user name for nexus
- * @param password (out) received the password for nexus
- * @return true if a username has been specified, false otherwise
- **/
- bool getSteamLogin(QString &username, QString &password) const;
+ QString profiles(bool resolve = true) const;
+ void setProfiles(const QString& path);
- /**
- * @return true if the user disabled internet features
- */
+ QString overwrite(bool resolve = true) const;
+ void setOverwrite(const QString& path);
+
+
+ // map of names to directories, used to remember the last directory used in
+ // various file pickers
+ //
+ std::map<QString, QString> recent() const;
+ void setRecent(const std::map<QString, QString>& map);
+
+private:
+ QSettings& m_Settings;
+
+ QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const;
+ void setConfigurablePath(const QString &key, const QString& path);
+};
+
+
+class NetworkSettings
+{
+public:
+ NetworkSettings(QSettings& settings);
+
+ // whether the user has disabled online features
+ //
bool offlineMode() const;
+ void setOfflineMode(bool b);
- /**
- * @return true if the user chose compact downloads
- */
- bool compactDownloads() const;
+ // whether the user wants to use the system proxy
+ //
+ bool useProxy() const;
+ void setUseProxy(bool b);
- /**
- * @return true if the user chose meta downloads
- */
- bool metaDownloads() const;
+ // add a new download speed to the list for the given server; each server
+ // remembers the last couple of download speeds and displays the average in
+ // the network settings
+ //
+ void setDownloadSpeed(const QString &serverName, int bytesPerSecond);
- /**
- * @return the configured log level
- */
- MOBase::log::Levels logLevel() const;
+ // known servers
+ //
+ ServerList servers() const;
- /**
- * sets the log level setting
- */
- void setLogLevel(MOBase::log::Levels level);
+ // sets the servers
+ //
+ void updateServers(ServerList servers);
- /**
- * @return the configured crash dumps type
- */
- int crashDumpsType() const;
+ // for 2.2.1 and before, rewrites the old byte array map to the new format
+ //
+ void updateFromOldMap();
- /**
- * @return the configured crash dumps max
- */
- int crashDumpsMax() const;
+ void dump() const;
- QColor modlistOverwrittenLooseColor() const;
+private:
+ QSettings& m_Settings;
- QColor modlistOverwritingLooseColor() const;
+ // for pre 2.2.1 ini files
+ //
+ ServerList serversFromOldMap() const;
+};
- QColor modlistOverwrittenArchiveColor() const;
- QColor modlistOverwritingArchiveColor() const;
+enum class EndorsementState
+{
+ Accepted = 1,
+ Refused,
+ NoDecision
+};
- QColor modlistContainsPluginColor() const;
+EndorsementState endorsementStateFromString(const QString& s);
+QString toString(EndorsementState s);
- QColor pluginListContainedColor() const;
- QString executablesBlacklist() const;
+class NexusSettings
+{
+public:
+ NexusSettings(Settings& parent, QSettings& settings);
- /**
- * @brief set the steam login information
- *
- * @param username username
- * @param password password
- */
- void setSteamLogin(QString username, QString password);
+ // 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;
- /**
- * @return the load mechanism to be used
- **/
- LoadMechanism::EMechanism getLoadMechanism() const;
+ // sets the api key in the credentials store, removes it if empty; returns
+ // false on errors
+ //
+ bool setApiKey(const QString& apiKey);
- /**
- * @brief activate the load mechanism selected by the user
- **/
- void setupLoadMechanism();
+ // removes the api key from the credentials store; returns false on errors
+ //
+ bool clearApiKey();
- /**
- * @return true if the user configured the use of a network proxy
- */
- bool useProxy() const;
+ // returns whether an API key is currently stored
+ //
+ bool hasApiKey() const;
- /**
- * @return true if endorsement integration is enabled
- */
+ // returns whether endorsement integration is enabled
+ //
bool endorsementIntegration() const;
+ void setEndorsementIntegration(bool b) const;
+
+ // returns the endorsement state of MO itself
+ //
+ EndorsementState endorsementState() const;
+ void setEndorsementState(EndorsementState s);
+
+ // registers MO as the handler for nxm links
+ //
+ // if 'force' is true, the registration dialog will be shown even if the user
+ // said earlier not to
+ //
+ void registerAsNXMHandler(bool force);
+
+private:
+ Settings& m_Parent;
+ QSettings& m_Settings;
+};
+
+
+class SteamSettings
+{
+public:
+ SteamSettings(Settings& parent, QSettings& settings);
+
+ // the steam appid is assigned by the steam platform to each product sold
+ // there.
+ //
+ // the appid may differ between different versions of a game so it may be
+ // impossible for MO to automatically recognize it, though usually it does
+ //
+ QString appID() const;
+ void setAppID(const QString& id);
+
+ // the steam username is stored in the ini, but the password is in the
+ // windows credentials store; both values are independent and either can be
+ // empty
+ //
+ // if the username exists in the ini, it is assigned to `username`; if not
+ // `username` is set to an empty string
+ //
+ // if the password exists in the credentials store, it is assigned to
+ // `password`; if not, `password` is set to an empty string
+ //
+ // returns whether _both_ the username and password have a value
+ //
+ bool login(QString &username, QString &password) const;
+
+ // sets the steam login; the username is saved in the ini file and the
+ // password in the credentials store
+ //
+ // if a value is empty, it is removed from its backing store
+ //
+ void setLogin(QString username, QString password);
+
+private:
+ Settings& m_Parent;
+ QSettings& m_Settings;
+};
+
+
+class InterfaceSettings
+{
+public:
+ InterfaceSettings(QSettings& settings);
+
+ // whether the GUI should be locked when running executables
+ //
+ bool lockGUI() const;
+ void setLockGUI(bool b);
+
+ // filename of the theme
+ //
+ std::optional<QString> styleName() const;
+ void setStyleName(const QString& name);
- /**
- * @return true if the API counter should be hidden
- */
+ // whether to show compact downloads
+ //
+ bool compactDownloads() const;
+ void setCompactDownloads(bool b);
+
+ // whether to show meta information for downloads
+ //
+ bool metaDownloads() const;
+ void setMetaDownloads(bool b);
+
+ // whether the API counter should be hidden
+ //
bool hideAPICounter() const;
+ void setHideAPICounter(bool b);
- /**
- * @return true if the user wants to see non-official plugins installed outside MO in his mod list
- */
+ // whether the user wants to see non-official plugins installed outside MO in
+ // the mod list
+ //
bool displayForeign() const;
+ void setDisplayForeign(bool b);
- /**
- * @brief sets the new motd hash
- **/
- void setMotDHash(uint hash);
+ // short code of the configured language (corresponding to the translation
+ // files)
+ //
+ QString language();
+ void setLanguage(const QString& name);
- /**
- * @return true if the user wants to have archives being parsed to show conflicts and contents
- */
- bool archiveParsing() const;
+ // whether the given tutorial has been completed
+ //
+ bool isTutorialCompleted(const QString& windowName) const;
+ void setTutorialCompleted(const QString& windowName, bool b=true);
- /**
- * @return hash of the last displayed message of the day
- **/
- uint getMotDHash() const;
+private:
+ QSettings& m_Settings;
+};
- /**
- * @brief allows direct access to the wrapped QSettings object
- * @return the wrapped QSettings object
- */
- QSettings &directInterface() { return m_Settings; }
- const QSettings &directInterface() const { return m_Settings; }
- /**
- * @brief retrieve a setting for one of the installed plugins
- * @param pluginName name of the plugin
- * @param key name of the setting to retrieve
- * @return the requested value as a QVariant
- * @note an invalid QVariant is returned if the the plugin/setting is not declared
- */
- QVariant pluginSetting(const QString &pluginName, const QString &key) const;
+class DiagnosticsSettings
+{
+public:
+ DiagnosticsSettings(QSettings& settings);
- /**
- * @brief set a setting for one of the installed mods
- * @param pluginName name of the plugin
- * @param key name of the setting to change
- * @param value the new value to set
- * @throw an exception is thrown if pluginName is invalid
- */
- void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value);
+ // log level for both MO and usvfs
+ //
+ MOBase::log::Levels logLevel() const;
+ void setLogLevel(MOBase::log::Levels level);
- /**
- * @brief retrieve a persistent value for a plugin
- * @param pluginName name of the plugin to store data for
- * @param key id of the value to retrieve
- * @param def default value to return if the value is not set
- * @return the requested value
- */
- QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const;
+ // crash dump type for both MO and usvfs
+ //
+ CrashDumpsType crashDumpsType() const;
+ void setCrashDumpsType(CrashDumpsType type);
- /**
- * @brief set a persistent value for a plugin
- * @param pluginName name of the plugin to store data for
- * @param key id of the value to retrieve
- * @param value value to set
- * @throw an exception is thrown if pluginName is invalid
- */
- void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync);
+ // maximum number of dump files keps, for both MO and usvfs
+ //
+ int crashDumpsMax() const;
+ void setCrashDumpsMax(int n);
- /**
- * @return short code of the configured language (corresponding to the translation files)
- */
- QString language();
+private:
+ QSettings& m_Settings;
+};
+
+
+// manages the settings for MO; the settings are accessed directly through a
+// QSettings and so are not cached here
+//
+class Settings : public QObject
+{
+ Q_OBJECT;
+
+public:
+ Settings(const QString& path);
+ ~Settings();
+
+ static Settings &instance();
- /**
- * @brief updates the list of known servers
- * @param list of servers from a recent query
- */
- void updateServers(const QList<ServerInfo> &servers);
+ // name of the ini file
+ //
+ QString filename() const;
- /**
- * @brief add a plugin that is to be blacklisted
- * @param fileName name of the plugin to blacklist
- */
- void addBlacklistPlugin(const QString &fileName);
+ // version of MO stored in the ini; this may be different from the current
+ // version if the user just updated
+ //
+ std::optional<QVersionNumber> version() const;
- /**
- * @brief test if a plugin is blacklisted and shouldn't be loaded
- * @param fileName name of the plugin
- * @return true if the file is blacklisted
- */
- bool pluginBlacklisted(const QString &fileName) const;
+ // updates the settings to bring them up to date
+ //
+ void processUpdates(const QVersionNumber& current, const QVersionNumber& last);
- /**
- * @return all loaded MO plugins
- */
- std::vector<MOBase::IPlugin*> plugins() const { return m_Plugins; }
+ // whether MO has been started for the first time
+ //
+ bool firstStart() const;
+ void setFirstStart(bool b);
+ // configured executables
+ //
+ std::vector<std::map<QString, QVariant>> executables() const;
+ void setExecutables(const std::vector<std::map<QString, QVariant>>& v);
+
+ // whether to backup existing mods on install
+ //
+ bool keepBackupOnInstall() const;
+ void setKeepBackupOnInstall(bool b);
+
+ // blacklisted executables do not get hooked by usvfs; this list is managed
+ // by MO but given to usvfs when starting an executable
+ //
+ QString executablesBlacklist() const;
+ void setExecutablesBlacklist(const QString& s);
+
+ // ? looks obsolete, only used by dead code
+ //
+ unsigned int motdHash() const;
+ void setMotdHash(unsigned int hash);
+
+ // whether archives should be parsed to show conflicts and contents
+ //
+ bool archiveParsing() const;
+ void setArchiveParsing(bool b);
+
+ // whether the user wants to upgrade to pre-releases
+ //
bool usePrereleases() const;
+ void setUsePrereleases(bool b);
- /**
- * @brief register MO as the handler for nxm links
- * @param force set to true to enforce the registration dialog to show up,
- * even if the user said earlier not to
- */
- void registerAsNXMHandler(bool force);
- /**
- * @brief color the scrollbar of the mod list for custom separator colors?
- * @return the state of the setting
- */
- bool colorSeparatorScrollbar() const;
+ GameSettings& game();
+ const GameSettings& game() const;
- static QColor getIdealTextColor(const QColor& rBackgroundColor);
+ GeometrySettings& geometry();
+ const GeometrySettings& geometry() const;
- MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; }
- const LoadMechanism& loadMechanism() const { return m_LoadMechanism; }
+ WidgetSettings& widgets();
+ const WidgetSettings& widgets() const;
- // temp
- QMap<QString, QVariantMap> m_PluginSettings;
- QMap<QString, QVariantMap> m_PluginDescriptions;
- QSet<QString> m_PluginBlacklist;
- void writePluginBlacklist();
+ ColorSettings& colors();
+ const ColorSettings& colors() const;
+
+ PluginSettings& plugins();
+ const PluginSettings& plugins() const;
+
+ PathSettings& paths();
+ const PathSettings& paths() const;
+
+ NetworkSettings& network();
+ const NetworkSettings& network() const;
+
+ NexusSettings& nexus();
+ const NexusSettings& nexus() const;
+
+ SteamSettings& steam();
+ const SteamSettings& steam() const;
+
+ InterfaceSettings& interface();
+ const InterfaceSettings& interface() const;
+
+ DiagnosticsSettings& diagnostics();
+ const DiagnosticsSettings& diagnostics() const;
+
+ // makes sure the ini file is written to disk
+ //
+ QSettings::Status sync() const;
+
+ void dump() const;
public slots:
+ // this slot is connected to by various parts of MO
+ //
void managedGameChanged(MOBase::IPluginGame const *gamePlugin);
signals:
+ // these are fired from outside the settings, mostly by the settings dialog
+ //
void languageChanged(const QString &newLanguage);
void styleChanged(const QString &newStyle);
private:
static Settings *s_Instance;
- MOBase::IPluginGame const *m_GamePlugin;
mutable QSettings m_Settings;
- LoadMechanism m_LoadMechanism;
- std::vector<MOBase::IPlugin*> m_Plugins;
-
- static bool obfuscate(const QString key, const QString data);
- static QString deObfuscate(const QString key);
- void readPluginBlacklist();
- QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const;
+ GameSettings m_Game;
+ GeometrySettings m_Geometry;
+ WidgetSettings m_Widgets;
+ ColorSettings m_Colors;
+ PluginSettings m_Plugins;
+ PathSettings m_Paths;
+ NetworkSettings m_Network;
+ NexusSettings m_Nexus;
+ SteamSettings m_Steam;
+ InterfaceSettings m_Interface;
+ DiagnosticsSettings m_Diagnostics;
};
#endif // SETTINGS_H
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp
index fbd9ecd1..1d3d4a39 100644
--- a/src/settingsdialog.cpp
+++ b/src/settingsdialog.cpp
@@ -29,12 +29,11 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
-SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent)
+SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& settings, QWidget *parent)
: TutorableDialog("SettingsDialog", parent)
, ui(new Ui::SettingsDialog)
, m_settings(settings)
, m_PluginContainer(pluginContainer)
- , m_GeometriesReset(false)
, m_keyChanged(false)
{
ui->setupUi(this);
@@ -46,65 +45,34 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti
m_tabs.push_back(std::unique_ptr<SettingsTab>(new SteamSettingsTab(settings, *this)));
m_tabs.push_back(std::unique_ptr<SettingsTab>(new PluginsSettingsTab(settings, *this)));
m_tabs.push_back(std::unique_ptr<SettingsTab>(new WorkaroundsSettingsTab(settings, *this)));
-
- auto& qsettings = settings->directInterface();
-
- QString key = QString("geometry/%1").arg(objectName());
- if (qsettings.contains(key)) {
- restoreGeometry(qsettings.value(key).toByteArray());
- }
}
int SettingsDialog::exec()
{
- auto& qsettings = m_settings->directInterface();
+ GeometrySaver gs(m_settings, this);
+
+ m_settings.widgets().restoreIndex(ui->tabWidget);
+
auto ret = TutorableDialog::exec();
- if (ret == QDialog::Accepted) {
+ m_settings.widgets().saveIndex(ui->tabWidget);
+ if (ret == QDialog::Accepted) {
for (auto&& tab : m_tabs) {
tab->closing();
}
- // remember settings before change
- QMap<QString, QString> before;
- qsettings.beginGroup("Settings");
- for (auto k : qsettings.allKeys())
- before[k] = qsettings.value(k).toString();
- qsettings.endGroup();
-
- // transfer modified settings to configuration file
+ // update settings for each tab
for (std::unique_ptr<SettingsTab> const &tab: m_tabs) {
tab->update();
}
-
- // print "changed" settings
- qsettings.beginGroup("Settings");
- bool first_update = true;
- for (auto k : qsettings.allKeys())
- if (qsettings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password"))
- {
- if (first_update) {
- log::debug("Changed settings:");
- first_update = false;
- }
- log::debug(" {}={}", k, qsettings.value(k).toString());
- }
- qsettings.endGroup();
}
- QString key = QString("geometry/%1").arg(objectName());
- qsettings.setValue(key, saveGeometry());
-
- // These changes happen regardless of accepted or rejected
bool restartNeeded = false;
if (getApiKeyChanged()) {
restartNeeded = true;
}
- if (getResetGeometries()) {
- restartNeeded = true;
- qsettings.setValue("reset_geometry", true);
- }
+
if (restartNeeded) {
if (QMessageBox::question(nullptr,
tr("Restart Mod Organizer?"),
@@ -141,7 +109,7 @@ void SettingsDialog::accept()
if ((QDir::fromNativeSeparators(newModPath) !=
QDir::fromNativeSeparators(
- Settings::instance().getModDirectory(true))) &&
+ Settings::instance().paths().mods(true))) &&
(QMessageBox::question(
nullptr, tr("Confirm"),
tr("Changing the mod directory affects all your profiles! "
@@ -156,29 +124,25 @@ void SettingsDialog::accept()
TutorableDialog::accept();
}
-bool SettingsDialog::getResetGeometries()
-{
- return ui->resetGeometryBtn->isChecked();
-}
-
bool SettingsDialog::getApiKeyChanged()
{
return m_keyChanged;
}
-SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog)
- : m_parent(m_parent)
- , m_Settings(m_parent->directInterface())
- , m_dialog(m_dialog)
- , ui(m_dialog.ui)
+SettingsTab::SettingsTab(Settings& s, SettingsDialog& d)
+ : ui(d.ui), m_settings(s), m_dialog(d)
{
}
-SettingsTab::~SettingsTab()
-{}
+SettingsTab::~SettingsTab() = default;
+
+Settings& SettingsTab::settings()
+{
+ return m_settings;
+}
-QWidget* SettingsTab::parentWidget()
+SettingsDialog& SettingsTab::dialog()
{
- return &m_dialog;
+ return m_dialog;
}
diff --git a/src/settingsdialog.h b/src/settingsdialog.h
index 03bba7cf..6a99cb8d 100644
--- a/src/settingsdialog.h
+++ b/src/settingsdialog.h
@@ -31,19 +31,21 @@ namespace Ui { class SettingsDialog; }
class SettingsTab
{
public:
- SettingsTab(Settings *m_parent, SettingsDialog &m_dialog);
+ SettingsTab(Settings& settings, SettingsDialog& m_dialog);
virtual ~SettingsTab();
virtual void update() = 0;
virtual void closing() {}
protected:
- Settings *m_parent;
- QSettings &m_Settings;
- SettingsDialog &m_dialog;
Ui::SettingsDialog* ui;
- QWidget* parentWidget();
+ Settings& settings();
+ SettingsDialog& dialog();
+
+private:
+ Settings& m_settings;
+ SettingsDialog& m_dialog;
};
@@ -58,7 +60,7 @@ class SettingsDialog : public MOBase::TutorableDialog
public:
explicit SettingsDialog(
- PluginContainer *pluginContainer, Settings* settings, QWidget *parent = 0);
+ PluginContainer *pluginContainer, Settings& settings, QWidget *parent = 0);
~SettingsDialog();
@@ -71,7 +73,6 @@ public:
// temp
Ui::SettingsDialog *ui;
bool m_keyChanged;
- bool m_GeometriesReset;
PluginContainer *m_PluginContainer;
int exec() override;
@@ -81,10 +82,9 @@ public slots:
public:
bool getApiKeyChanged();
- bool getResetGeometries();
private:
- Settings* m_settings;
+ Settings& m_settings;
std::vector<std::unique_ptr<SettingsTab>> m_tabs;
};
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index 660cae92..1a3726fb 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -1233,9 +1233,6 @@ programs you are intentionally running.</string>
<property name="text">
<string>Reset Window Geometries</string>
</property>
- <property name="checkable">
- <bool>true</bool>
- </property>
</widget>
</item>
<item>
@@ -1349,26 +1346,6 @@ programs you are intentionally running.</string>
&quot;Full&quot; Even larger dumps with a full memory dump of the process.
</string>
</property>
- <item>
- <property name="text">
- <string>None</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Mini (recommended)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Data</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Full</string>
- </property>
- </item>
</widget>
</item>
</layout>
diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp
index daf81d5c..386c7425 100644
--- a/src/settingsdialogdiagnostics.cpp
+++ b/src/settingsdialogdiagnostics.cpp
@@ -6,14 +6,17 @@
using namespace MOBase;
-DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog)
- : SettingsTab(m_parent, m_dialog)
+DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d)
+ : SettingsTab(s, d)
{
setLevelsBox();
- ui->dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType());
- ui->dumpsMaxEdit->setValue(m_parent->crashDumpsMax());
+ setCrashDumpTypesBox();
+
+ ui->dumpsMaxEdit->setValue(settings().diagnostics().crashDumpsMax());
+
QString logsPath = qApp->property("dataPath").toString()
+ "/" + QString::fromStdWString(AppConfig::logPath());
+
ui->diagnosticsExplainedLabel->setText(
ui->diagnosticsExplainedLabel->text()
.replace("LOGS_FULL_PATH", logsPath)
@@ -33,16 +36,44 @@ void DiagnosticsSettingsTab::setLevelsBox()
ui->logLevelBox->addItem(QObject::tr("Error"), log::Error);
for (int i=0; i<ui->logLevelBox->count(); ++i) {
- if (ui->logLevelBox->itemData(i) == m_parent->logLevel()) {
+ if (ui->logLevelBox->itemData(i) == settings().diagnostics().logLevel()) {
ui->logLevelBox->setCurrentIndex(i);
break;
}
}
}
+void DiagnosticsSettingsTab::setCrashDumpTypesBox()
+{
+ ui->dumpsTypeBox->clear();
+
+ auto add = [&](auto&& text, auto&& type) {
+ ui->dumpsTypeBox->addItem(text, static_cast<int>(type));
+ };
+
+ add(QObject::tr("None"), CrashDumpsType::None);
+ add(QObject::tr("Mini (recommended)"), CrashDumpsType::Mini);
+ add(QObject::tr("Data"), CrashDumpsType::Data);
+ add(QObject::tr("Full"), CrashDumpsType::Full);
+
+ const auto current = static_cast<int>(
+ settings().diagnostics().crashDumpsType());
+
+ for (int i=0; i<ui->dumpsTypeBox->count(); ++i) {
+ if (ui->dumpsTypeBox->itemData(i) == current) {
+ ui->dumpsTypeBox->setCurrentIndex(i);
+ break;
+ }
+ }
+}
+
void DiagnosticsSettingsTab::update()
{
- m_Settings.setValue("Settings/log_level", ui->logLevelBox->currentData().toInt());
- m_Settings.setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex());
- m_Settings.setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value());
+ settings().diagnostics().setLogLevel(
+ static_cast<log::Levels>(ui->logLevelBox->currentData().toInt()));
+
+ settings().diagnostics().setCrashDumpsType(
+ static_cast<CrashDumpsType>(ui->dumpsTypeBox->currentData().toInt()));
+
+ settings().diagnostics().setCrashDumpsMax(ui->dumpsMaxEdit->value());
}
diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h
index 4c1805e2..f0fbf770 100644
--- a/src/settingsdialogdiagnostics.h
+++ b/src/settingsdialogdiagnostics.h
@@ -7,12 +7,13 @@
class DiagnosticsSettingsTab : public SettingsTab
{
public:
- DiagnosticsSettingsTab(Settings *parent, SettingsDialog &dialog);
+ DiagnosticsSettingsTab(Settings& settings, SettingsDialog& dialog);
void update();
private:
void setLevelsBox();
+ void setCrashDumpTypesBox();
};
#endif // SETTINGSDIALOGDIAGNOSTICS_H
diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp
index 324dc4f4..3f7ece38 100644
--- a/src/settingsdialoggeneral.cpp
+++ b/src/settingsdialoggeneral.cpp
@@ -6,12 +6,12 @@
using MOBase::QuestionBoxMemory;
-GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dialog)
- : SettingsTab(m_parent, m_dialog)
+GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d)
+ : SettingsTab(s, d)
{
addLanguages();
{
- QString languageCode = m_parent->language();
+ QString languageCode = settings().interface().language();
int currentID = ui->languageBox->findData(languageCode);
// I made a mess. :( Most languages are stored with only the iso country
// code (2 characters like "de") but chinese
@@ -26,33 +26,35 @@ GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dia
}
addStyles();
+
{
- int currentID = ui->styleBox->findData(
- m_Settings.value("Settings/style", "").toString());
+ const int currentID = ui->styleBox->findData(
+ settings().interface().styleName().value_or(""));
+
if (currentID != -1) {
ui->styleBox->setCurrentIndex(currentID);
}
}
//version with stylesheet
- setButtonColor(ui->overwritingBtn, m_parent->modlistOverwritingLooseColor());
- setButtonColor(ui->overwrittenBtn, m_parent->modlistOverwrittenLooseColor());
- setButtonColor(ui->overwritingArchiveBtn, m_parent->modlistOverwritingArchiveColor());
- setButtonColor(ui->overwrittenArchiveBtn, m_parent->modlistOverwrittenArchiveColor());
- setButtonColor(ui->containsBtn, m_parent->modlistContainsPluginColor());
- setButtonColor(ui->containedBtn, m_parent->pluginListContainedColor());
+ setButtonColor(ui->overwritingBtn, settings().colors().modlistOverwritingLoose());
+ setButtonColor(ui->overwrittenBtn, settings().colors().modlistOverwrittenLoose());
+ setButtonColor(ui->overwritingArchiveBtn, settings().colors().modlistOverwritingArchive());
+ setButtonColor(ui->overwrittenArchiveBtn, settings().colors().modlistOverwrittenArchive());
+ setButtonColor(ui->containsBtn, settings().colors().modlistContainsPlugin());
+ setButtonColor(ui->containedBtn, settings().colors().pluginListContained());
- setOverwritingColor(m_parent->modlistOverwritingLooseColor());
- setOverwrittenColor(m_parent->modlistOverwrittenLooseColor());
- setOverwritingArchiveColor(m_parent->modlistOverwritingArchiveColor());
- setOverwrittenArchiveColor(m_parent->modlistOverwrittenArchiveColor());
- setContainsColor(m_parent->modlistContainsPluginColor());
- setContainedColor(m_parent->pluginListContainedColor());
+ setOverwritingColor(settings().colors().modlistOverwritingLoose());
+ setOverwrittenColor(settings().colors().modlistOverwrittenLoose());
+ setOverwritingArchiveColor(settings().colors().modlistOverwritingArchive());
+ setOverwrittenArchiveColor(settings().colors().modlistOverwrittenArchive());
+ setContainsColor(settings().colors().modlistContainsPlugin());
+ setContainedColor(settings().colors().pluginListContained());
- ui->compactBox->setChecked(m_parent->compactDownloads());
- ui->showMetaBox->setChecked(m_parent->metaDownloads());
- ui->usePrereleaseBox->setChecked(m_parent->usePrereleases());
- ui->colorSeparatorsBox->setChecked(m_parent->colorSeparatorScrollbar());
+ ui->compactBox->setChecked(settings().interface().compactDownloads());
+ ui->showMetaBox->setChecked(settings().interface().metaDownloads());
+ ui->usePrereleaseBox->setChecked(settings().usePrereleases());
+ ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar());
QObject::connect(ui->overwritingArchiveBtn, &QPushButton::clicked, [&]{ on_overwritingArchiveBtn_clicked(); });
QObject::connect(ui->overwritingBtn, &QPushButton::clicked, [&]{ on_overwritingBtn_clicked(); });
@@ -67,30 +69,32 @@ GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dia
void GeneralSettingsTab::update()
{
- QString oldLanguage = m_parent->language();
- QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString();
+ const QString oldLanguage = settings().interface().language();
+ const QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString();
+
if (newLanguage != oldLanguage) {
- m_Settings.setValue("Settings/language", newLanguage);
- emit m_parent->languageChanged(newLanguage);
+ settings().interface().setLanguage(newLanguage);
+ emit settings().languageChanged(newLanguage);
}
- QString oldStyle = m_Settings.value("Settings/style", "").toString();
- QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString();
+ const QString oldStyle = settings().interface().styleName().value_or("");
+ const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString();
if (oldStyle != newStyle) {
- m_Settings.setValue("Settings/style", newStyle);
- emit m_parent->styleChanged(newStyle);
+ settings().interface().setStyleName(newStyle);
+ emit settings().styleChanged(newStyle);
}
- m_Settings.setValue("Settings/overwritingLooseFilesColor", getOverwritingColor());
- m_Settings.setValue("Settings/overwrittenLooseFilesColor", getOverwrittenColor());
- m_Settings.setValue("Settings/overwritingArchiveFilesColor", getOverwritingArchiveColor());
- m_Settings.setValue("Settings/overwrittenArchiveFilesColor", getOverwrittenArchiveColor());
- m_Settings.setValue("Settings/containsPluginColor", getContainsColor());
- m_Settings.setValue("Settings/containedColor", getContainedColor());
- m_Settings.setValue("Settings/compact_downloads", ui->compactBox->isChecked());
- m_Settings.setValue("Settings/meta_downloads", ui->showMetaBox->isChecked());
- m_Settings.setValue("Settings/use_prereleases", ui->usePrereleaseBox->isChecked());
- m_Settings.setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked());
+ settings().colors().setModlistOverwritingLoose(getOverwritingColor());
+ settings().colors().setModlistOverwrittenLoose(getOverwrittenColor());
+ settings().colors().setModlistOverwritingArchive(getOverwritingArchiveColor());
+ settings().colors().setModlistOverwrittenArchive(getOverwrittenArchiveColor());
+ settings().colors().setModlistContainsPlugin(getContainsColor());
+ settings().colors().setPluginListContained(getContainedColor());
+
+ settings().interface().setCompactDownloads(ui->compactBox->isChecked());
+ settings().interface().setMetaDownloads(ui->showMetaBox->isChecked());
+ settings().setUsePrereleases(ui->usePrereleaseBox->isChecked());
+ settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked());
}
void GeneralSettingsTab::addLanguages()
@@ -141,7 +145,7 @@ void GeneralSettingsTab::addStyles()
void GeneralSettingsTab::resetDialogs()
{
- QuestionBoxMemory::resetDialogs();
+ settings().widgets().resetQuestionButtons();
}
void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color)
@@ -157,13 +161,13 @@ void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color
.arg(color.green())
.arg(color.blue())
.arg(color.alpha())
- .arg(Settings::getIdealTextColor(color).name())
+ .arg(ColorSettings::idealTextColor(color).name())
);
};
void GeneralSettingsTab::on_containsBtn_clicked()
{
- QColor result = QColorDialog::getColor(m_ContainsColor, parentWidget(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel);
+ QColor result = QColorDialog::getColor(m_ContainsColor, &dialog(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel);
if (result.isValid()) {
m_ContainsColor = result;
setButtonColor(ui->containsBtn, result);
@@ -172,7 +176,7 @@ void GeneralSettingsTab::on_containsBtn_clicked()
void GeneralSettingsTab::on_containedBtn_clicked()
{
- QColor result = QColorDialog::getColor(m_ContainedColor, parentWidget(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel);
+ QColor result = QColorDialog::getColor(m_ContainedColor, &dialog(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel);
if (result.isValid()) {
m_ContainedColor = result;
setButtonColor(ui->containedBtn, result);
@@ -181,7 +185,7 @@ void GeneralSettingsTab::on_containedBtn_clicked()
void GeneralSettingsTab::on_overwrittenBtn_clicked()
{
- QColor result = QColorDialog::getColor(m_OverwrittenColor, parentWidget(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel);
+ QColor result = QColorDialog::getColor(m_OverwrittenColor, &dialog(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel);
if (result.isValid()) {
m_OverwrittenColor = result;
setButtonColor(ui->overwrittenBtn, result);
@@ -190,7 +194,7 @@ void GeneralSettingsTab::on_overwrittenBtn_clicked()
void GeneralSettingsTab::on_overwritingBtn_clicked()
{
- QColor result = QColorDialog::getColor(m_OverwritingColor, parentWidget(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel);
+ QColor result = QColorDialog::getColor(m_OverwritingColor, &dialog(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel);
if (result.isValid()) {
m_OverwritingColor = result;
setButtonColor(ui->overwritingBtn, result);
@@ -199,7 +203,7 @@ void GeneralSettingsTab::on_overwritingBtn_clicked()
void GeneralSettingsTab::on_overwrittenArchiveBtn_clicked()
{
- QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, parentWidget(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel);
+ QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, &dialog(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel);
if (result.isValid()) {
m_OverwrittenArchiveColor = result;
setButtonColor(ui->overwrittenArchiveBtn, result);
@@ -208,7 +212,7 @@ void GeneralSettingsTab::on_overwrittenArchiveBtn_clicked()
void GeneralSettingsTab::on_overwritingArchiveBtn_clicked()
{
- QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, parentWidget(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel);
+ QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, &dialog(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel);
if (result.isValid()) {
m_OverwritingArchiveColor = result;
setButtonColor(ui->overwritingArchiveBtn, result);
@@ -234,7 +238,7 @@ void GeneralSettingsTab::on_resetColorsBtn_clicked()
void GeneralSettingsTab::on_resetDialogsButton_clicked()
{
- if (QMessageBox::question(parentWidget(), QObject::tr("Confirm?"),
+ if (QMessageBox::question(&dialog(), QObject::tr("Confirm?"),
QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
resetDialogs();
@@ -243,7 +247,7 @@ void GeneralSettingsTab::on_resetDialogsButton_clicked()
void GeneralSettingsTab::on_categoriesBtn_clicked()
{
- CategoriesDialog dialog(parentWidget());
+ CategoriesDialog dialog(&dialog());
if (dialog.exec() == QDialog::Accepted) {
dialog.commitChanges();
}
diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h
index c7fcae36..2038ba31 100644
--- a/src/settingsdialoggeneral.h
+++ b/src/settingsdialoggeneral.h
@@ -7,7 +7,7 @@
class GeneralSettingsTab : public SettingsTab
{
public:
- GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dialog);
+ GeneralSettingsTab(Settings& settings, SettingsDialog& dialog);
void update();
diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp
index 575f54d0..0b08f13f 100644
--- a/src/settingsdialognexus.cpp
+++ b/src/settingsdialognexus.cpp
@@ -2,9 +2,11 @@
#include "ui_settingsdialog.h"
#include "ui_nexusmanualkey.h"
#include "nexusinterface.h"
+#include "serverinfo.h"
+#include "log.h"
#include <utility.h>
-namespace shell = MOBase::shell;
+using namespace MOBase;
template <typename T>
class ServerItem : public QListWidgetItem {
@@ -69,39 +71,40 @@ private:
};
-NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog)
- : SettingsTab(parent, dialog)
+NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d)
+ : SettingsTab(s, d)
{
- ui->offlineBox->setChecked(parent->offlineMode());
- ui->proxyBox->setChecked(parent->useProxy());
- ui->endorsementBox->setChecked(parent->endorsementIntegration());
- ui->hideAPICounterBox->setChecked(parent->hideAPICounter());
+ 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
- m_Settings.beginGroup("Servers");
- for (const QString &key : m_Settings.childKeys()) {
- QVariantMap val = m_Settings.value(key).toMap();
- QString descriptor = key;
+ for (const auto& server : s.network().servers()) {
+ QString descriptor = server.name();
+
if (!descriptor.compare("CDN", Qt::CaseInsensitive)) {
descriptor += QStringLiteral(" (automatic)");
}
- if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) {
- int bps = static_cast<int>(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt());
- descriptor += QString(" (%1 kbps)").arg(bps / 1024);
+
+ const auto averageSpeed = server.averageSpeed();
+ if (averageSpeed > 0) {
+ descriptor += QString(" (%1)").arg(MOBase::localizedByteSpeed(averageSpeed));
}
QListWidgetItem *newItem = new ServerItem<int>(descriptor, Qt::UserRole + 1);
- newItem->setData(Qt::UserRole, key);
- newItem->setData(Qt::UserRole + 1, val["preferred"].toInt());
- if (val["preferred"].toInt() > 0) {
+ 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_Settings.endGroup();
QObject::connect(ui->nexusConnect, &QPushButton::clicked, [&]{ on_nexusConnect_clicked(); });
QObject::connect(ui->nexusManualKey, &QPushButton::clicked, [&]{ on_nexusManualKey_clicked(); });
@@ -114,27 +117,57 @@ NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog)
void NexusSettingsTab::update()
{
- m_Settings.setValue("Settings/offline_mode", ui->offlineBox->isChecked());
- m_Settings.setValue("Settings/use_proxy", ui->proxyBox->isChecked());
- m_Settings.setValue("Settings/endorsement_integration", ui->endorsementBox->isChecked());
- m_Settings.setValue("Settings/hide_api_counter", ui->hideAPICounterBox->isChecked());
+ 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
- m_Settings.beginGroup("Servers");
for (int i = 0; i < ui->knownServersList->count(); ++i) {
- QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString();
- QVariantMap val = m_Settings.value(key).toMap();
- val["preferred"] = 0;
- m_Settings.setValue(key, val);
+ 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);
+ }
}
- int count = ui->preferredServersList->count();
+
+ const int count = ui->preferredServersList->count();
+
for (int i = 0; i < count; ++i) {
- QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString();
- QVariantMap val = m_Settings.value(key).toMap();
- val["preferred"] = count - i;
- m_Settings.setValue(key, val);
+ 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);
+ }
}
- m_Settings.endGroup();
+
+ settings().network().updateServers(servers);
}
void NexusSettingsTab::on_nexusConnect_clicked()
@@ -168,12 +201,12 @@ void NexusSettingsTab::on_nexusManualKey_clicked()
return;
}
- NexusManualKeyDialog dialog(parentWidget());
- if (dialog.exec() != QDialog::Accepted) {
+ NexusManualKeyDialog d(&dialog());
+ if (d.exec() != QDialog::Accepted) {
return;
}
- const auto key = dialog.key();
+ const auto key = d.key();
if (key.isEmpty()) {
clearKey();
return;
@@ -192,20 +225,20 @@ void NexusSettingsTab::on_nexusDisconnect_clicked()
void NexusSettingsTab::on_clearCacheButton_clicked()
{
- QDir(Settings::instance().getCacheDirectory()).removeRecursively();
- NexusInterface::instance(m_dialog.m_PluginContainer)->clearCache();
+ QDir(Settings::instance().paths().cache()).removeRecursively();
+ NexusInterface::instance(dialog().m_PluginContainer)->clearCache();
}
void NexusSettingsTab::on_associateButton_clicked()
{
- Settings::instance().registerAsNXMHandler(true);
+ Settings::instance().nexus().registerAsNXMHandler(true);
}
void NexusSettingsTab::validateKey(const QString& key)
{
if (!m_nexusValidator) {
m_nexusValidator.reset(new NexusKeyValidator(
- *NexusInterface::instance(m_dialog.m_PluginContainer)->getAccessManager()));
+ *NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()));
m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){
onValidatorStateChanged(s, e);
@@ -261,7 +294,7 @@ void NexusSettingsTab::onValidatorStateChanged(
void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user)
{
- NexusInterface::instance(m_dialog.m_PluginContainer)->setUserAccount(user);
+ NexusInterface::instance(dialog().m_PluginContainer)->setUserAccount(user);
if (!user.apiKey().isEmpty()) {
if (setKey(user.apiKey())) {
@@ -278,18 +311,18 @@ void NexusSettingsTab::addNexusLog(const QString& s)
bool NexusSettingsTab::setKey(const QString& key)
{
- m_dialog.m_keyChanged = true;
- const bool ret = m_parent->setNexusApiKey(key);
+ dialog().m_keyChanged = true;
+ const bool ret = settings().nexus().setApiKey(key);
updateNexusState();
return ret;
}
bool NexusSettingsTab::clearKey()
{
- m_dialog.m_keyChanged = true;
- const auto ret = m_parent->clearNexusApiKey();
+ dialog().m_keyChanged = true;
+ const auto ret = settings().nexus().clearApiKey();
- NexusInterface::instance(m_dialog.m_PluginContainer)->getAccessManager()->clearApiKey();
+ NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey();
updateNexusState();
return ret;
@@ -319,7 +352,7 @@ void NexusSettingsTab::updateNexusButtons()
ui->nexusManualKey->setText(QObject::tr("Cancel"));
ui->nexusManualKey->setEnabled(true);
}
- else if (m_parent->hasNexusApiKey()) {
+ else if (settings().nexus().hasApiKey()) {
// api key is present
ui->nexusConnect->setText(QObject::tr("Connect to Nexus"));
ui->nexusConnect->setEnabled(false);
@@ -338,7 +371,7 @@ void NexusSettingsTab::updateNexusButtons()
void NexusSettingsTab::updateNexusData()
{
- const auto user = NexusInterface::instance(m_dialog.m_PluginContainer)
+ const auto user = NexusInterface::instance(dialog().m_PluginContainer)
->getAPIUserAccount();
if (user.isValid()) {
diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h
index cca2e1b5..89a6618f 100644
--- a/src/settingsdialognexus.h
+++ b/src/settingsdialognexus.h
@@ -8,7 +8,7 @@
class NexusSettingsTab : public SettingsTab
{
public:
- NexusSettingsTab(Settings *m_parent, SettingsDialog &m_dialog);
+ NexusSettingsTab(Settings& settings, SettingsDialog& dialog);
void update();
private:
diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp
index 6e8fe994..aeb4dd5d 100644
--- a/src/settingsdialogpaths.cpp
+++ b/src/settingsdialogpaths.cpp
@@ -3,20 +3,26 @@
#include "appconfig.h"
#include <iplugingame.h>
-PathsSettingsTab::PathsSettingsTab(Settings *parent, SettingsDialog &dialog)
- : SettingsTab(parent, dialog)
+PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d)
+ : SettingsTab(s, d)
{
- ui->baseDirEdit->setText(m_parent->getBaseDirectory());
- ui->managedGameDirEdit->setText(m_parent->gamePlugin()->gameDirectory().absoluteFilePath(m_parent->gamePlugin()->binaryName()));
- QString basePath = parent->getBaseDirectory();
+ ui->baseDirEdit->setText(settings().paths().base());
+
+ ui->managedGameDirEdit->setText(
+ settings().game().plugin()->gameDirectory().absoluteFilePath(
+ settings().game().plugin()->binaryName()));
+
+ QString basePath = settings().paths().base();
QDir baseDir(basePath);
+
for (const auto &dir : {
- std::make_pair(ui->downloadDirEdit, m_parent->getDownloadDirectory(false)),
- std::make_pair(ui->modDirEdit, m_parent->getModDirectory(false)),
- std::make_pair(ui->cacheDirEdit, m_parent->getCacheDirectory(false)),
- std::make_pair(ui->profilesDirEdit, m_parent->getProfileDirectory(false)),
- std::make_pair(ui->overwriteDirEdit, m_parent->getOverwriteDirectory(false))
+ std::make_pair(ui->downloadDirEdit, settings().paths().downloads(false)),
+ std::make_pair(ui->modDirEdit, settings().paths().mods(false)),
+ std::make_pair(ui->cacheDirEdit, settings().paths().cache(false)),
+ std::make_pair(ui->profilesDirEdit, settings().paths().profiles(false)),
+ std::make_pair(ui->overwriteDirEdit, settings().paths().overwrite(false))
}) {
+
QString storePath = baseDir.relativeFilePath(dir.second);
storePath = dir.second;
dir.first->setText(storePath);
@@ -40,22 +46,22 @@ PathsSettingsTab::PathsSettingsTab(Settings *parent, SettingsDialog &dialog)
void PathsSettingsTab::update()
{
- typedef std::tuple<QString, QString, std::wstring> Directory;
+ using Setter = void (PathSettings::*)(const QString&);
+ using Directory = std::tuple<QString, Setter, std::wstring>;
- QString basePath = m_parent->getBaseDirectory();
+ QString basePath = settings().paths().base();
for (const Directory &dir :{
- Directory{ui->downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()},
- Directory{ui->cacheDirEdit->text(), "cache_directory", AppConfig::cachePath()},
- Directory{ui->modDirEdit->text(), "mod_directory", AppConfig::modsPath()},
- Directory{ui->overwriteDirEdit->text(), "overwrite_directory", AppConfig::overwritePath()},
- Directory{ui->profilesDirEdit->text(), "profiles_directory", AppConfig::profilesPath()}
+ Directory{ui->downloadDirEdit->text(), &PathSettings::setDownloads, AppConfig::downloadPath()},
+ Directory{ui->cacheDirEdit->text(), &PathSettings::setCache, AppConfig::cachePath()},
+ Directory{ui->modDirEdit->text(), &PathSettings::setMods, AppConfig::modsPath()},
+ Directory{ui->overwriteDirEdit->text(), &PathSettings::setOverwrite, AppConfig::overwritePath()},
+ Directory{ui->profilesDirEdit->text(), &PathSettings::setProfiles, AppConfig::profilesPath()}
}) {
- QString path, settingsKey;
+ QString path;
+ Setter setter;
std::wstring defaultName;
- std::tie(path, settingsKey, defaultName) = dir;
-
- settingsKey = QString("Settings/%1").arg(settingsKey);
+ std::tie(path, setter, defaultName) = dir;
QString realPath = path;
realPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
@@ -69,32 +75,34 @@ void PathsSettingsTab::update()
}
}
- if (QFileInfo(realPath)
- != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) {
- m_Settings.setValue(settingsKey, path);
+ if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) {
+ (settings().paths().*setter)(path);
} else {
- m_Settings.remove(settingsKey);
+ (settings().paths().*setter)("");
}
}
- if (QFileInfo(ui->baseDirEdit->text()) !=
- QFileInfo(qApp->property("dataPath").toString())) {
- m_Settings.setValue("Settings/base_directory", ui->baseDirEdit->text());
+ if (QFileInfo(ui->baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) {
+ settings().paths().setBase(ui->baseDirEdit->text());
} else {
- m_Settings.remove("Settings/base_directory");
+ settings().paths().setBase("");
}
- QFileInfo oldGameExe(m_parent->gamePlugin()->gameDirectory().absoluteFilePath(m_parent->gamePlugin()->binaryName()));
+ QFileInfo oldGameExe(
+ settings().game().plugin()->gameDirectory().absoluteFilePath(
+ settings().game().plugin()->binaryName()));
+
QFileInfo newGameExe(ui->managedGameDirEdit->text());
+
if (oldGameExe != newGameExe) {
- m_Settings.setValue("gamePath", newGameExe.absolutePath());
+ settings().game().setDirectory(newGameExe.absolutePath());
}
}
void PathsSettingsTab::on_browseBaseDirBtn_clicked()
{
QString temp = QFileDialog::getExistingDirectory(
- parentWidget(), QObject::tr("Select base directory"), ui->baseDirEdit->text());
+ &dialog(), QObject::tr("Select base directory"), ui->baseDirEdit->text());
if (!temp.isEmpty()) {
ui->baseDirEdit->setText(temp);
}
@@ -105,7 +113,7 @@ void PathsSettingsTab::on_browseDownloadDirBtn_clicked()
QString searchPath = ui->downloadDirEdit->text();
searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
- QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select download directory"), searchPath);
+ QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select download directory"), searchPath);
if (!temp.isEmpty()) {
ui->downloadDirEdit->setText(temp);
}
@@ -116,7 +124,7 @@ void PathsSettingsTab::on_browseModDirBtn_clicked()
QString searchPath = ui->modDirEdit->text();
searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
- QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select mod directory"), searchPath);
+ QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select mod directory"), searchPath);
if (!temp.isEmpty()) {
ui->modDirEdit->setText(temp);
}
@@ -127,7 +135,7 @@ void PathsSettingsTab::on_browseCacheDirBtn_clicked()
QString searchPath = ui->cacheDirEdit->text();
searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
- QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select cache directory"), searchPath);
+ QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select cache directory"), searchPath);
if (!temp.isEmpty()) {
ui->cacheDirEdit->setText(temp);
}
@@ -138,7 +146,7 @@ void PathsSettingsTab::on_browseProfilesDirBtn_clicked()
QString searchPath = ui->profilesDirEdit->text();
searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
- QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select profiles directory"), searchPath);
+ QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select profiles directory"), searchPath);
if (!temp.isEmpty()) {
ui->profilesDirEdit->setText(temp);
}
@@ -149,7 +157,7 @@ void PathsSettingsTab::on_browseOverwriteDirBtn_clicked()
QString searchPath = ui->overwriteDirEdit->text();
searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text());
- QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select overwrite directory"), searchPath);
+ QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select overwrite directory"), searchPath);
if (!temp.isEmpty()) {
ui->overwriteDirEdit->setText(temp);
}
@@ -159,7 +167,7 @@ void PathsSettingsTab::on_browseGameDirBtn_clicked()
{
QFileInfo oldGameExe(ui->managedGameDirEdit->text());
- QString temp = QFileDialog::getOpenFileName(parentWidget(), QObject::tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName());
+ QString temp = QFileDialog::getOpenFileName(&dialog(), QObject::tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName());
if (!temp.isEmpty()) {
ui->managedGameDirEdit->setText(temp);
}
diff --git a/src/settingsdialogpaths.h b/src/settingsdialogpaths.h
index f661b624..a2073188 100644
--- a/src/settingsdialogpaths.h
+++ b/src/settingsdialogpaths.h
@@ -7,8 +7,7 @@
class PathsSettingsTab : public SettingsTab
{
public:
- PathsSettingsTab(Settings *parent, SettingsDialog &dialog);
-
+ PathsSettingsTab(Settings& settings, SettingsDialog& dialog);
void update();
private:
diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp
index 53b28fcc..c84d0556 100644
--- a/src/settingsdialogplugins.cpp
+++ b/src/settingsdialogplugins.cpp
@@ -5,26 +5,26 @@
using MOBase::IPlugin;
-PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog)
- : SettingsTab(m_parent, m_dialog)
+PluginsSettingsTab::PluginsSettingsTab(Settings& s, SettingsDialog& d)
+ : SettingsTab(s, d)
{
ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}");
// display plugin settings
QSet<QString> handledNames;
- for (IPlugin *plugin : m_parent->plugins()) {
+ for (IPlugin *plugin : settings().plugins().plugins()) {
if (handledNames.contains(plugin->name()))
continue;
QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), ui->pluginsList);
listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin));
- listItem->setData(Qt::UserRole + 1, m_parent->m_PluginSettings[plugin->name()]);
- listItem->setData(Qt::UserRole + 2, m_parent->m_PluginDescriptions[plugin->name()]);
+ listItem->setData(Qt::UserRole + 1, settings().plugins().settings(plugin->name()));
+ listItem->setData(Qt::UserRole + 2, settings().plugins().descriptions(plugin->name()));
ui->pluginsList->addItem(listItem);
handledNames.insert(plugin->name());
}
// display plugin blacklist
- for (const QString &pluginName : m_parent->m_PluginBlacklist) {
+ for (const QString &pluginName : settings().plugins().blacklist()) {
ui->pluginBlacklist->addItem(pluginName);
}
@@ -34,7 +34,7 @@ PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dia
QShortcut *delShortcut = new QShortcut(
QKeySequence(Qt::Key_Delete), ui->pluginBlacklist);
- QObject::connect(delShortcut, &QShortcut::activated, parentWidget(), [&]{ deleteBlacklistItem(); });
+ QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&]{ deleteBlacklistItem(); });
}
void PluginsSettingsTab::update()
@@ -42,21 +42,19 @@ void PluginsSettingsTab::update()
// transfer plugin settings to in-memory structure
for (int i = 0; i < ui->pluginsList->count(); ++i) {
QListWidgetItem *item = ui->pluginsList->item(i);
- m_parent->m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap();
- }
- // store plugin settings on disc
- for (auto iterPlugins = m_parent->m_PluginSettings.begin(); iterPlugins != m_parent->m_PluginSettings.end(); ++iterPlugins) {
- for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) {
- m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value());
- }
+ settings().plugins().setSettings(
+ item->text(), item->data(Qt::UserRole + 1).toMap());
}
- // store plugin blacklist
- m_parent->m_PluginBlacklist.clear();
+ // set plugin blacklist
+ QStringList names;
for (QListWidgetItem *item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) {
- m_parent->m_PluginBlacklist.insert(item->text());
+ names.push_back(item->text());
}
- m_parent->writePluginBlacklist();
+
+ settings().plugins().setBlacklist(names);
+
+ settings().plugins().save();
}
void PluginsSettingsTab::closing()
diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h
index 9d21daa6..8e2dae2a 100644
--- a/src/settingsdialogplugins.h
+++ b/src/settingsdialogplugins.h
@@ -7,7 +7,7 @@
class PluginsSettingsTab : public SettingsTab
{
public:
- PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog);
+ PluginsSettingsTab(Settings& settings, SettingsDialog& dialog);
void update();
void closing() override;
diff --git a/src/settingsdialogsteam.cpp b/src/settingsdialogsteam.cpp
index 34c2d76b..3c4c5de6 100644
--- a/src/settingsdialogsteam.cpp
+++ b/src/settingsdialogsteam.cpp
@@ -1,11 +1,11 @@
#include "settingsdialogsteam.h"
#include "ui_settingsdialog.h"
-SteamSettingsTab::SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog)
- : SettingsTab(m_parent, m_dialog)
+SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d)
+ : SettingsTab(s, d)
{
QString username, password;
- m_parent->getSteamLogin(username, password);
+ settings().steam().login(username, password);
ui->steamUserEdit->setText(username);
ui->steamPassEdit->setText(password);
@@ -13,5 +13,5 @@ SteamSettingsTab::SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog)
void SteamSettingsTab::update()
{
- m_parent->setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text());
+ settings().steam().setLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text());
}
diff --git a/src/settingsdialogsteam.h b/src/settingsdialogsteam.h
index dbd85151..6a3d75f4 100644
--- a/src/settingsdialogsteam.h
+++ b/src/settingsdialogsteam.h
@@ -7,11 +7,8 @@
class SteamSettingsTab : public SettingsTab
{
public:
- SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog);
-
+ SteamSettingsTab(Settings& settings, SettingsDialog& dialog);
void update();
-
-private:
};
#endif // SETTINGSDIALOGSTEAM_H
diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp
index 9ac46ac1..4d811e40 100644
--- a/src/settingsdialogworkarounds.cpp
+++ b/src/settingsdialogworkarounds.cpp
@@ -3,15 +3,15 @@
#include "helper.h"
#include <iplugingame.h>
-WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog)
- : SettingsTab(m_parent, m_dialog)
+WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d)
+ : SettingsTab(s, d)
{
- ui->appIDEdit->setText(m_parent->getSteamAppID());
+ ui->appIDEdit->setText(settings().steam().appID());
- LoadMechanism::EMechanism mechanismID = m_parent->getLoadMechanism();
+ LoadMechanism::EMechanism mechanismID = settings().game().loadMechanismType();
int index = 0;
- if (m_parent->loadMechanism().isDirectLoadingSupported()) {
+ if (settings().game().loadMechanism().isDirectLoadingSupported()) {
ui->mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER);
if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) {
index = ui->mechanismBox->count() - 1;
@@ -20,15 +20,13 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo
ui->mechanismBox->setCurrentIndex(index);
- ui->hideUncheckedBox->setChecked(m_parent->hideUncheckedPlugins());
- ui->forceEnableBox->setChecked(m_parent->forceEnableCoreFiles());
- ui->displayForeignBox->setChecked(m_parent->displayForeign());
- ui->lockGUIBox->setChecked(m_parent->lockGUI());
- ui->enableArchiveParsingBox->setChecked(m_parent->archiveParsing());
+ ui->hideUncheckedBox->setChecked(settings().game().hideUncheckedPlugins());
+ ui->forceEnableBox->setChecked(settings().game().forceEnableCoreFiles());
+ ui->displayForeignBox->setChecked(settings().interface().displayForeign());
+ ui->lockGUIBox->setChecked(settings().interface().lockGUI());
+ ui->enableArchiveParsingBox->setChecked(settings().archiveParsing());
- ui->resetGeometryBtn->setChecked(m_parent->directInterface().value("reset_geometry", false).toBool());
-
- setExecutableBlacklist(m_parent->executablesBlacklist());
+ setExecutableBlacklist(settings().executablesBlacklist());
QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); });
QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); });
@@ -37,26 +35,28 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo
void WorkaroundsSettingsTab::update()
{
- if (ui->appIDEdit->text() != m_parent->gamePlugin()->steamAPPId()) {
- m_Settings.setValue("Settings/app_id", ui->appIDEdit->text());
+ if (ui->appIDEdit->text() != settings().game().plugin()->steamAPPId()) {
+ settings().steam().setAppID(ui->appIDEdit->text());
} else {
- m_Settings.remove("Settings/app_id");
+ settings().steam().setAppID("");
}
- m_Settings.setValue("Settings/load_mechanism", ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt());
- m_Settings.setValue("Settings/hide_unchecked_plugins", ui->hideUncheckedBox->isChecked());
- m_Settings.setValue("Settings/force_enable_core_files", ui->forceEnableBox->isChecked());
- m_Settings.setValue("Settings/display_foreign", ui->displayForeignBox->isChecked());
- m_Settings.setValue("Settings/lock_gui", ui->lockGUIBox->isChecked());
- m_Settings.setValue("Settings/archive_parsing_experimental", ui->enableArchiveParsingBox->isChecked());
- m_Settings.setValue("Settings/executable_blacklist", getExecutableBlacklist());
+ settings().game().setLoadMechanism(static_cast<LoadMechanism::EMechanism>(
+ ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt()));
+
+ settings().game().setHideUncheckedPlugins(ui->hideUncheckedBox->isChecked());
+ settings().game().setForceEnableCoreFiles(ui->forceEnableBox->isChecked());
+ settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked());
+ settings().interface().setLockGUI(ui->lockGUIBox->isChecked());
+ settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked());
+ settings().setExecutablesBlacklist(getExecutableBlacklist());
}
void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked()
{
bool ok = false;
QString result = QInputDialog::getMultiLineText(
- parentWidget(),
+ &dialog(),
QObject::tr("Executables Blacklist"),
QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n"
"Mods and other virtualized files will not be visible to these executables and\n"
@@ -89,6 +89,16 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked()
void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked()
{
- m_dialog.m_GeometriesReset = true;
- ui->resetGeometryBtn->setChecked(true);
+ const auto caption = QObject::tr("Restart Mod Organizer?");
+ const auto text = QObject::tr(
+ "In order to reset the geometry, Mod Organizer must be restarted.\n"
+ "Restart now?");
+
+ const auto res = QMessageBox::question(
+ nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel);
+
+ if (res == QMessageBox::Yes) {
+ settings().geometry().requestReset();
+ qApp->exit(INT_MAX);
+ }
}
diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h
index 1687624b..d5d6815f 100644
--- a/src/settingsdialogworkarounds.h
+++ b/src/settingsdialogworkarounds.h
@@ -7,8 +7,7 @@
class WorkaroundsSettingsTab : public SettingsTab
{
public:
- WorkaroundsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog);
-
+ WorkaroundsSettingsTab(Settings& settings, SettingsDialog& dialog);
void update();
private:
diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp
new file mode 100644
index 00000000..7a9dcc35
--- /dev/null
+++ b/src/settingsutilities.cpp
@@ -0,0 +1,267 @@
+#include "settingsutilities.h"
+#include "expanderwidget.h"
+#include <utility.h>
+
+using namespace MOBase;
+
+bool shouldLogSetting(const QString& displayName)
+{
+ // don't log Geometry/ and Widgets/, too noisy and not very useful
+ static const QStringList ignorePrefixes = {"Geometry/", "Widgets/"};
+
+ for (auto&& prefix : ignorePrefixes) {
+ if (displayName.startsWith(prefix, Qt::CaseInsensitive)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void logRemoval(const QString& name)
+{
+ if (!shouldLogSetting(name)) {
+ return;
+ }
+
+ log::debug("setting '{}' removed", name);
+}
+
+
+QString settingName(const QString& section, const QString& key)
+{
+ if (section.isEmpty()) {
+ return key;
+ } else if (key.isEmpty()) {
+ return section;
+ } else {
+ if (section.compare("General", Qt::CaseInsensitive) == 0) {
+ return key;
+ } else {
+ return section + "/" + key;
+ }
+ }
+}
+
+void removeImpl(
+ QSettings& settings, const QString& displayName,
+ const QString& section, const QString& key)
+{
+ if (key.isEmpty()) {
+ if (!settings.childGroups().contains(section, Qt::CaseInsensitive)) {
+ // not there
+ return;
+ }
+ } else {
+ if (!settings.contains(settingName(section, key))) {
+ // not there
+ return;
+ }
+ }
+
+ logRemoval(displayName);
+ settings.remove(settingName(section, key));
+}
+
+void remove(QSettings& settings, const QString& section, const QString& key)
+{
+ removeImpl(settings, settingName(section, key), section, key);
+}
+
+void removeSection(QSettings& settings, const QString& section)
+{
+ removeImpl(settings, section, section, "");
+}
+
+
+ScopedGroup::ScopedGroup(QSettings& s, const QString& name)
+ : m_settings(s), m_name(name)
+{
+ m_settings.beginGroup(m_name);
+}
+
+ScopedGroup::~ScopedGroup()
+{
+ m_settings.endGroup();
+}
+
+void ScopedGroup::remove(const QString& key)
+{
+ removeImpl(m_settings, settingName(m_name, key), "", key);
+}
+
+QStringList ScopedGroup::keys() const
+{
+ return m_settings.childKeys();
+}
+
+
+ScopedReadArray::ScopedReadArray(QSettings& s, const QString& section)
+ : m_settings(s), m_count(0)
+{
+ m_count = m_settings.beginReadArray(section);
+}
+
+ScopedReadArray::~ScopedReadArray()
+{
+ m_settings.endArray();
+}
+
+int ScopedReadArray::count() const
+{
+ return m_count;
+}
+
+QStringList ScopedReadArray::keys() const
+{
+ return m_settings.childKeys();
+}
+
+
+ScopedWriteArray::ScopedWriteArray(
+ QSettings& s, const QString& section, std::size_t size)
+ : m_settings(s), m_section(section), m_i(0)
+{
+ m_settings.beginWriteArray(
+ section, size == NoSize ? -1 : static_cast<int>(size));
+}
+
+ScopedWriteArray::~ScopedWriteArray()
+{
+ m_settings.endArray();
+}
+
+void ScopedWriteArray::next()
+{
+ m_settings.setArrayIndex(m_i);
+ ++m_i;
+}
+
+
+QString widgetNameWithTopLevel(const QWidget* widget)
+{
+ QStringList components;
+
+ auto* tl = widget->window();
+
+ if (tl == widget) {
+ // this is a top level widget, such as a dialog
+ components.push_back(widget->objectName());
+ } else {
+ // this is a widget
+ const auto toplevelName = tl->objectName();
+ if (!toplevelName.isEmpty()) {
+ components.push_back(toplevelName);
+ }
+
+ const auto widgetName = widget->objectName();
+ if (!widgetName.isEmpty()) {
+ components.push_back(widgetName);
+ }
+ }
+
+ if (components.isEmpty()) {
+ // can't do much
+ return "unknown_widget";
+ }
+
+ return components.join("_");
+}
+
+QString widgetName(const QMainWindow* w)
+{
+ return w->objectName();
+}
+
+QString widgetName(const QHeaderView* w)
+{
+ return widgetNameWithTopLevel(w->parentWidget());
+}
+
+QString widgetName(const ExpanderWidget* w)
+{
+ return widgetNameWithTopLevel(w->button());
+}
+
+QString widgetName(const QWidget* w)
+{
+ return widgetNameWithTopLevel(w);
+}
+
+QString dockSettingName(const QDockWidget* dock)
+{
+ return "MainWindow_docks_" + dock->objectName() + "_size";
+}
+
+QString indexSettingName(const QWidget* widget)
+{
+ return widgetNameWithTopLevel(widget) + "_index";
+}
+
+QString checkedSettingName(const QAbstractButton* b)
+{
+ return widgetNameWithTopLevel(b) + "_checked";
+}
+
+void warnIfNotCheckable(const QAbstractButton* b)
+{
+ if (!b->isCheckable()) {
+ log::warn(
+ "button '{}' used in the settings as a checkbox or radio button "
+ "but is not checkable", b->objectName());
+ }
+}
+
+
+bool setWindowsCredential(const QString key, const QString data)
+{
+ QString finalKey("ModOrganizer2_" + key);
+ wchar_t* keyData = new wchar_t[finalKey.size()+1];
+ finalKey.toWCharArray(keyData);
+ keyData[finalKey.size()] = L'\0';
+ bool result = false;
+ if (data.isEmpty()) {
+ result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0);
+ if (!result)
+ if (GetLastError() == ERROR_NOT_FOUND)
+ result = true;
+ } else {
+ wchar_t* charData = new wchar_t[data.size()];
+ data.toWCharArray(charData);
+
+ CREDENTIALW cred = {};
+ cred.Flags = 0;
+ cred.Type = CRED_TYPE_GENERIC;
+ cred.TargetName = keyData;
+ cred.CredentialBlob = (LPBYTE)charData;
+ cred.CredentialBlobSize = sizeof(wchar_t) * data.size();
+ cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
+
+ result = CredWriteW(&cred, 0);
+ delete[] charData;
+ }
+ delete[] keyData;
+ return result;
+}
+
+QString getWindowsCredential(const QString key)
+{
+ QString result;
+ QString finalKey("ModOrganizer2_" + key);
+ wchar_t* keyData = new wchar_t[finalKey.size()+1];
+ finalKey.toWCharArray(keyData);
+ keyData[finalKey.size()] = L'\0';
+ PCREDENTIALW creds;
+ if (CredReadW(keyData, 1, 0, &creds)) {
+ wchar_t *charData = (wchar_t *)creds->CredentialBlob;
+ result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t));
+ CredFree(creds);
+ } else {
+ const auto e = GetLastError();
+ if (e != ERROR_NOT_FOUND) {
+ log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e));
+ }
+ }
+ delete[] keyData;
+ return result;
+}
diff --git a/src/settingsutilities.h b/src/settingsutilities.h
new file mode 100644
index 00000000..d99abb06
--- /dev/null
+++ b/src/settingsutilities.h
@@ -0,0 +1,274 @@
+#ifndef SETTINGSUTILITIES_H
+#define SETTINGSUTILITIES_H
+
+#include <log.h>
+
+class ExpanderWidget;
+
+template <class T, class=void>
+struct ValueConverter
+{
+ static const T& convert(const T& t)
+ {
+ return t;
+ }
+};
+
+template <class T>
+struct ValueConverter<T, std::enable_if_t<std::is_enum_v<T>>>
+{
+ static QString convert(const T& t)
+ {
+ return QString("%1").arg(static_cast<std::underlying_type_t<T>>(t));
+ }
+};
+
+
+bool shouldLogSetting(const QString& displayName);
+
+template <class T>
+void logChange(
+ const QString& displayName, std::optional<T> oldValue, const T& newValue)
+{
+ if (!shouldLogSetting(displayName)) {
+ return;
+ }
+
+ using VC = ValueConverter<T>;
+
+ if (oldValue) {
+ log::debug(
+ "setting '{}' changed from '{}' to '{}'",
+ displayName, VC::convert(*oldValue), VC::convert(newValue));
+ } else {
+ log::debug(
+ "setting '{}' set to '{}'",
+ displayName, VC::convert(newValue));
+ }
+}
+
+void logRemoval(const QString& name);
+
+
+QString settingName(const QString& section, const QString& key);
+
+template <class T>
+void setImpl(
+ QSettings& settings, const QString& displayName,
+ const QString& section, const QString& key, const T& value)
+{
+ const auto current = getOptional<T>(settings, section, key);
+
+ if (current && *current == value) {
+ // no change
+ return;
+ }
+
+ const auto name = settingName(section, key);
+
+ logChange(displayName, current, value);
+
+ if constexpr (std::is_enum_v<T>) {
+ settings.setValue(
+ name, static_cast<std::underlying_type_t<T>>(value));
+ } else {
+ settings.setValue(name, value);
+ }
+}
+
+void removeImpl(
+ QSettings& settings, const QString& displayName,
+ const QString& section, const QString& key);
+
+
+template <class T>
+std::optional<T> getOptional(
+ const QSettings& settings,
+ const QString& section, const QString& key, std::optional<T> def={})
+{
+ if (settings.contains(settingName(section, key))) {
+ const auto v = settings.value(settingName(section, key));
+
+ if constexpr (std::is_enum_v<T>) {
+ return static_cast<T>(v.value<std::underlying_type_t<T>>());
+ } else {
+ return v.value<T>();
+ }
+ }
+
+ return def;
+}
+
+template <class T>
+T get(
+ const QSettings& settings,
+ const QString& section, const QString& key, T def)
+{
+ if (auto v=getOptional<T>(settings, section, key)) {
+ return *v;
+ } else {
+ return def;
+ }
+}
+
+template <class T>
+void set(
+ QSettings& settings,
+ const QString& section, const QString& key, const T& value)
+{
+ setImpl(settings, settingName(section, key), section, key, value);
+}
+
+void remove(QSettings& settings, const QString& section, const QString& key);
+void removeSection(QSettings& settings, const QString& section);
+
+
+class ScopedGroup
+{
+public:
+ ScopedGroup(QSettings& s, const QString& name);
+ ~ScopedGroup();
+
+ ScopedGroup(const ScopedGroup&) = delete;
+ ScopedGroup& operator=(const ScopedGroup&) = delete;
+
+ template <class T>
+ void set(const QString& key, const T& value)
+ {
+ setImpl(m_settings, settingName(m_name, key), "", key, value);
+ }
+
+ void remove(const QString& key);
+
+ QStringList keys() const;
+
+ template <class F>
+ void for_each(F&& f) const
+ {
+ for (const QString& key : keys()) {
+ f(key);
+ }
+ }
+
+ template <class T>
+ std::optional<T> getOptional(const QString& key, std::optional<T> def={}) const
+ {
+ return ::getOptional<T>(m_settings, "", key, def);
+ }
+
+ template <class T>
+ T get(const QString& key, T def={}) const
+ {
+ return ::get<T>(m_settings, "", key, def);
+ }
+
+private:
+ QSettings& m_settings;
+ QString m_name;
+};
+
+
+class ScopedReadArray
+{
+public:
+ ScopedReadArray(QSettings& s, const QString& section);
+ ~ScopedReadArray();
+
+ ScopedReadArray(const ScopedReadArray&) = delete;
+ ScopedReadArray& operator=(const ScopedReadArray&) = delete;
+
+ template <class F>
+ void for_each(F&& f) const
+ {
+ for (int i=0; i<count(); ++i) {
+ m_settings.setArrayIndex(i);
+ f();
+ }
+ }
+
+ template <class T>
+ std::optional<T> getOptional(const QString& key, std::optional<T> def={}) const
+ {
+ return ::getOptional<T>(m_settings, "", key, def);
+ }
+
+ template <class T>
+ T get(const QString& key, T def={}) const
+ {
+ return ::get<T>(m_settings, "", key, def);
+ }
+
+ int count() const;
+ QStringList keys() const;
+
+private:
+ QSettings& m_settings;
+ int m_count;
+};
+
+
+class ScopedWriteArray
+{
+public:
+ static const auto NoSize = std::numeric_limits<std::size_t>::max();
+
+ ScopedWriteArray(QSettings& s, const QString& section, std::size_t size=NoSize);
+ ~ScopedWriteArray();
+
+ ScopedWriteArray(const ScopedWriteArray&) = delete;
+ ScopedWriteArray& operator=(const ScopedWriteArray&) = delete;
+
+ void next();
+
+ template <class T>
+ void set(const QString& key, const T& value)
+ {
+ const auto displayName = QString("%1/%2\\%3")
+ .arg(m_section)
+ .arg(m_i)
+ .arg(key);
+
+ setImpl(m_settings, displayName, "", key, value);
+ }
+
+private:
+ QSettings& m_settings;
+ QString m_section;
+ int m_i;
+};
+
+
+QString widgetNameWithTopLevel(const QWidget* widget);
+QString widgetName(const QMainWindow* w);
+QString widgetName(const QHeaderView* w);
+QString widgetName(const ExpanderWidget* w);
+QString widgetName(const QWidget* w);
+
+template <class Widget>
+QString geoSettingName(const Widget* widget)
+{
+ return widgetName(widget) + "_geometry";
+}
+
+template <class Widget>
+QString stateSettingName(const Widget* widget)
+{
+ return widgetName(widget) + "_state";
+}
+
+template <class Widget>
+QString visibilitySettingName(const Widget* widget)
+{
+ return widgetName(widget) + "_visibility";
+}
+
+QString dockSettingName(const QDockWidget* dock);
+QString indexSettingName(const QWidget* widget);
+QString checkedSettingName(const QAbstractButton* b);
+
+void warnIfNotCheckable(const QAbstractButton* b);
+
+bool setWindowsCredential(const QString key, const QString data);
+QString getWindowsCredential(const QString key);
+
+#endif // SETTINGSUTILITIES_H
diff --git a/src/statusbar.cpp b/src/statusbar.cpp
index e9a6e658..3734aa87 100644
--- a/src/statusbar.cpp
+++ b/src/statusbar.cpp
@@ -3,26 +3,32 @@
#include "settings.h"
#include "ui_mainwindow.h"
-StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) :
- m_bar(bar), m_progress(new QProgressBar),
- m_notifications(new StatusBarAction(ui->actionNotifications)),
- m_update(new StatusBarAction(ui->actionUpdate)),
- m_api(new QLabel)
+StatusBar::StatusBar(QWidget* parent) :
+ QStatusBar(parent), ui(nullptr), m_progress(new QProgressBar),
+ m_notifications(nullptr), m_update(nullptr), m_api(new QLabel)
{
+}
+
+void StatusBar::setup(Ui::MainWindow* mainWindowUI)
+{
+ ui = mainWindowUI;
+ m_notifications = new StatusBarAction(ui->actionNotifications);
+ m_update = new StatusBarAction(ui->actionUpdate);
+
QWidget* spacer1 = new QWidget;
spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
spacer1->setHidden(true);
spacer1->setVisible(true);
- m_bar->addPermanentWidget(spacer1, 0);
- m_bar->addPermanentWidget(m_progress);
+ addPermanentWidget(spacer1, 0);
+ addPermanentWidget(m_progress);
QWidget* spacer2 = new QWidget;
spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
spacer2->setHidden(true);
spacer2->setVisible(true);
- m_bar->addPermanentWidget(spacer2,0);
- m_bar->addPermanentWidget(m_notifications);
- m_bar->addPermanentWidget(m_update);
- m_bar->addPermanentWidget(m_api);
+ addPermanentWidget(spacer2,0);
+ addPermanentWidget(m_notifications);
+ addPermanentWidget(m_update);
+ addPermanentWidget(m_api);
m_progress->setTextVisible(true);
@@ -42,7 +48,7 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) :
"be unable to queue downloads, check updates, parse mod info, or even log "
"in. Both pools must be consumed before this happens."));
- m_bar->clearMessage();
+ clearMessage();
setProgress(-1);
setAPI({}, {});
}
@@ -50,10 +56,10 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) :
void StatusBar::setProgress(int percent)
{
if (percent < 0 || percent >= 100) {
- m_bar->clearMessage();
+ clearMessage();
m_progress->setVisible(false);
} else {
- m_bar->showMessage(QObject::tr("Loading..."));
+ showMessage(QObject::tr("Loading..."));
m_progress->setVisible(true);
m_progress->setValue(percent);
}
@@ -123,7 +129,38 @@ void StatusBar::setUpdateAvailable(bool b)
void StatusBar::checkSettings(const Settings& settings)
{
- m_api->setVisible(!settings.hideAPICounter());
+ m_api->setVisible(!settings.interface().hideAPICounter());
+}
+
+void StatusBar::showEvent(QShowEvent*)
+{
+ visibilityChanged(true);
+}
+
+void StatusBar::hideEvent(QHideEvent*)
+{
+ visibilityChanged(false);
+}
+
+void StatusBar::visibilityChanged(bool visible)
+{
+ // the central widget typically has no bottom padding because the status bar
+ // is more than enough, but when it's hidden, the bottom widget (currently
+ // the log) touches the bottom border of the window, which looks ugly
+ //
+ // when hiding the statusbar, the central widget is given the same border
+ // margin as it has on the top (which is typically 6, as it's the default from
+ // the qt designer)
+
+ auto m = ui->centralWidget->layout()->contentsMargins();
+
+ if (visible) {
+ m.setBottom(0);
+ } else {
+ m.setBottom(m.top());
+ }
+
+ ui->centralWidget->layout()->setContentsMargins(m);
}
diff --git a/src/statusbar.h b/src/statusbar.h
index 2baf12ee..442b9acf 100644
--- a/src/statusbar.h
+++ b/src/statusbar.h
@@ -29,10 +29,12 @@ private:
};
-class StatusBar
+class StatusBar : public QStatusBar
{
public:
- StatusBar(QStatusBar* bar, Ui::MainWindow* ui);
+ StatusBar(QWidget* parent=nullptr);
+
+ void setup(Ui::MainWindow* ui);
void setProgress(int percent);
void setNotifications(bool hasNotifications);
@@ -40,12 +42,18 @@ public:
void setUpdateAvailable(bool b);
void checkSettings(const Settings& settings);
+protected:
+ void showEvent(QShowEvent* e);
+ void hideEvent(QHideEvent* e);
+
private:
- QStatusBar* m_bar;
+ Ui::MainWindow* ui;
QProgressBar* m_progress;
StatusBarAction* m_notifications;
StatusBarAction* m_update;
QLabel* m_api;
+
+ void visibilityChanged(bool visible);
};
#endif // MO_STATUSBAR_H
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp
index b5e6edb1..4315ed92 100644
--- a/src/usvfsconnector.cpp
+++ b/src/usvfsconnector.cpp
@@ -163,8 +163,8 @@ QString toString(CrashDumpsType t)
UsvfsConnector::UsvfsConnector()
{
USVFSParameters params;
- LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel());
- CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType());
+ LogLevel level = toUsvfsLogLevel(Settings::instance().diagnostics().logLevel());
+ CrashDumpsType dumpType = Settings::instance().diagnostics().crashDumpsType();
std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true);
USVFSInitParameters(&params, SHMID, false, level, dumpType, dumpPath.c_str());
@@ -249,9 +249,10 @@ void UsvfsConnector::updateMapping(const MappingType &mapping)
}
void UsvfsConnector::updateParams(
- MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist)
+ MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType,
+ QString executableBlacklist)
{
- USVFSUpdateParams(toUsvfsLogLevel(logLevel), ::crashDumpsType(crashDumpsType));
+ USVFSUpdateParams(toUsvfsLogLevel(logLevel), crashDumpsType);
ClearExecutableBlacklist();
for (auto exec : executableBlacklist.split(";")) {
std::wstring buf = exec.toStdWString();
diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h
index b0bd320c..cd5d56b2 100644
--- a/src/usvfsconnector.h
+++ b/src/usvfsconnector.h
@@ -87,7 +87,7 @@ public:
void updateMapping(const MappingType &mapping);
void updateParams(
- MOBase::log::Levels logLevel, int crashDumpsType,
+ MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType,
QString executableBlacklist);
void updateForcedLibraries(const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries);