Qt Utilities 6.14.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
8
10
12
14
15#include "resources/config.h"
16
17#include "ui_qtappearanceoptionpage.h"
18#include "ui_qtenvoptionpage.h"
19#include "ui_qtlanguageoptionpage.h"
20
21#include <c++utilities/application/commandlineutils.h>
22
23#include <QDir>
24#include <QFileDialog>
25#include <QFontDialog>
26#include <QIcon>
27#include <QOperatingSystemVersion>
28#include <QSettings>
29#include <QStringBuilder>
30#include <QStyleFactory>
31#include <QVersionNumber>
32
33#if defined(Q_OS_WINDOWS) && (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) && (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
34#include <QOperatingSystemVersion>
35#define QT_UTILITIES_USE_FUSION_ON_WINDOWS_11
36#endif
37
38#include <iostream>
39#include <memory>
40#include <optional>
41
42namespace QtUtilities {
43
46
47 QFont font;
48 std::optional<QFont> initialFont;
49 QPalette palette; // the currently applied palette (only in use if customPalette is true, though)
50 QPalette selectedPalette; // the intermediately selected palette (chosen in palette editor but not yet applied)
51 QString widgetStyle;
54 QString iconTheme;
58 QString localeName;
72};
73
75 : iconTheme(QIcon::themeName())
76 , initialIconTheme(iconTheme)
77 , localeName(defaultLocale.name())
78 , customFont(false)
79 , customPalette(false)
80 , customWidgetStyle(false)
81 , customStyleSheet(false)
82 , customIconTheme(false)
83 , customLocale(false)
84 , isPaletteDark(false)
85 , showNotices(true)
86 , retranslatable(false)
87{
88}
89
97QtSettings::QtSettings()
98 : m_d(std::make_unique<QtSettingsData>())
99{
100}
101
107QtSettings::~QtSettings()
108{
109}
110
122void QtSettings::disableNotices()
123{
124 m_d->showNotices = false;
125}
126
135void QtSettings::setRetranslatable(bool retranslatable)
136{
137 m_d->retranslatable = retranslatable;
138}
139
143bool QtSettings::hasCustomFont() const
144{
145 return m_d->customFont;
146}
147
154void QtSettings::restore(QSettings &settings)
155{
156 settings.beginGroup(QStringLiteral("qt"));
157 m_d->font.fromString(settings.value(QStringLiteral("font")).toString());
158 m_d->customFont = settings.value(QStringLiteral("customfont"), false).toBool();
159 m_d->palette = settings.value(QStringLiteral("palette")).value<QPalette>();
160 m_d->customPalette = settings.value(QStringLiteral("custompalette"), false).toBool();
161 m_d->widgetStyle = settings.value(QStringLiteral("widgetstyle"), m_d->widgetStyle).toString();
162 m_d->customWidgetStyle = settings.value(QStringLiteral("customwidgetstyle"), false).toBool();
163 m_d->styleSheetPath = settings.value(QStringLiteral("stylesheetpath"), m_d->styleSheetPath).toString();
164 m_d->customStyleSheet = settings.value(QStringLiteral("customstylesheet"), false).toBool();
165 m_d->iconTheme = settings.value(QStringLiteral("icontheme"), m_d->iconTheme).toString();
166 m_d->customIconTheme = settings.value(QStringLiteral("customicontheme"), false).toBool();
167 m_d->localeName = settings.value(QStringLiteral("locale"), m_d->localeName).toString();
168 m_d->customLocale = settings.value(QStringLiteral("customlocale"), false).toBool();
169 m_d->additionalPluginDirectory = settings.value(QStringLiteral("plugindir")).toString();
170 m_d->additionalIconThemeSearchPath = settings.value(QStringLiteral("iconthemepath")).toString();
171 TranslationFiles::additionalTranslationFilePath() = settings.value(QStringLiteral("trpath")).toString();
172 settings.endGroup();
173}
174
178void QtSettings::save(QSettings &settings) const
179{
180 settings.beginGroup(QStringLiteral("qt"));
181 settings.setValue(QStringLiteral("font"), QVariant(m_d->font.toString()));
182 settings.setValue(QStringLiteral("customfont"), m_d->customFont);
183 settings.setValue(QStringLiteral("palette"), QVariant(m_d->palette));
184 settings.setValue(QStringLiteral("custompalette"), m_d->customPalette);
185 settings.setValue(QStringLiteral("widgetstyle"), m_d->widgetStyle);
186 settings.setValue(QStringLiteral("customwidgetstyle"), m_d->customWidgetStyle);
187 settings.setValue(QStringLiteral("stylesheetpath"), m_d->styleSheetPath);
188 settings.setValue(QStringLiteral("customstylesheet"), m_d->customStyleSheet);
189 settings.setValue(QStringLiteral("icontheme"), m_d->iconTheme);
190 settings.setValue(QStringLiteral("customicontheme"), m_d->customIconTheme);
191 settings.setValue(QStringLiteral("locale"), m_d->localeName);
192 settings.setValue(QStringLiteral("customlocale"), m_d->customLocale);
193 settings.setValue(QStringLiteral("plugindir"), m_d->additionalPluginDirectory);
194 settings.setValue(QStringLiteral("iconthemepath"), m_d->additionalIconThemeSearchPath);
195 settings.setValue(QStringLiteral("trpath"), QVariant(TranslationFiles::additionalTranslationFilePath()));
196 settings.endGroup();
197}
198
204static QMap<QString, QString> scanIconThemes(const QStringList &searchPaths)
205{
206 auto res = QMap<QString, QString>();
207 for (const auto &searchPath : searchPaths) {
208 const auto dir = QDir(searchPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
209 for (const auto &iconTheme : dir) {
210 auto indexFile = QFile(searchPath % QChar('/') % iconTheme % QStringLiteral("/index.theme"));
211 auto index = QByteArray();
212 if (indexFile.open(QFile::ReadOnly) && !(index = indexFile.readAll()).isEmpty()) {
213 const auto iconThemeSection = index.indexOf("[Icon Theme]");
214 const auto nameStart = index.indexOf("Name=", iconThemeSection != -1 ? iconThemeSection : 0);
215 if (nameStart != -1) {
216 auto nameLength = index.indexOf("\n", nameStart) - nameStart - 5;
217 if (nameLength > 0) {
218 auto displayName = QString::fromUtf8(index.mid(nameStart + 5, nameLength));
219 if (displayName != iconTheme) {
220 displayName += QChar(' ') % QChar('(') % iconTheme % QChar(')');
221 }
222 res[displayName] = iconTheme;
223 continue;
224 }
225 }
226 }
227 res[iconTheme] = iconTheme;
228 }
229 }
230 return res;
231}
232
244void QtSettings::apply()
245{
246 // apply environment
247 if (m_d->additionalPluginDirectory != m_d->previousPluginDirectory) {
248 if (!m_d->previousPluginDirectory.isEmpty()) {
249 QCoreApplication::removeLibraryPath(m_d->previousPluginDirectory);
250 }
251 if (!m_d->additionalPluginDirectory.isEmpty()) {
252 QCoreApplication::addLibraryPath(m_d->additionalPluginDirectory);
253 }
254 m_d->previousPluginDirectory = m_d->additionalPluginDirectory;
255 }
256 if (m_d->additionalIconThemeSearchPath != m_d->previousIconThemeSearchPath) {
257 auto paths = QIcon::themeSearchPaths();
258 if (!m_d->previousIconThemeSearchPath.isEmpty()) {
259 paths.removeAll(m_d->previousIconThemeSearchPath);
260 }
261 if (!m_d->additionalIconThemeSearchPath.isEmpty()) {
262 paths.append(m_d->additionalIconThemeSearchPath);
263 }
264 m_d->previousIconThemeSearchPath = m_d->additionalIconThemeSearchPath;
265 QIcon::setThemeSearchPaths(paths);
266 }
267
268 // read style sheet
269 auto styleSheet = QString();
270 if (m_d->customStyleSheet && !m_d->styleSheetPath.isEmpty()) {
271 auto file = QFile(m_d->styleSheetPath);
272 if (!file.open(QFile::ReadOnly)) {
273 std::cerr << "Unable to open the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data() << "\"." << std::endl;
274 }
275 styleSheet.append(file.readAll());
276 if (file.error() != QFile::NoError) {
277 std::cerr << "Unable to read the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data() << "\"." << std::endl;
278 }
279 }
280
281 // apply appearance
282 if (m_d->customFont) {
283 if (!m_d->initialFont.has_value()) {
284 m_d->initialFont = QGuiApplication::font();
285 }
286 QGuiApplication::setFont(m_d->font);
287 } else if (m_d->initialFont.has_value()) {
288 QGuiApplication::setFont(m_d->initialFont.value());
289 }
290#ifdef QT_UTILITIES_USE_FUSION_ON_WINDOWS_11
291 if (m_d->initialWidgetStyle.isEmpty()) {
292 // use Fusion on Windows 11 as the native style doesn't look good
293 // see https://bugreports.qt.io/browse/QTBUG-97668
294 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11) {
295 m_d->initialWidgetStyle = QStringLiteral("Fusion");
296 }
297 }
298#endif
299 if (m_d->customWidgetStyle) {
300#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
301 const auto *const currentStyle = QApplication::style();
302 if (m_d->initialWidgetStyle.isEmpty() && currentStyle) {
303 m_d->initialWidgetStyle = currentStyle->name();
304 }
305#endif
306 QApplication::setStyle(m_d->widgetStyle);
307 } else if (!m_d->initialWidgetStyle.isEmpty()) {
308 QApplication::setStyle(m_d->initialWidgetStyle);
309 }
310 if (auto *const qapp = qobject_cast<QApplication *>(QApplication::instance())) {
311 qapp->setStyleSheet(styleSheet);
312 } else {
313 std::cerr << "Unable to apply the specified stylesheet \"" << m_d->styleSheetPath.toLocal8Bit().data()
314 << "\" because no QApplication has been instantiated." << std::endl;
315 }
316 if (m_d->customPalette) {
317 QGuiApplication::setPalette(m_d->palette);
318 } else {
319 QGuiApplication::setPalette(QPalette());
320 }
321 m_d->isPaletteDark = isPaletteDark();
322 if (m_d->customIconTheme) {
323 QIcon::setThemeName(m_d->iconTheme);
324 } else if (!m_d->initialIconTheme.isEmpty()) {
325 if (m_d->iconTheme != m_d->initialIconTheme) {
326 // set the icon theme back to what it was before changing anything (not sure how to read the current system icon theme again)
327 QIcon::setThemeName(m_d->initialIconTheme);
328 }
329 } else {
330 // use bundled default icon theme matching the current palette
331 // notes: - It is ok that search paths specified via CLI arguments are not set here yet. When doing so one should also
332 // specify the desired icon theme explicitly.
333 // - The icon themes "default" and "default-dark" come from QtConfig.cmake which makes the first non-dark bundled
334 // icon theme available as "default" and the first dark icon theme available as "default-dark". An icon theme
335 // is considered dark if it ends with "-dark".
336 const auto bundledIconThemes = scanIconThemes(QStringList(QStringLiteral(":/icons")));
337 if (m_d->isPaletteDark && bundledIconThemes.contains(QStringLiteral("default-dark"))) {
338 QIcon::setThemeName(QStringLiteral("default-dark"));
339 } else if (bundledIconThemes.contains(QStringLiteral("default"))) {
340 QIcon::setThemeName(QStringLiteral("default"));
341 }
342 }
343
344 // apply locale
345 m_d->previousLocale = QLocale();
346 QLocale::setDefault(m_d->customLocale ? QLocale(m_d->localeName) : m_d->defaultLocale);
347
348 // log some debug information on the first call if env variable set
349 static auto debugInfoLogged = false;
350 if (debugInfoLogged) {
351 return;
352 }
353 const auto debugLoggingEnabled = CppUtilities::isEnvVariableSet(PROJECT_VARNAME_UPPER "_LOG_QT_CONFIG");
354 if (debugLoggingEnabled.has_value() && debugLoggingEnabled.value()) {
355 if (const auto os = QOperatingSystemVersion::current(); os.type() != static_cast<decltype(os.type())>(QOperatingSystemVersion::Unknown)) {
356 const auto version = QVersionNumber(os.majorVersion(), os.minorVersion(), os.microVersion());
357 std::cerr << "OS name and version: " << os.name().toStdString() << ' ' << version.toString().toStdString() << '\n';
358 }
359 std::cerr << "Qt version: " << qVersion() << '\n';
360 std::cerr << "Qt platform (set QT_QPA_PLATFORM to override): " << QGuiApplication::platformName().toStdString() << '\n';
361 std::cerr << "Qt locale: " << QLocale().name().toStdString() << '\n';
362 std::cerr << "Qt library paths: " << QCoreApplication::libraryPaths().join(':').toStdString() << '\n';
363 std::cerr << "Qt theme search paths: " << QIcon::themeSearchPaths().join(':').toStdString() << '\n';
364 debugInfoLogged = true;
365 }
366}
367
384void QtSettings::reapplyDefaultIconTheme(bool isPaletteDark)
385{
386 if (isPaletteDark == m_d->isPaletteDark) {
387 return; // no need to do anything if there's no change
388 }
389 m_d->isPaletteDark = isPaletteDark;
390 if (auto iconTheme = QIcon::themeName(); iconTheme == QStringLiteral("default") || iconTheme == QStringLiteral("default-dark")) {
391 QIcon::setThemeName(m_d->isPaletteDark ? QStringLiteral("default-dark") : QStringLiteral("default"));
392 }
393}
394
405void QtSettings::reevaluatePaletteAndDefaultIconTheme()
406{
407 reapplyDefaultIconTheme(QtUtilities::isPaletteDark());
408}
409
416bool QtSettings::isPaletteDark()
417{
418 return m_d->isPaletteDark;
419}
420
424bool QtSettings::hasLocaleChanged() const
425{
426 return m_d->previousLocale != QLocale();
427}
428
437OptionCategory *QtSettings::category()
438{
439 auto *category = new OptionCategory;
440 category->setDisplayName(QCoreApplication::translate("QtGui::QtOptionCategory", "Qt"));
441 category->setIcon(QIcon::fromTheme(QStringLiteral("qtcreator"), QIcon(QStringLiteral(":/qtutilities/icons/hicolor/48x48/apps/qtcreator.svg"))));
442 category->assignPages({ new QtAppearanceOptionPage(*m_d), new QtLanguageOptionPage(*m_d), new QtEnvOptionPage(*m_d) });
443 return category;
444}
445
446QtAppearanceOptionPage::QtAppearanceOptionPage(QtSettingsData &settings, QWidget *parentWidget)
447 : QtAppearanceOptionPageBase(parentWidget)
448 , m_settings(settings)
449 , m_fontDialog(nullptr)
450{
451}
452
453QtAppearanceOptionPage::~QtAppearanceOptionPage()
454{
455}
456
457bool QtAppearanceOptionPage::apply()
458{
459 m_settings.font = ui()->fontComboBox->currentFont();
460 m_settings.customFont = !ui()->fontCheckBox->isChecked();
461 m_settings.widgetStyle = ui()->widgetStyleComboBox->currentText();
462 m_settings.customWidgetStyle = !ui()->widgetStyleCheckBox->isChecked();
463 m_settings.styleSheetPath = ui()->styleSheetPathSelection->lineEdit()->text();
464 m_settings.customStyleSheet = !ui()->styleSheetCheckBox->isChecked();
465 m_settings.palette = m_settings.selectedPalette;
466 m_settings.customPalette = !ui()->paletteCheckBox->isChecked();
467 m_settings.iconTheme
468 = ui()->iconThemeComboBox->currentIndex() != -1 ? ui()->iconThemeComboBox->currentData().toString() : ui()->iconThemeComboBox->currentText();
469 m_settings.customIconTheme = !ui()->iconThemeCheckBox->isChecked();
470 return true;
471}
472
473void QtAppearanceOptionPage::reset()
474{
475 ui()->fontComboBox->setCurrentFont(m_settings.font);
476 ui()->fontCheckBox->setChecked(!m_settings.customFont);
477 ui()->widgetStyleComboBox->setCurrentText(
478 m_settings.widgetStyle.isEmpty() ? (QApplication::style() ? QApplication::style()->objectName() : QString()) : m_settings.widgetStyle);
479 ui()->widgetStyleCheckBox->setChecked(!m_settings.customWidgetStyle);
480 ui()->styleSheetPathSelection->lineEdit()->setText(m_settings.styleSheetPath);
481 ui()->styleSheetCheckBox->setChecked(!m_settings.customStyleSheet);
482 m_settings.selectedPalette = m_settings.palette;
483 ui()->paletteCheckBox->setChecked(!m_settings.customPalette);
484 int iconThemeIndex = ui()->iconThemeComboBox->findData(m_settings.iconTheme);
485 if (iconThemeIndex != -1) {
486 ui()->iconThemeComboBox->setCurrentIndex(iconThemeIndex);
487 } else {
488 ui()->iconThemeComboBox->setCurrentText(m_settings.iconTheme);
489 }
490 ui()->iconThemeCheckBox->setChecked(!m_settings.customIconTheme);
491}
492
493QWidget *QtAppearanceOptionPage::setupWidget()
494{
495 // call base implementation first, so ui() is available
496 auto *widget = QtAppearanceOptionPageBase::setupWidget();
497 if (!m_settings.showNotices) {
498 ui()->label->hide();
499 }
500
501 // setup widget style selection
502 ui()->widgetStyleComboBox->addItems(QStyleFactory::keys());
503
504 // setup style sheet selection
505 ui()->styleSheetPathSelection->provideCustomFileMode(QFileDialog::ExistingFile);
506
507 // setup font selection
508 QObject::connect(ui()->fontPushButton, &QPushButton::clicked, widget, [this] {
509 if (!m_fontDialog) {
510 m_fontDialog = new QFontDialog(this->widget());
511 m_fontDialog->setCurrentFont(ui()->fontComboBox->font());
512 QObject::connect(m_fontDialog, &QFontDialog::fontSelected, ui()->fontComboBox, &QFontComboBox::setCurrentFont);
513 QObject::connect(ui()->fontComboBox, &QFontComboBox::currentFontChanged, m_fontDialog, &QFontDialog::setCurrentFont);
514 }
515 m_fontDialog->show();
516 });
517
518 // setup palette selection
519 QObject::connect(ui()->paletteToolButton, &QToolButton::clicked, ui()->paletteToolButton,
520 [this] { m_settings.selectedPalette = PaletteEditor::getPalette(this->widget(), m_settings.selectedPalette); });
521
522 // setup icon theme selection
523 const auto iconThemes = scanIconThemes(QIcon::themeSearchPaths() << QStringLiteral("/usr/share/icons/"));
524 auto *iconThemeComboBox = ui()->iconThemeComboBox;
525 for (auto i = iconThemes.begin(), end = iconThemes.end(); i != end; ++i) {
526 const auto &displayName = i.key();
527 const auto &id = i.value();
528 if (const auto existingItemIndex = iconThemeComboBox->findData(id); existingItemIndex != -1) {
529 iconThemeComboBox->setItemText(existingItemIndex, displayName);
530 } else {
531 iconThemeComboBox->addItem(displayName, id);
532 }
533 }
534
535 return widget;
536}
537
538QtLanguageOptionPage::QtLanguageOptionPage(QtSettingsData &settings, QWidget *parentWidget)
539 : QtLanguageOptionPageBase(parentWidget)
540 , m_settings(settings)
541{
542}
543
544QtLanguageOptionPage::~QtLanguageOptionPage()
545{
546}
547
548bool QtLanguageOptionPage::apply()
549{
550 m_settings.localeName = ui()->localeComboBox->currentText();
551 m_settings.customLocale = !ui()->localeCheckBox->isChecked();
552 return true;
553}
554
555void QtLanguageOptionPage::reset()
556{
557 ui()->localeComboBox->setCurrentText(m_settings.localeName);
558 ui()->localeCheckBox->setChecked(!m_settings.customLocale);
559}
560
561QWidget *QtLanguageOptionPage::setupWidget()
562{
563 // call base implementation first, so ui() is available
564 auto *widget = QtLanguageOptionPageBase::setupWidget();
565 if (m_settings.retranslatable) {
566 ui()->label->hide();
567 }
568
569 // add all available locales to combo box
570 auto *localeComboBox = ui()->localeComboBox;
571 const auto locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
572 for (const QLocale &locale : locales) {
573 localeComboBox->addItem(locale.name());
574 }
575
576 auto *languageLabel = ui()->languageLabel;
577 QObject::connect(ui()->localeComboBox, &QComboBox::currentTextChanged, languageLabel, [languageLabel, localeComboBox] {
578 const auto selectedLocale = QLocale(localeComboBox->currentText());
579 const auto currentLocale = QLocale();
580 const auto territory =
581#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
582 currentLocale.territoryToString(selectedLocale.territory());
583#else
584 currentLocale.countryToString(selectedLocale.country());
585#endif
586 languageLabel->setText(QCoreApplication::translate("QtGui::QtLanguageOptionPage", "recognized by Qt as") % QStringLiteral(" <i>")
587 % currentLocale.languageToString(selectedLocale.language()) % QChar(',') % QChar(' ') % territory % QStringLiteral("</i>"));
588 });
589 return widget;
590}
591
592QtEnvOptionPage::QtEnvOptionPage(QtSettingsData &settings, QWidget *parentWidget)
593 : QtEnvOptionPageBase(parentWidget)
594 , m_settings(settings)
595{
596}
597
598QtEnvOptionPage::~QtEnvOptionPage()
599{
600}
601
602bool QtEnvOptionPage::apply()
603{
604 m_settings.additionalPluginDirectory = ui()->pluginPathSelection->lineEdit()->text();
605 m_settings.additionalIconThemeSearchPath = ui()->iconThemeSearchPathSelection->lineEdit()->text();
606 TranslationFiles::additionalTranslationFilePath() = ui()->translationPathSelection->lineEdit()->text();
607 return true;
608}
609
610void QtEnvOptionPage::reset()
611{
612 ui()->pluginPathSelection->lineEdit()->setText(m_settings.additionalPluginDirectory);
613 ui()->iconThemeSearchPathSelection->lineEdit()->setText(m_settings.additionalIconThemeSearchPath);
614 ui()->translationPathSelection->lineEdit()->setText(TranslationFiles::additionalTranslationFilePath());
615}
616
617QWidget *QtEnvOptionPage::setupWidget()
618{
619 // call base implementation first, so ui() is available
620 return QtEnvOptionPageBase::setupWidget();
621}
622
629QtSettings::operator QtSettingsData &() const
630{
631 return *m_d.get();
632}
633
634} // namespace QtUtilities
635
636INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(QtAppearanceOptionPage)
637INSTANTIATE_UI_FILE_BASED_OPTION_PAGE(QtLanguageOptionPage)
void setDisplayName(const QString &displayName)
Sets the display name of the category.
void assignPages(const QList< OptionPage * > &pages)
Assigns the specified pages to the category.
void setIcon(const QIcon &icon)
Sets the icon of the category.
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