Farbvorschau und Farbauswahl hinter Farbwerten implementiert
This commit is contained in:
@@ -11,6 +11,8 @@ set(EDITOR_SOURCES
|
||||
SearchPanel.h
|
||||
FileSearchPanel.cpp
|
||||
FileSearchPanel.h
|
||||
ColorIndicator.cpp
|
||||
ColorIndicator.h
|
||||
)
|
||||
|
||||
add_library(BareCode_Editor STATIC ${EDITOR_SOURCES})
|
||||
|
||||
@@ -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 <QPaintEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
@@ -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<int>(blockRect.left()
|
||||
+ layout->lineAt(0).position().x());
|
||||
}
|
||||
else
|
||||
{
|
||||
textOriginX = static_cast<int>(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<int>(blockRect.left()
|
||||
+ layout->lineAt(0).position().x());
|
||||
}
|
||||
else
|
||||
{
|
||||
textOriginX = static_cast<int>(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<int>(blockRect.top()),
|
||||
xPixel,
|
||||
static_cast<int>(blockRect.bottom()));
|
||||
}
|
||||
}
|
||||
|
||||
const int y1 = static_cast<int>(blockRect.top());
|
||||
const int y2 = 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)
|
||||
{
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
#include <QPlainTextEdit>
|
||||
#include <QFont>
|
||||
#include <QString>
|
||||
#include <QTextBlock>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
387
src/editor/ColorIndicator.cpp
Normal file
387
src/editor/ColorIndicator.cpp
Normal 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.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();
|
||||
}
|
||||
68
src/editor/ColorIndicator.h
Normal file
68
src/editor/ColorIndicator.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user