Revision control

++ // Copyright (C) 2025 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 "qqstylekitpropertyresolver_p.h"
++ #include "qqstylekitcontrol_p.h"
++ #include "qqstylekitcontrols_p.h"
++ #include "qqstylekitvariation_p.h"
++ #include "qqstylekitstyle_p.h"
++ #include "qqstylekittheme_p.h"
++ #include "qqstylekitdebug_p.h"
++
++ #include <QtQuickTemplates2/private/qquickcontrol_p.h>
++ #include <QtCore/QScopedValueRollback>
++
++ QT_BEGIN_NAMESPACE
++
++ bool QQStyleKitPropertyResolver::s_styleWarningsIssued = false;
++ bool QQStyleKitPropertyResolver::s_isReadingProperty = false;
++ QQSK::State QQStyleKitPropertyResolver::s_cachedState = QQSK::StateFlag::Unspecified;
++ QVarLengthArray<QQSK::StateFlag, 10> QQStyleKitPropertyResolver::s_cachedStateList;
++
++ const QList<QQStyleKitExtendableControlType> QQStyleKitPropertyResolver::baseTypesForType(
++ QQStyleKitExtendableControlType exactType)
++ {
++ /* By default, the base types should mirror the class hierarchy in Qt Quick Controls.
++ * However, to make it possible to style a base type without having to "undo" it again
++ * in a sub type, we choose to diverge in som cases:
++ *
++ * ItemDelegate — Normally used as a menu item in a ComboBox or as an item in a ListView.
++ * Although it behaves similarly to a button, it is typically styled very differently
++ * (e.g., without borders, drop shadows, gradients, etc.). For that reason, it falls
++ * back to Control rather than AbstractButton.
++ *
++ * MenuBarItem — Although it is technically a button, it is typically styled very differently
++ * from other buttons (e.g., without borders, drop shadows, gradients, etc.). For that
++ * reason, it falls back to Control rather than AbstractButton.
++ *
++ * MenuItem — In Qt Quick Controls, MenuItem inherits from AbstractButton. Although it
++ * behaves similarly to a button, it is typically styled very differently
++ * (e.g., without borders, drop shadows, gradients, etc.). For that reason, it falls
++ * back to Control rather than AbstractButton.
++ *
++ * TabBar — In Qt Quick Controls, ToolBar inherits Pane, while TabBar inherits Container.
++ * Since it is desirable for a TabBar to share styling characteristics (such as
++ * background color) with ToolBar and Pane, we let it fall back to Pane instead of
++ * Control.
++ */
++ switch (exactType) {
++ case QQStyleKitReader::ApplicationWindow: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::ApplicationWindow };
++ return t; }
++ case QQStyleKitReader::Button:
++ case QQStyleKitReader::DelayButton:
++ case QQStyleKitReader::FlatButton:
++ case QQStyleKitReader::ToolButton:
++ case QQStyleKitReader::TabButton:
++ case QQStyleKitReader::RadioButton:
++ case QQStyleKitReader::RoundButton:
++ case QQStyleKitReader::CheckBox:
++ case QQStyleKitReader::SwitchControl: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::AbstractButton, QQStyleKitReader::Control };
++ return t; }
++ case QQStyleKitReader::CheckDelegate:
++ case QQStyleKitReader::RadioDelegate:
++ case QQStyleKitReader::SwipeDelegate:
++ case QQStyleKitReader::SwitchDelegate:
++ case QQStyleKitReader::TableViewDelegate:
++ case QQStyleKitReader::TreeViewDelegate: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::ItemDelegate, QQStyleKitReader::Control };
++ return t; }
++ case QQStyleKitReader::HeaderViewDelegate: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::TableViewDelegate, QQStyleKitReader::ItemDelegate, QQStyleKitReader::Control };
++ return t;
++ }
++ case QQStyleKitReader::Dialog:
++ case QQStyleKitReader::Drawer:
++ case QQStyleKitReader::Menu:
++ case QQStyleKitReader::ToolTip: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::Popup, QQStyleKitReader::Control };
++ return t; }
++ case QQStyleKitReader::Page:
++ case QQStyleKitReader::DialogButtonBox:
++ case QQStyleKitReader::Frame:
++ case QQStyleKitReader::TabBar:
++ case QQStyleKitReader::ToolBar: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::Pane, QQStyleKitReader::Control };
++ return t; }
++ case QQStyleKitReader::GroupBox: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::Frame, QQStyleKitReader::Pane, QQStyleKitReader::Control };
++ return t;
++ }
++ case QQStyleKitReader::TextField:
++ case QQStyleKitReader::TextArea: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::TextInput, QQStyleKitReader::Control };
++ return t; }
++ default: {
++ static QList<QQStyleKitExtendableControlType> t =
++ { QQStyleKitReader::Control };
++ return t; }
++ }
++
++ Q_UNREACHABLE();
++ return {};
++ }
++
++ void QQStyleKitPropertyResolver::cacheReaderState(QQSK::State state)
++ {
++ Q_ASSERT(state != QQSK::StateFlag::Unspecified);
++ if (state == s_cachedState)
++ return;
++
++ s_cachedState = state;
++
++ /* Note: The order in which we add the states below matters.
++ * The reason is that the s_cachedStateList that we build is used by QQStyleKitPropertyResolver
++ * later to generate all the different state combinations that should be tested when
++ * searching for a property. And the states added first to the list will "win" if the
++ * same property is set in several of the states. */
++ s_cachedStateList.clear();
++ if (state.testFlag(QQSK::StateFlag::Pressed))
++ s_cachedStateList.append(QQSK::StateFlag::Pressed);
++ if (state.testFlag(QQSK::StateFlag::Hovered))
++ s_cachedStateList.append(QQSK::StateFlag::Hovered);
++ if (state.testFlag(QQSK::StateFlag::Highlighted))
++ s_cachedStateList.append(QQSK::StateFlag::Highlighted);
++ if (state.testFlag(QQSK::StateFlag::Focused))
++ s_cachedStateList.append(QQSK::StateFlag::Focused);
++ if (state.testFlag(QQSK::StateFlag::Checked))
++ s_cachedStateList.append(QQSK::StateFlag::Checked);
++ if (state.testFlag(QQSK::StateFlag::Vertical))
++ s_cachedStateList.append(QQSK::StateFlag::Vertical);
++ if (state.testFlag(QQSK::StateFlag::Disabled))
++ s_cachedStateList.append(QQSK::StateFlag::Disabled);
++ }
++
++ void QQStyleKitPropertyResolver::addVariationToReader(
++ QQStyleKitReader *styleReader,
++ QQStyleKitStyleAndThemeBase *styleOrTheme,
++ QQStyleKitVariation *variation)
++ {
++ /* Add the variation to the StyleReader's list of effective variations.
++ * A variation may appear more than once in a type variation list or in
++ * an instance variation list, but it only needs to be added once - the
++ * first in the list will shadow the other ones anyway. */
++ if (!styleReader->m_effectiveVariations.contains(variation))
++ styleReader->m_effectiveVariations.append(variation);
++
++ /* We also record in which Style or Theme the variation was found, so that
++ * it only takes effect for that specific Style or Theme during property
++ * propagation. Note that the same type variation can be used from both the
++ * Style and the Theme (its ID can be added to several variation lists). */
++ if (!variation->m_usageContext.contains(styleOrTheme))
++ variation->m_usageContext.append(styleOrTheme);
++ }
++
++ void QQStyleKitPropertyResolver::addTypeVariationsToReader(
++ QQStyleKitReader *styleReader,
++ QQStyleKitStyleAndThemeBase *styleOrTheme,
++ const AttachedVariationList &attachedVariations)
++ {
++ const QQStyleKitExtendableControlType styleReaderType = styleReader->controlType();
++ const auto styleReaderBaseType = baseTypesForType(styleReaderType);
++
++ static PropertyPathIds ids;
++ if (ids.property.property() == QQSK::Property::NoProperty) {
++ /* ids is made static, since the 'variations' path will be the same for all
++ * StyleKitControls. Also, since subtypes are only possible for delegates,
++ * and 'variations' is a control property, we can exclude subtypes. */
++ ids.property = QQStyleKitPropertyPath(styleReader, QQSK::Property::Variations, QQStyleKitPropertyPath::Flag::ExcludeSubtype);
++ ids.alternative = QQStyleKitPropertyPath(styleReader, QQSK::Property::NoProperty, QQStyleKitPropertyPath::Flag::ExcludeSubtype);
++ ids.subTypeProperty = QQStyleKitPropertyPath();
++ ids.subTypeAlternative = QQStyleKitPropertyPath();
++ }
++
++ for (const QQStyleKitVariationAttached *attached : attachedVariations) {
++ const auto parentType = attached->controlType();
++ const auto parentBaseTypes = baseTypesForType(parentType);
++
++ /* Search for the 'variations' property set on the parentType, or any of its base
++ * types, using normal propagation and fallback logic. Unlike when resolving other style
++ * properties, we limit the search to the style or theme we're processing, since it matters which
++ * style or theme a variation belongs when they're used to resolve other style properties later. */
++ const QVariant typeVariationsVariant = readPropertyInRelevantControls(styleOrTheme, ids, parentType, parentBaseTypes);
++ if (!typeVariationsVariant.isValid())
++ continue;
++
++ const auto typeVariations = *qvariant_cast<QList<QQStyleKitVariation *> *>(typeVariationsVariant);
++
++ for (QQStyleKitVariation *variation : typeVariations) {
++ /* Inside each type variation, check if the control type that styleReader represents has
++ * been defined. If so, it means that the variation _might_ affect it, and should therefore
++ * be added to the style readers list of effective variations. */
++ if (!variation) {
++ /* The variation will be nullptr if non-QQStyleKitVariation elements
++ * are added to the 'variations' list from QML (such as strings). */
++ continue;
++ }
++
++ if (variation->getControlStyle(styleReaderType)) {
++ addVariationToReader(styleReader, styleOrTheme, variation);
++ } else {
++ for (int type : styleReaderBaseType) {
++ if (variation->getControlStyle(type))
++ addVariationToReader(styleReader, styleOrTheme, variation);
++ }
++ }
++ }
++ }
++ }
++
++ void QQStyleKitPropertyResolver::addInstanceVariationsToReader(
++ QQStyleKitReader *styleReader,
++ QQStyleKitStyleAndThemeBase *styleOrTheme,
++ const AttachedVariationList &attachedVariations)
++ {
++ /* Add the variations set from the application to the list of effective variations
++ * in the styleReader. But, to speed up property look-up later on, we only add the
++ * variations that has the potential to affect the control type, or its base types,
++ * that the styleReader represents. The variations that are closest to styleReader
++ * in the hierarchy will be added first and take precendence over the ones added last. */
++ const QQStyleKitExtendableControlType styleReaderType = styleReader->controlType();
++ const auto styleReaderBaseTypes = baseTypesForType(styleReaderType);
++
++ for (const QQStyleKitVariationAttached *attached : attachedVariations) {
++ for (const QString &instanceVariationName : attached->variations()) {
++ for (QQStyleKitVariation *variation : styleOrTheme->m_styleVariations) {
++ if (variation->name() != instanceVariationName)
++ continue;
++
++ /* Invariant: we found a variation in the given Style or a Theme with a name that matches
++ * a name in the attached variation list. Check if the found variation contains the
++ * type, or the subtypes, of the style reader. If not, it doesn't affect it and can
++ * therefore be skipped. */
++ if (variation->getControlStyle(styleReaderType)) {
++ addVariationToReader(styleReader, styleOrTheme, variation);
++ } else {
++ for (int baseType : styleReaderBaseTypes) {
++ if (variation->getControlStyle(baseType))
++ addVariationToReader(styleReader, styleOrTheme, variation);
++ }
++ }
++ }
++ }
++ }
++ }
++
++ void QQStyleKitPropertyResolver::rebuildVariationsForReader(
++ QQStyleKitReader *styleReader, QQStyleKitStyle *style)
++ {
++ /* Traverse up the parent chain of \a styleReader, and for each parent, look for an
++ * instance of QQStyleKitVariationAttached. And for each attached object, check if it
++ * has variations that can potentially affect the style reader. If so, add the
++ * variations to the style readers list of effective variations.
++ * A QQStyleKitVariationAttached can specify both Instance Variations and Type Variations.
++ * The former should affect all descendant StyleKitReaders of the parent, while the
++ * latter should only affect descendant StyleKitReaders of a specific type. */
++ Q_ASSERT(styleReader->m_effectiveVariationsDirty);
++ styleReader->m_effectiveVariationsDirty = false;
++ styleReader->m_effectiveVariations.clear();
++
++ if (!style->m_hasVariations)
++ return;
++
++ /* Walk up the parent chain and collect all attached StyleVariation objects that
++ * may affect this StyleReader. Their variation lists affect instance variations,
++ * and their control type may affect type variations. */
++ AttachedVariationList attachedVariations;
++ for (QObject *current = styleReader; current; current = current->parent()) {
++ if (const QObject *attachedObject = qmlAttachedPropertiesObject<QQStyleKitVariation>(current, false)) {
++ const auto *attached = static_cast<const QQStyleKitVariationAttached *>(attachedObject);
++ attachedVariations.append(attached);
++ }
++ }
++
++ if (attachedVariations.isEmpty())
++ return;
++
++ for (QQStyleKitStyle *current = style; current; current = current->fallbackStyle()) {
++ if (QQStyleKitTheme *theme = current->theme()) {
++ addInstanceVariationsToReader(styleReader, theme, attachedVariations);
++ addTypeVariationsToReader(styleReader, theme, attachedVariations);
++ }
++ addInstanceVariationsToReader(styleReader, current, attachedVariations);
++ addTypeVariationsToReader(styleReader, current, attachedVariations);
++ }
++ }
++
++ template <class T>
++ QVariant QQStyleKitPropertyResolver::readPropertyInStorageForState(
++ const QQStyleKitPropertyPath main, const QQStyleKitPropertyPath alternative,
++ const T *storageProvider, QQSK::State state)
++ {
++ /* If either the main property or its alternative is set in the storage,
++ * we’ve found the best match and can return the value to the application.
++ * The reason we support an alternative property is that some properties can be
++ * specified in more than one way. For example, 'topLeftRadius' can be set either
++ * directly ('topLeftRadius', the main property) or indirectly via 'radius'
++ * (the alternative). Whichever one is encountered first in the propagation chain
++ * takes precedence.
++ * This means that when resolving 'topLeftRadius' for a Button, if 'radius' is set
++ * on the Button and 'topLeftRadius' is set on AbstractButton, then 'radius' will
++ * override 'topLeftRadius' and be used as the final value. */
++ Q_ASSERT(qlonglong(state) <= qlonglong(QQSK::StateFlag::MAX_STATE));
++
++ const PropertyStorageId propertyKey = main.createStorageId(state);
++
++ if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
++ QQStyleKitDebug::trace(main, storageProvider, state, propertyKey);
++
++ const QVariant propertyValue = storageProvider->readStyleProperty(propertyKey);
++ if (propertyValue.isValid()) {
++ if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
++ QQStyleKitDebug::notifyPropertyRead(main, storageProvider, state, propertyValue);
++ return propertyValue;
++ }
++
++ const PropertyStorageId altPropertyKey = alternative.createStorageId(state);
++
++ if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
++ QQStyleKitDebug::trace(alternative, storageProvider, state, altPropertyKey);
++
++ const QVariant altValue = storageProvider->readStyleProperty(altPropertyKey);
++ if (altValue.isValid()) {
++ if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
++ QQStyleKitDebug::notifyPropertyRead(main, storageProvider, state, altValue);
++ return altValue;
++ }
++
++ return {};
++ }
++
++ template <class INDICES_CONTAINER>
++ QVariant QQStyleKitPropertyResolver::readPropertyInControlForStates(
++ const QQStyleKitPropertyPath main, const QQStyleKitPropertyPath alternative,
++ const QQStyleKitControl *control, INDICES_CONTAINER &stateListIndices,
++ int startIndex, int recursionLevel)
++ {
++ for (int i = startIndex; i < s_cachedStateList.length(); ++i) {
++ /* stateListIndices is a helper list to track which index in the state list
++ * each recursion level is currently processing. The first recursion level
++ * will iterate through all of the states. The second recursion level will
++ * only the iterate through the states that comes after the state at the
++ * previous recursion. And so on recursively. The end result will be a list of
++ * state combinations (depth first) where no state is repeated more than
++ * once. And for each combination, we check if the storage has a value assigned
++ * for the given property for the given state combination. */
++ stateListIndices[recursionLevel] = i;
++ const QQSK::StateFlag stateFlag = s_cachedStateList[i];
++
++ /* Optimization: check if the control stores values for any properties for
++ * the state we're processing. Otherwise, skip the state. */
++ if (!control->m_writtenStates.testFlag(stateFlag))
++ continue;
++
++ /* Optimization: check if the style/theme/variation stores a value for the
++ * property in the state we're processing. Otherwise, skip the state. */
++ const QQStyleKitControls *controls = control->controls();
++ const QQSK::State statesAffectingProperty = controls->m_writtenPropertyPaths[main.pathId()];
++ if (!statesAffectingProperty.testFlag(stateFlag)) {
++ if (alternative.property() == QQSK::Property::NoProperty) {
++ continue;
++ } else {
++ const QQSK::State statesAffectingAlternative = controls->m_writtenPropertyPaths[alternative.pathId()];
++ if (!statesAffectingAlternative.testFlag(stateFlag))
++ continue;
++ }
++ }
++
++ if (recursionLevel < s_cachedStateList.length() - 1) {
++ // Continue the recursion towards the longest possible nested state
++ const QVariant value = readPropertyInControlForStates(
++ main, alternative, control, stateListIndices, i + 1, recursionLevel + 1);
++ if (value.isValid())
++ return value;
++ }
++
++ // Check the current combination
++ QQSK::State storageState = QQSK::StateFlag::Unspecified;
++ for (int j = 0; j <= recursionLevel; ++j)
++ storageState.setFlag(s_cachedStateList[stateListIndices[j]]);
++ const QVariant value = readPropertyInStorageForState(main, alternative, control, storageState);
++ if (value.isValid())
++ return value;
++ }
++
++ return {};
++ }
++
++ QVariant QQStyleKitPropertyResolver::readPropertyInControl(
++ const PropertyPathIds &ids, const QQStyleKitControl *control)
++ {
++ /* Find the most specific state combination (based on the state of the reader) that
++ * has a value set for the property in the contol. In case several state combinations
++ * could be found, the order of the states in the stateList decides the priority.
++ * If we're reading a property in a subtype, try all state combinations in the subtype
++ * first, before trying all the state combinations in the super type. */
++ QVarLengthArray<int, 10> stateListIndices(s_cachedStateList.length());
++
++ if (ids.subTypeProperty.property() != QQSK::Property::NoProperty) {
++ if (s_cachedState != QQSK::StateFlag::Normal) {
++ QVariant value = readPropertyInControlForStates(
++ ids.subTypeProperty, ids.subTypeAlternative, control, stateListIndices, 0, 0);
++ if (value.isValid())
++ return value;
++ }
++
++ if (control->m_writtenStates.testFlag(QQSK::StateFlag::Normal)) {
++ const QVariant value = readPropertyInStorageForState(
++ ids.subTypeProperty, ids.subTypeAlternative, control, QQSK::StateFlag::Normal);
++ if (value.isValid())
++ return value;
++ }
++ }
++
++ if (s_cachedState != QQSK::StateFlag::Normal) {
++ const QVariant value = readPropertyInControlForStates(
++ ids.property, ids.alternative, control, stateListIndices, 0, 0);
++ if (value.isValid())
++ return value;
++ }
++
++ /* The normal state is the propagation fall back for all state combinations.
++ * If the normal state has the property set, it'll return a valid QVariant,
++ * which will cause the propagation to stop. Otherwise we'll return an invalid
++ * variant which will cause the search to continue. */
++ if (control->m_writtenStates.testFlag(QQSK::StateFlag::Normal))
++ return readPropertyInStorageForState(ids.property, ids.alternative, control, QQSK::StateFlag::Normal);
++
++ return {};
++ }
++
++ QVariant QQStyleKitPropertyResolver::readPropertyInRelevantControls(
++ const QQStyleKitControls *controls, const PropertyPathIds &ids,
++ const QQStyleKitExtendableControlType exactType,
++ const QList<QQStyleKitExtendableControlType> baseTypes)
++ {
++ if (!controls)
++ return {};
++
++ /* Optimization: check if the style/theme/variation stores a value for
++ * the property, regardless of state. Otherwise we can just return. */
++ while (true) {
++ const auto writtenProperties = controls->m_writtenPropertyPaths;
++ if (writtenProperties.contains(ids.property.pathId()))
++ break;
++ const bool hasAlternative = ids.alternative.property() != QQSK::Property::NoProperty;
++ if (hasAlternative && writtenProperties.contains(ids.alternative.pathId()))
++ break;
++ if (ids.subTypeProperty.property() == QQSK::Property::NoProperty)
++ return {};
++ if (writtenProperties.contains(ids.subTypeProperty.pathId()))
++ break;
++ if (hasAlternative && writtenProperties.contains(ids.subTypeAlternative.pathId()))
++ break;
++ return {};
++ }
++
++ if (const QQStyleKitControl *control = controls->getControlStyle(exactType)) {
++ const QVariant value = readPropertyInControl(ids, control);
++ if (value.isValid())
++ return value;
++ }
++
++ for (const int type : baseTypes) {
++ if (const QQStyleKitControl *control = controls->getControlStyle(type)) {
++ const QVariant value = readPropertyInControl(ids, control);
++ if (value.isValid())
++ return value;
++ }
++ }
++
++ return {};
++ }
++
++ QVariant QQStyleKitPropertyResolver::readPropertyInVariations(
++ const QList<QPointer<QQStyleKitVariation>> &variations,
++ const QQStyleKitStyleAndThemeBase *styleOrTheme,
++ const PropertyPathIds &ids,
++ const QQStyleKitExtendableControlType exactType,
++ const QList<QQStyleKitExtendableControlType> baseTypes)
++ {
++ bool foundAtLeastOneVariation = false;
++ for (const QPointer<QQStyleKitVariation> &variation : variations) {
++ if (!variation)
++ continue;
++ if (!variation->m_usageContext.contains(styleOrTheme)) {
++ if (foundAtLeastOneVariation) {
++ /* Optimization: We have found at least one effective variation in the list, but this
++ * one has a different usage context. This means that the remaining variations will also
++ * have a different usage context, since the variations were added to the list sorted on context.
++ * So we can end the iteration. */
++ break;
++ }
++ /* The variations list is sorted on usage context. And the first variation in the list with
++ * the correct usage context has yet to be found. So continue to the next variation. */
++ continue;
++ }
++ foundAtLeastOneVariation = true;
++ const QVariant value = readPropertyInRelevantControls(variation, ids, exactType, baseTypes);
++ if (value.isValid())
++ return value;
++ }
++ return {};
++ }
++
++ QVariant QQStyleKitPropertyResolver::readPropertyInStyle(
++ QQStyleKitStyle *style, const PropertyPathIds &ids, QQStyleKitReader *styleReader)
++ {
++ /* Sync the palette of the style with the palette of the current reader. Note
++ * that this can cause palette bindings in the style to change, which will
++ * result in calls to writeStyleProperty(). */
++ style->syncFromQPalette(styleReader->effectivePalette());
++
++ /* Cache the state of the style reader to avoid rebuilding the same helper
++ * structures on subsequent reads. In practice, a single style reader
++ * typically processes many properties in sequence rather than just one. */
++ cacheReaderState(styleReader->controlState());
++
++ if (styleReader->m_effectiveVariationsDirty)
++ rebuildVariationsForReader(styleReader, style);
++
++ const QQStyleKitExtendableControlType exactType = styleReader->controlType();
++ const QList<QQStyleKitExtendableControlType> baseTypes = baseTypesForType(exactType);
++
++ QVariant value;
++
++ while (true) {
++ value = readPropertyInVariations(styleReader->m_effectiveVariations, style->theme(), ids, exactType, baseTypes);
++ if (value.isValid())
++ break;
++
++ value = readPropertyInRelevantControls(style->theme(), ids, exactType, baseTypes);
++ if (value.isValid())
++ break;
++
++ value = readPropertyInVariations(styleReader->m_effectiveVariations, style, ids, exactType, baseTypes);
++ if (value.isValid())
++ break;
++
++ value = readPropertyInRelevantControls(style, ids, exactType, baseTypes);
++ if (value.isValid())
++ break;
++
++ if (auto *fallbackStyle = style->fallbackStyle()) {
++ /* Recurse into the fallback style, and search for the property there. If not
++ * found, and the fallback style has a fallback style, the recursion continues. */
++ value = readPropertyInStyle(fallbackStyle, ids, styleReader);
++ if (value.isValid())
++ break;
++ }
++
++ break;
++ }
++
++ if (Q_UNLIKELY(QQStyleKitDebug::enabled())) {
++ if (!value.isValid())
++ QQStyleKitDebug::notifyPropertyNotResolved(ids.property);
++ }
++
++ return value;
++ }
++
++ QVariant QQStyleKitPropertyResolver::readStyleProperty(
++ const QQStyleKitPropertyGroup *group,
++ const QQSK::Property property,
++ const QQSK::Property alternative)
++ {
++ const QQStyleKitControlProperties *controlProperties = group->controlProperties();
++ const QQSK::PropertyPathFlags pathFlags = group->pathFlags();
++ const QQSK::Subclass subclass = controlProperties->subclass();
++
++ if (subclass != QQSK::Subclass::QQStyleKitReader) {
++ /* Due to propagation, we must know the reader’s state in order to resolve the value
++ * of a given property. For example, the background delegate’s color may follow a
++ * completely different lookup path—and therefore produce a different result—depending
++ * on whether the reader is a Slider or a Button, and whether it is hovered, pressed, etc.
++ * For this reason, when a property is not read via a QQStyleKitReader, propagation and
++ * fall back logic cannot be supported.
++ * However, to still allow static (i.e., non-propagating) bindings between properties
++ * _inside_ a Style, we fall back to return the value set directly on the accessed control. */
++ Q_ASSERT(subclass == QQSK::Subclass::QQStyleKitState);
++ const QQStyleKitControlState *controlState = controlProperties->asQQStyleKitState();
++ const QQStyleKitControl *control = controlState->control();
++ const PropertyStorageId key = QQStyleKitPropertyPath(group, property).createStorageId(controlState->nestedState());
++ return control->readStyleProperty(key);
++ }
++
++ QQStyleKitStyle *style = controlProperties->style();
++ if (!style || !style->loaded()) {
++ /* No style has been set yet, or the style is still loading. The application should set
++ * a style early (e.g. at the top of main.qml) before creating any controls, to avoid
++ * premature property reads that end up here. However, some controls like the menu bar
++ * are instantiated very early and may try to read properties before a style is available. */
++ return {};
++ }
++
++ QQStyleKitDebug::groupBeingRead = group;
++ QQStyleKitReader *styleReader = controlProperties->asQQStyleKitReader();
++
++ if (s_isReadingProperty) {
++ if (!s_styleWarningsIssued) {
++ s_styleWarningsIssued = true;
++ qmlWarning(styleReader) << "The style property '" << property << "' was read "
++ << "before finishing the read of another style property. "
++ << "This is likely to cause a style glitch.";
++ }
++ }
++ QScopedValueRollback rollback(s_isReadingProperty, true);
++
++ PropertyPathIds ids;
++ ids.property = QQStyleKitPropertyPath(group, property, QQStyleKitPropertyPath::Flag::ExcludeSubtype);
++ ids.alternative = QQStyleKitPropertyPath(group, alternative, QQStyleKitPropertyPath::Flag::ExcludeSubtype);
++ const bool insideSubType = pathFlags &
++ (QQSK::PropertyPathFlag::DelegateSubtype1 | QQSK::PropertyPathFlag::DelegateSubtype2);
++
++ if (insideSubType) {
++ ids.subTypeProperty = QQStyleKitPropertyPath(group, property);
++ ids.subTypeAlternative = QQStyleKitPropertyPath(group, alternative);
++ } else {
++ ids.subTypeProperty = QQStyleKitPropertyPath();
++ ids.subTypeAlternative = QQStyleKitPropertyPath();
++ }
++
++ if (!pathFlags.testFlag(QQSK::PropertyPathFlag::Global)) {
++ /* A style reader can have a storage that contains local property overrides (that is,
++ * interpolated values from an ongoing transition). When searching for a property, we
++ * therefore need to check this storage first. The exception is if the property was read
++ * inside the 'global' group, which means that we should read the values directly
++ * from the style. */
++ if (insideSubType) {
++ const QVariant value = readPropertyInStorageForState(
++ ids.subTypeProperty, ids.subTypeAlternative, styleReader, QQSK::StateFlag::Normal);
++ if (value.isValid())
++ return value;
++ }
++ const QVariant value = readPropertyInStorageForState(
++ ids.property, ids.alternative, styleReader, QQSK::StateFlag::Normal);
++ if (value.isValid())
++ return value;
++ }
++
++ return readPropertyInStyle(style, ids, styleReader);
++ }
++
++ bool QQStyleKitPropertyResolver::writeStyleProperty(
++ const QQStyleKitPropertyGroup *group,
++ const QQSK::Property property,
++ const QVariant &value)
++ {
++ // While readStyleProperty() takes propagation into account, writeStyleProperty() doesn't.
++ // Instead it writes \a value directly to the storage that the group belongs to.
++ Q_ASSERT(group);
++ const QQStyleKitControlProperties *controlProperties = group->controlProperties();
++ const QQSK::PropertyPathFlags pathFlags = group->pathFlags();
++ const QQSK::Subclass subclass = controlProperties->subclass();
++ const QQStyleKitPropertyPath propertyPath(group, property);
++
++ if (pathFlags.testFlag(QQSK::PropertyPathFlag::Global)) {
++ qmlWarning(controlProperties) << "Properties inside 'global' are read-only!";
++ return false;
++ }
++
++ if (subclass == QQSK::Subclass::QQStyleKitReader) {
++ // This is a write to a StyleKitReader, probably from an ongoing transition
++ QQStyleKitReader *reader = controlProperties->asQQStyleKitReader();
++ const PropertyStorageId key = propertyPath.createStorageId(QQSK::StateFlag::Normal);
++ const QVariant currentValue = reader->readStyleProperty(key);
++ const bool valueChanged = currentValue != value;
++ if (valueChanged) {
++ reader->writeStyleProperty(key, value);
++ QQStyleKitDebug::notifyPropertyWrite(group, property, reader, QQSK::StateFlag::Normal, key, value);
++ }
++ return valueChanged;
++ }
++
++ if (subclass == QQSK::Subclass::QQStyleKitState) {
++ // This is a write to a control inside the StyleKit style
++ const QQStyleKitControlState *controlState = controlProperties->asQQStyleKitState();
++ QQStyleKitControl *control = controlState->control();
++ const QQSK::State nestedState = controlState->nestedState();
++ const PropertyStorageId key = propertyPath.createStorageId(nestedState);
++ const QVariant currentValue = control->readStyleProperty(key);
++ const bool valueChanged = currentValue != value;
++ if (valueChanged) {
++ /* Optimization: Allow a control to track which states it stores property values for.
++ * When later reading a property via propagation, we can skip that control entirely
++ * if it has no stored values corresponding to the state requested by the StyleKitReader. */
++ control->m_writtenStates |= nestedState;
++ /* Optimization: Track which properties a style, theme, or variation defines
++ * values for, and for which states. When reading a property later, we can skip
++ * the style/theme/variation entirely if it has no stored values for it, or if
++ * none match the state requested by the StyleKitReader. */
++ QQStyleKitControls *controls = control->controls();
++ const QQSK::State alreadyWrittenStates = controls->m_writtenPropertyPaths[propertyPath.pathId()];
++ controls->m_writtenPropertyPaths[propertyPath.pathId()] = alreadyWrittenStates | nestedState;
++
++ control->writeStyleProperty(key, value);
++ QQStyleKitDebug::notifyPropertyWrite(group, property, control, nestedState, key, value);
++ }
++ return valueChanged;
++ }
++
++ Q_UNREACHABLE();
++ return false;
++ }
++
++ bool QQStyleKitPropertyResolver::hasLocalStyleProperty(
++ const QQStyleKitPropertyGroup *group,
++ const QQSK::Property property)
++ {
++ Q_ASSERT(group);
++ const QQStyleKitControlProperties *controlProperties = group->controlProperties();
++ const QQSK::PropertyPathFlags pathFlags = group->pathFlags();
++ const QQStyleKitPropertyPath propertyPath(group, property);
++ const QQSK::Subclass subclass = controlProperties->subclass();
++
++ if (pathFlags.testFlag(QQSK::PropertyPathFlag::Global))
++ return false;
++
++ if (subclass == QQSK::Subclass::QQStyleKitReader) {
++ const PropertyStorageId key = propertyPath.createStorageId(QQSK::StateFlag::Normal);
++ return controlProperties->asQQStyleKitReader()->readStyleProperty(key).isValid();
++ }
++
++ if (subclass == QQSK::Subclass::QQStyleKitState) {
++ const QQStyleKitControlState *controlState = controlProperties->asQQStyleKitState();
++ const QQStyleKitControl *control = controlState->control();
++ const PropertyStorageId key = propertyPath.createStorageId(controlState->nestedState());
++ return control->readStyleProperty(key).isValid();
++ }
++
++ Q_UNREACHABLE();
++ return false;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqstylekitpropertyresolver_p.cpp"