Merge lp:~unity-team/unity8/homeKey into lp:unity8

Proposed by Michał Sawicz on 2015-04-23
Status: Merged
Approved by: Michał Sawicz on 2015-04-23
Approved revision: 1738
Merged at revision: 1746
Proposed branch: lp:~unity-team/unity8/homeKey
Merge into: lp:unity8
Diff against target: 817 lines (+712/-4)
10 files modified
plugins/Utils/CMakeLists.txt (+2/-0)
plugins/Utils/ElapsedTimer.h (+51/-0)
plugins/Utils/HomeKeyWatcher.cpp (+116/-0)
plugins/Utils/HomeKeyWatcher.h (+74/-0)
plugins/Utils/Timer.cpp (+59/-0)
plugins/Utils/Timer.h (+79/-0)
plugins/Utils/plugin.cpp (+3/-4)
qml/Shell.qml (+4/-0)
tests/plugins/Utils/CMakeLists.txt (+1/-0)
tests/plugins/Utils/homekeywatchertest.cpp (+323/-0)
To merge this branch: bzr merge lp:~unity-team/unity8/homeKey
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Approve on 2015-04-23
Michał Sawicz Approve on 2015-04-23
Josh Arenson 2015-04-23 Pending
PS Jenkins bot continuous-integration 2015-04-23 Pending
Review via email: mp+257254@code.launchpad.net

This proposal supersedes a proposal from 2015-04-17.

Commit Message

Tapping home key shows unity8-dash home

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?
N/A

* If you changed the UI, has there been a design review?
N/A

To post a comment you must log in.
Josh Arenson (josharenson) wrote : Posted in a previous version of this proposal

We had discussed on IRC that pressing the home key should hide the launcher. Since this MP does _not_ do that, I was wondering if that was still the case.

review: Needs Information
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

The whole timer situation feels too complicated, could you at least split out the fake timer into tests/utils?

Any reason you're not consistent with file name case (homekeywatcher.* vs. ElapsedTimer.*)?

See inline (Ctrl+F + saviq) ;)

review: Needs Information
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 21/04/15 11:01, Michał Sawicz wrote:
> Review: Needs Information
>
> The whole timer situation feels too complicated, could you at least split out the fake timer into tests/utils?
Done.
That's the price you pay for testability. But at least this stuff can be
used by other classes in the future. This is actually a
copy&paste&rename from Ubuntu.Gestures plugin.
>
> Any reason you're not consistent with file name case (homekeywatcher.* vs. ElapsedTimer.*)?
Yes. The all-lowercase version was made be consistent with the naming of
other files in this plugin. But then I revolted against this bad
convention (can you name a single benefit of it?) and started doing
CamelCase instead. Made them all CamelCase now.
> +
> +class AbstractElapsedTimer {
> +public:
> + virtual ~AbstractElapsedTimer() {}
> + virtual void start() = 0;
> + virtual qint64 msecsSinceReference() const = 0;
> + virtual qint64 elapsed() const = 0;
> +};
> +
> +/*
> Please use /** where applicable so that docs are generated.

Done.
> +HomeKeyWatcher::HomeKeyWatcher(UnityUtil::AbstractTimer *timer,
> + UnityUtil::AbstractElapsedTimer *elapsedTimer,
> + QQuickItem *parent)
> + : QQuickItem(parent)
> + , m_windowBeingTouched(false)
> + , m_windowLastTouchedTimer(elapsedTimer)
> Doesn't this timer need to be started on construction (ideally if there are no touches)? Candidate for a unit test?

In theory yes, in practice it doesn't really matter. Added a start()
call there anyway.
>
>> + , m_emitActivatedIfNoTouchesAroundTimer(timer)
>> +{
>> + connect(this, &QQuickItem::windowChanged,
> Can we rely on this signal on startup?
Yes.

> +void HomeKeyWatcher::update(QEvent *event)
> +{
> + if (event->type() == QEvent::KeyPress) {
> + QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
> +
> + if (keyEvent->key() == Qt::Key_Home && !keyEvent->isAutoRepeat()
> Make Qt::Key_Home a define maybe? Oh and change it to Super_L already, it's going to be that on arale in the next image.
Not worth making it a define or static const or property as this is the
*only* place that uses it.
Done s/Key_Home/Key_Super_L.

>
>> + && !m_windowBeingTouched
>> + && m_windowLastTouchedTimer->elapsed() >= msecsWithoutTouches) {
>> + m_emitActivatedIfNoTouchesAroundTimer->start();
>> + }
>> +
>> + } else if (event->type() == QEvent::TouchBegin) {
>> +
>> + m_emitActivatedIfNoTouchesAroundTimer->stop();
>> + m_windowBeingTouched = true;
>> +
>> + } else if (event->type() == QEvent::TouchEnd) {
> Does this work with multiple touch points?

Yes. TouchBegin is emitted when the first touch point starts and
TouchEnd when the last remaining touch point ends.
> +private:
> + QPointer<QQuickWindow> m_filteredWindow;
> + bool m_windowBeingTouched;
> + UnityUtil::AbstractElapsedTimer *m_windowLastTouchedTimer;
> + UnityUtil::AbstractTimer *m_emitActivatedIfNoTouchesAroundTimer;
> Got a better name for this member? :)

No, do you? :)
It states precisely what it does. Anything shorter could end up being
ambiguous or misleading.

Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

W dniu 22.04.2015 o 12:51, Daniel d'Andrada pisze:
> On 21/04/15 11:01, Michał Sawicz wrote:
>> Any reason you're not consistent with file name case (homekeywatcher.* vs. ElapsedTimer.*)?
> Yes. The all-lowercase version was made be consistent with the naming of
> other files in this plugin. But then I revolted against this bad
> convention (can you name a single benefit of it?) and started doing
> CamelCase instead. Made them all CamelCase now.

Yeah, needs fixin' in trunk.

>> +private:
>> + QPointer<QQuickWindow> m_filteredWindow;
>> + bool m_windowBeingTouched;
>> + UnityUtil::AbstractElapsedTimer *m_windowLastTouchedTimer;
>> + UnityUtil::AbstractTimer *m_emitActivatedIfNoTouchesAroundTimer;
>> Got a better name for this member? :)
>
> No, do you? :)
> It states precisely what it does. Anything shorter could end up being
> ambiguous or misleading.

I was thinking m_activationTimer, it's not like the member name has to
explain everything it's used for. m_noTouchTimer maybe?

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 20/04/15 19:41, Josh Arenson wrote:
> Review: Needs Information
>
> We had discussed on IRC that pressing the home key should hide the launcher. Since this MP does _not_ do that, I was wondering if that was still the case.
Done.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 22/04/15 08:36, Michał Sawicz wrote:
>>> +private:
>>> >> + QPointer<QQuickWindow> m_filteredWindow;
>>> >> + bool m_windowBeingTouched;
>>> >> + UnityUtil::AbstractElapsedTimer *m_windowLastTouchedTimer;
>>> >> + UnityUtil::AbstractTimer *m_emitActivatedIfNoTouchesAroundTimer;
>>> >> Got a better name for this member? :)
>> >
>> > No, do you? :)
>> > It states precisely what it does. Anything shorter could end up being
>> > ambiguous or misleading.
> I was thinking m_activationTimer, it's not like the member name has to
> explain everything it's used for. m_noTouchTimer maybe?
Done.

Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Y
 * Did CI run pass? If not, please explain why.
Bug #1446846
 * Did you make sure that the branch does not contain spurious tags?
Y

review: Approve
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

During QA testing it occurred to us that a lazy bottom-swipe in apps is likely to bring dash in, disrupting your workflow. We need to refine that.

review: Needs Fixing
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

I think basically this additional testcase needs fixing:

=== modified file 'tests/plugins/Utils/homekeywatchertest.cpp'
--- tests/plugins/Utils/homekeywatchertest.cpp 2015-04-22 12:03:58 +0000
+++ tests/plugins/Utils/homekeywatchertest.cpp 2015-04-22 20:07:17 +0000
@@ -118,6 +118,7 @@
     QTest::newRow("touch followed by tap") << 10 << 50 << 2000 << 0;
     QTest::newRow("touch followed by long tap") << 10 << 500 << 2000 << 0;
     QTest::newRow("isolated tap") << 1000 << 50 << 1000 << 1;
+ QTest::newRow("isolated long press") << 1000 << 200 << 1000 << 0;
 }

 void HomeKeyWatcherTest::touchTapTouch()

Michael Zanetti (mzanetti) wrote :

I've been asked to review rev. 1738: Looks good to me. It disables longpress for the button but after an IRC discussion we agreed to go with that for now.

review: Approve (code)
Michał Sawicz (saviq) wrote :

Approving as per the superseded branch and above.

review: Approve
Michael Zanetti (mzanetti) wrote :

Tested this branch from silo. Feels good. Button feels like reacting quick, yet doesn't collide with bottom edge gestures of any speed.

review: Approve
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

> During QA testing it occurred to us that a lazy bottom-swipe in apps is likely to bring dash in, disrupting your workflow. We need to refine that.

Hmm... you sure this is a problem? I'm afraid the only way to make a lazy swipe starting from the home button to *not* activate the button would be adding even more delay to the button activation.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/Utils/CMakeLists.txt'
2--- plugins/Utils/CMakeLists.txt 2015-04-02 10:32:43 +0000
3+++ plugins/Utils/CMakeLists.txt 2015-04-23 12:59:59 +0000
4@@ -9,11 +9,13 @@
5
6 set(QMLPLUGIN_SRC
7 constants.cpp
8+ HomeKeyWatcher.cpp
9 inputwatcher.cpp
10 qlimitproxymodelqml.cpp
11 unitysortfilterproxymodelqml.cpp
12 relativetimeformatter.cpp
13 timeformatter.cpp
14+ Timer.cpp
15 unitymenumodelpaths.cpp
16 windowkeysfilter.cpp
17 easingcurve.cpp
18
19=== added file 'plugins/Utils/ElapsedTimer.h'
20--- plugins/Utils/ElapsedTimer.h 1970-01-01 00:00:00 +0000
21+++ plugins/Utils/ElapsedTimer.h 2015-04-23 12:59:59 +0000
22@@ -0,0 +1,51 @@
23+/*
24+ * Copyright (C) 2015 Canonical Ltd.
25+ *
26+ * This program is free software: you can redistribute it and/or modify it
27+ * under the terms of the GNU Lesser General Public License, as
28+ * published by the Free Software Foundation; either version 2.1 or 3.0
29+ * of the License.
30+ *
31+ * This program is distributed in the hope that it will be useful, but
32+ * WITHOUT ANY WARRANTY; without even the implied warranties of
33+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
34+ * PURPOSE. See the applicable version of the GNU Lesser General Public
35+ * License for more details.
36+ *
37+ * You should have received a copy of both the GNU Lesser General Public
38+ * License along with this program. If not, see <http://www.gnu.org/licenses/>
39+ */
40+
41+#ifndef UNITYUTIL_ELAPSEDTIMER_H
42+#define UNITYUTIL_ELAPSEDTIMER_H
43+
44+#include <QElapsedTimer>
45+
46+namespace UnityUtil {
47+
48+/**
49+ Interface for ElapsedTimer implementations
50+ */
51+class AbstractElapsedTimer {
52+public:
53+ virtual ~AbstractElapsedTimer() {}
54+ virtual void start() = 0;
55+ virtual qint64 msecsSinceReference() const = 0;
56+ virtual qint64 elapsed() const = 0;
57+};
58+
59+/**
60+ A QElapsedTimer wrapper
61+ */
62+class ElapsedTimer : public AbstractElapsedTimer {
63+public:
64+ void start() override { m_timer.start(); }
65+ qint64 msecsSinceReference() const override { return m_timer.msecsSinceReference(); }
66+ qint64 elapsed() const override { return m_timer.elapsed(); }
67+private:
68+ QElapsedTimer m_timer;
69+};
70+
71+} // namespace UnityUtil
72+
73+#endif // UNITYUTIL_ELAPSEDTIMER_H
74
75=== added file 'plugins/Utils/HomeKeyWatcher.cpp'
76--- plugins/Utils/HomeKeyWatcher.cpp 1970-01-01 00:00:00 +0000
77+++ plugins/Utils/HomeKeyWatcher.cpp 2015-04-23 12:59:59 +0000
78@@ -0,0 +1,116 @@
79+/*
80+ * Copyright (C) 2015 Canonical, Ltd.
81+ *
82+ * This program is free software; you can redistribute it and/or modify
83+ * it under the terms of the GNU General Public License as published by
84+ * the Free Software Foundation; version 3.
85+ *
86+ * This program is distributed in the hope that it will be useful,
87+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
88+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
89+ * GNU General Public License for more details.
90+ *
91+ * You should have received a copy of the GNU General Public License
92+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
93+ */
94+
95+#include "HomeKeyWatcher.h"
96+
97+#include <QQuickWindow>
98+
99+using namespace UnityUtil;
100+
101+HomeKeyWatcher::HomeKeyWatcher(QQuickItem *parent)
102+ : HomeKeyWatcher(new Timer, new ElapsedTimer, parent)
103+{
104+}
105+
106+HomeKeyWatcher::HomeKeyWatcher(UnityUtil::AbstractTimer *timer,
107+ UnityUtil::AbstractElapsedTimer *elapsedTimer,
108+ QQuickItem *parent)
109+ : QQuickItem(parent)
110+ , m_windowBeingTouched(false)
111+ , m_homeKeyPressed(false)
112+ , m_windowLastTouchedTimer(elapsedTimer)
113+ , m_activationTimer(timer)
114+{
115+ m_windowLastTouchedTimer->start();
116+
117+ connect(this, &QQuickItem::windowChanged,
118+ this, &HomeKeyWatcher::setupFilterOnWindow);
119+
120+ connect(m_activationTimer, &UnityUtil::AbstractTimer::timeout,
121+ this, &HomeKeyWatcher::emitActivatedIfNoTouchesAround);
122+ m_activationTimer->setInterval(msecsWithoutTouches);
123+}
124+
125+HomeKeyWatcher::~HomeKeyWatcher()
126+{
127+ delete m_windowLastTouchedTimer;
128+ delete m_activationTimer;
129+}
130+
131+bool HomeKeyWatcher::eventFilter(QObject *watched, QEvent *event)
132+{
133+ Q_ASSERT(!m_filteredWindow.isNull());
134+ Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
135+ Q_UNUSED(watched);
136+
137+ update(event);
138+
139+ // We're only monitoring, never filtering out events
140+ return false;
141+}
142+
143+void HomeKeyWatcher::update(QEvent *event)
144+{
145+ if (event->type() == QEvent::KeyPress) {
146+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
147+
148+ m_homeKeyPressed = true;
149+
150+ if (keyEvent->key() == Qt::Key_Super_L && !keyEvent->isAutoRepeat()
151+ && !m_windowBeingTouched
152+ && m_windowLastTouchedTimer->elapsed() >= msecsWithoutTouches) {
153+ m_activationTimer->start();
154+ }
155+
156+ } else if (event->type() == QEvent::KeyRelease) {
157+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
158+
159+ if (keyEvent->key() == Qt::Key_Super_L) {
160+ m_homeKeyPressed = false;
161+ }
162+
163+ } else if (event->type() == QEvent::TouchBegin) {
164+
165+ m_activationTimer->stop();
166+ m_windowBeingTouched = true;
167+
168+ } else if (event->type() == QEvent::TouchEnd) {
169+
170+ m_windowBeingTouched = false;
171+ m_windowLastTouchedTimer->start();
172+ }
173+}
174+
175+void HomeKeyWatcher::setupFilterOnWindow(QQuickWindow *window)
176+{
177+ if (!m_filteredWindow.isNull()) {
178+ m_filteredWindow->removeEventFilter(this);
179+ m_filteredWindow.clear();
180+ }
181+
182+ if (window) {
183+ window->installEventFilter(this);
184+ m_filteredWindow = window;
185+ }
186+}
187+
188+void HomeKeyWatcher::emitActivatedIfNoTouchesAround()
189+{
190+ if (!m_homeKeyPressed && !m_windowBeingTouched &&
191+ (m_windowLastTouchedTimer->elapsed() > msecsWithoutTouches)) {
192+ Q_EMIT activated();
193+ }
194+}
195
196=== added file 'plugins/Utils/HomeKeyWatcher.h'
197--- plugins/Utils/HomeKeyWatcher.h 1970-01-01 00:00:00 +0000
198+++ plugins/Utils/HomeKeyWatcher.h 2015-04-23 12:59:59 +0000
199@@ -0,0 +1,74 @@
200+/*
201+ * Copyright (C) 2015 Canonical, Ltd.
202+ *
203+ * This program is free software; you can redistribute it and/or modify
204+ * it under the terms of the GNU General Public License as published by
205+ * the Free Software Foundation; version 3.
206+ *
207+ * This program is distributed in the hope that it will be useful,
208+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
209+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
210+ * GNU General Public License for more details.
211+ *
212+ * You should have received a copy of the GNU General Public License
213+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
214+ */
215+
216+#ifndef UNITY_HOMEKEYWATCHER_H
217+#define UNITY_HOMEKEYWATCHER_H
218+
219+#include <QQuickItem>
220+#include <QPointer>
221+
222+#include "Timer.h"
223+#include "ElapsedTimer.h"
224+
225+/*
226+ Signals when the home key seems to be have been intentionally tapped.
227+
228+ It only says the home key has been activated if it has been tapped in isolation,
229+ that is, without being accompanied by touches on the screen. Home key taps that
230+ happen along with (or immediately after, or immediately before) touches on the
231+ screen are considered to have happened unintentionally and are thus ignored.
232+
233+ Rationale being that it's easy to accidentally hit the home key while performing
234+ a swipe from a screen edge, for instance. That's particularly the case when the
235+ home key is a capacitive key.
236+ */
237+class HomeKeyWatcher : public QQuickItem
238+{
239+ Q_OBJECT
240+public:
241+
242+ HomeKeyWatcher(QQuickItem *parent = 0);
243+
244+ // for testing
245+ HomeKeyWatcher(UnityUtil::AbstractTimer *timer,
246+ UnityUtil::AbstractElapsedTimer *elapsedTimer,
247+ QQuickItem *parent = 0);
248+
249+ virtual ~HomeKeyWatcher();
250+
251+ bool eventFilter(QObject *watched, QEvent *event);
252+
253+ void update(QEvent *event);
254+
255+ const qint64 msecsWithoutTouches = 150;
256+
257+Q_SIGNALS:
258+ // Emitted when the home key has been intentionally tapped
259+ void activated();
260+
261+private Q_SLOTS:
262+ void setupFilterOnWindow(QQuickWindow *window);
263+ void emitActivatedIfNoTouchesAround();
264+
265+private:
266+ QPointer<QQuickWindow> m_filteredWindow;
267+ bool m_windowBeingTouched;
268+ bool m_homeKeyPressed;
269+ UnityUtil::AbstractElapsedTimer *m_windowLastTouchedTimer;
270+ UnityUtil::AbstractTimer *m_activationTimer;
271+};
272+
273+#endif // UNITY_HOMEKEYWATCHER_H
274
275=== added file 'plugins/Utils/Timer.cpp'
276--- plugins/Utils/Timer.cpp 1970-01-01 00:00:00 +0000
277+++ plugins/Utils/Timer.cpp 2015-04-23 12:59:59 +0000
278@@ -0,0 +1,59 @@
279+/*
280+ * Copyright (C) 2015 Canonical, Ltd.
281+ *
282+ * This program is free software; you can redistribute it and/or modify
283+ * it under the terms of the GNU General Public License as published by
284+ * the Free Software Foundation; version 3.
285+ *
286+ * This program is distributed in the hope that it will be useful,
287+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
288+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
289+ * GNU General Public License for more details.
290+ *
291+ * You should have received a copy of the GNU General Public License
292+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
293+ */
294+
295+#include "Timer.h"
296+
297+namespace UnityUtil {
298+
299+Timer::Timer(QObject *parent) : AbstractTimer(parent)
300+{
301+ m_timer.setSingleShot(true);
302+ connect(&m_timer, &QTimer::timeout, this, &AbstractTimer::timeout);
303+}
304+
305+int Timer::interval() const
306+{
307+ return m_timer.interval();
308+}
309+
310+void Timer::setInterval(int msecs)
311+{
312+ m_timer.setInterval(msecs);
313+}
314+
315+void Timer::start()
316+{
317+ m_timer.start();
318+ AbstractTimer::start();
319+}
320+
321+void Timer::stop()
322+{
323+ m_timer.stop();
324+ AbstractTimer::stop();
325+}
326+
327+bool Timer::isSingleShot() const
328+{
329+ return m_timer.isSingleShot();
330+}
331+
332+void Timer::setSingleShot(bool value)
333+{
334+ m_timer.setSingleShot(value);
335+}
336+
337+} // namespace UnityUtil
338
339=== added file 'plugins/Utils/Timer.h'
340--- plugins/Utils/Timer.h 1970-01-01 00:00:00 +0000
341+++ plugins/Utils/Timer.h 2015-04-23 12:59:59 +0000
342@@ -0,0 +1,79 @@
343+/*
344+ * Copyright (C) 2015 Canonical, Ltd.
345+ *
346+ * This program is free software; you can redistribute it and/or modify
347+ * it under the terms of the GNU General Public License as published by
348+ * the Free Software Foundation; version 3.
349+ *
350+ * This program is distributed in the hope that it will be useful,
351+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
352+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
353+ * GNU General Public License for more details.
354+ *
355+ * You should have received a copy of the GNU General Public License
356+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
357+ */
358+
359+#ifndef UNITYUTIL_TIMER_H
360+#define UNITYUTIL_TIMER_H
361+
362+#include "ElapsedTimer.h"
363+
364+#include <QObject>
365+#include <QPointer>
366+#include <QTimer>
367+
368+namespace UnityUtil {
369+
370+/** Defines an interface for a Timer. Useful for tests. */
371+class AbstractTimer : public QObject
372+{
373+ Q_OBJECT
374+public:
375+ AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
376+ virtual int interval() const = 0;
377+ virtual void setInterval(int msecs) = 0;
378+ virtual void start() { m_isRunning = true; }
379+ virtual void stop() { m_isRunning = false; }
380+ bool isRunning() const { return m_isRunning; }
381+ virtual bool isSingleShot() const = 0;
382+ virtual void setSingleShot(bool value) = 0;
383+Q_SIGNALS:
384+ void timeout();
385+private:
386+ bool m_isRunning;
387+};
388+
389+/** A QTimer wrapper */
390+class Timer : public AbstractTimer
391+{
392+ Q_OBJECT
393+public:
394+ Timer(QObject *parent = nullptr);
395+
396+ int interval() const override;
397+ void setInterval(int msecs) override;
398+ void start() override;
399+ void stop() override;
400+ bool isSingleShot() const override;
401+ void setSingleShot(bool value) override;
402+private:
403+ QTimer m_timer;
404+};
405+
406+class AbstractTimerFactory
407+{
408+public:
409+ virtual ~AbstractTimerFactory() {}
410+ virtual AbstractTimer *create(QObject *parent = nullptr) = 0;
411+};
412+
413+class TimerFactory : public AbstractTimerFactory
414+{
415+public:
416+ AbstractTimer *create(QObject *parent = nullptr) override { return new Timer(parent); }
417+};
418+
419+} // namespace UnityUtil
420+
421+#endif // UNITYUTIL_TIMER_H
422
423=== modified file 'plugins/Utils/plugin.cpp'
424--- plugins/Utils/plugin.cpp 2015-04-07 11:50:48 +0000
425+++ plugins/Utils/plugin.cpp 2015-04-23 12:59:59 +0000
426@@ -1,5 +1,5 @@
427 /*
428- * Copyright (C) 2012 Canonical, Ltd.
429+ * Copyright (C) 2012-2015 Canonical, Ltd.
430 *
431 * This program is free software; you can redistribute it and/or modify
432 * it under the terms of the GNU General Public License as published by
433@@ -12,8 +12,6 @@
434 *
435 * You should have received a copy of the GNU General Public License
436 * along with this program. If not, see <http://www.gnu.org/licenses/>.
437- *
438- * Author: Michał Sawicz <michal.sawicz@canonical.com>
439 */
440
441 // Qt
442@@ -21,11 +19,11 @@
443 #include <QDBusConnection>
444 #include <QQmlContext>
445 #include <QtQuick/QQuickWindow>
446-#include <QDebug>
447 // self
448 #include "plugin.h"
449
450 // local
451+#include "HomeKeyWatcher.h"
452 #include "inputwatcher.h"
453 #include "qlimitproxymodelqml.h"
454 #include "unitysortfilterproxymodelqml.h"
455@@ -54,6 +52,7 @@
456 void UtilsPlugin::registerTypes(const char *uri)
457 {
458 Q_ASSERT(uri == QLatin1String("Utils"));
459+ qmlRegisterType<HomeKeyWatcher>(uri, 0, 1, "HomeKeyWatcher");
460 qmlRegisterType<QAbstractItemModel>();
461 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
462 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
463
464=== modified file 'qml/Shell.qml'
465--- qml/Shell.qml 2015-04-09 14:01:00 +0000
466+++ qml/Shell.qml 2015-04-23 12:59:59 +0000
467@@ -180,6 +180,10 @@
468 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
469 }
470
471+ HomeKeyWatcher {
472+ onActivated: { launcher.fadeOut(); shell.showHome(); }
473+ }
474+
475 Item {
476 id: stages
477 objectName: "stages"
478
479=== modified file 'tests/plugins/Utils/CMakeLists.txt'
480--- tests/plugins/Utils/CMakeLists.txt 2014-12-19 14:51:35 +0000
481+++ tests/plugins/Utils/CMakeLists.txt 2015-04-23 12:59:59 +0000
482@@ -25,6 +25,7 @@
483 qlimitproxymodeltest
484 unitysortfilterproxymodeltest
485 timeformattertest
486+ homekeywatchertest
487 )
488
489 # plain qml test
490
491=== added file 'tests/plugins/Utils/homekeywatchertest.cpp'
492--- tests/plugins/Utils/homekeywatchertest.cpp 1970-01-01 00:00:00 +0000
493+++ tests/plugins/Utils/homekeywatchertest.cpp 2015-04-23 12:59:59 +0000
494@@ -0,0 +1,323 @@
495+/*
496+ * Copyright (C) 2015 Canonical, Ltd.
497+ *
498+ * This program is free software; you can redistribute it and/or modify
499+ * it under the terms of the GNU General Public License as published by
500+ * the Free Software Foundation; version 3.
501+ *
502+ * This program is distributed in the hope that it will be useful,
503+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
504+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
505+ * GNU General Public License for more details.
506+ *
507+ * You should have received a copy of the GNU General Public License
508+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
509+ */
510+
511+#include "HomeKeyWatcher.h"
512+
513+#include <QTest>
514+#include <QSignalSpy>
515+
516+
517+namespace UnityUtil {
518+class FakeElapsedTimer : public AbstractElapsedTimer {
519+public:
520+ static qint64 msecsSinceEpoch;
521+
522+ FakeElapsedTimer() { start(); }
523+
524+ void start() override { m_msecsSinceReference = msecsSinceEpoch; }
525+ qint64 msecsSinceReference() const override { return m_msecsSinceReference; }
526+ qint64 elapsed() const override { return msecsSinceEpoch - m_msecsSinceReference; }
527+
528+private:
529+ qint64 m_msecsSinceReference;
530+};
531+qint64 FakeElapsedTimer::msecsSinceEpoch = 0;
532+
533+class FakeTimer : public AbstractTimer
534+{
535+ Q_OBJECT
536+public:
537+ FakeTimer(QObject *parent = nullptr);
538+
539+ void update();
540+ qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
541+
542+ int interval() const override;
543+ void setInterval(int msecs) override;
544+ void start() override;
545+ bool isSingleShot() const override;
546+ void setSingleShot(bool value) override;
547+private:
548+ int m_interval;
549+ bool m_singleShot;
550+ qint64 m_nextTimeoutTime;
551+};
552+
553+class FakeTimerFactory : public AbstractTimerFactory
554+{
555+public:
556+ FakeTimerFactory();
557+ virtual ~FakeTimerFactory();
558+
559+ void updateTime(qint64 targetTime);
560+
561+ AbstractTimer *create(QObject *parent = nullptr) override;
562+ QList<QPointer<FakeTimer>> timers;
563+};
564+} // namespace UnityUtil
565+
566+using namespace UnityUtil;
567+
568+class HomeKeyWatcherTest : public QObject {
569+ Q_OBJECT
570+private Q_SLOTS:
571+ void init(); // called right before each and every test function is executed
572+ void cleanup(); // called right after each and every test function is executed
573+
574+ void touchTapTouch_data();
575+ void touchTapTouch();
576+
577+ void tapWhileTouching();
578+
579+private:
580+ void passTime(qint64 timeSpanMs);
581+
582+ FakeTimerFactory *m_fakeTimerFactory;
583+};
584+
585+void HomeKeyWatcherTest::init()
586+{
587+ m_fakeTimerFactory = new FakeTimerFactory;
588+}
589+
590+void HomeKeyWatcherTest::cleanup()
591+{
592+ delete m_fakeTimerFactory;
593+ m_fakeTimerFactory = nullptr;
594+}
595+
596+
597+void HomeKeyWatcherTest::passTime(qint64 timeSpanMs)
598+{
599+ qint64 finalTime = FakeElapsedTimer::msecsSinceEpoch + timeSpanMs;
600+ m_fakeTimerFactory->updateTime(finalTime);
601+}
602+
603+void HomeKeyWatcherTest::touchTapTouch_data()
604+{
605+ QTest::addColumn<int>("silenceBeforeTap");
606+ QTest::addColumn<int>("tapDuration");
607+ QTest::addColumn<int>("silenceAfterTap");
608+ QTest::addColumn<int>("expectedActivatedCount");
609+
610+ QTest::newRow("tap followed by touch") << 2000 << 50 << 10 << 0;
611+ QTest::newRow("touch, tap and touch") << 10 << 50 << 10 << 0;
612+ QTest::newRow("touch followed by tap") << 10 << 50 << 2000 << 0;
613+ QTest::newRow("touch followed by long tap") << 10 << 500 << 2000 << 0;
614+ QTest::newRow("isolated tap") << 1000 << 50 << 1000 << 1;
615+ QTest::newRow("isolated long press") << 1000 << 200 << 1000 << 0;
616+}
617+
618+void HomeKeyWatcherTest::touchTapTouch()
619+{
620+ QFETCH(int, silenceBeforeTap);
621+ QFETCH(int, tapDuration);
622+ QFETCH(int, silenceAfterTap);
623+ QFETCH(int, expectedActivatedCount);
624+ HomeKeyWatcher homeKeyWatcher(m_fakeTimerFactory->create(), new FakeElapsedTimer);
625+ QSignalSpy activatedSpy(&homeKeyWatcher, SIGNAL(activated()));
626+ QVERIFY(activatedSpy.isValid());
627+
628+ {
629+ QTouchEvent touchEvent(QEvent::TouchBegin);
630+ homeKeyWatcher.update(&touchEvent);
631+ }
632+ passTime(100);
633+ {
634+ QTouchEvent touchEvent(QEvent::TouchUpdate);
635+ homeKeyWatcher.update(&touchEvent);
636+ }
637+ passTime(100);
638+ {
639+ QTouchEvent touchEvent(QEvent::TouchEnd);
640+ homeKeyWatcher.update(&touchEvent);
641+ }
642+
643+ passTime(silenceBeforeTap);
644+
645+ {
646+ QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
647+ homeKeyWatcher.update(&keyEvent);
648+ }
649+ int tapTime = 0;
650+ while (tapTime < tapDuration) {
651+ passTime(10);
652+ tapTime += 10;
653+ if (tapTime + 10 >= tapDuration) {
654+ QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
655+ homeKeyWatcher.update(&keyEvent);
656+ } else {
657+ QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier, QString(), true /*autorepeat*/);
658+ homeKeyWatcher.update(&keyEvent);
659+ }
660+ }
661+
662+ passTime(silenceAfterTap);
663+
664+ {
665+ QTouchEvent touchEvent(QEvent::TouchBegin);
666+ homeKeyWatcher.update(&touchEvent);
667+ }
668+ passTime(100);
669+ {
670+ QTouchEvent touchEvent(QEvent::TouchUpdate);
671+ homeKeyWatcher.update(&touchEvent);
672+ }
673+ passTime(100);
674+ {
675+ QTouchEvent touchEvent(QEvent::TouchEnd);
676+ homeKeyWatcher.update(&touchEvent);
677+ }
678+ passTime(1000);
679+
680+ QCOMPARE(activatedSpy.count(), expectedActivatedCount);
681+}
682+
683+void HomeKeyWatcherTest::tapWhileTouching()
684+{
685+ HomeKeyWatcher homeKeyWatcher(m_fakeTimerFactory->create(), new FakeElapsedTimer);
686+ QSignalSpy activatedSpy(&homeKeyWatcher, SIGNAL(activated()));
687+ QVERIFY(activatedSpy.isValid());
688+
689+ {
690+ QTouchEvent touchEvent(QEvent::TouchBegin);
691+ homeKeyWatcher.update(&touchEvent);
692+ }
693+ passTime(1000);
694+ {
695+ QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
696+ homeKeyWatcher.update(&keyEvent);
697+ }
698+ passTime(100);
699+ {
700+ QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
701+ homeKeyWatcher.update(&keyEvent);
702+ }
703+ passTime(1000);
704+ {
705+ QTouchEvent touchEvent(QEvent::TouchEnd);
706+ homeKeyWatcher.update(&touchEvent);
707+ }
708+ passTime(1000);
709+
710+
711+ QCOMPARE(activatedSpy.count(), 0);
712+}
713+
714+/////////////////////////////////// FakeTimer //////////////////////////////////
715+
716+FakeTimer::FakeTimer(QObject *parent)
717+ : UnityUtil::AbstractTimer(parent)
718+ , m_interval(0)
719+ , m_singleShot(true)
720+{
721+}
722+
723+void FakeTimer::update()
724+{
725+ if (!isRunning()) {
726+ return;
727+ }
728+
729+ if (m_nextTimeoutTime <= FakeElapsedTimer::msecsSinceEpoch) {
730+ if (isSingleShot()) {
731+ stop();
732+ } else {
733+ m_nextTimeoutTime += interval();
734+ }
735+ Q_EMIT timeout();
736+ }
737+}
738+
739+void FakeTimer::start()
740+{
741+ AbstractTimer::start();
742+ m_nextTimeoutTime = FakeElapsedTimer::msecsSinceEpoch + (qint64)interval();
743+}
744+
745+int FakeTimer::interval() const
746+{
747+ return m_interval;
748+}
749+
750+void FakeTimer::setInterval(int msecs)
751+{
752+ m_interval = msecs;
753+}
754+
755+bool FakeTimer::isSingleShot() const
756+{
757+ return m_singleShot;
758+}
759+
760+void FakeTimer::setSingleShot(bool value)
761+{
762+ m_singleShot = value;
763+}
764+
765+/////////////////////////////////// FakeTimerFactory //////////////////////////////////
766+
767+FakeTimerFactory::FakeTimerFactory()
768+{
769+}
770+
771+FakeTimerFactory::~FakeTimerFactory()
772+{
773+ for (int i = 0; i < timers.count(); ++i) {
774+ FakeTimer *timer = timers[i].data();
775+ if (timer) {
776+ delete timer;
777+ }
778+ }
779+}
780+
781+void FakeTimerFactory::updateTime(qint64 targetTime)
782+{
783+ qint64 minTimeoutTime = targetTime;
784+
785+ for (int i = 0; i < timers.count(); ++i) {
786+ FakeTimer *timer = timers[i].data();
787+ if (timer && timer->isRunning() && timer->nextTimeoutTime() < minTimeoutTime) {
788+ minTimeoutTime = timer->nextTimeoutTime();
789+ }
790+ }
791+
792+ FakeElapsedTimer::msecsSinceEpoch = minTimeoutTime;
793+
794+ for (int i = 0; i < timers.count(); ++i) {
795+ FakeTimer *timer = timers[i].data();
796+ if (timer) {
797+ timer->update();
798+ }
799+ }
800+
801+ if (FakeElapsedTimer::msecsSinceEpoch < targetTime) {
802+ updateTime(targetTime);
803+ }
804+}
805+
806+AbstractTimer *FakeTimerFactory::create(QObject *parent)
807+{
808+ FakeTimer *fakeTimer = new FakeTimer(parent);
809+
810+ timers.append(fakeTimer);
811+
812+ return fakeTimer;
813+}
814+
815+QTEST_GUILESS_MAIN(HomeKeyWatcherTest)
816+
817+#include "homekeywatchertest.moc"

Subscribers

People subscribed via source and target branches