Qt Utilities 6.12.0
Common Qt related C++ classes and routines used by my applications such as dialogs, widgets and models
Loading...
Searching...
No Matches
qtsettings.cpp
Go to the documentation of this file.
1#include "./qtsettings.h"
2#include "./optioncategory.h"
5#include "./optionpage.h"
6
7#include "../paletteeditor/paletteeditor.h"
8
9#include "../widgets/clearlineedit.h"
10
11#include "../resources/resources.h"
12
13#include "../misc/desktoputils.h"
14
15#include "ui_qtappearanceoptionpage.h"
16#include "ui_qtenvoptionpage.h"
17#include "ui_qtlanguageoptionpage.h"
18
19#include <QDir>
20#include <QFileDialog>
21#include <QFontDialog>
22#include <QIcon>
23#include <QSettings>
24#include <QStringBuilder>
25#include <QStyleFactory>
26
27#if defined(Q_OS_WINDOWS) && (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
28#include <QOperatingSystemVersion>
29#define QT_UTILITIES_USE_FUSION_ON_WINDOWS_11
30#endif
31
32#include <iostream>
33#include <memory>
34#include <optional>
35
36using namespace std;
37
38namespace QtUtilities {
39
42
43 QFont font;
44 std::optional<QFont> initialFont;
45 QPalette palette;
46 QString widgetStyle;
49 QString iconTheme;
52 QString localeName;
65};
66
68 : iconTheme(QIcon::themeName())
69 , initialIconTheme(iconTheme)
70 , localeName(defaultLocale.name())
71 , customFont(false)
72 , customPalette(false)
73 , customWidgetStyle(false)
74 , customStyleSheet(false)
75 , customIconTheme(false)
76 , customLocale(false)
77 , isPaletteDark(false)
78 , showNotices(true)
79{
80}
81
89QtSettings::QtSettings()
90 : m_d(make_unique<QtSettingsData>())
91{
92}
93
99QtSettings::~QtSettings()
100{
101}
102
111void QtSettings::disableNotices()
112{
113 m_d->showNotices = false;
114}
115
119bool QtSettings::hasCustomFont() const
120{
121 return m_d->customFont;
122}
123
130void QtSettings::restore(QSettings &settings)
131{
132 settings.beginGroup(QStringLiteral("qt"));
133 m_d->font.fromString(settings.value(QStringLiteral("font")).toString());
134 m_d->customFont = settings.value(QStringLiteral("customfont"), false).toBool();
135 m_d->palette = settings.value(QStringLiteral("palette")).value<QPalette>();
136 m_d->customPalette = settings.value(QStringLiteral("custompalette"), false).toBool();
137 m_d->widgetStyle = settings.value(QStringLiteral("widgetstyle"), m_d->widgetStyle).toString();
138 m_d->customWidgetStyle = settings.value(QStringLiteral("customwidgetstyle"), false).toBool();
139 m_d->styleSheetPath = settings.value(QStringLiteral("stylesheetpath"), m_d->styleSheetPath).toString();
140 m_d->customStyleSheet = settings.value(QStringLiteral("customstylesheet"), false).toBool();
141 m_d->iconTheme = settings.value(QStringLiteral("icontheme"), m_d->iconTheme).toString();
142 m_d->customIconTheme = settings.value(QStringLiteral("customicontheme"), false).toBool();
143 m_d->localeName = settings.value(QStringLiteral("locale"), m_d->localeName).toString();
144 m_d->customLocale = settings.value(QStringLiteral("customlocale"), false).toBool();
145 m_d->additionalPluginDirectory = settings.value(QStringLiteral("plugindir")).toString();
146 m_d->additionalIconThemeSearchPath = settings.value(QStringLiteral("iconthemepath")).toString();
147 TranslationFiles::additionalTranslationFilePath() = settings.value(QStringLiteral("trpath")).toString();
148 settings.endGroup();
149}
150
154void QtSettings::save(QSettings &settings) const
155{
156 settings.beginGroup(QStringLiteral("qt"));
157 settings.setValue(QStringLiteral("font"), QVariant(m_d->font.toString()));
158 settings.setValue(QStringLiteral("customfont"), m_d->customFont);
159 settings.setValue(QStringLiteral("palette"), QVariant(m_d->palette));
160 settings.setValue(QStringLiteral("custompalette"), m_d->customPalette);
161 settings.setValue(QStringLiteral("widgetstyle"), m_d->widgetStyle);
162 settings.setValue(QStringLiteral("customwidgetstyle"), m_d->customWidgetStyle);
163 settings.setValue(QStringLiteral("stylesheetpath"), m_d->styleSheetPath);
164 settings.setValue(QStringLiteral("customstylesheet"), m_d->customStyleSheet);
165 settings.setValue(QStringLiteral("icontheme"), m_d->iconTheme);
166 settings.setValue(QStringLiteral("customicontheme"), m_d->customIconTheme);
167 settings.setValue(QStringLiteral("locale"), m_d->localeName);
168 settings.setValue(QStringLiteral("customlocale"), m_d->customLocale);
169 settings.setValue(QStringLiteral("plugindir"), m_d->additionalPluginDirectory);
170 settings.setValue(QStringLiteral("iconthemepath"), m_d->additionalIconThemeSearchPath);
171 settings.setValue(QStringLiteral("trpath"), QVariant(TranslationFiles::additionalTranslationFilePath()));
172 settings.endGroup();
173}
174
180static QMap<QString, QString> scanIconThemes(const QStringList &searchPaths)
181{
182 auto res = QMap<QString, QString>();
183 for (const auto &searchPath : searchPaths) {
184 const auto dir = QDir(searchPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
185 for (const auto &iconTheme : dir) {
186 auto indexFile = QFile(searchPath % QChar('/') % iconTheme % QStringLiteral("/index.theme"));
187 auto index = QByteArray();
188 if (indexFile.open(QFile::ReadOnly) && !(index = indexFile.readAll()).isEmpty()) {
189 const auto iconThemeSection = index.indexOf("[Icon Theme]");
190 const auto nameStart = index.indexOf("Name=", iconThemeSection != -1 ? iconThemeSection : 0);
191 if (nameStart != -1) {
192 auto nameLength = index.indexOf("\n", nameStart) - nameStart - 5;
193 if (nameLength > 0) {
194 auto displayName = QString::fromUtf8(index.mid(nameStart + 5, nameLength));
195 if (displayName != iconTheme) {
196 displayName += QChar(' ') % QChar('(') % iconTheme % QChar(')');
197 }
198 res[displayName] = iconTheme;
199 continue;
200 }
201 }
202 }
203 res[iconTheme] = iconTheme;
204 }
205 }
206 return res;
207}
208
220void QtSettings::apply()
221{
222 // apply environment
223 if (m_d->additionalPluginDirectory != m_d->previousPluginDirectory) {
224 if (!m_d->previousPluginDirectory.isEmpty()) {
225 QCoreApplication::removeLibraryPath(m_d->previousPluginDirectory);
226 }
227 if (!m_d->additionalPluginDirectory.isEmpty()) {
228 QCoreApplication::addLibraryPath(m_d->additionalPluginDirectory);
229 }
230 m_d->previousPluginDirectory = m_d->additionalPluginDirectory;
231 }
232 if (m_d->additionalIconThemeSearchPath != m_d->previousIconThemeSearchPath) {
233 auto paths = QIcon::themeSearchPaths();
234 if (!m_d->previousIconThemeSearchPath.isEmpty()) {
235 paths.removeAll(m_d->previousIconThemeSearchPath);
236 }
237 if (!m_d->additionalIconThemeSearchPath.isEmpty()) {
238 paths.append(m_d->additionalIconThemeSearchPath);
239 }
240 m_d->previousIconThemeSearchPath = m_d->additionalIconThemeSearchPath;
241 QIcon::setThemeSearchPaths(paths);
242 }
243
244 // read style sheet
245 auto styleSheet = QString();
246 if (m_d->customStyleSheet && !m_d->styleSheetPath.isEmpty()) {
247 auto file = QFile(m_d->styleSheetPath);
248 if (!file.open(QFile::ReadOnly)) {
249 cerr << "Unable to open the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data() << "\"." << endl;
250 }
251 styleSheet.append(file.readAll());
252 if (file.error() != QFile::NoError) {
253 cerr << "Unable to read the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data() << "\"." << endl;
254 }
255 }
256
257 // apply appearance
258 if (m_d->customFont) {
259 if (!m_d->initialFont.has_value()) {
260 m_d->initialFont = QGuiApplication::font();
261 }
262 QGuiApplication::setFont(m_d->font);
263 } else if (m_d->initialFont.has_value()) {
264 QGuiApplication::setFont(m_d->initialFont.value());
265 }
266#ifdef QT_UTILITIES_USE_FUSION_ON_WINDOWS_11
267 if (m_d->initialWidgetStyle.isEmpty()) {
268 // use Fusion on Windows 11 as the native style doesn't look good
269 // see https://bugreports.qt.io/browse/QTBUG-97668
270 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11) {
271 m_d->initialWidgetStyle = QStringLiteral("Fusion");
272 }
273 }
274#endif
275 if (m_d->customWidgetStyle) {
276#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
277 const auto *const currentStyle = QApplication::style();
278 if (m_d->initialWidgetStyle.isEmpty() && currentStyle) {
279 m_d->initialWidgetStyle = currentStyle->name();
280 }
281#endif
282 QApplication::setStyle(m_d->widgetStyle);
283 } else if (!m_d->initialWidgetStyle.isEmpty()) {
284 QApplication::setStyle(m_d->initialWidgetStyle);
285 }
286 if (auto *const qapp = qobject_cast<QApplication *>(QApplication::instance())) {
287 qapp->setStyleSheet(styleSheet);
288 } else {
289 cerr << "Unable to apply the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data()
290 << "\" because no QApplication has been instantiated." << endl;
291 }
292 if (m_d->customPalette) {
293 QGuiApplication::setPalette(m_d->palette);
294 } else {
295 QGuiApplication::setPalette(QPalette());
296 }
297 m_d->isPaletteDark = isPaletteDark();
298 if (m_d->customIconTheme) {
299 QIcon::setThemeName(m_d->iconTheme);
300 } else if (!m_d->initialIconTheme.isEmpty()) {
301 if (m_d->iconTheme != m_d->initialIconTheme) {
302 // set the icon theme back to what it was before changing anything (not sure how to read the current system icon theme again)
303 QIcon::setThemeName(m_d->initialIconTheme);
304 }
305 } else {
306 // use bundled default icon theme matching the current palette
307 // notes: - It is ok that search paths specified via CLI arguments are not set here yet. When doing so one should also
308 // specify the desired icon theme explicitly.
309 // - The icon themes "default" and "default-dark" come from QtConfig.cmake which makes the first non-dark bundled
310 // icon theme available as "default" and the first dark icon theme available as "default-dark". An icon theme
311 // is considered dark if it ends with "-dark".
312 const auto bundledIconThemes = scanIconThemes(QStringList(QStringLiteral(":/icons")));
313 if (m_d->isPaletteDark && bundledIconThemes.contains(QStringLiteral("default-dark"))) {
314 QIcon::setThemeName(QStringLiteral("default-dark"));
315 } else if (bundledIconThemes.contains(QStringLiteral("default"))) {
316 QIcon::setThemeName(QStringLiteral("default"));
317 }
318 }
319
320 // apply locale
321 QLocale::setDefault(m_d->customLocale ? QLocale(m_d->localeName) : m_d->defaultLocale);
322}
323
334void QtSettings::reevaluatePaletteAndDefaultIconTheme()
335{
337 if (isPaletteDark == m_d->isPaletteDark) {
338 return; // no need to do anything if there's no change
339 }
340 m_d->isPaletteDark = isPaletteDark;
341 if (auto iconTheme = QIcon::themeName(); iconTheme == QStringLiteral("default") || iconTheme == QStringLiteral("default-dark")) {
342 QIcon::setThemeName(m_d->isPaletteDark ? QStringLiteral("default-dark") : QStringLiteral("default"));
343 }
344}
345
352bool QtSettings::isPaletteDark()
353{
354 return m_d->isPaletteDark;
355}
356
365OptionCategory *QtSettings::category()
366{
367 auto *category = new OptionCategory;
368 category->setDisplayName(QCoreApplication::translate("QtGui::QtOptionCategory", "Qt"));
369 category->setIcon(QIcon::fromTheme(QStringLiteral("qtcreator"), QIcon(QStringLiteral(":/qtutilities/icons/hicolor/48x48/apps/qtcreator.svg"))));
370 category->assignPages({ new QtAppearanceOptionPage(*m_d), new QtLanguageOptionPage(*m_d), new QtEnvOptionPage(*m_d) });
371 return category;
372}
373
374QtAppearanceOptionPage::QtAppearanceOptionPage(QtSettingsData &settings, QWidget *parentWidget)
375 : QtAppearanceOptionPageBase(parentWidget)
376 , m_settings(settings)
377 , m_fontDialog(nullptr)
378{
379}
380
381QtAppearanceOptionPage::~QtAppearanceOptionPage()
382{
383}
384
385bool QtAppearanceOptionPage::apply()
386{
387 m_settings.font = ui()->fontComboBox->currentFont();
388 m_settings.customFont = !ui()->fontCheckBox->isChecked();
389 m_settings.widgetStyle = ui()->widgetStyleComboBox->currentText();
390 m_settings.customWidgetStyle = !ui()->widgetStyleCheckBox->isChecked();
391 m_settings.styleSheetPath = ui()->styleSheetPathSelection->lineEdit()->text();
392 m_settings.customStyleSheet = !ui()->styleSheetCheckBox->isChecked();
393 m_settings.palette = ui()->paletteToolButton->palette();
394 m_settings.customPalette = !ui()->paletteCheckBox->isChecked();
395 m_settings.iconTheme
396 = ui()->iconThemeComboBox->currentIndex() != -1 ? ui()->iconThemeComboBox->currentData().toString() : ui()->iconThemeComboBox->currentText();
397 m_settings.customIconTheme = !ui()->iconThemeCheckBox->isChecked();
398 return true;
399}
400
401void QtAppearanceOptionPage::reset()
402{
403 ui()->fontComboBox->setCurrentFont(m_settings.font);
404 ui()->fontCheckBox->setChecked(!m_settings.customFont);
405 ui()->widgetStyleComboBox->setCurrentText(
406 m_settings.widgetStyle.isEmpty() ? (QApplication::style() ? QApplication::style()->objectName() : QString()) : m_settings.widgetStyle);
407 ui()->widgetStyleCheckBox->setChecked(!m_settings.customWidgetStyle);
408 ui()->styleSheetPathSelection->lineEdit()->setText(m_settings.styleSheetPath);
409 ui()->styleSheetCheckBox->setChecked(!m_settings.customStyleSheet);
410 ui()->paletteToolButton->setPalette(m_settings.palette);
411 ui()->paletteCheckBox->setChecked(!m_settings.customPalette);
412 int iconThemeIndex = ui()->iconThemeComboBox->findData(m_settings.iconTheme);
413 if (iconThemeIndex != -1) {
414 ui()->iconThemeComboBox->setCurrentIndex(iconThemeIndex);
415 } else {
416 ui()->iconThemeComboBox->setCurrentText(m_settings.iconTheme);
417 }
418 ui()->iconThemeCheckBox->setChecked(!m_settings.customIconTheme);
419}
420
421QWidget *QtAppearanceOptionPage::setupWidget()
422{
423 // call base implementation first, so ui() is available
424 auto *widget = QtAppearanceOptionPageBase::setupWidget();
425 if (!m_settings.showNotices) {
426 ui()->label->hide();
427 }
428
429 // setup widget style selection
430 ui()->widgetStyleComboBox->addItems(QStyleFactory::keys());
431
432 // setup style sheet selection
433 ui()->styleSheetPathSelection->provideCustomFileMode(QFileDialog::ExistingFile);
434
435 // setup font selection
436 QObject::connect(ui()->fontPushButton, &QPushButton::clicked, m_fontDialog, [this] {
437 if (!m_fontDialog) {
438 m_fontDialog = new QFontDialog(this->widget());
439 m_fontDialog->setCurrentFont(ui()->fontComboBox->font());
440 QObject::connect(m_fontDialog, &QFontDialog::fontSelected, ui()->fontComboBox, &QFontComboBox::setCurrentFont);
441 QObject::connect(ui()->fontComboBox, &QFontComboBox::currentFontChanged, m_fontDialog, &QFontDialog::setCurrentFont);
442 }
443 m_fontDialog->show();
444 });
445
446 // setup palette selection
447 QObject::connect(ui()->paletteToolButton, &QToolButton::clicked, ui()->paletteToolButton,
448 [this] { ui()->paletteToolButton->setPalette(PaletteEditor::getPalette(this->widget(), ui()->paletteToolButton->palette())); });
449
450 // setup icon theme selection
451 const auto iconThemes = scanIconThemes(QIcon::themeSearchPaths() << QStringLiteral("/usr/share/icons/"));
452 auto *iconThemeComboBox = ui()->iconThemeComboBox;
453 for (auto i = iconThemes.begin(), end = iconThemes.end(); i != end; ++i) {
454 const auto &displayName = i.key();
455 const auto &id = i.value();
456 if (const auto existingItemIndex = iconThemeComboBox->findData(id); existingItemIndex != -1) {
457 iconThemeComboBox->setItemText(existingItemIndex, displayName);
458 } else {
459 iconThemeComboBox->addItem(displayName, id);
460 }
461 }
462
463 return widget;
464}
465
466QtLanguageOptionPage::QtLanguageOptionPage(QtSettingsData &settings, QWidget *parentWidget)
467 : QtLanguageOptionPageBase(parentWidget)
468 , m_settings(settings)
469{
470}
471
472QtLanguageOptionPage::~QtLanguageOptionPage()
473{
474}
475
476bool QtLanguageOptionPage::apply()
477{
478 m_settings.localeName = ui()->localeComboBox->currentText();
479 m_settings.customLocale = !ui()->localeCheckBox->isChecked();
480 return true;
481}
482
483void QtLanguageOptionPage::reset()
484{
485 ui()->localeComboBox->setCurrentText(m_settings.localeName);
486 ui()->localeCheckBox->setChecked(!m_settings.customLocale);
487}
488
489QWidget *QtLanguageOptionPage::setupWidget()
490{
491 // call base implementation first, so ui() is available
492 auto *widget = QtLanguageOptionPageBase::setupWidget();
493 if (!m_settings.showNotices) {
494 ui()->label->hide();
495 }
496
497 // add all available locales to combo box
498 auto *localeComboBox = ui()->localeComboBox;
499 const auto locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
500 for (const QLocale &locale : locales) {
501 localeComboBox->addItem(locale.name());
502 }
503
504 auto *languageLabel = ui()->languageLabel;
505 QObject::connect(ui()->localeComboBox, &QComboBox::currentTextChanged, languageLabel, [languageLabel, localeComboBox] {
506 const QLocale selectedLocale(localeComboBox->currentText());
507 const QLocale currentLocale;
508 languageLabel->setText(QCoreApplication::translate("QtGui::QtLanguageOptionPage", "recognized by Qt as") % QStringLiteral(" <i>")
509 % currentLocale.languageToString(selectedLocale.language()) % QChar(',') % QChar(' ')
510 % currentLocale.countryToString(selectedLocale.country()) % QStringLiteral("</i>"));
511 });
512 return widget;
513}
514
515QtEnvOptionPage::QtEnvOptionPage(QtSettingsData &settings, QWidget *parentWidget)
516 : QtEnvOptionPageBase(parentWidget)
517 , m_settings(settings)
518{
519}
520
521QtEnvOptionPage::~QtEnvOptionPage()
522{
523}
524
525bool QtEnvOptionPage::apply()
526{
527 m_settings.additionalPluginDirectory = ui()->pluginPathSelection->lineEdit()->text();
528 m_settings.additionalIconThemeSearchPath = ui()->iconThemeSearchPathSelection->lineEdit()->text();
529 TranslationFiles::additionalTranslationFilePath() = ui()->translationPathSelection->lineEdit()->text();
530 return true;
531}
532
533void QtEnvOptionPage::reset()
534{
535 ui()->pluginPathSelection->lineEdit()->setText(m_settings.additionalPluginDirectory);
536 ui()->iconThemeSearchPathSelection->lineEdit()->setText(m_settings.additionalIconThemeSearchPath);
537 ui()->translationPathSelection->lineEdit()->setText(TranslationFiles::additionalTranslationFilePath());
538}
539
540QWidget *QtEnvOptionPage::setupWidget()
541{
542 // call base implementation first, so ui() is available
543 auto *widget = QtEnvOptionPageBase::setupWidget();
544 if (!m_settings.showNotices) {
545 ui()->label->hide();
546 }
547 return widget;
548}
549
550} // namespace QtUtilities
551
552INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(QtAppearanceOptionPage)
553INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(QtLanguageOptionPage)
QT_UTILITIES_EXPORT QString & additionalTranslationFilePath()
Allows to set an additional search path for translation files.
Definition: resources.cpp:75
QtEnvOptionPage(QtSettingsData &settings, QWidget *parentWidget=nullptr)
Definition: qtsettings.cpp:515
QtAppearanceOptionPage(QtSettingsData &settings, QWidget *parentWidget=nullptr)
Definition: qtsettings.cpp:374
QtLanguageOptionPage(QtSettingsData &settings, QWidget *parentWidget=nullptr)
Definition: qtsettings.cpp:466
QT_UTILITIES_EXPORT bool isPaletteDark(const QPalette &palette=QPalette())
Returns whether palette is dark.
#define INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(SomeClass)
Instantiates a class declared with BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE in a convenient way.
Definition: optionpage.h:248
std::optional< QFont > initialFont
Definition: qtsettings.cpp:44