1 /***************************************************************************
2 * Copyright (C) 2005-2016 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <QApplication>
23 #include "qssparser.h"
25 QssParser::QssParser()
27 _palette = QApplication::palette();
29 // Init palette color roles
30 _paletteColorRoles["alternate-base"] = QPalette::AlternateBase;
31 _paletteColorRoles["background"] = QPalette::Background;
32 _paletteColorRoles["base"] = QPalette::Base;
33 _paletteColorRoles["bright-text"] = QPalette::BrightText;
34 _paletteColorRoles["button"] = QPalette::Button;
35 _paletteColorRoles["button-text"] = QPalette::ButtonText;
36 _paletteColorRoles["dark"] = QPalette::Dark;
37 _paletteColorRoles["foreground"] = QPalette::Foreground;
38 _paletteColorRoles["highlight"] = QPalette::Highlight;
39 _paletteColorRoles["highlighted-text"] = QPalette::HighlightedText;
40 _paletteColorRoles["light"] = QPalette::Light;
41 _paletteColorRoles["link"] = QPalette::Link;
42 _paletteColorRoles["link-visited"] = QPalette::LinkVisited;
43 _paletteColorRoles["mid"] = QPalette::Mid;
44 _paletteColorRoles["midlight"] = QPalette::Midlight;
45 _paletteColorRoles["shadow"] = QPalette::Shadow;
46 _paletteColorRoles["text"] = QPalette::Text;
47 _paletteColorRoles["tooltip-base"] = QPalette::ToolTipBase;
48 _paletteColorRoles["tooltip-text"] = QPalette::ToolTipText;
49 _paletteColorRoles["window"] = QPalette::Window;
50 _paletteColorRoles["window-text"] = QPalette::WindowText;
52 _uiStylePalette = QVector<QBrush>(UiStyle::NumRoles, QBrush());
54 _uiStyleColorRoles["marker-line"] = UiStyle::MarkerLine;
56 _uiStyleColorRoles["sender-color-self"] = UiStyle::SenderColorSelf;
57 _uiStyleColorRoles["sender-color-00"] = UiStyle::SenderColor00;
58 _uiStyleColorRoles["sender-color-01"] = UiStyle::SenderColor01;
59 _uiStyleColorRoles["sender-color-02"] = UiStyle::SenderColor02;
60 _uiStyleColorRoles["sender-color-03"] = UiStyle::SenderColor03;
61 _uiStyleColorRoles["sender-color-04"] = UiStyle::SenderColor04;
62 _uiStyleColorRoles["sender-color-05"] = UiStyle::SenderColor05;
63 _uiStyleColorRoles["sender-color-06"] = UiStyle::SenderColor06;
64 _uiStyleColorRoles["sender-color-07"] = UiStyle::SenderColor07;
65 _uiStyleColorRoles["sender-color-08"] = UiStyle::SenderColor08;
66 _uiStyleColorRoles["sender-color-09"] = UiStyle::SenderColor09;
67 _uiStyleColorRoles["sender-color-0a"] = UiStyle::SenderColor0a;
68 _uiStyleColorRoles["sender-color-0b"] = UiStyle::SenderColor0b;
69 _uiStyleColorRoles["sender-color-0c"] = UiStyle::SenderColor0c;
70 _uiStyleColorRoles["sender-color-0d"] = UiStyle::SenderColor0d;
71 _uiStyleColorRoles["sender-color-0e"] = UiStyle::SenderColor0e;
72 _uiStyleColorRoles["sender-color-0f"] = UiStyle::SenderColor0f;
76 void QssParser::processStyleSheet(QString &ss)
81 // Remove C-style comments /* */ or //
82 QRegExp commentRx("(//.*(\\n|$)|/\\*.*\\*/)");
83 commentRx.setMinimal(true);
86 // Palette definitions first, so we can apply roles later on
87 QRegExp paletterx("(Palette[^{]*)\\{([^}]+)\\}");
89 while ((pos = paletterx.indexIn(ss, pos)) >= 0) {
90 parsePaletteBlock(paletterx.cap(1).trimmed(), paletterx.cap(2).trimmed());
91 ss.remove(pos, paletterx.matchedLength());
94 // Now we can parse the rest of our custom blocks
95 QRegExp blockrx("((?:ChatLine|ChatListItem|NickListItem)[^{]*)\\{([^}]+)\\}");
97 while ((pos = blockrx.indexIn(ss, pos)) >= 0) {
98 //qDebug() << blockrx.cap(1) << blockrx.cap(2);
99 QString declaration = blockrx.cap(1).trimmed();
100 QString contents = blockrx.cap(2).trimmed();
102 if (declaration.startsWith("ChatLine"))
103 parseChatLineBlock(declaration, contents);
104 else if (declaration.startsWith("ChatListItem") || declaration.startsWith("NickListItem"))
105 parseListItemBlock(declaration, contents);
107 // TODO: add moar here
109 ss.remove(pos, blockrx.matchedLength());
114 /******** Parse a whole block: declaration { contents } *******/
116 void QssParser::parseChatLineBlock(const QString &decl, const QString &contents)
118 quint64 fmtType = parseFormatType(decl);
119 if (fmtType == UiStyle::Invalid)
122 _formats[fmtType].merge(parseFormat(contents));
126 void QssParser::parseListItemBlock(const QString &decl, const QString &contents)
128 quint32 fmtType = parseItemFormatType(decl);
129 if (fmtType == UiStyle::Invalid)
132 _listItemFormats[fmtType].merge(parseFormat(contents));
136 // Palette { ... } specifies the application palette
137 // ColorGroups can be specified like pseudo states, chaining is OR (contrary to normal CSS handling):
138 // Palette:inactive:disabled { ... } applies to both the Inactive and the Disabled state
139 void QssParser::parsePaletteBlock(const QString &decl, const QString &contents)
141 QList<QPalette::ColorGroup> colorGroups;
143 // Check if we want to apply this palette definition for particular ColorGroups
144 QRegExp rx("Palette((:(normal|active|inactive|disabled))*)");
145 if (!rx.exactMatch(decl)) {
146 qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
149 if (!rx.cap(1).isEmpty()) {
150 QStringList groups = rx.cap(1).split(':', QString::SkipEmptyParts);
151 foreach(QString g, groups) {
152 if ((g == "normal" || g == "active") && !colorGroups.contains(QPalette::Active))
153 colorGroups.append(QPalette::Active);
154 else if (g == "inactive" && !colorGroups.contains(QPalette::Inactive))
155 colorGroups.append(QPalette::Inactive);
156 else if (g == "disabled" && !colorGroups.contains(QPalette::Disabled))
157 colorGroups.append(QPalette::Disabled);
161 // Now let's go through the roles
162 foreach(QString line, contents.split(';', QString::SkipEmptyParts)) {
163 int idx = line.indexOf(':');
165 qWarning() << Q_FUNC_INFO << tr("Invalid palette role assignment: %1").arg(line.trimmed());
168 QString rolestr = line.left(idx).trimmed();
169 QString brushstr = line.mid(idx + 1).trimmed();
171 if (_paletteColorRoles.contains(rolestr)) {
172 QBrush brush = parseBrush(brushstr);
173 if (colorGroups.count()) {
174 foreach(QPalette::ColorGroup group, colorGroups)
175 _palette.setBrush(group, _paletteColorRoles.value(rolestr), brush);
178 _palette.setBrush(_paletteColorRoles.value(rolestr), brush);
180 else if (_uiStyleColorRoles.contains(rolestr)) {
181 _uiStylePalette[_uiStyleColorRoles.value(rolestr)] = parseBrush(brushstr);
184 qWarning() << Q_FUNC_INFO << tr("Unknown palette role name: %1").arg(rolestr);
189 /******** Determine format types from a block declaration ********/
191 quint64 QssParser::parseFormatType(const QString &decl)
193 QRegExp rx("ChatLine(?:::(\\w+))?(?:#([\\w\\-]+))?(?:\\[([=-,\\\"\\w\\s]+)\\])?");
194 // $1: subelement; $2: msgtype; $3: conditionals
195 if (!rx.exactMatch(decl)) {
196 qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
197 return UiStyle::Invalid;
199 QString subElement = rx.cap(1);
200 QString msgType = rx.cap(2);
201 QString conditions = rx.cap(3);
205 // First determine the subelement
206 if (!subElement.isEmpty()) {
207 if (subElement == "timestamp")
208 fmtType |= UiStyle::Timestamp;
209 else if (subElement == "sender")
210 fmtType |= UiStyle::Sender;
211 else if (subElement == "nick")
212 fmtType |= UiStyle::Nick;
213 else if (subElement == "contents")
214 fmtType |= UiStyle::Contents;
215 else if (subElement == "hostmask")
216 fmtType |= UiStyle::Hostmask;
217 else if (subElement == "modeflags")
218 fmtType |= UiStyle::ModeFlags;
219 else if (subElement == "url")
220 fmtType |= UiStyle::Url;
222 qWarning() << Q_FUNC_INFO << tr("Invalid subelement name in %1").arg(decl);
223 return UiStyle::Invalid;
227 // Now, figure out the message type
228 if (!msgType.isEmpty()) {
229 if (msgType == "plain")
230 fmtType |= UiStyle::PlainMsg;
231 else if (msgType == "notice")
232 fmtType |= UiStyle::NoticeMsg;
233 else if (msgType == "action")
234 fmtType |= UiStyle::ActionMsg;
235 else if (msgType == "nick")
236 fmtType |= UiStyle::NickMsg;
237 else if (msgType == "mode")
238 fmtType |= UiStyle::ModeMsg;
239 else if (msgType == "join")
240 fmtType |= UiStyle::JoinMsg;
241 else if (msgType == "part")
242 fmtType |= UiStyle::PartMsg;
243 else if (msgType == "quit")
244 fmtType |= UiStyle::QuitMsg;
245 else if (msgType == "kick")
246 fmtType |= UiStyle::KickMsg;
247 else if (msgType == "kill")
248 fmtType |= UiStyle::KillMsg;
249 else if (msgType == "server")
250 fmtType |= UiStyle::ServerMsg;
251 else if (msgType == "info")
252 fmtType |= UiStyle::InfoMsg;
253 else if (msgType == "error")
254 fmtType |= UiStyle::ErrorMsg;
255 else if (msgType == "daychange")
256 fmtType |= UiStyle::DayChangeMsg;
257 else if (msgType == "topic")
258 fmtType |= UiStyle::TopicMsg;
259 else if (msgType == "netsplit-join")
260 fmtType |= UiStyle::NetsplitJoinMsg;
261 else if (msgType == "netsplit-quit")
262 fmtType |= UiStyle::NetsplitQuitMsg;
263 else if (msgType == "invite")
264 fmtType |= UiStyle::InviteMsg;
266 qWarning() << Q_FUNC_INFO << tr("Invalid message type in %1").arg(decl);
270 // Next up: conditional (formats, labels, nickhash)
271 QRegExp condRx("\\s*([\\w\\-]+)\\s*=\\s*\"(\\w+)\"\\s*");
272 if (!conditions.isEmpty()) {
273 foreach(const QString &cond, conditions.split(',', QString::SkipEmptyParts)) {
274 if (!condRx.exactMatch(cond)) {
275 qWarning() << Q_FUNC_INFO << tr("Invalid condition %1").arg(cond);
276 return UiStyle::Invalid;
278 QString condName = condRx.cap(1);
279 QString condValue = condRx.cap(2);
280 if (condName == "label") {
281 quint64 labeltype = 0;
282 if (condValue == "highlight")
283 labeltype = UiStyle::Highlight;
284 else if (condValue == "selected")
285 labeltype = UiStyle::Selected;
287 qWarning() << Q_FUNC_INFO << tr("Invalid message label: %1").arg(condValue);
288 return UiStyle::Invalid;
290 fmtType |= (labeltype << 32);
292 else if (condName == "sender") {
293 if (condValue == "self")
294 fmtType |= (quint64) UiStyle::OwnMsg << 32; // sender="self" is actually treated as a label
297 quint64 val = condValue.toUInt(&ok, 16);
299 qWarning() << Q_FUNC_INFO << tr("Invalid senderhash specification: %1").arg(condValue);
300 return UiStyle::Invalid;
303 qWarning() << Q_FUNC_INFO << tr("Senderhash can be at most \"0x0f\"!");
304 return UiStyle::Invalid;
306 fmtType |= ++val << 48;
309 else if (condName == "format") {
310 if (condValue == "bold")
311 fmtType |= UiStyle::Bold;
312 else if (condValue == "italic")
313 fmtType |= UiStyle::Italic;
314 else if (condValue == "underline")
315 fmtType |= UiStyle::Underline;
316 else if (condValue == "reverse")
317 fmtType |= UiStyle::Reverse;
319 qWarning() << Q_FUNC_INFO << tr("Invalid format name: %1").arg(condValue);
320 return UiStyle::Invalid;
323 else if (condName == "fg-color" || condName == "bg-color") {
325 quint8 col = condValue.toUInt(&ok, 16);
326 if (!ok || col > 0x0f) {
327 qWarning() << Q_FUNC_INFO << tr("Illegal IRC color specification (must be between 00 and 0f): %1").arg(condValue);
328 return UiStyle::Invalid;
330 if (condName == "fg-color")
331 fmtType |= 0x00400000 | (quint32)(col << 24);
333 fmtType |= 0x00800000 | (quint32)(col << 28);
336 qWarning() << Q_FUNC_INFO << tr("Unhandled condition: %1").arg(condName);
337 return UiStyle::Invalid;
346 // FIXME: Code duplication
347 quint32 QssParser::parseItemFormatType(const QString &decl)
349 QRegExp rx("(Chat|Nick)ListItem(?:\\[([=-,\\\"\\w\\s]+)\\])?");
350 // $1: item type; $2: properties
351 if (!rx.exactMatch(decl)) {
352 qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
353 return UiStyle::Invalid;
355 QString mainItemType = rx.cap(1);
356 QString properties = rx.cap(2);
360 // Next up: properties
362 if (!properties.isEmpty()) {
363 QHash<QString, QString> props;
364 QRegExp propRx("\\s*([\\w\\-]+)\\s*=\\s*\"([\\w\\-]+)\"\\s*");
365 foreach(const QString &prop, properties.split(',', QString::SkipEmptyParts)) {
366 if (!propRx.exactMatch(prop)) {
367 qWarning() << Q_FUNC_INFO << tr("Invalid proplist %1").arg(prop);
368 return UiStyle::Invalid;
370 props[propRx.cap(1)] = propRx.cap(2);
372 type = props.value("type");
373 state = props.value("state");
376 if (mainItemType == "Chat") {
377 fmtType |= UiStyle::BufferViewItem;
378 if (!type.isEmpty()) {
379 if (type == "network")
380 fmtType |= UiStyle::NetworkItem;
381 else if (type == "channel")
382 fmtType |= UiStyle::ChannelBufferItem;
383 else if (type == "query")
384 fmtType |= UiStyle::QueryBufferItem;
386 qWarning() << Q_FUNC_INFO << tr("Invalid chatlist item type %1").arg(type);
387 return UiStyle::Invalid;
390 if (!state.isEmpty()) {
391 if (state == "inactive")
392 fmtType |= UiStyle::InactiveBuffer;
393 else if (state == "channel-event")
394 fmtType |= UiStyle::ActiveBuffer;
395 else if (state == "unread-message")
396 fmtType |= UiStyle::UnreadBuffer;
397 else if (state == "highlighted")
398 fmtType |= UiStyle::HighlightedBuffer;
399 else if (state == "away")
400 fmtType |= UiStyle::UserAway;
402 qWarning() << Q_FUNC_INFO << tr("Invalid chatlist state %1").arg(state);
403 return UiStyle::Invalid;
408 fmtType |= UiStyle::NickViewItem;
409 if (!type.isEmpty()) {
410 if (type == "user") {
411 fmtType |= UiStyle::IrcUserItem;
413 fmtType |= UiStyle::UserAway;
415 else if (type == "category")
416 fmtType |= UiStyle::UserCategoryItem;
423 /******** Parse a whole format attribute block ********/
425 QTextCharFormat QssParser::parseFormat(const QString &qss)
427 QTextCharFormat format;
429 foreach(QString line, qss.split(';', QString::SkipEmptyParts)) {
430 int idx = line.indexOf(':');
432 qWarning() << Q_FUNC_INFO << tr("Invalid property declaration: %1").arg(line.trimmed());
435 QString property = line.left(idx).trimmed();
436 QString value = line.mid(idx + 1).simplified();
438 if (property == "background" || property == "background-color")
439 format.setBackground(parseBrush(value));
440 else if (property == "foreground" || property == "color")
441 format.setForeground(parseBrush(value));
443 // font-related properties
444 else if (property.startsWith("font")) {
445 if (property == "font")
446 parseFont(value, &format);
447 else if (property == "font-style")
448 parseFontStyle(value, &format);
449 else if (property == "font-weight")
450 parseFontWeight(value, &format);
451 else if (property == "font-size")
452 parseFontSize(value, &format);
453 else if (property == "font-family")
454 parseFontFamily(value, &format);
456 qWarning() << Q_FUNC_INFO << tr("Invalid font property: %1").arg(line);
462 qWarning() << Q_FUNC_INFO << tr("Unknown ChatLine property: %1").arg(property);
470 /******** Brush ********/
472 QBrush QssParser::parseBrush(const QString &str, bool *ok)
476 QColor c = parseColor(str);
483 if (str.startsWith("palette")) { // Palette color role
484 // Does the palette follow the expected format? For example:
485 // palette(marker-line)
486 // palette ( system-color-0f )
488 // Match the palette marker, grabbing the name inside in case-sensitive manner
489 // palette\s*\(\s*([a-z-0-9]+)\s*\)
490 // palette Match the string 'palette'
491 // \s* Match any amount of whitespace
492 // \(, \) Match literal '(' or ')' marks
493 // (...+) Match contents between 1 and unlimited number of times
494 // [a-z-] Match any character from a-z, case sensitive
495 // [0-9] Match any digit from 0-9
496 // Note that '\' must be escaped as '\\'
497 // Helpful interactive website for debugging and explaining: https://regex101.com/
498 QRegExp rx("palette\\s*\\(\\s*([a-z-0-9]+)\\s*\\)");
499 if (!rx.exactMatch(str)) {
500 qWarning() << Q_FUNC_INFO << tr("Invalid palette color role specification: %1").arg(str);
503 if (_paletteColorRoles.contains(rx.cap(1)))
504 return QBrush(_palette.brush(_paletteColorRoles.value(rx.cap(1))));
505 if (_uiStyleColorRoles.contains(rx.cap(1)))
506 return QBrush(_uiStylePalette.at(_uiStyleColorRoles.value(rx.cap(1))));
507 qWarning() << Q_FUNC_INFO << tr("Unknown palette color role: %1").arg(rx.cap(1));
510 else if (str.startsWith("qlineargradient")) {
511 static QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*");
512 QRegExp rx(QString("qlineargradient\\s*\\(\\s*x1:%1,\\s*y1:%1,\\s*x2:%1,\\s*y2:%1,(.+)\\)").arg(rxFloat));
513 if (!rx.exactMatch(str)) {
514 qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1").arg(str);
517 qreal x1 = rx.cap(1).toDouble();
518 qreal y1 = rx.cap(2).toDouble();
519 qreal x2 = rx.cap(3).toDouble();
520 qreal y2 = rx.cap(4).toDouble();
521 QGradientStops stops = parseGradientStops(rx.cap(5).trimmed());
522 if (!stops.count()) {
523 qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1").arg(str);
526 QLinearGradient gradient(x1, y1, x2, y2);
527 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
528 gradient.setStops(stops);
531 return QBrush(gradient);
533 else if (str.startsWith("qconicalgradient")) {
534 static QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*");
535 QRegExp rx(QString("qconicalgradient\\s*\\(\\s*cx:%1,\\s*cy:%1,\\s*angle:%1,(.+)\\)").arg(rxFloat));
536 if (!rx.exactMatch(str)) {
537 qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1").arg(str);
540 qreal cx = rx.cap(1).toDouble();
541 qreal cy = rx.cap(2).toDouble();
542 qreal angle = rx.cap(3).toDouble();
543 QGradientStops stops = parseGradientStops(rx.cap(4).trimmed());
544 if (!stops.count()) {
545 qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1").arg(str);
548 QConicalGradient gradient(cx, cy, angle);
549 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
550 gradient.setStops(stops);
553 return QBrush(gradient);
555 else if (str.startsWith("qradialgradient")) {
556 static QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*");
557 QRegExp rx(QString("qradialgradient\\s*\\(\\s*cx:%1,\\s*cy:%1,\\s*radius:%1,\\s*fx:%1,\\s*fy:%1,(.+)\\)").arg(rxFloat));
558 if (!rx.exactMatch(str)) {
559 qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1").arg(str);
562 qreal cx = rx.cap(1).toDouble();
563 qreal cy = rx.cap(2).toDouble();
564 qreal radius = rx.cap(3).toDouble();
565 qreal fx = rx.cap(4).toDouble();
566 qreal fy = rx.cap(5).toDouble();
567 QGradientStops stops = parseGradientStops(rx.cap(6).trimmed());
568 if (!stops.count()) {
569 qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1").arg(str);
572 QRadialGradient gradient(cx, cy, radius, fx, fy);
573 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
574 gradient.setStops(stops);
577 return QBrush(gradient);
584 QColor QssParser::parseColor(const QString &str)
586 if (str.startsWith("rgba")) {
587 ColorTuple tuple = parseColorTuple(str.mid(4));
588 if (tuple.count() == 4)
589 return QColor(tuple.at(0), tuple.at(1), tuple.at(2), tuple.at(3));
591 else if (str.startsWith("rgb")) {
592 ColorTuple tuple = parseColorTuple(str.mid(3));
593 if (tuple.count() == 3)
594 return QColor(tuple.at(0), tuple.at(1), tuple.at(2));
596 else if (str.startsWith("hsva")) {
597 ColorTuple tuple = parseColorTuple(str.mid(4));
598 if (tuple.count() == 4) {
600 c.setHsvF(tuple.at(0), tuple.at(1), tuple.at(2), tuple.at(3));
604 else if (str.startsWith("hsv")) {
605 ColorTuple tuple = parseColorTuple(str.mid(3));
606 if (tuple.count() == 3) {
608 c.setHsvF(tuple.at(0), tuple.at(1), tuple.at(2));
613 QRegExp rx("#?[0-9A-Fa-z]+");
614 if (rx.exactMatch(str))
621 // get a list of comma-separated int values or percentages (rel to 0-255)
622 QssParser::ColorTuple QssParser::parseColorTuple(const QString &str)
625 QRegExp rx("\\(((\\s*[0-9]{1,3}%?\\s*)(,\\s*[0-9]{1,3}%?\\s*)*)\\)");
626 if (!rx.exactMatch(str.trimmed())) {
629 QStringList values = rx.cap(1).split(',');
630 foreach(QString v, values) {
635 if (v.endsWith('%')) {
639 val = (qreal)v.toUInt(&ok);
650 QGradientStops QssParser::parseGradientStops(const QString &str_)
653 QGradientStops result;
654 static QString rxFloat("(0?\\.[0-9]+|[01])"); // values between 0 and 1
655 QRegExp rx(QString("\\s*,?\\s*stop:\\s*(%1)\\s+([^:]+)(,\\s*stop:|$)").arg(rxFloat));
657 while ((idx = rx.indexIn(str)) == 0) {
658 qreal x = rx.cap(1).toDouble();
659 QColor c = parseColor(rx.cap(3));
661 return QGradientStops();
662 result << QGradientStop(x, c);
663 str.remove(0, rx.matchedLength() - rx.cap(4).length());
665 if (!str.trimmed().isEmpty())
666 return QGradientStops();
672 /******** Font Properties ********/
674 void QssParser::parseFont(const QString &value, QTextCharFormat *format)
676 QRegExp rx("((?:(?:normal|italic|oblique|underline|bold|100|200|300|400|500|600|700|800|900) ){0,2}) ?(\\d+)(pt|px)? \"(.*)\"");
677 if (!rx.exactMatch(value)) {
678 qWarning() << Q_FUNC_INFO << tr("Invalid font specification: %1").arg(value);
681 format->setFontItalic(false);
682 format->setFontWeight(QFont::Normal);
683 QStringList proplist = rx.cap(1).split(' ', QString::SkipEmptyParts);
684 foreach(QString prop, proplist) {
685 if (prop == "italic")
686 format->setFontItalic(true);
687 else if (prop == "underline")
688 format->setFontUnderline(true);
689 //else if(prop == "oblique")
690 // format->setStyle(QFont::StyleOblique);
691 else if (prop == "bold")
692 format->setFontWeight(QFont::Bold);
694 int w = prop.toInt();
695 format->setFontWeight(qMin(w / 8, 99)); // taken from Qt's qss parser
699 if (rx.cap(3) == "px")
700 format->setProperty(QTextFormat::FontPixelSize, rx.cap(2).toInt());
702 format->setFontPointSize(rx.cap(2).toInt());
704 format->setFontFamily(rx.cap(4));
708 void QssParser::parseFontStyle(const QString &value, QTextCharFormat *format)
710 if (value == "normal")
711 format->setFontItalic(false);
712 else if (value == "italic")
713 format->setFontItalic(true);
714 else if (value == "underline")
715 format->setFontUnderline(true);
716 //else if(value == "oblique")
717 // format->setStyle(QFont::StyleOblique);
719 qWarning() << Q_FUNC_INFO << tr("Invalid font style specification: %1").arg(value);
724 void QssParser::parseFontWeight(const QString &value, QTextCharFormat *format)
726 if (value == "normal")
727 format->setFontWeight(QFont::Normal);
728 else if (value == "bold")
729 format->setFontWeight(QFont::Bold);
732 int w = value.toInt(&ok);
734 qWarning() << Q_FUNC_INFO << tr("Invalid font weight specification: %1").arg(value);
737 format->setFontWeight(qMin(w / 8, 99)); // taken from Qt's qss parser
742 void QssParser::parseFontSize(const QString &value, QTextCharFormat *format)
744 QRegExp rx("(\\d+)(pt|px)");
745 if (!rx.exactMatch(value)) {
746 qWarning() << Q_FUNC_INFO << tr("Invalid font size specification: %1").arg(value);
749 if (rx.cap(2) == "px")
750 format->setProperty(QTextFormat::FontPixelSize, rx.cap(1).toInt());
752 format->setFontPointSize(rx.cap(1).toInt());
756 void QssParser::parseFontFamily(const QString &value, QTextCharFormat *format)
758 QString family = value;
759 if (family.startsWith('"') && family.endsWith('"')) {
760 family = family.mid(1, family.length() - 2);
762 format->setFontFamily(family);