README.md aktualisiert
264
README.md
@@ -1,130 +1,76 @@
|
|||||||
# BareCode
|
# BareCode
|
||||||
|
|
||||||
**BareCode – Schlanker Code-Editor für Web-Entwickler**
|
**Schlanker, modularer Code-Editor für Web-Entwickler**
|
||||||
Version 1.1.0
|
Version 1.1.0 — von **Dany Thinnes** | [Projekt Hirnfrei](https://www.projekt-hirnfrei.de) | [Discord](https://discord.projekt-hirnfrei.de)
|
||||||
|
|
||||||
Kompiliert sauber auf **Linux**, **Windows** und **Haiku**.
|
Entwickelt mit **C++17**, **Qt6** und **CMake**.
|
||||||
|
Kompiliert auf **Linux**, **Windows** und **Haiku**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Arch Linux / Manjaro (AUR)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mit yay
|
||||||
|
yay -S barecode
|
||||||
|
|
||||||
|
# Mit paru
|
||||||
|
paru -S barecode
|
||||||
|
|
||||||
|
# Immer aktuellster Git-Stand
|
||||||
|
yay -S barecode-git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aus dem Quellcode bauen
|
||||||
|
|
||||||
|
Siehe Abschnitt **Bauen** weiter unten.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
### Editor
|
||||||
| Feature | Details |
|
| Feature | Details |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Projektbaum | Linkes Panel — zeigt nur das gewählte Projektverzeichnis |
|
| Syntax-Highlighting | HTML, PHP, CSS, C/C++ — inkl. mehrzeiliger Kommentare |
|
||||||
| Tabs | Mehrere Dateien gleichzeitig, verschiebbar und schließbar |
|
| Tabs | Mehrere Dateien gleichzeitig, verschiebbar und schließbar |
|
||||||
| Änderungsindikator | ● im Tab-Titel bei ungespeicherten Änderungen |
|
| Änderungsindikator | ● im Tab-Titel bei ungespeicherten Änderungen |
|
||||||
| Syntax-Highlighting | HTML, PHP, CSS, C/C++ inkl. mehrzeiliger Kommentare |
|
|
||||||
| Zeilennummern | Eigener Gutter, aktuelle Zeile hervorgehoben |
|
| Zeilennummern | Eigener Gutter, aktuelle Zeile hervorgehoben |
|
||||||
| Einrück-Führungslinien | Vertikale Linien wie in VS Code |
|
| Einrück-Führungslinien | Vertikale Linien wie in VS Code |
|
||||||
| Auto-Indent | Einrückungstiefe wird bei Enter übernommen |
|
| Auto-Indent | Einrückungstiefe wird bei Enter übernommen |
|
||||||
| Tab / Shift+Tab | Ein- und Ausrücken, auch für mehrere Zeilen gleichzeitig |
|
| Tab / Shift+Tab | Ein- und Ausrücken, auch für mehrere Zeilen gleichzeitig |
|
||||||
| Smart Backspace | Springt zur vorherigen Tab-Stop-Position |
|
| Smart Backspace | Springt zur vorherigen Tab-Stop-Position |
|
||||||
|
| Farbvorschau | CSS-Farbwerte (#hex, rgb, hsl) werden inline angezeigt |
|
||||||
|
| Farbauswahl | Klick auf das Farbquadrat öffnet einen Farbauswahl-Dialog |
|
||||||
|
| Funktionssignatur | Tooltip mit Parametern beim Tippen von PHP-Funktionen |
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
| Feature | Details |
|
||||||
|
|---|---|
|
||||||
|
| Projektbaum | Linkes Panel — zeigt nur das gewählte Projektverzeichnis |
|
||||||
|
| Neue Datei / Ordner | Rechtsklick im Dateibaum |
|
||||||
|
| Doppelklick-Navigation | Doppelklick auf einen Funktionsnamen springt zur Definition |
|
||||||
|
| Suchen & Ersetzen | Einzeln, Alle, In Auswahl, Regex, Live-Hervorhebung (Strg+F) |
|
||||||
|
| In Dateien suchen | Rekursive Projektsuche mit Ergebnisliste (Strg+Shift+F) |
|
||||||
|
|
||||||
|
### Projektanalyse
|
||||||
|
| Feature | Details |
|
||||||
|
|---|---|
|
||||||
|
| Projektfunktionen | Alle definierten Funktionen als durchsuchbare Liste (Strg+Shift+P) |
|
||||||
|
| Tote Funktionen | Analyse ungenutzter Funktionen mit Exclude-Verzeichnissen (Strg+Shift+T) |
|
||||||
|
| Export | Analyseergebnisse als TXT exportieren |
|
||||||
|
|
||||||
|
### Allgemein
|
||||||
|
| Feature | Details |
|
||||||
|
|---|---|
|
||||||
| Speichern | Speichern, Speichern unter, Alles speichern |
|
| Speichern | Speichern, Speichern unter, Alles speichern |
|
||||||
| Suchen & Ersetzen | Einzeln, Alle, In Auswahl, Regex, Live-Hervorhebung |
|
| Dark / Light Mode | Umschaltbar (Strg+Shift+D), wird gespeichert |
|
||||||
| In Dateien suchen | Rekursive Projektsuche mit Ergebnisliste, direkt zur Zeile |
|
|
||||||
| Dark / Light Mode | Umschaltbar, wird gespeichert |
|
|
||||||
| Session | Geöffnete Dateien und aktiver Tab werden wiederhergestellt |
|
| Session | Geöffnete Dateien und aktiver Tab werden wiederhergestellt |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Projektstruktur
|
|
||||||
|
|
||||||
```
|
|
||||||
BareCode/
|
|
||||||
├── CMakeLists.txt
|
|
||||||
├── main.cpp
|
|
||||||
├── resources/
|
|
||||||
│ ├── resources.qrc
|
|
||||||
│ └── icon_*.png
|
|
||||||
└── src/
|
|
||||||
├── core/ # MainWindow, ProjectManager, Settings, ThemeManager, IPlugin
|
|
||||||
├── editor/ # EditorPanel, EditorTab, CodeEditor, SearchPanel, FileSearchPanel
|
|
||||||
├── filetree/ # FileTreePanel
|
|
||||||
└── highlighter/ # SyntaxHighlighter, HighlighterFactory
|
|
||||||
```
|
|
||||||
|
|
||||||
Jedes Unterverzeichnis kompiliert als eigene statische Bibliothek.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Abhängigkeiten & Bauen
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
**Abhängigkeiten (Beispiel Ubuntu/Debian):**
|
|
||||||
```bash
|
|
||||||
sudo apt install cmake qt6-base-dev qt6-base-dev-tools libqt6concurrent6
|
|
||||||
```
|
|
||||||
|
|
||||||
**Abhängigkeiten (Arch/Manjaro):**
|
|
||||||
```bash
|
|
||||||
sudo pacman -S cmake qt6-base
|
|
||||||
```
|
|
||||||
|
|
||||||
**Bauen:**
|
|
||||||
```bash
|
|
||||||
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build build -j$(nproc)
|
|
||||||
./build/BareCode
|
|
||||||
```
|
|
||||||
|
|
||||||
**Installieren:**
|
|
||||||
```bash
|
|
||||||
sudo cmake --install build
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Windows (Visual Studio)
|
|
||||||
|
|
||||||
**Abhängigkeiten:**
|
|
||||||
- [Qt6](https://www.qt.io/download) (Komponenten: Qt6 Base, Qt6 Concurrent)
|
|
||||||
- CMake ≥ 3.16
|
|
||||||
- Visual Studio 2019 oder neuer
|
|
||||||
|
|
||||||
```bat
|
|
||||||
cmake -B build -G "Visual Studio 17 2022" -A x64
|
|
||||||
cmake --build build --config Release
|
|
||||||
build\Release\BareCode.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows Installer erstellen** (mit [Inno Setup](https://jrsoftware.org/isinfo.php)):
|
|
||||||
```bat
|
|
||||||
windeployqt --release build\Release\BareCode.exe
|
|
||||||
```
|
|
||||||
Danach das Inno Setup Skript ausführen.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Haiku
|
|
||||||
|
|
||||||
**Abhängigkeiten:**
|
|
||||||
```bash
|
|
||||||
pkgman install qt6_base qt6_base_devel
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Hinweis:** `qt6_base_devel` enthält auf Haiku sowohl die Header als auch
|
|
||||||
> die Build-Tools (`moc`, `rcc`, `qmake`). Getestet auf Haiku R1/beta5.
|
|
||||||
|
|
||||||
**Bauen:**
|
|
||||||
```bash
|
|
||||||
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build build -j$(nproc)
|
|
||||||
./build/BareCode
|
|
||||||
```
|
|
||||||
|
|
||||||
**Installieren:**
|
|
||||||
```bash
|
|
||||||
cmake --install build
|
|
||||||
```
|
|
||||||
|
|
||||||
> Nach der Installation wird `mimeset` automatisch aufgerufen um den
|
|
||||||
> MIME-Typ zu registrieren, damit BareCode im Haiku Tracker korrekt
|
|
||||||
> als Anwendung erkannt wird.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tastaturkürzel
|
## Tastaturkürzel
|
||||||
|
|
||||||
| Aktion | Kürzel |
|
| Aktion | Kürzel |
|
||||||
@@ -137,13 +83,105 @@ cmake --install build
|
|||||||
| Alles speichern | Strg+Shift+S |
|
| Alles speichern | Strg+Shift+S |
|
||||||
| Suchen / Ersetzen | Strg+F |
|
| Suchen / Ersetzen | Strg+F |
|
||||||
| In Dateien suchen | Strg+Shift+F |
|
| In Dateien suchen | Strg+Shift+F |
|
||||||
|
| Projektfunktionen | Strg+Shift+P |
|
||||||
|
| Tote Funktionen | Strg+Shift+T |
|
||||||
| Dark Mode | Strg+Shift+D |
|
| Dark Mode | Strg+Shift+D |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Erweitern
|
## Projektstruktur
|
||||||
|
|
||||||
### Neuen Highlighter hinzufügen
|
```
|
||||||
|
BareCode/
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── main.cpp
|
||||||
|
├── LICENSE
|
||||||
|
├── PKGBUILD # Stabiles AUR-Paket
|
||||||
|
├── PKGBUILD-git # AUR-Paket (Git-Stand)
|
||||||
|
├── BareCode.desktop # Linux-Anwendungsmenü
|
||||||
|
├── resources/
|
||||||
|
│ ├── resources.qrc
|
||||||
|
│ ├── icon_*.png
|
||||||
|
│ └── php_functions.json
|
||||||
|
└── src/
|
||||||
|
├── core/ # MainWindow, ProjectManager, Settings,
|
||||||
|
│ # ThemeManager, AboutDialog, IPlugin
|
||||||
|
├── editor/ # EditorPanel, EditorTab, CodeEditor,
|
||||||
|
│ # SearchPanel, FileSearchPanel,
|
||||||
|
│ # ColorIndicator, SignatureHelper,
|
||||||
|
│ # FunctionScanner, FunctionIndex,
|
||||||
|
│ # FunctionListPanel, FunctionListDialog,
|
||||||
|
│ # DeadCodeAnalyzer, DeadCodeDialog
|
||||||
|
├── filetree/ # FileTreePanel
|
||||||
|
└── highlighter/ # SyntaxHighlighter, HighlighterFactory
|
||||||
|
```
|
||||||
|
|
||||||
|
Jedes Unterverzeichnis kompiliert als eigene statische Bibliothek — Komponenten lassen sich unabhängig austauschen und erweitern.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Abhängigkeiten & Bauen
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
**Ubuntu / Debian:**
|
||||||
|
```bash
|
||||||
|
sudo apt install cmake ninja-build qt6-base-dev qt6-base-dev-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arch / Manjaro:**
|
||||||
|
```bash
|
||||||
|
sudo pacman -S cmake ninja qt6-base
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bauen:**
|
||||||
|
```bash
|
||||||
|
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build
|
||||||
|
./build/BareCode
|
||||||
|
```
|
||||||
|
|
||||||
|
**Installieren:**
|
||||||
|
```bash
|
||||||
|
sudo cmake --install build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
**Abhängigkeiten:**
|
||||||
|
- [Qt6](https://www.qt.io/download) — Komponenten: Qt6 Base, Qt6 Concurrent
|
||||||
|
- CMake ≥ 3.16
|
||||||
|
- Visual Studio 2019 oder neuer
|
||||||
|
|
||||||
|
```bat
|
||||||
|
cmake -B build -G "Visual Studio 17 2022" -A x64
|
||||||
|
cmake --build build --config Release
|
||||||
|
```
|
||||||
|
|
||||||
|
**Installer erstellen** (mit [Inno Setup](https://jrsoftware.org/isinfo.php)):
|
||||||
|
```bat
|
||||||
|
windeployqt --release build\Release\BareCode.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Haiku
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pkgman install qt6_base qt6_base_devel
|
||||||
|
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build -j$(nproc)
|
||||||
|
./build/BareCode
|
||||||
|
```
|
||||||
|
|
||||||
|
> Getestet auf Haiku R1/beta5. `qt6_base_devel` enthält auf Haiku
|
||||||
|
> sowohl die Header als auch die Build-Tools.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Neue Sprache hinzufügen
|
||||||
|
|
||||||
1. Subklasse von `SyntaxHighlighter` in `src/highlighter/` erstellen
|
1. Subklasse von `SyntaxHighlighter` in `src/highlighter/` erstellen
|
||||||
2. `m_rules` im Konstruktor befüllen (siehe `CppHighlighter` als Vorlage)
|
2. `m_rules` im Konstruktor befüllen (siehe `CppHighlighter` als Vorlage)
|
||||||
@@ -151,17 +189,11 @@ cmake --install build
|
|||||||
|
|
||||||
Keine weiteren Dateien müssen geändert werden.
|
Keine weiteren Dateien müssen geändert werden.
|
||||||
|
|
||||||
### Neues Panel / Plugin hinzufügen
|
|
||||||
|
|
||||||
1. `IPlugin`-Interface aus `src/core/IPlugin.h` implementieren
|
|
||||||
2. `QWidget`-Subklasse für die UI erstellen
|
|
||||||
3. In `MainWindow` instanziieren und verdrahten
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Code-Stil
|
## Lizenz
|
||||||
|
|
||||||
Allman-Klammerstil, C++17 durchgehend.
|
MIT License — Copyright (c) 2025 Dany Thinnes / Projekt Hirnfrei
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -169,3 +201,5 @@ Allman-Klammerstil, C++17 durchgehend.
|
|||||||
|
|
||||||
- Website: [www.projekt-hirnfrei.de](https://www.projekt-hirnfrei.de)
|
- Website: [www.projekt-hirnfrei.de](https://www.projekt-hirnfrei.de)
|
||||||
- Discord: [discord.projekt-hirnfrei.de](https://discord.projekt-hirnfrei.de)
|
- Discord: [discord.projekt-hirnfrei.de](https://discord.projekt-hirnfrei.de)
|
||||||
|
- Quellcode: [git.projekt-hirnfrei.de/diabolus/BareCode](https://git.projekt-hirnfrei.de/diabolus/BareCode)
|
||||||
|
- AUR: [aur.archlinux.org/packages/barecode](https://aur.archlinux.org/packages/barecode)
|
||||||
|
|||||||
BIN
barecode-1.1.0.tar.gz
Normal file
1
src/barecode-1.1.0.tar.gz
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/home/diabolus/Arbeit/Projekt-Hirnfrei/BareCode/barecode-1.1.0.tar.gz
|
||||||
2
src/barecode/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{src/
|
||||||
|
build/
|
||||||
13
src/barecode/BareCode.desktop
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name=BareCode
|
||||||
|
GenericName=Code Editor
|
||||||
|
Comment=Modularer Code-Editor für HTML, PHP, CSS und mehr
|
||||||
|
Exec=BareCode %F
|
||||||
|
Icon=barecode
|
||||||
|
Terminal=false
|
||||||
|
Categories=Development;TextEditor;
|
||||||
|
MimeType=text/plain;text/html;text/css;text/x-php;text/x-csrc;text/x-chdr;text/x-c++src;text/x-c++hdr;
|
||||||
|
Keywords=editor;code;html;php;css;c++;
|
||||||
|
StartupWMClass=BareCode
|
||||||
1
src/barecode/BareCode.rc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
IDI_ICON1 ICON "resources/BareCode.ico"
|
||||||
47
src/barecode/BareCodeAUR/PKGBUILD
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Maintainer: Dany Thinnes <deine@email.de>
|
||||||
|
|
||||||
|
pkgname=barecode-git
|
||||||
|
pkgver=r5.cb66172
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="Modularer Code-Editor, entwickelt von Projekt Hirnfrei (git)"
|
||||||
|
arch=('x86_64' 'aarch64')
|
||||||
|
url="https://git.projekt-hirnfrei.de/diabolus/BareCode"
|
||||||
|
license=('MIT')
|
||||||
|
depends=('qt6-base')
|
||||||
|
makedepends=('cmake' 'ninja' 'git')
|
||||||
|
provides=('barecode')
|
||||||
|
conflicts=('barecode')
|
||||||
|
|
||||||
|
source=("$pkgname::git+$url.git")
|
||||||
|
sha256sums=('SKIP')
|
||||||
|
|
||||||
|
pkgver()
|
||||||
|
{
|
||||||
|
cd "$pkgname"
|
||||||
|
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||||
|
}
|
||||||
|
|
||||||
|
build()
|
||||||
|
{
|
||||||
|
cmake \
|
||||||
|
-B build \
|
||||||
|
-S "$pkgname" \
|
||||||
|
-G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr
|
||||||
|
cmake --build build
|
||||||
|
}
|
||||||
|
|
||||||
|
package()
|
||||||
|
{
|
||||||
|
DESTDIR="$pkgdir" cmake --install build
|
||||||
|
|
||||||
|
install -Dm644 "$pkgname/barecode.desktop" \
|
||||||
|
"$pkgdir/usr/share/applications/barecode.desktop"
|
||||||
|
|
||||||
|
# install -Dm644 "$pkgname/resources/barecode.png" \
|
||||||
|
# "$pkgdir/usr/share/pixmaps/barecode.png"
|
||||||
|
|
||||||
|
install -Dm644 "$pkgname/LICENSE" \
|
||||||
|
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||||
|
}
|
||||||
47
src/barecode/BareCodeAUR/PKGBUILD-git
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Maintainer: Dany Thinnes <deine@email.de>
|
||||||
|
|
||||||
|
pkgname=barecode-git
|
||||||
|
pkgver=r1.0.0
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="Modularer Code-Editor, entwickelt von Projekt Hirnfrei (git)"
|
||||||
|
arch=('x86_64' 'aarch64')
|
||||||
|
url="https://git.projekt-hirnfrei.de/diabolus/BareCode"
|
||||||
|
license=('MIT')
|
||||||
|
depends=('qt6-base')
|
||||||
|
makedepends=('cmake' 'ninja' 'git')
|
||||||
|
provides=('barecode')
|
||||||
|
conflicts=('barecode')
|
||||||
|
|
||||||
|
source=("$pkgname::git+$url.git")
|
||||||
|
sha256sums=('SKIP')
|
||||||
|
|
||||||
|
pkgver()
|
||||||
|
{
|
||||||
|
cd "$pkgname"
|
||||||
|
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||||
|
}
|
||||||
|
|
||||||
|
build()
|
||||||
|
{
|
||||||
|
cmake \
|
||||||
|
-B build \
|
||||||
|
-S "$pkgname" \
|
||||||
|
-G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr
|
||||||
|
cmake --build build
|
||||||
|
}
|
||||||
|
|
||||||
|
package()
|
||||||
|
{
|
||||||
|
DESTDIR="$pkgdir" cmake --install build
|
||||||
|
|
||||||
|
install -Dm644 "$pkgname/barecode.desktop" \
|
||||||
|
"$pkgdir/usr/share/applications/barecode.desktop"
|
||||||
|
|
||||||
|
# install -Dm644 "$pkgname/resources/barecode.png" \
|
||||||
|
# "$pkgdir/usr/share/pixmaps/barecode.png"
|
||||||
|
|
||||||
|
install -Dm644 "$pkgname/LICENSE" \
|
||||||
|
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||||
|
}
|
||||||
41
src/barecode/BareCodeAUR/PKGBUILD-stable
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Maintainer: Dany Thinnes <deine@email.de>
|
||||||
|
|
||||||
|
pkgname=barecode
|
||||||
|
pkgver=1.0.0
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="Modularer Code-Editor, entwickelt von Projekt Hirnfrei"
|
||||||
|
arch=('x86_64' 'aarch64')
|
||||||
|
url="https://git.projekt-hirnfrei.de/diabolus/BareCode"
|
||||||
|
license=('MIT')
|
||||||
|
depends=('qt6-base')
|
||||||
|
makedepends=('cmake' 'ninja' 'git')
|
||||||
|
|
||||||
|
# Gitea erzeugt automatisch Tarballs unter:
|
||||||
|
# https://DEIN-GITEA-SERVER/DEINNAME/BareCode/archive/v1.0.0.tar.gz
|
||||||
|
#
|
||||||
|
# sha256sum nach dem Taggen ermitteln:
|
||||||
|
# curl -L https://DEIN-GITEA-SERVER/DEINNAME/BareCode/archive/v1.0.0.tar.gz | sha256sum
|
||||||
|
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
|
||||||
|
sha256sums=('SKIP') # Ersetzen nach erstem Tag
|
||||||
|
|
||||||
|
build()
|
||||||
|
{
|
||||||
|
cmake \
|
||||||
|
-B build \
|
||||||
|
-S "BareCode-$pkgver" \
|
||||||
|
-G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr
|
||||||
|
cmake --build build
|
||||||
|
}
|
||||||
|
|
||||||
|
package()
|
||||||
|
{
|
||||||
|
DESTDIR="$pkgdir" cmake --install build
|
||||||
|
|
||||||
|
install -Dm644 "BareCode-$pkgver/barecode.desktop" \
|
||||||
|
"$pkgdir/usr/share/applications/barecode.desktop"
|
||||||
|
|
||||||
|
install -Dm644 "BareCode-$pkgver/LICENSE" \
|
||||||
|
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||||
|
}
|
||||||
1
src/barecode/BareCodeAUR/barecode-git/HEAD
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ref: refs/heads/main
|
||||||
9
src/barecode/BareCodeAUR/barecode-git/config
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[core]
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = true
|
||||||
|
bare = true
|
||||||
|
[remote "origin"]
|
||||||
|
url = https://git.projekt-hirnfrei.de/diabolus/BareCode.git
|
||||||
|
tagOpt = --no-tags
|
||||||
|
fetch = +refs/*:refs/*
|
||||||
|
mirror = true
|
||||||
1
src/barecode/BareCodeAUR/barecode-git/description
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Unnamed repository; edit this file 'description' to name the repository.
|
||||||
15
src/barecode/BareCodeAUR/barecode-git/hooks/applypatch-msg.sample
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message taken by
|
||||||
|
# applypatch from an e-mail message.
|
||||||
|
#
|
||||||
|
# The hook should exit with non-zero status after issuing an
|
||||||
|
# appropriate message if it wants to stop the commit. The hook is
|
||||||
|
# allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "applypatch-msg".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||||
|
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||||
|
:
|
||||||
74
src/barecode/BareCodeAUR/barecode-git/hooks/commit-msg.sample
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message.
|
||||||
|
# Called by "git commit" with one argument, the name of the file
|
||||||
|
# that has the commit message. The hook should exit with non-zero
|
||||||
|
# status after issuing an appropriate message if it wants to stop the
|
||||||
|
# commit. The hook is allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "commit-msg".
|
||||||
|
|
||||||
|
# Uncomment the below to add a Signed-off-by line to the message.
|
||||||
|
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||||
|
# hook is more suited to it.
|
||||||
|
#
|
||||||
|
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||||
|
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||||
|
|
||||||
|
# This example catches duplicate Signed-off-by lines and messages that
|
||||||
|
# would confuse 'git am'.
|
||||||
|
|
||||||
|
ret=0
|
||||||
|
|
||||||
|
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||||
|
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||||
|
echo >&2 Duplicate Signed-off-by lines.
|
||||||
|
ret=1
|
||||||
|
}
|
||||||
|
|
||||||
|
comment_re="$(
|
||||||
|
{
|
||||||
|
git config --get-regexp "^core\.comment(char|string)\$" ||
|
||||||
|
echo '#'
|
||||||
|
} | sed -n -e '
|
||||||
|
${
|
||||||
|
s/^[^ ]* //
|
||||||
|
s|[][*./\]|\\&|g
|
||||||
|
s/^auto$/[#;@!$%^&|:]/
|
||||||
|
p
|
||||||
|
}'
|
||||||
|
)"
|
||||||
|
scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$"
|
||||||
|
comment_line="^${comment_re}.*"
|
||||||
|
blank_line='^[ ]*$'
|
||||||
|
# Disallow lines starting with "diff -" or "Index: " in the body of the
|
||||||
|
# message. Stop looking if we see a scissors line.
|
||||||
|
line="$(sed -n -e "
|
||||||
|
# Skip comments and blank lines at the start of the file.
|
||||||
|
/${scissors_line}/q
|
||||||
|
/${comment_line}/d
|
||||||
|
/${blank_line}/d
|
||||||
|
# The first paragraph will become the subject header so
|
||||||
|
# does not need to be checked.
|
||||||
|
: subject
|
||||||
|
n
|
||||||
|
/${scissors_line}/q
|
||||||
|
/${blank_line}/!b subject
|
||||||
|
# Check the body of the message for problematic
|
||||||
|
# prefixes.
|
||||||
|
: body
|
||||||
|
n
|
||||||
|
/${scissors_line}/q
|
||||||
|
/${comment_line}/b body
|
||||||
|
/^diff -/{p;q;}
|
||||||
|
/^Index: /{p;q;}
|
||||||
|
b body
|
||||||
|
" "$1")"
|
||||||
|
if test -n "$line"
|
||||||
|
then
|
||||||
|
echo >&2 "Message contains a diff that will confuse 'git am'."
|
||||||
|
echo >&2 "To fix this indent the diff."
|
||||||
|
ret=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $ret
|
||||||
168
src/barecode/BareCodeAUR/barecode-git/hooks/fsmonitor-watchman.sample
Executable file
@@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use IPC::Open2;
|
||||||
|
|
||||||
|
# An example hook script to integrate Watchman
|
||||||
|
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||||
|
# new and modified files.
|
||||||
|
#
|
||||||
|
# The hook is passed a version (currently 2) and last update token
|
||||||
|
# formatted as a string and outputs to stdout a new update token and
|
||||||
|
# all files that have been modified since the update token. Paths must
|
||||||
|
# be relative to the root of the working tree and separated by a single NUL.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "query-watchman" and set
|
||||||
|
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||||
|
#
|
||||||
|
my ($version, $last_update_token) = @ARGV;
|
||||||
|
|
||||||
|
# Uncomment for debugging
|
||||||
|
# print STDERR "$0 $version $last_update_token\n";
|
||||||
|
|
||||||
|
# Check the hook interface version
|
||||||
|
if ($version ne 2) {
|
||||||
|
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $git_work_tree = get_working_dir();
|
||||||
|
|
||||||
|
my $json_pkg;
|
||||||
|
eval {
|
||||||
|
require JSON::XS;
|
||||||
|
$json_pkg = "JSON::XS";
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
require JSON::PP;
|
||||||
|
$json_pkg = "JSON::PP";
|
||||||
|
};
|
||||||
|
|
||||||
|
launch_watchman();
|
||||||
|
|
||||||
|
sub launch_watchman {
|
||||||
|
my $o = watchman_query();
|
||||||
|
if (is_work_tree_watched($o)) {
|
||||||
|
output_result($o->{clock}, @{$o->{files}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub output_result {
|
||||||
|
my ($clockid, @files) = @_;
|
||||||
|
|
||||||
|
# Uncomment for debugging watchman output
|
||||||
|
# open (my $fh, ">", ".git/watchman-output.out");
|
||||||
|
# binmode $fh, ":utf8";
|
||||||
|
# print $fh "$clockid\n@files\n";
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
binmode STDOUT, ":utf8";
|
||||||
|
print $clockid;
|
||||||
|
print "\0";
|
||||||
|
local $, = "\0";
|
||||||
|
print @files;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub watchman_clock {
|
||||||
|
my $response = qx/watchman clock "$git_work_tree"/;
|
||||||
|
die "Failed to get clock id on '$git_work_tree'.\n" .
|
||||||
|
"Falling back to scanning...\n" if $? != 0;
|
||||||
|
|
||||||
|
return $json_pkg->new->utf8->decode($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub watchman_query {
|
||||||
|
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
|
||||||
|
or die "open2() failed: $!\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
|
||||||
|
# In the query expression below we're asking for names of files that
|
||||||
|
# changed since $last_update_token but not from the .git folder.
|
||||||
|
#
|
||||||
|
# To accomplish this, we're using the "since" generator to use the
|
||||||
|
# recency index to select candidate nodes and "fields" to limit the
|
||||||
|
# output to file names only. Then we're using the "expression" term to
|
||||||
|
# further constrain the results.
|
||||||
|
my $last_update_line = "";
|
||||||
|
if (substr($last_update_token, 0, 1) eq "c") {
|
||||||
|
$last_update_token = "\"$last_update_token\"";
|
||||||
|
$last_update_line = qq[\n"since": $last_update_token,];
|
||||||
|
}
|
||||||
|
my $query = <<" END";
|
||||||
|
["query", "$git_work_tree", {$last_update_line
|
||||||
|
"fields": ["name"],
|
||||||
|
"expression": ["not", ["dirname", ".git"]]
|
||||||
|
}]
|
||||||
|
END
|
||||||
|
|
||||||
|
# Uncomment for debugging the watchman query
|
||||||
|
# open (my $fh, ">", ".git/watchman-query.json");
|
||||||
|
# print $fh $query;
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
print CHLD_IN $query;
|
||||||
|
close CHLD_IN;
|
||||||
|
my $response = do {local $/; <CHLD_OUT>};
|
||||||
|
|
||||||
|
# Uncomment for debugging the watch response
|
||||||
|
# open ($fh, ">", ".git/watchman-response.json");
|
||||||
|
# print $fh $response;
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
die "Watchman: command returned no output.\n" .
|
||||||
|
"Falling back to scanning...\n" if $response eq "";
|
||||||
|
die "Watchman: command returned invalid output: $response\n" .
|
||||||
|
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||||
|
|
||||||
|
return $json_pkg->new->utf8->decode($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub is_work_tree_watched {
|
||||||
|
my ($output) = @_;
|
||||||
|
my $error = $output->{error};
|
||||||
|
if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||||
|
my $response = qx/watchman watch "$git_work_tree"/;
|
||||||
|
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||||
|
"Falling back to scanning...\n" if $? != 0;
|
||||||
|
$output = $json_pkg->new->utf8->decode($response);
|
||||||
|
$error = $output->{error};
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
# Uncomment for debugging watchman output
|
||||||
|
# open (my $fh, ">", ".git/watchman-output.out");
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
# Watchman will always return all files on the first query so
|
||||||
|
# return the fast "everything is dirty" flag to git and do the
|
||||||
|
# Watchman query just to get it over with now so we won't pay
|
||||||
|
# the cost in git to look up each individual file.
|
||||||
|
my $o = watchman_clock();
|
||||||
|
$error = $o->{error};
|
||||||
|
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
output_result($o->{clock}, ("/"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_working_dir {
|
||||||
|
my $working_dir;
|
||||||
|
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
|
||||||
|
$working_dir = Win32::GetCwd();
|
||||||
|
$working_dir =~ tr/\\/\//;
|
||||||
|
} else {
|
||||||
|
require Cwd;
|
||||||
|
$working_dir = Cwd::cwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $working_dir;
|
||||||
|
}
|
||||||
8
src/barecode/BareCodeAUR/barecode-git/hooks/post-update.sample
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to prepare a packed repository for use over
|
||||||
|
# dumb transports.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "post-update".
|
||||||
|
|
||||||
|
exec git update-server-info
|
||||||
14
src/barecode/BareCodeAUR/barecode-git/hooks/pre-applypatch.sample
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify what is about to be committed
|
||||||
|
# by applypatch from an e-mail message.
|
||||||
|
#
|
||||||
|
# The hook should exit with non-zero status after issuing an
|
||||||
|
# appropriate message if it wants to stop the commit.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-applypatch".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
precommit="$(git rev-parse --git-path hooks/pre-commit)"
|
||||||
|
test -x "$precommit" && exec "$precommit" ${1+"$@"}
|
||||||
|
:
|
||||||
49
src/barecode/BareCodeAUR/barecode-git/hooks/pre-commit.sample
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify what is about to be committed.
|
||||||
|
# Called by "git commit" with no arguments. The hook should
|
||||||
|
# exit with non-zero status after issuing an appropriate message if
|
||||||
|
# it wants to stop the commit.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-commit".
|
||||||
|
|
||||||
|
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
against=HEAD
|
||||||
|
else
|
||||||
|
# Initial commit: diff against an empty tree object
|
||||||
|
against=$(git hash-object -t tree /dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If you want to allow non-ASCII filenames set this variable to true.
|
||||||
|
allownonascii=$(git config --type=bool hooks.allownonascii)
|
||||||
|
|
||||||
|
# Redirect output to stderr.
|
||||||
|
exec 1>&2
|
||||||
|
|
||||||
|
# Cross platform projects tend to avoid non-ASCII filenames; prevent
|
||||||
|
# them from being added to the repository. We exploit the fact that the
|
||||||
|
# printable range starts at the space character and ends with tilde.
|
||||||
|
if [ "$allownonascii" != "true" ] &&
|
||||||
|
# Note that the use of brackets around a tr range is ok here, (it's
|
||||||
|
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||||
|
# the square bracket bytes happen to fall in the designated range.
|
||||||
|
test $(git diff-index --cached --name-only --diff-filter=A -z $against |
|
||||||
|
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||||
|
then
|
||||||
|
cat <<\EOF
|
||||||
|
Error: Attempt to add a non-ASCII file name.
|
||||||
|
|
||||||
|
This can cause problems if you want to work with people on other platforms.
|
||||||
|
|
||||||
|
To be portable it is advisable to rename the file.
|
||||||
|
|
||||||
|
If you know what you are doing you can disable this check using:
|
||||||
|
|
||||||
|
git config hooks.allownonascii true
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If there are whitespace errors, print the offending file names and fail.
|
||||||
|
exec git diff-index --check --cached $against --
|
||||||
13
src/barecode/BareCodeAUR/barecode-git/hooks/pre-merge-commit.sample
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify what is about to be committed.
|
||||||
|
# Called by "git merge" with no arguments. The hook should
|
||||||
|
# exit with non-zero status after issuing an appropriate message to
|
||||||
|
# stderr if it wants to stop the merge commit.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-merge-commit".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
test -x "$GIT_DIR/hooks/pre-commit" &&
|
||||||
|
exec "$GIT_DIR/hooks/pre-commit"
|
||||||
|
:
|
||||||
53
src/barecode/BareCodeAUR/barecode-git/hooks/pre-push.sample
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# An example hook script to verify what is about to be pushed. Called by "git
|
||||||
|
# push" after it has checked the remote status, but before anything has been
|
||||||
|
# pushed. If this script exits with a non-zero status nothing will be pushed.
|
||||||
|
#
|
||||||
|
# This hook is called with the following parameters:
|
||||||
|
#
|
||||||
|
# $1 -- Name of the remote to which the push is being done
|
||||||
|
# $2 -- URL to which the push is being done
|
||||||
|
#
|
||||||
|
# If pushing without using a named remote those arguments will be equal.
|
||||||
|
#
|
||||||
|
# Information about the commits which are being pushed is supplied as lines to
|
||||||
|
# the standard input in the form:
|
||||||
|
#
|
||||||
|
# <local ref> <local oid> <remote ref> <remote oid>
|
||||||
|
#
|
||||||
|
# This sample shows how to prevent push of commits where the log message starts
|
||||||
|
# with "WIP" (work in progress).
|
||||||
|
|
||||||
|
remote="$1"
|
||||||
|
url="$2"
|
||||||
|
|
||||||
|
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
|
||||||
|
|
||||||
|
while read local_ref local_oid remote_ref remote_oid
|
||||||
|
do
|
||||||
|
if test "$local_oid" = "$zero"
|
||||||
|
then
|
||||||
|
# Handle delete
|
||||||
|
:
|
||||||
|
else
|
||||||
|
if test "$remote_oid" = "$zero"
|
||||||
|
then
|
||||||
|
# New branch, examine all commits
|
||||||
|
range="$local_oid"
|
||||||
|
else
|
||||||
|
# Update to existing branch, examine new commits
|
||||||
|
range="$remote_oid..$local_oid"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for WIP commit
|
||||||
|
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
|
||||||
|
if test -n "$commit"
|
||||||
|
then
|
||||||
|
echo >&2 "Found WIP commit in $local_ref, not pushing"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
||||||
169
src/barecode/BareCodeAUR/barecode-git/hooks/pre-rebase.sample
Executable file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2006, 2008 Junio C Hamano
|
||||||
|
#
|
||||||
|
# The "pre-rebase" hook is run just before "git rebase" starts doing
|
||||||
|
# its job, and can prevent the command from running by exiting with
|
||||||
|
# non-zero status.
|
||||||
|
#
|
||||||
|
# The hook is called with the following parameters:
|
||||||
|
#
|
||||||
|
# $1 -- the upstream the series was forked from.
|
||||||
|
# $2 -- the branch being rebased (or empty when rebasing the current branch).
|
||||||
|
#
|
||||||
|
# This sample shows how to prevent topic branches that are already
|
||||||
|
# merged to 'next' branch from getting rebased, because allowing it
|
||||||
|
# would result in rebasing already published history.
|
||||||
|
|
||||||
|
publish=next
|
||||||
|
basebranch="$1"
|
||||||
|
if test "$#" = 2
|
||||||
|
then
|
||||||
|
topic="refs/heads/$2"
|
||||||
|
else
|
||||||
|
topic=`git symbolic-ref HEAD` ||
|
||||||
|
exit 0 ;# we do not interrupt rebasing detached HEAD
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$topic" in
|
||||||
|
refs/heads/??/*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 0 ;# we do not interrupt others.
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Now we are dealing with a topic branch being rebased
|
||||||
|
# on top of master. Is it OK to rebase it?
|
||||||
|
|
||||||
|
# Does the topic really exist?
|
||||||
|
git show-ref -q "$topic" || {
|
||||||
|
echo >&2 "No such branch $topic"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Is topic fully merged to master?
|
||||||
|
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
|
||||||
|
if test -z "$not_in_master"
|
||||||
|
then
|
||||||
|
echo >&2 "$topic is fully merged to master; better remove it."
|
||||||
|
exit 1 ;# we could allow it, but there is no point.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Is topic ever merged to next? If so you should not be rebasing it.
|
||||||
|
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
|
||||||
|
only_next_2=`git rev-list ^master ${publish} | sort`
|
||||||
|
if test "$only_next_1" = "$only_next_2"
|
||||||
|
then
|
||||||
|
not_in_topic=`git rev-list "^$topic" master`
|
||||||
|
if test -z "$not_in_topic"
|
||||||
|
then
|
||||||
|
echo >&2 "$topic is already up to date with master"
|
||||||
|
exit 1 ;# we could allow it, but there is no point.
|
||||||
|
else
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
|
||||||
|
/usr/bin/perl -e '
|
||||||
|
my $topic = $ARGV[0];
|
||||||
|
my $msg = "* $topic has commits already merged to public branch:\n";
|
||||||
|
my (%not_in_next) = map {
|
||||||
|
/^([0-9a-f]+) /;
|
||||||
|
($1 => 1);
|
||||||
|
} split(/\n/, $ARGV[1]);
|
||||||
|
for my $elem (map {
|
||||||
|
/^([0-9a-f]+) (.*)$/;
|
||||||
|
[$1 => $2];
|
||||||
|
} split(/\n/, $ARGV[2])) {
|
||||||
|
if (!exists $not_in_next{$elem->[0]}) {
|
||||||
|
if ($msg) {
|
||||||
|
print STDERR $msg;
|
||||||
|
undef $msg;
|
||||||
|
}
|
||||||
|
print STDERR " $elem->[1]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$topic" "$not_in_next" "$not_in_master"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
<<\DOC_END
|
||||||
|
|
||||||
|
This sample hook safeguards topic branches that have been
|
||||||
|
published from being rewound.
|
||||||
|
|
||||||
|
The workflow assumed here is:
|
||||||
|
|
||||||
|
* Once a topic branch forks from "master", "master" is never
|
||||||
|
merged into it again (either directly or indirectly).
|
||||||
|
|
||||||
|
* Once a topic branch is fully cooked and merged into "master",
|
||||||
|
it is deleted. If you need to build on top of it to correct
|
||||||
|
earlier mistakes, a new topic branch is created by forking at
|
||||||
|
the tip of the "master". This is not strictly necessary, but
|
||||||
|
it makes it easier to keep your history simple.
|
||||||
|
|
||||||
|
* Whenever you need to test or publish your changes to topic
|
||||||
|
branches, merge them into "next" branch.
|
||||||
|
|
||||||
|
The script, being an example, hardcodes the publish branch name
|
||||||
|
to be "next", but it is trivial to make it configurable via
|
||||||
|
$GIT_DIR/config mechanism.
|
||||||
|
|
||||||
|
With this workflow, you would want to know:
|
||||||
|
|
||||||
|
(1) ... if a topic branch has ever been merged to "next". Young
|
||||||
|
topic branches can have stupid mistakes you would rather
|
||||||
|
clean up before publishing, and things that have not been
|
||||||
|
merged into other branches can be easily rebased without
|
||||||
|
affecting other people. But once it is published, you would
|
||||||
|
not want to rewind it.
|
||||||
|
|
||||||
|
(2) ... if a topic branch has been fully merged to "master".
|
||||||
|
Then you can delete it. More importantly, you should not
|
||||||
|
build on top of it -- other people may already want to
|
||||||
|
change things related to the topic as patches against your
|
||||||
|
"master", so if you need further changes, it is better to
|
||||||
|
fork the topic (perhaps with the same name) afresh from the
|
||||||
|
tip of "master".
|
||||||
|
|
||||||
|
Let's look at this example:
|
||||||
|
|
||||||
|
o---o---o---o---o---o---o---o---o---o "next"
|
||||||
|
/ / / /
|
||||||
|
/ a---a---b A / /
|
||||||
|
/ / / /
|
||||||
|
/ / c---c---c---c B /
|
||||||
|
/ / / \ /
|
||||||
|
/ / / b---b C \ /
|
||||||
|
/ / / / \ /
|
||||||
|
---o---o---o---o---o---o---o---o---o---o---o "master"
|
||||||
|
|
||||||
|
|
||||||
|
A, B and C are topic branches.
|
||||||
|
|
||||||
|
* A has one fix since it was merged up to "next".
|
||||||
|
|
||||||
|
* B has finished. It has been fully merged up to "master" and "next",
|
||||||
|
and is ready to be deleted.
|
||||||
|
|
||||||
|
* C has not merged to "next" at all.
|
||||||
|
|
||||||
|
We would want to allow C to be rebased, refuse A, and encourage
|
||||||
|
B to be deleted.
|
||||||
|
|
||||||
|
To compute (1):
|
||||||
|
|
||||||
|
git rev-list ^master ^topic next
|
||||||
|
git rev-list ^master next
|
||||||
|
|
||||||
|
if these match, topic has not merged in next at all.
|
||||||
|
|
||||||
|
To compute (2):
|
||||||
|
|
||||||
|
git rev-list master..topic
|
||||||
|
|
||||||
|
if this is empty, it is fully merged to "master".
|
||||||
|
|
||||||
|
DOC_END
|
||||||
24
src/barecode/BareCodeAUR/barecode-git/hooks/pre-receive.sample
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to make use of push options.
|
||||||
|
# The example simply echoes all push options that start with 'echoback='
|
||||||
|
# and rejects all pushes when the "reject" push option is used.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-receive".
|
||||||
|
|
||||||
|
if test -n "$GIT_PUSH_OPTION_COUNT"
|
||||||
|
then
|
||||||
|
i=0
|
||||||
|
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
|
||||||
|
do
|
||||||
|
eval "value=\$GIT_PUSH_OPTION_$i"
|
||||||
|
case "$value" in
|
||||||
|
echoback=*)
|
||||||
|
echo "echo from the pre-receive-hook: ${value#*=}" >&2
|
||||||
|
;;
|
||||||
|
reject)
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
42
src/barecode/BareCodeAUR/barecode-git/hooks/prepare-commit-msg.sample
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to prepare the commit log message.
|
||||||
|
# Called by "git commit" with the name of the file that has the
|
||||||
|
# commit message, followed by the description of the commit
|
||||||
|
# message's source. The hook's purpose is to edit the commit
|
||||||
|
# message file. If the hook fails with a non-zero status,
|
||||||
|
# the commit is aborted.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "prepare-commit-msg".
|
||||||
|
|
||||||
|
# This hook includes three examples. The first one removes the
|
||||||
|
# "# Please enter the commit message..." help message.
|
||||||
|
#
|
||||||
|
# The second includes the output of "git diff --name-status -r"
|
||||||
|
# into the message, just before the "git status" output. It is
|
||||||
|
# commented because it doesn't cope with --amend or with squashed
|
||||||
|
# commits.
|
||||||
|
#
|
||||||
|
# The third example adds a Signed-off-by line to the message, that can
|
||||||
|
# still be edited. This is rarely a good idea.
|
||||||
|
|
||||||
|
COMMIT_MSG_FILE=$1
|
||||||
|
COMMIT_SOURCE=$2
|
||||||
|
SHA1=$3
|
||||||
|
|
||||||
|
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
|
||||||
|
|
||||||
|
# case "$COMMIT_SOURCE,$SHA1" in
|
||||||
|
# ,|template,)
|
||||||
|
# /usr/bin/perl -i.bak -pe '
|
||||||
|
# print "\n" . `git diff --cached --name-status -r`
|
||||||
|
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
|
||||||
|
# *) ;;
|
||||||
|
# esac
|
||||||
|
|
||||||
|
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||||
|
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
|
||||||
|
# if test -z "$COMMIT_SOURCE"
|
||||||
|
# then
|
||||||
|
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
|
||||||
|
# fi
|
||||||
78
src/barecode/BareCodeAUR/barecode-git/hooks/push-to-checkout.sample
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# An example hook script to update a checked-out tree on a git push.
|
||||||
|
#
|
||||||
|
# This hook is invoked by git-receive-pack(1) when it reacts to git
|
||||||
|
# push and updates reference(s) in its repository, and when the push
|
||||||
|
# tries to update the branch that is currently checked out and the
|
||||||
|
# receive.denyCurrentBranch configuration variable is set to
|
||||||
|
# updateInstead.
|
||||||
|
#
|
||||||
|
# By default, such a push is refused if the working tree and the index
|
||||||
|
# of the remote repository has any difference from the currently
|
||||||
|
# checked out commit; when both the working tree and the index match
|
||||||
|
# the current commit, they are updated to match the newly pushed tip
|
||||||
|
# of the branch. This hook is to be used to override the default
|
||||||
|
# behaviour; however the code below reimplements the default behaviour
|
||||||
|
# as a starting point for convenient modification.
|
||||||
|
#
|
||||||
|
# The hook receives the commit with which the tip of the current
|
||||||
|
# branch is going to be updated:
|
||||||
|
commit=$1
|
||||||
|
|
||||||
|
# It can exit with a non-zero status to refuse the push (when it does
|
||||||
|
# so, it must not modify the index or the working tree).
|
||||||
|
die () {
|
||||||
|
echo >&2 "$*"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Or it can make any necessary changes to the working tree and to the
|
||||||
|
# index to bring them to the desired state when the tip of the current
|
||||||
|
# branch is updated to the new commit, and exit with a zero status.
|
||||||
|
#
|
||||||
|
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
|
||||||
|
# in order to emulate git fetch that is run in the reverse direction
|
||||||
|
# with git push, as the two-tree form of git read-tree -u -m is
|
||||||
|
# essentially the same as git switch or git checkout that switches
|
||||||
|
# branches while keeping the local changes in the working tree that do
|
||||||
|
# not interfere with the difference between the branches.
|
||||||
|
|
||||||
|
# The below is a more-or-less exact translation to shell of the C code
|
||||||
|
# for the default behaviour for git's push-to-checkout hook defined in
|
||||||
|
# the push_to_deploy() function in builtin/receive-pack.c.
|
||||||
|
#
|
||||||
|
# Note that the hook will be executed from the repository directory,
|
||||||
|
# not from the working tree, so if you want to perform operations on
|
||||||
|
# the working tree, you will have to adapt your code accordingly, e.g.
|
||||||
|
# by adding "cd .." or using relative paths.
|
||||||
|
|
||||||
|
if ! git update-index -q --ignore-submodules --refresh
|
||||||
|
then
|
||||||
|
die "Up-to-date check failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git diff-files --quiet --ignore-submodules --
|
||||||
|
then
|
||||||
|
die "Working directory has unstaged changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This is a rough translation of:
|
||||||
|
#
|
||||||
|
# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
|
||||||
|
if git cat-file -e HEAD 2>/dev/null
|
||||||
|
then
|
||||||
|
head=HEAD
|
||||||
|
else
|
||||||
|
head=$(git hash-object -t tree --stdin </dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git diff-index --quiet --cached --ignore-submodules $head --
|
||||||
|
then
|
||||||
|
die "Working directory has staged changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git read-tree -u -m "$commit"
|
||||||
|
then
|
||||||
|
die "Could not update working tree to new HEAD"
|
||||||
|
fi
|
||||||
77
src/barecode/BareCodeAUR/barecode-git/hooks/sendemail-validate.sample
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# An example hook script to validate a patch (and/or patch series) before
|
||||||
|
# sending it via email.
|
||||||
|
#
|
||||||
|
# The hook should exit with non-zero status after issuing an appropriate
|
||||||
|
# message if it wants to prevent the email(s) from being sent.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "sendemail-validate".
|
||||||
|
#
|
||||||
|
# By default, it will only check that the patch(es) can be applied on top of
|
||||||
|
# the default upstream branch without conflicts in a secondary worktree. After
|
||||||
|
# validation (successful or not) of the last patch of a series, the worktree
|
||||||
|
# will be deleted.
|
||||||
|
#
|
||||||
|
# The following config variables can be set to change the default remote and
|
||||||
|
# remote ref that are used to apply the patches against:
|
||||||
|
#
|
||||||
|
# sendemail.validateRemote (default: origin)
|
||||||
|
# sendemail.validateRemoteRef (default: HEAD)
|
||||||
|
#
|
||||||
|
# Replace the TODO placeholders with appropriate checks according to your
|
||||||
|
# needs.
|
||||||
|
|
||||||
|
validate_cover_letter () {
|
||||||
|
file="$1"
|
||||||
|
# TODO: Replace with appropriate checks (e.g. spell checking).
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_patch () {
|
||||||
|
file="$1"
|
||||||
|
# Ensure that the patch applies without conflicts.
|
||||||
|
git am -3 "$file" || return
|
||||||
|
# TODO: Replace with appropriate checks for this patch
|
||||||
|
# (e.g. checkpatch.pl).
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_series () {
|
||||||
|
# TODO: Replace with appropriate checks for the whole series
|
||||||
|
# (e.g. quick build, coding style checks, etc.).
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
# main -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if test "$GIT_SENDEMAIL_FILE_COUNTER" = 1
|
||||||
|
then
|
||||||
|
remote=$(git config --default origin --get sendemail.validateRemote) &&
|
||||||
|
ref=$(git config --default HEAD --get sendemail.validateRemoteRef) &&
|
||||||
|
worktree=$(mktemp --tmpdir -d sendemail-validate.XXXXXXX) &&
|
||||||
|
git worktree add -fd --checkout "$worktree" "refs/remotes/$remote/$ref" &&
|
||||||
|
git config --replace-all sendemail.validateWorktree "$worktree"
|
||||||
|
else
|
||||||
|
worktree=$(git config --get sendemail.validateWorktree)
|
||||||
|
fi || {
|
||||||
|
echo "sendemail-validate: error: failed to prepare worktree" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
unset GIT_DIR GIT_WORK_TREE
|
||||||
|
cd "$worktree" &&
|
||||||
|
|
||||||
|
if grep -q "^diff --git " "$1"
|
||||||
|
then
|
||||||
|
validate_patch "$1"
|
||||||
|
else
|
||||||
|
validate_cover_letter "$1"
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL"
|
||||||
|
then
|
||||||
|
git config --unset-all sendemail.validateWorktree &&
|
||||||
|
trap 'git worktree remove -ff "$worktree"' EXIT &&
|
||||||
|
validate_series
|
||||||
|
fi
|
||||||
128
src/barecode/BareCodeAUR/barecode-git/hooks/update.sample
Executable file
@@ -0,0 +1,128 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to block unannotated tags from entering.
|
||||||
|
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "update".
|
||||||
|
#
|
||||||
|
# Config
|
||||||
|
# ------
|
||||||
|
# hooks.allowunannotated
|
||||||
|
# This boolean sets whether unannotated tags will be allowed into the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.allowdeletetag
|
||||||
|
# This boolean sets whether deleting tags will be allowed in the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.allowmodifytag
|
||||||
|
# This boolean sets whether a tag may be modified after creation. By default
|
||||||
|
# it won't be.
|
||||||
|
# hooks.allowdeletebranch
|
||||||
|
# This boolean sets whether deleting branches will be allowed in the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.denycreatebranch
|
||||||
|
# This boolean sets whether remotely creating branches will be denied
|
||||||
|
# in the repository. By default this is allowed.
|
||||||
|
#
|
||||||
|
|
||||||
|
# --- Command line
|
||||||
|
refname="$1"
|
||||||
|
oldrev="$2"
|
||||||
|
newrev="$3"
|
||||||
|
|
||||||
|
# --- Safety check
|
||||||
|
if [ -z "$GIT_DIR" ]; then
|
||||||
|
echo "Don't run this script from the command line." >&2
|
||||||
|
echo " (if you want, you could supply GIT_DIR then run" >&2
|
||||||
|
echo " $0 <ref> <oldrev> <newrev>)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
|
||||||
|
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Config
|
||||||
|
allowunannotated=$(git config --type=bool hooks.allowunannotated)
|
||||||
|
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
|
||||||
|
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
|
||||||
|
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
|
||||||
|
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
|
||||||
|
|
||||||
|
# check for no description
|
||||||
|
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
|
||||||
|
case "$projectdesc" in
|
||||||
|
"Unnamed repository"* | "")
|
||||||
|
echo "*** Project description file hasn't been set" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- Check types
|
||||||
|
# if $newrev is 0000...0000, it's a commit to delete a ref.
|
||||||
|
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
|
||||||
|
if [ "$newrev" = "$zero" ]; then
|
||||||
|
newrev_type=delete
|
||||||
|
else
|
||||||
|
newrev_type=$(git cat-file -t $newrev)
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$refname","$newrev_type" in
|
||||||
|
refs/tags/*,commit)
|
||||||
|
# un-annotated tag
|
||||||
|
short_refname=${refname##refs/tags/}
|
||||||
|
if [ "$allowunannotated" != "true" ]; then
|
||||||
|
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
|
||||||
|
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/tags/*,delete)
|
||||||
|
# delete tag
|
||||||
|
if [ "$allowdeletetag" != "true" ]; then
|
||||||
|
echo "*** Deleting a tag is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/tags/*,tag)
|
||||||
|
# annotated tag
|
||||||
|
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "*** Tag '$refname' already exists." >&2
|
||||||
|
echo "*** Modifying a tag is not allowed in this repository." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/heads/*,commit)
|
||||||
|
# branch
|
||||||
|
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
|
||||||
|
echo "*** Creating a branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/heads/*,delete)
|
||||||
|
# delete branch
|
||||||
|
if [ "$allowdeletebranch" != "true" ]; then
|
||||||
|
echo "*** Deleting a branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/remotes/*,commit)
|
||||||
|
# tracking branch
|
||||||
|
;;
|
||||||
|
refs/remotes/*,delete)
|
||||||
|
# delete tracking branch
|
||||||
|
if [ "$allowdeletebranch" != "true" ]; then
|
||||||
|
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Anything else (is there anything else?)
|
||||||
|
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- Finished
|
||||||
|
exit 0
|
||||||
1
src/barecode/BareCodeAUR/barecode-git/info/attributes
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* -export-subst -export-ignore
|
||||||
6
src/barecode/BareCodeAUR/barecode-git/info/exclude
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# git ls-files --others --exclude-from=.git/info/exclude
|
||||||
|
# Lines that start with '#' are comments.
|
||||||
|
# For a project mostly in C, the following would be a good set of
|
||||||
|
# exclude patterns (uncomment them if you want to use them):
|
||||||
|
# *.[oa]
|
||||||
|
# *~
|
||||||
4
src/barecode/BareCodeAUR/barecode-git/packed-refs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# pack-refs with: peeled fully-peeled sorted
|
||||||
|
cb6617222dbcac30aaf15bf336b150fed440cc7f refs/heads/main
|
||||||
|
ca573209e715409e5989324aaeaa4efbd4da5642 refs/tags/v1.0.0
|
||||||
|
^36e074f43d5b76d940d953956b0ed80899e71848
|
||||||
3020
src/barecode/BareCodeAUR/pkg/barecode-git/.BUILDINFO
Normal file
BIN
src/barecode/BareCodeAUR/pkg/barecode-git/.MTREE
Normal file
19
src/barecode/BareCodeAUR/pkg/barecode-git/.PKGINFO
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by makepkg 7.1.0
|
||||||
|
# using fakeroot version 1.37.2
|
||||||
|
pkgname = barecode-git
|
||||||
|
pkgbase = barecode-git
|
||||||
|
xdata = pkgtype=pkg
|
||||||
|
pkgver = r5.cb66172-1
|
||||||
|
pkgdesc = Modularer Code-Editor, entwickelt von Projekt Hirnfrei (git)
|
||||||
|
url = https://git.projekt-hirnfrei.de/diabolus/BareCode
|
||||||
|
builddate = 1781069213
|
||||||
|
packager = Unknown Packager
|
||||||
|
size = 1191503
|
||||||
|
arch = x86_64
|
||||||
|
license = MIT
|
||||||
|
conflict = barecode
|
||||||
|
provides = barecode
|
||||||
|
depend = qt6-base
|
||||||
|
makedepend = cmake
|
||||||
|
makedepend = ninja
|
||||||
|
makedepend = git
|
||||||
BIN
src/barecode/BareCodeAUR/pkg/barecode-git/usr/bin/BareCode
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name=BareCode
|
||||||
|
GenericName=Code Editor
|
||||||
|
Comment=Modularer Code-Editor für HTML, PHP, CSS und mehr
|
||||||
|
Exec=BareCode %F
|
||||||
|
Icon=barecode
|
||||||
|
Terminal=false
|
||||||
|
Categories=Development;TextEditor;
|
||||||
|
MimeType=text/plain;text/html;text/css;text/x-php;text/x-csrc;text/x-chdr;text/x-c++src;text/x-c++hdr;
|
||||||
|
Keywords=editor;code;html;php;css;c++;
|
||||||
|
StartupWMClass=BareCode
|
||||||
@@ -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
|
||||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 824 B |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -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.
|
||||||
143
src/barecode/CMakeLists.txt
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
project(BareCode
|
||||||
|
VERSION 1.1.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)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plattform-Erkennung
|
||||||
|
# Haiku definiert kein eigenes CMake-Flag — wir erkennen es über den
|
||||||
|
# Systemnamen. CMAKE_SYSTEM_NAME ist "Haiku" auf Haiku OS.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if(WIN32)
|
||||||
|
set(PLATFORM_WINDOWS TRUE)
|
||||||
|
add_compile_definitions(PLATFORM_WINDOWS)
|
||||||
|
set(CMAKE_WIN32_EXECUTABLE ON)
|
||||||
|
elseif(CMAKE_SYSTEM_NAME STREQUAL "Haiku")
|
||||||
|
set(PLATFORM_HAIKU TRUE)
|
||||||
|
add_compile_definitions(PLATFORM_HAIKU)
|
||||||
|
elseif(APPLE)
|
||||||
|
set(PLATFORM_MACOS TRUE)
|
||||||
|
add_compile_definitions(PLATFORM_MACOS)
|
||||||
|
elseif(UNIX)
|
||||||
|
set(PLATFORM_LINUX TRUE)
|
||||||
|
add_compile_definitions(PLATFORM_LINUX)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Qt6
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS
|
||||||
|
Core
|
||||||
|
Gui
|
||||||
|
Widgets
|
||||||
|
Concurrent
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Executable
|
||||||
|
# Unter Windows: .rc-Datei für EXE-Icon einbinden
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if(PLATFORM_WINDOWS)
|
||||||
|
qt_add_executable(BareCode
|
||||||
|
main.cpp
|
||||||
|
BareCode.rc
|
||||||
|
${BARECODE_RESOURCES}
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
qt_add_executable(BareCode
|
||||||
|
main.cpp
|
||||||
|
${BARECODE_RESOURCES}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Installation
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
install(TARGETS BareCode
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---- Linux (FreeDesktop) --------------------------------------------------
|
||||||
|
if(PLATFORM_LINUX)
|
||||||
|
foreach(SIZE 16 32 48 64 128 256 512)
|
||||||
|
install(FILES resources/icon_${SIZE}.png
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${SIZE}x${SIZE}/apps
|
||||||
|
RENAME barecode.png
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
install(FILES BareCode.desktop
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ---- Haiku ----------------------------------------------------------------
|
||||||
|
# Haiku verwendet kein FreeDesktop-System. Icons und MIME-Typen werden
|
||||||
|
# nativ über 'mimeset' gesetzt. Die PNG-Icons legen wir in den
|
||||||
|
# Haiku-typischen Pfad, ein post-install Skript ruft mimeset auf.
|
||||||
|
if(PLATFORM_HAIKU)
|
||||||
|
install(FILES resources/icon_256.png
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/BareCode
|
||||||
|
RENAME BareCode.png
|
||||||
|
)
|
||||||
|
|
||||||
|
# mimeset nach der Installation ausführen um MIME-Typ zu registrieren
|
||||||
|
install(CODE "
|
||||||
|
execute_process(
|
||||||
|
COMMAND mimeset -f \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/BareCode\"
|
||||||
|
RESULT_VARIABLE _mimeset_result
|
||||||
|
)
|
||||||
|
if(NOT _mimeset_result EQUAL 0)
|
||||||
|
message(WARNING \"mimeset konnte nicht ausgeführt werden – MIME-Typ muss manuell gesetzt werden.\")
|
||||||
|
endif()
|
||||||
|
")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ---- macOS ----------------------------------------------------------------
|
||||||
|
if(PLATFORM_MACOS)
|
||||||
|
set_target_properties(BareCode PROPERTIES
|
||||||
|
MACOSX_BUNDLE TRUE
|
||||||
|
MACOSX_BUNDLE_BUNDLE_NAME "BareCode"
|
||||||
|
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||||
|
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
21
src/barecode/LICENSE
Normal 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.
|
||||||
171
src/barecode/README.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# BareCode
|
||||||
|
|
||||||
|
**BareCode – Schlanker Code-Editor für Web-Entwickler**
|
||||||
|
Version 1.1.0
|
||||||
|
|
||||||
|
Kompiliert sauber auf **Linux**, **Windows** und **Haiku**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
| Feature | Details |
|
||||||
|
|---|---|
|
||||||
|
| Projektbaum | Linkes Panel — zeigt nur das gewählte Projektverzeichnis |
|
||||||
|
| Tabs | Mehrere Dateien gleichzeitig, verschiebbar und schließbar |
|
||||||
|
| Änderungsindikator | ● im Tab-Titel bei ungespeicherten Änderungen |
|
||||||
|
| Syntax-Highlighting | HTML, PHP, CSS, C/C++ inkl. mehrzeiliger Kommentare |
|
||||||
|
| Zeilennummern | Eigener Gutter, aktuelle Zeile hervorgehoben |
|
||||||
|
| Einrück-Führungslinien | Vertikale Linien wie in VS Code |
|
||||||
|
| Auto-Indent | Einrückungstiefe wird bei Enter übernommen |
|
||||||
|
| Tab / Shift+Tab | Ein- und Ausrücken, auch für mehrere Zeilen gleichzeitig |
|
||||||
|
| Smart Backspace | Springt zur vorherigen Tab-Stop-Position |
|
||||||
|
| Speichern | Speichern, Speichern unter, Alles speichern |
|
||||||
|
| Suchen & Ersetzen | Einzeln, Alle, In Auswahl, Regex, Live-Hervorhebung |
|
||||||
|
| In Dateien suchen | Rekursive Projektsuche mit Ergebnisliste, direkt zur Zeile |
|
||||||
|
| Dark / Light Mode | Umschaltbar, wird gespeichert |
|
||||||
|
| Session | Geöffnete Dateien und aktiver Tab werden wiederhergestellt |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
BareCode/
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── main.cpp
|
||||||
|
├── resources/
|
||||||
|
│ ├── resources.qrc
|
||||||
|
│ └── icon_*.png
|
||||||
|
└── src/
|
||||||
|
├── core/ # MainWindow, ProjectManager, Settings, ThemeManager, IPlugin
|
||||||
|
├── editor/ # EditorPanel, EditorTab, CodeEditor, SearchPanel, FileSearchPanel
|
||||||
|
├── filetree/ # FileTreePanel
|
||||||
|
└── highlighter/ # SyntaxHighlighter, HighlighterFactory
|
||||||
|
```
|
||||||
|
|
||||||
|
Jedes Unterverzeichnis kompiliert als eigene statische Bibliothek.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Abhängigkeiten & Bauen
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
**Abhängigkeiten (Beispiel Ubuntu/Debian):**
|
||||||
|
```bash
|
||||||
|
sudo apt install cmake qt6-base-dev qt6-base-dev-tools libqt6concurrent6
|
||||||
|
```
|
||||||
|
|
||||||
|
**Abhängigkeiten (Arch/Manjaro):**
|
||||||
|
```bash
|
||||||
|
sudo pacman -S cmake qt6-base
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bauen:**
|
||||||
|
```bash
|
||||||
|
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build -j$(nproc)
|
||||||
|
./build/BareCode
|
||||||
|
```
|
||||||
|
|
||||||
|
**Installieren:**
|
||||||
|
```bash
|
||||||
|
sudo cmake --install build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Windows (Visual Studio)
|
||||||
|
|
||||||
|
**Abhängigkeiten:**
|
||||||
|
- [Qt6](https://www.qt.io/download) (Komponenten: Qt6 Base, Qt6 Concurrent)
|
||||||
|
- CMake ≥ 3.16
|
||||||
|
- Visual Studio 2019 oder neuer
|
||||||
|
|
||||||
|
```bat
|
||||||
|
cmake -B build -G "Visual Studio 17 2022" -A x64
|
||||||
|
cmake --build build --config Release
|
||||||
|
build\Release\BareCode.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows Installer erstellen** (mit [Inno Setup](https://jrsoftware.org/isinfo.php)):
|
||||||
|
```bat
|
||||||
|
windeployqt --release build\Release\BareCode.exe
|
||||||
|
```
|
||||||
|
Danach das Inno Setup Skript ausführen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Haiku
|
||||||
|
|
||||||
|
**Abhängigkeiten:**
|
||||||
|
```bash
|
||||||
|
pkgman install qt6_base qt6_base_devel
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Hinweis:** `qt6_base_devel` enthält auf Haiku sowohl die Header als auch
|
||||||
|
> die Build-Tools (`moc`, `rcc`, `qmake`). Getestet auf Haiku R1/beta5.
|
||||||
|
|
||||||
|
**Bauen:**
|
||||||
|
```bash
|
||||||
|
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build -j$(nproc)
|
||||||
|
./build/BareCode
|
||||||
|
```
|
||||||
|
|
||||||
|
**Installieren:**
|
||||||
|
```bash
|
||||||
|
cmake --install build
|
||||||
|
```
|
||||||
|
|
||||||
|
> Nach der Installation wird `mimeset` automatisch aufgerufen um den
|
||||||
|
> MIME-Typ zu registrieren, damit BareCode im Haiku Tracker korrekt
|
||||||
|
> als Anwendung erkannt wird.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tastaturkürzel
|
||||||
|
|
||||||
|
| Aktion | Kürzel |
|
||||||
|
|---|---|
|
||||||
|
| Datei öffnen | Strg+O |
|
||||||
|
| Neue Datei | Strg+N |
|
||||||
|
| Projekt öffnen | Strg+Shift+O |
|
||||||
|
| Speichern | Strg+S |
|
||||||
|
| Speichern unter | Strg+Shift+A |
|
||||||
|
| Alles speichern | Strg+Shift+S |
|
||||||
|
| Suchen / Ersetzen | Strg+F |
|
||||||
|
| In Dateien suchen | Strg+Shift+F |
|
||||||
|
| Dark Mode | Strg+Shift+D |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Erweitern
|
||||||
|
|
||||||
|
### Neuen Highlighter hinzufügen
|
||||||
|
|
||||||
|
1. Subklasse von `SyntaxHighlighter` in `src/highlighter/` erstellen
|
||||||
|
2. `m_rules` im Konstruktor befüllen (siehe `CppHighlighter` als Vorlage)
|
||||||
|
3. Dateiendung in `HighlighterFactory.cpp` registrieren
|
||||||
|
|
||||||
|
Keine weiteren Dateien müssen geändert werden.
|
||||||
|
|
||||||
|
### Neues Panel / Plugin hinzufügen
|
||||||
|
|
||||||
|
1. `IPlugin`-Interface aus `src/core/IPlugin.h` implementieren
|
||||||
|
2. `QWidget`-Subklasse für die UI erstellen
|
||||||
|
3. In `MainWindow` instanziieren und verdrahten
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code-Stil
|
||||||
|
|
||||||
|
Allman-Klammerstil, C++17 durchgehend.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- Website: [www.projekt-hirnfrei.de](https://www.projekt-hirnfrei.de)
|
||||||
|
- Discord: [discord.projekt-hirnfrei.de](https://discord.projekt-hirnfrei.de)
|
||||||
13
src/barecode/barecode.desktop
Normal 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
|
||||||
28
src/barecode/main.cpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#include <QApplication>
|
||||||
|
#include <QIcon>
|
||||||
|
#include "core/MainWindow.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
app.setApplicationName("BareCode");
|
||||||
|
app.setApplicationVersion("1.1.0");
|
||||||
|
app.setOrganizationName("BareCode");
|
||||||
|
|
||||||
|
// Icon in allen verfügbaren Größen setzen
|
||||||
|
QIcon appIcon;
|
||||||
|
appIcon.addFile(":/icon_16.png", QSize(16, 16));
|
||||||
|
appIcon.addFile(":/icon_32.png", QSize(32, 32));
|
||||||
|
appIcon.addFile(":/icon_48.png", QSize(48, 48));
|
||||||
|
appIcon.addFile(":/icon_64.png", QSize(64, 64));
|
||||||
|
appIcon.addFile(":/icon_128.png", QSize(128, 128));
|
||||||
|
appIcon.addFile(":/icon_256.png", QSize(256, 256));
|
||||||
|
appIcon.addFile(":/icon_512.png", QSize(512, 512));
|
||||||
|
app.setWindowIcon(appIcon);
|
||||||
|
|
||||||
|
MainWindow window;
|
||||||
|
window.show();
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
BIN
src/barecode/resources/BareCode.ico
Normal file
|
After Width: | Height: | Size: 846 B |
BIN
src/barecode/resources/icon_128.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
src/barecode/resources/icon_16.png
Normal file
|
After Width: | Height: | Size: 824 B |
BIN
src/barecode/resources/icon_24.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/barecode/resources/icon_256.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
src/barecode/resources/icon_32.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/barecode/resources/icon_48.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/barecode/resources/icon_512.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
src/barecode/resources/icon_64.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
1197
src/barecode/resources/php_functions.json
Normal file
12
src/barecode/resources/resources.qrc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>icon_512.png</file>
|
||||||
|
<file>icon_256.png</file>
|
||||||
|
<file>icon_128.png</file>
|
||||||
|
<file>icon_64.png</file>
|
||||||
|
<file>icon_48.png</file>
|
||||||
|
<file>icon_32.png</file>
|
||||||
|
<file>icon_16.png</file>
|
||||||
|
<file>php_functions.json</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
4
src/barecode/src/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
add_subdirectory(core)
|
||||||
|
add_subdirectory(editor)
|
||||||
|
add_subdirectory(filetree)
|
||||||
|
add_subdirectory(highlighter)
|
||||||
105
src/barecode/src/core/AboutDialog.cpp
Normal 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.1.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/barecode/src/core/AboutDialog.h
Normal 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/barecode/src/core/CMakeLists.txt
Normal 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/barecode/src/core/IPlugin.h
Normal 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() {}
|
||||||
|
};
|
||||||
336
src/barecode/src/core/MainWindow.cpp
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Letzte Session wiederherstellen (geöffnete Dateien + aktiver Tab)
|
||||||
|
m_editor->restoreSession(
|
||||||
|
m_settings->lastOpenFiles(),
|
||||||
|
m_settings->lastActiveFile()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
m_actFileSearch = mBearbeiten->addAction(tr("In &Dateien suchen…"), this, &MainWindow::onShowFileSearch);
|
||||||
|
m_actFileSearch->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_F);
|
||||||
|
|
||||||
|
// ---- 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::onShowFileSearch()
|
||||||
|
{
|
||||||
|
m_editor->showFileSearchPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onProjectOpened(const QString &path)
|
||||||
|
{
|
||||||
|
setWindowTitle(QString("BareCode – %1").arg(path));
|
||||||
|
m_fileTree->setRootPath(path);
|
||||||
|
m_editor->setSearchRoot(path);
|
||||||
|
statusBar()->showMessage(tr("Projekt geöffnet: %1").arg(path), 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onProjectClosed()
|
||||||
|
{
|
||||||
|
setWindowTitle("BareCode");
|
||||||
|
m_fileTree->clearRoot();
|
||||||
|
m_editor->setSearchRoot(QString());
|
||||||
|
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());
|
||||||
|
|
||||||
|
// Session speichern
|
||||||
|
m_settings->setLastOpenFiles(m_editor->openFilePaths());
|
||||||
|
m_settings->setLastActiveFile(m_editor->activeFilePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
87
src/barecode/src/core/MainWindow.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#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();
|
||||||
|
void onShowFileSearch();
|
||||||
|
// 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_actFileSearch = nullptr;
|
||||||
|
QAction *m_actDarkMode = nullptr;
|
||||||
|
QAction *m_actAbout = nullptr;
|
||||||
|
};
|
||||||
33
src/barecode/src/core/ProjectManager.cpp
Normal 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/barecode/src/core/ProjectManager.h
Normal 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;
|
||||||
|
};
|
||||||
113
src/barecode/src/core/Settings.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Session – geöffnete Dateien
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
QStringList Settings::lastOpenFiles() const
|
||||||
|
{
|
||||||
|
return m_settings.value("session/openFiles", QStringList()).toStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setLastOpenFiles(const QStringList &files)
|
||||||
|
{
|
||||||
|
m_settings.setValue("session/openFiles", files);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Settings::lastActiveFile() const
|
||||||
|
{
|
||||||
|
return m_settings.value("session/activeFile", QString()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setLastActiveFile(const QString &file)
|
||||||
|
{
|
||||||
|
m_settings.setValue("session/activeFile", file);
|
||||||
|
}
|
||||||
52
src/barecode/src/core/Settings.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#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);
|
||||||
|
|
||||||
|
// Zuletzt geöffnete Dateien (Session-Wiederherstellung)
|
||||||
|
QStringList lastOpenFiles() const;
|
||||||
|
void setLastOpenFiles(const QStringList &files);
|
||||||
|
|
||||||
|
QString lastActiveFile() const;
|
||||||
|
void setLastActiveFile(const QString &file);
|
||||||
|
|
||||||
|
// 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/barecode/src/core/ThemeManager.cpp
Normal 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/barecode/src/core/ThemeManager.h
Normal 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;
|
||||||
|
};
|
||||||
35
src/barecode/src/editor/CMakeLists.txt
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
set(EDITOR_SOURCES
|
||||||
|
EditorPanel.cpp
|
||||||
|
EditorPanel.h
|
||||||
|
CodeEditor.cpp
|
||||||
|
CodeEditor.h
|
||||||
|
LineNumberArea.cpp
|
||||||
|
LineNumberArea.h
|
||||||
|
EditorTab.cpp
|
||||||
|
EditorTab.h
|
||||||
|
SearchPanel.cpp
|
||||||
|
SearchPanel.h
|
||||||
|
FileSearchPanel.cpp
|
||||||
|
FileSearchPanel.h
|
||||||
|
ColorIndicator.cpp
|
||||||
|
ColorIndicator.h
|
||||||
|
SignatureHelper.cpp
|
||||||
|
SignatureHelper.h
|
||||||
|
SignatureTooltip.cpp
|
||||||
|
SignatureTooltip.h
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(BareCode_Editor STATIC ${EDITOR_SOURCES})
|
||||||
|
|
||||||
|
target_link_libraries(BareCode_Editor PUBLIC
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::Gui
|
||||||
|
Qt6::Widgets
|
||||||
|
Qt6::Concurrent
|
||||||
|
BareCode_Highlighter
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(BareCode_Editor PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||||
|
)
|
||||||
556
src/barecode/src/editor/CodeEditor.cpp
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
#include "CodeEditor.h"
|
||||||
|
#include "LineNumberArea.h"
|
||||||
|
#include "ColorIndicator.h"
|
||||||
|
#include "SignatureHelper.h"
|
||||||
|
|
||||||
|
#include "core/Settings.h"
|
||||||
|
#include "highlighter/HighlighterFactory.h"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QTextBlock>
|
||||||
|
#include <QPaintEvent>
|
||||||
|
#include <QResizeEvent>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#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);
|
||||||
|
m_colorIndicator = new ColorIndicator(this);
|
||||||
|
m_signatureHelper = new SignatureHelper(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::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
// Zuerst den normalen Editor-Inhalt zeichnen
|
||||||
|
QPlainTextEdit::paintEvent(event);
|
||||||
|
|
||||||
|
// Einrück-Führungslinien
|
||||||
|
const int tabSize = m_settings->tabSize();
|
||||||
|
if (tabSize > 0)
|
||||||
|
{
|
||||||
|
QPainter painter(viewport());
|
||||||
|
|
||||||
|
QColor guideColor = palette().color(QPalette::Text);
|
||||||
|
guideColor.setAlpha(30);
|
||||||
|
painter.setPen(QPen(guideColor, 1, Qt::SolidLine));
|
||||||
|
|
||||||
|
const QFontMetrics fm(font());
|
||||||
|
const int spaceWidth = fm.horizontalAdvance(' ');
|
||||||
|
const int tabPixels = tabSize * spaceWidth;
|
||||||
|
|
||||||
|
if (tabPixels > 0)
|
||||||
|
{
|
||||||
|
int textOriginX = 0;
|
||||||
|
{
|
||||||
|
QTextBlock firstBlock = firstVisibleBlock();
|
||||||
|
if (!firstBlock.isValid())
|
||||||
|
{
|
||||||
|
firstBlock = document()->begin();
|
||||||
|
}
|
||||||
|
if (firstBlock.isValid())
|
||||||
|
{
|
||||||
|
const QRectF blockRect = blockBoundingGeometry(firstBlock)
|
||||||
|
.translated(contentOffset());
|
||||||
|
const QTextLayout *layout = firstBlock.layout();
|
||||||
|
if (layout && layout->lineCount() > 0)
|
||||||
|
{
|
||||||
|
textOriginX = static_cast<int>(blockRect.left()
|
||||||
|
+ layout->lineAt(0).position().x());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
textOriginX = static_cast<int>(blockRect.left());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int scrollX = horizontalScrollBar()->value();
|
||||||
|
|
||||||
|
QTextBlock block = firstVisibleBlock();
|
||||||
|
const int bottom = event->rect().bottom();
|
||||||
|
|
||||||
|
while (block.isValid())
|
||||||
|
{
|
||||||
|
const QRectF blockRect = blockBoundingGeometry(block)
|
||||||
|
.translated(contentOffset());
|
||||||
|
if (blockRect.top() > bottom) { break; }
|
||||||
|
|
||||||
|
if (block.isVisible())
|
||||||
|
{
|
||||||
|
const QString text = block.text();
|
||||||
|
int indentSpaces = 0;
|
||||||
|
for (const QChar &ch : text)
|
||||||
|
{
|
||||||
|
if (ch == ' ') { ++indentSpaces; }
|
||||||
|
else if (ch == '\t') { indentSpaces = ((indentSpaces / tabSize) + 1) * tabSize; }
|
||||||
|
else { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
const int indentStops = indentSpaces / tabSize;
|
||||||
|
for (int stop = 1; stop <= indentStops; ++stop)
|
||||||
|
{
|
||||||
|
const int xPixel = textOriginX + stop * tabPixels - scrollX;
|
||||||
|
if (xPixel < lineNumberAreaWidth() || xPixel > viewport()->width())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
painter.drawLine(xPixel,
|
||||||
|
static_cast<int>(blockRect.top()),
|
||||||
|
xPixel,
|
||||||
|
static_cast<int>(blockRect.bottom()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Farbvorschau-Quadrate zeichnen
|
||||||
|
m_colorIndicator->paint(painter, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CodeEditor::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
// Zuerst prüfen ob ein Farbquadrat geklickt wurde
|
||||||
|
if (m_colorIndicator->handleMousePress(event))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QPlainTextEdit::mousePressEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CodeEditor::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
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 – Tab/Shift+Tab, Smart-Backspace, Auto-Indent
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void CodeEditor::keyPressEvent(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
const int tabSize = m_settings->tabSize();
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Shift+Tab: Einrückung zurückziehen
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
if (event->key() == Qt::Key_Backtab ||
|
||||||
|
(event->key() == Qt::Key_Tab && event->modifiers() & Qt::ShiftModifier))
|
||||||
|
{
|
||||||
|
QTextCursor cursor = textCursor();
|
||||||
|
|
||||||
|
QTextBlock startBlock = document()->findBlock(cursor.selectionStart());
|
||||||
|
QTextBlock endBlock = document()->findBlock(cursor.selectionEnd());
|
||||||
|
|
||||||
|
cursor.beginEditBlock();
|
||||||
|
for (QTextBlock b = startBlock; b != endBlock.next(); b = b.next())
|
||||||
|
{
|
||||||
|
const QString lineText = b.text();
|
||||||
|
int toRemove = 0;
|
||||||
|
|
||||||
|
if (m_settings->useSpacesForTabs())
|
||||||
|
{
|
||||||
|
// Bis zu tabSize führende Leerzeichen entfernen
|
||||||
|
for (int i = 0; i < tabSize && i < lineText.length(); ++i)
|
||||||
|
{
|
||||||
|
if (lineText[i] == ' ')
|
||||||
|
{
|
||||||
|
++toRemove;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Einen führenden Tab entfernen
|
||||||
|
if (!lineText.isEmpty() && lineText[0] == '\t')
|
||||||
|
{
|
||||||
|
toRemove = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toRemove > 0)
|
||||||
|
{
|
||||||
|
QTextCursor lineCursor(b);
|
||||||
|
lineCursor.movePosition(QTextCursor::StartOfBlock);
|
||||||
|
lineCursor.movePosition(QTextCursor::Right,
|
||||||
|
QTextCursor::KeepAnchor,
|
||||||
|
toRemove);
|
||||||
|
lineCursor.removeSelectedText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.endEditBlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Tab: Einrücken (Leerzeichen oder echter Tab)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
if (event->key() == Qt::Key_Tab)
|
||||||
|
{
|
||||||
|
QTextCursor cursor = textCursor();
|
||||||
|
|
||||||
|
if (cursor.hasSelection())
|
||||||
|
{
|
||||||
|
// Mehrere Zeilen einrücken
|
||||||
|
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.movePosition(QTextCursor::StartOfBlock);
|
||||||
|
if (m_settings->useSpacesForTabs())
|
||||||
|
{
|
||||||
|
lineCursor.insertText(QString(tabSize, ' '));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lineCursor.insertText("\t");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.endEditBlock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_settings->useSpacesForTabs())
|
||||||
|
{
|
||||||
|
// Zum nächsten Tab-Stop auffüllen
|
||||||
|
const int col = cursor.columnNumber();
|
||||||
|
const int spacesNeeded = tabSize - (col % tabSize);
|
||||||
|
cursor.insertText(QString(spacesNeeded, ' '));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursor.insertText("\t");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Smart Backspace: springt zur vorherigen Einrückungsstufe
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
if (event->key() == Qt::Key_Backspace
|
||||||
|
&& !textCursor().hasSelection()
|
||||||
|
&& m_settings->useSpacesForTabs())
|
||||||
|
{
|
||||||
|
QTextCursor cursor = textCursor();
|
||||||
|
const int col = cursor.columnNumber();
|
||||||
|
|
||||||
|
if (col > 0)
|
||||||
|
{
|
||||||
|
// Prüfen ob links vom Cursor nur Leerzeichen bis Zeilenbeginn stehen
|
||||||
|
const QString lineText = cursor.block().text();
|
||||||
|
const QString leftOfCursor = lineText.left(col);
|
||||||
|
const bool onlySpaces = leftOfCursor.trimmed().isEmpty();
|
||||||
|
|
||||||
|
if (onlySpaces && col > 0)
|
||||||
|
{
|
||||||
|
// Zur vorherigen Tab-Stop-Position springen
|
||||||
|
const int targetCol = ((col - 1) / tabSize) * tabSize;
|
||||||
|
const int toDelete = col - targetCol;
|
||||||
|
|
||||||
|
cursor.movePosition(QTextCursor::Left,
|
||||||
|
QTextCursor::KeepAnchor,
|
||||||
|
toDelete);
|
||||||
|
cursor.removeSelectedText();
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Führende Leerzeichen der aktuellen Zeile zählen
|
||||||
|
int leadingSpaces = 0;
|
||||||
|
for (const QChar &ch : currentLine)
|
||||||
|
{
|
||||||
|
if (ch == ' ')
|
||||||
|
{
|
||||||
|
++leadingSpaces;
|
||||||
|
}
|
||||||
|
else if (ch == '\t')
|
||||||
|
{
|
||||||
|
leadingSpaces += tabSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPlainTextEdit::keyPressEvent(event);
|
||||||
|
|
||||||
|
if (leadingSpaces > 0)
|
||||||
|
{
|
||||||
|
const QString indent = m_settings->useSpacesForTabs()
|
||||||
|
? QString(leadingSpaces, ' ')
|
||||||
|
: QString(leadingSpaces / tabSize, '\t');
|
||||||
|
textCursor().insertText(indent);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPlainTextEdit::keyPressEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
76
src/barecode/src/editor/CodeEditor.h
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QPlainTextEdit>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTextBlock>
|
||||||
|
|
||||||
|
class LineNumberArea;
|
||||||
|
class Settings;
|
||||||
|
class SyntaxHighlighter;
|
||||||
|
class ColorIndicator;
|
||||||
|
class SignatureHelper;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Öffentliche Hilfsmethoden für ColorIndicator
|
||||||
|
// (die Qt-Originale sind protected und von außen nicht erreichbar)
|
||||||
|
QTextBlock firstVisibleBlockPublic() const { return firstVisibleBlock(); }
|
||||||
|
QRectF blockBoundingGeometryPublic(const QTextBlock &b) const { return blockBoundingGeometry(b); }
|
||||||
|
QPointF contentOffsetPublic() const { return contentOffset(); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void fileSaved(const QString &filePath);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void mousePressEvent(QMouseEvent *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;
|
||||||
|
ColorIndicator *m_colorIndicator = nullptr;
|
||||||
|
SignatureHelper *m_signatureHelper = nullptr;
|
||||||
|
QString m_filePath;
|
||||||
|
};
|
||||||
361
src/barecode/src/editor/ColorIndicator.cpp
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
#include "ColorIndicator.h"
|
||||||
|
#include "CodeEditor.h"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPaintEvent>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QColorDialog>
|
||||||
|
#include <QTextBlock>
|
||||||
|
#include <QTextCursor>
|
||||||
|
#include <QTextDocument>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Kombinierter Regex — erfasst alle CSS-Farbformate in einer Runde
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
const QRegularExpression ColorIndicator::s_colorRegex(
|
||||||
|
// #rgb / #rrggbb / #rrggbbaa
|
||||||
|
R"(#(?:[0-9A-Fa-f]{8}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})(?=[^0-9A-Fa-f]|$))"
|
||||||
|
R"(|rgba?\s*\([^)]+\))"
|
||||||
|
R"(|hsla?\s*\([^)]+\))",
|
||||||
|
QRegularExpression::CaseInsensitiveOption
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Konstruktor
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
ColorIndicator::ColorIndicator(CodeEditor *editor)
|
||||||
|
: QObject(editor)
|
||||||
|
, m_editor(editor)
|
||||||
|
{
|
||||||
|
// Cache invalidieren und Viewport neu zeichnen wenn sich der Text ändert
|
||||||
|
connect(m_editor->document(), &QTextDocument::contentsChanged,
|
||||||
|
this, [this]()
|
||||||
|
{
|
||||||
|
m_cacheFirstBlock = -1;
|
||||||
|
m_cacheLastBlock = -1;
|
||||||
|
m_editor->viewport()->update();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auch beim Scrollen neu zeichnen (Cache bleibt gültig, nur Position ändert sich)
|
||||||
|
connect(m_editor->verticalScrollBar(), &QScrollBar::valueChanged,
|
||||||
|
this, [this]()
|
||||||
|
{
|
||||||
|
m_cacheFirstBlock = -1;
|
||||||
|
m_cacheLastBlock = -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Paint – wird aus CodeEditor::paintEvent aufgerufen
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void ColorIndicator::paint(QPainter &painter, QPaintEvent *event)
|
||||||
|
{
|
||||||
|
rebuildCache();
|
||||||
|
|
||||||
|
const int squareSize = m_editor->fontMetrics().height() - 4;
|
||||||
|
const int radius = 2;
|
||||||
|
|
||||||
|
for (const ColorMatch &m : m_cache)
|
||||||
|
{
|
||||||
|
if (!event->rect().intersects(m.rect))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rahmen
|
||||||
|
painter.setPen(QColor(0, 0, 0, 80));
|
||||||
|
painter.setBrush(m.color);
|
||||||
|
painter.drawRoundedRect(m.rect, radius, radius);
|
||||||
|
|
||||||
|
// Schachbrettmuster als Hintergrund für transparente Farben
|
||||||
|
if (m.color.alpha() < 255)
|
||||||
|
{
|
||||||
|
const int half = squareSize / 2;
|
||||||
|
painter.setPen(Qt::NoPen);
|
||||||
|
painter.setBrush(QColor(180, 180, 180));
|
||||||
|
painter.drawRect(m.rect.x(), m.rect.y(), half, half);
|
||||||
|
painter.drawRect(m.rect.x() + half, m.rect.y() + half, half, half);
|
||||||
|
painter.setBrush(m.color);
|
||||||
|
painter.drawRoundedRect(m.rect, radius, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Mouse – wird aus CodeEditor::mousePressEvent aufgerufen
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
bool ColorIndicator::handleMousePress(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
for (const ColorMatch &m : m_cache)
|
||||||
|
{
|
||||||
|
if (!m.rect.contains(event->pos()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Farb-Dialog öffnen
|
||||||
|
QColorDialog dlg(m.color, m_editor);
|
||||||
|
dlg.setOption(QColorDialog::ShowAlphaChannel, true);
|
||||||
|
dlg.setWindowTitle(QObject::tr("Farbe wählen"));
|
||||||
|
|
||||||
|
if (dlg.exec() != QDialog::Accepted)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QColor newColor = dlg.selectedColor();
|
||||||
|
|
||||||
|
// Ursprünglichen Farbwert im Dokument ersetzen
|
||||||
|
QTextBlock block = m_editor->document()->findBlockByNumber(m.blockNumber);
|
||||||
|
if (!block.isValid())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Farbwert als Hex-String formatieren
|
||||||
|
QString newValue;
|
||||||
|
if (newColor.alpha() < 255)
|
||||||
|
{
|
||||||
|
newValue = newColor.name(QColor::HexArgb); // #aarrggbb
|
||||||
|
// CSS erwartet #rrggbbaa — Bytes umstellen
|
||||||
|
// Qt liefert #aarrggbb, CSS will #rrggbbaa
|
||||||
|
newValue = QString("#%1%2%3%4")
|
||||||
|
.arg(newColor.red(), 2, 16, QChar('0'))
|
||||||
|
.arg(newColor.green(), 2, 16, QChar('0'))
|
||||||
|
.arg(newColor.blue(), 2, 16, QChar('0'))
|
||||||
|
.arg(newColor.alpha(), 2, 16, QChar('0'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newValue = newColor.name(QColor::HexRgb); // #rrggbb
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextCursor cursor(block);
|
||||||
|
cursor.setPosition(block.position() + m.posInBlock);
|
||||||
|
cursor.setPosition(block.position() + m.posInBlock + m.length,
|
||||||
|
QTextCursor::KeepAnchor);
|
||||||
|
cursor.insertText(newValue);
|
||||||
|
|
||||||
|
// Cache invalidieren
|
||||||
|
m_cache.clear();
|
||||||
|
m_cacheFirstBlock = -1;
|
||||||
|
m_cacheLastBlock = -1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Cache aufbauen – nur für sichtbare Blöcke
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void ColorIndicator::rebuildCache()
|
||||||
|
{
|
||||||
|
QTextBlock firstVisible = m_editor->firstVisibleBlockPublic();
|
||||||
|
const int firstNum = firstVisible.blockNumber();
|
||||||
|
|
||||||
|
// Letzten sichtbaren Block bestimmen
|
||||||
|
int lastNum = firstNum;
|
||||||
|
{
|
||||||
|
QTextBlock b = firstVisible;
|
||||||
|
const int bot = m_editor->viewport()->height();
|
||||||
|
while (b.isValid())
|
||||||
|
{
|
||||||
|
const QRectF r = m_editor->blockBoundingGeometryPublic(b)
|
||||||
|
.translated(m_editor->contentOffsetPublic());
|
||||||
|
if (r.top() > bot)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastNum = b.blockNumber();
|
||||||
|
b = b.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache noch aktuell?
|
||||||
|
if (firstNum == m_cacheFirstBlock && lastNum == m_cacheLastBlock)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cache.clear();
|
||||||
|
m_cacheFirstBlock = firstNum;
|
||||||
|
m_cacheLastBlock = lastNum;
|
||||||
|
|
||||||
|
const int squareSize = m_editor->fontMetrics().height() - 4;
|
||||||
|
const int scrollX = m_editor->horizontalScrollBar()->value();
|
||||||
|
|
||||||
|
QTextBlock block = firstVisible;
|
||||||
|
while (block.isValid() && block.blockNumber() <= lastNum)
|
||||||
|
{
|
||||||
|
const QRectF blockRect = m_editor->blockBoundingGeometryPublic(block)
|
||||||
|
.translated(m_editor->contentOffsetPublic());
|
||||||
|
|
||||||
|
const QList<ColorMatch> found = findColorsInBlock(block.text(),
|
||||||
|
block.blockNumber());
|
||||||
|
for (ColorMatch m : found)
|
||||||
|
{
|
||||||
|
// X-Position des Farbwerts im Viewport berechnen
|
||||||
|
const QTextLayout *layout = block.layout();
|
||||||
|
if (!layout || layout->lineCount() == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QTextLine line = layout->lineAt(0);
|
||||||
|
// Position nach dem Ende des Farbwerts
|
||||||
|
const qreal endCharX = line.cursorToX(m.posInBlock + m.length);
|
||||||
|
const int x = static_cast<int>(blockRect.left() + endCharX)
|
||||||
|
- scrollX + 3;
|
||||||
|
|
||||||
|
if (x + squareSize > m_editor->viewport()->width())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int y = static_cast<int>(blockRect.top())
|
||||||
|
+ (static_cast<int>(blockRect.height()) - squareSize) / 2;
|
||||||
|
|
||||||
|
m.rect = QRect(x, y, squareSize, squareSize);
|
||||||
|
m_cache.append(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Farbwerte in einer Zeile suchen
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
QList<ColorIndicator::ColorMatch> ColorIndicator::findColorsInBlock(
|
||||||
|
const QString &text, int blockNumber) const
|
||||||
|
{
|
||||||
|
QList<ColorMatch> result;
|
||||||
|
|
||||||
|
QRegularExpressionMatchIterator it = s_colorRegex.globalMatch(text);
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
QRegularExpressionMatch match = it.next();
|
||||||
|
const QString token = match.captured(0);
|
||||||
|
const QColor color = parseColor(token);
|
||||||
|
|
||||||
|
if (!color.isValid())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorMatch m;
|
||||||
|
m.blockNumber = blockNumber;
|
||||||
|
m.posInBlock = static_cast<int>(match.capturedStart());
|
||||||
|
m.length = static_cast<int>(match.capturedLength());
|
||||||
|
m.color = color;
|
||||||
|
result.append(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Farb-Parser
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
QColor ColorIndicator::parseColor(const QString &token)
|
||||||
|
{
|
||||||
|
const QString t = token.trimmed();
|
||||||
|
|
||||||
|
if (t.startsWith('#')) { return parseHex(t); }
|
||||||
|
if (t.startsWith("rgba", Qt::CaseInsensitive)) { return parseRgba(t); }
|
||||||
|
if (t.startsWith("rgb", Qt::CaseInsensitive)) { return parseRgb(t); }
|
||||||
|
if (t.startsWith("hsla", Qt::CaseInsensitive)) { return parseHsla(t); }
|
||||||
|
if (t.startsWith("hsl", Qt::CaseInsensitive)) { return parseHsl(t); }
|
||||||
|
|
||||||
|
return QColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ColorIndicator::parseHex(const QString &s)
|
||||||
|
{
|
||||||
|
// #rgb → #rrggbb
|
||||||
|
if (s.length() == 4)
|
||||||
|
{
|
||||||
|
return QColor(QString("#%1%1%2%2%3%3")
|
||||||
|
.arg(s[1]).arg(s[2]).arg(s[3]));
|
||||||
|
}
|
||||||
|
// #rrggbb
|
||||||
|
if (s.length() == 7)
|
||||||
|
{
|
||||||
|
return QColor(s);
|
||||||
|
}
|
||||||
|
// #rrggbbaa (CSS) → Qt braucht #aarrggbb
|
||||||
|
if (s.length() == 9)
|
||||||
|
{
|
||||||
|
const QString rr = s.mid(1, 2);
|
||||||
|
const QString gg = s.mid(3, 2);
|
||||||
|
const QString bb = s.mid(5, 2);
|
||||||
|
const QString aa = s.mid(7, 2);
|
||||||
|
return QColor(QString("#%1%2%3%4").arg(aa, rr, gg, bb));
|
||||||
|
}
|
||||||
|
return QColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ColorIndicator::parseRgb(const QString &s)
|
||||||
|
{
|
||||||
|
// rgb(r, g, b)
|
||||||
|
static const QRegularExpression re(
|
||||||
|
R"(rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))",
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
QRegularExpressionMatch m = re.match(s);
|
||||||
|
if (!m.hasMatch()) { return QColor(); }
|
||||||
|
return QColor(m.captured(1).toInt(),
|
||||||
|
m.captured(2).toInt(),
|
||||||
|
m.captured(3).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ColorIndicator::parseRgba(const QString &s)
|
||||||
|
{
|
||||||
|
// rgba(r, g, b, a) — a ist 0.0–1.0
|
||||||
|
static const QRegularExpression re(
|
||||||
|
R"(rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([0-9.]+)\s*\))",
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
QRegularExpressionMatch m = re.match(s);
|
||||||
|
if (!m.hasMatch()) { return QColor(); }
|
||||||
|
return QColor(m.captured(1).toInt(),
|
||||||
|
m.captured(2).toInt(),
|
||||||
|
m.captured(3).toInt(),
|
||||||
|
qRound(m.captured(4).toDouble() * 255.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ColorIndicator::parseHsl(const QString &s)
|
||||||
|
{
|
||||||
|
// hsl(h, s%, l%)
|
||||||
|
static const QRegularExpression re(
|
||||||
|
R"(hsl\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\))",
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
QRegularExpressionMatch m = re.match(s);
|
||||||
|
if (!m.hasMatch()) { return QColor(); }
|
||||||
|
QColor c;
|
||||||
|
c.setHsl(m.captured(1).toInt(),
|
||||||
|
qRound(m.captured(2).toInt() * 2.55),
|
||||||
|
qRound(m.captured(3).toInt() * 2.55));
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ColorIndicator::parseHsla(const QString &s)
|
||||||
|
{
|
||||||
|
// hsla(h, s%, l%, a)
|
||||||
|
static const QRegularExpression re(
|
||||||
|
R"(hsla\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([0-9.]+)\s*\))",
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
QRegularExpressionMatch m = re.match(s);
|
||||||
|
if (!m.hasMatch()) { return QColor(); }
|
||||||
|
QColor c;
|
||||||
|
c.setHsl(m.captured(1).toInt(),
|
||||||
|
qRound(m.captured(2).toInt() * 2.55),
|
||||||
|
qRound(m.captured(3).toInt() * 2.55),
|
||||||
|
qRound(m.captured(4).toDouble() * 255.0));
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
67
src/barecode/src/editor/ColorIndicator.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QList>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class CodeEditor;
|
||||||
|
class QPainter;
|
||||||
|
class QPaintEvent;
|
||||||
|
class QMouseEvent;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ColorIndicator – Zeichnet kleine Farbquadrate neben CSS-Farbwerten und
|
||||||
|
// öffnet einen QColorDialog wenn der Nutzer darauf klickt.
|
||||||
|
//
|
||||||
|
// Unterstützte Formate:
|
||||||
|
// #rgb #rrggbb #rrggbbaa
|
||||||
|
// rgb(r, g, b) rgba(r, g, b, a)
|
||||||
|
// hsl(h, s%, l%) hsla(h, s%, l%, a)
|
||||||
|
// 140 benannte CSS-Farben (red, blue, cornflowerblue, ...)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
class ColorIndicator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ColorIndicator(CodeEditor *editor);
|
||||||
|
|
||||||
|
// Wird aus CodeEditor::paintEvent aufgerufen
|
||||||
|
void paint(QPainter &painter, QPaintEvent *event);
|
||||||
|
|
||||||
|
// Wird aus CodeEditor::mousePressEvent aufgerufen
|
||||||
|
// Gibt true zurück wenn der Klick auf einem Farbquadrat war
|
||||||
|
bool handleMousePress(QMouseEvent *event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ColorMatch
|
||||||
|
{
|
||||||
|
QRect rect; // Position des Quadrats im Viewport
|
||||||
|
QColor color; // Erkannte Farbe
|
||||||
|
int blockNumber;
|
||||||
|
int posInBlock; // Zeichenposition des Farbwerts im Block
|
||||||
|
int length; // Länge des Farbwerts im Text
|
||||||
|
};
|
||||||
|
|
||||||
|
void rebuildCache();
|
||||||
|
QList<ColorMatch> findColorsInBlock(const QString &text,
|
||||||
|
int blockNumber) const;
|
||||||
|
|
||||||
|
static QColor parseColor(const QString &token);
|
||||||
|
static QColor parseHex(const QString &s);
|
||||||
|
static QColor parseRgb(const QString &s);
|
||||||
|
static QColor parseRgba(const QString &s);
|
||||||
|
static QColor parseHsl(const QString &s);
|
||||||
|
static QColor parseHsla(const QString &s);
|
||||||
|
|
||||||
|
CodeEditor *m_editor = nullptr;
|
||||||
|
QList<ColorMatch> m_cache;
|
||||||
|
int m_cacheFirstBlock = -1;
|
||||||
|
int m_cacheLastBlock = -1;
|
||||||
|
|
||||||
|
// Kombinierter Regex für alle Farbformate
|
||||||
|
static const QRegularExpression s_colorRegex;
|
||||||
|
};
|
||||||
258
src/barecode/src/editor/EditorPanel.cpp
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
#include "EditorPanel.h"
|
||||||
|
#include "EditorTab.h"
|
||||||
|
#include "CodeEditor.h"
|
||||||
|
#include "SearchPanel.h"
|
||||||
|
#include "FileSearchPanel.h"
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTextDocument>
|
||||||
|
#include <QTextBlock>
|
||||||
|
|
||||||
|
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_fileSearch = new FileSearchPanel(this);
|
||||||
|
|
||||||
|
m_layout->addWidget(m_tabWidget, 1);
|
||||||
|
m_layout->addWidget(m_searchPanel, 0);
|
||||||
|
m_layout->addWidget(m_fileSearch, 0);
|
||||||
|
|
||||||
|
connect(m_tabWidget, &QTabWidget::tabCloseRequested,
|
||||||
|
this, &EditorPanel::onTabCloseRequested);
|
||||||
|
|
||||||
|
connect(m_tabWidget, &QTabWidget::currentChanged,
|
||||||
|
this, &EditorPanel::onCurrentTabChanged);
|
||||||
|
|
||||||
|
connect(m_fileSearch, &FileSearchPanel::fileLineRequested,
|
||||||
|
this, &EditorPanel::goToLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Änderungsindikator im Tab-Titel (● = ungespeichert)
|
||||||
|
connect(tab->editor()->document(), &QTextDocument::modificationChanged,
|
||||||
|
this, [this, tab](bool modified)
|
||||||
|
{
|
||||||
|
const int idx = m_tabWidget->indexOf(tab);
|
||||||
|
if (idx == -1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QString name = QFileInfo(tab->filePath()).fileName();
|
||||||
|
m_tabWidget->setTabText(idx, modified ? "● " + name : name);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tab-Titel nach "Speichern unter" aktualisieren (neuer Dateiname, kein Punkt)
|
||||||
|
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_fileSearch->hide();
|
||||||
|
m_searchPanel->activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPanel::showFileSearchPanel()
|
||||||
|
{
|
||||||
|
m_searchPanel->hide();
|
||||||
|
m_fileSearch->activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPanel::setSearchRoot(const QString &path)
|
||||||
|
{
|
||||||
|
m_fileSearch->setSearchRoot(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPanel::goToLine(const QString &filePath, int line)
|
||||||
|
{
|
||||||
|
// Datei öffnen falls noch nicht geöffnet
|
||||||
|
openFile(filePath);
|
||||||
|
|
||||||
|
EditorTab *tab = m_openTabs.value(filePath, nullptr);
|
||||||
|
if (!tab)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_tabWidget->setCurrentWidget(tab);
|
||||||
|
|
||||||
|
// Zur gewünschten Zeile springen
|
||||||
|
CodeEditor *editor = tab->editor();
|
||||||
|
QTextBlock block = editor->document()->findBlockByLineNumber(line - 1);
|
||||||
|
if (block.isValid())
|
||||||
|
{
|
||||||
|
QTextCursor cursor(block);
|
||||||
|
cursor.movePosition(QTextCursor::StartOfBlock);
|
||||||
|
editor->setTextCursor(cursor);
|
||||||
|
editor->centerCursor();
|
||||||
|
editor->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList EditorPanel::openFilePaths() const
|
||||||
|
{
|
||||||
|
QStringList paths;
|
||||||
|
for (int i = 0; i < m_tabWidget->count(); ++i)
|
||||||
|
{
|
||||||
|
EditorTab *tab = qobject_cast<EditorTab *>(m_tabWidget->widget(i));
|
||||||
|
if (tab)
|
||||||
|
{
|
||||||
|
paths.append(tab->filePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString EditorPanel::activeFilePath() const
|
||||||
|
{
|
||||||
|
EditorTab *tab = currentTab();
|
||||||
|
return tab ? tab->filePath() : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPanel::restoreSession(const QStringList &files, const QString &activeFile)
|
||||||
|
{
|
||||||
|
for (const QString &path : files)
|
||||||
|
{
|
||||||
|
if (QFile::exists(path))
|
||||||
|
{
|
||||||
|
openFile(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktiven Tab wiederherstellen
|
||||||
|
if (!activeFile.isEmpty())
|
||||||
|
{
|
||||||
|
const int idx = findTabForFile(activeFile);
|
||||||
|
if (idx != -1)
|
||||||
|
{
|
||||||
|
m_tabWidget->setCurrentIndex(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
60
src/barecode/src/editor/EditorPanel.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QTabWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class EditorTab;
|
||||||
|
class Settings;
|
||||||
|
class SearchPanel;
|
||||||
|
class FileSearchPanel;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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 setSearchRoot(const QString &path);
|
||||||
|
void showSearchPanel();
|
||||||
|
void showFileSearchPanel();
|
||||||
|
void goToLine(const QString &filePath, int line);
|
||||||
|
|
||||||
|
// Session
|
||||||
|
QStringList openFilePaths() const;
|
||||||
|
QString activeFilePath() const;
|
||||||
|
void restoreSession(const QStringList &files, const QString &activeFile);
|
||||||
|
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;
|
||||||
|
FileSearchPanel *m_fileSearch = nullptr;
|
||||||
|
|
||||||
|
QHash<QString, EditorTab *> m_openTabs;
|
||||||
|
};
|
||||||
53
src/barecode/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/barecode/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;
|
||||||
|
};
|
||||||
330
src/barecode/src/editor/FileSearchPanel.cpp
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
#include "FileSearchPanel.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
#include <QFuture>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Konstruktor
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
FileSearchPanel::FileSearchPanel(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
setupUi();
|
||||||
|
hide();
|
||||||
|
|
||||||
|
m_watcher = new QFutureWatcher<QList<Match>>(this);
|
||||||
|
connect(m_watcher, &QFutureWatcher<QList<Match>>::finished,
|
||||||
|
this, &FileSearchPanel::onSearchFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// UI
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void FileSearchPanel::setupUi()
|
||||||
|
{
|
||||||
|
QVBoxLayout *root = new QVBoxLayout(this);
|
||||||
|
root->setContentsMargins(6, 4, 6, 4);
|
||||||
|
root->setSpacing(4);
|
||||||
|
|
||||||
|
// ---- Zeile 1: Suchbegriff ----
|
||||||
|
QHBoxLayout *row1 = new QHBoxLayout();
|
||||||
|
row1->addWidget(new QLabel(tr("Suchen in Dateien:"), this));
|
||||||
|
|
||||||
|
m_searchEdit = new QLineEdit(this);
|
||||||
|
m_searchEdit->setPlaceholderText(tr("Suchbegriff…"));
|
||||||
|
m_searchEdit->setClearButtonEnabled(true);
|
||||||
|
row1->addWidget(m_searchEdit, 1);
|
||||||
|
|
||||||
|
m_btnSearch = new QPushButton(tr("Suchen"), this);
|
||||||
|
m_btnSearch->setDefault(true);
|
||||||
|
row1->addWidget(m_btnSearch);
|
||||||
|
|
||||||
|
m_btnClose = new QPushButton(tr("✕"), this);
|
||||||
|
m_btnClose->setFixedWidth(24);
|
||||||
|
m_btnClose->setFlat(true);
|
||||||
|
m_btnClose->setToolTip(tr("Schließen"));
|
||||||
|
row1->addWidget(m_btnClose);
|
||||||
|
|
||||||
|
root->addLayout(row1);
|
||||||
|
|
||||||
|
// ---- Zeile 2: Optionen + Filter ----
|
||||||
|
QHBoxLayout *row2 = new QHBoxLayout();
|
||||||
|
m_chkCase = new QCheckBox(tr("Groß-/Kleinschreibung"), this);
|
||||||
|
m_chkWord = new QCheckBox(tr("Ganzes Wort"), this);
|
||||||
|
m_chkRegex = new QCheckBox(tr("Regex"), this);
|
||||||
|
|
||||||
|
row2->addWidget(m_chkCase);
|
||||||
|
row2->addWidget(m_chkWord);
|
||||||
|
row2->addWidget(m_chkRegex);
|
||||||
|
row2->addSpacing(12);
|
||||||
|
|
||||||
|
row2->addWidget(new QLabel(tr("Dateitypen:"), this));
|
||||||
|
m_filterEdit = new QLineEdit(this);
|
||||||
|
m_filterEdit->setText("*.html *.php *.css *.js *.c *.cpp *.h");
|
||||||
|
m_filterEdit->setFixedWidth(220);
|
||||||
|
m_filterEdit->setToolTip(tr("Leerzeichen-getrennte Muster, z.B.: *.php *.html"));
|
||||||
|
row2->addWidget(m_filterEdit);
|
||||||
|
row2->addStretch();
|
||||||
|
|
||||||
|
root->addLayout(row2);
|
||||||
|
|
||||||
|
// ---- Fortschritt + Status ----
|
||||||
|
m_progress = new QProgressBar(this);
|
||||||
|
m_progress->setRange(0, 0); // Unbestimmter Modus
|
||||||
|
m_progress->setFixedHeight(4);
|
||||||
|
m_progress->hide();
|
||||||
|
root->addWidget(m_progress);
|
||||||
|
|
||||||
|
m_statusLabel = new QLabel(this);
|
||||||
|
m_statusLabel->setStyleSheet("color: palette(mid);");
|
||||||
|
root->addWidget(m_statusLabel);
|
||||||
|
|
||||||
|
// ---- Ergebnisliste ----
|
||||||
|
m_results = new QTreeWidget(this);
|
||||||
|
m_results->setHeaderHidden(true);
|
||||||
|
m_results->setRootIsDecorated(true);
|
||||||
|
m_results->setIndentation(16);
|
||||||
|
m_results->setUniformRowHeights(true);
|
||||||
|
m_results->setAlternatingRowColors(true);
|
||||||
|
root->addWidget(m_results, 1);
|
||||||
|
|
||||||
|
// ---- Verbindungen ----
|
||||||
|
connect(m_btnSearch, &QPushButton::clicked, this, &FileSearchPanel::onSearch);
|
||||||
|
connect(m_searchEdit, &QLineEdit::returnPressed, this, &FileSearchPanel::onSearch);
|
||||||
|
connect(m_btnClose, &QPushButton::clicked, this, [this]()
|
||||||
|
{
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
connect(m_results, &QTreeWidget::itemActivated,
|
||||||
|
this, &FileSearchPanel::onResultActivated);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Öffentliche Schnittstelle
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void FileSearchPanel::setSearchRoot(const QString &path)
|
||||||
|
{
|
||||||
|
m_searchRoot = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSearchPanel::activate()
|
||||||
|
{
|
||||||
|
show();
|
||||||
|
m_searchEdit->setFocus();
|
||||||
|
m_searchEdit->selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Suche starten
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void FileSearchPanel::onSearch()
|
||||||
|
{
|
||||||
|
const QString needle = m_searchEdit->text().trimmed();
|
||||||
|
if (needle.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_searchRoot.isEmpty())
|
||||||
|
{
|
||||||
|
m_statusLabel->setText(tr("Kein Projektverzeichnis geöffnet."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laufende Suche abbrechen
|
||||||
|
if (m_watcher->isRunning())
|
||||||
|
{
|
||||||
|
m_watcher->cancel();
|
||||||
|
m_watcher->waitForFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_results->clear();
|
||||||
|
m_statusLabel->setText(tr("Suche läuft…"));
|
||||||
|
m_progress->show();
|
||||||
|
m_btnSearch->setEnabled(false);
|
||||||
|
|
||||||
|
const QString root = m_searchRoot;
|
||||||
|
const bool cs = m_chkCase->isChecked();
|
||||||
|
const bool word = m_chkWord->isChecked();
|
||||||
|
const bool regex = m_chkRegex->isChecked();
|
||||||
|
const QStringList extensions = m_filterEdit->text().simplified().split(' ',
|
||||||
|
Qt::SkipEmptyParts);
|
||||||
|
|
||||||
|
QFuture<QList<Match>> future = QtConcurrent::run(
|
||||||
|
[this, root, needle, cs, word, regex, extensions]()
|
||||||
|
{
|
||||||
|
return searchInFiles(root, needle, cs, word, regex, extensions);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
m_watcher->setFuture(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Suchergebnisse anzeigen
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void FileSearchPanel::onSearchFinished()
|
||||||
|
{
|
||||||
|
m_progress->hide();
|
||||||
|
m_btnSearch->setEnabled(true);
|
||||||
|
|
||||||
|
if (m_watcher->isCanceled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QList<Match> matches = m_watcher->result();
|
||||||
|
|
||||||
|
// Ergebnisse gruppiert nach Datei aufbauen
|
||||||
|
QString currentFile;
|
||||||
|
QTreeWidgetItem *fileItem = nullptr;
|
||||||
|
int fileCount = 0;
|
||||||
|
int matchCount = 0;
|
||||||
|
|
||||||
|
for (const Match &m : matches)
|
||||||
|
{
|
||||||
|
if (m.filePath != currentFile)
|
||||||
|
{
|
||||||
|
currentFile = m.filePath;
|
||||||
|
++fileCount;
|
||||||
|
|
||||||
|
fileItem = new QTreeWidgetItem(m_results);
|
||||||
|
fileItem->setText(0, QFileInfo(m.filePath).fileName());
|
||||||
|
fileItem->setToolTip(0, m.filePath);
|
||||||
|
fileItem->setData(0, Qt::UserRole, m.filePath);
|
||||||
|
fileItem->setData(0, Qt::UserRole + 1, -1);
|
||||||
|
|
||||||
|
QFont boldFont = fileItem->font(0);
|
||||||
|
boldFont.setBold(true);
|
||||||
|
fileItem->setFont(0, boldFont);
|
||||||
|
fileItem->setExpanded(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTreeWidgetItem *lineItem = new QTreeWidgetItem(fileItem);
|
||||||
|
lineItem->setText(0, QString(" Zeile %1: %2")
|
||||||
|
.arg(m.line)
|
||||||
|
.arg(m.content.trimmed().left(120)));
|
||||||
|
lineItem->setToolTip(0, m.content.trimmed());
|
||||||
|
lineItem->setData(0, Qt::UserRole, m.filePath);
|
||||||
|
lineItem->setData(0, Qt::UserRole + 1, m.line);
|
||||||
|
|
||||||
|
++matchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datei-Titelzeilen um Trefferanzahl ergänzen
|
||||||
|
for (int i = 0; i < m_results->topLevelItemCount(); ++i)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *item = m_results->topLevelItem(i);
|
||||||
|
const int count = item->childCount();
|
||||||
|
item->setText(0, QString("%1 (%2 Treffer)")
|
||||||
|
.arg(QFileInfo(item->data(0, Qt::UserRole).toString()).fileName())
|
||||||
|
.arg(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchCount == 0)
|
||||||
|
{
|
||||||
|
m_statusLabel->setText(tr("Keine Treffer gefunden."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_statusLabel->setText(tr("%1 Treffer in %2 Datei(en).")
|
||||||
|
.arg(matchCount)
|
||||||
|
.arg(fileCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Klick auf Treffer → Datei + Zeile öffnen
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void FileSearchPanel::onResultActivated(QTreeWidgetItem *item, int /*column*/)
|
||||||
|
{
|
||||||
|
const QString path = item->data(0, Qt::UserRole).toString();
|
||||||
|
const int line = item->data(0, Qt::UserRole + 1).toInt();
|
||||||
|
|
||||||
|
if (path.isEmpty() || line < 0)
|
||||||
|
{
|
||||||
|
// Datei-Titelzeile: nur auf-/zuklappen
|
||||||
|
item->setExpanded(!item->isExpanded());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit fileLineRequested(path, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Eigentliche Suchroutine (läuft in Thread-Pool)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
QList<FileSearchPanel::Match> FileSearchPanel::searchInFiles(
|
||||||
|
const QString &root,
|
||||||
|
const QString &needle,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool wholeWord,
|
||||||
|
bool useRegex,
|
||||||
|
const QStringList &extensions) const
|
||||||
|
{
|
||||||
|
QList<Match> results;
|
||||||
|
|
||||||
|
// Regulären Ausdruck vorbereiten
|
||||||
|
QString pattern = useRegex ? needle : QRegularExpression::escape(needle);
|
||||||
|
if (wholeWord)
|
||||||
|
{
|
||||||
|
pattern = "\\b" + pattern + "\\b";
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegularExpression re(pattern,
|
||||||
|
caseSensitive
|
||||||
|
? QRegularExpression::NoPatternOption
|
||||||
|
: QRegularExpression::CaseInsensitiveOption);
|
||||||
|
|
||||||
|
if (!re.isValid())
|
||||||
|
{
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verzeichnis rekursiv durchsuchen
|
||||||
|
QDirIterator it(root,
|
||||||
|
extensions.isEmpty()
|
||||||
|
? QStringList("*")
|
||||||
|
: extensions,
|
||||||
|
QDir::Files,
|
||||||
|
QDirIterator::Subdirectories);
|
||||||
|
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
if (m_watcher->isCanceled())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString filePath = it.next();
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream stream(&file);
|
||||||
|
stream.setEncoding(QStringConverter::Utf8);
|
||||||
|
|
||||||
|
int lineNumber = 0;
|
||||||
|
while (!stream.atEnd())
|
||||||
|
{
|
||||||
|
++lineNumber;
|
||||||
|
const QString line = stream.readLine();
|
||||||
|
|
||||||
|
if (re.match(line).hasMatch())
|
||||||
|
{
|
||||||
|
results.append({ filePath, lineNumber, line });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
77
src/barecode/src/editor/FileSearchPanel.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QTreeWidgetItem>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// FileSearchPanel – Suche in allen Dateien eines Verzeichnisses.
|
||||||
|
//
|
||||||
|
// Ergebnisse werden als aufklappbare Liste angezeigt:
|
||||||
|
// Dateiname (N Treffer)
|
||||||
|
// └ Zeile 12: <Zeileninhalt>
|
||||||
|
// └ Zeile 34: <Zeileninhalt>
|
||||||
|
//
|
||||||
|
// Klick auf einen Treffer öffnet die Datei im Editor und springt zur Zeile.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
class FileSearchPanel : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FileSearchPanel(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void setSearchRoot(const QString &path);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void activate();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void fileLineRequested(const QString &filePath, int line);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onSearch();
|
||||||
|
void onResultActivated(QTreeWidgetItem *item, int column);
|
||||||
|
void onSearchFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Match
|
||||||
|
{
|
||||||
|
QString filePath;
|
||||||
|
int line;
|
||||||
|
QString content;
|
||||||
|
};
|
||||||
|
|
||||||
|
void setupUi();
|
||||||
|
QList<Match> searchInFiles(const QString &root,
|
||||||
|
const QString &needle,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool wholeWord,
|
||||||
|
bool useRegex,
|
||||||
|
const QStringList &extensions) const;
|
||||||
|
|
||||||
|
QString m_searchRoot;
|
||||||
|
|
||||||
|
QLineEdit *m_searchEdit = nullptr;
|
||||||
|
QCheckBox *m_chkCase = nullptr;
|
||||||
|
QCheckBox *m_chkWord = nullptr;
|
||||||
|
QCheckBox *m_chkRegex = nullptr;
|
||||||
|
QLineEdit *m_filterEdit = nullptr; // Dateiendungen-Filter
|
||||||
|
QPushButton *m_btnSearch = nullptr;
|
||||||
|
QPushButton *m_btnClose = nullptr;
|
||||||
|
QLabel *m_statusLabel = nullptr;
|
||||||
|
QTreeWidget *m_results = nullptr;
|
||||||
|
QProgressBar *m_progress = nullptr;
|
||||||
|
|
||||||
|
QFutureWatcher<QList<Match>> *m_watcher = nullptr;
|
||||||
|
};
|
||||||
18
src/barecode/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/barecode/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/barecode/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/barecode/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;
|
||||||
|
};
|
||||||
222
src/barecode/src/editor/SignatureHelper.cpp
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
#include "SignatureHelper.h"
|
||||||
|
#include "SignatureTooltip.h"
|
||||||
|
#include "CodeEditor.h"
|
||||||
|
|
||||||
|
#include <QTextCursor>
|
||||||
|
#include <QTextBlock>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
SignatureHelper::SignatureHelper(CodeEditor *editor)
|
||||||
|
: QObject(editor)
|
||||||
|
, m_editor(editor)
|
||||||
|
{
|
||||||
|
// Tooltip als Kind des Viewports — bleibt im Fenster
|
||||||
|
m_tooltip = new SignatureTooltip(editor->window());
|
||||||
|
|
||||||
|
loadDatabase();
|
||||||
|
|
||||||
|
connect(m_editor, &CodeEditor::cursorPositionChanged,
|
||||||
|
this, &SignatureHelper::onCursorPositionChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Datenbank laden
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void SignatureHelper::loadDatabase()
|
||||||
|
{
|
||||||
|
QFile f(":/php_functions.json");
|
||||||
|
if (!f.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
|
||||||
|
if (!doc.isArray())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QJsonValue &val : doc.array())
|
||||||
|
{
|
||||||
|
const QJsonObject obj = val.toObject();
|
||||||
|
const QString name = obj["name"].toString();
|
||||||
|
if (name.isEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionInfo info;
|
||||||
|
info.signature = obj["signature"].toString();
|
||||||
|
info.description = obj["desc"].toString();
|
||||||
|
m_functions.insert(name.toLower(), info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Cursor-Bewegung auswerten
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void SignatureHelper::onCursorPositionChanged()
|
||||||
|
{
|
||||||
|
const QTextCursor cursor = m_editor->textCursor();
|
||||||
|
const QString block = cursor.block().text();
|
||||||
|
const int col = cursor.columnNumber();
|
||||||
|
const QString leftText = block.left(col);
|
||||||
|
|
||||||
|
const QString funcName = extractFunctionName(leftText);
|
||||||
|
|
||||||
|
if (funcName.isEmpty())
|
||||||
|
{
|
||||||
|
m_tooltip->hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Klammern zählen — wenn alle geschlossen, Tooltip ausblenden
|
||||||
|
if (countOpenParens(leftText) <= 0)
|
||||||
|
{
|
||||||
|
m_tooltip->hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString key = funcName.toLower();
|
||||||
|
if (!m_functions.contains(key))
|
||||||
|
{
|
||||||
|
m_tooltip->hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FunctionInfo &info = m_functions[key];
|
||||||
|
|
||||||
|
// Position unter dem Cursor berechnen
|
||||||
|
const QRect cursorRect = m_editor->cursorRect(cursor);
|
||||||
|
const QPoint globalPos = m_editor->viewport()->mapToGlobal(
|
||||||
|
QPoint(cursorRect.left(), cursorRect.bottom() + 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
m_tooltip->showSignature(info.signature, info.description, globalPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Funktionsnamen links vor der öffnenden Klammer extrahieren
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
QString SignatureHelper::extractFunctionName(const QString &text) const
|
||||||
|
{
|
||||||
|
// Wir suchen das letzte '(' das zu einem Funktionsnamen gehört.
|
||||||
|
// Dabei müssen wir verschachtelte Klammern korrekt behandeln.
|
||||||
|
int depth = 0;
|
||||||
|
int openPos = -1;
|
||||||
|
|
||||||
|
for (int i = text.length() - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
const QChar ch = text[i];
|
||||||
|
if (ch == ')')
|
||||||
|
{
|
||||||
|
++depth;
|
||||||
|
}
|
||||||
|
else if (ch == '(')
|
||||||
|
{
|
||||||
|
if (depth == 0)
|
||||||
|
{
|
||||||
|
openPos = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
--depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openPos <= 0)
|
||||||
|
{
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funktionsnamen direkt links von '(' lesen
|
||||||
|
int end = openPos - 1;
|
||||||
|
|
||||||
|
// Leerzeichen überspringen
|
||||||
|
while (end >= 0 && text[end].isSpace())
|
||||||
|
{
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end < 0)
|
||||||
|
{
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bezeichner-Zeichen sammeln (Buchstaben, Ziffern, _, :, \)
|
||||||
|
int start = end;
|
||||||
|
while (start > 0 &&
|
||||||
|
(text[start - 1].isLetterOrNumber() ||
|
||||||
|
text[start - 1] == '_' ||
|
||||||
|
text[start - 1] == ':' ||
|
||||||
|
text[start - 1] == '\\'))
|
||||||
|
{
|
||||||
|
--start;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString name = text.mid(start, end - start + 1);
|
||||||
|
|
||||||
|
// Schlüsselwörter und leere Namen ausschließen
|
||||||
|
static const QStringList keywords = {
|
||||||
|
"if", "else", "elseif", "while", "for", "foreach",
|
||||||
|
"switch", "match", "catch", "function", "fn"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (name.isEmpty() || keywords.contains(name.toLower()))
|
||||||
|
{
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nur den letzten Teil nach :: oder -> nehmen
|
||||||
|
const int colonPos = name.lastIndexOf("::");
|
||||||
|
const int arrowPos = name.lastIndexOf("->");
|
||||||
|
const int backslashPos = name.lastIndexOf("\\");
|
||||||
|
const int splitPos = qMax(backslashPos, qMax(colonPos, arrowPos));
|
||||||
|
|
||||||
|
if (splitPos >= 0)
|
||||||
|
{
|
||||||
|
return name.mid(splitPos + (name[splitPos] == ':' ? 2 : (name[splitPos] == '\\' ? 1 : 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Offene Klammern zählen
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
int SignatureHelper::countOpenParens(const QString &text) const
|
||||||
|
{
|
||||||
|
int depth = 0;
|
||||||
|
bool inString = false;
|
||||||
|
QChar stringChar;
|
||||||
|
|
||||||
|
for (int i = 0; i < text.length(); ++i)
|
||||||
|
{
|
||||||
|
const QChar ch = text[i];
|
||||||
|
|
||||||
|
// Einfache String-Erkennung (kein vollständiger PHP-Parser)
|
||||||
|
if (!inString && (ch == '\'' || ch == '"'))
|
||||||
|
{
|
||||||
|
inString = true;
|
||||||
|
stringChar = ch;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inString)
|
||||||
|
{
|
||||||
|
if (ch == stringChar && (i == 0 || text[i - 1] != '\\'))
|
||||||
|
{
|
||||||
|
inString = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '(') { ++depth; }
|
||||||
|
else if (ch == ')') { --depth; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
51
src/barecode/src/editor/SignatureHelper.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
|
class CodeEditor;
|
||||||
|
class SignatureTooltip;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// SignatureHelper – Lädt die PHP-Funktionsdatenbank und zeigt beim Tippen
|
||||||
|
// automatisch die passende Funktionssignatur als Tooltip.
|
||||||
|
//
|
||||||
|
// Logik:
|
||||||
|
// • Bei jedem Tastendruck: Text links vom Cursor analysieren
|
||||||
|
// • Wenn "funktionsname(" erkannt wird → Tooltip anzeigen
|
||||||
|
// • Wenn ")" die öffnende Klammer schließt → Tooltip verstecken
|
||||||
|
// • Wenn Cursor sich weg bewegt → Tooltip verstecken
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
class SignatureHelper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SignatureHelper(CodeEditor *editor);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onCursorPositionChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct FunctionInfo
|
||||||
|
{
|
||||||
|
QString signature;
|
||||||
|
QString description;
|
||||||
|
};
|
||||||
|
|
||||||
|
void loadDatabase();
|
||||||
|
void loadProjectFunctions();
|
||||||
|
|
||||||
|
// Extrahiert den Funktionsnamen direkt links vor dem letzten '('
|
||||||
|
// Gibt leeren String zurück wenn kein Kontext gefunden
|
||||||
|
QString extractFunctionName(const QString &textUpToCursor) const;
|
||||||
|
|
||||||
|
// Zählt offene Klammern — bei 0 ist der Aufruf abgeschlossen
|
||||||
|
int countOpenParens(const QString &textUpToCursor) const;
|
||||||
|
|
||||||
|
CodeEditor *m_editor = nullptr;
|
||||||
|
SignatureTooltip *m_tooltip = nullptr;
|
||||||
|
|
||||||
|
QHash<QString, FunctionInfo> m_functions; // name → info
|
||||||
|
};
|
||||||
91
src/barecode/src/editor/SignatureTooltip.cpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include "SignatureTooltip.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QScreen>
|
||||||
|
|
||||||
|
SignatureTooltip::SignatureTooltip(QWidget *parent)
|
||||||
|
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
||||||
|
{
|
||||||
|
setFrameShape(QFrame::StyledPanel);
|
||||||
|
setFrameShadow(QFrame::Raised);
|
||||||
|
setAttribute(Qt::WA_ShowWithoutActivating);
|
||||||
|
|
||||||
|
// Dezentes Styling passend zu Hell- und Dunkeltheme
|
||||||
|
setStyleSheet(
|
||||||
|
"SignatureTooltip {"
|
||||||
|
" background: palette(toolTipBase);"
|
||||||
|
" border: 1px solid palette(mid);"
|
||||||
|
" border-radius: 4px;"
|
||||||
|
" padding: 4px;"
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
m_layout = new QVBoxLayout(this);
|
||||||
|
m_layout->setContentsMargins(8, 6, 8, 6);
|
||||||
|
m_layout->setSpacing(3);
|
||||||
|
|
||||||
|
// Signatur — Monospace, deutlich hervorgehoben
|
||||||
|
m_sigLabel = new QLabel(this);
|
||||||
|
m_sigLabel->setTextFormat(Qt::PlainText);
|
||||||
|
m_sigLabel->setWordWrap(false);
|
||||||
|
QFont sigFont = m_sigLabel->font();
|
||||||
|
sigFont.setFamily("Monospace");
|
||||||
|
sigFont.setStyleHint(QFont::Monospace);
|
||||||
|
sigFont.setPointSize(sigFont.pointSize());
|
||||||
|
m_sigLabel->setFont(sigFont);
|
||||||
|
m_sigLabel->setStyleSheet("color: palette(toolTipText); font-weight: bold;");
|
||||||
|
m_layout->addWidget(m_sigLabel);
|
||||||
|
|
||||||
|
// Beschreibung — kleiner, gedimmt
|
||||||
|
m_descLabel = new QLabel(this);
|
||||||
|
m_descLabel->setTextFormat(Qt::PlainText);
|
||||||
|
m_descLabel->setWordWrap(false);
|
||||||
|
m_descLabel->setStyleSheet("color: palette(mid);");
|
||||||
|
QFont descFont = m_descLabel->font();
|
||||||
|
descFont.setPointSize(qMax(descFont.pointSize() - 1, 8));
|
||||||
|
m_descLabel->setFont(descFont);
|
||||||
|
m_layout->addWidget(m_descLabel);
|
||||||
|
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignatureTooltip::showSignature(const QString &signature,
|
||||||
|
const QString &description,
|
||||||
|
const QPoint &globalPos)
|
||||||
|
{
|
||||||
|
m_sigLabel->setText(signature);
|
||||||
|
|
||||||
|
if (description.isEmpty())
|
||||||
|
{
|
||||||
|
m_descLabel->hide();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_descLabel->setText(description);
|
||||||
|
m_descLabel->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustSize();
|
||||||
|
|
||||||
|
// Position so wählen dass das Popup nicht aus dem Bildschirm ragt
|
||||||
|
QPoint pos = globalPos;
|
||||||
|
const QRect screen = QApplication::primaryScreen()->availableGeometry();
|
||||||
|
|
||||||
|
if (pos.x() + width() > screen.right())
|
||||||
|
{
|
||||||
|
pos.setX(screen.right() - width() - 4);
|
||||||
|
}
|
||||||
|
if (pos.y() + height() > screen.bottom())
|
||||||
|
{
|
||||||
|
pos.setY(globalPos.y() - height() - 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
move(pos);
|
||||||
|
show();
|
||||||
|
raise();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignatureTooltip::hide()
|
||||||
|
{
|
||||||
|
QFrame::hide();
|
||||||
|
}
|
||||||
29
src/barecode/src/editor/SignatureTooltip.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// SignatureTooltip – Schwebendes Popup das die Signatur einer Funktion zeigt.
|
||||||
|
// Erscheint unter dem Cursor, verschwindet automatisch wenn der Nutzer
|
||||||
|
// die Klammer schließt oder den Kontext verlässt.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
class SignatureTooltip : public QFrame
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SignatureTooltip(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void showSignature(const QString &signature,
|
||||||
|
const QString &description,
|
||||||
|
const QPoint &globalPos);
|
||||||
|
void hide();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVBoxLayout *m_layout = nullptr;
|
||||||
|
QLabel *m_sigLabel = nullptr;
|
||||||
|
QLabel *m_descLabel = nullptr;
|
||||||
|
};
|
||||||
17
src/barecode/src/filetree/CMakeLists.txt
Normal 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}/..
|
||||||
|
)
|
||||||
259
src/barecode/src/filetree/FileTreePanel.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/barecode/src/filetree/FileTreePanel.h
Normal 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;
|
||||||
|
};
|
||||||
19
src/barecode/src/highlighter/CMakeLists.txt
Normal 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}/..
|
||||||
|
)
|
||||||
45
src/barecode/src/highlighter/HighlighterFactory.cpp
Normal 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;
|
||||||
|
}
|
||||||