From be8e2372a9003da844178ac0d83721c68ec3a453 Mon Sep 17 00:00:00 2001 From: Beat Hangartner Date: Sun, 4 Oct 2026 19:07:11 +0200 Subject: [PATCH] Cancel a picture's handle drag with Escape Pressing Escape while a handle of a picture was dragged went to DiagramView, which clears the selection. That removed the handles under the mouse, so the drag stopped where it was: the picture kept the new size, angle or pivot, and since the release never arrived, no undo step was recorded and the change could not be undone. Diagram gets a keyboardItem: an item in a state Escape must end. While a resize, rotate, skew or pivot handle is dragged, the picture is that item; DiagramView sends each key to it first, and only keys it does not take reach the view's own shortcuts. Escape puts the picture back as it was when the handle was pressed (position, transform, and whether the pivot was placed by hand) and ignores the rest of the gesture, so no undo step is recorded. The drag also ends when the handles go away. Co-Authored-By: Claude Opus 5.5 --- sources/diagram.h | 6 ++ sources/diagramview.cpp | 11 +++ sources/qetgraphicsitem/diagramimageitem.cpp | 73 +++++++++++++++++++- sources/qetgraphicsitem/diagramimageitem.h | 4 ++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/sources/diagram.h b/sources/diagram.h index 7a11f6d07..e0587a96a 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -118,6 +118,7 @@ class Diagram : public QGraphicsScene ElementTextsMover m_element_texts_mover; QGIManager *qgi_manager_; QETProject *m_project; + QPointer m_keyboard_item; QDomDocument xml_document_; @@ -274,6 +275,11 @@ class Diagram : public QGraphicsScene QUndoStack &undoStack(); QGIManager &qgiManager(); + /// The item that gets keys before the view's own shortcuts, while + /// it is in a state Escape must end (a picture whose handle is + /// dragged). nullptr the rest of the time. + void setKeyboardItem(QGraphicsObject *item) { m_keyboard_item = item; } + QGraphicsObject *keyboardItem() const { return m_keyboard_item; } //methods related to element label Update Policy void freezeElements(bool freeze); diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index fe586a03b..092cd9ea5 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -1155,6 +1155,17 @@ void DiagramView::keyPressEvent(QKeyEvent *e) if (m_event_interface && m_event_interface->keyPressEvent(e)) return; + //An item in a state Escape must end -- a picture while one of its + //handles is dragged -- sees each key before the shortcuts below, + //which would otherwise clear the selection under it. + QGraphicsObject *keyboard_item = m_diagram ? m_diagram->keyboardItem() : nullptr; + if (keyboard_item && keyboard_item->scene() == m_diagram) { + e->ignore(); + m_diagram->sendEvent(keyboard_item, e); + if (e->isAccepted()) + return; + } + ProjectView *current_project = this->diagramEditor()->currentProjectView(); DiagramContent dc(m_diagram); switch(e -> key()) diff --git a/sources/qetgraphicsitem/diagramimageitem.cpp b/sources/qetgraphicsitem/diagramimageitem.cpp index e32464642..ed81b4619 100644 --- a/sources/qetgraphicsitem/diagramimageitem.cpp +++ b/sources/qetgraphicsitem/diagramimageitem.cpp @@ -33,6 +33,7 @@ #include "../utils/qetutils.h" #include "../QetGraphicsItemModeler/qetgraphicshandleritem.h" +#include #include #include #include @@ -599,6 +600,10 @@ void DiagramImageItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) */ void DiagramImageItem::clearHandles() { + // The handles go (deselected, or another handle mode): a drag of one + // of them cannot end with its release any more. + if (m_vector_index != -1) + endHandleDrag(); if (!m_handler_vector.isEmpty()) { qDeleteAll(m_handler_vector); @@ -726,17 +731,22 @@ bool DiagramImageItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event) if (event->type() == QEvent::GraphicsSceneMousePress) { + m_drag_cancelled = false; handlerMousePressEvent(index, static_cast(event)->modifiers()); return true; } if (event->type() == QEvent::GraphicsSceneMouseMove) { - handlerMouseMoveEvent(index, static_cast(event)); + if (!m_drag_cancelled) + handlerMouseMoveEvent(index, static_cast(event)); return true; } if (event->type() == QEvent::GraphicsSceneMouseRelease) { - handlerMouseReleaseEvent(index); + if (m_drag_cancelled) + m_drag_cancelled = false; + else + handlerMouseReleaseEvent(index); return true; } if (event->type() == QEvent::GraphicsSceneHoverEnter) @@ -806,6 +816,12 @@ void DiagramImageItem::handlerMousePressEvent(int index, Qt::KeyboardModifiers m m_original_pos = pos(); m_original_transform = m_transform; m_original_pivotIsCustom = m_pivotIsCustom; + // The handle has the mouse, not the keyboard: ask the view for the + // keys for the length of the drag, so that Escape reaches + // keyPressEvent() and not the view's own Escape, which clears the + // selection. + if (diagram()) + diagram()->setKeyboardItem(this); if (m_handleRoles.at(index) == HandleRole::Resize) { @@ -959,7 +975,60 @@ void DiagramImageItem::handlerMouseReleaseEvent(int index) } } + endHandleDrag(); +} + +/** + @brief DiagramImageItem::keyPressEvent + The view sends keys here only while one of the picture's handles is + dragged (see handlerMousePressEvent()): Escape cancels that drag. Any + other key is left to the view. +*/ +void DiagramImageItem::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape && m_vector_index != -1) + { + cancelHandleDrag(); + event->accept(); + return; + } + QetGraphicsItem::keyPressEvent(event); +} + +/** + @brief DiagramImageItem::endHandleDrag + The drag is over: no handle is dragged any more, and the keyboard + goes back to the view. +*/ +void DiagramImageItem::endHandleDrag() +{ m_vector_index = -1; + if (diagram() && diagram()->keyboardItem() == this) + diagram()->setKeyboardItem(nullptr); +} + +/** + @brief DiagramImageItem::cancelHandleDrag + Put the picture back exactly as it was when the handle was pressed -- + transform, pivot (Resize moves it for the drag) and position -- and + ignore the rest of the mouse gesture, so its release pushes no undo + command. +*/ +void DiagramImageItem::cancelHandleDrag() +{ + prepareGeometryChange(); + m_deferHandleReposition = true; + m_transform = m_original_transform; + m_pivotIsCustom = m_original_pivotIsCustom; // a pivot drag marks it as hand-placed + // Not the snapping setPos(): see handlerMousePressEvent(). + QGraphicsObject::setPos(m_original_pos); + setTransform(m_transform.toMatrix()); + m_deferHandleReposition = false; + emit transformChanged(); + repositionHandles(); + endHandleDrag(); + m_drag_cancelled = true; + clearStatusHint(); } /** diff --git a/sources/qetgraphicsitem/diagramimageitem.h b/sources/qetgraphicsitem/diagramimageitem.h index 820c9fd5c..61ede8ccb 100644 --- a/sources/qetgraphicsitem/diagramimageitem.h +++ b/sources/qetgraphicsitem/diagramimageitem.h @@ -174,6 +174,7 @@ class DiagramImageItem : public QetGraphicsItem { void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; private: @@ -203,6 +204,8 @@ class DiagramImageItem : public QetGraphicsItem { void handlerMousePressEvent(int index, Qt::KeyboardModifiers mods); void handlerMouseMoveEvent(int index, QGraphicsSceneMouseEvent *event); void handlerMouseReleaseEvent(int index); + void cancelHandleDrag(); + void endHandleDrag(); void dragResize(int index, const QPointF &localPos, Qt::KeyboardModifiers mods); void dragRotateHandle(int cornerIndex, const QPointF &scenePos, Qt::KeyboardModifiers mods); void dragSkewHandle(int edgeIndex, const QPointF &scenePos, Qt::KeyboardModifiers mods); @@ -264,6 +267,7 @@ class DiagramImageItem : public QetGraphicsItem { QVector m_handleRoles; int m_vector_index = -1; QPointF m_original_pos; // scene position at the start of a resize/rotate/pivot drag, for Escape-to-cancel + bool m_drag_cancelled = false; // Escape pressed: ignore the rest of this mouse gesture ShapeTransform m_original_transform; bool m_original_pivotIsCustom = false; bool m_deferHandleReposition = false; // see setPivot()'s comment