- length = 6;
- }
- } else if(s[pos+1] == 'O') { // reset formatting
- curfmt &= 0x000000ff; // we keep message type-specific formatting
- length = 2;
- } else if(s[pos+1] == 'R') { // reverse
- // TODO: implement reverse formatting
-
- length = 2;
- } else { // all others are toggles
- QString code = QString("%") + s[pos+1];
- if(s[pos+1] == 'D') code += s[pos+2];
- FormatType ftype = formatType(code);
- if(ftype == Invalid) {
- qWarning() << (QString("Invalid format code in string: %1").arg(s));
- continue;
- }
- curfmt ^= ftype;
- length = code.length();
- }
- s.remove(pos, length);
- if(pos == result.formatList.last().first)
- result.formatList.last().second = curfmt;
- else
- result.formatList.append(qMakePair((quint16)pos, curfmt));
- }
- result.plainText = s;
- return result;
-}
-
-QString UiStyle::mircToInternal(const QString &mirc_) {
- QString mirc = mirc_;
- mirc.replace('%', "%%"); // escape % just to be sure
- mirc.replace('\x02', "%B");
- mirc.replace('\x0f', "%O");
- mirc.replace('\x12', "%R");
- mirc.replace('\x16', "%R");
- mirc.replace('\x1d', "%S");
- mirc.replace('\x1f', "%U");
-
- // Now we bring the color codes (\x03) in a sane format that can be parsed more easily later.
- // %Dcfxx is foreground, %Dcbxx is background color, where xx is a 2 digit dec number denoting the color code.
- // %Dc- turns color off.
- // Note: We use the "mirc standard" as described in <http://www.mirc.co.uk/help/color.txt>.
- // This means that we don't accept something like \x03,5 (even though others, like WeeChat, do).
- int pos = 0;
- for(;;) {
- pos = mirc.indexOf('\x03', pos);
- if(pos < 0) break; // no more mirc color codes
- QString ins, num;
- int l = mirc.length();
- int i = pos + 1;
- // check for fg color
- if(i < l && mirc[i].isDigit()) {
- num = mirc[i++];
- if(i < l && mirc[i].isDigit()) num.append(mirc[i++]);
- else num.prepend('0');
- ins = QString("%Dcf%1").arg(num);
-
- if(i+1 < l && mirc[i] == ',' && mirc[i+1].isDigit()) {
- i++;
- num = mirc[i++];
- if(i < l && mirc[i].isDigit()) num.append(mirc[i++]);
- else num.prepend('0');
- ins += QString("%Dcb%1").arg(num);
- }
- } else {
- ins = "%Dc-";
- }
- mirc.replace(pos, i-pos, ins);
- }
- return mirc;
+ if (s[pos + 1] == 'D' && s[pos + 2] == 'c') { // mIRC color code
+ if (s[pos + 3] == '-') { // color off
+ curfmt.type &= 0x003fffff;
+ curfmt.foreground = QColor{};
+ curfmt.background = QColor{};
+ length = 4;
+ }
+ else {
+ quint32 color = 10 * s[pos + 4].digitValue() + s[pos + 5].digitValue();
+ // Color values 0-15 are traditional mIRC colors, defined in the stylesheet and thus going through the format engine
+ // Larger color values are hardcoded and applied separately (cf. https://modern.ircdocs.horse/formatting.html#colors-16-98)
+ if (s[pos + 3] == fgChar) {
+ if (color < 16) {
+ // Traditional mIRC color, defined in the stylesheet
+ curfmt.type &= 0xf0ffffff;
+ curfmt.type |= color << 24 | 0x00400000;
+ curfmt.foreground = QColor{};
+ }
+ else {
+ curfmt.type &= 0xf0bfffff; // mask out traditional foreground color
+ curfmt.foreground = extendedMircColor(color);
+ }
+ }
+ else {
+ if (color < 16) {
+ curfmt.type &= 0x0fffffff;
+ curfmt.type |= color << 28 | 0x00800000;
+ curfmt.background = QColor{};
+ }
+ else {
+ curfmt.type &= 0x0f7fffff; // mask out traditional background color
+ curfmt.background = extendedMircColor(color);
+ }
+ }
+ length = 6;
+ }
+ }
+ else if (s[pos + 1] == 'D' && s[pos + 2] == 'h') { // Hex color
+ QColor color{s.mid(pos + 4, 7)};
+ if (s[pos + 3] == fgChar) {
+ curfmt.type &= 0xf0bfffff; // mask out mIRC foreground color
+ curfmt.foreground = std::move(color);
+ }
+ else {
+ curfmt.type &= 0x0f7fffff; // mask out mIRC background color
+ curfmt.background = std::move(color);
+ }
+ length = 11;
+ }
+ else if (s[pos + 1] == 'O') { // reset formatting
+ curfmt.type &= 0x000000ff; // we keep message type-specific formatting
+ curfmt.foreground = QColor{};
+ curfmt.background = QColor{};
+ fgChar = 'f';
+ length = 2;
+ }
+ else if (s[pos + 1] == 'R') { // Reverse colors
+ fgChar = (fgChar == 'f' ? 'b' : 'f');
+ auto orig = static_cast<quint32>(curfmt.type & 0xffc00000);
+ curfmt.type &= 0x003fffff;
+ curfmt.type |= (orig & 0x00400000) << 1;
+ curfmt.type |= (orig & 0x0f000000) << 4;
+ curfmt.type |= (orig & 0x00800000) >> 1;
+ curfmt.type |= (orig & 0xf0000000) >> 4;
+ std::swap(curfmt.foreground, curfmt.background);
+ length = 2;
+ }
+ else { // all others are toggles
+ QString code = QString("%") + s[pos + 1];
+ if (s[pos + 1] == 'D')
+ code += s[pos + 2];
+ FormatType ftype = formatType(code);
+ if (ftype == FormatType::Invalid) {
+ pos++;
+ qWarning() << (QString("Invalid format code in string: %1").arg(s));
+ continue;
+ }
+ curfmt.type ^= ftype;
+ length = code.length();
+ }
+ s.remove(pos, length);
+ if (pos == result.formatList.back().first)
+ result.formatList.back().second = curfmt;
+ else
+ result.formatList.emplace_back(std::make_pair(pos, curfmt));
+ }
+ result.plainText = s;
+ return result;
+}
+
+QString UiStyle::mircToInternal(const QString& mirc_)
+{
+ QString mirc;
+ mirc.reserve(mirc_.size());
+ foreach (const QChar& c, mirc_) {
+ if ((c < '\x20' || c == '\x7f') && c != '\x03' && c != '\x04') {
+ switch (c.unicode()) {
+ case '\x02':
+ mirc += "%B";
+ break;
+ case '\x0f':
+ mirc += "%O";
+ break;
+ case '\x09':
+ mirc += " ";
+ break;
+ case '\x11':
+ // Monospace not supported yet
+ break;
+ case '\x12':
+ case '\x16':
+ mirc += "%R";
+ break;
+ case '\x1d':
+ mirc += "%I";
+ break;
+ case '\x1e':
+ mirc += "%S";
+ break;
+ case '\x1f':
+ mirc += "%U";
+ break;
+ case '\x7f':
+ mirc += QChar(0x2421);
+ break;
+ default:
+ mirc += QChar(0x2400 + c.unicode());
+ }
+ }
+ else {
+ if (c == '%')
+ mirc += c;
+ mirc += c;
+ }
+ }
+
+ // Now we bring the color codes (\x03) in a sane format that can be parsed more easily later.
+ // %Dcfxx is foreground, %Dcbxx is background color, where xx is a 2 digit dec number denoting the color code.
+ // %Dc- turns color off.
+ // Note: We use the "mirc standard" as described in <http://www.mirc.co.uk/help/color.txt>.
+ // This means that we don't accept something like \x03,5 (even though others, like WeeChat, do).
+ {
+ int pos = 0;
+ while (true) {
+ pos = mirc.indexOf('\x03', pos);
+ if (pos < 0)
+ break; // no more mirc color codes
+ QString ins, num;
+ int l = mirc.length();
+ int i = pos + 1;
+ // check for fg color
+ if (i < l && mirc[i].isDigit()) {
+ num = mirc[i++];
+ if (i < l && mirc[i].isDigit())
+ num.append(mirc[i++]);
+ else
+ num.prepend('0');
+ ins = QString("%Dcf%1").arg(num);
+
+ if (i + 1 < l && mirc[i] == ',' && mirc[i + 1].isDigit()) {
+ i++;
+ num = mirc[i++];
+ if (i < l && mirc[i].isDigit())
+ num.append(mirc[i++]);
+ else
+ num.prepend('0');
+ ins += QString("%Dcb%1").arg(num);
+ }
+ }
+ else {
+ ins = "%Dc-";
+ }
+ mirc.replace(pos, i - pos, ins);
+ }
+ }
+
+ // Hex colors, as specified in https://modern.ircdocs.horse/formatting.html#hex-color
+ // %Dhf#rrggbb is foreground, %Dhb#rrggbb is background
+ {
+ static const QRegExp rx{"[\\da-fA-F]{6}"};
+ int pos = 0;
+ while (true) {
+ if (pos >= mirc.length())
+ break;
+ pos = mirc.indexOf('\x04', pos);
+ if (pos < 0)
+ break;
+ int i = pos + 1;
+ QString ins;
+ auto num = mirc.mid(i, 6);
+ if (!num.isEmpty() && rx.exactMatch(num)) {
+ ins = "%Dhf#" + num.toLower();
+ i += 6;
+ if (i < mirc.length() && mirc[i] == ',' && !(num = mirc.mid(i + 1, 6)).isEmpty() && rx.exactMatch(num)) {
+ ins += "%Dhb#" + num.toLower();
+ i += 7;
+ }
+ }
+ else {
+ ins = "%Dc-";
+ }
+ mirc.replace(pos, i - pos, ins);
+ pos += ins.length();
+ }
+ }
+
+ return mirc;
+}
+
+QString UiStyle::systemTimestampFormatString()
+{
+ if (_systemTimestampFormatString.isEmpty()) {
+ // Calculate and cache the system timestamp format string
+ updateSystemTimestampFormat();
+ }
+ return _systemTimestampFormatString;
+}
+
+QString UiStyle::timestampFormatString()
+{
+ if (useCustomTimestampFormat()) {
+ return _timestampFormatString;
+ }
+ else {
+ return systemTimestampFormatString();
+ }