/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtGui module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qcssparser_p.h" | |
#include <qdebug.h> | |
#include <qcolor.h> | |
#include <qfont.h> | |
#include <qfileinfo.h> | |
#include <qfontmetrics.h> | |
#include <qbrush.h> | |
#include <qimagereader.h> | |
#include "private/qfunctions_p.h" | |
#ifndef QT_NO_CSSPARSER | |
QT_BEGIN_NAMESPACE | |
#include "qcssscanner.cpp" | |
using namespace QCss; | |
struct QCssKnownValue | |
{ | |
const char *name; | |
quint64 id; | |
}; | |
static const QCssKnownValue properties[NumProperties - 1] = { | |
{ "-qt-background-role", QtBackgroundRole }, | |
{ "-qt-block-indent", QtBlockIndent }, | |
{ "-qt-list-indent", QtListIndent }, | |
{ "-qt-paragraph-type", QtParagraphType }, | |
{ "-qt-style-features", QtStyleFeatures }, | |
{ "-qt-table-type", QtTableType }, | |
{ "-qt-user-state", QtUserState }, | |
{ "alternate-background-color", QtAlternateBackground }, | |
{ "background", Background }, | |
{ "background-attachment", BackgroundAttachment }, | |
{ "background-clip", BackgroundClip }, | |
{ "background-color", BackgroundColor }, | |
{ "background-image", BackgroundImage }, | |
{ "background-origin", BackgroundOrigin }, | |
{ "background-position", BackgroundPosition }, | |
{ "background-repeat", BackgroundRepeat }, | |
{ "border", Border }, | |
{ "border-bottom", BorderBottom }, | |
{ "border-bottom-color", BorderBottomColor }, | |
{ "border-bottom-left-radius", BorderBottomLeftRadius }, | |
{ "border-bottom-right-radius", BorderBottomRightRadius }, | |
{ "border-bottom-style", BorderBottomStyle }, | |
{ "border-bottom-width", BorderBottomWidth }, | |
{ "border-color", BorderColor }, | |
{ "border-image", BorderImage }, | |
{ "border-left", BorderLeft }, | |
{ "border-left-color", BorderLeftColor }, | |
{ "border-left-style", BorderLeftStyle }, | |
{ "border-left-width", BorderLeftWidth }, | |
{ "border-radius", BorderRadius }, | |
{ "border-right", BorderRight }, | |
{ "border-right-color", BorderRightColor }, | |
{ "border-right-style", BorderRightStyle }, | |
{ "border-right-width", BorderRightWidth }, | |
{ "border-style", BorderStyles }, | |
{ "border-top", BorderTop }, | |
{ "border-top-color", BorderTopColor }, | |
{ "border-top-left-radius", BorderTopLeftRadius }, | |
{ "border-top-right-radius", BorderTopRightRadius }, | |
{ "border-top-style", BorderTopStyle }, | |
{ "border-top-width", BorderTopWidth }, | |
{ "border-width", BorderWidth }, | |
{ "bottom", Bottom }, | |
{ "color", Color }, | |
{ "float", Float }, | |
{ "font", Font }, | |
{ "font-family", FontFamily }, | |
{ "font-size", FontSize }, | |
{ "font-style", FontStyle }, | |
{ "font-variant", FontVariant }, | |
{ "font-weight", FontWeight }, | |
{ "height", Height }, | |
{ "image", QtImage }, | |
{ "image-position", QtImageAlignment }, | |
{ "left", Left }, | |
{ "list-style", ListStyle }, | |
{ "list-style-type", ListStyleType }, | |
{ "margin" , Margin }, | |
{ "margin-bottom", MarginBottom }, | |
{ "margin-left", MarginLeft }, | |
{ "margin-right", MarginRight }, | |
{ "margin-top", MarginTop }, | |
{ "max-height", MaximumHeight }, | |
{ "max-width", MaximumWidth }, | |
{ "min-height", MinimumHeight }, | |
{ "min-width", MinimumWidth }, | |
{ "outline", Outline }, | |
{ "outline-bottom-left-radius", OutlineBottomLeftRadius }, | |
{ "outline-bottom-right-radius", OutlineBottomRightRadius }, | |
{ "outline-color", OutlineColor }, | |
{ "outline-offset", OutlineOffset }, | |
{ "outline-radius", OutlineRadius }, | |
{ "outline-style", OutlineStyle }, | |
{ "outline-top-left-radius", OutlineTopLeftRadius }, | |
{ "outline-top-right-radius", OutlineTopRightRadius }, | |
{ "outline-width", OutlineWidth }, | |
{ "padding", Padding }, | |
{ "padding-bottom", PaddingBottom }, | |
{ "padding-left", PaddingLeft }, | |
{ "padding-right", PaddingRight }, | |
{ "padding-top", PaddingTop }, | |
{ "page-break-after", PageBreakAfter }, | |
{ "page-break-before", PageBreakBefore }, | |
{ "position", Position }, | |
{ "right", Right }, | |
{ "selection-background-color", QtSelectionBackground }, | |
{ "selection-color", QtSelectionForeground }, | |
{ "spacing", QtSpacing }, | |
{ "subcontrol-origin", QtOrigin }, | |
{ "subcontrol-position", QtPosition }, | |
{ "text-align", TextAlignment }, | |
{ "text-decoration", TextDecoration }, | |
{ "text-indent", TextIndent }, | |
{ "text-transform", TextTransform }, | |
{ "text-underline-style", TextUnderlineStyle }, | |
{ "top", Top }, | |
{ "vertical-align", VerticalAlignment }, | |
{ "white-space", Whitespace }, | |
{ "width", Width } | |
}; | |
static const QCssKnownValue values[NumKnownValues - 1] = { | |
{ "active", Value_Active }, | |
{ "alternate-base", Value_AlternateBase }, | |
{ "always", Value_Always }, | |
{ "auto", Value_Auto }, | |
{ "base", Value_Base }, | |
{ "bold", Value_Bold }, | |
{ "bottom", Value_Bottom }, | |
{ "bright-text", Value_BrightText }, | |
{ "button", Value_Button }, | |
{ "button-text", Value_ButtonText }, | |
{ "center", Value_Center }, | |
{ "circle", Value_Circle }, | |
{ "dark", Value_Dark }, | |
{ "dashed", Value_Dashed }, | |
{ "decimal", Value_Decimal }, | |
{ "disabled", Value_Disabled }, | |
{ "disc", Value_Disc }, | |
{ "dot-dash", Value_DotDash }, | |
{ "dot-dot-dash", Value_DotDotDash }, | |
{ "dotted", Value_Dotted }, | |
{ "double", Value_Double }, | |
{ "groove", Value_Groove }, | |
{ "highlight", Value_Highlight }, | |
{ "highlighted-text", Value_HighlightedText }, | |
{ "inset", Value_Inset }, | |
{ "italic", Value_Italic }, | |
{ "large", Value_Large }, | |
{ "left", Value_Left }, | |
{ "light", Value_Light }, | |
{ "line-through", Value_LineThrough }, | |
{ "link", Value_Link }, | |
{ "link-visited", Value_LinkVisited }, | |
{ "lower-alpha", Value_LowerAlpha }, | |
{ "lower-roman", Value_LowerRoman }, | |
{ "lowercase", Value_Lowercase }, | |
{ "medium", Value_Medium }, | |
{ "mid", Value_Mid }, | |
{ "middle", Value_Middle }, | |
{ "midlight", Value_Midlight }, | |
{ "native", Value_Native }, | |
{ "none", Value_None }, | |
{ "normal", Value_Normal }, | |
{ "nowrap", Value_NoWrap }, | |
{ "oblique", Value_Oblique }, | |
{ "off", Value_Off }, | |
{ "on", Value_On }, | |
{ "outset", Value_Outset }, | |
{ "overline", Value_Overline }, | |
{ "pre", Value_Pre }, | |
{ "pre-wrap", Value_PreWrap }, | |
{ "ridge", Value_Ridge }, | |
{ "right", Value_Right }, | |
{ "selected", Value_Selected }, | |
{ "shadow", Value_Shadow }, | |
{ "small" , Value_Small }, | |
{ "small-caps", Value_SmallCaps }, | |
{ "solid", Value_Solid }, | |
{ "square", Value_Square }, | |
{ "sub", Value_Sub }, | |
{ "super", Value_Super }, | |
{ "text", Value_Text }, | |
{ "top", Value_Top }, | |
{ "transparent", Value_Transparent }, | |
{ "underline", Value_Underline }, | |
{ "upper-alpha", Value_UpperAlpha }, | |
{ "upper-roman", Value_UpperRoman }, | |
{ "uppercase", Value_Uppercase }, | |
{ "wave", Value_Wave }, | |
{ "window", Value_Window }, | |
{ "window-text", Value_WindowText }, | |
{ "x-large", Value_XLarge }, | |
{ "xx-large", Value_XXLarge } | |
}; | |
//Map id to strings as they appears in the 'values' array above | |
static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 54, 35, 26, 70, 71, 25, 43, 5, 63, 47, | |
29, 58, 59, 27, 51, 61, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 50, 24, 46, 67, 37, 3, 2, 40, 62, 16, | |
11, 57, 14, 32, 64, 33, 65, 55, 66, 34, 69, 8, 28, 38, 12, 36, 60, 7, 9, 4, 68, 53, 22, 23, 30, 31, | |
1, 15, 0, 52, 45, 44 }; | |
QString Value::toString() const | |
{ | |
if (type == KnownIdentifier) { | |
return QLatin1String(values[indexOfId[variant.toInt()]].name); | |
} else { | |
return variant.toString(); | |
} | |
} | |
static const QCssKnownValue pseudos[NumPseudos - 1] = { | |
{ "active", PseudoClass_Active }, | |
{ "adjoins-item", PseudoClass_Item }, | |
{ "alternate", PseudoClass_Alternate }, | |
{ "bottom", PseudoClass_Bottom }, | |
{ "checked", PseudoClass_Checked }, | |
{ "closable", PseudoClass_Closable }, | |
{ "closed", PseudoClass_Closed }, | |
{ "default", PseudoClass_Default }, | |
{ "disabled", PseudoClass_Disabled }, | |
{ "edit-focus", PseudoClass_EditFocus }, | |
{ "editable", PseudoClass_Editable }, | |
{ "enabled", PseudoClass_Enabled }, | |
{ "exclusive", PseudoClass_Exclusive }, | |
{ "first", PseudoClass_First }, | |
{ "flat", PseudoClass_Flat }, | |
{ "floatable", PseudoClass_Floatable }, | |
{ "focus", PseudoClass_Focus }, | |
{ "has-children", PseudoClass_Children }, | |
{ "has-siblings", PseudoClass_Sibling }, | |
{ "horizontal", PseudoClass_Horizontal }, | |
{ "hover", PseudoClass_Hover }, | |
{ "indeterminate" , PseudoClass_Indeterminate }, | |
{ "last", PseudoClass_Last }, | |
{ "left", PseudoClass_Left }, | |
{ "maximized", PseudoClass_Maximized }, | |
{ "middle", PseudoClass_Middle }, | |
{ "minimized", PseudoClass_Minimized }, | |
{ "movable", PseudoClass_Movable }, | |
{ "next-selected", PseudoClass_NextSelected }, | |
{ "no-frame", PseudoClass_Frameless }, | |
{ "non-exclusive", PseudoClass_NonExclusive }, | |
{ "off", PseudoClass_Unchecked }, | |
{ "on", PseudoClass_Checked }, | |
{ "only-one", PseudoClass_OnlyOne }, | |
{ "open", PseudoClass_Open }, | |
{ "pressed", PseudoClass_Pressed }, | |
{ "previous-selected", PseudoClass_PreviousSelected }, | |
{ "read-only", PseudoClass_ReadOnly }, | |
{ "right", PseudoClass_Right }, | |
{ "selected", PseudoClass_Selected }, | |
{ "top", PseudoClass_Top }, | |
{ "unchecked" , PseudoClass_Unchecked }, | |
{ "vertical", PseudoClass_Vertical }, | |
{ "window", PseudoClass_Window } | |
}; | |
static const QCssKnownValue origins[NumKnownOrigins - 1] = { | |
{ "border", Origin_Border }, | |
{ "content", Origin_Content }, | |
{ "margin", Origin_Margin }, // not in css | |
{ "padding", Origin_Padding } | |
}; | |
static const QCssKnownValue repeats[NumKnownRepeats - 1] = { | |
{ "no-repeat", Repeat_None }, | |
{ "repeat-x", Repeat_X }, | |
{ "repeat-xy", Repeat_XY }, | |
{ "repeat-y", Repeat_Y } | |
}; | |
static const QCssKnownValue tileModes[NumKnownTileModes - 1] = { | |
{ "repeat", TileMode_Repeat }, | |
{ "round", TileMode_Round }, | |
{ "stretch", TileMode_Stretch }, | |
}; | |
static const QCssKnownValue positions[NumKnownPositionModes - 1] = { | |
{ "absolute", PositionMode_Absolute }, | |
{ "fixed", PositionMode_Fixed }, | |
{ "relative", PositionMode_Relative }, | |
{ "static", PositionMode_Static } | |
}; | |
static const QCssKnownValue attachments[NumKnownAttachments - 1] = { | |
{ "fixed", Attachment_Fixed }, | |
{ "scroll", Attachment_Scroll } | |
}; | |
static const QCssKnownValue styleFeatures[NumKnownStyleFeatures - 1] = { | |
{ "background-color", StyleFeature_BackgroundColor }, | |
{ "background-gradient", StyleFeature_BackgroundGradient }, | |
{ "none", StyleFeature_None } | |
}; | |
Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &name, const QCssKnownValue &prop) | |
{ | |
return QString::compare(name, QLatin1String(prop.name), Qt::CaseInsensitive) < 0; | |
} | |
Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCssKnownValue &prop, const QString &name) | |
{ | |
return QString::compare(QLatin1String(prop.name), name, Qt::CaseInsensitive) < 0; | |
} | |
static quint64 findKnownValue(const QString &name, const QCssKnownValue *start, int numValues) | |
{ | |
const QCssKnownValue *end = &start[numValues - 1]; | |
const QCssKnownValue *prop = qBinaryFind(start, end, name); | |
if (prop == end) | |
return 0; | |
return prop->id; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// Value Extractor | |
ValueExtractor::ValueExtractor(const QVector<Declaration> &decls, const QPalette &pal) | |
: declarations(decls), adjustment(0), fontExtracted(false), pal(pal) | |
{ | |
} | |
LengthData ValueExtractor::lengthValue(const Value& v) | |
{ | |
QString s = v.variant.toString(); | |
s.reserve(s.length()); | |
LengthData data; | |
data.unit = LengthData::None; | |
if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive)) | |
data.unit = LengthData::Px; | |
else if (s.endsWith(QLatin1String("ex"), Qt::CaseInsensitive)) | |
data.unit = LengthData::Ex; | |
else if (s.endsWith(QLatin1String("em"), Qt::CaseInsensitive)) | |
data.unit = LengthData::Em; | |
if (data.unit != LengthData::None) | |
s.chop(2); | |
data.number = s.toDouble(); | |
return data; | |
} | |
static int lengthValueFromData(const LengthData& data, const QFont& f) | |
{ | |
if (data.unit == LengthData::Ex) | |
return qRound(QFontMetrics(f).xHeight() * data.number); | |
else if (data.unit == LengthData::Em) | |
return qRound(QFontMetrics(f).height() * data.number); | |
return qRound(data.number); | |
} | |
int ValueExtractor::lengthValue(const Declaration &decl) | |
{ | |
if (decl.d->parsed.isValid()) | |
return lengthValueFromData(qvariant_cast<LengthData>(decl.d->parsed), f); | |
if (decl.d->values.count() < 1) | |
return 0; | |
LengthData data = lengthValue(decl.d->values.at(0)); | |
decl.d->parsed = qVariantFromValue<LengthData>(data); | |
return lengthValueFromData(data,f); | |
} | |
void ValueExtractor::lengthValues(const Declaration &decl, int *m) | |
{ | |
if (decl.d->parsed.isValid()) { | |
QList<QVariant> v = decl.d->parsed.toList(); | |
for (int i = 0; i < 4; i++) | |
m[i] = lengthValueFromData(qvariant_cast<LengthData>(v.at(i)), f); | |
return; | |
} | |
LengthData datas[4]; | |
int i; | |
for (i = 0; i < qMin(decl.d->values.count(), 4); i++) | |
datas[i] = lengthValue(decl.d->values[i]); | |
if (i == 0) { | |
LengthData zero = {0.0, LengthData::None}; | |
datas[0] = datas[1] = datas[2] = datas[3] = zero; | |
} else if (i == 1) { | |
datas[3] = datas[2] = datas[1] = datas[0]; | |
} else if (i == 2) { | |
datas[2] = datas[0]; | |
datas[3] = datas[1]; | |
} else if (i == 3) { | |
datas[3] = datas[1]; | |
} | |
QList<QVariant> v; | |
for (i = 0; i < 4; i++) { | |
v += qVariantFromValue<LengthData>(datas[i]); | |
m[i] = lengthValueFromData(datas[i], f); | |
} | |
decl.d->parsed = v; | |
} | |
bool ValueExtractor::extractGeometry(int *w, int *h, int *minw, int *minh, int *maxw, int *maxh) | |
{ | |
extractFont(); | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); i++) { | |
const Declaration &decl = declarations.at(i); | |
switch (decl.d->propertyId) { | |
case Width: *w = lengthValue(decl); break; | |
case Height: *h = lengthValue(decl); break; | |
case MinimumWidth: *minw = lengthValue(decl); break; | |
case MinimumHeight: *minh = lengthValue(decl); break; | |
case MaximumWidth: *maxw = lengthValue(decl); break; | |
case MaximumHeight: *maxh = lengthValue(decl); break; | |
default: continue; | |
} | |
hit = true; | |
} | |
return hit; | |
} | |
bool ValueExtractor::extractPosition(int *left, int *top, int *right, int *bottom, QCss::Origin *origin, | |
Qt::Alignment *position, QCss::PositionMode *mode, Qt::Alignment *textAlignment) | |
{ | |
extractFont(); | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); i++) { | |
const Declaration &decl = declarations.at(i); | |
switch (decl.d->propertyId) { | |
case Left: *left = lengthValue(decl); break; | |
case Top: *top = lengthValue(decl); break; | |
case Right: *right = lengthValue(decl); break; | |
case Bottom: *bottom = lengthValue(decl); break; | |
case QtOrigin: *origin = decl.originValue(); break; | |
case QtPosition: *position = decl.alignmentValue(); break; | |
case TextAlignment: *textAlignment = decl.alignmentValue(); break; | |
case Position: *mode = decl.positionValue(); break; | |
default: continue; | |
} | |
hit = true; | |
} | |
return hit; | |
} | |
bool ValueExtractor::extractBox(int *margins, int *paddings, int *spacing) | |
{ | |
extractFont(); | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); i++) { | |
const Declaration &decl = declarations.at(i); | |
switch (decl.d->propertyId) { | |
case PaddingLeft: paddings[LeftEdge] = lengthValue(decl); break; | |
case PaddingRight: paddings[RightEdge] = lengthValue(decl); break; | |
case PaddingTop: paddings[TopEdge] = lengthValue(decl); break; | |
case PaddingBottom: paddings[BottomEdge] = lengthValue(decl); break; | |
case Padding: lengthValues(decl, paddings); break; | |
case MarginLeft: margins[LeftEdge] = lengthValue(decl); break; | |
case MarginRight: margins[RightEdge] = lengthValue(decl); break; | |
case MarginTop: margins[TopEdge] = lengthValue(decl); break; | |
case MarginBottom: margins[BottomEdge] = lengthValue(decl); break; | |
case Margin: lengthValues(decl, margins); break; | |
case QtSpacing: if (spacing) *spacing = lengthValue(decl); break; | |
default: continue; | |
} | |
hit = true; | |
} | |
return hit; | |
} | |
int ValueExtractor::extractStyleFeatures() | |
{ | |
int features = StyleFeature_None; | |
for (int i = 0; i < declarations.count(); i++) { | |
const Declaration &decl = declarations.at(i); | |
if (decl.d->propertyId == QtStyleFeatures) | |
features = decl.styleFeaturesValue(); | |
} | |
return features; | |
} | |
QSize ValueExtractor::sizeValue(const Declaration &decl) | |
{ | |
if (decl.d->parsed.isValid()) { | |
QList<QVariant> v = decl.d->parsed.toList(); | |
return QSize(lengthValueFromData(qvariant_cast<LengthData>(v.at(0)), f), | |
lengthValueFromData(qvariant_cast<LengthData>(v.at(1)), f)); | |
} | |
LengthData x[2] = { {0, LengthData::None }, {0, LengthData::None} }; | |
if (decl.d->values.count() > 0) | |
x[0] = lengthValue(decl.d->values.at(0)); | |
if (decl.d->values.count() > 1) | |
x[1] = lengthValue(decl.d->values.at(1)); | |
else | |
x[1] = x[0]; | |
QList<QVariant> v; | |
v << qVariantFromValue<LengthData>(x[0]) << qVariantFromValue<LengthData>(x[1]); | |
decl.d->parsed = v; | |
return QSize(lengthValueFromData(x[0], f), lengthValueFromData(x[1], f)); | |
} | |
void ValueExtractor::sizeValues(const Declaration &decl, QSize *radii) | |
{ | |
radii[0] = sizeValue(decl); | |
for (int i = 1; i < 4; i++) | |
radii[i] = radii[0]; | |
} | |
bool ValueExtractor::extractBorder(int *borders, QBrush *colors, BorderStyle *styles, | |
QSize *radii) | |
{ | |
extractFont(); | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); i++) { | |
const Declaration &decl = declarations.at(i); | |
switch (decl.d->propertyId) { | |
case BorderLeftWidth: borders[LeftEdge] = lengthValue(decl); break; | |
case BorderRightWidth: borders[RightEdge] = lengthValue(decl); break; | |
case BorderTopWidth: borders[TopEdge] = lengthValue(decl); break; | |
case BorderBottomWidth: borders[BottomEdge] = lengthValue(decl); break; | |
case BorderWidth: lengthValues(decl, borders); break; | |
case BorderLeftColor: colors[LeftEdge] = decl.brushValue(pal); break; | |
case BorderRightColor: colors[RightEdge] = decl.brushValue(pal); break; | |
case BorderTopColor: colors[TopEdge] = decl.brushValue(pal); break; | |
case BorderBottomColor: colors[BottomEdge] = decl.brushValue(pal); break; | |
case BorderColor: decl.brushValues(colors, pal); break; | |
case BorderTopStyle: styles[TopEdge] = decl.styleValue(); break; | |
case BorderBottomStyle: styles[BottomEdge] = decl.styleValue(); break; | |
case BorderLeftStyle: styles[LeftEdge] = decl.styleValue(); break; | |
case BorderRightStyle: styles[RightEdge] = decl.styleValue(); break; | |
case BorderStyles: decl.styleValues(styles); break; | |
case BorderTopLeftRadius: radii[0] = sizeValue(decl); break; | |
case BorderTopRightRadius: radii[1] = sizeValue(decl); break; | |
case BorderBottomLeftRadius: radii[2] = sizeValue(decl); break; | |
case BorderBottomRightRadius: radii[3] = sizeValue(decl); break; | |
case BorderRadius: sizeValues(decl, radii); break; | |
case BorderLeft: | |
borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); | |
break; | |
case BorderTop: | |
borderValue(decl, &borders[TopEdge], &styles[TopEdge], &colors[TopEdge]); | |
break; | |
case BorderRight: | |
borderValue(decl, &borders[RightEdge], &styles[RightEdge], &colors[RightEdge]); | |
break; | |
case BorderBottom: | |
borderValue(decl, &borders[BottomEdge], &styles[BottomEdge], &colors[BottomEdge]); | |
break; | |
case Border: | |
borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); | |
borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge]; | |
styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge]; | |
colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge]; | |
break; | |
default: continue; | |
} | |
hit = true; | |
} | |
return hit; | |
} | |
bool ValueExtractor::extractOutline(int *borders, QBrush *colors, BorderStyle *styles, | |
QSize *radii, int *offsets) | |
{ | |
extractFont(); | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); i++) { | |
const Declaration &decl = declarations.at(i); | |
switch (decl.d->propertyId) { | |
case OutlineWidth: lengthValues(decl, borders); break; | |
case OutlineColor: decl.brushValues(colors, pal); break; | |
case OutlineStyle: decl.styleValues(styles); break; | |
case OutlineTopLeftRadius: radii[0] = sizeValue(decl); break; | |
case OutlineTopRightRadius: radii[1] = sizeValue(decl); break; | |
case OutlineBottomLeftRadius: radii[2] = sizeValue(decl); break; | |
case OutlineBottomRightRadius: radii[3] = sizeValue(decl); break; | |
case OutlineRadius: sizeValues(decl, radii); break; | |
case OutlineOffset: lengthValues(decl, offsets); break; | |
case Outline: | |
borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); | |
borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge]; | |
styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge]; | |
colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge]; | |
break; | |
default: continue; | |
} | |
hit = true; | |
} | |
return hit; | |
} | |
static Qt::Alignment parseAlignment(const Value *values, int count) | |
{ | |
Qt::Alignment a[2] = { 0, 0 }; | |
for (int i = 0; i < qMin(2, count); i++) { | |
if (values[i].type != Value::KnownIdentifier) | |
break; | |
switch (values[i].variant.toInt()) { | |
case Value_Left: a[i] = Qt::AlignLeft; break; | |
case Value_Right: a[i] = Qt::AlignRight; break; | |
case Value_Top: a[i] = Qt::AlignTop; break; | |
case Value_Bottom: a[i] = Qt::AlignBottom; break; | |
case Value_Center: a[i] = Qt::AlignCenter; break; | |
default: break; | |
} | |
} | |
if (a[0] == Qt::AlignCenter && a[1] != 0 && a[1] != Qt::AlignCenter) | |
a[0] = (a[1] == Qt::AlignLeft || a[1] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter; | |
if ((a[1] == 0 || a[1] == Qt::AlignCenter) && a[0] != Qt::AlignCenter) | |
a[1] = (a[0] == Qt::AlignLeft || a[0] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter; | |
return a[0] | a[1]; | |
} | |
static ColorData parseColorValue(Value v) | |
{ | |
if (v.type == Value::Identifier || v.type == Value::String) { | |
v.variant.convert(QVariant::Color); | |
v.type = Value::Color; | |
} | |
if (v.type == Value::Color) | |
return qvariant_cast<QColor>(v.variant); | |
if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent) | |
return QColor(Qt::transparent); | |
if (v.type != Value::Function) | |
return ColorData(); | |
QStringList lst = v.variant.toStringList(); | |
if (lst.count() != 2) | |
return ColorData(); | |
if ((lst.at(0).compare(QLatin1String("palette"), Qt::CaseInsensitive)) == 0) { | |
int role = findKnownValue(lst.at(1).trimmed(), values, NumKnownValues); | |
if (role >= Value_FirstColorRole && role <= Value_LastColorRole) | |
return (QPalette::ColorRole)(role-Value_FirstColorRole); | |
return ColorData(); | |
} | |
bool rgb = lst.at(0).startsWith(QLatin1String("rgb")); | |
Parser p(lst.at(1)); | |
if (!p.testExpr()) | |
return ColorData(); | |
QVector<Value> colorDigits; | |
if (!p.parseExpr(&colorDigits)) | |
return ColorData(); | |
for (int i = 0; i < qMin(colorDigits.count(), 7); i += 2) { | |
if (colorDigits.at(i).type == Value::Percentage) { | |
colorDigits[i].variant = colorDigits.at(i).variant.toReal() * (255. / 100.); | |
colorDigits[i].type = Value::Number; | |
} else if (colorDigits.at(i).type != Value::Number) { | |
return ColorData(); | |
} | |
} | |
int v1 = colorDigits.at(0).variant.toInt(); | |
int v2 = colorDigits.at(2).variant.toInt(); | |
int v3 = colorDigits.at(4).variant.toInt(); | |
int alpha = colorDigits.count() >= 7 ? colorDigits.at(6).variant.toInt() : 255; | |
return rgb ? QColor::fromRgb(v1, v2, v3, alpha) | |
: QColor::fromHsv(v1, v2, v3, alpha); | |
} | |
static QColor colorFromData(const ColorData& c, const QPalette &pal) | |
{ | |
if (c.type == ColorData::Color) { | |
return c.color; | |
} else if (c.type == ColorData::Role) { | |
return pal.color(c.role); | |
} | |
return QColor(); | |
} | |
static BrushData parseBrushValue(const Value &v, const QPalette &pal) | |
{ | |
ColorData c = parseColorValue(v); | |
if (c.type == ColorData::Color) { | |
return QBrush(c.color); | |
} else if (c.type == ColorData::Role) { | |
return c.role; | |
} | |
if (v.type != Value::Function) | |
return BrushData(); | |
QStringList lst = v.variant.toStringList(); | |
if (lst.count() != 2) | |
return BrushData(); | |
QStringList gradFuncs; | |
gradFuncs << QLatin1String("qlineargradient") << QLatin1String("qradialgradient") << QLatin1String("qconicalgradient") << QLatin1String("qgradient"); | |
int gradType = -1; | |
if ((gradType = gradFuncs.indexOf(lst.at(0).toLower())) == -1) | |
return BrushData(); | |
QHash<QString, qreal> vars; | |
QVector<QGradientStop> stops; | |
int spread = -1; | |
QStringList spreads; | |
spreads << QLatin1String("pad") << QLatin1String("reflect") << QLatin1String("repeat"); | |
bool dependsOnThePalette = false; | |
Parser parser(lst.at(1)); | |
while (parser.hasNext()) { | |
parser.skipSpace(); | |
if (!parser.test(IDENT)) | |
return BrushData(); | |
QString attr = parser.lexem(); | |
parser.skipSpace(); | |
if (!parser.test(COLON)) | |
return BrushData(); | |
parser.skipSpace(); | |
if (attr.compare(QLatin1String("stop"), Qt::CaseInsensitive) == 0) { | |
Value stop, color; | |
parser.next(); | |
if (!parser.parseTerm(&stop)) return BrushData(); | |
parser.skipSpace(); | |
parser.next(); | |
if (!parser.parseTerm(&color)) return BrushData(); | |
ColorData cd = parseColorValue(color); | |
if(cd.type == ColorData::Role) | |
dependsOnThePalette = true; | |
stops.append(QGradientStop(stop.variant.toReal(), colorFromData(cd, pal))); | |
} else { | |
parser.next(); | |
Value value; | |
(void)parser.parseTerm(&value); | |
if (attr.compare(QLatin1String("spread"), Qt::CaseInsensitive) == 0) { | |
spread = spreads.indexOf(value.variant.toString()); | |
} else { | |
vars[attr] = value.variant.toReal(); | |
} | |
} | |
parser.skipSpace(); | |
(void)parser.test(COMMA); | |
} | |
if (gradType == 0) { | |
QLinearGradient lg(vars.value(QLatin1String("x1")), vars.value(QLatin1String("y1")), | |
vars.value(QLatin1String("x2")), vars.value(QLatin1String("y2"))); | |
lg.setCoordinateMode(QGradient::ObjectBoundingMode); | |
lg.setStops(stops); | |
if (spread != -1) | |
lg.setSpread(QGradient::Spread(spread)); | |
BrushData bd = QBrush(lg); | |
if (dependsOnThePalette) | |
bd.type = BrushData::DependsOnThePalette; | |
return bd; | |
} | |
if (gradType == 1) { | |
QRadialGradient rg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")), | |
vars.value(QLatin1String("radius")), vars.value(QLatin1String("fx")), | |
vars.value(QLatin1String("fy"))); | |
rg.setCoordinateMode(QGradient::ObjectBoundingMode); | |
rg.setStops(stops); | |
if (spread != -1) | |
rg.setSpread(QGradient::Spread(spread)); | |
BrushData bd = QBrush(rg); | |
if (dependsOnThePalette) | |
bd.type = BrushData::DependsOnThePalette; | |
return bd; | |
} | |
if (gradType == 2) { | |
QConicalGradient cg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")), | |
vars.value(QLatin1String("angle"))); | |
cg.setCoordinateMode(QGradient::ObjectBoundingMode); | |
cg.setStops(stops); | |
if (spread != -1) | |
cg.setSpread(QGradient::Spread(spread)); | |
BrushData bd = QBrush(cg); | |
if (dependsOnThePalette) | |
bd.type = BrushData::DependsOnThePalette; | |
return bd; | |
} | |
return BrushData(); | |
} | |
static QBrush brushFromData(const BrushData& c, const QPalette &pal) | |
{ | |
if (c.type == BrushData::Role) { | |
return pal.color(c.role); | |
} else { | |
return c.brush; | |
} | |
} | |
static BorderStyle parseStyleValue(Value v) | |
{ | |
if (v.type == Value::KnownIdentifier) { | |
switch (v.variant.toInt()) { | |
case Value_None: | |
return BorderStyle_None; | |
case Value_Dotted: | |
return BorderStyle_Dotted; | |
case Value_Dashed: | |
return BorderStyle_Dashed; | |
case Value_Solid: | |
return BorderStyle_Solid; | |
case Value_Double: | |
return BorderStyle_Double; | |
case Value_DotDash: | |
return BorderStyle_DotDash; | |
case Value_DotDotDash: | |
return BorderStyle_DotDotDash; | |
case Value_Groove: | |
return BorderStyle_Groove; | |
case Value_Ridge: | |
return BorderStyle_Ridge; | |
case Value_Inset: | |
return BorderStyle_Inset; | |
case Value_Outset: | |
return BorderStyle_Outset; | |
case Value_Native: | |
return BorderStyle_Native; | |
default: | |
break; | |
} | |
} | |
return BorderStyle_Unknown; | |
} | |
void ValueExtractor::borderValue(const Declaration &decl, int *width, QCss::BorderStyle *style, QBrush *color) | |
{ | |
if (decl.d->parsed.isValid()) { | |
BorderData data = qvariant_cast<BorderData>(decl.d->parsed); | |
*width = lengthValueFromData(data.width, f); | |
*style = data.style; | |
*color = data.color.type != BrushData::Invalid ? brushFromData(data.color, pal) : QBrush(QColor()); | |
return; | |
} | |
*width = 0; | |
*style = BorderStyle_None; | |
*color = QColor(); | |
if (decl.d->values.isEmpty()) | |
return; | |
BorderData data; | |
data.width.number = 0; | |
data.width.unit = LengthData::None; | |
data.style = BorderStyle_None; | |
int i = 0; | |
if (decl.d->values.at(i).type == Value::Length || decl.d->values.at(i).type == Value::Number) { | |
data.width = lengthValue(decl.d->values.at(i)); | |
*width = lengthValueFromData(data.width, f); | |
if (++i >= decl.d->values.count()) { | |
decl.d->parsed = qVariantFromValue<BorderData>(data); | |
return; | |
} | |
} | |
data.style = parseStyleValue(decl.d->values.at(i)); | |
if (data.style != BorderStyle_Unknown) { | |
*style = data.style; | |
if (++i >= decl.d->values.count()) { | |
decl.d->parsed = qVariantFromValue<BorderData>(data); | |
return; | |
} | |
} else { | |
data.style = BorderStyle_None; | |
} | |
data.color = parseBrushValue(decl.d->values.at(i), pal); | |
*color = brushFromData(data.color, pal); | |
if (data.color.type != BrushData::DependsOnThePalette) | |
decl.d->parsed = qVariantFromValue<BorderData>(data); | |
} | |
static void parseShorthandBackgroundProperty(const QVector<Value> &values, BrushData *brush, QString *image, Repeat *repeat, Qt::Alignment *alignment, const QPalette &pal) | |
{ | |
*brush = BrushData(); | |
*image = QString(); | |
*repeat = Repeat_XY; | |
*alignment = Qt::AlignTop | Qt::AlignLeft; | |
for (int i = 0; i < values.count(); ++i) { | |
const Value &v = values.at(i); | |
if (v.type == Value::Uri) { | |
*image = v.variant.toString(); | |
continue; | |
} else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_None) { | |
*image = QString(); | |
continue; | |
} else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent) { | |
*brush = QBrush(Qt::transparent); | |
} | |
Repeat repeatAttempt = static_cast<Repeat>(findKnownValue(v.variant.toString(), | |
repeats, NumKnownRepeats)); | |
if (repeatAttempt != Repeat_Unknown) { | |
*repeat = repeatAttempt; | |
continue; | |
} | |
if (v.type == Value::KnownIdentifier) { | |
const int start = i; | |
int count = 1; | |
if (i < values.count() - 1 | |
&& values.at(i + 1).type == Value::KnownIdentifier) { | |
++i; | |
++count; | |
} | |
Qt::Alignment a = parseAlignment(values.constData() + start, count); | |
if (int(a) != 0) { | |
*alignment = a; | |
continue; | |
} | |
i -= count - 1; | |
} | |
*brush = parseBrushValue(v, pal); | |
} | |
} | |
bool ValueExtractor::extractBackground(QBrush *brush, QString *image, Repeat *repeat, | |
Qt::Alignment *alignment, Origin *origin, Attachment *attachment, | |
Origin *clip) | |
{ | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); ++i) { | |
const Declaration &decl = declarations.at(i); | |
if (decl.d->values.isEmpty()) | |
continue; | |
const Value &val = decl.d->values.at(0); | |
switch (decl.d->propertyId) { | |
case BackgroundColor: | |
*brush = decl.brushValue(); | |
break; | |
case BackgroundImage: | |
if (val.type == Value::Uri) | |
*image = val.variant.toString(); | |
break; | |
case BackgroundRepeat: | |
if (decl.d->parsed.isValid()) { | |
*repeat = static_cast<Repeat>(decl.d->parsed.toInt()); | |
} else { | |
*repeat = static_cast<Repeat>(findKnownValue(val.variant.toString(), | |
repeats, NumKnownRepeats)); | |
decl.d->parsed = *repeat; | |
} | |
break; | |
case BackgroundPosition: | |
*alignment = decl.alignmentValue(); | |
break; | |
case BackgroundOrigin: | |
*origin = decl.originValue(); | |
break; | |
case BackgroundClip: | |
*clip = decl.originValue(); | |
break; | |
case Background: | |
if (decl.d->parsed.isValid()) { | |
BackgroundData data = qvariant_cast<BackgroundData>(decl.d->parsed); | |
*brush = brushFromData(data.brush, pal); | |
*image = data.image; | |
*repeat = data.repeat; | |
*alignment = data.alignment; | |
} else { | |
BrushData brushData; | |
parseShorthandBackgroundProperty(decl.d->values, &brushData, image, repeat, alignment, pal); | |
*brush = brushFromData(brushData, pal); | |
if (brushData.type != BrushData::DependsOnThePalette) { | |
#if defined Q_CC_MSVC && _MSC_VER <= 1300 | |
BackgroundData data; | |
data.brush = brushData; | |
data.image = *image; | |
data.repeat = *repeat; | |
data.alignment = *alignment; | |
#else | |
BackgroundData data = { brushData, *image, *repeat, *alignment }; | |
#endif | |
decl.d->parsed = qVariantFromValue<BackgroundData>(data); | |
} | |
} | |
break; | |
case BackgroundAttachment: | |
*attachment = decl.attachmentValue(); | |
break; | |
default: continue; | |
} | |
hit = true; | |
} | |
return hit; | |
} | |
static bool setFontSizeFromValue(Value value, QFont *font, int *fontSizeAdjustment) | |
{ | |
if (value.type == Value::KnownIdentifier) { | |
bool valid = true; | |
switch (value.variant.toInt()) { | |
case Value_Small: *fontSizeAdjustment = -1; break; | |
case Value_Medium: *fontSizeAdjustment = 0; break; | |
case Value_Large: *fontSizeAdjustment = 1; break; | |
case Value_XLarge: *fontSizeAdjustment = 2; break; | |
case Value_XXLarge: *fontSizeAdjustment = 3; break; | |
default: valid = false; break; | |
} | |
return valid; | |
} | |
if (value.type != Value::Length) | |
return false; | |
bool valid = false; | |
QString s = value.variant.toString(); | |
if (s.endsWith(QLatin1String("pt"), Qt::CaseInsensitive)) { | |
s.chop(2); | |
value.variant = s; | |
if (value.variant.convert((QVariant::Type)qMetaTypeId<qreal>())) { | |
font->setPointSizeF(value.variant.toReal()); | |
valid = true; | |
} | |
} else if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive)) { | |
s.chop(2); | |
value.variant = s; | |
if (value.variant.convert(QVariant::Int)) { | |
font->setPixelSize(value.variant.toInt()); | |
valid = true; | |
} | |
} | |
return valid; | |
} | |
static bool setFontStyleFromValue(const Value &value, QFont *font) | |
{ | |
if (value.type != Value::KnownIdentifier) | |
return false ; | |
switch (value.variant.toInt()) { | |
case Value_Normal: font->setStyle(QFont::StyleNormal); return true; | |
case Value_Italic: font->setStyle(QFont::StyleItalic); return true; | |
case Value_Oblique: font->setStyle(QFont::StyleOblique); return true; | |
default: break; | |
} | |
return false; | |
} | |
static bool setFontWeightFromValue(const Value &value, QFont *font) | |
{ | |
if (value.type == Value::KnownIdentifier) { | |
switch (value.variant.toInt()) { | |
case Value_Normal: font->setWeight(QFont::Normal); return true; | |
case Value_Bold: font->setWeight(QFont::Bold); return true; | |
default: break; | |
} | |
return false; | |
} | |
if (value.type != Value::Number) | |
return false; | |
font->setWeight(qMin(value.variant.toInt() / 8, 99)); | |
return true; | |
} | |
/** \internal | |
* parse the font family from the values (starting from index \a start) | |
* and set it the \a font | |
* \returns true if a family was extracted. | |
*/ | |
static bool setFontFamilyFromValues(const QVector<Value> &values, QFont *font, int start = 0) | |
{ | |
QString family; | |
bool shouldAddSpace = false; | |
for (int i = start; i < values.count(); ++i) { | |
const Value &v = values.at(i); | |
if (v.type == Value::TermOperatorComma) { | |
family += QLatin1Char(','); | |
shouldAddSpace = false; | |
continue; | |
} | |
const QString str = v.variant.toString(); | |
if (str.isEmpty()) | |
break; | |
if (shouldAddSpace) | |
family += QLatin1Char(' '); | |
family += str; | |
shouldAddSpace = true; | |
} | |
if (family.isEmpty()) | |
return false; | |
font->setFamily(family); | |
return true; | |
} | |
static void setTextDecorationFromValues(const QVector<Value> &values, QFont *font) | |
{ | |
for (int i = 0; i < values.count(); ++i) { | |
if (values.at(i).type != Value::KnownIdentifier) | |
continue; | |
switch (values.at(i).variant.toInt()) { | |
case Value_Underline: font->setUnderline(true); break; | |
case Value_Overline: font->setOverline(true); break; | |
case Value_LineThrough: font->setStrikeOut(true); break; | |
case Value_None: | |
font->setUnderline(false); | |
font->setOverline(false); | |
font->setStrikeOut(false); | |
break; | |
default: break; | |
} | |
} | |
} | |
static void parseShorthandFontProperty(const QVector<Value> &values, QFont *font, int *fontSizeAdjustment) | |
{ | |
font->setStyle(QFont::StyleNormal); | |
font->setWeight(QFont::Normal); | |
*fontSizeAdjustment = -255; | |
int i = 0; | |
while (i < values.count()) { | |
if (setFontStyleFromValue(values.at(i), font) | |
|| setFontWeightFromValue(values.at(i), font)) | |
++i; | |
else | |
break; | |
} | |
if (i < values.count()) { | |
setFontSizeFromValue(values.at(i), font, fontSizeAdjustment); | |
++i; | |
} | |
if (i < values.count()) { | |
setFontFamilyFromValues(values, font, i); | |
} | |
} | |
static void setFontVariantFromValue(const Value &value, QFont *font) | |
{ | |
if (value.type == Value::KnownIdentifier) { | |
switch (value.variant.toInt()) { | |
case Value_Normal: font->setCapitalization(QFont::MixedCase); break; | |
case Value_SmallCaps: font->setCapitalization(QFont::SmallCaps); break; | |
default: break; | |
} | |
} | |
} | |
static void setTextTransformFromValue(const Value &value, QFont *font) | |
{ | |
if (value.type == Value::KnownIdentifier) { | |
switch (value.variant.toInt()) { | |
case Value_None: font->setCapitalization(QFont::MixedCase); break; | |
case Value_Uppercase: font->setCapitalization(QFont::AllUppercase); break; | |
case Value_Lowercase: font->setCapitalization(QFont::AllLowercase); break; | |
default: break; | |
} | |
} | |
} | |
bool ValueExtractor::extractFont(QFont *font, int *fontSizeAdjustment) | |
{ | |
if (fontExtracted) { | |
*font = f; | |
*fontSizeAdjustment = adjustment; | |
return fontExtracted == 1; | |
} | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); ++i) { | |
const Declaration &decl = declarations.at(i); | |
if (decl.d->values.isEmpty()) | |
continue; | |
const Value &val = decl.d->values.at(0); | |
switch (decl.d->propertyId) { | |
case FontSize: setFontSizeFromValue(val, font, fontSizeAdjustment); break; | |
case FontStyle: setFontStyleFromValue(val, font); break; | |
case FontWeight: setFontWeightFromValue(val, font); break; | |
case FontFamily: setFontFamilyFromValues(decl.d->values, font); break; | |
case TextDecoration: setTextDecorationFromValues(decl.d->values, font); break; | |
case Font: parseShorthandFontProperty(decl.d->values, font, fontSizeAdjustment); break; | |
case FontVariant: setFontVariantFromValue(val, font); break; | |
case TextTransform: setTextTransformFromValue(val, font); break; | |
default: continue; | |
} | |
hit = true; | |
} | |
f = *font; | |
adjustment = *fontSizeAdjustment; | |
fontExtracted = hit ? 1 : 2; | |
return hit; | |
} | |
bool ValueExtractor::extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg) | |
{ | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); ++i) { | |
const Declaration &decl = declarations.at(i); | |
switch (decl.d->propertyId) { | |
case Color: *fg = decl.brushValue(pal); break; | |
case QtSelectionForeground: *sfg = decl.brushValue(pal); break; | |
case QtSelectionBackground: *sbg = decl.brushValue(pal); break; | |
case QtAlternateBackground: *abg = decl.brushValue(pal); break; | |
default: continue; | |
} | |
hit = true; | |
} | |
return hit; | |
} | |
void ValueExtractor::extractFont() | |
{ | |
if (fontExtracted) | |
return; | |
int dummy = -255; | |
extractFont(&f, &dummy); | |
} | |
bool ValueExtractor::extractImage(QIcon *icon, Qt::Alignment *a, QSize *size) | |
{ | |
bool hit = false; | |
for (int i = 0; i < declarations.count(); ++i) { | |
const Declaration &decl = declarations.at(i); | |
switch (decl.d->propertyId) { | |
case QtImage: | |
*icon = decl.iconValue(); | |
if (decl.d->values.count() > 0 && decl.d->values.at(0).type == Value::Uri) { | |
// try to pull just the size from the image... | |
QImageReader imageReader(decl.d->values.at(0).variant.toString()); | |
if ((*size = imageReader.size()).isNull()) { | |
// but we'll have to load the whole image if the | |
// format doesn't support just reading the size | |
*size = imageReader.read().size(); | |
} | |
} | |
break; | |
case QtImageAlignment: *a = decl.alignmentValue(); break; | |
default: continue; | |
} | |
hit = true; | |
} | |
return hit; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// Declaration | |
QColor Declaration::colorValue(const QPalette &pal) const | |
{ | |
if (d->values.count() != 1) | |
return QColor(); | |
if (d->parsed.isValid()) { | |
if (d->parsed.type() == QVariant::Color) | |
return qvariant_cast<QColor>(d->parsed); | |
if (d->parsed.type() == QVariant::Int) | |
return pal.color((QPalette::ColorRole)(d->parsed.toInt())); | |
} | |
ColorData color = parseColorValue(d->values.at(0)); | |
if(color.type == ColorData::Role) { | |
d->parsed = qVariantFromValue<int>(color.role); | |
return pal.color((QPalette::ColorRole)(color.role)); | |
} else { | |
d->parsed = qVariantFromValue<QColor>(color.color); | |
return color.color; | |
} | |
} | |
QBrush Declaration::brushValue(const QPalette &pal) const | |
{ | |
if (d->values.count() != 1) | |
return QBrush(); | |
if (d->parsed.isValid()) { | |
if (d->parsed.type() == QVariant::Brush) | |
return qvariant_cast<QBrush>(d->parsed); | |
if (d->parsed.type() == QVariant::Int) | |
return pal.color((QPalette::ColorRole)(d->parsed.toInt())); | |
} | |
BrushData data = parseBrushValue(d->values.at(0), pal); | |
if(data.type == BrushData::Role) { | |
d->parsed = qVariantFromValue<int>(data.role); | |
return pal.color((QPalette::ColorRole)(data.role)); | |
} else { | |
if (data.type != BrushData::DependsOnThePalette) | |
d->parsed = qVariantFromValue<QBrush>(data.brush); | |
return data.brush; | |
} | |
} | |
void Declaration::brushValues(QBrush *c, const QPalette &pal) const | |
{ | |
int needParse = 0x1f; // bits 0..3 say if we should parse the corresponding value. | |
// the bit 4 say we need to update d->parsed | |
int i = 0; | |
if (d->parsed.isValid()) { | |
needParse = 0; | |
QList<QVariant> v = d->parsed.toList(); | |
for (i = 0; i < qMin(v.count(), 4); i++) { | |
if (v.at(i).type() == QVariant::Brush) { | |
c[i] = qvariant_cast<QBrush>(v.at(i)); | |
} else if (v.at(i).type() == QVariant::Int) { | |
c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt())); | |
} else { | |
needParse |= (1<<i); | |
} | |
} | |
} | |
if (needParse != 0) { | |
QList<QVariant> v; | |
for (i = 0; i < qMin(d->values.count(), 4); i++) { | |
if (!(needParse & (1<<i))) | |
continue; | |
BrushData data = parseBrushValue(d->values.at(i), pal); | |
if(data.type == BrushData::Role) { | |
v += qVariantFromValue<int>(data.role); | |
c[i] = pal.color((QPalette::ColorRole)(data.role)); | |
} else { | |
if (data.type != BrushData::DependsOnThePalette) { | |
v += qVariantFromValue<QBrush>(data.brush); | |
} else { | |
v += QVariant(); | |
} | |
c[i] = data.brush; | |
} | |
} | |
if (needParse & 0x10) | |
d->parsed = v; | |
} | |
if (i == 0) c[0] = c[1] = c[2] = c[3] = QBrush(); | |
else if (i == 1) c[3] = c[2] = c[1] = c[0]; | |
else if (i == 2) c[2] = c[0], c[3] = c[1]; | |
else if (i == 3) c[3] = c[1]; | |
} | |
bool Declaration::realValue(qreal *real, const char *unit) const | |
{ | |
if (d->values.count() != 1) | |
return false; | |
const Value &v = d->values.at(0); | |
if (unit && v.type != Value::Length) | |
return false; | |
QString s = v.variant.toString(); | |
if (unit) { | |
if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive)) | |
return false; | |
s.chop(qstrlen(unit)); | |
} | |
bool ok = false; | |
qreal val = s.toDouble(&ok); | |
if (ok) | |
*real = val; | |
return ok; | |
} | |
static bool intValueHelper(const Value &v, int *i, const char *unit) | |
{ | |
if (unit && v.type != Value::Length) | |
return false; | |
QString s = v.variant.toString(); | |
if (unit) { | |
if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive)) | |
return false; | |
s.chop(qstrlen(unit)); | |
} | |
bool ok = false; | |
int val = s.toInt(&ok); | |
if (ok) | |
*i = val; | |
return ok; | |
} | |
bool Declaration::intValue(int *i, const char *unit) const | |
{ | |
if (d->values.count() != 1) | |
return false; | |
return intValueHelper(d->values.at(0), i, unit); | |
} | |
QSize Declaration::sizeValue() const | |
{ | |
if (d->parsed.isValid()) | |
return qvariant_cast<QSize>(d->parsed); | |
int x[2] = { 0, 0 }; | |
if (d->values.count() > 0) | |
intValueHelper(d->values.at(0), &x[0], "px"); | |
if (d->values.count() > 1) | |
intValueHelper(d->values.at(1), &x[1], "px"); | |
else | |
x[1] = x[0]; | |
QSize size(x[0], x[1]); | |
d->parsed = qVariantFromValue<QSize>(size); | |
return size; | |
} | |
QRect Declaration::rectValue() const | |
{ | |
if (d->values.count() != 1) | |
return QRect(); | |
if (d->parsed.isValid()) | |
return qvariant_cast<QRect>(d->parsed); | |
const Value &v = d->values.at(0); | |
if (v.type != Value::Function) | |
return QRect(); | |
QStringList func = v.variant.toStringList(); | |
if (func.count() != 2 || func.at(0).compare(QLatin1String("rect")) != 0) | |
return QRect(); | |
QStringList args = func[1].split(QLatin1Char(' '), QString::SkipEmptyParts); | |
if (args.count() != 4) | |
return QRect(); | |
QRect rect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt()); | |
d->parsed = qVariantFromValue<QRect>(rect); | |
return rect; | |
} | |
void Declaration::colorValues(QColor *c, const QPalette &pal) const | |
{ | |
int i; | |
if (d->parsed.isValid()) { | |
QList<QVariant> v = d->parsed.toList(); | |
for (i = 0; i < qMin(d->values.count(), 4); i++) { | |
if (v.at(i).type() == QVariant::Color) { | |
c[i] = qvariant_cast<QColor>(v.at(i)); | |
} else { | |
c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt())); | |
} | |
} | |
} else { | |
QList<QVariant> v; | |
for (i = 0; i < qMin(d->values.count(), 4); i++) { | |
ColorData color = parseColorValue(d->values.at(i)); | |
if(color.type == ColorData::Role) { | |
v += qVariantFromValue<int>(color.role); | |
c[i] = pal.color((QPalette::ColorRole)(color.role)); | |
} else { | |
v += qVariantFromValue<QColor>(color.color); | |
c[i] = color.color; | |
} | |
} | |
d->parsed = v; | |
} | |
if (i == 0) c[0] = c[1] = c[2] = c[3] = QColor(); | |
else if (i == 1) c[3] = c[2] = c[1] = c[0]; | |
else if (i == 2) c[2] = c[0], c[3] = c[1]; | |
else if (i == 3) c[3] = c[1]; | |
} | |
BorderStyle Declaration::styleValue() const | |
{ | |
if (d->values.count() != 1) | |
return BorderStyle_None; | |
return parseStyleValue(d->values.at(0)); | |
} | |
void Declaration::styleValues(BorderStyle *s) const | |
{ | |
int i; | |
for (i = 0; i < qMin(d->values.count(), 4); i++) | |
s[i] = parseStyleValue(d->values.at(i)); | |
if (i == 0) s[0] = s[1] = s[2] = s[3] = BorderStyle_None; | |
else if (i == 1) s[3] = s[2] = s[1] = s[0]; | |
else if (i == 2) s[2] = s[0], s[3] = s[1]; | |
else if (i == 3) s[3] = s[1]; | |
} | |
Repeat Declaration::repeatValue() const | |
{ | |
if (d->parsed.isValid()) | |
return static_cast<Repeat>(d->parsed.toInt()); | |
if (d->values.count() != 1) | |
return Repeat_Unknown; | |
int v = findKnownValue(d->values.at(0).variant.toString(), | |
repeats, NumKnownRepeats); | |
d->parsed = v; | |
return static_cast<Repeat>(v); | |
} | |
Origin Declaration::originValue() const | |
{ | |
if (d->parsed.isValid()) | |
return static_cast<Origin>(d->parsed.toInt()); | |
if (d->values.count() != 1) | |
return Origin_Unknown; | |
int v = findKnownValue(d->values.at(0).variant.toString(), | |
origins, NumKnownOrigins); | |
d->parsed = v; | |
return static_cast<Origin>(v); | |
} | |
PositionMode Declaration::positionValue() const | |
{ | |
if (d->parsed.isValid()) | |
return static_cast<PositionMode>(d->parsed.toInt()); | |
if (d->values.count() != 1) | |
return PositionMode_Unknown; | |
int v = findKnownValue(d->values.at(0).variant.toString(), | |
positions, NumKnownPositionModes); | |
d->parsed = v; | |
return static_cast<PositionMode>(v); | |
} | |
Attachment Declaration::attachmentValue() const | |
{ | |
if (d->parsed.isValid()) | |
return static_cast<Attachment>(d->parsed.toInt()); | |
if (d->values.count() != 1) | |
return Attachment_Unknown; | |
int v = findKnownValue(d->values.at(0).variant.toString(), | |
attachments, NumKnownAttachments); | |
d->parsed = v; | |
return static_cast<Attachment>(v); | |
} | |
int Declaration::styleFeaturesValue() const | |
{ | |
Q_ASSERT(d->propertyId == QtStyleFeatures); | |
if (d->parsed.isValid()) | |
return d->parsed.toInt(); | |
int features = StyleFeature_None; | |
for (int i = 0; i < d->values.count(); i++) { | |
features |= static_cast<int>(findKnownValue(d->values.value(i).variant.toString(), | |
styleFeatures, NumKnownStyleFeatures)); | |
} | |
d->parsed = features; | |
return features; | |
} | |
QString Declaration::uriValue() const | |
{ | |
if (d->values.isEmpty() || d->values.at(0).type != Value::Uri) | |
return QString(); | |
return d->values.at(0).variant.toString(); | |
} | |
Qt::Alignment Declaration::alignmentValue() const | |
{ | |
if (d->parsed.isValid()) | |
return Qt::Alignment(d->parsed.toInt()); | |
if (d->values.isEmpty() || d->values.count() > 2) | |
return Qt::AlignLeft | Qt::AlignTop; | |
Qt::Alignment v = parseAlignment(d->values.constData(), d->values.count()); | |
d->parsed = int(v); | |
return v; | |
} | |
void Declaration::borderImageValue(QString *image, int *cuts, | |
TileMode *h, TileMode *v) const | |
{ | |
*image = uriValue(); | |
for (int i = 0; i < 4; i++) | |
cuts[i] = -1; | |
*h = *v = TileMode_Stretch; | |
if (d->values.count() < 2) | |
return; | |
if (d->values.at(1).type == Value::Number) { // cuts! | |
int i; | |
for (i = 0; i < qMin(d->values.count()-1, 4); i++) { | |
const Value& v = d->values.at(i+1); | |
if (v.type != Value::Number) | |
break; | |
cuts[i] = v.variant.toString().toInt(); | |
} | |
if (i == 0) cuts[0] = cuts[1] = cuts[2] = cuts[3] = 0; | |
else if (i == 1) cuts[3] = cuts[2] = cuts[1] = cuts[0]; | |
else if (i == 2) cuts[2] = cuts[0], cuts[3] = cuts[1]; | |
else if (i == 3) cuts[3] = cuts[1]; | |
} | |
if (d->values.last().type == Value::Identifier) { | |
*v = static_cast<TileMode>(findKnownValue(d->values.last().variant.toString(), | |
tileModes, NumKnownTileModes)); | |
} | |
if (d->values[d->values.count() - 2].type == Value::Identifier) { | |
*h = static_cast<TileMode> | |
(findKnownValue(d->values[d->values.count()-2].variant.toString(), | |
tileModes, NumKnownTileModes)); | |
} else | |
*h = *v; | |
} | |
QIcon Declaration::iconValue() const | |
{ | |
if (d->parsed.isValid()) | |
return qvariant_cast<QIcon>(d->parsed); | |
QIcon icon; | |
for (int i = 0; i < d->values.count();) { | |
const Value &value = d->values.at(i++); | |
if (value.type != Value::Uri) | |
break; | |
QString uri = value.variant.toString(); | |
QIcon::Mode mode = QIcon::Normal; | |
QIcon::State state = QIcon::Off; | |
for (int j = 0; j < 2; j++) { | |
if (i != d->values.count() && d->values.at(i).type == Value::KnownIdentifier) { | |
switch (d->values.at(i).variant.toInt()) { | |
case Value_Disabled: mode = QIcon::Disabled; break; | |
case Value_Active: mode = QIcon::Active; break; | |
case Value_Selected: mode = QIcon::Selected; break; | |
case Value_Normal: mode = QIcon::Normal; break; | |
case Value_On: state = QIcon::On; break; | |
case Value_Off: state = QIcon::Off; break; | |
default: break; | |
} | |
++i; | |
} else { | |
break; | |
} | |
} | |
// QIcon is soo broken | |
if (icon.isNull()) | |
icon = QIcon(uri); | |
else | |
icon.addPixmap(uri, mode, state); | |
if (i == d->values.count()) | |
break; | |
if (d->values.at(i).type == Value::TermOperatorComma) | |
i++; | |
} | |
d->parsed = qVariantFromValue<QIcon>(icon); | |
return icon; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// Selector | |
int Selector::specificity() const | |
{ | |
int val = 0; | |
for (int i = 0; i < basicSelectors.count(); ++i) { | |
const BasicSelector &sel = basicSelectors.at(i); | |
if (!sel.elementName.isEmpty()) | |
val += 1; | |
val += (sel.pseudos.count() + sel.attributeSelectors.count()) * 0x10; | |
val += sel.ids.count() * 0x100; | |
} | |
return val; | |
} | |
QString Selector::pseudoElement() const | |
{ | |
const BasicSelector& bs = basicSelectors.last(); | |
if (!bs.pseudos.isEmpty() && bs.pseudos.at(0).type == PseudoClass_Unknown) | |
return bs.pseudos.at(0).name; | |
return QString(); | |
} | |
quint64 Selector::pseudoClass(quint64 *negated) const | |
{ | |
const BasicSelector& bs = basicSelectors.last(); | |
if (bs.pseudos.isEmpty()) | |
return PseudoClass_Unspecified; | |
quint64 pc = PseudoClass_Unknown; | |
for (int i = !pseudoElement().isEmpty(); i < bs.pseudos.count(); i++) { | |
const Pseudo &pseudo = bs.pseudos.at(i); | |
if (pseudo.type == PseudoClass_Unknown) | |
return PseudoClass_Unknown; | |
if (!pseudo.negated) | |
pc |= pseudo.type; | |
else if (negated) | |
*negated |= pseudo.type; | |
} | |
return pc; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// StyleSheet | |
void StyleSheet::buildIndexes(Qt::CaseSensitivity nameCaseSensitivity) | |
{ | |
QVector<StyleRule> universals; | |
for (int i = 0; i < styleRules.count(); ++i) { | |
const StyleRule &rule = styleRules.at(i); | |
QVector<Selector> universalsSelectors; | |
for (int j = 0; j < rule.selectors.count(); ++j) { | |
const Selector& selector = rule.selectors.at(j); | |
if (selector.basicSelectors.isEmpty()) | |
continue; | |
if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation) { | |
if (selector.basicSelectors.count() != 1) | |
continue; | |
} else if (selector.basicSelectors.count() <= 1) { | |
continue; | |
} | |
const BasicSelector &sel = selector.basicSelectors.at(selector.basicSelectors.count() - 1); | |
if (!sel.ids.isEmpty()) { | |
StyleRule nr; | |
nr.selectors += selector; | |
nr.declarations = rule.declarations; | |
nr.order = i; | |
idIndex.insert(sel.ids.at(0), nr); | |
} else if (!sel.elementName.isEmpty()) { | |
StyleRule nr; | |
nr.selectors += selector; | |
nr.declarations = rule.declarations; | |
nr.order = i; | |
QString name = sel.elementName; | |
if (nameCaseSensitivity == Qt::CaseInsensitive) | |
name=name.toLower(); | |
nameIndex.insert(name, nr); | |
} else { | |
universalsSelectors += selector; | |
} | |
} | |
if (!universalsSelectors.isEmpty()) { | |
StyleRule nr; | |
nr.selectors = universalsSelectors; | |
nr.declarations = rule.declarations; | |
nr.order = i; | |
universals << nr; | |
} | |
} | |
styleRules = universals; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// StyleSelector | |
StyleSelector::~StyleSelector() | |
{ | |
} | |
bool StyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const | |
{ | |
return nodeNames(node).contains(nodeName, nameCaseSensitivity); | |
} | |
QStringList StyleSelector::nodeIds(NodePtr node) const | |
{ | |
return QStringList(attribute(node, QLatin1String("id"))); | |
} | |
bool StyleSelector::selectorMatches(const Selector &selector, NodePtr node) | |
{ | |
if (selector.basicSelectors.isEmpty()) | |
return false; | |
if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation) { | |
if (selector.basicSelectors.count() != 1) | |
return false; | |
return basicSelectorMatches(selector.basicSelectors.at(0), node); | |
} | |
if (selector.basicSelectors.count() <= 1) | |
return false; | |
int i = selector.basicSelectors.count() - 1; | |
node = duplicateNode(node); | |
bool match = true; | |
BasicSelector sel = selector.basicSelectors.at(i); | |
do { | |
match = basicSelectorMatches(sel, node); | |
if (!match) { | |
if (sel.relationToNext == BasicSelector::MatchNextSelectorIfParent | |
|| i == selector.basicSelectors.count() - 1) // first element must always match! | |
break; | |
} | |
if (match || sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor) | |
--i; | |
if (i < 0) | |
break; | |
sel = selector.basicSelectors.at(i); | |
if (sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor | |
|| sel.relationToNext == BasicSelector::MatchNextSelectorIfParent) { | |
NodePtr nextParent = parentNode(node); | |
freeNode(node); | |
node = nextParent; | |
} else if (sel.relationToNext == BasicSelector::MatchNextSelectorIfPreceeds) { | |
NodePtr previousSibling = previousSiblingNode(node); | |
freeNode(node); | |
node = previousSibling; | |
} | |
if (isNullNode(node)) { | |
match = false; | |
break; | |
} | |
} while (i >= 0 && (match || sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor)); | |
freeNode(node); | |
return match; | |
} | |
bool StyleSelector::basicSelectorMatches(const BasicSelector &sel, NodePtr node) | |
{ | |
if (!sel.attributeSelectors.isEmpty()) { | |
if (!hasAttributes(node)) | |
return false; | |
for (int i = 0; i < sel.attributeSelectors.count(); ++i) { | |
const QCss::AttributeSelector &a = sel.attributeSelectors.at(i); | |
const QString attrValue = attribute(node, a.name); | |
if (attrValue.isNull()) | |
return false; | |
if (a.valueMatchCriterium == QCss::AttributeSelector::MatchContains) { | |
QStringList lst = attrValue.split(QLatin1Char(' ')); | |
if (!lst.contains(a.value)) | |
return false; | |
} else if ( | |
(a.valueMatchCriterium == QCss::AttributeSelector::MatchEqual | |
&& attrValue != a.value) | |
|| | |
(a.valueMatchCriterium == QCss::AttributeSelector::MatchBeginsWith | |
&& !attrValue.startsWith(a.value)) | |
) | |
return false; | |
} | |
} | |
if (!sel.elementName.isEmpty() | |
&& !nodeNameEquals(node, sel.elementName)) | |
return false; | |
if (!sel.ids.isEmpty() | |
&& sel.ids != nodeIds(node)) | |
return false; | |
return true; | |
} | |
void StyleSelector::matchRule(NodePtr node, const StyleRule &rule, StyleSheetOrigin origin, | |
int depth, QMap<uint, StyleRule> *weightedRules) | |
{ | |
for (int j = 0; j < rule.selectors.count(); ++j) { | |
const Selector& selector = rule.selectors.at(j); | |
if (selectorMatches(selector, node)) { | |
uint weight = rule.order | |
+ selector.specificity() *0x100 | |
+ (uint(origin) + depth)*0x100000; | |
StyleRule newRule = rule; | |
if(rule.selectors.count() > 1) { | |
newRule.selectors.resize(1); | |
newRule.selectors[0] = selector; | |
} | |
//We might have rules with the same weight if they came from a rule with several selectors | |
weightedRules->insertMulti(weight, newRule); | |
} | |
} | |
} | |
// Returns style rules that are in ascending order of specificity | |
// Each of the StyleRule returned will contain exactly one Selector | |
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 = 0; 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 = 0; i < styleSheet.mediaRules.count(); ++i) { | |
if (styleSheet.mediaRules.at(i).media.contains(medium, Qt::CaseInsensitive)) { | |
for (int j = 0; 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; | |
} | |
// for qtexthtmlparser which requires just the declarations with Enabled state | |
// and without pseudo elements | |
QVector<Declaration> StyleSelector::declarationsForNode(NodePtr node, const char *extraPseudo) | |
{ | |
QVector<Declaration> decls; | |
QVector<StyleRule> rules = styleRulesForNode(node); | |
for (int i = 0; i < rules.count(); i++) { | |
const Selector& selector = rules.at(i).selectors.at(0); | |
const QString pseudoElement = selector.pseudoElement(); | |
if (extraPseudo && pseudoElement == QLatin1String(extraPseudo)) { | |
decls += rules.at(i).declarations; | |
continue; | |
} | |
if (!pseudoElement.isEmpty()) // skip rules with pseudo elements | |
continue; | |
quint64 pseudoClass = selector.pseudoClass(); | |
if (pseudoClass == PseudoClass_Enabled || pseudoClass == PseudoClass_Unspecified) | |
decls += rules.at(i).declarations; | |
} | |
return decls; | |
} | |
static inline bool isHexDigit(const char c) | |
{ | |
return (c >= '0' && c <= '9') | |
|| (c >= 'a' && c <= 'f') | |
|| (c >= 'A' && c <= 'F') | |
; | |
} | |
QString Scanner::preprocess(const QString &input, bool *hasEscapeSequences) | |
{ | |
QString output = input; | |
if (hasEscapeSequences) | |
*hasEscapeSequences = false; | |
int i = 0; | |
while (i < output.size()) { | |
if (output.at(i) == QLatin1Char('\\')) { | |
++i; | |
// test for unicode hex escape | |
int hexCount = 0; | |
const int hexStart = i; | |
while (i < output.size() | |
&& isHexDigit(output.at(i).toLatin1()) | |
&& hexCount < 7) { | |
++hexCount; | |
++i; | |
} | |
if (hexCount == 0) { | |
if (hasEscapeSequences) | |
*hasEscapeSequences = true; | |
continue; | |
} | |
hexCount = qMin(hexCount, 6); | |
bool ok = false; | |
ushort code = output.mid(hexStart, hexCount).toUShort(&ok, 16); | |
if (ok) { | |
output.replace(hexStart - 1, hexCount + 1, QChar(code)); | |
i = hexStart; | |
} else { | |
i = hexStart; | |
} | |
} else { | |
++i; | |
} | |
} | |
return output; | |
} | |
int QCssScanner_Generated::handleCommentStart() | |
{ | |
while (pos < input.size() - 1) { | |
if (input.at(pos) == QLatin1Char('*') | |
&& input.at(pos + 1) == QLatin1Char('/')) { | |
pos += 2; | |
break; | |
} | |
++pos; | |
} | |
return S; | |
} | |
void Scanner::scan(const QString &preprocessedInput, QVector<Symbol> *symbols) | |
{ | |
QCssScanner_Generated scanner(preprocessedInput); | |
Symbol sym; | |
int tok = scanner.lex(); | |
while (tok != -1) { | |
sym.token = static_cast<QCss::TokenType>(tok); | |
sym.text = scanner.input; | |
sym.start = scanner.lexemStart; | |
sym.len = scanner.lexemLength; | |
symbols->append(sym); | |
tok = scanner.lex(); | |
} | |
} | |
QString Symbol::lexem() const | |
{ | |
QString result; | |
if (len > 0) | |
result.reserve(len); | |
for (int i = 0; i < len; ++i) { | |
if (text.at(start + i) == QLatin1Char('\\') && i < len - 1) | |
++i; | |
result += text.at(start + i); | |
} | |
return result; | |
} | |
Parser::Parser(const QString &css, bool isFile) | |
{ | |
init(css, isFile); | |
} | |
Parser::Parser() | |
{ | |
index = 0; | |
errorIndex = -1; | |
hasEscapeSequences = false; | |
} | |
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(0); | |
symbols.reserve(8); | |
Scanner::scan(Scanner::preprocess(styleSheet, &hasEscapeSequences), &symbols); | |
index = 0; | |
errorIndex = -1; | |
} | |
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; | |
} | |
Symbol Parser::errorSymbol() | |
{ | |
if (errorIndex == -1) return Symbol(); | |
return symbols.at(errorIndex); | |
} | |
static inline void removeOptionalQuotes(QString *str) | |
{ | |
if (!str->startsWith(QLatin1Char('\'')) | |
&& !str->startsWith(QLatin1Char('\"'))) | |
return; | |
str->remove(0, 1); | |
str->chop(1); | |
} | |
bool Parser::parseImport(ImportRule *importRule) | |
{ | |
skipSpace(); | |
if (test(STRING)) { | |
importRule->href = lexem(); | |
} else { | |
if (!testAndParseUri(&importRule->href)) return false; | |
} | |
removeOptionalQuotes(&importRule->href); | |
skipSpace(); | |
if (testMedium()) { | |
if (!parseMedium(&importRule->media)) return false; | |
while (test(COMMA)) { | |
skipSpace(); | |
if (!parseNextMedium(&importRule->media)) return false; | |
} | |
} | |
if (!next(SEMICOLON)) return false; | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parseMedia(MediaRule *mediaRule) | |
{ | |
do { | |
skipSpace(); | |
if (!parseNextMedium(&mediaRule->media)) return false; | |
} while (test(COMMA)); | |
if (!next(LBRACE)) return false; | |
skipSpace(); | |
while (testRuleset()) { | |
StyleRule rule; | |
if (!parseRuleset(&rule)) return false; | |
mediaRule->styleRules.append(rule); | |
} | |
if (!next(RBRACE)) return false; | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parseMedium(QStringList *media) | |
{ | |
media->append(lexem()); | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parsePage(PageRule *pageRule) | |
{ | |
skipSpace(); | |
if (testPseudoPage()) | |
if (!parsePseudoPage(&pageRule->selector)) return false; | |
skipSpace(); | |
if (!next(LBRACE)) return false; | |
do { | |
skipSpace(); | |
Declaration decl; | |
if (!parseNextDeclaration(&decl)) return false; | |
if (!decl.isEmpty()) | |
pageRule->declarations.append(decl); | |
} while (test(SEMICOLON)); | |
if (!next(RBRACE)) return false; | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parsePseudoPage(QString *selector) | |
{ | |
if (!next(IDENT)) return false; | |
*selector = lexem(); | |
return true; | |
} | |
bool Parser::parseNextOperator(Value *value) | |
{ | |
if (!hasNext()) return true; | |
switch (next()) { | |
case SLASH: value->type = Value::TermOperatorSlash; skipSpace(); break; | |
case COMMA: value->type = Value::TermOperatorComma; skipSpace(); break; | |
default: prev(); break; | |
} | |
return true; | |
} | |
bool Parser::parseCombinator(BasicSelector::Relation *relation) | |
{ | |
*relation = BasicSelector::NoRelation; | |
if (lookup() == S) { | |
*relation = BasicSelector::MatchNextSelectorIfAncestor; | |
skipSpace(); | |
} else { | |
prev(); | |
} | |
if (test(PLUS)) { | |
*relation = BasicSelector::MatchNextSelectorIfPreceeds; | |
} else if (test(GREATER)) { | |
*relation = BasicSelector::MatchNextSelectorIfParent; | |
} | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parseProperty(Declaration *decl) | |
{ | |
decl->d->property = lexem(); | |
decl->d->propertyId = static_cast<Property>(findKnownValue(decl->d->property, properties, NumProperties)); | |
skipSpace(); | |
return true; | |
} | |
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 - 1; | |
} else { | |
skipSpace(); | |
return foundRBrace; | |
} | |
} | |
if (!decl.isEmpty()) | |
styleRule->declarations.append(decl); | |
} while (test(SEMICOLON)); | |
if (!next(RBRACE)) return false; | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parseSelector(Selector *sel) | |
{ | |
BasicSelector basicSel; | |
if (!parseSimpleSelector(&basicSel)) return false; | |
while (testCombinator()) { | |
if (!parseCombinator(&basicSel.relationToNext)) return false; | |
if (!testSimpleSelector()) break; | |
sel->basicSelectors.append(basicSel); | |
basicSel = BasicSelector(); | |
if (!parseSimpleSelector(&basicSel)) return false; | |
} | |
sel->basicSelectors.append(basicSel); | |
return true; | |
} | |
bool Parser::parseSimpleSelector(BasicSelector *basicSel) | |
{ | |
int minCount = 0; | |
if (lookupElementName()) { | |
if (!parseElementName(&basicSel->elementName)) return false; | |
} else { | |
prev(); | |
minCount = 1; | |
} | |
bool onceMore; | |
int count = 0; | |
do { | |
onceMore = false; | |
if (test(HASH)) { | |
QString theid = lexem(); | |
// chop off leading # | |
theid.remove(0, 1); | |
basicSel->ids.append(theid); | |
onceMore = true; | |
} else if (testClass()) { | |
onceMore = true; | |
AttributeSelector a; | |
a.name = QLatin1String("class"); | |
a.valueMatchCriterium = AttributeSelector::MatchContains; | |
if (!parseClass(&a.value)) return false; | |
basicSel->attributeSelectors.append(a); | |
} else if (testAttrib()) { | |
onceMore = true; | |
AttributeSelector a; | |
if (!parseAttrib(&a)) return false; | |
basicSel->attributeSelectors.append(a); | |
} else if (testPseudo()) { | |
onceMore = true; | |
Pseudo ps; | |
if (!parsePseudo(&ps)) return false; | |
basicSel->pseudos.append(ps); | |
} | |
if (onceMore) ++count; | |
} while (onceMore); | |
return count >= minCount; | |
} | |
bool Parser::parseClass(QString *name) | |
{ | |
if (!next(IDENT)) return false; | |
*name = lexem(); | |
return true; | |
} | |
bool Parser::parseElementName(QString *name) | |
{ | |
switch (lookup()) { | |
case STAR: name->clear(); break; | |
case IDENT: *name = lexem(); break; | |
default: return false; | |
} | |
return true; | |
} | |
bool Parser::parseAttrib(AttributeSelector *attr) | |
{ | |
skipSpace(); | |
if (!next(IDENT)) return false; | |
attr->name = lexem(); | |
skipSpace(); | |
if (test(EQUAL)) { | |
attr->valueMatchCriterium = AttributeSelector::MatchEqual; | |
} else if (test(INCLUDES)) { | |
attr->valueMatchCriterium = AttributeSelector::MatchContains; | |
} else if (test(DASHMATCH)) { | |
attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith; | |
} else { | |
return next(RBRACKET); | |
} | |
skipSpace(); | |
if (!test(IDENT) && !test(STRING)) return false; | |
attr->value = unquotedLexem(); | |
skipSpace(); | |
return next(RBRACKET); | |
} | |
bool Parser::parsePseudo(Pseudo *pseudo) | |
{ | |
(void)test(COLON); | |
pseudo->negated = test(EXCLAMATION_SYM); | |
if (test(IDENT)) { | |
pseudo->name = lexem(); | |
pseudo->type = static_cast<quint64>(findKnownValue(pseudo->name, pseudos, NumPseudos)); | |
return true; | |
} | |
if (!next(FUNCTION)) return false; | |
pseudo->function = lexem(); | |
// chop off trailing parenthesis | |
pseudo->function.chop(1); | |
skipSpace(); | |
if (!test(IDENT)) return false; | |
pseudo->name = lexem(); | |
skipSpace(); | |
return next(RPAREN); | |
} | |
bool Parser::parseNextDeclaration(Declaration *decl) | |
{ | |
if (!testProperty()) | |
return true; // not an error! | |
if (!parseProperty(decl)) return false; | |
if (!next(COLON)) return false; | |
skipSpace(); | |
if (!parseNextExpr(&decl->d->values)) return false; | |
if (testPrio()) | |
if (!parsePrio(decl)) return false; | |
return true; | |
} | |
bool Parser::testPrio() | |
{ | |
const int rewind = index; | |
if (!test(EXCLAMATION_SYM)) return false; | |
skipSpace(); | |
if (!test(IDENT)) { | |
index = rewind; | |
return false; | |
} | |
if (lexem().compare(QLatin1String("important"), Qt::CaseInsensitive) != 0) { | |
index = rewind; | |
return false; | |
} | |
return true; | |
} | |
bool Parser::parsePrio(Declaration *declaration) | |
{ | |
declaration->d->important = true; | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parseExpr(QVector<Value> *values) | |
{ | |
Value val; | |
if (!parseTerm(&val)) return false; | |
values->append(val); | |
bool onceMore; | |
do { | |
onceMore = false; | |
val = Value(); | |
if (!parseNextOperator(&val)) return false; | |
if (val.type != QCss::Value::Unknown) | |
values->append(val); | |
if (testTerm()) { | |
onceMore = true; | |
val = Value(); | |
if (!parseTerm(&val)) return false; | |
values->append(val); | |
} | |
} while (onceMore); | |
return true; | |
} | |
bool Parser::testTerm() | |
{ | |
return test(PLUS) || test(MINUS) | |
|| test(NUMBER) | |
|| test(PERCENTAGE) | |
|| test(LENGTH) | |
|| test(STRING) | |
|| test(IDENT) | |
|| testHexColor() | |
|| testFunction(); | |
} | |
bool Parser::parseTerm(Value *value) | |
{ | |
QString str = lexem(); | |
bool haveUnary = false; | |
if (lookup() == PLUS || lookup() == MINUS) { | |
haveUnary = true; | |
if (!hasNext()) return false; | |
next(); | |
str += lexem(); | |
} | |
value->variant = str; | |
value->type = QCss::Value::String; | |
switch (lookup()) { | |
case NUMBER: | |
value->type = Value::Number; | |
value->variant.convert(QVariant::Double); | |
break; | |
case PERCENTAGE: | |
value->type = Value::Percentage; | |
str.chop(1); // strip off % | |
value->variant = str; | |
break; | |
case LENGTH: | |
value->type = Value::Length; | |
break; | |
case STRING: | |
if (haveUnary) return false; | |
value->type = Value::String; | |
str.chop(1); | |
str.remove(0, 1); | |
value->variant = str; | |
break; | |
case IDENT: { | |
if (haveUnary) return false; | |
value->type = Value::Identifier; | |
const int theid = findKnownValue(str, values, NumKnownValues); | |
if (theid != 0) { | |
value->type = Value::KnownIdentifier; | |
value->variant = theid; | |
} | |
break; | |
} | |
default: { | |
if (haveUnary) return false; | |
prev(); | |
if (testHexColor()) { | |
QColor col; | |
if (!parseHexColor(&col)) return false; | |
value->type = Value::Color; | |
value->variant = col; | |
} else if (testFunction()) { | |
QString name, args; | |
if (!parseFunction(&name, &args)) return false; | |
if (name == QLatin1String("url")) { | |
value->type = Value::Uri; | |
removeOptionalQuotes(&args); | |
if (QFileInfo(args).isRelative() && !sourcePath.isEmpty()) { | |
args.prepend(sourcePath); | |
} | |
value->variant = args; | |
} else { | |
value->type = Value::Function; | |
value->variant = QStringList() << name << args; | |
} | |
} else { | |
return recordError(); | |
} | |
return true; | |
} | |
} | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parseFunction(QString *name, QString *args) | |
{ | |
*name = lexem(); | |
name->chop(1); | |
skipSpace(); | |
const int start = index; | |
if (!until(RPAREN)) return false; | |
for (int i = start; i < index - 1; ++i) | |
args->append(symbols.at(i).lexem()); | |
/* | |
if (!nextExpr(&arguments)) return false; | |
if (!next(RPAREN)) return false; | |
*/ | |
skipSpace(); | |
return true; | |
} | |
bool Parser::parseHexColor(QColor *col) | |
{ | |
col->setNamedColor(lexem()); | |
if (!col->isValid()) { | |
qWarning("QCssParser::parseHexColor: Unknown color name '%s'",lexem().toLatin1().constData()); | |
return false; | |
} | |
skipSpace(); | |
return true; | |
} | |
bool Parser::testAndParseUri(QString *uri) | |
{ | |
const int rewind = index; | |
if (!testFunction()) return false; | |
QString name, args; | |
if (!parseFunction(&name, &args)) { | |
index = rewind; | |
return false; | |
} | |
if (name.toLower() != QLatin1String("url")) { | |
index = rewind; | |
return false; | |
} | |
*uri = args; | |
removeOptionalQuotes(uri); | |
return true; | |
} | |
bool Parser::testSimpleSelector() | |
{ | |
return testElementName() | |
|| (test(HASH)) | |
|| testClass() | |
|| testAttrib() | |
|| testPseudo(); | |
} | |
bool Parser::next(QCss::TokenType t) | |
{ | |
if (hasNext() && next() == t) | |
return true; | |
return recordError(); | |
} | |
bool Parser::test(QCss::TokenType t) | |
{ | |
if (index >= symbols.count()) | |
return false; | |
if (symbols.at(index).token == t) { | |
++index; | |
return true; | |
} | |
return false; | |
} | |
QString Parser::unquotedLexem() const | |
{ | |
QString s = lexem(); | |
if (lookup() == STRING) { | |
s.chop(1); | |
s.remove(0, 1); | |
} | |
return s; | |
} | |
QString Parser::lexemUntil(QCss::TokenType t) | |
{ | |
QString lexem; | |
while (hasNext() && next() != t) | |
lexem += symbol().lexem(); | |
return lexem; | |
} | |
bool Parser::until(QCss::TokenType target, QCss::TokenType target2) | |
{ | |
int braceCount = 0; | |
int brackCount = 0; | |
int parenCount = 0; | |
if (index) { | |
switch(symbols.at(index-1).token) { | |
case LBRACE: ++braceCount; break; | |
case LBRACKET: ++brackCount; break; | |
case FUNCTION: | |
case LPAREN: ++parenCount; break; | |
default: ; | |
} | |
} | |
while (index < symbols.size()) { | |
QCss::TokenType t = symbols.at(index++).token; | |
switch (t) { | |
case LBRACE: ++braceCount; break; | |
case RBRACE: --braceCount; break; | |
case LBRACKET: ++brackCount; break; | |
case RBRACKET: --brackCount; break; | |
case FUNCTION: | |
case LPAREN: ++parenCount; break; | |
case RPAREN: --parenCount; break; | |
default: break; | |
} | |
if ((t == target || (target2 != NONE && t == target2)) | |
&& braceCount <= 0 | |
&& brackCount <= 0 | |
&& parenCount <= 0) | |
return true; | |
if (braceCount < 0 || brackCount < 0 || parenCount < 0) { | |
--index; | |
break; | |
} | |
} | |
return false; | |
} | |
bool Parser::testTokenAndEndsWith(QCss::TokenType t, const QLatin1String &str) | |
{ | |
if (!test(t)) return false; | |
if (!lexem().endsWith(str, Qt::CaseInsensitive)) { | |
prev(); | |
return false; | |
} | |
return true; | |
} | |
QT_END_NAMESPACE | |
#endif // QT_NO_CSSPARSER |