Merge lp:~dandrader/unity8/homeKey into lp:unity8

Proposed by Daniel d'Andrada on 2015-04-17
Status: Superseded
Proposed branch: lp:~dandrader/unity8/homeKey
Merge into: lp:unity8
Diff against target: 804 lines (+699/-4)
10 files modified
plugins/Utils/CMakeLists.txt (+2/-0)
plugins/Utils/ElapsedTimer.h (+51/-0)
plugins/Utils/HomeKeyWatcher.cpp (+105/-0)
plugins/Utils/HomeKeyWatcher.h (+73/-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 (+322/-0)
To merge this branch: bzr merge lp:~dandrader/unity8/homeKey
Reviewer Review Type Date Requested Status
Michał Sawicz 2015-04-17 Needs Fixing on 2015-04-22
PS Jenkins bot continuous-integration 2015-04-17 Needs Fixing on 2015-04-22
Josh Arenson 2015-04-17 Needs Information on 2015-04-20
Review via email: mp+256658@code.launchpad.net

This proposal has been superseded by a proposal from 2015-04-23.

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 :

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 :

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 :

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 :

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 :

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 :

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 :

 * 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 :

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 :

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()

Daniel d'Andrada (dandrader) wrote :

> 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.

Unmerged revisions

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

Subscribers

People subscribed via source and target branches