diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake
index ddc1ba0e0..b3d973e29 100644
--- a/cmake/qet_compilation_vars.cmake
+++ b/cmake/qet_compilation_vars.cmake
@@ -287,6 +287,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/shortcutmanager.h
${QET_DIR}/sources/shortcutbarsettings.cpp
${QET_DIR}/sources/shortcutbarsettings.h
+ ${QET_DIR}/sources/diagramcontexttoolbar.cpp
+ ${QET_DIR}/sources/diagramcontexttoolbar.h
${QET_DIR}/sources/titleblockcell.cpp
${QET_DIR}/sources/titleblockcell.h
${QET_DIR}/sources/titleblockproperties.cpp
diff --git a/sources/diagramcontexttoolbar.cpp b/sources/diagramcontexttoolbar.cpp
new file mode 100644
index 000000000..c8e8ab3be
--- /dev/null
+++ b/sources/diagramcontexttoolbar.cpp
@@ -0,0 +1,164 @@
+/*
+ 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 "diagramcontexttoolbar.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+ /// Fully opaque within this distance of the toolbar, in pixels...
+const int opaque_distance = 30;
+ /// ...fading to hidden at this one
+const int hide_distance = 200;
+}
+
+/**
+ @brief DiagramContextToolbar::DiagramContextToolbar
+ @param viewport : the view's viewport, which it is drawn on and watches
+*/
+DiagramContextToolbar::DiagramContextToolbar(QWidget *viewport) :
+ QFrame(viewport)
+{
+ setFrameShape(QFrame::StyledPanel);
+ setAutoFillBackground(true);
+ setFocusPolicy(Qt::NoFocus);
+
+ m_layout = new QHBoxLayout(this);
+ m_layout->setContentsMargins(2, 2, 2, 2);
+ m_layout->setSpacing(1);
+
+ m_opacity = new QGraphicsOpacityEffect(this);
+ setGraphicsEffect(m_opacity);
+ hide();
+
+ viewport->installEventFilter(this);
+ //Key presses go to the view, not its viewport
+ if (viewport->parentWidget()) {
+ viewport->parentWidget()->installEventFilter(this);
+ }
+}
+
+/**
+ @return whether the toolbar should be shown at all (a preference)
+*/
+bool DiagramContextToolbar::isEnabled()
+{
+ return QSettings().value(QStringLiteral("diagrameditor/context_toolbar"),
+ true).toBool();
+}
+
+/**
+ @brief DiagramContextToolbar::showAt
+ Show @a actions just above and to the right of @a viewport_pos, kept
+ inside the viewport. Clicking a button triggers the action and leaves
+ the toolbar up, so a command such as rotate can be clicked again.
+*/
+void DiagramContextToolbar::showAt(const QPoint &viewport_pos,
+ const QList &actions)
+{
+ while (QLayoutItem *item = m_layout->takeAt(0)) {
+ delete item->widget();
+ delete item;
+ }
+ for (QAction *action : actions.mid(0, max_actions))
+ {
+ auto *button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setFocusPolicy(Qt::NoFocus);
+ button->setIconSize(QSize(20, 20));
+ const QString text = action->text().remove(QLatin1Char('&'));
+ if (action->icon().isNull()) {
+ button->setText(text);
+ } else {
+ button->setIcon(action->icon());
+ }
+ button->setToolTip(text);
+ button->setEnabled(action->isEnabled());
+ connect(button, &QToolButton::clicked, action, &QAction::trigger);
+ m_layout->addWidget(button);
+ }
+ if (actions.isEmpty()) {
+ hide();
+ return;
+ }
+
+ adjustSize();
+ const QRect area = parentWidget()->rect();
+ QPoint pos = viewport_pos + QPoint(16, -height() - 16);
+ pos.setX(qBound(area.left(), pos.x(), area.right() - width()));
+ pos.setY(qBound(area.top(), pos.y(), area.bottom() - height()));
+ move(pos);
+ m_opacity->setOpacity(1.0);
+ show();
+ raise();
+}
+
+/**
+ @brief DiagramContextToolbar::followMouse
+ Fade with the mouse's distance from the toolbar, and hide past
+ hide_distance.
+*/
+void DiagramContextToolbar::followMouse(const QPoint &viewport_pos)
+{
+ const QRect r = geometry();
+ const int dx = qMax(qMax(r.left() - viewport_pos.x(), 0), viewport_pos.x() - r.right());
+ const int dy = qMax(qMax(r.top() - viewport_pos.y(), 0), viewport_pos.y() - r.bottom());
+ const int distance = qMax(dx, dy);
+
+ if (distance >= hide_distance) {
+ hide();
+ return;
+ }
+ const qreal fade = qreal(distance - opaque_distance)
+ / (hide_distance - opaque_distance);
+ m_opacity->setOpacity(qBound(0.15, 1.0 - fade, 1.0));
+}
+
+/**
+ @brief DiagramContextToolbar::eventFilter
+ Watch the viewport and the view while shown: follow the mouse, and hide
+ on a press elsewhere, a wheel turn or a key press.
+*/
+bool DiagramContextToolbar::eventFilter(QObject *watched, QEvent *event)
+{
+ Q_UNUSED(watched)
+ if (!isVisible()) {
+ return false;
+ }
+
+ switch (event->type())
+ {
+ case QEvent::MouseMove:
+ followMouse(static_cast(event)->position().toPoint());
+ break;
+ case QEvent::MouseButtonPress:
+ case QEvent::Wheel:
+ case QEvent::KeyPress:
+ case QEvent::Leave:
+ hide();
+ break;
+ default:
+ break;
+ }
+ return false;
+}
diff --git a/sources/diagramcontexttoolbar.h b/sources/diagramcontexttoolbar.h
new file mode 100644
index 000000000..5f550a93f
--- /dev/null
+++ b/sources/diagramcontexttoolbar.h
@@ -0,0 +1,60 @@
+/*
+ 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 DIAGRAMCONTEXTTOOLBAR_H
+#define DIAGRAMCONTEXTTOOLBAR_H
+
+#include
+#include
+
+class QAction;
+class QGraphicsOpacityEffect;
+class QHBoxLayout;
+
+/**
+ @brief A small row of commands shown beside the cursor after a click
+ selects something on a folio, as SolidWorks does.
+
+ It lives on the view's viewport, so it never takes the focus or opens a
+ window. It fades as the mouse moves away and hides once the mouse is far
+ enough, on a click elsewhere, a wheel turn or a key press. The commands
+ are the shortcut bar's for the selection (ShortcutBarSettings).
+*/
+class DiagramContextToolbar : public QFrame
+{
+ Q_OBJECT
+
+ public:
+ explicit DiagramContextToolbar(QWidget *viewport);
+
+ void showAt(const QPoint &viewport_pos, const QList &actions);
+ static bool isEnabled();
+
+ /// At most this many commands, so it stays small
+ static const int max_actions = 8;
+
+ protected:
+ bool eventFilter(QObject *watched, QEvent *event) override;
+
+ private:
+ void followMouse(const QPoint &viewport_pos);
+
+ QHBoxLayout *m_layout = nullptr;
+ QGraphicsOpacityEffect *m_opacity = nullptr;
+};
+
+#endif // DIAGRAMCONTEXTTOOLBAR_H
diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp
index 9d9f3edf7..39feac21c 100644
--- a/sources/diagramview.cpp
+++ b/sources/diagramview.cpp
@@ -37,6 +37,9 @@
#include "utils/conductorcreator.h"
#include "undocommand/addgraphicsobjectcommand.h"
#include "diagram.h"
+#include "diagramcontexttoolbar.h"
+#include "shortcutbarsettings.h"
+#include "shortcutmanager.h"
#include "ElementsCollection/xmlelementcollection.h"
#include "NameList/nameslist.h"
#include "elementdialog.h"
@@ -104,6 +107,13 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
m_separators.last()->setSeparator(true);
}
+ m_context_toolbar = new DiagramContextToolbar(viewport());
+ connect(m_diagram, &QGraphicsScene::selectionChanged, this, [this]() {
+ if (m_diagram->selectedItems().isEmpty()) {
+ m_context_toolbar->hide();
+ }
+ });
+
connect(m_diagram, &Diagram::showDiagram, this, &DiagramView::showDiagram);
connect(m_diagram, &QGraphicsScene::sceneRectChanged, this, &DiagramView::adjustSceneRect);
connect(&(m_diagram -> border_and_titleblock), &BorderTitleBlock::informationChanged, this, &DiagramView::updateWindowTitle);
@@ -621,6 +631,10 @@ void DiagramView::mousePressEvent(QMouseEvent *e)
if (m_event_interface && m_event_interface->mousePressEvent(e)) return;
+ if (e->button() == Qt::LeftButton) {
+ m_press_pos = e->position().toPoint();
+ }
+
//Start drag view when hold the middle button
if (e->button() == Qt::MiddleButton)
{
@@ -777,7 +791,46 @@ void DiagramView::mouseReleaseEvent(QMouseEvent *e)
e->accept();
}
else
+ {
QGraphicsView::mouseReleaseEvent(e);
+
+ //A click, not a drag: moving items or a rubber band selection
+ //should not be followed by a toolbar under the mouse.
+ const QPoint pos = e->position().toPoint();
+ if (e->button() == Qt::LeftButton
+ && (pos - m_press_pos).manhattanLength() < QApplication::startDragDistance()) {
+ showContextToolbar(pos);
+ }
+ }
+}
+
+/**
+ @brief DiagramView::showContextToolbar
+ After a click that leaves something selected, show the shortcut bar's
+ commands for that selection beside the cursor. Not while placing or
+ drawing, nor on a read-only folio, nor when switched off in the
+ configuration.
+ @param viewport_pos : where the click was
+*/
+void DiagramView::showContextToolbar(const QPoint &viewport_pos)
+{
+ const QList selection = m_diagram->selectedItems();
+ QETDiagramEditor *qde = diagramEditor();
+ if (selection.isEmpty() || !qde
+ || m_diagram->isReadOnly() || m_diagram->eventInterfaceIsRunning()
+ || !DiagramContextToolbar::isEnabled()) {
+ m_context_toolbar->hide();
+ return;
+ }
+
+ QList actions;
+ const auto context = ShortcutBarSettings::contextFor(selection);
+ for (const QString &id : ShortcutBarSettings::ids(context)) {
+ if (QAction *action = ShortcutManager::instance().action(id, qde)) {
+ actions << action;
+ }
+ }
+ m_context_toolbar->showAt(viewport_pos, actions);
}
/**
diff --git a/sources/diagramview.h b/sources/diagramview.h
index 03eb067d5..a0c311cd1 100644
--- a/sources/diagramview.h
+++ b/sources/diagramview.h
@@ -27,6 +27,7 @@
class CellRuler;
class Conductor;
class Diagram;
+class DiagramContextToolbar;
class QETDiagramEditor;
class DVEventInterface;
class QInputEvent;
@@ -56,6 +57,8 @@ class DiagramView : public PaletteGraphicsView
QAction *m_multi_paste = nullptr;
QAction *m_create_template = nullptr;
QPoint m_paste_here_pos;
+ QPoint m_press_pos;
+ DiagramContextToolbar *m_context_toolbar = nullptr;
QPoint m_last_mouse_pos = QPoint(-1, -1);
QPointF m_drag_last_pos;
bool m_fresh_focus_in,
@@ -127,6 +130,7 @@ class DiagramView : public PaletteGraphicsView
bool gestures() const;
void updateCellRulers();
void placeCellRulers();
+ void showContextToolbar(const QPoint &viewport_pos);
/// Lowest and highest allowed value of the view transform scale (m11).
/// Prevents wheel-zoom from driving the transform to overflow, which
diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp
index e5f5f81cb..516d13c16 100644
--- a/sources/qetdiagrameditor.cpp
+++ b/sources/qetdiagrameditor.cpp
@@ -3157,16 +3157,8 @@ void QETDiagramEditor::showShortcutBar()
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;
- }
+ const ShortcutBarSettings::Context context =
+ ShortcutBarSettings::contextFor(dv->diagram()->selectedItems());
elementPicker()->popUpShortcutBar(QCursor::pos(), context);
}
diff --git a/sources/shortcutbarsettings.cpp b/sources/shortcutbarsettings.cpp
index fa66300a3..7832c6ee5 100644
--- a/sources/shortcutbarsettings.cpp
+++ b/sources/shortcutbarsettings.cpp
@@ -17,9 +17,11 @@
*/
#include "shortcutbarsettings.h"
+#include "qetgraphicsitem/conductor.h"
#include "shortcutmanager.h"
#include
+#include
#include
namespace {
@@ -46,6 +48,22 @@ QList ShortcutBarSettings::contexts()
return {Canvas, Selection, Conductor};
}
+/**
+ @return the context for @a selection: Canvas when empty, Conductor when
+ it holds only conductors, Selection otherwise
+*/
+ShortcutBarSettings::Context ShortcutBarSettings::contextFor(
+ const QList &selection)
+{
+ if (selection.isEmpty()) {
+ return Canvas;
+ }
+ const bool only_conductors = std::all_of(
+ selection.cbegin(), selection.cend(),
+ [](QGraphicsItem *item) { return item->type() == Conductor::Type; });
+ return only_conductors ? Conductor : Selection;
+}
+
/**
@return the name of @a context, for the configuration page
*/
diff --git a/sources/shortcutbarsettings.h b/sources/shortcutbarsettings.h
index 8e83b9232..adf533359 100644
--- a/sources/shortcutbarsettings.h
+++ b/sources/shortcutbarsettings.h
@@ -19,6 +19,8 @@
#define SHORTCUTBARSETTINGS_H
#include
+
+class QGraphicsItem;
#include
#include
@@ -41,6 +43,7 @@ class ShortcutBarSettings
};
static QList contexts();
+ static Context contextFor(const QList &selection);
static QString title(Context context);
static QStringList ids(Context context);
static QStringList defaultIds(Context context);
diff --git a/sources/ui/configpage/generalconfigurationpage.cpp b/sources/ui/configpage/generalconfigurationpage.cpp
index c59390c24..bd58a2e2a 100644
--- a/sources/ui/configpage/generalconfigurationpage.cpp
+++ b/sources/ui/configpage/generalconfigurationpage.cpp
@@ -70,6 +70,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
//Stored as "inserts" but presented as "edits", so the default (insert)
//is the unchecked state -- a preference reads better as an opt-out.
ui->m_collection_dblclick_edits->setChecked(!settings.value("elementscollection/double-click-inserts", true).toBool());
+ ui->m_context_toolbar_cb->setChecked(settings.value("diagrameditor/context_toolbar", true).toBool());
ui->DiagramEditor_xGrid_sb->setValue(settings.value("diagrameditor/Xgrid", 10).toInt());
ui->DiagramEditor_yGrid_sb->setValue(settings.value("diagrameditor/Ygrid", 10).toInt());
for (const qreal divisor : TextGrid::divisors)
@@ -298,6 +299,7 @@ void GeneralConfigurationPage::applyConf()
settings.setValue("diagrameditor/grid_display_startup", ui->grid_startup_cb->isChecked());
settings.setValue("diagrameditor/guides_display_startup", ui->guides_startup_cb->isChecked());
settings.setValue("elementscollection/double-click-inserts", !ui->m_collection_dblclick_edits->isChecked());
+ settings.setValue("diagrameditor/context_toolbar", ui->m_context_toolbar_cb->isChecked());
//Grid step and key navigation
settings.setValue("diagrameditor/Xgrid", ui->DiagramEditor_xGrid_sb->value());
settings.setValue("diagrameditor/Ygrid", ui->DiagramEditor_yGrid_sb->value());
diff --git a/sources/ui/configpage/generalconfigurationpage.ui b/sources/ui/configpage/generalconfigurationpage.ui
index 2ab58cdc2..de4b7445c 100644
--- a/sources/ui/configpage/generalconfigurationpage.ui
+++ b/sources/ui/configpage/generalconfigurationpage.ui
@@ -87,6 +87,16 @@
+ -
+
+
+ Après un clic qui sélectionne un élément ou un conducteur, les commandes de la barre de raccourcis apparaissent près du curseur et s'effacent quand la souris s'éloigne.
+
+
+ Afficher les commandes près de la sélection
+
+
+
-