diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt
index 9ea46caf6..00aafbfe2 100644
--- a/tests/qttest/CMakeLists.txt
+++ b/tests/qttest/CMakeLists.txt
@@ -260,6 +260,10 @@ endfunction()
# alone and with a group.
qet_add_app_test(tst_rotateselectionimage tst_rotateselectionimage.cpp)
+# Escape during a picture's resize, rotate or pivot drag puts it back, keeps
+# it selected and records no undo step (#1414); the key goes to the view.
+qet_add_app_test(tst_imagehandleescape tst_imagehandleescape.cpp)
+
# TextResizeHandles: the corner handles that change the width of a text box,
# on a plain QGraphicsTextItem with the "textWidth" property QET texts have.
add_executable(
diff --git a/tests/qttest/tst_imagehandleescape.cpp b/tests/qttest/tst_imagehandleescape.cpp
new file mode 100644
index 000000000..fed3a030b
--- /dev/null
+++ b/tests/qttest/tst_imagehandleescape.cpp
@@ -0,0 +1,177 @@
+/*
+ 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 "qetapptest.h"
+#include "QetGraphicsItemModeler/qetgraphicshandleritem.h"
+#include "qetgraphicsitem/diagramimageitem.h"
+
+#include
+
+/**
+ Escape while a handle of a picture is dragged (#1414): the key goes to
+ the real DiagramView, whose own Escape clears the selection. Before
+ #1414 that removed the handles mid-drag, so the picture kept the drag
+ and no undo step was recorded. Now the picture is back as it was at
+ the press, still selected, with no undo step, and the view's own
+ Escape still clears the selection afterwards.
+
+ The handles are pressed and dragged with scene mouse events, as the
+ view sends them; the key goes through the view.
+*/
+class tst_imagehandleescape : public QObject
+{
+ Q_OBJECT
+
+ Diagram *m_diagram = nullptr;
+ DiagramView *m_view = nullptr;
+ DiagramImageItem *m_picture = nullptr;
+
+ void send(QGraphicsItem *item, QEvent::Type type, const QPointF &scenePos)
+ {
+ QGraphicsSceneMouseEvent event(type);
+ event.setButton(Qt::LeftButton);
+ event.setButtons(type == QEvent::GraphicsSceneMouseRelease ? Qt::NoButton : Qt::LeftButton);
+ event.setScenePos(scenePos);
+ event.setPos(item->mapFromScene(scenePos));
+ event.setButtonDownScenePos(Qt::LeftButton, scenePos);
+ m_diagram->sendEvent(item, &event);
+ }
+
+ /// The picture's handle nearest to @p scenePos.
+ QGraphicsItem *handleAt(const QPointF &scenePos)
+ {
+ QGraphicsItem *best = nullptr;
+ qreal bestDistance = 0;
+ for (QGraphicsItem *item : m_diagram->items())
+ {
+ if (item->type() != QetGraphicsHandlerItem::Type)
+ continue;
+ const qreal d = QLineF(item->scenePos(), scenePos).length();
+ if (!best || d < bestDistance)
+ {
+ best = item;
+ bestDistance = d;
+ }
+ }
+ return best;
+ }
+
+ /// A second click on the selected picture: rotate/skew handles.
+ void switchToRotateMode()
+ {
+ const QPointF centre = m_picture->mapToScene(m_picture->imageRect().center());
+ send(m_picture, QEvent::GraphicsSceneMousePress, centre);
+ send(m_picture, QEvent::GraphicsSceneMouseRelease, centre);
+ }
+
+ struct State
+ {
+ QPointF pos;
+ qreal scaleX, scaleY, rotation;
+ QPointF pivot;
+ bool operator==(const State &o) const
+ {
+ return qFuzzyCompare(1 + QLineF(pos, o.pos).length(), 1.0)
+ && qFuzzyCompare(scaleX, o.scaleX) && qFuzzyCompare(scaleY, o.scaleY)
+ && qFuzzyCompare(1 + rotation, 1 + o.rotation)
+ && qFuzzyCompare(1 + QLineF(pivot, o.pivot).length(), 1.0);
+ }
+ };
+ State state() const
+ {
+ return {m_picture->pos(), m_picture->scaleFactorX(), m_picture->scaleFactorY(),
+ m_picture->rotationAngle(), m_picture->pivot()};
+ }
+
+ /// Press @p handle, drag it by @p delta, Escape on the view, release.
+ void dragThenEscape(QGraphicsItem *handle, const QPointF &delta)
+ {
+ QVERIFY(handle);
+ const State before = state();
+ const int undoIndex = m_diagram->undoStack().index();
+ const QPointF from = handle->scenePos(), to = from + delta;
+ send(handle, QEvent::GraphicsSceneMousePress, from);
+ send(handle, QEvent::GraphicsSceneMouseMove, from + delta / 2);
+ send(handle, QEvent::GraphicsSceneMouseMove, to);
+ QVERIFY2(!(state() == before), "the drag changed nothing");
+
+ QTest::keyClick(m_view, Qt::Key_Escape);
+
+ QVERIFY2(m_picture->isSelected(), "Escape cleared the selection under the drag");
+ QVERIFY2(state() == before, "the picture was not put back");
+ // The rest of the gesture: the handle is still under the mouse.
+ send(handle, QEvent::GraphicsSceneMouseMove, to + delta);
+ send(handle, QEvent::GraphicsSceneMouseRelease, to + delta);
+ QVERIFY2(state() == before, "the rest of the gesture moved the picture");
+ QCOMPARE(m_diagram->undoStack().index(), undoIndex);
+ }
+
+private slots:
+ void initTestCase()
+ {
+ m_view = QetAppTest::view();
+ QVERIFY(m_view);
+ m_diagram = m_view->diagram();
+ }
+
+ void init()
+ {
+ QPixmap pixmap(100, 60);
+ pixmap.fill(Qt::darkGreen);
+ m_picture = new DiagramImageItem(pixmap);
+ m_diagram->addItem(m_picture);
+ m_picture->setPos(300, 300);
+ m_picture->setSelected(true);
+ }
+
+ void cleanup()
+ {
+ m_diagram->clearSelection();
+ delete m_picture;
+ m_picture = nullptr;
+ m_diagram->undoStack().clear();
+ }
+
+ void resize()
+ {
+ dragThenEscape(handleAt(m_picture->mapToScene(m_picture->imageRect().bottomRight())),
+ QPointF(40, 30));
+ }
+
+ void rotate()
+ {
+ switchToRotateMode();
+ dragThenEscape(handleAt(m_picture->mapToScene(m_picture->imageRect().topRight())),
+ QPointF(0, 60));
+ }
+
+ void pivot()
+ {
+ switchToRotateMode();
+ dragThenEscape(handleAt(m_picture->mapToScene(m_picture->pivot())), QPointF(30, 20));
+ }
+
+ // Without a drag, Escape is the view's again: it clears the selection.
+ void escapeWithoutDragClearsTheSelection()
+ {
+ QTest::keyClick(m_view, Qt::Key_Escape);
+ QVERIFY(!m_picture->isSelected());
+ }
+};
+
+QET_APP_TEST_MAIN(tst_imagehandleescape)
+#include "tst_imagehandleescape.moc"