Revision control
Copy as Markdown
// 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"