Fix touch point positions of non-transformable QGraphicsItems

TouchEvent::TouchPoint::pos was not updated in
QGraphicsScenePrivate::updateTouchPointsForItem().

To prevent the transformation being calculated repeatedly for each touch
point member, extract a function genericMapFromSceneTransform()
from genericMapFromScene() returning the transformation and use
that whereever multiple points are transformed.

Add a test, extracting helper functionality from
tst_QGraphicsItem::touchEventPropagation().
In addition, fold tst_QGraphicsScene::checkTouchPointsEllipseDiameters() from
c48f4bde00 into this test, so that
it is testing all transformations.

Task-number: QTBUG-66192
Change-Id: If71886d2c14c4e216f7781ea2f22f1adc444e6cf
Reviewed-by: Andy Shaw <andy.shaw@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Friedemann Kleint 2018-02-12 16:26:38 +01:00
parent 76617fdb56
commit ed3ed0b9db
5 changed files with 226 additions and 129 deletions

View File

@ -1134,19 +1134,26 @@ void QGraphicsItemPrivate::remapItemPos(QEvent *event, QGraphicsItem *item)
is untransformable, this function will correctly map \a pos from the scene using the
view's transformation.
*/
QPointF QGraphicsItemPrivate::genericMapFromScene(const QPointF &pos,
const QWidget *viewport) const
QTransform QGraphicsItemPrivate::genericMapFromSceneTransform(const QWidget *viewport) const
{
Q_Q(const QGraphicsItem);
if (!itemIsUntransformable())
return q->mapFromScene(pos);
QGraphicsView *view = 0;
if (viewport)
view = qobject_cast<QGraphicsView *>(viewport->parentWidget());
if (!view)
return q->mapFromScene(pos);
return sceneTransform.inverted();
const QGraphicsView *view = viewport
? qobject_cast<QGraphicsView *>(viewport->parentWidget())
: nullptr;
if (view == nullptr)
return sceneTransform.inverted();
// ### More ping pong than needed.
return q->deviceTransform(view->viewportTransform()).inverted().map(view->mapFromScene(pos));
const QTransform viewportTransform = view->viewportTransform();
return viewportTransform * q->deviceTransform(viewportTransform).inverted();
}
QPointF QGraphicsItemPrivate::genericMapFromScene(const QPointF &pos,
const QWidget *viewport) const
{
return genericMapFromSceneTransform(viewport).map(pos);
}
/*!

View File

@ -190,6 +190,7 @@ public:
void updateAncestorFlags();
void setIsMemberOfGroup(bool enabled);
void remapItemPos(QEvent *event, QGraphicsItem *item);
QTransform genericMapFromSceneTransform(const QWidget *viewport = nullptr) const;
QPointF genericMapFromScene(const QPointF &pos, const QWidget *viewport) const;
inline bool itemIsUntransformable() const
{

View File

@ -1286,10 +1286,11 @@ void QGraphicsScenePrivate::sendHoverEvent(QEvent::Type type, QGraphicsItem *ite
{
QGraphicsSceneHoverEvent event(type);
event.setWidget(hoverEvent->widget());
event.setPos(item->d_ptr->genericMapFromScene(hoverEvent->scenePos(), hoverEvent->widget()));
const QTransform mapFromScene = item->d_ptr->genericMapFromSceneTransform(hoverEvent->widget());
event.setPos(mapFromScene.map(hoverEvent->scenePos()));
event.setScenePos(hoverEvent->scenePos());
event.setScreenPos(hoverEvent->screenPos());
event.setLastPos(item->d_ptr->genericMapFromScene(hoverEvent->lastScenePos(), hoverEvent->widget()));
event.setLastPos(mapFromScene.map(hoverEvent->lastScenePos()));
event.setLastScenePos(hoverEvent->lastScenePos());
event.setLastScreenPos(hoverEvent->lastScreenPos());
event.setModifiers(hoverEvent->modifiers());
@ -1312,14 +1313,16 @@ void QGraphicsScenePrivate::sendMouseEvent(QGraphicsSceneMouseEvent *mouseEvent)
if (item->isBlockedByModalPanel())
return;
const QTransform mapFromScene = item->d_ptr->genericMapFromSceneTransform(mouseEvent->widget());
const QPointF itemPos = mapFromScene.map(mouseEvent->scenePos());
for (int i = 0x1; i <= 0x10; i <<= 1) {
Qt::MouseButton button = Qt::MouseButton(i);
mouseEvent->setButtonDownPos(button, mouseGrabberButtonDownPos.value(button, item->d_ptr->genericMapFromScene(mouseEvent->scenePos(), mouseEvent->widget())));
mouseEvent->setButtonDownPos(button, mouseGrabberButtonDownPos.value(button, itemPos));
mouseEvent->setButtonDownScenePos(button, mouseGrabberButtonDownScenePos.value(button, mouseEvent->scenePos()));
mouseEvent->setButtonDownScreenPos(button, mouseGrabberButtonDownScreenPos.value(button, mouseEvent->screenPos()));
}
mouseEvent->setPos(item->d_ptr->genericMapFromScene(mouseEvent->scenePos(), mouseEvent->widget()));
mouseEvent->setLastPos(item->d_ptr->genericMapFromScene(mouseEvent->lastScenePos(), mouseEvent->widget()));
mouseEvent->setPos(itemPos);
mouseEvent->setLastPos(mapFromScene.map(mouseEvent->lastScenePos()));
sendEvent(item, mouseEvent);
}
@ -5858,10 +5861,17 @@ void QGraphicsScenePrivate::removeView(QGraphicsView *view)
void QGraphicsScenePrivate::updateTouchPointsForItem(QGraphicsItem *item, QTouchEvent *touchEvent)
{
const QTransform mapFromScene =
item->d_ptr->genericMapFromSceneTransform(static_cast<const QWidget *>(touchEvent->target()));
for (auto &touchPoint : touchEvent->_touchPoints) {
touchPoint.setRect(item->mapFromScene(touchPoint.sceneRect()).boundingRect());
touchPoint.setStartPos(item->d_ptr->genericMapFromScene(touchPoint.startScenePos(), static_cast<QWidget *>(touchEvent->target())));
touchPoint.setLastPos(item->d_ptr->genericMapFromScene(touchPoint.lastScenePos(), static_cast<QWidget *>(touchEvent->target())));
// Deprecated TouchPoint::setRect clobbers ellipseDiameters, restore
const QSizeF ellipseDiameters = touchPoint.ellipseDiameters();
touchPoint.setRect(mapFromScene.map(touchPoint.sceneRect()).boundingRect());
touchPoint.setEllipseDiameters(ellipseDiameters);
touchPoint.setPos(mapFromScene.map(touchPoint.scenePos()));
touchPoint.setStartPos(mapFromScene.map(touchPoint.startScenePos()));
touchPoint.setLastPos(mapFromScene.map(touchPoint.lastScenePos()));
}
}

View File

@ -38,6 +38,7 @@
#include <QAbstractTextDocumentLayout>
#include <QBitmap>
#include <QCursor>
#include <QDesktopWidget>
#include <QScreen>
#include <QLabel>
#include <QDial>
@ -54,10 +55,13 @@
#include <QPushButton>
#include <QLineEdit>
#include <QGraphicsLinearLayout>
#include <QTransform>
#include <float.h>
#include <QStyleHints>
Q_DECLARE_METATYPE(QPainterPath)
Q_DECLARE_METATYPE(QSizeF)
Q_DECLARE_METATYPE(QTransform)
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
#include <windows.h>
@ -435,6 +439,8 @@ private slots:
void focusHandling();
void touchEventPropagation_data();
void touchEventPropagation();
void touchEventTransformation_data();
void touchEventTransformation();
void deviceCoordinateCache_simpleRotations();
void resolvePaletteForItemChildren();
@ -465,6 +471,7 @@ private slots:
private:
QList<QGraphicsItem *> paintedItems;
QTouchDevice *m_touchDevice = nullptr;
};
void tst_QGraphicsItem::construction()
@ -10945,6 +10952,95 @@ void tst_QGraphicsItem::focusHandling()
QCOMPARE(scene.focusItem(), focusableUnder);
}
class TouchEventTestee : public QGraphicsRectItem
{
public:
TouchEventTestee(const QSizeF &size = QSizeF(100, 100)) :
QGraphicsRectItem(QRectF(QPointF(), size))
{
setAcceptTouchEvents(true);
setFlag(QGraphicsItem::ItemIsFocusable, false);
}
QList<QTouchEvent::TouchPoint> touchBeginPoints() const { return m_touchBeginPoints; }
int touchBeginEventCount() const { return m_touchBeginPoints.size(); }
QList<QTouchEvent::TouchPoint> touchUpdatePoints() const { return m_touchUpdatePoints; }
int touchUpdateEventCount() const { return m_touchUpdatePoints.size(); }
protected:
bool sceneEvent(QEvent *ev) override
{
switch (ev->type()) {
case QEvent::TouchBegin:
m_touchBeginPoints.append(static_cast<const QTouchEvent *>(ev)->touchPoints().constFirst());
ev->accept();
return true;
case QEvent::TouchUpdate:
m_touchUpdatePoints.append(static_cast<const QTouchEvent *>(ev)->touchPoints().constFirst());
ev->accept();
return true;
default:
break;
}
return QGraphicsRectItem::sceneEvent(ev);
}
private:
QList<QTouchEvent::TouchPoint> m_touchBeginPoints;
QList<QTouchEvent::TouchPoint> m_touchUpdatePoints;
};
static QList<QTouchEvent::TouchPoint>
createTouchPoints(const QGraphicsView &view,
const QPointF &scenePos,
const QSizeF &ellipseDiameters,
Qt::TouchPointState state = Qt::TouchPointPressed)
{
QTouchEvent::TouchPoint tp(0);
tp.setState(state);
tp.setScenePos(scenePos);
tp.setStartScenePos(scenePos);
tp.setLastScenePos(scenePos);
const QPointF screenPos = view.viewport()->mapToGlobal(view.mapFromScene(scenePos));
tp.setScreenPos(screenPos);
tp.setStartScreenPos(screenPos);
tp.setLastScreenPos(screenPos);
tp.setEllipseDiameters(ellipseDiameters);
const QSizeF screenSize = QApplication::desktop()->screenGeometry(&view).size();
tp.setNormalizedPos(QPointF(screenPos.x() / screenSize.width(), screenPos.y() / screenSize.height()));
return QList<QTouchEvent::TouchPoint>() << tp;
}
static bool comparePointF(const QPointF &p1, const QPointF &p2)
{
return qFuzzyCompare(p1.x(), p2.x()) && qFuzzyCompare(p1.y(), p2.y());
}
static bool compareSizeF(const QSizeF &s1, const QSizeF &s2)
{
return qFuzzyCompare(s1.width(), s2.width()) && qFuzzyCompare(s1.height(), s2.height());
}
static QByteArray msgPointFComparisonFailed(const QPointF &p1, const QPointF &p2)
{
return QByteArray::number(p1.x()) + ", " + QByteArray::number(p1.y())
+ " != " + QByteArray::number(p2.x()) + ", " + QByteArray::number(p2.y());
}
static QByteArray msgSizeFComparisonFailed(const QSizeF &s1, const QSizeF &s2)
{
return QByteArray::number(s1.width()) + 'x' + QByteArray::number(s1.height())
+ " != " + QByteArray::number(s2.width()) + 'x' + QByteArray::number(s2.height());
}
#define COMPARE_POINTF(ACTUAL, EXPECTED) \
QVERIFY2(comparePointF(ACTUAL, EXPECTED), msgPointFComparisonFailed(ACTUAL, EXPECTED).constData())
#define COMPARE_SIZEF(ACTUAL, EXPECTED) \
QVERIFY2(compareSizeF(ACTUAL, EXPECTED), msgSizeFComparisonFailed(ACTUAL, EXPECTED).constData())
void tst_QGraphicsItem::touchEventPropagation_data()
{
QTest::addColumn<QGraphicsItem::GraphicsItemFlag>("flag");
@ -10963,29 +11059,7 @@ void tst_QGraphicsItem::touchEventPropagation()
QFETCH(QGraphicsItem::GraphicsItemFlag, flag);
QFETCH(int, expectedCount);
class Testee : public QGraphicsRectItem
{
public:
int touchBeginEventCount;
Testee()
: QGraphicsRectItem(0, 0, 100, 100)
, touchBeginEventCount(0)
{
setAcceptTouchEvents(true);
setFlag(QGraphicsItem::ItemIsFocusable, false);
}
bool sceneEvent(QEvent *ev)
{
if (ev->type() == QEvent::TouchBegin)
++touchBeginEventCount;
return QGraphicsRectItem::sceneEvent(ev);
}
};
Testee *touchEventReceiver = new Testee;
TouchEventTestee *touchEventReceiver = new TouchEventTestee;
QGraphicsItem *topMost = new QGraphicsRectItem(touchEventReceiver->boundingRect());
QGraphicsScene scene;
@ -10998,26 +11072,107 @@ void tst_QGraphicsItem::touchEventPropagation()
topMost->setFlag(flag, true);
QGraphicsView view(&scene);
view.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String("::")
+ QLatin1String(QTest::currentDataTag()));
view.setSceneRect(touchEventReceiver->boundingRect());
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
QCOMPARE(touchEventReceiver->touchBeginEventCount, 0);
QCOMPARE(touchEventReceiver->touchBeginEventCount(), 0);
QTouchEvent::TouchPoint tp(0);
tp.setState(Qt::TouchPointPressed);
tp.setScenePos(view.sceneRect().center());
tp.setLastScenePos(view.sceneRect().center());
QList<QTouchEvent::TouchPoint> touchPoints;
touchPoints << tp;
sendMousePress(&scene, tp.scenePos());
QTouchDevice *device = QTest::createTouchDevice();
QTouchEvent touchBegin(QEvent::TouchBegin, device, Qt::NoModifier, Qt::TouchPointPressed, touchPoints);
const QPointF scenePos = view.sceneRect().center();
sendMousePress(&scene, scenePos);
if (m_touchDevice == nullptr)
m_touchDevice = QTest::createTouchDevice();
QTouchEvent touchBegin(QEvent::TouchBegin, m_touchDevice, Qt::NoModifier, Qt::TouchPointPressed,
createTouchPoints(view, scenePos, QSizeF(10, 10)));
touchBegin.setTarget(view.viewport());
qApp->sendEvent(&scene, &touchBegin);
QCOMPARE(touchEventReceiver->touchBeginEventCount, expectedCount);
QCOMPARE(touchEventReceiver->touchBeginEventCount(), expectedCount);
}
void tst_QGraphicsItem::touchEventTransformation_data()
{
QTest::addColumn<QGraphicsItem::GraphicsItemFlag>("flag");
QTest::addColumn<QTransform>("viewTransform");
QTest::addColumn<QPointF>("touchScenePos");
QTest::addColumn<QSizeF>("ellipseDiameters");
QTest::addColumn<QPointF>("expectedItemPos");
QTest::newRow("notransform")
<< QGraphicsItem::ItemIsSelectable << QTransform()
<< QPointF(150, 150) << QSizeF(7, 8) << QPointF(50, 50);
QTest::newRow("scaled")
<< QGraphicsItem::ItemIsSelectable << QTransform::fromScale(0.5, 0.5)
<< QPointF(150, 150) << QSizeF(7, 8) << QPointF(50, 50);
// QTBUG-66192: When the item ignores the downscaling transformation,
// it will receive the touch point at 25,25 instead of 50,50.
QTest::newRow("scaled/ItemIgnoresTransformations")
<< QGraphicsItem::ItemIgnoresTransformations << QTransform::fromScale(0.5, 0.5)
<< QPointF(150, 150) << QSizeF(7, 8) << QPointF(25, 25);
}
void tst_QGraphicsItem::touchEventTransformation()
{
QFETCH(QGraphicsItem::GraphicsItemFlag, flag);
QFETCH(QTransform, viewTransform);
QFETCH(QPointF, touchScenePos);
QFETCH(QSizeF, ellipseDiameters);
QFETCH(QPointF, expectedItemPos);
TouchEventTestee *touchEventReceiver = new TouchEventTestee;
QGraphicsScene scene;
scene.addItem(touchEventReceiver);
const QPointF itemPos(100, 100);
touchEventReceiver->setPos(itemPos);
touchEventReceiver->setFlag(flag, true);
QGraphicsView view(&scene);
view.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String("::")
+ QLatin1String(QTest::currentDataTag()));
view.setSceneRect(QRectF(QPointF(0, 0), QSizeF(300, 300)));
view.setTransform(viewTransform);
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
QCOMPARE(touchEventReceiver->touchBeginEventCount(), 0);
if (m_touchDevice == nullptr)
m_touchDevice = QTest::createTouchDevice();
QTouchEvent touchBegin(QEvent::TouchBegin, m_touchDevice, Qt::NoModifier, Qt::TouchPointPressed,
createTouchPoints(view, touchScenePos, ellipseDiameters));
touchBegin.setTarget(view.viewport());
QCoreApplication::sendEvent(&scene, &touchBegin);
QCOMPARE(touchEventReceiver->touchBeginEventCount(), 1);
const QTouchEvent::TouchPoint touchBeginPoint = touchEventReceiver->touchBeginPoints().constFirst();
COMPARE_POINTF(touchBeginPoint.scenePos(), touchScenePos);
COMPARE_POINTF(touchBeginPoint.startScenePos(), touchScenePos);
COMPARE_POINTF(touchBeginPoint.lastScenePos(), touchScenePos);
COMPARE_POINTF(touchBeginPoint.pos(), expectedItemPos);
COMPARE_SIZEF(touchBeginPoint.ellipseDiameters(), ellipseDiameters); // Must remain untransformed
QTouchEvent touchUpdate(QEvent::TouchUpdate, m_touchDevice, Qt::NoModifier, Qt::TouchPointMoved,
createTouchPoints(view, touchScenePos, ellipseDiameters, Qt::TouchPointMoved));
touchUpdate.setTarget(view.viewport());
QCoreApplication::sendEvent(&scene, &touchUpdate);
QCOMPARE(touchEventReceiver->touchUpdateEventCount(), 1);
const QTouchEvent::TouchPoint touchUpdatePoint = touchEventReceiver->touchUpdatePoints().constFirst();
COMPARE_POINTF(touchUpdatePoint.scenePos(), touchScenePos);
COMPARE_POINTF(touchBeginPoint.startScenePos(), touchScenePos);
COMPARE_POINTF(touchUpdatePoint.lastScenePos(), touchScenePos);
COMPARE_POINTF(touchUpdatePoint.pos(), expectedItemPos);
COMPARE_SIZEF(touchUpdatePoint.ellipseDiameters(), ellipseDiameters); // Must remain untransformed
}
void tst_QGraphicsItem::deviceCoordinateCache_simpleRotations()

View File

@ -254,7 +254,6 @@ private slots:
void zeroScale();
void focusItemChangedSignal();
void minimumRenderSize();
void checkTouchPointsEllipseDiameters();
// task specific tests below me
void task139710_bspTreeCrash();
@ -4765,81 +4764,6 @@ void tst_QGraphicsScene::minimumRenderSize()
QVERIFY(smallChild->repaints > smallerGrandChild->repaints);
}
class TouchItem : public QGraphicsRectItem
{
public:
TouchItem() : QGraphicsRectItem(QRectF(-10, -10, 20, 20)),
seenTouch(false)
{
setAcceptTouchEvents(true);
setFlag(QGraphicsItem::ItemIgnoresTransformations);
}
bool seenTouch;
QList<QTouchEvent::TouchPoint> touchPoints;
protected:
bool sceneEvent(QEvent *event) override
{
switch (event->type()) {
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
seenTouch = true;
touchPoints = static_cast<QTouchEvent *>(event)->touchPoints();
event->accept();
return true;
default:
break;
}
return QGraphicsRectItem::sceneEvent(event);
}
};
void tst_QGraphicsScene::checkTouchPointsEllipseDiameters()
{
QGraphicsScene scene;
QGraphicsView view(&scene);
scene.setSceneRect(1, 1, 198, 198);
view.scale(1.5, 1.5);
view.setFocus();
TouchItem *rect = new TouchItem;
scene.addItem(rect);
view.show();
QApplication::setActiveWindow(&view);
QVERIFY(QTest::qWaitForWindowActive(&view));
const QSizeF ellipseDiameters(10.0, 10.0);
QTouchEvent::TouchPoint touchPoint(0);
touchPoint.setState(Qt::TouchPointPressed);
touchPoint.setPos(view.mapFromScene(rect->mapToScene(rect->boundingRect().center())));
touchPoint.setScreenPos(view.mapToGlobal(touchPoint.pos().toPoint()));
touchPoint.setEllipseDiameters(ellipseDiameters);
QList<QTouchEvent::TouchPoint> touchPoints = { touchPoint };
QTouchDevice *testDevice = QTest::createTouchDevice(QTouchDevice::TouchPad);
QTouchEvent touchEvent(QEvent::TouchBegin,
testDevice,
Qt::NoModifier,
Qt::TouchPointPressed,
touchPoints);
QApplication::sendEvent(view.viewport(), &touchEvent);
QVERIFY(rect->seenTouch);
QVERIFY(rect->touchPoints.size() == 1);
QCOMPARE(ellipseDiameters, rect->touchPoints.first().ellipseDiameters());
rect->seenTouch = false;
rect->touchPoints.clear();
QTouchEvent touchUpdateEvent(QEvent::TouchUpdate,
testDevice,
Qt::NoModifier,
Qt::TouchPointMoved,
touchPoints);
QApplication::sendEvent(view.viewport(), &touchEvent);
QVERIFY(rect->seenTouch);
QVERIFY(rect->touchPoints.size() == 1);
QCOMPARE(ellipseDiameters, rect->touchPoints.first().ellipseDiameters());
}
void tst_QGraphicsScene::taskQTBUG_15977_renderWithDeviceCoordinateCache()
{
QGraphicsScene scene;