QCSS: Support Qt 5-style integer property selectors

In Qt 5 style sheets, objects could be selected by an enum-type property
using the integer value of the enum value, e.g

QToolButton[popupMode="1"] { ... }

In Qt 6, the the new meta type system and QVariant implementation enabled
QVariant::toString to return the string representation of the enum value
instead for a property containing an enum. Since QStyleSheetStyle's
attribute matching is string based, this breaks the Qt 5 style selector,
and QCSS code instead needs to use e.g.

QToolButton[popupMode=MenuButtonPopup] { ... }

While the new syntax is arguably preferable, this is an unintentional
change that silently breaks style sheet code (no error or warning at
compile- or run-time).

To support Qt 5-style selectors, we have to change the StyleSelector
interface of the QCssParser API so that we can pass through what type
of value the attribute extractor should return; if an integer string "1"
is provided, then we need to compare the enum integer value; if the
string provided does not represent a number, then we need to compare the
name of the enum value.

Since the pure virtual attribute() method that needs to be implemented
to extract the attribute value of the node is implemented in modules
outside qtbase, add a second virtual method that takes the entire
QCss::AttributeSelector, which includes the value to match. Extractor
implementations can use it to evaluate which type of data to return for
an exact match. The default implementation calls the old attribute()
method so that existing StyleSelector implementations continue to work.

Make the respective change in the QStyleSheetStyleSelector, and simplify
the surrounding code. Adjust other StyleSelector implemnentations in
qtbase. As a drive-by, remove the superfluous virtual declaration from
those overrides.

Once submodules are adjusted to override this virtual function instead
of the (now no longer pure) virtual attribute() method, that method can
be removed.

Pick-to: 6.3 6.2
Fixes: QTBUG-99642
Change-Id: I9a2b3498f77bf7cab5e90980b7dab2f621d3d859
Reviewed-by: Oliver Eftevaag <oliver.eftevaag@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Volker Hilsheimer 2022-01-13 22:42:59 +01:00
parent da6c8b2fc6
commit cb27ed30f7
6 changed files with 66 additions and 50 deletions

View File

@ -1988,7 +1988,7 @@ bool StyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const
QStringList StyleSelector::nodeIds(NodePtr node) const
{
return QStringList(attribute(node, QLatin1String("id")));
return QStringList(attributeValue(node, QCss::AttributeSelector{QLatin1String("id"), {}, AttributeSelector::NoMatch}));
}
bool StyleSelector::selectorMatches(const Selector &selector, NodePtr node)
@ -2060,7 +2060,7 @@ bool StyleSelector::basicSelectorMatches(const BasicSelector &sel, NodePtr node)
for (int i = 0; i < sel.attributeSelectors.count(); ++i) {
const QCss::AttributeSelector &a = sel.attributeSelectors.at(i);
const QString attrValue = attribute(node, a.name);
const QString attrValue = attributeValue(node, a);
if (attrValue.isNull())
return false;

View File

@ -557,11 +557,10 @@ struct AttributeSelector
MatchEndsWith,
MatchContains
};
inline AttributeSelector() : valueMatchCriterium(NoMatch) {}
QString name;
QString value;
ValueMatchType valueMatchCriterium;
ValueMatchType valueMatchCriterium = NoMatch;
};
QT_CSS_DECLARE_TYPEINFO(AttributeSelector, Q_RELOCATABLE_TYPE)
@ -666,7 +665,10 @@ public:
QList<Declaration> declarationsForNode(NodePtr node, const char *extraPseudo = nullptr);
virtual bool nodeNameEquals(NodePtr node, const QString& nodeName) const;
virtual QString attribute(NodePtr node, const QString &name) const = 0;
// ### TODO keep until QtSvg is updated with new qtbase and has overridden attributeValue
virtual QString attribute(NodePtr, const QString &) const { return QString(); }
virtual QString attributeValue(NodePtr node, const QCss::AttributeSelector &aSelector) const
{ return attribute(node, aSelector.name); }
virtual bool hasAttributes(NodePtr node) const = 0;
virtual QStringList nodeIds(NodePtr node) const;
virtual QStringList nodeNames(NodePtr node) const = 0;

View File

@ -1848,14 +1848,14 @@ public:
inline QTextHtmlStyleSelector(const QTextHtmlParser *parser)
: parser(parser) { nameCaseSensitivity = Qt::CaseInsensitive; }
virtual QStringList nodeNames(NodePtr node) const override;
virtual QString attribute(NodePtr node, const QString &name) const override;
virtual bool hasAttributes(NodePtr node) const override;
virtual bool isNullNode(NodePtr node) const override;
virtual NodePtr parentNode(NodePtr node) const override;
virtual NodePtr previousSiblingNode(NodePtr node) const override;
virtual NodePtr duplicateNode(NodePtr node) const override;
virtual void freeNode(NodePtr node) const override;
QStringList nodeNames(NodePtr node) const override;
QString attributeValue(NodePtr node, const QCss::AttributeSelector &aSelector) const override;
bool hasAttributes(NodePtr node) const override;
bool isNullNode(NodePtr node) const override;
NodePtr parentNode(NodePtr node) const override;
NodePtr previousSiblingNode(NodePtr node) const override;
NodePtr duplicateNode(NodePtr node) const override;
void freeNode(NodePtr node) const override;
private:
const QTextHtmlParser *parser;
@ -1879,10 +1879,10 @@ static inline int findAttribute(const QStringList &attributes, const QString &na
return idx;
}
QString QTextHtmlStyleSelector::attribute(NodePtr node, const QString &name) const
QString QTextHtmlStyleSelector::attributeValue(NodePtr node, const QCss::AttributeSelector &aSelector) const
{
const QStringList &attributes = parser->at(node.id).attributes;
const int idx = findAttribute(attributes, name);
const int idx = findAttribute(attributes, aSelector.name);
if (idx == -1)
return QString();
return attributes.at(idx + 1);

View File

@ -1570,39 +1570,53 @@ public:
} while (metaObject != nullptr);
return result;
}
QString attribute(NodePtr node, const QString& name) const override
QString attributeValue(NodePtr node, const QCss::AttributeSelector& aSelector) const override
{
if (isNullNode(node))
return QString();
const QString &name = aSelector.name;
QHash<QString, QString> &cache = m_attributeCache[OBJECT_PTR(node)];
QHash<QString, QString>::const_iterator cacheIt = cache.constFind(name);
if (cacheIt != cache.constEnd())
return cacheIt.value();
QVariant value;
QString valueStr;
QObject *obj = OBJECT_PTR(node);
QVariant value = obj->property(name.toLatin1());
if (!value.isValid()) {
if (name == QLatin1String("class")) {
QString className = QString::fromLatin1(obj->metaObject()->className());
if (className.contains(QLatin1Char(':')))
className.replace(QLatin1Char(':'), QLatin1Char('-'));
cache[name] = className;
return className;
} else if (name == QLatin1String("style")) {
QWidget *w = qobject_cast<QWidget *>(obj);
QStyleSheetStyle *proxy = w ? qt_styleSheet(w->style()) : nullptr;
if (proxy) {
QString styleName = QString::fromLatin1(proxy->baseStyle()->metaObject()->className());
cache[name] = styleName;
return styleName;
const int propertyIndex = obj->metaObject()->indexOfProperty(name.toLatin1());
if (propertyIndex == -1) {
value = obj->property(name.toLatin1()); // might be a dynamic property
if (!value.isValid()) {
if (name == u"class"_qs) {
QString className = QString::fromLatin1(obj->metaObject()->className());
if (className.contains(QLatin1Char(':')))
className.replace(QLatin1Char(':'), QLatin1Char('-'));
valueStr = className;
} else if (name == u"style"_qs) {
QWidget *w = qobject_cast<QWidget *>(obj);
QStyleSheetStyle *proxy = w ? qt_styleSheet(w->style()) : nullptr;
if (proxy)
valueStr = QString::fromLatin1(proxy->baseStyle()->metaObject()->className());
}
}
} else {
const QMetaProperty property = obj->metaObject()->property(propertyIndex);
value = property.read(obj);
// support Qt 5 selector syntax, which required the integer enum value
if (property.isEnumType()) {
bool isNumber;
aSelector.value.toInt(&isNumber);
if (isNumber)
value.convert(QMetaType::fromType<int>());
}
}
if (value.isValid()) {
valueStr = (value.userType() == QMetaType::QStringList
|| value.userType() == QMetaType::QVariantList)
? value.toStringList().join(QLatin1Char(' '))
: value.toString();
}
QString valueStr = (value.userType() == QMetaType::QStringList
|| value.userType() == QMetaType::QVariantList)
? value.toStringList().join(QLatin1Char(' '))
: value.toString();
cache[name] = valueStr;
return valueStr;
}

View File

@ -886,32 +886,34 @@ public:
styleSheets.append(sheet);
}
virtual QStringList nodeNames(NodePtr node) const override { return QStringList(reinterpret_cast<QDomElement *>(node.ptr)->tagName()); }
virtual QString attribute(NodePtr node, const QString &name) const override { return reinterpret_cast<QDomElement *>(node.ptr)->attribute(name); }
virtual bool hasAttribute(NodePtr node, const QString &name) const { return reinterpret_cast<QDomElement *>(node.ptr)->hasAttribute(name); }
virtual bool hasAttributes(NodePtr node) const override { return reinterpret_cast<QDomElement *>(node.ptr)->hasAttributes(); }
QStringList nodeNames(NodePtr node) const override
{ return QStringList(reinterpret_cast<QDomElement *>(node.ptr)->tagName()); }
QString attributeValue(NodePtr node, const QCss::AttributeSelector &aSel) const override
{ return reinterpret_cast<QDomElement *>(node.ptr)->attribute(aSel.name); }
bool hasAttribute(NodePtr node, const QString &name) const
{ return reinterpret_cast<QDomElement *>(node.ptr)->hasAttribute(name); }
bool hasAttributes(NodePtr node) const override
{ return reinterpret_cast<QDomElement *>(node.ptr)->hasAttributes(); }
virtual bool isNullNode(NodePtr node) const override {
return reinterpret_cast<QDomElement *>(node.ptr)->isNull();
}
virtual NodePtr parentNode(NodePtr node) const override {
bool isNullNode(NodePtr node) const override
{ return reinterpret_cast<QDomElement *>(node.ptr)->isNull(); }
NodePtr parentNode(NodePtr node) const override {
NodePtr parent;
parent.ptr = new QDomElement(reinterpret_cast<QDomElement *>(node.ptr)->parentNode().toElement());
return parent;
}
virtual NodePtr duplicateNode(NodePtr node) const override {
NodePtr duplicateNode(NodePtr node) const override {
NodePtr n;
n.ptr = new QDomElement(*reinterpret_cast<QDomElement *>(node.ptr));
return n;
}
virtual NodePtr previousSiblingNode(NodePtr node) const override {
NodePtr previousSiblingNode(NodePtr node) const override {
NodePtr sibling;
sibling.ptr = new QDomElement(reinterpret_cast<QDomElement *>(node.ptr)->previousSiblingElement());
return sibling;
}
virtual void freeNode(NodePtr node) const override {
delete reinterpret_cast<QDomElement *>(node.ptr);
}
void freeNode(NodePtr node) const override
{ delete reinterpret_cast<QDomElement *>(node.ptr); }
private:
QDomDocument doc;

View File

@ -2371,8 +2371,6 @@ void tst_QStyleSheetStyle::enumPropertySelector()
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QEXPECT_FAIL("Enum value", "In Qt 5, style sheet selectors have to use integer enum values", Continue);
#else
QEXPECT_FAIL("Int value", "In Qt 6, style sheet selectors must use the enum value name", Continue);
#endif
QVERIFY(styledSizeHint.width() > unstyledSizeHint.width());