@@ -27,19 +27,24 @@ AbstractStage {
2727 objectName: "stages"
2828 anchors.fill: parent
2929
30+ // <tutorial-hacks> The Tutorial looks into our implementation details
3031 property alias sideStageVisible: spreadView.sideStageVisible
3132 property alias sideStageWidth: spreadView.sideStageWidth
33+ // The stage the currently focused surface is in
34+ property int stageFocusedSurface: priv.focusedAppDelegate ? priv.focusedAppDelegate.stage : ApplicationInfoInterface.MainStage
35+ // </tutorial-hacks>
3236
3337 // Functions to be called from outside
3438 function updateFocusedAppOrientation() {
35− var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
36− if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
37− spreadRepeater.itemAt(mainStageAppIndex).matchShellOrientation();
39+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
40+
41+ if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
42+ spreadRepeater.itemAt(mainStageIndex).matchShellOrientation();
3843 }
3944
4045 for (var i = 0; i < spreadRepeater.count; ++i) {
4146
42− if (i === mainStageAppIndex) {
47+ if (i === mainStageIndex) {
4348 continue;
4449 }
4550
@@ -60,16 +65,14 @@ AbstractStage {
6065 }
6166 }
6267 function updateFocusedAppOrientationAnimated() {
63− var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
64− if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
65− spreadRepeater.itemAt(mainStageAppIndex).animateToShellOrientation();
68+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
69+ if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
70+ spreadRepeater.itemAt(mainStageIndex).animateToShellOrientation();
6671 }
6772
68− if (priv.sideStageAppId) {
69− var sideStageAppIndex = priv.indexOf(priv.sideStageAppId);
70− if (sideStageAppIndex >= 0 && sideStageAppIndex < spreadRepeater.count) {
71− spreadRepeater.itemAt(sideStageAppIndex).matchShellOrientation();
72− }
73+ var sideStageIndex = root.topLevelSurfaceList.indexForId(priv.sideStageItemId);
74+ if (sideStageIndex >= 0 && sideStageIndex < spreadRepeater.count) {
75+ spreadRepeater.itemAt(sideStageIndex).matchShellOrientation();
7376 }
7477 }
7578
@@ -79,15 +82,22 @@ AbstractStage {
7982 }
8083 }
8184
82− mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
83−
8485 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
8586
87+ mainApp: {
88+ if (priv.mainStageItemId > 0) {
89+ var index = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
90+ return root.topLevelSurfaceList.applicationAt(index);
91+ } else {
92+ return null;
93+ }
94+ }
95+
8696 supportedOrientations: {
8797 if (mainApp) {
8898 var orientations = mainApp.supportedOrientations;
8999 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
90− if (priv.sideStageAppId && !spreadView.surfaceDragging) {
100+ if (priv.sideStageItemId && !spreadView.surfaceDragging) {
91101 // If we have a sidestage app, support Portrait orientation
92102 // so that it will switch the sidestage app to mainstage on rotate
93103 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
@@ -118,7 +128,7 @@ AbstractStage {
118128 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
119129 // left edge drag released. Minimum distance is given by design.
120130 if (priv.oldInverseProgress > units.gu(22)) {
121− ApplicationManager.requestFocusApplication("unity8-dash");
131+ root.applicationManager.requestFocusApplication("unity8-dash");
122132 }
123133 }
124134 priv.oldInverseProgress = inverseProgress;
@@ -156,17 +166,51 @@ AbstractStage {
156166 }
157167 }
158168
169+ Connections {
170+ target: root.topLevelSurfaceList
171+ onListChanged: priv.updateMainAndSideStageIndexes()
172+ }
173+
159174 QtObject {
160175 id: priv
161176 objectName: "stagesPriv"
162177
163− property string focusedAppId: ApplicationManager.focusedApplicationId
164− readonly property var focusedAppDelegate: {
165− var index = indexOf(focusedAppId);
166− return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
178+ function updateMainAndSideStageIndexes() {
179+ var choseMainStage = false;
180+ var choseSideStage = false;
181+
182+ if (!root.topLevelSurfaceList)
183+ return;
184+
185+ for (var i = 0; i < spreadRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
186+ var spreadDelegate = spreadRepeater.itemAt(i);
187+ if (sideStage.shown && spreadDelegate.stage == ApplicationInfoInterface.SideStage
188+ && !choseSideStage) {
189+ priv.sideStageDelegate = spreadDelegate
190+ priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
191+ priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
192+ choseSideStage = true;
193+ } else if (!choseMainStage && spreadDelegate.stage == ApplicationInfoInterface.MainStage) {
194+ priv.mainStageDelegate = spreadDelegate;
195+ priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
196+ priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
197+ choseMainStage = true;
198+ }
199+ }
200+ if (!choseMainStage) {
201+ priv.mainStageDelegate = null;
202+ priv.mainStageItemId = 0;
203+ priv.mainStageAppId = "";
204+ }
205+ if (!choseSideStage) {
206+ priv.sideStageDelegate = null;
207+ priv.sideStageItemId = 0;
208+ priv.sideStageAppId = "";
209+ }
167210 }
168211
169− property string oldFocusedAppId: ""
212+ property var focusedAppDelegate: null
213+
170214 property bool mainAppOrientationChangesEnabled: false
171215
172216 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
@@ -175,36 +219,21 @@ AbstractStage {
175219 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
176220 || root.shellOrientation === Qt.InvertedLandscapeOrientation
177221
178− property string mainStageAppId
179− property string sideStageAppId
222+ property var mainStageDelegate: null
223+ property var sideStageDelegate: null
224+
225+ property int mainStageItemId: 0
226+ property int sideStageItemId: 0
180227
181− // For convenience, keep properties of the first two apps in the model
182− property string appId0
183− property string appId1
228+ property string mainStageAppId: ""
229+ property string sideStageAppId: ""
184230
185231 property int oldInverseProgress: 0
186232
187233 property int highlightIndex: 0
188234
189− onFocusedAppIdChanged: updateStageApps()
190−
191− onFocusedAppDelegateChanged: {
192− if (focusedAppDelegate) {
193− focusedAppDelegate.focus = true;
194− }
195− }
196−
197235 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
198236 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
199− function indexOf(appId) {
200− for (var i = 0; i < ApplicationManager.count; i++) {
201− if (ApplicationManager.get(i).appId == appId) {
202− return i;
203− }
204− }
205− return -1;
206− }
207−
208237 function evaluateOneWayFlick(gesturePoints) {
209238 // Need to have at least 3 points to recognize it as a flick
210239 if (gesturePoints.length < 3) {
@@ -233,88 +262,45 @@ AbstractStage {
233262 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
234263 }
235264
236− function getTopApp(stage) {
237− for (var i = 0; i < ApplicationManager.count; i++) {
238− var app = ApplicationManager.get(i)
239− if (app.stage === stage) {
240− return app;
241− }
242− }
243− return null;
244− }
245−
246− function setAppStage(appId, stage, save) {
247− var app = ApplicationManager.findApplication(appId);
248− if (app) {
249− app.stage = stage;
250− if (save) {
251− WindowStateStorage.saveStage(appId, stage);
252− }
253− }
254− }
255−
256− function updateStageApps() {
257− var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
258− priv.mainStageAppId = app ? app.appId : ""
259− root.mainApp = app;
260−
261− if (sideStage.shown) {
262− app = priv.getTopApp(ApplicationInfoInterface.SideStage);
263− priv.sideStageAppId = app ? app.appId : ""
264− } else {
265− priv.sideStageAppId = "";
266− }
267−
268− appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
269− appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
270− }
271−
272265 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
273266 root.shellOrientation == Qt.InvertedLandscapeOrientation
274− Component.onCompleted: updateStageApps();
275267 }
276268
277− Connections {
278− target: ApplicationManager
279− onFocusRequested: {
280− if (spreadView.interactive) {
281− spreadView.snapTo(priv.indexOf(appId));
282− } else {
283− ApplicationManager.focusApplication(appId);
269+ Instantiator {
270+ model: root.applicationManager
271+ delegate: QtObject {
272+ property var stateBinding: Binding {
273+ readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
274+ target: model.application
275+ property: "requestedState"
276+
277+ // NB: the first application clause is just to ensure we never get warnings for trying to access
278+ // members of a null variable.
279+ value: model.application &&
280+ (
281+ (isDash && root.keepDashRunning)
282+ || (!root.suspended && (model.application.appId === priv.mainStageAppId
283+ || model.application.appId === priv.sideStageAppId))
284+ )
285+ ? ApplicationInfoInterface.RequestedRunning
286+ : ApplicationInfoInterface.RequestedSuspended
284287 }
285− }
286288
287− onApplicationAdded: {
288− if (spreadView.phase == 2) {
289− spreadView.snapTo(ApplicationManager.count - 1);
290− } else {
291− spreadView.phase = 0;
292− spreadView.contentX = -spreadView.shift;
293− ApplicationManager.focusApplication(appId);
289+ property var lifecycleBinding: Binding {
290+ target: model.application
291+ property: "exemptFromLifecycle"
292+ value: model.application
293+ ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
294+ : false
294295 }
295296 }
297+ }
296298
297− onApplicationRemoved: {
298− if (priv.mainStageAppId == appId) {
299− ApplicationManager.focusApplication("unity8-dash")
300− }
301− if (priv.sideStageAppId == appId) {
302− var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
303− priv.sideStageAppId = app === null ? "" : app.appId;
304− }
305−
306− if (ApplicationManager.count == 0) {
307− spreadView.phase = 0;
308− spreadView.contentX = -spreadView.shift;
309− } else if (spreadView.closingIndex == -1) {
310− // Unless we're closing the app ourselves in the spread,
311− // lets make sure the spread doesn't mess up by the changing app list.
312− spreadView.phase = 0;
313− spreadView.contentX = -spreadView.shift;
314−
315− ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
316− }
317− }
299+ Binding {
300+ target: MirFocusController
301+ property: "focusedSurface"
302+ value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
303+ when: root.parent && !spreadRepeater.startingUp
318304 }
319305
320306 Flickable {
@@ -365,16 +351,45 @@ AbstractStage {
365351 property int selectedIndex: -1
366352 property int draggedDelegateCount: 0
367353 property int closingIndex: -1
368− property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
369−
370− // FIXME: Workaround Flickable's not keepping its contentX still when resized
371− onContentXChanged: { forceItToRemainStillIfBeingResized(); }
354+ property var selectedDelegate: selectedIndex !== -1 ? spreadRepeater.itemAt(selectedIndex) : null
355+
356+ // <FIXME-contentX> Workaround Flickable's behavior of bringing contentX back between valid boundaries
357+ // when resized. The proper way to fix this is refactoring PhoneStage so that it doesn't
358+ // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
359+ // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
360+ // the Flickable.boundsBehavior upon release).
361+ onContentXChanged: {
362+ if (!undoContentXReset()) {
363+ forceItToRemainStillIfBeingResized();
364+ }
365+ }
372366 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
373367 function forceItToRemainStillIfBeingResized() {
374− if (root.beingResized && contentX != -shift) {
368+ if (root.beingResized && contentX != -spreadView.shift) {
369+ contentX = -spreadView.shift;
370+ }
371+ }
372+ function undoContentXReset() {
373+ if (contentWidth <= 0) {
374+ contentWidthOnLastContentXChange = contentWidth;
375+ lastContentX = contentX;
376+ return false;
377+ }
378+
379+ if (contentWidth != contentWidthOnLastContentXChange
380+ && lastContentX == -shift && contentX == 0) {
381+ // Flickable is resetting contentX because contentWidth has changed. Undo it.
375382 contentX = -shift;
383+ return true;
376384 }
385+
386+ contentWidthOnLastContentXChange = contentWidth;
387+ lastContentX = contentX;
388+ return false;
377389 }
390+ property real contentWidthOnLastContentXChange: -1
391+ property real lastContentX: 0
392+ // </FIXME-contentX>
378393
379394 property bool animateX: true
380395 property bool beingResized: root.beingResized
@@ -387,31 +402,33 @@ AbstractStage {
387402 }
388403 }
389404
390− property real sideStageDragProgress: sideStage.progress
391− property bool sideStageVisible: priv.sideStageAppId
392405 property real sideStageWidth: units.gu(40)
393406
394407 property bool surfaceDragging: triGestureArea.recognisedDrag
395408
396− // In case the ApplicationManager already holds an app when starting up we're missing animations
409+ readonly property bool sideStageVisible: priv.sideStageItemId != 0
410+
411+ // In case applicationManager already holds an app when starting up we're missing animations
397412 // Make sure we end up in the same state
398413 Component.onCompleted: {
399414 spreadView.contentX = -spreadView.shift
400415 }
401416
402417 property int nextInStack: {
418+ var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.index : -1;
419+ var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.index : -1;
403420 switch (state) {
404421 case "main":
405− if (ApplicationManager.count > 1) {
422+ if (root.topLevelSurfaceList.count > 1) {
406423 return 1;
407424 }
408425 return -1;
409426 case "mainAndOverlay":
410− if (ApplicationManager.count <= 2) {
427+ if (root.topLevelSurfaceList.count <= 2) {
411428 return -1;
412429 }
413− if (priv.appId0 == priv.mainStageAppId || priv.appId0 == priv.sideStageAppId) {
414− if (priv.appId1 == priv.mainStageAppId || priv.appId1 == priv.sideStageAppId) {
430+ if (mainStageIndex == 0 || sideStageIndex == 0) {
431+ if (mainStageIndex == 1 || sideStageIndex == 1) {
415432 return 2;
416433 }
417434 return 1;
@@ -422,7 +439,7 @@ AbstractStage {
422439 }
423440 return -1;
424441 }
425− property int nextZInStack: indexToZIndex(nextInStack)
442+ property int nextZInStack
426443
427444 states: [
428445 State {
@@ -442,13 +459,13 @@ AbstractStage {
442459 }
443460 ]
444461 state: {
445− if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
462+ if ((priv.mainStageItemId && !priv.sideStageItemId) || !priv.sideStageEnabled) {
446463 return "main";
447464 }
448− if (!priv.mainStageAppId && priv.sideStageAppId) {
465+ if (!priv.mainStageItemId && priv.sideStageItemId) {
449466 return "overlay";
450467 }
451− if (priv.mainStageAppId && priv.sideStageAppId) {
468+ if (priv.mainStageItemId && priv.sideStageItemId) {
452469 return "mainAndOverlay";
453470 }
454471 return "empty";
@@ -513,38 +530,41 @@ AbstractStage {
513530 // only shuffle when we've got a main and overlay
514531 if (state !== "mainAndOverlay") return index;
515532
516− var app = ApplicationManager.get(index);
533+ var app = root.topLevelSurfaceList.applicationAt(index);
517534 if (!app) {
518535 return index;
519536 }
537+ var stage = spreadRepeater.itemAt(index) ? spreadRepeater.itemAt(index).stage : app.stage;
520538
521539 // don't shuffle indexes greater than "actives or next"
522540 if (index > 2) return index;
523541
524− if (app.appId === priv.mainStageAppId) {
542+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
543+
544+ if (index == mainStageIndex) {
525545 // Active main stage always at 0
526546 return 0;
527547 }
528548
529549 if (spreadView.nextInStack > 0) {
530− var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
550+ var stageOfNextInStack = spreadRepeater.itemAt(spreadView.nextInStack).stage;
531551
532552 if (index === spreadView.nextInStack) {
533553 // this is the next app in stack.
534554
535− if (app.stage === ApplicationInfoInterface.SideStage) {
555+ if (stage === ApplicationInfoInterface.SideStage) {
536556 // if the next app in stack is a sidestage app, it must order on top of other side stage app
537− return Math.min(2, ApplicationManager.count-1);
557+ return Math.min(2, root.topLevelSurfaceList.count-1);
538558 }
539559 return 1;
540560 }
541− if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
561+ if (stageOfNextInStack === ApplicationInfoInterface.SideStage) {
542562 // if the next app in stack is a sidestage app, it must order on top of other side stage app
543563 return 1;
544564 }
545− return Math.min(2, ApplicationManager.count-1);
565+ return Math.min(2, root.topLevelSurfaceList.count-1);
546566 }
547− return Math.min(index+1, ApplicationManager.count-1);
567+ return Math.min(index+1, root.topLevelSurfaceList.count-1);
548568 }
549569
550570 SequentialAnimation {
@@ -562,12 +582,13 @@ AbstractStage {
562582 script: {
563583 if (spreadView.selectedIndex >= 0) {
564584 var newIndex = spreadView.selectedIndex;
565− var application = ApplicationManager.get(newIndex);
566− if (application.stage === ApplicationInfoInterface.SideStage) {
585+ var application = root.topLevelSurfaceList.applicationAt(newIndex);
586+ var spreadDelegate = spreadRepeater.itemAt(newIndex);
587+ if (spreadDelegate.stage === ApplicationInfoInterface.SideStage) {
567588 sideStage.showNow();
568589 }
569590 spreadView.selectedIndex = -1;
570− ApplicationManager.focusApplication(application.appId);
591+ spreadDelegate.focus = true;
571592 spreadView.phase = 0;
572593 spreadView.contentX = -spreadView.shift;
573594 }
@@ -583,7 +604,7 @@ AbstractStage {
583604 MouseArea {
584605 id: spreadRow
585606 x: spreadView.contentX
586− width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
607+ width: spreadView.width + Math.max(spreadView.width, root.topLevelSurfaceList.count * spreadView.tileDistance)
587608 height: root.height
588609
589610 onClicked: {
@@ -601,8 +622,8 @@ AbstractStage {
601622 enabled: priv.sideStageEnabled
602623
603624 onDropped: {
604− priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
605− ApplicationManager.focusApplication(drag.source.appId);
625+ drop.source.spreadDelegate.stage = ApplicationInfoInterface.MainStage;
626+ drop.source.spreadDelegate.focus = true;
606627 }
607628 keys: "SideStage"
608629 }
@@ -613,12 +634,12 @@ AbstractStage {
613634 height: priv.landscapeHeight
614635 x: spreadView.width - width
615636 z: {
616− if (!priv.mainStageAppId) return 0;
637+ if (!priv.mainStageItemId) return 0;
617638
618− if (priv.sideStageAppId && spreadView.nextInStack > 0) {
619− var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
639+ if (priv.sideStageItemId && spreadView.nextInStack > 0) {
640+ var nextDelegateInStack = spreadRepeater.itemAt(spreadView.nextInStack);
620641
621− if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
642+ if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
622643 // if the next app in stack is a main stage app, put the sidestage on top of it.
623644 return 2;
624645 }
@@ -633,12 +654,11 @@ AbstractStage {
633654 Behavior on opacity { UbuntuNumberAnimation {} }
634655
635656 onShownChanged: {
636− if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
637− ApplicationManager.requestFocusApplication(priv.mainStageAppId);
638− }
639− priv.updateStageApps();
640− if (shown && priv.sideStageAppId) {
641− ApplicationManager.requestFocusApplication(priv.sideStageAppId);
657+ if (!shown && priv.sideStageDelegate && priv.focusedAppDelegate === priv.sideStageDelegate
658+ && priv.mainStageDelegate) {
659+ priv.mainStageDelegate.focus = true;
660+ } else if (shown && priv.sideStageDelegate) {
661+ priv.sideStageDelegate.focus = true;
642662 }
643663 }
644664
@@ -657,8 +677,8 @@ AbstractStage {
657677 }
658678 onDropped: {
659679 if (drop.keys == "MainStage") {
660− priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
661− ApplicationManager.requestFocusApplication(drop.source.appId);
680+ drop.source.spreadDelegate.stage = ApplicationInfoInterface.SideStage;
681+ drop.source.spreadDelegate.focus = true;
662682 }
663683 }
664684 drag {
@@ -671,41 +691,102 @@ AbstractStage {
671691 }
672692 }
673693
674− Repeater {
694+ TopLevelSurfaceRepeater {
675695 id: spreadRepeater
676696 objectName: "spreadRepeater"
677− model: ApplicationManager
697+ model: root.topLevelSurfaceList
698+
699+ onItemAdded: {
700+ priv.updateMainAndSideStageIndexes();
701+ if (spreadView.phase == 2) {
702+ spreadView.snapTo(index);
703+ }
704+ }
705+
706+ onItemRemoved: {
707+ priv.updateMainAndSideStageIndexes();
708+ // Unless we're closing the app ourselves in the spread,
709+ // lets make sure the spread doesn't mess up by the changing app list.
710+ if (spreadView.closingIndex == -1) {
711+ spreadView.phase = 0;
712+ spreadView.contentX = -spreadView.shift;
713+ focusTopMostApp();
714+ }
715+ }
716+ function focusTopMostApp() {
717+ if (spreadRepeater.count > 0) {
718+ var topmostDelegate = spreadRepeater.itemAt(0);
719+ topmostDelegate.focus = true;
720+ }
721+ }
678722
679723 delegate: TransformedTabletSpreadDelegate {
680724 id: spreadTile
681− objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
682− : "tabletSpreadDelegate_null";
725+ objectName: "spreadDelegate_" + model.id
726+
727+ readonly property int index: model.index
683728 width: spreadView.width
684729 height: spreadView.height
685− active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
730+ active: model.id == priv.mainStageItemId || model.id == priv.sideStageItemId
686731 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
732+ onZIndexChanged: {
733+ if (spreadView.nextInStack == model.index) {
734+ spreadView.nextZInStack = zIndex;
735+ }
736+ }
687737 selected: spreadView.selectedIndex == index
688738 otherSelected: spreadView.selectedIndex >= 0 && !selected
689− isInSideStage: priv.sideStageAppId === appId
739+ isInSideStage: priv.sideStageItemId == model.id
690740 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
691741 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
692742 maximizedAppTopMargin: root.maximizedAppTopMargin
693− dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
694− application: ApplicationManager.get(index)
743+ dragOffset: !isDash && model.id == priv.mainStageItemId && root.inverseProgress > 0
744+ && spreadView.phase === 0 ? root.inverseProgress : 0
745+ application: model.application
746+ surface: model.surface
695747 closeable: !isDash
696748 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
697749
698− readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
750+ readonly property bool wantsMainStage: stage == ApplicationInfoInterface.MainStage
699751
700− readonly property string appId: model.appId
701− readonly property bool isDash: model.appId == "unity8-dash"
752+ readonly property bool isDash: model.application.appId == "unity8-dash"
753+
754+ onFocusChanged: {
755+ if (focus && !spreadRepeater.startingUp) {
756+ priv.focusedAppDelegate = spreadTile;
757+ root.topLevelSurfaceList.raiseId(model.id);
758+ }
759+ if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
760+ sideStage.show();
761+ }
762+ }
763+ Connections {
764+ target: model.surface
765+ onFocusRequested: spreadTile.focus = true;
766+ }
767+ Connections {
768+ target: model.application
769+ onFocusRequested: {
770+ if (!model.surface) {
771+ // when an app has no surfaces, we assume there's only one entry representing it:
772+ // this delegate.
773+ spreadTile.focus = true;
774+ } else {
775+ // if the application has surfaces, focus request should be at surface-level.
776+ }
777+ }
778+ }
702779
703− stage: model.stage
704780 fullscreen: {
705− if (mainApp && stage === ApplicationInfoInterface.SideStage) {
706− return mainApp.fullscreen;
781+ if (priv.mainStageDelegate && stage === ApplicationInfoInterface.SideStage) {
782+ return priv.mainStageDelegate.fullscreen;
783+ } else if (surface) {
784+ return surface.state === Mir.FullscreenState;
785+ } else if (application) {
786+ return application.fullscreen;
787+ } else {
788+ return false;
707789 }
708− return application ? application.fullscreen : false;
709790 }
710791
711792 supportedOrientations: {
@@ -726,23 +807,6 @@ AbstractStage {
726807 }
727808 }
728809
729−
730− Binding {
731− target: spreadTile.application
732− property: "exemptFromLifecycle"
733− value: !model.isTouchApp || isExemptFromLifecycle(model.appId)
734− }
735−
736− Binding {
737− target: spreadTile.application
738− property: "requestedState"
739− value: (isDash && root.keepDashRunning)
740− || (!root.suspended && (model.appId == priv.mainStageAppId
741− || model.appId == priv.sideStageAppId))
742− ? ApplicationInfoInterface.RequestedRunning
743− : ApplicationInfoInterface.RequestedSuspended
744− }
745−
746810 // FIXME: A regular binding doesn't update any more after closing an app.
747811 // Using a Binding for now.
748812 Binding {
@@ -762,34 +826,33 @@ AbstractStage {
762826 onSideStageEnabledChanged: refreshStage()
763827 }
764828
829+ property bool _constructing: true;
830+ onStageChanged: {
831+ if (!_constructing) {
832+ priv.updateMainAndSideStageIndexes();
833+ }
834+ }
835+
765836 Component.onCompleted: {
766− refreshStage()
767− stageChanged.connect(priv.updateStageApps);
837+ // a top level window is always the focused one when it first appears, unfocusing
838+ // any preexisting one
839+ focus = true;
840+ refreshStage();
841+ _constructing = false;
842+ }
843+ Component.onDestruction: {
844+ WindowStateStorage.saveStage(model.application.appId, stage);
768845 }
769846
770847 function refreshStage() {
771− var stage = ApplicationInfoInterface.MainStage;
848+ var newStage = ApplicationInfoInterface.MainStage;
772849 if (priv.sideStageEnabled) {
773850 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
774− stage = WindowStateStorage.getStage(appId);
851+ newStage = WindowStateStorage.getStage(application.appId);
775852 }
776853 }
777854
778− if (model.stage !== stage) {
779− priv.setAppStage(appId, stage, false);
780− }
781− }
782−
783− // This is required because none of the bindings are triggered in some cases:
784− // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
785− // returns a different app even though the nextInStackIndex and all the related
786− // bindings (index, mainStageApp, sideStageApp, etc) don't change. Let's force a
787− // binding update in that case.
788− Connections {
789− target: ApplicationManager
790− onApplicationRemoved: spreadTile.z = Qt.binding(function() {
791− return spreadView.indexToZIndex(index);
792− })
855+ stage = newStage;
793856 }
794857
795858 progress: {
@@ -875,13 +938,10 @@ AbstractStage {
875938 script: {
876939 // rotate immediately.
877940 spreadTile.matchShellOrientation();
878− if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
941+ if (priv.focusedAppDelegate === spreadTile &&
879942 priv.sideStageEnabled && !sideStage.shown) {
880943 // Sidestage was focused, so show the side stage.
881944 sideStage.show();
882− // if we've switched to a main app which doesnt support portrait, hide the side stage.
883− } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
884− sideStage.hideNow();
885945 }
886946 }
887947 }
@@ -892,10 +952,10 @@ AbstractStage {
892952 SequentialAnimation {
893953 ScriptAction {
894954 script: {
895− if (priv.sideStageAppId === spreadTile.appId &&
955+ if (priv.sideStageDelegate === spreadTile &&
896956 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
897957 // The mainstage app did not natively support portrait orientation, so focus the sidestage.
898− ApplicationManager.requestFocusApplication(spreadTile.appId);
958+ spreadTile.focus = true;
899959 }
900960 }
901961 }
@@ -924,28 +984,26 @@ AbstractStage {
924984
925985 onClosed: {
926986 spreadView.closingIndex = index;
927− ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
928− }
929−
930− onFocusChanged: {
931− if (focus && ApplicationManager.focusedApplicationId !== appId) {
932− ApplicationManager.focusApplication(appId);
933− }
934−
935− if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
936− sideStage.show();
987+ if (spreadTile.surface) {
988+ spreadTile.surface.close();
989+ } else if (spreadTile.application) {
990+ root.applicationManager.stopApplication(spreadTile.application.appId);
991+ } else {
992+ // should never happen
993+ console.warn("Can't close topLevelSurfaceList entry as it has neither"
994+ + " a surface nor an application");
937995 }
938996 }
939997
940998 Binding {
941999 target: root
942− when: model.appId == priv.mainStageAppId
1000+ when: model.id == priv.mainStageItemId
9431001 property: "mainAppWindowOrientationAngle"
9441002 value: appWindowOrientationAngle
9451003 }
9461004 Binding {
9471005 target: priv
948− when: model.appId == priv.mainStageAppId
1006+ when: model.id == priv.mainStageItemId
9491007 property: "mainAppOrientationChangesEnabled"
9501008 value: orientationChangesEnabled
9511009 }
@@ -959,7 +1017,7 @@ AbstractStage {
9591017
9601018 StagedFullscreenPolicy {
9611019 id: fullscreenPolicy
962− application: spreadTile.application
1020+ surface: model.surface
9631021 }
9641022 Connections {
9651023 target: root
@@ -975,17 +1033,19 @@ AbstractStage {
9751033 anchors.fill: parent
9761034 enabled: priv.sideStageEnabled && !spreadView.active
9771035 property var dragObject: null
978− property string appId: ""
1036+
1037+ property Item spreadDelegate
1038+
9791039 dragComponent: dragComponent
980− dragComponentProperties: { "appId": appId }
1040+ dragComponentProperties: { "spreadDelegate": spreadDelegate }
9811041
9821042 onPressed: {
983− function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
1043+ function matchDelegate(obj) { return String(obj.objectName).indexOf("spreadDelegate") >= 0; }
9841044
9851045 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
9861046 if (!delegateAtCenter) return;
9871047
988− appId = delegateAtCenter.appId;
1048+ spreadDelegate = delegateAtCenter;
9891049 }
9901050
9911051 onClicked: {
@@ -1005,11 +1065,11 @@ AbstractStage {
10051065
10061066 Component {
10071067 id: dragComponent
1008− SessionContainer {
1009− property string appId: ""
1010− property var application: ApplicationManager.findApplication(appId)
1068+ SurfaceContainer {
1069+ property Item spreadDelegate
1070+
1071+ surface: spreadDelegate ? spreadDelegate.surface : null
10111072
1012− session: application ? application.session : null
10131073 interactive: false
10141074 resizeSurface: false
10151075 focus: false
@@ -1021,10 +1081,11 @@ AbstractStage {
10211081 Drag.hotSpot.y: height/2
10221082 // only accept opposite stage.
10231083 Drag.keys: {
1024− if (!application) return "Disabled";
1084+ if (!surface) return "Disabled";
10251085
1026− if (application.stage === ApplicationInfo.MainStage) {
1027− if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1086+ if (spreadDelegate.stage === ApplicationInfo.MainStage) {
1087+ if (spreadDelegate.application.supportedOrientations
1088+ & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
10281089 return "MainStage";
10291090 }
10301091 return "Disabled";