Files
zeal/src/libs/core/settings.cpp
T

454 lines
17 KiB
C++
Raw Normal View History

2024-10-06 01:14:20 -04:00
// Copyright (C) Oleg Shparber, et al. <https://zealdocs.org>
// SPDX-License-Identifier: GPL-3.0-or-later
2015-01-12 22:21:44 -08:00
#include "settings.h"
#include "application.h"
#include <QApplication>
2015-01-29 17:03:36 -08:00
#include <QDir>
#include <QFileInfo>
2026-02-14 22:08:21 +02:00
#include <QLoggingCategory>
2015-01-12 22:21:44 -08:00
#include <QSettings>
#include <QStandardPaths>
2026-05-02 16:08:55 +03:00
#include <QSystemTrayIcon>
#include <QUrl>
2015-08-31 00:34:59 -04:00
#include <QUuid>
2022-10-02 15:04:33 -04:00
#include <QWebEngineProfile>
#include <QWebEngineSettings>
2015-04-04 15:18:48 -07:00
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
#include <QStyle>
#include <QStyleFactory>
#include <QStyleHints>
#else
#include <QPalette>
#endif
2026-04-03 17:21:06 +03:00
namespace Zeal::Core {
2015-08-31 02:46:45 -04:00
namespace {
2026-04-03 17:21:06 +03:00
Q_LOGGING_CATEGORY(log, "zeal.core.settings")
2015-08-31 02:46:45 -04:00
// Configuration file groups
2025-09-01 01:19:18 -04:00
constexpr char GroupUI[] = "ui";
2019-11-13 04:28:17 +01:00
constexpr char GroupContent[] = "content";
constexpr char GroupDocsets[] = "docsets";
constexpr char GroupGlobalShortcuts[] = "global_shortcuts";
constexpr char GroupSearch[] = "search";
constexpr char GroupTabs[] = "tabs";
constexpr char GroupInternal[] = "internal";
constexpr char GroupProxy[] = "proxy";
2019-11-17 14:47:43 -05:00
} // namespace
2015-08-31 02:46:45 -04:00
Settings::Settings(QObject *parent)
: QObject(parent)
2015-01-12 22:21:44 -08:00
{
qRegisterMetaType<ContentAppearance>("ContentAppearance");
qRegisterMetaType<ExternalLinkPolicy>("ExternalLinkPolicy");
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
// When the OS color scheme changes, reapply the color scheme.
2026-03-31 13:54:04 +03:00
connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged, this, [this]() {
if (contentAppearance == ContentAppearance::Automatic) {
applyColorScheme();
}
});
#endif
2015-01-12 22:21:44 -08:00
load();
}
Settings::~Settings()
{
save();
}
bool Settings::isDarkModeEnabled() const
{
if (contentAppearance == ContentAppearance::Dark) {
return true;
}
if (contentAppearance == ContentAppearance::Automatic && colorScheme() == ColorScheme::Dark) {
return true;
}
return false;
}
2026-05-02 16:08:55 +03:00
bool Settings::isTrayActive() const
{
return showSystrayIcon && QSystemTrayIcon::isSystemTrayAvailable();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
void Settings::applyColorScheme()
{
Qt::ColorScheme scheme = Qt::ColorScheme::Unknown;
switch (contentAppearance) {
case ContentAppearance::Light:
scheme = Qt::ColorScheme::Light;
break;
case ContentAppearance::Dark:
scheme = Qt::ColorScheme::Dark;
break;
default:
break;
}
qApp->styleHints()->setColorScheme(scheme);
// setColorScheme() alone doesn't reliably update existing widgets:
// - Widgets with stylesheets (QStyleSheetStyle) only update on polish().
// - Direct palette consumers (QTreeView, QLineEdit) only update when the
// application palette changes.
// Re-instantiating the style triggers the full unpolish-polish cycle for
// QStyleSheetStyle widgets and also updates the application palette via
// the new style's standardPalette().
QStyle *newStyle = QStyleFactory::create(qApp->style()->name());
if (newStyle != nullptr) {
qApp->setStyle(newStyle);
}
}
#endif
Settings::ColorScheme Settings::colorScheme()
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
return QApplication::styleHints()->colorScheme();
#else
// Pre Qt 6.5 detection from https://www.qt.io/blog/dark-mode-on-windows-11-with-qt-6.5.
const QPalette p;
if (p.color(QPalette::WindowText).lightness() > p.color(QPalette::Window).lightness()) {
return ColorScheme::Dark;
}
return ColorScheme::Light;
#endif
}
2015-01-12 22:21:44 -08:00
void Settings::load()
{
auto settings = qsettings();
2026-03-29 04:31:44 +03:00
qCDebug(log, "Using settings file: %s", qPrintable(settings->fileName()));
migrate(settings.get());
2016-04-12 22:49:29 -04:00
// TODO: Put everything in groups
startMinimized = settings->value(QStringLiteral("start_minimized"), false).toBool();
#ifdef ZEAL_FEATURE_UPDATE_CHECK
checkForUpdate = settings->value(QStringLiteral("check_for_update"), true).toBool();
#else
checkForUpdate = settings->value(QStringLiteral("check_for_update"), false).toBool();
#endif
2015-01-12 22:21:44 -08:00
showSystrayIcon = settings->value(QStringLiteral("show_systray_icon"), true).toBool();
minimizeToSystray = settings->value(QStringLiteral("minimize_to_systray"), false).toBool();
hideOnClose = settings->value(QStringLiteral("hide_on_close"), false).toBool();
2015-01-12 22:21:44 -08:00
2025-09-01 01:19:18 -04:00
settings->beginGroup(GroupUI);
hideMenuBar = settings->value(QStringLiteral("hide_menu_bar"), false).toBool();
hideSidebar = settings->value(QStringLiteral("hide_sidebar"), false).toBool();
2025-09-01 01:19:18 -04:00
settings->endGroup();
settings->beginGroup(GroupGlobalShortcuts);
showShortcut = settings->value(QStringLiteral("show")).value<QKeySequence>();
settings->endGroup();
2015-01-12 22:21:44 -08:00
settings->beginGroup(GroupTabs);
openNewTabAfterActive = settings->value(QStringLiteral("open_new_tab_after_active"), false).toBool();
showTabCloseButton = settings->value(QStringLiteral("show_tab_close_button"), true).toBool();
settings->endGroup();
settings->beginGroup(GroupSearch);
isFuzzySearchEnabled = settings->value(QStringLiteral("fuzzy_search_enabled"), true).toBool();
settings->endGroup();
settings->beginGroup(GroupContent);
2026-03-31 13:54:04 +03:00
contentAppearance = settings->value(QStringLiteral("appearance"), QVariant::fromValue(ContentAppearance::Automatic))
.value<ContentAppearance>();
2025-08-31 00:14:21 -04:00
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
// Dark mode needs to be applied before Qt WebEngine is initialized.
if (isDarkModeEnabled()) {
2026-03-22 15:30:20 +02:00
QByteArray chromiumFlags = qgetenv("QTWEBENGINE_CHROMIUM_FLAGS");
if (!chromiumFlags.isEmpty()) {
chromiumFlags += ' ';
}
chromiumFlags += "--blink-settings=forceDarkModeEnabled=true,darkModeInversionAlgorithm=4";
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags);
}
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
applyColorScheme();
#endif
// Fonts
2022-10-02 15:04:33 -04:00
QWebEngineSettings *webSettings = QWebEngineProfile::defaultProfile()->settings();
2026-03-31 13:54:04 +03:00
serifFontFamily = settings
->value(QStringLiteral("serif_font_family"),
webSettings->fontFamily(QWebEngineSettings::SerifFont))
.toString();
sansSerifFontFamily = settings
->value(QStringLiteral("sans_serif_font_family"),
webSettings->fontFamily(QWebEngineSettings::SansSerifFont))
.toString();
fixedFontFamily = settings
->value(QStringLiteral("fixed_font_family"),
webSettings->fontFamily(QWebEngineSettings::FixedFont))
.toString();
static const QMap<QString, QWebEngineSettings::FontFamily> fontFamilies =
{{QStringLiteral("sans-serif"), QWebEngineSettings::SansSerifFont},
{QStringLiteral("serif"), QWebEngineSettings::SerifFont},
{QStringLiteral("monospace"), QWebEngineSettings::FixedFont}};
defaultFontFamily = settings->value(QStringLiteral("default_font_family"), QStringLiteral("serif")).toString();
// Fallback to the serif font family.
if (!fontFamilies.contains(defaultFontFamily)) {
defaultFontFamily = QStringLiteral("serif");
}
webSettings->setFontFamily(QWebEngineSettings::SansSerifFont, sansSerifFontFamily);
webSettings->setFontFamily(QWebEngineSettings::SerifFont, serifFontFamily);
webSettings->setFontFamily(QWebEngineSettings::FixedFont, fixedFontFamily);
const QString defaultFontFamilyResolved = webSettings->fontFamily(fontFamilies.value(defaultFontFamily));
webSettings->setFontFamily(QWebEngineSettings::StandardFont, defaultFontFamilyResolved);
2026-03-31 13:54:04 +03:00
defaultFontSize = settings
->value(QStringLiteral("default_font_size"),
webSettings->fontSize(QWebEngineSettings::DefaultFontSize))
.toInt();
defaultFixedFontSize = settings
->value(QStringLiteral("default_fixed_font_size"),
webSettings->fontSize(QWebEngineSettings::DefaultFixedFontSize))
.toInt();
minimumFontSize = settings
->value(QStringLiteral("minimum_font_size"),
webSettings->fontSize(QWebEngineSettings::MinimumFontSize))
.toInt();
webSettings->setFontSize(QWebEngineSettings::DefaultFontSize, defaultFontSize);
webSettings->setFontSize(QWebEngineSettings::DefaultFixedFontSize, defaultFixedFontSize);
webSettings->setFontSize(QWebEngineSettings::MinimumFontSize, minimumFontSize);
2017-03-02 22:43:36 -05:00
2023-04-12 22:21:03 -04:00
isHighlightOnNavigateEnabled = settings->value(QStringLiteral("highlight_on_navigate"), true).toBool();
customCssFile = settings->value(QStringLiteral("custom_css_file")).toString();
2026-03-31 13:54:04 +03:00
externalLinkPolicy = settings
->value(QStringLiteral("external_link_policy"),
QVariant::fromValue(ExternalLinkPolicy::Ask))
.value<ExternalLinkPolicy>();
isSmoothScrollingEnabled = settings->value(QStringLiteral("smooth_scrolling"), true).toBool();
settings->endGroup();
2015-01-12 22:21:44 -08:00
settings->beginGroup(GroupProxy);
2026-03-31 13:54:04 +03:00
proxyType = static_cast<ProxyType>(settings->value(QStringLiteral("type"), ProxyType::System).toUInt());
proxyHost = settings->value(QStringLiteral("host")).toString();
2016-08-14 22:30:36 -04:00
proxyPort = static_cast<quint16>(settings->value(QStringLiteral("port"), 0).toUInt());
proxyAuthenticate = settings->value(QStringLiteral("authenticate"), false).toBool();
proxyUserName = settings->value(QStringLiteral("username")).toString();
proxyPassword = settings->value(QStringLiteral("password")).toString();
2023-08-06 14:14:05 -04:00
isIgnoreSslErrorsEnabled = settings->value(QStringLiteral("ignore_ssl_errors"), false).toBool();
settings->endGroup();
settings->beginGroup(GroupDocsets);
if (settings->contains(QStringLiteral("path"))) {
docsetPath = settings->value(QStringLiteral("path")).toString();
2015-01-12 22:21:44 -08:00
} else {
2015-02-19 22:38:19 -08:00
#ifndef PORTABLE_BUILD
2026-03-31 13:54:04 +03:00
docsetPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QLatin1String("/docsets");
2015-02-19 22:38:19 -08:00
#else
docsetPath = QStringLiteral("docsets");
2015-02-19 22:38:19 -08:00
#endif
2015-01-12 22:21:44 -08:00
}
settings->endGroup();
2015-01-12 22:21:44 -08:00
// Create the docset storage directory if it doesn't exist.
const QFileInfo fi(docsetPath);
if (!fi.exists()) {
2026-03-31 13:54:04 +03:00
const QString path = fi.isRelative() ? QCoreApplication::applicationDirPath() + QLatin1String("/") + docsetPath
: docsetPath;
2026-02-14 22:08:21 +02:00
if (!QDir().mkpath(path)) {
qCWarning(log, "Failed to create docset storage directory '%s'.", qPrintable(path));
}
}
settings->beginGroup(GroupInternal);
2026-03-31 13:54:04 +03:00
installId = settings
->value(QStringLiteral("install_id"),
// Avoid curly braces (QTBUG-885)
QUuid::createUuid().toString().mid(1, 36))
.toString();
settings->endGroup();
2015-01-12 22:21:44 -08:00
}
void Settings::save()
{
auto settings = qsettings();
2016-04-12 22:49:29 -04:00
// TODO: Put everything in groups
settings->setValue(QStringLiteral("start_minimized"), startMinimized);
settings->setValue(QStringLiteral("check_for_update"), checkForUpdate);
settings->setValue(QStringLiteral("show_systray_icon"), showSystrayIcon);
settings->setValue(QStringLiteral("minimize_to_systray"), minimizeToSystray);
settings->setValue(QStringLiteral("hide_on_close"), hideOnClose);
2025-09-01 01:19:18 -04:00
settings->beginGroup(GroupUI);
settings->setValue(QStringLiteral("hide_menu_bar"), hideMenuBar);
settings->setValue(QStringLiteral("hide_sidebar"), hideSidebar);
2025-09-01 01:19:18 -04:00
settings->endGroup();
settings->beginGroup(GroupGlobalShortcuts);
settings->setValue(QStringLiteral("show"), showShortcut);
settings->endGroup();
settings->beginGroup(GroupTabs);
settings->setValue(QStringLiteral("open_new_tab_after_active"), openNewTabAfterActive);
settings->setValue(QStringLiteral("show_tab_close_button"), showTabCloseButton);
settings->endGroup();
settings->beginGroup(GroupSearch);
2023-04-12 22:21:03 -04:00
settings->setValue(QStringLiteral("fuzzy_search_enabled"), isFuzzySearchEnabled);
settings->endGroup();
settings->beginGroup(GroupContent);
settings->setValue(QStringLiteral("default_font_family"), defaultFontFamily);
settings->setValue(QStringLiteral("serif_font_family"), serifFontFamily);
settings->setValue(QStringLiteral("sans_serif_font_family"), sansSerifFontFamily);
settings->setValue(QStringLiteral("fixed_font_family"), fixedFontFamily);
settings->setValue(QStringLiteral("default_font_size"), defaultFontSize);
settings->setValue(QStringLiteral("default_fixed_font_size"), defaultFixedFontSize);
settings->setValue(QStringLiteral("minimum_font_size"), minimumFontSize);
settings->setValue(QStringLiteral("appearance"), QVariant::fromValue(contentAppearance));
2023-04-12 22:21:03 -04:00
settings->setValue(QStringLiteral("highlight_on_navigate"), isHighlightOnNavigateEnabled);
settings->setValue(QStringLiteral("custom_css_file"), customCssFile);
settings->setValue(QStringLiteral("external_link_policy"), QVariant::fromValue(externalLinkPolicy));
settings->setValue(QStringLiteral("smooth_scrolling"), isSmoothScrollingEnabled);
settings->endGroup();
settings->beginGroup(GroupProxy);
settings->setValue(QStringLiteral("type"), proxyType);
settings->setValue(QStringLiteral("host"), proxyHost);
settings->setValue(QStringLiteral("port"), proxyPort);
settings->setValue(QStringLiteral("authenticate"), proxyAuthenticate);
settings->setValue(QStringLiteral("username"), proxyUserName);
settings->setValue(QStringLiteral("password"), proxyPassword);
2023-08-06 14:14:05 -04:00
settings->setValue(QStringLiteral("ignore_ssl_errors"), isIgnoreSslErrorsEnabled);
settings->endGroup();
2015-01-12 22:21:44 -08:00
settings->beginGroup(GroupDocsets);
settings->setValue(QStringLiteral("path"), docsetPath);
settings->endGroup();
2015-01-12 22:21:44 -08:00
settings->beginGroup(GroupInternal);
settings->setValue(QStringLiteral("install_id"), installId);
2016-08-07 21:29:17 -04:00
// Version of configuration file format, should match Zeal version. Used for migration rules.
settings->setValue(QStringLiteral("version"), Application::version().toString());
settings->endGroup();
2015-08-31 00:34:59 -04:00
settings->sync();
2015-01-14 12:14:26 -08:00
emit updated();
2015-01-12 22:21:44 -08:00
}
/*!
* \internal
* \brief Migrates settings from older application versions.
* \param settings QSettings object to update.
*
* The settings migration process relies on 'internal/version' option, that was introduced in the
* release 0.2.0, so a missing option indicates pre-0.2 release.
*/
void Settings::migrate(QSettings *settings) const
{
settings->beginGroup(GroupInternal);
const auto version = QVersionNumber::fromString(settings->value(QStringLiteral("version")).toString());
settings->endGroup();
//
// 0.6.0
//
// Unset content.default_fixed_font_size.
// The causing bug was 0.6.1 (#903), but the incorrect setting still comes to haunt us (#1054).
if (version == QVersionNumber(0, 6, 0)) {
settings->beginGroup(GroupContent);
settings->remove(QStringLiteral("default_fixed_font_size"));
settings->endGroup();
}
//
// Pre 0.4
//
// Rename 'browser' group into 'content'.
if (version < QVersionNumber(0, 4, 0)) {
settings->beginGroup(QStringLiteral("browser"));
const QVariant tmpMinimumFontSize = settings->value(QStringLiteral("minimum_font_size"));
settings->endGroup();
settings->remove(QStringLiteral("browser"));
if (tmpMinimumFontSize.isValid()) {
settings->beginGroup(GroupContent);
settings->setValue(QStringLiteral("minimum_font_size"), tmpMinimumFontSize);
settings->endGroup();
}
}
}
/*!
* \internal
* \brief Returns an initialized QSettings object.
* \param parent Optional parent object.
* \return QSettings object.
*
* QSettings is initialized according to build options, e.g. standard vs portable.
* Caller is responsible for deleting the returned object.
*/
std::unique_ptr<QSettings> Settings::qsettings()
{
#ifndef PORTABLE_BUILD
return std::make_unique<QSettings>();
#else
return std::make_unique<QSettings>(QCoreApplication::applicationDirPath() + QLatin1String("/zeal.ini"),
QSettings::IniFormat);
#endif
}
2026-04-03 17:21:06 +03:00
} // namespace Zeal::Core
QDataStream &operator<<(QDataStream &out, Zeal::Core::Settings::ContentAppearance policy)
{
2026-04-03 17:21:06 +03:00
out << static_cast<std::underlying_type_t<Zeal::Core::Settings::ContentAppearance>>(policy);
return out;
}
2026-04-03 17:21:06 +03:00
QDataStream &operator>>(QDataStream &in, Zeal::Core::Settings::ContentAppearance &policy)
{
2026-04-03 17:21:06 +03:00
std::underlying_type_t<Zeal::Core::Settings::ContentAppearance> value;
in >> value;
2026-04-03 17:21:06 +03:00
policy = static_cast<Zeal::Core::Settings::ContentAppearance>(value);
return in;
}
2026-04-03 17:21:06 +03:00
QDataStream &operator<<(QDataStream &out, Zeal::Core::Settings::ExternalLinkPolicy policy)
{
2026-04-03 17:21:06 +03:00
out << static_cast<std::underlying_type_t<Zeal::Core::Settings::ExternalLinkPolicy>>(policy);
return out;
}
2026-04-03 17:21:06 +03:00
QDataStream &operator>>(QDataStream &in, Zeal::Core::Settings::ExternalLinkPolicy &policy)
{
2026-04-03 17:21:06 +03:00
std::underlying_type_t<Zeal::Core::Settings::ExternalLinkPolicy> value;
in >> value;
2026-04-03 17:21:06 +03:00
policy = static_cast<Zeal::Core::Settings::ExternalLinkPolicy>(value);
return in;
}