_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::loadStyleSheet(const QString &styleSheet) {
- QString ss = styleSheet;
- ss = "file:////home/sputnick/devel/quassel/test.qss"; // FIXME
- if(ss.startsWith("file:///")) {
- ss.remove(0, 8);
- QFile file(ss);
- if(file.open(QFile::ReadOnly)) {
- QTextStream stream(&file);
- ss = stream.readAll();
- } else {
- qWarning() << tr("Could not read stylesheet \"%1\"!").arg(file.fileName());
- return;
- }
- }
+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).trimmed();
-
- if(property == "background" || property == "background-color")
- format.setBackground(parseBrushValue(value));
- else if(property == "foreground" || property == "color")
- format.setForeground(parseBrushValue(value));
+ 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::Sender;
else if(subElement == "nick")
fmtType |= UiStyle::Nick;
+ else if(subElement == "contents")
+ fmtType |= UiStyle::Contents;
else if(subElement == "hostmask")
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::PlainMsg;
else if(msgType == "notice")
fmtType |= UiStyle::NoticeMsg;
- else if(msgType == "server")
- fmtType |= UiStyle::ServerMsg;
- else if(msgType == "error")
- fmtType |= UiStyle::ErrorMsg;
+ else if(msgType == "action")
+ fmtType |= UiStyle::ActionMsg;
+ else if(msgType == "nick")
+ fmtType |= UiStyle::NickMsg;
+ else if(msgType == "mode")
+ fmtType |= UiStyle::ModeMsg;
else if(msgType == "join")
fmtType |= UiStyle::JoinMsg;
else if(msgType == "part")
fmtType |= UiStyle::QuitMsg;
else if(msgType == "kick")
fmtType |= UiStyle::KickMsg;
- else if(msgType == "rename")
- fmtType |= UiStyle::RenameMsg;
- else if(msgType == "mode")
- fmtType |= UiStyle::ModeMsg;
- else if(msgType == "action")
- fmtType |= UiStyle::ActionMsg;
+ else if(msgType == "kill")
+ fmtType |= UiStyle::KillMsg;
+ else if(msgType == "server")
+ fmtType |= UiStyle::ServerMsg;
+ else if(msgType == "info")
+ fmtType |= UiStyle::InfoMsg;
+ else if(msgType == "error")
+ 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("Invalid senderhash specification: %1").arg(condValue);
return UiStyle::Invalid;
}
- if(val >= 255) {
- qWarning() << Q_FUNC_INFO << tr("Senderhash can be at most \"fe\"!");
+ if(val >= 16) {
+ 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;
+ else if(condValue == "italic")
+ fmtType |= UiStyle::Italic;
+ else if(condValue == "underline")
+ fmtType |= UiStyle::Underline;
+ 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 = parseBrushValue(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;
}
-QBrush QssParser::parseBrushValue(const QString &str, bool *ok) {
+/******** Brush ********/
+
+QBrush QssParser::parseBrush(const QString &str, bool *ok) {
if(ok)
*ok = false;
- QColor c = parseColorValue(str);
+ QColor c = parseColor(str);
if(c.isValid()) {
if(ok)
*ok = true;
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*");
return QBrush();
}
-QColor QssParser::parseColorValue(const QString &str) {
+QColor QssParser::parseColor(const QString &str) {
if(str.startsWith("rgba")) {
ColorTuple tuple = parseColorTuple(str.mid(4));
if(tuple.count() == 4)
int idx;
while((idx = rx.indexIn(str)) == 0) {
qreal x = rx.cap(1).toDouble();
- QColor c = parseColorValue(rx.cap(3));
+ QColor c = parseColor(rx.cap(3));
if(!c.isValid())
return QGradientStops();
result << QGradientStop(x, c);
return result;
}
+
+/******** Font Properties ********/
+
+void QssParser::parseFont(const QString& value, QTextCharFormat* format) {
+ 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;
+ }
+ format->setFontItalic(false);
+ format->setFontWeight(QFont::Normal);
+ QStringList proplist = rx.cap(1).split(' ', QString::SkipEmptyParts);
+ 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->setFontWeight(QFont::Bold);
+ else { // number
+ int w = prop.toInt();
+ format->setFontWeight(qMin(w / 8, 99)); // taken from Qt's qss parser
+ }
+ }
+
+ if(rx.cap(3) == "px")
+ format->setProperty(QTextFormat::FontPixelSize, rx.cap(2).toInt());
+ else
+ format->setFontPointSize(rx.cap(2).toInt());
+
+ format->setFontFamily(rx.cap(4));
+}
+
+void QssParser::parseFontStyle(const QString& value, QTextCharFormat* format) {
+ if(value == "normal")
+ 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 {
+ qWarning() << Q_FUNC_INFO << tr("Invalid font style specification: %1").arg(value);
+ }
+}
+
+void QssParser::parseFontWeight(const QString& value, QTextCharFormat* format) {
+ if(value == "normal")
+ format->setFontWeight(QFont::Normal);
+ else if(value == "bold")
+ format->setFontWeight(QFont::Bold);
+ else {
+ bool ok;
+ int w = value.toInt(&ok);
+ if(!ok) {
+ qWarning() << Q_FUNC_INFO << tr("Invalid font weight specification: %1").arg(value);
+ return;
+ }
+ format->setFontWeight(qMin(w / 8, 99)); // taken from Qt's qss parser
+ }
+}
+
+void QssParser::parseFontSize(const QString& value, QTextCharFormat* format) {
+ QRegExp rx("\\(d+)(pt|px)");
+ if(!rx.exactMatch(value)) {
+ qWarning() << Q_FUNC_INFO << tr("Invalid font size specification: %1").arg(value);
+ return;
+ }
+ if(rx.cap(2) == "px")
+ format->setProperty(QTextFormat::FontPixelSize, rx.cap(1).toInt());
+ else
+ format->setFontPointSize(rx.cap(1).toInt());
+}
+
+void QssParser::parseFontFamily(const QString& value, QTextCharFormat* format) {
+ QString family = value;
+ if(family.startsWith('"') && family.endsWith('"')) {
+ family = family.mid(1, family.length() - 2);
+ }
+ format->setFontFamily(family);
+}