Add dynamically-created Event Handlers to the relevant handlers vector

If any kind of Pointer Handler is created dynamically in JS by
calling Component.createObject(), QObject::setParent() is called
rather than passing the parent to the constructor, so
QQuickItemPrivate::data_append() did not take care of adding the
handler to QQuickItemPrivate's extra->pointerHandlers vector.
We need to use the auto-parent mechanism (just as we did with
handling dynamic creation of nested Windows in
8cb02e23ab). Added
QQuickItemPrivate::addPointerHandler() to put the prepend()
and implied setAcceptedMouseButtons() in one place.

Fixes: QTBUG-71427
Change-Id: I3be3dd033c1c89e6e5b5c3463e1a720bbe963281
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
This commit is contained in:
Shawn Rutledge 2018-11-22 14:00:39 +01:00
parent 1cd3b2acfe
commit ee2ac69595
6 changed files with 137 additions and 16 deletions

View File

@ -3281,11 +3281,7 @@ void QQuickItemPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
qWarning("Cannot add a QtQuick 1.0 item (%s) into a QtQuick 2.0 scene!", o->metaObject()->className());
else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) {
Q_ASSERT(pointerHandler->parentItem() == that);
// Accept all buttons, and leave filtering to pointerEvent() and/or user JS,
// because there can be multiple handlers...
that->setAcceptedMouseButtons(Qt::AllButtons);
QQuickItemPrivate *p = QQuickItemPrivate::get(that);
p->extra.value().pointerHandlers.prepend(pointerHandler);
QQuickItemPrivate::get(that)->addPointerHandler(pointerHandler);
} else {
QQuickWindow *thisWindow = qmlobject_cast<QQuickWindow *>(o);
QQuickItem *item = that;
@ -8207,6 +8203,17 @@ bool QQuickItemPrivate::hasHoverHandlers() const
return false;
}
void QQuickItemPrivate::addPointerHandler(QQuickPointerHandler *h)
{
Q_Q(QQuickItem);
// Accept all buttons, and leave filtering to pointerEvent() and/or user JS,
// because there can be multiple handlers...
q->setAcceptedMouseButtons(Qt::AllButtons);
auto &handlers = extra.value().pointerHandlers;
if (!handlers.contains(h))
handlers.prepend(h);
}
#if QT_CONFIG(quick_shadereffect)
QQuickItemLayer::QQuickItemLayer(QQuickItem *item)
: m_item(item)

View File

@ -281,6 +281,7 @@ public:
bool hasPointerHandlers() const;
bool hasHoverHandlers() const;
void addPointerHandler(QQuickPointerHandler *h);
// data property
static void data_append(QQmlListProperty<QObject> *, QObject *);

View File

@ -145,6 +145,7 @@ static QQmlPrivate::AutoParentResult qquickitem_autoParent(QObject *obj, QObject
return QQmlPrivate::Parented;
}
} else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(obj)) {
QQuickItemPrivate::get(parentItem)->addPointerHandler(handler);
handler->setParent(parent);
return QQmlPrivate::Parented;
}
@ -156,13 +157,14 @@ static QQmlPrivate::AutoParentResult qquickitem_autoParent(QObject *obj, QObject
qCDebug(lcTransient) << win << "is transient for" << parentWindow;
win->setTransientParent(parentWindow);
return QQmlPrivate::Parented;
} else {
QQuickItem *item = qmlobject_cast<QQuickItem *>(obj);
if (item) {
// The parent of an Item inside a Window is actually the implicit content Item
item->setParentItem(parentWindow->contentItem());
return QQmlPrivate::Parented;
}
} else if (QQuickItem *item = qmlobject_cast<QQuickItem *>(obj)) {
// The parent of an Item inside a Window is actually the implicit content Item
item->setParentItem(parentWindow->contentItem());
return QQmlPrivate::Parented;
} else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(obj)) {
QQuickItemPrivate::get(parentWindow->contentItem())->addPointerHandler(handler);
handler->setParent(parentWindow->contentItem());
return QQmlPrivate::Parented;
}
return QQmlPrivate::IncompatibleObject;
} else if (qmlobject_cast<QQuickItem *>(obj)) {

View File

@ -0,0 +1,34 @@
import QtQuick 2.12
import Qt.test 1.0
Item {
id: root
objectName: "root Item"
width: 320
height: 480
Rectangle {
objectName: "eventItem's bounds"
anchors.fill: eventItem
color: "lightsteelblue"
}
EventItem {
id: eventItem
objectName: "eventItem1"
x: 5
y: 5
height: 30
width: 30
Component.onCompleted: handlerComponent.createObject(eventItem)
Component {
id: handlerComponent
EventHandler {
objectName: "eventHandler"
}
}
}
}

View File

@ -0,0 +1,20 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import Qt.test 1.0
Window {
id: root
objectName: "root Window"
width: 320
height: 480
Component.onCompleted: handlerComponent.createObject(root)
Component {
id: handlerComponent
EventHandler {
objectName: "eventHandler"
}
}
}

View File

@ -182,12 +182,18 @@ public:
class EventHandler : public QQuickPointerHandler
{
public:
void handlePointerEventImpl(QQuickPointerEvent *event) override
{
QQuickPointerHandler::handlePointerEventImpl(event);
if (!enabled())
return;
EventItem *item = static_cast<EventItem *>(target());
++eventCount;
EventItem *item = qmlobject_cast<EventItem *>(target());
if (!item) {
event->point(0)->setGrabberPointerHandler(this);
return;
}
qCDebug(lcPointerTests) << item->objectName() << event;
int c = event->pointCount();
for (int i = 0; i < c; ++i) {
@ -206,10 +212,13 @@ class EventHandler : public QQuickPointerHandler
void onGrabChanged(QQuickPointerHandler *, QQuickEventPoint::GrabTransition stateChange, QQuickEventPoint *point) override
{
EventItem *item = static_cast<EventItem *>(target());
item->eventList.append(Event(Event::HandlerDestination, QEvent::None,
static_cast<Qt::TouchPointState>(point->state()), stateChange, eventPos(point), point->scenePosition()));
EventItem *item = qmlobject_cast<EventItem *>(target());
if (item)
item->eventList.append(Event(Event::HandlerDestination, QEvent::None,
static_cast<Qt::TouchPointState>(point->state()), stateChange, eventPos(point), point->scenePosition()));
}
int eventCount = 0;
};
class tst_PointerHandlers : public QQmlDataTest
@ -228,6 +237,8 @@ private slots:
void mouseEventDelivery();
void touchReleaseOutside_data();
void touchReleaseOutside();
void dynamicCreation();
void dynamicCreationInWindow();
protected:
bool eventFilter(QObject *, QEvent *event)
@ -593,6 +604,52 @@ void tst_PointerHandlers::touchReleaseOutside()
QCOMPARE_EVENT(endIndexToTest, endDestination, endType, endState, endGrabState);
}
void tst_PointerHandlers::dynamicCreation()
{
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "dynamicallyCreated.qml");
QQuickView * window = windowPtr.data();
EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1");
QVERIFY(eventItem1);
EventHandler *handler = window->rootObject()->findChild<EventHandler*>("eventHandler");
QVERIFY(handler);
QCOMPARE(handler->parentItem(), eventItem1);
QCOMPARE(handler->target(), eventItem1);
QPoint p1(20, 20);
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
QTRY_COMPARE(eventItem1->eventList.size(), 2);
QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, Qt::TouchPointPressed, NoGrab);
QCOMPARE_EVENT(1, Event::MouseDestination, QEvent::MouseButtonPress, Qt::TouchPointPressed, NoGrab);
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
}
void tst_PointerHandlers::dynamicCreationInWindow()
{
QQmlEngine engine;
QQmlComponent component(&engine);
component.loadUrl(testFileUrl("dynamicallyCreatedInWindow.qml"));
QQuickWindow *window = qobject_cast<QQuickWindow*>(component.create());
QScopedPointer<QQuickWindow> cleanup(window);
QVERIFY(window);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
EventHandler *handler = window->contentItem()->findChild<EventHandler*>("eventHandler");
QVERIFY(handler);
QCOMPARE(handler->parentItem(), window->contentItem());
QCOMPARE(handler->target(), window->contentItem());
QPoint p1(20, 20);
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
QTRY_COMPARE(handler->eventCount, 1);
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
QTRY_COMPARE(handler->eventCount, 2);
}
QTEST_MAIN(tst_PointerHandlers)
#include "tst_qquickpointerhandler.moc"