Erste Version
This commit is contained in:
345
src/editor/CodeEditor.cpp
Normal file
345
src/editor/CodeEditor.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
#include "CodeEditor.h"
|
||||
#include "LineNumberArea.h"
|
||||
|
||||
#include "core/Settings.h"
|
||||
#include "highlighter/HighlighterFactory.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QTextBlock>
|
||||
#include <QPaintEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QFileInfo>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
CodeEditor::CodeEditor(Settings *settings, QWidget *parent)
|
||||
: QPlainTextEdit(parent)
|
||||
, m_settings(settings)
|
||||
{
|
||||
m_lineNumberArea = new LineNumberArea(this);
|
||||
setupEditor();
|
||||
|
||||
connect(this, &CodeEditor::blockCountChanged,
|
||||
this, &CodeEditor::updateLineNumberAreaWidth);
|
||||
|
||||
connect(this, &CodeEditor::updateRequest,
|
||||
this, &CodeEditor::updateLineNumberArea);
|
||||
|
||||
connect(this, &CodeEditor::cursorPositionChanged,
|
||||
this, &CodeEditor::highlightCurrentLine);
|
||||
|
||||
updateLineNumberAreaWidth(0);
|
||||
highlightCurrentLine();
|
||||
}
|
||||
|
||||
CodeEditor::~CodeEditor() = default;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Setup
|
||||
// ---------------------------------------------------------------------------
|
||||
void CodeEditor::setupEditor()
|
||||
{
|
||||
applySettings();
|
||||
setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||
}
|
||||
|
||||
void CodeEditor::applySettings()
|
||||
{
|
||||
setFont(m_settings->editorFont());
|
||||
|
||||
const int tabStop = m_settings->tabSize();
|
||||
// Set tab stop width in pixels using font metrics
|
||||
QFontMetrics fm(m_settings->editorFont());
|
||||
setTabStopDistance(static_cast<qreal>(tabStop) * fm.horizontalAdvance(' '));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// File I/O
|
||||
// ---------------------------------------------------------------------------
|
||||
void CodeEditor::loadFile(const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Open File"),
|
||||
tr("Cannot open file:\n%1").arg(filePath));
|
||||
return;
|
||||
}
|
||||
|
||||
m_filePath = filePath;
|
||||
|
||||
QTextStream in(&file);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
in.setEncoding(QStringConverter::Utf8);
|
||||
#else
|
||||
in.setCodec("UTF-8");
|
||||
#endif
|
||||
|
||||
setPlainText(in.readAll());
|
||||
document()->setModified(false);
|
||||
|
||||
installHighlighter(filePath);
|
||||
}
|
||||
|
||||
QString CodeEditor::filePath() const
|
||||
{
|
||||
return m_filePath;
|
||||
}
|
||||
|
||||
bool CodeEditor::save()
|
||||
{
|
||||
if (m_filePath.isEmpty())
|
||||
{
|
||||
return saveAs();
|
||||
}
|
||||
|
||||
return writeToFile(m_filePath);
|
||||
}
|
||||
|
||||
bool CodeEditor::saveAs()
|
||||
{
|
||||
const QString path = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
tr("Speichern unter"),
|
||||
m_filePath
|
||||
);
|
||||
|
||||
if (path.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_filePath = path;
|
||||
installHighlighter(m_filePath);
|
||||
return writeToFile(m_filePath);
|
||||
}
|
||||
|
||||
bool CodeEditor::writeToFile(const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Speichern"),
|
||||
tr("Datei konnte nicht gespeichert werden:\n%1").arg(filePath));
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream out(&file);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
out.setEncoding(QStringConverter::Utf8);
|
||||
#else
|
||||
out.setCodec("UTF-8");
|
||||
#endif
|
||||
|
||||
out << toPlainText();
|
||||
document()->setModified(false);
|
||||
emit fileSaved(filePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CodeEditor::installHighlighter(const QString &filePath)
|
||||
{
|
||||
// Remove old highlighter first
|
||||
delete m_highlighter;
|
||||
m_highlighter = nullptr;
|
||||
|
||||
m_highlighter = HighlighterFactory::createForFile(filePath, document());
|
||||
}
|
||||
|
||||
bool CodeEditor::isModified() const
|
||||
{
|
||||
return document()->isModified();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Line number area
|
||||
// ---------------------------------------------------------------------------
|
||||
int CodeEditor::lineNumberAreaWidth() const
|
||||
{
|
||||
int digits = 1;
|
||||
int max = qMax(1, blockCount());
|
||||
while (max >= 10)
|
||||
{
|
||||
max /= 10;
|
||||
++digits;
|
||||
}
|
||||
|
||||
const int padding = 8;
|
||||
return fontMetrics().horizontalAdvance('9') * digits + padding * 2;
|
||||
}
|
||||
|
||||
void CodeEditor::updateLineNumberAreaWidth(int /*newBlockCount*/)
|
||||
{
|
||||
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
|
||||
}
|
||||
|
||||
void CodeEditor::updateLineNumberArea(const QRect &rect, int dy)
|
||||
{
|
||||
if (dy != 0)
|
||||
{
|
||||
m_lineNumberArea->scroll(0, dy);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lineNumberArea->update(0, rect.y(), m_lineNumberArea->width(), rect.height());
|
||||
}
|
||||
|
||||
if (rect.contains(viewport()->rect()))
|
||||
{
|
||||
updateLineNumberAreaWidth(0);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEditor::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QPlainTextEdit::resizeEvent(event);
|
||||
|
||||
const QRect cr = contentsRect();
|
||||
m_lineNumberArea->setGeometry(
|
||||
QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())
|
||||
);
|
||||
}
|
||||
|
||||
void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(m_lineNumberArea);
|
||||
|
||||
// Background
|
||||
const QColor bgColor = palette().color(QPalette::Window).darker(110);
|
||||
painter.fillRect(event->rect(), bgColor);
|
||||
|
||||
const QColor lineNumColor = palette().color(QPalette::Mid);
|
||||
const QColor activeColor = palette().color(QPalette::Text);
|
||||
|
||||
const int currentLine = textCursor().blockNumber();
|
||||
|
||||
QTextBlock block = firstVisibleBlock();
|
||||
int blockNumber = block.blockNumber();
|
||||
int top = static_cast<int>(blockBoundingGeometry(block).translated(contentOffset()).top());
|
||||
int bottom = top + static_cast<int>(blockBoundingRect(block).height());
|
||||
|
||||
while (block.isValid() && top <= event->rect().bottom())
|
||||
{
|
||||
if (block.isVisible() && bottom >= event->rect().top())
|
||||
{
|
||||
const QString number = QString::number(blockNumber + 1);
|
||||
painter.setPen(blockNumber == currentLine ? activeColor : lineNumColor);
|
||||
painter.drawText(
|
||||
0,
|
||||
top,
|
||||
m_lineNumberArea->width() - 4,
|
||||
fontMetrics().height(),
|
||||
Qt::AlignRight,
|
||||
number
|
||||
);
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
top = bottom;
|
||||
bottom = top + static_cast<int>(blockBoundingRect(block).height());
|
||||
++blockNumber;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Current line highlight
|
||||
// ---------------------------------------------------------------------------
|
||||
void CodeEditor::highlightCurrentLine()
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> extraSelections;
|
||||
|
||||
if (!isReadOnly())
|
||||
{
|
||||
QTextEdit::ExtraSelection selection;
|
||||
|
||||
const QColor lineColor = palette().color(QPalette::AlternateBase);
|
||||
selection.format.setBackground(lineColor);
|
||||
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
|
||||
selection.cursor = textCursor();
|
||||
selection.cursor.clearSelection();
|
||||
|
||||
extraSelections.append(selection);
|
||||
}
|
||||
|
||||
setExtraSelections(extraSelections);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Key handling – auto-indent + Tab → spaces
|
||||
// ---------------------------------------------------------------------------
|
||||
void CodeEditor::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
// Tab key: insert spaces instead of a real tab character
|
||||
if (event->key() == Qt::Key_Tab && m_settings->useSpacesForTabs())
|
||||
{
|
||||
const int tabSize = m_settings->tabSize();
|
||||
QTextCursor cursor = textCursor();
|
||||
|
||||
if (cursor.hasSelection())
|
||||
{
|
||||
// Indent selected lines
|
||||
QTextBlock startBlock = document()->findBlock(cursor.selectionStart());
|
||||
QTextBlock endBlock = document()->findBlock(cursor.selectionEnd());
|
||||
|
||||
cursor.beginEditBlock();
|
||||
for (QTextBlock b = startBlock; b != endBlock.next(); b = b.next())
|
||||
{
|
||||
QTextCursor lineCursor(b);
|
||||
lineCursor.insertText(QString(tabSize, ' '));
|
||||
}
|
||||
cursor.endEditBlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate spaces needed to reach next tab stop
|
||||
const int col = cursor.columnNumber();
|
||||
const int spacesNeeded = tabSize - (col % tabSize);
|
||||
cursor.insertText(QString(spacesNeeded, ' '));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Enter / Return: auto-indent
|
||||
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
const QString currentLine = cursor.block().text();
|
||||
|
||||
// Count leading whitespace
|
||||
int leadingSpaces = 0;
|
||||
for (const QChar &ch : currentLine)
|
||||
{
|
||||
if (ch == ' ')
|
||||
{
|
||||
++leadingSpaces;
|
||||
}
|
||||
else if (ch == '\t')
|
||||
{
|
||||
leadingSpaces += m_settings->tabSize();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Let the base class insert the newline first
|
||||
QPlainTextEdit::keyPressEvent(event);
|
||||
|
||||
// Then re-indent
|
||||
if (leadingSpaces > 0)
|
||||
{
|
||||
const QString indent = m_settings->useSpacesForTabs()
|
||||
? QString(leadingSpaces, ' ')
|
||||
: QString(leadingSpaces / m_settings->tabSize(), '\t');
|
||||
textCursor().insertText(indent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QPlainTextEdit::keyPressEvent(event);
|
||||
}
|
||||
Reference in New Issue
Block a user