Add mouse wheel events handler to MouseArea
authorLuis Gabriel Lima <luis.gabriel@openbossa.org>
Thu, 5 Jan 2012 18:19:26 +0000 (15:19 -0300)
committerQt by Nokia <qt-info@nokia.com>
Sat, 10 Mar 2012 01:28:11 +0000 (02:28 +0100)
This patch was based on the attached patch in QTBUG-7369. It basically
exposes the wheel events to MouseArea via the onWheel signal.

The current API is based on the new QWheelEvent API introduced by this
patch: http://codereview.qt-project.org/#change,12532

Task-number: QTBUG-7369
Change-Id: Id58513715c2d0ae81e3a69e9e1ed400bbae07507
Reviewed-by: Michael Brasser <michael.brasser@nokia.com>
examples/qtquick/mousearea/mousearea-wheel-example.qml [new file with mode: 0644]
src/quick/items/qquickcanvas.cpp
src/quick/items/qquickevents.cpp
src/quick/items/qquickevents_p_p.h
src/quick/items/qquickitemsmodule.cpp
src/quick/items/qquickmousearea.cpp
src/quick/items/qquickmousearea_p.h
src/quick/items/qquickmousearea_p_p.h
tests/auto/quick/qquickmousearea/data/wheel.qml [new file with mode: 0644]
tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp

diff --git a/examples/qtquick/mousearea/mousearea-wheel-example.qml b/examples/qtquick/mousearea/mousearea-wheel-example.qml
new file mode 100644 (file)
index 0000000..60cfb16
--- /dev/null
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+Rectangle {
+    height: 700
+    width: 485
+    color: "#333333"
+
+    Column {
+        anchors.centerIn: parent
+        spacing: 2
+
+        Repeater {
+            model: ["#9ACD32", "#EEEEEE", "#FFD700", "#87CEEB"]
+
+            Rectangle {
+                property real scaleFactor: 1
+
+                height: 40 * scaleFactor
+                width: 60 * scaleFactor
+                color: modelData
+                anchors.horizontalCenter: parent.horizontalCenter
+
+                MouseArea {
+                    anchors.fill: parent
+                    onWheel: {
+                        if (wheel.modifiers & Qt.ControlModifier) {
+                            if (wheel.angleDelta.y > 0)
+                                parent.scaleFactor += 0.2;
+                            else if (parent.scaleFactor - 0.2 >= 0.2)
+                                parent.scaleFactor -= 0.2;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    Text {
+        anchors.bottom: parent.bottom
+        anchors.horizontalCenter: parent.horizontalCenter
+        color: "#FFD700"
+        text: "Rotate the mouse wheel pressing <Control> to resize the squares."
+    }
+}
index d73701e..6438873 100644 (file)
@@ -1185,7 +1185,8 @@ bool QQuickCanvasPrivate::deliverWheelEvent(QQuickItem *item, QWheelEvent *event
 
     QPointF p = item->mapFromScene(event->posF());
     if (QRectF(0, 0, item->width(), item->height()).contains(p)) {
-        QWheelEvent wheel(p, event->delta(), event->buttons(), event->modifiers(), event->orientation());
+        QWheelEvent wheel(p, p, event->pixelDelta(), event->angleDelta(), event->delta(),
+                          event->orientation(), event->buttons(), event->modifiers());
         wheel.accept();
         q->sendEvent(item, &wheel);
         if (wheel.isAccepted()) {
index 597e64e..9222a77 100644 (file)
@@ -236,4 +236,108 @@ Item {
 */
 
 
+/*!
+    \qmlclass WheelEvent QQuickWheelEvent
+    \inqmlmodule QtQuick 2
+    \ingroup qml-event-elements
+
+    \brief The WheelEvent object provides information about a mouse wheel event.
+
+    The position of the mouse can be found via the \l x and \l y properties.
+
+    \sa MouseArea
+*/
+
+/*!
+    \internal
+    \class QQuickWheelEvent
+*/
+
+/*!
+    \qmlproperty int QtQuick2::WheelEvent::x
+    \qmlproperty int QtQuick2::WheelEvent::y
+
+    These properties hold the coordinates of the position supplied by the wheel event.
+*/
+
+/*!
+    \qmlproperty bool QtQuick2::WheelEvent::accepted
+
+    Setting \a accepted to true prevents the wheel event from being
+    propagated to items below this item.
+
+    Generally, if the item acts on the wheel event then it should be accepted
+    so that items lower in the stacking order do not also respond to the same event.
+*/
+
+/*!
+    \qmlproperty int QtQuick2::WheelEvent::buttons
+
+    This property holds the mouse buttons pressed when the wheel event was generated.
+
+    It contains a bitwise combination of:
+    \list
+    \o Qt.LeftButton
+    \o Qt.RightButton
+    \o Qt.MiddleButton
+    \endlist
+*/
+
+/*!
+    \qmlproperty point QtQuick2::WheelEvent::angleDelta
+
+    This property holds the distance that the wheel is rotated in wheel degrees.
+    The x and y cordinate of this property holds the delta in horizontal and
+    vertical orientation.
+
+    A positive value indicates that the wheel was rotated up/right;
+    a negative value indicates that the wheel was rotated down/left.
+
+    Most mouse types work in steps of 15 degrees, in which case the delta value is a
+    multiple of 120; i.e., 120 units * 1/8 = 15 degrees.
+*/
+
+/*!
+    \qmlproperty point QtQuick2::WheelEvent::pixelDelta
+
+    This property holds the delta in screen pixels and is available in plataforms that
+    have high-resolution trackpads, such as Mac OS X.
+    The x and y cordinate of this property holds the delta in horizontal and
+    vertical orientation. The value should be used directly to scroll content on screen.
+
+    For platforms without high-resolution trackpad support, pixelDelta will always be (0,0),
+    and angleDelta should be used instead.
+*/
+
+/*!
+    \qmlproperty int QtQuick2::WheelEvent::modifiers
+
+    This property holds the keyboard modifier flags that existed immediately
+    before the event occurred.
+
+    It contains a bitwise combination of:
+    \list
+    \o Qt.NoModifier - No modifier key is pressed.
+    \o Qt.ShiftModifier - A Shift key on the keyboard is pressed.
+    \o Qt.ControlModifier - A Ctrl key on the keyboard is pressed.
+    \o Qt.AltModifier - An Alt key on the keyboard is pressed.
+    \o Qt.MetaModifier - A Meta key on the keyboard is pressed.
+    \o Qt.KeypadModifier - A keypad button is pressed.
+    \endlist
+
+    For example, to react to a Control key pressed during the wheel event:
+    \qml
+    MouseArea {
+        onWheel: {
+            if (wheel.modifiers & Qt.ControlModifier) {
+                if (wheel.angleDelta.y > 0)
+                    zoomIn();
+                else
+                    zoomOut();
+            }
+        }
+    }
+    \endqml
+*/
+
 QT_END_NAMESPACE
index 6300b0f..004daaf 100644 (file)
@@ -201,9 +201,47 @@ private:
 };
 
 
+class QQuickWheelEvent : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(qreal x READ x)
+    Q_PROPERTY(qreal y READ y)
+    Q_PROPERTY(QPoint angleDelta READ angleDelta)
+    Q_PROPERTY(QPoint pixelDelta READ pixelDelta)
+    Q_PROPERTY(int buttons READ buttons)
+    Q_PROPERTY(int modifiers READ modifiers)
+    Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
+
+public:
+    QQuickWheelEvent(qreal x, qreal y, const QPoint& angleDelta, const QPoint& pixelDelta,
+                     Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
+        : _x(x), _y(y), _angleDelta(angleDelta), _pixelDelta(pixelDelta), _buttons(buttons),
+          _modifiers(modifiers), _accepted(true) {}
+
+    qreal x() const { return _x; }
+    qreal y() const { return _y; }
+    QPoint angleDelta() const { return _angleDelta; }
+    QPoint pixelDelta() const { return _pixelDelta; }
+    int buttons() const { return _buttons; }
+    int modifiers() const { return _modifiers; }
+
+    bool isAccepted() { return _accepted; }
+    void setAccepted(bool accepted) { _accepted = accepted; }
+
+private:
+    qreal _x;
+    qreal _y;
+    QPoint _angleDelta;
+    QPoint _pixelDelta;
+    Qt::MouseButtons _buttons;
+    Qt::KeyboardModifiers _modifiers;
+    bool _accepted;
+};
+
 QT_END_NAMESPACE
 
 QML_DECLARE_TYPE(QQuickKeyEvent)
 QML_DECLARE_TYPE(QQuickMouseEvent)
+QML_DECLARE_TYPE(QQuickWheelEvent)
 
 #endif // QQUICKEVENTS_P_P_H
index 3d23f7b..421a4cd 100644 (file)
@@ -169,6 +169,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
     qmlRegisterType<QQuickAnchors>();
     qmlRegisterType<QQuickKeyEvent>();
     qmlRegisterType<QQuickMouseEvent>();
+    qmlRegisterType<QQuickWheelEvent>();
     qmlRegisterType<QQuickTransform>();
     qmlRegisterType<QQuickPathElement>();
     qmlRegisterType<QQuickCurve>();
index ef57242..efb804b 100644 (file)
@@ -236,6 +236,13 @@ bool QQuickMouseAreaPrivate::isClickConnected()
     return QObjectPrivate::get(q)->isSignalConnected(idx);
 }
 
+bool QQuickMouseAreaPrivate::isWheelConnected()
+{
+    Q_Q(QQuickMouseArea);
+    static int idx = QObjectPrivate::get(q)->signalIndex("wheel(QQuickWheelEvent*)");
+    return QObjectPrivate::get(q)->isSignalConnected(idx);
+}
+
 void QQuickMouseAreaPrivate::propagate(QQuickMouseEvent* event, PropagateType t)
 {
     Q_Q(QQuickMouseArea);
@@ -331,7 +338,8 @@ bool QQuickMouseAreaPrivate::propagateHelper(QQuickMouseEvent *ev, QQuickItem *i
     Information about the mouse position and button clicks are provided via
     signals for which event handler properties are defined. The most commonly
     used involved handling mouse presses and clicks: onClicked, onDoubleClicked,
-    onPressed, onReleased and onPressAndHold.
+    onPressed, onReleased and onPressAndHold. It's also possible to handle mouse
+    wheel events via the onWheel signal.
 
     By default, MouseArea items only report mouse clicks and not changes to the
     position of the mouse cursor. Setting the hoverEnabled property ensures that
@@ -513,6 +521,17 @@ bool QQuickMouseAreaPrivate::propagateHelper(QQuickMouseEvent *ev, QQuickItem *i
     the logic when the MouseArea has lost the mouse handling to the \l Flickable,
     \c onCanceled should be used in addition to onReleased.
 */
+
+/*!
+    \qmlsignal QtQuick2::MouseArea::onWheel(WheelEvent mouse)
+
+    This handler is called in response to both mouse wheel and trackpad scroll gestures.
+
+    The \l {WheelEvent}{wheel} parameter provides information about the event, including the x and y
+    position, any buttons currently pressed, and information about the wheel movement, including
+    angleDelta and pixelDelta.
+*/
+
 QQuickMouseArea::QQuickMouseArea(QQuickItem *parent)
   : QQuickItem(*(new QQuickMouseAreaPrivate), parent)
 {
@@ -860,6 +879,22 @@ void QQuickMouseArea::hoverLeaveEvent(QHoverEvent *event)
         setHovered(false);
 }
 
+void QQuickMouseArea::wheelEvent(QWheelEvent *event)
+{
+    Q_D(QQuickMouseArea);
+    if (!d->absorb) {
+        QQuickItem::wheelEvent(event);
+        return;
+    }
+
+    QQuickWheelEvent we(event->posF().x(), event->posF().y(), event->angleDelta(),
+                        event->pixelDelta(), event->buttons(), event->modifiers());
+    we.setAccepted(d->isWheelConnected());
+    emit wheel(&we);
+    if (!we.isAccepted())
+        QQuickItem::wheelEvent(event);
+}
+
 void QQuickMouseArea::ungrabMouse()
 {
     Q_D(QQuickMouseArea);
index 93a0635..7eb9e5d 100644 (file)
@@ -119,6 +119,7 @@ private:
 };
 
 class QQuickMouseAreaPrivate;
+class QQuickWheelEvent;
 // used in QtLocation
 class Q_QUICK_EXPORT QQuickMouseArea : public QQuickItem
 {
@@ -182,6 +183,7 @@ Q_SIGNALS:
     void released(QQuickMouseEvent *mouse);
     void clicked(QQuickMouseEvent *mouse);
     void doubleClicked(QQuickMouseEvent *mouse);
+    void wheel(QQuickWheelEvent *wheel);
     void entered();
     void exited();
     void canceled();
@@ -199,6 +201,7 @@ protected:
     virtual void hoverEnterEvent(QHoverEvent *event);
     virtual void hoverMoveEvent(QHoverEvent *event);
     virtual void hoverLeaveEvent(QHoverEvent *event);
+    virtual void wheelEvent(QWheelEvent *event);
     virtual bool childMouseEventFilter(QQuickItem *i, QEvent *e);
     virtual void timerEvent(QTimerEvent *event);
     virtual void windowDeactivateEvent();
index bcdf033..f5521d9 100644 (file)
@@ -83,6 +83,7 @@ public:
     bool isPressAndHoldConnected();
     bool isDoubleClickConnected();
     bool isClickConnected();
+    bool isWheelConnected();
 
     bool absorb : 1;
     bool hovered : 1;
diff --git a/tests/auto/quick/qquickmousearea/data/wheel.qml b/tests/auto/quick/qquickmousearea/data/wheel.qml
new file mode 100644 (file)
index 0000000..3e0c0c2
--- /dev/null
@@ -0,0 +1,24 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+
+    property var angleDeltaY
+    property real mouseX
+    property real mouseY
+    property bool controlPressed
+
+    width: 400
+    height: 400
+
+    MouseArea {
+        anchors.fill: parent
+
+        onWheel: {
+            root.angleDeltaY = wheel.angleDelta.y;
+            root.mouseX = wheel.x;
+            root.mouseY = wheel.y;
+            root.controlPressed = wheel.modifiers & Qt.ControlModifier;
+        }
+    }
+}
index 4375e83..0b4a6fa 100644 (file)
@@ -73,6 +73,7 @@ private slots:
     void hoverPropagation();
     void hoverVisible();
     void disableAfterPress();
+    void onWheel();
 
 private:
     QQuickView *createView();
@@ -906,6 +907,26 @@ void tst_QQuickMouseArea::disableAfterPress()
     delete canvas;
 }
 
+void tst_QQuickMouseArea::onWheel()
+{
+    QQuickView *canvas = createView();
+    canvas->setSource(testFileUrl("wheel.qml"));
+
+    QQuickItem *root = canvas->rootObject();
+    QVERIFY(root != 0);
+
+    QWheelEvent wheelEvent(QPoint(10, 32), QPoint(10, 32), QPoint(60, 20), QPoint(0, 120),
+                           0, Qt::Vertical,Qt::NoButton, Qt::ControlModifier);
+    QGuiApplication::sendEvent(canvas, &wheelEvent);
+
+    QCOMPARE(root->property("angleDeltaY").toInt(), 120);
+    QCOMPARE(root->property("mouseX").toReal(), qreal(10));
+    QCOMPARE(root->property("mouseY").toReal(), qreal(32));
+    QCOMPARE(root->property("controlPressed").toBool(), true);
+
+    delete canvas;
+}
+
 QTEST_MAIN(tst_QQuickMouseArea)
 
 #include "tst_qquickmousearea.moc"