From f2e1be963f885a6030136591414cdbda26d50695 Mon Sep 17 00:00:00 2001 From: Luis Gabriel Lima Date: Thu, 5 Jan 2012 15:19:26 -0300 Subject: [PATCH] Add mouse wheel events handler to MouseArea 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 --- .../qtquick/mousearea/mousearea-wheel-example.qml | 84 +++++++++++++++++ src/quick/items/qquickcanvas.cpp | 3 +- src/quick/items/qquickevents.cpp | 104 +++++++++++++++++++++ src/quick/items/qquickevents_p_p.h | 38 ++++++++ src/quick/items/qquickitemsmodule.cpp | 1 + src/quick/items/qquickmousearea.cpp | 37 +++++++- src/quick/items/qquickmousearea_p.h | 3 + src/quick/items/qquickmousearea_p_p.h | 1 + tests/auto/quick/qquickmousearea/data/wheel.qml | 24 +++++ .../quick/qquickmousearea/tst_qquickmousearea.cpp | 21 +++++ 10 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 examples/qtquick/mousearea/mousearea-wheel-example.qml create mode 100644 tests/auto/quick/qquickmousearea/data/wheel.qml diff --git a/examples/qtquick/mousearea/mousearea-wheel-example.qml b/examples/qtquick/mousearea/mousearea-wheel-example.qml new file mode 100644 index 0000000..60cfb16 --- /dev/null +++ b/examples/qtquick/mousearea/mousearea-wheel-example.qml @@ -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 to resize the squares." + } +} diff --git a/src/quick/items/qquickcanvas.cpp b/src/quick/items/qquickcanvas.cpp index d73701e..6438873 100644 --- a/src/quick/items/qquickcanvas.cpp +++ b/src/quick/items/qquickcanvas.cpp @@ -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()) { diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 597e64e..9222a77 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -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 diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h index 6300b0f..004daaf 100644 --- a/src/quick/items/qquickevents_p_p.h +++ b/src/quick/items/qquickevents_p_p.h @@ -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 diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index 3d23f7b..421a4cd 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -169,6 +169,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); + qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); diff --git a/src/quick/items/qquickmousearea.cpp b/src/quick/items/qquickmousearea.cpp index ef57242..efb804b 100644 --- a/src/quick/items/qquickmousearea.cpp +++ b/src/quick/items/qquickmousearea.cpp @@ -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); diff --git a/src/quick/items/qquickmousearea_p.h b/src/quick/items/qquickmousearea_p.h index 93a0635..7eb9e5d 100644 --- a/src/quick/items/qquickmousearea_p.h +++ b/src/quick/items/qquickmousearea_p.h @@ -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(); diff --git a/src/quick/items/qquickmousearea_p_p.h b/src/quick/items/qquickmousearea_p_p.h index bcdf033..f5521d9 100644 --- a/src/quick/items/qquickmousearea_p_p.h +++ b/src/quick/items/qquickmousearea_p_p.h @@ -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 index 0000000..3e0c0c2 --- /dev/null +++ b/tests/auto/quick/qquickmousearea/data/wheel.qml @@ -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; + } + } +} diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index 4375e83..0b4a6fa 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -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" -- 2.7.4