Erste Version

This commit is contained in:
2026-05-23 13:14:30 +02:00
commit ca573209e7
39 changed files with 3430 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
{src/
build/

69
CMakeLists.txt Normal file
View File

@@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.16)
project(BareCode
VERSION 1.0.0
DESCRIPTION "A modular code editor built with Qt6"
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
# Platform-specific settings
if(WIN32)
set(CMAKE_WIN32_EXECUTABLE ON)
add_compile_definitions(PLATFORM_WINDOWS)
elseif(UNIX AND NOT APPLE)
add_compile_definitions(PLATFORM_LINUX)
elseif(APPLE)
add_compile_definitions(PLATFORM_MACOS)
endif()
# Find Qt6
find_package(Qt6 REQUIRED COMPONENTS
Core
Gui
Widgets
)
qt_standard_project_setup()
# Include cmake modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# Collect sources
add_subdirectory(src)
# Resources
qt_add_resources(BARECODE_RESOURCES resources/resources.qrc)
# Main executable
qt_add_executable(BareCode
main.cpp
${BARECODE_RESOURCES}
)
target_link_libraries(BareCode PRIVATE
BareCode_Core
BareCode_Editor
BareCode_FileTree
BareCode_Highlighter
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
target_include_directories(BareCode PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
# Install rules
include(GNUInstallDirs)
install(TARGETS BareCode
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Dany Thinnes Projekt Hirnfrei
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

95
README.md Normal file
View File

@@ -0,0 +1,95 @@
# BareCode
A modular code editor built with **C++17**, **Qt6**, and **CMake**.
Designed to compile cleanly on **Linux** and **Windows**.
---
## Features (v1.0)
| Feature | Details |
|---|---|
| Project tree | Left panel — shows only the selected project directory |
| Tabbed editor | Open multiple files simultaneously; tabs are movable and closable |
| Syntax highlighting | C / C++ (extensible via `HighlighterFactory`) |
| Line numbers | Painted in a dedicated gutter |
| Auto-indent | Preserves indentation level on Enter |
| Tab → spaces | Configurable; jumps to next tab stop |
| Persistent settings | Window geometry, font, tab size, last project (INI file) |
---
## Project Structure
```
BareCode/
├── CMakeLists.txt # Top-level build
├── main.cpp
├── resources/
│ └── resources.qrc
└── src/
├── core/ # MainWindow, ProjectManager, Settings, IPlugin
├── editor/ # EditorPanel, EditorTab, CodeEditor, LineNumberArea
├── filetree/ # FileTreePanel
└── highlighter/ # SyntaxHighlighter, CppHighlighter, HighlighterFactory
```
Each subdirectory compiles into its own **static library**, keeping modules independent.
---
## Building
### Prerequisites
- CMake ≥ 3.16
- Qt6 (Widgets module)
- A C++17-capable compiler (GCC, Clang, MSVC)
### Linux
```bash
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
./build/BareCode
```
### Windows (Visual Studio)
```bat
cmake -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
build\Release\BareCode.exe
```
### Windows (MinGW)
```bat
cmake -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release
cmake --build build
build\BareCode.exe
```
---
## Extending BareCode
### Adding a new syntax highlighter
1. Create a subclass of `SyntaxHighlighter` in `src/highlighter/`.
2. Populate `m_rules` in the constructor (see `CppHighlighter` as a template).
3. Register the file extension(s) in `HighlighterFactory.cpp`.
No other files need to change.
### Adding a new panel / plugin
1. Implement the `IPlugin` interface from `src/core/IPlugin.h`.
2. Create a `QWidget`-derived class for the UI.
3. Instantiate and wire it up in `MainWindow`.
---
## Code Style
All code uses **Allman brace style** and C++17 throughout.

13
barecode.desktop Normal file
View File

@@ -0,0 +1,13 @@
[Desktop Entry]
Type=Application
Name=BareCode
GenericName=Code-Editor
Comment=Modularer Code-Editor von Projekt Hirnfrei
Exec=BareCode %F
Icon=barecode
Terminal=false
Categories=Development;TextEditor;IDE;
MimeType=text/plain;text/x-csrc;text/x-chdr;text/x-c++src;text/x-c++hdr;
Keywords=editor;code;programmierung;entwicklung;
StartupNotify=true
StartupWMClass=BareCode

16
main.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include <QApplication>
#include "core/MainWindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setApplicationName("BareCode");
app.setApplicationVersion("1.0.0");
app.setOrganizationName("BareCode");
MainWindow window;
window.show();
return app.exec();
}

5
resources/resources.qrc Normal file
View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<!-- Icons and other assets can be added here -->
</qresource>
</RCC>

4
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
add_subdirectory(core)
add_subdirectory(editor)
add_subdirectory(filetree)
add_subdirectory(highlighter)

105
src/core/AboutDialog.cpp Normal file
View File

@@ -0,0 +1,105 @@
#include "AboutDialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QFrame>
#include <QFont>
#include <QApplication>
AboutDialog::AboutDialog(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(tr("Über BareCode"));
setFixedSize(440, 310);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QVBoxLayout *root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->setSpacing(0);
// -----------------------------------------------------------------------
// Header-Banner
// -----------------------------------------------------------------------
QFrame *banner = new QFrame(this);
banner->setFixedHeight(88);
banner->setStyleSheet(
"background: qlineargradient(x1:0, y1:0, x2:1, y2:0,"
" stop:0 #1a1a2e, stop:1 #16213e);"
);
QVBoxLayout *bannerLayout = new QVBoxLayout(banner);
bannerLayout->setContentsMargins(24, 10, 24, 10);
bannerLayout->setSpacing(2);
QLabel *appName = new QLabel("BareCode", banner);
QFont nameFont = appName->font();
nameFont.setPointSize(22);
nameFont.setBold(true);
appName->setFont(nameFont);
appName->setStyleSheet("color: #e0e0ff; background: transparent;");
QLabel *tagline = new QLabel(tr("Modularer Code-Editor"), banner);
tagline->setStyleSheet("color: #8888bb; background: transparent;");
bannerLayout->addWidget(appName);
bannerLayout->addWidget(tagline);
root->addWidget(banner);
// -----------------------------------------------------------------------
// Info-Tabelle
// -----------------------------------------------------------------------
QVBoxLayout *info = new QVBoxLayout();
info->setContentsMargins(28, 20, 28, 8);
info->setSpacing(10);
auto makeRow = [&](const QString &label, const QString &value)
{
QHBoxLayout *row = new QHBoxLayout();
row->setSpacing(12);
QLabel *lbl = new QLabel(label, this);
QFont boldFont = lbl->font();
boldFont.setBold(true);
lbl->setFont(boldFont);
lbl->setFixedWidth(100);
lbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLabel *val = new QLabel(value, this);
val->setTextInteractionFlags(Qt::TextSelectableByMouse);
row->addWidget(lbl);
row->addWidget(val, 1);
info->addLayout(row);
};
makeRow(tr("Version"), "1.0.0");
makeRow(tr("Entwickler"), "Dany Thinnes");
makeRow(tr("Projekt"), "Projekt Hirnfrei");
makeRow(tr("Framework"), QString("Qt %1").arg(qVersion()));
makeRow(tr("Sprache"), "C++17");
root->addLayout(info);
root->addStretch();
// -----------------------------------------------------------------------
// Trennlinie + Schließen-Button
// -----------------------------------------------------------------------
QFrame *line = new QFrame(this);
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
root->addWidget(line);
QHBoxLayout *btnRow = new QHBoxLayout();
btnRow->setContentsMargins(12, 8, 12, 12);
btnRow->addStretch();
QPushButton *btnClose = new QPushButton(tr("Schließen"), this);
btnClose->setDefault(true);
btnClose->setFixedWidth(110);
connect(btnClose, &QPushButton::clicked, this, &QDialog::accept);
btnRow->addWidget(btnClose);
root->addLayout(btnRow);
}

14
src/core/AboutDialog.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <QDialog>
// ---------------------------------------------------------------------------
// AboutDialog Zeigt Versionsinformationen und Entwicklerangaben.
// ---------------------------------------------------------------------------
class AboutDialog : public QDialog
{
Q_OBJECT
public:
explicit AboutDialog(QWidget *parent = nullptr);
};

28
src/core/CMakeLists.txt Normal file
View File

@@ -0,0 +1,28 @@
set(CORE_SOURCES
MainWindow.cpp
MainWindow.h
IPlugin.h
ProjectManager.cpp
ProjectManager.h
Settings.cpp
Settings.h
ThemeManager.cpp
ThemeManager.h
AboutDialog.cpp
AboutDialog.h
)
add_library(BareCode_Core STATIC ${CORE_SOURCES})
target_link_libraries(BareCode_Core PUBLIC
Qt6::Core
Qt6::Gui
Qt6::Widgets
BareCode_Editor
BareCode_FileTree
)
target_include_directories(BareCode_Core PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

26
src/core/IPlugin.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <QString>
#include <QWidget>
// ---------------------------------------------------------------------------
// IPlugin Interface that every BareCode module / plugin must implement.
// This allows components to be swapped or extended without touching the core.
// ---------------------------------------------------------------------------
class IPlugin
{
public:
virtual ~IPlugin() = default;
// Human-readable name of the plugin
virtual QString pluginName() const = 0;
// Version string, e.g. "1.0.0"
virtual QString pluginVersion() const = 0;
// Called once after all plugins are loaded so plugins can cross-reference
virtual void initialize() {}
// Called before the application shuts down
virtual void shutdown() {}
};

316
src/core/MainWindow.cpp Normal file
View File

@@ -0,0 +1,316 @@
#include "MainWindow.h"
#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QCloseEvent>
#include <QSettings>
#include "AboutDialog.h"
#include "filetree/FileTreePanel.h"
#include "editor/EditorPanel.h"
// ---------------------------------------------------------------------------
// Konstruktor
// ---------------------------------------------------------------------------
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_projectManager(std::make_unique<ProjectManager>())
, m_settings(std::make_unique<Settings>())
, m_themeManager(std::make_unique<ThemeManager>())
{
setWindowTitle("BareCode");
setMinimumSize(900, 600);
setupUi();
setupMenuBar();
setupStatusBar();
connectSignals();
restoreWindowState();
applyInitialTheme();
// Letztes Projekt wieder öffnen
const QString lastPath = m_settings->lastProjectPath();
if (!lastPath.isEmpty())
{
m_projectManager->openProject(lastPath);
}
}
MainWindow::~MainWindow() = default;
// ---------------------------------------------------------------------------
// UI aufbauen
// ---------------------------------------------------------------------------
void MainWindow::setupUi()
{
m_splitter = new QSplitter(Qt::Horizontal, this);
setCentralWidget(m_splitter);
m_fileTree = new FileTreePanel(m_splitter);
m_editor = new EditorPanel(m_settings.get(), m_splitter);
m_splitter->addWidget(m_fileTree);
m_splitter->addWidget(m_editor);
const int treeWidth = m_settings->fileTreeWidth();
m_splitter->setSizes({treeWidth, width() - treeWidth});
m_splitter->setStretchFactor(0, 0);
m_splitter->setStretchFactor(1, 1);
}
// ---------------------------------------------------------------------------
// Menüleiste
// ---------------------------------------------------------------------------
void MainWindow::setupMenuBar()
{
// ---- Datei ----
QMenu *mDatei = menuBar()->addMenu(tr("&Datei"));
m_actNewFile = mDatei->addAction(tr("&Neue Datei…"), this, &MainWindow::onNewFile);
m_actNewFile->setShortcut(QKeySequence::New);
mDatei->addSeparator();
m_actOpenFile = mDatei->addAction(tr("Datei &öffnen…"), this, &MainWindow::onOpenFile);
m_actOpenFile->setShortcut(QKeySequence::Open);
m_actOpenProject = mDatei->addAction(tr("&Projekt öffnen…"), this, &MainWindow::onOpenProject);
m_actOpenProject->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
m_actClose = mDatei->addAction(tr("Projekt &schließen"), this, &MainWindow::onCloseProject);
mDatei->addSeparator();
m_actSave = mDatei->addAction(tr("&Speichern"), this, &MainWindow::onSave);
m_actSave->setShortcut(QKeySequence::Save);
m_actSaveAs = mDatei->addAction(tr("Speichern &unter…"), this, &MainWindow::onSaveAs);
m_actSaveAs->setShortcut(QKeySequence::SaveAs);
m_actSaveAll = mDatei->addAction(tr("&Alles speichern"), this, &MainWindow::onSaveAll);
m_actSaveAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
mDatei->addSeparator();
m_actQuit = mDatei->addAction(tr("&Beenden"), qApp, &QApplication::quit);
m_actQuit->setShortcut(QKeySequence::Quit);
// ---- Bearbeiten ----
QMenu *mBearbeiten = menuBar()->addMenu(tr("&Bearbeiten"));
m_actUndo = mBearbeiten->addAction(tr("&Rückgängig"), this, &MainWindow::onUndo);
m_actUndo->setShortcut(QKeySequence::Undo);
m_actRedo = mBearbeiten->addAction(tr("&Wiederholen"), this, &MainWindow::onRedo);
m_actRedo->setShortcut(QKeySequence::Redo);
mBearbeiten->addSeparator();
m_actSearch = mBearbeiten->addAction(tr("&Suchen / Ersetzen…"), this, &MainWindow::onShowSearch);
m_actSearch->setShortcut(QKeySequence::Find);
// ---- Ansicht ----
QMenu *mAnsicht = menuBar()->addMenu(tr("&Ansicht"));
m_actDarkMode = mAnsicht->addAction(tr("&Dark Mode"));
m_actDarkMode->setCheckable(true);
m_actDarkMode->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_D);
connect(m_actDarkMode, &QAction::toggled, this, &MainWindow::onToggleDarkMode);
// ---- Hilfe ----
QMenu *mHilfe = menuBar()->addMenu(tr("&Hilfe"));
m_actAbout = mHilfe->addAction(tr("&Über BareCode…"), this, &MainWindow::onAbout);
}
void MainWindow::setupStatusBar()
{
statusBar()->showMessage(tr("Bereit"));
}
// ---------------------------------------------------------------------------
// Signale verbinden
// ---------------------------------------------------------------------------
void MainWindow::connectSignals()
{
connect(m_projectManager.get(), &ProjectManager::projectOpened,
this, &MainWindow::onProjectOpened);
connect(m_projectManager.get(), &ProjectManager::projectClosed,
this, &MainWindow::onProjectClosed);
// Dateibaum → Editor
connect(m_fileTree, &FileTreePanel::fileActivated,
m_editor, &EditorPanel::openFile);
connect(m_fileTree, &FileTreePanel::fileCreated,
m_editor, &EditorPanel::openFile);
// Gespeichert → Statusleiste
connect(m_editor, &EditorPanel::currentFileSaved, this, [this](const QString &path)
{
statusBar()->showMessage(tr("Gespeichert: %1").arg(path), 3000);
});
// Splitter-Breite merken
connect(m_splitter, &QSplitter::splitterMoved, this, [this](int pos, int)
{
m_settings->setFileTreeWidth(pos);
});
}
// ---------------------------------------------------------------------------
// Theme beim Start
// ---------------------------------------------------------------------------
void MainWindow::applyInitialTheme()
{
const bool dark = m_settings->darkMode();
// Block damit toggled-Signal nicht doppelt feuert
m_actDarkMode->blockSignals(true);
m_actDarkMode->setChecked(dark);
m_actDarkMode->blockSignals(false);
m_themeManager->applyTheme(dark ? ThemeManager::Theme::Dark
: ThemeManager::Theme::Light);
}
// ---------------------------------------------------------------------------
// Slots Datei
// ---------------------------------------------------------------------------
void MainWindow::onNewFile()
{
m_fileTree->triggerNewFile();
}
void MainWindow::onOpenFile()
{
const QString path = QFileDialog::getOpenFileName(
this,
tr("Datei öffnen"),
m_settings->lastProjectPath()
);
if (!path.isEmpty())
{
m_editor->openFile(path);
}
}
void MainWindow::onOpenProject()
{
const QString path = QFileDialog::getExistingDirectory(
this,
tr("Projektverzeichnis öffnen"),
m_settings->lastProjectPath()
);
if (!path.isEmpty())
{
m_projectManager->openProject(path);
m_settings->setLastProjectPath(path);
}
}
void MainWindow::onCloseProject()
{
m_projectManager->closeProject();
}
void MainWindow::onSave()
{
m_editor->saveCurrentFile();
}
void MainWindow::onSaveAs()
{
m_editor->saveCurrentFileAs();
}
void MainWindow::onSaveAll()
{
m_editor->saveAllFiles();
statusBar()->showMessage(tr("Alle Dateien gespeichert"), 3000);
}
// ---------------------------------------------------------------------------
// Slots Bearbeiten
// ---------------------------------------------------------------------------
void MainWindow::onUndo()
{
m_editor->undo();
}
void MainWindow::onRedo()
{
m_editor->redo();
}
void MainWindow::onShowSearch()
{
m_editor->showSearchPanel();
}
// ---------------------------------------------------------------------------
// Slots Ansicht
// ---------------------------------------------------------------------------
void MainWindow::onToggleDarkMode(bool checked)
{
m_themeManager->applyTheme(checked ? ThemeManager::Theme::Dark
: ThemeManager::Theme::Light);
m_settings->setDarkMode(checked);
}
// ---------------------------------------------------------------------------
// Slots Hilfe
// ---------------------------------------------------------------------------
void MainWindow::onAbout()
{
AboutDialog dlg(this);
dlg.exec();
}
// ---------------------------------------------------------------------------
// Slots Projekt
// ---------------------------------------------------------------------------
void MainWindow::onProjectOpened(const QString &path)
{
setWindowTitle(QString("BareCode %1").arg(path));
m_fileTree->setRootPath(path);
statusBar()->showMessage(tr("Projekt geöffnet: %1").arg(path), 4000);
}
void MainWindow::onProjectClosed()
{
setWindowTitle("BareCode");
m_fileTree->clearRoot();
statusBar()->showMessage(tr("Projekt geschlossen"), 3000);
}
// ---------------------------------------------------------------------------
// Fenster-Zustand
// ---------------------------------------------------------------------------
void MainWindow::saveWindowState()
{
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
s.setValue("window/geometry", saveGeometry());
s.setValue("window/state", saveState());
}
void MainWindow::restoreWindowState()
{
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
if (s.contains("window/geometry"))
{
restoreGeometry(s.value("window/geometry").toByteArray());
}
if (s.contains("window/state"))
{
restoreState(s.value("window/state").toByteArray());
}
}
void MainWindow::closeEvent(QCloseEvent *event)
{
saveWindowState();
event->accept();
}

85
src/core/MainWindow.h Normal file
View File

@@ -0,0 +1,85 @@
#pragma once
#include <QMainWindow>
#include <QSplitter>
#include <QMenuBar>
#include <QStatusBar>
#include <QAction>
#include <memory>
#include "ProjectManager.h"
#include "Settings.h"
#include "ThemeManager.h"
class FileTreePanel;
class EditorPanel;
// ---------------------------------------------------------------------------
// MainWindow Hauptfenster. Besitzt alle zentralen Dienste und das Layout.
// ---------------------------------------------------------------------------
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
protected:
void closeEvent(QCloseEvent *event) override;
private slots:
// Datei
void onNewFile();
void onOpenFile();
void onOpenProject();
void onCloseProject();
void onSave();
void onSaveAs();
void onSaveAll();
// Bearbeiten
void onUndo();
void onRedo();
void onShowSearch();
// Ansicht
void onToggleDarkMode(bool checked);
// Hilfe
void onAbout();
// Intern
void onProjectOpened(const QString &path);
void onProjectClosed();
private:
void setupUi();
void setupMenuBar();
void setupStatusBar();
void connectSignals();
void applyInitialTheme();
void saveWindowState();
void restoreWindowState();
// Dienste
std::unique_ptr<ProjectManager> m_projectManager;
std::unique_ptr<Settings> m_settings;
std::unique_ptr<ThemeManager> m_themeManager;
// Layout
QSplitter *m_splitter = nullptr;
FileTreePanel *m_fileTree = nullptr;
EditorPanel *m_editor = nullptr;
// Aktionen
QAction *m_actNewFile = nullptr;
QAction *m_actOpenFile = nullptr;
QAction *m_actOpenProject = nullptr;
QAction *m_actClose = nullptr;
QAction *m_actSave = nullptr;
QAction *m_actSaveAs = nullptr;
QAction *m_actSaveAll = nullptr;
QAction *m_actQuit = nullptr;
QAction *m_actUndo = nullptr;
QAction *m_actRedo = nullptr;
QAction *m_actSearch = nullptr;
QAction *m_actDarkMode = nullptr;
QAction *m_actAbout = nullptr;
};

View File

@@ -0,0 +1,33 @@
#include "ProjectManager.h"
ProjectManager::ProjectManager(QObject *parent)
: QObject(parent)
{
}
QString ProjectManager::currentProjectPath() const
{
return m_projectPath;
}
void ProjectManager::openProject(const QString &path)
{
if (m_projectPath == path)
{
return;
}
m_projectPath = path;
emit projectOpened(m_projectPath);
}
void ProjectManager::closeProject()
{
if (m_projectPath.isEmpty())
{
return;
}
m_projectPath.clear();
emit projectClosed();
}

27
src/core/ProjectManager.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <QObject>
#include <QString>
// ---------------------------------------------------------------------------
// ProjectManager Tracks the currently open project directory and emits
// signals when the project changes so other components can react.
// ---------------------------------------------------------------------------
class ProjectManager : public QObject
{
Q_OBJECT
public:
explicit ProjectManager(QObject *parent = nullptr);
QString currentProjectPath() const;
void openProject(const QString &path);
void closeProject();
signals:
void projectOpened(const QString &path);
void projectClosed();
private:
QString m_projectPath;
};

90
src/core/Settings.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include "Settings.h"
Settings::Settings(QObject *parent)
: QObject(parent)
, m_settings(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode")
{
}
// ---------------------------------------------------------------------------
// Editor font
// ---------------------------------------------------------------------------
QFont Settings::editorFont() const
{
QFont defaultFont("Monospace", 11);
defaultFont.setStyleHint(QFont::Monospace);
return m_settings.value("editor/font", defaultFont).value<QFont>();
}
void Settings::setEditorFont(const QFont &font)
{
m_settings.setValue("editor/font", font);
emit settingsChanged();
}
// ---------------------------------------------------------------------------
// Tab size
// ---------------------------------------------------------------------------
int Settings::tabSize() const
{
return m_settings.value("editor/tabSize", 4).toInt();
}
void Settings::setTabSize(int size)
{
m_settings.setValue("editor/tabSize", size);
emit settingsChanged();
}
// ---------------------------------------------------------------------------
// Spaces vs. tabs
// ---------------------------------------------------------------------------
bool Settings::useSpacesForTabs() const
{
return m_settings.value("editor/useSpacesForTabs", true).toBool();
}
void Settings::setUseSpacesForTabs(bool use)
{
m_settings.setValue("editor/useSpacesForTabs", use);
emit settingsChanged();
}
// ---------------------------------------------------------------------------
// File tree width
// ---------------------------------------------------------------------------
int Settings::fileTreeWidth() const
{
return m_settings.value("layout/fileTreeWidth", 240).toInt();
}
void Settings::setFileTreeWidth(int width)
{
m_settings.setValue("layout/fileTreeWidth", width);
}
// ---------------------------------------------------------------------------
// Dark mode
// ---------------------------------------------------------------------------
bool Settings::darkMode() const
{
return m_settings.value("appearance/darkMode", false).toBool();
}
void Settings::setDarkMode(bool dark)
{
m_settings.setValue("appearance/darkMode", dark);
}
// ---------------------------------------------------------------------------
// Last project path
// ---------------------------------------------------------------------------
QString Settings::lastProjectPath() const
{
return m_settings.value("project/lastPath", QString()).toString();
}
void Settings::setLastProjectPath(const QString &path)
{
m_settings.setValue("project/lastPath", path);
}

45
src/core/Settings.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include <QObject>
#include <QSettings>
#include <QString>
#include <QFont>
// ---------------------------------------------------------------------------
// Settings Centralised persistent application settings.
// ---------------------------------------------------------------------------
class Settings : public QObject
{
Q_OBJECT
public:
explicit Settings(QObject *parent = nullptr);
// Editor
QFont editorFont() const;
void setEditorFont(const QFont &font);
int tabSize() const;
void setTabSize(int size);
bool useSpacesForTabs() const;
void setUseSpacesForTabs(bool use);
// Layout
int fileTreeWidth() const;
void setFileTreeWidth(int width);
// Recent
QString lastProjectPath() const;
void setLastProjectPath(const QString &path);
// Erscheinungsbild
bool darkMode() const;
void setDarkMode(bool dark);
signals:
void settingsChanged();
private:
QSettings m_settings;
};

115
src/core/ThemeManager.cpp Normal file
View File

@@ -0,0 +1,115 @@
#include "ThemeManager.h"
#include <QApplication>
#include <QStyleFactory>
ThemeManager::ThemeManager(QObject *parent)
: QObject(parent)
{
}
void ThemeManager::applyTheme(Theme theme)
{
m_currentTheme = theme;
QApplication::setStyle(QStyleFactory::create("Fusion"));
if (theme == Theme::Dark)
{
QApplication::setPalette(buildDarkPalette());
}
else
{
QApplication::setPalette(buildLightPalette());
}
emit themeChanged(theme);
}
ThemeManager::Theme ThemeManager::currentTheme() const
{
return m_currentTheme;
}
QPalette ThemeManager::buildDarkPalette()
{
QPalette p;
const QColor bg = QColor("#1e1e1e");
const QColor widget = QColor("#252526");
const QColor alt = QColor("#2d2d30");
const QColor hi = QColor("#264f78");
const QColor hiText = QColor("#ffffff");
const QColor text = QColor("#d4d4d4");
const QColor disabled = QColor("#6d6d6d");
const QColor btn = QColor("#3c3c3c");
const QColor mid = QColor("#333333");
const QColor dark = QColor("#1a1a1a");
const QColor light = QColor("#454545");
const QColor link = QColor("#569cd6");
p.setColor(QPalette::Window, bg);
p.setColor(QPalette::WindowText, text);
p.setColor(QPalette::Base, widget);
p.setColor(QPalette::AlternateBase, alt);
p.setColor(QPalette::Text, text);
p.setColor(QPalette::Button, btn);
p.setColor(QPalette::ButtonText, text);
p.setColor(QPalette::Highlight, hi);
p.setColor(QPalette::HighlightedText, hiText);
p.setColor(QPalette::Link, link);
p.setColor(QPalette::LinkVisited, link.darker(120));
p.setColor(QPalette::Mid, mid);
p.setColor(QPalette::Dark, dark);
p.setColor(QPalette::Light, light);
p.setColor(QPalette::Shadow, QColor("#000000"));
p.setColor(QPalette::ToolTipBase, widget);
p.setColor(QPalette::ToolTipText, text);
p.setColor(QPalette::PlaceholderText, disabled);
p.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
p.setColor(QPalette::Disabled, QPalette::Text, disabled);
p.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
return p;
}
QPalette ThemeManager::buildLightPalette()
{
// Fusion-Standard-Palette
QPalette p;
const QColor bg = QColor("#f3f3f3");
const QColor widget = QColor("#ffffff");
const QColor alt = QColor("#e8e8e8");
const QColor hi = QColor("#0078d4");
const QColor hiText = QColor("#ffffff");
const QColor text = QColor("#1e1e1e");
const QColor disabled = QColor("#a0a0a0");
const QColor btn = QColor("#e1e1e1");
const QColor mid = QColor("#c8c8c8");
const QColor dark = QColor("#a0a0a0");
const QColor light = QColor("#ffffff");
const QColor link = QColor("#0078d4");
p.setColor(QPalette::Window, bg);
p.setColor(QPalette::WindowText, text);
p.setColor(QPalette::Base, widget);
p.setColor(QPalette::AlternateBase, alt);
p.setColor(QPalette::Text, text);
p.setColor(QPalette::Button, btn);
p.setColor(QPalette::ButtonText, text);
p.setColor(QPalette::Highlight, hi);
p.setColor(QPalette::HighlightedText, hiText);
p.setColor(QPalette::Link, link);
p.setColor(QPalette::LinkVisited, link.darker(130));
p.setColor(QPalette::Mid, mid);
p.setColor(QPalette::Dark, dark);
p.setColor(QPalette::Light, light);
p.setColor(QPalette::PlaceholderText, disabled);
p.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
p.setColor(QPalette::Disabled, QPalette::Text, disabled);
p.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
return p;
}

29
src/core/ThemeManager.h Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include <QObject>
#include <QPalette>
// ---------------------------------------------------------------------------
// ThemeManager Schaltet zwischen Hell- und Dunkelmodus um.
// ---------------------------------------------------------------------------
class ThemeManager : public QObject
{
Q_OBJECT
public:
enum class Theme { Light, Dark };
explicit ThemeManager(QObject *parent = nullptr);
void applyTheme(Theme theme);
Theme currentTheme() const;
signals:
void themeChanged(Theme theme);
private:
static QPalette buildDarkPalette();
static QPalette buildLightPalette();
Theme m_currentTheme = Theme::Light;
};

26
src/editor/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
};

View 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);
}

View 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
View 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
View 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;
};

View File

@@ -0,0 +1,17 @@
set(FILETREE_SOURCES
FileTreePanel.cpp
FileTreePanel.h
)
add_library(BareCode_FileTree STATIC ${FILETREE_SOURCES})
target_link_libraries(BareCode_FileTree PUBLIC
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
target_include_directories(BareCode_FileTree PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View File

@@ -0,0 +1,259 @@
#include "FileTreePanel.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QInputDialog>
#include <QMessageBox>
#include <QPoint>
FileTreePanel::FileTreePanel(QWidget *parent)
: QWidget(parent)
{
setupUi();
}
// ---------------------------------------------------------------------------
// Setup
// ---------------------------------------------------------------------------
void FileTreePanel::setupUi()
{
m_layout = new QVBoxLayout(this);
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
// Small header label showing the project name
m_label = new QLabel(tr("Kein Projekt geöffnet"), this);
m_label->setContentsMargins(6, 4, 6, 4);
m_label->setStyleSheet("font-weight: bold; background: palette(mid);");
m_label->setWordWrap(true);
m_layout->addWidget(m_label);
// File system model show only the project subtree
m_model = new QFileSystemModel(this);
m_model->setReadOnly(false);
m_model->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
// Tree view
m_tree = new QTreeView(this);
m_tree->setModel(m_model);
m_tree->setAnimated(true);
m_tree->setIndentation(16);
m_tree->setSortingEnabled(true);
m_tree->sortByColumn(0, Qt::AscendingOrder);
m_tree->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_tree->setHeaderHidden(true);
m_tree->setContextMenuPolicy(Qt::CustomContextMenu);
// Hide all columns except the file name
for (int col = 1; col < m_model->columnCount(); ++col)
{
m_tree->hideColumn(col);
}
m_layout->addWidget(m_tree);
connect(m_tree, &QTreeView::activated,
this, &FileTreePanel::onItemActivated);
connect(m_tree, &QTreeView::customContextMenuRequested,
this, &FileTreePanel::onContextMenuRequested);
}
// ---------------------------------------------------------------------------
// Public interface
// ---------------------------------------------------------------------------
void FileTreePanel::setRootPath(const QString &path)
{
const QModelIndex root = m_model->setRootPath(path);
m_tree->setRootIndex(root);
const QString projectName = QDir(path).dirName();
m_label->setText(projectName.isEmpty() ? path : projectName);
}
void FileTreePanel::clearRoot()
{
m_model->setRootPath(QString());
m_tree->setRootIndex(QModelIndex());
m_label->setText(tr("Kein Projekt geöffnet"));
}
void FileTreePanel::triggerNewFile()
{
onNewFile();
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
QString FileTreePanel::selectedDirectory() const
{
const QModelIndex index = m_tree->currentIndex();
if (!index.isValid())
{
return m_model->rootPath();
}
const QString path = m_model->filePath(index);
const QFileInfo info(path);
return info.isDir() ? path : info.absolutePath();
}
// ---------------------------------------------------------------------------
// Slots
// ---------------------------------------------------------------------------
void FileTreePanel::onItemActivated(const QModelIndex &index)
{
const QString path = m_model->filePath(index);
const QFileInfo info(path);
if (info.isFile())
{
emit fileActivated(path);
}
}
void FileTreePanel::onContextMenuRequested(const QPoint &pos)
{
QMenu menu(this);
QAction *actNewFile = menu.addAction(tr("Neue Datei…"));
QAction *actNewFolder = menu.addAction(tr("Neuer Ordner…"));
menu.addSeparator();
QAction *actDelete = menu.addAction(tr("Löschen"));
// Disable delete if nothing is selected
const QModelIndex index = m_tree->indexAt(pos);
actDelete->setEnabled(index.isValid());
QAction *chosen = menu.exec(m_tree->viewport()->mapToGlobal(pos));
if (chosen == actNewFile)
{
onNewFile();
}
else if (chosen == actNewFolder)
{
onNewFolder();
}
else if (chosen == actDelete)
{
onDeleteEntry();
}
}
void FileTreePanel::onNewFile()
{
const QString dir = selectedDirectory();
if (dir.isEmpty())
{
return;
}
bool ok = false;
const QString name = QInputDialog::getText(
this,
tr("Neue Datei"),
tr("Dateiname:"),
QLineEdit::Normal,
QString(),
&ok
);
if (!ok || name.trimmed().isEmpty())
{
return;
}
const QString filePath = QDir(dir).filePath(name.trimmed());
if (QFile::exists(filePath))
{
QMessageBox::warning(this, tr("Neue Datei"),
tr("Eine Datei mit diesem Namen existiert bereits:\n%1").arg(filePath));
return;
}
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly))
{
QMessageBox::critical(this, tr("Neue Datei"),
tr("Datei konnte nicht angelegt werden:\n%1").arg(filePath));
return;
}
file.close();
emit fileCreated(filePath);
// Select the new file in the tree
const QModelIndex newIndex = m_model->index(filePath);
m_tree->setCurrentIndex(newIndex);
m_tree->scrollTo(newIndex);
}
void FileTreePanel::onNewFolder()
{
const QString dir = selectedDirectory();
if (dir.isEmpty())
{
return;
}
bool ok = false;
const QString name = QInputDialog::getText(
this,
tr("Neuer Ordner"),
tr("Ordnername:"),
QLineEdit::Normal,
QString(),
&ok
);
if (!ok || name.trimmed().isEmpty())
{
return;
}
if (!QDir(dir).mkdir(name.trimmed()))
{
QMessageBox::critical(this, tr("Neuer Ordner"),
tr("Ordner konnte nicht angelegt werden:\n%1")
.arg(QDir(dir).filePath(name.trimmed())));
}
}
void FileTreePanel::onDeleteEntry()
{
const QModelIndex index = m_tree->currentIndex();
if (!index.isValid())
{
return;
}
const QString path = m_model->filePath(index);
const QFileInfo info(path);
const QString what = info.isDir() ? tr("Ordner") : tr("Datei");
const auto answer = QMessageBox::question(
this,
tr("%1 löschen").arg(what),
tr("%1 wirklich löschen?\n%2").arg(what, path),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);
if (answer != QMessageBox::Yes)
{
return;
}
if (info.isDir())
{
QDir(path).removeRecursively();
}
else
{
QFile::remove(path);
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <QWidget>
#include <QTreeView>
#include <QFileSystemModel>
#include <QVBoxLayout>
#include <QLabel>
#include <QString>
#include <QMenu>
#include <QAction>
// ---------------------------------------------------------------------------
// FileTreePanel Left panel showing the project directory tree.
// Emits fileActivated(path) when the user double-clicks a file.
// Supports creating new files/folders via context menu.
// ---------------------------------------------------------------------------
class FileTreePanel : public QWidget
{
Q_OBJECT
public:
explicit FileTreePanel(QWidget *parent = nullptr);
void setRootPath(const QString &path);
void clearRoot();
void triggerNewFile(); // Called from MainWindow menu action
signals:
void fileActivated(const QString &filePath);
void fileCreated(const QString &filePath);
private slots:
void onItemActivated(const QModelIndex &index);
void onContextMenuRequested(const QPoint &pos);
void onNewFile();
void onNewFolder();
void onDeleteEntry();
private:
void setupUi();
// Returns the directory of the currently selected item
QString selectedDirectory() const;
QVBoxLayout *m_layout = nullptr;
QLabel *m_label = nullptr;
QTreeView *m_tree = nullptr;
QFileSystemModel *m_model = nullptr;
};

View File

@@ -0,0 +1,19 @@
set(HIGHLIGHTER_SOURCES
SyntaxHighlighter.cpp
SyntaxHighlighter.h
HighlighterFactory.cpp
HighlighterFactory.h
)
add_library(BareCode_Highlighter STATIC ${HIGHLIGHTER_SOURCES})
target_link_libraries(BareCode_Highlighter PUBLIC
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
target_include_directories(BareCode_Highlighter PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View File

@@ -0,0 +1,45 @@
#include "HighlighterFactory.h"
#include <QFileInfo>
#include <QHash>
#include <functional>
SyntaxHighlighter *HighlighterFactory::createForFile(const QString &filePath,
QTextDocument *document)
{
// Erweiterung → Highlighter-Fabrik
// Neue Sprache hinzufügen: einfach einen Eintrag ergänzen.
static const QHash<QString, std::function<SyntaxHighlighter *(QTextDocument *)>> registry =
{
// C / C++
{ "c", [](QTextDocument *d) { return new CppHighlighter(d); } },
{ "cc", [](QTextDocument *d) { return new CppHighlighter(d); } },
{ "cpp", [](QTextDocument *d) { return new CppHighlighter(d); } },
{ "cxx", [](QTextDocument *d) { return new CppHighlighter(d); } },
{ "h", [](QTextDocument *d) { return new CppHighlighter(d); } },
{ "hpp", [](QTextDocument *d) { return new CppHighlighter(d); } },
{ "hxx", [](QTextDocument *d) { return new CppHighlighter(d); } },
// CSS
{ "css", [](QTextDocument *d) { return new CssHighlighter(d); } },
// HTML / Templates
{ "html", [](QTextDocument *d) { return new HtmlHighlighter(d); } },
{ "htm", [](QTextDocument *d) { return new HtmlHighlighter(d); } },
{ "xhtml",[](QTextDocument *d) { return new HtmlHighlighter(d); } },
// PHP (HTML + eingebettetes PHP)
{ "php", [](QTextDocument *d) { return new PhpHighlighter(d); } },
{ "phtml",[](QTextDocument *d) { return new PhpHighlighter(d); } },
{ "php3", [](QTextDocument *d) { return new PhpHighlighter(d); } },
{ "php4", [](QTextDocument *d) { return new PhpHighlighter(d); } },
{ "php5", [](QTextDocument *d) { return new PhpHighlighter(d); } },
{ "php7", [](QTextDocument *d) { return new PhpHighlighter(d); } },
{ "php8", [](QTextDocument *d) { return new PhpHighlighter(d); } },
};
const QString ext = QFileInfo(filePath).suffix().toLower();
const auto it = registry.find(ext);
return (it != registry.end()) ? it.value()(document) : nullptr;
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include <QString>
#include <QTextDocument>
#include "SyntaxHighlighter.h"
// ---------------------------------------------------------------------------
// HighlighterFactory Maps file extensions to the correct highlighter.
// To add a new language, register it in HighlighterFactory.cpp.
// ---------------------------------------------------------------------------
class HighlighterFactory
{
public:
// Creates and returns a highlighter for the given file path.
// Returns nullptr if no highlighter is registered for this file type.
static SyntaxHighlighter *createForFile(const QString &filePath,
QTextDocument *document);
};

View File

@@ -0,0 +1,450 @@
#include "SyntaxHighlighter.h"
#include <QTextDocument>
// ===========================================================================
// SyntaxHighlighter Basis
// ===========================================================================
SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
}
void SyntaxHighlighter::highlightBlock(const QString &text)
{
for (const HighlightRule &rule : m_rules)
{
QRegularExpressionMatchIterator it = rule.pattern.globalMatch(text);
while (it.hasNext())
{
QRegularExpressionMatch match = it.next();
setFormat(
static_cast<int>(match.capturedStart()),
static_cast<int>(match.capturedLength()),
rule.format
);
}
}
if (!m_hasMultiLineComment)
{
return;
}
setCurrentBlockState(0);
int startIndex = 0;
if (previousBlockState() != 1)
{
QRegularExpressionMatch m = m_commentStartExpression.match(text);
startIndex = m.hasMatch() ? static_cast<int>(m.capturedStart()) : -1;
}
while (startIndex >= 0)
{
QRegularExpressionMatch endMatch = m_commentEndExpression.match(text, startIndex);
int commentLength = 0;
if (endMatch.hasMatch())
{
commentLength = static_cast<int>(endMatch.capturedStart())
- startIndex
+ static_cast<int>(endMatch.capturedLength());
}
else
{
setCurrentBlockState(1);
commentLength = text.length() - startIndex;
}
setFormat(startIndex, commentLength, m_multiLineCommentFormat);
if (!endMatch.hasMatch())
{
break;
}
QRegularExpressionMatch nextStart =
m_commentStartExpression.match(text, startIndex + commentLength);
startIndex = nextStart.hasMatch()
? static_cast<int>(nextStart.capturedStart())
: -1;
}
}
// ===========================================================================
// CppHighlighter
// ===========================================================================
CppHighlighter::CppHighlighter(QTextDocument *parent)
: SyntaxHighlighter(parent)
{
m_hasMultiLineComment = true;
QTextCharFormat keywordFormat;
keywordFormat.setForeground(QColor("#569CD6"));
keywordFormat.setFontWeight(QFont::Bold);
const QStringList keywords = {
"alignas","alignof","and","and_eq","asm","auto","bitand","bitor",
"bool","break","case","catch","char","char8_t","char16_t","char32_t",
"class","compl","concept","const","consteval","constexpr","constinit",
"const_cast","continue","co_await","co_return","co_yield","decltype",
"default","delete","do","double","dynamic_cast","else","enum",
"explicit","export","extern","false","float","for","friend","goto",
"if","inline","int","long","mutable","namespace","new","noexcept",
"not","not_eq","nullptr","operator","or","or_eq","private","protected",
"public","register","reinterpret_cast","requires","return","short",
"signed","sizeof","static","static_assert","static_cast","struct",
"switch","template","this","thread_local","throw","true","try",
"typedef","typeid","typename","union","unsigned","using","virtual",
"void","volatile","wchar_t","while","xor","xor_eq","override","final"
};
for (const QString &kw : keywords)
{
HighlightRule rule;
rule.pattern = QRegularExpression(QString("\\b%1\\b").arg(kw));
rule.format = keywordFormat;
m_rules.append(rule);
}
QTextCharFormat preprocFormat;
preprocFormat.setForeground(QColor("#C586C0"));
{ HighlightRule r; r.pattern = QRegularExpression("^\\s*#\\s*\\w+"); r.format = preprocFormat; m_rules.append(r); }
QTextCharFormat stringFormat;
stringFormat.setForeground(QColor("#CE9178"));
{ HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*")"); r.format = stringFormat; m_rules.append(r); }
{ HighlightRule r; r.pattern = QRegularExpression(R"('(?:[^'\\]|\\.)*')"); r.format = stringFormat; m_rules.append(r); }
QTextCharFormat numberFormat;
numberFormat.setForeground(QColor("#B5CEA8"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(\b(0[xX][0-9A-Fa-f]+[uUlL]*|[0-9]+\.?[0-9]*([eE][+-]?[0-9]+)?[fFlLuU]*)\b)"); r.format = numberFormat; m_rules.append(r); }
QTextCharFormat commentFormat;
commentFormat.setForeground(QColor("#6A9955"));
commentFormat.setFontItalic(true);
{ HighlightRule r; r.pattern = QRegularExpression("//[^\n]*"); r.format = commentFormat; m_rules.append(r); }
m_multiLineCommentFormat = commentFormat;
m_commentStartExpression = QRegularExpression(R"(/\*)");
m_commentEndExpression = QRegularExpression(R"(\*/)");
}
void CppHighlighter::highlightBlock(const QString &text)
{
SyntaxHighlighter::highlightBlock(text);
}
// ===========================================================================
// CssHighlighter
// ===========================================================================
CssHighlighter::CssHighlighter(QTextDocument *parent)
: SyntaxHighlighter(parent)
{
m_hasMultiLineComment = true;
// Selektoren: .klasse #id element ::pseudo :pseudo
QTextCharFormat selectorFormat;
selectorFormat.setForeground(QColor("#D7BA7D"));
{ HighlightRule r; r.pattern = QRegularExpression(R"([.#]?[\w-]+\s*(?=\s*[,{]))"); r.format = selectorFormat; m_rules.append(r); }
{ HighlightRule r; r.pattern = QRegularExpression(R"(:{1,2}[\w-]+)"); r.format = selectorFormat; m_rules.append(r); }
// Eigenschaften (property:)
QTextCharFormat propFormat;
propFormat.setForeground(QColor("#9CDCFE"));
{ HighlightRule r; r.pattern = QRegularExpression(R"([\w-]+\s*(?=:))"); r.format = propFormat; m_rules.append(r); }
// Werte Farben #hex
QTextCharFormat colorFormat;
colorFormat.setForeground(QColor("#CE9178"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(#[0-9A-Fa-f]{3,8}\b)"); r.format = colorFormat; m_rules.append(r); }
// Zahlen + Einheiten
QTextCharFormat numberFormat;
numberFormat.setForeground(QColor("#B5CEA8"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(\b\d+\.?\d*(px|em|rem|%|vh|vw|pt|cm|mm|s|ms)?\b)"); r.format = numberFormat; m_rules.append(r); }
// Strings
QTextCharFormat stringFormat;
stringFormat.setForeground(QColor("#CE9178"));
{ HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = stringFormat; m_rules.append(r); }
// !important
QTextCharFormat importantFormat;
importantFormat.setForeground(QColor("#F44747"));
importantFormat.setFontWeight(QFont::Bold);
{ HighlightRule r; r.pattern = QRegularExpression(R"(!important)"); r.format = importantFormat; m_rules.append(r); }
// @-Regeln
QTextCharFormat atFormat;
atFormat.setForeground(QColor("#C586C0"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(@[\w-]+)"); r.format = atFormat; m_rules.append(r); }
QTextCharFormat commentFormat;
commentFormat.setForeground(QColor("#6A9955"));
commentFormat.setFontItalic(true);
m_multiLineCommentFormat = commentFormat;
m_commentStartExpression = QRegularExpression(R"(/\*)");
m_commentEndExpression = QRegularExpression(R"(\*/)");
}
void CssHighlighter::highlightBlock(const QString &text)
{
SyntaxHighlighter::highlightBlock(text);
}
// ===========================================================================
// HtmlHighlighter
// ===========================================================================
HtmlHighlighter::HtmlHighlighter(QTextDocument *parent)
: SyntaxHighlighter(parent)
{
// Tag-Namen <div </div />
QTextCharFormat tagFormat;
tagFormat.setForeground(QColor("#569CD6"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(</?[\w:-]+)"); r.format = tagFormat; m_rules.append(r); }
{ HighlightRule r; r.pattern = QRegularExpression(R"(/?>)"); r.format = tagFormat; m_rules.append(r); }
// Attribute name=
QTextCharFormat attrFormat;
attrFormat.setForeground(QColor("#9CDCFE"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(\b[\w:-]+=)"); r.format = attrFormat; m_rules.append(r); }
// Attributwerte "wert" 'wert'
QTextCharFormat valueFormat;
valueFormat.setForeground(QColor("#CE9178"));
{ HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = valueFormat; m_rules.append(r); }
// DOCTYPE
QTextCharFormat doctypeFormat;
doctypeFormat.setForeground(QColor("#808080"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(<!DOCTYPE[^>]*>)", QRegularExpression::CaseInsensitiveOption); r.format = doctypeFormat; m_rules.append(r); }
// Entities &amp; &#123;
QTextCharFormat entityFormat;
entityFormat.setForeground(QColor("#D7BA7D"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(&(?:#\d+|#x[0-9A-Fa-f]+|[\w]+);)"); r.format = entityFormat; m_rules.append(r); }
// Kommentare <!-- ... --> (mehrzeilig)
QTextCharFormat commentFormat;
commentFormat.setForeground(QColor("#6A9955"));
commentFormat.setFontItalic(true);
m_multiLineCommentFormat = commentFormat;
m_commentStartExpression = QRegularExpression("<!--");
m_commentEndExpression = QRegularExpression("-->");
m_hasMultiLineComment = true;
}
void HtmlHighlighter::highlightBlock(const QString &text)
{
SyntaxHighlighter::highlightBlock(text);
}
// ===========================================================================
// PhpHighlighter
// ===========================================================================
PhpHighlighter::PhpHighlighter(QTextDocument *parent)
: SyntaxHighlighter(parent)
{
// ---- HTML-Regeln (Basis, für den Teil außerhalb von <?php ?>) ----
QTextCharFormat tagFormat;
tagFormat.setForeground(QColor("#569CD6"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(</?[\w:-]+)"); r.format = tagFormat; m_rules.append(r); }
{ HighlightRule r; r.pattern = QRegularExpression(R"(/?>)"); r.format = tagFormat; m_rules.append(r); }
QTextCharFormat attrFormat;
attrFormat.setForeground(QColor("#9CDCFE"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(\b[\w:-]+=)"); r.format = attrFormat; m_rules.append(r); }
QTextCharFormat valueFormat;
valueFormat.setForeground(QColor("#CE9178"));
{ HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = valueFormat; m_rules.append(r); }
QTextCharFormat entityFormat;
entityFormat.setForeground(QColor("#D7BA7D"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(&(?:#\d+|#x[0-9A-Fa-f]+|[\w]+);)"); r.format = entityFormat; m_rules.append(r); }
// HTML-Kommentare
QTextCharFormat htmlCommentFormat;
htmlCommentFormat.setForeground(QColor("#6A9955"));
htmlCommentFormat.setFontItalic(true);
m_multiLineCommentFormat = htmlCommentFormat;
m_commentStartExpression = QRegularExpression("<!--");
m_commentEndExpression = QRegularExpression("-->");
m_hasMultiLineComment = true;
// ---- PHP-Tags hervorheben ----
m_phpTagFormat.setForeground(QColor("#C586C0"));
m_phpTagFormat.setFontWeight(QFont::Bold);
// ---- PHP-spezifische Regeln ----
m_phpStringFormat.setForeground(QColor("#CE9178"));
m_phpCommentFormat.setForeground(QColor("#6A9955"));
m_phpCommentFormat.setFontItalic(true);
// Keywords
QTextCharFormat kwFormat;
kwFormat.setForeground(QColor("#569CD6"));
kwFormat.setFontWeight(QFont::Bold);
const QStringList phpKeywords = {
"abstract","and","array","as","break","callable","case","catch",
"class","clone","const","continue","declare","default","die","do",
"echo","else","elseif","empty","enddeclare","endfor","endforeach",
"endif","endswitch","endwhile","enum","extends","final","finally",
"fn","for","foreach","function","global","goto","if","implements",
"include","include_once","instanceof","insteadof","interface",
"isset","list","match","namespace","new","or","print","private",
"protected","public","readonly","require","require_once","return",
"static","switch","throw","trait","try","unset","use","var",
"while","xor","yield","null","true","false","NULL","TRUE","FALSE"
};
for (const QString &kw : phpKeywords)
{
HighlightRule r;
r.pattern = QRegularExpression(QString("\\b%1\\b").arg(kw));
r.format = kwFormat;
m_phpRules.append(r);
}
// Variablen $var
QTextCharFormat varFormat;
varFormat.setForeground(QColor("#9CDCFE"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(\$[\w]+)"); r.format = varFormat; m_phpRules.append(r); }
// Strings
{ HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = m_phpStringFormat; m_phpRules.append(r); }
// Zahlen
QTextCharFormat numFormat;
numFormat.setForeground(QColor("#B5CEA8"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(\b\d+\.?\d*\b)"); r.format = numFormat; m_phpRules.append(r); }
// Einzeilige Kommentare
{ HighlightRule r; r.pattern = QRegularExpression(R"((//|#)[^\n]*)"); r.format = m_phpCommentFormat; m_phpRules.append(r); }
// Eingebaute Funktionen (Auswahl der häufigsten)
QTextCharFormat builtinFormat;
builtinFormat.setForeground(QColor("#DCDCAA"));
const QStringList builtins = {
"array_map","array_filter","array_keys","array_values","array_merge",
"array_push","array_pop","array_shift","array_slice","array_splice",
"count","strlen","substr","strpos","strtolower","strtoupper","trim",
"ltrim","rtrim","explode","implode","str_replace","preg_match",
"preg_replace","sprintf","printf","print_r","var_dump","isset",
"empty","unset","intval","floatval","strval","is_array","is_string",
"is_int","is_float","is_null","is_bool","is_numeric","date","time",
"mktime","json_encode","json_decode","header","session_start",
"htmlspecialchars","htmlentities","strip_tags","nl2br","round",
"floor","ceil","abs","min","max","rand","in_array","array_key_exists",
"sort","rsort","usort","ksort","krsort","ob_start","ob_get_clean"
};
for (const QString &fn : builtins)
{
HighlightRule r;
r.pattern = QRegularExpression(QString("\\b%1\\b").arg(fn));
r.format = builtinFormat;
m_phpRules.append(r);
}
}
void PhpHighlighter::highlightPhpRange(const QString &text, int start, int length)
{
if (length <= 0)
{
return;
}
const QString phpText = text.mid(start, length);
for (const HighlightRule &rule : m_phpRules)
{
QRegularExpressionMatchIterator it = rule.pattern.globalMatch(phpText);
while (it.hasNext())
{
QRegularExpressionMatch match = it.next();
setFormat(
start + static_cast<int>(match.capturedStart()),
static_cast<int>(match.capturedLength()),
rule.format
);
}
}
}
void PhpHighlighter::highlightBlock(const QString &text)
{
// Zuerst HTML-Basis-Regeln auf den gesamten Text anwenden
SyntaxHighlighter::highlightBlock(text);
// Dann PHP-Blöcke <?php ... ?> und <?= ... ?> finden und überschreiben
// Block-Zustände: 0 = HTML, 2 = innerhalb PHP-Block
setCurrentBlockState(0);
static const QRegularExpression phpOpen(R"(<\?(?:php|=)?\s?)",
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression phpClose(R"(\?>)");
int pos = 0;
if (previousBlockState() == 2)
{
// Wir befinden uns bereits in einem PHP-Block
QRegularExpressionMatch closeMatch = phpClose.match(text, 0);
if (closeMatch.hasMatch())
{
const int end = static_cast<int>(closeMatch.capturedStart())
+ static_cast<int>(closeMatch.capturedLength());
highlightPhpRange(text, 0, end);
setFormat(static_cast<int>(closeMatch.capturedStart()),
static_cast<int>(closeMatch.capturedLength()),
m_phpTagFormat);
pos = end;
setCurrentBlockState(0);
}
else
{
highlightPhpRange(text, 0, text.length());
setCurrentBlockState(2);
return;
}
}
while (pos < text.length())
{
QRegularExpressionMatch openMatch = phpOpen.match(text, pos);
if (!openMatch.hasMatch())
{
break;
}
const int openStart = static_cast<int>(openMatch.capturedStart());
const int openEnd = openStart + static_cast<int>(openMatch.capturedLength());
// <?php-Tag selbst einfärben
setFormat(openStart, static_cast<int>(openMatch.capturedLength()), m_phpTagFormat);
QRegularExpressionMatch closeMatch = phpClose.match(text, openEnd);
if (closeMatch.hasMatch())
{
const int closeStart = static_cast<int>(closeMatch.capturedStart());
const int closeEnd = closeStart + static_cast<int>(closeMatch.capturedLength());
highlightPhpRange(text, openEnd, closeStart - openEnd);
setFormat(closeStart, static_cast<int>(closeMatch.capturedLength()), m_phpTagFormat);
pos = closeEnd;
setCurrentBlockState(0);
}
else
{
// PHP-Block geht über Zeilenende hinaus
highlightPhpRange(text, openEnd, text.length() - openEnd);
setCurrentBlockState(2);
return;
}
}
}

View File

@@ -0,0 +1,94 @@
#pragma once
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
#include <QRegularExpression>
#include <QVector>
// ---------------------------------------------------------------------------
// SyntaxHighlighter Regelbasierte Basis-Klasse.
// Unterklassen befüllen m_rules und können highlightBlock() überschreiben
// um mehrzeilige Konstrukte (Block-Kommentare, heredocs, …) zu behandeln.
// ---------------------------------------------------------------------------
class SyntaxHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
explicit SyntaxHighlighter(QTextDocument *parent = nullptr);
protected:
struct HighlightRule
{
QRegularExpression pattern;
QTextCharFormat format;
};
void highlightBlock(const QString &text) override;
// Unterklassen befüllen dies im Konstruktor
QVector<HighlightRule> m_rules;
// Mehrzeilige Block-Kommentare (/* ... */)
QRegularExpression m_commentStartExpression;
QRegularExpression m_commentEndExpression;
QTextCharFormat m_multiLineCommentFormat;
bool m_hasMultiLineComment = false;
};
// ---------------------------------------------------------------------------
// CppHighlighter C und C++
// ---------------------------------------------------------------------------
class CppHighlighter : public SyntaxHighlighter
{
Q_OBJECT
public:
explicit CppHighlighter(QTextDocument *parent = nullptr);
protected:
void highlightBlock(const QString &text) override;
};
// ---------------------------------------------------------------------------
// CssHighlighter CSS
// ---------------------------------------------------------------------------
class CssHighlighter : public SyntaxHighlighter
{
Q_OBJECT
public:
explicit CssHighlighter(QTextDocument *parent = nullptr);
protected:
void highlightBlock(const QString &text) override;
};
// ---------------------------------------------------------------------------
// HtmlHighlighter HTML (mit eingebettetem CSS in <style> und JS in <script>)
// ---------------------------------------------------------------------------
class HtmlHighlighter : public SyntaxHighlighter
{
Q_OBJECT
public:
explicit HtmlHighlighter(QTextDocument *parent = nullptr);
protected:
void highlightBlock(const QString &text) override;
};
// ---------------------------------------------------------------------------
// PhpHighlighter PHP (HTML + eingebettetes PHP zwischen <?php ... ?>)
// ---------------------------------------------------------------------------
class PhpHighlighter : public SyntaxHighlighter
{
Q_OBJECT
public:
explicit PhpHighlighter(QTextDocument *parent = nullptr);
protected:
void highlightBlock(const QString &text) override;
private:
// Hilfsmethode: wendet PHP-Regeln auf einen Teilbereich an
void highlightPhpRange(const QString &text, int start, int length);
QVector<HighlightRule> m_phpRules;
QTextCharFormat m_phpStringFormat;
QTextCharFormat m_phpCommentFormat;
QTextCharFormat m_phpTagFormat;
};