aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2021-10-07 14:25:50 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2021-10-11 09:39:28 +0200
commitf51231164e7074bfcd4deea31e82428b31483e53 (patch)
tree405c02b2f98b46b2a701e2839bc2f536a881b57a
parentd8fcc1b8f4c20536cad4f806c4c42d3b8086115f (diff)
QQmlEngine: Fine grained translation binding tracking
This commit changes translation bindings in such a way that they always depend on the a newly introduced translationLanguage property of the QML engine. Moreover, captureTranslationBinding is changed in the same way, hence any binding containing a call to a translation function like qsTr will also be refreshed. After those changes, QQmlEngine::retranslate can be implemented as a notify on translationLanguage. Finally, QQmlTranslationPropertyBinding is also changed to depend on translationLanguage instead of uiLanguage to ensure consistency between the various binding types. Note that we do not use the existing uiLanguage property of QJSEngine. If we were to do this, changing uiLanguage would already trigger the binding reevaluation, which is a behavior change. In case someone is manually connecting uiLanguageChanged to QQmlEngine::retranslate, this would cause a double evaluation of translation bindings. In a future change, we can however introduce a new enableAutoTranslationUpdate method, which would cause binding evaluation as soon as uiLanguage is changed. Special care is taken for ListModel/ListElement, which does not store a proper translation binding, but instead operates on QV4::CompiledData::Binding. For now, we simply refresh the complete ListModel in case it contains a translation binding. It should be possible to optimize this further in a future change. Note that only "static" ListElement properties can contain translations: The dynamic insertion API makes it impossible to insert a translation, as it would already be resolved to a string when insert is called. [ChangeLog][QQmlEngine] QQmlEngine::retranslate no longer refreshes all bindings, but only translation bindings. Fixes: QTBUG-96192 Change-Id: I54d213fd46b6914e8f686843b49e155909117218 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> (cherry picked from commit d4039298e710b6b79c018589bcc2145e22e1d5aa)
-rw-r--r--src/qml/qml/qqmlapplicationengine.cpp7
-rw-r--r--src/qml/qml/qqmlbinding.cpp9
-rw-r--r--src/qml/qml/qqmlengine.cpp11
-rw-r--r--src/qml/qml/qqmlengine_p.h1
-rw-r--r--src/qml/qml/qqmljavascriptexpression.cpp18
-rw-r--r--src/qml/qml/qqmljavascriptexpression_p.h15
-rw-r--r--src/qml/qml/qqmlpropertybinding.cpp10
-rw-r--r--src/qmlmodels/qqmllistmodel.cpp35
-rw-r--r--src/qmlmodels/qqmllistmodel_p.h3
-rw-r--r--src/qmlmodels/qqmllistmodel_p_p.h2
10 files changed, 74 insertions, 37 deletions
diff --git a/src/qml/qml/qqmlapplicationengine.cpp b/src/qml/qml/qqmlapplicationengine.cpp
index e0ab25892f..83323a7087 100644
--- a/src/qml/qml/qqmlapplicationengine.cpp
+++ b/src/qml/qml/qqmlapplicationengine.cpp
@@ -73,7 +73,9 @@ void QQmlApplicationEnginePrivate::init()
&QCoreApplication::quit, Qt::QueuedConnection);
q->connect(q, &QQmlApplicationEngine::exit, QCoreApplication::instance(),
&QCoreApplication::exit, Qt::QueuedConnection);
- q->connect(q, SIGNAL(uiLanguageChanged()), q_func(), SLOT(_q_loadTranslations()));
+ QObject::connect(q, &QJSEngine::uiLanguageChanged, q, [this](){
+ _q_loadTranslations();
+ });
#if QT_CONFIG(translation)
QTranslator* qtTranslator = new QTranslator(q);
if (qtTranslator->load(QLocale(), QLatin1String("qt"), QLatin1String("_"), QLibraryInfo::path(QLibraryInfo::TranslationsPath), QLatin1String(".qm")))
@@ -89,11 +91,10 @@ void QQmlApplicationEnginePrivate::init()
void QQmlApplicationEnginePrivate::_q_loadTranslations()
{
#if QT_CONFIG(translation)
+ Q_Q(QQmlApplicationEngine);
if (translationsDirectory.isEmpty())
return;
- Q_Q(QQmlApplicationEngine);
-
QScopedPointer<QTranslator> translator(new QTranslator);
if (!uiLanguage.value().isEmpty()) {
QLocale locale(uiLanguage);
diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp
index c137a85654..4138e882cc 100644
--- a/src/qml/qml/qqmlbinding.cpp
+++ b/src/qml/qml/qqmlbinding.cpp
@@ -394,12 +394,14 @@ protected:
}
};
-class QQmlTranslationBinding : public GenericBinding<QMetaType::QString> {
+class QQmlTranslationBinding : public GenericBinding<QMetaType::QString>, public QPropertyObserver {
public:
QQmlTranslationBinding(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding)
+ : QPropertyObserver(&QQmlTranslationBinding::onLanguageChange)
{
setCompilationUnit(compilationUnit);
m_binding = binding;
+ setSource(QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage);
}
QQmlSourceLocation sourceLocation() const override final
@@ -407,6 +409,9 @@ public:
return QQmlSourceLocation(m_compilationUnit->fileName(), m_binding->valueLocation.line, m_binding->valueLocation.column);
}
+ static void onLanguageChange(QPropertyObserver *observer, QUntypedPropertyData *)
+ { static_cast<QQmlTranslationBinding *>(observer)->update(); }
+
void doUpdate(const DeleteWatcher &watcher,
QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) override final
{
@@ -788,7 +793,7 @@ QVector<QQmlProperty> QQmlBinding::dependencies() const
bool QQmlBinding::hasDependencies() const
{
- return !activeGuards.isEmpty() || translationsCaptured() || qpropertyChangeTriggers;
+ return !activeGuards.isEmpty() || qpropertyChangeTriggers;
}
class QObjectPointerBinding: public QQmlNonbindingBinding
diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp
index 4188b63240..b3cf5eea82 100644
--- a/src/qml/qml/qqmlengine.cpp
+++ b/src/qml/qml/qqmlengine.cpp
@@ -1260,21 +1260,12 @@ QJSValue QQmlEngine::singletonInstance<QJSValue>(int qmlTypeId)
QCoreApplication::installTranslator, to ensure that your user-interface
shows up-to-date translations.
- \note Due to a limitation in the implementation, this function
- refreshes all the engine's bindings, not only those that use strings
- marked for translation.
- This may be optimized in a future release.
-
\since 5.10
*/
void QQmlEngine::retranslate()
{
Q_D(QQmlEngine);
- for (QQmlRefPointer<QQmlContextData> context
- = QQmlContextData::get(d->rootContext)->childContexts();
- context; context = context->nextChild()) {
- context->refreshExpressions();
- }
+ d->translationLanguage.notify();
}
/*!
diff --git a/src/qml/qml/qqmlengine_p.h b/src/qml/qml/qqmlengine_p.h
index 18a0164028..9e83aa9470 100644
--- a/src/qml/qml/qqmlengine_p.h
+++ b/src/qml/qml/qqmlengine_p.h
@@ -162,6 +162,7 @@ public:
QRecyclePool<TriggerList> qPropertyTriggerPool;
QQmlContext *rootContext;
+ Q_OBJECT_BINDABLE_PROPERTY(QQmlEnginePrivate, QString, translationLanguage);
#if !QT_CONFIG(qml_debug)
static const quintptr profiler = 0;
diff --git a/src/qml/qml/qqmljavascriptexpression.cpp b/src/qml/qml/qqmljavascriptexpression.cpp
index 1e32cd6926..107ff54888 100644
--- a/src/qml/qml/qqmljavascriptexpression.cpp
+++ b/src/qml/qml/qqmljavascriptexpression.cpp
@@ -221,9 +221,6 @@ public:
while (QQmlJavaScriptExpressionGuard *g = capture.guards.takeFirst())
g->Delete();
- if (!watcher.wasDeleted())
- capture.expression->setTranslationsCaptured(capture.translationCaptured);
-
ep->propertyCapture = lastPropertyCapture;
}
@@ -393,6 +390,21 @@ void QQmlPropertyCapture::captureProperty(
captureNonBindableProperty(o, propertyData->notifyIndex(), propertyData->coreIndex(), doNotify);
}
+void QQmlPropertyCapture::captureTranslation()
+{
+ // use a unique invalid index to avoid needlessly querying the metaobject for
+ // the correct index of of the translationLanguage property
+ int const invalidIndex = -2;
+ for (auto trigger = expression->qpropertyChangeTriggers; trigger;
+ trigger = trigger->next) {
+ if (trigger->target == engine && trigger->propertyIndex == invalidIndex)
+ return; // already installed
+ }
+ auto trigger = expression->allocatePropertyChangeTrigger(engine, invalidIndex);
+
+ trigger->setSource(QQmlEnginePrivate::get(engine)->translationLanguage);
+}
+
void QQmlPropertyCapture::captureBindableProperty(
QObject *o, const QMetaObject *metaObjectForBindable, int c)
{
diff --git a/src/qml/qml/qqmljavascriptexpression_p.h b/src/qml/qml/qqmljavascriptexpression_p.h
index dc2289a357..bfdc922729 100644
--- a/src/qml/qml/qqmljavascriptexpression_p.h
+++ b/src/qml/qml/qqmljavascriptexpression_p.h
@@ -187,22 +187,12 @@ protected:
QForwardFieldList<QQmlJavaScriptExpressionGuard, &QQmlJavaScriptExpressionGuard::next, GuardTag> activeGuards;
- void setTranslationsCaptured(bool captured) {
- Tag newTag = captured ? TranslationsCaptured : NoTag;
- if (m_error.tag() & InEvaluationLoop)
- newTag = Tag(newTag | InEvaluationLoop);
- m_error.setTag(newTag);
- }
- bool translationsCaptured() const { return m_error.tag() & TranslationsCaptured; }
-
enum Tag {
NoTag,
- TranslationsCaptured,
InEvaluationLoop
};
- // m_error:flag1 translationsCapturedDuringEvaluation
- QTaggedPointer<QQmlDelayedError> m_error;
+ QTaggedPointer<QQmlDelayedError, Tag> m_error;
private:
friend class QQmlContextData;
@@ -239,14 +229,13 @@ public:
void captureProperty(QQmlNotifier *);
void captureProperty(QObject *, int, int, bool doNotify = true);
void captureProperty(QObject *, const QQmlPropertyCache *, const QQmlPropertyData *, bool doNotify = true);
- void captureTranslation() { translationCaptured = true; }
+ void captureTranslation();
QQmlEngine *engine;
QQmlJavaScriptExpression *expression;
QQmlJavaScriptExpression::DeleteWatcher *watcher;
QForwardFieldList<QQmlJavaScriptExpressionGuard, &QQmlJavaScriptExpressionGuard::next> guards;
QStringList *errorString;
- bool translationCaptured = false;
private:
void captureBindableProperty(QObject *o, const QMetaObject *metaObjectForBindable, int c);
diff --git a/src/qml/qml/qqmlpropertybinding.cpp b/src/qml/qml/qqmlpropertybinding.cpp
index 1e50da7934..82c873e687 100644
--- a/src/qml/qml/qqmlpropertybinding.cpp
+++ b/src/qml/qml/qqmlpropertybinding.cpp
@@ -125,7 +125,7 @@ void QQmlPropertyBindingJS::expressionChanged()
if (!asBinding()->propertyDataPtr)
return;
const auto currentTag = m_error.tag();
- if (currentTag & InEvaluationLoop) {
+ if (currentTag == InEvaluationLoop) {
QQmlError err;
auto location = QQmlJavaScriptExpression::sourceLocation();
err.setUrl(QUrl{location.sourceFile});
@@ -141,10 +141,10 @@ void QQmlPropertyBindingJS::expressionChanged()
qmlWarning(this->scopeObject(), err);
return;
}
- m_error.setTag(currentTag | InEvaluationLoop);
+ m_error.setTag(InEvaluationLoop);
asBinding()->evaluateRecursive();
asBinding()->notifyRecursive();
- m_error.setTag(currentTag);
+ m_error.setTag(NoTag);
}
QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction)
@@ -293,8 +293,8 @@ void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that)
QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(const QQmlPropertyData *pd, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding)
{
auto translationBinding = [compilationUnit, binding](const QMetaType &metaType, void *dataPtr) -> bool {
- // Create a dependency to the uiLanguage
- QJSEnginePrivate::get(compilationUnit->engine)->uiLanguage.value();
+ // Create a dependency to the translationLanguage
+ QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage.value();
QVariant resultVariant(compilationUnit->bindingValueAsString(binding));
if (metaType.id() != QMetaType::QString)
diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp
index b9f59040a1..47a95a7ea1 100644
--- a/src/qmlmodels/qqmllistmodel.cpp
+++ b/src/qmlmodels/qqmllistmodel.cpp
@@ -567,6 +567,17 @@ ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &
return e->getListProperty(role);
}
+void ListModel::updateTranslations()
+{
+ for (int index = 0; index != elements.count(); ++index) {
+ ListElement *e = elements[index];
+ if (ModelNodeMetaObject *cache = e->objectCache()) {
+ // TODO: more fine grained tracking?
+ cache->updateValues();
+ }
+ }
+}
+
void ListModel::set(int elementIndex, QV4::Object *object, QVector<int> *roles)
{
ListElement *e = elements[elementIndex];
@@ -2499,6 +2510,17 @@ void QQmlListModel::removeElements(int index, int removeCount)
destroyer();
}
+void QQmlListModel::updateTranslations()
+{
+ // assumption: it is impossible to have retranslatable strings in a
+ // dynamic list model, as they would already have "decayed" to strings
+ // when they were inserted
+ if (m_dynamicRoles)
+ return;
+ Q_ASSERT(m_listModel);
+ m_listModel->updateTranslations();
+}
+
/*!
\qmlmethod ListModel::insert(int index, jsobject dict)
@@ -2924,7 +2946,8 @@ bool QQmlListModelParser::applyProperty(
} else {
QVariant value;
- if (binding->isTranslationBinding()) {
+ const bool isTranslationBinding = binding->isTranslationBinding();
+ if (isTranslationBinding) {
value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
} else if (binding->evaluatesToString()) {
value = compilationUnit->bindingValueAsString(binding);
@@ -2966,7 +2989,17 @@ bool QQmlListModelParser::applyProperty(
Q_UNREACHABLE();
}
+ if (!model)
+ return roleSet;
model->setOrCreateProperty(outterElementIndex, elementName, value);
+ auto listModel = model->m_modelCache;
+ if (isTranslationBinding && listModel) {
+ if (!listModel->translationChangeHandler) {
+ auto ep = QQmlEnginePrivate::get(compilationUnit->engine);
+ model->m_modelCache->translationChangeHandler = std::make_unique<QPropertyNotifier>(
+ ep->translationLanguage.addNotifier([listModel](){ listModel->updateTranslations(); }));
+ }
+ }
roleSet = true;
}
return roleSet;
diff --git a/src/qmlmodels/qqmllistmodel_p.h b/src/qmlmodels/qqmllistmodel_p.h
index 2e8181e10c..7cd2b4662b 100644
--- a/src/qmlmodels/qqmllistmodel_p.h
+++ b/src/qmlmodels/qqmllistmodel_p.h
@@ -148,6 +148,7 @@ private:
ListLayout *m_layout;
ListModel *m_listModel;
+ std::unique_ptr<QPropertyNotifier> translationChangeHandler;
QVector<class DynamicRoleModelNode *> m_modelObjects;
QVector<QString> m_roles;
@@ -169,6 +170,8 @@ private:
void emitItemsInserted();
void removeElements(int index, int removeCount);
+
+ void updateTranslations();
};
// ### FIXME
diff --git a/src/qmlmodels/qqmllistmodel_p_p.h b/src/qmlmodels/qqmllistmodel_p_p.h
index 6c40934c04..2ec7ea04db 100644
--- a/src/qmlmodels/qqmllistmodel_p_p.h
+++ b/src/qmlmodels/qqmllistmodel_p_p.h
@@ -399,6 +399,8 @@ public:
QVariant getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng);
ListModel *getListProperty(int elementIndex, const ListLayout::Role &role);
+ void updateTranslations();
+
int roleCount() const
{
return m_layout->roleCount();