Farbvorschau und Farbauswahl hinter Farbwerten implementiert

This commit is contained in:
2026-08-09 21:57:49 +02:00
parent 0c53c919b7
commit 95f1ccb2ee
5 changed files with 541 additions and 91 deletions

View File

@@ -11,6 +11,8 @@ set(EDITOR_SOURCES
SearchPanel.h SearchPanel.h
FileSearchPanel.cpp FileSearchPanel.cpp
FileSearchPanel.h FileSearchPanel.h
ColorIndicator.cpp
ColorIndicator.h
) )
add_library(BareCode_Editor STATIC ${EDITOR_SOURCES}) add_library(BareCode_Editor STATIC ${EDITOR_SOURCES})

View File

@@ -1,5 +1,6 @@
#include "CodeEditor.h" #include "CodeEditor.h"
#include "LineNumberArea.h" #include "LineNumberArea.h"
#include "ColorIndicator.h"
#include "core/Settings.h" #include "core/Settings.h"
#include "highlighter/HighlighterFactory.h" #include "highlighter/HighlighterFactory.h"
@@ -9,6 +10,7 @@
#include <QPaintEvent> #include <QPaintEvent>
#include <QResizeEvent> #include <QResizeEvent>
#include <QKeyEvent> #include <QKeyEvent>
#include <QMouseEvent>
#include <QScrollBar> #include <QScrollBar>
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>
@@ -21,6 +23,7 @@ CodeEditor::CodeEditor(Settings *settings, QWidget *parent)
, m_settings(settings) , m_settings(settings)
{ {
m_lineNumberArea = new LineNumberArea(this); m_lineNumberArea = new LineNumberArea(this);
m_colorIndicator = new ColorIndicator(this);
setupEditor(); setupEditor();
connect(this, &CodeEditor::blockCountChanged, connect(this, &CodeEditor::blockCountChanged,
@@ -199,16 +202,12 @@ void CodeEditor::paintEvent(QPaintEvent *event)
// Zuerst den normalen Editor-Inhalt zeichnen // Zuerst den normalen Editor-Inhalt zeichnen
QPlainTextEdit::paintEvent(event); QPlainTextEdit::paintEvent(event);
// Danach die Einrück-Führungslinien darüber legen // Einrück-Führungslinien
const int tabSize = m_settings->tabSize(); const int tabSize = m_settings->tabSize();
if (tabSize <= 0) if (tabSize > 0)
{ {
return;
}
QPainter painter(viewport()); QPainter painter(viewport());
// Farbe: subtil, passt zu Hell- und Dunkeltheme
QColor guideColor = palette().color(QPalette::Text); QColor guideColor = palette().color(QPalette::Text);
guideColor.setAlpha(30); guideColor.setAlpha(30);
painter.setPen(QPen(guideColor, 1, Qt::SolidLine)); painter.setPen(QPen(guideColor, 1, Qt::SolidLine));
@@ -217,14 +216,8 @@ void CodeEditor::paintEvent(QPaintEvent *event)
const int spaceWidth = fm.horizontalAdvance(' '); const int spaceWidth = fm.horizontalAdvance(' ');
const int tabPixels = tabSize * spaceWidth; const int tabPixels = tabSize * spaceWidth;
if (tabPixels <= 0) 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; int textOriginX = 0;
{ {
QTextBlock firstBlock = firstVisibleBlock(); QTextBlock firstBlock = firstVisibleBlock();
@@ -236,7 +229,6 @@ void CodeEditor::paintEvent(QPaintEvent *event)
{ {
const QRectF blockRect = blockBoundingGeometry(firstBlock) const QRectF blockRect = blockBoundingGeometry(firstBlock)
.translated(contentOffset()); .translated(contentOffset());
// Position des ersten Zeichens im Layout
const QTextLayout *layout = firstBlock.layout(); const QTextLayout *layout = firstBlock.layout();
if (layout && layout->lineCount() > 0) if (layout && layout->lineCount() > 0)
{ {
@@ -250,69 +242,60 @@ void CodeEditor::paintEvent(QPaintEvent *event)
} }
} }
// Horizontalen Scroll-Offset berücksichtigen
const int scrollX = horizontalScrollBar()->value(); const int scrollX = horizontalScrollBar()->value();
// Sichtbaren Zeilenbereich bestimmen
QTextBlock block = firstVisibleBlock(); QTextBlock block = firstVisibleBlock();
const int bottom = event->rect().bottom(); const int bottom = event->rect().bottom();
while (block.isValid()) while (block.isValid())
{ {
const QRectF blockRect = blockBoundingGeometry(block).translated(contentOffset()); const QRectF blockRect = blockBoundingGeometry(block)
.translated(contentOffset());
if (blockRect.top() > bottom) if (blockRect.top() > bottom) { break; }
{
break;
}
if (block.isVisible()) if (block.isVisible())
{ {
const QString text = block.text(); const QString text = block.text();
// Einrückungstiefe der Zeile zählen (Leerzeichen / Tabs)
int indentSpaces = 0; int indentSpaces = 0;
for (const QChar &ch : text) for (const QChar &ch : text)
{ {
if (ch == ' ') if (ch == ' ') { ++indentSpaces; }
{ else if (ch == '\t') { indentSpaces = ((indentSpaces / tabSize) + 1) * tabSize; }
++indentSpaces; else { break; }
}
else if (ch == '\t')
{
// Tab auffüllen auf nächsten Tab-Stop
indentSpaces = ((indentSpaces / tabSize) + 1) * tabSize;
}
else
{
break;
}
} }
const int indentStops = indentSpaces / tabSize; const int indentStops = indentSpaces / tabSize;
// Für jeden Einrückungslevel eine vertikale Linie zeichnen
for (int stop = 1; stop <= indentStops; ++stop) for (int stop = 1; stop <= indentStops; ++stop)
{ {
const int xPixel = textOriginX + stop * tabPixels - scrollX; const int xPixel = textOriginX + stop * tabPixels - scrollX;
// Nur im sichtbaren Bereich zeichnen
if (xPixel < lineNumberAreaWidth() || xPixel > viewport()->width()) if (xPixel < lineNumberAreaWidth() || xPixel > viewport()->width())
{ {
continue; continue;
} }
painter.drawLine(xPixel,
const int y1 = static_cast<int>(blockRect.top()); static_cast<int>(blockRect.top()),
const int y2 = static_cast<int>(blockRect.bottom()); xPixel,
static_cast<int>(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) void CodeEditor::resizeEvent(QResizeEvent *event)
{ {

View File

@@ -3,10 +3,12 @@
#include <QPlainTextEdit> #include <QPlainTextEdit>
#include <QFont> #include <QFont>
#include <QString> #include <QString>
#include <QTextBlock>
class LineNumberArea; class LineNumberArea;
class Settings; class Settings;
class SyntaxHighlighter; class SyntaxHighlighter;
class ColorIndicator;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// CodeEditor Core editing widget. // CodeEditor Core editing widget.
@@ -39,6 +41,12 @@ public:
QString filePath() const; QString filePath() const;
bool isModified() 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: signals:
void fileSaved(const QString &filePath); void fileSaved(const QString &filePath);
@@ -46,6 +54,7 @@ protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
private slots: private slots:
void updateLineNumberAreaWidth(int newBlockCount); void updateLineNumberAreaWidth(int newBlockCount);
@@ -60,5 +69,6 @@ private:
Settings *m_settings = nullptr; Settings *m_settings = nullptr;
LineNumberArea *m_lineNumberArea = nullptr; LineNumberArea *m_lineNumberArea = nullptr;
SyntaxHighlighter *m_highlighter = nullptr; SyntaxHighlighter *m_highlighter = nullptr;
ColorIndicator *m_colorIndicator = nullptr;
QString m_filePath; QString m_filePath;
}; };

View File

@@ -0,0 +1,387 @@
#include "ColorIndicator.h"
#include "CodeEditor.h"
#include <QPainter>
#include <QPaintEvent>
#include <QMouseEvent>
#include <QColorDialog>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextDocument>
#include <QScrollBar>
#include <QHash>
// ---------------------------------------------------------------------------
// 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<ColorMatch> 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<int>(blockRect.left() + endCharX)
- scrollX + 3;
if (x + squareSize > m_editor->viewport()->width())
{
continue;
}
const int y = static_cast<int>(blockRect.top())
+ (static_cast<int>(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::ColorMatch> ColorIndicator::findColorsInBlock(
const QString &text, int blockNumber) const
{
QList<ColorMatch> 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<int>(match.capturedStart());
m.length = static_cast<int>(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.01.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();
}

View File

@@ -0,0 +1,68 @@
#pragma once
#include <QObject>
#include <QColor>
#include <QRect>
#include <QList>
#include <QRegularExpression>
#include <QString>
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<ColorMatch> 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<ColorMatch> m_cache;
int m_cacheFirstBlock = -1;
int m_cacheLastBlock = -1;
// Kombinierter Regex für alle Farbformate
static const QRegularExpression s_colorRegex;
};