From 3dd977a0403b981745299554055d67ae66d091be Mon Sep 17 00:00:00 2001 From: Beat Hangartner Date: Fri, 9 Oct 2026 14:20:04 +0200 Subject: [PATCH 1/3] Build the application's code once as an object library The executable compiled every source file itself, so a test could only reach the application's classes by listing their .cpp files one by one, which stops working as soon as a class needs a folio, its view or the editor window -- that pulls in nearly everything. The sources (except main.cpp) and the two .qrc files now build once as qelectrotech_objects, an OBJECT library; the executable is main.cpp linked with it. Its link libraries and include folders are PUBLIC, and it passes on this directory's compile definitions, so a target in tests/ that links it compiles and links like the application. lupdate's targets scan the library (main.cpp has no tr() text). The executable, its compile flags and what it saves do not change. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99973095d..1cfa48a77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,6 +146,25 @@ endif() # als laatse include(cmake/define_definitions.cmake) +# The application's code, compiled once, as an object library: the +# executable is main.cpp linked with it, and tests that need the real +# classes (a folio, its view, the editor window) link it too. +set(QET_MAIN_FILE ${QET_DIR}/sources/main.cpp) +list(REMOVE_ITEM QET_SRC_FILES ${QET_MAIN_FILE}) +add_library( + ${PROJECT_NAME}_objects + OBJECT + ${QET_RES_FILES} + ${QET_SRC_FILES} + ${QET_DIR}/qelectrotech.qrc + ${QET_DIR}/ico/icon-themes.qrc +) +# The definitions above are set for this whole directory; a test target +# lives in tests/ and gets them from here, through the library. +get_directory_property(QET_DIRECTORY_DEFINITIONS COMPILE_DEFINITIONS) +target_compile_definitions(${PROJECT_NAME}_objects + INTERFACE ${QET_DIRECTORY_DEFINITIONS}) + # On Windows, WIN32 sets /SUBSYSTEM:WINDOWS to suppress the console window. # Qt automatically links qtmain.lib which provides the WinMain entry point, # so no source code change is needed. @@ -153,11 +172,8 @@ if(WIN32) add_executable( ${PROJECT_NAME} WIN32 - ${QET_RES_FILES} - ${QET_SRC_FILES} + ${QET_MAIN_FILE} ${QM_FILES} - ${QET_DIR}/qelectrotech.qrc - ${QET_DIR}/ico/icon-themes.qrc ) # Our own manifest (it declares longPathAware). MSVC merges a .manifest # listed as a source; MinGW needs it embedded through a resource file, @@ -174,13 +190,11 @@ if(WIN32) else() add_executable( ${PROJECT_NAME} - ${QET_RES_FILES} - ${QET_SRC_FILES} + ${QET_MAIN_FILE} ${QM_FILES} - ${QET_DIR}/qelectrotech.qrc - ${QET_DIR}/ico/icon-themes.qrc ) endif() +target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_objects) if(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED) list(APPEND QET_SPACEMOUSE_LIBRARIES PkgConfig::SPNAV) @@ -249,7 +263,7 @@ elseif(Qt6_VERSION VERSION_LESS "6.7") # Automatically creates the update_translations umbrella target. qt_add_lupdate( - ${PROJECT_NAME} + ${PROJECT_NAME}_objects TS_FILES ${TS_FILES} ) @@ -262,7 +276,7 @@ else() ) qt_add_lupdate( - SOURCE_TARGETS ${PROJECT_NAME} + SOURCE_TARGETS ${PROJECT_NAME}_objects TS_FILES ${TS_FILES} LUPDATE_TARGET update_translations NO_GLOBAL_TARGET @@ -288,7 +302,7 @@ if(QET_ENABLE_PCH) "QET_ENABLE_PCH needs CMake 3.16 or newer (found ${CMAKE_VERSION}); " "building without precompiled headers.") else() - target_precompile_headers(${PROJECT_NAME} PRIVATE + target_precompile_headers(${PROJECT_NAME}_objects PRIVATE "$<$:>" "$<$:>" "$<$:>" @@ -299,9 +313,8 @@ if(QET_ENABLE_PCH) endif() target_link_libraries( - ${PROJECT_NAME} + ${PROJECT_NAME}_objects PUBLIC - PRIVATE pugixml::pugixml SingleApplication::SingleApplication SQLite3::SQLite3 @@ -311,8 +324,8 @@ target_link_libraries( ) target_include_directories( - ${PROJECT_NAME} - PRIVATE + ${PROJECT_NAME}_objects + PUBLIC ${QET_DIR}/sources/titleblock ${QET_DIR}/sources/ui ${QET_DIR}/sources/qetgraphicsitem @@ -349,8 +362,8 @@ target_include_directories( if(NOT BUILD_WITH_KF) target_include_directories( - ${PROJECT_NAME} - PRIVATE + ${PROJECT_NAME}_objects + PUBLIC ${QET_DIR}/sources/ui/nokde ) endif() From eab557d9af36b3cfc6abd60471eb9fb4a86ed41d Mon Sep 17 00:00:00 2001 From: Beat Hangartner Date: Fri, 9 Oct 2026 14:20:04 +0200 Subject: [PATCH 2/3] Test that Space on a picture turns the rotation its file saves The first test that runs a real QETApp, offscreen (qetapptest.h): it links qelectrotech_objects, keeps its settings, configuration and data in a temporary folder, and triggers the editor's own actions. tst_rotateselectionimage covers #1413: Space ("rotate_selection") turns a picture's rotationAngle, not QGraphicsItem's rotation, in place, and a picture read back from its XML has the angle; undo and redo, twice; and a group turn ("rotate_group_selection") moves each picture's pivot a quarter turn around the group's centre. All three fail with the code from before #1413. Co-Authored-By: Claude Opus 5.5 --- tests/qttest/CMakeLists.txt | 16 +++ tests/qttest/qetapptest.h | 112 ++++++++++++++++ tests/qttest/tst_rotateselectionimage.cpp | 152 ++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 tests/qttest/qetapptest.h create mode 100644 tests/qttest/tst_rotateselectionimage.cpp diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 8653c86a4..1a5073f3c 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -235,6 +235,22 @@ set_tests_properties(tst_handlersize PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=off target_include_directories(tst_handlersize PRIVATE ${QET_DIR}/sources) target_link_libraries(tst_handlersize PRIVATE Qt::Test Qt::Widgets) +# Tests that need the application around the class they test -- a folio, +# its view, the editor window and its actions -- link the application's own +# code (the qelectrotech_objects library) and run a real QETApp offscreen, +# with settings and folders of their own (qetapptest.h). +function(qet_add_app_test name) + add_executable(${name} ${ARGN} qetapptest.h) + add_test(NAME ${name} COMMAND ${name}) + set_tests_properties(${name} PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen") + target_include_directories(${name} PRIVATE ${QET_DIR}/sources) + target_link_libraries(${name} PRIVATE qelectrotech_objects Qt::Test) +endfunction() + +# Space on a picture turns its own transform, which the file saves (#1413), +# alone and with a group. +qet_add_app_test(tst_rotateselectionimage tst_rotateselectionimage.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/qetapptest.h b/tests/qttest/qetapptest.h new file mode 100644 index 000000000..6e93f8ea5 --- /dev/null +++ b/tests/qttest/qetapptest.h @@ -0,0 +1,112 @@ +/* + 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 QETAPPTEST_H +#define QETAPPTEST_H + +/** + For tests that need the application around the class they test: a + folio, the view showing it, the editor window and its actions. They + link the application's own code (see qet_add_app_test() in + CMakeLists.txt) and run a real QETApp, offscreen, with its settings, + configuration and data in a folder of their own. +*/ + +#include "diagram.h" +#include "diagramview.h" +#include "qetapp.h" +#include "qetarguments.h" +#include "qetdiagrameditor.h" + +#include +#include +#include +#include +#include +#include + +namespace QetAppTest { + +/// The editor window QETApp opened at start. +inline QETDiagramEditor *editor() +{ + const QList editors = QETApp::diagramEditors(); + return editors.isEmpty() ? nullptr : editors.first(); +} + +/// The view of the folio shown in the editor, after a new project of one +/// folio if the editor has none open. +inline DiagramView *view() +{ + QETDiagramEditor *e = editor(); + if (!e) + return nullptr; + if (!e->findChild()) + e->newProject(); + return e->findChild(); +} + +inline Diagram *diagram() +{ + DiagramView *v = view(); + return v ? v->diagram() : nullptr; +} + +/// The editor's action whose data() is @p name, as the menus, the +/// toolbar and the keyboard shortcut trigger it ("rotate_selection" is +/// Space). +inline QAction *action(const QString &name) +{ + QETDiagramEditor *e = editor(); + if (!e) + return nullptr; + for (QAction *a : e->findChildren()) + if (a->data().toString() == name) + return a; + return nullptr; +} + +/// Run @p test inside a QETApp whose settings, configuration and data +/// live in a temporary folder, so that no test reads or changes the +/// user's own. +template +int run(int argc, char **argv) +{ + QApplication app(argc, argv); + QCoreApplication::setOrganizationName("QElectroTech"); + QCoreApplication::setApplicationName("QElectroTech"); + QTemporaryDir home; + if (!home.isValid()) + return 1; + QSettings::setDefaultFormat(QSettings::IniFormat); + QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, home.filePath("settings")); + QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, home.filePath("settings")); + QETApp::applyDirectoryArguments(QETArguments(QList{ + "--config-dir=" + home.filePath("config"), + "--data-dir=" + home.filePath("data")})); + QETApp qetapp; + QCoreApplication::processEvents(); + Test test; + return QTest::qExec(&test, argc, argv); +} + +} // namespace QetAppTest + +#define QET_APP_TEST_MAIN(Test) \ + int main(int argc, char **argv) { return QetAppTest::run(argc, argv); } + +#endif diff --git a/tests/qttest/tst_rotateselectionimage.cpp b/tests/qttest/tst_rotateselectionimage.cpp new file mode 100644 index 000000000..1b23a0f60 --- /dev/null +++ b/tests/qttest/tst_rotateselectionimage.cpp @@ -0,0 +1,152 @@ +/* + 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 "qetgraphicsitem/diagramimageitem.h" + +#include + +/** + Space -- the editor's "rotate_selection" action -- on a picture turns + the picture's own transform (rotationAngle, around its pivot), the + rotation its file saves. Before #1413 it turned QGraphicsItem's plain + rotation, which toXml() does not save, so the picture came back + unrotated once the project was reopened. +*/ +class tst_rotateselectionimage : public QObject +{ + Q_OBJECT + + Diagram *m_diagram = nullptr; + + DiagramImageItem *addPicture(const QPointF &pos) + { + QPixmap pixmap(100, 60); + pixmap.fill(Qt::darkGreen); + auto *picture = new DiagramImageItem(pixmap); + m_diagram->addItem(picture); + picture->setPos(pos); + return picture; + } + + static QPointF pivotInScene(DiagramImageItem *picture) + { + return picture->mapToScene(picture->pivot()); + } + + /// The angle and position a file saves, read back by a new picture. + static DiagramImageItem *reloaded(DiagramImageItem *picture) + { + QDomDocument document; + auto *copy = new DiagramImageItem(); + copy->fromXml(picture->toXml(document)); + return copy; + } + + static void trigger(const char *name) + { + QAction *a = QetAppTest::action(QString::fromLatin1(name)); + QVERIFY2(a, name); + a->trigger(); + } + +private slots: + void initTestCase() + { + QVERIFY(QetAppTest::editor()); + m_diagram = QetAppTest::diagram(); + QVERIFY(m_diagram); + } + + void cleanup() + { + m_diagram->clearSelection(); + for (QGraphicsItem *item : m_diagram->items()) + if (item->type() == DiagramImageItem::Type) + delete item; + m_diagram->undoStack().clear(); + } + + void spaceTurnsThePictureAndTheFileKeepsIt() + { + DiagramImageItem *picture = addPicture(QPointF(200, 200)); + const QPointF pivot = pivotInScene(picture); + picture->setSelected(true); + + trigger("rotate_selection"); + trigger("rotate_selection"); + + QCOMPARE(picture->rotationAngle(), 180.0); + QCOMPARE(picture->rotation(), 0.0); // not QGraphicsItem's own rotation + QCOMPARE(pivotInScene(picture), pivot); // turned in place + + std::unique_ptr copy(reloaded(picture)); + QCOMPARE(copy->rotationAngle(), 180.0); + QCOMPARE(copy->pos(), picture->pos()); + } + + void undoAndRedo() + { + DiagramImageItem *picture = addPicture(QPointF(200, 200)); + const QPointF pos = picture->pos(); + picture->setSelected(true); + trigger("rotate_selection"); + + // RotateSelectionCommand animates undo and redo after the first + // time: the values arrive a moment later. + for (int round = 0; round < 2; ++round) + { + m_diagram->undoStack().undo(); + QTRY_COMPARE(picture->rotationAngle(), 0.0); + QCOMPARE(picture->pos(), pos); + m_diagram->undoStack().redo(); + QTRY_COMPARE(picture->rotationAngle(), 90.0); + QCOMPARE(picture->pos(), pos); + } + } + + // Turned as a group, each picture turns on itself and its pivot moves + // a quarter turn around the group's centre (the centre of the + // selection's bounding box, snapped to the grid). + void groupTurnMovesEachPivotAroundTheCentre() + { + DiagramImageItem *a = addPicture(QPointF(100, 100)); + DiagramImageItem *b = addPicture(QPointF(400, 160)); + a->setSelected(true); + b->setSelected(true); + const QPointF centre = Diagram::snapToGrid( + (a->sceneBoundingRect() | b->sceneBoundingRect()).center()); + const QTransform quarter = QTransform().translate(centre.x(), centre.y()) + .rotate(90).translate(-centre.x(), -centre.y()); + const QPointF expectedA = quarter.map(pivotInScene(a)); + const QPointF expectedB = quarter.map(pivotInScene(b)); + + trigger("rotate_group_selection"); + + QCOMPARE(a->rotationAngle(), 90.0); + QCOMPARE(b->rotationAngle(), 90.0); + QCOMPARE(pivotInScene(a), expectedA); + QCOMPARE(pivotInScene(b), expectedB); + + std::unique_ptr copy(reloaded(a)); + QCOMPARE(copy->rotationAngle(), 90.0); + QCOMPARE(pivotInScene(copy.get()) - copy->pos(), pivotInScene(a) - a->pos()); + } +}; + +QET_APP_TEST_MAIN(tst_rotateselectionimage) +#include "tst_rotateselectionimage.moc" From c52cd3b2fca48c70c54344a7e8dfc1aa69bdda3a Mon Sep 17 00:00:00 2001 From: Beat Hangartner Date: Fri, 9 Oct 2026 14:35:26 +0200 Subject: [PATCH 3/3] Keep a line's pivot at its centre when one of its ends is dragged A shape's pivot follows its centre when it is resized, unless it was placed by hand: setLine() and setRect() do that, and so did dragging a rectangle's or an ellipse's handles, which goes through setRect(). Dragging an end of a line moved the end directly and left the pivot where it was, so the next rotation turned the line around a point beside it. Undo and redo already re-centred it, through setLine(). The line's handle drag now applies the same rule. New tst_linepivotresize, on a real folio with scene mouse events: the pivot is at the new centre after an end is dragged (fails without the change: it stays at the old centre), and after undo and redo; a pivot placed by hand stays; a rectangle's pivot follows as before. Co-Authored-By: Claude Opus 5.5 --- sources/qetgraphicsitem/qetshapeitem.cpp | 4 + tests/qttest/CMakeLists.txt | 4 + tests/qttest/tst_linepivotresize.cpp | 163 +++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 tests/qttest/tst_linepivotresize.cpp diff --git a/sources/qetgraphicsitem/qetshapeitem.cpp b/sources/qetgraphicsitem/qetshapeitem.cpp index 19e7ebdda..f80da1583 100644 --- a/sources/qetgraphicsitem/qetshapeitem.cpp +++ b/sources/qetgraphicsitem/qetshapeitem.cpp @@ -2465,6 +2465,10 @@ void QetShapeItem::dragResize(int index, const QPointF &localPos, Qt::KeyboardMo { prepareGeometryChange(); (index == 0 ? m_P1 : m_P2) = localPos; + // The same rule as setLine() and setRect(): a pivot nobody placed + // by hand follows the centre. + if (!m_pivotIsCustom) + resetPivotToBoundingRectCenter(); repositionHandles(); emit geometryChanged(); return; diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 1a5073f3c..ba6f0e2f4 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -263,6 +263,10 @@ set_tests_properties(tst_textresizehandles PROPERTIES ENVIRONMENT "QT_QPA_PLATFO target_include_directories(tst_textresizehandles PRIVATE ${QET_DIR}/sources) target_link_libraries(tst_textresizehandles PRIVATE Qt::Test Qt::Widgets) +# A line's pivot follows its centre when one of its ends is dragged, as a +# rectangle's does; a pivot placed by hand stays. +qet_add_app_test(tst_linepivotresize tst_linepivotresize.cpp) + # legacyelementtypes.h is header-only: the rewrite of element type names in # nomenclature queries saved before June 2022. add_executable(tst_legacyelementtypes tst_legacyelementtypes.cpp) diff --git a/tests/qttest/tst_linepivotresize.cpp b/tests/qttest/tst_linepivotresize.cpp new file mode 100644 index 000000000..78f7eb5d7 --- /dev/null +++ b/tests/qttest/tst_linepivotresize.cpp @@ -0,0 +1,163 @@ +/* + 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/qetshapeitem.h" + +#include + +/** + A shape's pivot follows its centre when it is resized, unless it was + placed by hand. Rectangles and ellipses did; a line did not: dragging + one of its ends moved the end and left the pivot where it was, so the + next rotation turned the line around a point beside it. + + The handles are pressed and dragged with scene mouse events, as the + view sends them. +*/ +class tst_linepivotresize : public QObject +{ + Q_OBJECT + + Diagram *m_diagram = nullptr; + QetShapeItem *m_shape = 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); + } + + 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; + } + + void drag(QGraphicsItem *handle, const QPointF &delta) + { + QVERIFY(handle); + const QPointF from = handle->scenePos(); + send(handle, QEvent::GraphicsSceneMousePress, from); + send(handle, QEvent::GraphicsSceneMouseMove, from + delta / 2); + send(handle, QEvent::GraphicsSceneMouseMove, from + delta); + send(handle, QEvent::GraphicsSceneMouseRelease, from + delta); + } + + /// A click on the selected shape: the next handle mode. + void click(const QPointF &local) + { + const QPointF at = m_shape->mapToScene(local); + send(m_shape, QEvent::GraphicsSceneMousePress, at); + send(m_shape, QEvent::GraphicsSceneMouseRelease, at); + } + + QetShapeItem *add(QetShapeItem::ShapeType type, const QPointF &p1, const QPointF &p2) + { + auto *shape = new QetShapeItem(p1, p2, type); + m_diagram->addItem(shape); + shape->setPos(200, 200); + shape->setSelected(true); + return shape; + } + +private slots: + void initTestCase() + { + QVERIFY(QetAppTest::editor()); + m_diagram = QetAppTest::diagram(); + QVERIFY(m_diagram); + } + + void cleanup() + { + m_diagram->clearSelection(); + delete m_shape; + m_shape = nullptr; + m_diagram->undoStack().clear(); + } + + void lineEndDragKeepsThePivotAtTheCentre() + { + m_shape = add(QetShapeItem::Line, QPointF(0, 0), QPointF(100, 0)); + QCOMPARE(m_shape->pivot(), QPointF(50, 0)); + + drag(handleAt(m_shape->mapToScene(QPointF(100, 0))), QPointF(40, 30)); + + QCOMPARE(m_shape->line().p2(), QPointF(140, 30)); + QCOMPARE(m_shape->pivot(), m_shape->line().center()); + QVERIFY(!m_shape->pivotIsCustom()); + } + + void undoAndRedoKeepItAtTheCentre() + { + m_shape = add(QetShapeItem::Line, QPointF(0, 0), QPointF(100, 0)); + drag(handleAt(m_shape->mapToScene(QPointF(100, 0))), QPointF(40, 30)); + for (int round = 0; round < 2; ++round) + { + m_diagram->undoStack().undo(); + QCOMPARE(m_shape->line().p2(), QPointF(100, 0)); + QCOMPARE(m_shape->pivot(), QPointF(50, 0)); + m_diagram->undoStack().redo(); + QCOMPARE(m_shape->pivot(), m_shape->line().center()); + } + } + + void handPlacedPivotStays() + { + m_shape = add(QetShapeItem::Line, QPointF(0, 0), QPointF(100, 0)); + click(QPointF(25, 0)); // rotate/skew mode + drag(handleAt(m_shape->mapToScene(m_shape->pivot())), QPointF(20, 10)); + QVERIFY(m_shape->pivotIsCustom()); + const QPointF pivot = m_shape->pivot(); + click(QPointF(25, 0)); // back to resize mode + + drag(handleAt(m_shape->mapToScene(QPointF(100, 0))), QPointF(40, 30)); + + QCOMPARE(m_shape->line().p2(), QPointF(140, 30)); + QCOMPARE(m_shape->pivot(), pivot); + } + + // What a line now does, as a rectangle always did. + void rectangleResizeKeepsThePivotAtTheCentre() + { + m_shape = add(QetShapeItem::Rectangle, QPointF(0, 0), QPointF(100, 60)); + drag(handleAt(m_shape->mapToScene(QPointF(100, 60))), QPointF(40, 30)); + QCOMPARE(m_shape->pivot(), QRectF(QPointF(0, 0), QPointF(140, 90)).center()); + } +}; + +QET_APP_TEST_MAIN(tst_linepivotresize) +#include "tst_linepivotresize.moc"