_paletteColorRoles["tooltip-text"] = QPalette::ToolTipText;
_paletteColorRoles["window"] = QPalette::Window;
_paletteColorRoles["window-text"] = QPalette::WindowText;
+
+ _uiStylePalette = QVector<QBrush>(UiStyle::NumRoles, QBrush());
+
+ _uiStyleColorRoles["marker-line"] = UiStyle::MarkerLine;
}
void QssParser::processStyleSheet(QString &ss) {
if(ss.isEmpty())
return;
- // Now we have the stylesheet itself in ss, start parsing
+ // Remove C-style comments /* */ or //
+ QRegExp commentRx("(//.*(\\n|$)|/\\*.*\\*/)");
+ commentRx.setMinimal(true);
+ ss.remove(commentRx);
+
// Palette definitions first, so we can apply roles later on
QRegExp paletterx("(Palette[^{]*)\\{([^}]+)\\}");
int pos = 0;
while((pos = paletterx.indexIn(ss, pos)) >= 0) {
- parsePaletteData(paletterx.cap(1).trimmed(), paletterx.cap(2).trimmed());
- pos += paletterx.matchedLength();
+ parsePaletteBlock(paletterx.cap(1).trimmed(), paletterx.cap(2).trimmed());
+ ss.remove(pos, paletterx.matchedLength());
}
// Now we can parse the rest of our custom blocks
- QRegExp blockrx("((?:ChatLine|BufferList|NickList|TreeView)[^{]*)\\{([^}]+)\\}");
+ QRegExp blockrx("((?:ChatLine|ChatListItem|NickListItem)[^{]*)\\{([^}]+)\\}");
pos = 0;
while((pos = blockrx.indexIn(ss, pos)) >= 0) {
//qDebug() << blockrx.cap(1) << blockrx.cap(2);
+ QString declaration = blockrx.cap(1).trimmed();
+ QString contents = blockrx.cap(2).trimmed();
- if(blockrx.cap(1).startsWith("ChatLine"))
- parseChatLineData(blockrx.cap(1).trimmed(), blockrx.cap(2).trimmed());
+ if(declaration.startsWith("ChatLine"))
+ parseChatLineBlock(declaration, contents);
+ else if(declaration.startsWith("ChatListItem") || declaration.startsWith("NickListItem"))
+ parseListItemBlock(declaration, contents);
//else
// TODO: add moar here
- pos += blockrx.matchedLength();
+ ss.remove(pos, blockrx.matchedLength());
}
-
}
-void QssParser::parseChatLineData(const QString &decl, const QString &contents) {
+/******** Parse a whole block: declaration { contents } *******/
+
+void QssParser::parseChatLineBlock(const QString &decl, const QString &contents) {
quint64 fmtType = parseFormatType(decl);
if(fmtType == UiStyle::Invalid)
return;
- QTextCharFormat format;
+ _formats[fmtType].merge(parseFormat(contents));
+}
+
+void QssParser::parseListItemBlock(const QString &decl, const QString &contents) {
+ quint32 fmtType = parseItemFormatType(decl);
+ if(fmtType == UiStyle::Invalid)
+ return;
+ _listItemFormats[fmtType].merge(parseFormat(contents));
+}
+
+// Palette { ... } specifies the application palette
+// ColorGroups can be specified like pseudo states, chaining is OR (contrary to normal CSS handling):
+// Palette:inactive:disabled { ... } applies to both the Inactive and the Disabled state
+void QssParser::parsePaletteBlock(const QString &decl, const QString &contents) {
+ QList<QPalette::ColorGroup> colorGroups;
+
+ // Check if we want to apply this palette definition for particular ColorGroups
+ QRegExp rx("Palette((:(normal|active|inactive|disabled))*)");
+ if(!rx.exactMatch(decl)) {
+ qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
+ return;
+ }
+ if(!rx.cap(1).isEmpty()) {
+ QStringList groups = rx.cap(1).split(':', QString::SkipEmptyParts);
+ foreach(QString g, groups) {
+ if((g == "normal" || g == "active") && !colorGroups.contains(QPalette::Active))
+ colorGroups.append(QPalette::Active);
+ else if(g == "inactive" && !colorGroups.contains(QPalette::Inactive))
+ colorGroups.append(QPalette::Inactive);
+ else if(g == "disabled" && !colorGroups.contains(QPalette::Disabled))
+ colorGroups.append(QPalette::Disabled);
+ }
+ }
+
+ // Now let's go through the roles
foreach(QString line, contents.split(';', QString::SkipEmptyParts)) {
int idx = line.indexOf(':');
if(idx <= 0) {
- qWarning() << Q_FUNC_INFO << tr("Invalid property declaration: %1").arg(line.trimmed());
+ qWarning() << Q_FUNC_INFO << tr("Invalid palette role assignment: %1").arg(line.trimmed());
continue;
}
- QString property = line.left(idx).trimmed();
- QString value = line.mid(idx + 1).simplified();
-
- if(property == "background" || property == "background-color")
- format.setBackground(parseBrush(value));
- else if(property == "foreground" || property == "color")
- format.setForeground(parseBrush(value));
-
- // font-related properties
- else if(property.startsWith("font")) {
- if(property == "font")
- parseFont(value, &format);
- else if(property == "font-style")
- parseFontStyle(value, &format);
- else if(property == "font-weight")
- parseFontWeight(value, &format);
- else if(property == "font-size")
- parseFontSize(value, &format);
- else if(property == "font-family")
- parseFontFamily(value, &format);
- else {
- qWarning() << Q_FUNC_INFO << tr("Invalid font property: %1").arg(line);
- continue;
- }
- }
+ QString rolestr = line.left(idx).trimmed();
+ QString brushstr = line.mid(idx + 1).trimmed();
- else {
- qWarning() << Q_FUNC_INFO << tr("Unknown ChatLine property: %1").arg(property);
- }
+ if(_paletteColorRoles.contains(rolestr)) {
+ QBrush brush = parseBrush(brushstr);
+ if(colorGroups.count()) {
+ foreach(QPalette::ColorGroup group, colorGroups)
+ _palette.setBrush(group, _paletteColorRoles.value(rolestr), brush);
+ } else
+ _palette.setBrush(_paletteColorRoles.value(rolestr), brush);
+ } else if(_uiStyleColorRoles.contains(rolestr)) {
+ _uiStylePalette[_uiStyleColorRoles.value(rolestr)] = parseBrush(brushstr);
+ } else
+ qWarning() << Q_FUNC_INFO << tr("Unknown palette role name: %1").arg(rolestr);
}
-
- _formats[fmtType] = format;
}
+/******** Determine format types from a block declaration ********/
+
quint64 QssParser::parseFormatType(const QString &decl) {
- QRegExp rx("ChatLine(?:::(\\w+))?(?:#(\\w+))?(?:\\[([=-,\\\"\\w\\s]+)\\])?\\s*");
+ QRegExp rx("ChatLine(?:::(\\w+))?(?:#([\\w\\-]+))?(?:\\[([=-,\\\"\\w\\s]+)\\])?");
// $1: subelement; $2: msgtype; $3: conditionals
if(!rx.exactMatch(decl)) {
qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
fmtType |= UiStyle::Hostmask;
else if(subElement == "modeflags")
fmtType |= UiStyle::ModeFlags;
+ else if(subElement == "url")
+ fmtType |= UiStyle::Url;
else {
qWarning() << Q_FUNC_INFO << tr("Invalid subelement name in %1").arg(decl);
return UiStyle::Invalid;
fmtType |= UiStyle::ErrorMsg;
else if(msgType == "daychange")
fmtType |= UiStyle::DayChangeMsg;
+ else if(msgType == "topic")
+ fmtType |= UiStyle::TopicMsg;
+ else if(msgType == "netsplit-join")
+ fmtType |= UiStyle::NetsplitJoinMsg;
+ else if(msgType == "netsplit-quit")
+ fmtType |= UiStyle::NetsplitQuitMsg;
else {
qWarning() << Q_FUNC_INFO << tr("Invalid message type in %1").arg(decl);
}
}
// Next up: conditional (formats, labels, nickhash)
- QRegExp condRx("\\s*(\\w+)\\s*=\\s*\"(\\w+)\"\\s*");
+ QRegExp condRx("\\s*([\\w\\-]+)\\s*=\\s*\"(\\w+)\"\\s*");
if(!conditions.isEmpty()) {
foreach(const QString &cond, conditions.split(',', QString::SkipEmptyParts)) {
if(!condRx.exactMatch(cond)) {
quint64 labeltype = 0;
if(condValue == "highlight")
labeltype = UiStyle::Highlight;
+ else if(condValue == "selected")
+ labeltype = UiStyle::Selected;
else {
qWarning() << Q_FUNC_INFO << tr("Invalid message label: %1").arg(condValue);
return UiStyle::Invalid;
qWarning() << Q_FUNC_INFO << tr("Senderhash can be at most \"0x0f\"!");
return UiStyle::Invalid;
}
- fmtType |= val << 48;
+ fmtType |= ++val << 48;
}
} else if(condName == "format") {
if(condValue == "bold")
fmtType |= UiStyle::Bold;
- if(condValue == "italic")
+ else if(condValue == "italic")
fmtType |= UiStyle::Italic;
- if(condValue == "underline")
+ else if(condValue == "underline")
fmtType |= UiStyle::Underline;
- if(condValue == "reverse")
+ else if(condValue == "reverse")
fmtType |= UiStyle::Reverse;
else {
qWarning() << Q_FUNC_INFO << tr("Invalid format name: %1").arg(condValue);
return UiStyle::Invalid;
}
-
+ } else if(condName == "fg-color" || condName == "bg-color") {
+ bool ok;
+ quint8 col = condValue.toUInt(&ok, 16);
+ if(!ok || col > 0x0f) {
+ qWarning() << Q_FUNC_INFO << tr("Illegal IRC color specification (must be between 00 and 0f): %1").arg(condValue);
+ return UiStyle::Invalid;
+ }
+ if(condName == "fg-color")
+ fmtType |= 0x00400000 | (quint32)(col << 24);
+ else
+ fmtType |= 0x00800000 | (quint32)(col << 28);
+ } else {
+ qWarning() << Q_FUNC_INFO << tr("Unhandled condition: %1").arg(condName);
+ return UiStyle::Invalid;
}
- // TODO: colors
}
}
return fmtType;
}
-// Palette { ... } specifies the application palette
-// ColorGroups can be specified like pseudo states, chaining is OR (contrary to normal CSS handling):
-// Palette:inactive:disabled { ... } applies to both the Inactive and the Disabled state
-void QssParser::parsePaletteData(const QString &decl, const QString &contents) {
- QList<QPalette::ColorGroup> colorGroups;
-
- // Check if we want to apply this palette definition for particular ColorGroups
- QRegExp rx("Palette((:(normal|active|inactive|disabled))*)");
+// FIXME: Code duplication
+quint32 QssParser::parseItemFormatType(const QString &decl) {
+ QRegExp rx("(Chat|Nick)ListItem(?:\\[([=-,\\\"\\w\\s]+)\\])?");
+ // $1: item type; $2: properties
if(!rx.exactMatch(decl)) {
qWarning() << Q_FUNC_INFO << tr("Invalid block declaration: %1").arg(decl);
- return;
+ return UiStyle::Invalid;
}
- if(!rx.cap(1).isEmpty()) {
- QStringList groups = rx.cap(1).split(':', QString::SkipEmptyParts);
- foreach(QString g, groups) {
- if((g == "normal" || g == "active") && !colorGroups.contains(QPalette::Active))
- colorGroups.append(QPalette::Active);
- else if(g == "inactive" && !colorGroups.contains(QPalette::Inactive))
- colorGroups.append(QPalette::Inactive);
- else if(g == "disabled" && !colorGroups.contains(QPalette::Disabled))
- colorGroups.append(QPalette::Disabled);
+ QString mainItemType = rx.cap(1);
+ QString properties = rx.cap(2);
+
+ quint32 fmtType = 0;
+
+ // Next up: properties
+ QString type, state;
+ if(!properties.isEmpty()) {
+ QHash<QString, QString> props;
+ QRegExp propRx("\\s*([\\w\\-]+)\\s*=\\s*\"([\\w\\-]+)\"\\s*");
+ foreach(const QString &prop, properties.split(',', QString::SkipEmptyParts)) {
+ if(!propRx.exactMatch(prop)) {
+ qWarning() << Q_FUNC_INFO << tr("Invalid proplist %1").arg(prop);
+ return UiStyle::Invalid;
+ }
+ props[propRx.cap(1)] = propRx.cap(2);
}
+ type = props.value("type");
+ state = props.value("state");
}
- // Now let's go through the roles
- foreach(QString line, contents.split(';', QString::SkipEmptyParts)) {
+ if(mainItemType == "Chat") {
+ fmtType |= UiStyle::BufferViewItem;
+ if(!type.isEmpty()) {
+ if(type == "network")
+ fmtType |= UiStyle::NetworkItem;
+ else if(type == "channel")
+ fmtType |= UiStyle::ChannelBufferItem;
+ else if(type == "query")
+ fmtType |= UiStyle::QueryBufferItem;
+ else {
+ qWarning() << Q_FUNC_INFO << tr("Invalid chatlist item type %1").arg(type);
+ return UiStyle::Invalid;
+ }
+ }
+ if(!state.isEmpty()) {
+ if(state == "inactive")
+ fmtType |= UiStyle::InactiveBuffer;
+ else if(state == "channel-event")
+ fmtType |= UiStyle::ActiveBuffer;
+ else if(state == "unread-message")
+ fmtType |= UiStyle::UnreadBuffer;
+ else if(state == "highlighted")
+ fmtType |= UiStyle::HighlightedBuffer;
+ else if(state == "away")
+ fmtType |= UiStyle::UserAway;
+ else {
+ qWarning() << Q_FUNC_INFO << tr("Invalid chatlist state %1").arg(state);
+ return UiStyle::Invalid;
+ }
+ }
+ } else { // NickList
+ fmtType |= UiStyle::NickViewItem;
+ if(!type.isEmpty()) {
+ if(type == "user") {
+ fmtType |= UiStyle::IrcUserItem;
+ if(state == "away")
+ fmtType |= UiStyle::UserAway;
+ } else if(type == "category")
+ fmtType |= UiStyle::UserCategoryItem;
+ }
+ }
+ return fmtType;
+}
+
+/******** Parse a whole format attribute block ********/
+
+QTextCharFormat QssParser::parseFormat(const QString &qss) {
+ QTextCharFormat format;
+
+ foreach(QString line, qss.split(';', QString::SkipEmptyParts)) {
int idx = line.indexOf(':');
if(idx <= 0) {
- qWarning() << Q_FUNC_INFO << tr("Invalid palette role assignment: %1").arg(line.trimmed());
+ qWarning() << Q_FUNC_INFO << tr("Invalid property declaration: %1").arg(line.trimmed());
continue;
}
- QString rolestr = line.left(idx).trimmed();
- QString brushstr = line.mid(idx + 1).trimmed();
- if(!_paletteColorRoles.contains(rolestr)) {
- qWarning() << Q_FUNC_INFO << tr("Unknown palette role name: %1").arg(rolestr);
- continue;
+ QString property = line.left(idx).trimmed();
+ QString value = line.mid(idx + 1).simplified();
+
+ if(property == "background" || property == "background-color")
+ format.setBackground(parseBrush(value));
+ else if(property == "foreground" || property == "color")
+ format.setForeground(parseBrush(value));
+
+ // font-related properties
+ else if(property.startsWith("font")) {
+ if(property == "font")
+ parseFont(value, &format);
+ else if(property == "font-style")
+ parseFontStyle(value, &format);
+ else if(property == "font-weight")
+ parseFontWeight(value, &format);
+ else if(property == "font-size")
+ parseFontSize(value, &format);
+ else if(property == "font-family")
+ parseFontFamily(value, &format);
+ else {
+ qWarning() << Q_FUNC_INFO << tr("Invalid font property: %1").arg(line);
+ continue;
+ }
+ }
+
+ else {
+ qWarning() << Q_FUNC_INFO << tr("Unknown ChatLine property: %1").arg(property);
}
- QBrush brush = parseBrush(brushstr);
- if(colorGroups.count()) {
- foreach(QPalette::ColorGroup group, colorGroups)
- _palette.setBrush(group, _paletteColorRoles.value(rolestr), brush);
- } else
- _palette.setBrush(_paletteColorRoles.value(rolestr), brush);
}
+
+ return format;
}
+/******** Brush ********/
+
QBrush QssParser::parseBrush(const QString &str, bool *ok) {
if(ok)
*ok = false;
qWarning() << Q_FUNC_INFO << tr("Invalid palette color role specification: %1").arg(str);
return QBrush();
}
- if(!_paletteColorRoles.contains(rx.cap(1))) {
- qWarning() << Q_FUNC_INFO << tr("Unknown palette color role: %1").arg(rx.cap(1));
- return QBrush();
- }
- return QBrush(_palette.brush(_paletteColorRoles.value(rx.cap(1))));
+ if(_paletteColorRoles.contains(rx.cap(1)))
+ return QBrush(_palette.brush(_paletteColorRoles.value(rx.cap(1))));
+ if(_uiStyleColorRoles.contains(rx.cap(1)))
+ return QBrush(_uiStylePalette.at(_uiStyleColorRoles.value(rx.cap(1))));
+ qWarning() << Q_FUNC_INFO << tr("Unknown palette color role: %1").arg(rx.cap(1));
+ return QBrush();
} else if(str.startsWith("qlineargradient")) {
static QString rxFloat("\\s*(-?\\s*[0-9]*\\.?[0-9]+)\\s*");
/******** Font Properties ********/
void QssParser::parseFont(const QString& value, QTextCharFormat* format) {
- QRegExp rx("((?:(?:normal|italic|oblique|bold|100|200|300|400|500|600|700|800|900) ){0,2}) ?(\\d+)(pt|px)? \"(.*)\"");
+ QRegExp rx("((?:(?:normal|italic|oblique|underline|bold|100|200|300|400|500|600|700|800|900) ){0,2}) ?(\\d+)(pt|px)? \"(.*)\"");
if(!rx.exactMatch(value)) {
qWarning() << Q_FUNC_INFO << tr("Invalid font specification: %1").arg(value);
return;
foreach(QString prop, proplist) {
if(prop == "italic")
format->setFontItalic(true);
+ else if(prop == "underline")
+ format->setFontUnderline(true);
//else if(prop == "oblique")
// format->setStyle(QFont::StyleOblique);
else if(prop == "bold")
format->setFontItalic(false);
else if(value == "italic")
format->setFontItalic(true);
+ else if(value == "underline")
+ format->setFontUnderline(true);
//else if(value == "oblique")
// format->setStyle(QFont::StyleOblique);
else {