From 5b81ef9c12146416bf44cbf8cb18afbaaa9d9706 Mon Sep 17 00:00:00 2001 From: Dany Thinnes Date: Mon, 25 May 2026 02:50:52 +0200 Subject: [PATCH] =?UTF-8?q?Vertikale=20Linien=20zur=20Visualisierung=20von?= =?UTF-8?q?=20Einspr=C3=BCngen=20implementiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/CodeEditor.cpp | 120 ++++++++++++++++++++++++++++++++++++++ src/editor/CodeEditor.h | 1 + 2 files changed, 121 insertions(+) diff --git a/src/editor/CodeEditor.cpp b/src/editor/CodeEditor.cpp index 367d1fd..21c80ce 100644 --- a/src/editor/CodeEditor.cpp +++ b/src/editor/CodeEditor.cpp @@ -194,6 +194,126 @@ void CodeEditor::updateLineNumberArea(const QRect &rect, int dy) } } +void CodeEditor::paintEvent(QPaintEvent *event) +{ + // Zuerst den normalen Editor-Inhalt zeichnen + QPlainTextEdit::paintEvent(event); + + // Danach die Einrück-Führungslinien darüber legen + const int tabSize = m_settings->tabSize(); + if (tabSize <= 0) + { + return; + } + + QPainter painter(viewport()); + + // 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; + + 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()) + { + 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) + { + 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 == ' ') + { + ++indentSpaces; + } + else if (ch == '\t') + { + // Tab auffüllen auf nächsten Tab-Stop + indentSpaces = ((indentSpaces / tabSize) + 1) * tabSize; + } + else + { + break; + } + } + + const int indentStops = indentSpaces / tabSize; + + // Für jeden Einrückungslevel eine vertikale Linie zeichnen + for (int stop = 1; stop <= indentStops; ++stop) + { + const int xPixel = textOriginX + stop * tabPixels - scrollX; + + // Nur im sichtbaren Bereich zeichnen + if (xPixel < lineNumberAreaWidth() || xPixel > viewport()->width()) + { + continue; + } + + const int y1 = static_cast(blockRect.top()); + const int y2 = static_cast(blockRect.bottom()); + + painter.drawLine(xPixel, y1, xPixel, y2); + } + } + + block = block.next(); + } +} + + void CodeEditor::resizeEvent(QResizeEvent *event) { QPlainTextEdit::resizeEvent(event); diff --git a/src/editor/CodeEditor.h b/src/editor/CodeEditor.h index dd52331..603a07f 100644 --- a/src/editor/CodeEditor.h +++ b/src/editor/CodeEditor.h @@ -45,6 +45,7 @@ signals: protected: void resizeEvent(QResizeEvent *event) override; void keyPressEvent(QKeyEvent *event) override; + void paintEvent(QPaintEvent *event) override; private slots: void updateLineNumberAreaWidth(int newBlockCount);