From 2da101ca90ce127c54766d01c71f9c942de8bca9 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 26 Sep 2026 12:38:40 +1200 Subject: [PATCH] Add a shortcut bar that opens at the cursor with S Pressing S on a folio opens the element picker at the cursor with a row of commands above it, chosen by what is selected, like the SolidWorks shortcut bar: nothing selected insert last element, element picker, text, line, rectangle, terminal strip plan, paste, folio properties elements selected rotate, rotate texts, edit, copy, cut, delete only conductors reset path, edit, delete Each row is a list of ShortcutManager ids, so any registered command can go on it and the bar carries no command list of its own. The lists are in QSettings (diagrameditor/shortcut_bar/); a context the user has not changed follows the defaults. A disabled command keeps its place, greyed, so a row looks the same each time. Clicking a button closes the bar and triggers the action. A new configuration page, "Barre de raccourcis", edits the three lists: add, remove and reorder any diagram editor command. To make that possible: - ShortcutManager::action(id, owner) returns the action a given window registered under an id, since each editor window registers its own. - The add-item actions (text, image, shapes, terminal strip plan) are registered as diagrameditor.add_, with no default key. They also appear in the Shortcuts page and can now be bound. Discussion #1033. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG (cherry picked from commit 53c1e213282b9f7226ca1c09e81703113236bb88) --- cmake/qet_compilation_vars.cmake | 4 + .../ElementsCollection/elementpickerpopup.cpp | 62 ++++- .../ElementsCollection/elementpickerpopup.h | 8 +- sources/qetapp.cpp | 2 + sources/qetdiagrameditor.cpp | 91 ++++++- sources/qetdiagrameditor.h | 3 + sources/shortcutbarsettings.cpp | 122 +++++++++ sources/shortcutbarsettings.h | 50 ++++ sources/shortcutmanager.cpp | 33 +++ sources/shortcutmanager.h | 2 + .../ui/configpage/shortcutbarconfigpage.cpp | 231 ++++++++++++++++++ sources/ui/configpage/shortcutbarconfigpage.h | 64 +++++ 12 files changed, 659 insertions(+), 13 deletions(-) create mode 100644 sources/shortcutbarsettings.cpp create mode 100644 sources/shortcutbarsettings.h create mode 100644 sources/ui/configpage/shortcutbarconfigpage.cpp create mode 100644 sources/ui/configpage/shortcutbarconfigpage.h diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index 0a6a14d75..ddc1ba0e0 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -285,6 +285,8 @@ set(QET_SRC_FILES ${QET_DIR}/sources/recentfiles.h ${QET_DIR}/sources/shortcutmanager.cpp ${QET_DIR}/sources/shortcutmanager.h + ${QET_DIR}/sources/shortcutbarsettings.cpp + ${QET_DIR}/sources/shortcutbarsettings.h ${QET_DIR}/sources/titleblockcell.cpp ${QET_DIR}/sources/titleblockcell.h ${QET_DIR}/sources/titleblockproperties.cpp @@ -799,6 +801,8 @@ set(QET_SRC_FILES ${QET_DIR}/sources/ui/configpage/guidespropertieswidget.h ${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.cpp ${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.h + ${QET_DIR}/sources/ui/configpage/shortcutbarconfigpage.cpp + ${QET_DIR}/sources/ui/configpage/shortcutbarconfigpage.h ${QET_DIR}/sources/undocommand/addelementtextcommand.cpp ${QET_DIR}/sources/undocommand/addelementtextcommand.h diff --git a/sources/ElementsCollection/elementpickerpopup.cpp b/sources/ElementsCollection/elementpickerpopup.cpp index 553aa3d5c..d3c9e9a39 100644 --- a/sources/ElementsCollection/elementpickerpopup.cpp +++ b/sources/ElementsCollection/elementpickerpopup.cpp @@ -19,7 +19,10 @@ #include "elementscollectionwidget.h" +#include #include +#include +#include #include #include #include @@ -58,6 +61,13 @@ ElementPickerPopup::ElementPickerPopup(ElementsCollectionWidget *source, layout->setContentsMargins(6, 6, 6, 6); layout->setSpacing(4); + //Command row, shown when the picker is opened as the shortcut bar + m_commands = new QWidget(this); + m_commands_layout = new QHBoxLayout(m_commands); + m_commands_layout->setContentsMargins(0, 0, 0, 0); + m_commands_layout->setSpacing(2); + m_commands->hide(); + m_search = new QLineEdit(this); m_search->setPlaceholderText(tr("Rechercher un élément…")); m_search->setClearButtonEnabled(true); @@ -73,6 +83,7 @@ ElementPickerPopup::ElementPickerPopup(ElementsCollectionWidget *source, m_hint = new QLabel(tr("Entrée pour insérer · Échap pour fermer"), this); m_hint->setEnabled(false); + layout->addWidget(m_commands); layout->addWidget(m_search); layout->addWidget(m_view); layout->addWidget(m_hint); @@ -97,8 +108,10 @@ ElementPickerPopup::ElementPickerPopup(ElementsCollectionWidget *source, focused and any previous query cleared. @param global_pos */ -void ElementPickerPopup::popUpAt(const QPoint &global_pos) +void ElementPickerPopup::popUpAt(const QPoint &global_pos, + const QList &commands) { + setCommands(commands); m_search->clear(); m_model->clear(); showPalette(); @@ -121,6 +134,53 @@ void ElementPickerPopup::popUpAt(const QPoint &global_pos) m_search->setFocus(); } +/** + @brief ElementPickerPopup::setCommands + Show @a commands as a row of buttons above the search field, or hide the + row when there are none. A disabled command keeps its place, greyed out, + so the row looks the same every time for a given selection. + + Clicking a button closes the picker first, then triggers the action: a + command such as "add a line" starts a mode on the folio, which needs the + focus the popup holds. + @param commands +*/ +void ElementPickerPopup::setCommands(const QList &commands) +{ + while (QLayoutItem *item = m_commands_layout->takeAt(0)) { + delete item->widget(); + delete item; + } + + for (QAction *action : commands) + { + auto *button = new QToolButton(m_commands); + button->setAutoRaise(true); + button->setIconSize(QSize(24, 24)); + const QString text = action->text().remove(QLatin1Char('&')); + if (action->icon().isNull()) { + button->setText(text); + button->setToolButtonStyle(Qt::ToolButtonTextOnly); + } else { + button->setIcon(action->icon()); + } + const QKeySequence key = action->shortcut(); + button->setToolTip(key.isEmpty() + ? text + : QStringLiteral("%1 (%2)").arg( + text, key.toString(QKeySequence::NativeText))); + button->setEnabled(action->isEnabled()); + button->setFocusPolicy(Qt::NoFocus); + connect(button, &QToolButton::clicked, this, [this, action]() { + hide(); + action->trigger(); + }); + m_commands_layout->addWidget(button); + } + m_commands_layout->addStretch(); + m_commands->setVisible(!commands.isEmpty()); +} + /** @brief ElementPickerPopup::runSearch */ diff --git a/sources/ElementsCollection/elementpickerpopup.h b/sources/ElementsCollection/elementpickerpopup.h index ab5213579..ae3200a83 100644 --- a/sources/ElementsCollection/elementpickerpopup.h +++ b/sources/ElementsCollection/elementpickerpopup.h @@ -27,6 +27,8 @@ class QLineEdit; class QListView; class QStandardItemModel; class QLabel; +class QAction; +class QHBoxLayout; /** @brief A cursor-anchored element picker. @@ -49,7 +51,8 @@ class ElementPickerPopup : public QFrame explicit ElementPickerPopup(ElementsCollectionWidget *source, QWidget *parent = nullptr); - void popUpAt(const QPoint &global_pos); + void popUpAt(const QPoint &global_pos, + const QList &commands = {}); signals: /// Emitted when the user picks an element; the popup has closed @@ -62,6 +65,7 @@ class ElementPickerPopup : public QFrame void runSearch(); void chooseCurrent(); void showPalette(); + void setCommands(const QList &commands); int loadPaletteDir(const QString &dir_path, const QString &prefix, int depth); @@ -70,6 +74,8 @@ class ElementPickerPopup : public QFrame QListView *m_view = nullptr; QStandardItemModel *m_model = nullptr; QLabel *m_hint = nullptr; + QWidget *m_commands = nullptr; + QHBoxLayout *m_commands_layout = nullptr; bool m_palette_mode = true; }; diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index 48d9001b6..f7c086756 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -40,6 +40,7 @@ #include "ui/aboutqetdialog.h" #include "ui/configpage/generalconfigurationpage.h" #include "ui/configpage/shortcutsconfigpage.h" +#include "ui/configpage/shortcutbarconfigpage.h" #include "machine_info.h" #include "TerminalStrip/ui/terminalstripeditorwindow.h" #include "qetversion.h" @@ -2213,6 +2214,7 @@ void QETApp::configureQET() cd.addPage(new ExportConfigPage()); cd.addPage(new PrintConfigPage()); cd.addPage(new ShortcutsConfigPage()); + cd.addPage(new ShortcutBarConfigPage()); #ifdef QET_SPACEMOUSE_SUPPORT cd.addPage(new SpaceMouseConfigPage()); #endif diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index eaedcfc6e..08571a337 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -23,6 +23,8 @@ #include #include "ElementsCollection/elementscollectionwidget.h" #include "ElementsCollection/elementpickerpopup.h" +#include "shortcutbarsettings.h" +#include "qetgraphicsitem/conductor.h" #include "QWidgetAnimation/qwidgetanimation.h" #include "autoNum/ui/autonumberingdockwidget.h" #include "conductornumexport.h" @@ -76,6 +78,7 @@ #include #include #include +#include #ifdef BUILD_WITHOUT_KF # include "ui/nokde/kautosavefile.h" #else @@ -840,6 +843,20 @@ void QETDiagramEditor::setUpActions() this, &QETDiagramEditor::showElementPicker); addAction(m_show_element_picker); + //The picker with a row of commands above it, chosen by what is + //selected -- the SolidWorks "S" shortcut bar. S is unbound in this + //editor. + m_show_shortcut_bar = new QAction(tr("Barre de raccourcis"), this); + m_show_shortcut_bar->setStatusTip( + tr("Ouvre à la position du curseur les commandes utiles pour la sélection, et le sélecteur d'éléments", + "status bar tip")); + ShortcutManager::instance().registerAction( + m_show_shortcut_bar, "diagrameditor.show_shortcut_bar", + tr("Éditeur de schémas"), Qt::Key_S); + connect(m_show_shortcut_bar, &QAction::triggered, + this, &QETDiagramEditor::showShortcutBar); + addAction(m_show_shortcut_bar); + m_delete_selection->setStatusTip( tr("Enlève les éléments sélectionnés du folio", "status bar tip")); m_rotate_selection->setStatusTip( tr("Pivote les éléments et textes sélectionnés", "status bar tip")); m_rotate_group_selection->setStatusTip( tr("Pivote la sélection comme un groupe autour de son centre, au lieu de chaque élément sur place", "status bar tip")); @@ -971,6 +988,13 @@ void QETDiagramEditor::setUpActions() add_path->setCheckable(true); connect(&m_add_item_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::addItemGroupTriggered); + //No default key, but an id: they can then be bound in the Shortcuts + //page and placed on the shortcut bar, like every other command. + for (QAction *action : m_add_item_actions_group.actions()) { + ShortcutManager::instance().registerAction( + action, "diagrameditor.add_" + action->data().toString(), + tr("Éditeur de schémas"), QKeySequence()); + } //Depth action m_depth_action_group = QET::depthActionGroup(this); @@ -1126,6 +1150,7 @@ void QETDiagramEditor::setUpMenu() menu_edition -> addAction(m_configure_duplicate); menu_edition -> addAction(m_insert_last_element); menu_edition -> addAction(m_show_element_picker); + menu_edition -> addAction(m_show_shortcut_bar); menu_edition -> addSeparator(); //The same actions the "Ajouter" toolbar holds. They were toolbar-only, //which left them unreachable for anyone working without a mouse: a @@ -2035,6 +2060,7 @@ void QETDiagramEditor::slot_updateActions() m_add_item_actions_group. setEnabled(editable_project); m_insert_last_element-> setEnabled(opened_diagram && editable_project && !m_last_inserted_element.isNull()); m_show_element_picker-> setEnabled(opened_diagram && editable_project); + m_show_shortcut_bar-> setEnabled(opened_diagram && editable_project); m_row_column_actions_group. setEnabled(editable_project); m_background_color_button-> setEnabled(opened_diagram); m_draw_grid-> setEnabled(opened_diagram); @@ -3087,18 +3113,12 @@ void QETDiagramEditor::insertLastElement() } /** - @brief QETDiagramEditor::showElementPicker - Open the element picker where the mouse is. - - Built lazily: most sessions of the diagram editor never open it, and it - holds a list view and a model of its own. + @brief QETDiagramEditor::elementPicker + @return the element picker, built on first use: most sessions never open + it, and it holds a list view and a model of its own. */ -void QETDiagramEditor::showElementPicker() +ElementPickerPopup *QETDiagramEditor::elementPicker() { - if (!currentDiagramView()) { - return; - } - if (!m_element_picker) { m_element_picker = new ElementPickerPopup(m_element_collection_widget, @@ -3106,7 +3126,56 @@ void QETDiagramEditor::showElementPicker() connect(m_element_picker, &ElementPickerPopup::elementChosen, this, &QETDiagramEditor::insertElementFromCollection); } - m_element_picker->popUpAt(QCursor::pos()); + return m_element_picker; +} + +/** + @brief QETDiagramEditor::showElementPicker + Open the element picker where the mouse is. +*/ +void QETDiagramEditor::showElementPicker() +{ + if (!currentDiagramView()) { + return; + } + + elementPicker()->popUpAt(QCursor::pos()); +} + +/** + @brief QETDiagramEditor::showShortcutBar + Open the element picker where the mouse is, with a row of commands above + it chosen by what is selected: the folio's commands with nothing + selected, conductor commands when only conductors are, and selection + commands otherwise. The commands are ShortcutManager ids, listed in + ShortcutBarSettings and editable in the configuration dialog. +*/ +void QETDiagramEditor::showShortcutBar() +{ + DiagramView *dv = currentDiagramView(); + if (!dv) { + return; + } + + const QList selection = dv->diagram()->selectedItems(); + ShortcutBarSettings::Context context = ShortcutBarSettings::Canvas; + if (!selection.isEmpty()) + { + const bool only_conductors = std::all_of( + selection.cbegin(), selection.cend(), + [](QGraphicsItem *item) { return item->type() == Conductor::Type; }); + context = only_conductors ? ShortcutBarSettings::Conductor + : ShortcutBarSettings::Selection; + } + + QList commands; + for (const QString &id : ShortcutBarSettings::ids(context)) { + if (QAction *action = ShortcutManager::instance().action(id, this)) { + commands << action; + } + } + + elementPicker()->popUpAt(QCursor::pos(), commands); } /** diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 657d56d22..7a6abc53a 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -132,6 +132,7 @@ class QETDiagramEditor : public QETMainWindow void insertLastElement(); void rememberPlacedElement(const ElementsLocation &location); void showElementPicker(); + void showShortcutBar(); void generateTerminalBlock(); void setWindowedMode(); void setTabbedMode(); @@ -273,7 +274,9 @@ class QETDiagramEditor : public QETMainWindow *m_qdw_elmt_collection, *qdw_undo; /// Dock for the undo list + ElementPickerPopup *elementPicker(); QAction *m_show_element_picker = nullptr; + QAction *m_show_shortcut_bar = nullptr; ElementPickerPopup *m_element_picker = nullptr; ///< Built on first use ElementsCollectionWidget *m_element_collection_widget; /// Last element placed from the collection, for "insert last" diff --git a/sources/shortcutbarsettings.cpp b/sources/shortcutbarsettings.cpp new file mode 100644 index 000000000..95072076a --- /dev/null +++ b/sources/shortcutbarsettings.cpp @@ -0,0 +1,122 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "shortcutbarsettings.h" + +#include +#include + +namespace { +QString settingsKey(ShortcutBarSettings::Context context) +{ + switch (context) + { + case ShortcutBarSettings::Canvas: + return QStringLiteral("diagrameditor/shortcut_bar/canvas"); + case ShortcutBarSettings::Selection: + return QStringLiteral("diagrameditor/shortcut_bar/selection"); + case ShortcutBarSettings::Conductor: + return QStringLiteral("diagrameditor/shortcut_bar/conductor"); + } + return QString(); +} +} + +/** + @return every context, in the order they are shown to the user +*/ +QList ShortcutBarSettings::contexts() +{ + return {Canvas, Selection, Conductor}; +} + +/** + @return the name of @a context, for the configuration page +*/ +QString ShortcutBarSettings::title(Context context) +{ + switch (context) + { + case Canvas: + return QCoreApplication::translate("ShortcutBarSettings", "Folio, rien de sélectionné"); + case Selection: + return QCoreApplication::translate("ShortcutBarSettings", "Éléments sélectionnés"); + case Conductor: + return QCoreApplication::translate("ShortcutBarSettings", "Conducteurs sélectionnés"); + } + return QString(); +} + +/** + @return the ids to show for @a context: the user's list if they saved + one, the defaults otherwise. A saved empty list stays empty. +*/ +QStringList ShortcutBarSettings::ids(Context context) +{ + QSettings settings; + const QString key = settingsKey(context); + if (!settings.contains(key)) { + return defaultIds(context); + } + return settings.value(key).toStringList(); +} + +/** + @return the commands a new user sees for @a context +*/ +QStringList ShortcutBarSettings::defaultIds(Context context) +{ + switch (context) + { + case Canvas: + return {QStringLiteral("diagrameditor.insert_last_element"), + QStringLiteral("diagrameditor.show_element_picker"), + QStringLiteral("diagrameditor.add_text"), + QStringLiteral("diagrameditor.add_line"), + QStringLiteral("diagrameditor.add_rectangle"), + QStringLiteral("diagrameditor.add_terminal_strip"), + QStringLiteral("diagrameditor.paste"), + QStringLiteral("diagrameditor.edit_diagram_properties")}; + case Selection: + return {QStringLiteral("diagrameditor.rotate_selection"), + QStringLiteral("diagrameditor.rotate_texts"), + QStringLiteral("diagrameditor.edit_selection"), + QStringLiteral("diagrameditor.copy"), + QStringLiteral("diagrameditor.cut"), + QStringLiteral("diagrameditor.delete_selection")}; + case Conductor: + return {QStringLiteral("diagrameditor.conductor_reset"), + QStringLiteral("diagrameditor.edit_selection"), + QStringLiteral("diagrameditor.delete_selection")}; + } + return {}; +} + +/** + @brief ShortcutBarSettings::setIds + Save @a ids for @a context. Saving the defaults removes the key, so a + later change of defaults still reaches this user. +*/ +void ShortcutBarSettings::setIds(Context context, const QStringList &ids) +{ + QSettings settings; + if (ids == defaultIds(context)) { + settings.remove(settingsKey(context)); + } else { + settings.setValue(settingsKey(context), ids); + } +} diff --git a/sources/shortcutbarsettings.h b/sources/shortcutbarsettings.h new file mode 100644 index 000000000..753698e75 --- /dev/null +++ b/sources/shortcutbarsettings.h @@ -0,0 +1,50 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef SHORTCUTBARSETTINGS_H +#define SHORTCUTBARSETTINGS_H + +#include +#include +#include + +/** + @brief The commands shown on the diagram editor's shortcut bar. + + The bar opens at the cursor and shows a different row of commands + depending on what is selected. Each row is a list of ShortcutManager ids, + so any registered command can go on it and the bar needs no command list + of its own. Stored in QSettings, one key per context; a context the user + never changed uses the defaults below. +*/ +class ShortcutBarSettings +{ + public: + enum Context { + Canvas, ///< nothing selected + Selection, ///< elements, texts or shapes selected + Conductor ///< only conductors selected + }; + + static QList contexts(); + static QString title(Context context); + static QStringList ids(Context context); + static QStringList defaultIds(Context context); + static void setIds(Context context, const QStringList &ids); +}; + +#endif // SHORTCUTBARSETTINGS_H diff --git a/sources/shortcutmanager.cpp b/sources/shortcutmanager.cpp index 6f8e856a4..56d43b228 100644 --- a/sources/shortcutmanager.cpp +++ b/sources/shortcutmanager.cpp @@ -197,3 +197,36 @@ bool ShortcutManager::trigger(const QString &id) const } return false; } + +/** + @return the QAction registered under @a id that belongs to @a owner -- + that is, has @a owner among its ancestors -- or nullptr. Several windows + of the same kind each register their own action under one id, so a + window asking for "its" action has to say which window it is. + @param id + @param owner : the window, or nullptr for the first live action +*/ +QAction *ShortcutManager::action(const QString &id, const QObject *owner) const +{ + auto it = m_entries.find(id); + if (it == m_entries.end()) { + return nullptr; + } + + for (const QPointer &target : qAsConst(it->targets)) + { + auto *action = qobject_cast(target.data()); + if (!action) { + continue; + } + if (!owner) { + return action; + } + for (const QObject *o = action->parent(); o; o = o->parent()) { + if (o == owner) { + return action; + } + } + } + return nullptr; +} diff --git a/sources/shortcutmanager.h b/sources/shortcutmanager.h index d40448c52..b42cd0d44 100644 --- a/sources/shortcutmanager.h +++ b/sources/shortcutmanager.h @@ -26,6 +26,7 @@ #include class QObject; +class QAction; /** @brief The ShortcutManager class @@ -84,6 +85,7 @@ class ShortcutManager /// multi-window case, not a guaranteed-correct dispatch. /// @return whether a live target was found and triggered. bool trigger(const QString &id) const; + QAction *action(const QString &id, const QObject *owner) const; private: ShortcutManager() = default; diff --git a/sources/ui/configpage/shortcutbarconfigpage.cpp b/sources/ui/configpage/shortcutbarconfigpage.cpp new file mode 100644 index 000000000..d805aaa2d --- /dev/null +++ b/sources/ui/configpage/shortcutbarconfigpage.cpp @@ -0,0 +1,231 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "shortcutbarconfigpage.h" + +#include "../../qeticons.h" +#include "../../shortcutmanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + /// The bar can hold any diagram editor command, except the one that + /// opens it. +bool offerable(const QString &id) +{ + return id.startsWith(QLatin1String("diagrameditor.")) + && id != QLatin1String("diagrameditor.show_shortcut_bar"); +} +} + +/** + @brief ShortcutBarConfigPage::ShortcutBarConfigPage + @param parent +*/ +ShortcutBarConfigPage::ShortcutBarConfigPage(QWidget *parent) : + ConfigPage(parent) +{ + for (const ShortcutManager::ShortcutInfo &info : + ShortcutManager::instance().allShortcuts()) { + if (offerable(info.id)) { + m_descriptions.insert(info.id, info.description); + } + } + for (const ShortcutBarSettings::Context c : ShortcutBarSettings::contexts()) { + m_pending.insert(c, ShortcutBarSettings::ids(c)); + } + + auto *explanation = new QLabel( + tr("La barre de raccourcis s'ouvre à la position du curseur " + "(touche S par défaut). Elle montre les commandes choisies " + "ici selon ce qui est sélectionné, puis le sélecteur " + "d'éléments."), this); + explanation->setWordWrap(true); + + m_context = new QComboBox(this); + for (const ShortcutBarSettings::Context c : ShortcutBarSettings::contexts()) { + m_context->addItem(ShortcutBarSettings::title(c), c); + } + + m_available = new QListWidget(this); + m_available->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_available->setSortingEnabled(true); + m_chosen = new QListWidget(this); + m_chosen->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_chosen->setDragDropMode(QAbstractItemView::InternalMove); + + auto *add = new QPushButton(tr("Ajouter →"), this); + auto *remove = new QPushButton(tr("← Retirer"), this); + auto *up = new QPushButton(tr("Monter"), this); + auto *down = new QPushButton(tr("Descendre"), this); + auto *reset = new QPushButton(tr("Valeurs par défaut"), this); + + auto *buttons = new QVBoxLayout(); + buttons->addStretch(); + buttons->addWidget(add); + buttons->addWidget(remove); + buttons->addSpacing(12); + buttons->addWidget(up); + buttons->addWidget(down); + buttons->addStretch(); + + auto *grid = new QGridLayout(); + grid->addWidget(new QLabel(tr("Commandes disponibles"), this), 0, 0); + grid->addWidget(new QLabel(tr("Dans la barre, dans l'ordre"), this), 0, 2); + grid->addWidget(m_available, 1, 0); + grid->addLayout(buttons, 1, 1); + grid->addWidget(m_chosen, 1, 2); + + auto *context_row = new QHBoxLayout(); + context_row->addWidget(new QLabel(tr("Contexte :"), this)); + context_row->addWidget(m_context, 1); + context_row->addWidget(reset); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(explanation); + layout->addLayout(context_row); + layout->addLayout(grid); + + connect(m_context, qOverload(&QComboBox::currentIndexChanged), this, [this]() { + storeContext(); + showContext(); + }); + connect(add, &QPushButton::clicked, this, &ShortcutBarConfigPage::addSelected); + connect(remove, &QPushButton::clicked, this, &ShortcutBarConfigPage::removeSelected); + connect(up, &QPushButton::clicked, this, [this]() { moveSelected(-1); }); + connect(down, &QPushButton::clicked, this, [this]() { moveSelected(1); }); + connect(reset, &QPushButton::clicked, this, &ShortcutBarConfigPage::resetContext); + connect(m_available, &QListWidget::itemDoubleClicked, this, &ShortcutBarConfigPage::addSelected); + connect(m_chosen, &QListWidget::itemDoubleClicked, this, &ShortcutBarConfigPage::removeSelected); + + showContext(); +} + +/** + @brief ShortcutBarConfigPage::applyConf + Save every context's list. +*/ +void ShortcutBarConfigPage::applyConf() +{ + storeContext(); + for (const ShortcutBarSettings::Context c : ShortcutBarSettings::contexts()) { + ShortcutBarSettings::setIds(c, m_pending.value(c)); + } +} + +QString ShortcutBarConfigPage::title() const +{ + return tr("Barre de raccourcis", "configuration page title"); +} + +QIcon ShortcutBarConfigPage::icon() const +{ + return QET::Icons::ConfigureShortcuts; +} + +/** + @brief ShortcutBarConfigPage::showContext + Fill both lists for the context chosen in the combo box. +*/ +void ShortcutBarConfigPage::showContext() +{ + m_shown = static_cast( + m_context->currentData().toInt()); + const QStringList chosen = m_pending.value(m_shown); + + m_available->clear(); + m_chosen->clear(); + for (const QString &id : chosen) { + appendItem(m_chosen, id); + } + for (auto it = m_descriptions.cbegin(); it != m_descriptions.cend(); ++it) { + if (!chosen.contains(it.key())) { + appendItem(m_available, it.key()); + } + } +} + +/** + @brief ShortcutBarConfigPage::storeContext + Keep the shown context's list, in the order on screen. +*/ +void ShortcutBarConfigPage::storeContext() +{ + QStringList ids; + for (int i = 0 ; i < m_chosen->count() ; ++i) { + ids << m_chosen->item(i)->data(Qt::UserRole).toString(); + } + m_pending.insert(m_shown, ids); +} + +void ShortcutBarConfigPage::addSelected() +{ + for (QListWidgetItem *item : m_available->selectedItems()) { + m_chosen->addItem(m_available->takeItem(m_available->row(item))); + } +} + +void ShortcutBarConfigPage::removeSelected() +{ + for (QListWidgetItem *item : m_chosen->selectedItems()) { + m_available->addItem(m_chosen->takeItem(m_chosen->row(item))); + } +} + +/** + @brief ShortcutBarConfigPage::moveSelected + Move the selected command @a step rows, keeping it selected. +*/ +void ShortcutBarConfigPage::moveSelected(int step) +{ + const int row = m_chosen->currentRow(); + const int target = row + step; + if (row < 0 || target < 0 || target >= m_chosen->count()) { + return; + } + QListWidgetItem *item = m_chosen->takeItem(row); + m_chosen->insertItem(target, item); + m_chosen->setCurrentRow(target); +} + +void ShortcutBarConfigPage::resetContext() +{ + m_pending.insert(m_shown, ShortcutBarSettings::defaultIds(m_shown)); + showContext(); +} + +/** + @brief ShortcutBarConfigPage::appendItem + Add @a id to @a list with the command's text and icon. An id no live + action carries (a command from a build without it) is still listed, by + its id, so saving does not silently drop it. +*/ +void ShortcutBarConfigPage::appendItem(QListWidget *list, const QString &id) +{ + QAction *action = ShortcutManager::instance().action(id, nullptr); + const QString text = m_descriptions.value(id, id); + auto *item = new QListWidgetItem(action ? action->icon() : QIcon(), text); + item->setData(Qt::UserRole, id); + list->addItem(item); +} diff --git a/sources/ui/configpage/shortcutbarconfigpage.h b/sources/ui/configpage/shortcutbarconfigpage.h new file mode 100644 index 000000000..9a15537dd --- /dev/null +++ b/sources/ui/configpage/shortcutbarconfigpage.h @@ -0,0 +1,64 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef SHORTCUTBARCONFIGPAGE_H +#define SHORTCUTBARCONFIGPAGE_H + +#include "configpage.h" +#include "../../shortcutbarsettings.h" + +#include + +class QComboBox; +class QListWidget; + +/** + @brief The ShortcutBarConfigPage class + Choose which commands the diagram editor's shortcut bar shows, and in + what order, for each selection context. Any command registered with + ShortcutManager by the diagram editor can be added. Changes are kept + per context while the dialog is open and saved by applyConf(). +*/ +class ShortcutBarConfigPage : public ConfigPage +{ + Q_OBJECT + + public: + explicit ShortcutBarConfigPage(QWidget *parent = nullptr); + + void applyConf() override; + QString title() const override; + QIcon icon() const override; + + private: + void showContext(); + void storeContext(); + void addSelected(); + void removeSelected(); + void moveSelected(int step); + void resetContext(); + void appendItem(QListWidget *list, const QString &id); + + QComboBox *m_context; + QListWidget *m_available; + QListWidget *m_chosen; + ShortcutBarSettings::Context m_shown = ShortcutBarSettings::Canvas; + QHash m_pending; + QHash m_descriptions; +}; + +#endif // SHORTCUTBARCONFIGPAGE_H