/*
Copyright (C) 2012 Sebastian Herbord. All rights reserved.
This file is part of Mod Organizer.
Mod Organizer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Mod Organizer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see .
*/
#include "settings.h"
#include "env.h"
#include "envmetrics.h"
#include "executableslist.h"
#include "instancemanager.h"
#include "modelutils.h"
#include "serverinfo.h"
#include "settingsutilities.h"
#include "shared/appconfig.h"
#include
#include
#include
#include
#include
using namespace MOBase;
using namespace MOShared;
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 QString& path, bool globalInstance)
: m_Settings(path, QSettings::IniFormat), m_Game(m_Settings),
m_Geometry(m_Settings), m_Widgets(m_Settings, globalInstance),
m_Colors(m_Settings), m_Plugins(m_Settings), m_Paths(m_Settings),
m_Network(m_Settings, globalInstance), m_Nexus(*this, m_Settings),
m_Steam(*this, m_Settings), m_Interface(m_Settings), m_Diagnostics(m_Settings)
{
if (globalInstance) {
if (s_Instance != nullptr) {
throw std::runtime_error("second instance of \"Settings\" created");
} else {
s_Instance = this;
}
}
}
Settings::~Settings()
{
if (s_Instance == this) {
MOBase::QuestionBoxMemory::setCallbacks({}, {}, {});
s_Instance = nullptr;
}
}
Settings& Settings::instance()
{
if (s_Instance == nullptr) {
throw std::runtime_error("no instance of \"Settings\"");
}
return *s_Instance;
}
Settings* Settings::maybeInstance()
{
return s_Instance;
}
void Settings::processUpdates(const QVersionNumber& currentVersion,
const QVersionNumber& lastVersion)
{
if (firstStart()) {
set(m_Settings, "General", "version", currentVersion.toString());
return;
}
if (currentVersion == lastVersion) {
return;
}
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();
});
version({2, 4, 0}, [&] {
// removed
remove(m_Settings, "Settings", "hide_unchecked_plugins");
remove(m_Settings, "Settings", "load_mechanism");
});
// save version in all case
set(m_Settings, "General", "version", currentVersion.toString());
log::debug("updating done");
}
QString Settings::filename() const
{
return m_Settings.fileName();
}
bool Settings::checkForUpdates() const
{
return get(m_Settings, "Settings", "check_for_updates", true);
}
void Settings::setCheckForUpdates(bool b)
{
set(m_Settings, "Settings", "check_for_updates", b);
}
bool Settings::usePrereleases() const
{
return get(m_Settings, "Settings", "use_prereleases", false);
}
void Settings::setUsePrereleases(bool b)
{
set(m_Settings, "Settings", "use_prereleases", b);
}
bool Settings::profileLocalInis() const
{
return get(m_Settings, "Settings", "profile_local_inis", true);
}
void Settings::setProfileLocalInis(bool b)
{
set(m_Settings, "Settings", "profile_local_inis", b);
}
bool Settings::profileLocalSaves() const
{
return get(m_Settings, "Settings", "profile_local_saves", false);
}
void Settings::setProfileLocalSaves(bool b)
{
set(m_Settings, "Settings", "profile_local_saves", b);
}
bool Settings::profileArchiveInvalidation() const
{
return get(m_Settings, "Settings", "profile_archive_invalidation", false);
}
void Settings::setProfileArchiveInvalidation(bool b)
{
set(m_Settings, "Settings", "profile_archive_invalidation", b);
}
bool Settings::useSplash() const
{
return get(m_Settings, "Settings", "use_splash", true);
}
void Settings::setUseSplash(bool b)
{
set(m_Settings, "Settings", "use_splash", b);
}
std::size_t Settings::refreshThreadCount() const
{
return get(m_Settings, "Settings", "refresh_thread_count", 10);
}
void Settings::setRefreshThreadCount(std::size_t n) const
{
return set(m_Settings, "Settings", "refresh_thread_count", n);
}
std::optional Settings::version() const
{
if (auto v = getOptional(m_Settings, "General", "version")) {
return QVersionNumber::fromString(*v).normalized();
}
return {};
}
bool Settings::firstStart() const
{
return get(m_Settings, "General", "first_start", true);
}
void Settings::setFirstStart(bool b)
{
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"
<< "Brave.exe")
.join(";");
return get(m_Settings, "Settings", "executable_blacklist", def);
}
bool Settings::isExecutableBlacklisted(const QString& s) const
{
for (auto exec : executablesBlacklist().split(";")) {
if (exec.compare(s, Qt::CaseInsensitive) == 0) {
return true;
}
}
return false;
}
void Settings::setExecutablesBlacklist(const QString& s)
{
set(m_Settings, "Settings", "executable_blacklist", s);
}
QStringList Settings::skipFileSuffixes() const
{
static const QStringList def = QStringList() << ".mohidden";
auto setting = get(m_Settings, "Settings", "skip_file_suffixes", def);
return setting;
}
void Settings::setSkipFileSuffixes(const QStringList& s)
{
set(m_Settings, "Settings", "skip_file_suffixes", s);
}
QStringList Settings::skipDirectories() const
{
static const QStringList def = QStringList() << ".git";
auto setting = get(m_Settings, "Settings", "skip_directories", def);
return setting;
}
void Settings::setSkipDirectories(const QStringList& s)
{
set(m_Settings, "Settings", "skip_directories", s);
}
void Settings::setMotdHash(uint hash)
{
set(m_Settings, "General", "motd_hash", hash);
}
unsigned int Settings::motdHash() const
{
return get(m_Settings, "General", "motd_hash", 0);
}
bool Settings::archiveParsing() const
{
return get(m_Settings, "Settings", "archive_parsing_experimental", false);
}
void Settings::setArchiveParsing(bool b)
{
set(m_Settings, "Settings", "archive_parsing_experimental", b);
}
std::vector> Settings::executables() const
{
ScopedReadArray sra(m_Settings, "customExecutables");
std::vector> v;
sra.for_each([&] {
std::map map;
for (auto&& key : sra.keys()) {
map[key] = sra.get(key);
}
v.push_back(map);
});
return v;
}
void Settings::setExecutables(const std::vector>& v)
{
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");
}
ScopedWriteArray swa(m_Settings, "customExecutables", v.size());
for (const auto& map : v) {
swa.next();
for (auto&& p : map) {
swa.set(p.first, p.second);
}
}
}
bool Settings::keepBackupOnInstall() const
{
return get(m_Settings, "General", "backup_install", false);
}
void Settings::setKeepBackupOnInstall(bool b)
{
set(m_Settings, "General", "backup_install", b);
}
void Settings::registerDownloadHandlers(bool force)
{
registerAsMODLHandler(force, true);
}
void Settings::registerAsMODLHandler(bool force, bool includeNxm)
{
const auto nxmPath = QCoreApplication::applicationDirPath() + "/" +
QString::fromStdWString(AppConfig::nxmHandlerExe());
const auto executable = QCoreApplication::applicationFilePath();
QStringList parameters = {force ? "forcereg" : "reg", "modl"};
QStringList games = {m_Game.plugin()->gameShortName()};
games.append(m_Game.plugin()->validShortNames());
parameters.append(games.join(","));
parameters.append({executable, "-n %name% -m %modname% -v %version% -s %source%"});
QProcess* modlNxmProcess = new QProcess(this);
connect(modlNxmProcess, &QProcess::finished,
[=](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::NormalExit) {
if (includeNxm) {
nexus().registerAsNXMHandler(force);
}
} else {
QMessageBox::critical(
nullptr, QObject::tr("Failed"),
QObject::tr("Failed to start the helper application: %1")
.arg(modlNxmProcess->program()));
}
modlNxmProcess->deleteLater();
});
modlNxmProcess->start(nxmPath, parameters);
}
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();
const auto s = m_Settings.status();
// there's a bug in Qt at least until 5.15.0 where a utf-8 bom in the ini is
// handled correctly but still sets FormatError
//
// see qsettings.cpp, in QConfFileSettingsPrivate::readIniFile(), there's a
// specific check for utf-8, which adjusts `dataPos` so it's skipped, but
// the FLUSH_CURRENT_SECTION() macro uses `currentSectionStart`, and that one
// isn't adjusted when changing `dataPos` on the first line and so stays 0
//
// this puts the bom in `unparsedIniSections` and eventually sets FormatError
// somewhere
//
//
// the other problem is that the status is never reset, not even when calling
// sync(), so the FormatError that's returned here is actually from reading
// the ini, not writing it
//
//
// since it's impossible to get a FormatError on write, it's considered to
// be a NoError here
if (s == QSettings::FormatError) {
return QSettings::NoError;
} else {
return s;
}
}
QSettings::Status Settings::iniStatus() const
{
return m_Settings.status();
}
void Settings::dump() const
{
static const QStringList ignore({"username", "password", "nexus_api_key",
"nexus_username", "nexus_password",
"steam_username"});
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());
}
}
m_Network.dump();
m_Nexus.dump();
}
void Settings::managedGameChanged(IPluginGame const* gamePlugin)
{
m_Game.setPlugin(gamePlugin);
}
GameSettings::GameSettings(QSettings& settings)
: m_Settings(settings), m_GamePlugin(nullptr)
{}
const MOBase::IPluginGame* GameSettings::plugin()
{
return m_GamePlugin;
}
void GameSettings::setPlugin(const MOBase::IPluginGame* gamePlugin)
{
m_GamePlugin = gamePlugin;
}
bool GameSettings::forceEnableCoreFiles() const
{
return get(m_Settings, "Settings", "force_enable_core_files", true);
}
void GameSettings::setForceEnableCoreFiles(bool b)
{
set(m_Settings, "Settings", "force_enable_core_files", b);
}
std::optional GameSettings::directory() const
{
if (auto v = getOptional(m_Settings, "General", "gamePath")) {
return QString::fromUtf8(*v);
}
return {};
}
void GameSettings::setDirectory(const QString& path)
{
set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8());
}
std::optional GameSettings::name() const
{
return getOptional(m_Settings, "General", "gameName");
}
void GameSettings::setName(const QString& name)
{
set(m_Settings, "General", "gameName", name);
}
std::optional GameSettings::edition() const
{
return getOptional(m_Settings, "General", "game_edition");
}
void GameSettings::setEdition(const QString& name)
{
set(m_Settings, "General", "game_edition", name);
}
std::optional GameSettings::selectedProfileName() const
{
if (auto v = getOptional(m_Settings, "General", "selected_profile")) {
return QString::fromUtf8(*v);
}
return {};
}
void GameSettings::setSelectedProfileName(const QString& name)
{
set(m_Settings, "General", "selected_profile", name.toUtf8());
}
GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s), m_Reset(false) {}
void GeometrySettings::requestReset()
{
m_Reset = true;
}
void GeometrySettings::resetIfNeeded()
{
if (!m_Reset) {
return;
}
removeSection(m_Settings, "Geometry");
}
void GeometrySettings::saveGeometry(const QMainWindow* w)
{
saveWindowGeometry(w);
}
bool GeometrySettings::restoreGeometry(QMainWindow* w) const
{
return restoreWindowGeometry(w);
}
void GeometrySettings::saveGeometry(const QDialog* d)
{
saveWindowGeometry(d);
}
bool GeometrySettings::restoreGeometry(QDialog* d) const
{
const auto r = restoreWindowGeometry(d);
if (centerDialogs()) {
centerOnParent(d);
}
return r;
}
void GeometrySettings::saveWindowGeometry(const QWidget* w)
{
set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry());
}
bool GeometrySettings::restoreWindowGeometry(QWidget* w) const
{
if (auto v = getOptional(m_Settings, "Geometry", geoSettingName(w))) {
w->restoreGeometry(*v);
ensureWindowOnScreen(w);
return true;
}
return false;
}
void GeometrySettings::ensureWindowOnScreen(QWidget* w) const
{
// users report that the main window and/or dialogs are displayed off-screen;
// the usual workaround is keyboard navigation to move it
//
// qt should have code that deals with multiple monitors and off-screen
// geometries, but there seems to be bugs or inconsistencies that can't be
// reproduced
//
// the closest would probably be https://bugreports.qt.io/browse/QTBUG-64498,
// which is about multiple monitors and high dpi, but it seems fixed as of
// 5.12.4, which is shipped with 2.2.1
//
// without being to reproduce the problem, some simple checks are made in a
// timer, which may mitigate the issues
QTimer::singleShot(100, w, [w] {
const auto borders = 20;
// desktop geometry, made smaller to make sure there isn't just a few pixels
const auto originalDg = env::Environment().metrics().desktopGeometry();
const auto dg = originalDg.adjusted(borders, borders, -borders, -borders);
const auto g = w->geometry();
if (!dg.intersects(g)) {
log::warn("window '{}' is offscreen, moving to main monitor; geo={}, desktop={}",
w->objectName(), g, originalDg);
// widget is off-screen, center it on main monitor
centerOnMonitor(w, -1);
log::warn("window '{}' now at {}", w->objectName(), w->geometry());
}
});
}
void GeometrySettings::saveState(const QMainWindow* w)
{
set(m_Settings, "Geometry", stateSettingName(w), w->saveState());
}
bool GeometrySettings::restoreState(QMainWindow* w) const
{
if (auto v = getOptional(m_Settings, "Geometry", stateSettingName(w))) {
w->restoreState(*v);
return true;
}
return false;
}
void GeometrySettings::saveState(const QHeaderView* w)
{
set(m_Settings, "Geometry", stateSettingName(w), w->saveState());
}
bool GeometrySettings::restoreState(QHeaderView* w) const
{
if (auto v = getOptional(m_Settings, "Geometry", stateSettingName(w))) {
w->restoreState(*v);
return true;
}
return false;
}
void GeometrySettings::saveState(const QSplitter* w)
{
set(m_Settings, "Geometry", stateSettingName(w), w->saveState());
}
bool GeometrySettings::restoreState(QSplitter* w) const
{
if (auto v = getOptional(m_Settings, "Geometry", stateSettingName(w))) {
w->restoreState(*v);
return true;
}
return false;
}
void GeometrySettings::saveState(const ExpanderWidget* expander)
{
set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState());
}
bool GeometrySettings::restoreState(ExpanderWidget* expander) const
{
if (auto v =
getOptional(m_Settings, "Geometry", stateSettingName(expander))) {
expander->restoreState(*v);
return true;
}
return false;
}
void GeometrySettings::saveVisibility(const QWidget* w)
{
set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible());
}
bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const
{
if (auto v =
getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) {
w->setVisible(*v);
return true;
}
return false;
}
void GeometrySettings::restoreToolbars(QMainWindow* w) const
{
// all toolbars have the same size and button style settings
const auto size = getOptional(m_Settings, "Geometry", "toolbar_size");
const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style");
for (auto* tb : w->findChildren()) {
if (size) {
tb->setIconSize(*size);
}
if (style) {
tb->setToolButtonStyle(static_cast(*style));
}
restoreVisibility(tb);
}
}
void GeometrySettings::saveToolbars(const QMainWindow* w)
{
const auto tbs = w->findChildren();
// 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(tb->toolButtonStyle()));
}
}
QStringList GeometrySettings::modInfoTabOrder() const
{
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(m_Settings, "Widgets", "ModInfoTabOrder", "");
QTextStream stream(&string);
while (!stream.atEnd()) {
QString s;
stream >> s;
v.push_back(s);
}
}
return v;
}
void GeometrySettings::setModInfoTabOrder(const QString& names)
{
set(m_Settings, "Widgets", "ModInfoTabOrder", names);
}
bool GeometrySettings::centerDialogs() const
{
return get(m_Settings, "Settings", "center_dialogs", false);
}
void GeometrySettings::setCenterDialogs(bool b)
{
set(m_Settings, "Settings", "center_dialogs", b);
}
void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) const
{
const auto monitor =
getOptional(m_Settings, "Geometry", "MainWindow_monitor").value_or(-1);
centerOnMonitor(w, monitor);
}
void GeometrySettings::centerOnMonitor(QWidget* w, int monitor)
{
QPoint center;
if (monitor >= 0 && monitor < QGuiApplication::screens().size()) {
center = QGuiApplication::screens().at(monitor)->geometry().center();
} else {
center = QGuiApplication::primaryScreen()->geometry().center();
}
w->move(center - w->rect().center());
}
void GeometrySettings::centerOnParent(QWidget* w, QWidget* parent)
{
if (!parent) {
parent = w->parentWidget();
if (!parent) {
parent = qApp->activeWindow();
}
}
if (parent && parent->isVisible()) {
const auto pr = parent->geometry();
w->move(pr.center() - w->rect().center());
}
}
void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w)
{
if (auto* handle = w->windowHandle()) {
if (auto* screen = handle->screen()) {
const int screenId = QGuiApplication::screens().indexOf(screen);
set(m_Settings, "Geometry", "MainWindow_monitor", screenId);
}
}
}
Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d)
{
// docks in these areas are horizontal
const auto horizontalAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea;
if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) {
return Qt::Horizontal;
} else {
return Qt::Vertical;
}
}
void GeometrySettings::saveDocks(const QMainWindow* mw)
{
// 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()) {
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);
}
}
void GeometrySettings::restoreDocks(QMainWindow* mw) const
{
struct DockInfo
{
QDockWidget* d;
int size = 0;
Qt::Orientation ori;
};
std::vector dockInfos;
// for each dock
for (auto* dock : mw->findChildren()) {
if (auto size = getOptional(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);
}
});
}
WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance) : m_Settings(s)
{
if (globalInstance) {
MOBase::QuestionBoxMemory::setCallbacks(
[this](auto&& w, auto&& f) {
return questionButton(w, f);
},
[this](auto&& w, auto&& b) {
setQuestionWindowButton(w, b);
},
[this](auto&& w, auto&& f, auto&& b) {
setQuestionFileButton(w, f, b);
});
}
}
void WidgetSettings::saveTreeCheckState(const QTreeView* tv, int role)
{
QVariantList data;
for (auto index : flatIndex(tv->model())) {
data.append(index.data(role));
}
set(m_Settings, "Widgets", indexSettingName(tv), data);
}
void WidgetSettings::restoreTreeCheckState(QTreeView* tv, int role) const
{
if (auto states =
getOptional(m_Settings, "Widgets", indexSettingName(tv))) {
auto allIndex = flatIndex(tv->model());
MOBase::log::debug("restoreTreeCheckState: {}, {}", states->size(),
allIndex.size());
if (states->size() != allIndex.size()) {
return;
}
for (int i = 0; i < states->size(); ++i) {
tv->model()->setData(allIndex[i], states->at(i), role);
}
}
}
void WidgetSettings::saveTreeExpandState(const QTreeView* tv, int role)
{
QVariantList expanded;
for (auto index : flatIndex(tv->model())) {
if (tv->isExpanded(index)) {
expanded.append(index.data(role));
}
}
set(m_Settings, "Widgets", indexSettingName(tv), expanded);
}
void WidgetSettings::restoreTreeExpandState(QTreeView* tv, int role) const
{
if (auto expanded =
getOptional(m_Settings, "Widgets", indexSettingName(tv))) {
tv->collapseAll();
for (auto index : flatIndex(tv->model())) {
if (expanded->contains(index.data(role))) {
tv->expand(index);
}
}
}
}
std::optional WidgetSettings::index(const QComboBox* cb) const
{
return getOptional(m_Settings, "Widgets", indexSettingName(cb));
}
void WidgetSettings::saveIndex(const QComboBox* cb)
{
set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex());
}
void WidgetSettings::restoreIndex(QComboBox* cb, std::optional def) const
{
if (auto v = getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) {
cb->setCurrentIndex(*v);
}
}
std::optional WidgetSettings::index(const QTabWidget* w) const
{
return getOptional(m_Settings, "Widgets", indexSettingName(w));
}
void WidgetSettings::saveIndex(const QTabWidget* w)
{
set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex());
}
void WidgetSettings::restoreIndex(QTabWidget* w, std::optional def) const
{
if (auto v = getOptional(m_Settings, "Widgets", indexSettingName(w), def)) {
w->setCurrentIndex(*v);
}
}
std::optional WidgetSettings::checked(const QAbstractButton* w) const
{
warnIfNotCheckable(w);
return getOptional(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 def) const
{
warnIfNotCheckable(w);
if (auto v = getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) {
w->setChecked(*v);
}
}
QuestionBoxMemory::Button WidgetSettings::questionButton(const QString& windowName,
const QString& filename) const
{
const QString sectionName("DialogChoices");
if (!filename.isEmpty()) {
const auto fileSetting = windowName + "/" + filename;
if (auto v = getOptional(m_Settings, sectionName, fileSetting)) {
return static_cast(*v);
}
}
if (auto v = getOptional(m_Settings, sectionName, windowName)) {
return static_cast(*v);
}
return QuestionBoxMemory::NoButton;
}
void WidgetSettings::setQuestionWindowButton(const QString& windowName,
QuestionBoxMemory::Button button)
{
const QString sectionName("DialogChoices");
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 WidgetSettings::resetQuestionButtons()
{
removeSection(m_Settings, "DialogChoices");
}
ColorSettings::ColorSettings(QSettings& s) : m_Settings(s) {}
QColor ColorSettings::modlistOverwrittenLoose() const
{
return get(m_Settings, "Settings", "overwrittenLooseFilesColor",
QColor(0, 255, 0, 64));
}
void ColorSettings::setModlistOverwrittenLoose(const QColor& c)
{
set(m_Settings, "Settings", "overwrittenLooseFilesColor", c);
}
QColor ColorSettings::modlistOverwritingLoose() const
{
return get(m_Settings, "Settings", "overwritingLooseFilesColor",
QColor(255, 0, 0, 64));
}
void ColorSettings::setModlistOverwritingLoose(const QColor& c)
{
set(m_Settings, "Settings", "overwritingLooseFilesColor", c);
}
QColor ColorSettings::modlistOverwrittenArchive() const
{
return get(m_Settings, "Settings", "overwrittenArchiveFilesColor",
QColor(0, 255, 255, 64));
}
void ColorSettings::setModlistOverwrittenArchive(const QColor& c)
{
set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c);
}
QColor ColorSettings::modlistOverwritingArchive() const
{
return get(m_Settings, "Settings", "overwritingArchiveFilesColor",
QColor(255, 0, 255, 64));
}
void ColorSettings::setModlistOverwritingArchive(const QColor& c)
{
set(m_Settings, "Settings", "overwritingArchiveFilesColor", c);
}
QColor ColorSettings::modlistContainsFile() const
{
return get(m_Settings, "Settings", "containsFileColor",
QColor(0, 0, 255, 64));
}
void ColorSettings::setModlistContainsFile(const QColor& c)
{
set(m_Settings, "Settings", "containsFileColor", c);
}
QColor ColorSettings::pluginListContained() const
{
return get(m_Settings, "Settings", "containedColor", QColor(0, 0, 255, 64));
}
void ColorSettings::setPluginListContained(const QColor& c)
{
set(m_Settings, "Settings", "containedColor", c);
}
QColor ColorSettings::pluginListMaster() const
{
return get(m_Settings, "Settings", "masterColor", QColor(255, 255, 0, 64));
}
void ColorSettings::setPluginListMaster(const QColor& c)
{
set(m_Settings, "Settings", "masterColor", c);
}
std::optional ColorSettings::previousSeparatorColor() const
{
const auto c = getOptional(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(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() < 50)
return QColor(Qt::black);
// "inverse' of luminance of the background
int iLuminance = (rBackgroundColor.red() * 0.299) +
(rBackgroundColor.green() * 0.587) +
(rBackgroundColor.blue() * 0.114);
return QColor(iLuminance >= 128 ? 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(m_Settings, "Plugins", settingName, QVariant());
// No previous enabled? Skip.
if (setting.key == "enabled" && (!temp.isValid() || !temp.canConvert())) {
continue;
}
if (!temp.isValid()) {
temp = setting.defaultValue;
} else 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());
}
// Handle previous "enabled" settings:
if (m_PluginSettings[plugin->name()].contains("enabled")) {
setPersistent(plugin->name(), "enabled",
m_PluginSettings[plugin->name()]["enabled"].toBool(), true);
m_PluginSettings[plugin->name()].remove("enabled");
m_PluginDescriptions[plugin->name()].remove("enabled");
// We need to drop it manually in Settings since it is not possible to remove plugin
// settings:
remove(m_Settings, "Plugins", plugin->name() + "/enabled");
}
}
void PluginSettings::unregisterPlugin(IPlugin* plugin)
{
auto it = std::find(m_Plugins.begin(), m_Plugins.end(), plugin);
if (it != m_Plugins.end()) {
m_Plugins.erase(it);
}
m_PluginSettings.remove(plugin->name());
m_PluginDescriptions.remove(plugin->name());
}
std::vector 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();
}
return *iterSetting;
}
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(QObject::tr("attempt to store setting for unknown plugin \"%1\"")
.arg(pluginName));
}
QVariant oldValue = m_PluginSettings[pluginName][key];
// store the new setting both in memory and in the ini
m_PluginSettings[pluginName][key] = value;
set(m_Settings, "Plugins", pluginName + "/" + key, value);
// emit signal:
emit pluginSettingChanged(pluginName, key, oldValue, value);
}
QVariantMap PluginSettings::settings(const QString& pluginName) const
{
return m_PluginSettings[pluginName];
}
void PluginSettings::setSettings(const QString& pluginName, const QVariantMap& map)
{
auto iterPlugin = m_PluginSettings.find(pluginName);
if (iterPlugin == m_PluginSettings.end()) {
throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"")
.arg(pluginName));
}
QVariantMap oldSettings = m_PluginSettings[pluginName];
m_PluginSettings[pluginName] = map;
// Emit signals for settings that have been changed or added:
for (auto& k : map.keys()) {
// .value() return a default-constructed QVariant if k is not in oldSettings:
QVariant oldValue = oldSettings.value(k);
if (oldValue != map[k]) {
emit pluginSettingChanged(pluginName, k, oldSettings.value(k), map[k]);
}
}
// Emit signals for settings that have been removed:
for (auto& k : oldSettings.keys()) {
if (!map.contains(k)) {
emit pluginSettingChanged(pluginName, k, oldSettings[k], QVariant());
}
}
}
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 get(m_Settings, "PluginPersistance", pluginName + "/" + key, def);
}
void PluginSettings::setPersistent(const QString& pluginName, const QString& key,
const QVariant& value, bool sync)
{
if (!m_PluginSettings.contains(pluginName)) {
throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"")
.arg(pluginName));
}
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& PluginSettings::blacklist() const
{
return m_PluginBlacklist;
}
void PluginSettings::save()
{
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 PluginSettings::readBlacklist() const
{
QSet set;
ScopedReadArray sra(m_Settings, "pluginBlacklist");
sra.for_each([&] {
set.insert(sra.get("name"));
});
return set;
}
const QString PathSettings::BaseDirVariable = "%BASE_DIR%";
PathSettings::PathSettings(QSettings& settings) : m_Settings(settings) {}
std::map PathSettings::recent() const
{
std::map map;
ScopedReadArray sra(m_Settings, "recentDirectories");
sra.for_each([&] {
const QVariant name = sra.get("name");
const QVariant dir = sra.get("directory");
if (name.isValid() && dir.isValid()) {
map.emplace(name.toString(), dir.toString());
}
});
return map;
}
void PathSettings::setRecent(const std::map& 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(m_Settings, "Settings", key, makeDefaultPath(def)));
if (resolve) {
result = PathSettings::resolve(result, 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::resolve(const QString& path, const QString& baseDir)
{
QString s = path;
s.replace(BaseDirVariable, baseDir);
return s;
}
QString PathSettings::makeDefaultPath(const QString dirName)
{
return BaseDirVariable + "/" + dirName;
}
QString PathSettings::base() const
{
const QString dataPath = QFileInfo(m_Settings.fileName()).dir().path();
return QDir::fromNativeSeparators(
get(m_Settings, "Settings", "base_directory", dataPath));
}
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, bool globalInstance)
: m_Settings(settings)
{
if (globalInstance) {
updateCustomBrowser();
}
}
void NetworkSettings::updateCustomBrowser()
{
if (useCustomBrowser()) {
MOBase::shell::SetUrlHandler(customBrowserCommand());
} else {
MOBase::shell::SetUrlHandler("");
}
}
bool NetworkSettings::offlineMode() const
{
return get(m_Settings, "Settings", "offline_mode", false);
}
void NetworkSettings::setOfflineMode(bool b)
{
set(m_Settings, "Settings", "offline_mode", b);
}
bool NetworkSettings::useProxy() const
{
return get(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("lastDownloads", "");
for (const auto& s : lastDownloadsString.split(" ")) {
const auto bytesPerSecond = s.toInt();
if (bytesPerSecond > 0) {
lastDownloads.push_back(bytesPerSecond);
}
}
ServerInfo server(
sra.get("name", ""), sra.get("premium", false),
QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate),
sra.get("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);
}
bool NetworkSettings::useCustomBrowser() const
{
return get(m_Settings, "Settings", "use_custom_browser", false);
}
void NetworkSettings::setUseCustomBrowser(bool b)
{
set(m_Settings, "Settings", "use_custom_browser", b);
updateCustomBrowser();
}
QString NetworkSettings::customBrowserCommand() const
{
return get(m_Settings, "Settings", "custom_browser", "");
}
void NetworkSettings::setCustomBrowserCommand(const QString& s)
{
set(m_Settings, "Settings", "custom_browser", s);
updateCustomBrowser();
}
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(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::endorsementIntegration() const
{
return get(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(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);
}
}
bool NexusSettings::trackedIntegration() const
{
return get(m_Settings, "Settings", "tracked_integration", true);
}
void NexusSettings::setTrackedIntegration(bool b) const
{
set(m_Settings, "Settings", "tracked_integration", b);
}
bool NexusSettings::categoryMappings() const
{
return get(m_Settings, "Settings", "category_mappings", true);
}
void NexusSettings::setCategoryMappings(bool b) const
{
set(m_Settings, "Settings", "category_mappings", b);
}
void NexusSettings::registerAsNXMHandler(bool force)
{
const auto nxmPath = QCoreApplication::applicationDirPath() + "/" +
QString::fromStdWString(AppConfig::nxmHandlerExe());
const auto executable = QCoreApplication::applicationFilePath();
QString mode = force ? "forcereg" : "reg";
QString parameters = mode + " nxm " + m_Parent.game().plugin()->gameShortName();
for (const QString& altGame : m_Parent.game().plugin()->validShortNames()) {
parameters += "," + altGame;
}
parameters += " \"" + executable + "\"";
const auto r = shell::Execute(nxmPath, parameters);
if (!r.success()) {
QMessageBox::critical(
nullptr, QObject::tr("Failed"),
QObject::tr("Failed to start the helper application: %1").arg(r.toString()));
}
}
std::vector NexusSettings::validationTimeouts() const
{
using namespace std::chrono_literals;
const auto s = get(m_Settings, "Settings", "validation_timeouts", "");
const auto numbers = s.split(" ");
std::vector v;
for (auto ns : numbers) {
ns = ns.trimmed();
if (ns.isEmpty())
continue;
bool ok = false;
const auto n = ns.toInt(&ok);
if (!ok || n < 0 || n > 100) {
log::error("bad validation_timeouts number '{}'", ns);
continue;
}
v.push_back(std::chrono::seconds(n));
}
if (v.empty())
v = {10s, 15s, 20s};
return v;
}
void NexusSettings::dump() const
{
const auto iniPath = InstanceManager::singleton().globalInstancesRootPath() + "/" +
QString::fromStdWString(AppConfig::nxmHandlerIni());
if (!QFileInfo(iniPath).exists()) {
log::debug("nxm ini not found at {}", iniPath);
return;
}
QSettings s(iniPath, QSettings::IniFormat);
if (const auto st = s.status(); st != QSettings::NoError) {
log::debug("can't read nxm ini from {}", iniPath);
return;
}
log::debug("nxmhandler settings:");
QSettings handler("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\",
QSettings::NativeFormat);
log::debug(" . primary: {}", handler.value("shell/open/command/Default").toString());
const auto noregister = getOptional(s, "General", "noregister");
if (noregister) {
log::debug(" . noregister: {}", *noregister);
} else {
log::debug(" . noregister: (not found)");
}
ScopedReadArray sra(s, "handlers");
sra.for_each([&] {
const auto games = sra.get("games");
const auto executable = sra.get("executable");
const auto arguments = sra.get("arguments");
log::debug(" . handler:");
log::debug(" . games: {}", games.toString());
log::debug(" . executable: {}", executable.toString());
log::debug(" . arguments: {}", arguments.toString());
});
}
SteamSettings::SteamSettings(Settings& parent, QSettings& settings)
: m_Parent(parent), m_Settings(settings)
{}
QString SteamSettings::appID() const
{
return get(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(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(m_Settings, "Settings", "lock_gui", true);
}
void InterfaceSettings::setLockGUI(bool b)
{
set(m_Settings, "Settings", "lock_gui", b);
}
std::optional InterfaceSettings::styleName() const
{
return getOptional(m_Settings, "Settings", "style");
}
void InterfaceSettings::setStyleName(const QString& name)
{
set(m_Settings, "Settings", "style", name);
}
bool InterfaceSettings::collapsibleSeparators(Qt::SortOrder order) const
{
return get(m_Settings, "Settings",
order == Qt::AscendingOrder ? "collapsible_separators_asc"
: "collapsible_separators_dsc",
true);
}
void InterfaceSettings::setCollapsibleSeparators(bool ascending, bool descending)
{
set(m_Settings, "Settings", "collapsible_separators_asc", ascending);
set(m_Settings, "Settings", "collapsible_separators_dsc", descending);
}
bool InterfaceSettings::collapsibleSeparatorsHighlightTo() const
{
return get(m_Settings, "Settings", "collapsible_separators_conflicts_to", true);
}
void InterfaceSettings::setCollapsibleSeparatorsHighlightTo(bool b)
{
set(m_Settings, "Settings", "collapsible_separators_conflicts_to", b);
}
bool InterfaceSettings::collapsibleSeparatorsHighlightFrom() const
{
return get(m_Settings, "Settings", "collapsible_separators_conflicts_from",
true);
}
void InterfaceSettings::setCollapsibleSeparatorsHighlightFrom(bool b)
{
set(m_Settings, "Settings", "collapsible_separators_conflicts_from", b);
}
bool InterfaceSettings::collapsibleSeparatorsIcons(int column) const
{
return get(m_Settings, "Settings",
QString("collapsible_separators_icons_%1").arg(column), true);
}
void InterfaceSettings::setCollapsibleSeparatorsIcons(int column, bool show)
{
set(m_Settings, "Settings", QString("collapsible_separators_icons_%1").arg(column),
show);
}
bool InterfaceSettings::collapsibleSeparatorsPerProfile() const
{
return get(m_Settings, "Settings", "collapsible_separators_per_profile", false);
}
void InterfaceSettings::setCollapsibleSeparatorsPerProfile(bool b)
{
set(m_Settings, "Settings", "collapsible_separators_per_profile", b);
}
bool InterfaceSettings::saveFilters() const
{
return get(m_Settings, "Settings", "save_filters", false);
}
void InterfaceSettings::setSaveFilters(bool b)
{
set(m_Settings, "Settings", "save_filters", b);
}
bool InterfaceSettings::autoCollapseOnHover() const
{
return get(m_Settings, "Settings", "auto_collapse_on_hover", false);
}
void InterfaceSettings::setAutoCollapseOnHover(bool b)
{
set(m_Settings, "Settings", "auto_collapse_on_hover", b);
}
bool InterfaceSettings::checkUpdateAfterInstallation() const
{
return get(m_Settings, "Settings", "autocheck_update_install", true);
}
void InterfaceSettings::setCheckUpdateAfterInstallation(bool b)
{
set(m_Settings, "Settings", "autocheck_update_install", b);
}
bool InterfaceSettings::compactDownloads() const
{
return get(m_Settings, "Settings", "compact_downloads", false);
}
void InterfaceSettings::setCompactDownloads(bool b)
{
set(m_Settings, "Settings", "compact_downloads", b);
}
bool InterfaceSettings::metaDownloads() const
{
return get(m_Settings, "Settings", "meta_downloads", false);
}
void InterfaceSettings::setMetaDownloads(bool b)
{
set(m_Settings, "Settings", "meta_downloads", b);
}
bool InterfaceSettings::hideDownloadsAfterInstallation() const
{
return get(m_Settings, "Settings", "autohide_downloads", false);
}
void InterfaceSettings::setHideDownloadsAfterInstallation(bool b)
{
set(m_Settings, "Settings", "autohide_downloads", b);
}
bool InterfaceSettings::showDownloadNotifications() const
{
return get(m_Settings, "Settings", "download_notifications", false);
}
void InterfaceSettings::setShowDownloadNotifications(bool b)
{
set(m_Settings, "Settings", "download_notifications", b);
}
bool InterfaceSettings::hideAPICounter() const
{
return get(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(m_Settings, "Settings", "display_foreign", true);
}
void InterfaceSettings::setDisplayForeign(bool b)
{
set(m_Settings, "Settings", "display_foreign", b);
}
QString InterfaceSettings::language()
{
QString result = get(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);
} else {
// fallback system locale
result = QLocale::system().name();
}
}
return result;
}
void InterfaceSettings::setLanguage(const QString& name)
{
set(m_Settings, "Settings", "language", name);
}
bool InterfaceSettings::isTutorialCompleted(const QString& windowName) const
{
return get(m_Settings, "CompletedWindowTutorials", windowName, false);
}
void InterfaceSettings::setTutorialCompleted(const QString& windowName, bool b)
{
set(m_Settings, "CompletedWindowTutorials", windowName, b);
}
bool InterfaceSettings::showChangeGameConfirmation() const
{
return get(m_Settings, "Settings", "show_change_game_confirmation", true);
}
void InterfaceSettings::setShowChangeGameConfirmation(bool b)
{
set(m_Settings, "Settings", "show_change_game_confirmation", b);
}
bool InterfaceSettings::showMenubarOnAlt() const
{
return get(m_Settings, "Settings", "show_menubar_on_alt", true);
}
void InterfaceSettings::setShowMenubarOnAlt(bool b)
{
set(m_Settings, "Settings", "show_menubar_on_alt", b);
}
bool InterfaceSettings::doubleClicksOpenPreviews() const
{
return get(m_Settings, "Settings", "double_click_previews", true);
}
void InterfaceSettings::setDoubleClicksOpenPreviews(bool b)
{
set(m_Settings, "Settings", "double_click_previews", b);
}
FilterWidget::Options InterfaceSettings::filterOptions() const
{
FilterWidget::Options o;
o.useRegex = get(m_Settings, "Settings", "filter_regex", false);
o.regexCaseSensitive =
get(m_Settings, "Settings", "regex_case_sensitive", false);
o.regexExtended = get(m_Settings, "Settings", "regex_extended", false);
o.scrollToSelection =
get(m_Settings, "Settings", "filter_scroll_to_selection", false);
return o;
}
void InterfaceSettings::setFilterOptions(const FilterWidget::Options& o)
{
set(m_Settings, "Settings", "filter_regex", o.useRegex);
set(m_Settings, "Settings", "regex_case_sensitive", o.regexCaseSensitive);
set(m_Settings, "Settings", "regex_extended", o.regexExtended);
set(m_Settings, "Settings", "filter_scroll_to_selection", o.scrollToSelection);
}
DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) : m_Settings(settings) {}
log::Levels DiagnosticsSettings::logLevel() const
{
return get(m_Settings, "Settings", "log_level", log::Levels::Info);
}
void DiagnosticsSettings::setLogLevel(log::Levels level)
{
set(m_Settings, "Settings", "log_level", level);
}
lootcli::LogLevels DiagnosticsSettings::lootLogLevel() const
{
return get(m_Settings, "Settings", "loot_log_level",
lootcli::LogLevels::Info);
}
void DiagnosticsSettings::setLootLogLevel(lootcli::LogLevels level)
{
set(m_Settings, "Settings", "loot_log_level", level);
}
env::CoreDumpTypes DiagnosticsSettings::coreDumpType() const
{
return get(m_Settings, "Settings", "crash_dumps_type",
env::CoreDumpTypes::Mini);
}
void DiagnosticsSettings::setCoreDumpType(env::CoreDumpTypes type)
{
set(m_Settings, "Settings", "crash_dumps_type", type);
}
int DiagnosticsSettings::maxCoreDumps() const
{
return get(m_Settings, "Settings", "crash_dumps_max", 5);
}
void DiagnosticsSettings::setMaxCoreDumps(int n)
{
set(m_Settings, "Settings", "crash_dumps_max", n);
}
std::chrono::seconds DiagnosticsSettings::spawnDelay() const
{
return std::chrono::seconds(get(m_Settings, "Settings", "spawn_delay", 0));
}
void DiagnosticsSettings::setSpawnDelay(std::chrono::seconds t)
{
set(m_Settings, "Settings", "spawn_delay", t.count());
}
void GlobalSettings::updateRegistryKey()
{
const QString OldOrganization = "Tannin";
const QString OldApplication = "Mod Organizer";
const QString OldInstanceValue = "CurrentInstance";
const QString OldRootKey = "Software\\" + OldOrganization;
if (env::registryValueExists(OldRootKey + "\\" + OldApplication, OldInstanceValue)) {
QSettings old(OldOrganization, OldApplication);
setCurrentInstance(old.value(OldInstanceValue).toString());
old.remove(OldInstanceValue);
}
env::deleteRegistryKeyIfEmpty(OldRootKey);
}
QString GlobalSettings::currentInstance()
{
return settings().value("CurrentInstance", "").toString();
}
void GlobalSettings::setCurrentInstance(const QString& s)
{
settings().setValue("CurrentInstance", s);
}
QSettings GlobalSettings::settings()
{
const QString Organization = "Mod Organizer Team";
const QString Application = "Mod Organizer";
return QSettings(Organization, Application);
}
bool GlobalSettings::hideCreateInstanceIntro()
{
return settings().value("HideCreateInstanceIntro", false).toBool();
}
void GlobalSettings::setHideCreateInstanceIntro(bool b)
{
settings().setValue("HideCreateInstanceIntro", b);
}
bool GlobalSettings::hideTutorialQuestion()
{
return settings().value("HideTutorialQuestion", false).toBool();
}
void GlobalSettings::setHideTutorialQuestion(bool b)
{
settings().setValue("HideTutorialQuestion", b);
}
bool GlobalSettings::hideCategoryReminder()
{
return settings().value("HideCategoryReminder", false).toBool();
}
void GlobalSettings::setHideCategoryReminder(bool b)
{
settings().setValue("HideCategoryReminder", b);
}
bool GlobalSettings::hideAssignCategoriesQuestion()
{
return settings().value("HideAssignCategoriesQuestion", false).toBool();
}
void GlobalSettings::setHideAssignCategoriesQuestion(bool b)
{
settings().setValue("HideAssignCategoriesQuestion", b);
}
namespace
{
constexpr auto NexusLegacyCredentialKey = "APIKEY";
constexpr auto NexusOAuthCredentialKey = "NEXUS_OAUTH_TOKENS";
std::optional parseStoredTokens(const QString& raw)
{
if (raw.isEmpty()) {
return std::nullopt;
}
const auto doc = QJsonDocument::fromJson(raw.toUtf8());
if (doc.isNull() || !doc.isObject()) {
return std::nullopt;
}
return NexusOAuthTokens::fromJson(doc.object());
}
} // namespace
bool GlobalSettings::nexusApiKey(QString& apiKey)
{
QString tempKey = getWindowsCredential(NexusLegacyCredentialKey);
if (tempKey.isEmpty())
return false;
apiKey = tempKey;
return true;
}
bool GlobalSettings::setNexusApiKey(const QString& apiKey)
{
if (!setWindowsCredential(NexusLegacyCredentialKey, apiKey)) {
const auto e = GetLastError();
log::error("Storing API key failed: {}", formatSystemMessage(e));
return false;
}
return true;
}
bool GlobalSettings::clearNexusApiKey()
{
return setNexusApiKey("");
}
bool GlobalSettings::hasNexusApiKey()
{
return !getWindowsCredential(NexusLegacyCredentialKey).isEmpty();
}
bool GlobalSettings::nexusOAuthTokens(NexusOAuthTokens& tokens)
{
const auto raw = getWindowsCredential(NexusOAuthCredentialKey);
const auto parsed = parseStoredTokens(raw);
if (!parsed) {
return false;
} else {
tokens = *parsed;
}
return true;
}
bool GlobalSettings::setNexusOAuthTokens(const NexusOAuthTokens& tokens)
{
const auto payload = QJsonDocument(tokens.toJson()).toJson(QJsonDocument::Compact);
if (!setWindowsCredential(NexusOAuthCredentialKey, payload)) {
const auto e = GetLastError();
log::error("Storing OAuth tokens failed: {}", formatSystemMessage(e));
return false;
}
return true;
}
bool GlobalSettings::clearNexusOAuthTokens()
{
return setWindowsCredential(NexusOAuthCredentialKey, "");
}
bool GlobalSettings::hasNexusOAuthTokens()
{
return !getWindowsCredential(NexusOAuthCredentialKey).isEmpty();
}
void GlobalSettings::resetDialogs()
{
setHideCreateInstanceIntro(false);
setHideTutorialQuestion(false);
}