on xenial help sign in
Restore windows when activating from the spread, maintain a focus stack

Stop displaying the "grabbing" icon when we merely click to focus the app's decoration. Provide keyboard shortcuts for common window operations Approved by: Michael Zanetti

commitd3df9714fe50f0cb9f959a3ef0c51a7ac6279224 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateThu 12 Nov 2015, 20:39 (11 years ago) parent8dde54cc1cee66
7 files changed+316−72
qml/Panel/Panel.qml+18−1 qml/Stages/DecoratedWindow.qml+3−1 qml/Stages/DesktopSpread.qml+1−1 qml/Stages/DesktopSpreadDelegate.qml+2−2 qml/Stages/DesktopStage.qml+147−33 qml/Stages/WindowDecoration.qml+6−2 tests/qmltests/Stages/tst_DesktopStage.qml+139−32
qml/Panel/Panel.qml+18−1
@@ -137,7 +137,7 @@ Item {
137137 }
138138
139139 shown: false
140− width: root.width - (windowControlButtons.visible ? windowControlButtons.width : 0)
140+ width: root.width - (windowControlButtons.visible ? windowControlButtons.width + titleLabel.width : 0)
141141 minimizedPanelHeight: units.gu(3)
142142 expandedPanelHeight: units.gu(7)
143143 openedHeight: root.height - indicatorOrangeLine.height
@@ -178,6 +178,23 @@ Item {
178178 onMaximize: PanelState.maximize()
179179 }
180180
181+ Label {
182+ id: titleLabel
183+ objectName: "windowDecorationTitle"
184+ anchors {
185+ left: windowControlButtons.right
186+ top: parent.top
187+ margins: units.gu(0.7)
188+ }
189+ color: "#DFDBD2"
190+ height: windowControlButtons.height
191+ visible: windowControlButtons.visible
192+ verticalAlignment: Text.AlignVCenter
193+ fontSize: "small"
194+ font.bold: true
195+ text: PanelState.title
196+ }
197+
181198 PanelSeparatorLine {
182199 id: indicatorOrangeLine
183200 anchors {
qml/Stages/DecoratedWindow.qml+3−1
@@ -26,6 +26,7 @@ FocusScope {
2626 property alias window: applicationWindow
2727 property alias application: applicationWindow.application
2828 property alias active: decoration.active
29+ property alias title: decoration.title
2930
3031 property bool decorationShown: true
3132 property bool highlightShown: false
@@ -66,7 +67,8 @@ FocusScope {
6667 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
6768 anchors { left: parent.left; top: parent.top; right: parent.right }
6869 height: units.gu(3)
69− title: window.title !== "" ? window.title : model.name
70+ width: root.width
71+ title: window.title !== "" ? window.title : ""
7072 onClose: root.close();
7173 onMaximize: root.maximize();
7274 onMinimize: root.minimize();
qml/Stages/DesktopSpread.qml+1−1
@@ -87,7 +87,7 @@ FocusScope {
8787 function focusSelected() {
8888 if (spreadRepeater.highlightedIndex != -1) {
8989 var application = ApplicationManager.get(spreadRepeater.highlightedIndex);
90− ApplicationManager.focusApplication(application.appId);
90+ ApplicationManager.requestFocusApplication(application.appId);
9191 }
9292 }
9393
qml/Stages/DesktopSpreadDelegate.qml+2−2
@@ -29,8 +29,8 @@ Item {
2929 property bool highlightShown: false
3030 property real shadowOpacity: 1
3131
32− property int windowWidth: application.session && application.session.surface ? application.session.surface.size.width : 0
33− property int windowHeight: application.session && application.session.surface ? application.session.surface.size.height : 0
32+ property int windowWidth: application && application.session && application.session.surface ? application.session.surface.size.width : 0
33+ property int windowHeight: application && application.session && application.session.surface ? application.session.surface.size.height : 0
3434
3535 state: "normal"
3636 states: [
qml/Stages/DesktopStage.qml+147−33
@@ -20,11 +20,11 @@ import QtQuick 2.4
2020import QtQuick.Layouts 1.1
2121import Ubuntu.Components 1.3
2222import Unity.Application 0.1
23−import "../Components"
2423import "../Components/PanelState"
2524import "../Components"
2625import Utils 0.1
2726import Ubuntu.Gestures 0.1
27+import GlobalShortcut 1.0
2828
2929AbstractStage {
3030 id: root
@@ -45,21 +45,72 @@ AbstractStage {
4545 spread.state = "";
4646 }
4747
48− ApplicationManager.requestFocusApplication(appId)
48+ ApplicationManager.focusApplication(appId);
49+ }
50+
51+ onApplicationRemoved: {
52+ priv.focusNext();
4953 }
5054
5155 onFocusRequested: {
5256 var appIndex = priv.indexOf(appId);
5357 var appDelegate = appRepeater.itemAt(appIndex);
54− appDelegate.minimized = false;
55− ApplicationManager.focusApplication(appId)
58+ appDelegate.restoreFromMinimized();
5659
5760 if (spread.state == "altTab") {
58− spread.cancel()
61+ spread.cancel();
5962 }
6063 }
6164 }
6265
66+ GlobalShortcut {
67+ id: closeWindowShortcut
68+ shortcut: Qt.AltModifier|Qt.Key_F4
69+ onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
70+ active: priv.focusedAppId !== ""
71+ }
72+
73+ GlobalShortcut {
74+ id: showSpreadShortcut
75+ shortcut: Qt.MetaModifier|Qt.Key_W
76+ onTriggered: spread.state = "altTab"
77+ }
78+
79+ GlobalShortcut {
80+ id: minimizeAllShortcut
81+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
82+ onTriggered: priv.minimizeAllWindows()
83+ }
84+
85+ GlobalShortcut {
86+ id: maximizeWindowShortcut
87+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
88+ onTriggered: priv.focusedAppDelegate.maximize()
89+ active: priv.focusedAppDelegate !== null
90+ }
91+
92+ GlobalShortcut {
93+ id: maximizeWindowLeftShortcut
94+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
95+ onTriggered: priv.focusedAppDelegate.maximizeLeft()
96+ active: priv.focusedAppDelegate !== null
97+ }
98+
99+ GlobalShortcut {
100+ id: maximizeWindowRightShortcut
101+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
102+ onTriggered: priv.focusedAppDelegate.maximizeRight()
103+ active: priv.focusedAppDelegate !== null
104+ }
105+
106+ GlobalShortcut {
107+ id: minimizeRestoreShortcut
108+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
109+ onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight
110+ ? priv.focusedAppDelegate.restore() : priv.focusedAppDelegate.minimize(true)
111+ active: priv.focusedAppDelegate !== null
112+ }
113+
63114 QtObject {
64115 id: priv
65116
@@ -93,6 +144,28 @@ AbstractStage {
93144 }
94145 return -1;
95146 }
147+
148+ function minimizeAllWindows() {
149+ for (var i = 0; i < appRepeater.count; i++) {
150+ var appDelegate = appRepeater.itemAt(i);
151+ if (appDelegate && !appDelegate.minimized) {
152+ appDelegate.minimize(false); // minimize but don't switch focus
153+ }
154+ }
155+
156+ ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
157+ }
158+
159+ function focusNext() {
160+ ApplicationManager.unfocusCurrentApplication();
161+ for (var i = 0; i < appRepeater.count; i++) {
162+ var appDelegate = appRepeater.itemAt(i);
163+ if (appDelegate && !appDelegate.minimized) {
164+ ApplicationManager.focusApplication(appDelegate.appId);
165+ return;
166+ }
167+ }
168+ }
96169 }
97170
98171 Connections {
@@ -100,15 +173,23 @@ AbstractStage {
100173 onClose: {
101174 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
102175 }
103− onMinimize: appRepeater.itemAt(0).minimize();
104− onMaximize: appRepeater.itemAt(0).unmaximize();
176+ onMinimize: appRepeater.itemAt(0).minimize(true);
177+ onMaximize: appRepeater.itemAt(0).restore();
105178 }
106179
107180 Binding {
108181 target: PanelState
109182 property: "buttonsVisible"
110− value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
183+ value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized
111184 }
185+
186+ Binding {
187+ target: PanelState
188+ property: "title"
189+ value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.title
190+ when: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
191+ }
192+
112193 Component.onDestruction: PanelState.buttonsVisible = false;
113194
114195 FocusScope {
@@ -135,23 +216,26 @@ AbstractStage {
135216
136217 delegate: FocusScope {
137218 id: appDelegate
138− objectName: "stageDelegate_" + model.appId
219+ objectName: "appDelegate_" + appId
139220 z: ApplicationManager.count - index
140221 y: units.gu(3)
141222 width: units.gu(60)
142223 height: units.gu(50)
143− focus: model.appId === priv.focusedAppId
224+ focus: appId === priv.focusedAppId
144225
145226 property bool maximized: false
227+ property bool maximizedLeft: false
228+ property bool maximizedRight: false
146229 property bool minimized: false
230+ readonly property string appId: model.appId
147231 property bool animationsEnabled: true
148−
232+ property alias title: decoratedWindow.title
149233 property bool visuallyMaximized: false
150234 property bool visuallyMinimized: false
151235
152236 onFocusChanged: {
153− if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
154− ApplicationManager.focusApplication(model.appId);
237+ if (focus && ApplicationManager.focusedApplicationId !== appId) {
238+ ApplicationManager.focusApplication(appId);
155239 }
156240 }
157241
@@ -163,8 +247,6 @@ AbstractStage {
163247 (priv.foregroundMaximizedAppIdIndex === -1 || priv.foregroundMaximizedAppIdIndex >= index) ||
164248 (spread.state == "altTab" && index === spread.highlightedIndex)
165249
166− onVisibleChanged: console.log("VISIBLE", model.appId, visible)
167−
168250 Binding {
169251 target: ApplicationManager.get(index)
170252 property: "requestedState"
@@ -180,22 +262,50 @@ AbstractStage {
180262 animationsEnabled = (animated === undefined) || animated;
181263 minimized = false;
182264 maximized = true;
265+ maximizedLeft = false;
266+ maximizedRight = false;
267+ }
268+ function maximizeLeft() {
269+ minimized = false;
270+ maximized = false;
271+ maximizedLeft = true;
272+ maximizedRight = false;
273+ }
274+ function maximizeRight() {
275+ minimized = false;
276+ maximized = false;
277+ maximizedLeft = false;
278+ maximizedRight = true;
183279 }
184280 function minimize(animated) {
185281 animationsEnabled = (animated === undefined) || animated;
186282 maximized = false;
187283 minimized = true;
188284 }
189− function unmaximize(animated) {
285+ function restore(animated) {
190286 animationsEnabled = (animated === undefined) || animated;
191287 minimized = false;
192288 maximized = false;
289+ maximizedLeft = false;
290+ maximizedRight = false;
291+ }
292+ function restoreFromMinimized(animated) {
293+ animationsEnabled = (animated === undefined) || animated;
294+ minimized = false;
295+ if (maximized)
296+ maximize();
297+ else if (maximizedLeft)
298+ maximizeLeft();
299+ else if (maximizedRight)
300+ maximizeRight();
301+ ApplicationManager.focusApplication(appId);
193302 }
194303
195304 states: [
196305 State {
197306 name: "normal";
198307 when: !appDelegate.maximized && !appDelegate.minimized
308+ && !appDelegate.maximizedLeft && !appDelegate.maximizedRight
199309 PropertyChanges {
200310 target: appDelegate;
201311 visuallyMinimized: false;
@@ -212,6 +322,14 @@ AbstractStage {
212322 visuallyMaximized: true
213323 }
214324 },
325+ State {
326+ name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
327+ PropertyChanges { target: appDelegate; x: 0; y: units.gu(3); width: root.width/2; height: root.height - units.gu(3) }
328+ },
329+ State {
330+ name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
331+ PropertyChanges { target: appDelegate; x: root.width/2; y: units.gu(3); width: root.width/2; height: root.height - units.gu(3) }
332+ },
215333 State {
216334 name: "minimized"; when: appDelegate.minimized
217335 PropertyChanges {
@@ -229,14 +347,14 @@ AbstractStage {
229347 to: "normal"
230348 enabled: appDelegate.animationsEnabled
231349 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
232− PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
350+ PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
233351 },
234352 Transition {
235353 to: "maximized"
236354 enabled: appDelegate.animationsEnabled
237355 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
238356 SequentialAnimation {
239− PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
357+ PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
240358 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
241359 }
242360 },
@@ -245,20 +363,15 @@ AbstractStage {
245363 enabled: appDelegate.animationsEnabled
246364 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
247365 SequentialAnimation {
248− PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
366+ PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
249367 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
250− }
251− },
252− Transition {
253− from: ""
254− to: "altTab"
255− PropertyAction { target: appDelegate; properties: "y,angle,z,itemScale,itemScaleOriginY" }
256− PropertyAction { target: decoratedWindow; properties: "anchors.topMargin" }
257− PropertyAnimation {
258− target: appDelegate; properties: "x"
259− from: root.width
260− duration: rightEdgePushArea.containsMouse ? UbuntuAnimation.FastDuration :0
261− easing: UbuntuAnimation.StandardEasing
368+ ScriptAction {
369+ script: {
370+ if (appDelegate.animationsEnabled && state === "minimized" ) {
371+ priv.focusNext();
372+ }
373+ }
374+ }
262375 }
263376 }
264377 ]
@@ -294,8 +407,9 @@ AbstractStage {
294407 focus: true
295408
296409 onClose: ApplicationManager.stopApplication(model.appId)
297− onMaximize: appDelegate.maximized ? appDelegate.unmaximize() : appDelegate.maximize()
298− onMinimize: appDelegate.minimize()
410+ onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
411+ ? appDelegate.restore() : appDelegate.maximize()
412+ onMinimize: appDelegate.minimize(true)
299413 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
300414 }
301415 }
qml/Stages/WindowDecoration.qml+6−2
@@ -45,14 +45,15 @@ MouseArea {
4545 priv.distanceX = pos.x;
4646 priv.distanceY = pos.y;
4747 priv.dragging = true;
48− Mir.cursorName = "grabbing";
4948 } else {
5049 priv.dragging = false;
5150 Mir.cursorName = "";
5251 }
5352 }
53+
5454 onPositionChanged: {
5555 if (priv.dragging) {
56+ Mir.cursorName = "grabbing";
5657 var pos = mapToItem(root.target.parent, mouseX, mouseY);
5758 root.target.x = pos.x - priv.distanceX;
5859 root.target.y = pos.y - priv.distanceY;
@@ -70,11 +71,12 @@ MouseArea {
7071 }
7172
7273 Row {
73− anchors { left: parent.left; top: parent.top; bottom: parent.bottom; margins: units.gu(0.7) }
74+ anchors { fill: parent; margins: units.gu(0.7) }
7475 spacing: units.gu(1)
7576 opacity: root.active ? 1 : 0.5
7677
7778 WindowControlButtons {
79+ id: buttons
7880 height: parent.height
7981 onClose: root.close();
8082 onMinimize: root.minimize();
@@ -86,9 +88,11 @@ MouseArea {
8688 objectName: "windowDecorationTitle"
8789 color: "#DFDBD2"
8890 height: parent.height
91+ width: parent.width - buttons.width - parent.anchors.rightMargin - parent.anchors.leftMargin
8992 verticalAlignment: Text.AlignVCenter
9093 fontSize: "small"
9194 font.bold: true
95+ elide: Text.ElideRight
9296 }
9397 }
9498}
tests/qmltests/Stages/tst_DesktopStage.qml+139−32
@@ -60,14 +60,10 @@ Item {
6060
6161 focus: true
6262
63− property bool itemDestroyed: false
6463 sourceComponent: Component {
6564 DesktopStage {
6665 color: "darkblue"
6766 anchors.fill: parent
68− Component.onDestruction: {
69− desktopStageLoader.itemDestroyed = true;
70− }
7167 orientations: Orientations {}
7268 }
7369 }
@@ -103,16 +99,10 @@ Item {
10399 property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null
104100
105101 function cleanup() {
106− desktopStageLoader.itemDestroyed = false;
107102 desktopStageLoader.active = false;
108103
109104 tryCompare(desktopStageLoader, "status", Loader.Null);
110105 tryCompare(desktopStageLoader, "item", null);
111− // Loader.status might be Loader.Null and Loader.item might be null but the Loader
112− // actually took place. Likely because Loader waits until the next event loop
113− // iteration to do its work. So to ensure the reload, we will wait until the
114− // Shell instance gets destroyed.
115− tryCompare(desktopStageLoader, "itemDestroyed", true);
116106
117107 killAllRunningApps();
118108
@@ -155,10 +145,7 @@ Item {
155145 }
156146
157147 function test_appFocusSwitch(data) {
158− var i;
159− for (i = 0; i < data.apps.length; i++) {
160− startApplication(data.apps[i]);
161− }
148+ data.apps.forEach(startApplication);
162149
163150 ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]);
164151 tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
@@ -175,10 +162,7 @@ Item {
175162 }
176163
177164 function test_tappingOnWindowChangesFocusedApp(data) {
178− var i;
179− for (i = 0; i < data.apps.length; i++) {
180− startApplication(data.apps[i]);
181− }
165+ data.apps.forEach(startApplication);
182166 var fromAppId = data.apps[data.focusfrom];
183167 var toAppId = data.apps[data.focusTo]
184168
@@ -195,18 +179,37 @@ Item {
195179 compare(ApplicationManager.focusedApplicationId, toAppId);
196180 }
197181
182+ function test_clickingOnWindowChangesFocusedApp_data() {
183+ return test_tappingOnWindowChangesFocusedApp_data(); // reuse test data
184+ }
185+
186+ function test_clickingOnWindowChangesFocusedApp(data) {
187+ data.apps.forEach(startApplication);
188+ var fromAppId = data.apps[data.focusfrom];
189+ var toAppId = data.apps[data.focusTo]
190+
191+ var fromAppWindow = findChild(desktopStage, "appWindow_" + fromAppId);
192+ verify(fromAppWindow);
193+ mouseClick(fromAppWindow);
194+ compare(fromAppWindow.application.session.surface.activeFocus, true);
195+ compare(ApplicationManager.focusedApplicationId, fromAppId);
196+
197+ var toAppWindow = findChild(desktopStage, "appWindow_" + toAppId);
198+ verify(toAppWindow);
199+ mouseClick(toAppWindow);
200+ compare(toAppWindow.application.session.surface.activeFocus, true);
201+ compare(ApplicationManager.focusedApplicationId, toAppId);
202+ }
203+
198204 function test_tappingOnDecorationFocusesApplication_data() {
199205 return [
200− {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
201− {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
206+ {tag: "dash to dialer", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
207+ {tag: "dialer to dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
202208 ]
203209 }
204210
205211 function test_tappingOnDecorationFocusesApplication(data) {
206− var i;
207− for (i = 0; i < data.apps.length; i++) {
208− startApplication(data.apps[i]);
209− }
212+ data.apps.forEach(startApplication);
210213
211214 var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
212215 verify(fromAppDecoration);
@@ -219,10 +222,114 @@ Item {
219222 tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
220223 }
221224
225+ function test_clickingOnDecorationFocusesApplication_data() {
226+ return test_tappingOnDecorationFocusesApplication_data(); // reuse test data
227+ }
228+
229+ function test_clickingOnDecorationFocusesApplication(data) {
230+ data.apps.forEach(startApplication);
231+
232+ var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
233+ verify(fromAppDecoration);
234+ mouseClick(fromAppDecoration);
235+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
236+
237+ var toAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusTo]);
238+ verify(toAppDecoration);
239+ mouseClick(toAppDecoration);
240+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
241+ }
242+
243+ function test_windowMaximize() {
244+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
245+ apps.forEach(startApplication);
246+ var appName = "dialer-app";
247+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
248+ verify(appDelegate);
249+ ApplicationManager.focusApplication(appName);
250+ keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
251+ tryCompare(appDelegate, "maximized", true);
252+ tryCompare(appDelegate, "minimized", false);
253+ }
254+
255+ function test_windowMaximizeLeft() {
256+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
257+ apps.forEach(startApplication);
258+ var appName = "dialer-app";
259+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
260+ verify(appDelegate);
261+ ApplicationManager.focusApplication(appName);
262+ keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
263+ tryCompare(appDelegate, "maximized", false);
264+ tryCompare(appDelegate, "minimized", false);
265+ tryCompare(appDelegate, "maximizedLeft", true);
266+ tryCompare(appDelegate, "maximizedRight", false);
267+ }
268+
269+ function test_windowMaximizeRight() {
270+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
271+ apps.forEach(startApplication);
272+ var appName = "dialer-app";
273+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
274+ verify(appDelegate);
275+ ApplicationManager.focusApplication(appName);
276+ keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
277+ tryCompare(appDelegate, "maximized", false);
278+ tryCompare(appDelegate, "minimized", false);
279+ tryCompare(appDelegate, "maximizedLeft", false);
280+ tryCompare(appDelegate, "maximizedRight", true);
281+ }
282+
283+ function test_windowMinimize() {
284+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
285+ apps.forEach(startApplication);
286+ var appName = "dialer-app";
287+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
288+ verify(appDelegate);
289+ ApplicationManager.focusApplication(appName);
290+ keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
291+ tryCompare(appDelegate, "maximized", false);
292+ tryCompare(appDelegate, "minimized", true);
293+ verify(ApplicationManager.focusedApplicationId != ""); // verify we don't lose focus when minimizing an app
294+ }
295+
296+ function test_windowMinimizeAll() {
297+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
298+ apps.forEach(startApplication);
299+ verify(ApplicationManager.count == 3);
300+ keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
301+ tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify no app is focused
302+ }
303+
304+ function test_windowClose() {
305+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
306+ apps.forEach(startApplication);
307+ verify(ApplicationManager.count == 3);
308+ var appName = "dialer-app";
309+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
310+ verify(appDelegate);
311+ ApplicationManager.focusApplication(appName);
312+ keyClick(Qt.Key_F4, Qt.AltModifier); // Alt+F4 shortcut to close
313+ verify(ApplicationManager.count == 2); // verify the app is gone
314+ verify(ApplicationManager.findApplication(appName) === null); // and it's not in running apps
315+ }
316+
317+ function test_smashCursorKeys() {
318+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
319+ apps.forEach(startApplication);
320+ verify(ApplicationManager.count == 3);
321+ keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
322+ tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify no app is focused
323+
324+ // now try pressing all 4 arrow keys + ctrl + meta
325+ keyClick(Qt.Key_Up | Qt.Key_Down | Qt.Key_Left | Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // smash it!!!
326+ tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify still no app is focused
327+ }
328+
222329 function test_minimizeApplicationHidesSurface() {
223330 var dashApp = startApplication("unity8-dash");
224331
225− var dashDelegate = findChild(desktopStage, "stageDelegate_unity8-dash");
332+ var dashDelegate = findChild(desktopStage, "appDelegate_unity8-dash");
226333 verify(dashDelegate);
227334
228335 findChild(dashDelegate, "decoratedWindow").minimize();
@@ -234,11 +341,11 @@ Item {
234341 var dialerApp = startApplication("dialer-app");
235342 var cameraApp = startApplication("camera-app");
236343
237− var dashDelegate = findChild(desktopStage, "stageDelegate_unity8-dash");
344+ var dashDelegate = findChild(desktopStage, "appDelegate_unity8-dash");
238345 verify(dashDelegate);
239− var dialerDelegate = findChild(desktopStage, "stageDelegate_dialer-app");
346+ var dialerDelegate = findChild(desktopStage, "appDelegate_dialer-app");
240347 verify(dialerDelegate);
241− var cameraDelegate = findChild(desktopStage, "stageDelegate_camera-app");
348+ var cameraDelegate = findChild(desktopStage, "appDelegate_camera-app");
242349 verify(cameraDelegate);
243350
244351 // maximize
@@ -256,20 +363,20 @@ Item {
256363
257364 function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
258365 var dashApp = startApplication("unity8-dash");
259− var dashDelegate = findChild(desktopStage, "stageDelegate_unity8-dash");
366+ var dashDelegate = findChild(desktopStage, "appDelegate_unity8-dash");
260367 verify(dashDelegate);
261368
262369 var dialerApp = startApplication("dialer-app");
263− var dialerDelegate = findChild(desktopStage, "stageDelegate_dialer-app");
370+ var dialerDelegate = findChild(desktopStage, "appDelegate_dialer-app");
264371 verify(dialerDelegate);
265372
266373 var cameraApp = startApplication("camera-app");
267− var cameraDelegate = findChild(desktopStage, "stageDelegate_camera-app");
374+ var cameraDelegate = findChild(desktopStage, "appDelegate_camera-app");
268375 verify(cameraDelegate);
269376 findChild(dialerDelegate, "decoratedWindow").maximize();
270377
271378 var galleryApp = startApplication("gallery-app");
272− var galleryDelegate = findChild(desktopStage, "stageDelegate_gallery-app");
379+ var galleryDelegate = findChild(desktopStage, "appDelegate_gallery-app");
273380 verify(galleryDelegate);
274381 findChild(galleryDelegate, "decoratedWindow").maximize();
275382