Erste Version
This commit is contained in:
26
src/editor/CMakeLists.txt
Normal file
26
src/editor/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
set(EDITOR_SOURCES
|
||||
EditorPanel.cpp
|
||||
EditorPanel.h
|
||||
CodeEditor.cpp
|
||||
CodeEditor.h
|
||||
LineNumberArea.cpp
|
||||
LineNumberArea.h
|
||||
EditorTab.cpp
|
||||
EditorTab.h
|
||||
SearchPanel.cpp
|
||||
SearchPanel.h
|
||||
)
|
||||
|
||||
add_library(BareCode_Editor STATIC ${EDITOR_SOURCES})
|
||||
|
||||
target_link_libraries(BareCode_Editor PUBLIC
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
BareCode_Highlighter
|
||||
)
|
||||
|
||||
target_include_directories(BareCode_Editor PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
)
|
||||
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);
|
||||
}
|
||||
63
src/editor/CodeEditor.h
Normal file
63
src/editor/CodeEditor.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPlainTextEdit>
|
||||
#include <QFont>
|
||||
#include <QString>
|
||||
|
||||
class LineNumberArea;
|
||||
class Settings;
|
||||
class SyntaxHighlighter;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CodeEditor – Core editing widget.
|
||||
// Features:
|
||||
// • Line number gutter
|
||||
// • Current-line highlight
|
||||
// • Auto-indent on Enter
|
||||
// • Tab → spaces (configurable)
|
||||
// • Syntax highlighting (via pluggable SyntaxHighlighter)
|
||||
// ---------------------------------------------------------------------------
|
||||
class CodeEditor : public QPlainTextEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CodeEditor(Settings *settings, QWidget *parent = nullptr);
|
||||
~CodeEditor() override;
|
||||
|
||||
void loadFile(const QString &filePath);
|
||||
void applySettings();
|
||||
|
||||
// Speichern
|
||||
bool save();
|
||||
bool saveAs();
|
||||
|
||||
// Called by LineNumberArea
|
||||
int lineNumberAreaWidth() const;
|
||||
void lineNumberAreaPaintEvent(QPaintEvent *event);
|
||||
|
||||
QString filePath() const;
|
||||
bool isModified() const;
|
||||
|
||||
signals:
|
||||
void fileSaved(const QString &filePath);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void updateLineNumberAreaWidth(int newBlockCount);
|
||||
void highlightCurrentLine();
|
||||
void updateLineNumberArea(const QRect &rect, int dy);
|
||||
|
||||
private:
|
||||
void setupEditor();
|
||||
void installHighlighter(const QString &filePath);
|
||||
bool writeToFile(const QString &filePath);
|
||||
|
||||
Settings *m_settings = nullptr;
|
||||
LineNumberArea *m_lineNumberArea = nullptr;
|
||||
SyntaxHighlighter *m_highlighter = nullptr;
|
||||
QString m_filePath;
|
||||
};
|
||||
157
src/editor/EditorPanel.cpp
Normal file
157
src/editor/EditorPanel.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#include "EditorPanel.h"
|
||||
#include "EditorTab.h"
|
||||
#include "CodeEditor.h"
|
||||
#include "SearchPanel.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
EditorPanel::EditorPanel(Settings *settings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_settings(settings)
|
||||
{
|
||||
setupUi();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Setup
|
||||
// ---------------------------------------------------------------------------
|
||||
void EditorPanel::setupUi()
|
||||
{
|
||||
m_layout = new QVBoxLayout(this);
|
||||
m_layout->setContentsMargins(0, 0, 0, 0);
|
||||
m_layout->setSpacing(0);
|
||||
|
||||
m_tabWidget = new QTabWidget(this);
|
||||
m_tabWidget->setTabsClosable(true);
|
||||
m_tabWidget->setMovable(true);
|
||||
m_tabWidget->setDocumentMode(true);
|
||||
|
||||
m_searchPanel = new SearchPanel(this);
|
||||
|
||||
m_layout->addWidget(m_tabWidget, 1);
|
||||
m_layout->addWidget(m_searchPanel, 0);
|
||||
|
||||
connect(m_tabWidget, &QTabWidget::tabCloseRequested,
|
||||
this, &EditorPanel::onTabCloseRequested);
|
||||
|
||||
connect(m_tabWidget, &QTabWidget::currentChanged,
|
||||
this, &EditorPanel::onCurrentTabChanged);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hilfsmethoden
|
||||
// ---------------------------------------------------------------------------
|
||||
EditorTab *EditorPanel::currentTab() const
|
||||
{
|
||||
return qobject_cast<EditorTab *>(m_tabWidget->currentWidget());
|
||||
}
|
||||
|
||||
int EditorPanel::findTabForFile(const QString &filePath) const
|
||||
{
|
||||
EditorTab *tab = m_openTabs.value(filePath, nullptr);
|
||||
return tab ? m_tabWidget->indexOf(tab) : -1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Öffentliche Slots
|
||||
// ---------------------------------------------------------------------------
|
||||
void EditorPanel::openFile(const QString &filePath)
|
||||
{
|
||||
const int existing = findTabForFile(filePath);
|
||||
if (existing != -1)
|
||||
{
|
||||
m_tabWidget->setCurrentIndex(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorTab *tab = new EditorTab(filePath, m_settings, m_tabWidget);
|
||||
const int index = m_tabWidget->addTab(tab, tab->fileName());
|
||||
m_tabWidget->setCurrentIndex(index);
|
||||
m_tabWidget->setTabToolTip(index, filePath);
|
||||
m_openTabs.insert(filePath, tab);
|
||||
|
||||
// Tab-Titel nach "Speichern unter" aktualisieren
|
||||
connect(tab->editor(), &CodeEditor::fileSaved, this, [this, tab](const QString &savedPath)
|
||||
{
|
||||
const int idx = m_tabWidget->indexOf(tab);
|
||||
if (idx != -1)
|
||||
{
|
||||
m_tabWidget->setTabText(idx, QFileInfo(savedPath).fileName());
|
||||
m_tabWidget->setTabToolTip(idx, savedPath);
|
||||
}
|
||||
emit currentFileSaved(savedPath);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorPanel::saveCurrentFile()
|
||||
{
|
||||
if (EditorTab *tab = currentTab())
|
||||
{
|
||||
tab->save();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPanel::saveCurrentFileAs()
|
||||
{
|
||||
if (EditorTab *tab = currentTab())
|
||||
{
|
||||
tab->saveAs();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPanel::saveAllFiles()
|
||||
{
|
||||
for (int i = 0; i < m_tabWidget->count(); ++i)
|
||||
{
|
||||
EditorTab *tab = qobject_cast<EditorTab *>(m_tabWidget->widget(i));
|
||||
if (tab && tab->isModified())
|
||||
{
|
||||
tab->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPanel::showSearchPanel()
|
||||
{
|
||||
m_searchPanel->activate();
|
||||
}
|
||||
|
||||
void EditorPanel::undo()
|
||||
{
|
||||
if (EditorTab *tab = currentTab())
|
||||
{
|
||||
tab->editor()->undo();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPanel::redo()
|
||||
{
|
||||
if (EditorTab *tab = currentTab())
|
||||
{
|
||||
tab->editor()->redo();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Slots
|
||||
// ---------------------------------------------------------------------------
|
||||
void EditorPanel::onTabCloseRequested(int index)
|
||||
{
|
||||
EditorTab *tab = qobject_cast<EditorTab *>(m_tabWidget->widget(index));
|
||||
if (!tab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_openTabs.remove(tab->filePath());
|
||||
m_tabWidget->removeTab(index);
|
||||
tab->deleteLater();
|
||||
|
||||
m_searchPanel->setEditor(currentTab() ? currentTab()->editor() : nullptr);
|
||||
}
|
||||
|
||||
void EditorPanel::onCurrentTabChanged(int /*index*/)
|
||||
{
|
||||
EditorTab *tab = currentTab();
|
||||
m_searchPanel->setEditor(tab ? tab->editor() : nullptr);
|
||||
}
|
||||
50
src/editor/EditorPanel.h
Normal file
50
src/editor/EditorPanel.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
class EditorTab;
|
||||
class Settings;
|
||||
class SearchPanel;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EditorPanel – Rechtes Panel: Tab-Leiste + Editoren + Such/Ersetzen-Panel.
|
||||
// ---------------------------------------------------------------------------
|
||||
class EditorPanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EditorPanel(Settings *settings, QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void openFile(const QString &filePath);
|
||||
void saveCurrentFile();
|
||||
void saveCurrentFileAs();
|
||||
void saveAllFiles();
|
||||
void showSearchPanel();
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
signals:
|
||||
void currentFileSaved(const QString &filePath);
|
||||
|
||||
private slots:
|
||||
void onTabCloseRequested(int index);
|
||||
void onCurrentTabChanged(int index);
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
int findTabForFile(const QString &filePath) const;
|
||||
EditorTab *currentTab() const;
|
||||
|
||||
Settings *m_settings = nullptr;
|
||||
QVBoxLayout *m_layout = nullptr;
|
||||
QTabWidget *m_tabWidget = nullptr;
|
||||
SearchPanel *m_searchPanel = nullptr;
|
||||
|
||||
QHash<QString, EditorTab *> m_openTabs;
|
||||
};
|
||||
53
src/editor/EditorTab.cpp
Normal file
53
src/editor/EditorTab.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "EditorTab.h"
|
||||
#include "CodeEditor.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
EditorTab::EditorTab(const QString &filePath, Settings *settings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_filePath(filePath)
|
||||
{
|
||||
m_layout = new QVBoxLayout(this);
|
||||
m_layout->setContentsMargins(0, 0, 0, 0);
|
||||
m_layout->setSpacing(0);
|
||||
|
||||
m_editor = new CodeEditor(settings, this);
|
||||
m_editor->loadFile(filePath);
|
||||
|
||||
m_layout->addWidget(m_editor);
|
||||
}
|
||||
|
||||
QString EditorTab::filePath() const
|
||||
{
|
||||
return m_filePath;
|
||||
}
|
||||
|
||||
QString EditorTab::fileName() const
|
||||
{
|
||||
return QFileInfo(m_filePath).fileName();
|
||||
}
|
||||
|
||||
CodeEditor *EditorTab::editor() const
|
||||
{
|
||||
return m_editor;
|
||||
}
|
||||
|
||||
bool EditorTab::isModified() const
|
||||
{
|
||||
return m_editor->isModified();
|
||||
}
|
||||
|
||||
bool EditorTab::save()
|
||||
{
|
||||
const bool ok = m_editor->save();
|
||||
// Path may have changed if this was an untitled buffer saved for the first time
|
||||
m_filePath = m_editor->filePath();
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool EditorTab::saveAs()
|
||||
{
|
||||
const bool ok = m_editor->saveAs();
|
||||
m_filePath = m_editor->filePath();
|
||||
return ok;
|
||||
}
|
||||
33
src/editor/EditorTab.h
Normal file
33
src/editor/EditorTab.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class CodeEditor;
|
||||
class Settings;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EditorTab – Widget placed inside each tab of the tab bar.
|
||||
// Owns a CodeEditor for a single file.
|
||||
// ---------------------------------------------------------------------------
|
||||
class EditorTab : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EditorTab(const QString &filePath, Settings *settings, QWidget *parent = nullptr);
|
||||
|
||||
QString filePath() const;
|
||||
QString fileName() const;
|
||||
CodeEditor *editor() const;
|
||||
bool isModified() const;
|
||||
|
||||
bool save();
|
||||
bool saveAs();
|
||||
|
||||
private:
|
||||
QString m_filePath;
|
||||
QVBoxLayout *m_layout = nullptr;
|
||||
CodeEditor *m_editor = nullptr;
|
||||
};
|
||||
18
src/editor/LineNumberArea.cpp
Normal file
18
src/editor/LineNumberArea.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "LineNumberArea.h"
|
||||
#include "CodeEditor.h"
|
||||
|
||||
LineNumberArea::LineNumberArea(CodeEditor *editor)
|
||||
: QWidget(editor)
|
||||
, m_codeEditor(editor)
|
||||
{
|
||||
}
|
||||
|
||||
QSize LineNumberArea::sizeHint() const
|
||||
{
|
||||
return QSize(m_codeEditor->lineNumberAreaWidth(), 0);
|
||||
}
|
||||
|
||||
void LineNumberArea::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
m_codeEditor->lineNumberAreaPaintEvent(event);
|
||||
}
|
||||
25
src/editor/LineNumberArea.h
Normal file
25
src/editor/LineNumberArea.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class CodeEditor;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LineNumberArea – Thin widget painted on the left side of the CodeEditor.
|
||||
// Painted by CodeEditor::lineNumberAreaPaintEvent().
|
||||
// ---------------------------------------------------------------------------
|
||||
class LineNumberArea : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LineNumberArea(CodeEditor *editor);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
CodeEditor *m_codeEditor;
|
||||
};
|
||||
492
src/editor/SearchPanel.cpp
Normal file
492
src/editor/SearchPanel.cpp
Normal file
@@ -0,0 +1,492 @@
|
||||
#include "SearchPanel.h"
|
||||
#include "CodeEditor.h"
|
||||
|
||||
#include <QTextCursor>
|
||||
#include <QTextBlock>
|
||||
#include <QRegularExpression>
|
||||
#include <QMessageBox>
|
||||
#include <QKeyEvent>
|
||||
#include <QShortcut>
|
||||
|
||||
SearchPanel::SearchPanel(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setupUi();
|
||||
hide();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UI
|
||||
// ---------------------------------------------------------------------------
|
||||
void SearchPanel::setupUi()
|
||||
{
|
||||
m_grid = new QGridLayout(this);
|
||||
m_grid->setContentsMargins(6, 4, 6, 4);
|
||||
m_grid->setSpacing(4);
|
||||
|
||||
// ---- Row 0: Suchen ----
|
||||
m_searchEdit = new QLineEdit(this);
|
||||
m_searchEdit->setPlaceholderText(tr("Suchen…"));
|
||||
m_searchEdit->setClearButtonEnabled(true);
|
||||
|
||||
m_btnPrev = new QPushButton(tr("▲"), this);
|
||||
m_btnNext = new QPushButton(tr("▼"), this);
|
||||
m_btnPrev->setFixedWidth(28);
|
||||
m_btnNext->setFixedWidth(28);
|
||||
m_btnPrev->setToolTip(tr("Vorheriger Treffer (Shift+F3)"));
|
||||
m_btnNext->setToolTip(tr("Nächster Treffer (F3)"));
|
||||
|
||||
m_matchLabel = new QLabel(this);
|
||||
m_matchLabel->setMinimumWidth(80);
|
||||
|
||||
m_btnClose = new QPushButton(tr("✕"), this);
|
||||
m_btnClose->setFixedWidth(24);
|
||||
m_btnClose->setToolTip(tr("Schließen (Esc)"));
|
||||
m_btnClose->setFlat(true);
|
||||
|
||||
QHBoxLayout *searchRow = new QHBoxLayout();
|
||||
searchRow->addWidget(new QLabel(tr("Suchen:"), this));
|
||||
searchRow->addWidget(m_searchEdit, 1);
|
||||
searchRow->addWidget(m_btnPrev);
|
||||
searchRow->addWidget(m_btnNext);
|
||||
searchRow->addWidget(m_matchLabel);
|
||||
searchRow->addWidget(m_btnClose);
|
||||
m_grid->addLayout(searchRow, 0, 0);
|
||||
|
||||
// ---- Row 1: Ersetzen ----
|
||||
m_replaceEdit = new QLineEdit(this);
|
||||
m_replaceEdit->setPlaceholderText(tr("Ersetzen durch…"));
|
||||
m_replaceEdit->setClearButtonEnabled(true);
|
||||
|
||||
m_btnReplace = new QPushButton(tr("Ersetzen"), this);
|
||||
m_btnReplaceAll = new QPushButton(tr("Alle ersetzen"), this);
|
||||
m_btnReplaceSelection = new QPushButton(tr("In Auswahl ersetzen"), this);
|
||||
|
||||
QHBoxLayout *replaceRow = new QHBoxLayout();
|
||||
replaceRow->addWidget(new QLabel(tr("Ersetzen:"), this));
|
||||
replaceRow->addWidget(m_replaceEdit, 1);
|
||||
replaceRow->addWidget(m_btnReplace);
|
||||
replaceRow->addWidget(m_btnReplaceAll);
|
||||
replaceRow->addWidget(m_btnReplaceSelection);
|
||||
m_grid->addLayout(replaceRow, 1, 0);
|
||||
|
||||
// ---- Row 2: Optionen ----
|
||||
m_chkCase = new QCheckBox(tr("Groß-/Kleinschreibung"), this);
|
||||
m_chkWord = new QCheckBox(tr("Ganzes Wort"), this);
|
||||
m_chkRegex = new QCheckBox(tr("Regulärer Ausdruck"), this);
|
||||
|
||||
QHBoxLayout *optRow = new QHBoxLayout();
|
||||
optRow->addWidget(m_chkCase);
|
||||
optRow->addWidget(m_chkWord);
|
||||
optRow->addWidget(m_chkRegex);
|
||||
optRow->addStretch();
|
||||
m_grid->addLayout(optRow, 2, 0);
|
||||
|
||||
// ---- Connections ----
|
||||
connect(m_searchEdit, &QLineEdit::textChanged,
|
||||
this, &SearchPanel::onSearchTextChanged);
|
||||
|
||||
connect(m_searchEdit, &QLineEdit::returnPressed,
|
||||
this, &SearchPanel::findNext);
|
||||
|
||||
connect(m_btnNext, &QPushButton::clicked, this, &SearchPanel::findNext);
|
||||
connect(m_btnPrev, &QPushButton::clicked, this, &SearchPanel::findPrevious);
|
||||
|
||||
connect(m_btnReplace, &QPushButton::clicked, this, &SearchPanel::replaceCurrent);
|
||||
connect(m_btnReplaceAll, &QPushButton::clicked, this, &SearchPanel::replaceAll);
|
||||
connect(m_btnReplaceSelection, &QPushButton::clicked, this, &SearchPanel::replaceInSelection);
|
||||
|
||||
connect(m_btnClose, &QPushButton::clicked, this, &SearchPanel::onCloseClicked);
|
||||
|
||||
connect(m_chkCase, &QCheckBox::toggled, this, &SearchPanel::onOptionChanged);
|
||||
connect(m_chkWord, &QCheckBox::toggled, this, &SearchPanel::onOptionChanged);
|
||||
connect(m_chkRegex, &QCheckBox::toggled, this, &SearchPanel::onOptionChanged);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public interface
|
||||
// ---------------------------------------------------------------------------
|
||||
void SearchPanel::setEditor(CodeEditor *editor)
|
||||
{
|
||||
clearHighlights();
|
||||
m_editor = editor;
|
||||
}
|
||||
|
||||
void SearchPanel::activate()
|
||||
{
|
||||
show();
|
||||
m_searchEdit->setFocus();
|
||||
m_searchEdit->selectAll();
|
||||
|
||||
// Pre-fill with selected text if short enough
|
||||
if (m_editor)
|
||||
{
|
||||
const QString sel = m_editor->textCursor().selectedText();
|
||||
if (!sel.isEmpty() && !sel.contains('\n') && sel.length() < 200)
|
||||
{
|
||||
m_searchEdit->setText(sel);
|
||||
}
|
||||
}
|
||||
|
||||
updateMatchLabel();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Find helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
QTextDocument::FindFlags SearchPanel::buildFindFlags(bool backwards) const
|
||||
{
|
||||
QTextDocument::FindFlags flags;
|
||||
if (backwards) { flags |= QTextDocument::FindBackward; }
|
||||
if (m_chkCase->isChecked()) { flags |= QTextDocument::FindCaseSensitively; }
|
||||
if (m_chkWord->isChecked()) { flags |= QTextDocument::FindWholeWords; }
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool SearchPanel::performFind(bool backwards)
|
||||
{
|
||||
if (!m_editor || m_searchEdit->text().isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QTextDocument::FindFlags flags = buildFindFlags(backwards);
|
||||
bool found = false;
|
||||
|
||||
if (m_chkRegex->isChecked())
|
||||
{
|
||||
QRegularExpression re(m_searchEdit->text());
|
||||
if (m_chkCase->isChecked())
|
||||
{
|
||||
re.setPatternOptions(QRegularExpression::NoPatternOption);
|
||||
}
|
||||
else
|
||||
{
|
||||
re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
|
||||
}
|
||||
found = m_editor->find(re, flags);
|
||||
|
||||
// Wrap around
|
||||
if (!found)
|
||||
{
|
||||
QTextCursor c = m_editor->textCursor();
|
||||
c.movePosition(backwards ? QTextCursor::End : QTextCursor::Start);
|
||||
m_editor->setTextCursor(c);
|
||||
found = m_editor->find(re, flags);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
found = m_editor->find(m_searchEdit->text(), flags);
|
||||
|
||||
// Wrap around
|
||||
if (!found)
|
||||
{
|
||||
QTextCursor c = m_editor->textCursor();
|
||||
c.movePosition(backwards ? QTextCursor::End : QTextCursor::Start);
|
||||
m_editor->setTextCursor(c);
|
||||
found = m_editor->find(m_searchEdit->text(), flags);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void SearchPanel::highlightAllMatches()
|
||||
{
|
||||
if (!m_editor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QTextEdit::ExtraSelection> extras;
|
||||
|
||||
const QString needle = m_searchEdit->text();
|
||||
if (needle.isEmpty())
|
||||
{
|
||||
m_editor->setExtraSelections(extras);
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setBackground(QColor("#3a3a00"));
|
||||
fmt.setForeground(QColor("#ffff80"));
|
||||
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor(doc);
|
||||
|
||||
const QTextDocument::FindFlags flags = buildFindFlags(false);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (m_chkRegex->isChecked())
|
||||
{
|
||||
QRegularExpression re(needle);
|
||||
if (!m_chkCase->isChecked())
|
||||
{
|
||||
re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
|
||||
}
|
||||
cursor = doc->find(re, cursor, flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor = doc->find(needle, cursor, flags);
|
||||
}
|
||||
|
||||
if (cursor.isNull())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
QTextEdit::ExtraSelection sel;
|
||||
sel.cursor = cursor;
|
||||
sel.format = fmt;
|
||||
extras.append(sel);
|
||||
}
|
||||
|
||||
m_editor->setExtraSelections(extras);
|
||||
}
|
||||
|
||||
void SearchPanel::clearHighlights()
|
||||
{
|
||||
if (m_editor)
|
||||
{
|
||||
m_editor->setExtraSelections({});
|
||||
}
|
||||
}
|
||||
|
||||
void SearchPanel::updateMatchLabel()
|
||||
{
|
||||
if (!m_editor || m_searchEdit->text().isEmpty())
|
||||
{
|
||||
m_matchLabel->setText(QString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Count total matches
|
||||
int count = 0;
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor(doc);
|
||||
const QTextDocument::FindFlags flags = buildFindFlags(false);
|
||||
const QString needle = m_searchEdit->text();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (m_chkRegex->isChecked())
|
||||
{
|
||||
QRegularExpression re(needle);
|
||||
if (!m_chkCase->isChecked())
|
||||
{
|
||||
re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
|
||||
}
|
||||
cursor = doc->find(re, cursor, flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor = doc->find(needle, cursor, flags);
|
||||
}
|
||||
|
||||
if (cursor.isNull())
|
||||
{
|
||||
break;
|
||||
}
|
||||
++count;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
m_matchLabel->setText(tr("Kein Treffer"));
|
||||
m_matchLabel->setStyleSheet("color: #cc4444;");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_matchLabel->setText(tr("%1 Treffer").arg(count));
|
||||
m_matchLabel->setStyleSheet(QString());
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public slots
|
||||
// ---------------------------------------------------------------------------
|
||||
void SearchPanel::findNext()
|
||||
{
|
||||
performFind(false);
|
||||
}
|
||||
|
||||
void SearchPanel::findPrevious()
|
||||
{
|
||||
performFind(true);
|
||||
}
|
||||
|
||||
void SearchPanel::replaceCurrent()
|
||||
{
|
||||
if (!m_editor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
|
||||
// If current selection matches the search term, replace it
|
||||
// Otherwise just find the next occurrence first
|
||||
const bool hasMatch = !cursor.selectedText().isEmpty();
|
||||
if (!hasMatch)
|
||||
{
|
||||
performFind(false);
|
||||
return;
|
||||
}
|
||||
|
||||
cursor.insertText(m_replaceEdit->text());
|
||||
|
||||
// Move to next match
|
||||
performFind(false);
|
||||
updateMatchLabel();
|
||||
highlightAllMatches();
|
||||
}
|
||||
|
||||
void SearchPanel::replaceAll()
|
||||
{
|
||||
if (!m_editor || m_searchEdit->text().isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor(doc);
|
||||
cursor.beginEditBlock();
|
||||
|
||||
int count = 0;
|
||||
const QTextDocument::FindFlags flags = buildFindFlags(false);
|
||||
const QString needle = m_searchEdit->text();
|
||||
const QString replacement = m_replaceEdit->text();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (m_chkRegex->isChecked())
|
||||
{
|
||||
QRegularExpression re(needle);
|
||||
if (!m_chkCase->isChecked())
|
||||
{
|
||||
re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
|
||||
}
|
||||
cursor = doc->find(re, cursor, flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor = doc->find(needle, cursor, flags);
|
||||
}
|
||||
|
||||
if (cursor.isNull())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
cursor.insertText(replacement);
|
||||
++count;
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
|
||||
updateMatchLabel();
|
||||
clearHighlights();
|
||||
|
||||
QMessageBox::information(this, tr("Alle ersetzen"),
|
||||
tr("%1 Ersetzung(en) durchgeführt.").arg(count));
|
||||
}
|
||||
|
||||
void SearchPanel::replaceInSelection()
|
||||
{
|
||||
if (!m_editor || m_searchEdit->text().isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor selCursor = m_editor->textCursor();
|
||||
if (!selCursor.hasSelection())
|
||||
{
|
||||
QMessageBox::information(this, tr("In Auswahl ersetzen"),
|
||||
tr("Es ist kein Text ausgewählt."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Work only within the selected region
|
||||
const int selStart = selCursor.selectionStart();
|
||||
const int selEnd = selCursor.selectionEnd();
|
||||
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor(doc);
|
||||
cursor.setPosition(selStart);
|
||||
cursor.beginEditBlock();
|
||||
|
||||
int count = 0;
|
||||
int offset = 0; // Replacement may be longer/shorter than search term
|
||||
const QTextDocument::FindFlags flags = buildFindFlags(false);
|
||||
const QString needle = m_searchEdit->text();
|
||||
const QString replacement = m_replaceEdit->text();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (m_chkRegex->isChecked())
|
||||
{
|
||||
QRegularExpression re(needle);
|
||||
if (!m_chkCase->isChecked())
|
||||
{
|
||||
re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
|
||||
}
|
||||
cursor = doc->find(re, cursor, flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor = doc->find(needle, cursor, flags);
|
||||
}
|
||||
|
||||
if (cursor.isNull())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop if we've left the original selection
|
||||
if (cursor.selectionEnd() > selEnd + offset)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
offset += replacement.length() - cursor.selectedText().length();
|
||||
cursor.insertText(replacement);
|
||||
++count;
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
|
||||
updateMatchLabel();
|
||||
clearHighlights();
|
||||
|
||||
QMessageBox::information(this, tr("In Auswahl ersetzen"),
|
||||
tr("%1 Ersetzung(en) in der Auswahl durchgeführt.").arg(count));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private slots
|
||||
// ---------------------------------------------------------------------------
|
||||
void SearchPanel::onSearchTextChanged(const QString &/*text*/)
|
||||
{
|
||||
highlightAllMatches();
|
||||
updateMatchLabel();
|
||||
}
|
||||
|
||||
void SearchPanel::onOptionChanged()
|
||||
{
|
||||
highlightAllMatches();
|
||||
updateMatchLabel();
|
||||
}
|
||||
|
||||
void SearchPanel::onCloseClicked()
|
||||
{
|
||||
clearHighlights();
|
||||
m_matchLabel->setText(QString());
|
||||
hide();
|
||||
if (m_editor)
|
||||
{
|
||||
m_editor->setFocus();
|
||||
}
|
||||
}
|
||||
79
src/editor/SearchPanel.h
Normal file
79
src/editor/SearchPanel.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTextDocument>
|
||||
|
||||
class CodeEditor;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SearchPanel – Collapsible find/replace bar that operates on a CodeEditor.
|
||||
//
|
||||
// Capabilities:
|
||||
// • Nächsten / Vorherigen Treffer suchen
|
||||
// • Einzeln ersetzen
|
||||
// • Alle ersetzen
|
||||
// • Nur in Auswahl ersetzen
|
||||
// • Optionen: Groß-/Kleinschreibung, Ganzes Wort, Reguläre Ausdrücke
|
||||
// ---------------------------------------------------------------------------
|
||||
class SearchPanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SearchPanel(QWidget *parent = nullptr);
|
||||
|
||||
// Must be called whenever the active editor changes
|
||||
void setEditor(CodeEditor *editor);
|
||||
|
||||
// Toggle visibility and focus the search field
|
||||
void activate();
|
||||
|
||||
public slots:
|
||||
void findNext();
|
||||
void findPrevious();
|
||||
void replaceCurrent();
|
||||
void replaceAll();
|
||||
void replaceInSelection();
|
||||
|
||||
private slots:
|
||||
void onSearchTextChanged(const QString &text);
|
||||
void onOptionChanged(); // Für Checkbox-Signale (bool-Parameter wird ignoriert)
|
||||
void onCloseClicked();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
|
||||
QTextDocument::FindFlags buildFindFlags(bool backwards = false) const;
|
||||
bool performFind(bool backwards = false);
|
||||
void highlightAllMatches();
|
||||
void clearHighlights();
|
||||
void updateMatchLabel();
|
||||
|
||||
CodeEditor *m_editor = nullptr;
|
||||
|
||||
// Search row
|
||||
QLineEdit *m_searchEdit = nullptr;
|
||||
QPushButton *m_btnPrev = nullptr;
|
||||
QPushButton *m_btnNext = nullptr;
|
||||
QLabel *m_matchLabel = nullptr;
|
||||
QPushButton *m_btnClose = nullptr;
|
||||
|
||||
// Replace row
|
||||
QLineEdit *m_replaceEdit = nullptr;
|
||||
QPushButton *m_btnReplace = nullptr;
|
||||
QPushButton *m_btnReplaceAll = nullptr;
|
||||
QPushButton *m_btnReplaceSelection = nullptr;
|
||||
|
||||
// Options row
|
||||
QCheckBox *m_chkCase = nullptr;
|
||||
QCheckBox *m_chkWord = nullptr;
|
||||
QCheckBox *m_chkRegex = nullptr;
|
||||
|
||||
QGridLayout *m_grid = nullptr;
|
||||
};
|
||||
Reference in New Issue
Block a user