Revision control
++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include <QtQuick/private/qquickitem_p.h>
++
++ #include "qqstylekitdelegatecontainer_p.h"
++ #include "../qqstylekitcontrolproperties_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ QQmlComponent* QQStyleKitDelegateContainer::s_defaultDelegateComponent = nullptr;
++ QQmlComponent* QQStyleKitDelegateContainer::s_defaultShadowComponent = nullptr;
++
++ QQStyleKitDelegateContainer::QQStyleKitDelegateContainer(QQuickItem *parent)
++ : QQuickItem(parent)
++ {
++ }
++
++ QQStyleKitDelegateContainer::~QQStyleKitDelegateContainer()
++ {
++ // Disconnect before QQuickItem::~QQuickItem() reparents children, which emits
++ // signals. At that point our vtable would already be replaced with QQuickItem's,
++ // causing assertObjectType to fail.
++ if (m_delegateInstance)
++ disconnect(m_delegateInstance, nullptr, this, nullptr);
++ }
++
++ QQStyleKitDelegateProperties *QQStyleKitDelegateContainer::delegateStyle() const
++ {
++ return m_delegateProperties;
++ }
++
++ void QQStyleKitDelegateContainer::setDelegateStyle(QQStyleKitDelegateProperties *delegateProperties)
++ {
++ if (m_delegateProperties == delegateProperties)
++ return;
++
++ if (m_delegateProperties) {
++ /* We don't expect m_delegateProperties to change after componentCompleted(), since it's bound
++ * to a controls StyleKitReader, which is not supposed to change. So, considering that there
++ * might be hundreds of delegate instances in an application, we try to save some connections
++ * this way. But note, this is only an optimization, not a technical limitation. */
++ qmlWarning(this) << "Changing delegateStyle on StyleKitContainer is not supported.";
++ return;
++ }
++
++ m_delegateProperties = delegateProperties;
++ emit delegateStyleChanged();
++ }
++
++ QObject *QQStyleKitDelegateContainer::quickControl() const
++ {
++ return m_control;
++ }
++
++ void QQStyleKitDelegateContainer::setQuickControl(QObject *control)
++ {
++ if (m_control == control)
++ return;
++ m_control = control;
++ emit quickControlChanged();
++ }
++
++ QQuickItem *QQStyleKitDelegateContainer::delegateInstance() const
++ {
++ return m_delegateInstance;
++ }
++
++ void QQStyleKitDelegateContainer::updateImplicitSize()
++ {
++ if (m_inGeometryChange) {
++ /* The delegate instance is expected (but not restricted) to bind its size to the parent
++ * container since the final size of the control (and its delegates) is set from the
++ * application. However, some delegates also change their implicit size when resized.
++ * Shape, for example, recomputes its implicit size from the bounding rect of its
++ * ShapePaths, which may in turn depend on the Shape's own size. And a change to the
++ * implicit size can cause the layout that the control is in to relayout (and resize)
++ * the control once more, which causes a binding loop. To prevent this, we ignore changes
++ * to the delegate's implicit size while we're resizing it. */
++ return;
++ }
++
++ qreal implicitWidth = 0;
++ qreal implicitHeight = 0;
++
++ if (m_delegateInstance) {
++ implicitWidth = m_delegateInstance->implicitWidth();
++ implicitHeight = m_delegateInstance->implicitHeight();
++ } else if (m_delegateProperties) {
++ /* The delegate instance may not exist yet (e.g. because it is hidden),
++ * but the container should still report the style's implicit size so
++ * that it reserves the correct space in a layout. */
++ implicitWidth = qMax(m_delegateProperties->minimumWidth(), m_delegateProperties->width());
++ implicitHeight = qMax(m_delegateProperties->minimumHeight(), m_delegateProperties->height());
++ }
++
++ setImplicitWidth(implicitWidth);
++ setImplicitHeight(implicitHeight);
++ }
++
++ void QQStyleKitDelegateContainer::maybeCreateDelegate()
++ {
++ if (m_delegateInstance)
++ return;
++ if (!m_delegateProperties)
++ return;
++
++ if (!m_delegateProperties->visible()) {
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::visibleChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateDelegate, Qt::UniqueConnection);
++ return;
++ }
++
++ disconnect(m_delegateProperties, &QQStyleKitDelegateProperties::visibleChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateDelegate);
++
++ if (QQmlComponent *delegateComponent = m_delegateProperties->delegate()) {
++ m_delegateComponent = delegateComponent;
++ } else {
++ if (!s_defaultDelegateComponent || s_defaultDelegateComponent->engine() != qmlEngine(this)) {
++ delete s_defaultDelegateComponent;
++ QQmlEngine *engine = qmlEngine(this);
++ s_defaultDelegateComponent = new QQmlComponent(engine);
++ const QString qmlCode = QString::fromUtf8(R"(
++ import Qt.labs.StyleKit
++ StyledItem {
++ visible: delegateStyle.visible
++ width: parent.width
++ height: parent.height
++ }
++ )");
++ s_defaultDelegateComponent->setData(qmlCode.toUtf8(), QUrl());
++ Q_ASSERT_X(!s_defaultDelegateComponent->isError(), __FUNCTION__,
++ s_defaultDelegateComponent->errorString().toUtf8().constData());
++ }
++ m_delegateComponent = s_defaultDelegateComponent;
++ }
++
++ QQmlContext *ctx = QQmlEngine::contextForObject(this);
++ m_delegateInstance = qobject_cast<QQuickItem*>(m_delegateComponent->beginCreate(ctx));
++ Q_ASSERT(m_delegateInstance);
++ m_delegateInstance->setParent(this);
++ m_delegateInstance->setParentItem(this);
++ m_delegateInstance->setProperty("control", QVariant::fromValue(m_control.get()));
++ m_delegateInstance->setProperty("delegateStyle", QVariant::fromValue(m_delegateProperties.get()));
++ m_delegateComponent->completeCreate();
++
++ updateImplicitSize();
++ connect(m_delegateInstance, &QQuickItem::implicitWidthChanged, this, &QQStyleKitDelegateContainer::updateImplicitSize);
++ connect(m_delegateInstance, &QQuickItem::implicitHeightChanged, this, &QQStyleKitDelegateContainer::updateImplicitSize);
++
++ }
++
++ void QQStyleKitDelegateContainer::maybeCreateShadow()
++ {
++ if (m_shadowInstance)
++ return;
++ if (!m_delegateProperties)
++ return;
++
++ if (!m_delegateProperties->visible()) {
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::visibleChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateShadow, Qt::UniqueConnection);
++ return;
++ }
++ if (!m_delegateProperties->shadow()->visible()) {
++ connect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::visibleChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateShadow, Qt::UniqueConnection);
++ return;
++ }
++ if (m_delegateProperties->shadow()->color().alpha() == 0) {
++ connect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::colorChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateShadow, Qt::UniqueConnection);
++ return;
++ }
++ if (m_delegateProperties->shadow()->opacity() == 0) {
++ connect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::opacityChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateShadow, Qt::UniqueConnection);
++ return;
++ }
++
++ disconnect(m_delegateProperties, &QQStyleKitDelegateProperties::visibleChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateShadow);
++ disconnect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::visibleChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateShadow);
++ disconnect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::colorChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateShadow);
++ disconnect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::opacityChanged,
++ this, &QQStyleKitDelegateContainer::maybeCreateShadow);
++
++ if (QQmlComponent *shadowComponent = m_delegateProperties->shadow()->delegate()) {
++ m_shadowComponent = shadowComponent;
++ } else {
++ if (!s_defaultShadowComponent || s_defaultShadowComponent->engine() != qmlEngine(this)) {
++ delete s_defaultShadowComponent;
++ QQmlEngine *engine = qmlEngine(this);
++ s_defaultShadowComponent = new QQmlComponent(engine);
++ const QString qmlCode = QString::fromUtf8(R"(
++ import Qt.labs.StyleKit.impl
++ Shadow {}
++ )");
++ s_defaultShadowComponent->setData(qmlCode.toUtf8(), QUrl());
++ Q_ASSERT_X(!s_defaultShadowComponent->isError(), __FUNCTION__,
++ s_defaultShadowComponent->errorString().toUtf8().constData());
++ }
++ m_shadowComponent = s_defaultShadowComponent;
++ }
++
++ QQmlContext *ctx = QQmlEngine::contextForObject(this);
++ m_shadowInstance = qobject_cast<QQuickItem*>(m_shadowComponent->beginCreate(ctx));
++ Q_ASSERT(m_shadowInstance);
++ m_shadowInstance->setParent(this);
++ m_shadowInstance->setParentItem(this);
++ m_shadowInstance->setZ(-1);
++ m_shadowInstance->setProperty("control", QVariant::fromValue(m_control.get()));
++ m_shadowInstance->setProperty("delegateStyle", QVariant::fromValue(m_delegateProperties.get()));
++ m_shadowComponent->completeCreate();
++ }
++
++ void QQStyleKitDelegateContainer::componentComplete()
++ {
++ QQuickItem::componentComplete();
++ Q_ASSERT(m_delegateProperties);
++ Q_ASSERT(m_control);
++
++ maybeCreateDelegate();
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::delegateChanged, this, [this]{
++ if (!m_delegateInstance) {
++ maybeCreateDelegate();
++ } else {
++ const QQmlComponent *newDelegateComp = m_delegateProperties->delegate();
++ if (m_delegateComponent == newDelegateComp)
++ return;
++ if (!newDelegateComp && m_delegateComponent == s_defaultDelegateComponent) {
++ /* If newDelegateComp is nullptr, it means that we should use the default
++ * delegate instead, which we already do. */
++ return;
++ }
++
++ delete m_delegateInstance;
++ maybeCreateDelegate();
++ emit delegateInstanceChanged();
++ }
++ });
++
++ maybeCreateShadow();
++ connect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::delegateChanged, this, [this]{
++ if (!m_shadowInstance) {
++ maybeCreateShadow();
++ } else {
++ const QQmlComponent *newShadowComp = m_delegateProperties->shadow()->delegate();
++ if (m_shadowComponent == newShadowComp)
++ return;
++ if (!newShadowComp && m_shadowComponent == s_defaultShadowComponent) {
++ /* If newShadowComp is nullptr, it means that we should use the default
++ * delegate instead, which we already do. */
++ return;
++ }
++
++ delete m_shadowInstance;
++ maybeCreateShadow();
++ }
++ });
++
++ /* rotation and scale are applied here rather than as QML bindings
++ * in each QML file that uses a container, simply to avoid repeating the same
++ * bindings in all container instances. But note, the properties we set from
++ * c++ cannot then be bound to another value from QML since the c++ code would
++ * ignore and overwrite such bindings. For this reason, properties like 'visible'
++ * still needs to be set from QML since a container should sometimes be hidden
++ * based on the state of the control (e.g a menu arrow should only be visible
++ * when the menu has children etc). */
++ setRotation(m_delegateProperties->rotation());
++ setScale(m_delegateProperties->scale());
++
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::rotationChanged, this, [this]() {
++ setRotation(m_delegateProperties->rotation());
++ });
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::scaleChanged, this, [this]() {
++ setScale(m_delegateProperties->scale());
++ });
++
++ updateImplicitSize();
++ }
++
++ void QQStyleKitDelegateContainer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
++ {
++ QScopedValueRollback implicitSizeUpdateGuard(m_inGeometryChange, true);
++ QQuickItem::geometryChange(newGeometry, oldGeometry);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqstylekitdelegatecontainer_p.cpp"