QT 样式解析流程(转载)

样式解析原始文章,此处私人备份
qt 不同版本可能有差异

  1. "QApplication::setStyleSheet()"设置样式表:
  2. 创建新的样式表。
  3. 设置新的样式。
void QApplication::setStyleSheet(const QString& styleSheet)
{
    QApplicationPrivate::styleSheet = styleSheet;
    QStyleSheetStyle *proxy = qobject_cast<QStyleSheetStyle*>(QApplicationPrivate::app_style);
    if (styleSheet.isEmpty()) { // application style sheet removed
        if (!proxy)
            return; // there was no stylesheet before
        setStyle(proxy->base);
    } else if (proxy) { // style sheet update, just repolish
        proxy->repolish(qApp);
    } else { // stylesheet set the first time
        QStyleSheetStyle *newProxy = new QStyleSheetStyle(QApplicationPrivate::app_style);
        QApplicationPrivate::app_style->setParent(newProxy);
        setStyle(newProxy);
    }
}
  1. “QApplication::setStyle”——设置样式:
void QApplication::setStyle(QStyle *style)
{
    if (!style || style == QApplicationPrivate::app_style)
        return;
 
    QWidgetList all = allWidgets();
 
    // clean up the old style
    if (QApplicationPrivate::app_style) {
        if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) {
            for (QWidgetList::ConstIterator it = all.constBegin(), cend = all.constEnd(); it != cend; ++it) {
                QWidget *w = *it;
                if (!(w->windowType() == Qt::Desktop) &&        // except desktop
                     w->testAttribute(Qt::WA_WState_Polished)) { // has been polished
                    QApplicationPrivate::app_style->unpolish(w);
                }
            }
        }
        QApplicationPrivate::app_style->unpolish(qApp);
    }
 
    QStyle *old = QApplicationPrivate::app_style; // save
 
    QApplicationPrivate::overrides_native_style =
        nativeStyleClassName() == QByteArray(style->metaObject()->className());
 
#ifndef QT_NO_STYLE_STYLESHEET
    if (!QApplicationPrivate::styleSheet.isEmpty() && !qobject_cast<QStyleSheetStyle *>(style)) {
        // we have a stylesheet already and a new style is being set
        QStyleSheetStyle *newProxy = new QStyleSheetStyle(style);
        style->setParent(newProxy);
        QApplicationPrivate::app_style = newProxy;
    } else
#endif // QT_NO_STYLE_STYLESHEET
        QApplicationPrivate::app_style = style;
    QApplicationPrivate::app_style->setParent(qApp); // take ownership
 
    // take care of possible palette requirements of certain gui
    // styles. Do it before polishing the application since the style
    // might call QApplication::setPalette() itself
    if (QApplicationPrivate::set_pal) {
        QApplication::setPalette(*QApplicationPrivate::set_pal);
    } else if (QApplicationPrivate::sys_pal) {
        clearSystemPalette();
        initSystemPalette();
        QApplicationPrivate::initializeWidgetPaletteHash();
        QApplicationPrivate::initializeWidgetFontHash();
        QApplicationPrivate::setPalette_helper(*QApplicationPrivate::sys_pal, /*className=*/, /*clearWidgetPaletteHash=*/false);
    } else if (!QApplicationPrivate::sys_pal) {
        // Initialize the sys_pal if it hasn't happened yet...
        QApplicationPrivate::setSystemPalette(QApplicationPrivate::app_style->standardPalette());
    }
 
    // initialize the application with the new style
    QApplicationPrivate::app_style->polish(qApp);
 
    // re-polish existing widgets if necessary
    if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) {
        for (QWidgetList::ConstIterator it = all.constBegin(), cend = all.constEnd(); it != cend; ++it) {
            QWidget *w = *it;
            if (w->windowType() != Qt::Desktop && w->testAttribute(Qt::WA_WState_Polished)) {
                if (w->style() == QApplicationPrivate::app_style)
                    QApplicationPrivate::app_style->polish(w);                // repolish
#ifndef QT_NO_STYLE_STYLESHEET
                else
                    w->setStyleSheet(w->styleSheet()); // touch
#endif
            }
        }
 
        for (QWidgetList::ConstIterator it = all.constBegin(), cend = all.constEnd(); it != cend; ++it) {
            QWidget *w = *it;
            if (w->windowType() != Qt::Desktop && !w->testAttribute(Qt::WA_SetStyle)) {
                    QEvent e(QEvent::StyleChange);
                    QApplication::sendEvent(w, &e);
                    w->update();
            }
        }
    }
 
#ifndef QT_NO_STYLE_STYLESHEET
    if (QStyleSheetStyle *oldProxy = qobject_cast<QStyleSheetStyle *>(old)) {
        oldProxy->deref();
    } else
#endif
    if (old && old->parent() == qApp) {
        delete old;
    }
 
    if (QApplicationPrivate::focus_widget) {
        QFocusEvent in(QEvent::FocusIn, Qt::OtherFocusReason);
        QApplication::sendEvent(QApplicationPrivate::focus_widget->style(), &in);
        QApplicationPrivate::focus_widget->update();
    }
}

2-1. 获取所有的QWidget列表(在QWidget的构造函数中调用QWidgetPrivate::init函数,将当前QWidget加入到列表)。

2-2. 移除所有QWidget上旧的样式。

2-3. 设置所有QWidget新的样式。

2-4. 更新所有的QWidget。

  1. “QStyleSheetStyle::styleRules”——经过一系列调用到这个函数中,获取样式规则:
QVector<QCss::StyleRule> QStyleSheetStyle::styleRules(const QObject *obj) const
{
    QHash<const QObject *, QVector<StyleRule> >::const_iterator cacheIt = styleSheetCaches->styleRulesCache.constFind(obj);
    if (cacheIt != styleSheetCaches->styleRulesCache.constEnd())
        return cacheIt.value();
 
    if (!initObject(obj)) {
        return QVector<StyleRule>();
    }
 
    QStyleSheetStyleSelector styleSelector;
 
    StyleSheet defaultSs;
    QHash<const void *, StyleSheet>::const_iterator defaultCacheIt = styleSheetCaches->styleSheetCache.constFind(baseStyle());
    if (defaultCacheIt == styleSheetCaches->styleSheetCache.constEnd()) {
        defaultSs = getDefaultStyleSheet();
        QStyle *bs = baseStyle();
        styleSheetCaches->styleSheetCache.insert(bs, defaultSs);
        QObject::connect(bs, SIGNAL(destroyed(QObject*)), styleSheetCaches, SLOT(styleDestroyed(QObject*)), Qt::UniqueConnection);
    } else {
        defaultSs = defaultCacheIt.value();
    }
    styleSelector.styleSheets += defaultSs;
 
    if (!qApp->styleSheet().isEmpty()) {
        StyleSheet appSs;
        QHash<const void *, StyleSheet>::const_iterator appCacheIt = styleSheetCaches->styleSheetCache.constFind(qApp);
        if (appCacheIt == styleSheetCaches->styleSheetCache.constEnd()) {
            QString ss = qApp->styleSheet();
            if (ss.startsWith(QLatin1String("file:///")))
                ss.remove(0, 8);
            parser.init(ss, qApp->styleSheet() != ss);
            if (!parser.parse(&appSs))
                qWarning("Could not parse application stylesheet");
            appSs.origin = StyleSheetOrigin_Inline;
            appSs.depth = 1;
            styleSheetCaches->styleSheetCache.insert(qApp, appSs);
        } else {
            appSs = appCacheIt.value();
        }
        styleSelector.styleSheets += appSs;
    }
 
    QVector<QCss::StyleSheet> objectSs;
    for (const QObject *o = obj; o; o = parentObject(o)) {
        QString styleSheet = o->property("styleSheet").toString();
        if (styleSheet.isEmpty())
            continue;
        StyleSheet ss;
        QHash<const void *, StyleSheet>::const_iterator objCacheIt = styleSheetCaches->styleSheetCache.constFind(o);
        if (objCacheIt == styleSheetCaches->styleSheetCache.constEnd()) {
            parser.init(styleSheet);
            if (!parser.parse(&ss)) {
                parser.init(QLatin1String("* {") + styleSheet + QLatin1Char('}'));
                if (!parser.parse(&ss))
                   qWarning("Could not parse stylesheet of object %p", o);
            }
            ss.origin = StyleSheetOrigin_Inline;
            styleSheetCaches->styleSheetCache.insert(o, ss);
        } else {
            ss = objCacheIt.value();
        }
        objectSs.append(ss);
    }
 
    for (int i = ; i < objectSs.count(); i++)
        objectSs[i].depth = objectSs.count() - i + ;
 
    styleSelector.styleSheets += objectSs;
 
    StyleSelector::NodePtr n;
    n.ptr = const_cast<QObject *>(obj);
    QVector<QCss::StyleRule> rules = styleSelector.styleRulesForNode(n);
    styleSheetCaches->styleRulesCache.insert(obj, rules);
    return rules;
}

4-1. 初始化如果缓存中没有样式,先进行初始化。

4-2. 获取基本样式到@defaultSs中,并添加到@styleSelector.styleSheets

4-3. 在缓存中获取全局@qApp样式到@appSs中,并添加到@styleSelector.styleSheets。
4-3-1. 如果在缓存中没有找到全局@qApp样式,则获取@qApp样式表字符串进行解析生成StyleSheet

4-4. 不断遍历基类,从缓存中获取基类的样式到@objectSs中,并添加到@styleSelector.styleSheets。

4-4-1. 如果缓存没有找到样式,则利用字符串重新解析生成StyleSheet

4-5. 根据样式选着器(QStyleSheetStyleSelector)获取样式规添加到@rules。

4-6. 最后将对象(QObject)与样式规则(StyleRule)插入到缓存@styleSheetCaches->styleRulesCache中。

  1. 将字符串解析生成样式表StyleSheet(主要在qcssparser.cpp文件中):

在"QStyleSheetStyle::styleRules"主要通过通过两个函数"Parser::init"和"Parser::parse"。

5-1. 初始化解析器:

void Parser::init(const QString &css, bool isFile)
{
    QString styleSheet = css;
    if (isFile) {
        QFile file(css);
        if (file.open(QFile::ReadOnly)) {
            sourcePath = QFileInfo(styleSheet).absolutePath() + QLatin1Char('/');
            QTextStream stream(&file);
            styleSheet = stream.readAll();
        } else {
            qWarning() << "QCss::Parser - Failed to load file " << css;
            styleSheet.clear();
        }
    } else {
        sourcePath.clear();
    }
 
    hasEscapeSequences = false;
    symbols.resize();
    symbols.reserve();
    Scanner::scan(Scanner::preprocess(styleSheet, &hasEscapeSequences), &symbols);
    index = ;
    errorIndex = -;
}

5-1-1. 将字符串进行预处理(这里主要是处理转义字符)。

5-1-2. 逐个扫描形成一个一个符号(与编译原理中的扫描程序一致,形成符号后,方便后面进一步处理)。
5-2. 解析样式表:

bool Parser::parse(StyleSheet *styleSheet, Qt::CaseSensitivity nameCaseSensitivity)
{
    if (testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("charset"))) {
        if (!next(STRING)) return false;
        if (!next(SEMICOLON)) return false;
    }
 
    while (test(S) || test(CDO) || test(CDC)) {}
 
    while (testImport()) {
        ImportRule rule;
        if (!parseImport(&rule)) return false;
        styleSheet->importRules.append(rule);
        while (test(S) || test(CDO) || test(CDC)) {}
    }
 
    do {
        if (testMedia()) {
            MediaRule rule;
            if (!parseMedia(&rule)) return false;
            styleSheet->mediaRules.append(rule);
        } else if (testPage()) {
            PageRule rule;
            if (!parsePage(&rule)) return false;
            styleSheet->pageRules.append(rule);
        } else if (testRuleset()) {
            StyleRule rule;
            if (!parseRuleset(&rule)) return false;
            styleSheet->styleRules.append(rule);
        } else if (test(ATKEYWORD_SYM)) {
            if (!until(RBRACE)) return false;
        } else if (hasNext()) {
            return false;
        }
        while (test(S) || test(CDO) || test(CDC)) {}
    } while (hasNext());
    styleSheet->buildIndexes(nameCaseSensitivity);
    return true;
}

5-2-1. 遍历所有符号,根据符号的类型进行相应的处理(这里我们只关注样式规则"parseRuleset(&rule)")。

5-2-2. 解析样式规则:

bool Parser::parseRuleset(StyleRule *styleRule)
{
    Selector sel;
    if (!parseSelector(&sel)) return false;
    styleRule->selectors.append(sel);
 
    while (test(COMMA)) {
        skipSpace();
        Selector sel;
        if (!parseNextSelector(&sel)) return false;
        styleRule->selectors.append(sel);
    }
 
    skipSpace();
    if (!next(LBRACE)) return false;
    const int declarationStart = index;
 
    do {
        skipSpace();
        Declaration decl;
        const int rewind = index;
        if (!parseNextDeclaration(&decl)) {
            index = rewind;
            const bool foundSemicolon = until(SEMICOLON);
            const int semicolonIndex = index;
 
            index = declarationStart;
            const bool foundRBrace = until(RBRACE);
 
            if (foundSemicolon && semicolonIndex < index) {
                decl = Declaration();
                index = semicolonIndex - ;
            } else {
                skipSpace();
                return foundRBrace;
            }
        }
        if (!decl.isEmpty())
            styleRule->declarations.append(decl);
    } while (test(SEMICOLON));
 
    if (!next(RBRACE)) return false;
    skipSpace();
    return true;
}

5-2-2-1. 解析选择器(id选着器,class选着器,属性选着器等)。

5-2-2-2. 解析声明,包括属性名、属性值等。

5-2-2-3. 将解析的声明添加到样式规则中。

  1. (4-5的细化)根据样式选着器(QStyleSheetStyleSelector)得到样式规则"":
QVector<StyleRule> StyleSelector::styleRulesForNode(NodePtr node)
 {
     QVector<StyleRule> rules;
     if (styleSheets.isEmpty())
         return rules;
 
     QMap<uint, StyleRule> weightedRules; // (spec, rule) that will be sorted below
 
     //prune using indexed stylesheet
     for (int sheetIdx = ; sheetIdx < styleSheets.count(); ++sheetIdx) {
         const StyleSheet &styleSheet = styleSheets.at(sheetIdx);
         for (int i = 0; i < styleSheet.styleRules.count(); ++i) {
             matchRule(node, styleSheet.styleRules.at(i), styleSheet.origin, styleSheet.depth, &weightedRules);
         }
 
         if (!styleSheet.idIndex.isEmpty()) {
             QStringList ids = nodeIds(node);
             for (int i = 0; i < ids.count(); i++) {
                 const QString &key = ids.at(i);
                 QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.idIndex.constFind(key);
                 while (it != styleSheet.idIndex.constEnd() && it.key() == key) {
                     matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
                     ++it;
                 }
             }
         }
         if (!styleSheet.nameIndex.isEmpty()) {
             QStringList names = nodeNames(node);
             for (int i = 0; i < names.count(); i++) {
                 QString name = names.at(i);
                 if (nameCaseSensitivity == Qt::CaseInsensitive)
                     name = name.toLower();
                 QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.nameIndex.constFind(name);
                 while (it != styleSheet.nameIndex.constEnd() && it.key() == name) {
                     matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules);
                     ++it;
                 }
             }
         }
         if (!medium.isEmpty()) {
             for (int i = ; i < styleSheet.mediaRules.count(); ++i) {
                 if (styleSheet.mediaRules.at(i).media.contains(medium, Qt::CaseInsensitive)) {
                     for (int j = ; j < styleSheet.mediaRules.at(i).styleRules.count(); ++j) {
                         matchRule(node, styleSheet.mediaRules.at(i).styleRules.at(j), styleSheet.origin,
                                styleSheet.depth, &weightedRules);
                     }
                 }
             }
         }
     }
 
     rules.reserve(weightedRules.count());
     QMap<uint, StyleRule>::const_iterator it = weightedRules.constBegin();
     for ( ; it != weightedRules.constEnd() ; ++it)
         rules += *it;
 
     return rules;
 }

6-1. 遍历已有的样式规则(styleSheet.styleRules),如果匹配规则,将规则添加到@weightedRules中。

6-2. 遍历id样式(styleSheet.idIndex),获取当前对象的名称(objectName),对象名称相同则匹配规则,将匹配的规则添加到@weightedRules中。

6-3. 遍历class样式(styleSheet.nameIndex),获取当前对象的类名称(className)和基类的类名称,类名称相同则匹配规则,将规则添加到@weightedRules中。

6-4. 将@weightedRules中的样式规则添加到@rules,并返回@rules

  1. 最后需要渲染规则,将得到的样式规则转换为绘制时需要的对象(字体、颜色、背景画刷、背景图片等等):
QRenderRule::QRenderRule(const QVector<Declaration> &declarations, const QObject *object)
: features(), hasFont(false), pal(), b(), bg(), bd(), ou(), geo(), p(), img(), clipset()
{
    QPalette palette = QApplication::palette(); // ###: ideally widget's palette
    ValueExtractor v(declarations, palette)zhua
    features = v.extractStyleFeatures();
 
    int w = -, h = -, minw = -, minh = -, maxw = -, maxh = -;
    if (v.extractGeometry(&w, &h, &minw, &minh, &maxw, &maxh))
        geo = new QStyleSheetGeometryData(w, h, minw, minh, maxw, maxh);
 
    int left = , top = , right = , bottom = ;
    Origin origin = Origin_Unknown;
    Qt::Alignment position = ;
    QCss::PositionMode mode = PositionMode_Unknown;
    Qt::Alignment textAlignment = ;
    if (v.extractPosition(&left, &top, &right, &bottom, &origin, &position, &mode, &textAlignment))
        p = new QStyleSheetPositionData(left, top, right, bottom, origin, position, mode, textAlignment);
 
    int margins[], paddings[], spacing = -;
    for (int i = ; i < ; i++)
        margins[i] = paddings[i] = ;
    if (v.extractBox(margins, paddings, &spacing))
        b = new QStyleSheetBoxData(margins, paddings, spacing);
 
    int borders[];
    QBrush colors[];
    QCss::BorderStyle styles[];
    QSize radii[];
    for (int i = ; i < ; i++) {
        borders[i] = ;
        styles[i] = BorderStyle_None;
    }
    if (v.extractBorder(borders, colors, styles, radii))
        bd = new QStyleSheetBorderData(borders, colors, styles, radii);
 
    int offsets[];
    for (int i = ; i < ; i++) {
        borders[i] = offsets[i] = ;
        styles[i] = BorderStyle_None;
    }
    if (v.extractOutline(borders, colors, styles, radii, offsets))
        ou = new QStyleSheetOutlineData(borders, colors, styles, radii, offsets);
 
    QBrush brush;
    QString uri;
    Repeat repeat = Repeat_XY;
    Qt::Alignment alignment = Qt::AlignTop | Qt::AlignLeft;
    Attachment attachment = Attachment_Scroll;
    origin = Origin_Padding;
    Origin clip = Origin_Border;
    if (v.extractBackground(&brush, &uri, &repeat, &alignment, &origin, &attachment, &clip))
        bg = new QStyleSheetBackgroundData(brush, QPixmap(uri), repeat, alignment, origin, attachment, clip);
 
    QBrush sfg, fg;
    QBrush sbg, abg;
    if (v.extractPalette(&fg, &sfg, &sbg, &abg))
        pal = new QStyleSheetPaletteData(fg, sfg, sbg, abg);
 
    QIcon icon;
    alignment = Qt::AlignCenter;
    QSize size;
    if (v.extractImage(&icon, &alignment, &size))
        img = new QStyleSheetImageData(icon, alignment, size);
 
    int adj = -;
    hasFont = v.extractFont(&font, &adj);
 
#ifndef QT_NO_TOOLTIP
    if (object && qstrcmp(object->metaObject()->className(), "QTipLabel") == )
        palette = QToolTip::palette();
#endif
 
    for (int i = ; i < declarations.count(); i++) {
        const Declaration& decl = declarations.at(i);
        if (decl.d->propertyId == BorderImage) {
            QString uri;
            QCss::TileMode horizStretch, vertStretch;
            int cuts[];
 
            decl.borderImageValue(&uri, cuts, &horizStretch, &vertStretch);
            if (uri.isEmpty() || uri == QLatin1String("none")) {
                if (bd && bd->bi)
                    bd->bi->pixmap = QPixmap();
            } else {
                if (!bd)
                    bd = new QStyleSheetBorderData;
                if (!bd->bi)
                    bd->bi = new QStyleSheetBorderImageData;
 
                QStyleSheetBorderImageData *bi = bd->bi;
                bi->pixmap = QPixmap(uri);
                for (int i = ; i < ; i++)
                    bi->cuts[i] = cuts[i];
                bi->horizStretch = horizStretch;
                bi->vertStretch = vertStretch;
            }
        } else if (decl.d->propertyId == QtBackgroundRole) {
            if (bg && bg->brush.style() != Qt::NoBrush)
                continue;
            int role = decl.d->values.at().variant.toInt();
            if (role >= Value_FirstColorRole && role <= Value_LastColorRole)
                defaultBackground = palette.color((QPalette::ColorRole)(role-Value_FirstColorRole));
        } else if (decl.d->property.startsWith(QLatin1String("qproperty-"), Qt::CaseInsensitive)) {
            // intentionally left blank...
        } else if (decl.d->propertyId == UnknownProperty) {
            bool knownStyleHint = false;
            for (int i = ; i < numKnownStyleHints; i++) {
                QLatin1String styleHint(knownStyleHints[i]);
                if (decl.d->property.compare(styleHint) == ) {
                   QString hintName = QString(styleHint);
                   QVariant hintValue;
                   if (hintName.endsWith(QLatin1String("alignment"))) {
                       hintValue = (int) decl.alignmentValue();
                   } else if (hintName.endsWith(QLatin1String("color"))) {
                       hintValue = (int) decl.colorValue().rgba();
                   } else if (hintName.endsWith(QLatin1String("size"))) {
                       hintValue = decl.sizeValue();
                   } else if (hintName.endsWith(QLatin1String("icon"))) {
                       hintValue = decl.iconValue();
                   } else if (hintName == QLatin1String("button-layout")
                              && decl.d->values.count() !=  && decl.d->values.at().type == Value::String) {
                       hintValue = subControlLayout(decl.d->values.at().variant.toString());
                   } else {
                       int integer;
                       decl.intValue(&integer);
                       hintValue = integer;
                   }
                   styleHints[decl.d->property] = hintValue;
                   knownStyleHint = true;
                   break;
                }
            }
            if (!knownStyleHint)
                qDebug("Unknown property %s", qPrintable(decl.d->property));
        }
    }
 
    if (const QWidget *widget = qobject_cast<const QWidget *>(object)) {
        QStyleSheetStyle *style = const_cast<QStyleSheetStyle *>(globalStyleSheetStyle);
        if (!style)
            style = qobject_cast<QStyleSheetStyle *>(widget->style());
        if (style)
            fixupBorder(style->nativeFrameWidth(widget));
    }
    if (hasBorder() && border()->hasBorderImage())
        defaultBackground = QBrush();
}

从样式规则中得到样式声明,然后根据样式声明去创建不同的绘制对象,为后面的绘制做准备。

绘制对象包括:位置(Geomeory、Position、Box)、边框(Border、Outline)、背景(BackgroundColor、BackgroundImage等)、字体(FontFamily、FontSize、FontStyle、FontWeight等)等等。
  
最后如果想查看QSS属性名称和值的列表,可以查看qtbase\src\gui\text\qcssparser.cpp文件:

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值