From 57d418f4c286c35b430cf84f9fa2ddefa66bcc33 Mon Sep 17 00:00:00 2001 From: Diego Iastrubni Date: Thu, 12 Feb 2026 22:16:45 +0200 Subject: [PATCH 1/4] BoldItemDelegate: move to its own file Previously, this was an implementation detail of `qmdiEditor` that leaked. It was used also in the git plugin. Now, this is migrated to a full widget. With a very minimal implementation. In hope that `GitPlugin.cpp` will remove the dependency (see https://github.com/diegoiast/qmdilib/issues/34) --- CMakeLists.txt | 2 ++ src/plugins/git/GitPlugin.cpp | 1 + src/widgets/BoldItemDelegate.cpp | 37 ++++++++++++++++++++++++++++++++ src/widgets/BoldItemDelegate.hpp | 30 ++++++++++++++++++++++++++ src/widgets/qmdieditor.cpp | 25 +-------------------- src/widgets/qmdieditor.h | 9 -------- 6 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 src/widgets/BoldItemDelegate.cpp create mode 100644 src/widgets/BoldItemDelegate.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b2af6d..aba88dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,8 @@ set(codepointer_sources src/widgets/bannerwidget.h src/widgets/HistoryLineEdit.cpp src/widgets/HistoryLineEdit.h + src/widgets/BoldItemDelegate.cpp + src/widgets/BoldItemDelegate.hpp src/widgets/gotolineform.ui src/widgets/bannermessage.ui src/widgets/replaceform.ui diff --git a/src/plugins/git/GitPlugin.cpp b/src/plugins/git/GitPlugin.cpp index 0f7003e..eb9cc24 100644 --- a/src/plugins/git/GitPlugin.cpp +++ b/src/plugins/git/GitPlugin.cpp @@ -23,6 +23,7 @@ #include "ui_GitCommands.h" #include "ui_GitCommit.h" #include "widgets/AutoShrinkLabel.hpp" +#include "widgets/BoldItemDelegate.hpp" #include "widgets/qmdieditor.h" QString shortGitSha1(const QString &fullSha1, int length = 7) { diff --git a/src/widgets/BoldItemDelegate.cpp b/src/widgets/BoldItemDelegate.cpp new file mode 100644 index 0000000..581da31 --- /dev/null +++ b/src/widgets/BoldItemDelegate.cpp @@ -0,0 +1,37 @@ +/** + * \file BoldItemDelegate.cpp + * \brief Implementation of the bold item delegate + * \author Diego Iastrubni diegoiast@gmail.com + */ + +// SPDX-License-Identifier: MIT + +#include + +#include "widgets/BoldItemDelegate.hpp" + +void BoldItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const { + QString text = index.data(Qt::DisplayRole).toString(); + painter->save(); + + bool isSelected = option.state & QStyle::State_Selected; + if (isSelected) { + painter->fillRect(option.rect, option.palette.highlight()); + painter->setPen(option.palette.highlightedText().color()); + } else { + painter->setPen(option.palette.text().color()); + } + + QFont font = painter->font(); + if (text == boldItemStr) { + font.setBold(true); + } + painter->setFont(font); + + // I honestly don't remember why I needed to adjust 4 pixels, but it looks + // beeter this way. + QRect textRect = option.rect.adjusted(4, 0, -4, 0); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); + painter->restore(); +} diff --git a/src/widgets/BoldItemDelegate.hpp b/src/widgets/BoldItemDelegate.hpp new file mode 100644 index 0000000..e5527db --- /dev/null +++ b/src/widgets/BoldItemDelegate.hpp @@ -0,0 +1,30 @@ +/** + * \file BoldItemDelegate.hpp + * \brief Definition of the bold item delegate + * \author Diego Iastrubni diegoiast@gmail.com + */ + +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +/** + * \class Bold + * \brief item delegate used for highlighting a specific valud + * + * This is a quick and dirty delegate to strap into a QListView or QComboBox + * (or anything supported) that will display a specific item, at the same font + * color as the default - but also bold. + * + * Can be used to mark the current item. + */ +class BoldItemDelegate : public QStyledItemDelegate { + public: + QString boldItemStr = ""; + explicit BoldItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; diff --git a/src/widgets/qmdieditor.cpp b/src/widgets/qmdieditor.cpp index a664e87..1217490 100644 --- a/src/widgets/qmdieditor.cpp +++ b/src/widgets/qmdieditor.cpp @@ -50,6 +50,7 @@ #include "GlobalCommands.hpp" #include "plugins/texteditor/thememanager.h" #include "qmdieditor.h" +#include "widgets/BoldItemDelegate.hpp" #include "widgets/textoperationswidget.h" #include "widgets/textpreview.h" #include "widgets/ui_bannermessage.h" @@ -171,30 +172,6 @@ static auto createSubFollowSymbolSubmenu(const CommandArgs &data, QMenu *menu, } } -void BoldItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const { - QString text = index.data(Qt::DisplayRole).toString(); - painter->save(); - - bool isSelected = option.state & QStyle::State_Selected; - if (isSelected) { - painter->fillRect(option.rect, option.palette.highlight()); - painter->setPen(option.palette.highlightedText().color()); - } else { - painter->setPen(option.palette.text().color()); - } - - QFont font = painter->font(); - if (text == boldItemStr) { - font.setBold(true); - } - painter->setFont(font); - QRect textRect = option.rect.adjusted(4, 0, -4, 0); - // painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, text); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); - painter->restore(); -} - namespace Qutepart { QStringList getAvailableHighlihters() { extern QMap languageNameToXmlFileName; diff --git a/src/widgets/qmdieditor.h b/src/widgets/qmdieditor.h index 5dfb7d7..e716602 100644 --- a/src/widgets/qmdieditor.h +++ b/src/widgets/qmdieditor.h @@ -36,15 +36,6 @@ namespace Qutepart { class ThemeManager; } -class BoldItemDelegate : public QStyledItemDelegate { - public: - QString boldItemStr = ""; - explicit BoldItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} - - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; -}; - /** A source editor with MDI interface. This class will be a very rich text editor which will also have a set of toolbars and menus From caddcd874a1723f2c9077168755b1c43d3c4b338 Mon Sep 17 00:00:00 2001 From: Diego Iastrubni Date: Fri, 13 Feb 2026 11:22:27 +0200 Subject: [PATCH 2/4] qmdiEditor: we can now undo reload When a file gets reloaded from disk, the user can undo the loading. Before, when reloaded, all undo history was lost. --- src/widgets/qmdieditor.cpp | 101 +++++++++++++++++++++---------------- src/widgets/qmdieditor.h | 1 + 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/src/widgets/qmdieditor.cpp b/src/widgets/qmdieditor.cpp index 1217490..bcda068 100644 --- a/src/widgets/qmdieditor.cpp +++ b/src/widgets/qmdieditor.cpp @@ -796,44 +796,7 @@ void qmdiEditor::on_fileChanged(const QString &filename) { auto isModified = textEditor->document()->isModified(); if (!isModified) { - auto oldCursor = textEditor->textCursor(); - auto line = oldCursor.blockNumber(); - auto column = oldCursor.positionInBlock(); - auto anchorLine = oldCursor.anchor() >= oldCursor.position() - ? oldCursor.blockNumber() - : textEditor->document() - ->findBlock(oldCursor.anchor()) - .blockNumber(); - auto anchorColumn = oldCursor.anchor() >= oldCursor.position() - ? oldCursor.positionInBlock() - : oldCursor.anchor() - - textEditor->document()->findBlock(oldCursor.anchor()).position(); - - documentHasBeenLoaded = false; - loadContent(false); - - auto doc = textEditor->document(); - auto newCursor = QTextCursor (doc); - auto maxLine = doc->blockCount() - 1; - auto caretLine = std::min(line, maxLine); - auto caretBlock = doc->findBlockByNumber(caretLine); - newCursor.setPosition(caretBlock.position()); - - auto caretMaxColumn = caretBlock.length() - 1; - newCursor.movePosition( - QTextCursor::Right, - QTextCursor::MoveAnchor, - std::min(column, caretMaxColumn) - ); - auto anchorMaxLine = doc->blockCount() - 1; - auto selLine = std::min(anchorLine, anchorMaxLine); - auto anchorBlock = doc->findBlockByNumber(selLine); - auto anchorMaxColumn = anchorBlock.length() - 1; - auto anchorPos = - anchorBlock.position() + - std::min(anchorColumn, anchorMaxColumn); - newCursor.setPosition(anchorPos, QTextCursor::KeepAnchor); - textEditor->setTextCursor(newCursor); + reload(); return; } @@ -850,6 +813,40 @@ void qmdiEditor::on_fileChanged(const QString &filename) { displayBannerMessage(message, -1); } +void qmdiEditor::reload() { + auto oldCursor = textEditor->textCursor(); + auto line = oldCursor.blockNumber(); + auto column = oldCursor.positionInBlock(); + auto anchorLine = oldCursor.anchor() >= oldCursor.position() + ? oldCursor.blockNumber() + : textEditor->document()->findBlock(oldCursor.anchor()).blockNumber(); + auto anchorColumn = oldCursor.anchor() >= oldCursor.position() + ? oldCursor.positionInBlock() + : oldCursor.anchor() - + textEditor->document()->findBlock(oldCursor.anchor()).position(); + + documentHasBeenLoaded = false; + loadContent(false); + + auto doc = textEditor->document(); + auto newCursor = QTextCursor(doc); + auto maxLine = doc->blockCount() - 1; + auto caretLine = std::min(line, maxLine); + auto caretBlock = doc->findBlockByNumber(caretLine); + newCursor.setPosition(caretBlock.position()); + + auto caretMaxColumn = caretBlock.length() - 1; + newCursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, + std::min(column, caretMaxColumn)); + auto anchorMaxLine = doc->blockCount() - 1; + auto selLine = std::min(anchorLine, anchorMaxLine); + auto anchorBlock = doc->findBlockByNumber(selLine); + auto anchorMaxColumn = anchorBlock.length() - 1; + auto anchorPos = anchorBlock.position() + std::min(anchorColumn, anchorMaxColumn); + newCursor.setPosition(anchorPos, QTextCursor::KeepAnchor); + textEditor->setTextCursor(newCursor); +} + void qmdiEditor::hideTimer_timeout() { if (m_timerHideout != 0) { QString s; @@ -1332,8 +1329,7 @@ void qmdiEditor::transformBlockCase() { void qmdiEditor::fileMessage_clicked(const QString &s) { if (s == ":reload") { - documentHasBeenLoaded = false; - loadContent(false); + reload(); hideBannerMessage(); } else if (s == ":forcerw") { hideBannerMessage(); @@ -1398,9 +1394,28 @@ void qmdiEditor::loadContent(bool useBackup) { // When loading, (setPlainText()) a signal is emitted, which triggers the system to believe // that the content has been modified. Just don't do this. It will also save some time // on loading, since really, signals emitted a this stage are not meaningful. - textEditor->blockSignals(true); - textEditor->setPlainText(textStream.readAll()); - file.close(); + { + auto doc = textEditor->document(); + auto oldCursor = textEditor->textCursor(); + auto selStart = oldCursor.selectionStart(); + auto selEnd = oldCursor.selectionEnd(); + auto cursor = QTextCursor (doc); + + cursor.beginEditBlock(); + cursor.select(QTextCursor::Document); + cursor.removeSelectedText(); + cursor.insertText(textStream.readAll()); + cursor.endEditBlock(); + + auto docLength = doc->characterCount() - 1; + selStart = qMin(selStart, docLength); + selEnd = qMin(selEnd, docLength); + + auto newCursor = QTextCursor (doc); + newCursor.setPosition(selStart); + newCursor.setPosition(selEnd, QTextCursor::KeepAnchor); + textEditor->setTextCursor(newCursor); + } QFileInfo fileInfo(fileName); auto elapsed = timer.elapsed(); diff --git a/src/widgets/qmdieditor.h b/src/widgets/qmdieditor.h index e716602..12bd195 100644 --- a/src/widgets/qmdieditor.h +++ b/src/widgets/qmdieditor.h @@ -98,6 +98,7 @@ class qmdiEditor : public QWidget, public qmdiClient { void newDocument(); void setPlainText(const QString &plainText); + void reload(); bool doSave(); bool doSaveAs(); bool loadFile(const QString &fileName); From da78509972bee8ca7a9b47618977e31d254277ce Mon Sep 17 00:00:00 2001 From: Diego Iastrubni Date: Fri, 13 Feb 2026 18:56:25 +0200 Subject: [PATCH 3/4] qmdiEditor: run clang-format on save (172) 1) Separate load/save events. 2) New configuration: clang-format, and when to save 3) When a file gets saved, run run "clang-format" depending on user configuration. This is done based on file extension, doing it only for supported types. A cool improvement might be doing a file extension. We can have a configurtion file with a extension, binary to execute, and which where to download it. For example: bash can be formatted using https://github.com/mvdan/sh which is written in GO, so installation is simple as the ctags plugin. rust,go are baked into the toolchains. cmake has https://github.com/cheshirekow/cmake_format closes #172 --- src/GlobalCommands.hpp | 3 + .../ProjectManager/ProjectManagerPlg.cpp | 253 ++++++++++++------ .../ProjectManager/ProjectManagerPlg.h | 9 + src/widgets/qmdieditor.cpp | 2 +- 4 files changed, 180 insertions(+), 87 deletions(-) diff --git a/src/GlobalCommands.hpp b/src/GlobalCommands.hpp index 88bb651..74e2f78 100644 --- a/src/GlobalCommands.hpp +++ b/src/GlobalCommands.hpp @@ -15,6 +15,9 @@ inline constexpr const char *OpenFile = "OpenFile"; // Broadcast - a new file has been loaded. The payload will contain the filename inline constexpr const char *LoadedFile = "LoadedFile"; +// Broadcast - a file has been saved. The payload will contain the filename +inline constexpr const char *SavedFile = "SavedFile"; + // Broadcast - a file has been closed inline constexpr const char *ClosedFile = "ClosedFile"; diff --git a/src/plugins/ProjectManager/ProjectManagerPlg.cpp b/src/plugins/ProjectManager/ProjectManagerPlg.cpp index b8fb10c..2068759 100644 --- a/src/plugins/ProjectManager/ProjectManagerPlg.cpp +++ b/src/plugins/ProjectManager/ProjectManagerPlg.cpp @@ -1,4 +1,3 @@ -#include "qmdipluginconfig.h" #include #include #include @@ -16,6 +15,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #if defined(__linux__) || defined(__unix__) || defined(__APPLE__) #include @@ -31,6 +36,7 @@ #endif #include +#include #include #include #include @@ -148,6 +154,17 @@ auto static setupPty(QProcess &process, int &masterFd) -> bool { #endif } +auto static isClangFormatSupported(const QString& fileName) -> bool { + auto static supported = QSet{ + "c","cc","cpp","cxx", + "h","hh","hpp","hxx", + "m","mm","cu","cuh" + }; + auto ext = QFileInfo(fileName).suffix().toLower(); + return supported.contains(ext); +} + + void ProjectBuildModel::addConfig(std::shared_ptr config) { int row = configs.size(); beginInsertRows(QModelIndex(), row, row); @@ -233,7 +250,7 @@ QStringList ProjectBuildModel::getAllOpenDirs() const { } ProjectManagerPlugin::ProjectManagerPlugin() { - name = tr("Project manager"); + name = "ProjectManager"; author = tr("Diego Iastrubni "); iVersion = 0; sVersion = "0.0.1"; @@ -241,18 +258,27 @@ ProjectManagerPlugin::ProjectManagerPlugin() { alwaysEnabled = true; auto monospacedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); + auto values = QStringList() + << tr("Never") + << tr("Always") + << tr("Loaded projects"); + +#if defined(Q_OS_LINUX) + clangFormatExe = "/usr/bin/clang-format"; +#else + clangFormatExe = R"(C:\Program Files\llvm\bin\ctags.exe)"; +#endif + config.pluginName = tr("Project manager"); config.description = tr("Add support for building using CMake/Cargo/Go"); - config.configItems.push_back( - qmdiConfigItem::Builder() + config.configItems.push_back(qmdiConfigItem::Builder() .setDisplayName(tr("Save before running tasks (build, config etc)")) .setDescription(tr("If checked, files are saved before running any task")) .setKey(Config::SaveBeforeTaskKey) .setType(qmdiConfigItem::Bool) .setDefaultValue(true) .build()); - config.configItems.push_back( - qmdiConfigItem::Builder() + config.configItems.push_back(qmdiConfigItem::Builder() .setDisplayName(tr("Black console")) .setDescription(tr("Should the console background be black, or default")) .setKey(Config::BlackConsoleKey) @@ -260,12 +286,28 @@ ProjectManagerPlugin::ProjectManagerPlugin() { .setDefaultValue(false) .build()); config.configItems.push_back(qmdiConfigItem::Builder() - .setDisplayName(tr("Console font")) - .setKey(Config::ConsoleFontKey) - .setType(qmdiConfigItem::Font) - .setDefaultValue(monospacedFont) - .setValue(monospacedFont) - .build()); + .setDisplayName(tr("Console font")) + .setKey(Config::ConsoleFontKey) + .setType(qmdiConfigItem::Font) + .setDefaultValue(monospacedFont) + .setValue(monospacedFont) + .build()); + config.configItems.push_back(qmdiConfigItem::Builder() + .setDisplayName(tr("clang-format exe")) + .setDescription(tr("Where do you have LLVM tools installed")) + .setKey(Config::ClangFormatExeKey) + .setType(qmdiConfigItem::Path) + .setDefaultValue(clangFormatExe) + .setPossibleValue(true) // Must be an existing file + .build()); + config.configItems.push_back(qmdiConfigItem::Builder() + .setDisplayName(tr("Clang-format behaviour")) + .setDescription(tr("When to run clang-format")) + .setKey(Config::ClangFormatBehaviourKey) + .setType(qmdiConfigItem::OneOf) + .setPossibleValue(values) + .setDefaultValue(ClangFormatOnSave::InProjects) + .build()); /* config.configItems.push_back(qmdiConfigItem::Builder() @@ -807,19 +849,15 @@ int ProjectManagerPlugin::canHandleCommand(const QString &command, const Command if (command == GlobalCommands::LoadedFile) { return true; } + if (command == GlobalCommands::SavedFile) { + return true; + } if (command == GlobalCommands::ClosedFile) { return true; } return false; } -#include -#include -#include -#include -#include -#include - #ifdef Q_OS_WIN #include #endif @@ -861,73 +899,15 @@ auto verifyRunnable(const QString &fileName) -> bool { CommandArgs ProjectManagerPlugin::handleCommand(const QString &command, const CommandArgs &args) { if (command == GlobalCommands::LoadedFile) { auto client = args.value(GlobalArguments::Client).value(); - if (!client) { - return {}; - } - - auto filename = args[GlobalArguments::FileName]; - auto action = client->contextMenu.findActionNamed("runScript"); - auto isScript = verifyRunnable(client->mdiClientFileName()); - if (isScript) { - if (!action) { - auto shortcut = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_R); - action = new QAction(tr("Run script")); - action->setObjectName("runScript"); - action->setShortcut(shortcut); - action->setData(QVariant::fromValue(client)); - client->contextMenu.addAction(action); - client->menus[tr("&Project")]->addAction(action); - - connect(action, &QAction::triggered, action, [action, this]() { - auto client = action->data().value(); - if (!client->canCloseClient(CloseReason::CloseTab)) { - return; - } - auto fi = QFileInfo(client->mdiClientFileName()); - auto workingDir = fi.dir().absolutePath(); - auto scriptName = fi.absoluteFilePath(); - auto arguments = QStringList(); - auto env = QProcessEnvironment(); - auto capture = - outputPanel ? outputPanel->captureTasksOutput->isChecked() : true; - this->runCommand(workingDir, scriptName, arguments, env, capture); - }); - if (client == mdiServer->getCurrentClient()) { - this->mdiServer->mdiHost->unmergeClient(client); - this->mdiServer->mdiHost->mergeClient(client); - this->mdiServer->mdiHost->updateGUI(); - } - } - } else { - client->contextMenu.removeAction(action); - client->menus[tr("&Project")]->removeAction(action); - if (client == mdiServer->getCurrentClient()) { - this->mdiServer->mdiHost->unmergeClient(client); - this->mdiServer->mdiHost->mergeClient(client); - this->mdiServer->mdiHost->updateGUI(); - } - } - - auto widget = dynamic_cast(client); - auto actionCopyFilePath = new QAction(tr("Copy path relative to project"), widget); - connect(actionCopyFilePath, &QAction::triggered, actionCopyFilePath, [client, this]() { - auto c = QApplication::clipboard(); - auto fileName = client->mdiClientFileName(); - auto project = projectModel->findProjectForFile(fileName); - if (!project || !fileName.startsWith(project->sourceDir)) { - c->setText(fileName); - return; - } - auto fixed = fileName; - auto l = project->sourceDir.length(); - if (!project->sourceDir.endsWith('\\') && !project->sourceDir.endsWith('/')) { - l++; - } - fixed.remove(0, l); - c->setText(fixed); - }); - client->contextMenu.addAction(actionCopyFilePath); - + auto filename = args[GlobalArguments::FileName].toString(); + fixClientsMenu(client, filename); + return {}; + } + if (command == GlobalCommands::SavedFile) { + auto client = args.value(GlobalArguments::Client).value(); + auto filename = args[GlobalArguments::FileName].toString(); + fixClientsMenu(client, filename); + saveFileExternalActions(client); return {}; } if (command == GlobalCommands::ClosedFile) { @@ -1666,3 +1646,104 @@ auto ProjectManagerPlugin::tryScrollOutput(int line) -> bool { vScrollBar->setValue(centerScrollValue); return true; } + +auto ProjectManagerPlugin::fixClientsMenu(qmdiClient *client, const QString &fileName) -> void { + if (!client) { + return; + } + + // TODO - do we even need this argument? + if (fileName.isEmpty()) { + return; + } + + auto action = client->contextMenu.findActionNamed("runScript"); + auto isScript = verifyRunnable(client->mdiClientFileName()); + if (isScript) { + if (!action) { + auto shortcut = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_R); + action = new QAction(tr("Run script")); + action->setObjectName("runScript"); + action->setShortcut(shortcut); + action->setData(QVariant::fromValue(client)); + client->contextMenu.addAction(action); + client->menus[tr("&Project")]->addAction(action); + + connect(action, &QAction::triggered, action, [action, this]() { + auto client = action->data().value(); + if (!client->canCloseClient(CloseReason::CloseTab)) { + return; + } + auto fi = QFileInfo(client->mdiClientFileName()); + auto workingDir = fi.dir().absolutePath(); + auto scriptName = fi.absoluteFilePath(); + auto arguments = QStringList(); + auto env = QProcessEnvironment(); + auto capture = + outputPanel ? outputPanel->captureTasksOutput->isChecked() : true; + this->runCommand(workingDir, scriptName, arguments, env, capture); + }); + this->mdiServer->mdiHost->unmergeClient(client); + this->mdiServer->mdiHost->mergeClient(client); + this->mdiServer->mdiHost->updateGUI(); + } + } else { + client->contextMenu.removeAction(action); + client->menus[tr("&Project")]->removeAction(action); + this->mdiServer->mdiHost->unmergeClient(client); + this->mdiServer->mdiHost->mergeClient(client); + this->mdiServer->mdiHost->updateGUI(); + } + + auto widget = dynamic_cast(client); + auto actionCopyFilePath = new QAction(tr("Copy path relative to project"), widget); + connect(actionCopyFilePath, &QAction::triggered, actionCopyFilePath, [client, this]() { + auto c = QApplication::clipboard(); + auto fileName = client->mdiClientFileName(); + auto project = projectModel->findProjectForFile(fileName); + if (!project || !fileName.startsWith(project->sourceDir)) { + c->setText(fileName); + return; + } + auto fixed = fileName; + auto l = project->sourceDir.length(); + if (!project->sourceDir.endsWith('\\') && !project->sourceDir.endsWith('/')) { + l++; + } + fixed.remove(0, l); + c->setText(fixed); + }); + client->contextMenu.addAction(actionCopyFilePath); +} + +auto ProjectManagerPlugin::saveFileExternalActions(qmdiClient *client) -> void { + // TODO how can we notify of errors? + if (getConfig().getClangFormatBehaviour() == ClangFormatOnSave::Never) { + return; + } + + auto fileName = client->mdiClientFileName(); + if (!isClangFormatSupported(fileName)) { + qDebug() << "saveFileExternalActions: not suppported by clang-format" << fileName; + return; + } + if (getConfig().getClangFormatBehaviour() == ClangFormatOnSave::InProjects) { + auto project = projectModel->findProjectForFile(fileName); + if (!project || !fileName.startsWith(project->sourceDir)) { + qDebug() << "saveFileExternalActions: will not autoformat file" << fileName; + return; + } + } + + auto clangFormat = getConfig().getClangFormatExe(); + auto args = QStringList{ "-style=file", "-fallback-style=none", "-i", fileName }; + auto p = QProcess(); + p.start(clangFormat, args); + p.waitForFinished(); + auto exitCode = p.exitCode(); + if (p.exitStatus() != QProcess::NormalExit || exitCode != 0) { + auto output = QString::fromUtf8(p.readAllStandardOutput()).trimmed(); + qDebug() << "Running clang-format failed, exit=" << exitCode << output; + qDebug() << QString("clang ") + args.join(" "); + } +} diff --git a/src/plugins/ProjectManager/ProjectManagerPlg.h b/src/plugins/ProjectManager/ProjectManagerPlg.h index 5589bb0..04d68a6 100644 --- a/src/plugins/ProjectManager/ProjectManagerPlg.h +++ b/src/plugins/ProjectManager/ProjectManagerPlg.h @@ -42,12 +42,18 @@ class ProjectBuildModel : public QAbstractListModel { QStringList getAllOpenDirs() const; }; +enum class ClangFormatOnSave { + Never, Always, InProjects +}; + class ProjectManagerPlugin : public IPlugin { struct Config { CONFIG_DEFINE(SaveBeforeTask, bool); CONFIG_DEFINE(BlackConsole, bool); CONFIG_DEFINE(ConsoleFont, QString) + CONFIG_DEFINE(ClangFormatExe, QString); + CONFIG_DEFINE(ClangFormatBehaviour, ClangFormatOnSave) CONFIG_DEFINE(ExtraPath, QStringList); CONFIG_DEFINE(OpenDirs, QStringList); CONFIG_DEFINE(SelectedDirectory, QString); @@ -113,6 +119,8 @@ class ProjectManagerPlugin : public IPlugin { auto updateExecutablesUI(std::shared_ptr buildConfig) -> void; auto tryOpenProject(const QString &filename, const QString &dir) -> bool; auto tryScrollOutput(int line) -> bool; + auto fixClientsMenu(qmdiClient *client, const QString &filename) -> void; + auto saveFileExternalActions(qmdiClient *client) -> void; int panelIndex = -1; Ui::ProjectManagerGUI *gui = nullptr; @@ -132,6 +140,7 @@ class ProjectManagerPlugin : public IPlugin { ProjectBuildModel *projectModel = nullptr; CommandPalette *commandPalette = nullptr; ProjectSearch *searchPanelUI = nullptr; + QString clangFormatExe; QAction *runAction = nullptr; QAction *buildAction = nullptr; diff --git a/src/widgets/qmdieditor.cpp b/src/widgets/qmdieditor.cpp index bcda068..a72978f 100644 --- a/src/widgets/qmdieditor.cpp +++ b/src/widgets/qmdieditor.cpp @@ -1245,7 +1245,7 @@ bool qmdiEditor::saveFile(const QString &newFileName, bool makeExecutable) { auto pluginManager = dynamic_cast(mdiServer->mdiHost); pluginManager->handleCommand( - GlobalCommands::LoadedFile, + GlobalCommands::SavedFile, { {GlobalArguments::FileName, mdiClientFileName()}, {GlobalArguments::Client, QVariant::fromValue(static_cast(this))}, From ad7099439c2c2e274aa2555b6e648fef19cb738a Mon Sep 17 00:00:00 2001 From: Diego Iastrubni Date: Fri, 13 Feb 2026 20:55:39 +0200 Subject: [PATCH 4/4] spell check + clang format --- .../ProjectManager/ProjectManagerPlg.cpp | 82 +++++++++---------- .../ProjectManager/ProjectManagerPlg.h | 4 +- src/widgets/BoldItemDelegate.cpp | 2 +- src/widgets/BoldItemDelegate.hpp | 2 +- 4 files changed, 41 insertions(+), 49 deletions(-) diff --git a/src/plugins/ProjectManager/ProjectManagerPlg.cpp b/src/plugins/ProjectManager/ProjectManagerPlg.cpp index 2068759..cec2316 100644 --- a/src/plugins/ProjectManager/ProjectManagerPlg.cpp +++ b/src/plugins/ProjectManager/ProjectManagerPlg.cpp @@ -1,26 +1,26 @@ +#include #include #include #include +#include #include +#include #include #include #include #include #include #include +#include +#include #include #include #include #include +#include #include #include #include -#include -#include -#include -#include -#include -#include #if defined(__linux__) || defined(__unix__) || defined(__APPLE__) #include @@ -36,9 +36,9 @@ #endif #include -#include #include #include +#include #include #include @@ -154,17 +154,13 @@ auto static setupPty(QProcess &process, int &masterFd) -> bool { #endif } -auto static isClangFormatSupported(const QString& fileName) -> bool { - auto static supported = QSet{ - "c","cc","cpp","cxx", - "h","hh","hpp","hxx", - "m","mm","cu","cuh" - }; +auto static isClangFormatSupported(const QString &fileName) -> bool { + auto static supported = + QSet{"c", "cc", "cpp", "cxx", "h", "hh", "hpp", "hxx", "m", "mm", "cu", "cuh"}; auto ext = QFileInfo(fileName).suffix().toLower(); return supported.contains(ext); } - void ProjectBuildModel::addConfig(std::shared_ptr config) { int row = configs.size(); beginInsertRows(QModelIndex(), row, row); @@ -258,10 +254,7 @@ ProjectManagerPlugin::ProjectManagerPlugin() { alwaysEnabled = true; auto monospacedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); - auto values = QStringList() - << tr("Never") - << tr("Always") - << tr("Loaded projects"); + auto values = QStringList() << tr("Never") << tr("Always") << tr("Loaded projects"); #if defined(Q_OS_LINUX) clangFormatExe = "/usr/bin/clang-format"; @@ -271,14 +264,16 @@ ProjectManagerPlugin::ProjectManagerPlugin() { config.pluginName = tr("Project manager"); config.description = tr("Add support for building using CMake/Cargo/Go"); - config.configItems.push_back(qmdiConfigItem::Builder() + config.configItems.push_back( + qmdiConfigItem::Builder() .setDisplayName(tr("Save before running tasks (build, config etc)")) .setDescription(tr("If checked, files are saved before running any task")) .setKey(Config::SaveBeforeTaskKey) .setType(qmdiConfigItem::Bool) .setDefaultValue(true) .build()); - config.configItems.push_back(qmdiConfigItem::Builder() + config.configItems.push_back( + qmdiConfigItem::Builder() .setDisplayName(tr("Black console")) .setDescription(tr("Should the console background be black, or default")) .setKey(Config::BlackConsoleKey) @@ -286,28 +281,28 @@ ProjectManagerPlugin::ProjectManagerPlugin() { .setDefaultValue(false) .build()); config.configItems.push_back(qmdiConfigItem::Builder() - .setDisplayName(tr("Console font")) - .setKey(Config::ConsoleFontKey) - .setType(qmdiConfigItem::Font) - .setDefaultValue(monospacedFont) - .setValue(monospacedFont) - .build()); + .setDisplayName(tr("Console font")) + .setKey(Config::ConsoleFontKey) + .setType(qmdiConfigItem::Font) + .setDefaultValue(monospacedFont) + .setValue(monospacedFont) + .build()); config.configItems.push_back(qmdiConfigItem::Builder() - .setDisplayName(tr("clang-format exe")) - .setDescription(tr("Where do you have LLVM tools installed")) - .setKey(Config::ClangFormatExeKey) - .setType(qmdiConfigItem::Path) - .setDefaultValue(clangFormatExe) - .setPossibleValue(true) // Must be an existing file - .build()); + .setDisplayName(tr("clang-format exe")) + .setDescription(tr("Where do you have LLVM tools installed")) + .setKey(Config::ClangFormatExeKey) + .setType(qmdiConfigItem::Path) + .setDefaultValue(clangFormatExe) + .setPossibleValue(true) // Must be an existing file + .build()); config.configItems.push_back(qmdiConfigItem::Builder() - .setDisplayName(tr("Clang-format behaviour")) - .setDescription(tr("When to run clang-format")) - .setKey(Config::ClangFormatBehaviourKey) - .setType(qmdiConfigItem::OneOf) - .setPossibleValue(values) - .setDefaultValue(ClangFormatOnSave::InProjects) - .build()); + .setDisplayName(tr("Clang-format behaviour")) + .setDescription(tr("When to run clang-format")) + .setKey(Config::ClangFormatBehaviourKey) + .setType(qmdiConfigItem::OneOf) + .setPossibleValue(values) + .setDefaultValue(ClangFormatOnSave::InProjects) + .build()); /* config.configItems.push_back(qmdiConfigItem::Builder() @@ -1679,8 +1674,7 @@ auto ProjectManagerPlugin::fixClientsMenu(qmdiClient *client, const QString &fil auto scriptName = fi.absoluteFilePath(); auto arguments = QStringList(); auto env = QProcessEnvironment(); - auto capture = - outputPanel ? outputPanel->captureTasksOutput->isChecked() : true; + auto capture = outputPanel ? outputPanel->captureTasksOutput->isChecked() : true; this->runCommand(workingDir, scriptName, arguments, env, capture); }); this->mdiServer->mdiHost->unmergeClient(client); @@ -1724,7 +1718,7 @@ auto ProjectManagerPlugin::saveFileExternalActions(qmdiClient *client) -> void { auto fileName = client->mdiClientFileName(); if (!isClangFormatSupported(fileName)) { - qDebug() << "saveFileExternalActions: not suppported by clang-format" << fileName; + qDebug() << "saveFileExternalActions: not supported by clang-format" << fileName; return; } if (getConfig().getClangFormatBehaviour() == ClangFormatOnSave::InProjects) { @@ -1736,7 +1730,7 @@ auto ProjectManagerPlugin::saveFileExternalActions(qmdiClient *client) -> void { } auto clangFormat = getConfig().getClangFormatExe(); - auto args = QStringList{ "-style=file", "-fallback-style=none", "-i", fileName }; + auto args = QStringList{"-style=file", "-fallback-style=none", "-i", fileName}; auto p = QProcess(); p.start(clangFormat, args); p.waitForFinished(); diff --git a/src/plugins/ProjectManager/ProjectManagerPlg.h b/src/plugins/ProjectManager/ProjectManagerPlg.h index 04d68a6..d61665e 100644 --- a/src/plugins/ProjectManager/ProjectManagerPlg.h +++ b/src/plugins/ProjectManager/ProjectManagerPlg.h @@ -42,9 +42,7 @@ class ProjectBuildModel : public QAbstractListModel { QStringList getAllOpenDirs() const; }; -enum class ClangFormatOnSave { - Never, Always, InProjects -}; +enum class ClangFormatOnSave { Never, Always, InProjects }; class ProjectManagerPlugin : public IPlugin { diff --git a/src/widgets/BoldItemDelegate.cpp b/src/widgets/BoldItemDelegate.cpp index 581da31..276a19a 100644 --- a/src/widgets/BoldItemDelegate.cpp +++ b/src/widgets/BoldItemDelegate.cpp @@ -30,7 +30,7 @@ void BoldItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti painter->setFont(font); // I honestly don't remember why I needed to adjust 4 pixels, but it looks - // beeter this way. + // better this way. QRect textRect = option.rect.adjusted(4, 0, -4, 0); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); painter->restore(); diff --git a/src/widgets/BoldItemDelegate.hpp b/src/widgets/BoldItemDelegate.hpp index e5527db..3295829 100644 --- a/src/widgets/BoldItemDelegate.hpp +++ b/src/widgets/BoldItemDelegate.hpp @@ -12,7 +12,7 @@ /** * \class Bold - * \brief item delegate used for highlighting a specific valud + * \brief item delegate used for highlighting a specific value * * This is a quick and dirty delegate to strap into a QListView or QComboBox * (or anything supported) that will display a specific item, at the same font