Merge lp:~mterry/unity8/no-touch-no-lifecycle into lp:unity8

Proposed by Michael Terry on 2015-09-29
Status: Superseded
Proposed branch: lp:~mterry/unity8/no-touch-no-lifecycle
Merge into: lp:unity8
Diff against target: 1734 lines (+670/-256)
31 files modified
debian/control (+1/-1)
plugins/Utils/windowstatestorage.cpp (+62/-23)
plugins/Utils/windowstatestorage.h (+13/-1)
qml/Components/Orientations.qml (+29/-0)
qml/Components/WindowControlButtons.qml (+3/-0)
qml/DeviceConfiguration.qml (+91/-71)
qml/OrientedShell.qml (+32/-22)
qml/Panel/Panel.qml (+5/-3)
qml/Shell.qml (+8/-12)
qml/Stages/AbstractStage.qml (+63/-0)
qml/Stages/DesktopStage.qml (+14/-24)
qml/Stages/PhoneStage.qml (+21/-34)
qml/Stages/ShimStage.qml (+1/-1)
qml/Stages/SpreadDelegate.qml (+8/-9)
qml/Stages/TabletStage.qml (+24/-36)
qml/Stages/WindowResizeArea.qml (+38/-7)
tests/mocks/GSettings.1.0/fake_gsettings.cpp (+31/-0)
tests/mocks/GSettings.1.0/fake_gsettings.h (+10/-0)
tests/mocks/Unity/Application/ApplicationInfo.cpp (+12/-0)
tests/mocks/Unity/Application/ApplicationInfo.h (+4/-0)
tests/mocks/Unity/Application/ApplicationManager.cpp (+9/-0)
tests/mocks/Unity/Application/ApplicationManager.h (+1/-1)
tests/mocks/Utils/windowstatestorage.cpp (+17/-0)
tests/mocks/Utils/windowstatestorage.h (+12/-0)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+1/-0)
tests/qmltests/Stages/tst_PhoneStage.qml (+0/-2)
tests/qmltests/Stages/tst_SpreadDelegate.qml (+6/-2)
tests/qmltests/Stages/tst_TabletStage.qml (+2/-2)
tests/qmltests/Stages/tst_WindowResizeArea.qml (+35/-2)
tests/qmltests/tst_OrientedShell.qml (+1/-1)
tests/qmltests/tst_Shell.qml (+116/-2)
To merge this branch: bzr merge lp:~mterry/unity8/no-touch-no-lifecycle
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing on 2015-10-28
Gerry Boland Needs Fixing on 2015-10-15
Andrea Cimitan (community) 2015-09-29 Needs Fixing on 2015-10-08
Review via email: mp+272844@code.launchpad.net

This proposal has been superseded by a proposal from 2015-10-27.

Commit Message

Set lifecycle policy ourselves, instead of letting qtmir do it for us and allow non-Touch apps to opt-out of the Touch lifecycle.

This requires using the new isTouchApp unity-api property to ApplicationInfoInterface.

Now that qtmir won't decide policy for suspending anymore, we take over the interpretation of the lifecycleException GSettings key. Since the GSettings key for that was registered under the qtmir namespace (and there's no technical reason to migrate settings), I left the schema in qtmir itself. We merely consume it.

Description of the Change

Set lifecycle policy ourselves, instead of letting qtmir do it for us and allow non-Touch apps to opt-out of the Touch lifecycle.

This requires using the new isTouchApp unity-api property to ApplicationInfoInterface.

Now that qtmir won't decide policy for suspending anymore, we take over the interpretation of the lifecycleException GSettings key. Since the GSettings key for that was registered under the qtmir namespace (and there's no technical reason to migrate settings), I left the schema in qtmir itself. We merely consume it.

== Checklist ==

* Are there any related MPs required for this MP to build/function as expected? Please list.
 - https://code.launchpad.net/~mterry/unity-api/no-touch-no-lifecycle/+merge/272829
 - https://code.launchpad.net/~mterry/qtmir/no-touch-no-lifecycle/+merge/272791

 * 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?
 I'm a member of that team.

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

To post a comment you must log in.
1990. By Michael Terry on 2015-10-01

Update to drop canSuspend and simply request different states based on info we have

1991. By Michael Terry on 2015-10-01

Add AbstractStage item to hold shared stage code

Andrea Cimitan (cimi) :
review: Needs Fixing
1992. By Michael Terry on 2015-10-08

Drop down to sdk 1.2

Michael Terry (mterry) wrote :

> 1.2 for now

Done.

Gerry Boland (gerboland) wrote :

=== added file 'qml/graphics/applicationIcons/libreoffice@18.png'
Binary files qml/graphics/applicationIcons/libreoffice@18.png 1970-01-01 00:00:00 +0000 and qml/graphics/applicationIcons/libreoffice@18.png 2015-10-08 19:48:39 +0000 differ
Why did that change?

review: Needs Information
Gerry Boland (gerboland) wrote :

=== added file 'qml/Stages/AbstractStage.qml'
Instead of destroying and re-creating the GSettings instance when Stage changes, could this file be a Singleton shared by all Stages?

+Rectangle {
This looks like a useless rectangle, no color is set. It could be a QtObject, if you weren't inheriting from it.

Oh, I see why the LibreOffice icon. Please ignore above comment

review: Needs Fixing
Gerry Boland (gerboland) wrote :

+ !model.isTouchApp
+ || isExemptFromLifecycle(model.appId)
I think it would improve readability to make a quick boolean property :
canSuspend: model.isTouchApp && !isExemptFromLifecycle(model.appId)

1993. By Michael Terry on 2015-10-15

Address review comments; consolidate some shared stage code

Michael Terry (mterry) wrote :

> === added file 'qml/Stages/AbstractStage.qml'
> Instead of destroying and re-creating the GSettings instance when
> Stage changes, could this file be a Singleton shared by all Stages?

It could be, sure, but I wasn't in the mindset of optimizing for Stage changes. What about the hundred other objects that get destroyed & created in that case? Are GSettings instances particularly expensive (honest question)?

> +Rectangle {
> This looks like a useless rectangle, no color is set. It could be
> a QtObject, if you weren't inheriting from it.

Fair point. I was trying to avoid unrelated changes by not merging common Stage code. I've done that now, and consolidated the color: settings as well as the public API for stages (which various of the Stage implementations didn't fully implement!).

> + !model.isTouchApp
> + || isExemptFromLifecycle(model.appId)
> I think it would improve readability to make a quick boolean property :
> canSuspend: model.isTouchApp && !isExemptFromLifecycle(model.appId)

Good point. Done.

1994. By Michael Terry on 2015-10-27

Go back to using 1.3

1995. By Michael Terry on 2015-10-27

Merge new_fix_upsidedown branch

1996. By Michael Terry on 2015-10-28

Break test apart

1997. By Michael Terry on 2015-10-28

Also confirm that app no longer has focus

1998. By Michael Terry on 2015-10-28

Add quick clarifying comment

1999. By Michael Terry on 2015-10-30

Bump shell application version

2000. By Michael Terry on 2015-10-30

Bump shell application version harder

2001. By Michael Terry on 2015-11-03

Whoops, remove testing changes

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2015-10-21 11:51:11 +0000
3+++ debian/control 2015-10-27 20:51:44 +0000
4@@ -29,7 +29,7 @@
5 libqt5xmlpatterns5-dev,
6 libsystemsettings-dev,
7 libudev-dev,
8- libunity-api-dev (>= 7.101),
9+ libunity-api-dev (>= 7.102),
10 libusermetricsoutput1-dev,
11 libxcb1-dev,
12 pkg-config,
13
14=== modified file 'plugins/Utils/windowstatestorage.cpp'
15--- plugins/Utils/windowstatestorage.cpp 2015-09-14 09:11:08 +0000
16+++ plugins/Utils/windowstatestorage.cpp 2015-10-27 20:51:44 +0000
17@@ -47,26 +47,38 @@
18 m_db.close();
19 }
20
21+void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
22+{
23+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
24+ .arg(windowId)
25+ .arg((int)state);
26+
27+ saveValue(queryString);
28+}
29+
30+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
31+{
32+ const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
33+ .arg(windowId);
34+
35+ QSqlQuery query = getValue(queryString);
36+
37+ if (!query.first()) {
38+ return defaultValue;
39+ }
40+ return (WindowState)query.value("state").toInt();
41+}
42+
43 void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
44 {
45- QMutexLocker mutexLocker(&s_mutex);
46-
47- QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
48+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
49 .arg(windowId)
50 .arg(rect.x())
51 .arg(rect.y())
52 .arg(rect.width())
53 .arg(rect.height());
54
55- QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
56- m_asyncQueries.append(future);
57-
58- QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
59- futureWatcher->setFuture(future);
60- connect(futureWatcher, &QFutureWatcher<void>::finished,
61- this,
62- [=](){ m_asyncQueries.removeAll(futureWatcher->future());
63- futureWatcher->deleteLater(); });
64+ saveValue(queryString);
65 }
66
67 void WindowStateStorage::executeAsyncQuery(const QString &queryString)
68@@ -82,20 +94,13 @@
69 }
70 }
71
72-QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
73+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
74 {
75- QMutexLocker l(&s_mutex);
76 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
77 .arg(windowId);
78- QSqlQuery query;
79-
80- bool ok = query.exec(queryString);
81- if (!ok) {
82- qWarning() << "Error retrieving window state for" << windowId
83- << "Driver error:" << query.lastError().driverText()
84- << "Database error:" << query.lastError().databaseText();
85- return defaultValue;
86- }
87+
88+ QSqlQuery query = getValue(queryString);
89+
90 if (!query.first()) {
91 return defaultValue;
92 }
93@@ -114,4 +119,38 @@
94 QSqlQuery query;
95 query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
96 }
97+
98+ if (!m_db.tables().contains("state")) {
99+ QSqlQuery query;
100+ query.exec("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
101+ }
102+}
103+
104+void WindowStateStorage::saveValue(const QString &queryString)
105+{
106+ QMutexLocker mutexLocker(&s_mutex);
107+
108+ QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
109+ m_asyncQueries.append(future);
110+
111+ QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
112+ futureWatcher->setFuture(future);
113+ connect(futureWatcher, &QFutureWatcher<void>::finished,
114+ this,
115+ [=](){ m_asyncQueries.removeAll(futureWatcher->future());
116+ futureWatcher->deleteLater(); });
117+}
118+
119+QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
120+{
121+ QMutexLocker l(&s_mutex);
122+ QSqlQuery query;
123+
124+ bool ok = query.exec(queryString);
125+ if (!ok) {
126+ qWarning() << "Error retrieving database query:" << queryString
127+ << "Driver error:" << query.lastError().driverText()
128+ << "Database error:" << query.lastError().databaseText();
129+ }
130+ return query;
131 }
132
133=== modified file 'plugins/Utils/windowstatestorage.h'
134--- plugins/Utils/windowstatestorage.h 2015-03-13 19:01:32 +0000
135+++ plugins/Utils/windowstatestorage.h 2015-10-27 20:51:44 +0000
136@@ -22,16 +22,28 @@
137 class WindowStateStorage: public QObject
138 {
139 Q_OBJECT
140+ Q_ENUMS(WindowState)
141 public:
142+ enum WindowState {
143+ WindowStateNormal,
144+ WindowStateMaximized
145+ };
146+
147 WindowStateStorage(QObject *parent = 0);
148 virtual ~WindowStateStorage();
149
150+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
151+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
152+
153 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
154- Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
155+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
156
157 private:
158 void initdb();
159
160+ void saveValue(const QString &queryString);
161+ QSqlQuery getValue(const QString &queryString) const;
162+
163 static void executeAsyncQuery(const QString &queryString);
164 static QMutex s_mutex;
165
166
167=== added file 'qml/Components/Orientations.qml'
168--- qml/Components/Orientations.qml 1970-01-01 00:00:00 +0000
169+++ qml/Components/Orientations.qml 2015-10-27 20:51:44 +0000
170@@ -0,0 +1,29 @@
171+/*
172+ * Copyright 2015 Canonical Ltd.
173+ *
174+ * This program is free software; you can redistribute it and/or modify
175+ * it under the terms of the GNU General Public License as published by
176+ * the Free Software Foundation; version 3.
177+ *
178+ * This program is distributed in the hope that it will be useful,
179+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
180+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
181+ * GNU General Public License for more details.
182+ *
183+ * You should have received a copy of the GNU General Public License
184+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
185+ */
186+
187+import QtQuick 2.4
188+
189+QtObject {
190+ // Just because "native" is a reserved keyword :(
191+ property int native_: Qt.PortraitOrientation
192+
193+ property int primary: Qt.PortraitOrientation
194+
195+ property int landscape: Qt.LandscapeOrientation
196+ property int invertedLandscape: Qt.InvertedLandscapeOrientation
197+ property int portrait: Qt.PortraitOrientation
198+ property int invertedPortrait: Qt.InvertedPortraitOrientation
199+}
200
201=== modified file 'qml/Components/WindowControlButtons.qml'
202--- qml/Components/WindowControlButtons.qml 2014-11-24 11:21:38 +0000
203+++ qml/Components/WindowControlButtons.qml 2015-10-27 20:51:44 +0000
204@@ -28,6 +28,7 @@
205 signal maximize()
206
207 Rectangle {
208+ objectName: "closeWindowButton"
209 height: parent.height; width: height; radius: height / 2
210 gradient: Gradient {
211 GradientStop { color: "#F49073"; position: 0 }
212@@ -38,6 +39,7 @@
213 MouseArea { anchors.fill: parent; onClicked: root.close() }
214 }
215 Rectangle {
216+ objectName: "minimizeWindowButton"
217 height: parent.height; width: height; radius: height / 2
218 gradient: Gradient {
219 GradientStop { color: "#92918C"; position: 0 }
220@@ -48,6 +50,7 @@
221 MouseArea { anchors.fill: parent; onClicked: root.minimize() }
222 }
223 Rectangle {
224+ objectName: "maximizeWindowButton"
225 height: parent.height; width: height; radius: height / 2
226 gradient: Gradient {
227 GradientStop { color: "#92918C"; position: 0 }
228
229=== modified file 'qml/DeviceConfiguration.qml'
230--- qml/DeviceConfiguration.qml 2015-10-05 16:43:41 +0000
231+++ qml/DeviceConfiguration.qml 2015-10-27 20:51:44 +0000
232@@ -16,79 +16,99 @@
233
234 import QtQuick 2.0
235
236-StateGroup {
237+QtObject {
238 id: root
239
240 readonly property int useNativeOrientation: -1
241
242- property int primaryOrientation: useNativeOrientation
243-
244- property int supportedOrientations: Qt.PortraitOrientation
245- | Qt.InvertedPortraitOrientation
246- | Qt.LandscapeOrientation
247- | Qt.InvertedLandscapeOrientation
248-
249- // Supported values so far:
250- // "phone", "tablet" or "desktop"
251- property string category: "phone"
252-
253- property int ignoredMice: 0
254-
255-
256- property alias name: root.state
257-
258- states: [
259- State {
260- name: "mako"
261- PropertyChanges {
262- target: root
263- supportedOrientations: Qt.PortraitOrientation
264- | Qt.LandscapeOrientation
265- | Qt.InvertedLandscapeOrientation
266- }
267- },
268- State {
269- name: "krillin"
270- PropertyChanges {
271- target: root
272- supportedOrientations: Qt.PortraitOrientation
273- | Qt.LandscapeOrientation
274- | Qt.InvertedLandscapeOrientation
275- }
276- },
277- State {
278- name: "arale"
279- PropertyChanges {
280- target: root
281- supportedOrientations: Qt.PortraitOrientation
282- | Qt.LandscapeOrientation
283- | Qt.InvertedLandscapeOrientation
284- ignoredMice: 1
285- }
286- },
287- State {
288- name: "manta"
289- PropertyChanges {
290- target: root
291- category: "tablet"
292- }
293- },
294- State {
295- name: "flo"
296- PropertyChanges {
297- target: root
298- primaryOrientation: Qt.InvertedLandscapeOrientation
299- category: "tablet"
300- }
301- },
302- State {
303- name: "desktop"
304- PropertyChanges {
305- target: root
306- category: "desktop"
307- supportedOrientations: root.useNativeOrientation
308- }
309- }
310- ]
311-
312+ // The only writable property in the API
313+ // all other properties are set according to the device name
314+ property alias name: priv.state
315+
316+ readonly property alias primaryOrientation: priv.primaryOrientation
317+ readonly property alias supportedOrientations: priv.supportedOrientations
318+ readonly property alias landscapeOrientation: priv.landscapeOrientation
319+ readonly property alias invertedLandscapeOrientation: priv.invertedLandscapeOrientation
320+ readonly property alias portraitOrientation: priv.portraitOrientation
321+ readonly property alias invertedPortraitOrientation: priv.invertedPortraitOrientation
322+
323+ readonly property alias category: priv.category
324+
325+ readonly property alias ignoredMice: priv.ignoredMice
326+
327+ readonly property var priv: StateGroup {
328+ id: priv
329+
330+ property int primaryOrientation: root.useNativeOrientation
331+
332+ property int supportedOrientations: Qt.PortraitOrientation
333+ | Qt.InvertedPortraitOrientation
334+ | Qt.LandscapeOrientation
335+ | Qt.InvertedLandscapeOrientation
336+
337+ property int landscapeOrientation: Qt.LandscapeOrientation
338+ property int invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
339+ property int portraitOrientation: Qt.PortraitOrientation
340+ property int invertedPortraitOrientation: Qt.InvertedPortraitOrientation
341+
342+ // Supported values so far:
343+ // "phone", "tablet" or "desktop"
344+ property string category: "phone"
345+
346+ property int ignoredMice: 0
347+
348+ states: [
349+ State {
350+ name: "mako"
351+ PropertyChanges {
352+ target: priv
353+ supportedOrientations: Qt.PortraitOrientation
354+ | Qt.LandscapeOrientation
355+ | Qt.InvertedLandscapeOrientation
356+ }
357+ },
358+ State {
359+ name: "krillin"
360+ PropertyChanges {
361+ target: priv
362+ supportedOrientations: Qt.PortraitOrientation
363+ | Qt.LandscapeOrientation
364+ | Qt.InvertedLandscapeOrientation
365+ }
366+ },
367+ State {
368+ name: "arale"
369+ PropertyChanges {
370+ target: priv
371+ supportedOrientations: Qt.PortraitOrientation
372+ | Qt.LandscapeOrientation
373+ | Qt.InvertedLandscapeOrientation
374+ ignoredMice: 1
375+ }
376+ },
377+ State {
378+ name: "manta"
379+ PropertyChanges {
380+ target: priv
381+ category: "tablet"
382+ }
383+ },
384+ State {
385+ name: "flo"
386+ PropertyChanges {
387+ target: priv
388+ landscapeOrientation: Qt.InvertedLandscapeOrientation
389+ primaryOrientation: Qt.InvertedLandscapeOrientation
390+ category: "tablet"
391+ }
392+ },
393+ State {
394+ name: "desktop"
395+ PropertyChanges {
396+ target: priv
397+ category: "desktop"
398+ }
399+ }
400+ ]
401+ }
402 }
403
404=== modified file 'qml/OrientedShell.qml'
405--- qml/OrientedShell.qml 2015-10-05 19:15:03 +0000
406+++ qml/OrientedShell.qml 2015-10-27 20:51:44 +0000
407@@ -32,20 +32,31 @@
408 implicitWidth: units.gu(40)
409 implicitHeight: units.gu(71)
410
411- // NB: native and primary orientations here don't map exactly to their QScreen counterparts
412- readonly property int nativeOrientation: width > height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
413-
414- readonly property int primaryOrientation:
415- deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
416- ? nativeOrientation : deviceConfiguration.primaryOrientation
417-
418 DeviceConfiguration {
419 id: deviceConfiguration
420 name: applicationArguments.deviceName
421 }
422
423-
424- // to be overwritten by tests
425+ property alias orientations: d.orientations
426+
427+ Item {
428+ id: d
429+
430+ property Orientations orientations: Orientations {
431+ id: orientations
432+ // NB: native and primary orientations here don't map exactly to their QScreen counterparts
433+ native_: root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
434+
435+ primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
436+ ? native_ : deviceConfiguration.primaryOrientation
437+
438+ landscape: deviceConfiguration.landscapeOrientation
439+ invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
440+ portrait: deviceConfiguration.portraitOrientation
441+ invertedPortrait: deviceConfiguration.invertedPortraitOrientation
442+ }
443+ }
444+ // to be overwritten by tests
445 property var unity8Settings: Unity8Settings {}
446 property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
447
448@@ -101,7 +112,7 @@
449
450 property int acceptedOrientationAngle: {
451 if (orientation & supportedOrientations) {
452- return Screen.angleBetween(nativeOrientation, orientation);
453+ return Screen.angleBetween(orientations.native_, orientation);
454 } else if (shell.orientation & supportedOrientations) {
455 // stay where we are
456 return shell.orientationAngle;
457@@ -111,16 +122,16 @@
458 // rotate to some supported orientation as we can't stay where we currently are
459 // TODO: Choose the closest to the current one
460 if (supportedOrientations & Qt.PortraitOrientation) {
461- return Screen.angleBetween(nativeOrientation, Qt.PortraitOrientation);
462+ return Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
463 } else if (supportedOrientations & Qt.LandscapeOrientation) {
464- return Screen.angleBetween(nativeOrientation, Qt.LandscapeOrientation);
465+ return Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
466 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
467- return Screen.angleBetween(nativeOrientation, Qt.InvertedPortraitOrientation);
468+ return Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
469 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
470- return Screen.angleBetween(nativeOrientation, Qt.InvertedLandscapeOrientation);
471+ return Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
472 } else {
473 // if all fails, fallback to primary orientation
474- return Screen.angleBetween(nativeOrientation, primaryOrientation);
475+ return Screen.angleBetween(orientations.native_, orientations.primary);
476 }
477 }
478 }
479@@ -128,19 +139,19 @@
480 function angleToOrientation(angle) {
481 switch (angle) {
482 case 0:
483- return nativeOrientation;
484+ return orientations.native_;
485 case 90:
486- return nativeOrientation === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
487+ return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
488 : Qt.PortraitOrientation;
489 case 180:
490- return nativeOrientation === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
491+ return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
492 : Qt.InvertedLandscapeOrientation;
493 case 270:
494- return nativeOrientation === Qt.PortraitOrientation ? Qt.LandscapeOrientation
495+ return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
496 : Qt.InvertedPortraitOrientation;
497 default:
498 console.warn("angleToOrientation: Invalid orientation angle: " + angle);
499- return primaryOrientation;
500+ return orientations.primary;
501 }
502 }
503
504@@ -159,8 +170,7 @@
505 width: root.width
506 height: root.height
507 orientation: root.angleToOrientation(orientationAngle)
508- primaryOrientation: root.primaryOrientation
509- nativeOrientation: root.nativeOrientation
510+ orientations: root.orientations
511 nativeWidth: root.width
512 nativeHeight: root.height
513 mode: applicationArguments.mode
514
515=== modified file 'qml/Panel/Panel.qml'
516--- qml/Panel/Panel.qml 2015-09-29 12:48:46 +0000
517+++ qml/Panel/Panel.qml 2015-10-27 20:51:44 +0000
518@@ -28,6 +28,7 @@
519 property alias callHint: __callHint
520 property bool fullscreenMode: false
521 property real indicatorAreaShowProgress: 1.0
522+ property bool locked: false
523
524 opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.0
525
526@@ -136,7 +137,7 @@
527 }
528
529 shown: false
530- width: root.width - (PanelState.buttonsVisible ? windowControlButtons.width : 0)
531+ width: root.width - (windowControlButtons.visible ? windowControlButtons.width : 0)
532 minimizedPanelHeight: units.gu(3)
533 expandedPanelHeight: units.gu(7)
534 openedHeight: root.height - indicatorOrangeLine.height
535@@ -164,13 +165,14 @@
536
537 WindowControlButtons {
538 id: windowControlButtons
539+ objectName: "panelWindowControlButtons"
540 anchors {
541 left: parent.left
542 top: parent.top
543 margins: units.gu(0.7)
544 }
545 height: indicators.minimizedPanelHeight - anchors.margins * 2
546- visible: PanelState.buttonsVisible
547+ visible: PanelState.buttonsVisible && !root.locked
548 onClose: PanelState.close()
549 onMinimize: PanelState.minimize()
550 onMaximize: PanelState.maximize()
551@@ -189,7 +191,7 @@
552 id: __callHint
553 anchors {
554 top: parent.top
555- left: PanelState.buttonsVisible ? windowControlButtons.right : parent.left
556+ left: windowControlButtons.visible ? windowControlButtons.right : parent.left
557 }
558 height: indicators.minimizedPanelHeight
559 visible: active && indicators.state == "initial"
560
561=== modified file 'qml/Shell.qml'
562--- qml/Shell.qml 2015-10-16 17:11:54 +0000
563+++ qml/Shell.qml 2015-10-27 20:51:44 +0000
564@@ -49,14 +49,14 @@
565 // to be set from outside
566 property int orientationAngle: 0
567 property int orientation
568- property int primaryOrientation
569- property int nativeOrientation
570+ property Orientations orientations
571 property real nativeWidth
572 property real nativeHeight
573 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
574 property bool beingResized
575 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
576 property string mode: "full-greeter"
577+ property bool cursorVisible: false
578 function updateFocusedAppOrientation() {
579 applicationsDisplayLoader.item.updateFocusedAppOrientation();
580 }
581@@ -298,21 +298,16 @@
582 }
583 Binding {
584 target: applicationsDisplayLoader.item
585+ property: "orientations"
586+ value: shell.orientations
587+ }
588+ Binding {
589+ target: applicationsDisplayLoader.item
590 property: "background"
591 value: wallpaperResolver.background
592 }
593 Binding {
594 target: applicationsDisplayLoader.item
595- property: "shellPrimaryOrientation"
596- value: shell.primaryOrientation
597- }
598- Binding {
599- target: applicationsDisplayLoader.item
600- property: "nativeOrientation"
601- value: shell.nativeOrientation
602- }
603- Binding {
604- target: applicationsDisplayLoader.item
605 property: "nativeWidth"
606 value: shell.nativeWidth
607 }
608@@ -536,6 +531,7 @@
609
610 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
611 || greeter.hasLockedApp
612+ locked: greeter && greeter.active
613 }
614
615 Launcher {
616
617=== added file 'qml/Stages/AbstractStage.qml'
618--- qml/Stages/AbstractStage.qml 1970-01-01 00:00:00 +0000
619+++ qml/Stages/AbstractStage.qml 2015-10-27 20:51:44 +0000
620@@ -0,0 +1,63 @@
621+/*
622+ * Copyright (C) 2015 Canonical, Ltd.
623+ *
624+ * This program is free software; you can redistribute it and/or modify
625+ * it under the terms of the GNU General Public License as published by
626+ * the Free Software Foundation; version 3.
627+ *
628+ * This program is distributed in the hope that it will be useful,
629+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
630+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
631+ * GNU General Public License for more details.
632+ *
633+ * You should have received a copy of the GNU General Public License
634+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
635+ */
636+
637+import QtQuick 2.4
638+import Ubuntu.Components 1.2
639+import GSettings 1.0
640+
641+Rectangle {
642+ id: root
643+
644+ color: "#111111"
645+
646+ // Controls to be set from outside
647+ property bool altTabPressed
648+ property url background
649+ property bool beingResized
650+ property int dragAreaWidth
651+ property bool interactive
652+ property real inverseProgress // This is the progress for left edge drags, in pixels.
653+ property bool keepDashRunning: true
654+ property real maximizedAppTopMargin
655+ property real nativeHeight
656+ property real nativeWidth
657+ property QtObject orientations
658+ property int shellOrientation
659+ property int shellOrientationAngle
660+ property bool spreadEnabled: true // If false, animations and right edge will be disabled
661+ property bool suspended
662+
663+ // To be read from outside
664+ property var mainApp: null
665+ property int mainAppWindowOrientationAngle
666+ property bool orientationChangesEnabled
667+
668+ // Shared code for use in stage implementations
669+ GSettings {
670+ id: lifecycleExceptions
671+ schema.id: "com.canonical.qtmir"
672+ }
673+
674+ function isExemptFromLifecycle(appId) {
675+ var shortAppId = appId.split('_')[0];
676+ for (var i = 0; i < lifecycleExceptions.lifecycleExemptAppids.length; i++) {
677+ if (shortAppId === lifecycleExceptions.lifecycleExemptAppids[i]) {
678+ return true;
679+ }
680+ }
681+ return false;
682+ }
683+}
684
685=== modified file 'qml/Stages/DesktopStage.qml'
686--- qml/Stages/DesktopStage.qml 2015-10-19 14:27:57 +0000
687+++ qml/Stages/DesktopStage.qml 2015-10-27 20:51:44 +0000
688@@ -22,39 +22,21 @@
689 import Unity.Application 0.1
690 import "../Components"
691 import "../Components/PanelState"
692+import "../Components"
693 import Utils 0.1
694 import Ubuntu.Gestures 0.1
695
696-Rectangle {
697+AbstractStage {
698 id: root
699 anchors.fill: parent
700
701- // Controls to be set from outside
702- property int dragAreaWidth // just to comply with the interface shared between stages
703- property real maximizedAppTopMargin
704- property bool interactive
705- property bool spreadEnabled // just to comply with the interface shared between stages
706- property real inverseProgress: 0 // just to comply with the interface shared between stages
707- property int shellOrientationAngle: 0
708- property int shellOrientation
709- property int shellPrimaryOrientation
710- property int nativeOrientation
711- property bool beingResized: false
712- property bool keepDashRunning: true
713- property bool suspended: false
714- property alias background: wallpaper.source
715- property alias altTabPressed: spread.altTabPressed
716-
717 // functions to be called from outside
718 function updateFocusedAppOrientation() { /* TODO */ }
719 function updateFocusedAppOrientationAnimated() { /* TODO */}
720
721- // To be read from outside
722- readonly property var mainApp: ApplicationManager.focusedApplicationId
723+ mainApp: ApplicationManager.focusedApplicationId
724 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
725 : null
726- property int mainAppWindowOrientationAngle: 0
727- readonly property bool orientationChangesEnabled: false
728
729 Connections {
730 target: ApplicationManager
731@@ -111,6 +93,7 @@
732 property: "buttonsVisible"
733 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
734 }
735+ Component.onDestruction: PanelState.buttonsVisible = false;
736
737 FocusScope {
738 id: appContainer
739@@ -121,6 +104,7 @@
740 CrossFadeImage {
741 id: wallpaper
742 anchors.fill: parent
743+ source: root.background
744 sourceSize { height: root.height; width: root.width }
745 fillMode: Image.PreserveAspectCrop
746 }
747@@ -140,6 +124,7 @@
748
749 property bool maximized: false
750 property bool minimized: false
751+ property bool animationsEnabled: true
752
753 onFocusChanged: {
754 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
755@@ -158,15 +143,18 @@
756 value: ApplicationInfoInterface.RequestedRunning // Always running for now
757 }
758
759- function maximize() {
760+ function maximize(animated) {
761+ animationsEnabled = (animated === undefined) || animated;
762 minimized = false;
763 maximized = true;
764 }
765- function minimize() {
766+ function minimize(animated) {
767+ animationsEnabled = (animated === undefined) || animated;
768 maximized = false;
769 minimized = true;
770 }
771- function unmaximize() {
772+ function unmaximize(animated) {
773+ animationsEnabled = (animated === undefined) || animated;
774 minimized = false;
775 maximized = false;
776 }
777@@ -188,6 +176,7 @@
778 Transition {
779 from: "maximized,minimized,normal,"
780 to: "maximized,minimized,normal,"
781+ enabled: appDelegate.animationsEnabled
782 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
783 },
784 Transition {
785@@ -269,5 +258,6 @@
786 anchors.fill: parent
787 workspace: appContainer
788 focus: state == "altTab"
789+ altTabPressed: root.altTabPressed
790 }
791 }
792
793=== modified file 'qml/Stages/PhoneStage.qml'
794--- qml/Stages/PhoneStage.qml 2015-09-21 13:37:47 +0000
795+++ qml/Stages/PhoneStage.qml 2015-10-27 20:51:44 +0000
796@@ -22,29 +22,15 @@
797 import Utils 0.1
798 import "../Components"
799
800-Rectangle {
801+AbstractStage {
802 id: root
803
804- // Controls to be set from outside
805- property int dragAreaWidth
806- property real maximizedAppTopMargin
807- property bool interactive
808- property bool spreadEnabled: true // If false, animations and right edge will be disabled
809- property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
810 property QtObject applicationManager: ApplicationManager
811 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
812 property bool altTabEnabled: true
813 property real startScale: 1.1
814 property real endScale: 0.7
815- property bool keepDashRunning: true
816- property bool suspended: false
817- property int shellOrientationAngle: 0
818- property int shellOrientation
819- property int shellPrimaryOrientation
820- property int nativeOrientation
821- property real nativeWidth
822- property real nativeHeight
823- property bool beingResized: false
824+
825 onBeingResizedChanged: {
826 if (beingResized) {
827 // Brace yourselves for impact!
828@@ -56,6 +42,8 @@
829 priv.reset();
830 }
831 }
832+
833+ // Functions to be called from outside
834 function updateFocusedAppOrientation() {
835 if (spreadRepeater.count > 0) {
836 spreadRepeater.itemAt(0).matchShellOrientation();
837@@ -71,7 +59,7 @@
838
839 var supportedOrientations = spreadDelegate.application.supportedOrientations;
840 if (supportedOrientations === Qt.PrimaryOrientation) {
841- supportedOrientations = spreadDelegate.shellPrimaryOrientation;
842+ supportedOrientations = root.orientations.primary;
843 }
844
845 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
846@@ -85,16 +73,14 @@
847 }
848 }
849
850- // To be read from outside
851- readonly property var mainApp: applicationManager.focusedApplicationId
852+ mainApp: applicationManager.focusedApplicationId
853 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
854 : null
855
856- property int mainAppWindowOrientationAngle: 0
857- readonly property bool orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
858- && !priv.focusedAppDelegateIsDislocated
859- && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
860- && spreadView.phase === 0
861+ orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
862+ && !priv.focusedAppDelegateIsDislocated
863+ && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
864+ && spreadView.phase === 0
865
866 // How far left the stage has been dragged
867 readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
868@@ -106,8 +92,6 @@
869
870 signal opened()
871
872- color: "#111111"
873-
874 function select(appId) {
875 spreadView.snapTo(priv.indexOf(appId));
876 }
877@@ -438,16 +422,21 @@
878 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
879 focusFirstApp: root.focusFirstApp
880
881+ readonly property bool isDash: model.appId == "unity8-dash"
882+
883+ readonly property bool canSuspend: model.isTouchApp
884+ && !isExemptFromLifecycle(model.appId)
885+
886 Binding {
887 target: appDelegate.application
888 property: "requestedState"
889- value: (isDash && root.keepDashRunning) || (!root.suspended && appDelegate.focus)
890- ? ApplicationInfoInterface.RequestedRunning
891- : ApplicationInfoInterface.RequestedSuspended
892+ value: !canSuspend
893+ || (isDash && root.keepDashRunning)
894+ || (!root.suspended && appDelegate.focus)
895+ ? ApplicationInfoInterface.RequestedRunning
896+ : ApplicationInfoInterface.RequestedSuspended
897 }
898
899- readonly property bool isDash: model.appId == "unity8-dash"
900-
901 z: isDash && !spreadView.active ? -1 : behavioredIndex
902
903 x: {
904@@ -535,11 +524,9 @@
905 visible: (progress >= 0 && progress < 1.7)
906 || (isDash && priv.focusedAppDelegateIsDislocated)
907
908-
909 shellOrientationAngle: root.shellOrientationAngle
910 shellOrientation: root.shellOrientation
911- shellPrimaryOrientation: root.shellPrimaryOrientation
912- nativeOrientation: root.nativeOrientation
913+ orientations: root.orientations
914
915 onClicked: {
916 if (root.altTabEnabled && spreadView.phase == 2) {
917
918=== modified file 'qml/Stages/ShimStage.qml'
919--- qml/Stages/ShimStage.qml 2015-04-21 19:43:25 +0000
920+++ qml/Stages/ShimStage.qml 2015-10-27 20:51:44 +0000
921@@ -17,7 +17,7 @@
922 import QtQuick 2.3
923 import Ubuntu.Components 0.1
924
925-Rectangle {
926+AbstractStage {
927 id: shimStage
928
929 anchors.fill: parent
930
931=== modified file 'qml/Stages/SpreadDelegate.qml'
932--- qml/Stages/SpreadDelegate.qml 2015-09-21 13:10:02 +0000
933+++ qml/Stages/SpreadDelegate.qml 2015-10-27 20:51:44 +0000
934@@ -42,8 +42,7 @@
935 property alias application: appWindow.application
936 property int shellOrientationAngle
937 property int shellOrientation
938- property int shellPrimaryOrientation
939- property int nativeOrientation
940+ property QtObject orientations
941
942 function matchShellOrientation() {
943 if (!root.application)
944@@ -157,7 +156,7 @@
945 var supportedOrientations = root.application.supportedOrientations;
946
947 if (supportedOrientations === Qt.PrimaryOrientation) {
948- supportedOrientations = root.shellPrimaryOrientation;
949+ supportedOrientations = root.orientations.primary;
950 }
951
952 // If it doesn't support shell's current orientation
953@@ -166,18 +165,18 @@
954 if (supportedOrientations & root.shellOrientation) {
955 chosenOrientation = root.shellOrientation;
956 } else if (supportedOrientations & Qt.PortraitOrientation) {
957- chosenOrientation = Qt.PortraitOrientation;
958+ chosenOrientation = root.orientations.portrait;
959 } else if (supportedOrientations & Qt.LandscapeOrientation) {
960- chosenOrientation = Qt.LandscapeOrientation;
961+ chosenOrientation = root.orientations.landscape;
962 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
963- chosenOrientation = Qt.InvertedPortraitOrientation;
964+ chosenOrientation = root.orientations.invertedPortrait;
965 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
966- chosenOrientation = Qt.InvertedLandscapeOrientation;
967+ chosenOrientation = root.orientations.invertedLandscape;
968 } else {
969- chosenOrientation = root.shellPrimaryOrientation;
970+ chosenOrientation = root.orientations.primary;
971 }
972
973- return Screen.angleBetween(root.nativeOrientation, chosenOrientation);
974+ return Screen.angleBetween(root.orientations.native_, chosenOrientation);
975 }
976
977 rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
978
979=== modified file 'qml/Stages/TabletStage.qml'
980--- qml/Stages/TabletStage.qml 2015-08-03 13:47:44 +0000
981+++ qml/Stages/TabletStage.qml 2015-10-27 20:51:44 +0000
982@@ -21,29 +21,12 @@
983 import Utils 0.1
984 import "../Components"
985
986-Rectangle {
987+AbstractStage {
988 id: root
989 objectName: "stages"
990 anchors.fill: parent
991- color: "#111111"
992-
993- // Controls to be set from outside
994- property int dragAreaWidth
995- property real maximizedAppTopMargin
996- property bool interactive
997- property alias beingResized: spreadView.beingResized
998-
999- property bool spreadEnabled: true // If false, animations and right edge will be disabled
1000-
1001- property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
1002- property bool keepDashRunning: true
1003- property bool suspended: false
1004- property int shellOrientationAngle: 0
1005- property int shellOrientation
1006- property int shellPrimaryOrientation
1007- property int nativeOrientation
1008- property real nativeWidth
1009- property real nativeHeight
1010+
1011+ // Functions to be called from outside
1012 function updateFocusedAppOrientation() {
1013 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
1014 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
1015@@ -64,7 +47,7 @@
1016
1017 var supportedOrientations = spreadDelegate.application.supportedOrientations;
1018 if (supportedOrientations === Qt.PrimaryOrientation) {
1019- supportedOrientations = spreadDelegate.shellPrimaryOrientation;
1020+ supportedOrientations = spreadDelegate.orientations.primary;
1021 }
1022
1023 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
1024@@ -86,10 +69,7 @@
1025 }
1026 }
1027
1028- // To be read from outside
1029- property var mainApp: null
1030- property int mainAppWindowOrientationAngle: 0
1031- readonly property bool orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
1032+ orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
1033
1034 onWidthChanged: {
1035 spreadView.selectedIndex = -1;
1036@@ -130,7 +110,7 @@
1037 property string oldFocusedAppId: ""
1038 property bool mainAppOrientationChangesEnabled: false
1039
1040- property real landscapeHeight: root.nativeOrientation == Qt.LandscapeOrientation ?
1041+ property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
1042 root.nativeHeight : root.nativeWidth
1043
1044 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
1045@@ -302,7 +282,7 @@
1046 }
1047
1048 property bool animateX: true
1049- property bool beingResized: false
1050+ property bool beingResized: root.beingResized
1051 onBeingResizedChanged: {
1052 if (beingResized) {
1053 // Brace yourselves for impact!
1054@@ -629,15 +609,18 @@
1055
1056 readonly property bool isDash: model.appId == "unity8-dash"
1057
1058+ readonly property bool canSuspend: model.isTouchApp
1059+ && !isExemptFromLifecycle(model.appId)
1060+
1061 Binding {
1062 target: spreadTile.application
1063 property: "requestedState"
1064- value: (spreadTile.isDash && root.keepDashRunning)
1065- ||
1066- (!root.suspended && (model.appId == priv.mainStageAppId
1067- || model.appId == priv.sideStageAppId))
1068- ? ApplicationInfoInterface.RequestedRunning
1069- : ApplicationInfoInterface.RequestedSuspended
1070+ value: !canSuspend
1071+ || (isDash && root.keepDashRunning)
1072+ || (!root.suspended && (model.appId == priv.mainStageAppId
1073+ || model.appId == priv.sideStageAppId))
1074+ ? ApplicationInfoInterface.RequestedRunning
1075+ : ApplicationInfoInterface.RequestedSuspended
1076 }
1077
1078 // FIXME: A regular binding doesn't update any more after closing an app.
1079@@ -691,9 +674,14 @@
1080
1081 shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
1082 shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
1083- shellPrimaryOrientation: wantsMainStage ? root.shellPrimaryOrientation : Qt.PortraitOrientation
1084- nativeOrientation: wantsMainStage ? root.nativeOrientation : Qt.PortraitOrientation
1085-
1086+ orientations: Orientations {
1087+ primary: spreadTile.wantsMainStage ? root.orientations.primary : Qt.PortraitOrientation
1088+ native_: spreadTile.wantsMainStage ? root.orientations.native_ : Qt.PortraitOrientation
1089+ portrait: root.orientations.portrait
1090+ invertedPortrait: root.orientations.invertedPortrait
1091+ landscape: root.orientations.landscape
1092+ invertedLandscape: root.orientations.invertedLandscape
1093+ }
1094
1095 onClicked: {
1096 if (spreadView.phase == 2) {
1097
1098=== modified file 'qml/Stages/WindowResizeArea.qml'
1099--- qml/Stages/WindowResizeArea.qml 2015-09-29 12:48:46 +0000
1100+++ qml/Stages/WindowResizeArea.qml 2015-10-27 20:51:44 +0000
1101@@ -36,18 +36,49 @@
1102 property int minWidth: 0
1103 property int minHeight: 0
1104
1105+ QtObject {
1106+ id: priv
1107+
1108+ property int normalX: 0
1109+ property int normalY: 0
1110+ property int normalWidth: 0
1111+ property int normalHeight: 0
1112+
1113+ function updateNormalGeometry() {
1114+ if (root.target.state == "normal") {
1115+ normalX = root.target.x
1116+ normalY = root.target.y
1117+ normalWidth = root.target.width
1118+ normalHeight = root.target.height
1119+ }
1120+ }
1121+ }
1122+
1123+ Connections {
1124+ target: root.target
1125+ onXChanged: priv.updateNormalGeometry();
1126+ onYChanged: priv.updateNormalGeometry();
1127+ onWidthChanged: priv.updateNormalGeometry();
1128+ onHeightChanged: priv.updateNormalGeometry();
1129+ }
1130+
1131 Component.onCompleted: {
1132- var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
1133- if (windowState !== undefined) {
1134- target.x = windowState.x
1135- target.y = windowState.y
1136- target.width = windowState.width
1137- target.height = windowState.height
1138+ var windowGeometry = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
1139+ if (windowGeometry !== undefined) {
1140+ target.x = windowGeometry.x
1141+ target.y = windowGeometry.y
1142+ target.width = windowGeometry.width
1143+ target.height = windowGeometry.height
1144+ }
1145+ var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
1146+ if (windowState === WindowStateStorage.WindowStateMaximized) {
1147+ target.maximize(false)
1148 }
1149 }
1150
1151 Component.onDestruction: {
1152- windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
1153+ windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
1154+ windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
1155 }
1156
1157 QtObject {
1158
1159=== added file 'qml/graphics/applicationIcons/libreoffice@18.png'
1160Binary files qml/graphics/applicationIcons/libreoffice@18.png 1970-01-01 00:00:00 +0000 and qml/graphics/applicationIcons/libreoffice@18.png 2015-10-27 20:51:44 +0000 differ
1161=== modified file 'tests/mocks/GSettings.1.0/fake_gsettings.cpp'
1162--- tests/mocks/GSettings.1.0/fake_gsettings.cpp 2015-09-02 13:06:56 +0000
1163+++ tests/mocks/GSettings.1.0/fake_gsettings.cpp 2015-10-27 20:51:44 +0000
1164@@ -75,6 +75,19 @@
1165 }
1166 }
1167
1168+QStringList GSettingsControllerQml::lifecycleExemptAppids() const
1169+{
1170+ return m_lifecycleExemptAppids;
1171+}
1172+
1173+void GSettingsControllerQml::setLifecycleExemptAppids(const QStringList &appIds)
1174+{
1175+ if (m_lifecycleExemptAppids != appIds) {
1176+ m_lifecycleExemptAppids = appIds;
1177+ Q_EMIT lifecycleExemptAppidsChanged(m_lifecycleExemptAppids);
1178+ }
1179+}
1180+
1181 GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
1182 }
1183
1184@@ -114,6 +127,8 @@
1185 this, &GSettingsQml::usageModeChanged);
1186 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lockedOutTimeChanged,
1187 this, &GSettingsQml::lockedOutTimeChanged);
1188+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
1189+ this, &GSettingsQml::lifecycleExemptAppidsChanged);
1190 }
1191
1192 GSettingsSchemaQml * GSettingsQml::schema() const {
1193@@ -167,3 +182,19 @@
1194 GSettingsControllerQml::instance()->setLockedOutTime(timestamp);
1195 }
1196 }
1197+
1198+QStringList GSettingsQml::lifecycleExemptAppids() const
1199+{
1200+ if (m_schema->id() == "com.canonical.qtmir") {
1201+ return GSettingsControllerQml::instance()->lifecycleExemptAppids();
1202+ } else {
1203+ return QStringList();
1204+ }
1205+}
1206+
1207+void GSettingsQml::setLifecycleExemptAppids(const QStringList &appIds)
1208+{
1209+ if (m_schema->id() == "com.canonical.qtmir") {
1210+ GSettingsControllerQml::instance()->setLifecycleExemptAppids(appIds);
1211+ }
1212+}
1213
1214=== modified file 'tests/mocks/GSettings.1.0/fake_gsettings.h'
1215--- tests/mocks/GSettings.1.0/fake_gsettings.h 2015-08-19 23:41:18 +0000
1216+++ tests/mocks/GSettings.1.0/fake_gsettings.h 2015-10-27 20:51:44 +0000
1217@@ -19,6 +19,7 @@
1218
1219 #include <QList>
1220 #include <QObject>
1221+#include <QStringList>
1222
1223 class GSettingsSchemaQml: public QObject
1224 {
1225@@ -48,6 +49,7 @@
1226 Q_PROPERTY(QString pictureUri READ pictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
1227 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
1228 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
1229+ Q_PROPERTY(QStringList lifecycleExemptAppids READ lifecycleExemptAppids WRITE setLifecycleExemptAppids NOTIFY lifecycleExemptAppidsChanged)
1230
1231 public:
1232 GSettingsQml(QObject *parent = nullptr);
1233@@ -56,16 +58,19 @@
1234 QString pictureUri() const;
1235 QString usageMode() const;
1236 qint64 lockedOutTime() const;
1237+ QStringList lifecycleExemptAppids() const;
1238
1239 void setPictureUri(const QString &str);
1240 void setUsageMode(const QString &usageMode);
1241 void setLockedOutTime(qint64 timestamp);
1242+ void setLifecycleExemptAppids(const QStringList &appIds);
1243
1244 Q_SIGNALS:
1245 void schemaChanged();
1246 void pictureUriChanged(const QString&);
1247 void usageModeChanged(const QString&);
1248 void lockedOutTimeChanged(qint64);
1249+ void lifecycleExemptAppidsChanged(const QStringList &);
1250
1251 private:
1252 GSettingsSchemaQml* m_schema;
1253@@ -90,10 +95,14 @@
1254 qint64 lockedOutTime() const;
1255 Q_INVOKABLE void setLockedOutTime(qint64 timestamp);
1256
1257+ QStringList lifecycleExemptAppids() const;
1258+ Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
1259+
1260 Q_SIGNALS:
1261 void pictureUriChanged(const QString&);
1262 void usageModeChanged(const QString&);
1263 void lockedOutTimeChanged(qint64 timestamp);
1264+ void lifecycleExemptAppidsChanged(const QStringList&);
1265
1266 private:
1267 GSettingsControllerQml();
1268@@ -101,6 +110,7 @@
1269 QString m_pictureUri;
1270 QString m_usageMode;
1271 qint64 m_lockedOutTime;
1272+ QStringList m_lifecycleExemptAppids;
1273
1274 static GSettingsControllerQml* s_controllerInstance;
1275 QList<GSettingsQml *> m_registeredGSettings;
1276
1277=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.cpp'
1278--- tests/mocks/Unity/Application/ApplicationInfo.cpp 2015-08-31 10:25:07 +0000
1279+++ tests/mocks/Unity/Application/ApplicationInfo.cpp 2015-10-27 20:51:44 +0000
1280@@ -41,6 +41,7 @@
1281 Qt::InvertedLandscapeOrientation)
1282 , m_rotatesWindowContents(false)
1283 , m_requestedState(RequestedRunning)
1284+ , m_isTouchApp(true)
1285 , m_manualSurfaceCreation(false)
1286 {
1287 }
1288@@ -58,6 +59,7 @@
1289 Qt::InvertedLandscapeOrientation)
1290 , m_rotatesWindowContents(false)
1291 , m_requestedState(RequestedRunning)
1292+ , m_isTouchApp(true)
1293 , m_manualSurfaceCreation(false)
1294 {
1295 }
1296@@ -245,6 +247,16 @@
1297 }
1298 }
1299
1300+bool ApplicationInfo::isTouchApp() const
1301+{
1302+ return m_isTouchApp;
1303+}
1304+
1305+void ApplicationInfo::setIsTouchApp(bool isTouchApp)
1306+{
1307+ m_isTouchApp = isTouchApp;
1308+}
1309+
1310 void ApplicationInfo::onSessionSurfaceChanged(MirSurface* surface)
1311 {
1312 if (surface != nullptr && m_state == Starting) {
1313
1314=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.h'
1315--- tests/mocks/Unity/Application/ApplicationInfo.h 2015-08-03 15:00:47 +0000
1316+++ tests/mocks/Unity/Application/ApplicationInfo.h 2015-10-27 20:51:44 +0000
1317@@ -89,6 +89,9 @@
1318 bool manualSurfaceCreation() const { return m_manualSurfaceCreation; }
1319 void setManualSurfaceCreation(bool value);
1320
1321+ bool isTouchApp() const override;
1322+ void setIsTouchApp(bool isTouchApp); // only in mock
1323+
1324 public:
1325 void setSession(Session* session);
1326 Session* session() const { return m_session; }
1327@@ -121,6 +124,7 @@
1328 Qt::ScreenOrientations m_supportedOrientations;
1329 bool m_rotatesWindowContents;
1330 RequestedState m_requestedState;
1331+ bool m_isTouchApp;
1332
1333 bool m_manualSurfaceCreation;
1334 };
1335
1336=== modified file 'tests/mocks/Unity/Application/ApplicationManager.cpp'
1337--- tests/mocks/Unity/Application/ApplicationManager.cpp 2015-08-03 13:47:44 +0000
1338+++ tests/mocks/Unity/Application/ApplicationManager.cpp 2015-10-27 20:51:44 +0000
1339@@ -105,6 +105,8 @@
1340 return app->state();
1341 case RoleFocused:
1342 return app->focused();
1343+ case RoleIsTouchApp:
1344+ return app->isTouchApp();
1345 case RoleSession:
1346 return QVariant::fromValue(app->session());
1347 case RoleFullscreen:
1348@@ -458,6 +460,13 @@
1349 application->setName("YouTube");
1350 application->setIconId("youtube");
1351 m_availableApplications.append(application);
1352+
1353+ application = new ApplicationInfo(this);
1354+ application->setAppId("libreoffice");
1355+ application->setName("LibreOffice");
1356+ application->setIconId("libreoffice");
1357+ application->setIsTouchApp(false);
1358+ m_availableApplications.append(application);
1359 }
1360
1361
1362
1363=== modified file 'tests/mocks/Unity/Application/ApplicationManager.h'
1364--- tests/mocks/Unity/Application/ApplicationManager.h 2015-06-19 18:19:33 +0000
1365+++ tests/mocks/Unity/Application/ApplicationManager.h 2015-10-27 20:51:44 +0000
1366@@ -43,7 +43,7 @@
1367 static ApplicationManager *singleton();
1368
1369 enum MoreRoles {
1370- RoleSession = RoleFocused+1,
1371+ RoleSession = RoleIsTouchApp+1,
1372 RoleFullscreen,
1373 };
1374 enum Flag {
1375
1376=== modified file 'tests/mocks/Utils/windowstatestorage.cpp'
1377--- tests/mocks/Utils/windowstatestorage.cpp 2015-08-24 15:39:53 +0000
1378+++ tests/mocks/Utils/windowstatestorage.cpp 2015-10-27 20:51:44 +0000
1379@@ -44,3 +44,20 @@
1380 if (!m_geometry.contains(windowId)) return defaultValue;
1381 return m_geometry.value(windowId).toRect();
1382 }
1383+
1384+void WindowStateStorage::clear()
1385+{
1386+ m_state.clear();
1387+ m_geometry.clear();
1388+}
1389+
1390+void WindowStateStorage::saveState(const QString &windowId, WindowState state)
1391+{
1392+ m_state[windowId] = state;
1393+}
1394+
1395+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
1396+{
1397+ if (!m_state.contains(windowId)) return defaultValue;
1398+ return m_state.value(windowId);
1399+}
1400
1401=== modified file 'tests/mocks/Utils/windowstatestorage.h'
1402--- tests/mocks/Utils/windowstatestorage.h 2015-08-24 15:39:53 +0000
1403+++ tests/mocks/Utils/windowstatestorage.h 2015-10-27 20:51:44 +0000
1404@@ -22,12 +22,23 @@
1405 {
1406 Q_OBJECT
1407 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
1408+ Q_ENUMS(WindowState)
1409 public:
1410+ enum WindowState {
1411+ WindowStateNormal,
1412+ WindowStateMaximized
1413+ };
1414 WindowStateStorage(QObject *parent = 0);
1415
1416+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
1417+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
1418+
1419 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
1420 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
1421
1422+ // Only in the mock, to easily restore a fresh state
1423+ Q_INVOKABLE void clear();
1424+
1425 Q_SIGNALS:
1426 void geometryChanged(const QVariantMap& geometry);
1427
1428@@ -35,5 +46,6 @@
1429 void setGeometry(const QVariantMap& geometry);
1430 QVariantMap geometry() const;
1431
1432+ QHash<QString, WindowState> m_state;
1433 QVariantMap m_geometry;
1434 };
1435
1436=== modified file 'tests/plugins/Unity/Launcher/launchermodeltest.cpp'
1437--- tests/plugins/Unity/Launcher/launchermodeltest.cpp 2015-08-19 14:24:07 +0000
1438+++ tests/plugins/Unity/Launcher/launchermodeltest.cpp 2015-10-27 20:51:44 +0000
1439@@ -57,6 +57,7 @@
1440 QColor splashColorFooter() const override { return QColor(0,0,0,0); }
1441 Qt::ScreenOrientations supportedOrientations() const override { return Qt::PortraitOrientation; }
1442 bool rotatesWindowContents() const override { return false; }
1443+ bool isTouchApp() const override { return true; }
1444
1445 // Methods used for mocking (not in the interface)
1446 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
1447
1448=== modified file 'tests/qmltests/Stages/tst_PhoneStage.qml'
1449--- tests/qmltests/Stages/tst_PhoneStage.qml 2015-08-12 15:08:09 +0000
1450+++ tests/qmltests/Stages/tst_PhoneStage.qml 2015-10-27 20:51:44 +0000
1451@@ -34,8 +34,6 @@
1452 maximizedAppTopMargin: units.gu(3) + units.dp(2)
1453 interactive: true
1454 shellOrientation: Qt.PortraitOrientation
1455- shellPrimaryOrientation: Qt.PortraitOrientation
1456- nativeOrientation: Qt.PortraitOrientation
1457 }
1458
1459 Binding {
1460
1461=== modified file 'tests/qmltests/Stages/tst_SpreadDelegate.qml'
1462--- tests/qmltests/Stages/tst_SpreadDelegate.qml 2015-03-06 04:44:11 +0000
1463+++ tests/qmltests/Stages/tst_SpreadDelegate.qml 2015-10-27 20:51:44 +0000
1464@@ -18,6 +18,7 @@
1465 import QtTest 1.0
1466 import Unity.Test 0.1 as UT
1467 import ".."
1468+import "../../../qml/Components"
1469 import "../../../qml/Stages"
1470 import Ubuntu.Components 0.1
1471 import Ubuntu.Components.ListItems 1.0 as ListItem
1472@@ -73,8 +74,11 @@
1473 return Qt.LandscapeOrientation;
1474 }
1475 }
1476- shellPrimaryOrientation: Qt.PortraitOrientation
1477- nativeOrientation: Qt.PortraitOrientation
1478+
1479+ orientations: Orientations {
1480+ // the default values will do
1481+ }
1482+
1483 maximizedAppTopMargin: units.gu(3)
1484 Component.onDestruction: {
1485 spreadDelegateLoader.itemDestroyed = true;
1486
1487=== modified file 'tests/qmltests/Stages/tst_TabletStage.qml'
1488--- tests/qmltests/Stages/tst_TabletStage.qml 2015-09-02 14:26:43 +0000
1489+++ tests/qmltests/Stages/tst_TabletStage.qml 2015-10-27 20:51:44 +0000
1490@@ -22,6 +22,7 @@
1491 import Unity.Test 0.1
1492
1493 import "../../../qml/Stages"
1494+import "../../../qml/Components"
1495
1496 Rectangle {
1497 id: root
1498@@ -50,10 +51,9 @@
1499 maximizedAppTopMargin: units.gu(3) + units.dp(2)
1500 interactive: true
1501 shellOrientation: Qt.LandscapeOrientation
1502- shellPrimaryOrientation: Qt.LandscapeOrientation
1503- nativeOrientation: Qt.LandscapeOrientation
1504 nativeWidth: width
1505 nativeHeight: height
1506+ orientations: Orientations{} // Defaults are fine for testing
1507 focus: true
1508 }
1509 }
1510
1511=== modified file 'tests/qmltests/Stages/tst_WindowResizeArea.qml'
1512--- tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-10-08 14:27:12 +0000
1513+++ tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-10-27 20:51:44 +0000
1514@@ -44,8 +44,11 @@
1515 width: units.gu(20)
1516 property int windowHeight: height
1517 property int windowWidth: width
1518- onWindowHeightChanged: height = windowHeight
1519- onWindowWidthChanged: width = windowWidth
1520+ state: "normal"
1521+
1522+ function maximize() {
1523+ state = "maximized"
1524+ }
1525
1526 WindowResizeArea {
1527 id: windowResizeArea
1528@@ -195,5 +198,35 @@
1529 tryCompare(fakeWindow, "width", initialWindowWidth);
1530 tryCompare(fakeWindow, "height", initialWindowHeight);
1531 }
1532+
1533+ function test_saveRestoreMaximized() {
1534+ var initialWindowX = fakeWindow.x;
1535+ var initialWindowY = fakeWindow.y;
1536+
1537+ var moveDelta = units.gu(5);
1538+
1539+ fakeWindow.x = initialWindowX + moveDelta
1540+ fakeWindow.y = initialWindowY + moveDelta
1541+
1542+ // Now change the state to maximized. The window should not keep updating the stored values
1543+ fakeWindow.state = "maximized"
1544+ fakeWindow.x = 31415 // 0 is too risky to pass the test even when broken
1545+ fakeWindow.y = 31415
1546+
1547+ // This will destroy the window and recreate it
1548+ windowLoader.active = false;
1549+ waitForRendering(root);
1550+ windowLoader.active = true;
1551+
1552+ // Make sure it's again where we left it in normal state before destroying
1553+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
1554+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
1555+
1556+ // Make sure maximize() has been called after restoring
1557+ tryCompare(fakeWindow, "state", "maximized")
1558+
1559+ // clean up
1560+ fakeWindow.state = "normal"
1561+ }
1562 }
1563 }
1564
1565=== modified file 'tests/qmltests/tst_OrientedShell.qml'
1566--- tests/qmltests/tst_OrientedShell.qml 2015-10-05 22:04:32 +0000
1567+++ tests/qmltests/tst_OrientedShell.qml 2015-10-27 20:51:44 +0000
1568@@ -840,7 +840,7 @@
1569 // shell should remain in its primery orientation as the app in the main stage
1570 // is the one that dictates its orientation. In this case it's unity8-dash
1571 // which supports only primary orientation
1572- compare(shell.orientation, orientedShell.primaryOrientation);
1573+ compare(shell.orientation, orientedShell.orientations.primary);
1574 }
1575
1576 function test_sideStageAppsRemainPortraitInSpread_data() {
1577
1578=== modified file 'tests/qmltests/tst_Shell.qml'
1579--- tests/qmltests/tst_Shell.qml 2015-10-20 08:10:03 +0000
1580+++ tests/qmltests/tst_Shell.qml 2015-10-27 20:51:44 +0000
1581@@ -29,8 +29,11 @@
1582 import Unity.Test 0.1
1583 import Powerd 0.1
1584 import Wizard 0.1 as Wizard
1585+import Utils 0.1
1586
1587 import "../../qml"
1588+import "../../qml/Components"
1589+import "../../qml/Components/PanelState"
1590 import "Stages"
1591
1592 Rectangle {
1593@@ -97,8 +100,7 @@
1594 id: __shell
1595 usageScenario: usageScenarioSelector.model[usageScenarioSelector.selectedIndex]
1596 orientation: Qt.PortraitOrientation
1597- primaryOrientation: Qt.PortraitOrientation
1598- nativeOrientation: Qt.PortraitOrientation
1599+ orientations: Orientations{} // Defaults are fine for testing
1600 Component.onDestruction: {
1601 shellLoader.itemDestroyed = true;
1602 }
1603@@ -302,6 +304,7 @@
1604 mouseEmulation.checked = true;
1605 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
1606 tearDown();
1607+ WindowStateStorage.clear();
1608 }
1609
1610 function loadShell(formFactor) {
1611@@ -377,6 +380,8 @@
1612 LightDM.Greeter.authenticate(""); // reset greeter
1613
1614 sessionSpy.clear();
1615+
1616+ GSettingsController.setLifecycleExemptAppids([]);
1617 }
1618
1619 function killApps() {
1620@@ -1707,5 +1712,114 @@
1621
1622 keyRelease(Qt.Key_Control);
1623 }
1624+
1625+ // regression test for http://pad.lv/1443319
1626+ function test_closeMaximizedAndRestart() {
1627+ loadDesktopShellWithApps();
1628+
1629+ var appRepeater = findChild(shell, "appRepeater")
1630+ var appId = ApplicationManager.get(0).appId;
1631+ var appDelegate = appRepeater.itemAt(0);
1632+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1633+
1634+ wait(5000)
1635+ tryCompare(appDelegate, "state", "normal");
1636+ tryCompare(PanelState, "buttonsVisible", false)
1637+
1638+ wait(5000)
1639+ mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1640+ tryCompare(appDelegate, "state", "maximized");
1641+ tryCompare(PanelState, "buttonsVisible", true)
1642+
1643+ ApplicationManager.stopApplication(appId);
1644+ tryCompare(PanelState, "buttonsVisible", false)
1645+
1646+ ApplicationManager.startApplication(appId);
1647+ tryCompare(PanelState, "buttonsVisible", true)
1648+ }
1649+
1650+ // bug http://pad.lv/1431566
1651+ function test_switchToStagedHidesPanelButtons() {
1652+ loadDesktopShellWithApps();
1653+ var appRepeater = findChild(shell, "appRepeater")
1654+ var appId = ApplicationManager.get(0).appId;
1655+ var appDelegate = appRepeater.itemAt(0);
1656+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1657+
1658+ tryCompare(appDelegate, "state", "normal");
1659+ tryCompare(panelButtons, "visible", false);
1660+
1661+ appDelegate.maximize(false);
1662+ tryCompare(panelButtons, "visible", true);
1663+
1664+ shell.usageScenario = "phone";
1665+ waitForRendering(shell);
1666+ tryCompare(panelButtons, "visible", false);
1667+
1668+ shell.usageScenario = "desktop";
1669+ waitForRendering(shell);
1670+ tryCompare(panelButtons, "visible", true);
1671+ }
1672+
1673+ function test_lockingGreeterHidesPanelButtons() {
1674+ loadDesktopShellWithApps();
1675+ var appRepeater = findChild(shell, "appRepeater")
1676+ var appId = ApplicationManager.get(0).appId;
1677+ var appDelegate = appRepeater.itemAt(0);
1678+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1679+
1680+ tryCompare(appDelegate, "state", "normal");
1681+ tryCompare(panelButtons, "visible", false);
1682+
1683+ appDelegate.maximize(false);
1684+ tryCompare(panelButtons, "visible", true);
1685+
1686+ LightDM.Greeter.showGreeter();
1687+ waitForRendering(shell);
1688+ tryCompare(panelButtons, "visible", false);
1689+
1690+ LightDM.Greeter.hideGreeter();
1691+ waitForRendering(shell);
1692+ tryCompare(panelButtons, "visible", true);
1693+ }
1694+
1695+ function test_lifecyclePolicy_data() {
1696+ return [
1697+ {tag: "phone", formFactor: "phone", usageScenario: "phone", suspendsApps: true},
1698+ {tag: "tablet", formFactor: "tablet", usageScenario: "tablet", suspendsApps: true},
1699+ {tag: "desktop", formFactor: "tablet", usageScenario: "desktop", suspendsApps: false}
1700+ ]
1701+ }
1702+
1703+ function test_lifecyclePolicy(data) {
1704+ loadShell(data.formFactor);
1705+ shell.usageScenario = data.usageScenario;
1706+
1707+ GSettingsController.setLifecycleExemptAppids(["webbrowser-app"]);
1708+
1709+ var app1 = ApplicationManager.startApplication("camera-app");
1710+ waitUntilAppWindowIsFullyLoaded(app1);
1711+ var app2 = ApplicationManager.startApplication("webbrowser-app");
1712+ waitUntilAppWindowIsFullyLoaded(app2);
1713+ var app3 = ApplicationManager.startApplication("libreoffice");
1714+ waitUntilAppWindowIsFullyLoaded(app3);
1715+ var app4 = ApplicationManager.startApplication("dialer-app");
1716+ waitUntilAppWindowIsFullyLoaded(app4);
1717+
1718+ // Normal background app
1719+ compare(app1.requestedState, data.suspendsApps ?
1720+ ApplicationInfoInterface.RequestedSuspended :
1721+ ApplicationInfoInterface.RequestedRunning);
1722+
1723+ // Excluded from lifecycle by gsettings
1724+ compare(app2.requestedState, ApplicationInfoInterface.RequestedRunning);
1725+
1726+ // Excluded from lifecycle because app is non-touch
1727+ compare(app3.isTouchApp, false); // sanity check our mock, which sets this for us
1728+ compare(app3.requestedState, ApplicationInfoInterface.RequestedRunning);
1729+
1730+ // Focused app
1731+ compare(app4.requestedState, ApplicationInfoInterface.RequestedRunning);
1732+ }
1733 }
1734 }

Subscribers

People subscribed via source and target branches