Qt allows you to style GUI using stylesheets. Those use similar selector syntax to regular CSS. I would like to use those selectors in code to reach specific Widgets, similarly to the way jQuery works. For example:
SomeQtCSSMagic::resolveSelector("QPushButton#okButton");
Is this possible? If not, is there at least code I could copy-paste from Qt library and edit to suit my needs?
At the moment, Qt offers no official support for that. But it's not all that hard to do using plain C++ code.
Your selector translates to:
for (auto top : qApp->topLevelWidgets()) {
auto widgets = top->findChildren<QWidget*>("okButton");
widgets << top;
for (auto widget : widgets)
if (widget->inherits("QPushButton"))
qDebug() << "found" << widget;
}
If you find such code tedious, you can leverage the private Qt code. The CSS parser interface is in src/gui/text/qcssparser_p.h. Apart from the parser, you also need some code to use the parsed selector to find the widget. Namely, you have to implement a QCss::StyleSelector
for a node that points to a QObject
. That's done by QStyleSheetStyleSelector
in src/widgets/styles/qstylesheetstyle.cpp, but that's a local class that's not exported, so you do have some copypasta to deal with that.
Once you have implemented a QCss::StyleSelector
, here as QObjectStyleSelector
, obtaining the widgets that the selector applies to is easy. You have to iterate over all widgets, and see if the CSS machinery has any rules for a given node, assuming a dummy stylesheet consisting of your selector and an empty body {}
. This approach supports all selectors that the style sheets support, with exception of selecting on a widget's style:
QWidgetList select(const QString & selector) {
static QHash<QString, StyleSheet> cache;
QWidgetList result;
QObjectStyleSelector oSel;
auto it = cache.find(selector);
if (it == cache.end()) {
StyleSheet styleSheet;
Parser parser(selector + "{}");
if (!parser.parse(&styleSheet))
return result;
it = cache.insert(selector, styleSheet);
}
oSel.styleSheets.append(*it);
for (auto top : qApp->topLevelWidgets()) {
auto widgets = top->findChildren<QWidget*>();
widgets << top;
for (auto widget : widgets) {
StyleSelector::NodePtr n { widget };
auto rules = oSel.styleRulesForNode(n);
if (!rules.isEmpty()) result << widget;
}
}
return result;
}
This code above is not "distracted" by any style sheets that might exist on the widgets - it takes into account their side effects (e.g. property changes), but otherwise ignores them.
You can test it as follows:
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QDialog dialog;
QPushButton button{"OK", &dialog};
button.setObjectName("okButton");
button.setStyleSheet("color: red");
auto dialog_l = QWidgetList() << &dialog;
auto button_l = QWidgetList() << &button;
auto all_l = button_l + dialog_l;
Q_ASSERT(select("QPushButton#okButton") == button_l);
Q_ASSERT(select("QDialog QPushButton") == button_l);
Q_ASSERT(select("QDialog") == dialog_l);
Q_ASSERT(select("QDialog, QPushButton") == all_l);
}
The copy-pasta precedes the above:
// https://github.com/KubaO/stackoverflown/tree/master/questions/css-selector-35129103
#include <QtWidgets>
#include <private/qcssparser_p.h>
using namespace QCss;
// FROM src/widgets/styles/qstylesheetstyle.cpp
#define OBJECT_PTR(node) (static_cast<QObject*>((node).ptr))
static inline QObject *parentObject(const QObject *obj)
{
if (qobject_cast<const QLabel *>(obj) && qstrcmp(obj->metaObject()->className(), "QTipLabel") == 0) {
QObject *p = qvariant_cast<QObject *>(obj->property("_q_stylesheet_parent"));
if (p)
return p;
}
return obj->parent();
}
class QObjectStyleSelector : public StyleSelector
{
public:
QObjectStyleSelector() { }
QStringList nodeNames(NodePtr node) const Q_DECL_OVERRIDE
{
if (isNullNode(node))
return QStringList();
const QMetaObject *metaObject = OBJECT_PTR(node)->metaObject();
#ifndef QT_NO_TOOLTIP
if (qstrcmp(metaObject->className(), "QTipLabel") == 0)
return QStringList(QLatin1String("QToolTip"));
#endif
QStringList result;
do {
result += QString::fromLatin1(metaObject->className()).replace(QLatin1Char(':'), QLatin1Char('-'));
metaObject = metaObject->superClass();
} while (metaObject != 0);
return result;
}
QString attribute(NodePtr node, const QString& name) const Q_DECL_OVERRIDE
{
if (isNullNode(node))
return QString();
auto obj = OBJECT_PTR(node);
auto value = obj->property(name.toLatin1());
if (!value.isValid()) {
if (name == QLatin1String("class")) {
auto className = QString::fromLatin1(obj->metaObject()->className());
if (className.contains(QLatin1Char(':')))
className.replace(QLatin1Char(':'), QLatin1Char('-'));
return className;
}
}
if(value.type() == QVariant::StringList || value.type() == QVariant::List)
return value.toStringList().join(QLatin1Char(' '));
else
return value.toString();
}
bool nodeNameEquals(NodePtr node, const QString& nodeName) const Q_DECL_OVERRIDE
{
if (isNullNode(node))
return false;
auto metaObject = OBJECT_PTR(node)->metaObject();
#ifndef QT_NO_TOOLTIP
if (qstrcmp(metaObject->className(), "QTipLabel") == 0)
return nodeName == QLatin1String("QToolTip");
#endif
do {
const ushort *uc = (const ushort *)nodeName.constData();
const ushort *e = uc + nodeName.length();
const uchar *c = (const uchar *)metaObject->className();
while (*c && uc != e && (*uc == *c || (*c == ':' && *uc == '-'))) {
++uc;
++c;
}
if (uc == e && !*c)
return true;
metaObject = metaObject->superClass();
} while (metaObject != 0);
return false;
}
bool hasAttributes(NodePtr) const Q_DECL_OVERRIDE
{ return true; }
QStringList nodeIds(NodePtr node) const Q_DECL_OVERRIDE
{ return isNullNode(node) ? QStringList() : QStringList(OBJECT_PTR(node)->objectName()); }
bool isNullNode(NodePtr node) const Q_DECL_OVERRIDE
{ return node.ptr == 0; }
NodePtr parentNode(NodePtr node) const Q_DECL_OVERRIDE
{ NodePtr n; n.ptr = isNullNode(node) ? 0 : parentObject(OBJECT_PTR(node)); return n; }
NodePtr previousSiblingNode(NodePtr) const Q_DECL_OVERRIDE
{ NodePtr n; n.ptr = 0; return n; }
NodePtr duplicateNode(NodePtr node) const Q_DECL_OVERRIDE
{ return node; }
void freeNode(NodePtr) const Q_DECL_OVERRIDE
{ }
};
// END FROM
#cssparser-35129103.pro
QT = widgets gui-private
CONFIG += c++11
TARGET = cssparser-35129103
TEMPLATE = app
SOURCES += main.cpp
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With