1 /***************************************************************************
2 * Copyright (C) 2005-2018 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 ***************************************************************************/
24 #include <QApplication>
26 #include "qssparser.h"
28 QssParser::QssParser()
30 _palette = QApplication::palette();
32 // Init palette color roles
33 _paletteColorRoles["alternate-base"] = QPalette::AlternateBase;
34 _paletteColorRoles["background"] = QPalette::Background;
35 _paletteColorRoles["base"] = QPalette::Base;
36 _paletteColorRoles["bright-text"] = QPalette::BrightText;
37 _paletteColorRoles["button"] = QPalette::Button;
38 _paletteColorRoles["button-text"] = QPalette::ButtonText;
39 _paletteColorRoles["dark"] = QPalette::Dark;
40 _paletteColorRoles["foreground"] = QPalette::Foreground;
41 _paletteColorRoles["highlight"] = QPalette::Highlight;
42 _paletteColorRoles["highlighted-text"] = QPalette::HighlightedText;
43 _paletteColorRoles["light"] = QPalette::Light;
44 _paletteColorRoles["link"] = QPalette::Link;
45 _paletteColorRoles["link-visited"] = QPalette::LinkVisited;
46 _paletteColorRoles["mid"] = QPalette::Mid;
47 _paletteColorRoles["midlight"] = QPalette::Midlight;
48 _paletteColorRoles["shadow"] = QPalette::Shadow;
49 _paletteColorRoles["text"] = QPalette::Text;
50 _paletteColorRoles["tooltip-base"] = QPalette::ToolTipBase;
51 _paletteColorRoles["tooltip-text"] = QPalette::ToolTipText;
52 _paletteColorRoles["window"] = QPalette::Window;
53 _paletteColorRoles["window-text"] = QPalette::WindowText;
55 _uiStylePalette = QVector<QBrush>(static_cast<int>(UiStyle::ColorRole::NumRoles), QBrush());
57 _uiStyleColorRoles["marker-line"] = UiStyle::ColorRole::MarkerLine;
59 _uiStyleColorRoles["sender-color-self"] = UiStyle::ColorRole::SenderColorSelf;
60 _uiStyleColorRoles["sender-color-00"] = UiStyle::ColorRole::SenderColor00;
61 _uiStyleColorRoles["sender-color-01"] = UiStyle::ColorRole::SenderColor01;
62 _uiStyleColorRoles["sender-color-02"] = UiStyle::ColorRole::SenderColor02;
63 _uiStyleColorRoles["sender-color-03"] = UiStyle::ColorRole::SenderColor03;
64 _uiStyleColorRoles["sender-color-04"] = UiStyle::ColorRole::SenderColor04;
65 _uiStyleColorRoles["sender-color-05"] = UiStyle::ColorRole::SenderColor05;
66 _uiStyleColorRoles["sender-color-06"] = UiStyle::ColorRole::SenderColor06;
67 _uiStyleColorRoles["sender-color-07"] = UiStyle::ColorRole::SenderColor07;
68 _uiStyleColorRoles["sender-color-08"] = UiStyle::ColorRole::SenderColor08;
69 _uiStyleColorRoles["sender-color-09"] = UiStyle::ColorRole::SenderColor09;
70 _uiStyleColorRoles["sender-color-0a"] = UiStyle::ColorRole::SenderColor0a;
71 _uiStyleColorRoles["sender-color-0b"] = UiStyle::ColorRole::SenderColor0b;
72 _uiStyleColorRoles["sender-color-0c"] = UiStyle::ColorRole::SenderColor0c;
73 _uiStyleColorRoles["sender-color-0d"] = UiStyle::ColorRole::SenderColor0d;
74 _uiStyleColorRoles["sender-color-0e"] = UiStyle::ColorRole::SenderColor0e;
75 _uiStyleColorRoles["sender-color-0f"] = UiStyle::ColorRole::SenderColor0f;
79 void QssParser::processStyleSheet(QString &ss)
84 // Remove C-style comments /* */ or //
85 static QRegExp commentRx("(//.*(\\n|$)|/\\*.*\\*/)");
86 commentRx.setMinimal(true);
89 // Palette definitions first, so we can apply roles later on
90 static const QRegExp paletterx("(Palette[^{]*)\\{([^}]+)\\}");
92 while ((pos = paletterx.indexIn(ss, pos)) >= 0) {
93 parsePaletteBlock(paletterx.cap(1).trimmed(), paletterx.cap(2).trimmed());
94 ss.remove(pos, paletterx.matchedLength());
97 // Now we can parse the rest of our custom blocks
98 static const QRegExp blockrx("((?:ChatLine|ChatListItem|NickListItem)[^{]*)\\{([^}]+)\\}");
100 while ((pos = blockrx.indexIn(ss, pos)) >= 0) {
101 //qDebug() << blockrx.cap(1) << blockrx.cap(2);
102 QString declaration = blockrx.cap(1).trimmed();
103 QString contents = blockrx.cap(2).trimmed();
105 if (declaration.startsWith("ChatLine"))
106 parseChatLineBlock(declaration, contents);
107 else if (declaration.startsWith("ChatListItem") || declaration.startsWith("NickListItem"))
108 parseListItemBlock(declaration, contents);
110 // TODO: add moar here
112 ss.remove(pos, blockrx.matchedLength());
117 /******** Parse a whole block: declaration { contents } *******/
119 void QssParser::parseChatLineBlock(const QString &decl, const QString &contents)
121 UiStyle::FormatType fmtType;
122 UiStyle::MessageLabel label;
123 std::tie(fmtType, label) = parseFormatType(decl);
124 if (fmtType == UiStyle::FormatType::Invalid)
127 _formats[fmtType|label].merge(parseFormat(contents));
131 void QssParser::parseListItemBlock(const QString &decl, const QString &contents)
133 UiStyle::ItemFormatType fmtType = parseItemFormatType(decl);
134 if (fmtType == UiStyle::ItemFormatType::Invalid)
137 _listItemFormats[fmtType].merge(parseFormat(contents));
141 // Palette { ... } specifies the application palette
142 // ColorGroups can be specified like pseudo states, chaining is OR (contrary to normal CSS handling):
143 // Palette:inactive:disabled { ... } applies to both the Inactive and the Disabled state
144 void QssParser::parsePaletteBlock(const QString &decl, const QString &contents)
146 QList<QPalette::ColorGroup> colorGroups;
148 // Check if we want to apply this palette definition for particular ColorGroups
149 static const QRegExp rx("Palette((:(normal|active|inactive|disabled))*)");
150 if (!rx.exactMatch(decl)) {
151 qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
154 if (!rx.cap(1).isEmpty()) {
155 QStringList groups = rx.cap(1).split(':', QString::SkipEmptyParts);
156 foreach(QString g, groups) {
157 if ((g == "normal" || g == "active") && !colorGroups.contains(QPalette::Active))
158 colorGroups.append(QPalette::Active);
159 else if (g == "inactive" && !colorGroups.contains(QPalette::Inactive))
160 colorGroups.append(QPalette::Inactive);
161 else if (g == "disabled" && !colorGroups.contains(QPalette::Disabled))
162 colorGroups.append(QPalette::Disabled);
166 // Now let's go through the roles
167 foreach(QString line, contents.split(';', QString::SkipEmptyParts)) {
168 int idx = line.indexOf(':');
170 qWarning() << Q_FUNC_INFO << tr("Invalid palette role assignment: %1").arg(line.trimmed());
173 QString rolestr = line.left(idx).trimmed();
174 QString brushstr = line.mid(idx + 1).trimmed();
176 if (_paletteColorRoles.contains(rolestr)) {
177 QBrush brush = parseBrush(brushstr);
178 if (colorGroups.count()) {
179 foreach(QPalette::ColorGroup group, colorGroups)
180 _palette.setBrush(group, _paletteColorRoles.value(rolestr), brush);
183 _palette.setBrush(_paletteColorRoles.value(rolestr), brush);
185 else if (_uiStyleColorRoles.contains(rolestr)) {
186 _uiStylePalette[static_cast<int>(_uiStyleColorRoles.value(rolestr))] = parseBrush(brushstr);
189 qWarning() << Q_FUNC_INFO << tr("Unknown palette role name: %1").arg(rolestr);
194 /******** Determine format types from a block declaration ********/
196 std::pair<UiStyle::FormatType, UiStyle::MessageLabel> QssParser::parseFormatType(const QString &decl)
198 using FormatType = UiStyle::FormatType;
199 using MessageLabel = UiStyle::MessageLabel;
201 const std::pair<UiStyle::FormatType, UiStyle::MessageLabel> invalid{FormatType::Invalid, MessageLabel::None};
203 static const QRegExp rx("ChatLine(?:::(\\w+))?(?:#([\\w\\-]+))?(?:\\[([=-,\\\"\\w\\s]+)\\])?");
204 // $1: subelement; $2: msgtype; $3: conditionals
205 if (!rx.exactMatch(decl)) {
206 qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
209 QString subElement = rx.cap(1);
210 QString msgType = rx.cap(2);
211 QString conditions = rx.cap(3);
213 FormatType fmtType{FormatType::Base};
214 MessageLabel label{MessageLabel::None};
216 // First determine the subelement
217 if (!subElement.isEmpty()) {
218 if (subElement == "timestamp")
219 fmtType |= FormatType::Timestamp;
220 else if (subElement == "sender")
221 fmtType |= FormatType::Sender;
222 else if (subElement == "nick")
223 fmtType |= FormatType::Nick;
224 else if (subElement == "contents")
225 fmtType |= FormatType::Contents;
226 else if (subElement == "hostmask")
227 fmtType |= FormatType::Hostmask;
228 else if (subElement == "modeflags")
229 fmtType |= FormatType::ModeFlags;
230 else if (subElement == "url")
231 fmtType |= FormatType::Url;
233 qWarning() << Q_FUNC_INFO << tr("Invalid subelement name in %1").arg(decl);
238 // Now, figure out the message type
239 if (!msgType.isEmpty()) {
240 if (msgType == "plain")
241 fmtType |= FormatType::PlainMsg;
242 else if (msgType == "notice")
243 fmtType |= FormatType::NoticeMsg;
244 else if (msgType == "action")
245 fmtType |= FormatType::ActionMsg;
246 else if (msgType == "nick")
247 fmtType |= FormatType::NickMsg;
248 else if (msgType == "mode")
249 fmtType |= FormatType::ModeMsg;
250 else if (msgType == "join")
251 fmtType |= FormatType::JoinMsg;
252 else if (msgType == "part")
253 fmtType |= FormatType::PartMsg;
254 else if (msgType == "quit")
255 fmtType |= FormatType::QuitMsg;
256 else if (msgType == "kick")
257 fmtType |= FormatType::KickMsg;
258 else if (msgType == "kill")
259 fmtType |= FormatType::KillMsg;
260 else if (msgType == "server")
261 fmtType |= FormatType::ServerMsg;
262 else if (msgType == "info")
263 fmtType |= FormatType::InfoMsg;
264 else if (msgType == "error")
265 fmtType |= FormatType::ErrorMsg;
266 else if (msgType == "daychange")
267 fmtType |= FormatType::DayChangeMsg;
268 else if (msgType == "topic")
269 fmtType |= FormatType::TopicMsg;
270 else if (msgType == "netsplit-join")
271 fmtType |= FormatType::NetsplitJoinMsg;
272 else if (msgType == "netsplit-quit")
273 fmtType |= FormatType::NetsplitQuitMsg;
274 else if (msgType == "invite")
275 fmtType |= FormatType::InviteMsg;
277 qWarning() << Q_FUNC_INFO << tr("Invalid message type in %1").arg(decl);
281 // Next up: conditional (formats, labels, nickhash)
282 static const QRegExp condRx("\\s*([\\w\\-]+)\\s*=\\s*\"(\\w+)\"\\s*");
283 if (!conditions.isEmpty()) {
284 foreach(const QString &cond, conditions.split(',', QString::SkipEmptyParts)) {
285 if (!condRx.exactMatch(cond)) {
286 qWarning() << Q_FUNC_INFO << tr("Invalid condition %1").arg(cond);
289 QString condName = condRx.cap(1);
290 QString condValue = condRx.cap(2);
291 if (condName == "label") {
292 if (condValue == "highlight")
293 label |= MessageLabel::Highlight;
294 else if (condValue == "selected")
295 label |= MessageLabel::Selected;
297 qWarning() << Q_FUNC_INFO << tr("Invalid message label: %1").arg(condValue);
301 else if (condName == "sender") {
302 if (condValue == "self")
303 label |= MessageLabel::OwnMsg; // sender="self" is actually treated as a label
306 quint32 val = condValue.toUInt(&ok, 16);
308 qWarning() << Q_FUNC_INFO << tr("Invalid senderhash specification: %1").arg(condValue);
312 qWarning() << Q_FUNC_INFO << tr("Senderhash can be at most \"0x0f\"!");
315 label |= static_cast<MessageLabel>(++val << 16);
318 else if (condName == "format") {
319 if (condValue == "bold")
320 fmtType |= FormatType::Bold;
321 else if (condValue == "italic")
322 fmtType |= FormatType::Italic;
323 else if (condValue == "underline")
324 fmtType |= FormatType::Underline;
325 else if (condValue == "strikethrough")
326 fmtType |= FormatType::Strikethrough;
328 qWarning() << Q_FUNC_INFO << tr("Invalid format name: %1").arg(condValue);
332 else if (condName == "fg-color" || condName == "bg-color") {
334 quint32 col = condValue.toUInt(&ok, 16);
335 if (!ok || col > 0x0f) {
336 qWarning() << Q_FUNC_INFO << tr("Illegal IRC color specification (must be between 00 and 0f): %1").arg(condValue);
339 if (condName == "fg-color")
340 fmtType |= 0x00400000 | (col << 24);
342 fmtType |= 0x00800000 | (col << 28);
345 qWarning() << Q_FUNC_INFO << tr("Unhandled condition: %1").arg(condName);
351 return std::make_pair(fmtType, label);
355 // FIXME: Code duplication
356 UiStyle::ItemFormatType QssParser::parseItemFormatType(const QString &decl)
358 using ItemFormatType = UiStyle::ItemFormatType;
360 static const QRegExp rx("(Chat|Nick)ListItem(?:\\[([=-,\\\"\\w\\s]+)\\])?");
361 // $1: item type; $2: properties
362 if (!rx.exactMatch(decl)) {
363 qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
364 return ItemFormatType::Invalid;
366 QString mainItemType = rx.cap(1);
367 QString properties = rx.cap(2);
369 ItemFormatType fmtType{ItemFormatType::None};
371 // Next up: properties
373 if (!properties.isEmpty()) {
374 QHash<QString, QString> props;
375 static const QRegExp propRx("\\s*([\\w\\-]+)\\s*=\\s*\"([\\w\\-]+)\"\\s*");
376 foreach(const QString &prop, properties.split(',', QString::SkipEmptyParts)) {
377 if (!propRx.exactMatch(prop)) {
378 qWarning() << Q_FUNC_INFO << tr("Invalid proplist %1").arg(prop);
379 return ItemFormatType::Invalid;
381 props[propRx.cap(1)] = propRx.cap(2);
383 type = props.value("type");
384 state = props.value("state");
387 if (mainItemType == "Chat") {
388 fmtType |= ItemFormatType::BufferViewItem;
389 if (!type.isEmpty()) {
390 if (type == "network")
391 fmtType |= ItemFormatType::NetworkItem;
392 else if (type == "channel")
393 fmtType |= ItemFormatType::ChannelBufferItem;
394 else if (type == "query")
395 fmtType |= ItemFormatType::QueryBufferItem;
397 qWarning() << Q_FUNC_INFO << tr("Invalid chatlist item type %1").arg(type);
398 return ItemFormatType::Invalid;
401 if (!state.isEmpty()) {
402 if (state == "inactive")
403 fmtType |= ItemFormatType::InactiveBuffer;
404 else if (state == "channel-event")
405 fmtType |= ItemFormatType::ActiveBuffer;
406 else if (state == "unread-message")
407 fmtType |= ItemFormatType::UnreadBuffer;
408 else if (state == "highlighted")
409 fmtType |= ItemFormatType::HighlightedBuffer;
410 else if (state == "away")
411 fmtType |= ItemFormatType::UserAway;
413 qWarning() << Q_FUNC_INFO << tr("Invalid chatlist state %1").arg(state);
414 return ItemFormatType::Invalid;
419 fmtType |= ItemFormatType::NickViewItem;
420 if (!type.isEmpty()) {
421 if (type == "user") {
422 fmtType |= ItemFormatType::IrcUserItem;
424 fmtType |= ItemFormatType::UserAway;
426 else if (type == "category")
427 fmtType |= ItemFormatType::UserCategoryItem;
434 /******** Parse a whole format attribute block ********/
436 QTextCharFormat QssParser::parseFormat(const QString &qss)
438 QTextCharFormat format;
440 foreach(QString line, qss.split(';', QString::SkipEmptyParts)) {
441 int idx = line.indexOf(':');
443 qWarning() << Q_FUNC_INFO << tr("Invalid property declaration: %1").arg(line.trimmed());
446 QString property = line.left(idx).trimmed();
447 QString value = line.mid(idx + 1).simplified();
449 if (property == "background" || property == "background-color")
450 format.setBackground(parseBrush(value));
451 else if (property == "foreground" || property == "color")
452 format.setForeground(parseBrush(value));
454 // Color code overrides
455 else if (property == "allow-foreground-override") {
457 bool v = parseBoolean(value, &ok);
459 format.setProperty(static_cast<int>(UiStyle::FormatProperty::AllowForegroundOverride), v);
461 else if (property == "allow-background-override") {
463 bool v = parseBoolean(value, &ok);
465 format.setProperty(static_cast<int>(UiStyle::FormatProperty::AllowBackgroundOverride), v);
468 // font-related properties
469 else if (property.startsWith("font")) {
470 if (property == "font")
471 parseFont(value, &format);
472 else if (property == "font-style")
473 parseFontStyle(value, &format);
474 else if (property == "font-weight")
475 parseFontWeight(value, &format);
476 else if (property == "font-size")
477 parseFontSize(value, &format);
478 else if (property == "font-family")
479 parseFontFamily(value, &format);
481 qWarning() << Q_FUNC_INFO << tr("Invalid font property: %1").arg(line);
487 qWarning() << Q_FUNC_INFO << tr("Unknown ChatLine property: %1").arg(property);
494 /******** Boolean value ********/
496 bool QssParser::parseBoolean(const QString &str, bool *ok) const
506 qWarning() << Q_FUNC_INFO << tr("Invalid boolean value: %1").arg(str);
512 /******** Brush ********/
514 QBrush QssParser::parseBrush(const QString &str, bool *ok)
518 QColor c = parseColor(str);
525 if (str.startsWith("palette")) { // Palette color role
526 // Does the palette follow the expected format? For example:
527 // palette(marker-line)
528 // palette ( system-color-0f )
530 // Match the palette marker, grabbing the name inside in case-sensitive manner
531 // palette\s*\(\s*([a-z-0-9]+)\s*\)
532 // palette Match the string 'palette'
533 // \s* Match any amount of whitespace
534 // \(, \) Match literal '(' or ')' marks
535 // (...+) Match contents between 1 and unlimited number of times
536 // [a-z-] Match any character from a-z, case sensitive
537 // [0-9] Match any digit from 0-9
538 // Note that '\' must be escaped as '\\'
539 // Helpful interactive website for debugging and explaining: https://regex101.com/
540 static const QRegExp rx("palette\\s*\\(\\s*([a-z-0-9]+)\\s*\\)");
541 if (!rx.exactMatch(str)) {
542 qWarning() << Q_FUNC_INFO << tr("Invalid palette color role specification: %1").arg(str);
545 if (_paletteColorRoles.contains(rx.cap(1)))
546 return QBrush(_palette.brush(_paletteColorRoles.value(rx.cap(1))));
547 if (_uiStyleColorRoles.contains(rx.cap(1)))
548 return QBrush(_uiStylePalette.at(static_cast<int>(_uiStyleColorRoles.value(rx.cap(1)))));
549 qWarning() << Q_FUNC_INFO << tr("Unknown palette color role: %1").arg(rx.cap(1));
552 else if (str.startsWith("qlineargradient")) {
553 static const QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*");
554 static const QRegExp rx(QString("qlineargradient\\s*\\(\\s*x1:%1,\\s*y1:%1,\\s*x2:%1,\\s*y2:%1,(.+)\\)").arg(rxFloat));
555 if (!rx.exactMatch(str)) {
556 qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1").arg(str);
559 qreal x1 = rx.cap(1).toDouble();
560 qreal y1 = rx.cap(2).toDouble();
561 qreal x2 = rx.cap(3).toDouble();
562 qreal y2 = rx.cap(4).toDouble();
563 QGradientStops stops = parseGradientStops(rx.cap(5).trimmed());
564 if (!stops.count()) {
565 qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1").arg(str);
568 QLinearGradient gradient(x1, y1, x2, y2);
569 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
570 gradient.setStops(stops);
573 return QBrush(gradient);
575 else if (str.startsWith("qconicalgradient")) {
576 static const QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*");
577 static const QRegExp rx(QString("qconicalgradient\\s*\\(\\s*cx:%1,\\s*cy:%1,\\s*angle:%1,(.+)\\)").arg(rxFloat));
578 if (!rx.exactMatch(str)) {
579 qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1").arg(str);
582 qreal cx = rx.cap(1).toDouble();
583 qreal cy = rx.cap(2).toDouble();
584 qreal angle = rx.cap(3).toDouble();
585 QGradientStops stops = parseGradientStops(rx.cap(4).trimmed());
586 if (!stops.count()) {
587 qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1").arg(str);
590 QConicalGradient gradient(cx, cy, angle);
591 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
592 gradient.setStops(stops);
595 return QBrush(gradient);
597 else if (str.startsWith("qradialgradient")) {
598 static const QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*");
599 static const QRegExp rx(QString("qradialgradient\\s*\\(\\s*cx:%1,\\s*cy:%1,\\s*radius:%1,\\s*fx:%1,\\s*fy:%1,(.+)\\)").arg(rxFloat));
600 if (!rx.exactMatch(str)) {
601 qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1").arg(str);
604 qreal cx = rx.cap(1).toDouble();
605 qreal cy = rx.cap(2).toDouble();
606 qreal radius = rx.cap(3).toDouble();
607 qreal fx = rx.cap(4).toDouble();
608 qreal fy = rx.cap(5).toDouble();
609 QGradientStops stops = parseGradientStops(rx.cap(6).trimmed());
610 if (!stops.count()) {
611 qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1").arg(str);
614 QRadialGradient gradient(cx, cy, radius, fx, fy);
615 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
616 gradient.setStops(stops);
619 return QBrush(gradient);
626 QColor QssParser::parseColor(const QString &str)
628 if (str.startsWith("rgba")) {
629 ColorTuple tuple = parseColorTuple(str.mid(4));
630 if (tuple.count() == 4)
631 return QColor(tuple.at(0), tuple.at(1), tuple.at(2), tuple.at(3));
633 else if (str.startsWith("rgb")) {
634 ColorTuple tuple = parseColorTuple(str.mid(3));
635 if (tuple.count() == 3)
636 return QColor(tuple.at(0), tuple.at(1), tuple.at(2));
638 else if (str.startsWith("hsva")) {
639 ColorTuple tuple = parseColorTuple(str.mid(4));
640 if (tuple.count() == 4) {
642 c.setHsvF(tuple.at(0), tuple.at(1), tuple.at(2), tuple.at(3));
646 else if (str.startsWith("hsv")) {
647 ColorTuple tuple = parseColorTuple(str.mid(3));
648 if (tuple.count() == 3) {
650 c.setHsvF(tuple.at(0), tuple.at(1), tuple.at(2));
655 static const QRegExp rx("#?[0-9A-Fa-z]+");
656 if (rx.exactMatch(str))
663 // get a list of comma-separated int values or percentages (rel to 0-255)
664 QssParser::ColorTuple QssParser::parseColorTuple(const QString &str)
667 static const QRegExp rx("\\(((\\s*[0-9]{1,3}%?\\s*)(,\\s*[0-9]{1,3}%?\\s*)*)\\)");
668 if (!rx.exactMatch(str.trimmed())) {
671 QStringList values = rx.cap(1).split(',');
672 foreach(QString v, values) {
677 if (v.endsWith('%')) {
681 val = (qreal)v.toUInt(&ok);
692 QGradientStops QssParser::parseGradientStops(const QString &str_)
695 QGradientStops result;
696 static const QString rxFloat("(0?\\.[0-9]+|[01])"); // values between 0 and 1
697 static const QRegExp rx(QString("\\s*,?\\s*stop:\\s*(%1)\\s+([^:]+)(,\\s*stop:|$)").arg(rxFloat));
699 while ((idx = rx.indexIn(str)) == 0) {
700 qreal x = rx.cap(1).toDouble();
701 QColor c = parseColor(rx.cap(3));
703 return QGradientStops();
704 result << QGradientStop(x, c);
705 str.remove(0, rx.matchedLength() - rx.cap(4).length());
707 if (!str.trimmed().isEmpty())
708 return QGradientStops();
714 /******** Font Properties ********/
716 void QssParser::parseFont(const QString &value, QTextCharFormat *format)
718 static const QRegExp rx("((?:(?:normal|italic|oblique|underline|strikethrough|bold|100|200|300|400|500|600|700|800|900) ){0,2}) ?(\\d+)(pt|px)? \"(.*)\"");
719 if (!rx.exactMatch(value)) {
720 qWarning() << Q_FUNC_INFO << tr("Invalid font specification: %1").arg(value);
723 format->setFontItalic(false);
724 format->setFontWeight(QFont::Normal);
725 QStringList proplist = rx.cap(1).split(' ', QString::SkipEmptyParts);
726 foreach(QString prop, proplist) {
727 if (prop == "italic")
728 format->setFontItalic(true);
729 else if (prop == "underline")
730 format->setFontUnderline(true);
731 // Oblique is not a property supported by QTextCharFormat
732 //else if(prop == "oblique")
733 // format->setStyle(QFont::StyleOblique);
734 else if (prop == "bold")
735 format->setFontWeight(QFont::Bold);
737 int w = prop.toInt();
738 format->setFontWeight(qMin(w / 8, 99)); // taken from Qt's qss parser
742 if (rx.cap(3) == "px")
743 format->setProperty(QTextFormat::FontPixelSize, rx.cap(2).toInt());
745 format->setFontPointSize(rx.cap(2).toInt());
747 format->setFontFamily(rx.cap(4));
751 void QssParser::parseFontStyle(const QString &value, QTextCharFormat *format)
753 if (value == "normal")
754 format->setFontItalic(false);
755 else if (value == "italic")
756 format->setFontItalic(true);
757 else if (value == "underline")
758 format->setFontUnderline(true);
759 else if (value == "strikethrough")
760 format->setFontStrikeOut(true);
761 // Oblique is not a property supported by QTextCharFormat
762 //else if(value == "oblique")
763 // format->setStyle(QFont::StyleOblique);
765 qWarning() << Q_FUNC_INFO << tr("Invalid font style specification: %1").arg(value);
770 void QssParser::parseFontWeight(const QString &value, QTextCharFormat *format)
772 if (value == "normal")
773 format->setFontWeight(QFont::Normal);
774 else if (value == "bold")
775 format->setFontWeight(QFont::Bold);
778 int w = value.toInt(&ok);
780 qWarning() << Q_FUNC_INFO << tr("Invalid font weight specification: %1").arg(value);
783 format->setFontWeight(qMin(w / 8, 99)); // taken from Qt's qss parser
788 void QssParser::parseFontSize(const QString &value, QTextCharFormat *format)
790 static const QRegExp rx("(\\d+)(pt|px)");
791 if (!rx.exactMatch(value)) {
792 qWarning() << Q_FUNC_INFO << tr("Invalid font size specification: %1").arg(value);
795 if (rx.cap(2) == "px")
796 format->setProperty(QTextFormat::FontPixelSize, rx.cap(1).toInt());
798 format->setFontPointSize(rx.cap(1).toInt());
802 void QssParser::parseFontFamily(const QString &value, QTextCharFormat *format)
804 QString family = value;
805 if (family.startsWith('"') && family.endsWith('"')) {
806 family = family.mid(1, family.length() - 2);
808 format->setFontFamily(family);