File indexing completed on 2024-12-22 04:40:10

0001 /*
0002     SPDX-FileCopyrightText: 2022 Mladen Milinkovic <max@smoothware.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "csshighlighter.h"
0008 
0009 #include <QDebug>
0010 #include <QGuiApplication>
0011 #include <QPalette>
0012 
0013 using namespace SubtitleComposer;
0014 
0015 union BlockState {
0016     int state;
0017     struct {
0018         quint16 indent;
0019         bool commentStart : 1;
0020         bool attributeStart : 1;
0021         bool styleStart : 1;
0022         bool stringStart : 1;
0023         bool valueStart : 1;
0024     };
0025 };
0026 
0027 CSSHighlighter::CSSHighlighter(QTextDocument *parent)
0028     : QSyntaxHighlighter(parent)
0029 {
0030     onPaletteChanged();
0031 }
0032 
0033 static QColor
0034 correctedColor(const QColor &color)
0035 {
0036     int h, s, v;
0037     color.getHsv(&h, &s, &v);
0038     return QColor::fromHsv(h, s, qMin(v * 5 / 2, 255));
0039 }
0040 
0041 bool
0042 CSSHighlighter::event(QEvent *ev)
0043 {
0044     if(ev->type() == QEvent::ApplicationPaletteChange || ev->type() == QEvent::PaletteChange) {
0045         onPaletteChanged();
0046     }
0047 
0048     return QSyntaxHighlighter::event(ev);
0049 }
0050 
0051 void
0052 CSSHighlighter::onPaletteChanged()
0053 {
0054     const QPalette pal = QGuiApplication::palette();
0055     bool dark = pal.color(QPalette::Window).value() < pal.color(QPalette::WindowText).value();
0056     m_commentFormat.setForeground(dark ? correctedColor(Qt::cyan) : Qt::cyan);
0057     m_attributeFormat.setForeground(dark ? correctedColor(Qt::green) : Qt::green);
0058     m_styleFormat.setForeground(dark ? correctedColor(Qt::yellow) : Qt::yellow);
0059     m_stringFormat.setForeground(dark ? correctedColor(Qt::red) : Qt::red);
0060     m_valueFormat.setForeground(dark ? correctedColor(Qt::white) : Qt::white);
0061 }
0062 
0063 #define FMT_START(x) { x ## Start = i; }
0064 #define FMT_END(x, pos) { setFormat(x ## Start, pos - x ## Start, m_ ## x ## Format); x ## Start = -1; }
0065 
0066 void
0067 CSSHighlighter::highlightBlock(const QString &text)
0068 {
0069     // init state
0070     BlockState state = { qMax(0, previousBlockState()) };
0071     int commentStart = state.commentStart ? 0 : -1;
0072     int attributeStart = state.attributeStart ? 0 : -1;
0073     int styleStart = state.styleStart ? 0 : -1;
0074     int stringStart = state.stringStart ? 0 : -1;
0075     int valueStart = state.valueStart ? 0 : -1;
0076     quint16 indent = state.indent;
0077 
0078     // process
0079     const int last = text.size() - 1;
0080     for(int i = 0; i <= last; i++) {
0081         const QChar &ch = text.at(i);
0082         if(commentStart != -1) {
0083             if(ch == QChar('*') && i != last && text.at(i + 1) == QChar('/')) {
0084                 i++;
0085                 FMT_END(comment, i + 1);
0086             }
0087         } else if(ch == QChar('/') && i != last && text.at(i + 1) == QChar('*')) {
0088             FMT_START(comment)
0089             i++;
0090         } else if(ch == QChar('{') && stringStart == -1) {
0091             indent++;
0092             if(attributeStart != -1)
0093                 FMT_END(attribute, i);
0094             styleStart = -1;
0095             if(valueStart != -1)
0096                 FMT_END(value, i);
0097         } else if(ch == QChar('}') && stringStart == -1) {
0098             indent--;
0099             styleStart = -1;
0100             if(valueStart != -1)
0101                 FMT_END(value, i);
0102         } else if(indent == 0) {
0103             if(attributeStart == -1) {
0104                 if(ch != QChar(','))
0105                     FMT_START(attribute);
0106             } else {
0107                 if(ch.isSpace() || ch == QChar(','))
0108                     FMT_END(attribute, i);
0109             }
0110         } else {
0111             if(stringStart != -1) {
0112                 if(ch == QChar('"')) {
0113                     FMT_END(string, i + 1);
0114                     if(valueStart != -1)
0115                         valueStart = i != last ? i + 1 : -1;
0116                 }
0117             } else if(valueStart != -1) {
0118                 if(ch == QChar(';')) {
0119                     FMT_END(value, i + 1);
0120                 } else if(ch == QChar('"')) {
0121                     FMT_START(string);
0122                 }
0123             } else if(styleStart == -1) {
0124                 if(!ch.isSpace() && ch != QChar(':'))
0125                     FMT_START(style);
0126             } else {
0127                 if(ch.isSpace() || ch == QChar(';')) {
0128                     styleStart = -1;
0129                 } else if(ch == QChar(':')) {
0130                     FMT_END(style, i);
0131                     while(i != last) {
0132                         const QChar &ch = text.at(++i);
0133                         if(ch.isSpace())
0134                             continue;
0135                         FMT_START(value);
0136                         if(ch == QChar('"'))
0137                             FMT_START(string);
0138                         break;
0139                     }
0140                 }
0141             }
0142 
0143         }
0144     }
0145 
0146     // cleanup
0147     if(commentStart != -1)
0148         FMT_END(comment, last + 1);
0149     if(attributeStart != -1)
0150         setFormat(attributeStart, last - attributeStart + 1, m_attributeFormat);
0151     if(styleStart != -1)
0152         setFormat(styleStart, last - styleStart + 1, m_styleFormat);
0153     if(stringStart != -1)
0154         setFormat(stringStart, last - stringStart + 1, m_stringFormat);
0155     else if(valueStart != -1)
0156         setFormat(valueStart, last - valueStart + 1, m_valueFormat);
0157 
0158     // store state
0159     state.commentStart = commentStart != -1;
0160     state.attributeStart = attributeStart != -1;
0161     state.styleStart = styleStart != -1;
0162     state.stringStart = stringStart != -1;
0163     state.valueStart = valueStart != -1;
0164     state.indent = indent;
0165     setCurrentBlockState(state.state);
0166 }