Drop unnecessary creation contexts from Quick Controls

Bound components can only be instantiated in the context they're
declared in. Adding a context in between before instantiating a
component makes it impossible to bind the component. We want to use
bound components so that we can safely use IDs of other objects from
the same context inside the objects created from the component.

Pick-to: 6.4
Fixes: QTBUG-106042
Change-Id: I7a0e1ea3e079ccd0f5fe156f07f8bc62149c6c0a
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
This commit is contained in:
Ulf Hermann 2022-08-30 10:02:40 +02:00
parent 2a37ff2f49
commit fc683799fe
17 changed files with 185 additions and 41 deletions

View File

@ -489,6 +489,16 @@ bool QQmlComponent::isLoading() const
return status() == Loading;
}
/*!
Returns true if the component was created in a QML files that specifies
\c{pragma ComponentBehavior: Bound}, otherwise returns false.
*/
bool QQmlComponent::isBound() const
{
Q_D(const QQmlComponent);
return d->isBound();
}
/*!
\qmlproperty real Component::progress
The progress of loading the component, from 0.0 (nothing loaded)

View File

@ -63,6 +63,8 @@ public:
bool isError() const;
bool isLoading() const;
bool isBound() const;
QList<QQmlError> errors() const;
Q_INVOKABLE QString errorString() const;

View File

@ -26,3 +26,11 @@ qt_internal_add_module(QuickControlsTestUtilsPrivate
Qt::QuickTemplates2Private
Qt::QuickTestUtilsPrivate
)
# This is used by both C++ and QML tests, so we need it to be a library and a QML plugin,
# hence qt_internal_add_qml_module. We use it in addition to qt_internal_add_module,
# because otherwise syncqt complains that there is no "QtQuickControlsTestUtilsPrivate" module.
qt_internal_add_qml_module(QuickControlsTestUtilsPrivate
URI "Qt.test.controls"
VERSION "${PROJECT_VERSION}"
)

View File

@ -4,6 +4,7 @@
#include "controlstestutils_p.h"
#include <QtTest/qsignalspy.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQuickControls2/qquickstyle.h>
#include <QtQuickTemplates2/private/qquickabstractbutton_p.h>
#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
@ -155,3 +156,16 @@ bool QQuickControlsTestUtils::doubleClickButton(QQuickAbstractButton *button)
return true;
}
/*!
Allows creating QQmlComponents in C++, which is useful for tests that need
to check if items created from the component have the correct QML context.
*/
Q_INVOKABLE QQmlComponent *QQuickControlsTestUtils::ComponentCreator::createComponent(const QByteArray &data)
{
std::unique_ptr<QQmlComponent> component(new QQmlComponent(qmlEngine(this)));
component->setData(data, QUrl());
if (component->isError())
qmlWarning(this) << "Failed to create component from the following data:\n" << data;
return component.release();
}

View File

@ -19,6 +19,7 @@
QT_BEGIN_NAMESPACE
class QQmlComponent;
class QQmlEngine;
class QQuickApplicationWindow;
class QQuickAbstractButton;
@ -53,6 +54,17 @@ namespace QQuickControlsTestUtils
[[nodiscard]] bool verifyButtonClickable(QQuickAbstractButton *button);
[[nodiscard]] bool clickButton(QQuickAbstractButton *button);
[[nodiscard]] bool doubleClickButton(QQuickAbstractButton *button);
class ComponentCreator : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_MOC_INCLUDE(<QtQml/qqmlcomponent.h>)
public:
Q_INVOKABLE QQmlComponent *createComponent(const QByteArray &data);
};
}
QT_END_NAMESPACE

View File

@ -35,13 +35,19 @@ QQuickItem *QQuickFolderBreadcrumbBarPrivate::createDelegateItem(QQmlComponent *
Q_Q(QQuickFolderBreadcrumbBar);
// If we don't use the correct context, it won't be possible to refer to
// the control's id from within the delegates.
QQmlContext *creationContext = component->creationContext();
QQmlContext *context = component->creationContext();
// The component might not have been created in QML, in which case
// the creation context will be null and we have to create it ourselves.
if (!creationContext)
creationContext = qmlContext(q);
QQmlContext *context = new QQmlContext(creationContext, q);
context->setContextObject(q);
if (!context)
context = qmlContext(q);
// If we have initial properties we assume that all necessary information is passed via
// initial properties.
if (!component->isBound() && initialProperties.isEmpty()) {
context = new QQmlContext(context, q);
context->setContextObject(q);
}
QQuickItem *item = qobject_cast<QQuickItem*>(component->createWithInitialProperties(initialProperties, context));
if (item)
QQml_setParent_noEvent(item, q);

View File

@ -254,11 +254,9 @@ QQuickItem *QQuickMenuPrivate::beginCreateItem()
if (!delegate)
return nullptr;
QQmlContext *creationContext = delegate->creationContext();
if (!creationContext)
creationContext = qmlContext(q);
QQmlContext *context = new QQmlContext(creationContext, q);
context->setContextObject(q);
QQmlContext *context = delegate->creationContext();
if (!context)
context = qmlContext(q);
QObject *object = delegate->beginCreate(context);
QQuickItem *item = qobject_cast<QQuickItem *>(object);

View File

@ -49,17 +49,14 @@ QQuickItem *QQuickMenuBarPrivate::beginCreateItem(QQuickMenu *menu)
if (!delegate)
return nullptr;
QQmlContext *creationContext = delegate->creationContext();
if (!creationContext)
creationContext = qmlContext(q);
QQmlContext *context = new QQmlContext(creationContext, q);
context->setContextObject(q);
QQmlContext *context = delegate->creationContext();
if (!context)
context = qmlContext(q);
QObject *object = delegate->beginCreate(context);
QQuickItem *item = qobject_cast<QQuickItem *>(object);
if (!item) {
delete object;
delete context;
return nullptr;
}

View File

@ -715,11 +715,9 @@ static QQuickItem *createDimmer(QQmlComponent *component, QQuickPopup *popup, QQ
{
QQuickItem *item = nullptr;
if (component) {
QQmlContext *creationContext = component->creationContext();
if (!creationContext)
creationContext = qmlContext(popup);
QQmlContext *context = new QQmlContext(creationContext, popup);
context->setContextObject(popup);
QQmlContext *context = component->creationContext();
if (!context)
context = qmlContext(popup);
item = qobject_cast<QQuickItem*>(component->beginCreate(context));
}

View File

@ -700,13 +700,11 @@ void QQuickSplitViewPrivate::createHandleItem(int index)
// If we don't use the correct context, it won't be possible to refer to
// the control's id from within the delegate.
QQmlContext *creationContext = m_handle->creationContext();
QQmlContext *context = m_handle->creationContext();
// The component might not have been created in QML, in which case
// the creation context will be null and we have to create it ourselves.
if (!creationContext)
creationContext = qmlContext(q);
QQmlContext *context = new QQmlContext(creationContext, q);
context->setContextObject(q);
if (!context)
context = qmlContext(q);
QQuickItem *handleItem = qobject_cast<QQuickItem*>(m_handle->beginCreate(context));
if (handleItem) {
handleItem->setParent(q);

View File

@ -79,8 +79,6 @@ QQuickStackElement::~QQuickStackElement()
if (attached)
emit attached->removed();
delete context;
}
QQuickStackElement *QQuickStackElement::fromString(const QString &str, QQuickStackView *view, QString *error)
@ -134,11 +132,9 @@ bool QQuickStackElement::load(QQuickStackView *parent)
return true;
}
QQmlContext *creationContext = component->creationContext();
if (!creationContext)
creationContext = qmlContext(parent);
context = new QQmlContext(creationContext, parent);
context->setContextObject(parent);
QQmlContext *context = component->creationContext();
if (!context)
context = qmlContext(parent);
QQuickStackIncubator incubator(this);
component->create(incubator, context);

View File

@ -61,7 +61,6 @@ public:
bool ownComponent = false;
bool widthValid = false;
bool heightValid = false;
QQmlContext *context = nullptr;
QQmlComponent *component = nullptr;
QQuickStackView *view = nullptr;
QPointer<QQuickItem> originalParent;

View File

@ -185,13 +185,11 @@ QQuickItem *QQuickSwipePrivate::createDelegateItem(QQmlComponent *component)
{
// If we don't use the correct context, it won't be possible to refer to
// the control's id from within the delegates.
QQmlContext *creationContext = component->creationContext();
QQmlContext *context = component->creationContext();
// The component might not have been created in QML, in which case
// the creation context will be null and we have to create it ourselves.
if (!creationContext)
creationContext = qmlContext(control);
QQmlContext *context = new QQmlContext(creationContext, control);
context->setContextObject(control);
if (!context)
context = qmlContext(control);
QQuickItem *item = qobject_cast<QQuickItem*>(component->beginCreate(context));
if (item) {
item->setParentItem(control);

View File

@ -6,6 +6,7 @@ import QtTest
import QtQuick.Controls
import QtQuick.Templates as T
import QtQuick.NativeStyle as NativeStyle
import Qt.test.controls
TestCase {
id: testCase
@ -1470,4 +1471,24 @@ TestCase {
mouseRelease(title, pressPoint.x, pressPoint.y)
compare(title.pressedPosition, Qt.point(0, 0))
}
Component {
id: cppDimmerComponent
Popup {
dim: true
Overlay.modeless: ComponentCreator.createComponent(
"import QtQuick; Rectangle { objectName: \"rect\"; color: \"tomato\" }")
}
}
function test_dimmerComponentCreatedInCpp() {
let control = createTemporaryObject(cppDimmerComponent, testCase)
verify(control)
control.open()
tryCompare(control, "opened", true)
let rect = findChild(control.Overlay.overlay, "rect")
verify(rect)
}
}

View File

@ -6,6 +6,7 @@ import QtQuick.Controls
import QtQuick.Window
import QtTest
import Qt.labs.settings
import Qt.test.controls
TestCase {
id: testCase
@ -2528,4 +2529,43 @@ TestCase {
mouseMove(control, control.width - 100, control.height / 2)
verify(!targetHandle.SplitHandle.hovered)
}
Component {
id: cppHandleSplitViewComponent
SplitView {
anchors.fill: parent
handle: ComponentCreator.createComponent(`
import QtQuick
Rectangle {
objectName: "handle"
implicitWidth: 10
implicitHeight: 10
color: "tomato"
}`)
Rectangle {
objectName: "navajowhite"
color: objectName
implicitWidth: 100
implicitHeight: 100
}
Rectangle {
objectName: "steelblue"
color: objectName
implicitWidth: 200
implicitHeight: 200
}
}
}
function test_handleComponentCreatedInCpp() {
let control = createTemporaryObject(cppHandleSplitViewComponent, testCase)
verify(control)
let handles = findHandles(control)
compare(handles.length, 1)
compare(handles[0].color, Qt.color("tomato"))
}
}

View File

@ -4,6 +4,7 @@
import QtQuick
import QtTest
import QtQuick.Controls
import Qt.test.controls
TestCase {
id: testCase
@ -1225,7 +1226,12 @@ TestCase {
Item {
objectName: "clearUponDestructionItem"
Component.onDestruction: container.onDestructionCallback(stackView)
onParentChanged: {
// We don't actually do this on destruction because destruction is delayed.
// Rather, we do it when we get un-parented.
if (parent === null)
container.onDestructionCallback(stackView)
}
}
}
@ -1544,4 +1550,26 @@ TestCase {
tryCompare(control, "busy", true)
tryCompare(control, "busy", false)
}
Component {
id: cppComponent
StackView {
id: stackView
anchors.fill: parent
initialItem: cppComponent
property Component cppComponent: ComponentCreator.createComponent("import QtQuick; Rectangle { color: \"navajowhite\" }")
}
}
// Test that a component created in C++ works with StackView.
function test_componentCreatedInCpp() {
let control = createTemporaryObject(cppComponent, testCase)
verify(control)
compare(control.currentItem.color, Qt.color("navajowhite"))
control.push(control.cppComponent, { color: "tomato" })
compare(control.currentItem.color, Qt.color("tomato"))
}
}

View File

@ -4,7 +4,7 @@
import QtQuick
import QtTest
import QtQuick.Controls
import Qt.test.controls
TestCase {
id: testCase
@ -1726,4 +1726,23 @@ TestCase {
compare(control.background.width, 200)
compare(control.contentItem.width, 200 - control.leftPadding - control.rightPadding)
}
Component {
id: cppDelegateComponent
SwipeDelegate {
text: "SwipeDelegate"
width: 150
swipe.right: ComponentCreator.createComponent(
"import QtQuick; Rectangle { width: 100; height: parent.height; color: \"tomato\" }")
}
}
function test_delegateComponentCreatedInCpp() {
let control = createTemporaryObject(cppDelegateComponent, testCase)
verify(control)
swipe(control, 0, -1.0)
compare(control.swipe.rightItem.color, Qt.color("tomato"))
}
}