Qt Utilities 6.18.3
Common Qt related C++ classes and routines used by my applications such as dialogs, widgets and models
Loading...
Searching...
No Matches
resources.cpp
Go to the documentation of this file.
1#include "./resources.h"
2
3#include "resources/config.h"
4
5#include <QDir>
6#include <QFile>
7#include <QFont>
8#include <QIcon>
9#include <QLibraryInfo>
10#include <QLocale>
11#include <QSettings>
12#include <QString>
13#include <QStringBuilder>
14#include <QTranslator>
15#if defined(QT_UTILITIES_GUI_QTWIDGETS)
16#include <QApplication>
17#elif defined(QT_UTILITIES_GUI_QTQUICK)
18#include <QGuiApplication>
19#else
20#include <QCoreApplication>
21#endif
22
23#ifdef Q_OS_ANDROID
24#include <QDebug>
25#include <QStandardPaths>
26#endif
27
28#ifdef Q_OS_ANDROID
29#include <c++utilities/conversion/stringbuilder.h>
30#endif
31
32#include <iostream>
33
34using namespace std;
35
37inline void initResources()
38{
39 Q_INIT_RESOURCE(qtutilsicons);
40}
41
42inline void cleanupResources()
43{
44 Q_CLEANUP_RESOURCE(qtutilsicons);
45}
47
48namespace QtUtilities {
49
54namespace QtUtilitiesResources {
55
60void init()
61{
62 initResources();
63}
64
69void cleanup()
70{
71 cleanupResources();
72}
73} // namespace QtUtilitiesResources
74
78namespace TranslationFiles {
79
83static QList<QTranslator *> translators;
84
90{
91 static QString path;
92 return path;
93}
94
96static QString relativeBase()
97{
98 static const auto relativeBase = [] {
99 auto appDir = QCoreApplication::applicationDirPath();
100 if (appDir.isEmpty()) {
101 appDir = QStringLiteral(".");
102 }
103 return appDir;
104 }();
105 return relativeBase;
106}
108
123void loadQtTranslationFile(std::initializer_list<QString> repositoryNames)
124{
125 loadQtTranslationFile(repositoryNames, QLocale().name());
126}
127
143void loadQtTranslationFile(initializer_list<QString> repositoryNames, const QString &localeName)
144{
145 const auto debugTranslations = qEnvironmentVariableIntValue("QT_DEBUG_TRANSLATIONS");
146 const auto relBase = relativeBase();
147 for (const auto &repoName : repositoryNames) {
148 auto *const qtTranslator = new QTranslator(QCoreApplication::instance());
149 const auto fileName = QString(repoName % QChar('_') % localeName);
150
151 QString path;
152 if (
153 // allow putting translations into configurable directory
154 (!additionalTranslationFilePath().isEmpty() && qtTranslator->load(fileName, path = additionalTranslationFilePath()))
155 // allow putting translations next to the Qt libraries
156 || qtTranslator->load(fileName,
157 path =
158#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
159 QLibraryInfo::location(QLibraryInfo::TranslationsPath)
160#else
161 QLibraryInfo::path(QLibraryInfo::TranslationsPath)
162#endif
163 )
164 // load translations from the "share" directory to support installations following the usual UNIX file system hierarchy
165 || qtTranslator->load(fileName, path = relBase + QStringLiteral("/../share/qt/translations"))
166 // load built-in translations
167 || qtTranslator->load(fileName, path = QStringLiteral(":/translations"))) {
168 QCoreApplication::installTranslator(qtTranslator);
169 translators.append(qtTranslator);
170 if (debugTranslations) {
171 cerr << "Loading translation file for Qt repository \"" << repoName.toLocal8Bit().data() << "\" and the locale \""
172 << localeName.toLocal8Bit().data() << "\" from \"" << path.toLocal8Bit().data() << "\"." << endl;
173 }
174 } else {
175 delete qtTranslator;
176 if (localeName.startsWith(QLatin1String("en"))) {
177 // the translation file is probably just empty (English is built-in and usually only used for plural forms)
178 continue;
179 }
180#if defined(Q_OS_ANDROID)
181 qDebug() << "Unable to load translation file for Qt repository: " << repoName << localeName;
182#else
183 cerr << "Unable to load translation file for Qt repository \"" << repoName.toLocal8Bit().data() << "\" and locale "
184 << localeName.toLocal8Bit().data() << "." << endl;
185#endif
186 }
187 }
188}
189
210void loadApplicationTranslationFile(const QString &configName, const QString &applicationName)
211{
212 // load English translation files as fallback
213 loadApplicationTranslationFile(configName, applicationName, QStringLiteral("en_US"));
214 // load translation files for current locale
215 const auto defaultLocale(QLocale().name());
216 if (defaultLocale != QLatin1String("en_US")) {
217 loadApplicationTranslationFile(configName, applicationName, defaultLocale);
218 }
219}
220
222static void logTranslationEvent(
223 const char *event, const QString &configName, const QString &applicationName, const QString &localeName, const QString &path = QString())
224{
225#if defined(Q_OS_ANDROID)
226 qDebug() << CppUtilities::argsToString(event, " translation file for: ").data() << applicationName << localeName;
227 if (!configName.isEmpty()) {
228 qDebug() << "config: " << configName;
229 }
230 if (!path.isEmpty()) {
231 qDebug() << "path: " << path;
232 }
233#else
234 cerr << event << " translation file for \"" << applicationName.toLocal8Bit().data() << "\"";
235 if (!configName.isEmpty()) {
236 cerr << " (config \"" << configName.toLocal8Bit().data() << "\")";
237 }
238 cerr << " and locale \"" << localeName.toLocal8Bit().data() << '\"';
239 if (!path.isEmpty()) {
240 cerr << " from \"" << path.toLocal8Bit().data() << '\"';
241 }
242 cerr << '.' << endl;
243#endif
244}
246
268void loadApplicationTranslationFile(const QString &configName, const QString &applicationName, const QString &localeName)
269{
270 auto *const appTranslator = new QTranslator(QCoreApplication::instance());
271 const auto fileName = QString(applicationName % QChar('_') % localeName);
272 const auto directoryName = configName.isEmpty() ? applicationName : QString(applicationName % QChar('-') % configName);
273 const auto relBase = relativeBase();
274
275 if (auto path = QString();
276 // allow putting translations into configurable directory
277 (!additionalTranslationFilePath().isEmpty() && appTranslator->load(fileName, path = additionalTranslationFilePath()))
278 // allow putting translations next to the executable (useful during development and for easily supplying additional
279 // translations, e.g. under Windows)
280 || appTranslator->load(fileName, path = relBase)
281 // load translations from sibling directories (useful during development to load translations of e.g. qtutilities
282 // from its build directory)
283 || appTranslator->load(fileName, path = relBase % QStringLiteral("/../") % applicationName)
284 || appTranslator->load(fileName, path = relBase % QStringLiteral("/../../") % applicationName)
285 // allow putting translations next to the executable into "translations" subdirectory (useful for easily supplying
286 // additional translations, e.g. under Windows)
287 || appTranslator->load(fileName, path = relBase % QStringLiteral("/translations"))
288 // load translations from the "share" directory to support installations following the usual UNIX file system hierarchy
289 || appTranslator->load(fileName, path = relBase % QStringLiteral("/../share/") % directoryName % QStringLiteral("/translations"))
290 || appTranslator->load(fileName, path = QStringLiteral(APP_INSTALL_PREFIX "/share/") % directoryName % QStringLiteral("/translations"))
291 // load built-in translations
292 || appTranslator->load(fileName, path = QStringLiteral(":/translations"))) {
293 QCoreApplication::installTranslator(appTranslator);
294 translators.append(appTranslator);
295 if (qEnvironmentVariableIntValue("QT_DEBUG_TRANSLATIONS")) {
296 logTranslationEvent("Loading", configName, applicationName, localeName, path);
297 }
298 } else {
299 delete appTranslator;
300 if (localeName.startsWith(QLatin1String("en"))) {
301 // the translation file is probably just empty (English is built-in and usually only used for plural forms)
302 return;
303 }
304 logTranslationEvent("Unable to load", configName, applicationName, localeName);
305 }
306}
307
314void loadApplicationTranslationFile(const QString &configName, const std::initializer_list<QString> &applicationNames)
315{
316 for (const QString &applicationName : applicationNames) {
317 loadApplicationTranslationFile(configName, applicationName);
318 }
319}
320
328void loadApplicationTranslationFile(const QString &configName, const std::initializer_list<QString> &applicationNames, const QString &localeName)
329{
330 for (const QString &applicationName : applicationNames) {
331 loadApplicationTranslationFile(configName, applicationName, localeName);
332 }
333}
334
339{
340 for (auto *const translator : translators) {
341 QCoreApplication::removeTranslator(translator);
342 }
343 translators.clear();
344}
345
346} // namespace TranslationFiles
347
353namespace ApplicationInstances {
354
355#if defined(QT_UTILITIES_GUI_QTWIDGETS)
359bool hasWidgetsApp()
360{
361 return qobject_cast<QApplication *>(QCoreApplication::instance()) != nullptr;
362}
363#endif
364
365#if defined(QT_UTILITIES_GUI_QTWIDGETS) || defined(QT_UTILITIES_GUI_QTQUICK)
369bool hasGuiApp()
370{
371 return qobject_cast<QGuiApplication *>(QCoreApplication::instance()) != nullptr;
372}
373#endif
374
379{
380 return qobject_cast<QCoreApplication *>(QCoreApplication::instance()) != nullptr;
381}
382} // namespace ApplicationInstances
383
391{
392 // enable dark window frame on Windows if the configured color palette is dark
393 // - supported as of Qt 6.4; no longer required as of Qt 6.5
394 // - see https://bugreports.qt.io/browse/QTBUG-72028?focusedCommentId=677819#comment-677819
395 // and https://www.qt.io/blog/dark-mode-on-windows-11-with-qt-6.5
396#if defined(Q_OS_WINDOWS) && (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) && (QT_VERSION < QT_VERSION_CHECK(6, 5, 0))
397 if (const auto qtVersion = QLibraryInfo::version();
398 qtVersion >= QVersionNumber(6, 4, 0) && qtVersion < QVersionNumber(6, 5, 0) && !qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
399 qputenv("QT_QPA_PLATFORM", "windows:darkmode=1");
400 }
401#endif
402
403 // ensure FONTCONFIG_PATH is set (mainly required for static GNU/Linux builds)
404#ifdef QT_FEATURE_fontdialog
405 if (!qEnvironmentVariableIsSet("FONTCONFIG_PATH") && QDir(QStringLiteral("/etc/fonts")).exists()) {
406 qputenv("FONTCONFIG_PATH", "/etc/fonts");
407 }
408#endif
409
410 // enable settings for High-DPI scaling
411#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
412 if (!QCoreApplication::instance()) {
413 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
414 }
415 QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
416#endif
417}
418
431std::unique_ptr<QSettings> getSettings(const QString &organization, const QString &application)
432{
433 auto settings = std::unique_ptr<QSettings>();
434 const auto portableFileName
435 = application.isEmpty() ? organization + QStringLiteral(".ini") : organization % QChar('/') % application % QStringLiteral(".ini");
436 if (const auto portableFileWorkingDir = QFile(portableFileName); portableFileWorkingDir.exists()) {
437 settings = std::make_unique<QSettings>(portableFileWorkingDir.fileName(), QSettings::IniFormat);
438 } else if (const auto portableFileNextToApp = QFile(QCoreApplication::applicationDirPath() % QChar('/') % portableFileName);
439 portableFileNextToApp.exists()) {
440 settings = std::make_unique<QSettings>(portableFileNextToApp.fileName(), QSettings::IniFormat);
441 } else {
442 settings = std::make_unique<QSettings>(QSettings::IniFormat, QSettings::UserScope, organization, application);
443 // move config created by older versions to new location
444 if (organization != QCoreApplication::organizationName() || application != QCoreApplication::applicationName()) {
445 const auto oldConfig
446 = QSettings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName())
447 .fileName();
448 QFile::rename(oldConfig, settings->fileName()) || QFile::remove(oldConfig);
449 }
450 }
451 loadSettingsWithLogging(*settings);
452 return settings;
453}
454
458void loadSettingsWithLogging(QSettings &settings)
459{
460 settings.sync();
461 if (!qEnvironmentVariableIntValue("QT_DEBUG_SETTINGS")) {
462 return;
463 }
464 const auto error = errorMessageForSettings(settings);
465 if (error.isEmpty()) {
466 std::cerr << "Loaded/synced settings from " << settings.fileName().toStdString() << '\n';
467 } else {
468 std::cerr << "Unable to load settings: " << error.toStdString() << '\n';
469 }
470}
471
475void saveSettingsWithLogging(QSettings &settings)
476{
477 settings.sync();
478 if (!qEnvironmentVariableIntValue("QT_DEBUG_SETTINGS")) {
479 return;
480 }
481 const auto error = errorMessageForSettings(settings);
482 if (error.isEmpty()) {
483 std::cerr << "Saved/synced settings to " << settings.fileName().toStdString() << '\n';
484 } else {
485 std::cerr << "Unable to save settings: " << error.toStdString() << '\n';
486 }
487}
488
492QString errorMessageForSettings(const QSettings &settings)
493{
494 auto errorMessage = QString();
495 switch (settings.status()) {
496 case QSettings::NoError:
497 return QString();
498 case QSettings::AccessError:
499 errorMessage = QCoreApplication::translate("QtUtilities", "unable to access file");
500 break;
501 case QSettings::FormatError:
502 errorMessage = QCoreApplication::translate("QtUtilities", "file has invalid format");
503 break;
504 default:
505 errorMessage = QCoreApplication::translate("QtUtilities", "unknown error");
506 }
507 return QCoreApplication::translate("QtUtilities", "Unable to sync settings from \"%1\": %2").arg(settings.fileName(), errorMessage);
508}
509
514{
515#ifdef Q_OS_ANDROID
516 // delete OpenGL pipeline cache under Android as it seems to break loading the app in certain cases
517 const auto cachePaths = QStandardPaths::standardLocations(QStandardPaths::CacheLocation);
518 for (const auto &cachePath : cachePaths) {
519 const auto cacheDir = QDir(cachePath);
520 const auto subdirs = cacheDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
521 for (const auto &subdir : subdirs) {
522 if (subdir.startsWith(QLatin1String("qtpipelinecache"))) {
523 QFile::remove(cachePath % QChar('/') % subdir % QStringLiteral("/qqpc_opengl"));
524 }
525 }
526 }
527#endif
528}
529
530} // namespace QtUtilities
QT_UTILITIES_EXPORT bool hasCoreApp()
Returns whether a QCoreApplication has been instantiated yet.
Functions for using the resources provided by this library.
Definition resources.h:53
QT_UTILITIES_EXPORT void init()
Initiates the resources used and provided by this library.
Definition resources.cpp:60
QT_UTILITIES_EXPORT void cleanup()
Frees the resources used and provided by this library.
Definition resources.cpp:69
QT_UTILITIES_EXPORT void loadQtTranslationFile(std::initializer_list< QString > repositoryNames)
Loads and installs the appropriate Qt translation file for the current locale.
QT_UTILITIES_EXPORT void clearTranslationFiles()
Clears all translation files previously loaded via the load-functions in this namespace.
QT_UTILITIES_EXPORT void loadApplicationTranslationFile(const QString &configName, const QString &applicationName)
Loads and installs the appropriate application translation file for the current locale.
QT_UTILITIES_EXPORT QString & additionalTranslationFilePath()
Allows to set an additional search path for translation files.
Definition resources.cpp:89
QT_UTILITIES_EXPORT void loadSettingsWithLogging(QSettings &settings)
Loads settings and logs a corresponding message if the env variable QT_DEBUG_SETTINGS is set.
QT_UTILITIES_EXPORT std::unique_ptr< QSettings > getSettings(const QString &organization, const QString &application=QString())
Returns the settings object for the specified organization and application.
QT_UTILITIES_EXPORT void deletePipelineCacheIfNeeded()
Deletes the Qt Quick pipeline cache on platforms where this is needed to workaround issues with the c...
QT_UTILITIES_EXPORT void setupCommonQtApplicationAttributes()
Sets Qt application attributes which are commonly used within my Qt applications.
QT_UTILITIES_EXPORT void saveSettingsWithLogging(QSettings &settings)
Saves settings and logs a corresponding message if the env variable QT_DEBUG_SETTINGS is set.
QT_UTILITIES_EXPORT QString errorMessageForSettings(const QSettings &settings)
Returns an error message for the specified settings or an empty string if there's no error.