From 47e5a8072b81e3c7e328c5864acdf32a683109da Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sun, 27 Sep 2026 03:15:07 +1300 Subject: [PATCH] Shortcut bar: reopen at the cursor, wrap to a width the user sets After customising, the bar now opens at the cursor like every other time, instead of where the customising window was. A size grip in the bar's corner sets its width. The tiles are laid out in rows that wrap at that width, so a bar with many commands and pinned elements becomes a block rather than one long strip; the height follows the rows, and the width is saved. Until the user sets one, the tiles stay on one row, as before. The grip is driven by hand: QSizeGrip asks the window manager to resize, and on X11 a popup is not managed, so nothing would happen. The hint line wraps rather than being cut off at narrow widths. The customising window remembers the size it was left at. Discussion #1033. Co-Authored-By: Claude Opus 5.5 --- .../ElementsCollection/elementpickerpopup.cpp | 139 +++++++++++++++++- .../ElementsCollection/elementpickerpopup.h | 13 ++ sources/shortcutbarsettings.cpp | 28 ++++ sources/shortcutbarsettings.h | 5 + 4 files changed, 178 insertions(+), 7 deletions(-) diff --git a/sources/ElementsCollection/elementpickerpopup.cpp b/sources/ElementsCollection/elementpickerpopup.cpp index a2d383a7b..2cfc1aaa4 100644 --- a/sources/ElementsCollection/elementpickerpopup.cpp +++ b/sources/ElementsCollection/elementpickerpopup.cpp @@ -27,11 +27,14 @@ #include #include #include +#include #include +#include #include #include #include #include +#include #include #include #include @@ -39,6 +42,8 @@ #include #include +#include + #include "../qetapp.h" #include "../shortcutmanager.h" #include "elementslocation.h" @@ -219,11 +224,22 @@ ElementPickerPopup::ElementPickerPopup(ElementsCollectionWidget *source, m_hint = new QLabel(tr("Entrée pour insérer · Échap pour fermer"), this); m_hint->setEnabled(false); + //Wrapped rather than cut off when the bar is made narrow + m_hint->setWordWrap(true); + + //The bar's width is the user's: dragging the grip wraps the tiles + //onto more rows, and the width is kept for next time + m_grip = new QSizeGrip(this); + m_grip->setToolTip(tr("Glisser pour changer la largeur de la barre")); + m_grip->installEventFilter(this); + auto *hint_row = new QHBoxLayout(); + hint_row->addWidget(m_hint, 1); + hint_row->addWidget(m_grip, 0, Qt::AlignBottom | Qt::AlignRight); layout->addWidget(m_commands); layout->addWidget(m_search); layout->addWidget(m_view); - layout->addWidget(m_hint); + layout->addLayout(hint_row); layout->addWidget(m_editor); //Search as you type, after a short idle: shorter than the dock's @@ -303,6 +319,16 @@ void ElementPickerPopup::show(const QPoint &global_pos) void ElementPickerPopup::keepOnScreen(const QPoint &global_pos) { adjustSize(); + //The sizes the user gave the customising window and the bar + if (m_customising) { + const QSize size = ShortcutBarSettings::editorSize(); + if (size.isValid()) { + resize(size.expandedTo(minimumSizeHint())); + } + } else if (m_bar_mode && ShortcutBarSettings::barWidth() > 0) { + resize(qMax(ShortcutBarSettings::barWidth(), minimumSizeHint().width()), + height()); + } QPoint pos = global_pos; if (QScreen *screen = QGuiApplication::screenAt(global_pos)) { @@ -335,6 +361,11 @@ void ElementPickerPopup::setCommands(const QStringList &ids) delete item->widget(); delete item; } + qDeleteAll(m_tiles); + m_tiles.clear(); + m_tile_rows = new QVBoxLayout(); + m_tile_rows->setSpacing(2); + m_commands_layout->addLayout(m_tile_rows); for (const QString &id : ids) { @@ -354,7 +385,7 @@ void ElementPickerPopup::setCommands(const QStringList &ids) hide(); emit elementChosen(location); }); - m_commands_layout->addWidget(button); + m_tiles << button; continue; } @@ -383,7 +414,7 @@ void ElementPickerPopup::setCommands(const QStringList &ids) hide(); action->trigger(); }); - m_commands_layout->addWidget(button); + m_tiles << button; } m_commands_layout->addStretch(); if (m_bar_mode) @@ -394,12 +425,103 @@ void ElementPickerPopup::setCommands(const QStringList &ids) customise->setToolTip(tr("Personnaliser la barre…")); customise->setFocusPolicy(Qt::NoFocus); connect(customise, &QToolButton::clicked, this, &ElementPickerPopup::startCustomising); - m_commands_layout->addWidget(customise); + m_commands_layout->addWidget(customise, 0, Qt::AlignTop); } + layoutTiles(ShortcutBarSettings::barWidth()); + m_grip->setVisible(m_bar_mode); //An empty bar still shows, so it can be customised back m_commands->setVisible(m_bar_mode || !ids.isEmpty()); } +/** + @brief ElementPickerPopup::layoutTiles + Put the bar's tiles in rows no wider than the bar at @a bar_width, in + their order, as many per row as fit. A @a bar_width of 0 or less, the + width the user never set, keeps them on one row. +*/ +void ElementPickerPopup::layoutTiles(int bar_width) +{ + if (!m_tile_rows) { + return; + } + //Emptying a row layout leaves its buttons alone; they go into the + //new rows below + while (QLayoutItem *item = m_tile_rows->takeAt(0)) { + delete item->layout(); + } + + //Room left beside the tiles: the popup's margins and the "…" button + const int limit = bar_width > 0 + ? bar_width - 12 - 30 + : std::numeric_limits::max(); + QHBoxLayout *row = nullptr; + int used = 0; + for (QToolButton *tile : std::as_const(m_tiles)) + { + const int w = tile->sizeHint().width() + m_tile_rows->spacing(); + if (!row || (used + w > limit && used > 0)) { + row = new QHBoxLayout(); + row->setSpacing(2); + row->addStretch(); + m_tile_rows->addLayout(row); + used = 0; + } + //Before the stretch, which stays last so the row is left-aligned + row->insertWidget(row->count() - 1, tile); + used += w; + } +} + +/** + @brief ElementPickerPopup::eventFilter + Drive the size grip by hand. QSizeGrip asks the window manager to do the + resize, and a popup is a window the window manager does not manage, so + on X11 nothing would happen. Only the width follows the mouse: the tiles + re-flow into rows as it changes, the height follows the rows, and the + width is saved when the grip is let go. +*/ +bool ElementPickerPopup::eventFilter(QObject *watched, QEvent *event) +{ + if (watched != m_grip) { + return QFrame::eventFilter(watched, event); + } + switch (event->type()) + { + case QEvent::MouseButtonPress: { + auto *me = static_cast(event); + if (me->button() != Qt::LeftButton) { + break; + } + m_grip_active = true; + m_grip_press_x = me->globalPosition().toPoint().x(); + m_grip_press_width = width(); + return true; + } + case QEvent::MouseMove: { + if (!m_grip_active) { + break; + } + auto *me = static_cast(event); + const int w = qMax(minimumWidth(), m_grip_press_width + + me->globalPosition().toPoint().x() - m_grip_press_x); + layoutTiles(w); + layout()->activate(); + resize(w, sizeHint().height()); + return true; + } + case QEvent::MouseButtonRelease: + if (!m_grip_active) { + break; + } + m_grip_active = false; + ShortcutBarSettings::setBarWidth(width()); + return true; + default: + break; + } + return QFrame::eventFilter(watched, event); +} + /** @brief ElementPickerPopup::barItem @return a list item for @a id, a command or a pinned element: icon only, @@ -451,6 +573,7 @@ void ElementPickerPopup::startCustomising() fillCustomising(ShortcutBarSettings::ids(m_context)); m_commands->hide(); setPickerVisible(false); + m_grip->hide(); //Elements are pinned to the empty-folio bar only: with something //selected, the bar is for acting on it, not for adding more m_edit_symbols_search->clear(); @@ -516,7 +639,8 @@ void ElementPickerPopup::runSymbolSearch() /** @brief ElementPickerPopup::finishCustomising Leave the edit, saving the bar row when @a save, and show the bar again - where it was so the result can be seen and used straight away. + at the cursor so the result can be seen and used straight away. The + window's size is kept either way. */ void ElementPickerPopup::finishCustomising(bool save) { @@ -529,14 +653,15 @@ void ElementPickerPopup::finishCustomising(bool save) ShortcutBarSettings::setIds(m_context, ids); } - const QPoint where = pos(); + ShortcutBarSettings::setEditorSize(size()); m_customising = false; hide(); m_editor->hide(); setPickerVisible(true); setWindowFlags(Qt::Popup); setWindowTitle(QString()); - popUpShortcutBar(where, m_context); + //At the cursor, like any other opening of the bar + popUpShortcutBar(QCursor::pos(), m_context); } /** diff --git a/sources/ElementsCollection/elementpickerpopup.h b/sources/ElementsCollection/elementpickerpopup.h index fdbd4af95..9983661f1 100644 --- a/sources/ElementsCollection/elementpickerpopup.h +++ b/sources/ElementsCollection/elementpickerpopup.h @@ -32,6 +32,9 @@ class QAction; class QHBoxLayout; class QListWidget; class QListWidgetItem; +class QSizeGrip; +class QToolButton; +class QVBoxLayout; /** @brief A cursor-anchored element picker. @@ -65,6 +68,7 @@ class ElementPickerPopup : public QFrame protected: void keyPressEvent(QKeyEvent *event) override; void closeEvent(QCloseEvent *event) override; + bool eventFilter(QObject *watched, QEvent *event) override; private: void runSearch(); @@ -72,6 +76,7 @@ class ElementPickerPopup : public QFrame void showPalette(); void show(const QPoint &global_pos); void setCommands(const QStringList &ids); + void layoutTiles(int bar_width); QAction *commandAction(const QString &id) const; QListWidgetItem *barItem(const QString &id, bool icon_only) const; void startCustomising(); @@ -90,6 +95,14 @@ class ElementPickerPopup : public QFrame QLabel *m_hint = nullptr; QWidget *m_commands = nullptr; QHBoxLayout *m_commands_layout = nullptr; + /// The bar's tiles, commands and elements, in order + QList m_tiles; + QVBoxLayout *m_tile_rows = nullptr; + QSizeGrip *m_grip = nullptr; + /// The size grip is being dragged, from this x at this width + bool m_grip_active = false; + int m_grip_press_x = 0; + int m_grip_press_width = 0; /// Opened as the shortcut bar, as opposed to the plain picker bool m_bar_mode = false; ShortcutBarSettings::Context m_context = ShortcutBarSettings::Canvas; diff --git a/sources/shortcutbarsettings.cpp b/sources/shortcutbarsettings.cpp index 3a3c1c9ee..97e66416d 100644 --- a/sources/shortcutbarsettings.cpp +++ b/sources/shortcutbarsettings.cpp @@ -160,3 +160,31 @@ bool ShortcutBarSettings::hasElements(Context context) const QStringList list = ids(context); return std::any_of(list.cbegin(), list.cend(), &ShortcutBarSettings::isElement); } + +/** + @return the width the user gave the bar with its size grip, or 0 when + they never did: the tiles then stay on one row +*/ +int ShortcutBarSettings::barWidth() +{ + return QSettings().value(QStringLiteral("diagrameditor/shortcut_bar/width"), 0).toInt(); +} + +void ShortcutBarSettings::setBarWidth(int width) +{ + QSettings().setValue(QStringLiteral("diagrameditor/shortcut_bar/width"), width); +} + +/** + @return the size the user left the customising window at, or an invalid + size when it was never opened +*/ +QSize ShortcutBarSettings::editorSize() +{ + return QSettings().value(QStringLiteral("diagrameditor/shortcut_bar/editor_size")).toSize(); +} + +void ShortcutBarSettings::setEditorSize(const QSize &size) +{ + QSettings().setValue(QStringLiteral("diagrameditor/shortcut_bar/editor_size"), size); +} diff --git a/sources/shortcutbarsettings.h b/sources/shortcutbarsettings.h index b655aca76..a56d845ab 100644 --- a/sources/shortcutbarsettings.h +++ b/sources/shortcutbarsettings.h @@ -21,6 +21,7 @@ #include #include #include +#include /** @brief The commands shown on the diagram editor's shortcut bar. @@ -52,6 +53,10 @@ class ShortcutBarSettings static QStringList availableIds(); static bool isElement(const QString &id); static bool hasElements(Context context); + static int barWidth(); + static void setBarWidth(int width); + static QSize editorSize(); + static void setEditorSize(const QSize &size); }; #endif // SHORTCUTBARSETTINGS_H