on xenial help sign in
Redesign the first-boot edge tutorial
7fd52cbMichael Zanetti
plugins/AccountsService/AccountsService.cpp+16
@@ -32,6 +32,7 @@
32
32
33#define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
33#define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
34#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
34#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
35+#define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
35#define PROP_EMAIL QStringLiteral("Email")
36#define PROP_EMAIL QStringLiteral("Email")
36#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
37#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
37#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
38#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
@@ -89,6 +90,7 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
89 registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
90 registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
90 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
91 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
91 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
92 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
93+ registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
92 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
94 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
93
95
94 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
96 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
@@ -152,6 +154,20 @@ void AccountsService::setDemoEdges(bool demoEdges)
152 setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
154 setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
153}
155}
154
156
157+QStringList AccountsService::demoEdgesCompleted() const
158+{
159+ auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED);
160+ return value.toStringList();
161+}
162+
163+void AccountsService::markDemoEdgeCompleted(const QString &edge)
164+{
165+ auto currentList = demoEdgesCompleted();
166+ if (!currentList.contains(edge)) {
167+ setProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, currentList << edge);
168+ }
169+}
170+
155bool AccountsService::enableLauncherWhileLocked() const
171bool AccountsService::enableLauncherWhileLocked() const
156{
172{
157 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
173 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
plugins/AccountsService/AccountsService.h+7
@@ -20,6 +20,7 @@
20#include <QHash>
20#include <QHash>
21#include <QObject>
21#include <QObject>
22#include <QString>
22#include <QString>
23+#include <QStringList>
23#include <QVariant>
24#include <QVariant>
24
25
25class AccountsServiceDBusAdaptor;
26class AccountsServiceDBusAdaptor;
@@ -37,6 +38,9 @@ class AccountsService: public QObject
37 READ demoEdges
38 READ demoEdges
38 WRITE setDemoEdges
39 WRITE setDemoEdges
39 NOTIFY demoEdgesChanged)
40 NOTIFY demoEdgesChanged)
41+ Q_PROPERTY (QStringList demoEdgesCompleted
42+ READ demoEdgesCompleted
43+ NOTIFY demoEdgesCompletedChanged)
40 Q_PROPERTY (bool enableLauncherWhileLocked
44 Q_PROPERTY (bool enableLauncherWhileLocked
41 READ enableLauncherWhileLocked
45 READ enableLauncherWhileLocked
42 NOTIFY enableLauncherWhileLockedChanged)
46 NOTIFY enableLauncherWhileLockedChanged)
@@ -82,6 +86,8 @@ public:
82 void setUser(const QString &user);
86 void setUser(const QString &user);
83 bool demoEdges() const;
87 bool demoEdges() const;
84 void setDemoEdges(bool demoEdges);
88 void setDemoEdges(bool demoEdges);
89+ QStringList demoEdgesCompleted() const;
90+ Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
85 bool enableLauncherWhileLocked() const;
91 bool enableLauncherWhileLocked() const;
86 bool enableIndicatorsWhileLocked() const;
92 bool enableIndicatorsWhileLocked() const;
87 QString backgroundFile() const;
93 QString backgroundFile() const;
@@ -101,6 +107,7 @@ public:
101Q_SIGNALS:
107Q_SIGNALS:
102 void userChanged();
108 void userChanged();
103 void demoEdgesChanged();
109 void demoEdgesChanged();
110+ void demoEdgesCompletedChanged();
104 void enableLauncherWhileLockedChanged();
111 void enableLauncherWhileLockedChanged();
105 void enableIndicatorsWhileLockedChanged();
112 void enableIndicatorsWhileLockedChanged();
106 void backgroundFileChanged();
113 void backgroundFileChanged();
plugins/AccountsService/AccountsService.qmltypes+5
@@ -23,6 +23,7 @@ Module {
23 }
23 }
24 Property { name: "user"; type: "string" }
24 Property { name: "user"; type: "string" }
25 Property { name: "demoEdges"; type: "bool" }
25 Property { name: "demoEdges"; type: "bool" }
26+ Property { name: "demoEdgesCompleted"; type: "QStringList"; isReadonly: true }
26 Property { name: "enableLauncherWhileLocked"; type: "bool"; isReadonly: true }
27 Property { name: "enableLauncherWhileLocked"; type: "bool"; isReadonly: true }
27 Property { name: "enableIndicatorsWhileLocked"; type: "bool"; isReadonly: true }
28 Property { name: "enableIndicatorsWhileLocked"; type: "bool"; isReadonly: true }
28 Property { name: "backgroundFile"; type: "string"; isReadonly: true }
29 Property { name: "backgroundFile"; type: "string"; isReadonly: true }
@@ -32,5 +33,9 @@ Module {
32 Property { name: "hereEnabled"; type: "bool" }
33 Property { name: "hereEnabled"; type: "bool" }
33 Property { name: "hereLicensePath"; type: "string"; isReadonly: true }
34 Property { name: "hereLicensePath"; type: "string"; isReadonly: true }
34 Property { name: "hereLicensePathValid"; type: "bool"; isReadonly: true }
35 Property { name: "hereLicensePathValid"; type: "bool"; isReadonly: true }
36+ Method {
37+ name: "markDemoEdgeCompleted"
38+ Parameter { name: "edge"; type: "string" }
39+ }
35 }
40 }
36}
41}
plugins/AccountsService/com.canonical.unity.AccountsService.xml+5
@@ -14,6 +14,11 @@
14 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
14 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
15 </property>
15 </property>
16
16
17+ <!-- List of tutorial pages that have been completed by the user -->
18+ <property name="DemoEdgesCompleted" type="as" access="readwrite">
19+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="[]"/>
20+ </property>
21+
17 <property name="LauncherItems" type="aa{sv}" access="readwrite">
22 <property name="LauncherItems" type="aa{sv}" access="readwrite">
18 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
23 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
19 </property>
24 </property>
plugins/ScreenGrabber/screengrabber.cpp−2
@@ -46,12 +46,10 @@ ScreenGrabber::ScreenGrabber(QObject *parent)
46
46
47 QDir screenshotsDir;
47 QDir screenshotsDir;
48 if (qEnvironmentVariableIsSet("UNITY_TESTING")) {
48 if (qEnvironmentVariableIsSet("UNITY_TESTING")) {
49− qDebug() << "Using test environment";
50 QTemporaryDir tDir;
49 QTemporaryDir tDir;
51 tDir.setAutoRemove(false);
50 tDir.setAutoRemove(false);
52 screenshotsDir = tDir.path();
51 screenshotsDir = tDir.path();
53 } else {
52 } else {
54− qDebug() << "Using real environment";
55 screenshotsDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
53 screenshotsDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
56 }
54 }
57 screenshotsDir.mkpath(QStringLiteral("Screenshots"));
55 screenshotsDir.mkpath(QStringLiteral("Screenshots"));
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+40−5
@@ -194,6 +194,26 @@ void DirectionalDragArea::setImmediateRecognition(bool enabled)
194 }
194 }
195}
195}
196
196
197+bool DirectionalDragArea::monitorOnly() const
198+{
199+ return d->monitorOnly;
200+}
201+
202+void DirectionalDragArea::setMonitorOnly(bool monitorOnly)
203+{
204+ if (d->monitorOnly != monitorOnly) {
205+ d->monitorOnly = monitorOnly;
206+
207+ if (monitorOnly && d->status == DirectionalDragAreaPrivate::Undecided) {
208+ TouchRegistry::instance()->removeCandidateOwnerForTouch(d->touchId, this);
209+ // We still wanna know when it ends for keeping the composition time window up-to-date
210+ TouchRegistry::instance()->addTouchWatcher(d->touchId, this);
211+ }
212+
213+ Q_EMIT monitorOnlyChanged(monitorOnly);
214+ }
215+}
216+
197void DirectionalDragArea::removeTimeConstraints()
217void DirectionalDragArea::removeTimeConstraints()
198{
218{
199 d->setMaxTime(60 * 60 * 1000);
219 d->setMaxTime(60 * 60 * 1000);
@@ -260,7 +280,10 @@ void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTou
260 unownedTouchEvent_undecided(unownedTouchEvent);
280 unownedTouchEvent_undecided(unownedTouchEvent);
261 break;
281 break;
262 default: // Recognized:
282 default: // Recognized:
263− // do nothing
283+ if (monitorOnly) {
284+ // Treat unowned event as if we owned it, but we are really just watching it
285+ touchEvent_recognized(event);
286+ }
264 break;
287 break;
265 }
288 }
266
289
@@ -311,7 +334,9 @@ void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *
311 }
334 }
312
335
313 if (movedFarEnoughAlongGestureAxis()) {
336 if (movedFarEnoughAlongGestureAxis()) {
337+ if (!monitorOnly) {
314− TouchRegistry::instance()->requestTouchOwnership(touchId, q);
338+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
339+ }
315 setStatus(Recognized);
340 setStatus(Recognized);
316 setPublicPos(touchPoint->pos());
341 setPublicPos(touchPoint->pos());
317 setPublicScenePos(touchScenePos);
342 setPublicScenePos(touchScenePos);
@@ -411,12 +436,21 @@ void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
411 if (recognitionIsDisabled()) {
436 if (recognitionIsDisabled()) {
412 // Behave like a dumb TouchArea
437 // Behave like a dumb TouchArea
413 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
438 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
414− TouchRegistry::instance()->requestTouchOwnership(touchId, q);
415 setStatus(Recognized);
439 setStatus(Recognized);
440+ if (monitorOnly) {
441+ watchPressedTouchPoints(touchPoints);
442+ event->ignore();
443+ } else {
444+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
416− event->accept();
445+ event->accept();
446+ }
417 } else {
447 } else {
418 // just monitor the touch points for now.
448 // just monitor the touch points for now.
449+ if (monitorOnly) {
450+ watchPressedTouchPoints(touchPoints);
451+ } else {
419− TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
452+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
453+ }
420
454
421 setStatus(Undecided);
455 setStatus(Undecided);
422 // Let the item below have it. We will monitor it and grab it later if a gesture
456 // Let the item below have it. We will monitor it and grab it later if a gesture
@@ -893,5 +927,6 @@ DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
893 , recognitionTimer(nullptr)
927 , recognitionTimer(nullptr)
894 , timeSource(new RealTimeSource)
928 , timeSource(new RealTimeSource)
895 , activeTouches(timeSource)
929 , activeTouches(timeSource)
930+ , monitorOnly(false)
896{
931{
897}
932}
plugins/Ubuntu/Gestures/DirectionalDragArea.h+8
@@ -77,6 +77,10 @@ class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem {
77 WRITE setImmediateRecognition
77 WRITE setImmediateRecognition
78 NOTIFY immediateRecognitionChanged)
78 NOTIFY immediateRecognitionChanged)
79
79
80+ // Whether we are merely monitoring touch events (in which case, we don't
81+ // claim ownership of the touch).
82+ Q_PROPERTY(bool monitorOnly READ monitorOnly WRITE setMonitorOnly NOTIFY monitorOnlyChanged)
83+
80 Q_ENUMS(Direction)
84 Q_ENUMS(Direction)
81public:
85public:
82 DirectionalDragArea(QQuickItem *parent = 0);
86 DirectionalDragArea(QQuickItem *parent = 0);
@@ -100,6 +104,9 @@ public:
100 bool immediateRecognition() const;
104 bool immediateRecognition() const;
101 void setImmediateRecognition(bool enabled);
105 void setImmediateRecognition(bool enabled);
102
106
107+ bool monitorOnly() const;
108+ void setMonitorOnly(bool monitorOnly);
109+
103 bool event(QEvent *e) override;
110 bool event(QEvent *e) override;
104
111
105 /*
112 /*
@@ -123,6 +130,7 @@ Q_SIGNALS:
123 void touchSceneXChanged(qreal value);
130 void touchSceneXChanged(qreal value);
124 void touchSceneYChanged(qreal value);
131 void touchSceneYChanged(qreal value);
125 void immediateRecognitionChanged(bool value);
132 void immediateRecognitionChanged(bool value);
133+ void monitorOnlyChanged(bool value);
126
134
127protected:
135protected:
128 void touchEvent(QTouchEvent *event) override;
136 void touchEvent(QTouchEvent *event) override;
plugins/Ubuntu/Gestures/DirectionalDragArea_p.h+2
@@ -160,6 +160,8 @@ public:
160
160
161 ActiveTouchesInfo activeTouches;
161 ActiveTouchesInfo activeTouches;
162
162
163+ bool monitorOnly;
164+
163Q_SIGNALS:
165Q_SIGNALS:
164 void statusChanged(Status value);
166 void statusChanged(Status value);
165};
167};
plugins/Utils/CMakeLists.txt+1−1
@@ -17,7 +17,7 @@ set(QMLPLUGIN_SRC
17 unitysortfilterproxymodelqml.cpp
17 unitysortfilterproxymodelqml.cpp
18 Timer.cpp
18 Timer.cpp
19 unitymenumodelpaths.cpp
19 unitymenumodelpaths.cpp
20− windowkeysfilter.cpp
20+ windowinputfilter.cpp
21 windowscreenshotprovider.cpp
21 windowscreenshotprovider.cpp
22 easingcurve.cpp
22 easingcurve.cpp
23 windowstatestorage.cpp
23 windowstatestorage.cpp
plugins/Utils/Utils.qmltypes+2−2
@@ -147,10 +147,10 @@ Module {
147 }
147 }
148 }
148 }
149 Component {
149 Component {
150− name: "WindowKeysFilter"
150+ name: "WindowInputFilter"
151 defaultProperty: "data"
151 defaultProperty: "data"
152 prototype: "QQuickItem"
152 prototype: "QQuickItem"
153− exports: ["Utils/WindowKeysFilter 0.1"]
153+ exports: ["Utils/WindowInputFilter 0.1"]
154 exportMetaObjectRevisions: [0]
154 exportMetaObjectRevisions: [0]
155 }
155 }
156 Component {
156 Component {
plugins/Utils/plugin.cpp+2−2
@@ -30,7 +30,7 @@
30#include "qlimitproxymodelqml.h"
30#include "qlimitproxymodelqml.h"
31#include "unitysortfilterproxymodelqml.h"
31#include "unitysortfilterproxymodelqml.h"
32#include "unitymenumodelpaths.h"
32#include "unitymenumodelpaths.h"
33−#include "windowkeysfilter.h"
33+#include "windowinputfilter.h"
34#include "windowscreenshotprovider.h"
34#include "windowscreenshotprovider.h"
35#include "windowstatestorage.h"
35#include "windowstatestorage.h"
36#include "constants.h"
36#include "constants.h"
@@ -69,7 +69,7 @@ void UtilsPlugin::registerTypes(const char *uri)
69 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
69 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
70 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
70 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
71 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
71 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
72− qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
72+ qmlRegisterType<WindowInputFilter>(uri, 0, 1, "WindowInputFilter");
73 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
73 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
74 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
74 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
75 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
75 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
plugins/Utils/windowkeysfilter.cpp → plugins/Utils/windowinputfilter.cpprenamed+14−16
@@ -16,36 +16,34 @@
16 * Author: Daniel d'Andrada <daniel.dandrada@canonical.com>
16 * Author: Daniel d'Andrada <daniel.dandrada@canonical.com>
17 */
17 */
18
18
19−#include "windowkeysfilter.h"
19+#include "windowinputfilter.h"
20
20
21#include <QQuickWindow>
21#include <QQuickWindow>
22
22
23−WindowKeysFilter::WindowKeysFilter(QQuickItem *parent)
23+WindowInputFilter::WindowInputFilter(QQuickItem *parent)
24 : QQuickItem(parent),
24 : QQuickItem(parent),
25− m_currentEventTimestamp(0)
25+ m_lastInputTimestamp(0)
26{
26{
27 connect(this, &QQuickItem::windowChanged,
27 connect(this, &QQuickItem::windowChanged,
28− this, &WindowKeysFilter::setupFilterOnWindow);
28+ this, &WindowInputFilter::setupFilterOnWindow);
29}
29}
30
30
31−bool WindowKeysFilter::eventFilter(QObject *watched, QEvent *event)
31+bool WindowInputFilter::eventFilter(QObject *watched, QEvent *event)
32{
32{
33 Q_ASSERT(!m_filteredWindow.isNull());
33 Q_ASSERT(!m_filteredWindow.isNull());
34 Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
34 Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
35 Q_UNUSED(watched);
35 Q_UNUSED(watched);
36
36
37+ QInputEvent *inputEvent = dynamic_cast<QInputEvent*>(event);
38+ if (inputEvent) {
39+ m_lastInputTimestamp = inputEvent->timestamp();
40+ Q_EMIT lastInputTimestampChanged();
41+ }
42+
37 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
43 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
38 // Let QML see this event and decide if it does not want it
44 // Let QML see this event and decide if it does not want it
39 event->accept();
45 event->accept();
40−
41− m_currentEventTimestamp = static_cast<QInputEvent*>(event)->timestamp();
42− Q_EMIT currentEventTimestampChanged();
43−
44 QCoreApplication::sendEvent(this, event);
46 QCoreApplication::sendEvent(this, event);
45−
46− m_currentEventTimestamp = 0;
47− Q_EMIT currentEventTimestampChanged();
48−
49 return event->isAccepted();
47 return event->isAccepted();
50 } else {
48 } else {
51 // Not interested
49 // Not interested
@@ -53,7 +51,7 @@ bool WindowKeysFilter::eventFilter(QObject *watched, QEvent *event)
53 }
51 }
54}
52}
55
53
56−void WindowKeysFilter::setupFilterOnWindow(QQuickWindow *window)
54+void WindowInputFilter::setupFilterOnWindow(QQuickWindow *window)
57{
55{
58 if (!m_filteredWindow.isNull()) {
56 if (!m_filteredWindow.isNull()) {
59 m_filteredWindow->removeEventFilter(this);
57 m_filteredWindow->removeEventFilter(this);
@@ -66,7 +64,7 @@ void WindowKeysFilter::setupFilterOnWindow(QQuickWindow *window)
66 }
64 }
67}
65}
68
66
69−ulong WindowKeysFilter::currentEventTimestamp() const
67+ulong WindowInputFilter::lastInputTimestamp() const
70{
68{
71− return m_currentEventTimestamp;
69+ return m_lastInputTimestamp;
72}
70}
plugins/Utils/windowkeysfilter.h → plugins/Utils/windowinputfilter.hrenamed+11−11
@@ -16,8 +16,8 @@
16 * Author: Daniel d'Andrada <daniel.dandrada@canonical.com>
16 * Author: Daniel d'Andrada <daniel.dandrada@canonical.com>
17 */
17 */
18
18
19−#ifndef UNITY_WINDOWKEYSFILTER_H
19+#ifndef UNITY_WINDOWINPUTFILTER_H
20−#define UNITY_WINDOWKEYSFILTER_H
20+#define UNITY_WINDOWINPUTFILTER_H
21
21
22#include <QQuickItem>
22#include <QQuickItem>
23#include <QPointer>
23#include <QPointer>
@@ -29,30 +29,30 @@
29 accepted ones will be filtered out. Events are accepted by default, so make sure you reject
29 accepted ones will be filtered out. Events are accepted by default, so make sure you reject
30 the keys you're not interested in.
30 the keys you're not interested in.
31
31
32− If more than one WindowKeysFilter exist in the same QML scene (and thus in the same QQuickWindow)
32+ If more than one WindowInputFilter exist in the same QML scene (and thus in the same QQuickWindow)
33 they will be called in the order of creation, which can be tricky to assess. So the best practice
33 they will be called in the order of creation, which can be tricky to assess. So the best practice
34− is to have at most one WindowKeysFilter per QML scene.
34+ is to have at most one WindowInputFilter per QML scene.
35 */
35 */
36−class WindowKeysFilter : public QQuickItem
36+class WindowInputFilter : public QQuickItem
37{
37{
38 Q_OBJECT
38 Q_OBJECT
39− Q_PROPERTY(ulong currentEventTimestamp READ currentEventTimestamp NOTIFY currentEventTimestampChanged)
39+ Q_PROPERTY(ulong lastInputTimestamp READ lastInputTimestamp NOTIFY lastInputTimestampChanged)
40public:
40public:
41− WindowKeysFilter(QQuickItem *parent = 0);
41+ WindowInputFilter(QQuickItem *parent = 0);
42
42
43 bool eventFilter(QObject *watched, QEvent *event) override;
43 bool eventFilter(QObject *watched, QEvent *event) override;
44
44
45− ulong currentEventTimestamp() const;
45+ ulong lastInputTimestamp() const;
46
46
47Q_SIGNALS:
47Q_SIGNALS:
48− void currentEventTimestampChanged();
48+ void lastInputTimestampChanged();
49
49
50private Q_SLOTS:
50private Q_SLOTS:
51 void setupFilterOnWindow(QQuickWindow *window);
51 void setupFilterOnWindow(QQuickWindow *window);
52
52
53private:
53private:
54 QPointer<QQuickWindow> m_filteredWindow;
54 QPointer<QQuickWindow> m_filteredWindow;
55− ulong m_currentEventTimestamp;
55+ ulong m_lastInputTimestamp;
56};
56};
57
57
58−#endif // UNITY_WINDOWKEYSFILTER_H
58+#endif // UNITY_WINDOWINPUTFILTER_H
qml/Greeter/Greeter.qml+10−6
@@ -160,6 +160,14 @@ Showable {
160 return false;
160 return false;
161 }
161 }
162 }
162 }
163+
164+ function checkForcedUnlock() {
165+ if (forcedUnlock && shown && loader.item) {
166+ // pretend we were just authenticated
167+ loader.item.notifyAuthenticationSucceeded();
168+ loader.item.hide();
169+ }
170+ }
163 }
171 }
164
172
165 onLauncherOffsetChanged: {
173 onLauncherOffsetChanged: {
@@ -168,12 +176,8 @@ Showable {
168 }
176 }
169 }
177 }
170
178
171− onForcedUnlockChanged: {
179+ onForcedUnlockChanged: d.checkForcedUnlock()
172− if (forcedUnlock && shown) {
180+ Component.onCompleted: d.checkForcedUnlock()
173− // pretend we were just authenticated
174− loader.item.notifyAuthenticationSucceeded();
175− }
176− }
177
181
178 onRequiredChanged: {
182 onRequiredChanged: {
179 if (required) {
183 if (required) {
qml/Launcher/Launcher.qml+2−3
@@ -28,7 +28,6 @@ FocusScope {
28 property bool lockedVisible: false
28 property bool lockedVisible: false
29 property bool available: true // can be used to disable all interactions
29 property bool available: true // can be used to disable all interactions
30 property alias inverted: panel.inverted
30 property alias inverted: panel.inverted
31− property bool shadeBackground: true // can be used to disable background shade when launcher is visible
32
31
33 property int panelWidth: units.gu(10)
32 property int panelWidth: units.gu(10)
34 property int dragAreaWidth: units.gu(1)
33 property int dragAreaWidth: units.gu(1)
@@ -298,7 +297,7 @@ FocusScope {
298 InverseMouseArea {
297 InverseMouseArea {
299 id: closeMouseArea
298 id: closeMouseArea
300 anchors.fill: panel
299 anchors.fill: panel
301− enabled: root.shadeBackground && root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
300+ enabled: root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
302 visible: enabled
301 visible: enabled
303 onPressed: {
302 onPressed: {
304 panel.highlightIndex = -2
303 panel.highlightIndex = -2
@@ -310,7 +309,7 @@ FocusScope {
310 id: backgroundShade
309 id: backgroundShade
311 anchors.fill: parent
310 anchors.fill: parent
312 color: "black"
311 color: "black"
313− opacity: root.shadeBackground && root.state == "visible" && !root.lockedVisible ? 0.6 : 0
312+ opacity: root.state == "visible" && !root.lockedVisible ? 0.6 : 0
314
313
315 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
314 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
316 }
315 }
qml/Panel/IndicatorsMenu.qml−2
@@ -36,7 +36,6 @@ Showable {
36 readonly property bool partiallyOpened: unitProgress > 0 && unitProgress < 1.0
36 readonly property bool partiallyOpened: unitProgress > 0 && unitProgress < 1.0
37 readonly property bool fullyClosed: unitProgress == 0
37 readonly property bool fullyClosed: unitProgress == 0
38 property bool enableHint: true
38 property bool enableHint: true
39− property bool contentEnabled: true
40 property bool showOnClick: true
39 property bool showOnClick: true
41 property color panelColor: theme.palette.normal.background
40 property color panelColor: theme.palette.normal.background
42
41
@@ -85,7 +84,6 @@ Showable {
85 height: openedHeight - bar.height - handle.height
84 height: openedHeight - bar.height - handle.height
86 indicatorsModel: root.indicatorsModel
85 indicatorsModel: root.indicatorsModel
87 visible: root.unitProgress > 0
86 visible: root.unitProgress > 0
88− enabled: contentEnabled
89 currentMenuIndex: bar.currentItemIndex
87 currentMenuIndex: bar.currentItemIndex
90 }
88 }
91
89
qml/Shell.qml+27−45
@@ -24,7 +24,7 @@ import Ubuntu.Gestures 0.1
24import Ubuntu.Telephony 0.1 as Telephony
24import Ubuntu.Telephony 0.1 as Telephony
25import Unity.Connectivity 0.1
25import Unity.Connectivity 0.1
26import Unity.Launcher 0.1
26import Unity.Launcher 0.1
27−import GlobalShortcut 1.0 // has to be before Utils, because of WindowKeysFilter
27+import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
28import GSettings 1.0
28import GSettings 1.0
29import Utils 0.1
29import Utils 0.1
30import Powerd 0.1
30import Powerd 0.1
@@ -167,12 +167,13 @@ Item {
167 }
167 }
168
168
169 GlobalShortcut {
169 GlobalShortcut {
170− // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
170+ // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
171 }
171 }
172
172
173− WindowKeysFilter {
173+ WindowInputFilter {
174+ id: inputFilter
174− Keys.onPressed: physicalKeysMapper.onKeyPressed(event, currentEventTimestamp);
175+ Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
175− Keys.onReleased: physicalKeysMapper.onKeyReleased(event, currentEventTimestamp);
176+ Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
176 }
177 }
177
178
178 WindowInputMonitor {
179 WindowInputMonitor {
@@ -205,7 +206,7 @@ Item {
205 onFocusedApplicationIdChanged: {
206 onFocusedApplicationIdChanged: {
206 var appId = ApplicationManager.focusedApplicationId;
207 var appId = ApplicationManager.focusedApplicationId;
207
208
208− if (tutorial.running && appId != "" && appId != "unity8-dash") {
209+ if (wizard.active && appId != "" && appId != "unity8-dash") {
209 // If this happens on first boot, we may be in edge
210 // If this happens on first boot, we may be in edge
210 // tutorial or wizard while receiving a call. But a call
211 // tutorial or wizard while receiving a call. But a call
211 // is more important than wizard so just bail out of those.
212 // is more important than wizard so just bail out of those.
@@ -262,8 +263,7 @@ Item {
262 source = qmlComponent;
263 source = qmlComponent;
263 }
264 }
264
265
265− property bool interactive: tutorial.spreadEnabled
266− && (!greeter || !greeter.shown)
266+ property bool interactive: (!greeter || !greeter.shown)
267 && panel.indicators.fullyClosed
267 && panel.indicators.fullyClosed
268 && launcher.progress == 0
268 && launcher.progress == 0
269 && !notifications.useModal
269 && !notifications.useModal
@@ -357,30 +357,6 @@ Item {
357 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
357 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
358 }
358 }
359 }
359 }
360−
361− Tutorial {
362− id: tutorial
363− objectName: "tutorial"
364− anchors.fill: parent
365−
366− // EdgeDragAreas don't work with mice. So to avoid trapping the user,
367− // we skip the tutorial on the Desktop to avoid using them. The
368− // Desktop doesn't use the same spread design anyway. The tutorial is
369− // all a bit of a placeholder on non-phone form factors right now.
370− // When the design team gives us more guidance, we can do something
371− // more clever here.
372− active: usageScenario != "desktop" && AccountsService.demoEdges
373−
374− paused: lightDM.greeter.active
375− launcher: launcher
376− panel: panel
377− edgeSize: shell.edgeSize
378−
379− onFinished: {
380− AccountsService.demoEdges = false;
381− active = false; // for immediate response / if AS is having problems
382− }
383− }
384 }
360 }
385
361
386 InputMethod {
362 InputMethod {
@@ -391,7 +367,7 @@ Item {
391 topMargin: panel.panelHeight
367 topMargin: panel.panelHeight
392 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
368 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
393 }
369 }
394− z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
370+ z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
395 }
371 }
396
372
397 Connections {
373 Connections {
@@ -422,7 +398,7 @@ Item {
422 hides: [launcher, panel.indicators]
398 hides: [launcher, panel.indicators]
423 tabletMode: shell.usageScenario != "phone"
399 tabletMode: shell.usageScenario != "phone"
424 launcherOffset: launcher.progress
400 launcherOffset: launcher.progress
425− forcedUnlock: tutorial.running
401+ forcedUnlock: wizard.active
426 background: wallpaperResolver.background
402 background: wallpaperResolver.background
427
403
428 // avoid overlapping with Launcher's edge drag area
404 // avoid overlapping with Launcher's edge drag area
@@ -479,7 +455,7 @@ Item {
479
455
480 onStatusChanged: {
456 onStatusChanged: {
481 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
457 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
482− !callManager.hasCalls && !tutorial.running) {
458+ !callManager.hasCalls && !wizard.active) {
483 // We don't want to simply call greeter.showNow() here, because
459 // We don't want to simply call greeter.showNow() here, because
484 // that will take too long. Qt will delay button event
460 // that will take too long. Qt will delay button event
485 // handling until the greeter is done loading and may think the
461 // handling until the greeter is done loading and may think the
@@ -495,10 +471,6 @@ Item {
495 }
471 }
496
472
497 function showHome() {
473 function showHome() {
498− if (tutorial.running) {
499− return
500− }
501−
502 greeter.notifyAboutToFocusApp("unity8-dash");
474 greeter.notifyAboutToFocusApp("unity8-dash");
503
475
504 var animate = !lightDM.greeter.active && !stages.shown
476 var animate = !lightDM.greeter.active && !stages.shown
@@ -532,7 +504,6 @@ Item {
532 available: tutorial.panelEnabled
504 available: tutorial.panelEnabled
533 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
505 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
534 && (!greeter || !greeter.hasLockedApp)
506 && (!greeter || !greeter.hasLockedApp)
535− contentEnabled: tutorial.panelContentEnabled
536 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
507 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
537
508
538 minimizedPanelHeight: units.gu(3)
509 minimizedPanelHeight: units.gu(3)
@@ -571,7 +542,6 @@ Item {
571 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
542 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
572 && !greeter.hasLockedApp
543 && !greeter.hasLockedApp
573 inverted: shell.usageScenario !== "desktop"
544 inverted: shell.usageScenario !== "desktop"
574− shadeBackground: !tutorial.running
575 superPressed: physicalKeysMapper.superPressed
545 superPressed: physicalKeysMapper.superPressed
576 superTabPressed: physicalKeysMapper.superTabPressed
546 superTabPressed: physicalKeysMapper.superTabPressed
577 panelWidth: units.gu(settings.launcherWidth)
547 panelWidth: units.gu(settings.launcherWidth)
@@ -585,10 +555,8 @@ Item {
585 }
555 }
586 }
556 }
587 onLauncherApplicationSelected: {
557 onLauncherApplicationSelected: {
588− if (!tutorial.running) {
589− greeter.notifyAboutToFocusApp(appId);
558+ greeter.notifyAboutToFocusApp(appId);
590− shell.activateApplication(appId)
559+ shell.activateApplication(appId);
591− }
592 }
560 }
593 onShownChanged: {
561 onShownChanged: {
594 if (shown) {
562 if (shown) {
@@ -628,6 +596,20 @@ Item {
628 }
596 }
629 }
597 }
630
598
599+ Tutorial {
600+ id: tutorial
601+ objectName: "tutorial"
602+ anchors.fill: parent
603+
604+ paused: callManager.hasCalls || greeter.shown
605+ keyboardVisible: inputMethod.state === "shown"
606+ usageScenario: shell.usageScenario
607+ lastInputTimestamp: inputFilter.lastInputTimestamp
608+ launcher: launcher
609+ panel: panel
610+ stage: applicationsDisplayLoader.item
611+ }
612+
631 Wizard {
613 Wizard {
632 id: wizard
614 id: wizard
633 objectName: "wizard"
615 objectName: "wizard"
qml/Stages/AbstractStage.qml+1
@@ -28,6 +28,7 @@ Rectangle {
28 property url background
28 property url background
29 property bool beingResized
29 property bool beingResized
30 property int dragAreaWidth
30 property int dragAreaWidth
31+ property real dragProgress // How far left the stage has been dragged, used externally by tutorial code
31 property bool interactive
32 property bool interactive
32 property real inverseProgress // This is the progress for left edge drags, in pixels.
33 property real inverseProgress // This is the progress for left edge drags, in pixels.
33 property bool keepDashRunning: true
34 property bool keepDashRunning: true
qml/Stages/ApplicationWindow.qml−2
@@ -60,7 +60,6 @@ FocusScope {
60 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
60 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
61 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
61 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
62 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
62 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
63− readonly property url defaultScreenshot: (root.application && root.application.defaultScreenshot !== undefined) ? root.application.defaultScreenshot : ""
64
63
65 // Whether the Application had a surface before but lost it.
64 // Whether the Application had a surface before but lost it.
66 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
65 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
@@ -112,7 +111,6 @@ FocusScope {
112 Image {
111 Image {
113 id: screenshotImage
112 id: screenshotImage
114 objectName: "screenshotImage"
113 objectName: "screenshotImage"
115− source: d.defaultScreenshot
116 anchors.fill: parent
114 anchors.fill: parent
117 antialiasing: !root.interactive
115 antialiasing: !root.interactive
118
116
qml/Stages/PhoneStage.qml+3−6
@@ -120,14 +120,11 @@ AbstractStage {
120 : (Qt.PortraitOrientation | Qt.LandscapeOrientation
120 : (Qt.PortraitOrientation | Qt.LandscapeOrientation
121 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation)
121 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation)
122
122
123− // How far left the stage has been dragged
123+ // How far left the stage has been dragged, used externally by tutorial code
124− readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
124+ dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
125
125
126 readonly property alias dragging: spreadDragArea.dragging
126 readonly property alias dragging: spreadDragArea.dragging
127
127
128− // Only used by the tutorial right now, when it is teasing the right edge
129− property real dragAreaOverlap
130−
131 signal opened()
128 signal opened()
132
129
133 function select(appId) {
130 function select(appId) {
@@ -657,7 +654,7 @@ AbstractStage {
657 direction: Direction.Leftwards
654 direction: Direction.Leftwards
658 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
655 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
659
656
660− anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: -root.dragAreaOverlap }
657+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; }
661 width: root.dragAreaWidth
658 width: root.dragAreaWidth
662
659
663 property var gesturePoints: new Array()
660 property var gesturePoints: new Array()
qml/Stages/TabletStage.qml+9−2
@@ -27,6 +27,9 @@ AbstractStage {
27 objectName: "stages"
27 objectName: "stages"
28 anchors.fill: parent
28 anchors.fill: parent
29
29
30+ property alias sideStageVisible: spreadView.sideStageVisible
31+ property alias sideStageWidth: spreadView.sideStageWidth
32+
30 // Functions to be called from outside
33 // Functions to be called from outside
31 function updateFocusedAppOrientation() {
34 function updateFocusedAppOrientation() {
32 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
35 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
@@ -97,6 +100,9 @@ AbstractStage {
97 }
100 }
98 }
101 }
99
102
103+ // How far left the stage has been dragged, used externally by tutorial code
104+ dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
105+
100 onWidthChanged: {
106 onWidthChanged: {
101 spreadView.selectedIndex = -1;
107 spreadView.selectedIndex = -1;
102 spreadView.phase = 0;
108 spreadView.phase = 0;
@@ -318,8 +324,6 @@ AbstractStage {
318 contentX: -shift
324 contentX: -shift
319
325
320 property int tileDistance: units.gu(20)
326 property int tileDistance: units.gu(20)
321− property int sideStageWidth: units.gu(40)
322− property bool sideStageVisible: priv.sideStageAppId
323
327
324 // This indicates when the spreadView is active. That means, all the animations
328 // This indicates when the spreadView is active. That means, all the animations
325 // are activated and tiles need to line up for the spread.
329 // are activated and tiles need to line up for the spread.
@@ -382,6 +386,9 @@ AbstractStage {
382 }
386 }
383
387
384 property real sideStageDragProgress: sideStage.progress
388 property real sideStageDragProgress: sideStage.progress
389+ property bool sideStageVisible: priv.sideStageAppId
390+ property real sideStageWidth: units.gu(40)
391+
385 property bool surfaceDragging: triGestureArea.recognisedDrag
392 property bool surfaceDragging: triGestureArea.recognisedDrag
386
393
387 // In case the ApplicationManager already holds an app when starting up we're missing animations
394 // In case the ApplicationManager already holds an app when starting up we're missing animations
qml/Tutorial/Arrow.qmldeleted−56
@@ -1,56 +0,0 @@
1−/*
2− * Copyright (C) 2014 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import Ubuntu.Components 1.3
19−
20−Item {
21− id: root
22−
23− property alias color: circle.color
24−
25− // Will make whole arrow darker
26− property real darkenBy: 0
27−
28− property alias chevronOpacity: chevron.opacity
29−
30− ////
31−
32− Rectangle {
33− id: circle
34− anchors.fill: parent
35− radius: width / 2
36− }
37−
38− Image {
39− id: chevron
40− anchors.centerIn: parent
41− source: Qt.resolvedUrl("graphics/chevron.png")
42− fillMode: Image.PreserveAspectFit
43− sourceSize.width: 152
44− sourceSize.height: 152
45− width: parent.width / 2
46− height: parent.height / 2
47− }
48−
49− Rectangle {
50− id: darkCircle
51− anchors.fill: parent
52− radius: width / 2
53− color: "black"
54− opacity: root.darkenBy
55− }
56−}
qml/Tutorial/InactivityTimer.qmlnew file+62
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+Item {
21+ readonly property alias running: internalTimer.running
22+ property alias interval: internalTimer.interval
23+ property var page
24+ property int lastInputTimestamp
25+
26+ function start() {
27+ internalTimer.start();
28+ }
29+
30+ ////
31+
32+ onLastInputTimestampChanged: {
33+ if (internalTimer.running) {
34+ internalTimer.restart();
35+ }
36+ }
37+
38+ Connections {
39+ target: page
40+ onIsReadyChanged: {
41+ if (page.isReady && internalTimer.running) {
42+ internalTimer.restart();
43+ }
44+ }
45+ }
46+
47+ Timer {
48+ id: internalTimer
49+
50+ interval: 3000
51+
52+ onTriggered: {
53+ if (page.isReady) {
54+ if (!page.shown) {
55+ page.show();
56+ }
57+ } else if (!page.skipped) {
58+ restart();
59+ }
60+ }
61+ }
62+}
qml/Tutorial/Slider.qmldeleted−123
@@ -1,123 +0,0 @@
1−/*
2− * Copyright (C) 2014 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import Ubuntu.Components 1.3
19−
20−Item {
21− id: root
22−
23− // Whether this slider is short or long
24− property bool shortSwipe
25−
26− // How far the user has slid
27− property real offset
28−
29− // Set to true when slider is being used
30− property bool active
31−
32− // How far in percentage terms
33− readonly property real percent: d.slideOffset / target.x
34−
35− QtObject {
36− id: d
37− readonly property color trayColor: "#424141"
38− readonly property real margin: units.gu(0.5)
39− readonly property real arrowSize: root.height - margin * 2
40− readonly property real dotSize: units.dp(1)
41− readonly property real slideOffset: MathUtils.clamp(root.offset - offscreenOffset, -offscreenOffset, target.x)
42− readonly property real offscreenOffset: units.gu(2)
43− }
44−
45− implicitWidth: shortSwipe ? units.gu(15) : units.gu(27.5)
46− implicitHeight: units.gu(6.5)
47−
48− Rectangle {
49− color: d.trayColor
50− anchors.fill: parent
51− anchors.rightMargin: clipBox.width - 1
52− }
53−
54− // We want to have a circular border around the target. But we can't just
55− // do a radius on two of a rectangle's corners. So we clip a full circle.
56− Item {
57− id: clipBox
58−
59− clip: true
60− anchors.top: parent.top
61− anchors.bottom: parent.bottom
62− anchors.right: parent.right
63− width: parent.height / 2
64−
65− Rectangle {
66− color: d.trayColor
67− anchors.top: parent.top
68− anchors.bottom: parent.bottom
69− anchors.right: parent.right
70− width: parent.width * 2
71− radius: parent.width
72− }
73− }
74−
75− Arrow {
76− id: target
77− width: d.arrowSize
78− height: d.arrowSize
79− color: "#73000000"
80− chevronOpacity: 0.52
81− anchors.right: parent.right
82− anchors.rightMargin: d.margin
83− anchors.verticalCenter: parent.verticalCenter
84− }
85−
86− Row {
87− anchors.left: handle.horizontalCenter
88− anchors.right: target.horizontalCenter
89− anchors.verticalCenter: parent.verticalCenter
90−
91− layoutDirection: Qt.RightToLeft
92− spacing: d.dotSize * 2
93−
94− Repeater {
95− model: parent.width / (parent.spacing + d.dotSize)
96− Rectangle {
97− anchors.verticalCenter: parent ? parent.verticalCenter : undefined
98− height: d.dotSize
99− width: height
100− radius: width
101− color: "white"
102− opacity: 0.2
103− }
104− }
105− }
106−
107− Arrow {
108− id: handle
109− width: d.arrowSize
110− height: d.arrowSize
111− color: UbuntuColors.orange
112− darkenBy: root.active ? 0.5 : 0
113− anchors.left: parent.left
114− // We use a Translate transform rather than anchors.leftMargin because
115− // the latter has weird performance problems on the TutorialRight page.
116− transform: [
117− Translate {
118− x: d.slideOffset
119− }
120− ]
121− anchors.verticalCenter: parent.verticalCenter
122− }
123−}
qml/Tutorial/Tick.qmldeleted−29
@@ -1,29 +0,0 @@
1−/*
2− * Copyright (C) 2015 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import Ubuntu.Components 1.3
19−
20−MouseArea {
21− implicitHeight: tick.height
22− implicitWidth: tick.width
23− Image {
24− id: tick
25− source: Qt.resolvedUrl("graphics/tick.png")
26− height: units.gu(6.5)
27− width: units.gu(6.5)
28− }
29−}
qml/Tutorial/Tutorial.qml+39−14
@@ -16,21 +16,29 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19+import AccountsService 0.1
20+
21+/**
22+ * This object is always present, so it should be lean and mean. It will
23+ * use a Loader to create the heavier tutorial pages if needed.
24+ */
19
25
20Item {
26Item {
21 id: root
27 id: root
22
28
23 property alias active: loader.active
29 property alias active: loader.active
24− property bool paused
25− property real edgeSize
26
30
27 property Item launcher
31 property Item launcher
28 property Item panel
32 property Item panel
33+ property Item stage
34+ property string usageScenario
35+ property bool paused
36+ property bool keyboardVisible
37+ property int lastInputTimestamp
29
38
30 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
39 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
31 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
40 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
32 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
41 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
33− readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true
34 readonly property bool running: loader.item ? loader.item.running : false
42 readonly property bool running: loader.item ? loader.item.running : false
35
43
36 function finish() {
44 function finish() {
@@ -39,40 +47,57 @@ Item {
39 }
47 }
40 }
48 }
41
49
42− signal finished()
43−
44 Loader {
50 Loader {
45 id: loader
51 id: loader
46 anchors.fill: parent
52 anchors.fill: parent
47 source: "TutorialContent.qml"
53 source: "TutorialContent.qml"
54+ active: AccountsService.demoEdges
48
55
49 Binding {
56 Binding {
50 target: loader.item
57 target: loader.item
51− property: "paused"
58+ property: "launcher"
52− value: root.paused
59+ value: root.launcher
53 }
60 }
54
61
55 Binding {
62 Binding {
56 target: loader.item
63 target: loader.item
57− property: "edgeSize"
64+ property: "panel"
58− value: root.edgeSize
65+ value: root.panel
59 }
66 }
60
67
61 Binding {
68 Binding {
62 target: loader.item
69 target: loader.item
63− property: "launcher"
70+ property: "stage"
64− value: root.launcher
71+ value: root.stage
65 }
72 }
66
73
67 Binding {
74 Binding {
68 target: loader.item
75 target: loader.item
69− property: "panel"
76+ property: "usageScenario"
77+ value: root.usageScenario
78+ }
79+
80+ Binding {
81+ target: loader.item
82+ property: "paused"
70− value: root.panel
83+ value: root.paused
84+ }
85+
86+ Binding {
87+ target: loader.item
88+ property: "keyboardVisible"
89+ value: root.keyboardVisible
90+ }
91+
92+ Binding {
93+ target: loader.item
94+ property: "lastInputTimestamp"
95+ value: root.lastInputTimestamp
71 }
96 }
72
97
73 Connections {
98 Connections {
74 target: loader.item
99 target: loader.item
75− onFinished: root.finished()
100+ onFinished: AccountsService.demoEdges = false
76 }
101 }
77 }
102 }
78}
103}
qml/Tutorial/TutorialBottom.qml+86−66
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -17,88 +17,108 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19import Ubuntu.Gestures 0.1
19import Ubuntu.Gestures 0.1
20−import "../Components"
20+import Unity.Application 0.1
21−import "." as LocalComponents
22
21
23TutorialPage {
22TutorialPage {
24 id: root
23 id: root
25
24
26− property alias edgeSize: dragArea.height
25+ property string usageScenario
26+ property var stage
27+ property var application: null
27
28
28− title: i18n.tr("Open special menus")
29+ // This page is a bit fragile. It relies on knowing how the app beneath
29− text: i18n.tr("Swipe up from the bottom edge.")
30+ // the shell will react to a drag. What we do is put a monitor-only DDA
30− fullTextWidth: true
31+ // at the bottom of the page (so that we know when the drag is finished)
32+ // and pass the events on through to the app. Thus, it sees the drag and
33+ // brings its bottom edge up.
34+ //
35+ // Unfortunately, each app is on its own when implementing the bottom edge
36+ // drag. Most share copied-and-pasted code right now, but they will
37+ // eventually consolidate on a version of DirectionalDragArea that will
38+ // land in the SDK (making our guessing job easier). Though, also in the
39+ // future, this whole bottom tutorial component will also land in the SDK,
40+ // rendering our version here obsolete.
41+ //
42+ // Anyway, for the moment, we base our guesses on the copied-and-pasted
43+ // code used in several of the core apps and only bring this component
44+ // up if we are in those core apps.
31
45
32− SequentialAnimation {
46+ readonly property real mainStageWidth: stage.width - sideStageWidth
33− id: teaseAnimation
47+ readonly property real sideStageWidth: root.usageScenario === "tablet" && stage.sideStageVisible ?
34− paused: running && root.paused
48+ stage.sideStageWidth : 0
35− running: !dragArea.useTouchY && slider.dragOffset === 0
49+ readonly property bool isMainStageApp: usageScenario !== "tablet" ||
36− loops: Animation.Infinite
50+ application.stage === ApplicationInfoInterface.MainStage
51+ readonly property real dragAreaHeight: units.gu(3) // based on PageWithBottomEdge.qml
52+ readonly property real targetDistance: height * 0.2 + dragAreaHeight // based on PageWithBottomEdge.qml
37
53
38− UbuntuNumberAnimation {
54+ opacityOverride: dragArea.dragging ? 1 - (-dragArea.distance / targetDistance) : 1
39− target: slider
55+
40− property: "teaseOffset"
56+ mouseArea {
41− to: units.gu(1)
57+ anchors.bottomMargin: root.dragAreaHeight
42− duration: UbuntuAnimation.SleepyDuration
43− }
58+ }
44− UbuntuNumberAnimation {
59+
45− target: slider
60+ background {
46− property: "teaseOffset"
61+ sourceSize.height: 1916
47− to: 0
62+ sourceSize.width: 1080
48− duration: UbuntuAnimation.SleepyDuration
63+ source: Qt.resolvedUrl("graphics/background2.png")
49− }
64+ rotation: 180
50 }
65 }
51
66
52− foreground {
67+ arrow {
53− children: [
54− LocalComponents.Slider {
55− id: slider
56− anchors {
57− bottom: parent.bottom
68+ anchors.bottom: root.bottom
58− bottomMargin: width / 2 - height / 2
69+ anchors.bottomMargin: units.gu(3)
59− horizontalCenter: parent.horizontalCenter
70+ anchors.horizontalCenter: label.horizontalCenter
60− }
71+ anchors.horizontalCenterOffset: -(label.width - label.contentWidth) / 2
61− rotation: -90
72+ rotation: 90
62− offset: teaseOffset + dragOffset
73+ }
63− active: dragArea.dragging
64
74
65− property real teaseOffset
75+ label {
66− property real dragOffset: dragArea.useTouchY ? -dragArea.touchY : 0
76+ text: !application ? "" :
77+ application.appId === "address-book-app" ?
78+ i18n.tr("Swipe up to add a contact") :
79+ application.appId === "com.ubuntu.calculator_calculator" ?
80+ i18n.tr("Swipe up for favorite calculations") :
81+ application.appId === "dialer-app" ?
82+ i18n.tr("Swipe up for recent calls") :
83+ application.appId === "messaging-app" ?
84+ i18n.tr("Swipe up to create a message") :
85+ i18n.tr("Swipe up to manage the app") // shouldn't be used
86+ anchors.bottom: arrow.top
87+ anchors.bottomMargin: units.gu(3)
88+ anchors.left: root.left
89+ anchors.leftMargin: (label.width - label.contentWidth) / 2 + sideMargin +
90+ (isMainStageApp ? 0 : mainStageWidth)
91+ width: (isMainStageApp ? mainStageWidth : sideStageWidth) - sideMargin * 2
67
92
68− Behavior on dragOffset {
93+ readonly property real sideMargin: units.gu(4)
69− id: offsetAnimation
70− UbuntuNumberAnimation {}
71− }
72− }
73− ]
74 }
94 }
75
95
96+ // Watches drag events but does not intercept them, so that the app beneath
97+ // will still drag the bottom edge up.
76 DirectionalDragArea {
98 DirectionalDragArea {
77 id: dragArea
99 id: dragArea
100+ monitorOnly: true
78 direction: Direction.Upwards
101 direction: Direction.Upwards
79− anchors {
80− bottom: parent.bottom
81− left: parent.left
102+ anchors.left: parent.left
82− right: parent.right
103+ anchors.right: parent.right
83− }
104+ anchors.bottom: parent.bottom
84−
105+ height: root.dragAreaHeight
85− property bool useTouchY
86
106
87− onDraggingChanged: {
107+ // Apps currently don't use DDA. DDA will stop a gesture if
88− if (!dragging) {
108+ // horizontal motion is detected. But our apps won't. So turn off
89− if (slider.percent >= 0.85) {
109+ // that gesture cleverness on our part, it will only get us out of sync.
90− root.hide();
110+ immediateRecognition: true
91− } else if (slider.percent >= 0.15) {
92− root.showError();
93− }
94− }
111+ }
95
112
96− // We use a separate vars here rather than just directly looking at
113+ MouseArea {
97− // 'dragging' because we want to preserve our 'slider.offset'
114+ // A second mouse area because in tablet mode, we only want to let the
98− // value during the above percent check. Now that we made it,
115+ // user drag up on one of the stages, not both. So we want to cover
99− // we can have 'slider.offset' go back to zero.
116+ // the second bottom edge with an event eater.
100− offsetAnimation.enabled = !dragging;
117+ enabled: root.usageScenario === "tablet"
101− useTouchY = dragging;
118+ height: root.dragAreaHeight
102− }
119+ width: isMainStageApp ? sideStageWidth : mainStageWidth
120+ anchors.bottom: parent.bottom
121+ anchors.left: isMainStageApp ? undefined : parent.left
122+ anchors.right: isMainStageApp ? parent.right : undefined
103 }
123 }
104}
124}
qml/Tutorial/TutorialBottomFinish.qmldeleted−41
@@ -1,41 +0,0 @@
1−/*
2− * Copyright (C) 2014 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import Ubuntu.Components 1.3
19−import "." as LocalComponents
20−
21−TutorialPage {
22− id: root
23−
24− title: i18n.tr("This action does different things for different apps")
25− text: i18n.tr("Tap here to finish.")
26− fullTextWidth: true
27−
28− foreground {
29− children: [
30− LocalComponents.Tick {
31− objectName: "tick"
32− anchors {
33− horizontalCenter: parent.horizontalCenter
34− top: parent.top
35− topMargin: root.textBottom + units.gu(3)
36− }
37− onClicked: root.hide()
38− }
39− ]
40− }
41−}
qml/Tutorial/TutorialContent.qml+225−55
@@ -16,103 +16,273 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19+import AccountsService 0.1
20+import Unity.Application 0.1
19
21
20Item {
22Item {
21 id: root
23 id: root
22
24
23 property Item launcher
25 property Item launcher
24 property Item panel
26 property Item panel
27+ property Item stage
28+ property string usageScenario
29+ property bool paused
30+ property bool keyboardVisible
31+ property var lastInputTimestamp
25
32
26− readonly property bool launcherEnabled: !running ||
33+ readonly property bool launcherEnabled: !running
27− (!paused && tutorialLeft.shown)
34+ || tutorialLeftLoader.shown
35+ || tutorialLeftLongLoader.shown
28− readonly property bool spreadEnabled: !running
36+ readonly property bool spreadEnabled: !running || tutorialRightLoader.shown
29− readonly property bool panelEnabled: !running
37+ readonly property bool panelEnabled: !running || tutorialTopLoader.shown
30− readonly property bool panelContentEnabled: !running
31− readonly property alias running: d.running
38+ readonly property bool running: tutorialLeftLoader.shown
32−
39+ || tutorialTopLoader.shown
33− property bool paused: false
40+ || tutorialRightLoader.shown
34− property real edgeSize
41+ || tutorialBottomLoader.shown
35
42
36 signal finished()
43 signal finished()
37
44
38 function finish() {
45 function finish() {
39− d.stop();
40 finished();
46 finished();
41 }
47 }
42
48
43 ////
49 ////
44
50
45− Component.onCompleted: {
46− d.start();
47− }
48−
49 QtObject {
51 QtObject {
50 id: d
52 id: d
51
53
52− property bool running
54+ // We allow "" because it is briefly empty on startup, and we don't
55+ // want to improperly skip any mobile tutorials.
56+ property bool mobileScenario: root.usageScenario === "" ||
57+ root.usageScenario === "phone" ||
58+ root.usageScenario === "tablet"
53
59
54− function stop() {
60+ property var focusedApp: ApplicationManager.focusedApplicationId
55− running = false;
61+ ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
56− }
62+ : null
57
63
58− function start() {
64+ function haveShown(tutorialId) {
59− running = true;
65+ return AccountsService.demoEdgesCompleted.indexOf(tutorialId) != -1;
60− tutorialLeft.show();
61 }
66 }
67+
68+ property bool endPointsFinished: tutorialRightLoader.skipped &&
69+ tutorialBottomLoader.skipped
70+ onEndPointsFinishedChanged: if (endPointsFinished) root.finish()
62 }
71 }
63
72
64− TutorialLeft {
73+ Loader {
65− id: tutorialLeft
74+ id: tutorialLeftLoader
66− objectName: "tutorialLeft"
75+ objectName: "tutorialLeftLoader"
67 anchors.fill: parent
76 anchors.fill: parent
68− launcher: root.launcher
69− paused: !shown || root.paused
70
77
71− onFinished: tutorialLeftFinish.show()
78+ readonly property bool skipped: !d.mobileScenario || d.haveShown("left")
79+ readonly property bool shown: item && item.shown
80+ active: !skipped || (item && item.visible)
81+ onSkippedChanged: if (skipped && shown) item.hide()
82+
83+ sourceComponent: TutorialLeft {
84+ id: tutorialLeft
85+ objectName: "tutorialLeft"
86+ anchors.fill: parent
87+ launcher: root.launcher
88+ hides: [launcher, panel.indicators]
89+ paused: root.paused
90+
91+ isReady: !tutorialLeftLoader.skipped && !paused && !keyboardVisible
92+
93+ // Use an idle timer here, because when constructed, all our isReady variables will be false.
94+ // Qml needs a moment to copy their values from Tutorial.qml. So we idle it and recheck.
95+ Timer {
96+ id: tutorialLeftTimer
97+ interval: 0
98+ onTriggered: if (tutorialLeft.isReady && !tutorialLeft.shown) tutorialLeft.show()
99+ }
100+
101+ onIsReadyChanged: if (isReady && !shown) tutorialLeftTimer.start()
102+ onFinished: AccountsService.markDemoEdgeCompleted("left")
103+ }
72 }
104 }
73
105
74− TutorialLeftFinish {
106+ Loader {
75− id: tutorialLeftFinish
107+ id: tutorialLeftLongLoader
76− objectName: "tutorialLeftFinish"
108+ objectName: "tutorialLeftLongLoader"
77 anchors.fill: parent
109 anchors.fill: parent
78− textXOffset: root.launcher.panelWidth
79− paused: !shown || root.paused
80− text: i18n.tr("Tap here to continue.")
81
110
82− onFinished: {
111+ readonly property bool skipped: !d.mobileScenario || d.haveShown("left-long")
83− root.launcher.hide();
112+ readonly property bool shown: item && item.shown
84− tutorialRight.show();
113+ active: !skipped || (item && item.visible)
114+ onSkippedChanged: if (skipped && shown) item.hide()
115+
116+ sourceComponent: TutorialLeftLong {
117+ id: tutorialLeftLong
118+ objectName: "tutorialLeftLong"
119+ anchors.fill: parent
120+ launcher: root.launcher
121+ hides: [launcher, panel.indicators]
122+ paused: root.paused
123+
124+ skipped: tutorialLeftLongLoader.skipped
125+ isReady: tutorialLeftLoader.skipped && !skipped && !paused && !keyboardVisible &&
126+ !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
127+
128+ Timer {
129+ id: tutorialLeftLongTimer
130+ objectName: "tutorialLeftLongTimer"
131+ interval: 5000
132+ onTriggered: {
133+ if (parent.isReady) {
134+ if (!parent.shown) {
135+ parent.show();
136+ }
137+ } else if (!parent.skipped) {
138+ restart();
139+ }
140+ }
141+ }
142+
143+ onIsReadyChanged: if (isReady && !shown) tutorialLeftLongTimer.start()
144+ onFinished: AccountsService.markDemoEdgeCompleted("left-long")
85 }
145 }
86 }
146 }
87
147
88− TutorialRight {
148+ Loader {
89− id: tutorialRight
149+ id: tutorialTopLoader
90− objectName: "tutorialRight"
150+ objectName: "tutorialTopLoader"
91 anchors.fill: parent
151 anchors.fill: parent
92− edgeSize: root.edgeSize
93− panel: root.panel
94− paused: !shown || root.paused
95
152
96− onFinished: tutorialBottom.show()
153+ readonly property bool skipped: !d.mobileScenario || d.haveShown("top")
154+ readonly property bool shown: item && item.shown
155+ active: !skipped || (item && item.visible)
156+ onSkippedChanged: if (skipped && shown) item.hide()
157+
158+ sourceComponent: TutorialTop {
159+ id: tutorialTop
160+ objectName: "tutorialTop"
161+ anchors.fill: parent
162+ panel: root.panel
163+ hides: [launcher, panel.indicators]
164+ paused: root.paused
165+
166+ skipped: tutorialTopLoader.skipped
167+ isReady: tutorialLeftLongLoader.skipped && !skipped && !paused && !keyboardVisible &&
168+ !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
169+
170+ // We fire 30s after left edge tutorial, with at least 3s of inactivity
171+
172+ InactivityTimer {
173+ id: tutorialTopInactivityTimer
174+ lastInputTimestamp: root.lastInputTimestamp
175+ page: parent
176+ }
177+
178+ Timer {
179+ id: tutorialTopTimer
180+ objectName: "tutorialTopTimer"
181+ interval: 27000
182+ onTriggered: tutorialTopInactivityTimer.start()
183+ }
184+
185+ onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
186+ onFinished: AccountsService.markDemoEdgeCompleted("top")
187+ }
97 }
188 }
98
189
99− TutorialBottom {
190+ Loader {
100− id: tutorialBottom
191+ id: tutorialRightLoader
101− objectName: "tutorialBottom"
192+ objectName: "tutorialRightLoader"
102 anchors.fill: parent
193 anchors.fill: parent
103− edgeSize: root.edgeSize
104− paused: !shown || root.paused
105
194
106− onFinished: tutorialBottomFinish.show()
195+ readonly property bool skipped: d.haveShown("right")
196+ readonly property bool shown: item && item.shown
197+ active: !skipped || (item && item.visible)
198+ onSkippedChanged: if (skipped && shown) item.hide()
199+
200+ sourceComponent: TutorialRight {
201+ id: tutorialRight
202+ objectName: "tutorialRight"
203+ anchors.fill: parent
204+ stage: root.stage
205+ usageScenario: root.usageScenario
206+ hides: [launcher, panel.indicators]
207+ paused: root.paused
208+
209+ skipped: tutorialRightLoader.skipped
210+ isReady: tutorialTopLoader.skipped && !skipped && !paused && !keyboardVisible &&
211+ !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow &&
212+ ApplicationManager.count >= 3
213+
214+ InactivityTimer {
215+ id: tutorialRightInactivityTimer
216+ objectName: "tutorialRightInactivityTimer"
217+ lastInputTimestamp: root.lastInputTimestamp
218+ page: parent
219+ }
220+
221+ Connections {
222+ target: d
223+ onFocusedAppChanged: {
224+ if (tutorialRight.isReady && !tutorialRight.shown && d.focusedApp
225+ && d.focusedApp.state === ApplicationInfoInterface.Starting) {
226+ tutorialRight.show();
227+ }
228+ }
229+ }
230+
231+ onIsReadyChanged: if (isReady && !shown) tutorialRightInactivityTimer.start()
232+ onFinished: AccountsService.markDemoEdgeCompleted("right")
233+ }
107 }
234 }
108
235
109− TutorialBottomFinish {
236+ Loader {
110− id: tutorialBottomFinish
237+ id: tutorialBottomLoader
111− objectName: "tutorialBottomFinish"
238+ objectName: "tutorialBottomLoader"
112 anchors.fill: parent
239 anchors.fill: parent
113− backgroundFadesOut: true
114− paused: !shown || root.paused
115
240
116− onFinished: root.finish()
241+ // See TutorialBottom.qml for an explanation of why we only support
242+ // certain apps.
243+ readonly property var supportedApps: ["address-book-app",
244+ "com.ubuntu.calculator_calculator",
245+ "dialer-app",
246+ "messaging-app"]
247+ readonly property bool skipped: {
248+ if (!d.mobileScenario) {
249+ return true;
250+ }
251+ for (var i = 0; i < supportedApps.length; i++) {
252+ if (!d.haveShown("bottom-" + supportedApps[i])) {
253+ return false;
254+ }
255+ }
256+ return true;
257+ }
258+ readonly property bool shown: item && item.shown
259+ readonly property bool haveShownFocusedApp: d.focusedApp &&
260+ d.haveShown("bottom-" + d.focusedApp.appId)
261+ readonly property bool mightShow: !skipped && d.focusedApp &&
262+ supportedApps.indexOf(d.focusedApp.appId) !== -1 &&
263+ !haveShownFocusedApp
264+ active: !skipped || (item && item.visible)
265+ onHaveShownFocusedAppChanged: if (haveShownFocusedApp && shown) hide()
266+ onSkippedChanged: if (skipped && shown) item.hide()
267+
268+ sourceComponent: TutorialBottom {
269+ id: tutorialBottom
270+ objectName: "tutorialBottom"
271+ anchors.fill: parent
272+ hides: [launcher, panel.indicators]
273+ paused: root.paused
274+ usageScenario: root.usageScenario
275+ stage: root.stage
276+ application: d.focusedApp
277+
278+ skipped: tutorialBottomLoader.skipped
279+ isReady: !tutorialBottomLoader.skipped && !paused && !keyboardVisible &&
280+ !tutorialTopLoader.shown && !tutorialRightLoader.shown &&
281+ tutorialBottomLoader.mightShow &&
282+ d.focusedApp.state === ApplicationInfoInterface.Running
283+
284+ onIsReadyChanged: if (isReady && !shown) show()
285+ onFinished: AccountsService.markDemoEdgeCompleted("bottom-" + d.focusedApp.appId)
286+ }
117 }
287 }
118}
288}
qml/Tutorial/TutorialLeft.qml+21−68
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -23,79 +23,32 @@ TutorialPage {
23
23
24 property var launcher
24 property var launcher
25
25
26− title: i18n.tr("Open the launcher")
26+ opacityOverride: 1 - launcher.visibleWidth / launcher.panelWidth
27− text: i18n.tr("Short swipe from the left edge.")
28
27
29− textXOffset: root.launcher.x + root.launcher.visibleWidth
28+ mouseArea {
30−
29+ anchors.leftMargin: launcher.dragAreaWidth
31− Connections {
32− target: root.launcher
33−
34− onStateChanged: {
35− if (root.launcher.state === "visible") {
36− finishTimer.start();
37− }
38− }
39−
40− onDash: {
41− finishTimer.stop();
42− root.showError();
43− root.launcher.hide();
44− }
45− }
46−
47− SequentialAnimation {
48− id: teaseAnimation
49− objectName: "teaseAnimation"
50− paused: running && root.paused
51− running: !slider.active && root.launcher.visibleWidth === 0 && root.shown
52− loops: Animation.Infinite
53− property real bounce: 0
54− readonly property real maxBounce: units.gu(2)
55−
56− UbuntuNumberAnimation {
57− target: teaseAnimation
58− property: "bounce"
59− to: teaseAnimation.maxBounce
60− duration: UbuntuAnimation.SleepyDuration
61− }
62− UbuntuNumberAnimation {
63− target: teaseAnimation
64− property: "bounce"
65− to: 0
66− duration: UbuntuAnimation.SleepyDuration
67− }
68 }
30 }
69
31
70− Binding {
32+ background {
71− target: root.launcher
33+ sourceSize.height: 1916
72− when: root.shown
34+ sourceSize.width: 1080
73− property: "x"
35+ source: Qt.resolvedUrl("graphics/background1.png")
74− value: Math.min(root.launcher.panelWidth - root.launcher.visibleWidth, teaseAnimation.bounce)
36+ mirror: true
75 }
37 }
76
38
77− Timer {
39+ arrow {
78− id: finishTimer
40+ anchors.left: root.left
79− interval: 1
41+ anchors.leftMargin: units.gu(2)
80− onTriggered: {
42+ anchors.verticalCenter: root.verticalCenter
81− root.hide();
43+ rotation: 180
82− root.launcher.x = 0; // make sure to reset launcher before we go
83− }
84 }
44 }
85
45
86− foreground {
46+ label {
87− children: [
47+ text: i18n.tr("Swipe from the left edge to open the launcher")
88− LocalComponents.Slider {
48+ anchors.left: arrow.right
89− id: slider
49+ anchors.leftMargin: units.gu(3)
90− anchors {
50+ anchors.right: root.right
91− left: parent.left
51+ anchors.rightMargin: units.gu(4)
92− top: parent.top
52+ anchors.verticalCenter: arrow.verticalCenter
93− topMargin: root.textBottom + units.gu(3)
94− }
95− offset: root.launcher.x + root.launcher.visibleWidth + root.launcher.progress
96− active: root.launcher.dragging
97− shortSwipe: true
98− }
99− ]
100 }
53 }
101}
54}
qml/Tutorial/TutorialLeftFinish.qmldeleted−41
@@ -1,41 +0,0 @@
1−/*
2− * Copyright (C) 2014 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import Ubuntu.Components 1.3
19−import "." as LocalComponents
20−
21−TutorialPage {
22− id: root
23−
24− title: i18n.tr("These are the shortcuts to favorite apps")
25− text: i18n.tr("Tap here to continue.")
26− fullTextWidth: true
27−
28− foreground {
29− children: [
30− LocalComponents.Tick {
31− objectName: "tick"
32− anchors {
33− horizontalCenter: parent.horizontalCenter
34− top: parent.top
35− topMargin: root.textBottom + units.gu(3)
36− }
37− onClicked: root.hide()
38− }
39− ]
40− }
41−}
qml/Tutorial/TutorialLeftLong.qmlnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ property var launcher
25+
26+ opacityOverride: 1 - launcher.dragDistance / launcher.minimizeDistance
27+
28+ mouseArea {
29+ anchors.leftMargin: launcher.dragAreaWidth
30+ }
31+
32+ background {
33+ sourceSize.height: 1916
34+ sourceSize.width: 1080
35+ source: Qt.resolvedUrl("graphics/background1.png")
36+ mirror: true
37+ }
38+
39+ arrow {
40+ anchors.left: root.left
41+ anchors.leftMargin: units.gu(2)
42+ anchors.verticalCenter: root.verticalCenter
43+ rotation: 180
44+ }
45+
46+ label {
47+ text: i18n.tr("Long swipe from the left edge to open the Today scope")
48+ anchors.left: arrow.right
49+ anchors.leftMargin: units.gu(3)
50+ anchors.right: root.right
51+ anchors.rightMargin: units.gu(4)
52+ anchors.verticalCenter: arrow.verticalCenter
53+ }
54+}
qml/Tutorial/TutorialPage.qml+62−186
@@ -21,225 +21,101 @@ import "../Components"
21Showable {
21Showable {
22 id: root
22 id: root
23
23
24− // This is the header displayed, like "Right edge"
25− property alias title: titleLabel.text
24+ property alias arrow: arrow
26−
27− // This is the block of text displayed below the header
28− property alias text: textLabel.text
25+ property alias label: label
29−
26+ property alias background: background
30− // Whether animations are paused
31− property bool paused
32−
33− // Whether to give the text the full width that the title has
34− property bool fullTextWidth
35−
36− // Whether whole page (background + foreground) or just the foreground fades in
37− property bool backgroundFadesIn: false
38−
39− // Whether whole page (background + foreground) or just the foreground fades out
40− property bool backgroundFadesOut: false
41−
42− // The foreground Item, add children to it that you want to fade in
43− property alias foreground: foregroundExtra
44−
45− // The text label bottom, so you can position elements relative to it
46− readonly property real textBottom: Math.max(textLabel.y + textLabel.height, errorTextLabel.y + errorTextLabel.height)
47−
48− // The MouseArea that eats events (so you can adjust size as you will)
49 property alias mouseArea: mouseArea
27 property alias mouseArea: mouseArea
50−
51− // X/Y offsets for text
52− property real textXOffset: 0
28+ property real opacityOverride: 1
53− property real textYOffset: 0
29+ property bool paused
54−
30+ property bool skipped
55− // Foreground text opacity
31+ property bool isReady
56− property real textOpacity: 1
57
32
58 signal finished()
33 signal finished()
59
34
60− function showError() {
61− errorTimer.start();
62− }
63−
64 ////
35 ////
65
36
37+ QtObject {
38+ id: d
39+ property bool showOnUnpause
40+ }
41+
66 visible: false
42 visible: false
67 shown: false
43 shown: false
68
44
45+ opacity: Math.max(Math.min(_showOpacity, opacityOverride), 0)
46+ onOpacityOverrideChanged: {
47+ if (opacityOverride <= 0) {
48+ d.showOnUnpause = false;
49+ hide();
50+ }
51+ }
69− property real _foregroundHideOpacity
52+ property real _showOpacity: 0
53+
54+ onPausedChanged: {
55+ if (paused && shown) {
56+ d.showOnUnpause = true;
57+ hide();
58+ } else if (!paused && d.showOnUnpause) {
59+ if (isReady) {
60+ show();
61+ } else if (hideAnimation.running) {
62+ hideAnimation.stop();
63+ }
64+ d.showOnUnpause = false;
65+ }
66+ }
70
67
71 showAnimation: StandardAnimation {
68 showAnimation: StandardAnimation {
72− property: root.backgroundFadesIn ? "opacity" : "_foregroundHideOpacity"
69+ property: "_showOpacity"
73 from: 0
70 from: 0
74 to: 1
71 to: 1
75− duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration
72+ duration: UbuntuAnimation.SleepyDuration
76 onStarted: root.visible = true
73 onStarted: root.visible = true
77 }
74 }
78
75
79 hideAnimation: StandardAnimation {
76 hideAnimation: StandardAnimation {
80− property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity"
77+ property: "_showOpacity"
81 to: 0
78 to: 0
82 duration: UbuntuAnimation.BriskDuration
79 duration: UbuntuAnimation.BriskDuration
83 onStopped: {
80 onStopped: {
84 root.visible = false;
81 root.visible = false;
85− root.finished();
82+ if (!d.showOnUnpause) {
86− }
83+ root.finished();
87− }
88−
89− QtObject {
90− id: d
91−
92− readonly property real sideMargin: units.gu(5.5)
93− readonly property real verticalOffset: -units.gu(9)
94− readonly property real textXOffset: Math.max(0, root.textXOffset - sideMargin + units.gu(2))
95−
96− property real fadeInOffset: {
97− if (showAnimation.running) {
98− var opacity = root[root.showAnimation.property]
99− return (1 - opacity) * units.gu(3);
100− } else {
101− return 0;
102 }
84 }
103 }
85 }
104 }
86 }
105
87
106− Timer {
107− id: errorTimer
108− interval: 3500
109− }
110−
111 MouseArea { // eat any errant presses
88 MouseArea { // eat any errant presses
112 id: mouseArea
89 id: mouseArea
113 anchors.fill: parent
90 anchors.fill: parent
114 }
91 }
115
92
116− Rectangle {
93+ Image {
117− anchors.fill: parent
94+ id: background
118− color: "black"
95+ // Use x/y/height/width instead of anchors so that we don't adjust
119− opacity: 0.82
96+ // the image if the OSK appears.
97+ x: 0
98+ y: 0
99+ height: root.height
100+ width: root.width
101+ fillMode: Image.PreserveAspectCrop
120 }
102 }
121
103
122− Item {
104+ Image {
123− id: foreground
105+ id: arrow
124− anchors.fill: parent
106+ width: units.gu(1.5)
125− opacity: root._foregroundHideOpacity
107+ source: Qt.resolvedUrl("graphics/arrow.svg")
126−
108+ fillMode: Image.PreserveAspectFit
127− Item {
109+ mipmap: true
128− anchors.fill: parent
129− opacity: root.textOpacity
130−
131− Label {
132− id: titleLabel
133− anchors {
134− top: parent.verticalCenter
135− topMargin: d.verticalOffset + root.textYOffset
136− left: parent.left
137− leftMargin: d.sideMargin + d.textXOffset
138− }
139− width: parent.width - d.sideMargin * 2
140− horizontalAlignment: Text.AlignLeft
141− wrapMode: Text.Wrap
142− font.weight: Font.Light
143− font.pixelSize: units.gu(3.5)
144− }
145−
146− Label {
147− id: textLabel
148− anchors {
149− top: titleLabel.bottom
150− topMargin: units.gu(2)
151− left: parent.left
152− leftMargin: d.sideMargin + d.textXOffset
153− }
154− width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66)
155− horizontalAlignment: Text.AlignLeft
156− wrapMode: Text.Wrap
157− font.weight: Font.Light
158− font.pixelSize: units.gu(2.5)
159− }
160−
161− // We use two separate labels like this rather than just changing
162− // the text of the above labels because we want to know where to place
163− // sliders (via root.textBottom) without having that place change
164− // as the text changes length.
165− Label {
166− id: errorTitleLabel
167− objectName: "errorTitleLabel"
168− anchors {
169− top: titleLabel.top
170− left: titleLabel.left
171− }
172− width: titleLabel.width
173− horizontalAlignment: titleLabel.horizontalAlignment
174− wrapMode: titleLabel.wrapMode
175− font.weight: titleLabel.font.weight
176− font.pixelSize: titleLabel.font.pixelSize
177− opacity: 0
178− text: i18n.tr("You almost got it!")
179− }
180−
181− Label {
182− id: errorTextLabel
183− objectName: "errorTextLabel"
184− anchors {
185− top: errorTitleLabel.bottom
186− topMargin: textLabel.anchors.topMargin
187− left: textLabel.left
188− }
189− width: textLabel.width
190− horizontalAlignment: textLabel.horizontalAlignment
191− wrapMode: textLabel.wrapMode
192− font.weight: textLabel.font.weight
193− font.pixelSize: textLabel.font.pixelSize
194− opacity: 0
195− text: i18n.tr("Try again.")
196− }
197− }
198−
199− // A place for subclasses to add extra widgets
200− Item {
201− id: foregroundExtra
202− anchors.fill: parent
203− }
204− }
205−
206− states: State {
207− name: "errorState"
208− when: errorTimer.running
209− PropertyChanges { target: titleLabel; opacity: 0 }
210− PropertyChanges { target: textLabel; opacity: 0 }
211− PropertyChanges { target: errorTitleLabel; opacity: 1 }
212− PropertyChanges { target: errorTextLabel; opacity: 1 }
213 }
110 }
214
111
215− transitions: Transition {
112+ Label {
216− to: "errorState"
113+ id: label
217− reversible: true
114+ objectName: "tutorialLabel"
218− SequentialAnimation {
115+ fontSize: "large"
219− ParallelAnimation {
116+ font.weight: Font.Light
220− StandardAnimation {
117+ color: "#333333"
221− target: titleLabel
118+ wrapMode: Text.Wrap
222− property: "opacity"
119+ lineHeight: 1.2
223− duration: UbuntuAnimation.BriskDuration
224− }
225− StandardAnimation {
226− target: textLabel
227− property: "opacity"
228− duration: UbuntuAnimation.BriskDuration
229− }
230− }
231− ParallelAnimation {
232− StandardAnimation {
233− target: errorTitleLabel
234− property: "opacity"
235− duration: UbuntuAnimation.BriskDuration
236− }
237− StandardAnimation {
238− target: errorTextLabel
239− property: "opacity"
240− duration: UbuntuAnimation.BriskDuration
241− }
242− }
243− }
244 }
120 }
245}
121}
qml/Tutorial/TutorialRight.qml+30−192
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -16,208 +16,46 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19−import Ubuntu.Gestures 0.1
20−import Unity.Application 0.1
21−import "../Components"
22−import "../Stages"
23−import "." as LocalComponents
24
19
25TutorialPage {
20TutorialPage {
26 id: root
21 id: root
27
22
28− property var panel
23+ property var stage
29− property alias edgeSize: stage.dragAreaWidth
24+ property string usageScenario
30
25
31− title: i18n.tr("To view open apps")
26+ // When on phone or tablet, fade out as the drag progresses
32− text: i18n.tr("Long swipe from the right edge.")
27+ opacityOverride: usageScenario === "desktop" ? 1 : 1 - stage.dragProgress * 2
33
28
34− textOpacity: 1 - slider.percent
29+ // Else on desktop, fade out when the spread is shown
35−
30+ Connections {
36− SequentialAnimation {
31+ target: usageScenario === "desktop" ? stage : null
37− id: teaseAnimation
32+ ignoreUnknownSignals: true
38− paused: running && root.paused
33+ onSpreadShownChanged: if (stage.spreadShown && root.shown) root.hide()
39− running: !stage.dragging && stage.dragProgress === 0
40− loops: Animation.Infinite
41−
42− UbuntuNumberAnimation {
43− target: stage
44− property: "x"
45− to: -units.gu(2)
46− duration: UbuntuAnimation.SleepyDuration
47− }
48− UbuntuNumberAnimation {
49− target: stage
50− property: "x"
51− to: 0
52− duration: UbuntuAnimation.SleepyDuration
53− }
54 }
34 }
55
35
56− foreground {
36+ mouseArea {
57− children: [
37+ anchors.rightMargin: stage.dragAreaWidth
58− LocalComponents.Slider {
59− id: slider
60− anchors {
61− right: parent.right
62− top: parent.top
63− topMargin: root.textBottom + units.gu(3)
64− }
65− rotation: 180
66− offset: stage.dragProgress - stage.x
67− active: stage.dragging
68− },
69−
70− // Just assume PhoneStage for now. The tablet version of the right-edge
71− // tutorial is still being spec'd by the design team.
72− PhoneStage {
73− id: stage
74− objectName: "stage"
75− anchors.top: parent.top
76− width: parent.width
77− height: parent.height
78− applicationManager: fakeAppManager
79− color: "transparent"
80− interactive: false
81− altTabEnabled: false
82− focusFirstApp: false
83− startScale: 0.8
84− endScale: 0.6
85− dragAreaOverlap: -x
86−
87− onOpened: {
88− overlay.show();
89− root.textOpacity = 0;
90− slider.visible = false;
91− }
92−
93− onDraggingChanged: {
94− if (!dragging) {
95− if (!overlay.shown) {
96− root.showError();
97− }
98− teaseAnimation.complete();
99− }
100− }
101− },
102−
103− Showable {
104− id: overlay
105− objectName: "overlay"
106− anchors.fill: parent
107−
108− opacity: 0
109− shown: false
110− showAnimation: UbuntuNumberAnimation { property: "opacity"; to: 1 }
111−
112− Label {
113− anchors.top: parent.top
114− anchors.topMargin: root.panel.panelHeight + units.gu(2)
115− anchors.left: parent.left
116− anchors.leftMargin: units.gu(2)
117− anchors.right: parent.right
118− anchors.rightMargin: units.gu(2)
119− wrapMode: Text.Wrap
120− horizontalAlignment: Text.AlignHCenter
121− fontSize: "large"
122− text: i18n.tr("View all your running tasks.")
123− }
124−
125− LocalComponents.Tick {
126− objectName: "tick"
127− anchors.bottom: bottomOverlayText.top
128− anchors.bottomMargin: units.gu(1)
129− anchors.horizontalCenter: bottomOverlayText.horizontalCenter
130− onClicked: root.hide()
131− }
132−
133− Label {
134− id: bottomOverlayText
135− anchors.bottom: parent.bottom
136− anchors.bottomMargin: units.gu(2)
137− anchors.left: parent.left
138− anchors.leftMargin: units.gu(2)
139− anchors.right: parent.right
140− anchors.rightMargin: units.gu(2)
141− wrapMode: Text.Wrap
142− horizontalAlignment: Text.AlignHCenter
143− fontSize: "small"
144− text: i18n.tr("Tap here to continue.")
145− }
146− }
147− ]
148 }
38 }
149
39
150− ListModel {
40+ background {
151− id: fakeAppManager
41+ sourceSize.height: 1916
152−
42+ sourceSize.width: 1080
153− readonly property string focusedApplicationId: "facebook"
43+ source: Qt.resolvedUrl("graphics/background1.png")
154−
155− function focusApplication(appId) {}
156− function requestFocusApplication(appId) {}
157− function findApplication(appId) {return null;}
158−
159− signal applicationAdded(string appId)
160− signal applicationRemoved(string appId)
161− signal focusRequested(string appId)
162−
163− ListElement {
164− appId: "facebook"
165− fullscreen: false
166− name: ""
167− icon: ""
168− state: ApplicationInfoInterface.Stopped
169− splashTitle: ""
170− splashImage: ""
171− splashShowHeader: false
172− splashColor: "transparent"
173− splashColorHeader: "transparent"
174− splashColorFooter: "transparent"
175− defaultScreenshot: "../Tutorial/graphics/facebook.png"
176− }
177−
178− ListElement {
179− appId: "camera"
180− fullscreen: false
181− name: ""
182− icon: ""
183− state: ApplicationInfoInterface.Stopped
184− splashTitle: ""
185− splashImage: ""
186− splashShowHeader: false
187− splashColor: "transparent"
188− splashColorHeader: "transparent"
189− splashColorFooter: "transparent"
190− defaultScreenshot: "../Tutorial/graphics/camera.png"
191− }
44+ }
192
45
193− ListElement {
46+ arrow {
194− appId: "gallery"
47+ anchors.right: root.right
195− fullscreen: false
48+ anchors.rightMargin: units.gu(2)
196− name: ""
49+ anchors.verticalCenter: root.verticalCenter
197− icon: ""
198− state: ApplicationInfoInterface.Stopped
199− splashTitle: ""
200− splashImage: ""
201− splashShowHeader: false
202− splashColor: "transparent"
203− splashColorHeader: "transparent"
204− splashColorFooter: "transparent"
205− defaultScreenshot: "../Tutorial/graphics/gallery.png"
206− }
50+ }
207
51
208− ListElement {
52+ label {
209− appId: "dialer"
53+ text: root.usageScenario === "desktop" ?
210− fullscreen: false
54+ i18n.tr("Hover your mouse on the right edge to view your open apps") :
211− name: ""
55+ i18n.tr("Short or long swipe from the right edge to view your open apps")
212− icon: ""
56+ anchors.right: arrow.left
213− state: ApplicationInfoInterface.Stopped
57+ anchors.rightMargin: units.gu(2) - (label.width - label.contentWidth)
214− splashTitle: ""
58+ anchors.verticalCenter: arrow.verticalCenter
215− splashImage: ""
59+ width: Math.min(units.gu(40), arrow.x - units.gu(4))
216− splashShowHeader: false
217− splashColor: "transparent"
218− splashColorHeader: "transparent"
219− splashColorFooter: "transparent"
220− defaultScreenshot: "../Tutorial/graphics/dialer.png"
221− }
222 }
60 }
223}
61}
qml/Tutorial/TutorialTop.qmlnew file+61
@@ -0,0 +1,61 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ property var panel
25+
26+ opacityOverride: 1 - panel.indicators.unitProgress
27+
28+ QtObject {
29+ id: d
30+ readonly property bool landscape: root.width > units.gu(50)
31+ readonly property real columnWidth: landscape ? panel.indicators.width : root.width
32+ }
33+
34+ mouseArea {
35+ anchors.topMargin: panel.indicators.minimizedPanelHeight
36+ }
37+
38+ background {
39+ sourceSize.height: 1916
40+ sourceSize.width: 1080
41+ source: Qt.resolvedUrl("graphics/background2.png")
42+ }
43+
44+ arrow {
45+ anchors.top: root.top
46+ anchors.topMargin: units.gu(4)
47+ anchors.horizontalCenter: root.right
48+ anchors.horizontalCenterOffset: - d.columnWidth / 2
49+ rotation: -90
50+ }
51+
52+ label {
53+ text: d.landscape ? i18n.tr("Swipe from the top right edge to open the notification bar")
54+ : i18n.tr("Swipe from the top edge to open the notification bar")
55+ anchors.top: arrow.bottom
56+ anchors.topMargin: units.gu(3)
57+ anchors.horizontalCenter: arrow.horizontalCenter
58+ anchors.horizontalCenterOffset: (label.width - label.contentWidth) / 2
59+ width: d.columnWidth - units.gu(8)
60+ }
61+}
qml/Tutorial/graphics/arrow.svgnew file+19
@@ -0,0 +1,19 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<svg width="35px" height="106px" viewBox="0 0 35 106" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
3+ <!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
4+ <title>left swipe arrow</title>
5+ <desc>Created with Sketch.</desc>
6+ <defs></defs>
7+ <g id="Final-design" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
8+ <g id="1.Left-swipe" sketch:type="MSArtboardGroup" transform="translate(-54.000000, -906.000000)">
9+ <g id="left-swipe-arrow" sketch:type="MSLayerGroup" transform="translate(15.000000, 903.000000)">
10+ <g id="layer1" transform="translate(0.857146, 0.494959)" sketch:type="MSShapeGroup">
11+ <g id="g4216" transform="translate(0.952384, 0.831650)">
12+ <path d="M38.0127338,5.94292186 C40.8696258,9.70328846 43.6961579,13.5492607 46.4521869,17.5776877 C50.0756129,22.8738077 53.4767719,28.2917937 56.6611719,33.8315937 C60.6031619,40.9214757 64.3797399,47.5658927 67.7217189,54.6714377 C64.3797399,61.7769828 60.6031619,68.4213998 56.6611719,75.5112818 C53.4767719,81.0510808 50.0756129,86.4690678 46.4521869,91.7651878 C43.6961579,95.7936138 40.8696258,99.6395938 38.0127338,103.399951 L38.0127338,107.669481 C42.0030779,102.508161 45.9748129,97.3405748 49.5537499,92.0679218 C54.5609019,84.6937758 59.0395839,77.5443828 62.9990629,70.6226098 C66.5019159,64.4983168 69.0866949,59.3452977 71.2158589,54.6733907 L69.4678129,54.6713907 L71.2158589,54.6693907 C69.0866949,49.9975777 66.5019159,44.8445587 62.9990629,38.7202657 C59.0395839,31.7984927 54.5609019,24.6490997 49.5537499,17.2749527 C45.9748129,12.0022997 42.0030779,6.83471186 38.0127338,1.67339065 L38.0127338,5.94292186 Z" id="left-swipe-arrow" fill="#6C0D52" transform="translate(54.614296, 54.671436) scale(-1, 1) translate(-54.614296, -54.671436) "></path>
13+ <rect id="rect4782-01" transform="translate(54.190469, 54.673389) rotate(90.000000) translate(-54.190469, -54.673389) " x="0.190470662" y="0.67339065" width="107.999997" height="107.999997"></rect>
14+ </g>
15+ </g>
16+ </g>
17+ </g>
18+ </g>
19+</svg>
qml/Tutorial/graphics/background1.pngnew file

Binary file not shown.

qml/Tutorial/graphics/background2.pngnew file

Binary file not shown.

qml/Tutorial/graphics/camera.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/chevron.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/dialer.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/facebook.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/gallery.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/tick.pngdeleted

Binary file not shown.

tests/autopilot/unity8/shell/emulators.py−2
@@ -36,7 +36,6 @@ __all__ = [
36 'greeter',
36 'greeter',
37 'launcher',
37 'launcher',
38 'main_window',
38 'main_window',
39− 'tutorial',
40 'UnityEmulatorException',
39 'UnityEmulatorException',
41]
40]
42
41
@@ -46,7 +45,6 @@ from unity8 import (
46 dash,
45 dash,
47 launcher,
46 launcher,
48 shell as main_window,
47 shell as main_window,
49− tutorial,
50 UnityException as UnityEmulatorException
48 UnityException as UnityEmulatorException
51)
49)
52from unity8.shell import create_interactive_notification
50from unity8.shell import create_interactive_notification
tests/autopilot/unity8/shell/tests/test_tutorial.pydeleted−57
@@ -1,57 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−#
3−# Unity Autopilot Test Suite
4−# Copyright (C) 2014, 2015 Canonical
5−#
6−# This program is free software: you can redistribute it and/or modify
7−# it under the terms of the GNU General Public License as published by
8−# the Free Software Foundation, either version 3 of the License, or
9−# (at your option) any later version.
10−#
11−# This program is distributed in the hope that it will be useful,
12−# but WITHOUT ANY WARRANTY; without even the implied warranty of
13−# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14−# GNU General Public License for more details.
15−#
16−# You should have received a copy of the GNU General Public License
17−# along with this program. If not, see <http://www.gnu.org/licenses/>.
18−#
19−
20−from autopilot.matchers import Eventually
21−from testtools.matchers import Equals
22−
23−from unity8.shell import (
24− fixture_setup,
25− tests
26−)
27−# unused import to load the tutorial helpers custom proxy objects.
28−from unity8 import tutorial as tutorial_helpers # NOQA
29−
30−
31−class TutorialTestCase(tests.UnityTestCase):
32−
33− def setUp(self):
34− super().setUp()
35− self._qml_mock_enabled = False
36− self._data_dirs_mock_enabled = False
37−
38− self.useFixture(fixture_setup.Tutorial(True))
39− self.unity = self.launch_unity()
40−
41− def test_complete_tutorial(self):
42− greeter = self.main_window.get_greeter()
43− tutorial = self.unity.select_single('Tutorial')
44− self.assertThat(tutorial.running, Eventually(Equals(True)))
45− greeter.swipe()
46− page = self.unity.wait_select_single(objectName='tutorialLeft')
47− page.short_swipe_right()
48− page = self.unity.wait_select_single(objectName='tutorialLeftFinish')
49− page.tap()
50− page = self.unity.wait_select_single(objectName='tutorialRight')
51− page.swipe_left()
52− page.tap()
53− page = self.unity.wait_select_single(objectName='tutorialBottom')
54− page.swipe_up()
55− page = self.unity.wait_select_single(objectName='tutorialBottomFinish')
56− page.tap()
57− self.assertThat(tutorial.running, Eventually(Equals(False)))
tests/autopilot/unity8/tutorial.pydeleted−73
@@ -1,73 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−#
3−# Unity Autopilot Test Suite
4−# Copyright (C) 2014 Canonical
5−#
6−# This program is free software: you can redistribute it and/or modify
7−# it under the terms of the GNU General Public License as published by
8−# the Free Software Foundation, either version 3 of the License, or
9−# (at your option) any later version.
10−#
11−# This program is distributed in the hope that it will be useful,
12−# but WITHOUT ANY WARRANTY; without even the implied warranty of
13−# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14−# GNU General Public License for more details.
15−#
16−# You should have received a copy of the GNU General Public License
17−# along with this program. If not, see <http://www.gnu.org/licenses/>.
18−#
19−
20−import logging
21−
22−import ubuntuuitoolkit
23−
24−import autopilot
25−from autopilot import introspection
26−
27−
28−logger = logging.getLogger(__name__)
29−
30−
31−class TutorialPage(
32− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
33−
34− @classmethod
35− def validate_dbus_object(cls, path, state):
36− name = introspection.get_classname_from_path(path)
37− return name in (b'TutorialPage', b'TutorialLeft',
38− b'TutorialLeftFinish', b'TutorialRight',
39− b'TutorialBottom', b'TutorialBottomFinish')
40−
41− @autopilot.logging.log_action(logger.info)
42− def short_swipe_right(self):
43− self.shown.wait_for(True)
44− x, y, width, height = self.globalRect
45− start_x = x
46− stop_x = x + width // 3
47− start_y = stop_y = y + height // 2
48− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
49−
50− @autopilot.logging.log_action(logger.info)
51− def swipe_left(self):
52− self.shown.wait_for(True)
53− x, y, width, height = self.globalRect
54− start_x = width
55− stop_x = x
56− start_y = stop_y = y + height // 2
57− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
58−
59− @autopilot.logging.log_action(logger.info)
60− def swipe_up(self):
61− self.shown.wait_for(True)
62− x, y, width, height = self.globalRect
63− start_y = height
64− stop_y = y
65− start_x = stop_x = x + width // 2
66− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
67−
68− @autopilot.logging.log_action(logger.info)
69− def tap(self):
70− """Tap the tick button to complete this step."""
71− self.shown.wait_for(True)
72− button = self.wait_select_single(objectName="tick")
73− self.pointing_device.click_object(button)
tests/mocks/AccountsService/AccountsService.cpp+20
@@ -26,6 +26,7 @@ AccountsService::AccountsService(QObject* parent)
26 m_statsWelcomeScreen(true),
26 m_statsWelcomeScreen(true),
27 m_failedLogins(0),
27 m_failedLogins(0),
28 m_demoEdges(false),
28 m_demoEdges(false),
29+ m_demoEdgesCompleted(),
29 m_hereEnabled(false),
30 m_hereEnabled(false),
30 m_hereLicensePath("")
31 m_hereLicensePath("")
31{
32{
@@ -54,6 +55,25 @@ void AccountsService::setDemoEdges(bool demoEdges)
54 Q_EMIT demoEdgesChanged();
55 Q_EMIT demoEdgesChanged();
55}
56}
56
57
58+QStringList AccountsService::demoEdgesCompleted() const
59+{
60+ return m_demoEdgesCompleted;
61+}
62+
63+void AccountsService::markDemoEdgeCompleted(const QString &edge)
64+{
65+ if (!m_demoEdgesCompleted.contains(edge)) {
66+ m_demoEdgesCompleted << edge;
67+ Q_EMIT demoEdgesCompletedChanged();
68+ }
69+}
70+
71+void AccountsService::setDemoEdgesCompleted(const QStringList &demoEdgesCompleted)
72+{
73+ m_demoEdgesCompleted = demoEdgesCompleted;
74+ Q_EMIT demoEdgesCompletedChanged();
75+}
76+
57bool AccountsService::enableLauncherWhileLocked() const
77bool AccountsService::enableLauncherWhileLocked() const
58{
78{
59 return m_enableLauncherWhileLocked;
79 return m_enableLauncherWhileLocked;
tests/mocks/AccountsService/AccountsService.h+10
@@ -19,6 +19,7 @@
19
19
20#include <QObject>
20#include <QObject>
21#include <QString>
21#include <QString>
22+#include <QStringList>
22#include <QVariant>
23#include <QVariant>
23
24
24class AccountsService: public QObject
25class AccountsService: public QObject
@@ -33,6 +34,10 @@ class AccountsService: public QObject
33 READ demoEdges
34 READ demoEdges
34 WRITE setDemoEdges
35 WRITE setDemoEdges
35 NOTIFY demoEdgesChanged)
36 NOTIFY demoEdgesChanged)
37+ Q_PROPERTY (QStringList demoEdgesCompleted
38+ READ demoEdgesCompleted
39+ WRITE setDemoEdgesCompleted // only available in mock
40+ NOTIFY demoEdgesCompletedChanged)
36 Q_PROPERTY (bool enableLauncherWhileLocked
41 Q_PROPERTY (bool enableLauncherWhileLocked
37 READ enableLauncherWhileLocked
42 READ enableLauncherWhileLocked
38 WRITE setEnableLauncherWhileLocked // only available in mock
43 WRITE setEnableLauncherWhileLocked // only available in mock
@@ -82,6 +87,9 @@ public:
82 void setUser(const QString &user);
87 void setUser(const QString &user);
83 bool demoEdges() const;
88 bool demoEdges() const;
84 void setDemoEdges(bool demoEdges);
89 void setDemoEdges(bool demoEdges);
90+ QStringList demoEdgesCompleted() const;
91+ void setDemoEdgesCompleted(const QStringList &demoEdges);
92+ Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
85 bool enableLauncherWhileLocked() const;
93 bool enableLauncherWhileLocked() const;
86 void setEnableLauncherWhileLocked(bool enableLauncherWhileLocked);
94 void setEnableLauncherWhileLocked(bool enableLauncherWhileLocked);
87 bool enableIndicatorsWhileLocked() const;
95 bool enableIndicatorsWhileLocked() const;
@@ -106,6 +114,7 @@ public:
106Q_SIGNALS:
114Q_SIGNALS:
107 void userChanged();
115 void userChanged();
108 void demoEdgesChanged();
116 void demoEdgesChanged();
117+ void demoEdgesCompletedChanged();
109 void enableLauncherWhileLockedChanged();
118 void enableLauncherWhileLockedChanged();
110 void enableIndicatorsWhileLockedChanged();
119 void enableIndicatorsWhileLockedChanged();
111 void backgroundFileChanged();
120 void backgroundFileChanged();
@@ -125,6 +134,7 @@ private:
125 bool m_statsWelcomeScreen;
134 bool m_statsWelcomeScreen;
126 uint m_failedLogins;
135 uint m_failedLogins;
127 bool m_demoEdges;
136 bool m_demoEdges;
137+ QStringList m_demoEdgesCompleted;
128 bool m_hereEnabled;
138 bool m_hereEnabled;
129 QString m_hereLicensePath;
139 QString m_hereLicensePath;
130 QString m_realName;
140 QString m_realName;
tests/mocks/AccountsService/AccountsService.qmltypes+5
@@ -23,6 +23,7 @@ Module {
23 }
23 }
24 Property { name: "user"; type: "string" }
24 Property { name: "user"; type: "string" }
25 Property { name: "demoEdges"; type: "bool" }
25 Property { name: "demoEdges"; type: "bool" }
26+ Property { name: "demoEdgesCompleted"; type: "QStringList" }
26 Property { name: "enableLauncherWhileLocked"; type: "bool" }
27 Property { name: "enableLauncherWhileLocked"; type: "bool" }
27 Property { name: "enableIndicatorsWhileLocked"; type: "bool" }
28 Property { name: "enableIndicatorsWhileLocked"; type: "bool" }
28 Property { name: "backgroundFile"; type: "string" }
29 Property { name: "backgroundFile"; type: "string" }
@@ -32,5 +33,9 @@ Module {
32 Property { name: "hereEnabled"; type: "bool" }
33 Property { name: "hereEnabled"; type: "bool" }
33 Property { name: "hereLicensePath"; type: "string" }
34 Property { name: "hereLicensePath"; type: "string" }
34 Property { name: "hereLicensePathValid"; type: "bool"; isReadonly: true }
35 Property { name: "hereLicensePathValid"; type: "bool"; isReadonly: true }
36+ Method {
37+ name: "markDemoEdgeCompleted"
38+ Parameter { name: "edge"; type: "string" }
39+ }
35 }
40 }
36}
41}
tests/mocks/Unity/Application/ApplicationInfo.h+1−1
@@ -60,7 +60,7 @@ public:
60
60
61 QUrl icon() const override { return m_icon; }
61 QUrl icon() const override { return m_icon; }
62
62
63− void setStage(Stage value);
63+ Q_INVOKABLE void setStage(Stage value); // invokable only for mock
64 Stage stage() const override { return m_stage; }
64 Stage stage() const override { return m_stage; }
65
65
66 Q_INVOKABLE void setState(State value);
66 Q_INVOKABLE void setState(State value);
tests/mocks/Utils/CMakeLists.txt+1−1
@@ -12,7 +12,7 @@ set(QMLPLUGIN_SRC
12 ${CMAKE_SOURCE_DIR}/plugins/Utils/qlimitproxymodelqml.cpp
12 ${CMAKE_SOURCE_DIR}/plugins/Utils/qlimitproxymodelqml.cpp
13 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
13 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
14 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitymenumodelpaths.cpp
14 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitymenumodelpaths.cpp
15− ${CMAKE_SOURCE_DIR}/plugins/Utils/windowkeysfilter.cpp
15+ ${CMAKE_SOURCE_DIR}/plugins/Utils/windowinputfilter.cpp
16 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
16 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
17 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
17 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
18 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
18 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
tests/mocks/Utils/Utils.qmltypes+2−2
@@ -145,10 +145,10 @@ Module {
145 }
145 }
146 }
146 }
147 Component {
147 Component {
148− name: "WindowKeysFilter"
148+ name: "WindowInputFilter"
149 defaultProperty: "data"
149 defaultProperty: "data"
150 prototype: "QQuickItem"
150 prototype: "QQuickItem"
151− exports: ["Utils/WindowKeysFilter 0.1"]
151+ exports: ["Utils/WindowInputFilter 0.1"]
152 exportMetaObjectRevisions: [0]
152 exportMetaObjectRevisions: [0]
153 }
153 }
154 Component {
154 Component {
tests/mocks/Utils/plugin.cpp+2−2
@@ -32,7 +32,7 @@
32#include <qlimitproxymodelqml.h>
32#include <qlimitproxymodelqml.h>
33#include <unitysortfilterproxymodelqml.h>
33#include <unitysortfilterproxymodelqml.h>
34#include <unitymenumodelpaths.h>
34#include <unitymenumodelpaths.h>
35−#include <windowkeysfilter.h>
35+#include <windowinputfilter.h>
36#include <windowscreenshotprovider.h>
36#include <windowscreenshotprovider.h>
37#include <easingcurve.h>
37#include <easingcurve.h>
38#include <timezoneFormatter.h>
38#include <timezoneFormatter.h>
@@ -69,7 +69,7 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
69 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
69 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
70 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
70 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
71 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
71 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
72− qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
72+ qmlRegisterType<WindowInputFilter>(uri, 0, 1, "WindowInputFilter");
73 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
73 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
74 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
74 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
75 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
75 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
tests/plugins/AccountsService/PropertiesServer.cpp+1
@@ -89,6 +89,7 @@ void PropertiesServer::Set(const QString &interface, const QString &property, co
89void PropertiesServer::Reset()
89void PropertiesServer::Reset()
90{
90{
91 m_properties["com.canonical.unity.AccountsService"]["demo-edges"] = false;
91 m_properties["com.canonical.unity.AccountsService"]["demo-edges"] = false;
92+ m_properties["com.canonical.unity.AccountsService"]["DemoEdgesCompleted"] = QStringList();
92 m_properties["com.canonical.unity.AccountsService"]["LauncherItems"] = QVariant::fromValue(QList<QVariantMap>());
93 m_properties["com.canonical.unity.AccountsService"]["LauncherItems"] = QVariant::fromValue(QList<QVariantMap>());
93 m_properties["com.canonical.unity.AccountsService.Private"]["FailedLogins"] = 0;
94 m_properties["com.canonical.unity.AccountsService.Private"]["FailedLogins"] = 0;
94 m_properties["com.ubuntu.touch.AccountsService.SecurityPrivacy"]["StatsWelcomeScreen"] = true;
95 m_properties["com.ubuntu.touch.AccountsService.SecurityPrivacy"]["StatsWelcomeScreen"] = true;
tests/plugins/AccountsService/client.cpp+37
@@ -117,6 +117,43 @@ private Q_SLOTS:
117 QCOMPARE(session.hereEnabled(), true);
117 QCOMPARE(session.hereEnabled(), true);
118 }
118 }
119
119
120+ void testMarkDemoEdgeCompleted()
121+ {
122+ AccountsService session(this, QTest::currentTestFunction());
123+ QSignalSpy changedSpy(&session, &AccountsService::demoEdgesCompletedChanged);
124+
125+ QCOMPARE(changedSpy.count(), 0);
126+ QCOMPARE(session.demoEdgesCompleted(), QStringList());
127+
128+ session.markDemoEdgeCompleted("testedge");
129+ QCOMPARE(changedSpy.count(), 1);
130+ QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
131+
132+ session.markDemoEdgeCompleted("testedge");
133+ QCOMPARE(changedSpy.count(), 1);
134+ QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
135+
136+ session.markDemoEdgeCompleted("testedge2");
137+ QCOMPARE(changedSpy.count(), 2);
138+ QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge" << "testedge2");
139+ }
140+
141+ void testAsynchronousChangeForDemoEdgesCompleted()
142+ {
143+ AccountsService session(this, QTest::currentTestFunction());
144+ QSignalSpy changedSpy(&session, &AccountsService::demoEdgesCompletedChanged);
145+
146+ QCOMPARE(changedSpy.count(), 0);
147+ QCOMPARE(session.demoEdgesCompleted(), QStringList());
148+
149+ ASSERT_DBUS_CALL(m_userInterface->call("Set",
150+ "com.canonical.unity.AccountsService",
151+ "DemoEdgesCompleted",
152+ dbusVariant(QStringList() << "testedge")));
153+ QTRY_COMPARE(changedSpy.count(), 1);
154+ QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
155+ }
156+
120 void testAsynchronousChangeForDemoEdges()
157 void testAsynchronousChangeForDemoEdges()
121 {
158 {
122 AccountsService session(this, QTest::currentTestFunction());
159 AccountsService session(this, QTest::currentTestFunction());
tests/qmltests/Components/tst_PhysicalKeysMapper.qml+3−3
@@ -26,9 +26,9 @@ Item {
26
26
27 property var physicalKeysMapper: loader.item
27 property var physicalKeysMapper: loader.item
28
28
29− WindowKeysFilter {
29+ WindowInputFilter {
30− Keys.onPressed: physicalKeysMapper.onKeyPressed(event, currentEventTimestamp);
30+ Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
31− Keys.onReleased: physicalKeysMapper.onKeyReleased(event, currentEventTimestamp);
31+ Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
32 }
32 }
33
33
34 Loader {
34 Loader {
tests/qmltests/Greeter/tst_Greeter.qml+7−1
@@ -328,9 +328,15 @@ Item {
328 LightDM.Greeter.active = true;
328 LightDM.Greeter.active = true;
329
329
330 greeter.forcedUnlock = true;
330 greeter.forcedUnlock = true;
331− compare(view.locked, false);
331+ compare(greeter.required, false);
332 greeter.forcedUnlock = false;
332 greeter.forcedUnlock = false;
333
333
334+ // Now recover from tearing down the view above
335+ LightDM.Greeter.showGreeter();
336+ tryCompare(greeter, "required", true);
337+ tryCompare(greeter, "waiting", false);
338+ view = findChild(greeter, "testView");
339+
334 selectUser("no-password");
340 selectUser("no-password");
335 tryCompare(view, "locked", false);
341 tryCompare(view, "locked", false);
336 selectUser("has-password");
342 selectUser("has-password");
tests/qmltests/Tutorial/tst_Tutorial.qml+586−193
@@ -19,15 +19,20 @@ import QtTest 1.0
19import AccountsService 0.1
19import AccountsService 0.1
20import IntegratedLightDM 0.1 as LightDM
20import IntegratedLightDM 0.1 as LightDM
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22+import Ubuntu.Components.ListItems 1.3
23+import Ubuntu.Telephony 0.1 as Telephony
22import Unity.Application 0.1
24import Unity.Application 0.1
23import Unity.Test 0.1 as UT
25import Unity.Test 0.1 as UT
26+import Utils 0.1
24
27
25import "../../../qml"
28import "../../../qml"
29+import "../../../qml/Components"
26
30
27−Item {
31+Rectangle {
28 id: root
32 id: root
33+ color: UbuntuColors.lightGrey
29− width: shellLoader.width + buttons.width
34+ width: units.gu(100) + buttons.width
30− height: shellLoader.height
35+ height: units.gu(71)
31
36
32 QtObject {
37 QtObject {
33 id: applicationArguments
38 id: applicationArguments
@@ -45,6 +50,11 @@ Item {
45 }
50 }
46 }
51 }
47
52
53+ Telephony.CallEntry {
54+ id: phoneCall
55+ phoneNumber: "+447812221111"
56+ }
57+
48 Component.onCompleted: {
58 Component.onCompleted: {
49 // must set the mock mode before loading the Shell
59 // must set the mock mode before loading the Shell
50 LightDM.Greeter.mockMode = "single-pin";
60 LightDM.Greeter.mockMode = "single-pin";
@@ -52,21 +62,63 @@ Item {
52 shellLoader.active = true;
62 shellLoader.active = true;
53 }
63 }
54
64
55− Row {
65+ Item {
56− spacing: 0
66+ id: shellContainer
57− anchors.fill: parent
67+ anchors.left: root.left
68+ anchors.right: buttons.left
69+ anchors.top: root.top
70+ anchors.bottom: root.bottom
58
71
59 Loader {
72 Loader {
60 id: shellLoader
73 id: shellLoader
61
74
62 active: false
75 active: false
76+ anchors.horizontalCenter: parent.horizontalCenter
77+ anchors.top: parent.top
78+ anchors.bottom: parent.bottom
79+
80+ property int shellOrientation: Qt.PortraitOrientation
81+ property int nativeOrientation: Qt.PortraitOrientation
82+ property int primaryOrientation: Qt.PortraitOrientation
83+
84+ state: modeSelector.model[modeSelector.selectedIndex]
85+ states: [
86+ State {
87+ name: "phone"
88+ PropertyChanges {
89+ target: shellLoader
63− width: units.gu(40)
90+ width: units.gu(40)
91+ }
92+ },
93+ State {
94+ name: "tablet"
95+ PropertyChanges {
96+ target: shellLoader
97+ width: units.gu(100)
98+ shellOrientation: Qt.LandscapeOrientation
99+ nativeOrientation: Qt.LandscapeOrientation
100+ primaryOrientation: Qt.LandscapeOrientation
101+ }
102+ },
103+ State {
104+ name: "desktop"
105+ PropertyChanges {
106+ target: shellLoader
64− height: units.gu(71)
107+ width: units.gu(100)
108+ }
109+ }
110+ ]
65
111
66 property bool itemDestroyed: false
112 property bool itemDestroyed: false
67 sourceComponent: Component {
113 sourceComponent: Component {
68 Shell {
114 Shell {
115+ usageScenario: shellLoader.state
69 property string indicatorProfile: "phone"
116 property string indicatorProfile: "phone"
117+ orientation: shellLoader.shellOrientation
118+ orientations: Orientations {
119+ native_: shellLoader.nativeOrientation
120+ primary: shellLoader.primaryOrientation
121+ }
70
122
71 Component.onDestruction: {
123 Component.onDestruction: {
72 shellLoader.itemDestroyed = true;
124 shellLoader.itemDestroyed = true;
@@ -74,30 +126,93 @@ Item {
74 }
126 }
75 }
127 }
76 }
128 }
129+ }
77
130
78− Rectangle {
131+ Rectangle {
79− id: buttons
132+ id: buttons
80− color: "white"
133+ color: UbuntuColors.darkGrey
81− width: units.gu(30)
134+ width: units.gu(30)
82− height: shellLoader.height
135+ anchors.top: root.top
83−
136+ anchors.bottom: root.bottom
137+ anchors.right: root.right
138+
84− Column {
139+ Column {
85− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
140+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
86− spacing: units.gu(1)
141+ spacing: units.gu(1)
87− Row {
142+ Row {
88− anchors { left: parent.left; right: parent.right }
143+ anchors { left: parent.left; right: parent.right }
89− Button {
144+ Button {
90− text: "Restart Tutorial"
145+ text: "Hide Greeter"
91− onClicked: {
146+ onClicked: {
92− if (shellLoader.status !== Loader.Ready)
147+ if (shellLoader.status !== Loader.Ready)
93− return;
148+ return;
94−
149+
95− AccountsService.demoEdges = false;
150+ var greeter = testCase.findChild(shellLoader.item, "greeter");
96− AccountsService.demoEdges = true;
151+ if (greeter.shown) {
152+ greeter.hide();
97 }
153 }
98 }
154 }
99 }
155 }
100 }
156 }
157+
158+ Row {
159+ anchors { left: parent.left; right: parent.right }
160+ Button {
161+ text: "Restart Tutorial"
162+ onClicked: {
163+ if (shellLoader.status !== Loader.Ready)
164+ return;
165+
166+ AccountsService.demoEdges = false;
167+ AccountsService.demoEdgesCompleted = [];
168+ AccountsService.demoEdges = true;
169+ }
170+ }
171+ }
172+
173+ Row {
174+ anchors { left: parent.left; right: parent.right }
175+ CheckBox {
176+ onCheckedChanged: {
177+ if (checked) {
178+ callManager.foregroundCall = phoneCall;
179+ } else {
180+ callManager.foregroundCall = null;
181+ }
182+ }
183+ }
184+ Label {
185+ text: "Active Call"
186+ anchors.verticalCenter: parent.verticalCenter
187+ }
188+ }
189+
190+ Row {
191+ anchors { left: parent.left; right: parent.right }
192+ CheckBox {
193+ activeFocusOnPress: false
194+ onCheckedChanged: {
195+ var surface = SurfaceManager.inputMethodSurface;
196+ if (checked) {
197+ surface.setState(Mir.RestoredState);
198+ } else {
199+ surface.setState(Mir.MinimizedState);
200+ }
201+ }
202+ }
203+ Label {
204+ text: "Input Method"
205+ anchors.verticalCenter: parent.verticalCenter
206+ }
207+ }
208+
209+ ItemSelector {
210+ id: modeSelector
211+ anchors { left: parent.left; right: parent.right }
212+ activeFocusOnPress: false
213+ text: "Mode"
214+ model: ["phone", "tablet", "desktop"]
215+ }
101 }
216 }
102 }
217 }
103
218
@@ -111,17 +226,21 @@ Item {
111 property real halfHeight: shell ? shell.height / 2 : 0
226 property real halfHeight: shell ? shell.height / 2 : 0
112
227
113 function init() {
228 function init() {
114− tryCompare(shell, "enabled", true); // enabled by greeter when ready
229+ prepareShell();
115
230
116− AccountsService.demoEdges = false;
231+ var tutorialLeft = findChild(shell, "tutorialLeft");
117− AccountsService.demoEdges = true;
232+ tryCompare(tutorialLeft, "opacity", 1);
118− swipeAwayGreeter();
119 }
233 }
120
234
121 function cleanup() {
235 function cleanup() {
236+ resetLoader("phone");
237+ }
238+
239+ function resetLoader(state) {
122 shellLoader.itemDestroyed = false;
240 shellLoader.itemDestroyed = false;
123
241
124 shellLoader.active = false;
242 shellLoader.active = false;
243+ shellLoader.state = state;
125
244
126 tryCompare(shellLoader, "status", Loader.Null);
245 tryCompare(shellLoader, "status", Loader.Null);
127 tryCompare(shellLoader, "item", null);
246 tryCompare(shellLoader, "item", null);
@@ -143,6 +262,24 @@ Item {
143 removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item);
262 removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item);
144 }
263 }
145
264
265+ function prepareShell() {
266+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
267+
268+ WindowStateStorage.clear();
269+ SurfaceManager.inputMethodSurface.setState(Mir.MinimizedState);
270+ callManager.foregroundCall = null;
271+ AccountsService.demoEdges = false;
272+ AccountsService.demoEdgesCompleted = [];
273+ AccountsService.demoEdges = true;
274+
275+ LightDM.Greeter.hideGreeter();
276+ }
277+
278+ function loadShell(state) {
279+ resetLoader(state);
280+ prepareShell();
281+ }
282+
146 function killApps() {
283 function killApps() {
147 while (ApplicationManager.count > 1) {
284 while (ApplicationManager.count > 1) {
148 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
285 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
@@ -163,234 +300,490 @@ Item {
163 waitForRendering(greeter);
300 waitForRendering(greeter);
164 }
301 }
165
302
166− function waitForPage(name) {
303+ function openTutorialTop() {
167− waitForRendering(findChild(shell, name));
304+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
168− var page = findChild(shell, name);
305+ var tutorialTop = findChild(shell, "tutorialTop");
169− tryCompare(page, "shown", true);
306+ var tutorialTopTimer = findInvisibleChild(tutorialTop, "tutorialTopTimer");
170− tryCompare(page.showAnimation, "running", false);
307+
171− return page;
308+ tutorialTopTimer.interval = 1;
309+ AccountsService.demoEdgesCompleted = ["left", "left-long"];
310+
311+ tryCompare(tutorialLeftLoader, "active", false);
312+ tryCompare(tutorialTop, "shown", true);
313+ tryCompare(tutorialTop, "opacity", 1);
172 }
314 }
173
315
174− function checkTopEdge() {
316+ function openTutorialRight() {
175− touchFlick(shell, halfWidth, 0, halfWidth, halfHeight);
317+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
318+ var tutorialRight = findChild(shell, "tutorialRight");
176
319
177− var panel = findChild(shell, "panel");
320+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
178− tryCompare(panel.indicators, "fullyClosed", true);
321+ ApplicationManager.startApplication("gallery-app");
322+ ApplicationManager.startApplication("facebook-webapp");
323+
324+ tryCompare(tutorialLeftLoader, "active", false);
325+ tryCompare(tutorialRight, "shown", true);
326+ tryCompare(tutorialRight, "opacity", 1);
179 }
327 }
180
328
181− function checkLeftEdge() {
329+ function openTutorialBottom() {
182− touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
330+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
331+ var tutorialBottom = findChild(shell, "tutorialBottom");
332+
333+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top", "right"];
334+ tryCompare(tutorialLeftLoader, "active", false);
183
335
336+ ApplicationManager.startApplication("dialer-app");
337+ ApplicationManager.requestFocusApplication("dialer-app");
338+
339+ tryCompare(tutorialBottom, "shown", true);
340+ tryCompare(tutorialBottom, "opacity", 1);
341+ }
342+
343+ function test_tutorialLeftEdges() {
344+ var tutorial = findChild(shell, "tutorial");
345+ var tutorialLeft = findChild(tutorial, "tutorialLeft");
184 var launcher = findChild(shell, "launcher");
346 var launcher = findChild(shell, "launcher");
185− tryCompare(launcher, "state", "");
347+ var stage = findChild(shell, "stage");
348+ var panel = findChild(shell, "panel");
349+
350+ verify(tutorial.running);
351+ verify(tutorial.launcherEnabled);
352+ verify(!tutorial.spreadEnabled);
353+ verify(!tutorial.panelEnabled);
354+ verify(tutorialLeft.shown);
355+ verify(launcher.available);
356+ verify(!stage.spreadEnabled);
357+ verify(!panel.indicators.available);
186 }
358 }
187
359
188− function checkRightEdge() {
360+ function test_tutorialLeftFinish() {
189− if (shell.usageScenario === "phone") {
361+ var tutorial = findChild(shell, "tutorial");
190− touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
362+ var tutorialLeft = findChild(tutorial, "tutorialLeft");
363+ var launcher = findChild(shell, "launcher");
191
364
192− var stage = findChild(shell, "stage");
365+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
193− var spreadView = findChild(stage, "spreadView");
366+
194− tryCompare(spreadView, "phase", 0);
367+ tryCompare(tutorialLeft, "shown", false);
195− }
368+ tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
369+ tryCompare(launcher, "state", "visible");
196 }
370 }
197
371
198− function checkBottomEdge() {
372+ function test_tutorialLeftShortDrag() {
199− // Can't actually check effect of swipe, since dash isn't really loaded
373+ // Here we want to test just barely pulling the launcher out and
200− var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
374+ // letting go (i.e. not triggering the "progress" property of
201− tryCompare(applicationsDisplayLoader, "interactive", false);
375+ // Launcher).
376+ var tutorialLeft = findChild(shell, "tutorialLeft");
377+ var launcher = findChild(shell, "launcher");
378+
379+ // Confirm fade during drag
380+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight, true, false);
381+ // compare opacity with a bound rather than hard 0.6 because progress doesn't
382+ // always match the drag perfectly (takes a moment for drag to kick in)
383+ tryCompareFunction(function() {
384+ return tutorialLeft.opacity >= 0.6 && tutorialLeft.opacity < 0.8;
385+ }, true);
386+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight, false, true);
387+
388+
389+ // Make sure we don't do anything if we don't pull the launcher
390+ // out much.
391+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
392+ tryCompare(launcher, "state", ""); // should remain hidden
393+ tryCompare(tutorialLeft, "shown", true); // and we should still be on left
394+
395+
396+ // Now drag out but not past launcher itself
397+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
398+
399+ tryCompare(tutorialLeft, "shown", false);
400+ tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
401+ tryCompare(launcher, "state", "visible");
202 }
402 }
203
403
204− function checkFinished() {
404+ function test_tutorialLeftLongDrag() {
205− tryCompare(AccountsService, "demoEdges", false);
405+ // Just confirm that a long drag ("dash" drag) doesn't confuse us
206
406
207− var tutorial = findChild(shell, "tutorial");
407+ // So that we actually switch to dash and launcher hides
208− tryCompare(tutorial, "running", false);
408+ ApplicationManager.startApplication("gallery-app");
209
409
210 var launcher = findChild(shell, "launcher");
410 var launcher = findChild(shell, "launcher");
411+ var tutorialLeft = findChild(shell, "tutorialLeft");
412+ touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
413+
211− tryCompare(launcher, "shown", false);
414+ tryCompare(tutorialLeft, "shown", false);
415+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long"]);
212 }
416 }
213
417
214− function goToPage(name) {
418+ function test_tutorialLeftAutoSkipped() {
419+ // Test that we skip the tutorial if user uses left edge themselves
420+
215− var page = waitForPage("tutorialLeft");
421+ var tutorialLeft = findChild(shell, "tutorialLeft");
216− checkTopEdge();
422+ LightDM.Greeter.showGreeter();
217− checkRightEdge();
423+ tryCompare(tutorialLeft, "visible", false);
218− checkBottomEdge();
424+ compare(AccountsService.demoEdgesCompleted, []);
219− if (name === "tutorialLeft") return page;
425+
220 touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
426 touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
427+ tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
428+ }
221
429
222− page = waitForPage("tutorialLeftFinish");
430+ function test_tutorialTopEdges() {
223− if (name === "tutorialLeftFinish") return page;
224− var tick = findChild(page, "tick");
431+ var tutorial = findChild(shell, "tutorial");
225− tap(tick);
432+ var tutorialTop = findChild(tutorial, "tutorialTop");
226−
433+ var launcher = findChild(shell, "launcher");
227− page = waitForPage("tutorialRight");
228− checkTopEdge();
229− checkLeftEdge();
230− checkBottomEdge();
231− if (name === "tutorialRight") return page;
232− touchFlick(shell,
233− shell.width, halfHeight,
234− halfWidth, halfHeight,
235− true /* beginTouch */, true /* endTouch */,
236− 20 /* speed */, 50 /* iterations */);
237− var overlay = findChild(page, "overlay");
238− tryCompare(overlay, "shown", true);
239− var tick = findChild(page, "tick");
434+ var stage = findChild(shell, "stage");
240− tap(tick);
435+ var panel = findChild(shell, "panel");
241−
242− var page = waitForPage("tutorialBottom");
243− checkTopEdge();
244− checkLeftEdge();
245− checkRightEdge();
246− if (name === "tutorialBottom") return page;
247− touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
248
436
249− var page = waitForPage("tutorialBottomFinish");
437+ openTutorialTop();
250− checkTopEdge();
251− checkLeftEdge();
252− checkRightEdge();
253− checkBottomEdge();
254− if (name === "tutorialBottomFinish") return page;
255− var tick = findChild(page, "tick");
256− tap(tick);
257
438
258− checkFinished();
439+ tryCompare(tutorial, "running", true);
259− return null;
440+ verify(!tutorial.launcherEnabled);
441+ verify(!tutorial.spreadEnabled);
442+ verify(tutorial.panelEnabled);
443+ verify(tutorialTop.shown);
444+ verify(!launcher.available);
445+ verify(!stage.spreadEnabled);
446+ verify(panel.indicators.available);
260 }
447 }
261
448
262− function test_walkthrough() {
449+ function test_tutorialTopFinish() {
263− goToPage(null);
450+ var tutorial = findChild(shell, "tutorial");
451+ var tutorialTop = findChild(tutorial, "tutorialTop");
452+ var panel = findChild(shell, "panel");
453+
454+ openTutorialTop();
455+ touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
456+
457+ tryCompare(tutorialTop, "shown", false);
458+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top"]);
459+ tryCompare(panel.indicators, "fullyOpened", true);
264 }
460 }
265
461
266− function test_skipOnDesktop() {
462+ function test_tutorialTopShortDrag() {
267 var tutorial = findChild(shell, "tutorial");
463 var tutorial = findChild(shell, "tutorial");
268− tryCompare(tutorial, "active", true);
464+ var tutorialTop = findChild(tutorial, "tutorialTop");
269− tryCompare(tutorial, "running", true);
465+ var panel = findChild(shell, "panel");
466+
467+ openTutorialTop();
468+ touchFlick(shell, halfWidth, 0, halfWidth, shell.height * 0.4, true, false);
469+ // compare opacity with a bound rather than hard 0.6 because progress doesn't
470+ // always match the drag perfectly (takes a moment for drag to kick in)
471+ tryCompareFunction(function() {
472+ return tutorialTop.opacity >= 0.6 && tutorialTop.opacity < 0.7;
473+ }, true);
474+ touchFlick(shell, halfWidth, 0, halfWidth, shell.height * 0.4, false, true);
270
475
271− shell.usageScenario = "desktop";
476+ compare(tutorialTop.shown, true);
272− tryCompare(tutorial, "active", false);
273− tryCompare(tutorial, "running", false);
274 }
477 }
275
478
276− function test_launcherShortDrag() {
479+ function test_tutorialTopAutoSkipped() {
277− // goToPage does a normal launcher pull. But here we want to test
480+ // Test that we skip the tutorial if user uses top edge themselves
278− // just barely pulling the launcher out and letting go (i.e. not
279− // triggering the "progress" property of Launcher).
280
481
281− var left = goToPage("tutorialLeft");
482+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
483+ var tutorialTop = findChild(shell, "tutorialTop");
484+ AccountsService.demoEdgesCompleted = ["left", "left-long"];
485+ tryCompare(tutorialLeftLoader, "active", false);
486+ verify(!tutorialTop.shown);
282
487
283− // Make sure we don't do anything if we don't pull the launcher
488+ touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
284− // out much.
489+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top"]);
490+ }
491+
492+ function test_tutorialRightEdges() {
493+ var tutorial = findChild(shell, "tutorial");
494+ var tutorialRight = findChild(tutorial, "tutorialRight");
285 var launcher = findChild(shell, "launcher");
495 var launcher = findChild(shell, "launcher");
286− touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
496+ var stage = findChild(shell, "stage");
287− tryCompare(launcher, "state", ""); // should remain hidden
497+ var panel = findChild(shell, "panel");
288− tryCompare(left, "shown", true); // and we should still be on left
289
498
290− // Now drag out but not past launcher itself
499+ openTutorialRight();
500+
501+ tryCompare(tutorial, "running", true);
502+ verify(!tutorial.launcherEnabled);
503+ verify(tutorial.spreadEnabled);
504+ verify(!tutorial.panelEnabled);
505+ verify(tutorialRight.shown);
506+ verify(!launcher.available);
507+ verify(stage.spreadEnabled);
508+ verify(!panel.indicators.available);
509+ }
510+
511+ function test_tutorialRightFinish() {
512+ var tutorial = findChild(shell, "tutorial");
513+ var tutorialRight = findChild(tutorial, "tutorialRight");
514+ var stage = findChild(shell, "stage");
515+
516+ openTutorialRight();
517+ touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
518+
519+ tryCompare(tutorialRight, "shown", false);
520+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top", "right"]);
521+ }
522+
523+ function test_tutorialRightShortDrag() {
524+ var tutorial = findChild(shell, "tutorial");
525+ var tutorialRight = findChild(tutorial, "tutorialRight");
526+ var stage = findChild(shell, "stage");
527+
528+ openTutorialRight();
529+ touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
530+ // compare opacity with a bound rather than hard 0.6 because progress doesn't
531+ // always match the drag perfectly (takes a moment for drag to kick in)
532+ tryCompareFunction(function() {
533+ return tutorialRight.opacity >= 0.6 && tutorialRight.opacity < 0.8;
534+ }, true);
291− touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
535+ touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, false, true);
536+
537+ compare(tutorialRight.shown, true);
538+ }
292
539
293− waitForPage("tutorialLeftFinish");
540+ function test_tutorialRightDelay() {
541+ // Test that if we exit the top tutorial, we don't immediately
542+ // jump into right tutorial.
543+ var tutorialRight = findChild(shell, "tutorialRight");
544+ var tutorialRightTimer = findInvisibleChild(tutorialRight, "tutorialRightInactivityTimer");
545+
546+ tutorialRightTimer.interval = 1;
547+ openTutorialTop();
548+ ApplicationManager.startApplication("gallery-app");
549+ ApplicationManager.startApplication("facebook-webapp");
550+ tryCompare(ApplicationManager, "count", 3);
551+
552+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
553+ verify(tutorialRightTimer.running, true);
554+ verify(!tutorialRight.shown);
555+ tryCompare(tutorialRight, "shown", true);
294 }
556 }
295
557
296− function test_launcherLongDrag() {
558+ function test_tutorialRightAutoSkipped() {
297− // goToPage does a normal launcher pull. But here we want to test
559+ // Test that we skip the tutorial if user uses right edge themselves
298− // a full pull across the page.
560+
561+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
562+ AccountsService.demoEdgesCompleted = ["left", "left-long"];
563+ tryCompare(tutorialLeftLoader, "active", false);
299
564
300− var left = goToPage("tutorialLeft");
565+ touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
566+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "right"]);
567+ }
301
568
569+ function test_tutorialBottomEdges() {
570+ var tutorial = findChild(shell, "tutorial");
571+ var tutorialBottom = findChild(tutorial, "tutorialBottom");
572+ var tutorialLabel = findChild(tutorialBottom, "tutorialLabel");
302 var launcher = findChild(shell, "launcher");
573 var launcher = findChild(shell, "launcher");
303− touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
574+ var stage = findChild(shell, "stage");
575+ var panel = findChild(shell, "panel");
576+
577+ openTutorialBottom();
578+
579+ tryCompare(tutorial, "running", true);
580+ verify(!tutorial.launcherEnabled);
581+ verify(!tutorial.spreadEnabled);
582+ verify(!tutorial.panelEnabled);
583+ verify(tutorialBottom.shown);
584+ verify(!launcher.available);
585+ verify(!stage.spreadEnabled);
586+ verify(!panel.indicators.available);
587+ compare(tutorialLabel.text, "Swipe up for recent calls");
588+ }
304
589
305− var errorTextLabel = findChild(left, "errorTextLabel");
590+ function test_tutorialBottomFinish() {
306− var errorTitleLabel = findChild(left, "errorTitleLabel");
591+ var tutorial = findChild(shell, "tutorial");
307− tryCompare(launcher, "state", ""); // launcher goes away
592+ var tutorialBottom = findChild(tutorial, "tutorialBottom");
593+
594+ openTutorialBottom();
595+ touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
596+
308− tryCompare(left, "shown", true); // still on left page
597+ tryCompare(tutorialBottom, "shown", false);
309− tryCompare(errorTextLabel, "opacity", 1); // show error
598+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top", "right", "bottom-dialer-app"]);
310− tryCompare(errorTitleLabel, "opacity", 1); // show error
599+
600+ // OK, we did one, just confirm that when all are done, we mark whole tutorial as done.
601+ verify(AccountsService.demoEdges);
602+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top", "right",
603+ "bottom-address-book-app",
604+ "bottom-com.ubuntu.calculator_calculator",
605+ "bottom-dialer-app",
606+ "bottom-messaging-app"];
607+ verify(!AccountsService.demoEdges);
311 }
608 }
312
609
313− function test_launcherDragBack() {
610+ function test_tutorialBottomAppearsBeforeRight() {
314− // goToPage does a full launcher pull. But here we test pulling
611+ // Confirm that if bottom edge and right edge would appear on the
315− // all the way out, then dragging back into place.
612+ // the same app open, bottom edge appears first. (this is a lightly
613+ // edited version of openTutorialRight)
614+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
615+ var tutorialRight = findChild(shell, "tutorialRight");
616+ var tutorialBottom = findChild(shell, "tutorialBottom");
316
617
317− var left = goToPage("tutorialLeft");
618+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
318− touchFlick(shell, 0, halfHeight, halfWidth, halfHeight, true, false);
619+ ApplicationManager.startApplication("gallery-app");
319− touchFlick(shell, halfWidth, halfHeight, 0, halfHeight, false, true);
620+ ApplicationManager.startApplication("dialer-app");
320
621
321− tryCompare(left, "shown", true); // and we should still be on left
622+ tryCompare(tutorialLeftLoader, "active", false);
623+ tryCompare(tutorialBottom, "shown", true);
624+ tryCompare(tutorialBottom, "opacity", 1);
625+ tryCompare(tutorialRight, "visible", false);
322 }
626 }
323
627
324− function test_launcherNoDragGap() {
628+ function test_tutorialBottomOnlyCoversSideStageOnTablet() {
325− // See bug 1454882, where if you dragged the launcher while it was
629+ loadShell("tablet");
326− // visible, you could pull it further than the edge of the screen.
327
630
328− var left = goToPage("tutorialLeft");
631+ var tutorialBottom = findChild(shell, "tutorialBottom");
329− var launcher = findChild(shell, "launcher");
632+ var targetHeight = shell.height - units.gu(4);
330− var teaseAnimation = findInvisibleChild(left, "teaseAnimation");
633+ var mainStageX = units.gu(20);
634+ var sideStageX = shell.width - units.gu(20);
635+
636+ WindowStateStorage.saveStage("dialer-app", ApplicationInfoInterface.SideStage);
637+ openTutorialBottom();
638+
639+ touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, true, false);
640+ compare(tutorialBottom.opacity, 1);
641+ touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, false, true);
642+
643+ touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, true, false);
644+ verify(tutorialBottom.opacity < 1);
645+ touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, false, true);
646+ }
331
647
332− // Wait for launcher to be really out there
648+ function test_tutorialBottomOnlyCoversMainStageOnTablet() {
333− tryCompareFunction(function() {return launcher.x > teaseAnimation.maxBounce/2}, true);
649+ loadShell("tablet");
334− verify(teaseAnimation.running);
335
650
336− // Start a drag, make sure animation stops
651+ var tutorialBottom = findChild(shell, "tutorialBottom");
337− touchFlick(shell, 0, halfHeight, units.gu(4), halfHeight, true, false);
652+ var targetHeight = shell.height - units.gu(4);
338− verify(!teaseAnimation.running);
653+ var mainStageX = units.gu(20);
339− verify(launcher.visibleWidth > 0);
654+ var sideStageX = shell.width - units.gu(20);
340− verify(launcher.x > 0);
341− compare(launcher.x, teaseAnimation.bounce);
342
655
343− // Continue drag, make sure we don't create a gap on the left hand side
656+ WindowStateStorage.saveStage("notes-app", ApplicationInfoInterface.SideStage);
344− touchFlick(shell, units.gu(4), halfHeight, shell.width, halfHeight, false, false);
657+ ApplicationManager.startApplication("notes-app");
345− verify(!teaseAnimation.running);
346− compare(launcher.visibleWidth, launcher.panelWidth);
347− compare(launcher.x, 0);
348
658
349− // Finish and make sure we continue animation
659+ openTutorialBottom();
350− touchFlick(shell, shell.width, halfHeight, shell.width, halfHeight, false, true);
660+
351− tryCompare(teaseAnimation, "running", true);
661+ touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, true, false);
662+ compare(tutorialBottom.opacity, 1);
663+ touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, false, true);
664+
665+ touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, true, false);
666+ verify(tutorialBottom.opacity < 1);
667+ touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, false, true);
352 }
668 }
353
669
354− function test_spread() {
670+ function test_activeCallInterruptsTutorial() {
355− // Unfortunately, most of what we want to test of the spread is
671+ var tutorialLeft = findChild(shell, "tutorialLeft");
356− // "did it render correctly?" but that's hard to test. So instead,
672+ verify(tutorialLeft.shown);
357− // just poke and prod it a little bit to see if some of the values
673+ verify(!tutorialLeft.paused);
358− // we'd expect to be correct, are so.
359
674
360− var right = goToPage("tutorialRight");
675+ callManager.foregroundCall = phoneCall;
361− var stage = findChild(right, "stage");
676+ verify(!tutorialLeft.shown);
362− var delegate0 = findChild(right, "appDelegate0");
677+ verify(tutorialLeft.paused);
678+ tryCompare(tutorialLeft, "visible", false);
363
679
364− tryCompare(stage, "dragProgress", 0);
680+ callManager.foregroundCall = null;
365− touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
681+ tryCompare(tutorialLeft, "shown", true);
366− verify(stage.dragProgress > 0);
682+ verify(!tutorialLeft.paused);
367− compare(stage.dragProgress, -delegate0.xTranslate);
683+ }
368− touchFlick(shell, shell.width * 0.8, halfHeight, shell.width, halfHeight, false, true);
684+
369− tryCompare(stage, "dragProgress", 0);
685+ function test_greeterInterruptsTutorial() {
686+ var tutorialLeft = findChild(shell, "tutorialLeft");
687+ verify(tutorialLeft.shown);
688+ verify(!tutorialLeft.paused);
370
689
371− tryCompare(delegate0, "x", shell.width);
690+ LightDM.Greeter.showGreeter();
691+ verify(!tutorialLeft.shown);
692+ verify(tutorialLeft.paused);
693+ tryCompare(tutorialLeft, "visible", false);
372
694
373− var screenshotImage = findChild(right, "screenshotImage");
695+ LightDM.Greeter.hideGreeter();
374− tryCompare(screenshotImage, "source", Qt.resolvedUrl("../../../qml/Tutorial/graphics/facebook.png"));
375− tryCompare(screenshotImage, "visible", true);
696+ tryCompare(tutorialLeft, "shown", true);
697+ verify(!tutorialLeft.paused);
376 }
698 }
377
699
378− function test_bottomShortDrag() {
700+ function test_interruptionChecksReadyStateWhenDone() {
379− var bottom = goToPage("tutorialBottom");
701+ // If we're done with an interruption (like active call), make sure
702+ // that we don't blindly resume the tutorial -- our trigger
703+ // conditions still need to be met. For example, there need to be
704+ // enough apps open for the right edge tutorial.
380
705
381− touchFlick(shell, halfWidth, shell.height, halfWidth, shell.height * 0.8);
706+ openTutorialRight();
382
707
383− var errorTextLabel = findChild(bottom, "errorTextLabel");
708+ var tutorialRight = findChild(shell, "tutorialRight");
384− var errorTitleLabel = findChild(bottom, "errorTitleLabel");
709+ verify(tutorialRight.isReady);
385− tryCompare(bottom, "shown", true); // still on bottom page
710+ verify(tutorialRight.shown);
386− tryCompare(errorTextLabel, "opacity", 1); // show error
711+ verify(!tutorialRight.paused);
387− tryCompare(errorTitleLabel, "opacity", 1); // show error
712+
713+ callManager.foregroundCall = phoneCall;
714+ killApps();
715+ callManager.foregroundCall = null;
716+
717+ verify(!tutorialRight.isReady);
718+ verify(!tutorialRight.shown);
719+ verify(!tutorialRight.paused);
720+ compare(AccountsService.demoEdgesCompleted, ["left", "left-long", "top"]);
388 }
721 }
389
722
390− function test_interrupted() {
723+ function test_desktopOnlyShowsTutorialRight() {
391− goToPage("tutorialLeft");
724+ loadShell("desktop");
725+
726+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
727+ var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
728+ var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
729+ var tutorialBottomLoader = findChild(shell, "tutorialBottomLoader");
730+ verify(!tutorialLeftLoader.active);
731+ verify(!tutorialTopLoader.active);
732+ verify(tutorialRightLoader.active);
733+ verify(!tutorialBottomLoader.active);
734+ compare(AccountsService.demoEdgesCompleted, []);
735+
392 ApplicationManager.startApplication("dialer-app");
736 ApplicationManager.startApplication("dialer-app");
393− checkFinished();
737+ ApplicationManager.startApplication("camera-app");
738+ tryCompare(tutorialRightLoader.item, "isReady", true);
739+ tryCompare(tutorialRightLoader, "shown", true);
740+ }
741+
742+ function test_oskDoesNotHideTutorial() {
743+ var tutorialLeft = findChild(shell, "tutorialLeft");
744+ verify(tutorialLeft.shown);
745+
746+ var surface = SurfaceManager.inputMethodSurface;
747+ surface.setState(Mir.RestoredState);
748+
749+ var inputMethod = findInvisibleChild(shell, "inputMethod");
750+ tryCompare(inputMethod, "state", "shown");
751+
752+ verify(tutorialLeft.shown);
753+ }
754+
755+ function test_oskPreventsTutorial() {
756+ var surface = SurfaceManager.inputMethodSurface;
757+ var inputMethod = findInvisibleChild(shell, "inputMethod");
758+
759+ AccountsService.demoEdges = false;
760+ surface.setState(Mir.RestoredState);
761+ tryCompare(inputMethod, "state", "shown");
762+
763+ var tutorial = findChild(shell, "tutorial");
764+ tryCompare(tutorial, "keyboardVisible", true);
765+
766+ AccountsService.demoEdges = true;
767+ var tutorialLeft = findChild(shell, "tutorialLeft");
768+ verify(!tutorialLeft.shown);
769+
770+ surface.setState(Mir.MinimizedState);
771+ tryCompare(inputMethod, "state", "hidden");
772+ tryCompare(tutorialLeft, "shown", false);
773+ }
774+
775+ function test_accountsServiceSettings() {
776+ var tutorialLeft = findChild(shell, "tutorialLeft");
777+ verify(tutorialLeft != null);
778+ verify(tutorialLeft.shown);
779+
780+ AccountsService.demoEdges = false;
781+ verify(findChild(shell, "tutorialLeft") == null);
782+
783+ AccountsService.demoEdges = true;
784+ tutorialLeft = findChild(shell, "tutorialLeft");
785+ verify(tutorialLeft != null);
786+ tryCompare(tutorialLeft, "shown", true);
394 }
787 }
395 }
788 }
396}
789}
tests/qmltests/tst_Shell.qml+3−8
@@ -499,6 +499,7 @@ Rectangle {
499 setLightDMMockMode("single"); // back to the default value
499 setLightDMMockMode("single"); // back to the default value
500
500
501 AccountsService.demoEdges = false;
501 AccountsService.demoEdges = false;
502+ AccountsService.demoEdgesCompleted = [];
502 Wizard.System.wizardEnabled = false;
503 Wizard.System.wizardEnabled = false;
503
504
504 // kill all (fake) running apps
505 // kill all (fake) running apps
@@ -650,9 +651,8 @@ Rectangle {
650
651
651 function test_tabletLeftEdgeDrag_data() {
652 function test_tabletLeftEdgeDrag_data() {
652 return [
653 return [
653− {tag: "without password", user: "no-password", loggedIn: true, demo: false},
654+ {tag: "without password", user: "no-password", loggedIn: true},
654− {tag: "with password", user: "has-password", loggedIn: false, demo: false},
655+ {tag: "with password", user: "has-password", loggedIn: false},
655− {tag: "with demo", user: "has-password", loggedIn: true, demo: true},
656 ]
656 ]
657 }
657 }
658
658
@@ -662,10 +662,6 @@ Rectangle {
662
662
663 selectUser(data.user)
663 selectUser(data.user)
664
664
665− AccountsService.demoEdges = data.demo
666− var tutorial = findChild(shell, "tutorial");
667− tryCompare(tutorial, "running", data.demo);
668−
669 swipeFromLeftEdge(shell.width * 0.75)
665 swipeFromLeftEdge(shell.width * 0.75)
670 wait(500) // to give time to handle dash() signal from Launcher
666 wait(500) // to give time to handle dash() signal from Launcher
671 confirmLoggedIn(data.loggedIn)
667 confirmLoggedIn(data.loggedIn)
@@ -1262,7 +1258,6 @@ Rectangle {
1262 var tutorial = findChild(shell, "tutorial");
1258 var tutorial = findChild(shell, "tutorial");
1263
1259
1264 AccountsService.demoEdges = true;
1260 AccountsService.demoEdges = true;
1265− tryCompare(tutorial, "running", true);
1266 tryCompare(tutorial, "paused", true);
1261 tryCompare(tutorial, "paused", true);
1267
1262
1268 swipeAwayGreeter();
1263 swipeAwayGreeter();
tests/qmltests/tst_ShellWithPin.qml−22
@@ -270,28 +270,6 @@ Item {
270 tryCompare(sessionSpy, "count", 1)
270 tryCompare(sessionSpy, "count", 1)
271 }
271 }
272
272
273− function test_edgeDemoHidesLockscreen() {
274− LightDM.Greeter.showGreeter()
275− sessionSpy.clear()
276− var lockscreen = findChild(shell, "lockscreen")
277−
278− tryCompare(lockscreen, "shown", true)
279− AccountsService.demoEdges = true
280− tryCompare(lockscreen, "shown", false)
281−
282− var greeter = findChild(shell, "greeter");
283− swipeAwayGreeter(false);
284− tryCompare(greeter, "shown", false);
285− tryCompare(sessionSpy, "count", 1)
286−
287− // Lockscreen is only hidden by the edge demo, so if we turn that
288− // off and show greeter again, lockscreen should appear
289− AccountsService.demoEdges = false
290− LightDM.Greeter.showGreeter()
291− lockscreen = findChild(shell, "lockscreen");
292− tryCompare(lockscreen, "shown", true)
293− }
294−
295 function test_disabledEdges() {
273 function test_disabledEdges() {
296 var launcher = findChild(shell, "launcher")
274 var launcher = findChild(shell, "launcher")
297 tryCompare(launcher, "available", true)
275 tryCompare(launcher, "available", true)