mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-06 03:04:13 +02:00
Merge pull request #1055 from ispyisail/feature/context-toolbar
Add a command bar beside the selection after a click
This commit is contained in:
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "diagramcontexttoolbar.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QEvent>
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QSettings>
|
||||
#include <QToolButton>
|
||||
|
||||
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<QAction *> &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<QMouseEvent *>(event)->position().toPoint());
|
||||
break;
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::Wheel:
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::Leave:
|
||||
hide();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef DIAGRAMCONTEXTTOOLBAR_H
|
||||
#define DIAGRAMCONTEXTTOOLBAR_H
|
||||
|
||||
#include <QFrame>
|
||||
#include <QList>
|
||||
|
||||
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<QAction *> &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
|
||||
@@ -38,6 +38,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"
|
||||
@@ -109,6 +112,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);
|
||||
@@ -627,6 +637,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)
|
||||
{
|
||||
@@ -783,7 +797,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<QGraphicsItem *> 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<QAction *> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
class CellRuler;
|
||||
class Conductor;
|
||||
class Diagram;
|
||||
class DiagramContextToolbar;
|
||||
class QETDiagramEditor;
|
||||
class DVEventInterface;
|
||||
class QInputEvent;
|
||||
@@ -58,6 +59,8 @@ class DiagramView : public PaletteGraphicsView
|
||||
QAction *m_create_template = nullptr;
|
||||
QMenu *m_folio_report_menu = 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,
|
||||
@@ -133,6 +136,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
|
||||
|
||||
@@ -3194,16 +3194,8 @@ void QETDiagramEditor::showShortcutBar()
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<QGraphicsItem *> 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);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
*/
|
||||
#include "shortcutbarsettings.h"
|
||||
|
||||
#include "qetgraphicsitem/conductor.h"
|
||||
#include "shortcutmanager.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <algorithm>
|
||||
#include <QSettings>
|
||||
|
||||
namespace {
|
||||
@@ -46,6 +48,22 @@ QList<ShortcutBarSettings::Context> 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<QGraphicsItem *> &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
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#define SHORTCUTBARSETTINGS_H
|
||||
|
||||
#include <QList>
|
||||
|
||||
class QGraphicsItem;
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
@@ -41,6 +43,7 @@ class ShortcutBarSettings
|
||||
};
|
||||
|
||||
static QList<Context> contexts();
|
||||
static Context contextFor(const QList<QGraphicsItem *> &selection);
|
||||
static QString title(Context context);
|
||||
static QStringList ids(Context context);
|
||||
static QStringList defaultIds(Context context);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -87,6 +87,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="m_context_toolbar_cb">
|
||||
<property name="toolTip">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Afficher les commandes près de la sélection</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="guides_startup_cb">
|
||||
<property name="text">
|
||||
|
||||
Reference in New Issue
Block a user