Qt Utilities 6.13.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; // the currently applied palette (only in use if customPalette is true, though)
46 QPalette selectedPalette; // the intermediately selected palette (chosen in palette editor but not yet applied)
47 QString widgetStyle;
50 QString iconTheme;
54 QString localeName;
68};
69
71 : iconTheme(QIcon::themeName())
72 , initialIconTheme(iconTheme)
73 , localeName(defaultLocale.name())
74 , customFont(false)
75 , customPalette(false)
76 , customWidgetStyle(false)
77 , customStyleSheet(false)
78 , customIconTheme(false)
79 , customLocale(false)
80 , isPaletteDark(false)
81 , showNotices(true)
82 , retranslatable(false)
83{
84}
85
93QtSettings::QtSettings()
94 : m_d(make_unique<QtSettingsData>())
95{
96}
97
103QtSettings::~QtSettings()
104{
105}
106
118void QtSettings::disableNotices()
119{
120 m_d->showNotices = false;
121}
122
131void QtSettings::setRetranslatable(bool retranslatable)
132{
133 m_d->retranslatable = retranslatable;
134}
135
139bool QtSettings::hasCustomFont() const
140{
141 return m_d->customFont;
142}
143
150void QtSettings::restore(QSettings &settings)
151{
152 settings.beginGroup(QStringLiteral("qt"));
153 m_d->font.fromString(settings.value(QStringLiteral("font")).toString());
154 m_d->customFont = settings.value(QStringLiteral("customfont"), false).toBool();
155 m_d->palette = settings.value(QStringLiteral("palette")).value<QPalette>();
156 m_d->customPalette = settings.value(QStringLiteral("custompalette"), false).toBool();
157 m_d->widgetStyle = settings.value(QStringLiteral("widgetstyle"), m_d->widgetStyle).toString();
158 m_d->customWidgetStyle = settings.value(QStringLiteral("customwidgetstyle"), false).toBool();
159 m_d->styleSheetPath = settings.value(QStringLiteral("stylesheetpath"), m_d->styleSheetPath).toString();
160 m_d->customStyleSheet = settings.value(QStringLiteral("customstylesheet"), false).toBool();
161 m_d->iconTheme = settings.value(QStringLiteral("icontheme"), m_d->iconTheme).toString();
162 m_d->customIconTheme = settings.value(QStringLiteral("customicontheme"), false).toBool();
163 m_d->localeName = settings.value(QStringLiteral("locale"), m_d->localeName).toString();
164 m_d->customLocale = settings.value(QStringLiteral("customlocale"), false).toBool();
165 m_d->additionalPluginDirectory = settings.value(QStringLiteral("plugindir")).toString();
166 m_d->additionalIconThemeSearchPath = settings.value(QStringLiteral("iconthemepath")).toString();
167 TranslationFiles::additionalTranslationFilePath() = settings.value(QStringLiteral("trpath")).toString();
168 settings.endGroup();
169}
170
174void QtSettings::save(QSettings &settings) const
175{
176 settings.beginGroup(QStringLiteral("qt"));
177 settings.setValue(QStringLiteral("font"), QVariant(m_d->font.toString()));
178 settings.setValue(QStringLiteral("customfont"), m_d->customFont);
179 settings.setValue(QStringLiteral("palette"), QVariant(m_d->palette));
180 settings.setValue(QStringLiteral("custompalette"), m_d->customPalette);
181 settings.setValue(QStringLiteral("widgetstyle"), m_d->widgetStyle);
182 settings.setValue(QStringLiteral("customwidgetstyle"), m_d->customWidgetStyle);
183 settings.setValue(QStringLiteral("stylesheetpath"), m_d->styleSheetPath);
184 settings.setValue(QStringLiteral("customstylesheet"), m_d->customStyleSheet);
185 settings.setValue(QStringLiteral("icontheme"), m_d->iconTheme);
186 settings.setValue(QStringLiteral("customicontheme"), m_d->customIconTheme);
187 settings.setValue(QStringLiteral("locale"), m_d->localeName);
188 settings.setValue(QStringLiteral("customlocale"), m_d->customLocale);
189 settings.setValue(QStringLiteral("plugindir"), m_d->additionalPluginDirectory);
190 settings.setValue(QStringLiteral("iconthemepath"), m_d->additionalIconThemeSearchPath);
191 settings.setValue(QStringLiteral("trpath"), QVariant(TranslationFiles::additionalTranslationFilePath()));
192 settings.endGroup();
193}
194
200static QMap<QString, QString> scanIconThemes(const QStringList &searchPaths)
201{
202 auto res = QMap<QString, QString>();
203 for (const auto &searchPath : searchPaths) {
204 const auto dir = QDir(searchPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
205 for (const auto &iconTheme : dir) {
206 auto indexFile = QFile(searchPath % QChar('/') % iconTheme % QStringLiteral("/index.theme"));
207 auto index = QByteArray();
208 if (indexFile.open(QFile::ReadOnly) && !(index = indexFile.readAll()).isEmpty()) {
209 const auto iconThemeSection = index.indexOf("[Icon Theme]");
210 const auto nameStart = index.indexOf("Name=", iconThemeSection != -1 ? iconThemeSection : 0);
211 if (nameStart != -1) {
212 auto nameLength = index.indexOf("\n", nameStart) - nameStart - 5;
213 if (nameLength > 0) {
214 auto displayName = QString::fromUtf8(index.mid(nameStart + 5, nameLength));
215 if (displayName != iconTheme) {
216 displayName += QChar(' ') % QChar('(') % iconTheme % QChar(')');
217 }
218 res[displayName] = iconTheme;
219 continue;
220 }
221 }
222 }
223 res[iconTheme] = iconTheme;
224 }
225 }
226 return res;
227}
228
240void QtSettings::apply()
241{
242 // apply environment
243 if (m_d->additionalPluginDirectory != m_d->previousPluginDirectory) {
244 if (!m_d->previousPluginDirectory.isEmpty()) {
245 QCoreApplication::removeLibraryPath(m_d->previousPluginDirectory);
246 }
247 if (!m_d->additionalPluginDirectory.isEmpty()) {
248 QCoreApplication::addLibraryPath(m_d->additionalPluginDirectory);
249 }
250 m_d->previousPluginDirectory = m_d->additionalPluginDirectory;
251 }
252 if (m_d->additionalIconThemeSearchPath != m_d->previousIconThemeSearchPath) {
253 auto paths = QIcon::themeSearchPaths();
254 if (!m_d->previousIconThemeSearchPath.isEmpty()) {
255 paths.removeAll(m_d->previousIconThemeSearchPath);
256 }
257 if (!m_d->additionalIconThemeSearchPath.isEmpty()) {
258 paths.append(m_d->additionalIconThemeSearchPath);
259 }
260 m_d->previousIconThemeSearchPath = m_d->additionalIconThemeSearchPath;
261 QIcon::setThemeSearchPaths(paths);
262 }
263
264 // read style sheet
265 auto styleSheet = QString();
266 if (m_d->customStyleSheet && !m_d->styleSheetPath.isEmpty()) {
267 auto file = QFile(m_d->styleSheetPath);
268 if (!file.open(QFile::ReadOnly)) {
269 cerr << "Unable to open the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data() << "\"." << endl;
270 }
271 styleSheet.append(file.readAll());
272 if (file.error() != QFile::NoError) {
273 cerr << "Unable to read the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data() << "\"." << endl;
274 }
275 }
276
277 // apply appearance
278 if (m_d->customFont) {
279 if (!m_d->initialFont.has_value()) {
280 m_d->initialFont = QGuiApplication::font();
281 }
282 QGuiApplication::setFont(m_d->font);
283 } else if (m_d->initialFont.has_value()) {
284 QGuiApplication::setFont(m_d->initialFont.value());
285 }
286#ifdef QT_UTILITIES_USE_FUSION_ON_WINDOWS_11
287 if (m_d->initialWidgetStyle.isEmpty()) {
288 // use Fusion on Windows 11 as the native style doesn't look good
289 // see https://bugreports.qt.io/browse/QTBUG-97668
290 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11) {
291 m_d->initialWidgetStyle = QStringLiteral("Fusion");
292 }
293 }
294#endif
295 if (m_d->customWidgetStyle) {
296#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
297 const auto *const currentStyle = QApplication::style();
298 if (m_d->initialWidgetStyle.isEmpty() && currentStyle) {
299 m_d->initialWidgetStyle = currentStyle->name();
300 }
301#endif
302 QApplication::setStyle(m_d->widgetStyle);
303 } else if (!m_d->initialWidgetStyle.isEmpty()) {
304 QApplication::setStyle(m_d->initialWidgetStyle);
305 }
306 if (auto *const qapp = qobject_cast<QApplication *>(QApplication::instance())) {
307 qapp->setStyleSheet(styleSheet);
308 } else {
309 cerr << "Unable to apply the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data()
310 << "\" because no QApplication has been instantiated." << endl;
311 }
312 if (m_d->customPalette) {
313 QGuiApplication::setPalette(m_d->palette);
314 } else {
315 QGuiApplication::setPalette(QPalette());
316 }
317 m_d->isPaletteDark = isPaletteDark();
318 if (m_d->customIconTheme) {
319 QIcon::setThemeName(m_d->iconTheme);
320 } else if (!m_d->initialIconTheme.isEmpty()) {
321 if (m_d->iconTheme != m_d->initialIconTheme) {
322 // set the icon theme back to what it was before changing anything (not sure how to read the current system icon theme again)
323 QIcon::setThemeName(m_d->initialIconTheme);
324 }
325 } else {
326 // use bundled default icon theme matching the current palette
327 // notes: - It is ok that search paths specified via CLI arguments are not set here yet. When doing so one should also
328 // specify the desired icon theme explicitly.
329 // - The icon themes "default" and "default-dark" come from QtConfig.cmake which makes the first non-dark bundled
330 // icon theme available as "default" and the first dark icon theme available as "default-dark". An icon theme
331 // is considered dark if it ends with "-dark".
332 const auto bundledIconThemes = scanIconThemes(QStringList(QStringLiteral(":/icons")));
333 if (m_d->isPaletteDark && bundledIconThemes.contains(QStringLiteral("default-dark"))) {
334 QIcon::setThemeName(QStringLiteral("default-dark"));
335 } else if (bundledIconThemes.contains(QStringLiteral("default"))) {
336 QIcon::setThemeName(QStringLiteral("default"));
337 }
338 }
339
340 // apply locale
341 m_d->previousLocale = QLocale();
342 QLocale::setDefault(m_d->customLocale ? QLocale(m_d->localeName) : m_d->defaultLocale);
343}
344
355void QtSettings::reevaluatePaletteAndDefaultIconTheme()
356{
358 if (isPaletteDark == m_d->isPaletteDark) {
359 return; // no need to do anything if there's no change
360 }
361 m_d->isPaletteDark = isPaletteDark;
362 if (auto iconTheme = QIcon::themeName(); iconTheme == QStringLiteral("default") || iconTheme == QStringLiteral("default-dark")) {
363 QIcon::setThemeName(m_d->isPaletteDark ? QStringLiteral("default-dark") : QStringLiteral("default"));
364 }
365}
366
373bool QtSettings::isPaletteDark()
374{
375 return m_d->isPaletteDark;
376}
377
381bool QtSettings::hasLocaleChanged() const
382{
383 return m_d->previousLocale != QLocale();
384}
385
394OptionCategory *QtSettings::category()
395{
396 auto *category = new OptionCategory;
397 category->setDisplayName(QCoreApplication::translate("QtGui::QtOptionCategory", "Qt"));
398 category->setIcon(QIcon::fromTheme(QStringLiteral("qtcreator"), QIcon(QStringLiteral(":/qtutilities/icons/hicolor/48x48/apps/qtcreator.svg"))));
399 category->assignPages({ new QtAppearanceOptionPage(*m_d), new QtLanguageOptionPage(*m_d), new QtEnvOptionPage(*m_d) });
400 return category;
401}
402
403QtAppearanceOptionPage::QtAppearanceOptionPage(QtSettingsData &settings, QWidget *parentWidget)
404 : QtAppearanceOptionPageBase(parentWidget)
405 , m_settings(settings)
406 , m_fontDialog(nullptr)
407{
408}
409
410QtAppearanceOptionPage::~QtAppearanceOptionPage()
411{
412}
413
414bool QtAppearanceOptionPage::apply()
415{
416 m_settings.font = ui()->fontComboBox->currentFont();
417 m_settings.customFont = !ui()->fontCheckBox->isChecked();
418 m_settings.widgetStyle = ui()->widgetStyleComboBox->currentText();
419 m_settings.customWidgetStyle = !ui()->widgetStyleCheckBox->isChecked();
420 m_settings.styleSheetPath = ui()->styleSheetPathSelection->lineEdit()->text();
421 m_settings.customStyleSheet = !ui()->styleSheetCheckBox->isChecked();
422 m_settings.palette = m_settings.selectedPalette;
423 m_settings.customPalette = !ui()->paletteCheckBox->isChecked();
424 m_settings.iconTheme
425 = ui()->iconThemeComboBox->currentIndex() != -1 ? ui()->iconThemeComboBox->currentData().toString() : ui()->iconThemeComboBox->currentText();
426 m_settings.customIconTheme = !ui()->iconThemeCheckBox->isChecked();
427 return true;
428}
429
430void QtAppearanceOptionPage::reset()
431{
432 ui()->fontComboBox->setCurrentFont(m_settings.font);
433 ui()->fontCheckBox->setChecked(!m_settings.customFont);
434 ui()->widgetStyleComboBox->setCurrentText(
435 m_settings.widgetStyle.isEmpty() ? (QApplication::style() ? QApplication::style()->objectName() : QString()) : m_settings.widgetStyle);
436 ui()->widgetStyleCheckBox->setChecked(!m_settings.customWidgetStyle);
437 ui()->styleSheetPathSelection->lineEdit()->setText(m_settings.styleSheetPath);
438 ui()->styleSheetCheckBox->setChecked(!m_settings.customStyleSheet);
439 m_settings.selectedPalette = m_settings.palette;
440 ui()->paletteCheckBox->setChecked(!m_settings.customPalette);
441 int iconThemeIndex = ui()->iconThemeComboBox->findData(m_settings.iconTheme);
442 if (iconThemeIndex != -1) {
443 ui()->iconThemeComboBox->setCurrentIndex(iconThemeIndex);
444 } else {
445 ui()->iconThemeComboBox->setCurrentText(m_settings.iconTheme);
446 }
447 ui()->iconThemeCheckBox->setChecked(!m_settings.customIconTheme);
448}
449
450QWidget *QtAppearanceOptionPage::setupWidget()
451{
452 // call base implementation first, so ui() is available
453 auto *widget = QtAppearanceOptionPageBase::setupWidget();
454 if (!m_settings.showNotices) {
455 ui()->label->hide();
456 }
457
458 // setup widget style selection
459 ui()->widgetStyleComboBox->addItems(QStyleFactory::keys());
460
461 // setup style sheet selection
462 ui()->styleSheetPathSelection->provideCustomFileMode(QFileDialog::ExistingFile);
463
464 // setup font selection
465 QObject::connect(ui()->fontPushButton, &QPushButton::clicked, widget, [this] {
466 if (!m_fontDialog) {
467 m_fontDialog = new QFontDialog(this->widget());
468 m_fontDialog->setCurrentFont(ui()->fontComboBox->font());
469 QObject::connect(m_fontDialog, &QFontDialog::fontSelected, ui()->fontComboBox, &QFontComboBox::setCurrentFont);
470 QObject::connect(ui()->fontComboBox, &QFontComboBox::currentFontChanged, m_fontDialog, &QFontDialog::setCurrentFont);
471 }
472 m_fontDialog->show();
473 });
474
475 // setup palette selection
476 QObject::connect(ui()->paletteToolButton, &QToolButton::clicked, ui()->paletteToolButton,
477 [this] { m_settings.selectedPalette = PaletteEditor::getPalette(this->widget(), m_settings.selectedPalette); });
478
479 // setup icon theme selection
480 const auto iconThemes = scanIconThemes(QIcon::themeSearchPaths() << QStringLiteral("/usr/share/icons/"));
481 auto *iconThemeComboBox = ui()->iconThemeComboBox;
482 for (auto i = iconThemes.begin(), end = iconThemes.end(); i != end; ++i) {
483 const auto &displayName = i.key();
484 const auto &id = i.value();
485 if (const auto existingItemIndex = iconThemeComboBox->findData(id); existingItemIndex != -1) {
486 iconThemeComboBox->setItemText(existingItemIndex, displayName);
487 } else {
488 iconThemeComboBox->addItem(displayName, id);
489 }
490 }
491
492 return widget;
493}
494
495QtLanguageOptionPage::QtLanguageOptionPage(QtSettingsData &settings, QWidget *parentWidget)
496 : QtLanguageOptionPageBase(parentWidget)
497 , m_settings(settings)
498{
499}
500
501QtLanguageOptionPage::~QtLanguageOptionPage()
502{
503}
504
505bool QtLanguageOptionPage::apply()
506{
507 m_settings.localeName = ui()->localeComboBox->currentText();
508 m_settings.customLocale = !ui()->localeCheckBox->isChecked();
509 return true;
510}
511
512void QtLanguageOptionPage::reset()
513{
514 ui()->localeComboBox->setCurrentText(m_settings.localeName);
515 ui()->localeCheckBox->setChecked(!m_settings.customLocale);
516}
517
518QWidget *QtLanguageOptionPage::setupWidget()
519{
520 // call base implementation first, so ui() is available
521 auto *widget = QtLanguageOptionPageBase::setupWidget();
522 if (m_settings.retranslatable) {
523 ui()->label->hide();
524 }
525
526 // add all available locales to combo box
527 auto *localeComboBox = ui()->localeComboBox;
528 const auto locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
529 for (const QLocale &locale : locales) {
530 localeComboBox->addItem(locale.name());
531 }
532
533 auto *languageLabel = ui()->languageLabel;
534 QObject::connect(ui()->localeComboBox, &QComboBox::currentTextChanged, languageLabel, [languageLabel, localeComboBox] {
535 const QLocale selectedLocale(localeComboBox->currentText());
536 const QLocale currentLocale;
537 languageLabel->setText(QCoreApplication::translate("QtGui::QtLanguageOptionPage", "recognized by Qt as") % QStringLiteral(" <i>")
538 % currentLocale.languageToString(selectedLocale.language()) % QChar(',') % QChar(' ')
539 % currentLocale.countryToString(selectedLocale.country()) % QStringLiteral("</i>"));
540 });
541 return widget;
542}
543
544QtEnvOptionPage::QtEnvOptionPage(QtSettingsData &settings, QWidget *parentWidget)
545 : QtEnvOptionPageBase(parentWidget)
546 , m_settings(settings)
547{
548}
549
550QtEnvOptionPage::~QtEnvOptionPage()
551{
552}
553
554bool QtEnvOptionPage::apply()
555{
556 m_settings.additionalPluginDirectory = ui()->pluginPathSelection->lineEdit()->text();
557 m_settings.additionalIconThemeSearchPath = ui()->iconThemeSearchPathSelection->lineEdit()->text();
558 TranslationFiles::additionalTranslationFilePath() = ui()->translationPathSelection->lineEdit()->text();
559 return true;
560}
561
562void QtEnvOptionPage::reset()
563{
564 ui()->pluginPathSelection->lineEdit()->setText(m_settings.additionalPluginDirectory);
565 ui()->iconThemeSearchPathSelection->lineEdit()->setText(m_settings.additionalIconThemeSearchPath);
566 ui()->translationPathSelection->lineEdit()->setText(TranslationFiles::additionalTranslationFilePath());
567}
568
569QWidget *QtEnvOptionPage::setupWidget()
570{
571 // call base implementation first, so ui() is available
572 return QtEnvOptionPageBase::setupWidget();
573}
574
581QtSettings::operator QtSettingsData &() const
582{
583 return *m_d.get();
584}
585
586} // namespace QtUtilities
587
588INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(QtAppearanceOptionPage)
589INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(QtLanguageOptionPage)
QT_UTILITIES_EXPORT QString & additionalTranslationFilePath()
Allows to set an additional search path for translation files.
Definition resources.cpp:80
QtEnvOptionPage(QtSettingsData &settings, QWidget *parentWidget=nullptr)
QtAppearanceOptionPage(QtSettingsData &settings, QWidget *parentWidget=nullptr)
QtLanguageOptionPage(QtSettingsData &settings, QWidget *parentWidget=nullptr)
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:250
std::optional< QFont > initialFont