- Bug in Funktionanzeige behoben

- Auszuschliessende Verzeichnisse in Projektfunktionen hinzugefügt
- Dateien können als Paramter übergeben werden
- Englisch hinzugefügt
This commit is contained in:
2026-08-25 21:57:23 +02:00
parent 04c5212d54
commit cee8f57ab7
14 changed files with 233 additions and 14 deletions

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(BareCode project(BareCode
VERSION 1.2.0 VERSION 1.3.0
DESCRIPTION "A modular code editor built with Qt6" DESCRIPTION "A modular code editor built with Qt6"
LANGUAGES CXX LANGUAGES CXX
) )

View File

@@ -1,7 +1,7 @@
# BareCode # BareCode
**Schlanker, modularer Code-Editor für Web-Entwickler** **Schlanker, modularer Code-Editor für Web-Entwickler**
Version 1.2.0 — von **Dany Thinnes** | [Projekt Hirnfrei](https://www.projekt-hirnfrei.de) | [Discord](https://discord.projekt-hirnfrei.de) Version 1.3.0 — von **Dany Thinnes** | [Projekt Hirnfrei](https://www.projekt-hirnfrei.de) | [Discord](https://discord.projekt-hirnfrei.de)
Entwickelt mit **C++17**, **Qt6** und **CMake**. Entwickelt mit **C++17**, **Qt6** und **CMake**.
Kompiliert auf **Linux**, **Windows** und **Haiku**. Kompiliert auf **Linux**, **Windows** und **Haiku**.

View File

@@ -13,7 +13,7 @@ int main(int argc, char *argv[])
QApplication app(argc, argv); QApplication app(argc, argv);
app.setApplicationName("BareCode"); app.setApplicationName("BareCode");
app.setApplicationVersion("1.2.0"); app.setApplicationVersion("1.3.0");
app.setOrganizationName("BareCode"); app.setOrganizationName("BareCode");
// Icon // Icon
@@ -89,5 +89,18 @@ int main(int argc, char *argv[])
MainWindow window; MainWindow window;
window.show(); window.show();
// Datei(en) öffnen, die beim Start per Kommandozeile übergeben wurden
// (z. B. Doppelklick auf eine Datei im Dateibrowser → "Öffnen mit BareCode").
// Das erste Argument ist der Programmname selbst und wird übersprungen.
QStringList cliFiles = app.arguments();
if (!cliFiles.isEmpty())
{
cliFiles.removeFirst();
}
if (!cliFiles.isEmpty())
{
window.openFilesFromArguments(cliFiles);
}
return app.exec(); return app.exec();
} }

View File

@@ -74,7 +74,7 @@ AboutDialog::AboutDialog(QWidget *parent)
info->addLayout(row); info->addLayout(row);
}; };
makeRow(tr("Version"), "1.2.0"); makeRow(tr("Version"), "1.3.0");
makeRow(tr("Entwickler"), "Dany Thinnes"); makeRow(tr("Entwickler"), "Dany Thinnes");
makeRow(tr("Projekt"), "Projekt Hirnfrei"); makeRow(tr("Projekt"), "Projekt Hirnfrei");
makeRow(tr("Framework"), QString("Qt %1").arg(qVersion())); makeRow(tr("Framework"), QString("Qt %1").arg(qVersion()));

View File

@@ -5,6 +5,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QCloseEvent> #include <QCloseEvent>
#include <QSettings> #include <QSettings>
#include <QFileInfo>
#include "AboutDialog.h" #include "AboutDialog.h"
#include "filetree/FileTreePanel.h" #include "filetree/FileTreePanel.h"
@@ -45,6 +46,21 @@ MainWindow::MainWindow(QWidget *parent)
MainWindow::~MainWindow() = default; MainWindow::~MainWindow() = default;
// ---------------------------------------------------------------------------
// Datei(en) von der Kommandozeile öffnen
// ---------------------------------------------------------------------------
void MainWindow::openFilesFromArguments(const QStringList &filePaths)
{
for (const QString &path : filePaths)
{
const QFileInfo info(path);
if (info.exists() && info.isFile())
{
m_editor->openFile(info.absoluteFilePath());
}
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// UI aufbauen // UI aufbauen
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -26,6 +26,11 @@ public:
explicit MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override; ~MainWindow() override;
// Datei(en) öffnen, die beim Programmstart per Kommandozeile übergeben
// wurden — z. B. durch Doppelklick auf eine Datei im Dateibrowser
// ("Öffnen mit BareCode").
void openFilesFromArguments(const QStringList &filePaths);
protected: protected:
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;

View File

@@ -14,6 +14,7 @@
#include <QResizeEvent> #include <QResizeEvent>
#include <QKeyEvent> #include <QKeyEvent>
#include <QMouseEvent> #include <QMouseEvent>
#include <QFocusEvent>
#include <QScrollBar> #include <QScrollBar>
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>
@@ -747,3 +748,15 @@ void CodeEditor::keyPressEvent(QKeyEvent *event)
m_varCompleter->handleKeyPress(event); m_varCompleter->handleKeyPress(event);
} }
// ---------------------------------------------------------------------------
// Fokus verloren — z. B. beim Wechsel zu einem anderen Tab.
// Ein noch offenes Variablen-Popup muss hier geschlossen werden, sonst
// bleibt es als verwaistes Fenster stehen und kann bei mehreren offenen
// Dateien den Fokus blockieren.
// ---------------------------------------------------------------------------
void CodeEditor::focusOutEvent(QFocusEvent *event)
{
QPlainTextEdit::focusOutEvent(event);
m_varCompleter->notifyFocusLost();
}

View File

@@ -56,11 +56,13 @@ signals:
void fileSaved(const QString &filePath); void fileSaved(const QString &filePath);
void navigateToRequested(const QString &filePath, int line); void navigateToRequested(const QString &filePath, int line);
protected: void resizeEvent(QResizeEvent *event) override; protected:
void resizeEvent(QResizeEvent *event) override;
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override;
void focusOutEvent(QFocusEvent *event) override;
private slots: private slots:
void updateLineNumberAreaWidth(int newBlockCount); void updateLineNumberAreaWidth(int newBlockCount);

View File

@@ -4,6 +4,9 @@
#include <QFileInfo> #include <QFileInfo>
#include <QFont> #include <QFont>
#include <QProgressBar> #include <QProgressBar>
#include <QSettings>
#include <QFileDialog>
#include <QDir>
FunctionListPanel::FunctionListPanel(QWidget *parent) FunctionListPanel::FunctionListPanel(QWidget *parent)
: QWidget(parent) : QWidget(parent)
@@ -67,6 +70,78 @@ void FunctionListPanel::setupUi()
root->addLayout(toolRow); root->addLayout(toolRow);
// ---- Exclude-Verzeichnisse ----
QHBoxLayout *excludeRow = new QHBoxLayout();
excludeRow->setContentsMargins(6, 0, 6, 4);
excludeRow->addWidget(new QLabel(tr("Ausschließen:"), this));
m_excludeEdit = new QLineEdit(this);
m_excludeEdit->setPlaceholderText(tr("vendor lib node_modules (leerzeichen-getrennt)"));
m_excludeEdit->setToolTip(tr(
"Verzeichnisnamen die beim Scan übersprungen werden.\n"
"Leerzeichen-getrennt, z.B.: vendor lib node_modules cache"
));
m_excludeEdit->setText(tr("vendor lib node_modules"));
excludeRow->addWidget(m_excludeEdit, 1);
// Verzeichnis-Auswahl Button
QPushButton *btnBrowse = new QPushButton(tr("+ Verzeichnis"), this);
btnBrowse->setToolTip(tr("Verzeichnis aus dem Projekt auswählen und zur Ausschlussliste hinzufügen"));
connect(btnBrowse, &QPushButton::clicked, this, [this]()
{
const QString startPath = m_projectRoot.isEmpty()
? QDir::homePath()
: m_projectRoot;
const QString dir = QFileDialog::getExistingDirectory(
this,
tr("Verzeichnis ausschließen"),
startPath,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks
);
if (dir.isEmpty())
{
return;
}
// Nur den letzten Verzeichnisnamen verwenden, nicht den vollen Pfad
// So funktioniert der Exclude auch für gleichnamige Unterverzeichnisse
const QString dirName = QFileInfo(dir).fileName();
// Prüfen ob bereits in der Liste
const QStringList current = m_excludeEdit->text()
.simplified()
.split(' ', Qt::SkipEmptyParts);
if (current.contains(dirName, Qt::CaseInsensitive))
{
return;
}
// Anhängen
QString newText = m_excludeEdit->text().trimmed();
if (!newText.isEmpty())
{
newText += ' ';
}
newText += dirName;
m_excludeEdit->setText(newText);
});
excludeRow->addWidget(btnBrowse);
// Löschen-Button — ganzes Feld leeren
QPushButton *btnClear = new QPushButton(tr(""), this);
btnClear->setFixedWidth(28);
btnClear->setFlat(true);
btnClear->setToolTip(tr("Ausschlussliste leeren"));
connect(btnClear, &QPushButton::clicked, this, [this]()
{
m_excludeEdit->clear();
});
excludeRow->addWidget(btnClear);
root->addLayout(excludeRow);
// ---- Status ---- // ---- Status ----
m_statusLabel = new QLabel(this); m_statusLabel = new QLabel(this);
m_statusLabel->setContentsMargins(6, 0, 6, 2); m_statusLabel->setContentsMargins(6, 0, 6, 2);
@@ -107,9 +182,14 @@ void FunctionListPanel::setupUi()
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
void FunctionListPanel::setProjectRoot(const QString &path) void FunctionListPanel::setProjectRoot(const QString &path)
{ {
// Alte Exclude-Liste für das vorherige Projekt speichern
saveExcludeDirsForProject();
m_projectRoot = path; m_projectRoot = path;
if (!path.isEmpty()) if (!path.isEmpty())
{ {
loadExcludeDirsForProject();
refresh(); refresh();
} }
else else
@@ -147,16 +227,21 @@ void FunctionListPanel::refresh()
m_watcher->waitForFinished(); m_watcher->waitForFinished();
} }
saveExcludeDirsForProject();
m_statusLabel->setText(tr("Scanne…")); m_statusLabel->setText(tr("Scanne…"));
m_btnRefresh->setEnabled(false); m_btnRefresh->setEnabled(false);
const QString root = m_projectRoot; const QString root = m_projectRoot;
const QStringList excludeDirs = m_excludeEdit->text()
.simplified()
.split(' ', Qt::SkipEmptyParts);
FunctionScanner *scanner = m_scanner; FunctionScanner *scanner = m_scanner;
QFuture<QList<FunctionScanner::FunctionInfo>> future = QFuture<QList<FunctionScanner::FunctionInfo>> future =
QtConcurrent::run([scanner, root]() QtConcurrent::run([scanner, root, excludeDirs]()
{ {
return scanner->scanDirectory(root); return scanner->scanDirectory(root, {"*.php"}, excludeDirs);
}); });
m_watcher->setFuture(future); m_watcher->setFuture(future);
@@ -330,3 +415,35 @@ void FunctionListPanel::onGroupingChanged(int /*index*/)
populateTree(m_lastResult); populateTree(m_lastResult);
} }
} }
// ---------------------------------------------------------------------------
// Ausschlussliste projektspezifisch speichern/laden
// (gleiches Schema wie bei der Suche nach toten Funktionen)
// ---------------------------------------------------------------------------
void FunctionListPanel::saveExcludeDirsForProject()
{
if (m_projectRoot.isEmpty())
{
return;
}
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
const QString key = "funclist/exclude/" +
QString(m_projectRoot).replace('/', '_').replace('\\', '_');
s.setValue(key, m_excludeEdit->text());
s.setValue("funclist/excludeDirs", m_excludeEdit->text()); // globaler Fallback
}
void FunctionListPanel::loadExcludeDirsForProject()
{
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
const QString key = "funclist/exclude/" +
QString(m_projectRoot).replace('/', '_').replace('\\', '_');
// Projektspezifisch vorhanden? Sonst globalen Fallback nehmen
const QString saved = s.value(key,
s.value("funclist/excludeDirs", "vendor lib node_modules").toString()
).toString();
m_excludeEdit->setText(saved);
}

View File

@@ -11,6 +11,7 @@
#include <QComboBox> #include <QComboBox>
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QString> #include <QString>
#include <QStringList>
#include "FunctionScanner.h" #include "FunctionScanner.h"
@@ -21,6 +22,10 @@
// Gruppierung: nach Datei oder nach Klasse // Gruppierung: nach Datei oder nach Klasse
// Klick: öffnet Datei und springt zur Definition // Klick: öffnet Datei und springt zur Definition
// Aktualisierung: automatisch nach jedem Speichern // Aktualisierung: automatisch nach jedem Speichern
//
// Verzeichnisse wie vendor/ oder node_modules/ lassen sich über eine
// Ausschlussliste vom Scan ausnehmen (gleiches Prinzip wie bei der
// Suche nach toten Funktionen).
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
class FunctionListPanel : public QWidget class FunctionListPanel : public QWidget
{ {
@@ -48,6 +53,8 @@ private:
void setupUi(); void setupUi();
void populateTree(const QList<FunctionScanner::FunctionInfo> &functions); void populateTree(const QList<FunctionScanner::FunctionInfo> &functions);
void applyFilter(const QString &text); void applyFilter(const QString &text);
void loadExcludeDirsForProject();
void saveExcludeDirsForProject();
static void formatFunctionItem(QTreeWidgetItem *item, static void formatFunctionItem(QTreeWidgetItem *item,
const FunctionScanner::FunctionInfo &f); const FunctionScanner::FunctionInfo &f);
@@ -59,6 +66,9 @@ private:
QLabel *m_statusLabel = nullptr; QLabel *m_statusLabel = nullptr;
QTreeWidget *m_tree = nullptr; QTreeWidget *m_tree = nullptr;
// Ausschlussliste — Verzeichnisnamen die beim Scan übersprungen werden
QLineEdit *m_excludeEdit = nullptr;
FunctionScanner *m_scanner = nullptr; FunctionScanner *m_scanner = nullptr;
QFutureWatcher<QList<FunctionScanner::FunctionInfo>> *m_watcher = nullptr; QFutureWatcher<QList<FunctionScanner::FunctionInfo>> *m_watcher = nullptr;

View File

@@ -16,7 +16,8 @@ FunctionScanner::FunctionScanner(QObject *parent)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
QList<FunctionScanner::FunctionInfo> FunctionScanner::scanDirectory( QList<FunctionScanner::FunctionInfo> FunctionScanner::scanDirectory(
const QString &rootPath, const QString &rootPath,
const QStringList &extensions) const const QStringList &extensions,
const QStringList &excludeDirs) const
{ {
QList<FunctionInfo> results; QList<FunctionInfo> results;
@@ -28,6 +29,27 @@ QList<FunctionScanner::FunctionInfo> FunctionScanner::scanDirectory(
while (it.hasNext()) while (it.hasNext())
{ {
const QString path = it.next(); const QString path = it.next();
// Exclude-Verzeichnisse prüfen (gleiche Logik wie DeadCodeAnalyzer)
bool excluded = false;
if (!excludeDirs.isEmpty())
{
const QStringList parts = path.split('/');
for (const QString &excl : excludeDirs)
{
if (parts.contains(excl.trimmed(), Qt::CaseInsensitive))
{
excluded = true;
break;
}
}
}
if (excluded)
{
continue;
}
results.append(scanFile(path)); results.append(scanFile(path));
} }

View File

@@ -35,8 +35,11 @@ public:
explicit FunctionScanner(QObject *parent = nullptr); explicit FunctionScanner(QObject *parent = nullptr);
// Synchroner Scan — für direkten Aufruf aus Threads // Synchroner Scan — für direkten Aufruf aus Threads
// excludeDirs: Verzeichnisnamen die komplett übersprungen werden
// z.B. {"vendor", "lib", "node_modules"}
QList<FunctionInfo> scanDirectory(const QString &rootPath, QList<FunctionInfo> scanDirectory(const QString &rootPath,
const QStringList &extensions = {"*.php"}) const; const QStringList &extensions = {"*.php"},
const QStringList &excludeDirs = {}) const;
// Scannt eine einzelne Datei // Scannt eine einzelne Datei
QList<FunctionInfo> scanFile(const QString &filePath) const; QList<FunctionInfo> scanFile(const QString &filePath) const;

View File

@@ -14,10 +14,15 @@ VariableCompleter::VariableCompleter(CodeEditor *editor)
: QObject(editor) : QObject(editor)
, m_editor(editor) , m_editor(editor)
{ {
// Popup als eigenständiges, rahmenloses Werkzeugfenster // Popup als reines Anzeige-Fenster — dieselbe Fensterart wie
m_popup = new QListWidget(nullptr); // SignatureTooltip, die nachweislich nie den Fokus an sich reißt.
m_popup->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | // Wichtig: Ein echtes Elternfenster (editor->window()) angeben, sonst
Qt::WindowStaysOnTopHint); // behandelt der Fenstermanager das Popup als eigenständiges Fenster.
// Das führte dazu, dass bei mehreren offenen Dateien/Tabs verwaiste
// Popups entstehen konnten, die den Fokus übernehmen und die
// Texteingabe blockieren.
m_popup = new QListWidget(editor->window());
m_popup->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
m_popup->setAttribute(Qt::WA_ShowWithoutActivating); m_popup->setAttribute(Qt::WA_ShowWithoutActivating);
m_popup->setFocusPolicy(Qt::NoFocus); m_popup->setFocusPolicy(Qt::NoFocus);
m_popup->setMaximumHeight(200); m_popup->setMaximumHeight(200);
@@ -155,6 +160,14 @@ void VariableCompleter::hidePopup()
m_popup->clear(); m_popup->clear();
} }
// ---------------------------------------------------------------------------
// Editor hat den Fokus verloren (z. B. Tab-Wechsel) — Popup schließen
// ---------------------------------------------------------------------------
void VariableCompleter::notifyFocusLost()
{
hidePopup();
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Variablen im aktuellen Dokument sammeln // Variablen im aktuellen Dokument sammeln
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -24,6 +24,11 @@ public:
// Muss nach jedem Tastendruck aufgerufen werden // Muss nach jedem Tastendruck aufgerufen werden
void handleKeyPress(QKeyEvent *event); void handleKeyPress(QKeyEvent *event);
// Muss aufgerufen werden, wenn der Editor den Fokus verliert
// (z. B. beim Wechsel des Tabs) — schließt ein evtl. offenes Popup,
// damit es nicht als "Geisterfenster" stehen bleibt.
void notifyFocusLost();
private slots: private slots:
void onItemActivated(QListWidgetItem *item); void onItemActivated(QListWidgetItem *item);