1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 by the Quassel Project * |
3 | * devel@quassel-irc.org * |
4 | * * |
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. * |
9 | * * |
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. * |
14 | * * |
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 | ***************************************************************************/ |
20 | |
21 | #include <QApplication> |
22 | |
23 | #include "qssparser.h" |
24 | |
25 | QssParser::QssParser() |
26 | { |
27 | _palette = QApplication::palette(); |
28 | |
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; |
51 | |
52 | _uiStylePalette = QVector<QBrush>(UiStyle::NumRoles, QBrush()); |
53 | |
54 | _uiStyleColorRoles["marker-line" ] = UiStyle::MarkerLine; |
55 | } |
56 | |
57 | |
58 | void QssParser::processStyleSheet(QString &ss) |
59 | { |
60 | if (ss.isEmpty()) |
61 | return; |
62 | |
63 | // Remove C-style comments /* */ or // |
64 | QRegExp ("(//.*(\\n|$)|/\\*.*\\*/)" ); |
65 | commentRx.setMinimal(true); |
66 | ss.remove(commentRx); |
67 | |
68 | // Palette definitions first, so we can apply roles later on |
69 | QRegExp paletterx("(Palette[^{]*)\\{([^}]+)\\}" ); |
70 | int pos = 0; |
71 | while ((pos = paletterx.indexIn(ss, pos)) >= 0) { |
72 | parsePaletteBlock(paletterx.cap(1).trimmed(), paletterx.cap(2).trimmed()); |
73 | ss.remove(pos, paletterx.matchedLength()); |
74 | } |
75 | |
76 | // Now we can parse the rest of our custom blocks |
77 | QRegExp blockrx("((?:ChatLine|ChatListItem|NickListItem)[^{]*)\\{([^}]+)\\}" ); |
78 | pos = 0; |
79 | while ((pos = blockrx.indexIn(ss, pos)) >= 0) { |
80 | //qDebug() << blockrx.cap(1) << blockrx.cap(2); |
81 | QString declaration = blockrx.cap(1).trimmed(); |
82 | QString contents = blockrx.cap(2).trimmed(); |
83 | |
84 | if (declaration.startsWith("ChatLine" )) |
85 | parseChatLineBlock(declaration, contents); |
86 | else if (declaration.startsWith("ChatListItem" ) || declaration.startsWith("NickListItem" )) |
87 | parseListItemBlock(declaration, contents); |
88 | //else |
89 | // TODO: add moar here |
90 | |
91 | ss.remove(pos, blockrx.matchedLength()); |
92 | } |
93 | } |
94 | |
95 | |
96 | /******** Parse a whole block: declaration { contents } *******/ |
97 | |
98 | void QssParser::parseChatLineBlock(const QString &decl, const QString &contents) |
99 | { |
100 | quint64 fmtType = parseFormatType(decl); |
101 | if (fmtType == UiStyle::Invalid) |
102 | return; |
103 | |
104 | _formats[fmtType].merge(parseFormat(contents)); |
105 | } |
106 | |
107 | |
108 | void QssParser::parseListItemBlock(const QString &decl, const QString &contents) |
109 | { |
110 | quint32 fmtType = parseItemFormatType(decl); |
111 | if (fmtType == UiStyle::Invalid) |
112 | return; |
113 | |
114 | _listItemFormats[fmtType].merge(parseFormat(contents)); |
115 | } |
116 | |
117 | |
118 | // Palette { ... } specifies the application palette |
119 | // ColorGroups can be specified like pseudo states, chaining is OR (contrary to normal CSS handling): |
120 | // Palette:inactive:disabled { ... } applies to both the Inactive and the Disabled state |
121 | void QssParser::parsePaletteBlock(const QString &decl, const QString &contents) |
122 | { |
123 | QList<QPalette::ColorGroup> colorGroups; |
124 | |
125 | // Check if we want to apply this palette definition for particular ColorGroups |
126 | QRegExp rx("Palette((:(normal|active|inactive|disabled))*)" ); |
127 | if (!rx.exactMatch(decl)) { |
128 | qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1" ).arg(decl); |
129 | return; |
130 | } |
131 | if (!rx.cap(1).isEmpty()) { |
132 | QStringList groups = rx.cap(1).split(':', QString::SkipEmptyParts); |
133 | foreach(QString g, groups) { |
134 | if ((g == "normal" || g == "active" ) && !colorGroups.contains(QPalette::Active)) |
135 | colorGroups.append(QPalette::Active); |
136 | else if (g == "inactive" && !colorGroups.contains(QPalette::Inactive)) |
137 | colorGroups.append(QPalette::Inactive); |
138 | else if (g == "disabled" && !colorGroups.contains(QPalette::Disabled)) |
139 | colorGroups.append(QPalette::Disabled); |
140 | } |
141 | } |
142 | |
143 | // Now let's go through the roles |
144 | foreach(QString line, contents.split(';', QString::SkipEmptyParts)) { |
145 | int idx = line.indexOf(':'); |
146 | if (idx <= 0) { |
147 | qWarning() << Q_FUNC_INFO << tr("Invalid palette role assignment: %1" ).arg(line.trimmed()); |
148 | continue; |
149 | } |
150 | QString rolestr = line.left(idx).trimmed(); |
151 | QString brushstr = line.mid(idx + 1).trimmed(); |
152 | |
153 | if (_paletteColorRoles.contains(rolestr)) { |
154 | QBrush brush = parseBrush(brushstr); |
155 | if (colorGroups.count()) { |
156 | foreach(QPalette::ColorGroup group, colorGroups) |
157 | _palette.setBrush(group, _paletteColorRoles.value(rolestr), brush); |
158 | } |
159 | else |
160 | _palette.setBrush(_paletteColorRoles.value(rolestr), brush); |
161 | } |
162 | else if (_uiStyleColorRoles.contains(rolestr)) { |
163 | _uiStylePalette[_uiStyleColorRoles.value(rolestr)] = parseBrush(brushstr); |
164 | } |
165 | else |
166 | qWarning() << Q_FUNC_INFO << tr("Unknown palette role name: %1" ).arg(rolestr); |
167 | } |
168 | } |
169 | |
170 | |
171 | /******** Determine format types from a block declaration ********/ |
172 | |
173 | quint64 QssParser::parseFormatType(const QString &decl) |
174 | { |
175 | QRegExp rx("ChatLine(?:::(\\w+))?(?:#([\\w\\-]+))?(?:\\[([=-,\\\"\\w\\s]+)\\])?" ); |
176 | // $1: subelement; $2: msgtype; $3: conditionals |
177 | if (!rx.exactMatch(decl)) { |
178 | qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1" ).arg(decl); |
179 | return UiStyle::Invalid; |
180 | } |
181 | QString subElement = rx.cap(1); |
182 | QString msgType = rx.cap(2); |
183 | QString conditions = rx.cap(3); |
184 | |
185 | quint64 fmtType = 0; |
186 | |
187 | // First determine the subelement |
188 | if (!subElement.isEmpty()) { |
189 | if (subElement == "timestamp" ) |
190 | fmtType |= UiStyle::Timestamp; |
191 | else if (subElement == "sender" ) |
192 | fmtType |= UiStyle::Sender; |
193 | else if (subElement == "nick" ) |
194 | fmtType |= UiStyle::Nick; |
195 | else if (subElement == "contents" ) |
196 | fmtType |= UiStyle::Contents; |
197 | else if (subElement == "hostmask" ) |
198 | fmtType |= UiStyle::Hostmask; |
199 | else if (subElement == "modeflags" ) |
200 | fmtType |= UiStyle::ModeFlags; |
201 | else if (subElement == "url" ) |
202 | fmtType |= UiStyle::Url; |
203 | else { |
204 | qWarning() << Q_FUNC_INFO << tr("Invalid subelement name in %1" ).arg(decl); |
205 | return UiStyle::Invalid; |
206 | } |
207 | } |
208 | |
209 | // Now, figure out the message type |
210 | if (!msgType.isEmpty()) { |
211 | if (msgType == "plain" ) |
212 | fmtType |= UiStyle::PlainMsg; |
213 | else if (msgType == "notice" ) |
214 | fmtType |= UiStyle::NoticeMsg; |
215 | else if (msgType == "action" ) |
216 | fmtType |= UiStyle::ActionMsg; |
217 | else if (msgType == "nick" ) |
218 | fmtType |= UiStyle::NickMsg; |
219 | else if (msgType == "mode" ) |
220 | fmtType |= UiStyle::ModeMsg; |
221 | else if (msgType == "join" ) |
222 | fmtType |= UiStyle::JoinMsg; |
223 | else if (msgType == "part" ) |
224 | fmtType |= UiStyle::PartMsg; |
225 | else if (msgType == "quit" ) |
226 | fmtType |= UiStyle::QuitMsg; |
227 | else if (msgType == "kick" ) |
228 | fmtType |= UiStyle::KickMsg; |
229 | else if (msgType == "kill" ) |
230 | fmtType |= UiStyle::KillMsg; |
231 | else if (msgType == "server" ) |
232 | fmtType |= UiStyle::ServerMsg; |
233 | else if (msgType == "info" ) |
234 | fmtType |= UiStyle::InfoMsg; |
235 | else if (msgType == "error" ) |
236 | fmtType |= UiStyle::ErrorMsg; |
237 | else if (msgType == "daychange" ) |
238 | fmtType |= UiStyle::DayChangeMsg; |
239 | else if (msgType == "topic" ) |
240 | fmtType |= UiStyle::TopicMsg; |
241 | else if (msgType == "netsplit-join" ) |
242 | fmtType |= UiStyle::NetsplitJoinMsg; |
243 | else if (msgType == "netsplit-quit" ) |
244 | fmtType |= UiStyle::NetsplitQuitMsg; |
245 | else if (msgType == "invite" ) |
246 | fmtType |= UiStyle::InviteMsg; |
247 | else { |
248 | qWarning() << Q_FUNC_INFO << tr("Invalid message type in %1" ).arg(decl); |
249 | } |
250 | } |
251 | |
252 | // Next up: conditional (formats, labels, nickhash) |
253 | QRegExp condRx("\\s*([\\w\\-]+)\\s*=\\s*\"(\\w+)\"\\s*" ); |
254 | if (!conditions.isEmpty()) { |
255 | foreach(const QString &cond, conditions.split(',', QString::SkipEmptyParts)) { |
256 | if (!condRx.exactMatch(cond)) { |
257 | qWarning() << Q_FUNC_INFO << tr("Invalid condition %1" ).arg(cond); |
258 | return UiStyle::Invalid; |
259 | } |
260 | QString condName = condRx.cap(1); |
261 | QString condValue = condRx.cap(2); |
262 | if (condName == "label" ) { |
263 | quint64 labeltype = 0; |
264 | if (condValue == "highlight" ) |
265 | labeltype = UiStyle::Highlight; |
266 | else if (condValue == "selected" ) |
267 | labeltype = UiStyle::Selected; |
268 | else { |
269 | qWarning() << Q_FUNC_INFO << tr("Invalid message label: %1" ).arg(condValue); |
270 | return UiStyle::Invalid; |
271 | } |
272 | fmtType |= (labeltype << 32); |
273 | } |
274 | else if (condName == "sender" ) { |
275 | if (condValue == "self" ) |
276 | fmtType |= (quint64) UiStyle::OwnMsg << 32; // sender="self" is actually treated as a label |
277 | else { |
278 | bool ok = true; |
279 | quint64 val = condValue.toUInt(&ok, 16); |
280 | if (!ok) { |
281 | qWarning() << Q_FUNC_INFO << tr("Invalid senderhash specification: %1" ).arg(condValue); |
282 | return UiStyle::Invalid; |
283 | } |
284 | if (val >= 16) { |
285 | qWarning() << Q_FUNC_INFO << tr("Senderhash can be at most \"0x0f\"!" ); |
286 | return UiStyle::Invalid; |
287 | } |
288 | fmtType |= ++val << 48; |
289 | } |
290 | } |
291 | else if (condName == "format" ) { |
292 | if (condValue == "bold" ) |
293 | fmtType |= UiStyle::Bold; |
294 | else if (condValue == "italic" ) |
295 | fmtType |= UiStyle::Italic; |
296 | else if (condValue == "underline" ) |
297 | fmtType |= UiStyle::Underline; |
298 | else if (condValue == "reverse" ) |
299 | fmtType |= UiStyle::Reverse; |
300 | else { |
301 | qWarning() << Q_FUNC_INFO << tr("Invalid format name: %1" ).arg(condValue); |
302 | return UiStyle::Invalid; |
303 | } |
304 | } |
305 | else if (condName == "fg-color" || condName == "bg-color" ) { |
306 | bool ok; |
307 | quint8 col = condValue.toUInt(&ok, 16); |
308 | if (!ok || col > 0x0f) { |
309 | qWarning() << Q_FUNC_INFO << tr("Illegal IRC color specification (must be between 00 and 0f): %1" ).arg(condValue); |
310 | return UiStyle::Invalid; |
311 | } |
312 | if (condName == "fg-color" ) |
313 | fmtType |= 0x00400000 | (quint32)(col << 24); |
314 | else |
315 | fmtType |= 0x00800000 | (quint32)(col << 28); |
316 | } |
317 | else { |
318 | qWarning() << Q_FUNC_INFO << tr("Unhandled condition: %1" ).arg(condName); |
319 | return UiStyle::Invalid; |
320 | } |
321 | } |
322 | } |
323 | |
324 | return fmtType; |
325 | } |
326 | |
327 | |
328 | // FIXME: Code duplication |
329 | quint32 QssParser::parseItemFormatType(const QString &decl) |
330 | { |
331 | QRegExp rx("(Chat|Nick)ListItem(?:\\[([=-,\\\"\\w\\s]+)\\])?" ); |
332 | // $1: item type; $2: properties |
333 | if (!rx.exactMatch(decl)) { |
334 | qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1" ).arg(decl); |
335 | return UiStyle::Invalid; |
336 | } |
337 | QString mainItemType = rx.cap(1); |
338 | QString properties = rx.cap(2); |
339 | |
340 | quint32 fmtType = 0; |
341 | |
342 | // Next up: properties |
343 | QString type, state; |
344 | if (!properties.isEmpty()) { |
345 | QHash<QString, QString> props; |
346 | QRegExp propRx("\\s*([\\w\\-]+)\\s*=\\s*\"([\\w\\-]+)\"\\s*" ); |
347 | foreach(const QString &prop, properties.split(',', QString::SkipEmptyParts)) { |
348 | if (!propRx.exactMatch(prop)) { |
349 | qWarning() << Q_FUNC_INFO << tr("Invalid proplist %1" ).arg(prop); |
350 | return UiStyle::Invalid; |
351 | } |
352 | props[propRx.cap(1)] = propRx.cap(2); |
353 | } |
354 | type = props.value("type" ); |
355 | state = props.value("state" ); |
356 | } |
357 | |
358 | if (mainItemType == "Chat" ) { |
359 | fmtType |= UiStyle::BufferViewItem; |
360 | if (!type.isEmpty()) { |
361 | if (type == "network" ) |
362 | fmtType |= UiStyle::NetworkItem; |
363 | else if (type == "channel" ) |
364 | fmtType |= UiStyle::ChannelBufferItem; |
365 | else if (type == "query" ) |
366 | fmtType |= UiStyle::QueryBufferItem; |
367 | else { |
368 | qWarning() << Q_FUNC_INFO << tr("Invalid chatlist item type %1" ).arg(type); |
369 | return UiStyle::Invalid; |
370 | } |
371 | } |
372 | if (!state.isEmpty()) { |
373 | if (state == "inactive" ) |
374 | fmtType |= UiStyle::InactiveBuffer; |
375 | else if (state == "channel-event" ) |
376 | fmtType |= UiStyle::ActiveBuffer; |
377 | else if (state == "unread-message" ) |
378 | fmtType |= UiStyle::UnreadBuffer; |
379 | else if (state == "highlighted" ) |
380 | fmtType |= UiStyle::HighlightedBuffer; |
381 | else if (state == "away" ) |
382 | fmtType |= UiStyle::UserAway; |
383 | else { |
384 | qWarning() << Q_FUNC_INFO << tr("Invalid chatlist state %1" ).arg(state); |
385 | return UiStyle::Invalid; |
386 | } |
387 | } |
388 | } |
389 | else { // NickList |
390 | fmtType |= UiStyle::NickViewItem; |
391 | if (!type.isEmpty()) { |
392 | if (type == "user" ) { |
393 | fmtType |= UiStyle::IrcUserItem; |
394 | if (state == "away" ) |
395 | fmtType |= UiStyle::UserAway; |
396 | } |
397 | else if (type == "category" ) |
398 | fmtType |= UiStyle::UserCategoryItem; |
399 | } |
400 | } |
401 | return fmtType; |
402 | } |
403 | |
404 | |
405 | /******** Parse a whole format attribute block ********/ |
406 | |
407 | QTextCharFormat QssParser::parseFormat(const QString &qss) |
408 | { |
409 | QTextCharFormat format; |
410 | |
411 | foreach(QString line, qss.split(';', QString::SkipEmptyParts)) { |
412 | int idx = line.indexOf(':'); |
413 | if (idx <= 0) { |
414 | qWarning() << Q_FUNC_INFO << tr("Invalid property declaration: %1" ).arg(line.trimmed()); |
415 | continue; |
416 | } |
417 | QString property = line.left(idx).trimmed(); |
418 | QString value = line.mid(idx + 1).simplified(); |
419 | |
420 | if (property == "background" || property == "background-color" ) |
421 | format.setBackground(parseBrush(value)); |
422 | else if (property == "foreground" || property == "color" ) |
423 | format.setForeground(parseBrush(value)); |
424 | |
425 | // font-related properties |
426 | else if (property.startsWith("font" )) { |
427 | if (property == "font" ) |
428 | parseFont(value, &format); |
429 | else if (property == "font-style" ) |
430 | parseFontStyle(value, &format); |
431 | else if (property == "font-weight" ) |
432 | parseFontWeight(value, &format); |
433 | else if (property == "font-size" ) |
434 | parseFontSize(value, &format); |
435 | else if (property == "font-family" ) |
436 | parseFontFamily(value, &format); |
437 | else { |
438 | qWarning() << Q_FUNC_INFO << tr("Invalid font property: %1" ).arg(line); |
439 | continue; |
440 | } |
441 | } |
442 | |
443 | else { |
444 | qWarning() << Q_FUNC_INFO << tr("Unknown ChatLine property: %1" ).arg(property); |
445 | } |
446 | } |
447 | |
448 | return format; |
449 | } |
450 | |
451 | |
452 | /******** Brush ********/ |
453 | |
454 | QBrush QssParser::parseBrush(const QString &str, bool *ok) |
455 | { |
456 | if (ok) |
457 | *ok = false; |
458 | QColor c = parseColor(str); |
459 | if (c.isValid()) { |
460 | if (ok) |
461 | *ok = true; |
462 | return QBrush(c); |
463 | } |
464 | |
465 | if (str.startsWith("palette" )) { // Palette color role |
466 | QRegExp rx("palette\\s*\\(\\s*([a-z-]+)\\s*\\)" ); |
467 | if (!rx.exactMatch(str)) { |
468 | qWarning() << Q_FUNC_INFO << tr("Invalid palette color role specification: %1" ).arg(str); |
469 | return QBrush(); |
470 | } |
471 | if (_paletteColorRoles.contains(rx.cap(1))) |
472 | return QBrush(_palette.brush(_paletteColorRoles.value(rx.cap(1)))); |
473 | if (_uiStyleColorRoles.contains(rx.cap(1))) |
474 | return QBrush(_uiStylePalette.at(_uiStyleColorRoles.value(rx.cap(1)))); |
475 | qWarning() << Q_FUNC_INFO << tr("Unknown palette color role: %1" ).arg(rx.cap(1)); |
476 | return QBrush(); |
477 | } |
478 | else if (str.startsWith("qlineargradient" )) { |
479 | static QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*" ); |
480 | QRegExp rx(QString("qlineargradient\\s*\\(\\s*x1:%1,\\s*y1:%1,\\s*x2:%1,\\s*y2:%1,(.+)\\)" ).arg(rxFloat)); |
481 | if (!rx.exactMatch(str)) { |
482 | qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1" ).arg(str); |
483 | return QBrush(); |
484 | } |
485 | qreal x1 = rx.cap(1).toDouble(); |
486 | qreal y1 = rx.cap(2).toDouble(); |
487 | qreal x2 = rx.cap(3).toDouble(); |
488 | qreal y2 = rx.cap(4).toDouble(); |
489 | QGradientStops stops = parseGradientStops(rx.cap(5).trimmed()); |
490 | if (!stops.count()) { |
491 | qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1" ).arg(str); |
492 | return QBrush(); |
493 | } |
494 | QLinearGradient gradient(x1, y1, x2, y2); |
495 | gradient.setCoordinateMode(QGradient::ObjectBoundingMode); |
496 | gradient.setStops(stops); |
497 | if (ok) |
498 | *ok = true; |
499 | return QBrush(gradient); |
500 | } |
501 | else if (str.startsWith("qconicalgradient" )) { |
502 | static QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*" ); |
503 | QRegExp rx(QString("qconicalgradient\\s*\\(\\s*cx:%1,\\s*cy:%1,\\s*angle:%1,(.+)\\)" ).arg(rxFloat)); |
504 | if (!rx.exactMatch(str)) { |
505 | qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1" ).arg(str); |
506 | return QBrush(); |
507 | } |
508 | qreal cx = rx.cap(1).toDouble(); |
509 | qreal cy = rx.cap(2).toDouble(); |
510 | qreal angle = rx.cap(3).toDouble(); |
511 | QGradientStops stops = parseGradientStops(rx.cap(4).trimmed()); |
512 | if (!stops.count()) { |
513 | qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1" ).arg(str); |
514 | return QBrush(); |
515 | } |
516 | QConicalGradient gradient(cx, cy, angle); |
517 | gradient.setCoordinateMode(QGradient::ObjectBoundingMode); |
518 | gradient.setStops(stops); |
519 | if (ok) |
520 | *ok = true; |
521 | return QBrush(gradient); |
522 | } |
523 | else if (str.startsWith("qradialgradient" )) { |
524 | static QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*" ); |
525 | QRegExp rx(QString("qradialgradient\\s*\\(\\s*cx:%1,\\s*cy:%1,\\s*radius:%1,\\s*fx:%1,\\s*fy:%1,(.+)\\)" ).arg(rxFloat)); |
526 | if (!rx.exactMatch(str)) { |
527 | qWarning() << Q_FUNC_INFO << tr("Invalid gradient declaration: %1" ).arg(str); |
528 | return QBrush(); |
529 | } |
530 | qreal cx = rx.cap(1).toDouble(); |
531 | qreal cy = rx.cap(2).toDouble(); |
532 | qreal radius = rx.cap(3).toDouble(); |
533 | qreal fx = rx.cap(4).toDouble(); |
534 | qreal fy = rx.cap(5).toDouble(); |
535 | QGradientStops stops = parseGradientStops(rx.cap(6).trimmed()); |
536 | if (!stops.count()) { |
537 | qWarning() << Q_FUNC_INFO << tr("Invalid gradient stops list: %1" ).arg(str); |
538 | return QBrush(); |
539 | } |
540 | QRadialGradient gradient(cx, cy, radius, fx, fy); |
541 | gradient.setCoordinateMode(QGradient::ObjectBoundingMode); |
542 | gradient.setStops(stops); |
543 | if (ok) |
544 | *ok = true; |
545 | return QBrush(gradient); |
546 | } |
547 | |
548 | return QBrush(); |
549 | } |
550 | |
551 | |
552 | QColor QssParser::parseColor(const QString &str) |
553 | { |
554 | if (str.startsWith("rgba" )) { |
555 | ColorTuple tuple = parseColorTuple(str.mid(4)); |
556 | if (tuple.count() == 4) |
557 | return QColor(tuple.at(0), tuple.at(1), tuple.at(2), tuple.at(3)); |
558 | } |
559 | else if (str.startsWith("rgb" )) { |
560 | ColorTuple tuple = parseColorTuple(str.mid(3)); |
561 | if (tuple.count() == 3) |
562 | return QColor(tuple.at(0), tuple.at(1), tuple.at(2)); |
563 | } |
564 | else if (str.startsWith("hsva" )) { |
565 | ColorTuple tuple = parseColorTuple(str.mid(4)); |
566 | if (tuple.count() == 4) { |
567 | QColor c; |
568 | c.setHsvF(tuple.at(0), tuple.at(1), tuple.at(2), tuple.at(3)); |
569 | return c; |
570 | } |
571 | } |
572 | else if (str.startsWith("hsv" )) { |
573 | ColorTuple tuple = parseColorTuple(str.mid(3)); |
574 | if (tuple.count() == 3) { |
575 | QColor c; |
576 | c.setHsvF(tuple.at(0), tuple.at(1), tuple.at(2)); |
577 | return c; |
578 | } |
579 | } |
580 | else { |
581 | QRegExp rx("#?[0-9A-Fa-z]+" ); |
582 | if (rx.exactMatch(str)) |
583 | return QColor(str); |
584 | } |
585 | return QColor(); |
586 | } |
587 | |
588 | |
589 | // get a list of comma-separated int values or percentages (rel to 0-255) |
590 | QssParser::ColorTuple QssParser::parseColorTuple(const QString &str) |
591 | { |
592 | ColorTuple result; |
593 | QRegExp rx("\\(((\\s*[0-9]{1,3}%?\\s*)(,\\s*[0-9]{1,3}%?\\s*)*)\\)" ); |
594 | if (!rx.exactMatch(str.trimmed())) { |
595 | return ColorTuple(); |
596 | } |
597 | QStringList values = rx.cap(1).split(','); |
598 | foreach(QString v, values) { |
599 | qreal val; |
600 | bool perc = false; |
601 | bool ok; |
602 | v = v.trimmed(); |
603 | if (v.endsWith('%')) { |
604 | perc = true; |
605 | v.chop(1); |
606 | } |
607 | val = (qreal)v.toUInt(&ok); |
608 | if (!ok) |
609 | return ColorTuple(); |
610 | if (perc) |
611 | val = 255 * val/100; |
612 | result.append(val); |
613 | } |
614 | return result; |
615 | } |
616 | |
617 | |
618 | QGradientStops QssParser::parseGradientStops(const QString &str_) |
619 | { |
620 | QString str = str_; |
621 | QGradientStops result; |
622 | static QString rxFloat("(0?\\.[0-9]+|[01])" ); // values between 0 and 1 |
623 | QRegExp rx(QString("\\s*,?\\s*stop:\\s*(%1)\\s+([^:]+)(,\\s*stop:|$)" ).arg(rxFloat)); |
624 | int idx; |
625 | while ((idx = rx.indexIn(str)) == 0) { |
626 | qreal x = rx.cap(1).toDouble(); |
627 | QColor c = parseColor(rx.cap(3)); |
628 | if (!c.isValid()) |
629 | return QGradientStops(); |
630 | result << QGradientStop(x, c); |
631 | str.remove(0, rx.matchedLength() - rx.cap(4).length()); |
632 | } |
633 | if (!str.trimmed().isEmpty()) |
634 | return QGradientStops(); |
635 | |
636 | return result; |
637 | } |
638 | |
639 | |
640 | /******** Font Properties ********/ |
641 | |
642 | void QssParser::parseFont(const QString &value, QTextCharFormat *format) |
643 | { |
644 | QRegExp rx("((?:(?:normal|italic|oblique|underline|bold|100|200|300|400|500|600|700|800|900) ){0,2}) ?(\\d+)(pt|px)? \"(.*)\"" ); |
645 | if (!rx.exactMatch(value)) { |
646 | qWarning() << Q_FUNC_INFO << tr("Invalid font specification: %1" ).arg(value); |
647 | return; |
648 | } |
649 | format->setFontItalic(false); |
650 | format->setFontWeight(QFont::Normal); |
651 | QStringList proplist = rx.cap(1).split(' ', QString::SkipEmptyParts); |
652 | foreach(QString prop, proplist) { |
653 | if (prop == "italic" ) |
654 | format->setFontItalic(true); |
655 | else if (prop == "underline" ) |
656 | format->setFontUnderline(true); |
657 | //else if(prop == "oblique") |
658 | // format->setStyle(QFont::StyleOblique); |
659 | else if (prop == "bold" ) |
660 | format->setFontWeight(QFont::Bold); |
661 | else { // number |
662 | int w = prop.toInt(); |
663 | format->setFontWeight(qMin(w / 8, 99)); // taken from Qt's qss parser |
664 | } |
665 | } |
666 | |
667 | if (rx.cap(3) == "px" ) |
668 | format->setProperty(QTextFormat::FontPixelSize, rx.cap(2).toInt()); |
669 | else |
670 | format->setFontPointSize(rx.cap(2).toInt()); |
671 | |
672 | format->setFontFamily(rx.cap(4)); |
673 | } |
674 | |
675 | |
676 | void QssParser::parseFontStyle(const QString &value, QTextCharFormat *format) |
677 | { |
678 | if (value == "normal" ) |
679 | format->setFontItalic(false); |
680 | else if (value == "italic" ) |
681 | format->setFontItalic(true); |
682 | else if (value == "underline" ) |
683 | format->setFontUnderline(true); |
684 | //else if(value == "oblique") |
685 | // format->setStyle(QFont::StyleOblique); |
686 | else { |
687 | qWarning() << Q_FUNC_INFO << tr("Invalid font style specification: %1" ).arg(value); |
688 | } |
689 | } |
690 | |
691 | |
692 | void QssParser::parseFontWeight(const QString &value, QTextCharFormat *format) |
693 | { |
694 | if (value == "normal" ) |
695 | format->setFontWeight(QFont::Normal); |
696 | else if (value == "bold" ) |
697 | format->setFontWeight(QFont::Bold); |
698 | else { |
699 | bool ok; |
700 | int w = value.toInt(&ok); |
701 | if (!ok) { |
702 | qWarning() << Q_FUNC_INFO << tr("Invalid font weight specification: %1" ).arg(value); |
703 | return; |
704 | } |
705 | format->setFontWeight(qMin(w / 8, 99)); // taken from Qt's qss parser |
706 | } |
707 | } |
708 | |
709 | |
710 | void QssParser::parseFontSize(const QString &value, QTextCharFormat *format) |
711 | { |
712 | QRegExp rx("(\\d+)(pt|px)" ); |
713 | if (!rx.exactMatch(value)) { |
714 | qWarning() << Q_FUNC_INFO << tr("Invalid font size specification: %1" ).arg(value); |
715 | return; |
716 | } |
717 | if (rx.cap(2) == "px" ) |
718 | format->setProperty(QTextFormat::FontPixelSize, rx.cap(1).toInt()); |
719 | else |
720 | format->setFontPointSize(rx.cap(1).toInt()); |
721 | } |
722 | |
723 | |
724 | void QssParser::parseFontFamily(const QString &value, QTextCharFormat *format) |
725 | { |
726 | QString family = value; |
727 | if (family.startsWith('"') && family.endsWith('"')) { |
728 | family = family.mid(1, family.length() - 2); |
729 | } |
730 | format->setFontFamily(family); |
731 | } |
732 | |