Merge lp:~dandrader/unity8/doubleTapUserMetrics-lp1388359 into lp:unity8

Proposed by Daniel d'Andrada
Status: Merged
Approved by: Michael Terry
Approved revision: 1400
Merged at revision: 1416
Proposed branch: lp:~dandrader/unity8/doubleTapUserMetrics-lp1388359
Merge into: lp:unity8
Prerequisite: lp:~aacid/unity8/multimediaMocks
Diff against target: 497 lines (+207/-46)
9 files modified
libs/UbuntuGestures/DebugHelpers.cpp (+1/-1)
plugins/Ubuntu/Gestures/TouchDispatcher.cpp (+43/-0)
plugins/Ubuntu/Gestures/TouchDispatcher.h (+3/-0)
qml/Greeter/Greeter.qml (+1/-1)
qml/Greeter/Infographics.qml (+20/-15)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp (+6/-0)
tests/plugins/Ubuntu/Gestures/GestureTest.h (+2/-0)
tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp (+59/-0)
tests/qmltests/Greeter/tst_SingleGreeter.qml (+72/-29)
To merge this branch: bzr merge lp:~dandrader/unity8/doubleTapUserMetrics-lp1388359
Reviewer Review Type Date Requested Status
Michael Terry Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
Albert Astals Cid Pending
Review via email: mp+240453@code.launchpad.net

Commit message

TouchDispatcher: synthesize MouseButtonDblClick events

+ Adds a regression test for lp:1388359 and refactors tst_SingleGreeter and Infographics as needed

Description of the change

* Are there any related MPs required for this MP to build/function as expected? Please list.
No.

* Did you perform an exploratory manual test run of your code change and any related functionality?
Yes.

* Did you make sure that your branch does not contain spurious tags?
Yes.

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Not applicable.

* If you changed the UI, has there been a design review?
Not applicable.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

LGTM

 * Did you perform an exploratory manual test run of the code change and any related functionality?
 Yes

 * Did CI run pass? If not, please explain why.
 TBD

 * Did you make sure that the branch does not contain spurious tags?
 Yes

review: Approve
Revision history for this message
Michael Terry (mterry) wrote :

Wait... While you're here, can you fix the typo in Greeter.qml:

enabled: targetITem.enabled

Should be:

enabled: targetItem.enabled (fixed capitalization of 'T')

1401. By Daniel d'Andrada

Fix typo

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 03/11/14 19:40, Michael Terry wrote:
> Wait... While you're here, can you fix the typo in Greeter.qml:
>
> enabled: targetITem.enabled
>
> Should be:
>
> enabled: targetItem.enabled (fixed capitalization of 'T')

Done. Thanks for spotting that.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libs/UbuntuGestures/DebugHelpers.cpp'
2--- libs/UbuntuGestures/DebugHelpers.cpp 2014-10-17 11:01:53 +0000
3+++ libs/UbuntuGestures/DebugHelpers.cpp 2014-11-04 11:20:23 +0000
4@@ -83,7 +83,7 @@
5 message.append("MouseButtonDblClick ");
6 break;
7 case QEvent::MouseMove:
8- message.append("MouseButtonMove ");
9+ message.append("MouseMove ");
10 break;
11 default:
12 message.append("INVALID_MOUSE_EVENT_TYPE ");
13
14=== modified file 'plugins/Ubuntu/Gestures/TouchDispatcher.cpp'
15--- plugins/Ubuntu/Gestures/TouchDispatcher.cpp 2014-10-20 14:24:30 +0000
16+++ plugins/Ubuntu/Gestures/TouchDispatcher.cpp 2014-11-04 11:20:23 +0000
17@@ -16,7 +16,9 @@
18
19 #include "TouchDispatcher.h"
20
21+#include <QGuiApplication>
22 #include <QScopedPointer>
23+#include <QStyleHints>
24
25 #pragma GCC diagnostic push
26 #pragma GCC diagnostic ignored "-pedantic"
27@@ -32,6 +34,7 @@
28 TouchDispatcher::TouchDispatcher()
29 : m_status(NoActiveTouch)
30 , m_touchMouseId(-1)
31+ , m_touchMousePressTimestamp(0)
32 {
33 }
34
35@@ -148,6 +151,18 @@
36 #endif
37 m_status = DeliveringMouseEvents;
38 m_touchMouseId = targetTouchPoints.at(0).id();
39+
40+ if (checkIfDoubleClicked(timestamp)) {
41+ QScopedPointer<QMouseEvent> doubleClickEvent(
42+ touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
43+ modifiers, false /* transformNeeded */));
44+ #if TOUCHDISPATCHER_DEBUG
45+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
46+ << "to" << m_targetItem.data();
47+ #endif
48+ QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
49+ }
50+
51 } else {
52 #if TOUCHDISPATCHER_DEBUG
53 qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
54@@ -322,3 +337,31 @@
55 //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
56 return me;
57 }
58+
59+/*
60+ Copied from qquickwindow.cpp which has:
61+ Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies)
62+ Under GPL 3.0 license.
63+*/
64+bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
65+{
66+ bool doubleClicked;
67+
68+ if (m_touchMousePressTimestamp == 0) {
69+ // just initialize the variable
70+ m_touchMousePressTimestamp = newPressEventTimestamp;
71+ doubleClicked = false;
72+ } else {
73+ ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp;
74+ ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->
75+ mouseDoubleClickInterval());
76+ doubleClicked = timeBetweenPresses < doubleClickInterval;
77+ if (doubleClicked) {
78+ m_touchMousePressTimestamp = 0;
79+ } else {
80+ m_touchMousePressTimestamp = newPressEventTimestamp;
81+ }
82+ }
83+
84+ return doubleClicked;
85+}
86
87=== modified file 'plugins/Ubuntu/Gestures/TouchDispatcher.h'
88--- plugins/Ubuntu/Gestures/TouchDispatcher.h 2014-10-17 11:01:53 +0000
89+++ plugins/Ubuntu/Gestures/TouchDispatcher.h 2014-11-04 11:20:23 +0000
90@@ -71,6 +71,8 @@
91 QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::TouchPoint &p,
92 ulong timestamp, Qt::KeyboardModifiers modifiers, bool transformNeeded = true);
93
94+ bool checkIfDoubleClicked(ulong newPressEventTimestamp);
95+
96 QPointer<QQuickItem> m_targetItem;
97
98 enum {
99@@ -81,6 +83,7 @@
100 } m_status;
101
102 int m_touchMouseId;
103+ ulong m_touchMousePressTimestamp;
104 };
105
106 #endif // UBUNTU_TOUCH_DISPATCHER_H
107
108=== modified file 'qml/Greeter/Greeter.qml'
109--- qml/Greeter/Greeter.qml 2014-10-17 11:01:53 +0000
110+++ qml/Greeter/Greeter.qml 2014-11-04 11:20:23 +0000
111@@ -132,7 +132,7 @@
112 TouchGate {
113 targetItem: dragHandle
114 anchors.fill: targetItem
115- enabled: targetITem.enabled
116+ enabled: targetItem.enabled
117 }
118
119 Loader {
120
121=== modified file 'qml/Greeter/Infographics.qml'
122--- qml/Greeter/Infographics.qml 2014-07-29 13:47:33 +0000
123+++ qml/Greeter/Infographics.qml 2014-11-04 11:20:23 +0000
124@@ -26,8 +26,15 @@
125
126 property int animDuration: 10
127
128- property bool __useDotAnimation: true
129- property int __circleModifier: __useDotAnimation ? 1 : 2
130+ QtObject {
131+ id: d
132+ objectName: "infographicPrivate"
133+ property bool useDotAnimation: true
134+ property int circleModifier: useDotAnimation ? 1 : 2
135+ property bool animating: dotHideAnimTimer.running
136+ || dotShowAnimTimer.running
137+ || circleChangeAnimTimer.running
138+ }
139
140 Connections {
141 target: model
142@@ -46,7 +53,7 @@
143 dotHideAnimTimer.stop()
144 notification.hideAnim.stop()
145
146- if (__useDotAnimation) {
147+ if (d.useDotAnimation) {
148 dotShowAnimTimer.startFromBeginning()
149 }
150 notification.showAnim.start()
151@@ -57,7 +64,7 @@
152 circleChangeAnimTimer.stop()
153 notification.showAnim.stop()
154
155- if (__useDotAnimation) {
156+ if (d.useDotAnimation) {
157 dotHideAnimTimer.startFromBeginning()
158 } else {
159 circleChangeAnimTimer.startFromBeginning()
160@@ -148,21 +155,21 @@
161 property: "opacity"
162 to: pastCircle.circleOpacity
163 easing.type: Easing.OutCurve
164- duration: circleChangeAnimTimer.interval * __circleModifier
165+ duration: circleChangeAnimTimer.interval * d.circleModifier
166 }
167 PropertyAnimation {
168 target: pastCircle
169 property: "scale"
170 to: modelData
171 easing.type: Easing.OutCurve
172- duration: circleChangeAnimTimer.interval * __circleModifier
173+ duration: circleChangeAnimTimer.interval * d.circleModifier
174 }
175 ColorAnimation {
176 target: pastCircle
177 property: "color"
178 to: Gradient.threeColorByIndex(index, count, infographic.model.secondColor)
179 easing.type: Easing.OutCurve
180- duration: circleChangeAnimTimer.interval * __circleModifier
181+ duration: circleChangeAnimTimer.interval * d.circleModifier
182 }
183 }
184 }
185@@ -209,21 +216,21 @@
186 property: "opacity"
187 to: presentCircle.circleOpacity
188 easing.type: Easing.OutCurve
189- duration: circleChangeAnimTimer.interval * __circleModifier
190+ duration: circleChangeAnimTimer.interval * d.circleModifier
191 }
192 PropertyAnimation {
193 target: presentCircle
194 property: "scale"
195 to: modelData
196 easing.type: Easing.OutCurve
197- duration: circleChangeAnimTimer.interval * __circleModifier
198+ duration: circleChangeAnimTimer.interval * d.circleModifier
199 }
200 ColorAnimation {
201 target: presentCircle
202 property: "color"
203 to: Gradient.threeColorByIndex(index, infographic.model.currentDay, infographic.model.firstColor)
204 easing.type: Easing.OutCurve
205- duration: circleChangeAnimTimer.interval * __circleModifier
206+ duration: circleChangeAnimTimer.interval * d.circleModifier
207 }
208 }
209 }
210@@ -385,7 +392,7 @@
211 from: notification.baseOpacity
212 to: 0.0
213 duration: notification.duration * dots.count
214- onStopped: if (!__useDotAnimation) infographic.model.readyForDataChange()
215+ onStopped: if (!d.useDotAnimation) infographic.model.readyForDataChange()
216 }
217 }
218 }
219@@ -394,10 +401,8 @@
220 anchors.fill: dataCircle
221
222 onDoubleClicked: {
223- if (!dotHideAnimTimer.running &&
224- !dotShowAnimTimer.running &&
225- !circleChangeAnimTimer.running) {
226- __useDotAnimation = false
227+ if (!d.animating) {
228+ d.useDotAnimation = false
229 infographic.model.nextDataSource()
230 }
231 }
232
233=== modified file 'tests/plugins/Ubuntu/Gestures/GestureTest.cpp'
234--- tests/plugins/Ubuntu/Gestures/GestureTest.cpp 2014-10-17 11:01:53 +0000
235+++ tests/plugins/Ubuntu/Gestures/GestureTest.cpp 2014-11-04 11:20:23 +0000
236@@ -90,6 +90,7 @@
237 mousePressEventHandler = defaultMouseEventHandler;
238 mouseMoveEventHandler = defaultMouseEventHandler;
239 mouseReleaseEventHandler = defaultMouseEventHandler;
240+ mouseDoubleClickEventHandler = defaultMouseEventHandler;
241 }
242
243 void DummyItem::touchEvent(QTouchEvent *event)
244@@ -113,6 +114,11 @@
245 mouseReleaseEventHandler(event);
246 }
247
248+void DummyItem::mouseDoubleClickEvent(QMouseEvent *event)
249+{
250+ mouseDoubleClickEventHandler(event);
251+}
252+
253 void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
254 {
255 event->accept();
256
257=== modified file 'tests/plugins/Ubuntu/Gestures/GestureTest.h'
258--- tests/plugins/Ubuntu/Gestures/GestureTest.h 2014-10-17 11:01:53 +0000
259+++ tests/plugins/Ubuntu/Gestures/GestureTest.h 2014-11-04 11:20:23 +0000
260@@ -52,12 +52,14 @@
261 std::function<void(QMouseEvent*)> mousePressEventHandler;
262 std::function<void(QMouseEvent*)> mouseMoveEventHandler;
263 std::function<void(QMouseEvent*)> mouseReleaseEventHandler;
264+ std::function<void(QMouseEvent*)> mouseDoubleClickEventHandler;
265 protected:
266 void touchEvent(QTouchEvent *event) override;
267
268 void mousePressEvent(QMouseEvent *event) override;
269 void mouseMoveEvent(QMouseEvent *event) override;
270 void mouseReleaseEvent(QMouseEvent *event) override;
271+ void mouseDoubleClickEvent(QMouseEvent *event) override;
272 private:
273 static void defaultTouchEventHandler(QTouchEvent *event);
274 static void defaultMouseEventHandler(QMouseEvent *event);
275
276=== modified file 'tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp'
277--- tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp 2014-10-17 11:01:53 +0000
278+++ tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp 2014-11-04 11:20:23 +0000
279@@ -16,6 +16,7 @@
280
281 #include <QtTest>
282 #include <QQuickView>
283+#include <QStyleHints>
284
285 #include "GestureTest.h"
286
287@@ -29,6 +30,8 @@
288 private Q_SLOTS:
289 void sendMouseEventIfTouchIgnored_data();
290 void sendMouseEventIfTouchIgnored();
291+ void mouseDoubleClick_data();
292+ void mouseDoubleClick();
293 };
294
295 tst_TouchDispatcher::tst_TouchDispatcher()
296@@ -85,6 +88,62 @@
297 QCOMPARE(gotMousePressEvent, shouldGetMousePress);
298 }
299
300+void tst_TouchDispatcher::mouseDoubleClick_data()
301+{
302+ QTest::addColumn<ulong>("timeBetweenClicks");
303+ QTest::addColumn<bool>("shouldSendDoubleClick");
304+
305+ QTest::newRow("double click") << static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval() / 10) << true;
306+ QTest::newRow("two separate clicks") << static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval() * 2) << false;
307+}
308+
309+void tst_TouchDispatcher::mouseDoubleClick()
310+{
311+ QFETCH(ulong, timeBetweenClicks);
312+ QFETCH(bool, shouldSendDoubleClick);
313+ DummyItem *dummyItem = new DummyItem(m_view->rootObject());
314+ dummyItem->setAcceptedMouseButtons(Qt::LeftButton);
315+
316+ TouchDispatcher touchDispatcher;
317+ touchDispatcher.setTargetItem(dummyItem);
318+
319+ bool gotDoubleClickEvent = false;
320+ dummyItem->mouseDoubleClickEventHandler = [&](QMouseEvent *event) {
321+ gotDoubleClickEvent = true;
322+ event->accept();
323+ };
324+
325+ dummyItem->touchEventHandler = [&](QTouchEvent *event) {
326+ event->ignore();
327+ };
328+
329+ ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval());
330+
331+ QList<QTouchEvent::TouchPoint> touchPoints;
332+ {
333+ QTouchEvent::TouchPoint touchPoint;
334+ touchPoint.setId(0);
335+ touchPoint.setState(Qt::TouchPointPressed);
336+ touchPoints.append(touchPoint);
337+ }
338+ ulong timestamp = 12345;
339+
340+ touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
341+
342+ touchPoints[0].setState(Qt::TouchPointReleased);
343+ timestamp += doubleClickInterval / 10;
344+
345+ touchDispatcher.dispatch(QEvent::TouchEnd, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
346+
347+ touchPoints[0].setId(1);
348+ touchPoints[0].setState(Qt::TouchPointPressed);
349+ timestamp += timeBetweenClicks;
350+
351+ touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
352+
353+ QCOMPARE(gotDoubleClickEvent, shouldSendDoubleClick);
354+}
355+
356 QTEST_MAIN(tst_TouchDispatcher)
357
358 #include "tst_TouchDispatcher.moc"
359
360=== modified file 'tests/qmltests/Greeter/tst_SingleGreeter.qml'
361--- tests/qmltests/Greeter/tst_SingleGreeter.qml 2014-10-01 13:20:32 +0000
362+++ tests/qmltests/Greeter/tst_SingleGreeter.qml 2014-11-04 11:20:23 +0000
363@@ -27,50 +27,72 @@
364 width: units.gu(60)
365 height: units.gu(80)
366
367- Greeter {
368- id: greeter
369- width: parent.width
370- height: parent.height
371- x: 0; y: 0
372-
373- property int minX: 0
374-
375- onXChanged: {
376- if (x < minX) {
377- minX = x;
378- }
379- }
380- }
381-
382- Component {
383- id: greeterComponent
384- Greeter {
385- SignalSpy {
386- objectName: "selectedSpy"
387- target: parent
388- signalName: "selected"
389+ Loader {
390+ id: greeterLoader
391+ anchors.fill: parent
392+
393+ property bool itemDestroyed: false
394+
395+ sourceComponent: Component {
396+ Greeter {
397+ anchors.fill: greeterLoader
398+
399+ property int minX: 0
400+
401+ onXChanged: {
402+ if (x < minX) {
403+ minX = x;
404+ }
405+ }
406+
407+ Component.onDestruction: {
408+ greeterLoader.itemDestroyed = true;
409+ }
410+ SignalSpy {
411+ objectName: "selectedSpy"
412+ target: parent
413+ signalName: "selected"
414+ }
415 }
416 }
417 }
418
419 SignalSpy {
420 id: unlockSpy
421- target: greeter
422+ target: greeterLoader.item
423 signalName: "unlocked"
424 }
425
426 SignalSpy {
427 id: teaseSpy
428- target: greeter
429+ target: greeterLoader.item
430 signalName: "tease"
431 }
432
433+ SignalSpy {
434+ id: infographicDataChangedSpy
435+ target: LightDM.Infographic
436+ signalName: "dataChanged"
437+ }
438+
439 UT.UnityTestCase {
440 name: "SingleGreeter"
441 when: windowShown
442
443+ property Greeter greeter: greeterLoader.item
444+
445 function cleanup() {
446 AccountsService.statsWelcomeScreen = true
447+
448+ // force a reload so that we get a fresh Greeter for the next test
449+ greeterLoader.itemDestroyed = false;
450+ greeterLoader.active = false;
451+ tryCompare(greeterLoader, "itemDestroyed", true);
452+
453+ unlockSpy.clear();
454+ teaseSpy.clear();
455+
456+ greeterLoader.active = true;
457 }
458
459 function test_properties() {
460@@ -103,11 +125,32 @@
461 }
462
463 function test_initial_selected_signal() {
464- var greeterObj = greeterComponent.createObject(this)
465- var spy = findChild(greeterObj, "selectedSpy")
466- spy.wait()
467- tryCompare(spy, "count", 1)
468- greeterObj.destroy()
469+ var selectedSpy = findChild(greeter, "selectedSpy");
470+ selectedSpy.wait();
471+ tryCompare(selectedSpy, "count", 1);
472+ }
473+
474+ /*
475+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
476+ "User metrics can no longer be changed by double tap"
477+ */
478+ function test_doubleTapSwitchesToNextInfographic() {
479+ infographicDataChangedSpy.clear();
480+
481+ var infographicPrivate = findInvisibleChild(greeter, "infographicPrivate");
482+ verify(infographicPrivate);
483+
484+ // wait for the UI to settle down before double tapping it
485+ tryCompare(infographicPrivate, "animating", false);
486+
487+ var dataCircle = findChild(greeter, "dataCircle");
488+ verify(dataCircle);
489+
490+ tap(dataCircle);
491+ wait(1);
492+ tap(dataCircle);
493+
494+ tryCompare(infographicDataChangedSpy, "count", 1);
495 }
496 }
497 }

Subscribers

People subscribed via source and target branches