Files
OmaPhoto/tests/CanvasNavigationTests.cpp
T

260 lines
13 KiB
C++

#include "CanvasFixtures.h"
#include <QSignalSpy>
// The canvas: Zoom, Hand, Space, the wheel, a pinch.
class CanvasNavigationTests : public QObject {
Q_OBJECT
private slots:
void theZoomToolClicksAndDrags();
void theHandToolAndSpaceDragTheView();
void theWheelPansAndZoomsWithAModifier();
void aPinchZoomsAboutThePointer();
void theMiddleButtonPansFromAnyTool();
};
void CanvasNavigationTests::theZoomToolClicksAndDrags()
{
Shown shown;
CanvasView &canvas = *shown.canvas;
EditorSession &session = shown.session;
shown.settle();
session.zoom(1);
session.selectTool(NavigationTool::zoom);
const QSizeF size = shown.documentSize();
const QPointF click(120, 90);
const QPointF under = session.viewport.documentPoint(click, size);
QTest::mouseClick(&canvas, Qt::LeftButton, Qt::NoModifier, click.toPoint());
QCOMPARE(session.viewport.zoom(), 2.0);
QCOMPARE(session.viewport.documentPoint(click, size), under);
QTest::mouseClick(&canvas, Qt::LeftButton, Qt::AltModifier, click.toPoint());
QCOMPARE(session.viewport.zoom(), 1.0);
// Dragging 100 points doubles about the start; release adds nothing.
const QPointF start(260, 110);
const QPointF anchor = session.viewport.documentPoint(start, size);
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, start.toPoint());
drag(canvas, QPointF(360, 110));
QCOMPARE(session.viewport.zoom(), 2.0);
QCOMPARE(session.viewport.documentPoint(start, size), anchor);
drag(canvas, QPointF(310, 110));
QVERIFY(qFuzzyCompare(session.viewport.zoom(), std::sqrt(2.0)));
QCOMPARE(session.viewport.documentPoint(start, size), anchor);
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(310, 110));
QVERIFY(qFuzzyCompare(session.viewport.zoom(), std::sqrt(2.0)));
// Two points of drift still count as a click.
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
drag(canvas, QPointF(102, 100));
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(102, 100));
QVERIFY(qFuzzyCompare(session.viewport.zoom(), 2 * std::sqrt(2.0)));
// Busy, importing or without a document a press does nothing.
session.setIsProjectBusy(true);
QTest::mouseClick(&canvas, Qt::LeftButton, Qt::NoModifier, click.toPoint());
QVERIFY(qFuzzyCompare(session.viewport.zoom(), 2 * std::sqrt(2.0)));
session.setIsProjectBusy(false);
session.setIsImporting(true);
QTest::mouseClick(&canvas, Qt::LeftButton, Qt::NoModifier, click.toPoint());
QVERIFY(qFuzzyCompare(session.viewport.zoom(), 2 * std::sqrt(2.0)));
session.setIsImporting(false);
QTest::mouseClick(&canvas, Qt::RightButton, Qt::NoModifier, click.toPoint());
QVERIFY(qFuzzyCompare(session.viewport.zoom(), 2 * std::sqrt(2.0)));
Shown empty(std::nullopt);
empty.session.selectTool(NavigationTool::zoom);
QTest::mouseClick(empty.canvas, Qt::LeftButton, Qt::NoModifier, click.toPoint());
QCOMPARE(empty.session.viewport.zoom(), 1.0);
}
void CanvasNavigationTests::theHandToolAndSpaceDragTheView()
{
Shown shown;
CanvasView &canvas = *shown.canvas;
EditorSession &session = shown.session;
shown.settle();
session.zoom(1);
session.selectTool(NavigationTool::hand);
canvas.synchronizeDisplay();
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
QSignalSpy changes(&session, &EditorSession::changed);
const QSizeF pan = session.viewport.pan;
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
drag(canvas, QPointF(130, 120));
QCOMPARE(session.viewport.pan, pan + QSizeF(30, 20));
QVERIFY(!session.viewport.followsFit());
QCOMPARE(changes.count(), 1);
drag(canvas, QPointF(135, 120));
QCOMPARE(session.viewport.pan, pan + QSizeF(35, 20));
QCOMPARE(changes.count(), 2);
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(135, 120));
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
drag(canvas, QPointF(150, 120));
QCOMPARE(session.viewport.pan, pan + QSizeF(35, 20));
// Space drags the view in any tool.
session.selectTool(NavigationTool::move);
canvas.synchronizeDisplay();
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
QTRY_VERIFY(canvas.hasFocus());
QTest::keyPress(&canvas, Qt::Key_Space);
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50));
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
// Swift 1.4.7: Space's repeats keep the hand closed all through.
for (int repeat = 0; repeat < 3; ++repeat) {
QKeyEvent release(QEvent::KeyRelease, Qt::Key_Space, Qt::NoModifier, QStringLiteral(" "), true);
QApplication::sendEvent(&canvas, &release);
QKeyEvent press(QEvent::KeyPress, Qt::Key_Space, Qt::NoModifier, QStringLiteral(" "), true);
QApplication::sendEvent(&canvas, &press);
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
}
drag(canvas, QPointF(60, 70));
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
QCOMPARE(session.viewport.pan, pan + QSizeF(45, 40));
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(60, 70));
// Space still held: the hand opens with the button.
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
// A repeat's release is no release: the hand stays.
QKeyEvent repeated(QEvent::KeyRelease, Qt::Key_Space, Qt::NoModifier, QStringLiteral(" "), true);
QApplication::sendEvent(&canvas, &repeated);
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
QTest::keyRelease(&canvas, Qt::Key_Space);
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
// Space let go mid-pan: the pan goes on, closed.
QTest::keyPress(&canvas, Qt::Key_Space);
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50));
QTest::keyRelease(&canvas, Qt::Key_Space);
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
drag(canvas, QPointF(55, 50));
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
QCOMPARE(session.viewport.pan, pan + QSizeF(50, 40));
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(55, 50));
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
// Importing, a press starts no drag.
session.selectTool(NavigationTool::hand);
session.setIsImporting(true);
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50));
drag(canvas, QPointF(90, 90));
QCOMPARE(session.viewport.pan, pan + QSizeF(50, 40));
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(90, 90));
}
void CanvasNavigationTests::theWheelPansAndZoomsWithAModifier()
{
Shown shown;
CanvasView &canvas = *shown.canvas;
EditorSession &session = shown.session;
shown.settle();
session.zoom(1);
const QSizeF size = shown.documentSize();
const QPointF pointer(120, 90);
const double lines = QApplication::wheelScrollLines();
QSizeF pan = session.viewport.pan;
// Pixels count as they are, and win over notches.
wheel(canvas, pointer, QPoint(0, -30), QPoint(0, -240), Qt::NoModifier);
pan += QSizeF(0, -30);
QCOMPARE(session.viewport.pan, pan);
QVERIFY(!session.viewport.followsFit());
// A notch is a few lines, twelve points each.
wheel(canvas, pointer, QPoint(), QPoint(0, 120), Qt::NoModifier);
pan += QSizeF(0, 12 * lines);
QCOMPARE(session.viewport.pan, pan);
wheel(canvas, pointer, QPoint(), QPoint(-60, 0), Qt::NoModifier);
pan += QSizeF(-6 * lines, 0);
QCOMPARE(session.viewport.pan, pan);
// Ctrl or Alt zooms about the pointer, by Swift's curve.
const QPointF under = session.viewport.documentPoint(pointer, size);
wheel(canvas, pointer, QPoint(), QPoint(0, 120), Qt::ControlModifier);
QVERIFY(qFuzzyCompare(session.viewport.zoom(), std::exp(-lines * 0.015)));
QCOMPARE(session.viewport.documentPoint(pointer, size), under);
wheel(canvas, pointer, QPoint(0, 10), QPoint(), Qt::AltModifier);
QVERIFY(qFuzzyCompare(session.viewport.zoom(), std::exp(-lines * 0.015) * std::exp(-10 * 0.015)));
QCOMPARE(session.viewport.documentPoint(pointer, size), under);
// Without a document the wheel does nothing.
Shown empty(std::nullopt);
wheel(*empty.canvas, pointer, QPoint(0, -30), QPoint(), Qt::NoModifier);
QCOMPARE(empty.session.viewport.pan, QSizeF(0, 0));
wheel(*empty.canvas, pointer, QPoint(), QPoint(0, 120), Qt::ControlModifier);
QCOMPARE(empty.session.viewport.zoom(), 1.0);
}
void CanvasNavigationTests::aPinchZoomsAboutThePointer()
{
Shown shown;
shown.settle();
shown.session.zoom(1);
const QSizeF size = shown.documentSize();
const QPointF pointer(120, 90);
const QPointF under = shown.session.viewport.documentPoint(pointer, size);
QNativeGestureEvent pinch(Qt::ZoomNativeGesture, QPointingDevice::primaryPointingDevice(), 2, pointer, pointer,
shown.canvas->mapToGlobal(pointer.toPoint()), 0.5, QPointF());
QVERIFY(QApplication::sendEvent(shown.canvas, &pinch));
QCOMPARE(shown.session.viewport.zoom(), 1.5);
QCOMPARE(shown.session.viewport.documentPoint(pointer, size), under);
// Other gestures pass on.
QNativeGestureEvent swipe(Qt::SwipeNativeGesture, QPointingDevice::primaryPointingDevice(), 3, pointer, pointer,
shown.canvas->mapToGlobal(pointer.toPoint()), 0.5, QPointF());
QApplication::sendEvent(shown.canvas, &swipe);
QCOMPARE(shown.session.viewport.zoom(), 1.5);
}
void CanvasNavigationTests::theMiddleButtonPansFromAnyTool()
{
Shown shown;
CanvasView &canvas = *shown.canvas;
EditorSession &session = shown.session;
shown.settle();
session.zoom(1);
session.selectTool(NavigationTool::brush);
canvas.synchronizeDisplay();
const auto move = [&](QPointF to, Qt::MouseButtons buttons) {
QMouseEvent event(QEvent::MouseMove, to, to, canvas.mapToGlobal(to.toPoint()), Qt::NoButton, buttons, Qt::NoModifier);
QApplication::sendEvent(&canvas, &event);
};
move(QPointF(100, 100), Qt::NoButton);
const Qt::CursorShape brush = canvas.cursor().shape();
QVERIFY(canvas.brushCursor().circle().has_value());
const QSizeF pan = session.viewport.pan;
QSignalSpy changes(&session, &EditorSession::changed);
// The circle hides and nothing paints; the view follows.
QTest::mousePress(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(100, 100));
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
QVERIFY(!canvas.brushCursor().circle().has_value());
move(QPointF(130, 120), Qt::MiddleButton);
QCOMPARE(session.viewport.pan, pan + QSizeF(30, 20));
QCOMPARE(changes.count(), 1);
move(QPointF(150, 150), Qt::MiddleButton);
QCOMPARE(session.viewport.pan, pan + QSizeF(50, 50));
move(QPointF(130, 120), Qt::MiddleButton);
QCOMPARE(session.viewport.pan, pan + QSizeF(30, 20));
QVERIFY(!session.brushStroke() && !canvas.brushCursor().circle().has_value());
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
QTest::mouseRelease(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(130, 120));
QCOMPARE(canvas.cursor().shape(), brush);
QVERIFY(canvas.brushCursor().circle().has_value());
// Beside a Hand drag each button keeps its own point.
session.selectTool(NavigationTool::hand);
canvas.synchronizeDisplay();
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50));
QTest::mousePress(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(200, 200));
move(QPointF(60, 50), Qt::LeftButton | Qt::MiddleButton);
QCOMPARE(session.viewport.pan, pan + QSizeF(30, 20) + QSizeF(10, 0) + QSizeF(-140, -150));
QTest::mouseRelease(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(60, 50));
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(60, 50));
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
// A lost release: a buttonless move ends the pan.
const QSizeF held = session.viewport.pan;
QTest::mousePress(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50));
move(QPointF(70, 70), Qt::NoButton);
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
move(QPointF(90, 90), Qt::MiddleButton);
QCOMPARE(session.viewport.pan, held);
// Without a document the middle button does nothing.
Shown empty(std::nullopt);
empty.settle();
const Qt::CursorShape before = empty.canvas->cursor().shape();
QTest::mousePress(empty.canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50));
QCOMPARE(empty.canvas->cursor().shape(), before);
}
QTEST_MAIN(CanvasNavigationTests)
#include "CanvasNavigationTests.moc"