mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-13 16:34:14 +02:00
Merge pull request #1435 from bhangart/fix/line-pivot-follows-resize
Keep a line's pivot at its centre when one of its ends is dragged
This commit is contained in:
+30
-17
@@ -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
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<QtCore/QtCore$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<QtGui/QtGui$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<QtWidgets/QtWidgets$<ANGLE-R>>"
|
||||
@@ -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
|
||||
@@ -350,8 +363,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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -244,6 +244,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(
|
||||
@@ -256,6 +272,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)
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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 <QAction>
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QSettings>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
namespace QetAppTest {
|
||||
|
||||
/// The editor window QETApp opened at start.
|
||||
inline QETDiagramEditor *editor()
|
||||
{
|
||||
const QList<QETDiagramEditor *> 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<DiagramView *>())
|
||||
e->newProject();
|
||||
return e->findChild<DiagramView *>();
|
||||
}
|
||||
|
||||
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<QAction *>())
|
||||
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 <typename Test>
|
||||
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<QString>{
|
||||
"--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<Test>(argc, argv); }
|
||||
|
||||
#endif
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "qetapptest.h"
|
||||
#include "QetGraphicsItemModeler/qetgraphicshandleritem.h"
|
||||
#include "qetgraphicsitem/qetshapeitem.h"
|
||||
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
|
||||
/**
|
||||
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"
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "qetapptest.h"
|
||||
#include "qetgraphicsitem/diagramimageitem.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
|
||||
/**
|
||||
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<DiagramImageItem> 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<DiagramImageItem> 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"
|
||||
Reference in New Issue
Block a user