on xenial help sign in
TouchDispatcher: synthesize MouseButtonDblClick events

+ Adds a regression test for lp:1388359 and refactors tst_SingleGreeter and Infographics as needed Fixes: 1388359 Approved by: Michael Terry

commit645165eea6d77a6649b850452b7ed19608842001 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateTue 4 Nov 2014, 12:55 (12 years ago) parentb6680031e3c5fd
9 files changed+204−43
libs/UbuntuGestures/DebugHelpers.cpp+1−1 plugins/Ubuntu/Gestures/TouchDispatcher.cpp+43 plugins/Ubuntu/Gestures/TouchDispatcher.h+3 qml/Greeter/Greeter.qml+1−1 qml/Greeter/Infographics.qml+20−15 tests/plugins/Ubuntu/Gestures/GestureTest.cpp+6 tests/plugins/Ubuntu/Gestures/GestureTest.h+2 tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp+59 tests/qmltests/Greeter/tst_SingleGreeter.qml+69−26
libs/UbuntuGestures/DebugHelpers.cpp+1−1
@@ -83,7 +83,7 @@ QString mouseEventToString(const QMouseEvent *ev)
8383 message.append("MouseButtonDblClick ");
8484 break;
8585 case QEvent::MouseMove:
86− message.append("MouseButtonMove ");
86+ message.append("MouseMove ");
8787 break;
8888 default:
8989 message.append("INVALID_MOUSE_EVENT_TYPE ");
plugins/Ubuntu/Gestures/TouchDispatcher.cpp+43
@@ -16,7 +16,9 @@
1616
1717#include "TouchDispatcher.h"
1818
19+#include <QGuiApplication>
1920#include <QScopedPointer>
21+#include <QStyleHints>
2022
2123#pragma GCC diagnostic push
2224#pragma GCC diagnostic ignored "-pedantic"
@@ -32,6 +34,7 @@
3234TouchDispatcher::TouchDispatcher()
3335 : m_status(NoActiveTouch)
3436 , m_touchMouseId(-1)
37+ , m_touchMousePressTimestamp(0)
3538{
3639}
3740
@@ -148,6 +151,18 @@ void TouchDispatcher::dispatchTouchBegin(
148151 #endif
149152 m_status = DeliveringMouseEvents;
150153 m_touchMouseId = targetTouchPoints.at(0).id();
154+
155+ if (checkIfDoubleClicked(timestamp)) {
156+ QScopedPointer<QMouseEvent> doubleClickEvent(
157+ touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
158+ modifiers, false /* transformNeeded */));
159+ #if TOUCHDISPATCHER_DEBUG
160+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
161+ << "to" << m_targetItem.data();
162+ #endif
163+ QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
164+ }
165+
151166 } else {
152167 #if TOUCHDISPATCHER_DEBUG
153168 qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
@@ -322,3 +337,31 @@ QMouseEvent *TouchDispatcher::touchToMouseEvent(
322337 //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
323338 return me;
324339}
340+
341+/*
342+ Copied from qquickwindow.cpp which has:
343+ Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies)
344+ Under GPL 3.0 license.
345+*/
346+bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
347+{
348+ bool doubleClicked;
349+
350+ if (m_touchMousePressTimestamp == 0) {
351+ // just initialize the variable
352+ m_touchMousePressTimestamp = newPressEventTimestamp;
353+ doubleClicked = false;
354+ } else {
355+ ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp;
356+ ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->
357+ mouseDoubleClickInterval());
358+ doubleClicked = timeBetweenPresses < doubleClickInterval;
359+ if (doubleClicked) {
360+ m_touchMousePressTimestamp = 0;
361+ } else {
362+ m_touchMousePressTimestamp = newPressEventTimestamp;
363+ }
364+ }
365+
366+ return doubleClicked;
367+}
plugins/Ubuntu/Gestures/TouchDispatcher.h+3
@@ -71,6 +71,8 @@ private:
7171 QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::TouchPoint &p,
7272 ulong timestamp, Qt::KeyboardModifiers modifiers, bool transformNeeded = true);
7373
74+ bool checkIfDoubleClicked(ulong newPressEventTimestamp);
75+
7476 QPointer<QQuickItem> m_targetItem;
7577
7678 enum {
@@ -81,6 +83,7 @@ private:
8183 } m_status;
8284
8385 int m_touchMouseId;
86+ ulong m_touchMousePressTimestamp;
8487};
8588
8689#endif // UBUNTU_TOUCH_DISPATCHER_H
qml/Greeter/Greeter.qml+1−1
@@ -132,7 +132,7 @@ Showable {
132132 TouchGate {
133133 targetItem: dragHandle
134134 anchors.fill: targetItem
135− enabled: targetITem.enabled
135+ enabled: targetItem.enabled
136136 }
137137
138138 Loader {
qml/Greeter/Infographics.qml+20−15
@@ -26,8 +26,15 @@ Item {
2626
2727 property int animDuration: 10
2828
29− property bool __useDotAnimation: true
30− property int __circleModifier: __useDotAnimation ? 1 : 2
29+ QtObject {
30+ id: d
31+ objectName: "infographicPrivate"
32+ property bool useDotAnimation: true
33+ property int circleModifier: useDotAnimation ? 1 : 2
34+ property bool animating: dotHideAnimTimer.running
35+ || dotShowAnimTimer.running
36+ || circleChangeAnimTimer.running
37+ }
3138
3239 Connections {
3340 target: model
@@ -46,7 +53,7 @@ Item {
4653 dotHideAnimTimer.stop()
4754 notification.hideAnim.stop()
4855
49− if (__useDotAnimation) {
56+ if (d.useDotAnimation) {
5057 dotShowAnimTimer.startFromBeginning()
5158 }
5259 notification.showAnim.start()
@@ -57,7 +64,7 @@ Item {
5764 circleChangeAnimTimer.stop()
5865 notification.showAnim.stop()
5966
60− if (__useDotAnimation) {
67+ if (d.useDotAnimation) {
6168 dotHideAnimTimer.startFromBeginning()
6269 } else {
6370 circleChangeAnimTimer.startFromBeginning()
@@ -148,21 +155,21 @@ Item {
148155 property: "opacity"
149156 to: pastCircle.circleOpacity
150157 easing.type: Easing.OutCurve
151− duration: circleChangeAnimTimer.interval * __circleModifier
158+ duration: circleChangeAnimTimer.interval * d.circleModifier
152159 }
153160 PropertyAnimation {
154161 target: pastCircle
155162 property: "scale"
156163 to: modelData
157164 easing.type: Easing.OutCurve
158− duration: circleChangeAnimTimer.interval * __circleModifier
165+ duration: circleChangeAnimTimer.interval * d.circleModifier
159166 }
160167 ColorAnimation {
161168 target: pastCircle
162169 property: "color"
163170 to: Gradient.threeColorByIndex(index, count, infographic.model.secondColor)
164171 easing.type: Easing.OutCurve
165− duration: circleChangeAnimTimer.interval * __circleModifier
172+ duration: circleChangeAnimTimer.interval * d.circleModifier
166173 }
167174 }
168175 }
@@ -209,21 +216,21 @@ Item {
209216 property: "opacity"
210217 to: presentCircle.circleOpacity
211218 easing.type: Easing.OutCurve
212− duration: circleChangeAnimTimer.interval * __circleModifier
219+ duration: circleChangeAnimTimer.interval * d.circleModifier
213220 }
214221 PropertyAnimation {
215222 target: presentCircle
216223 property: "scale"
217224 to: modelData
218225 easing.type: Easing.OutCurve
219− duration: circleChangeAnimTimer.interval * __circleModifier
226+ duration: circleChangeAnimTimer.interval * d.circleModifier
220227 }
221228 ColorAnimation {
222229 target: presentCircle
223230 property: "color"
224231 to: Gradient.threeColorByIndex(index, infographic.model.currentDay, infographic.model.firstColor)
225232 easing.type: Easing.OutCurve
226− duration: circleChangeAnimTimer.interval * __circleModifier
233+ duration: circleChangeAnimTimer.interval * d.circleModifier
227234 }
228235 }
229236 }
@@ -385,7 +392,7 @@ Item {
385392 from: notification.baseOpacity
386393 to: 0.0
387394 duration: notification.duration * dots.count
388− onStopped: if (!__useDotAnimation) infographic.model.readyForDataChange()
395+ onStopped: if (!d.useDotAnimation) infographic.model.readyForDataChange()
389396 }
390397 }
391398 }
@@ -394,10 +401,8 @@ Item {
394401 anchors.fill: dataCircle
395402
396403 onDoubleClicked: {
397− if (!dotHideAnimTimer.running &&
398− !dotShowAnimTimer.running &&
399− !circleChangeAnimTimer.running) {
400− __useDotAnimation = false
404+ if (!d.animating) {
405+ d.useDotAnimation = false
401406 infographic.model.nextDataSource()
402407 }
403408 }
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+6
@@ -90,6 +90,7 @@ DummyItem::DummyItem(QQuickItem *parent)
9090 mousePressEventHandler = defaultMouseEventHandler;
9191 mouseMoveEventHandler = defaultMouseEventHandler;
9292 mouseReleaseEventHandler = defaultMouseEventHandler;
93+ mouseDoubleClickEventHandler = defaultMouseEventHandler;
9394}
9495
9596void DummyItem::touchEvent(QTouchEvent *event)
@@ -113,6 +114,11 @@ void DummyItem::mouseReleaseEvent(QMouseEvent *event)
113114 mouseReleaseEventHandler(event);
114115}
115116
117+void DummyItem::mouseDoubleClickEvent(QMouseEvent *event)
118+{
119+ mouseDoubleClickEventHandler(event);
120+}
121+
116122void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
117123{
118124 event->accept();
tests/plugins/Ubuntu/Gestures/GestureTest.h+2
@@ -52,12 +52,14 @@ public:
5252 std::function<void(QMouseEvent*)> mousePressEventHandler;
5353 std::function<void(QMouseEvent*)> mouseMoveEventHandler;
5454 std::function<void(QMouseEvent*)> mouseReleaseEventHandler;
55+ std::function<void(QMouseEvent*)> mouseDoubleClickEventHandler;
5556protected:
5657 void touchEvent(QTouchEvent *event) override;
5758
5859 void mousePressEvent(QMouseEvent *event) override;
5960 void mouseMoveEvent(QMouseEvent *event) override;
6061 void mouseReleaseEvent(QMouseEvent *event) override;
62+ void mouseDoubleClickEvent(QMouseEvent *event) override;
6163private:
6264 static void defaultTouchEventHandler(QTouchEvent *event);
6365 static void defaultMouseEventHandler(QMouseEvent *event);
tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp+59
@@ -16,6 +16,7 @@
1616
1717#include <QtTest>
1818#include <QQuickView>
19+#include <QStyleHints>
1920
2021#include "GestureTest.h"
2122
@@ -29,6 +30,8 @@ public:
2930private Q_SLOTS:
3031 void sendMouseEventIfTouchIgnored_data();
3132 void sendMouseEventIfTouchIgnored();
33+ void mouseDoubleClick_data();
34+ void mouseDoubleClick();
3235};
3336
3437tst_TouchDispatcher::tst_TouchDispatcher()
@@ -85,6 +88,62 @@ void tst_TouchDispatcher::sendMouseEventIfTouchIgnored()
8588 QCOMPARE(gotMousePressEvent, shouldGetMousePress);
8689}
8790
91+void tst_TouchDispatcher::mouseDoubleClick_data()
92+{
93+ QTest::addColumn<ulong>("timeBetweenClicks");
94+ QTest::addColumn<bool>("shouldSendDoubleClick");
95+
96+ QTest::newRow("double click") << static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval() / 10) << true;
97+ QTest::newRow("two separate clicks") << static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval() * 2) << false;
98+}
99+
100+void tst_TouchDispatcher::mouseDoubleClick()
101+{
102+ QFETCH(ulong, timeBetweenClicks);
103+ QFETCH(bool, shouldSendDoubleClick);
104+ DummyItem *dummyItem = new DummyItem(m_view->rootObject());
105+ dummyItem->setAcceptedMouseButtons(Qt::LeftButton);
106+
107+ TouchDispatcher touchDispatcher;
108+ touchDispatcher.setTargetItem(dummyItem);
109+
110+ bool gotDoubleClickEvent = false;
111+ dummyItem->mouseDoubleClickEventHandler = [&](QMouseEvent *event) {
112+ gotDoubleClickEvent = true;
113+ event->accept();
114+ };
115+
116+ dummyItem->touchEventHandler = [&](QTouchEvent *event) {
117+ event->ignore();
118+ };
119+
120+ ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval());
121+
122+ QList<QTouchEvent::TouchPoint> touchPoints;
123+ {
124+ QTouchEvent::TouchPoint touchPoint;
125+ touchPoint.setId(0);
126+ touchPoint.setState(Qt::TouchPointPressed);
127+ touchPoints.append(touchPoint);
128+ }
129+ ulong timestamp = 12345;
130+
131+ touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
132+
133+ touchPoints[0].setState(Qt::TouchPointReleased);
134+ timestamp += doubleClickInterval / 10;
135+
136+ touchDispatcher.dispatch(QEvent::TouchEnd, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
137+
138+ touchPoints[0].setId(1);
139+ touchPoints[0].setState(Qt::TouchPointPressed);
140+ timestamp += timeBetweenClicks;
141+
142+ touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
143+
144+ QCOMPARE(gotDoubleClickEvent, shouldSendDoubleClick);
145+}
146+
88147QTEST_MAIN(tst_TouchDispatcher)
89148
90149#include "tst_TouchDispatcher.moc"
tests/qmltests/Greeter/tst_SingleGreeter.qml+69−26
@@ -27,50 +27,72 @@ Item {
2727 width: units.gu(60)
2828 height: units.gu(80)
2929
30− Greeter {
31− id: greeter
32− width: parent.width
33− height: parent.height
34− x: 0; y: 0
30+ Loader {
31+ id: greeterLoader
32+ anchors.fill: parent
3533
36− property int minX: 0
34+ property bool itemDestroyed: false
3735
38− onXChanged: {
39− if (x < minX) {
40− minX = x;
41− }
42− }
43− }
36+ sourceComponent: Component {
37+ Greeter {
38+ anchors.fill: greeterLoader
39+
40+ property int minX: 0
41+
42+ onXChanged: {
43+ if (x < minX) {
44+ minX = x;
45+ }
46+ }
4447
45− Component {
46− id: greeterComponent
47− Greeter {
48− SignalSpy {
49− objectName: "selectedSpy"
50− target: parent
51− signalName: "selected"
48+ Component.onDestruction: {
49+ greeterLoader.itemDestroyed = true;
50+ }
51+ SignalSpy {
52+ objectName: "selectedSpy"
53+ target: parent
54+ signalName: "selected"
55+ }
5256 }
5357 }
5458 }
5559
5660 SignalSpy {
5761 id: unlockSpy
58− target: greeter
62+ target: greeterLoader.item
5963 signalName: "unlocked"
6064 }
6165
6266 SignalSpy {
6367 id: teaseSpy
64− target: greeter
68+ target: greeterLoader.item
6569 signalName: "tease"
6670 }
6771
72+ SignalSpy {
73+ id: infographicDataChangedSpy
74+ target: LightDM.Infographic
75+ signalName: "dataChanged"
76+ }
77+
6878 UT.UnityTestCase {
6979 name: "SingleGreeter"
7080 when: windowShown
7181
82+ property Greeter greeter: greeterLoader.item
83+
7284 function cleanup() {
7385 AccountsService.statsWelcomeScreen = true
86+
87+ // force a reload so that we get a fresh Greeter for the next test
88+ greeterLoader.itemDestroyed = false;
89+ greeterLoader.active = false;
90+ tryCompare(greeterLoader, "itemDestroyed", true);
91+
92+ unlockSpy.clear();
93+ teaseSpy.clear();
94+
95+ greeterLoader.active = true;
7496 }
7597
7698 function test_properties() {
@@ -103,11 +125,32 @@ Item {
103125 }
104126
105127 function test_initial_selected_signal() {
106− var greeterObj = greeterComponent.createObject(this)
107− var spy = findChild(greeterObj, "selectedSpy")
108− spy.wait()
109− tryCompare(spy, "count", 1)
110− greeterObj.destroy()
128+ var selectedSpy = findChild(greeter, "selectedSpy");
129+ selectedSpy.wait();
130+ tryCompare(selectedSpy, "count", 1);
131+ }
132+
133+ /*
134+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
135+ "User metrics can no longer be changed by double tap"
136+ */
137+ function test_doubleTapSwitchesToNextInfographic() {
138+ infographicDataChangedSpy.clear();
139+
140+ var infographicPrivate = findInvisibleChild(greeter, "infographicPrivate");
141+ verify(infographicPrivate);
142+
143+ // wait for the UI to settle down before double tapping it
144+ tryCompare(infographicPrivate, "animating", false);
145+
146+ var dataCircle = findChild(greeter, "dataCircle");
147+ verify(dataCircle);
148+
149+ tap(dataCircle);
150+ wait(1);
151+ tap(dataCircle);
152+
153+ tryCompare(infographicDataChangedSpy, "count", 1);
111154 }
112155 }
113156}