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 <noreply@anthropic.com>
This commit is contained in:
Beat Hangartner
2026-10-04 19:07:11 +02:00
parent e89c69719a
commit be8e2372a9
4 changed files with 92 additions and 2 deletions
+6
View File
@@ -118,6 +118,7 @@ class Diagram : public QGraphicsScene
ElementTextsMover m_element_texts_mover;
QGIManager *qgi_manager_;
QETProject *m_project;
QPointer<QGraphicsObject> 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);
+11
View File
@@ -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())
+71 -2
View File
@@ -33,6 +33,7 @@
#include "../utils/qetutils.h"
#include "../QetGraphicsItemModeler/qetgraphicshandleritem.h"
#include <QKeyEvent>
#include <QAction>
#include <QBuffer>
#include <QFileDialog>
@@ -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<QGraphicsSceneMouseEvent *>(event)->modifiers());
return true;
}
if (event->type() == QEvent::GraphicsSceneMouseMove)
{
handlerMouseMoveEvent(index, static_cast<QGraphicsSceneMouseEvent *>(event));
if (!m_drag_cancelled)
handlerMouseMoveEvent(index, static_cast<QGraphicsSceneMouseEvent *>(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();
}
/**
@@ -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<HandleRole> 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