Farbvorschau und Farbauswahl hinter Farbwerten implementiert
This commit is contained in:
@@ -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})
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
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