From 95f1ccb2ee4537cec7ecd2e834b6608dea2b7dcb Mon Sep 17 00:00:00 2001 From: Dany Thinnes Date: Sun, 9 Aug 2026 21:57:49 +0200 Subject: [PATCH] Farbvorschau und Farbauswahl hinter Farbwerten implementiert --- src/editor/CMakeLists.txt | 2 + src/editor/CodeEditor.cpp | 165 +++++++-------- src/editor/CodeEditor.h | 10 + src/editor/ColorIndicator.cpp | 387 ++++++++++++++++++++++++++++++++++ src/editor/ColorIndicator.h | 68 ++++++ 5 files changed, 541 insertions(+), 91 deletions(-) create mode 100644 src/editor/ColorIndicator.cpp create mode 100644 src/editor/ColorIndicator.h diff --git a/src/editor/CMakeLists.txt b/src/editor/CMakeLists.txt index 2d5e180..63e8322 100644 --- a/src/editor/CMakeLists.txt +++ b/src/editor/CMakeLists.txt @@ -11,6 +11,8 @@ set(EDITOR_SOURCES SearchPanel.h FileSearchPanel.cpp FileSearchPanel.h + ColorIndicator.cpp + ColorIndicator.h ) add_library(BareCode_Editor STATIC ${EDITOR_SOURCES}) diff --git a/src/editor/CodeEditor.cpp b/src/editor/CodeEditor.cpp index 21c80ce..0579409 100644 --- a/src/editor/CodeEditor.cpp +++ b/src/editor/CodeEditor.cpp @@ -1,5 +1,6 @@ #include "CodeEditor.h" #include "LineNumberArea.h" +#include "ColorIndicator.h" #include "core/Settings.h" #include "highlighter/HighlighterFactory.h" @@ -9,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +22,8 @@ CodeEditor::CodeEditor(Settings *settings, QWidget *parent) : QPlainTextEdit(parent) , m_settings(settings) { - m_lineNumberArea = new LineNumberArea(this); + m_lineNumberArea = new LineNumberArea(this); + m_colorIndicator = new ColorIndicator(this); setupEditor(); connect(this, &CodeEditor::blockCountChanged, @@ -199,120 +202,100 @@ void CodeEditor::paintEvent(QPaintEvent *event) // Zuerst den normalen Editor-Inhalt zeichnen QPlainTextEdit::paintEvent(event); - // Danach die Einrück-Führungslinien darüber legen + // Einrück-Führungslinien const int tabSize = m_settings->tabSize(); - if (tabSize <= 0) + if (tabSize > 0) { - return; - } + QPainter painter(viewport()); - QPainter painter(viewport()); + QColor guideColor = palette().color(QPalette::Text); + guideColor.setAlpha(30); + painter.setPen(QPen(guideColor, 1, Qt::SolidLine)); - // Farbe: subtil, passt zu Hell- und Dunkeltheme - QColor guideColor = palette().color(QPalette::Text); - guideColor.setAlpha(30); - painter.setPen(QPen(guideColor, 1, Qt::SolidLine)); + const QFontMetrics fm(font()); + const int spaceWidth = fm.horizontalAdvance(' '); + const int tabPixels = tabSize * spaceWidth; - const QFontMetrics fm(font()); - const int spaceWidth = fm.horizontalAdvance(' '); - const int tabPixels = tabSize * spaceWidth; - - if (tabPixels <= 0) - { - return; - } - - // X-Startposition des Textes direkt aus dem Layout des ersten Blocks holen. - // Das ist der einzige zuverlässige Weg — Qt berücksichtigt intern Margins, - // Gutter und Frame-Abstände die sich nicht sauber manuell nachrechnen lassen. - int textOriginX = 0; - { - QTextBlock firstBlock = firstVisibleBlock(); - if (!firstBlock.isValid()) + if (tabPixels > 0) { - firstBlock = document()->begin(); - } - if (firstBlock.isValid()) - { - const QRectF blockRect = blockBoundingGeometry(firstBlock) - .translated(contentOffset()); - // Position des ersten Zeichens im Layout - const QTextLayout *layout = firstBlock.layout(); - if (layout && layout->lineCount() > 0) + int textOriginX = 0; { - textOriginX = static_cast(blockRect.left() - + layout->lineAt(0).position().x()); - } - else - { - textOriginX = static_cast(blockRect.left()); - } - } - } - - // Horizontalen Scroll-Offset berücksichtigen - const int scrollX = horizontalScrollBar()->value(); - - // Sichtbaren Zeilenbereich bestimmen - QTextBlock block = firstVisibleBlock(); - const int bottom = event->rect().bottom(); - - while (block.isValid()) - { - const QRectF blockRect = blockBoundingGeometry(block).translated(contentOffset()); - - if (blockRect.top() > bottom) - { - break; - } - - if (block.isVisible()) - { - const QString text = block.text(); - - // Einrückungstiefe der Zeile zählen (Leerzeichen / Tabs) - int indentSpaces = 0; - for (const QChar &ch : text) - { - if (ch == ' ') + QTextBlock firstBlock = firstVisibleBlock(); + if (!firstBlock.isValid()) { - ++indentSpaces; + firstBlock = document()->begin(); } - else if (ch == '\t') + if (firstBlock.isValid()) { - // Tab auffüllen auf nächsten Tab-Stop - indentSpaces = ((indentSpaces / tabSize) + 1) * tabSize; - } - else - { - break; + const QRectF blockRect = blockBoundingGeometry(firstBlock) + .translated(contentOffset()); + const QTextLayout *layout = firstBlock.layout(); + if (layout && layout->lineCount() > 0) + { + textOriginX = static_cast(blockRect.left() + + layout->lineAt(0).position().x()); + } + else + { + textOriginX = static_cast(blockRect.left()); + } } } - const int indentStops = indentSpaces / tabSize; + const int scrollX = horizontalScrollBar()->value(); - // Für jeden Einrückungslevel eine vertikale Linie zeichnen - for (int stop = 1; stop <= indentStops; ++stop) + QTextBlock block = firstVisibleBlock(); + const int bottom = event->rect().bottom(); + + while (block.isValid()) { - const int xPixel = textOriginX + stop * tabPixels - scrollX; + const QRectF blockRect = blockBoundingGeometry(block) + .translated(contentOffset()); + if (blockRect.top() > bottom) { break; } - // Nur im sichtbaren Bereich zeichnen - if (xPixel < lineNumberAreaWidth() || xPixel > viewport()->width()) + if (block.isVisible()) { - continue; + const QString text = block.text(); + int indentSpaces = 0; + for (const QChar &ch : text) + { + if (ch == ' ') { ++indentSpaces; } + else if (ch == '\t') { indentSpaces = ((indentSpaces / tabSize) + 1) * tabSize; } + else { break; } + } + + const int indentStops = indentSpaces / tabSize; + for (int stop = 1; stop <= indentStops; ++stop) + { + const int xPixel = textOriginX + stop * tabPixels - scrollX; + if (xPixel < lineNumberAreaWidth() || xPixel > viewport()->width()) + { + continue; + } + painter.drawLine(xPixel, + static_cast(blockRect.top()), + xPixel, + static_cast(blockRect.bottom())); + } } - - const int y1 = static_cast(blockRect.top()); - const int y2 = static_cast(blockRect.bottom()); - - painter.drawLine(xPixel, y1, xPixel, y2); + block = block.next(); } } - block = block.next(); + // Farbvorschau-Quadrate zeichnen + m_colorIndicator->paint(painter, event); } } +void CodeEditor::mousePressEvent(QMouseEvent *event) +{ + // Zuerst prüfen ob ein Farbquadrat geklickt wurde + if (m_colorIndicator->handleMousePress(event)) + { + return; + } + QPlainTextEdit::mousePressEvent(event); +} void CodeEditor::resizeEvent(QResizeEvent *event) { diff --git a/src/editor/CodeEditor.h b/src/editor/CodeEditor.h index 603a07f..9ca7837 100644 --- a/src/editor/CodeEditor.h +++ b/src/editor/CodeEditor.h @@ -3,10 +3,12 @@ #include #include #include +#include class LineNumberArea; class Settings; class SyntaxHighlighter; +class ColorIndicator; // --------------------------------------------------------------------------- // CodeEditor – Core editing widget. @@ -39,6 +41,12 @@ public: QString filePath() const; bool isModified() const; + // Öffentliche Hilfsmethoden für ColorIndicator + // (die Qt-Originale sind protected und von außen nicht erreichbar) + QTextBlock firstVisibleBlockPublic() const { return firstVisibleBlock(); } + QRectF blockBoundingGeometryPublic(const QTextBlock &b) const { return blockBoundingGeometry(b); } + QPointF contentOffsetPublic() const { return contentOffset(); } + signals: void fileSaved(const QString &filePath); @@ -46,6 +54,7 @@ protected: void resizeEvent(QResizeEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; private slots: void updateLineNumberAreaWidth(int newBlockCount); @@ -60,5 +69,6 @@ private: Settings *m_settings = nullptr; LineNumberArea *m_lineNumberArea = nullptr; SyntaxHighlighter *m_highlighter = nullptr; + ColorIndicator *m_colorIndicator = nullptr; QString m_filePath; }; diff --git a/src/editor/ColorIndicator.cpp b/src/editor/ColorIndicator.cpp new file mode 100644 index 0000000..d440f8b --- /dev/null +++ b/src/editor/ColorIndicator.cpp @@ -0,0 +1,387 @@ +#include "ColorIndicator.h" +#include "CodeEditor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Kombinierter Regex — erfasst alle CSS-Farbformate in einer Runde +// --------------------------------------------------------------------------- +const QRegularExpression ColorIndicator::s_colorRegex( + // #rgb / #rrggbb / #rrggbbaa + R"(#(?:[0-9A-Fa-f]{8}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})(?=[^0-9A-Fa-f]|$))" + R"(|rgba?\s*\([^)]+\))" // rgb() / rgba() + R"(|hsla?\s*\([^)]+\))" // hsl() / hsla() + // Benannte Farben — nur als ganzes Wort + R"(|\b(?:aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|)" + R"(blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|)" + R"(chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|)" + R"(darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|)" + R"(darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|)" + R"(darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|)" + R"(deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|)" + R"(forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|)" + R"(greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|)" + R"(lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|)" + R"(lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|)" + R"(lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|)" + R"(lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|)" + R"(mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|)" + R"(mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|)" + R"(navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|)" + R"(palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|)" + R"(powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|)" + R"(sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|)" + R"(slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|)" + R"(violet|wheat|white|whitesmoke|yellow|yellowgreen)\b)", + QRegularExpression::CaseInsensitiveOption +); + +// --------------------------------------------------------------------------- +// Konstruktor +// --------------------------------------------------------------------------- +ColorIndicator::ColorIndicator(CodeEditor *editor) + : QObject(editor) + , m_editor(editor) +{ + // Cache invalidieren und Viewport neu zeichnen wenn sich der Text ändert + connect(m_editor->document(), &QTextDocument::contentsChanged, + this, [this]() + { + m_cacheFirstBlock = -1; + m_cacheLastBlock = -1; + m_editor->viewport()->update(); + }); + + // Auch beim Scrollen neu zeichnen (Cache bleibt gültig, nur Position ändert sich) + connect(m_editor->verticalScrollBar(), &QScrollBar::valueChanged, + this, [this]() + { + m_cacheFirstBlock = -1; + m_cacheLastBlock = -1; + }); +} + +// --------------------------------------------------------------------------- +// Paint – wird aus CodeEditor::paintEvent aufgerufen +// --------------------------------------------------------------------------- +void ColorIndicator::paint(QPainter &painter, QPaintEvent *event) +{ + rebuildCache(); + + const int squareSize = m_editor->fontMetrics().height() - 4; + const int radius = 2; + + for (const ColorMatch &m : m_cache) + { + if (!event->rect().intersects(m.rect)) + { + continue; + } + + // Rahmen + painter.setPen(QColor(0, 0, 0, 80)); + painter.setBrush(m.color); + painter.drawRoundedRect(m.rect, radius, radius); + + // Schachbrettmuster als Hintergrund für transparente Farben + if (m.color.alpha() < 255) + { + const int half = squareSize / 2; + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(180, 180, 180)); + painter.drawRect(m.rect.x(), m.rect.y(), half, half); + painter.drawRect(m.rect.x() + half, m.rect.y() + half, half, half); + painter.setBrush(m.color); + painter.drawRoundedRect(m.rect, radius, radius); + } + } +} + +// --------------------------------------------------------------------------- +// Mouse – wird aus CodeEditor::mousePressEvent aufgerufen +// --------------------------------------------------------------------------- +bool ColorIndicator::handleMousePress(QMouseEvent *event) +{ + for (const ColorMatch &m : m_cache) + { + if (!m.rect.contains(event->pos())) + { + continue; + } + + // Farb-Dialog öffnen + QColorDialog dlg(m.color, m_editor); + dlg.setOption(QColorDialog::ShowAlphaChannel, true); + dlg.setWindowTitle(QObject::tr("Farbe wählen")); + + if (dlg.exec() != QDialog::Accepted) + { + return true; + } + + const QColor newColor = dlg.selectedColor(); + + // Ursprünglichen Farbwert im Dokument ersetzen + QTextBlock block = m_editor->document()->findBlockByNumber(m.blockNumber); + if (!block.isValid()) + { + return true; + } + + // Neuen Farbwert als Hex-String formatieren + QString newValue; + if (newColor.alpha() < 255) + { + newValue = newColor.name(QColor::HexArgb); // #aarrggbb + // CSS erwartet #rrggbbaa — Bytes umstellen + // Qt liefert #aarrggbb, CSS will #rrggbbaa + newValue = QString("#%1%2%3%4") + .arg(newColor.red(), 2, 16, QChar('0')) + .arg(newColor.green(), 2, 16, QChar('0')) + .arg(newColor.blue(), 2, 16, QChar('0')) + .arg(newColor.alpha(), 2, 16, QChar('0')); + } + else + { + newValue = newColor.name(QColor::HexRgb); // #rrggbb + } + + QTextCursor cursor(block); + cursor.setPosition(block.position() + m.posInBlock); + cursor.setPosition(block.position() + m.posInBlock + m.length, + QTextCursor::KeepAnchor); + cursor.insertText(newValue); + + // Cache invalidieren + m_cache.clear(); + m_cacheFirstBlock = -1; + m_cacheLastBlock = -1; + + return true; + } + + return false; +} + +// --------------------------------------------------------------------------- +// Cache aufbauen – nur für sichtbare Blöcke +// --------------------------------------------------------------------------- +void ColorIndicator::rebuildCache() +{ + QTextBlock firstVisible = m_editor->firstVisibleBlockPublic(); + const int firstNum = firstVisible.blockNumber(); + + // Letzten sichtbaren Block bestimmen + int lastNum = firstNum; + { + QTextBlock b = firstVisible; + const int bot = m_editor->viewport()->height(); + while (b.isValid()) + { + const QRectF r = m_editor->blockBoundingGeometryPublic(b) + .translated(m_editor->contentOffsetPublic()); + if (r.top() > bot) + { + break; + } + lastNum = b.blockNumber(); + b = b.next(); + } + } + + // Cache noch aktuell? + if (firstNum == m_cacheFirstBlock && lastNum == m_cacheLastBlock) + { + return; + } + + m_cache.clear(); + m_cacheFirstBlock = firstNum; + m_cacheLastBlock = lastNum; + + const int squareSize = m_editor->fontMetrics().height() - 4; + const int scrollX = m_editor->horizontalScrollBar()->value(); + + QTextBlock block = firstVisible; + while (block.isValid() && block.blockNumber() <= lastNum) + { + const QRectF blockRect = m_editor->blockBoundingGeometryPublic(block) + .translated(m_editor->contentOffsetPublic()); + + const QList found = findColorsInBlock(block.text(), + block.blockNumber()); + for (ColorMatch m : found) + { + // X-Position des Farbwerts im Viewport berechnen + const QTextLayout *layout = block.layout(); + if (!layout || layout->lineCount() == 0) + { + continue; + } + + const QTextLine line = layout->lineAt(0); + // Position nach dem Ende des Farbwerts + const qreal endCharX = line.cursorToX(m.posInBlock + m.length); + const int x = static_cast(blockRect.left() + endCharX) + - scrollX + 3; + + if (x + squareSize > m_editor->viewport()->width()) + { + continue; + } + + const int y = static_cast(blockRect.top()) + + (static_cast(blockRect.height()) - squareSize) / 2; + + m.rect = QRect(x, y, squareSize, squareSize); + m_cache.append(m); + } + + block = block.next(); + } +} + +// --------------------------------------------------------------------------- +// Farbwerte in einer Zeile suchen +// --------------------------------------------------------------------------- +QList ColorIndicator::findColorsInBlock( + const QString &text, int blockNumber) const +{ + QList result; + + QRegularExpressionMatchIterator it = s_colorRegex.globalMatch(text); + while (it.hasNext()) + { + QRegularExpressionMatch match = it.next(); + const QString token = match.captured(0); + const QColor color = parseColor(token); + + if (!color.isValid()) + { + continue; + } + + ColorMatch m; + m.blockNumber = blockNumber; + m.posInBlock = static_cast(match.capturedStart()); + m.length = static_cast(match.capturedLength()); + m.color = color; + result.append(m); + } + + return result; +} + +// --------------------------------------------------------------------------- +// Farb-Parser +// --------------------------------------------------------------------------- +QColor ColorIndicator::parseColor(const QString &token) +{ + const QString t = token.trimmed(); + + if (t.startsWith('#')) { return parseHex(t); } + if (t.startsWith("rgba", Qt::CaseInsensitive)) { return parseRgba(t); } + if (t.startsWith("rgb", Qt::CaseInsensitive)) { return parseRgb(t); } + if (t.startsWith("hsla", Qt::CaseInsensitive)) { return parseHsla(t); } + if (t.startsWith("hsl", Qt::CaseInsensitive)) { return parseHsl(t); } + + return parseNamed(t); +} + +QColor ColorIndicator::parseHex(const QString &s) +{ + // #rgb → #rrggbb + if (s.length() == 4) + { + return QColor(QString("#%1%1%2%2%3%3") + .arg(s[1]).arg(s[2]).arg(s[3])); + } + // #rrggbb + if (s.length() == 7) + { + return QColor(s); + } + // #rrggbbaa (CSS) → Qt braucht #aarrggbb + if (s.length() == 9) + { + const QString rr = s.mid(1, 2); + const QString gg = s.mid(3, 2); + const QString bb = s.mid(5, 2); + const QString aa = s.mid(7, 2); + return QColor(QString("#%1%2%3%4").arg(aa, rr, gg, bb)); + } + return QColor(); +} + +QColor ColorIndicator::parseRgb(const QString &s) +{ + // rgb(r, g, b) + static const QRegularExpression re( + R"(rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))", + QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch m = re.match(s); + if (!m.hasMatch()) { return QColor(); } + return QColor(m.captured(1).toInt(), + m.captured(2).toInt(), + m.captured(3).toInt()); +} + +QColor ColorIndicator::parseRgba(const QString &s) +{ + // rgba(r, g, b, a) — a ist 0.0–1.0 + static const QRegularExpression re( + R"(rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([0-9.]+)\s*\))", + QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch m = re.match(s); + if (!m.hasMatch()) { return QColor(); } + return QColor(m.captured(1).toInt(), + m.captured(2).toInt(), + m.captured(3).toInt(), + qRound(m.captured(4).toDouble() * 255.0)); +} + +QColor ColorIndicator::parseHsl(const QString &s) +{ + // hsl(h, s%, l%) + static const QRegularExpression re( + R"(hsl\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\))", + QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch m = re.match(s); + if (!m.hasMatch()) { return QColor(); } + QColor c; + c.setHsl(m.captured(1).toInt(), + qRound(m.captured(2).toInt() * 2.55), + qRound(m.captured(3).toInt() * 2.55)); + return c; +} + +QColor ColorIndicator::parseHsla(const QString &s) +{ + // hsla(h, s%, l%, a) + static const QRegularExpression re( + R"(hsla\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([0-9.]+)\s*\))", + QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch m = re.match(s); + if (!m.hasMatch()) { return QColor(); } + QColor c; + c.setHsl(m.captured(1).toInt(), + qRound(m.captured(2).toInt() * 2.55), + qRound(m.captured(3).toInt() * 2.55), + qRound(m.captured(4).toDouble() * 255.0)); + return c; +} + +QColor ColorIndicator::parseNamed(const QString &s) +{ + // QColor kennt alle 140 CSS-Farbnamen direkt + const QColor c(s.toLower()); + return c.isValid() ? c : QColor(); +} diff --git a/src/editor/ColorIndicator.h b/src/editor/ColorIndicator.h new file mode 100644 index 0000000..d315a12 --- /dev/null +++ b/src/editor/ColorIndicator.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class CodeEditor; +class QPainter; +class QPaintEvent; +class QMouseEvent; + +// --------------------------------------------------------------------------- +// ColorIndicator – Zeichnet kleine Farbquadrate neben CSS-Farbwerten und +// öffnet einen QColorDialog wenn der Nutzer darauf klickt. +// +// Unterstützte Formate: +// #rgb #rrggbb #rrggbbaa +// rgb(r, g, b) rgba(r, g, b, a) +// hsl(h, s%, l%) hsla(h, s%, l%, a) +// 140 benannte CSS-Farben (red, blue, cornflowerblue, ...) +// --------------------------------------------------------------------------- +class ColorIndicator : public QObject +{ + Q_OBJECT + +public: + explicit ColorIndicator(CodeEditor *editor); + + // Wird aus CodeEditor::paintEvent aufgerufen + void paint(QPainter &painter, QPaintEvent *event); + + // Wird aus CodeEditor::mousePressEvent aufgerufen + // Gibt true zurück wenn der Klick auf einem Farbquadrat war + bool handleMousePress(QMouseEvent *event); + +private: + struct ColorMatch + { + QRect rect; // Position des Quadrats im Viewport + QColor color; // Erkannte Farbe + int blockNumber; + int posInBlock; // Zeichenposition des Farbwerts im Block + int length; // Länge des Farbwerts im Text + }; + + void rebuildCache(); + QList findColorsInBlock(const QString &text, + int blockNumber) const; + + static QColor parseColor(const QString &token); + static QColor parseHex(const QString &s); + static QColor parseRgb(const QString &s); + static QColor parseRgba(const QString &s); + static QColor parseHsl(const QString &s); + static QColor parseHsla(const QString &s); + static QColor parseNamed(const QString &s); + + CodeEditor *m_editor = nullptr; + QList m_cache; + int m_cacheFirstBlock = -1; + int m_cacheLastBlock = -1; + + // Kombinierter Regex für alle Farbformate + static const QRegularExpression s_colorRegex; +};