diff options
author | Alex Merry <alex.merry@kde.org> | 2015-11-08 23:10:59 +0000 |
---|---|---|
committer | Alex Merry <alex.merry@kde.org> | 2015-11-30 14:07:07 +0000 |
commit | fb4d91996affdbcd3ffc19c7646fbf77b96bf1e5 (patch) | |
tree | 2f66eec33d3b7b96691fe5aa5f9fe96f2029229f /modules | |
parent | 1e8e0da3eb475bb8b78baa54cb0c34b913c2dc5d (diff) | |
download | extra-cmake-modules-fb4d91996affdbcd3ffc19c7646fbf77b96bf1e5.tar.gz extra-cmake-modules-fb4d91996affdbcd3ffc19c7646fbf77b96bf1e5.tar.bz2 |
Make sure we load translations on the main thread.
Because the old implementation (accidentally) worked when you put the
ecm_create_qm_loader call in a different CMakeLists.txt file to the
target the file was added to, some projects did this.
This won't work with build-time-generated files, though, like moc files.
So we (ab)use QTimer events to make the loading happen on the main
thread.
BUG: 346188
REVIEW: 126000
Diffstat (limited to 'modules')
-rw-r--r-- | modules/ECMPoQmTools.cmake | 17 | ||||
-rw-r--r-- | modules/ECMQmLoader.cpp.in | 93 |
2 files changed, 79 insertions, 31 deletions
diff --git a/modules/ECMPoQmTools.cmake b/modules/ECMPoQmTools.cmake index 0af5b12f..2547df1a 100644 --- a/modules/ECMPoQmTools.cmake +++ b/modules/ECMPoQmTools.cmake @@ -28,8 +28,8 @@ # # ecm_create_qm_loader(<source_files_var> <catalog_name>) # -# Generates a C++ file which ensures translations are automatically loaded at -# startup. The path of the .cpp file is appended to ``<source_files_var>``. +# Generates C++ code which ensures translations are automatically loaded at +# startup. The generated files are appended to ``<source_files_var>``. # # It assumes that the .qm file for the language code ``<lang>`` is installed as # ``<sharedir>/locale/<lang>/LC_MESSAGES/<catalog_name>.qm``, where @@ -99,9 +99,16 @@ endfunction() function(ecm_create_qm_loader out_var catalog_name) - # catalog_name is used in ECMQmLoader.cpp.in - configure_file(${ECM_MODULE_DIR}/ECMQmLoader.cpp.in ECMQmLoader-${catalog_name}.cpp @ONLY) - set(${out_var} ${${out_var}} ${CMAKE_CURRENT_BINARY_DIR}/ECMQmLoader-${catalog_name}.cpp PARENT_SCOPE) + set(loader_base ${CMAKE_CURRENT_BINARY_DIR}/ECMQmLoader-${catalog_name}) + + set(QM_LOADER_CATALOG_NAME "${catalog_name}") + + configure_file( + ${ECM_MODULE_DIR}/ECMQmLoader.cpp.in + "${loader_base}.cpp" + @ONLY + ) + set(${out_var} "${${out_var}}" "${loader_base}.cpp" PARENT_SCOPE) endfunction() diff --git a/modules/ECMQmLoader.cpp.in b/modules/ECMQmLoader.cpp.in index 423d1c93..97c5c826 100644 --- a/modules/ECMQmLoader.cpp.in +++ b/modules/ECMQmLoader.cpp.in @@ -33,40 +33,81 @@ #include <QCoreApplication> #include <QLocale> #include <QStandardPaths> +#include <QThread> #include <QTranslator> -#include <QDebug> +namespace { -static bool loadTranslation(const QString &localeDirName) -{ - QString subPath = QStringLiteral("locale/") + localeDirName + QStringLiteral("/LC_MESSAGES/@catalog_name@.qm"); - QString fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, subPath); - if (fullPath.isEmpty()) { - return false; + bool loadTranslation(const QString &localeDirName) + { + QString subPath = QStringLiteral("locale/") + localeDirName + QStringLiteral("/LC_MESSAGES/@QM_LOADER_CATALOG_NAME@.qm"); + QString fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, subPath); + if (fullPath.isEmpty()) { + return false; + } + QTranslator *translator = new QTranslator(QCoreApplication::instance()); + if (!translator->load(fullPath)) { + delete translator; + return false; + } + QCoreApplication::instance()->installTranslator(translator); + return true; } - QTranslator *translator = new QTranslator(QCoreApplication::instance()); - if (!translator->load(fullPath)) { - delete translator; - return false; + + void load() + { + // The way Qt translation system handles plural forms makes it necessary to + // have a translation file which contains only plural forms for `en`. That's + // why we load the `en` translation unconditionally, then load the + // translation for the current locale to overload it. + loadTranslation(QStringLiteral("en")); + + QLocale locale = QLocale::system(); + if (locale.name() != QStringLiteral("en")) { + if (!loadTranslation(locale.name())) { + loadTranslation(locale.bcp47Name()); + } + } } - QCoreApplication::instance()->installTranslator(translator); - return true; -} -static void load() -{ - // The way Qt translation system handles plural forms makes it necessary to - // have a translation file which contains only plural forms for `en`. That's - // why we load the `en` translation unconditionally, then load the - // translation for the current locale to overload it. - loadTranslation(QStringLiteral("en")); + // Helper to call load() on the main thread. + // + // Calling functions on another thread without using moc is non-trivial in + // Qt until 5.4 (when some useful QTimer::singleShot overloads were added). + // + // Instead, we have to use QEvents. Ideally, we'd use a custom QEvent, but + // there's a chance this could cause trouble with applications that claim + // QEvent codes themselves, but don't register them with Qt (and we also + // want to avoid registering a new QEvent code for every plugin that might + // be loaded). We use QTimer because it's unlikely to be filtered by + // applications, and is also unlikely to cause Qt to do something it + // shouldn't. + class Loader : public QObject + { + protected: + void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE + { + load(); + this->deleteLater(); + } + }; - QLocale locale = QLocale::system(); - if (locale.name() != QStringLiteral("en")) { - if (!loadTranslation(locale.name())) { - loadTranslation(locale.bcp47Name()); + void loadOnMainThread() + { + // If this library is loaded after the QCoreApplication instance is + // created (eg: because it is brought in by a plugin), there is no + // guarantee this function will be called on the main thread. + // QCoreApplication::installTranslator needs to be called on the main + // thread, because it uses QCoreApplication::sendEvent. + if (QThread::currentThread() == QCoreApplication::instance()->thread()) { + load(); + } else { + // QObjects inherit their parent object's thread + Loader *loader = new Loader(); + loader->moveToThread(QCoreApplication::instance()->thread()); + QCoreApplication::instance()->postEvent(loader, new QTimerEvent(0), Qt::HighEventPriority); } } } -Q_COREAPP_STARTUP_FUNCTION(load) +Q_COREAPP_STARTUP_FUNCTION(loadOnMainThread) |