Merge lp:~unity-team/unity8/rtm-14.09-staging into lp:unity8/rtm-14.09

Proposed by Michał Sawicz
Status: Merged
Approved by: kevin gunn
Approved revision: no longer in the source branch.
Merged at revision: 1397
Proposed branch: lp:~unity-team/unity8/rtm-14.09-staging
Merge into: lp:unity8/rtm-14.09
Diff against target: 4113 lines (+2365/-788)
51 files modified
debian/changelog (+37/-0)
debian/unity8-private.install (+1/-0)
libs/UbuntuGestures/DebugHelpers.cpp (+1/-1)
plugins/CMakeLists.txt (+1/-0)
plugins/ScreenGrabber/CMakeLists.txt (+13/-0)
plugins/ScreenGrabber/ScreenGrabber.qmltypes (+17/-0)
plugins/ScreenGrabber/plugin.cpp (+28/-0)
plugins/ScreenGrabber/plugin.h (+33/-0)
plugins/ScreenGrabber/qmldir (+3/-0)
plugins/ScreenGrabber/screengrabber.cpp (+94/-0)
plugins/ScreenGrabber/screengrabber.h (+42/-0)
plugins/Ubuntu/Gestures/TouchDispatcher.cpp (+43/-0)
plugins/Ubuntu/Gestures/TouchDispatcher.h (+3/-0)
plugins/Unity/Launcher/dbusinterface.cpp (+2/-2)
plugins/Unity/Launcher/desktopfilehandler.cpp (+2/-0)
po/unity8.pot (+51/-47)
po/update-unity-pot (+1/-0)
qml/Components/Dialogs.qml (+1/-1)
qml/Components/ScreenGrabber.qml (+72/-0)
qml/Components/VolumeKeyFilter.qml (+63/-0)
qml/Greeter/Greeter.qml (+1/-1)
qml/Greeter/Infographics.qml (+20/-15)
qml/Notifications/Notification.qml (+22/-3)
qml/Notifications/SwipeToAct.qml (+309/-0)
qml/Panel/IndicatorPage.qml (+12/-1)
qml/Panel/Indicators/MenuItemFactory.qml (+36/-8)
qml/Shell.qml (+15/-5)
tests/autopilot/unity8/shell/tests/test_notifications.py (+0/-51)
tests/mocks/QtMultimedia/QtMultimedia.qmltypes (+10/-0)
tests/mocks/QtMultimedia/audio.cpp (+10/-0)
tests/mocks/QtMultimedia/audio.h (+12/-0)
tests/mocks/Unity/Notifications/CMakeLists.txt (+15/-1)
tests/mocks/Unity/Notifications/MockActionModel.cpp (+72/-0)
tests/mocks/Unity/Notifications/MockActionModel.h (+51/-0)
tests/mocks/Unity/Notifications/MockNotificationTypes.cpp (+26/-0)
tests/mocks/Unity/Notifications/MockNotificationTypes.h (+36/-0)
tests/mocks/Unity/Notifications/notification.js (+0/-23)
tests/mocks/Unity/Notifications/plugin.cpp (+31/-0)
tests/mocks/Unity/Notifications/plugin.h (+35/-0)
tests/mocks/Unity/Notifications/qmldir (+2/-1)
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/plugins/Unity/Launcher/launchermodeltest.cpp (+2/-2)
tests/qmltests/CMakeLists.txt (+1/-0)
tests/qmltests/Greeter/tst_SingleGreeter.qml (+72/-29)
tests/qmltests/Notifications/tst_Notifications.qml (+595/-587)
tests/qmltests/Notifications/tst_SwipeToAct.qml (+276/-0)
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml (+17/-1)
tests/qmltests/Panel/tst_IndicatorPage.qml (+54/-9)
tests/qmltests/tst_ShellWithPin.qml (+58/-0)
To merge this branch: bzr merge lp:~unity-team/unity8/rtm-14.09-staging
Reviewer Review Type Date Requested Status
kevin gunn (community) Approve
Review via email: mp+240664@code.launchpad.net

Commit message

[ Albert Astals ]
* Fix i18n (LP: #1389165)

[ Michael Zanetti ]
* Fix DBusVariant conversion for launcher emblems (LP: #1387261)
* Fix desktop file encoding in launcher (LP: #1387083)

[ Alberto Aguirre ]
* Add audioRole to QtMultimedia mock
* Add a plugin to take screenshots on vol up + vol down

[ Nick Dedekind ]
* Reset current item selection on deleted. (LP: #1378462)
* Added timer to re-assert server value after indicator menu item
  activation. (LP: #1336715)

[ Mirco Müller ]
* Added dedicated swipe-to-act button for snap-decisions, which avoids
  accidental taps/button-presses. (LP: #1358343)

[ Michael Terry ]
* Don't lock phone if user tries to switch back to an active call.
  (LP: #1388156)

[ Daniel d'Andrada ]
* TouchDispatcher: synthesize MouseButtonDblClick events (LP: #1388359)

To post a comment you must log in.
Revision history for this message
kevin gunn (kgunn72) wrote :

deemed good to go through a lot of testing

review: Approve
1397. By Michael Terry

[ Albert Astals ]
* Fix i18n (LP: #1389165)

[ Michael Zanetti ]
* Fix DBusVariant conversion for launcher emblems (LP: #1387261)
* Fix desktop file encoding in launcher (LP: #1387083)

[ Alberto Aguirre ]
* Add audioRole to QtMultimedia mock
* Add a plugin to take screenshots on vol up + vol down

[ Nick Dedekind ]
* Reset current item selection on deleted. (LP: #1378462)
* Added timer to re-assert server value after indicator menu item
  activation. (LP: #1336715)

[ Mirco Müller ]
* Added dedicated swipe-to-act button for snap-decisions, which avoids
  accidental taps/button-presses. (LP: #1358343)

[ Michael Terry ]
* Don't lock phone if user tries to switch back to an active call.
  (LP: #1388156)

[ Daniel d'Andrada ]
* TouchDispatcher: synthesize MouseButtonDblClick events (LP: #1388359) Fixes: 1336715, 1358343, 1378462, 1387083, 1387261, 1388156, 1388359, 1389165

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2014-10-30 21:43:43 +0000
3+++ debian/changelog 2014-11-05 14:37:57 +0000
4@@ -1,3 +1,40 @@
5+unity8 (8.01.1-0ubuntu1) UNRELEASED; urgency=medium
6+
7+ [ Albert Astals ]
8+ * Fix i18n (LP: #1389165)
9+
10+ [ Michael Zanetti ]
11+ * Fix DBusVariant conversion for launcher emblems (LP: #1387261)
12+ * Fix desktop file encoding in launcher (LP: #1387083)
13+
14+ [ Alberto Aguirre ]
15+ * Add audioRole to QtMultimedia mock
16+ * Add a plugin to take screenshots on vol up + vol down
17+
18+ [ Nick Dedekind ]
19+ * Reset current item selection on deleted. (LP: #1378462)
20+ * Added timer to re-assert server value after indicator menu item
21+ activation. (LP: #1336715)
22+
23+ [ Mirco Müller ]
24+ * Added dedicated swipe-to-act button for snap-decisions, which avoids
25+ accidental taps/button-presses. (LP: #1358343)
26+
27+ [ Michael Terry ]
28+ * Don't lock phone if user tries to switch back to an active call.
29+ (LP: #1388156)
30+
31+ [ Daniel d'Andrada ]
32+ * TouchDispatcher: synthesize MouseButtonDblClick events (LP:
33+ #1388359)
34+
35+ [ Michał Sawicz ]
36+ * Bump the version above the version synced from vivid, otherwise
37+ train will generate a lower-than-archive version number.
38+ * fix positive/negative answer order on the new swipe notification
39+
40+ -- Michał Sawicz <michal.sawicz@canonical.com> Wed, 05 Nov 2014 02:37:24 +0100
41+
42 unity8 (8.01+15.04.20141030-0ubuntu1) vivid; urgency=medium
43
44 [ Michael Terry ]
45
46=== modified file 'debian/unity8-private.install'
47--- debian/unity8-private.install 2014-10-06 15:45:25 +0000
48+++ debian/unity8-private.install 2014-11-05 14:37:57 +0000
49@@ -6,6 +6,7 @@
50 usr/lib/*/unity8/qml/Lights
51 usr/lib/*/unity8/qml/Powerd
52 usr/lib/*/unity8/qml/SessionBroadcast
53+usr/lib/*/unity8/qml/ScreenGrabber
54 usr/lib/*/unity8/qml/Ubuntu
55 usr/lib/*/unity8/qml/Unity
56 usr/lib/*/unity8/qml/Utils
57
58=== modified file 'libs/UbuntuGestures/DebugHelpers.cpp'
59--- libs/UbuntuGestures/DebugHelpers.cpp 2014-10-17 11:01:53 +0000
60+++ libs/UbuntuGestures/DebugHelpers.cpp 2014-11-05 14:37:57 +0000
61@@ -83,7 +83,7 @@
62 message.append("MouseButtonDblClick ");
63 break;
64 case QEvent::MouseMove:
65- message.append("MouseButtonMove ");
66+ message.append("MouseMove ");
67 break;
68 default:
69 message.append("INVALID_MOUSE_EVENT_TYPE ");
70
71=== modified file 'plugins/CMakeLists.txt'
72--- plugins/CMakeLists.txt 2014-09-29 09:43:18 +0000
73+++ plugins/CMakeLists.txt 2014-11-05 14:37:57 +0000
74@@ -18,6 +18,7 @@
75 add_subdirectory(Dash)
76 add_subdirectory(Powerd)
77 add_subdirectory(SessionBroadcast)
78+add_subdirectory(ScreenGrabber)
79 add_subdirectory(Ubuntu)
80 add_subdirectory(Unity)
81 add_subdirectory(Utils)
82
83=== added directory 'plugins/ScreenGrabber'
84=== added file 'plugins/ScreenGrabber/CMakeLists.txt'
85--- plugins/ScreenGrabber/CMakeLists.txt 1970-01-01 00:00:00 +0000
86+++ plugins/ScreenGrabber/CMakeLists.txt 2014-11-05 14:37:57 +0000
87@@ -0,0 +1,13 @@
88+include_directories(
89+ ${CMAKE_CURRENT_BINARY_DIR}
90+)
91+
92+set(SCREENGRABBERSOURCES
93+ screengrabber.cpp
94+ plugin.cpp
95+)
96+
97+add_library(ScreenGrabber-qml MODULE ${SCREENGRABBERSOURCES})
98+qt5_use_modules(ScreenGrabber-qml Qml Gui Quick Concurrent)
99+
100+add_unity8_plugin(ScreenGrabber 0.1 ScreenGrabber TARGETS ScreenGrabber-qml)
101
102=== added file 'plugins/ScreenGrabber/ScreenGrabber.qmltypes'
103--- plugins/ScreenGrabber/ScreenGrabber.qmltypes 1970-01-01 00:00:00 +0000
104+++ plugins/ScreenGrabber/ScreenGrabber.qmltypes 2014-11-05 14:37:57 +0000
105@@ -0,0 +1,17 @@
106+import QtQuick.tooling 1.1
107+
108+// This file describes the plugin-supplied types contained in the library.
109+// It is used for QML tooling purposes only.
110+//
111+// This file was auto-generated by:
112+// 'qmlplugindump -nonrelocatable ScreenGrabber 0.1 plugins'
113+
114+Module {
115+ Component {
116+ name: "ScreenGrabber"
117+ prototype: "QObject"
118+ exports: ["ScreenGrabber/ScreenGrabber 0.1"]
119+ exportMetaObjectRevisions: [0]
120+ Method { name: "captureAndSave" }
121+ }
122+}
123
124=== added file 'plugins/ScreenGrabber/plugin.cpp'
125--- plugins/ScreenGrabber/plugin.cpp 1970-01-01 00:00:00 +0000
126+++ plugins/ScreenGrabber/plugin.cpp 2014-11-05 14:37:57 +0000
127@@ -0,0 +1,28 @@
128+/*
129+ * Copyright (C) 2014 Canonical, Ltd.
130+ *
131+ * This program is free software; you can redistribute it and/or modify
132+ * it under the terms of the GNU General Public License as published by
133+ * the Free Software Foundation; version 3.
134+ *
135+ * This program is distributed in the hope that it will be useful,
136+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
137+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138+ * GNU General Public License for more details.
139+ *
140+ * You should have received a copy of the GNU General Public License
141+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
142+ *
143+ * Authors: Alberto Aguirre <alberto.aguirre@canonical.com>
144+ */
145+
146+#include "plugin.h"
147+#include "screengrabber.h"
148+
149+#include <QtQml/qqml.h>
150+
151+void ScreenGrabberPlugin::registerTypes(const char *uri)
152+{
153+ Q_ASSERT(uri == QLatin1String("ScreenGrabber"));
154+ qmlRegisterType<ScreenGrabber>(uri, 0, 1, "ScreenGrabber");
155+}
156
157=== added file 'plugins/ScreenGrabber/plugin.h'
158--- plugins/ScreenGrabber/plugin.h 1970-01-01 00:00:00 +0000
159+++ plugins/ScreenGrabber/plugin.h 2014-11-05 14:37:57 +0000
160@@ -0,0 +1,33 @@
161+/*
162+ * Copyright (C) 2014 Canonical, Ltd.
163+ *
164+ * This program is free software; you can redistribute it and/or modify
165+ * it under the terms of the GNU General Public License as published by
166+ * the Free Software Foundation; version 3.
167+ *
168+ * This program is distributed in the hope that it will be useful,
169+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
170+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
171+ * GNU General Public License for more details.
172+ *
173+ * You should have received a copy of the GNU General Public License
174+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
175+ *
176+ * Authors: Alberto Aguirre <alberto.aguirre@canonical.com>
177+ */
178+
179+#ifndef SCREENGRABBER_PLUGIN_H
180+#define SCREENGRABBER_PLUGIN_H
181+
182+#include <QtQml/QQmlExtensionPlugin>
183+
184+class ScreenGrabberPlugin : public QQmlExtensionPlugin
185+{
186+ Q_OBJECT
187+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
188+
189+public:
190+ void registerTypes(const char *uri);
191+};
192+
193+#endif // SCREENGRABBER_PLUGIN_H
194
195=== added file 'plugins/ScreenGrabber/qmldir'
196--- plugins/ScreenGrabber/qmldir 1970-01-01 00:00:00 +0000
197+++ plugins/ScreenGrabber/qmldir 2014-11-05 14:37:57 +0000
198@@ -0,0 +1,3 @@
199+module ScreenGrabber
200+plugin ScreenGrabber-qml
201+typeinfo ScreenGrabber.qmltypes
202
203=== added file 'plugins/ScreenGrabber/screengrabber.cpp'
204--- plugins/ScreenGrabber/screengrabber.cpp 1970-01-01 00:00:00 +0000
205+++ plugins/ScreenGrabber/screengrabber.cpp 2014-11-05 14:37:57 +0000
206@@ -0,0 +1,94 @@
207+/*
208+ * Copyright (C) 2014 Canonical, Ltd.
209+ *
210+ * This program is free software; you can redistribute it and/or modify
211+ * it under the terms of the GNU General Public License as published by
212+ * the Free Software Foundation; version 3.
213+ *
214+ * This program is distributed in the hope that it will be useful,
215+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
216+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
217+ * GNU General Public License for more details.
218+ *
219+ * You should have received a copy of the GNU General Public License
220+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
221+ *
222+ * Authors: Alberto Aguirre <alberto.aguirre@canonical.com>
223+ */
224+
225+#include "screengrabber.h"
226+
227+#include <QDir>
228+#include <QDateTime>
229+#include <QStandardPaths>
230+#include <QtGui/QImage>
231+#include <QtGui/QGuiApplication>
232+#include <QtQuick/QQuickWindow>
233+#include <QtConcurrent/QtConcurrentRun>
234+
235+#include <QDebug>
236+
237+void saveScreenshot(QImage screenshot, QString filename, QString format, int quality)
238+{
239+ if (!screenshot.save(filename, format.toLatin1().data(), quality))
240+ qWarning() << "ScreenShotter: failed to save snapshot!";
241+}
242+
243+ScreenGrabber::ScreenGrabber(QObject *parent)
244+ : QObject(parent),
245+ screenshotQuality(0)
246+{
247+ QDir screenshotsDir(QStandardPaths::displayName(QStandardPaths::PicturesLocation));
248+ screenshotsDir.mkdir("Screenshots");
249+ screenshotsDir.cd("Screenshots");
250+ if (screenshotsDir.exists())
251+ {
252+ fileNamePrefix = screenshotsDir.absolutePath();
253+ fileNamePrefix.append("/screenshot");
254+ }
255+ else
256+ {
257+ qWarning() << "ScreenShotter: failed to create directory at: " << screenshotsDir.absolutePath();
258+ }
259+}
260+
261+void ScreenGrabber::captureAndSave()
262+{
263+ if (fileNamePrefix.isEmpty())
264+ {
265+ qWarning() << "ScreenShotter: no directory to save screenshot";
266+ return;
267+ }
268+
269+ const QWindowList windows = QGuiApplication::topLevelWindows();
270+ if (windows.empty())
271+ {
272+ qWarning() << "ScreenShotter: no top level windows found!";
273+ return;
274+ }
275+
276+ QQuickWindow *main_window = qobject_cast<QQuickWindow *>(windows[0]);
277+ if (!main_window)
278+ {
279+ qWarning() << "ScreenShotter: can only take screenshots of QQuickWindows";
280+ return;
281+ }
282+
283+ QImage screenshot = main_window->grabWindow();
284+ QtConcurrent::run(saveScreenshot, screenshot, makeFileName(), getFormat(), screenshotQuality);
285+}
286+
287+QString ScreenGrabber::makeFileName()
288+{
289+ QString fileName(fileNamePrefix);
290+ fileName.append(QDateTime::currentDateTime().toString("yyyymmdd_hhmmsszzz"));
291+ fileName.append(".");
292+ fileName.append(getFormat());
293+ return fileName;
294+}
295+
296+QString ScreenGrabber::getFormat()
297+{
298+ //TODO: This should be configurable (perhaps through gsettings?)
299+ return "png";
300+}
301
302=== added file 'plugins/ScreenGrabber/screengrabber.h'
303--- plugins/ScreenGrabber/screengrabber.h 1970-01-01 00:00:00 +0000
304+++ plugins/ScreenGrabber/screengrabber.h 2014-11-05 14:37:57 +0000
305@@ -0,0 +1,42 @@
306+/*
307+ * Copyright (C) 2014 Canonical, Ltd.
308+ *
309+ * This program is free software; you can redistribute it and/or modify
310+ * it under the terms of the GNU General Public License as published by
311+ * the Free Software Foundation; version 3.
312+ *
313+ * This program is distributed in the hope that it will be useful,
314+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
315+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
316+ * GNU General Public License for more details.
317+ *
318+ * You should have received a copy of the GNU General Public License
319+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
320+ *
321+ * Authors: Alberto Aguirre <alberto.aguirre@canonical.com>
322+ */
323+
324+#ifndef SNAPSHOTTER_H
325+#define SNAPSHOTTER_H
326+
327+#include <QObject>
328+#include <QString>
329+
330+class ScreenGrabber: public QObject
331+{
332+ Q_OBJECT
333+
334+public:
335+ explicit ScreenGrabber(QObject *parent = 0);
336+
337+public Q_SLOTS:
338+ void captureAndSave();
339+
340+private:
341+ QString makeFileName();
342+ QString getFormat();
343+ QString fileNamePrefix;
344+ int screenshotQuality;
345+};
346+
347+#endif
348
349=== modified file 'plugins/Ubuntu/Gestures/TouchDispatcher.cpp'
350--- plugins/Ubuntu/Gestures/TouchDispatcher.cpp 2014-10-20 14:24:30 +0000
351+++ plugins/Ubuntu/Gestures/TouchDispatcher.cpp 2014-11-05 14:37:57 +0000
352@@ -16,7 +16,9 @@
353
354 #include "TouchDispatcher.h"
355
356+#include <QGuiApplication>
357 #include <QScopedPointer>
358+#include <QStyleHints>
359
360 #pragma GCC diagnostic push
361 #pragma GCC diagnostic ignored "-pedantic"
362@@ -32,6 +34,7 @@
363 TouchDispatcher::TouchDispatcher()
364 : m_status(NoActiveTouch)
365 , m_touchMouseId(-1)
366+ , m_touchMousePressTimestamp(0)
367 {
368 }
369
370@@ -148,6 +151,18 @@
371 #endif
372 m_status = DeliveringMouseEvents;
373 m_touchMouseId = targetTouchPoints.at(0).id();
374+
375+ if (checkIfDoubleClicked(timestamp)) {
376+ QScopedPointer<QMouseEvent> doubleClickEvent(
377+ touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
378+ modifiers, false /* transformNeeded */));
379+ #if TOUCHDISPATCHER_DEBUG
380+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
381+ << "to" << m_targetItem.data();
382+ #endif
383+ QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
384+ }
385+
386 } else {
387 #if TOUCHDISPATCHER_DEBUG
388 qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
389@@ -322,3 +337,31 @@
390 //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
391 return me;
392 }
393+
394+/*
395+ Copied from qquickwindow.cpp which has:
396+ Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies)
397+ Under GPL 3.0 license.
398+*/
399+bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
400+{
401+ bool doubleClicked;
402+
403+ if (m_touchMousePressTimestamp == 0) {
404+ // just initialize the variable
405+ m_touchMousePressTimestamp = newPressEventTimestamp;
406+ doubleClicked = false;
407+ } else {
408+ ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp;
409+ ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->
410+ mouseDoubleClickInterval());
411+ doubleClicked = timeBetweenPresses < doubleClickInterval;
412+ if (doubleClicked) {
413+ m_touchMousePressTimestamp = 0;
414+ } else {
415+ m_touchMousePressTimestamp = newPressEventTimestamp;
416+ }
417+ }
418+
419+ return doubleClicked;
420+}
421
422=== modified file 'plugins/Ubuntu/Gestures/TouchDispatcher.h'
423--- plugins/Ubuntu/Gestures/TouchDispatcher.h 2014-10-17 11:01:53 +0000
424+++ plugins/Ubuntu/Gestures/TouchDispatcher.h 2014-11-05 14:37:57 +0000
425@@ -71,6 +71,8 @@
426 QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::TouchPoint &p,
427 ulong timestamp, Qt::KeyboardModifiers modifiers, bool transformNeeded = true);
428
429+ bool checkIfDoubleClicked(ulong newPressEventTimestamp);
430+
431 QPointer<QQuickItem> m_targetItem;
432
433 enum {
434@@ -81,6 +83,7 @@
435 } m_status;
436
437 int m_touchMouseId;
438+ ulong m_touchMousePressTimestamp;
439 };
440
441 #endif // UBUNTU_TOUCH_DISPATCHER_H
442
443=== modified file 'plugins/Unity/Launcher/dbusinterface.cpp'
444--- plugins/Unity/Launcher/dbusinterface.cpp 2014-10-09 21:29:00 +0000
445+++ plugins/Unity/Launcher/dbusinterface.cpp 2014-11-05 14:37:57 +0000
446@@ -171,13 +171,13 @@
447 }
448 } else if (message.member() == "Set") {
449 if (message.arguments()[1].toString() == "count") {
450- int newCount = message.arguments()[2].toInt();
451+ int newCount = message.arguments()[2].value<QDBusVariant>().variant().toInt();
452 if (!item || newCount != item->count()) {
453 Q_EMIT countChanged(appid, newCount);
454 notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "count", QVariant(newCount));
455 }
456 } else if (message.arguments()[1].toString() == "countVisible") {
457- bool newVisible = message.arguments()[2].toBool();
458+ bool newVisible = message.arguments()[2].value<QDBusVariant>().variant().toBool();
459 if (!item || newVisible != item->countVisible()) {
460 Q_EMIT countVisibleChanged(appid, newVisible);
461 notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "countVisible", newVisible);
462
463=== modified file 'plugins/Unity/Launcher/desktopfilehandler.cpp'
464--- plugins/Unity/Launcher/desktopfilehandler.cpp 2014-09-30 16:48:28 +0000
465+++ plugins/Unity/Launcher/desktopfilehandler.cpp 2014-11-05 14:37:57 +0000
466@@ -106,6 +106,7 @@
467 }
468
469 QSettings settings(m_filename, QSettings::IniFormat);
470+ settings.setIniCodec("UTF-8");
471 settings.beginGroup("Desktop Entry");
472
473 // First try to find Name[xx_YY] and Name[xx] in .desktop file
474@@ -138,6 +139,7 @@
475 }
476
477 QSettings settings(m_filename, QSettings::IniFormat);
478+ settings.setIniCodec("UTF-8");
479 settings.beginGroup("Desktop Entry");
480 QString iconString = settings.value("Icon").toString();
481 QString pathString = settings.value("Path").toString();
482
483=== modified file 'po/unity8.pot'
484--- po/unity8.pot 2014-10-07 08:41:28 +0000
485+++ po/unity8.pot 2014-11-05 14:37:57 +0000
486@@ -8,7 +8,7 @@
487 msgstr ""
488 "Project-Id-Version: unity8\n"
489 "Report-Msgid-Bugs-To: \n"
490-"POT-Creation-Date: 2014-10-07 11:37+0300\n"
491+"POT-Creation-Date: 2014-10-31 10:36+0100\n"
492 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
493 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
494 "Language-Team: LANGUAGE <LL@li.org>\n"
495@@ -50,59 +50,59 @@
496 msgstr[0] ""
497 msgstr[1] ""
498
499-#: qml/Components/Dialogs.qml:70
500+#: qml/Components/Dialogs.qml:74
501 msgid "Log out"
502 msgstr ""
503
504-#: qml/Components/Dialogs.qml:71
505+#: qml/Components/Dialogs.qml:75
506 msgid "Are you sure you want to log out?"
507 msgstr ""
508
509-#: qml/Components/Dialogs.qml:73 qml/Components/Dialogs.qml:97
510-#: qml/Components/Dialogs.qml:122
511+#: qml/Components/Dialogs.qml:77 qml/Components/Dialogs.qml:101
512+#: qml/Components/Dialogs.qml:126
513 msgid "No"
514 msgstr ""
515
516-#: qml/Components/Dialogs.qml:80 qml/Components/Dialogs.qml:104
517-#: qml/Components/Dialogs.qml:129
518+#: qml/Components/Dialogs.qml:84 qml/Components/Dialogs.qml:108
519+#: qml/Components/Dialogs.qml:133
520 msgid "Yes"
521 msgstr ""
522
523-#: qml/Components/Dialogs.qml:94
524+#: qml/Components/Dialogs.qml:98
525 msgid "Shut down"
526 msgstr ""
527
528-#: qml/Components/Dialogs.qml:95
529+#: qml/Components/Dialogs.qml:99
530 msgid "Are you sure you want to shut down?"
531 msgstr ""
532
533-#: qml/Components/Dialogs.qml:119
534+#: qml/Components/Dialogs.qml:123
535 msgid "Reboot"
536 msgstr ""
537
538-#: qml/Components/Dialogs.qml:120
539+#: qml/Components/Dialogs.qml:124
540 msgid "Are you sure you want to reboot?"
541 msgstr ""
542
543-#: qml/Components/Dialogs.qml:144
544+#: qml/Components/Dialogs.qml:148
545 msgid "Power"
546 msgstr ""
547
548-#: qml/Components/Dialogs.qml:145
549+#: qml/Components/Dialogs.qml:149
550 msgid ""
551 "Are you sure you would like\n"
552 "to power off?"
553 msgstr ""
554
555-#: qml/Components/Dialogs.qml:147
556+#: qml/Components/Dialogs.qml:151
557 msgid "Power off"
558 msgstr ""
559
560-#: qml/Components/Dialogs.qml:157
561+#: qml/Components/Dialogs.qml:161
562 msgid "Restart"
563 msgstr ""
564
565-#: qml/Components/Dialogs.qml:167
566+#: qml/Components/Dialogs.qml:171
567 msgid "Cancel"
568 msgstr ""
569
570@@ -134,29 +134,33 @@
571 msgid "Swipe up again to close the settings screen"
572 msgstr ""
573
574-#: qml/Components/EdgeDemo.qml:198
575+#: qml/Components/EdgeDemo.qml:201
576 msgid "Left edge"
577 msgstr ""
578
579-#: qml/Components/EdgeDemo.qml:199
580+#: qml/Components/EdgeDemo.qml:202
581 msgid "Swipe from the left to reveal the launcher for quick access to apps"
582 msgstr ""
583
584-#: qml/Components/EdgeDemo.qml:226
585+#: qml/Components/EdgeDemo.qml:229
586 msgid "Well done"
587 msgstr ""
588
589-#: qml/Components/EdgeDemo.qml:227
590+#: qml/Components/EdgeDemo.qml:230
591 msgid ""
592 "You have now mastered the edge gestures and can start using the "
593 "phone<br><br>Tap on the screen to start"
594 msgstr ""
595
596-#: qml/Components/Lockscreen.qml:220
597+#: qml/Components/Lockscreen.qml:223
598+msgid "Return to Call"
599+msgstr ""
600+
601+#: qml/Components/Lockscreen.qml:223
602 msgid "Emergency Call"
603 msgstr ""
604
605-#: qml/Components/Lockscreen.qml:252
606+#: qml/Components/Lockscreen.qml:255
607 msgid "OK"
608 msgstr ""
609
610@@ -213,15 +217,15 @@
611 msgid "Release to refresh…"
612 msgstr ""
613
614-#: qml/Dash/ScopesOverview.qml:206
615+#: qml/Dash/ScopesOverview.qml:215
616 msgid "Manage Scopes"
617 msgstr ""
618
619-#: qml/Dash/ScopesOverview.qml:428
620+#: qml/Dash/ScopesOverview.qml:437
621 msgid "Done"
622 msgstr ""
623
624-#: qml/Dash/ScopesOverview.qml:454
625+#: qml/Dash/ScopesOverview.qml:463
626 msgid "Store"
627 msgstr ""
628
629@@ -233,7 +237,7 @@
630 msgid "All"
631 msgstr ""
632
633-#: qml/Greeter/Greeter.qml:157
634+#: qml/Greeter/Greeter.qml:174
635 msgid "Unlock"
636 msgstr ""
637
638@@ -261,39 +265,39 @@
639 msgid "Show password"
640 msgstr ""
641
642-#: qml/Panel/ActiveCallHint.qml:77
643+#: qml/Panel/ActiveCallHint.qml:79
644 msgid "Tap to return to call..."
645 msgstr ""
646
647-#: qml/Panel/ActiveCallHint.qml:90
648+#: qml/Panel/ActiveCallHint.qml:92
649 msgid "Conference"
650 msgstr ""
651
652-#: qml/Panel/Indicators/MenuItemFactory.qml:600
653+#: qml/Panel/Indicators/MenuItemFactory.qml:615
654 msgid "Nothing is playing"
655 msgstr ""
656
657-#: qml/Panel/Indicators/MenuItemFactory.qml:748
658+#: qml/Panel/Indicators/MenuItemFactory.qml:763
659 msgid "In queue…"
660 msgstr ""
661
662-#: qml/Panel/Indicators/MenuItemFactory.qml:752
663+#: qml/Panel/Indicators/MenuItemFactory.qml:767
664 msgid "Downloading"
665 msgstr ""
666
667-#: qml/Panel/Indicators/MenuItemFactory.qml:754
668+#: qml/Panel/Indicators/MenuItemFactory.qml:769
669 msgid "Paused, tap to resume"
670 msgstr ""
671
672-#: qml/Panel/Indicators/MenuItemFactory.qml:756
673+#: qml/Panel/Indicators/MenuItemFactory.qml:771
674 msgid "Canceled"
675 msgstr ""
676
677-#: qml/Panel/Indicators/MenuItemFactory.qml:758
678+#: qml/Panel/Indicators/MenuItemFactory.qml:773
679 msgid "Finished"
680 msgstr ""
681
682-#: qml/Panel/Indicators/MenuItemFactory.qml:760
683+#: qml/Panel/Indicators/MenuItemFactory.qml:775
684 msgid "Failed, tap to retry"
685 msgstr ""
686
687@@ -305,55 +309,55 @@
688 msgid "Roaming"
689 msgstr ""
690
691-#: qml/Shell.qml:346
692+#: qml/Shell.qml:381
693 msgid "Enter passphrase"
694 msgstr ""
695
696-#: qml/Shell.qml:347
697+#: qml/Shell.qml:382
698 msgid "Sorry, incorrect passphrase"
699 msgstr ""
700
701-#: qml/Shell.qml:348
702+#: qml/Shell.qml:383
703 msgid "Please re-enter"
704 msgstr ""
705
706-#: qml/Shell.qml:350
707+#: qml/Shell.qml:385
708 msgid "Enter passcode"
709 msgstr ""
710
711-#: qml/Shell.qml:351
712+#: qml/Shell.qml:386
713 msgid "Sorry, incorrect passcode"
714 msgstr ""
715
716-#: qml/Shell.qml:354
717+#: qml/Shell.qml:389
718 #, qt-format
719 msgid "Enter %1"
720 msgstr ""
721
722-#: qml/Shell.qml:355
723+#: qml/Shell.qml:390
724 #, qt-format
725 msgid "Sorry, incorrect %1"
726 msgstr ""
727
728-#: qml/Shell.qml:390
729+#: qml/Shell.qml:431
730 msgid "Sorry, incorrect passphrase."
731 msgstr ""
732
733-#: qml/Shell.qml:391
734+#: qml/Shell.qml:432
735 msgid "Sorry, incorrect passcode."
736 msgstr ""
737
738-#: qml/Shell.qml:392
739+#: qml/Shell.qml:433
740 msgid "This will be your last attempt."
741 msgstr ""
742
743-#: qml/Shell.qml:394
744+#: qml/Shell.qml:435
745 msgid ""
746 "If passphrase is entered incorrectly, your phone will conduct a factory "
747 "reset and all personal data will be deleted."
748 msgstr ""
749
750-#: qml/Shell.qml:395
751+#: qml/Shell.qml:436
752 msgid ""
753 "If passcode is entered incorrectly, your phone will conduct a factory reset "
754 "and all personal data will be deleted."
755
756=== modified file 'po/update-unity-pot'
757--- po/update-unity-pot 2014-07-07 11:15:37 +0000
758+++ po/update-unity-pot 2014-11-05 14:37:57 +0000
759@@ -21,6 +21,7 @@
760 --add-comments=TRANSLATORS \
761 --keyword=tr \
762 --keyword=tr:1,2 \
763+ --keyword=dtr:2 \
764 --package-name="unity8" \
765 --copyright-holder="Canonical Ltd." \
766 --from-code="UTF-8"
767
768=== modified file 'qml/Components/Dialogs.qml'
769--- qml/Components/Dialogs.qml 2014-10-15 20:35:42 +0000
770+++ qml/Components/Dialogs.qml 2014-11-05 14:37:57 +0000
771@@ -26,7 +26,7 @@
772
773 // Explicitly use the right domain for this widget because it might be used
774 // in other applications like the welcome wizard.
775- property string domain: "unity8"
776+ readonly property string domain: "unity8"
777
778 function onPowerKeyPressed() {
779 // FIXME: event.isAutoRepeat is always false on Nexus 4.
780
781=== added file 'qml/Components/ScreenGrabber.qml'
782--- qml/Components/ScreenGrabber.qml 1970-01-01 00:00:00 +0000
783+++ qml/Components/ScreenGrabber.qml 2014-11-05 14:37:57 +0000
784@@ -0,0 +1,72 @@
785+/*
786+ * Copyright (C) 2014 Canonical, Ltd.
787+ *
788+ * This program is free software; you can redistribute it and/or modify
789+ * it under the terms of the GNU General Public License as published by
790+ * the Free Software Foundation; version 3.
791+ *
792+ * This program is distributed in the hope that it will be useful,
793+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
794+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
795+ * GNU General Public License for more details.
796+ *
797+ * You should have received a copy of the GNU General Public License
798+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
799+ */
800+
801+import QtQuick 2.0
802+import QtMultimedia 5.0
803+import ScreenGrabber 0.1
804+
805+Rectangle {
806+ id: root
807+ enabled: true
808+ visible: false
809+ color: "white"
810+ anchors.fill: parent
811+ opacity: 0.0
812+
813+ ScreenGrabber {
814+ id: screenGrabber
815+ objectName: "screenGrabber"
816+ }
817+
818+ Audio {
819+ id: shutterSound
820+ audioRole: MediaPlayer.alert
821+ source: "/system/media/audio/ui/camera_click.ogg"
822+ }
823+
824+ function capture() {
825+ if (!enabled)
826+ return;
827+
828+ visible = true;
829+ shutterSound.stop();
830+ shutterSound.play();
831+ fadeIn.start();
832+ }
833+
834+ NumberAnimation on opacity {
835+ id: fadeIn
836+ from: 0.0
837+ to: 1.0
838+ onStopped: {
839+ if (visible) {
840+ fadeOut.start();
841+ }
842+ }
843+ }
844+
845+ NumberAnimation on opacity {
846+ id: fadeOut
847+ from: 1.0
848+ to: 0.0
849+ onStopped: {
850+ if (visible) {
851+ screenGrabber.captureAndSave();
852+ visible = false;
853+ }
854+ }
855+ }
856+}
857
858=== added file 'qml/Components/VolumeKeyFilter.qml'
859--- qml/Components/VolumeKeyFilter.qml 1970-01-01 00:00:00 +0000
860+++ qml/Components/VolumeKeyFilter.qml 2014-11-05 14:37:57 +0000
861@@ -0,0 +1,63 @@
862+/*
863+ * Copyright (C) 2014 Canonical, Ltd.
864+ *
865+ * This program is free software; you can redistribute it and/or modify
866+ * it under the terms of the GNU General Public License as published by
867+ * the Free Software Foundation; version 3.
868+ *
869+ * This program is distributed in the hope that it will be useful,
870+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
871+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
872+ * GNU General Public License for more details.
873+ *
874+ * You should have received a copy of the GNU General Public License
875+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
876+ */
877+
878+import QtQuick 2.0
879+/*!
880+ \brief A filter for volume keys
881+
882+A filter which treats volume keys as single tri-state key with the states:
883+VolumeUp Pressed, VolumeDown Pressed or Volume Up+Down pressed
884+*/
885+QtObject {
886+ id: root
887+
888+ signal volumeUpPressed()
889+ signal volumeDownPressed()
890+ signal bothVolumeKeysPressed()
891+
892+ property bool volumeUpKeyPressed: false
893+ property bool volumeDownKeyPressed: false
894+ property bool aVolumeKeyWasReleased: true
895+
896+ function onKeyPressed(key) {
897+ if (key == Qt.Key_VolumeUp)
898+ volumeUpKeyPressed = true;
899+ else if (key == Qt.Key_VolumeDown)
900+ volumeDownKeyPressed = true;
901+
902+ if (volumeDownKeyPressed && volumeUpKeyPressed) {
903+ //avoids sending a signal repeatedly if both keys are held
904+ //instead one of the keys must have been previously released
905+ if (aVolumeKeyWasReleased)
906+ bothVolumeKeysPressed();
907+ aVolumeKeyWasReleased = false;
908+ } else if (volumeDownKeyPressed) {
909+ volumeDownPressed();
910+ } else if (volumeUpKeyPressed) {
911+ volumeUpPressed();
912+ }
913+ }
914+
915+ function onKeyReleased(key) {
916+ if (key == Qt.Key_VolumeUp) {
917+ volumeUpKeyPressed = false;
918+ aVolumeKeyWasReleased = true;
919+ } else if (key == Qt.Key_VolumeDown) {
920+ volumeDownKeyPressed = false;
921+ aVolumeKeyWasReleased = true;
922+ }
923+ }
924+}
925
926=== modified file 'qml/Greeter/Greeter.qml'
927--- qml/Greeter/Greeter.qml 2014-10-17 11:01:53 +0000
928+++ qml/Greeter/Greeter.qml 2014-11-05 14:37:57 +0000
929@@ -132,7 +132,7 @@
930 TouchGate {
931 targetItem: dragHandle
932 anchors.fill: targetItem
933- enabled: targetITem.enabled
934+ enabled: targetItem.enabled
935 }
936
937 Loader {
938
939=== modified file 'qml/Greeter/Infographics.qml'
940--- qml/Greeter/Infographics.qml 2014-07-29 13:47:33 +0000
941+++ qml/Greeter/Infographics.qml 2014-11-05 14:37:57 +0000
942@@ -26,8 +26,15 @@
943
944 property int animDuration: 10
945
946- property bool __useDotAnimation: true
947- property int __circleModifier: __useDotAnimation ? 1 : 2
948+ QtObject {
949+ id: d
950+ objectName: "infographicPrivate"
951+ property bool useDotAnimation: true
952+ property int circleModifier: useDotAnimation ? 1 : 2
953+ property bool animating: dotHideAnimTimer.running
954+ || dotShowAnimTimer.running
955+ || circleChangeAnimTimer.running
956+ }
957
958 Connections {
959 target: model
960@@ -46,7 +53,7 @@
961 dotHideAnimTimer.stop()
962 notification.hideAnim.stop()
963
964- if (__useDotAnimation) {
965+ if (d.useDotAnimation) {
966 dotShowAnimTimer.startFromBeginning()
967 }
968 notification.showAnim.start()
969@@ -57,7 +64,7 @@
970 circleChangeAnimTimer.stop()
971 notification.showAnim.stop()
972
973- if (__useDotAnimation) {
974+ if (d.useDotAnimation) {
975 dotHideAnimTimer.startFromBeginning()
976 } else {
977 circleChangeAnimTimer.startFromBeginning()
978@@ -148,21 +155,21 @@
979 property: "opacity"
980 to: pastCircle.circleOpacity
981 easing.type: Easing.OutCurve
982- duration: circleChangeAnimTimer.interval * __circleModifier
983+ duration: circleChangeAnimTimer.interval * d.circleModifier
984 }
985 PropertyAnimation {
986 target: pastCircle
987 property: "scale"
988 to: modelData
989 easing.type: Easing.OutCurve
990- duration: circleChangeAnimTimer.interval * __circleModifier
991+ duration: circleChangeAnimTimer.interval * d.circleModifier
992 }
993 ColorAnimation {
994 target: pastCircle
995 property: "color"
996 to: Gradient.threeColorByIndex(index, count, infographic.model.secondColor)
997 easing.type: Easing.OutCurve
998- duration: circleChangeAnimTimer.interval * __circleModifier
999+ duration: circleChangeAnimTimer.interval * d.circleModifier
1000 }
1001 }
1002 }
1003@@ -209,21 +216,21 @@
1004 property: "opacity"
1005 to: presentCircle.circleOpacity
1006 easing.type: Easing.OutCurve
1007- duration: circleChangeAnimTimer.interval * __circleModifier
1008+ duration: circleChangeAnimTimer.interval * d.circleModifier
1009 }
1010 PropertyAnimation {
1011 target: presentCircle
1012 property: "scale"
1013 to: modelData
1014 easing.type: Easing.OutCurve
1015- duration: circleChangeAnimTimer.interval * __circleModifier
1016+ duration: circleChangeAnimTimer.interval * d.circleModifier
1017 }
1018 ColorAnimation {
1019 target: presentCircle
1020 property: "color"
1021 to: Gradient.threeColorByIndex(index, infographic.model.currentDay, infographic.model.firstColor)
1022 easing.type: Easing.OutCurve
1023- duration: circleChangeAnimTimer.interval * __circleModifier
1024+ duration: circleChangeAnimTimer.interval * d.circleModifier
1025 }
1026 }
1027 }
1028@@ -385,7 +392,7 @@
1029 from: notification.baseOpacity
1030 to: 0.0
1031 duration: notification.duration * dots.count
1032- onStopped: if (!__useDotAnimation) infographic.model.readyForDataChange()
1033+ onStopped: if (!d.useDotAnimation) infographic.model.readyForDataChange()
1034 }
1035 }
1036 }
1037@@ -394,10 +401,8 @@
1038 anchors.fill: dataCircle
1039
1040 onDoubleClicked: {
1041- if (!dotHideAnimTimer.running &&
1042- !dotShowAnimTimer.running &&
1043- !circleChangeAnimTimer.running) {
1044- __useDotAnimation = false
1045+ if (!d.animating) {
1046+ d.useDotAnimation = false
1047 infographic.model.nextDataSource()
1048 }
1049 }
1050
1051=== modified file 'qml/Notifications/Notification.qml'
1052--- qml/Notifications/Notification.qml 2014-10-30 21:42:32 +0000
1053+++ qml/Notifications/Notification.qml 2014-11-05 14:37:57 +0000
1054@@ -398,7 +398,7 @@
1055
1056 spacing: contentSpacing
1057
1058- visible: notification.type == Notification.SnapDecision && oneOverTwoRepeaterTop.count == 3
1059+ visible: notification.type === Notification.SnapDecision && oneOverTwoRepeaterTop.count === 3
1060
1061 Repeater {
1062 id: oneOverTwoRepeaterTop
1063@@ -468,22 +468,41 @@
1064 spacing: units.gu(2)
1065 layoutDirection: Qt.RightToLeft
1066
1067+ Loader {
1068+ id: notifySwipeButtonLoader
1069+ active: notification.hints["x-canonical-snap-decisions-swipe"] === "true"
1070+
1071+ sourceComponent: SwipeToAct {
1072+ objectName: "notify_swipe_button"
1073+ width: buttonRow.width
1074+ leftIconName: "call-end"
1075+ rightIconName: "call-start"
1076+ onRightTriggered: {
1077+ notification.notification.invokeAction(notification.actions.data(0, ActionModel.RoleActionId))
1078+ }
1079+
1080+ onLeftTriggered: {
1081+ notification.notification.invokeAction(notification.actions.data(1, ActionModel.RoleActionId))
1082+ }
1083+ }
1084+ }
1085+
1086 Repeater {
1087 id: actionRepeater
1088-
1089 model: notification.actions
1090 delegate: Loader {
1091 id: loader
1092
1093 property string actionId: id
1094 property string actionLabel: label
1095+ active: !notifySwipeButtonLoader.active
1096
1097 Component {
1098 id: actionButton
1099
1100 Button {
1101 objectName: "notify_button" + index
1102- width: buttonRow.width / 2 - spacing*2
1103+ width: buttonRow.width / 2 - spacing * 2
1104 text: loader.actionLabel
1105 color: {
1106 var result = sdDarkGrey;
1107
1108=== added file 'qml/Notifications/SwipeToAct.qml'
1109--- qml/Notifications/SwipeToAct.qml 1970-01-01 00:00:00 +0000
1110+++ qml/Notifications/SwipeToAct.qml 2014-11-05 14:37:57 +0000
1111@@ -0,0 +1,309 @@
1112+/*
1113+ * Copyright (C) 2014 Canonical, Ltd.
1114+ *
1115+ * This program is free software; you can redistribute it and/or modify
1116+ * it under the terms of the GNU General Public License as published by
1117+ * the Free Software Foundation; version 3.
1118+ *
1119+ * This program is distributed in the hope that it will be useful,
1120+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1121+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1122+ * GNU General Public License for more details.
1123+ *
1124+ * You should have received a copy of the GNU General Public License
1125+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1126+ */
1127+
1128+import QtQuick 2.3
1129+import Ubuntu.Components 1.1
1130+import QtGraphicalEffects 1.0
1131+
1132+Item {
1133+ id: swipeToAct
1134+
1135+ width: parent.width
1136+ height: childrenRect.height
1137+
1138+ signal leftTriggered()
1139+ signal rightTriggered()
1140+
1141+ property string leftIconName
1142+ property string rightIconName
1143+ readonly property double sliderHeight: units.gu(6)
1144+ readonly property double gap: units.gu(1)
1145+ readonly property double halfWay: mouseArea.drag.maximumX / 2
1146+
1147+ Rectangle {
1148+ id: gradient
1149+ width: parent.width * 5
1150+ height: sliderHeight
1151+ visible: false
1152+ LinearGradient {
1153+ anchors.fill: parent
1154+ start: Qt.point(parent.x, parent.y)
1155+ end: Qt.point(parent.width, parent.y)
1156+ gradient: Gradient {
1157+ GradientStop { position: 0.0; color: UbuntuColors.red }
1158+ GradientStop { position: 0.2; color: UbuntuColors.red }
1159+ GradientStop { position: 0.4; color: "#dddddd" }
1160+ GradientStop { position: 0.6; color: "#dddddd" }
1161+ GradientStop { position: 0.8; color: UbuntuColors.green }
1162+ GradientStop { position: 1.0; color: UbuntuColors.green }
1163+ }
1164+ }
1165+ }
1166+
1167+ ShaderEffectSource {
1168+ id: effectSourceGradient
1169+ sourceItem: gradient
1170+ width: gradient.width
1171+ height: gradient.height
1172+ sourceRect: Qt.rect(0.4 * gradient.width * (slider.x / halfWay), 0, mask.width, mask.height)
1173+ visible: false
1174+ hideSource: true
1175+ }
1176+
1177+ UbuntuShape {
1178+ id: mask
1179+ color: "black"
1180+ width: parent.width
1181+ height: sliderHeight
1182+ borderSource: "none"
1183+ visible: false
1184+ }
1185+
1186+ ShaderEffectSource {
1187+ id: effectSourceMask
1188+ sourceItem: mask
1189+ width: mask.width
1190+ height: mask.height
1191+ visible: false
1192+ hideSource: true
1193+ }
1194+
1195+ ShaderEffect {
1196+ width: parent.width
1197+ height: sliderHeight
1198+ property variant mask: effectSourceMask
1199+ property variant gradient: effectSourceGradient
1200+ vertexShader: "
1201+ uniform highp mat4 qt_Matrix;
1202+ attribute highp vec4 qt_Vertex;
1203+ attribute highp vec2 qt_MultiTexCoord0;
1204+ varying highp vec2 coord;
1205+ void main() {
1206+ coord = qt_MultiTexCoord0;
1207+ gl_Position = qt_Matrix * qt_Vertex;
1208+ }"
1209+ fragmentShader: "
1210+ varying highp vec2 coord;
1211+ uniform sampler2D mask;
1212+ uniform sampler2D gradient;
1213+ void main() {
1214+ lowp vec4 texMask = texture2D(mask, coord);
1215+ lowp vec4 texGradient = texture2D(gradient, coord);
1216+ gl_FragColor = texGradient.rgba * texMask.a ;
1217+ }"
1218+
1219+ Row {
1220+ id: row
1221+ anchors.fill: parent
1222+ spacing: gap
1223+ anchors.margins: gap
1224+
1225+ UbuntuShape {
1226+ id: leftShape
1227+ states: [
1228+ State {
1229+ name: "normal"
1230+ PropertyChanges {
1231+ target: leftShape
1232+ color: UbuntuColors.red
1233+ }
1234+ PropertyChanges {
1235+ target: innerLeftShape
1236+ color: UbuntuColors.red
1237+ visible: false
1238+ }
1239+ },
1240+ State {
1241+ name: "selected"
1242+ PropertyChanges {
1243+ target: leftShape
1244+ color: "white"
1245+ }
1246+ PropertyChanges {
1247+ target: innerLeftShape
1248+ color: UbuntuColors.red
1249+ visible: true
1250+ }
1251+ }
1252+ ]
1253+ state: "normal"
1254+ height: units.gu(4)
1255+ width: units.gu(7)
1256+ borderSource: "none"
1257+ opacity: slider.x <= halfWay ? 1.0 : 1.0 - ((slider.x - halfWay) / halfWay)
1258+ UbuntuShape {
1259+ id: innerLeftShape
1260+ anchors.centerIn: parent
1261+ borderSource: "none"
1262+ width: parent.width - units.gu(.5)
1263+ height: parent.height - units.gu(.5)
1264+ }
1265+ Icon {
1266+ anchors.centerIn: parent
1267+ width: units.gu(2)
1268+ height: units.gu(2)
1269+ name: leftIconName
1270+ color: "white"
1271+ }
1272+ }
1273+
1274+ Rectangle {
1275+ id: leftSpacer
1276+ width: (row.width - (leftShape.width + slider.width + rightShape.width + 4 * row.spacing)) / 2
1277+ height: units.gu(4)
1278+ opacity: 0
1279+ }
1280+
1281+ UbuntuShape {
1282+ id: slider
1283+ objectName: "slider"
1284+
1285+ Behavior on x {
1286+ UbuntuNumberAnimation {
1287+ duration: UbuntuAnimation.FastDuration
1288+ easing.type: Easing.OutBounce
1289+ }
1290+ }
1291+
1292+ Behavior on opacity {
1293+ UbuntuNumberAnimation {
1294+ duration: UbuntuAnimation.FastDuration
1295+ }
1296+ }
1297+
1298+ onOpacityChanged: {
1299+ if (opacity === 0) {
1300+ if (rightShape.state === "selected") {
1301+ rightTriggered()
1302+ }
1303+ if (leftShape.state === "selected") {
1304+ leftTriggered()
1305+ }
1306+ }
1307+ }
1308+
1309+ z: 1
1310+ color: "white"
1311+ height: units.gu(4)
1312+ width: units.gu(7)
1313+ borderSource: "none"
1314+ Row {
1315+ anchors.fill: parent
1316+ spacing: 2 * gap
1317+ anchors.leftMargin: units.gu(.5)
1318+ anchors.rightMargin: units.gu(.5)
1319+ Icon {
1320+ anchors.verticalCenter: parent.verticalCenter
1321+ name: "back"
1322+ width: units.gu(2)
1323+ height: units.gu(2)
1324+ }
1325+ Icon {
1326+ anchors.verticalCenter: parent.verticalCenter
1327+ name: "next"
1328+ width: units.gu(2)
1329+ height: units.gu(2)
1330+ }
1331+ }
1332+ }
1333+
1334+ Rectangle {
1335+ id: rightSpacer
1336+ width: leftSpacer.width
1337+ height: units.gu(4)
1338+ opacity: 0
1339+ }
1340+
1341+ UbuntuShape {
1342+ id: rightShape
1343+ states: [
1344+ State {
1345+ name: "normal"
1346+ PropertyChanges {
1347+ target: rightShape
1348+ color: UbuntuColors.green
1349+ }
1350+ PropertyChanges {
1351+ target: innerRightShape
1352+ color: UbuntuColors.green
1353+ visible: false
1354+ }
1355+ },
1356+ State {
1357+ name: "selected"
1358+ PropertyChanges {
1359+ target: rightShape
1360+ color: "white"
1361+ }
1362+ PropertyChanges {
1363+ target: innerRightShape
1364+ color: UbuntuColors.green
1365+ visible: true
1366+ }
1367+ }
1368+ ]
1369+ state: "normal"
1370+ height: units.gu(4)
1371+ width: units.gu(7)
1372+ borderSource: "none"
1373+ opacity: slider.x >= halfWay ? 1.0 : slider.x / halfWay
1374+ UbuntuShape {
1375+ id: innerRightShape
1376+ anchors.centerIn: parent
1377+ borderSource: "none"
1378+ width: parent.width - units.gu(.5)
1379+ height: parent.height - units.gu(.5)
1380+ }
1381+ Icon {
1382+ anchors.centerIn: parent
1383+ width: units.gu(2)
1384+ height: units.gu(2)
1385+ name: rightIconName
1386+ color: "white"
1387+ }
1388+ }
1389+ }
1390+
1391+ MouseArea {
1392+ id: mouseArea
1393+ objectName: "swipeMouseArea"
1394+
1395+ anchors.fill: row
1396+ drag.target: slider
1397+ drag.axis: Drag.XAxis
1398+ drag.minimumX: 0
1399+ drag.maximumX: row.width - slider.width
1400+
1401+ onReleased: {
1402+ if (slider.x !== drag.minimumX || slider.x !== drag.maximumX) {
1403+ slider.x = halfWay
1404+ }
1405+ if (slider.x === drag.minimumX) {
1406+ slider.x = drag.minimumX
1407+ slider.opacity = 0
1408+ enabled = false
1409+ leftShape.state = "selected"
1410+ }
1411+ if (slider.x === drag.maximumX) {
1412+ slider.x = drag.maximumX
1413+ slider.opacity = 0
1414+ enabled = false
1415+ rightShape.state = "selected"
1416+ }
1417+ }
1418+ }
1419+ }
1420+}
1421
1422=== modified file 'qml/Panel/IndicatorPage.qml'
1423--- qml/Panel/IndicatorPage.qml 2014-10-23 11:59:22 +0000
1424+++ qml/Panel/IndicatorPage.qml 2014-11-05 14:37:57 +0000
1425@@ -25,6 +25,7 @@
1426 //const
1427 property string title: rootActionState.title
1428 property alias highlightFollowsCurrentItem : mainMenu.highlightFollowsCurrentItem
1429+ readonly property alias factory: _factory
1430
1431 Indicators.UnityMenuModelStack {
1432 id: menuStack
1433@@ -117,6 +118,16 @@
1434 }
1435 }
1436
1437+ Connections {
1438+ target: mainMenu.model ? mainMenu.model : null
1439+ onRowsAboutToBeRemoved: {
1440+ // track current item deletion.
1441+ if (mainMenu.selectedIndex >= first && mainMenu.selectedIndex <= last) {
1442+ mainMenu.selectedIndex = -1;
1443+ }
1444+ }
1445+ }
1446+
1447 delegate: Loader {
1448 id: loader
1449 objectName: "menuItem" + index
1450@@ -164,7 +175,7 @@
1451 }
1452
1453 MenuItemFactory {
1454- id: factory
1455+ id: _factory
1456 rootModel: main.menuModel ? main.menuModel : null
1457 menuModel: mainMenu.model ? mainMenu.model : null
1458 }
1459
1460=== modified file 'qml/Panel/Indicators/MenuItemFactory.qml'
1461--- qml/Panel/Indicators/MenuItemFactory.qml 2014-10-06 16:22:57 +0000
1462+++ qml/Panel/Indicators/MenuItemFactory.qml 2014-11-05 14:37:57 +0000
1463@@ -290,12 +290,26 @@
1464 checked: serverChecked
1465 highlightWhenPressed: false
1466
1467- onServerCheckedChanged: {
1468- // value can be changed by menu, so a binding won't work.
1469- checked = serverChecked;
1470- }
1471+ onServerCheckedChanged: updateFromServer()
1472 onTriggered: {
1473 menuModel.activate(menuIndex);
1474+ resyncTimer.restart();
1475+ }
1476+
1477+ // value can be changed by menu, so a binding won't work.
1478+ function updateFromServer() {
1479+ resyncTimer.stop();
1480+ if (checked != serverChecked) {
1481+ checked = serverChecked;
1482+ }
1483+ }
1484+
1485+ // Server value is not guaranteed to change to what we expect from an activation.
1486+ // In this case, we need to re-assert that we are presenting the UI with the set backend value.
1487+ Timer {
1488+ id: resyncTimer
1489+ interval: 1500
1490+ onTriggered: updateFromServer()
1491 }
1492 }
1493 }
1494@@ -315,12 +329,26 @@
1495 checked: serverChecked
1496 highlightWhenPressed: false
1497
1498- onServerCheckedChanged: {
1499- // value can be changed by menu, so a binding won't work.
1500- checked = serverChecked;
1501- }
1502+ onServerCheckedChanged: updateFromServer()
1503 onTriggered: {
1504 menuModel.activate(menuIndex);
1505+ resyncTimer.restart();
1506+ }
1507+
1508+ // value can be changed by menu, so a binding won't work.
1509+ function updateFromServer() {
1510+ resyncTimer.stop();
1511+ if (checked != serverChecked) {
1512+ checked = serverChecked;
1513+ }
1514+ }
1515+
1516+ // Server value is not guaranteed to change to what we expect from an activation.
1517+ // In this case, we need to re-assert that we are presenting the UI with the set backend value.
1518+ Timer {
1519+ id: resyncTimer
1520+ interval: 1500
1521+ onTriggered: updateFromServer()
1522 }
1523 }
1524 }
1525
1526=== modified file 'qml/Shell.qml'
1527--- qml/Shell.qml 2014-10-30 21:43:18 +0000
1528+++ qml/Shell.qml 2014-11-05 14:37:57 +0000
1529@@ -133,23 +133,32 @@
1530 objectName: "dashCommunicator"
1531 }
1532
1533+ ScreenGrabber {
1534+ id: screenGrabber
1535+ z: edgeDemo.z + 10
1536+ enabled: Powerd.status === Powerd.On
1537+ }
1538+
1539 Binding {
1540 target: ApplicationManager
1541 property: "forceDashActive"
1542 value: launcher.shown || launcher.dashSwipe
1543 }
1544
1545+ VolumeKeyFilter {
1546+ id: volumeKeyFilter
1547+ onVolumeDownPressed: volumeControl.volumeDown()
1548+ onVolumeUpPressed: volumeControl.volumeUp()
1549+ onBothVolumeKeysPressed: screenGrabber.capture()
1550+ }
1551
1552 WindowKeysFilter {
1553- // Handle but do not filter out volume keys
1554- Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
1555- Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
1556-
1557 Keys.onPressed: {
1558 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
1559 dialogs.onPowerKeyPressed();
1560 event.accepted = true;
1561 } else {
1562+ volumeKeyFilter.onKeyPressed(event.key);
1563 event.accepted = false;
1564 }
1565 }
1566@@ -159,6 +168,7 @@
1567 dialogs.onPowerKeyReleased();
1568 event.accepted = true;
1569 } else {
1570+ volumeKeyFilter.onKeyReleased(event.key);
1571 event.accepted = false;
1572 }
1573 }
1574@@ -175,7 +185,7 @@
1575 target: ApplicationManager
1576 onFocusRequested: {
1577 if (greeter.narrowMode) {
1578- if (appId === "dialer-app" && callManager.hasCalls) {
1579+ if (appId === "dialer-app" && callManager.hasCalls && shell.locked) {
1580 // If we are in the middle of a call, make dialer lockedApp and show it.
1581 // This can happen if user backs out of dialer back to greeter, then
1582 // launches dialer again.
1583
1584=== modified file 'tests/autopilot/unity8/shell/tests/test_notifications.py'
1585--- tests/autopilot/unity8/shell/tests/test_notifications.py 2014-10-30 21:43:06 +0000
1586+++ tests/autopilot/unity8/shell/tests/test_notifications.py 2014-11-05 14:37:57 +0000
1587@@ -157,57 +157,6 @@
1588
1589 self.assert_notification_action_id_was_called('action_id')
1590
1591- def test_sd_incoming_call(self):
1592- """Rejecting a call should make notification expand and
1593- offer more options."""
1594- unity_proxy = self.launch_unity()
1595- unlock_unity(unity_proxy)
1596-
1597- summary = "Incoming call"
1598- body = "Frank Zappa\n+44 (0)7736 027340"
1599- icon_path = self._get_icon_path('avatars/anna_olsson.png')
1600- hints = [
1601- ("x-canonical-secondary-icon", "incoming-call"),
1602- ("x-canonical-snap-decisions", "true"),
1603- ("x-canonical-private-affirmative-tint", "true"),
1604- ("x-canonical-private-rejection-tint", "true"),
1605- ]
1606-
1607- actions = [
1608- ('action_accept', 'Hold + Answer'),
1609- ('action_decline_1', 'End + Answer'),
1610- ('action_decline_2', 'Decline'),
1611- ('action_decline_3', 'message:I missed your call - can you call me now?'),
1612- ('action_decline_4', 'message:I\'m running late. I\'m on my way.'),
1613- ('action_decline_5', 'message:I\'m busy at the moment. I\'ll call later.'),
1614- ('action_decline_6', 'edit:Custom'),
1615- ]
1616-
1617- self._create_interactive_notification(
1618- summary,
1619- body,
1620- icon_path,
1621- "NORMAL",
1622- actions,
1623- hints
1624- )
1625-
1626- notify_list = self._get_notifications_list()
1627- get_notification = lambda: notify_list.wait_select_single(
1628- 'Notification', objectName='notification1')
1629- notification = get_notification()
1630- self._assert_notification(notification, summary, body, True, True, 1.0)
1631- notification.pointing_device.click_object(
1632- notification.select_single(objectName="combobutton_dropdown"))
1633- self.assertThat(
1634- notification.select_single(objectName="notify_button2").expanded,
1635- Eventually(Equals(True)))
1636- time.sleep(2)
1637- notification.pointing_device.click_object(
1638- notification.select_single(objectName="notify_button4"))
1639- self.assert_notification_action_id_was_called("action_decline_4")
1640-
1641-
1642 def test_sd_one_over_two_layout(self):
1643 """Snap-decision with three actions should use one-over two button layout."""
1644 unity_proxy = self.launch_unity()
1645
1646=== modified file 'tests/mocks/QtMultimedia/QtMultimedia.qmltypes'
1647--- tests/mocks/QtMultimedia/QtMultimedia.qmltypes 2014-06-26 07:47:57 +0000
1648+++ tests/mocks/QtMultimedia/QtMultimedia.qmltypes 2014-11-05 14:37:57 +0000
1649@@ -20,11 +20,21 @@
1650 "StoppedState": 2
1651 }
1652 }
1653+ Enum {
1654+ name: "AudioRole"
1655+ values: {
1656+ "AlarmRole": 0,
1657+ "AlertRole": 1,
1658+ "MultimediaRole": 2,
1659+ "PhoneRole": 3
1660+ }
1661+ }
1662 Property { name: "source"; type: "QUrl" }
1663 Property { name: "playbackState"; type: "PlaybackState"; isReadonly: true }
1664 Property { name: "position"; type: "int"; isReadonly: true }
1665 Property { name: "duration"; type: "int"; isReadonly: true }
1666 Property { name: "errorString"; type: "string"; isReadonly: true }
1667+ Property { name: "audioRole"; type: "AudioRole" }
1668 Signal {
1669 name: "sourceChanged"
1670 Parameter { name: "source"; type: "QUrl" }
1671
1672=== modified file 'tests/mocks/QtMultimedia/audio.cpp'
1673--- tests/mocks/QtMultimedia/audio.cpp 2014-09-12 14:51:55 +0000
1674+++ tests/mocks/QtMultimedia/audio.cpp 2014-11-05 14:37:57 +0000
1675@@ -105,3 +105,13 @@
1676 stop();
1677 }
1678 }
1679+
1680+Audio::AudioRole Audio::audioRole() const
1681+{
1682+ return Audio::MultimediaRole;
1683+}
1684+
1685+void Audio::setAudioRole(Audio::AudioRole audioRole)
1686+{
1687+ Q_UNUSED(audioRole);
1688+}
1689
1690=== modified file 'tests/mocks/QtMultimedia/audio.h'
1691--- tests/mocks/QtMultimedia/audio.h 2013-11-14 12:12:05 +0000
1692+++ tests/mocks/QtMultimedia/audio.h 2014-11-05 14:37:57 +0000
1693@@ -27,11 +27,13 @@
1694 {
1695 Q_OBJECT
1696 Q_ENUMS(PlaybackState)
1697+ Q_ENUMS(AudioRole)
1698 Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
1699 Q_PROPERTY(PlaybackState playbackState READ playbackState NOTIFY playbackStateChanged)
1700 Q_PROPERTY(int position READ position NOTIFY positionChanged)
1701 Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
1702 Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
1703+ Q_PROPERTY(AudioRole audioRole READ audioRole WRITE setAudioRole)
1704 public:
1705 enum PlaybackState {
1706 PlayingState,
1707@@ -39,6 +41,13 @@
1708 StoppedState
1709 };
1710
1711+ enum AudioRole {
1712+ AlarmRole,
1713+ AlertRole,
1714+ MultimediaRole,
1715+ PhoneRole
1716+ };
1717+
1718 explicit Audio(QObject *parent = 0);
1719
1720 QUrl source() const;
1721@@ -52,6 +61,9 @@
1722
1723 QString errorString() const;
1724
1725+ AudioRole audioRole() const;
1726+ void setAudioRole(AudioRole audioRole);
1727+
1728 public Q_SLOTS:
1729 void pause();
1730 void play();
1731
1732=== modified file 'tests/mocks/Unity/Notifications/CMakeLists.txt'
1733--- tests/mocks/Unity/Notifications/CMakeLists.txt 2014-05-02 23:27:02 +0000
1734+++ tests/mocks/Unity/Notifications/CMakeLists.txt 2014-11-05 14:37:57 +0000
1735@@ -1,1 +1,15 @@
1736-add_unity8_mock(Unity.Notifications 1.0 Unity/Notifications)
1737+include_directories(
1738+ ${CMAKE_CURRENT_SOURCE_DIR}
1739+)
1740+
1741+set(MockNotificationsPlugin_SOURCES
1742+ plugin.cpp
1743+ MockNotificationTypes.cpp
1744+ MockActionModel.cpp
1745+)
1746+
1747+add_library(MockNotificationsPlugin MODULE ${MockNotificationsPlugin_SOURCES})
1748+
1749+qt5_use_modules(MockNotificationsPlugin Core Quick)
1750+
1751+add_unity8_mock(Unity.Notifications 1.0 Unity/Notifications TARGETS MockNotificationsPlugin)
1752
1753=== added file 'tests/mocks/Unity/Notifications/MockActionModel.cpp'
1754--- tests/mocks/Unity/Notifications/MockActionModel.cpp 1970-01-01 00:00:00 +0000
1755+++ tests/mocks/Unity/Notifications/MockActionModel.cpp 2014-11-05 14:37:57 +0000
1756@@ -0,0 +1,72 @@
1757+/*
1758+ * Copyright 2014 Canonical Ltd.
1759+ *
1760+ * This program is free software; you can redistribute it and/or modify
1761+ * it under the terms of the GNU Lesser General Public License as published by
1762+ * the Free Software Foundation; version 3.
1763+ *
1764+ * This program is distributed in the hope that it will be useful,
1765+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1766+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1767+ * GNU Lesser General Public License for more details.
1768+ *
1769+ * You should have received a copy of the GNU Lesser General Public License
1770+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1771+ *
1772+ * Authors:
1773+ * Mirco Mueller <mirco.mueller@canonical.com>
1774+ */
1775+
1776+#include "MockActionModel.h"
1777+
1778+struct ActionModelPrivate {
1779+ QList<QString> labels;
1780+ QList<QString> ids;
1781+};
1782+
1783+ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
1784+ insertAction("ok_id", "Ok");
1785+ insertAction("cancel_id", "Cancel");
1786+}
1787+
1788+ActionModel::~ActionModel() {
1789+}
1790+
1791+int ActionModel::rowCount(const QModelIndex &index) const {
1792+ return p->labels.size();
1793+}
1794+
1795+QVariant ActionModel::data(const QModelIndex &index, int role) const {
1796+ if (!index.isValid())
1797+ return QVariant();
1798+
1799+ switch(role) {
1800+ case RoleActionLabel:
1801+ return QVariant(p->labels[index.row()]);
1802+
1803+ case RoleActionId:
1804+ return QVariant(p->ids[index.row()]);
1805+
1806+ default:
1807+ return QVariant();
1808+ }
1809+}
1810+
1811+QHash<int, QByteArray> ActionModel::roleNames() const {
1812+ QHash<int, QByteArray> roles;
1813+
1814+ roles.insert(RoleActionLabel, "label");
1815+ roles.insert(RoleActionId, "id");
1816+
1817+ return roles;
1818+}
1819+
1820+Q_INVOKABLE QVariant ActionModel::data(int row, int role) const
1821+{
1822+ return data(index(row, 0), role);
1823+}
1824+
1825+void ActionModel::insertAction(const QString &id, const QString &label) {
1826+ p->ids.push_back(id);
1827+ p->labels.push_back(label);
1828+}
1829
1830=== added file 'tests/mocks/Unity/Notifications/MockActionModel.h'
1831--- tests/mocks/Unity/Notifications/MockActionModel.h 1970-01-01 00:00:00 +0000
1832+++ tests/mocks/Unity/Notifications/MockActionModel.h 2014-11-05 14:37:57 +0000
1833@@ -0,0 +1,51 @@
1834+/*
1835+ * Copyright 2014 Canonical Ltd.
1836+ *
1837+ * This program is free software; you can redistribute it and/or modify
1838+ * it under the terms of the GNU Lesser General Public License as published by
1839+ * the Free Software Foundation; version 3.
1840+ *
1841+ * This program is distributed in the hope that it will be useful,
1842+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1843+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1844+ * GNU Lesser General Public License for more details.
1845+ *
1846+ * You should have received a copy of the GNU Lesser General Public License
1847+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1848+ *
1849+ * Authors:
1850+ * Mirco Mueller <mirco.mueller@canonical.com>
1851+ */
1852+
1853+#ifndef MOCK_ACTION_MODEL_H
1854+#define MOCK_ACTION_MODEL_H
1855+
1856+#include <QStringListModel>
1857+
1858+struct ActionModelPrivate;
1859+
1860+class ActionModel : public QStringListModel {
1861+ Q_OBJECT
1862+
1863+public:
1864+ ActionModel(QObject *parent=nullptr);
1865+ virtual ~ActionModel();
1866+
1867+ virtual int rowCount(const QModelIndex &index) const;
1868+ virtual QVariant data(const QModelIndex &index, int role) const;
1869+ virtual QHash<int, QByteArray> roleNames() const;
1870+
1871+ Q_ENUMS(ActionsRoles)
1872+ enum ActionsRoles {
1873+ RoleActionLabel = Qt::UserRole + 1,
1874+ RoleActionId = Qt::UserRole + 2
1875+ };
1876+ Q_INVOKABLE QVariant data(int row, int role) const;
1877+
1878+ void insertAction(const QString &id, const QString &label);
1879+
1880+private:
1881+ QScopedPointer<ActionModelPrivate> p;
1882+};
1883+
1884+#endif // MOCK_ACTION_MODEL_H
1885
1886=== added file 'tests/mocks/Unity/Notifications/MockNotificationTypes.cpp'
1887--- tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 1970-01-01 00:00:00 +0000
1888+++ tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 2014-11-05 14:37:57 +0000
1889@@ -0,0 +1,26 @@
1890+/*
1891+ * Copyright 2014 Canonical Ltd.
1892+ *
1893+ * This program is free software; you can redistribute it and/or modify
1894+ * it under the terms of the GNU Lesser General Public License as published by
1895+ * the Free Software Foundation; version 3.
1896+ *
1897+ * This program is distributed in the hope that it will be useful,
1898+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1899+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1900+ * GNU Lesser General Public License for more details.
1901+ *
1902+ * You should have received a copy of the GNU Lesser General Public License
1903+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1904+ *
1905+ * Authors:
1906+ * Mirco Mueller <mirco.mueller@canonical.com>
1907+ */
1908+
1909+#include "MockNotificationTypes.h"
1910+
1911+MockNotification::MockNotification(QObject *parent) : QObject(parent) {
1912+}
1913+
1914+MockNotification::~MockNotification() {
1915+}
1916
1917=== added file 'tests/mocks/Unity/Notifications/MockNotificationTypes.h'
1918--- tests/mocks/Unity/Notifications/MockNotificationTypes.h 1970-01-01 00:00:00 +0000
1919+++ tests/mocks/Unity/Notifications/MockNotificationTypes.h 2014-11-05 14:37:57 +0000
1920@@ -0,0 +1,36 @@
1921+/*
1922+ * Copyright 2014 Canonical Ltd.
1923+ *
1924+ * This program is free software; you can redistribute it and/or modify
1925+ * it under the terms of the GNU Lesser General Public License as published by
1926+ * the Free Software Foundation; version 3.
1927+ *
1928+ * This program is distributed in the hope that it will be useful,
1929+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1930+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1931+ * GNU Lesser General Public License for more details.
1932+ *
1933+ * You should have received a copy of the GNU Lesser General Public License
1934+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1935+ *
1936+ * Authors:
1937+ * Mirco Mueller <mirco.mueller@canonical.com>
1938+ */
1939+
1940+#ifndef MOCK_NOTIFICATION_TYPES_H
1941+#define MOCK_NOTIFICATION_TYPES_H
1942+
1943+#include <QObject>
1944+
1945+class MockNotification : public QObject {
1946+ Q_OBJECT
1947+ Q_ENUMS(Type)
1948+
1949+public:
1950+ MockNotification(QObject *parent=nullptr);
1951+ virtual ~MockNotification();
1952+
1953+ enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
1954+};
1955+
1956+#endif // MOCK_NOTIFICATION_TYPES_H
1957
1958=== removed file 'tests/mocks/Unity/Notifications/notification.js'
1959--- tests/mocks/Unity/Notifications/notification.js 2013-06-19 10:24:31 +0000
1960+++ tests/mocks/Unity/Notifications/notification.js 1970-01-01 00:00:00 +0000
1961@@ -1,23 +0,0 @@
1962-/*
1963- * Copyright (C) 2013 Canonical, Ltd.
1964- *
1965- * This program is free software; you can redistribute it and/or modify
1966- * it under the terms of the GNU General Public License as published by
1967- * the Free Software Foundation; version 3.
1968- *
1969- * This program is distributed in the hope that it will be useful,
1970- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1971- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1972- * GNU General Public License for more details.
1973- *
1974- * You should have received a copy of the GNU General Public License
1975- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1976- */
1977-
1978-.pragma library
1979-
1980-var Confirmation = 0;
1981-var Ephemeral = 1;
1982-var Interactive = 2;
1983-var SnapDecision = 3;
1984-var PlaceHolder = 4;
1985
1986=== added file 'tests/mocks/Unity/Notifications/plugin.cpp'
1987--- tests/mocks/Unity/Notifications/plugin.cpp 1970-01-01 00:00:00 +0000
1988+++ tests/mocks/Unity/Notifications/plugin.cpp 2014-11-05 14:37:57 +0000
1989@@ -0,0 +1,31 @@
1990+/*
1991+ * Copyright 2014 Canonical Ltd.
1992+ *
1993+ * This program is free software; you can redistribute it and/or modify
1994+ * it under the terms of the GNU Lesser General Public License as published by
1995+ * the Free Software Foundation; version 3.
1996+ *
1997+ * This program is distributed in the hope that it will be useful,
1998+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1999+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2000+ * GNU Lesser General Public License for more details.
2001+ *
2002+ * You should have received a copy of the GNU Lesser General Public License
2003+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2004+ *
2005+ * Authors:
2006+ * Mirco Mueller <mirco.mueller@canonical.com>
2007+ */
2008+
2009+#include "plugin.h"
2010+#include "MockActionModel.h"
2011+#include "MockNotificationTypes.h"
2012+
2013+#include <QtQml/qqml.h>
2014+
2015+void TestNotificationPlugin::registerTypes(const char* uri)
2016+{
2017+ // @uri Unity.Notifications
2018+ qmlRegisterUncreatableType<MockNotification>(uri, 1, 0, "Notification", "Notification objects can only be created by the plugin");
2019+ qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
2020+}
2021
2022=== added file 'tests/mocks/Unity/Notifications/plugin.h'
2023--- tests/mocks/Unity/Notifications/plugin.h 1970-01-01 00:00:00 +0000
2024+++ tests/mocks/Unity/Notifications/plugin.h 2014-11-05 14:37:57 +0000
2025@@ -0,0 +1,35 @@
2026+/*
2027+ * Copyright 2014 Canonical Ltd.
2028+ *
2029+ * This program is free software; you can redistribute it and/or modify
2030+ * it under the terms of the GNU Lesser General Public License as published by
2031+ * the Free Software Foundation; version 3.
2032+ *
2033+ * This program is distributed in the hope that it will be useful,
2034+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2035+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2036+ * GNU Lesser General Public License for more details.
2037+ *
2038+ * You should have received a copy of the GNU Lesser General Public License
2039+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2040+ *
2041+ * Authors:
2042+ * Mirco Mueller <mirco.mueller@canonical.com>
2043+ */
2044+
2045+
2046+#ifndef TESTNOTIFICATION_PLUGIN_H
2047+#define TESTNOTIFICATION_PLUGIN_H
2048+
2049+#include <QtQml/QQmlExtensionPlugin>
2050+
2051+class TestNotificationPlugin : public QQmlExtensionPlugin
2052+{
2053+ Q_OBJECT
2054+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
2055+
2056+public:
2057+ void registerTypes(const char* uri);
2058+};
2059+
2060+#endif // TESTNOTIFICATION_PLUGIN_H
2061
2062=== modified file 'tests/mocks/Unity/Notifications/qmldir'
2063--- tests/mocks/Unity/Notifications/qmldir 2014-05-02 22:57:21 +0000
2064+++ tests/mocks/Unity/Notifications/qmldir 2014-11-05 14:37:57 +0000
2065@@ -1,3 +1,4 @@
2066 module Unity.Notifications
2067-Notification 1.0 notification.js
2068+plugin MockNotificationsPlugin
2069 typeinfo Notifications.qmltypes
2070+
2071
2072=== modified file 'tests/plugins/Ubuntu/Gestures/GestureTest.cpp'
2073--- tests/plugins/Ubuntu/Gestures/GestureTest.cpp 2014-10-17 11:01:53 +0000
2074+++ tests/plugins/Ubuntu/Gestures/GestureTest.cpp 2014-11-05 14:37:57 +0000
2075@@ -90,6 +90,7 @@
2076 mousePressEventHandler = defaultMouseEventHandler;
2077 mouseMoveEventHandler = defaultMouseEventHandler;
2078 mouseReleaseEventHandler = defaultMouseEventHandler;
2079+ mouseDoubleClickEventHandler = defaultMouseEventHandler;
2080 }
2081
2082 void DummyItem::touchEvent(QTouchEvent *event)
2083@@ -113,6 +114,11 @@
2084 mouseReleaseEventHandler(event);
2085 }
2086
2087+void DummyItem::mouseDoubleClickEvent(QMouseEvent *event)
2088+{
2089+ mouseDoubleClickEventHandler(event);
2090+}
2091+
2092 void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
2093 {
2094 event->accept();
2095
2096=== modified file 'tests/plugins/Ubuntu/Gestures/GestureTest.h'
2097--- tests/plugins/Ubuntu/Gestures/GestureTest.h 2014-10-17 11:01:53 +0000
2098+++ tests/plugins/Ubuntu/Gestures/GestureTest.h 2014-11-05 14:37:57 +0000
2099@@ -52,12 +52,14 @@
2100 std::function<void(QMouseEvent*)> mousePressEventHandler;
2101 std::function<void(QMouseEvent*)> mouseMoveEventHandler;
2102 std::function<void(QMouseEvent*)> mouseReleaseEventHandler;
2103+ std::function<void(QMouseEvent*)> mouseDoubleClickEventHandler;
2104 protected:
2105 void touchEvent(QTouchEvent *event) override;
2106
2107 void mousePressEvent(QMouseEvent *event) override;
2108 void mouseMoveEvent(QMouseEvent *event) override;
2109 void mouseReleaseEvent(QMouseEvent *event) override;
2110+ void mouseDoubleClickEvent(QMouseEvent *event) override;
2111 private:
2112 static void defaultTouchEventHandler(QTouchEvent *event);
2113 static void defaultMouseEventHandler(QMouseEvent *event);
2114
2115=== modified file 'tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp'
2116--- tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp 2014-10-17 11:01:53 +0000
2117+++ tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp 2014-11-05 14:37:57 +0000
2118@@ -16,6 +16,7 @@
2119
2120 #include <QtTest>
2121 #include <QQuickView>
2122+#include <QStyleHints>
2123
2124 #include "GestureTest.h"
2125
2126@@ -29,6 +30,8 @@
2127 private Q_SLOTS:
2128 void sendMouseEventIfTouchIgnored_data();
2129 void sendMouseEventIfTouchIgnored();
2130+ void mouseDoubleClick_data();
2131+ void mouseDoubleClick();
2132 };
2133
2134 tst_TouchDispatcher::tst_TouchDispatcher()
2135@@ -85,6 +88,62 @@
2136 QCOMPARE(gotMousePressEvent, shouldGetMousePress);
2137 }
2138
2139+void tst_TouchDispatcher::mouseDoubleClick_data()
2140+{
2141+ QTest::addColumn<ulong>("timeBetweenClicks");
2142+ QTest::addColumn<bool>("shouldSendDoubleClick");
2143+
2144+ QTest::newRow("double click") << static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval() / 10) << true;
2145+ QTest::newRow("two separate clicks") << static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval() * 2) << false;
2146+}
2147+
2148+void tst_TouchDispatcher::mouseDoubleClick()
2149+{
2150+ QFETCH(ulong, timeBetweenClicks);
2151+ QFETCH(bool, shouldSendDoubleClick);
2152+ DummyItem *dummyItem = new DummyItem(m_view->rootObject());
2153+ dummyItem->setAcceptedMouseButtons(Qt::LeftButton);
2154+
2155+ TouchDispatcher touchDispatcher;
2156+ touchDispatcher.setTargetItem(dummyItem);
2157+
2158+ bool gotDoubleClickEvent = false;
2159+ dummyItem->mouseDoubleClickEventHandler = [&](QMouseEvent *event) {
2160+ gotDoubleClickEvent = true;
2161+ event->accept();
2162+ };
2163+
2164+ dummyItem->touchEventHandler = [&](QTouchEvent *event) {
2165+ event->ignore();
2166+ };
2167+
2168+ ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->mouseDoubleClickInterval());
2169+
2170+ QList<QTouchEvent::TouchPoint> touchPoints;
2171+ {
2172+ QTouchEvent::TouchPoint touchPoint;
2173+ touchPoint.setId(0);
2174+ touchPoint.setState(Qt::TouchPointPressed);
2175+ touchPoints.append(touchPoint);
2176+ }
2177+ ulong timestamp = 12345;
2178+
2179+ touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
2180+
2181+ touchPoints[0].setState(Qt::TouchPointReleased);
2182+ timestamp += doubleClickInterval / 10;
2183+
2184+ touchDispatcher.dispatch(QEvent::TouchEnd, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
2185+
2186+ touchPoints[0].setId(1);
2187+ touchPoints[0].setState(Qt::TouchPointPressed);
2188+ timestamp += timeBetweenClicks;
2189+
2190+ touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
2191+
2192+ QCOMPARE(gotDoubleClickEvent, shouldSendDoubleClick);
2193+}
2194+
2195 QTEST_MAIN(tst_TouchDispatcher)
2196
2197 #include "tst_TouchDispatcher.moc"
2198
2199=== modified file 'tests/plugins/Unity/Launcher/launchermodeltest.cpp'
2200--- tests/plugins/Unity/Launcher/launchermodeltest.cpp 2014-10-30 21:40:34 +0000
2201+++ tests/plugins/Unity/Launcher/launchermodeltest.cpp 2014-11-05 14:37:57 +0000
2202@@ -365,8 +365,8 @@
2203 QCOMPARE(map.value("countVisible").toBool(), false);
2204
2205 // Now make it visible and set it to 55 through D-Bus
2206- interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", 55);
2207- interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", true);
2208+ interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", QVariant::fromValue(QDBusVariant(55)));
2209+ interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", QVariant::fromValue(QDBusVariant(true)));
2210
2211 // Fetch it again using GetAll
2212 reply = interface.call("GetAll");
2213
2214=== modified file 'tests/qmltests/CMakeLists.txt'
2215--- tests/qmltests/CMakeLists.txt 2014-10-13 09:23:34 +0000
2216+++ tests/qmltests/CMakeLists.txt 2014-11-05 14:37:57 +0000
2217@@ -70,6 +70,7 @@
2218 add_qml_test(Launcher Launcher)
2219 add_qml_test(Notifications Notifications)
2220 add_qml_test(Notifications VisualSnapDecisionsQueue)
2221+add_qml_test(Notifications SwipeToAct)
2222 add_qml_test(Panel ActiveCallHint)
2223 add_qml_test(Panel IndicatorItem)
2224 add_qml_test(Panel IndicatorItemRow ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
2225
2226=== modified file 'tests/qmltests/Greeter/tst_SingleGreeter.qml'
2227--- tests/qmltests/Greeter/tst_SingleGreeter.qml 2014-10-01 13:20:32 +0000
2228+++ tests/qmltests/Greeter/tst_SingleGreeter.qml 2014-11-05 14:37:57 +0000
2229@@ -27,50 +27,72 @@
2230 width: units.gu(60)
2231 height: units.gu(80)
2232
2233- Greeter {
2234- id: greeter
2235- width: parent.width
2236- height: parent.height
2237- x: 0; y: 0
2238-
2239- property int minX: 0
2240-
2241- onXChanged: {
2242- if (x < minX) {
2243- minX = x;
2244- }
2245- }
2246- }
2247-
2248- Component {
2249- id: greeterComponent
2250- Greeter {
2251- SignalSpy {
2252- objectName: "selectedSpy"
2253- target: parent
2254- signalName: "selected"
2255+ Loader {
2256+ id: greeterLoader
2257+ anchors.fill: parent
2258+
2259+ property bool itemDestroyed: false
2260+
2261+ sourceComponent: Component {
2262+ Greeter {
2263+ anchors.fill: greeterLoader
2264+
2265+ property int minX: 0
2266+
2267+ onXChanged: {
2268+ if (x < minX) {
2269+ minX = x;
2270+ }
2271+ }
2272+
2273+ Component.onDestruction: {
2274+ greeterLoader.itemDestroyed = true;
2275+ }
2276+ SignalSpy {
2277+ objectName: "selectedSpy"
2278+ target: parent
2279+ signalName: "selected"
2280+ }
2281 }
2282 }
2283 }
2284
2285 SignalSpy {
2286 id: unlockSpy
2287- target: greeter
2288+ target: greeterLoader.item
2289 signalName: "unlocked"
2290 }
2291
2292 SignalSpy {
2293 id: teaseSpy
2294- target: greeter
2295+ target: greeterLoader.item
2296 signalName: "tease"
2297 }
2298
2299+ SignalSpy {
2300+ id: infographicDataChangedSpy
2301+ target: LightDM.Infographic
2302+ signalName: "dataChanged"
2303+ }
2304+
2305 UT.UnityTestCase {
2306 name: "SingleGreeter"
2307 when: windowShown
2308
2309+ property Greeter greeter: greeterLoader.item
2310+
2311 function cleanup() {
2312 AccountsService.statsWelcomeScreen = true
2313+
2314+ // force a reload so that we get a fresh Greeter for the next test
2315+ greeterLoader.itemDestroyed = false;
2316+ greeterLoader.active = false;
2317+ tryCompare(greeterLoader, "itemDestroyed", true);
2318+
2319+ unlockSpy.clear();
2320+ teaseSpy.clear();
2321+
2322+ greeterLoader.active = true;
2323 }
2324
2325 function test_properties() {
2326@@ -103,11 +125,32 @@
2327 }
2328
2329 function test_initial_selected_signal() {
2330- var greeterObj = greeterComponent.createObject(this)
2331- var spy = findChild(greeterObj, "selectedSpy")
2332- spy.wait()
2333- tryCompare(spy, "count", 1)
2334- greeterObj.destroy()
2335+ var selectedSpy = findChild(greeter, "selectedSpy");
2336+ selectedSpy.wait();
2337+ tryCompare(selectedSpy, "count", 1);
2338+ }
2339+
2340+ /*
2341+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
2342+ "User metrics can no longer be changed by double tap"
2343+ */
2344+ function test_doubleTapSwitchesToNextInfographic() {
2345+ infographicDataChangedSpy.clear();
2346+
2347+ var infographicPrivate = findInvisibleChild(greeter, "infographicPrivate");
2348+ verify(infographicPrivate);
2349+
2350+ // wait for the UI to settle down before double tapping it
2351+ tryCompare(infographicPrivate, "animating", false);
2352+
2353+ var dataCircle = findChild(greeter, "dataCircle");
2354+ verify(dataCircle);
2355+
2356+ tap(dataCircle);
2357+ wait(1);
2358+ tap(dataCircle);
2359+
2360+ tryCompare(infographicDataChangedSpy, "count", 1);
2361 }
2362 }
2363 }
2364
2365=== modified file 'tests/qmltests/Notifications/tst_Notifications.qml'
2366--- tests/qmltests/Notifications/tst_Notifications.qml 2014-10-14 10:07:44 +0000
2367+++ tests/qmltests/Notifications/tst_Notifications.qml 2014-11-05 14:37:57 +0000
2368@@ -23,280 +23,51 @@
2369 import Unity.Notifications 1.0
2370 import QtMultimedia 5.0
2371
2372-Row {
2373- id: rootRow
2374-
2375- Component {
2376- id: mockNotification
2377-
2378- QtObject {
2379- function invokeAction(actionId) {
2380- mockModel.actionInvoked(actionId)
2381- }
2382- }
2383- }
2384-
2385- ListModel {
2386- id: mockModel
2387-
2388- signal actionInvoked(string actionId)
2389-
2390- function getRaw(id) {
2391- return mockNotification.createObject(mockModel)
2392- }
2393-
2394- // add the default/PlaceHolder notification to the model
2395- Component.onCompleted: {
2396+Item {
2397+ width: notificationsRect.width + interactiveControls.width
2398+ height: notificationsRect.height
2399+
2400+ Row {
2401+ id: rootRow
2402+
2403+ Component {
2404+ id: mockNotification
2405+
2406+ QtObject {
2407+ function invokeAction(actionId) {
2408+ mockModel.actionInvoked(actionId)
2409+ }
2410+ }
2411+ }
2412+
2413+ ListModel {
2414+ id: mockModel
2415+ dynamicRoles: true
2416+
2417+ signal actionInvoked(string actionId)
2418+
2419+ function getRaw(id) {
2420+ return mockNotification.createObject(mockModel)
2421+ }
2422+
2423+ // add the default/PlaceHolder notification to the model
2424+ Component.onCompleted: {
2425+ var n = {
2426+ type: Notification.PlaceHolder,
2427+ hints: {},
2428+ summary: "",
2429+ body: "",
2430+ icon: "",
2431+ secondaryIcon: "",
2432+ actions: []
2433+ }
2434+
2435+ append(n)
2436+ }
2437+ }
2438+
2439+ function addSnapDecisionNotification() {
2440 var n = {
2441- type: Notification.PlaceHolder,
2442- hints: {},
2443- summary: "",
2444- body: "",
2445- icon: "",
2446- value: 0,
2447- secondaryIcon: "",
2448- actions: []
2449- }
2450-
2451- append(n)
2452- }
2453- }
2454-
2455- function addSnapDecisionNotification() {
2456- var n = {
2457- type: Notification.SnapDecision,
2458- hints: {"x-canonical-private-affirmative-tint": "true"},
2459- summary: "Tom Ato",
2460- body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
2461- icon: "../graphics/avatars/funky.png",
2462- secondaryIcon: "../graphics/applicationIcons/facebook.png",
2463- actions: [{ id: "ok_id", label: "Ok"},
2464- { id: "cancel_id", label: "Cancel"},
2465- { id: "notreally_id", label: "Not really"},
2466- { id: "noway_id", label: "messages:No way"},
2467- { id: "nada_id", label: "messages:Nada"}]
2468- }
2469-
2470- mockModel.append(n)
2471- }
2472-
2473- function add2over1SnapDecisionNotification() {
2474- var n = {
2475- type: Notification.SnapDecision,
2476- hints: {"x-canonical-private-affirmative-tint": "true",},
2477- summary: "Theatre at Ferria Stadium",
2478- body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
2479- icon: "",
2480- secondaryIcon: "",
2481- actions: [{ id: "ok_id", label: "Ok"},
2482- { id: "snooze_id", label: "Snooze"},
2483- { id: "view_id", label: "View"}]
2484- }
2485-
2486- mockModel.append(n)
2487- }
2488-
2489- function addEphemeralNotification() {
2490- var n = {
2491- type: Notification.Ephemeral,
2492- summary: "Cole Raby",
2493- body: "I did not expect it to be that late.",
2494- icon: "../graphics/avatars/amanda.png",
2495- secondaryIcon: "../graphics/applicationIcons/facebook.png",
2496- actions: []
2497- }
2498-
2499- mockModel.append(n)
2500- }
2501-
2502- function addEphemeralNonShapedIconNotification() {
2503- var n = {
2504- type: Notification.Ephemeral,
2505- hints: {"x-canonical-non-shaped-icon": "true"},
2506- summary: "Contacts",
2507- body: "Synchronised contacts-database with cloud-storage.",
2508- icon: "../graphics/applicationIcons/contacts-app.png",
2509- secondaryIcon: "",
2510- actions: []
2511- }
2512-
2513- mockModel.append(n)
2514- }
2515-
2516- function addEphemeralIconSummaryNotification() {
2517- var n = {
2518- type: Notification.Ephemeral,
2519- hints: {"x-canonical-non-shaped-icon": "false"},
2520- summary: "Photo upload completed",
2521- body: "",
2522- icon: "../graphics/applicationIcons/facebook.png",
2523- secondaryIcon: "",
2524- actions: []
2525- }
2526-
2527- mockModel.append(n)
2528- }
2529-
2530- function addInteractiveNotification() {
2531- var n = {
2532- type: Notification.Interactive,
2533- summary: "Interactive notification",
2534- body: "This is a notification that can be clicked",
2535- icon: "../graphics/avatars/anna_olsson.png",
2536- secondaryIcon: "",
2537- actions: [{ id: "reply_id", label: "Dummy"}],
2538- }
2539-
2540- mockModel.append(n)
2541- }
2542-
2543- function addConfirmationNotification() {
2544- var n = {
2545- type: Notification.Confirmation,
2546- hints: {"x-canonical-non-shaped-icon": "true"},
2547- summary: "Confirmation notification",
2548- body: "",
2549- icon: "image://theme/audio-volume-medium",
2550- secondaryIcon: "",
2551- value: 50,
2552- actions: [],
2553- }
2554-
2555- mockModel.append(n)
2556- }
2557-
2558- function add2ndConfirmationNotification() {
2559- var n = {
2560- type: Notification.Confirmation,
2561- hints: {"x-canonical-non-shaped-icon": "true",
2562- "x-canonical-value-bar-tint": "true"},
2563- summary: "Confirmation notification",
2564- body: "High Volume",
2565- icon: "image://theme/audio-volume-high",
2566- secondaryIcon: "",
2567- value: 85,
2568- actions: [],
2569- }
2570-
2571- mockModel.append(n)
2572- }
2573-
2574- function clearNotifications() {
2575- while(mockModel.count > 1) {
2576- remove1stNotification()
2577- }
2578- }
2579-
2580- function remove1stNotification() {
2581- if (mockModel.count > 1)
2582- mockModel.remove(1)
2583- }
2584-
2585- Rectangle {
2586- id: notificationsRect
2587-
2588- width: units.gu(40)
2589- height: units.gu(115)
2590-
2591- MouseArea{
2592- id: clickThroughCatcher
2593-
2594- anchors.fill: parent
2595- }
2596-
2597- Notifications {
2598- id: notifications
2599-
2600- margin: units.gu(1)
2601-
2602- anchors.fill: parent
2603- model: mockModel
2604- }
2605- }
2606-
2607- Rectangle {
2608- id: interactiveControls
2609-
2610- width: units.gu(30)
2611- height: units.gu(115)
2612- color: "grey"
2613-
2614- Column {
2615- spacing: units.gu(1)
2616- anchors.fill: parent
2617- anchors.margins: units.gu(1)
2618-
2619- Button {
2620- width: parent.width
2621- text: "add a snap-decision"
2622- onClicked: addSnapDecisionNotification()
2623- }
2624-
2625- Button {
2626- width: parent.width
2627- text: "add a 2over1 snap-decision"
2628- onClicked: add2over1SnapDecisionNotification()
2629- }
2630-
2631- Button {
2632- width: parent.width
2633- text: "add an ephemeral"
2634- onClicked: addEphemeralNotification()
2635- }
2636-
2637- Button {
2638- width: parent.width
2639- text: "add an non-shaped-icon-summary-body"
2640- onClicked: addEphemeralNonShapedIconNotification()
2641- }
2642-
2643- Button {
2644- width: parent.width
2645- text: "add an icon-summary"
2646- onClicked: addEphemeralIconSummaryNotification()
2647- }
2648-
2649- Button {
2650- width: parent.width
2651- text: "add an interactive"
2652- onClicked: addInteractiveNotification()
2653- }
2654-
2655- Button {
2656- width: parent.width
2657- text: "add a confirmation"
2658- onClicked: addConfirmationNotification()
2659- }
2660-
2661- Button {
2662- width: parent.width
2663- text: "add a 2nd confirmation"
2664- onClicked: add2ndConfirmationNotification()
2665- }
2666-
2667- Button {
2668- width: parent.width
2669- text: "remove 1st notification"
2670- onClicked: remove1stNotification()
2671- }
2672-
2673- Button {
2674- width: parent.width
2675- text: "clear model"
2676- onClicked: clearNotifications()
2677- }
2678- }
2679- }
2680-
2681- UnityTestCase {
2682- id: root
2683- name: "NotificationRendererTest"
2684- when: windowShown
2685-
2686- function test_NotificationRenderer_data() {
2687- return [
2688- {
2689- tag: "Snap Decision with secondary icon and button-tint",
2690 type: Notification.SnapDecision,
2691 hints: {"x-canonical-private-affirmative-tint": "true"},
2692 summary: "Tom Ato",
2693@@ -307,23 +78,14 @@
2694 { id: "cancel_id", label: "Cancel"},
2695 { id: "notreally_id", label: "Not really"},
2696 { id: "noway_id", label: "messages:No way"},
2697- { id: "nada_id", label: "messages:Nada"}],
2698- summaryVisible: true,
2699- bodyVisible: true,
2700- iconVisible: true,
2701- centeredIconVisible: false,
2702- shaped: true,
2703- nonShaped: false,
2704- secondaryIconVisible: true,
2705- buttonRowVisible: true,
2706- buttonTinted: true,
2707- hasSound: false,
2708- valueVisible: false,
2709- valueLabelVisible: false,
2710- valueTinted: false
2711- },
2712- {
2713- tag: "2-over-1 Snap Decision with button-tint",
2714+ { id: "nada_id", label: "messages:Nada"}]
2715+ }
2716+
2717+ mockModel.append(n)
2718+ }
2719+
2720+ function add2over1SnapDecisionNotification() {
2721+ var n = {
2722 type: Notification.SnapDecision,
2723 hints: {"x-canonical-private-affirmative-tint": "true"},
2724 summary: "Theatre at Ferria Stadium",
2725@@ -332,327 +94,573 @@
2726 secondaryIcon: "",
2727 actions: [{ id: "ok_id", label: "Ok"},
2728 { id: "snooze_id", label: "Snooze"},
2729- { id: "view_id", label: "View"}],
2730- summaryVisible: true,
2731- bodyVisible: true,
2732- iconVisible: false,
2733- centeredIconVisible: false,
2734- shaped: false,
2735- secondaryIconVisible: false,
2736- buttonRowVisible: false,
2737- buttonTinted: true,
2738- hasSound: false,
2739- valueVisible: false,
2740- valueLabelVisible: false,
2741- valueTinted: false
2742- },
2743- {
2744- tag: "Ephemeral notification - icon-summary layout",
2745- type: Notification.Ephemeral,
2746- hints: {},
2747+ { id: "view_id", label: "View"}]
2748+ }
2749+
2750+ mockModel.append(n)
2751+ }
2752+
2753+ function addEphemeralNotification() {
2754+ var n = {
2755+ type: Notification.Ephemeral,
2756+ summary: "Cole Raby",
2757+ body: "I did not expect it to be that late.",
2758+ icon: "../graphics/avatars/amanda.png",
2759+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
2760+ actions: []
2761+ }
2762+
2763+ mockModel.append(n)
2764+ }
2765+
2766+ function addEphemeralNonShapedIconNotification() {
2767+ var n = {
2768+ type: Notification.Ephemeral,
2769+ hints: {"x-canonical-non-shaped-icon": "true"},
2770+ summary: "Contacts",
2771+ body: "Synchronised contacts-database with cloud-storage.",
2772+ icon: "../graphics/applicationIcons/contacts-app.png",
2773+ secondaryIcon: "",
2774+ actions: []
2775+ }
2776+
2777+ mockModel.append(n)
2778+ }
2779+
2780+ function addEphemeralIconSummaryNotification() {
2781+ var n = {
2782+ type: Notification.Ephemeral,
2783+ hints: {"x-canonical-non-shaped-icon": "false"},
2784 summary: "Photo upload completed",
2785 body: "",
2786 icon: "../graphics/applicationIcons/facebook.png",
2787 secondaryIcon: "",
2788- actions: [],
2789- summaryVisible: true,
2790- bodyVisible: false,
2791- iconVisible: true,
2792- centeredIconVisible: false,
2793- shaped: true,
2794- secondaryIconVisible: false,
2795- buttonRowVisible: false,
2796- buttonTinted: false,
2797- hasSound: false,
2798- valueVisible: false,
2799- valueLabelVisible: false,
2800- valueTinted: false
2801- },
2802- {
2803- tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
2804- type: Notification.Ephemeral,
2805- hints: {"x-canonical-private-affirmative-tint": "false",
2806- "sound-file": "dummy.ogg",
2807- "suppress-sound": "true"},
2808- summary: "New comment successfully published",
2809- body: "",
2810- icon: "",
2811- secondaryIcon: "../graphics/applicationIcons/facebook.png",
2812- actions: [],
2813- summaryVisible: true,
2814- bodyVisible: false,
2815- interactiveAreaEnabled: false,
2816- iconVisible: false,
2817- centeredIconVisible: false,
2818- shaped: false,
2819- secondaryIconVisible: true,
2820- buttonRowVisible: false,
2821- buttonTinted: false,
2822- hasSound: false,
2823- valueVisible: false,
2824- valueLabelVisible: false,
2825- valueTinted: false
2826- },
2827- {
2828- tag: "Interactive notification",
2829+ actions: []
2830+ }
2831+
2832+ mockModel.append(n)
2833+ }
2834+
2835+ function addInteractiveNotification() {
2836+ var n = {
2837 type: Notification.Interactive,
2838- hints: {"x-canonical-private-affirmative-tint": "false",
2839- "sound-file": "dummy.ogg"},
2840 summary: "Interactive notification",
2841 body: "This is a notification that can be clicked",
2842- icon: "../graphics/avatars/amanda.png",
2843+ icon: "../graphics/avatars/anna_olsson.png",
2844 secondaryIcon: "",
2845 actions: [{ id: "reply_id", label: "Dummy"}],
2846- summaryVisible: true,
2847- bodyVisible: true,
2848- iconVisible: true,
2849- centeredIconVisible: false,
2850- shaped: true,
2851- secondaryIconVisible: false,
2852- buttonRowVisible: false,
2853- buttonTinted: false,
2854- hasSound: true,
2855- valueVisible: false,
2856- valueLabelVisible: false,
2857- valueTinted: false
2858- },
2859- {
2860- tag: "Snap Decision without secondary icon and no button-tint",
2861- type: Notification.SnapDecision,
2862- hints: {"x-canonical-private-affirmative-tint": "false",
2863- "sound-file": "dummy.ogg"},
2864- summary: "Bro Coly",
2865- body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
2866- icon: "../graphics/avatars/anna_olsson.png",
2867- secondaryIcon: "",
2868- actions: [{ id: "accept_id", label: "Accept"},
2869- { id: "reject_id", label: "Reject"}],
2870- summaryVisible: true,
2871- bodyVisible: true,
2872- iconVisible: true,
2873- centeredIconVisible: false,
2874- shaped: true,
2875- secondaryIconVisible: false,
2876- buttonRowVisible: true,
2877- buttonTinted: false,
2878- hasSound: true,
2879- valueVisible: false,
2880- valueLabelVisible: false,
2881- valueTinted: false
2882- },
2883- {
2884- tag: "Ephemeral notification",
2885- type: Notification.Ephemeral,
2886- hints: {"x-canonical-private-affirmative-tint": "false",
2887- "sound-file": "dummy.ogg"},
2888- summary: "Cole Raby",
2889- body: "I did not expect it to be that late.",
2890- icon: "../graphics/avatars/funky.png",
2891- secondaryIcon: "../graphics/applicationIcons/facebook.png",
2892- actions: [],
2893- summaryVisible: true,
2894- bodyVisible: true,
2895- iconVisible: true,
2896- centeredIconVisible: false,
2897- shaped: true,
2898- secondaryIconVisible: true,
2899- buttonRowVisible: false,
2900- buttonTinted: false,
2901- hasSound: true,
2902- valueVisible: false,
2903- valueLabelVisible: false,
2904- valueTinted: false
2905- },
2906- {
2907- tag: "Ephemeral notification with non-shaped icon",
2908- type: Notification.Ephemeral,
2909- hints: {"x-canonical-private-affirmative-tint": "false",
2910- "x-canonical-non-shaped-icon": "true"},
2911- summary: "Contacts",
2912- body: "Synchronised contacts-database with cloud-storage.",
2913- icon: "image://theme/contacts-app",
2914- secondaryIcon: "",
2915- actions: [],
2916- summaryVisible: true,
2917- bodyVisible: true,
2918- iconVisible: true,
2919- centeredIconVisible: false,
2920- shaped: false,
2921- secondaryIconVisible: false,
2922- buttonRowVisible: false,
2923- buttonTinted: false,
2924- hasSound: false,
2925- valueVisible: false,
2926- valueLabelVisible: false,
2927- valueTinted: false
2928- },
2929- {
2930- tag: "Confirmation notification with value",
2931+ }
2932+
2933+ mockModel.append(n)
2934+ }
2935+
2936+ function addConfirmationNotification() {
2937+ var n = {
2938 type: Notification.Confirmation,
2939 hints: {"x-canonical-non-shaped-icon": "true"},
2940- summary: "",
2941+ summary: "Confirmation notification",
2942 body: "",
2943 icon: "image://theme/audio-volume-medium",
2944 secondaryIcon: "",
2945 value: 50,
2946 actions: [],
2947- summaryVisible: false,
2948- bodyVisible: false,
2949- iconVisible: false,
2950- centeredIconVisible: true,
2951- shaped: false,
2952- secondaryIconVisible: false,
2953- buttonRowVisible: false,
2954- buttonTinted: false,
2955- hasSound: false,
2956- valueVisible: true,
2957- valueLabelVisible: false,
2958- valueTinted: false
2959- },
2960- {
2961- tag: "Confirmation notification with value, label and tint",
2962+ }
2963+
2964+ mockModel.append(n)
2965+ }
2966+
2967+ function add2ndConfirmationNotification() {
2968+ var n = {
2969 type: Notification.Confirmation,
2970 hints: {"x-canonical-non-shaped-icon": "true",
2971- "x-canonical-value-bar-tint" : "true"},
2972- summary: "",
2973+ "x-canonical-value-bar-tint": "true"},
2974+ summary: "Confirmation notification",
2975 body: "High Volume",
2976 icon: "image://theme/audio-volume-high",
2977 secondaryIcon: "",
2978 value: 85,
2979 actions: [],
2980- summaryVisible: false,
2981- bodyVisible: false,
2982- iconVisible: false,
2983- centeredIconVisible: true,
2984- shaped: false,
2985- secondaryIconVisible: false,
2986- buttonRowVisible: false,
2987- buttonTinted: false,
2988- hasSound: false,
2989- valueVisible: true,
2990- valueLabelVisible: true,
2991- valueTinted: true
2992- }
2993- ]
2994- }
2995-
2996- SignalSpy {
2997- id: clickThroughSpy
2998-
2999- target: clickThroughCatcher
3000- signalName: "clicked"
3001- }
3002-
3003- SignalSpy {
3004- id: actionSpy
3005-
3006- target: mockModel
3007- signalName: "actionInvoked"
3008- }
3009-
3010- function cleanup() {
3011- clickThroughSpy.clear()
3012- actionSpy.clear()
3013- }
3014-
3015- function test_NotificationRenderer(data) {
3016- // populate model with some mock notifications
3017- mockModel.append(data)
3018-
3019- // make sure the view is properly updated before going on
3020- notifications.forceLayout();
3021- waitForRendering(notifications);
3022-
3023- var notification = findChild(notifications, "notification" + (mockModel.count - 1))
3024- verify(notification !== undefined, "notification wasn't found");
3025-
3026- waitForRendering(notification);
3027-
3028- var icon = findChild(notification, "icon")
3029- var centeredIcon = findChild(notification, "centeredIcon")
3030- var interactiveArea = findChild(notification, "interactiveArea")
3031- var secondaryIcon = findChild(notification, "secondaryIcon")
3032- var summaryLabel = findChild(notification, "summaryLabel")
3033- var bodyLabel = findChild(notification, "bodyLabel")
3034- var buttonRow = findChild(notification, "buttonRow")
3035- var valueIndicator = findChild(notification, "valueIndicator")
3036- var valueLabel = findChild(notification, "valueLabel")
3037- var innerBar = findChild(notification, "innerBar")
3038-
3039- compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
3040- if (icon.visible) {
3041- compare(icon.shaped, data.shaped, "shaped-status is incorrect")
3042- }
3043- compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")
3044- if (centeredIcon.visible) {
3045- compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
3046- }
3047- compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
3048- if (valueIndicator.visible) {
3049- verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
3050- }
3051- compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
3052-
3053- // test input does not fall through
3054- mouseClick(notification, notification.width / 2, notification.height / 2)
3055- if(data.type == Notification.Interactive) {
3056- actionSpy.wait()
3057- compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
3058- }
3059- compare(clickThroughSpy.count, 0, "click on notification fell through")
3060-
3061- compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
3062- compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
3063- compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
3064- compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
3065-
3066- var audioItem = findInvisibleChild(notification, "sound")
3067- compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
3068-
3069- if(data.buttonRowVisible) {
3070- var buttonCancel = findChild(buttonRow, "notify_button1")
3071- var buttonAccept = findChild(buttonRow, "notify_button0")
3072-
3073- // only test the left/cancel-button if two actions have been passed in
3074- if (data.actions.length == 2) {
3075- tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);
3076- compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
3077- actionSpy.clear()
3078- }
3079-
3080- // check the tinting of the positive/right button
3081- verify(buttonAccept.color === data.buttonTinted ? "#3fb24f" : "#dddddd", "button has the wrong color-tint")
3082-
3083- // click the positive/right button
3084- tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);
3085- compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
3086+ }
3087+
3088+ mockModel.append(n)
3089+ }
3090+
3091+ function clearNotifications() {
3092+ while(mockModel.count > 1) {
3093+ remove1stNotification()
3094+ }
3095+ }
3096+
3097+ function remove1stNotification() {
3098+ if (mockModel.count > 1)
3099+ mockModel.remove(1)
3100+ }
3101+
3102+ Rectangle {
3103+ id: notificationsRect
3104+
3105+ width: units.gu(40)
3106+ height: units.gu(115)
3107+
3108+ MouseArea{
3109+ id: clickThroughCatcher
3110+
3111+ anchors.fill: parent
3112+ }
3113+
3114+ Notifications {
3115+ id: notifications
3116+
3117+ margin: units.gu(1)
3118+
3119+ anchors.fill: parent
3120+ model: mockModel
3121+ }
3122+ }
3123+
3124+ Rectangle {
3125+ id: interactiveControls
3126+
3127+ width: units.gu(30)
3128+ height: units.gu(115)
3129+ color: "grey"
3130+
3131+ Column {
3132+ spacing: units.gu(1)
3133+ anchors.fill: parent
3134+ anchors.margins: units.gu(1)
3135+
3136+ Button {
3137+ width: parent.width
3138+ text: "add a snap-decision"
3139+ onClicked: rootRow.addSnapDecisionNotification()
3140+ }
3141+
3142+ Button {
3143+ width: parent.width
3144+ text: "add a 2over1 snap-decision"
3145+ onClicked: rootRow.add2over1SnapDecisionNotification()
3146+ }
3147+
3148+ Button {
3149+ width: parent.width
3150+ text: "add an ephemeral"
3151+ onClicked: rootRow.addEphemeralNotification()
3152+ }
3153+
3154+ Button {
3155+ width: parent.width
3156+ text: "add an non-shaped-icon-summary-body"
3157+ onClicked: rootRow.addEphemeralNonShapedIconNotification()
3158+ }
3159+
3160+ Button {
3161+ width: parent.width
3162+ text: "add an icon-summary"
3163+ onClicked: rootRow.addEphemeralIconSummaryNotification()
3164+ }
3165+
3166+ Button {
3167+ width: parent.width
3168+ text: "add an interactive"
3169+ onClicked: rootRow.addInteractiveNotification()
3170+ }
3171+
3172+ Button {
3173+ width: parent.width
3174+ text: "add a confirmation"
3175+ onClicked: rootRow.addConfirmationNotification()
3176+ }
3177+
3178+ Button {
3179+ width: parent.width
3180+ text: "add a 2nd confirmation"
3181+ onClicked: rootRow.add2ndConfirmationNotification()
3182+ }
3183+
3184+ Button {
3185+ width: parent.width
3186+ text: "remove 1st notification"
3187+ onClicked: rootRow.remove1stNotification()
3188+ }
3189+
3190+ Button {
3191+ width: parent.width
3192+ text: "clear model"
3193+ onClicked: rootRow.clearNotifications()
3194+ }
3195+ }
3196+ }
3197+
3198+ ActionModel {
3199+ id: myActionModel
3200+ }
3201+
3202+ UnityTestCase {
3203+ id: root
3204+ name: "NotificationRendererTest"
3205+ when: windowShown
3206+
3207+ function test_NotificationRenderer_data() {
3208+ return [
3209+ {
3210+ tag: "Snap Decision with secondary icon and button-tint",
3211+ type: Notification.SnapDecision,
3212+ hints: {"x-canonical-private-affirmative-tint": "true"},
3213+ summary: "Tom Ato",
3214+ body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
3215+ icon: "../graphics/avatars/funky.png",
3216+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
3217+ actions: [{ id: "ok_id", label: "Ok"},
3218+ { id: "cancel_id", label: "Cancel"},
3219+ { id: "notreally_id", label: "Not really"},
3220+ { id: "noway_id", label: "messages:No way"},
3221+ { id: "nada_id", label: "messages:Nada"}],
3222+ summaryVisible: true,
3223+ bodyVisible: true,
3224+ iconVisible: true,
3225+ centeredIconVisible: false,
3226+ shaped: true,
3227+ secondaryIconVisible: true,
3228+ buttonRowVisible: true,
3229+ buttonTinted: true,
3230+ hasSound: false,
3231+ valueVisible: false,
3232+ valueLabelVisible: false,
3233+ valueTinted: false
3234+ },
3235+ {
3236+ tag: "2-over-1 Snap Decision with button-tint",
3237+ type: Notification.SnapDecision,
3238+ hints: {"x-canonical-private-affirmative-tint": "true"},
3239+ summary: "Theatre at Ferria Stadium",
3240+ body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
3241+ icon: "",
3242+ secondaryIcon: "",
3243+ actions: [{ id: "ok_id", label: "Ok"},
3244+ { id: "snooze_id", label: "Snooze"},
3245+ { id: "view_id", label: "View"}],
3246+ summaryVisible: true,
3247+ bodyVisible: true,
3248+ iconVisible: false,
3249+ centeredIconVisible: false,
3250+ shaped: false,
3251+ secondaryIconVisible: false,
3252+ buttonRowVisible: false,
3253+ buttonTinted: true,
3254+ hasSound: false,
3255+ valueVisible: false,
3256+ valueLabelVisible: false,
3257+ valueTinted: false
3258+ },
3259+ {
3260+ tag: "Ephemeral notification - icon-summary layout",
3261+ type: Notification.Ephemeral,
3262+ hints: {},
3263+ summary: "Photo upload completed",
3264+ body: "",
3265+ icon: "../graphics/applicationIcons/facebook.png",
3266+ secondaryIcon: "",
3267+ actions: [],
3268+ summaryVisible: true,
3269+ bodyVisible: false,
3270+ iconVisible: true,
3271+ centeredIconVisible: false,
3272+ shaped: true,
3273+ secondaryIconVisible: false,
3274+ buttonRowVisible: false,
3275+ buttonTinted: false,
3276+ hasSound: false,
3277+ valueVisible: false,
3278+ valueLabelVisible: false,
3279+ valueTinted: false
3280+ },
3281+ {
3282+ tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
3283+ type: Notification.Ephemeral,
3284+ hints: {"x-canonical-private-affirmative-tint": "false",
3285+ "sound-file": "dummy.ogg",
3286+ "suppress-sound": "true"},
3287+ summary: "New comment successfully published",
3288+ body: "",
3289+ icon: "",
3290+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
3291+ actions: [],
3292+ summaryVisible: true,
3293+ bodyVisible: false,
3294+ interactiveAreaEnabled: false,
3295+ iconVisible: false,
3296+ centeredIconVisible: false,
3297+ shaped: false,
3298+ secondaryIconVisible: true,
3299+ buttonRowVisible: false,
3300+ buttonTinted: false,
3301+ hasSound: false,
3302+ valueVisible: false,
3303+ valueLabelVisible: false,
3304+ valueTinted: false
3305+ },
3306+ {
3307+ tag: "Interactive notification",
3308+ type: Notification.Interactive,
3309+ hints: {"x-canonical-private-affirmative-tint": "false",
3310+ "sound-file": "dummy.ogg"},
3311+ summary: "Interactive notification",
3312+ body: "This is a notification that can be clicked",
3313+ icon: "../graphics/avatars/amanda.png",
3314+ secondaryIcon: "",
3315+ actions: [{ id: "reply_id", label: "Dummy"}],
3316+ summaryVisible: true,
3317+ bodyVisible: true,
3318+ iconVisible: true,
3319+ centeredIconVisible: false,
3320+ shaped: true,
3321+ secondaryIconVisible: false,
3322+ buttonRowVisible: false,
3323+ buttonTinted: false,
3324+ hasSound: true,
3325+ valueVisible: false,
3326+ valueLabelVisible: false,
3327+ valueTinted: false
3328+ },
3329+ {
3330+ tag: "Snap Decision without secondary icon and no button-tint",
3331+ type: Notification.SnapDecision,
3332+ hints: {"x-canonical-private-affirmative-tint": "false",
3333+ "sound-file": "dummy.ogg"},
3334+ summary: "Bro Coly",
3335+ body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
3336+ icon: "../graphics/avatars/anna_olsson.png",
3337+ secondaryIcon: "",
3338+ actions: [{ id: "accept_id", label: "Accept"},
3339+ { id: "reject_id", label: "Reject"}],
3340+ summaryVisible: true,
3341+ bodyVisible: true,
3342+ iconVisible: true,
3343+ centeredIconVisible: false,
3344+ shaped: true,
3345+ secondaryIconVisible: false,
3346+ buttonRowVisible: true,
3347+ buttonTinted: false,
3348+ hasSound: true,
3349+ valueVisible: false,
3350+ valueLabelVisible: false,
3351+ valueTinted: false
3352+ },
3353+ {
3354+ tag: "Ephemeral notification",
3355+ type: Notification.Ephemeral,
3356+ hints: {"x-canonical-private-affirmative-tint": "false",
3357+ "sound-file": "dummy.ogg"},
3358+ summary: "Cole Raby",
3359+ body: "I did not expect it to be that late.",
3360+ icon: "../graphics/avatars/funky.png",
3361+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
3362+ actions: [],
3363+ summaryVisible: true,
3364+ bodyVisible: true,
3365+ iconVisible: true,
3366+ centeredIconVisible: false,
3367+ shaped: true,
3368+ secondaryIconVisible: true,
3369+ buttonRowVisible: false,
3370+ buttonTinted: false,
3371+ hasSound: true,
3372+ valueVisible: false,
3373+ valueLabelVisible: false,
3374+ valueTinted: false
3375+ },
3376+ {
3377+ tag: "Ephemeral notification with non-shaped icon",
3378+ type: Notification.Ephemeral,
3379+ hints: {"x-canonical-private-affirmative-tint": "false",
3380+ "x-canonical-non-shaped-icon": "true"},
3381+ summary: "Contacts",
3382+ body: "Synchronised contacts-database with cloud-storage.",
3383+ icon: "image://theme/contacts-app",
3384+ secondaryIcon: "",
3385+ actions: [],
3386+ summaryVisible: true,
3387+ bodyVisible: true,
3388+ iconVisible: true,
3389+ centeredIconVisible: false,
3390+ shaped: false,
3391+ secondaryIconVisible: false,
3392+ buttonRowVisible: false,
3393+ buttonTinted: false,
3394+ hasSound: false,
3395+ valueVisible: false,
3396+ valueLabelVisible: false,
3397+ valueTinted: false
3398+ },
3399+ {
3400+ tag: "Confirmation notification with value",
3401+ type: Notification.Confirmation,
3402+ hints: {"x-canonical-non-shaped-icon": "true"},
3403+ summary: "",
3404+ body: "",
3405+ icon: "image://theme/audio-volume-medium",
3406+ secondaryIcon: "",
3407+ value: 50,
3408+ actions: [],
3409+ summaryVisible: false,
3410+ bodyVisible: false,
3411+ iconVisible: false,
3412+ centeredIconVisible: true,
3413+ shaped: false,
3414+ secondaryIconVisible: false,
3415+ buttonRowVisible: false,
3416+ buttonTinted: false,
3417+ hasSound: false,
3418+ valueVisible: true,
3419+ valueLabelVisible: false,
3420+ valueTinted: false
3421+ },
3422+ {
3423+ tag: "Confirmation notification with value, label and tint",
3424+ type: Notification.Confirmation,
3425+ hints: {"x-canonical-non-shaped-icon": "true",
3426+ "x-canonical-value-bar-tint" : "true"},
3427+ summary: "",
3428+ body: "High Volume",
3429+ icon: "image://theme/audio-volume-high",
3430+ secondaryIcon: "",
3431+ value: 85,
3432+ actions: [],
3433+ summaryVisible: false,
3434+ bodyVisible: false,
3435+ iconVisible: false,
3436+ centeredIconVisible: true,
3437+ shaped: false,
3438+ secondaryIconVisible: false,
3439+ buttonRowVisible: false,
3440+ buttonTinted: false,
3441+ hasSound: false,
3442+ valueVisible: true,
3443+ valueLabelVisible: true,
3444+ valueTinted: true
3445+ }
3446+ ]
3447+ }
3448+
3449+ SignalSpy {
3450+ id: clickThroughSpy
3451+
3452+ target: clickThroughCatcher
3453+ signalName: "clicked"
3454+ }
3455+
3456+ SignalSpy {
3457+ id: actionSpy
3458+
3459+ target: mockModel
3460+ signalName: "actionInvoked"
3461+ }
3462+
3463+ function cleanup() {
3464+ clickThroughSpy.clear()
3465 actionSpy.clear()
3466- waitForRendering(notification)
3467-
3468- // check if there's a ComboButton created due to more actions being passed
3469- if (data.actions.length > 2) {
3470- var comboButton = findChild(notification, "notify_button2")
3471- tryCompareFunction(function() { return comboButton.expanded == false; }, true);
3472-
3473- // click to expand
3474- tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
3475-
3476- // try clicking on choices in expanded comboList
3477- var choiceButton1 = findChild(notification, "notify_button3")
3478- tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);
3479- compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
3480- actionSpy.clear()
3481-
3482- var choiceButton2 = findChild(notification, "notify_button4")
3483- tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);
3484- compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
3485- actionSpy.clear()
3486-
3487- // click to collapse
3488- //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
3489- } else {
3490- mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
3491- compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
3492+ }
3493+
3494+ function test_NotificationRenderer(data) {
3495+ // populate model with some mock notifications
3496+ mockModel.append(data)
3497+
3498+ // make sure the view is properly updated before going on
3499+ notifications.forceLayout();
3500+ waitForRendering(notifications);
3501+
3502+ var notification = findChild(notifications, "notification" + (mockModel.count - 1))
3503+ verify(notification !== undefined, "notification wasn't found");
3504+
3505+ waitForRendering(notification);
3506+
3507+ var icon = findChild(notification, "icon")
3508+ var centeredIcon = findChild(notification, "centeredIcon")
3509+ var interactiveArea = findChild(notification, "interactiveArea")
3510+ var secondaryIcon = findChild(notification, "secondaryIcon")
3511+ var summaryLabel = findChild(notification, "summaryLabel")
3512+ var bodyLabel = findChild(notification, "bodyLabel")
3513+ var buttonRow = findChild(notification, "buttonRow")
3514+ var valueIndicator = findChild(notification, "valueIndicator")
3515+ var valueLabel = findChild(notification, "valueLabel")
3516+ var innerBar = findChild(notification, "innerBar")
3517+
3518+ compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
3519+ if (icon.visible) {
3520+ compare(icon.shaped, data.shaped, "shaped-status is incorrect")
3521+ }
3522+ compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")
3523+ if (centeredIcon.visible) {
3524+ compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
3525+ }
3526+ compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
3527+ if (valueIndicator.visible) {
3528+ verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
3529+ }
3530+ compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
3531+
3532+ // test input does not fall through
3533+ mouseClick(notification, notification.width / 2, notification.height / 2)
3534+ if(data.type == Notification.Interactive) {
3535+ actionSpy.wait()
3536+ compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
3537+ }
3538+ compare(clickThroughSpy.count, 0, "click on notification fell through")
3539+
3540+ compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
3541+ compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
3542+ compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
3543+ compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
3544+
3545+ var audioItem = findInvisibleChild(notification, "sound")
3546+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
3547+
3548+ if(data.buttonRowVisible) {
3549+ var buttonCancel = findChild(buttonRow, "notify_button1")
3550+ var buttonAccept = findChild(buttonRow, "notify_button0")
3551+
3552+ // only test the left/cancel-button if two actions have been passed in
3553+ if (data.actions.length == 2) {
3554+ tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);
3555+ compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
3556+ actionSpy.clear()
3557+ }
3558+
3559+ // check the tinting of the positive/right button
3560+ verify(buttonAccept.color === data.buttonTinted ? "#3fb24f" : "#dddddd", "button has the wrong color-tint")
3561+
3562+ // click the positive/right button
3563+ tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);
3564+ compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
3565+ actionSpy.clear()
3566+ waitForRendering (notification)
3567+
3568+ // check if there's a ComboButton created due to more actions being passed
3569+ if (data.actions.length > 2) {
3570+ var comboButton = findChild(notification, "notify_button2")
3571+ tryCompareFunction(function() { return comboButton.expanded == false; }, true);
3572+
3573+ // click to expand
3574+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
3575+
3576+ // try clicking on choices in expanded comboList
3577+ var choiceButton1 = findChild(notification, "notify_button3")
3578+ tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);
3579+ compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
3580+ actionSpy.clear()
3581+
3582+ var choiceButton2 = findChild(notification, "notify_button4")
3583+ tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);
3584+ compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
3585+ actionSpy.clear()
3586+
3587+ // click to collapse
3588+ //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
3589+ } else {
3590+ mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
3591+ compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
3592+ }
3593 }
3594 }
3595 }
3596
3597=== added file 'tests/qmltests/Notifications/tst_SwipeToAct.qml'
3598--- tests/qmltests/Notifications/tst_SwipeToAct.qml 1970-01-01 00:00:00 +0000
3599+++ tests/qmltests/Notifications/tst_SwipeToAct.qml 2014-11-05 14:37:57 +0000
3600@@ -0,0 +1,276 @@
3601+/*
3602+ * Copyright (C) 2014 Canonical, Ltd.
3603+ *
3604+ * This program is free software; you can redistribute it and/or modify
3605+ * it under the terms of the GNU General Public License as published by
3606+ * the Free Software Foundation; version 3.
3607+ *
3608+ * This program is distributed in the hope that it will be useful,
3609+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3610+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3611+ * GNU General Public License for more details.
3612+ *
3613+ * You should have received a copy of the GNU General Public License
3614+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3615+ */
3616+
3617+import QtQuick 2.0
3618+import QtTest 1.0
3619+import ".."
3620+import "../../../qml/Notifications"
3621+import Ubuntu.Components 0.1
3622+import Unity.Test 0.1
3623+import Unity.Notifications 1.0
3624+
3625+Item {
3626+ width: notificationsRect.width + interactiveControls.width
3627+ height: notificationsRect.height
3628+
3629+ Row {
3630+ id: rootRow
3631+
3632+ Component {
3633+ id: mockNotification
3634+
3635+ QtObject {
3636+ function invokeAction(actionId) {
3637+ mockModel.actionInvoked(actionId)
3638+ }
3639+ }
3640+ }
3641+
3642+ ListModel {
3643+ id: mockModel
3644+ dynamicRoles: true
3645+
3646+ signal actionInvoked(string actionId)
3647+
3648+ function getRaw(id) {
3649+ return mockNotification.createObject(mockModel)
3650+ }
3651+
3652+ // add the default/PlaceHolder notification to the model
3653+ Component.onCompleted: {
3654+ var n = {
3655+ type: Notification.PlaceHolder,
3656+ hints: {},
3657+ summary: "",
3658+ body: "",
3659+ icon: "",
3660+ secondaryIcon: "",
3661+ actions: []
3662+ }
3663+
3664+ append(n)
3665+ }
3666+ }
3667+
3668+ function addSwipeToActNotification() {
3669+ var n = {
3670+ type: Notification.SnapDecision,
3671+ hints: {"x-canonical-snap-decisions-swipe": "true"},
3672+ summary: "Incoming call",
3673+ body: "Frank Zappa\n+44 (0)7736 027340",
3674+ icon: "../graphics/avatars/amanda.png",
3675+ secondaryIcon: "incoming-call",
3676+ actions: [{ id: "ok_id", label: "Ok"},
3677+ { id: "cancel_id", label: "Cancel"}]
3678+ }
3679+
3680+ mockModel.append(n)
3681+ }
3682+
3683+ function clearNotifications() {
3684+ mockModel.clear()
3685+ }
3686+
3687+ function remove1stNotification() {
3688+ if (mockModel.count > 0)
3689+ mockModel.remove(0)
3690+ }
3691+
3692+ Rectangle {
3693+ id: notificationsRect
3694+
3695+ width: units.gu(40)
3696+ height: units.gu(71)
3697+
3698+ MouseArea{
3699+ id: clickThroughCatcher
3700+
3701+ anchors.fill: parent
3702+ }
3703+
3704+ Notifications {
3705+ id: notifications
3706+
3707+ margin: units.gu(1)
3708+
3709+ anchors.fill: parent
3710+ model: mockModel
3711+ }
3712+ }
3713+
3714+ Rectangle {
3715+ id: interactiveControls
3716+
3717+ width: units.gu(30)
3718+ height: units.gu(81)
3719+ color: "grey"
3720+
3721+ Column {
3722+ spacing: units.gu(1)
3723+ anchors.fill: parent
3724+ anchors.margins: units.gu(1)
3725+
3726+ Button {
3727+ width: parent.width
3728+ text: "add a SwipeToAct snap-decision"
3729+ onClicked: rootRow.addSwipeToActNotification()
3730+ }
3731+
3732+ Button {
3733+ width: parent.width
3734+ text: "remove 1st notification"
3735+ onClicked: rootRow.remove1stNotification()
3736+ }
3737+
3738+ Button {
3739+ width: parent.width
3740+ text: "clear model"
3741+ onClicked: rootRow.clearNotifications()
3742+ }
3743+ }
3744+ }
3745+
3746+ ActionModel {
3747+ id: myActionModel
3748+ }
3749+
3750+ UnityTestCase {
3751+ id: root
3752+ name: "NotificationRendererTest"
3753+ when: windowShown
3754+
3755+ function test_NotificationRenderer_data() {
3756+ return [
3757+ {
3758+ tag: "Snap Decision with SwipeToAct-widget (accept)",
3759+ type: Notification.SnapDecision,
3760+ hints: {"x-canonical-snap-decisions-swipe": "true"},
3761+ summary: "Incoming call",
3762+ body: "Frank Zappa\n+44 (0)7736 027340",
3763+ icon: "../graphics/avatars/amanda.png",
3764+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
3765+ actions: myActionModel,
3766+ summaryVisible: true,
3767+ bodyVisible: true,
3768+ iconVisible: true,
3769+ shaped: true,
3770+ secondaryIconVisible: true,
3771+ buttonRowVisible: true,
3772+ buttonTinted: false,
3773+ checkSwipeToActAccept: true,
3774+ checkSwipeToActReject: false
3775+ },
3776+ {
3777+ tag: "Snap Decision with SwipeToAct-widget (reject)",
3778+ type: Notification.SnapDecision,
3779+ hints: {"x-canonical-snap-decisions-swipe": "true"},
3780+ summary: "Incoming call",
3781+ body: "Bro Coly\n+49 (0)221 426973",
3782+ icon: "../graphics/avatars/funky.png",
3783+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
3784+ actions: myActionModel,
3785+ summaryVisible: true,
3786+ bodyVisible: true,
3787+ iconVisible: true,
3788+ shaped: true,
3789+ secondaryIconVisible: true,
3790+ buttonRowVisible: true,
3791+ buttonTinted: false,
3792+ checkSwipeToActAccept: false,
3793+ checkSwipeToActReject: true
3794+ }
3795+ ]
3796+ }
3797+
3798+ SignalSpy {
3799+ id: clickThroughSpy
3800+
3801+ target: clickThroughCatcher
3802+ signalName: "clicked"
3803+ }
3804+
3805+ SignalSpy {
3806+ id: actionSpy
3807+
3808+ target: mockModel
3809+ signalName: "actionInvoked"
3810+ }
3811+
3812+ function cleanup() {
3813+ clickThroughSpy.clear()
3814+ actionSpy.clear()
3815+ }
3816+
3817+ function test_NotificationRenderer(data) {
3818+ // populate model with some mock notifications
3819+ mockModel.append(data)
3820+
3821+ // make sure the view is properly updated before going on
3822+ notifications.forceLayout();
3823+ waitForRendering(notifications);
3824+
3825+ var notification = findChild(notifications, "notification" + (mockModel.count - 1))
3826+ verify(notification !== undefined, "notification wasn't found");
3827+
3828+ waitForRendering(notification);
3829+
3830+ var icon = findChild(notification, "icon")
3831+ var interactiveArea = findChild(notification, "interactiveArea")
3832+ var secondaryIcon = findChild(notification, "secondaryIcon")
3833+ var summaryLabel = findChild(notification, "summaryLabel")
3834+ var bodyLabel = findChild(notification, "bodyLabel")
3835+ var buttonRow = findChild(notification, "buttonRow")
3836+
3837+ compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
3838+ if (icon.visible) {
3839+ compare(icon.shaped, data.shaped, "shaped-status is incorrect")
3840+ }
3841+
3842+ // test input does not fall through
3843+ mouseClick(notification, notification.width / 2, notification.height / 2)
3844+ if(data.type == Notification.Interactive) {
3845+ actionSpy.wait()
3846+ compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
3847+ }
3848+ compare(clickThroughSpy.count, 0, "click on notification fell through")
3849+
3850+ compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
3851+ compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
3852+ compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
3853+ compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
3854+
3855+ if(data.buttonRowVisible) {
3856+ var swipeButton = findChild(buttonRow, "notify_swipe_button")
3857+ var slider = findChild(swipeButton, "slider")
3858+ var swipeMouseArea = findChild(swipeButton, "swipeMouseArea")
3859+ var x = swipeMouseArea.width / 2
3860+ var y = swipeMouseArea.height / 2
3861+
3862+ if(data.checkSwipeToActAccept) {
3863+ tryCompareFunction(function() { mouseDrag(slider, x, y, (swipeMouseArea.width / 2) - slider.width, 0); return actionSpy.signalArguments.length > 0; }, true);
3864+ compare(actionSpy.signalArguments[0][0], data.actions.data(0, ActionModel.RoleActionId), "got wrong id for positive action")
3865+ actionSpy.clear()
3866+ }
3867+ if(data.checkSwipeToActReject) {
3868+ tryCompareFunction(function() { mouseDrag(slider, x, y, -(swipeMouseArea.width / 2), 0); return actionSpy.signalArguments.length > 0; }, true);
3869+ compare(actionSpy.signalArguments[0][0], data.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
3870+ actionSpy.clear()
3871+ }
3872+ }
3873+ }
3874+ }
3875+ }
3876+}
3877
3878=== modified file 'tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml'
3879--- tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml 2014-09-29 15:04:42 +0000
3880+++ tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml 2014-11-05 14:37:57 +0000
3881@@ -740,7 +740,7 @@
3882 compare(loader.item.enabled, data.enabled, "Enabled does not match data");
3883 }
3884
3885- function test_lp1336715_broken_checkbox_bindings() {
3886+ function test_lp1336715_broken_switch_bindings() {
3887 menuData.type = "com.canonical.indicator.switch";
3888 menuData.sensitive = true;
3889 menuData.isToggled = false;
3890@@ -758,5 +758,21 @@
3891
3892 compare(loader.item.checked, false, "Server updates no longer working");
3893 }
3894+
3895+ // test that the server value is re-aserted if it is not confirmed.
3896+ function test_lp1336715_switch_server_value_reassertion() {
3897+ menuData.type = "com.canonical.indicator.switch";
3898+ menuData.sensitive = true;
3899+ menuData.isToggled = false;
3900+
3901+ loader.data = menuData;
3902+ loader.sourceComponent = factory.load(menuData);
3903+
3904+ compare(loader.item.checked, false, "Loader did not load check state");
3905+ mouseClick(loader.item,
3906+ loader.item.width / 2, loader.item.height / 2);
3907+ compare(loader.item.checked, true, "Clicking switch menu should toggle check");
3908+ tryCompare(loader.item, "checked", false);
3909+ }
3910 }
3911 }
3912
3913=== modified file 'tests/qmltests/Panel/tst_IndicatorPage.qml'
3914--- tests/qmltests/Panel/tst_IndicatorPage.qml 2014-10-17 14:55:35 +0000
3915+++ tests/qmltests/Panel/tst_IndicatorPage.qml 2014-11-05 14:37:57 +0000
3916@@ -18,6 +18,7 @@
3917 import QtTest 1.0
3918 import Unity.Test 0.1 as UT
3919 import Unity.Indicators 0.1 as Indicators
3920+import Ubuntu.Settings.Menus 0.1 as Menus
3921 import "../../../qml/Panel"
3922
3923 Item {
3924@@ -33,8 +34,24 @@
3925 busName: "com.caninical.indicator.test"
3926 actionsObjectPath: "/com/canonical/indicator/test"
3927 menuObjectPath: "/com/canonical/indicator/test"
3928+
3929+ factory {
3930+ _map: {
3931+ "default": {
3932+ "com.canonical.indicator.test" : testMenu
3933+ }
3934+ }
3935+ }
3936 }
3937
3938+ Component {
3939+ id: testMenu
3940+ Menus.StandardMenu {
3941+ signal menuSelected
3942+ signal menuDeselected
3943+ }
3944+ }
3945+
3946 property var fullMenuData: [{
3947 "rowData": { // 1
3948 "label": "root",
3949@@ -51,39 +68,39 @@
3950 },
3951 "submenu": [{
3952 "rowData": { // 1.1
3953- "label": "menu1",
3954+ "label": "menu0",
3955 "sensitive": true,
3956 "isSeparator": false,
3957 "icon": "",
3958- "type": "",
3959+ "type": "com.canonical.indicator.test",
3960 "ext": {},
3961- "action": "",
3962+ "action": "menu0",
3963 "actionState": {},
3964 "isCheck": false,
3965 "isRadio": false,
3966 "isToggled": false,
3967 }}, {
3968 "rowData": { // 1.2
3969- "label": "menu2",
3970+ "label": "menu1",
3971 "sensitive": true,
3972 "isSeparator": false,
3973 "icon": "",
3974- "type": "",
3975+ "type": "com.canonical.indicator.test",
3976 "ext": {},
3977- "action": "",
3978+ "action": "menu1",
3979 "actionState": {},
3980 "isCheck": false,
3981 "isRadio": false,
3982 "isToggled": false,
3983 }}, {
3984 "rowData": { // row 1.2
3985- "label": "menu3",
3986+ "label": "menu2",
3987 "sensitive": true,
3988 "isSeparator": false,
3989 "icon": "",
3990- "type": "",
3991+ "type": "com.canonical.indicator.test",
3992 "ext": {},
3993- "action": "",
3994+ "action": "menu2",
3995 "actionState": {},
3996 "isCheck": false,
3997 "isRadio": false,
3998@@ -147,5 +164,33 @@
3999 var mainMenu = findChild(page, "mainMenu");
4000 tryCompare(mainMenu, "count", data.expectedCount);
4001 }
4002+
4003+ function test_remove_selected_item_data() {
4004+ return [
4005+ { remove: 0 },
4006+ { remove: 2 },
4007+ ]
4008+ }
4009+
4010+ function test_remove_selected_item(data) {
4011+ var mainMenu = findChild(page, "mainMenu");
4012+ initializeMenuData(fullMenuData);
4013+
4014+ var menuId = "menu"+data.remove
4015+
4016+ tryCompareFunction(function() { return findChild(page, menuId) !== null;}, true);
4017+ var menu = findChild(page, menuId);
4018+
4019+ menu.menuSelected();
4020+ compare(mainMenu.currentIndex, data.remove, "Incorrect index selected");
4021+ mainMenu.model.removeRow(data.remove);
4022+
4023+ compare(mainMenu.currentIndex, -1, "Current index should be reset after current item removal");
4024+
4025+ // now make sure selecting a new menu works.
4026+ var menu1 = findChild(page, "menu1");
4027+ menu1.menuSelected();
4028+ compare(menu1.selected, true, "Item not selected");
4029+ }
4030 }
4031 }
4032
4033=== modified file 'tests/qmltests/tst_ShellWithPin.qml'
4034--- tests/qmltests/tst_ShellWithPin.qml 2014-10-30 21:40:34 +0000
4035+++ tests/qmltests/tst_ShellWithPin.qml 2014-11-05 14:37:57 +0000
4036@@ -160,6 +160,7 @@
4037 AccountsService.enableLauncherWhileLocked = true
4038 AccountsService.enableIndicatorsWhileLocked = true
4039 AccountsService.demoEdges = false
4040+ callManager.foregroundCall = null
4041
4042 // reload our test subject to get it in a fresh state once again
4043 shellLoader.active = true
4044@@ -212,6 +213,7 @@
4045 tryCompare(lockscreen, "shown", false)
4046 tryCompare(greeter, "hasLockedApp", true)
4047 tryCompare(greeter, "lockedApp", app)
4048+ tryCompare(LightDM.Greeter, "active", true)
4049 tryCompare(ApplicationManager, "focusedApplicationId", app)
4050 }
4051
4052@@ -386,5 +388,61 @@
4053 callManager.foregroundCall = phoneCall
4054 confirmLockedApp("dialer-app")
4055 }
4056+
4057+ function test_emergencyDialerActiveCallPanel() {
4058+ // Make sure that the following sequence works:
4059+ // - Enter emergency mode call
4060+ // - Return to greeter
4061+ // - Click on active call panel
4062+ // - Should be back in emergency mode dialer
4063+
4064+ var greeter = findChild(shell, "greeter");
4065+ var lockscreen = findChild(shell, "lockscreen");
4066+
4067+ lockscreen.emergencyCall();
4068+ confirmLockedApp("dialer-app");
4069+ callManager.foregroundCall = phoneCall;
4070+
4071+ LightDM.Greeter.showGreeter();
4072+ tryCompare(lockscreen, "shown", true);
4073+ tryCompare(greeter, "hasLockedApp", false);
4074+
4075+ // simulate a callHint press, the real thing requires dialer: url support
4076+ ApplicationManager.requestFocusApplication("dialer-app");
4077+
4078+ confirmLockedApp("dialer-app");
4079+ }
4080+
4081+ function test_normalDialerActiveCallPanel() {
4082+ // Make sure that the following sequence works:
4083+ // - Log in
4084+ // - Start a call
4085+ // - Switch apps
4086+ // - Click on active call panel
4087+ // - Should be back in normal dialer
4088+ // (we've had a bug where we locked screen in this case)
4089+
4090+ var lockscreen = findChild(shell, "lockscreen");
4091+ var panel = findChild(shell, "panel");
4092+
4093+ enterPin("1234");
4094+ tryCompare(lockscreen, "shown", false);
4095+ tryCompare(LightDM.Greeter, "active", false);
4096+
4097+ ApplicationManager.startApplication("dialer-app", ApplicationManager.NoFlag);
4098+ tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
4099+ callManager.foregroundCall = phoneCall;
4100+
4101+ ApplicationManager.requestFocusApplication("unity8-dash");
4102+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
4103+ tryCompare(panel.callHint, "visible", true);
4104+
4105+ // simulate a callHint press, the real thing requires dialer: url support
4106+ ApplicationManager.requestFocusApplication("dialer-app");
4107+
4108+ tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
4109+ tryCompare(lockscreen, "shown", false);
4110+ tryCompare(LightDM.Greeter, "active", false);
4111+ }
4112 }
4113 }

Subscribers

People subscribed via source and target branches

to all changes: