Merge lp:~mterry/unity8/tutorial-reappears into lp:unity8

Proposed by Michael Terry on 2015-03-26
Status: Superseded
Proposed branch: lp:~mterry/unity8/tutorial-reappears
Merge into: lp:unity8
Diff against target: 501 lines (+151/-87)
8 files modified
qml/Shell.qml (+25/-17)
qml/Tutorial/Tutorial.qml (+7/-14)
qml/Tutorial/TutorialContent.qml (+9/-8)
tests/mocks/GSettings.1.0/fake_gsettings.cpp (+64/-28)
tests/mocks/GSettings.1.0/fake_gsettings.h (+16/-6)
tests/mocks/GSettings.1.0/plugin.cpp (+0/-6)
tests/qmltests/Tutorial/tst_Tutorial.qml (+22/-5)
tests/qmltests/tst_Shell.qml (+8/-3)
To merge this branch: bzr merge lp:~mterry/unity8/tutorial-reappears
Reviewer Review Type Date Requested Status
Albert Astals Cid (community) 2015-03-26 Approve on 2015-04-01
PS Jenkins bot continuous-integration Needs Fixing on 2015-03-26
Review via email: mp+254263@code.launchpad.net

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

Commit Message

Make sure the edge tutorial is destroyed when we receive a call during the wizard.

Description of the Change

Make sure the edge tutorial is destroyed when we receive a call during the wizard.

Now... This bug doesn't make sense to me. What's happening is that the Tutorial.qml Loader is correctly marked as "active=false". But the loaded object is not actually released/destroyed.

I couldn't figure why this was happening. And I couldn't reproduce it via qmltests. In all cases I saw, the object was correctly destroyed. But I could reproduce the bug on the device. And adding log printfs indicated that yup, the object was staying around after Loader.active was false.

But on a hunch, cleaning up the object hierarchy helped. Before, each tutorial page would have a visual parent of "shell.stages" but a QtObject parent of the tutorial loader item. This is a leftover from before the latest tutorial redesign. Now visual parents *shouldn't* affect object lifecycle, I think...? But it seems like it does.

Because when I made the tutorial page's visual parents be the same as their QtObject parents (and just moved the whole Tutorial object underneath shell.stages), it worked fine. The tutorial objects were destroyed when the loader went inactive.

Do any Qml-heads know why this happens? I would guess some refcounting somewhere, but I don't know why I couldn't reproduce in qmltests.

I added some sanity checks at the end of the current test for wizard/tutorial early exit, which don't hurt. But even without my qml changes, they still pass (because I could never reproduce this bug in laboratory conditions).

== Checklist ==

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

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

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

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

 * If you changed the UI, has there been a design review?
 NA

To post a comment you must log in.
Michael Terry (mterry) wrote :

(Oh yeah, and I cleared out a couple unused vestigial properties: stages and overlay)

Albert Astals Cid (aacid) wrote :

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

 * Did CI run pass?
It passed up to the expected passing standards

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

review: Approve
lp:~mterry/unity8/tutorial-reappears updated on 2015-04-03
1700. By Michael Terry on 2015-04-03

Merge in lp:~mterry/unity8/skip-spread-tutorial-on-desktop

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'qml/Shell.qml'
2--- qml/Shell.qml 2015-03-10 14:25:12 +0000
3+++ qml/Shell.qml 2015-04-03 14:23:20 +0000
4@@ -283,6 +283,31 @@
5 value: shell.background
6 }
7 }
8+
9+ Tutorial {
10+ id: tutorial
11+ objectName: "tutorial"
12+ anchors.fill: parent
13+ active: AccountsService.demoEdges
14+ paused: LightDM.Greeter.active
15+ launcher: launcher
16+ panel: panel
17+ edgeSize: shell.edgeSize
18+
19+ // EdgeDragAreas don't work with mice. So to avoid trapping the user,
20+ // we'll tell the tutorial to avoid using them on the Desktop. The
21+ // Desktop doesn't use the same spread design anyway. The tutorial is
22+ // all a bit of a placeholder on non-phone form factors right now.
23+ // When the design team gives us more guidance, we can do something
24+ // more clever here.
25+ // TODO: use DeviceConfiguration instead of checking source
26+ useEdgeDragArea: applicationsDisplayLoader.source != Qt.resolvedUrl("Stages/DesktopStage.qml")
27+
28+ onFinished: {
29+ AccountsService.demoEdges = false;
30+ active = false; // for immediate response / if AS is having problems
31+ }
32+ }
33 }
34
35 InputMethod {
36@@ -580,23 +605,6 @@
37 }
38 }
39
40- Tutorial {
41- id: tutorial
42- objectName: "tutorial"
43- active: AccountsService.demoEdges
44- paused: LightDM.Greeter.active
45- launcher: launcher
46- panel: panel
47- stages: stages
48- overlay: overlay
49- edgeSize: shell.edgeSize
50-
51- onFinished: {
52- AccountsService.demoEdges = false;
53- active = false; // for immediate response / if AS is having problems
54- }
55- }
56-
57 Connections {
58 target: SessionBroadcast
59 onShowHome: showHome()
60
61=== modified file 'qml/Tutorial/Tutorial.qml'
62--- qml/Tutorial/Tutorial.qml 2015-01-06 19:50:14 +0000
63+++ qml/Tutorial/Tutorial.qml 2015-04-03 14:23:20 +0000
64@@ -23,11 +23,10 @@
65 property alias active: loader.active
66 property bool paused
67 property real edgeSize
68+ property bool useEdgeDragArea
69
70 property Item launcher
71 property Item panel
72- property Item stages
73- property Item overlay
74
75 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
76 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
77@@ -62,6 +61,12 @@
78
79 Binding {
80 target: loader.item
81+ property: "useEdgeDragArea"
82+ value: root.useEdgeDragArea
83+ }
84+
85+ Binding {
86+ target: loader.item
87 property: "launcher"
88 value: root.launcher
89 }
90@@ -72,18 +77,6 @@
91 value: root.panel
92 }
93
94- Binding {
95- target: loader.item
96- property: "stages"
97- value: root.stages
98- }
99-
100- Binding {
101- target: loader.item
102- property: "overlay"
103- value: root.overlay
104- }
105-
106 Connections {
107 target: loader.item
108 onFinished: root.finished()
109
110=== modified file 'qml/Tutorial/TutorialContent.qml'
111--- qml/Tutorial/TutorialContent.qml 2015-01-06 19:13:58 +0000
112+++ qml/Tutorial/TutorialContent.qml 2015-04-03 14:23:20 +0000
113@@ -22,8 +22,6 @@
114
115 property Item launcher
116 property Item panel
117- property Item stages
118- property Item overlay
119
120 readonly property bool launcherEnabled: !running ||
121 (!paused && tutorialLeft.shown)
122@@ -34,6 +32,7 @@
123
124 property bool paused: false
125 property real edgeSize
126+ property bool useEdgeDragArea
127
128 signal finished()
129
130@@ -66,7 +65,6 @@
131 TutorialLeft {
132 id: tutorialLeft
133 objectName: "tutorialLeft"
134- parent: root.stages
135 anchors.fill: parent
136 launcher: root.launcher
137 paused: !shown || root.paused
138@@ -77,21 +75,26 @@
139 TutorialLeftFinish {
140 id: tutorialLeftFinish
141 objectName: "tutorialLeftFinish"
142- parent: root.stages
143 anchors.fill: parent
144 textXOffset: root.launcher.panelWidth
145 paused: !shown || root.paused
146+ text: root.useEdgeDragArea ? i18n.tr("Tap here to continue.") : i18n.tr("Click here to finish.")
147
148 onFinished: {
149 root.launcher.hide();
150- tutorialRight.show();
151+ if (root.useEdgeDragArea) {
152+ tutorialRight.show();
153+ } else {
154+ // Last two screens use EdgeDragArea (right edge spread and
155+ // bottom edge). So just end tutorial here.
156+ root.finish();
157+ }
158 }
159 }
160
161 TutorialRight {
162 id: tutorialRight
163 objectName: "tutorialRight"
164- parent: root.stages
165 anchors.fill: parent
166 edgeSize: root.edgeSize
167 panel: root.panel
168@@ -103,7 +106,6 @@
169 TutorialBottom {
170 id: tutorialBottom
171 objectName: "tutorialBottom"
172- parent: root.stages
173 anchors.fill: parent
174 edgeSize: root.edgeSize
175 paused: !shown || root.paused
176@@ -114,7 +116,6 @@
177 TutorialBottomFinish {
178 id: tutorialBottomFinish
179 objectName: "tutorialBottomFinish"
180- parent: root.stages
181 anchors.fill: parent
182 backgroundFadesOut: true
183 paused: !shown || root.paused
184
185=== modified file 'tests/mocks/GSettings.1.0/fake_gsettings.cpp'
186--- tests/mocks/GSettings.1.0/fake_gsettings.cpp 2013-08-15 08:11:04 +0000
187+++ tests/mocks/GSettings.1.0/fake_gsettings.cpp 2015-04-03 14:23:20 +0000
188@@ -20,7 +20,9 @@
189
190 GSettingsControllerQml* GSettingsControllerQml::s_controllerInstance = 0;
191
192-GSettingsControllerQml::GSettingsControllerQml() {
193+GSettingsControllerQml::GSettingsControllerQml()
194+ : m_usageMode("Staged")
195+{
196 }
197
198 GSettingsControllerQml::~GSettingsControllerQml() {
199@@ -34,17 +36,29 @@
200 return s_controllerInstance;
201 }
202
203-void GSettingsControllerQml::registerSettingsObject(GSettingsQml *obj) {
204- m_registeredGSettings.append(obj);
205-}
206-
207-void GSettingsControllerQml::unRegisterSettingsObject(GSettingsQml *obj) {
208- m_registeredGSettings.removeOne(obj);
209-}
210-
211-void GSettingsControllerQml::setPictureUri(const QString &str) {
212- Q_FOREACH (GSettingsQml *obj, m_registeredGSettings) {
213- obj->setPictureUri(str);
214+QString GSettingsControllerQml::pictureUri() const
215+{
216+ return m_pictureUri;
217+}
218+
219+void GSettingsControllerQml::setPictureUri(const QString &str)
220+{
221+ if (str != m_pictureUri) {
222+ m_pictureUri = str;
223+ Q_EMIT pictureUriChanged(m_pictureUri);
224+ }
225+}
226+
227+QString GSettingsControllerQml::usageMode() const
228+{
229+ return m_usageMode;
230+}
231+
232+void GSettingsControllerQml::setUsageMode(const QString &usageMode)
233+{
234+ if (usageMode != m_usageMode) {
235+ m_usageMode = usageMode;
236+ Q_EMIT usageModeChanged(m_usageMode);
237 }
238 }
239
240@@ -56,7 +70,7 @@
241 }
242
243 void GSettingsSchemaQml::setId(const QByteArray &id) {
244- if (m_id.isEmpty()) {
245+ if (!m_id.isEmpty()) {
246 qWarning("GSettings.schema.id may only be set on construction");
247 return;
248 }
249@@ -69,7 +83,7 @@
250 }
251
252 void GSettingsSchemaQml::setPath(const QByteArray &path) {
253- if (m_path.isEmpty()) {
254+ if (!m_path.isEmpty()) {
255 qWarning("GSettings.schema.path may only be set on construction");
256 return;
257 }
258@@ -77,26 +91,48 @@
259 m_path = path;
260 }
261
262-GSettingsQml::GSettingsQml(QObject *parent): QObject(parent) {
263+GSettingsQml::GSettingsQml(QObject *parent)
264+ : QObject(parent)
265+{
266 m_schema = new GSettingsSchemaQml(this);
267- GSettingsControllerQml::instance()->registerSettingsObject(this);
268-}
269-
270-GSettingsQml::~GSettingsQml() {
271- GSettingsControllerQml::instance()->unRegisterSettingsObject(this);
272+ connect(GSettingsControllerQml::instance(), SIGNAL(pictureUriChanged(const QString &)),
273+ this, SIGNAL(pictureUriChanged(const QString &)));
274+ connect(GSettingsControllerQml::instance(), SIGNAL(usageModeChanged(const QString &)),
275+ this, SIGNAL(usageModeChanged(const QString &)));
276 }
277
278 GSettingsSchemaQml * GSettingsQml::schema() const {
279 return m_schema;
280 }
281
282-QString GSettingsQml::pictureUri() const {
283- return m_pictureUri;
284-}
285-
286-void GSettingsQml::setPictureUri(const QString &str) {
287- if (str != m_pictureUri) {
288- m_pictureUri = str;
289- Q_EMIT pictureUriChanged(m_pictureUri);
290+QString GSettingsQml::pictureUri() const
291+{
292+ if (m_schema->id() == "org.gnome.desktop.background") {
293+ return GSettingsControllerQml::instance()->pictureUri();
294+ } else {
295+ return "";
296+ }
297+}
298+
299+void GSettingsQml::setPictureUri(const QString &str)
300+{
301+ if (m_schema->id() == "org.gnome.desktop.background") {
302+ GSettingsControllerQml::instance()->setPictureUri(str);
303+ }
304+}
305+
306+QString GSettingsQml::usageMode() const
307+{
308+ if (m_schema->id() == "com.canonical.Unity8") {
309+ return GSettingsControllerQml::instance()->usageMode();
310+ } else {
311+ return "";
312+ }
313+}
314+
315+void GSettingsQml::setUsageMode(const QString &usageMode)
316+{
317+ if (m_schema->id() == "com.canonical.Unity8") {
318+ GSettingsControllerQml::instance()->setUsageMode(usageMode);
319 }
320 }
321
322=== modified file 'tests/mocks/GSettings.1.0/fake_gsettings.h'
323--- tests/mocks/GSettings.1.0/fake_gsettings.h 2014-08-26 08:14:44 +0000
324+++ tests/mocks/GSettings.1.0/fake_gsettings.h 2015-04-03 14:23:20 +0000
325@@ -46,25 +46,26 @@
326
327 Q_PROPERTY(GSettingsSchemaQml* schema READ schema NOTIFY schemaChanged)
328 Q_PROPERTY(QString pictureUri READ pictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
329+ Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
330
331 public:
332 GSettingsQml(QObject *parent = nullptr);
333- ~GSettingsQml();
334
335 GSettingsSchemaQml * schema() const;
336 QString pictureUri() const;
337+ QString usageMode() const;
338
339 void setPictureUri(const QString &str);
340+ void setUsageMode(const QString &usageMode);
341
342 Q_SIGNALS:
343 void schemaChanged();
344 void pictureUriChanged(const QString&);
345+ void usageModeChanged(const QString&);
346
347 private:
348 GSettingsSchemaQml* m_schema;
349
350- QString m_pictureUri;
351-
352 friend class GSettingsSchemaQml;
353 };
354
355@@ -76,13 +77,22 @@
356 static GSettingsControllerQml* instance();
357 ~GSettingsControllerQml();
358
359- void registerSettingsObject(GSettingsQml *obj);
360- void unRegisterSettingsObject(GSettingsQml *obj);
361- Q_INVOKABLE void setPictureUri(const QString &str);
362+ QString pictureUri() const;
363+ void setPictureUri(const QString &str);
364+
365+ QString usageMode() const;
366+ void setUsageMode(const QString &usageMode);
367+
368+Q_SIGNALS:
369+ void pictureUriChanged(const QString&);
370+ void usageModeChanged(const QString&);
371
372 private:
373 GSettingsControllerQml();
374
375+ QString m_pictureUri;
376+ QString m_usageMode;
377+
378 static GSettingsControllerQml* s_controllerInstance;
379 QList<GSettingsQml *> m_registeredGSettings;
380 };
381
382=== modified file 'tests/mocks/GSettings.1.0/plugin.cpp'
383--- tests/mocks/GSettings.1.0/plugin.cpp 2013-08-23 11:56:44 +0000
384+++ tests/mocks/GSettings.1.0/plugin.cpp 2015-04-03 14:23:20 +0000
385@@ -19,14 +19,8 @@
386
387 #include <QtQml/qqml.h>
388
389-static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
390-{
391- return GSettingsControllerQml::instance();
392-}
393-
394 void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
395 {
396- qmlRegisterSingletonType<GSettingsControllerQml>(uri, 1, 0, "GSettingsController", controllerProvider);
397 qmlRegisterType<GSettingsQml>(uri, 1, 0, "GSettings");
398 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
399 "GSettingsSchema can only be used inside of a GSettings component");
400
401=== modified file 'tests/qmltests/Tutorial/tst_Tutorial.qml'
402--- tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-05 21:13:16 +0000
403+++ tests/qmltests/Tutorial/tst_Tutorial.qml 2015-04-03 14:23:20 +0000
404@@ -17,6 +17,7 @@
405 import QtQuick 2.0
406 import QtTest 1.0
407 import AccountsService 0.1
408+import GSettings 1.0
409 import LightDM 0.1 as LightDM
410 import Ubuntu.Components 1.1
411 import Unity.Application 0.1
412@@ -45,6 +46,11 @@
413 }
414 }
415
416+ GSettings {
417+ id: unity8Settings
418+ schema.id: "com.canonical.Unity8"
419+ }
420+
421 Component.onCompleted: {
422 // must set the mock mode before loading the Shell
423 LightDM.Greeter.mockMode = "single-pin";
424@@ -112,6 +118,7 @@
425
426 function init() {
427 tryCompare(shell, "enabled", true); // enabled by greeter when ready
428+ unity8Settings.usageMode = "Staged";
429 AccountsService.demoEdges = false;
430 AccountsService.demoEdges = true;
431 swipeAwayGreeter();
432@@ -185,17 +192,19 @@
433 }
434
435 function checkRightEdge() {
436- touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
437+ if (unity8Settings.usageMode === "Staged") {
438+ touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
439
440- var stage = findChild(shell, "stage");
441- var spreadView = findChild(stage, "spreadView");
442- tryCompare(spreadView, "phase", 0);
443+ var stage = findChild(shell, "stage");
444+ var spreadView = findChild(stage, "spreadView");
445+ tryCompare(spreadView, "phase", 0);
446+ }
447 }
448
449 function checkBottomEdge() {
450 // Can't actually check effect of swipe, since dash isn't really loaded
451 var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
452- tryCompare(applicationsDisplayLoader.item, "interactive", false);
453+ tryCompare(applicationsDisplayLoader, "interactive", false);
454 }
455
456 function checkFinished() {
457@@ -256,6 +265,14 @@
458 goToPage(null);
459 }
460
461+ function test_walkthroughOnDesktop() {
462+ unity8Settings.usageMode = "Windowed";
463+ var page = goToPage("tutorialLeftFinish");
464+ var tick = findChild(page, "tick");
465+ tap(tick);
466+ checkFinished();
467+ }
468+
469 function test_launcherShortDrag() {
470 // goToPage does a normal launcher pull. But here we want to test
471 // just barely pulling the launcher out and letting go (i.e. not
472
473=== modified file 'tests/qmltests/tst_Shell.qml'
474--- tests/qmltests/tst_Shell.qml 2015-03-18 10:16:42 +0000
475+++ tests/qmltests/tst_Shell.qml 2015-04-03 14:23:20 +0000
476@@ -988,6 +988,11 @@
477 tryCompare(ApplicationManager, "focusedApplicationId", "gallery-app");
478 compare(wizard.shown, false);
479 compare(tutorial.running, false);
480+ tryCompare(AccountsService, "demoEdges", false);
481+ tryCompare(Wizard.System, "wizardEnabled", false);
482+
483+ var tutorialLeft = findChild(tutorial, "tutorialLeft");
484+ compare(tutorialLeft, null); // should be destroyed with tutorial
485 }
486
487 function test_tapOnRightEdgeReachesApplicationSurface() {
488@@ -1084,11 +1089,11 @@
489 {
490 var buttonShowDashHome = findChild(launcher, "buttonShowDashHome");
491 var startPos = buttonShowDashHome.mapToItem(shell,
492+ buttonShowDashHome.width * 0.2,
493+ buttonShowDashHome.height * 0.8);
494+ var endPos = buttonShowDashHome.mapToItem(shell,
495 buttonShowDashHome.width * 0.8,
496 buttonShowDashHome.height * 0.2);
497- var endPos = buttonShowDashHome.mapToItem(shell,
498- buttonShowDashHome.width * 0.2,
499- buttonShowDashHome.height * 0.8);
500 touchFlick(shell, startPos.x, startPos.y, endPos.x, endPos.y);
501 }
502

Subscribers

People subscribed via source and target branches