Englisch hinzugefügt
@@ -16,8 +16,6 @@ set(CMAKE_AUTOUIC ON)
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Plattform-Erkennung
|
# Plattform-Erkennung
|
||||||
# Haiku definiert kein eigenes CMake-Flag — wir erkennen es über den
|
|
||||||
# Systemnamen. CMAKE_SYSTEM_NAME ist "Haiku" auf Haiku OS.
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(PLATFORM_WINDOWS TRUE)
|
set(PLATFORM_WINDOWS TRUE)
|
||||||
@@ -37,41 +35,60 @@ endif()
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Qt6
|
# Qt6
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
find_package(Qt6 REQUIRED COMPONENTS
|
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Concurrent)
|
||||||
Core
|
|
||||||
Gui
|
|
||||||
Widgets
|
|
||||||
Concurrent
|
|
||||||
)
|
|
||||||
|
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
|
|
||||||
# Include cmake modules
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
# Collect sources
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Übersetzungen — lrelease → .qm → ins Build-Verzeichnis
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
find_program(LRELEASE_EXECUTABLE
|
||||||
|
NAMES lrelease lrelease-qt6
|
||||||
|
HINTS "${Qt6_DIR}/../../../bin"
|
||||||
|
REQUIRED
|
||||||
|
)
|
||||||
|
|
||||||
|
set(TS_FILES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/translations/barecode_de.ts
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/translations/barecode_en.ts
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QM_FILES)
|
||||||
|
foreach(TS_FILE ${TS_FILES})
|
||||||
|
get_filename_component(TS_NAME ${TS_FILE} NAME_WE)
|
||||||
|
set(QM_FILE "${CMAKE_CURRENT_BINARY_DIR}/translations/${TS_NAME}.qm")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${QM_FILE}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/translations"
|
||||||
|
COMMAND ${LRELEASE_EXECUTABLE} "${TS_FILE}" -qm "${QM_FILE}"
|
||||||
|
DEPENDS "${TS_FILE}"
|
||||||
|
COMMENT "lrelease: ${TS_NAME}.qm"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
list(APPEND QM_FILES "${QM_FILE}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
add_custom_target(BareCode_translations ALL DEPENDS ${QM_FILES})
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# Resources
|
# Resources
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
qt_add_resources(BARECODE_RESOURCES resources/resources.qrc)
|
qt_add_resources(BARECODE_RESOURCES resources/resources.qrc)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Executable
|
# Executable
|
||||||
# Unter Windows: .rc-Datei für EXE-Icon einbinden
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
if(PLATFORM_WINDOWS)
|
if(PLATFORM_WINDOWS)
|
||||||
qt_add_executable(BareCode
|
qt_add_executable(BareCode main.cpp BareCode.rc ${BARECODE_RESOURCES})
|
||||||
main.cpp
|
|
||||||
BareCode.rc
|
|
||||||
${BARECODE_RESOURCES}
|
|
||||||
)
|
|
||||||
else()
|
else()
|
||||||
qt_add_executable(BareCode
|
qt_add_executable(BareCode main.cpp ${BARECODE_RESOURCES})
|
||||||
main.cpp
|
|
||||||
${BARECODE_RESOURCES}
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_dependencies(BareCode BareCode_translations)
|
||||||
|
|
||||||
target_link_libraries(BareCode PRIVATE
|
target_link_libraries(BareCode PRIVATE
|
||||||
BareCode_Core
|
BareCode_Core
|
||||||
BareCode_Editor
|
BareCode_Editor
|
||||||
@@ -99,7 +116,11 @@ install(FILES LICENSE
|
|||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/barecode
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/barecode
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---- Linux (FreeDesktop) --------------------------------------------------
|
# .qm-Dateien installieren
|
||||||
|
install(FILES ${QM_FILES}
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/BareCode/translations
|
||||||
|
)
|
||||||
|
|
||||||
if(PLATFORM_LINUX)
|
if(PLATFORM_LINUX)
|
||||||
foreach(SIZE 16 32 48 64 128 256 512)
|
foreach(SIZE 16 32 48 64 128 256 512)
|
||||||
install(FILES resources/icon_${SIZE}.png
|
install(FILES resources/icon_${SIZE}.png
|
||||||
@@ -107,35 +128,27 @@ if(PLATFORM_LINUX)
|
|||||||
RENAME barecode.png
|
RENAME barecode.png
|
||||||
)
|
)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
install(FILES BareCode.desktop
|
install(FILES BareCode.desktop
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
|
||||||
)
|
)
|
||||||
endif()
|
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)
|
if(PLATFORM_HAIKU)
|
||||||
install(FILES resources/icon_256.png
|
install(FILES resources/icon_256.png
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/BareCode
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/BareCode
|
||||||
RENAME BareCode.png
|
RENAME BareCode.png
|
||||||
)
|
)
|
||||||
|
|
||||||
# mimeset nach der Installation ausführen um MIME-Typ zu registrieren
|
|
||||||
install(CODE "
|
install(CODE "
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND mimeset -f \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/BareCode\"
|
COMMAND mimeset -f \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/BareCode\"
|
||||||
RESULT_VARIABLE _mimeset_result
|
RESULT_VARIABLE _mimeset_result
|
||||||
)
|
)
|
||||||
if(NOT _mimeset_result EQUAL 0)
|
if(NOT _mimeset_result EQUAL 0)
|
||||||
message(WARNING \"mimeset konnte nicht ausgeführt werden – MIME-Typ muss manuell gesetzt werden.\")
|
message(WARNING \"mimeset konnte nicht ausgeführt werden.\")
|
||||||
endif()
|
endif()
|
||||||
")
|
")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ---- macOS ----------------------------------------------------------------
|
|
||||||
if(PLATFORM_MACOS)
|
if(PLATFORM_MACOS)
|
||||||
set_target_properties(BareCode PROPERTIES
|
set_target_properties(BareCode PROPERTIES
|
||||||
MACOSX_BUNDLE TRUE
|
MACOSX_BUNDLE TRUE
|
||||||
@@ -144,4 +157,3 @@ if(PLATFORM_MACOS)
|
|||||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
|
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
133
bauen.sh
Executable file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# BareCode – Build-Skript
|
||||||
|
# Verwendung: ./bauen.sh [release|debug|clean|install]
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
BUILD_TYPE="${1:-release}"
|
||||||
|
BUILD_DIR="build"
|
||||||
|
|
||||||
|
# Farben
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${BLUE}==>${NC} $1"; }
|
||||||
|
success() { echo -e "${GREEN}==>${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}==>${NC} $1"; }
|
||||||
|
error() { echo -e "${RED}==>${NC} $1"; exit 1; }
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# clean
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ "$BUILD_TYPE" = "clean" ]; then
|
||||||
|
info "Build-Verzeichnis wird gelöscht…"
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
success "Fertig."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Abhängigkeiten prüfen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
info "Prüfe Abhängigkeiten…"
|
||||||
|
|
||||||
|
command -v cmake &>/dev/null || error "cmake nicht gefunden. Installation: sudo pacman -S cmake"
|
||||||
|
command -v ninja &>/dev/null || error "ninja nicht gefunden. Installation: sudo pacman -S ninja"
|
||||||
|
command -v lrelease &>/dev/null || \
|
||||||
|
command -v lrelease-qt6 &>/dev/null || \
|
||||||
|
error "lrelease nicht gefunden. Installation: sudo pacman -S qt6-tools"
|
||||||
|
|
||||||
|
# Qt6 prüfen
|
||||||
|
if ! pkg-config --exists Qt6Core 2>/dev/null; then
|
||||||
|
if ! cmake --find-package -DNAME=Qt6 -DCOMPILER_ID=GNU \
|
||||||
|
-DLANGUAGE=CXX -DMODE=EXIST &>/dev/null 2>&1; then
|
||||||
|
warn "Qt6 konnte nicht automatisch geprüft werden — cmake wird es versuchen."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Abhängigkeiten OK."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# CMake-Build-Typ
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
case "$BUILD_TYPE" in
|
||||||
|
release|Release)
|
||||||
|
CMAKE_BUILD_TYPE="Release"
|
||||||
|
;;
|
||||||
|
debug|Debug)
|
||||||
|
CMAKE_BUILD_TYPE="Debug"
|
||||||
|
;;
|
||||||
|
install)
|
||||||
|
CMAKE_BUILD_TYPE="Release"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Unbekannter Build-Typ: '$BUILD_TYPE'. Erlaubt: release, debug, clean, install"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Konfigurieren
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
info "Konfiguriere mit CMake (${CMAKE_BUILD_TYPE})…"
|
||||||
|
|
||||||
|
cmake -B "$BUILD_DIR" \
|
||||||
|
-G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
|
2>&1 || error "CMake-Konfiguration fehlgeschlagen."
|
||||||
|
|
||||||
|
success "Konfiguration OK."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Bauen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
JOBS=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)
|
||||||
|
info "Baue mit $JOBS parallelen Jobs…"
|
||||||
|
|
||||||
|
cmake --build "$BUILD_DIR" -j"$JOBS" 2>&1 || error "Build fehlgeschlagen."
|
||||||
|
|
||||||
|
success "Build erfolgreich!"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Installieren (optional)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ "$BUILD_TYPE" = "install" ]; then
|
||||||
|
info "Installiere nach /usr (sudo erforderlich)…"
|
||||||
|
sudo cmake --install "$BUILD_DIR" || error "Installation fehlgeschlagen."
|
||||||
|
success "BareCode wurde installiert."
|
||||||
|
echo ""
|
||||||
|
echo " Starten: BareCode"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Fertig
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
BINARY="$BUILD_DIR/BareCode"
|
||||||
|
if [ -f "$BINARY" ]; then
|
||||||
|
# .qm-Dateien neben die Binary kopieren damit sie gefunden werden
|
||||||
|
mkdir -p "$BUILD_DIR/translations"
|
||||||
|
if compgen -G "$BUILD_DIR/translations/barecode_*.qm" > /dev/null 2>&1; then
|
||||||
|
success "Übersetzungen bereits vorhanden."
|
||||||
|
else
|
||||||
|
# Aus dem cmake-Zwischenverzeichnis holen
|
||||||
|
find "$BUILD_DIR" -name "barecode_*.qm" -not -path "$BUILD_DIR/translations/*" \
|
||||||
|
-exec cp {} "$BUILD_DIR/translations/" \; 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
success "BareCode ist bereit:"
|
||||||
|
echo " Starten: ./$BINARY"
|
||||||
|
echo " Installieren: ./bauen.sh install"
|
||||||
|
echo " Debug-Build: ./bauen.sh debug"
|
||||||
|
echo " Aufräumen: ./bauen.sh clean"
|
||||||
|
fi
|
||||||
67
main.cpp
@@ -1,5 +1,11 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QTranslator>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QLibraryInfo>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QCoreApplication>
|
||||||
#include "core/MainWindow.h"
|
#include "core/MainWindow.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
@@ -10,7 +16,7 @@ int main(int argc, char *argv[])
|
|||||||
app.setApplicationVersion("1.2.0");
|
app.setApplicationVersion("1.2.0");
|
||||||
app.setOrganizationName("BareCode");
|
app.setOrganizationName("BareCode");
|
||||||
|
|
||||||
// Icon in allen verfügbaren Größen setzen
|
// Icon
|
||||||
QIcon appIcon;
|
QIcon appIcon;
|
||||||
appIcon.addFile(":/icon_16.png", QSize(16, 16));
|
appIcon.addFile(":/icon_16.png", QSize(16, 16));
|
||||||
appIcon.addFile(":/icon_32.png", QSize(32, 32));
|
appIcon.addFile(":/icon_32.png", QSize(32, 32));
|
||||||
@@ -21,6 +27,65 @@ int main(int argc, char *argv[])
|
|||||||
appIcon.addFile(":/icon_512.png", QSize(512, 512));
|
appIcon.addFile(":/icon_512.png", QSize(512, 512));
|
||||||
app.setWindowIcon(appIcon);
|
app.setWindowIcon(appIcon);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Übersetzung laden
|
||||||
|
// .qm-Dateien liegen im Unterverzeichnis "translations" neben der Binary
|
||||||
|
// oder unter /usr/share/BareCode/translations nach Installation
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
QSettings settings(QSettings::IniFormat, QSettings::UserScope,
|
||||||
|
"BareCode", "BareCode");
|
||||||
|
QString locale = settings.value("language/locale", QString()).toString();
|
||||||
|
|
||||||
|
if (locale.isEmpty())
|
||||||
|
{
|
||||||
|
locale = QLocale::system().name(); // z.B. "de_DE", "en_US"
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString shortLocale = locale.left(2); // "de", "en"
|
||||||
|
|
||||||
|
// Suchpfade für .qm-Dateien
|
||||||
|
QStringList searchPaths;
|
||||||
|
searchPaths << QCoreApplication::applicationDirPath() + "/translations"
|
||||||
|
<< QDir::homePath() + "/.local/share/BareCode/translations"
|
||||||
|
<< "/usr/share/BareCode/translations"
|
||||||
|
<< "/usr/local/share/BareCode/translations";
|
||||||
|
|
||||||
|
// Qt-eigene Übersetzungen (Dialoge, Standard-Buttons)
|
||||||
|
QTranslator qtTranslator;
|
||||||
|
if (qtTranslator.load("qt_" + locale,
|
||||||
|
QLibraryInfo::path(QLibraryInfo::TranslationsPath)))
|
||||||
|
{
|
||||||
|
app.installTranslator(&qtTranslator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BareCode-Übersetzung — in allen Suchpfaden versuchen
|
||||||
|
QTranslator appTranslator;
|
||||||
|
bool loaded = false;
|
||||||
|
|
||||||
|
for (const QString &path : searchPaths)
|
||||||
|
{
|
||||||
|
if (appTranslator.load(QString("barecode_%1").arg(locale), path) ||
|
||||||
|
appTranslator.load(QString("barecode_%1").arg(shortLocale), path))
|
||||||
|
{
|
||||||
|
app.installTranslator(&appTranslator);
|
||||||
|
loaded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Englisch
|
||||||
|
if (!loaded && shortLocale != "de")
|
||||||
|
{
|
||||||
|
for (const QString &path : searchPaths)
|
||||||
|
{
|
||||||
|
if (appTranslator.load("barecode_en", path))
|
||||||
|
{
|
||||||
|
app.installTranslator(&appTranslator);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow window;
|
MainWindow window;
|
||||||
window.show();
|
window.show();
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/home/diabolus/Arbeit/Projekt-Hirnfrei/BareCode/barecode-1.1.0.tar.gz
|
|
||||||
2
src/barecode/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
{src/
|
|
||||||
build/
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[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 +0,0 @@
|
|||||||
IDI_ICON1 ICON "resources/BareCode.ico"
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# 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"
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# 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"
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# 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 +0,0 @@
|
|||||||
ref: refs/heads/main
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[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 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/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+"$@"}
|
|
||||||
:
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
#!/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;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#!/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+"$@"}
|
|
||||||
:
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#!/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 --
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
:
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
#!/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 +0,0 @@
|
|||||||
* -export-subst -export-ignore
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# 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]
|
|
||||||
# *~
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# pack-refs with: peeled fully-peeled sorted
|
|
||||||
cb6617222dbcac30aaf15bf336b150fed440cc7f refs/heads/main
|
|
||||||
ca573209e715409e5989324aaeaa4efbd4da5642 refs/tags/v1.0.0
|
|
||||||
^36e074f43d5b76d940d953956b0ed80899e71848
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[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,13 +0,0 @@
|
|||||||
[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
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 824 B |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -1,21 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
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()
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[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
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 824 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -1,12 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
add_subdirectory(core)
|
|
||||||
add_subdirectory(editor)
|
|
||||||
add_subdirectory(filetree)
|
|
||||||
add_subdirectory(highlighter)
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// AboutDialog – Zeigt Versionsinformationen und Entwicklerangaben.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
class AboutDialog : public QDialog
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AboutDialog(QWidget *parent = nullptr);
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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}/..
|
|
||||||
)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#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() {}
|
|
||||||
};
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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}/..
|
|
||||||
)
|
|
||||||
@@ -1,556 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,361 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,330 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,492 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#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
|
|
||||||
};
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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}/..
|
|
||||||
)
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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}/..
|
|
||||||
)
|
|
||||||