QDeclarativeMouseArea: block context menu events
authorCaio Marcelo de Oliveira Filho <caio.oliveira@openbossa.org>
Fri, 13 May 2011 07:01:28 +0000 (17:01 +1000)
committerMartin Jones <martin.jones@nokia.com>
Mon, 30 May 2011 05:51:04 +0000 (15:51 +1000)
If the MouseArea accepts the same button used to trigger a context
menu event, it should not let the event to be delivered to item
behind.

This is important for items that do implement contextMenuEvent(), like
QDeclarativeWebView. When there's a mouse area on top of this item and
that accepts the right click (in Linux), the event was still being
delivered and the WebView menu was incorrectly appearing.

QtWebKit bug https://bugs.webkit.org/show_bug.cgi?id=56526 documents this
problem.

Change-Id: I386fac6c96f47b8616e2eeb7e5f97043ba418980
Merge-request: 1185
Reviewed-by: Martin Jones <martin.jones@nokia.com>
(cherry picked from commit 80db0a1e59658f9e445219fc48d9236a79edca72)

src/declarative/graphicsitems/qdeclarativemousearea.cpp
src/declarative/graphicsitems/qdeclarativemousearea_p.h
tests/auto/declarative/qdeclarativemousearea/data/preventContextMenu.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativemousearea/tst_qdeclarativemousearea.cpp

index d4e7f7b..6633256 100644 (file)
@@ -660,6 +660,32 @@ void QDeclarativeMouseArea::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
         setHovered(false);
 }
 
+#ifndef QT_NO_CONTEXTMENU
+void QDeclarativeMouseArea::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
+{
+    bool acceptsContextMenuButton;
+#if defined(Q_OS_SYMBIAN)
+    // In Symbian a Long Tap on the screen will trigger. See QSymbianControl::HandleLongTapEventL().
+    acceptsContextMenuButton = acceptedButtons() & Qt::LeftButton;
+#elif defined(Q_WS_WINCE)
+    // ### WinCE can trigger context menu event with a gesture in the left button or a
+    // click with the right button. Since we have no way here to differentiate them when
+    // event happens, accepting either of the them will block the event.
+    acceptsContextMenuButton = acceptedButtons() & (Qt::LeftButton | Qt::RightButton);
+#else
+    acceptsContextMenuButton = acceptedButtons() & Qt::RightButton;
+#endif
+
+    if (isEnabled() && event->reason() == QGraphicsSceneContextMenuEvent::Mouse
+        && acceptsContextMenuButton) {
+        // Do not let the context menu event propagate to items behind.
+        return;
+    }
+
+    QDeclarativeItem::contextMenuEvent(event);
+}
+#endif // QT_NO_CONTEXTMENU
+
 bool QDeclarativeMouseArea::sceneEvent(QEvent *event)
 {
     bool rv = QDeclarativeItem::sceneEvent(event);
index 351d4de..0fe8c6a 100644 (file)
@@ -190,6 +190,9 @@ protected:
     void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
     void hoverMoveEvent(QGraphicsSceneHoverEvent *event);
     void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
+#ifndef QT_NO_CONTEXTMENU
+    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);
+#endif // QT_NO_CONTEXTMENU
     bool sceneEvent(QEvent *);
     bool sendMouseEvent(QGraphicsSceneMouseEvent *event);
     bool sceneEventFilter(QGraphicsItem *i, QEvent *e);
diff --git a/tests/auto/declarative/qdeclarativemousearea/data/preventContextMenu.qml b/tests/auto/declarative/qdeclarativemousearea/data/preventContextMenu.qml
new file mode 100644 (file)
index 0000000..dcbb5d7
--- /dev/null
@@ -0,0 +1,22 @@
+import QtQuick 1.1
+import Test 1.0
+
+Item {
+    width: 200
+    height: 200
+
+    property alias mouseAreaEnabled: mouseArea.enabled
+    property alias eventsReceived: receiver.eventCount
+
+    ContextMenuEventReceiver {
+        id: receiver
+        anchors.fill: parent
+    }
+
+    MouseArea {
+        id: mouseArea
+        anchors.fill: parent
+        acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
+        enabled: true
+    }
+}
index e1c34fc..fea7865 100644 (file)
@@ -47,6 +47,7 @@
 #include <QtDeclarative/qdeclarativeview.h>
 #include <QtDeclarative/qdeclarativecontext.h>
 #include <QtDeclarative/qdeclarativeengine.h>
+#include <QtDeclarative/qdeclarativeproperty.h>
 
 #ifdef Q_OS_SYMBIAN
 // In Symbian OS test data is located in applications private dir
@@ -70,6 +71,9 @@ private slots:
     void preventStealing();
     void testQtQuick11Attributes();
     void testQtQuick11Attributes_data();
+#ifndef QT_NO_CONTEXTMENU
+    void preventContextMenu();
+#endif // QT_NO_CONTEXTMENU
 
 private:
     QDeclarativeView *createView();
@@ -637,6 +641,65 @@ void tst_QDeclarativeMouseArea::testQtQuick11Attributes_data()
         << ":1 \"MouseArea.preventStealing\" is not available in QtQuick 1.0.\n";
 }
 
+#ifndef QT_NO_CONTEXTMENU
+class ContextMenuEventReceiver : public QDeclarativeItem
+{
+    Q_OBJECT
+    Q_PROPERTY(int eventCount READ eventCount NOTIFY eventCountChanged);
+public:
+    ContextMenuEventReceiver(QDeclarativeItem *parent = 0) : QDeclarativeItem(parent), m_eventCount(0) { }
+    int eventCount() const { return m_eventCount; }
+signals:
+    void eventCountChanged(int);
+protected:
+    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
+        if (event->reason() == QGraphicsSceneContextMenuEvent::Mouse) {
+            m_eventCount++;
+            emit eventCountChanged(m_eventCount);
+        }
+    }
+private:
+    int m_eventCount;
+};
+
+void tst_QDeclarativeMouseArea::preventContextMenu()
+{
+    // A MouseArea accepting Left, Middle and Right buttons should prevent context menu
+    // events with "Mouse" reason to hit the Item below.
+
+    qmlRegisterType<ContextMenuEventReceiver>("Test", 1, 0, "ContextMenuEventReceiver");
+
+    QDeclarativeView *view = createView();
+    view->setSource(QUrl::fromLocalFile(SRCDIR "/data/preventContextMenu.qml"));
+    view->show();
+    QVERIFY(view->rootObject() != 0);
+
+    QDeclarativeProperty mouseAreaEnabled(view->rootObject(), "mouseAreaEnabled");
+    QVERIFY(mouseAreaEnabled.read().toBool());
+
+    QDeclarativeProperty eventsReceived(view->rootObject(), "eventsReceived");
+    QCOMPARE(eventsReceived.read().toInt(), 0);
+
+    QPoint targetPoint = view->mapFromScene(QPoint(80, 80));
+
+    QContextMenuEvent fakeEvent1(QContextMenuEvent::Mouse, targetPoint);
+    QApplication::sendEvent(view->viewport(), &fakeEvent1);
+    QCOMPARE(eventsReceived.read().toInt(), 0);
+
+    mouseAreaEnabled.write(false);
+    QVERIFY(!mouseAreaEnabled.read().toBool());
+    QContextMenuEvent fakeEvent2(QContextMenuEvent::Mouse, targetPoint);
+    QApplication::sendEvent(view->viewport(), &fakeEvent2);
+    QCOMPARE(eventsReceived.read().toInt(), 1);
+
+    mouseAreaEnabled.write(true);
+    QVERIFY(mouseAreaEnabled.read().toBool());
+    QContextMenuEvent fakeEvent3(QContextMenuEvent::Mouse, targetPoint);
+    QApplication::sendEvent(view->viewport(), &fakeEvent3);
+    QCOMPARE(eventsReceived.read().toInt(), 1);
+}
+#endif // QT_NO_CONTEXTMENU
+
 QTEST_MAIN(tst_QDeclarativeMouseArea)
 
 #include "tst_qdeclarativemousearea.moc"