Merge lp:~mzanetti/unity8/ubuntuanimations into lp:unity8

Proposed by Michael Zanetti
Status: Superseded
Proposed branch: lp:~mzanetti/unity8/ubuntuanimations
Merge into: lp:unity8
Diff against target: 1344 lines (+692/-104)
17 files modified
plugins/Utils/windowstatestorage.cpp (+62/-23)
plugins/Utils/windowstatestorage.h (+13/-1)
qml/Components/PanelState/PanelState.qml (+2/-0)
qml/Components/WindowControlButtons.qml (+3/-0)
qml/Panel/Panel.qml (+28/-3)
qml/Shell.qml (+2/-0)
qml/Stages/DecoratedWindow.qml (+3/-1)
qml/Stages/DesktopSpread.qml (+1/-1)
qml/Stages/DesktopSpreadDelegate.qml (+2/-2)
qml/Stages/DesktopStage.qml (+180/-32)
qml/Stages/WindowDecoration.qml (+8/-3)
qml/Stages/WindowResizeArea.qml (+41/-7)
tests/mocks/Utils/windowstatestorage.cpp (+17/-0)
tests/mocks/Utils/windowstatestorage.h (+12/-0)
tests/qmltests/Stages/tst_DesktopStage.qml (+132/-25)
tests/qmltests/Stages/tst_WindowResizeArea.qml (+78/-2)
tests/qmltests/tst_Shell.qml (+108/-4)
To merge this branch: bzr merge lp:~mzanetti/unity8/ubuntuanimations
Reviewer Review Type Date Requested Status
Unity Team Pending
Review via email: mp+276510@code.launchpad.net

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

Commit message

use UbuntuNumberAnimations instead of linear ones for window state transitions

Description of the change

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

see prereq

 * 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?

n/a

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

changed as peer design feedback

To post a comment you must log in.
lp:~mzanetti/unity8/ubuntuanimations updated
2027. By Michael Zanetti

merge prereq

2028. By Michael Zanetti

update after merging

2029. By Michael Zanetti

mrege prereq

2030. By Michael Zanetti

wait for animations to be finished before destroying the test

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/Utils/windowstatestorage.cpp'
2--- plugins/Utils/windowstatestorage.cpp 2015-09-14 09:11:08 +0000
3+++ plugins/Utils/windowstatestorage.cpp 2015-11-03 11:00:18 +0000
4@@ -47,26 +47,38 @@
5 m_db.close();
6 }
7
8+void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
9+{
10+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
11+ .arg(windowId)
12+ .arg((int)state);
13+
14+ saveValue(queryString);
15+}
16+
17+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
18+{
19+ const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
20+ .arg(windowId);
21+
22+ QSqlQuery query = getValue(queryString);
23+
24+ if (!query.first()) {
25+ return defaultValue;
26+ }
27+ return (WindowState)query.value("state").toInt();
28+}
29+
30 void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
31 {
32- QMutexLocker mutexLocker(&s_mutex);
33-
34- QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
35+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
36 .arg(windowId)
37 .arg(rect.x())
38 .arg(rect.y())
39 .arg(rect.width())
40 .arg(rect.height());
41
42- QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
43- m_asyncQueries.append(future);
44-
45- QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
46- futureWatcher->setFuture(future);
47- connect(futureWatcher, &QFutureWatcher<void>::finished,
48- this,
49- [=](){ m_asyncQueries.removeAll(futureWatcher->future());
50- futureWatcher->deleteLater(); });
51+ saveValue(queryString);
52 }
53
54 void WindowStateStorage::executeAsyncQuery(const QString &queryString)
55@@ -82,20 +94,13 @@
56 }
57 }
58
59-QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
60+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
61 {
62- QMutexLocker l(&s_mutex);
63 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
64 .arg(windowId);
65- QSqlQuery query;
66-
67- bool ok = query.exec(queryString);
68- if (!ok) {
69- qWarning() << "Error retrieving window state for" << windowId
70- << "Driver error:" << query.lastError().driverText()
71- << "Database error:" << query.lastError().databaseText();
72- return defaultValue;
73- }
74+
75+ QSqlQuery query = getValue(queryString);
76+
77 if (!query.first()) {
78 return defaultValue;
79 }
80@@ -114,4 +119,38 @@
81 QSqlQuery query;
82 query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
83 }
84+
85+ if (!m_db.tables().contains("state")) {
86+ QSqlQuery query;
87+ query.exec("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
88+ }
89+}
90+
91+void WindowStateStorage::saveValue(const QString &queryString)
92+{
93+ QMutexLocker mutexLocker(&s_mutex);
94+
95+ QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
96+ m_asyncQueries.append(future);
97+
98+ QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
99+ futureWatcher->setFuture(future);
100+ connect(futureWatcher, &QFutureWatcher<void>::finished,
101+ this,
102+ [=](){ m_asyncQueries.removeAll(futureWatcher->future());
103+ futureWatcher->deleteLater(); });
104+}
105+
106+QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
107+{
108+ QMutexLocker l(&s_mutex);
109+ QSqlQuery query;
110+
111+ bool ok = query.exec(queryString);
112+ if (!ok) {
113+ qWarning() << "Error retrieving database query:" << queryString
114+ << "Driver error:" << query.lastError().driverText()
115+ << "Database error:" << query.lastError().databaseText();
116+ }
117+ return query;
118 }
119
120=== modified file 'plugins/Utils/windowstatestorage.h'
121--- plugins/Utils/windowstatestorage.h 2015-03-13 19:01:32 +0000
122+++ plugins/Utils/windowstatestorage.h 2015-11-03 11:00:18 +0000
123@@ -22,16 +22,28 @@
124 class WindowStateStorage: public QObject
125 {
126 Q_OBJECT
127+ Q_ENUMS(WindowState)
128 public:
129+ enum WindowState {
130+ WindowStateNormal,
131+ WindowStateMaximized
132+ };
133+
134 WindowStateStorage(QObject *parent = 0);
135 virtual ~WindowStateStorage();
136
137+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
138+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
139+
140 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
141- Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
142+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
143
144 private:
145 void initdb();
146
147+ void saveValue(const QString &queryString);
148+ QSqlQuery getValue(const QString &queryString) const;
149+
150 static void executeAsyncQuery(const QString &queryString);
151 static QMutex s_mutex;
152
153
154=== modified file 'qml/Components/PanelState/PanelState.qml'
155--- qml/Components/PanelState/PanelState.qml 2014-11-24 11:21:38 +0000
156+++ qml/Components/PanelState/PanelState.qml 2015-11-03 11:00:18 +0000
157@@ -23,6 +23,8 @@
158 property string title: ""
159 property bool buttonsVisible: false
160
161+ property int panelHeight: 0
162+
163 signal close()
164 signal minimize()
165 signal maximize()
166
167=== modified file 'qml/Components/WindowControlButtons.qml'
168--- qml/Components/WindowControlButtons.qml 2014-11-24 11:21:38 +0000
169+++ qml/Components/WindowControlButtons.qml 2015-11-03 11:00:18 +0000
170@@ -28,6 +28,7 @@
171 signal maximize()
172
173 Rectangle {
174+ objectName: "closeWindowButton"
175 height: parent.height; width: height; radius: height / 2
176 gradient: Gradient {
177 GradientStop { color: "#F49073"; position: 0 }
178@@ -38,6 +39,7 @@
179 MouseArea { anchors.fill: parent; onClicked: root.close() }
180 }
181 Rectangle {
182+ objectName: "minimizeWindowButton"
183 height: parent.height; width: height; radius: height / 2
184 gradient: Gradient {
185 GradientStop { color: "#92918C"; position: 0 }
186@@ -48,6 +50,7 @@
187 MouseArea { anchors.fill: parent; onClicked: root.minimize() }
188 }
189 Rectangle {
190+ objectName: "maximizeWindowButton"
191 height: parent.height; width: height; radius: height / 2
192 gradient: Gradient {
193 GradientStop { color: "#92918C"; position: 0 }
194
195=== modified file 'qml/Panel/Panel.qml'
196--- qml/Panel/Panel.qml 2015-09-29 12:48:46 +0000
197+++ qml/Panel/Panel.qml 2015-11-03 11:00:18 +0000
198@@ -28,9 +28,16 @@
199 property alias callHint: __callHint
200 property bool fullscreenMode: false
201 property real indicatorAreaShowProgress: 1.0
202+ property bool locked: false
203
204 opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.0
205
206+ Binding {
207+ target: PanelState
208+ property: "panelHeight"
209+ value: root.panelHeight
210+ }
211+
212 Rectangle {
213 id: darkenedArea
214 property real darkenedOpacity: 0.6
215@@ -136,7 +143,7 @@
216 }
217
218 shown: false
219- width: root.width - (PanelState.buttonsVisible ? windowControlButtons.width : 0)
220+ width: root.width - (windowControlButtons.visible ? windowControlButtons.width + titleLabel.width : 0)
221 minimizedPanelHeight: units.gu(3)
222 expandedPanelHeight: units.gu(7)
223 openedHeight: root.height - indicatorOrangeLine.height
224@@ -164,18 +171,36 @@
225
226 WindowControlButtons {
227 id: windowControlButtons
228+ objectName: "panelWindowControlButtons"
229 anchors {
230 left: parent.left
231 top: parent.top
232 margins: units.gu(0.7)
233 }
234 height: indicators.minimizedPanelHeight - anchors.margins * 2
235- visible: PanelState.buttonsVisible
236+ visible: PanelState.buttonsVisible && !root.locked
237 onClose: PanelState.close()
238 onMinimize: PanelState.minimize()
239 onMaximize: PanelState.maximize()
240 }
241
242+ Label {
243+ id: titleLabel
244+ objectName: "windowDecorationTitle"
245+ anchors {
246+ left: windowControlButtons.right
247+ top: parent.top
248+ margins: units.gu(0.7)
249+ }
250+ color: "#DFDBD2"
251+ height: windowControlButtons.height
252+ visible: windowControlButtons.visible
253+ verticalAlignment: Text.AlignVCenter
254+ fontSize: "small"
255+ font.bold: true
256+ text: PanelState.title
257+ }
258+
259 PanelSeparatorLine {
260 id: indicatorOrangeLine
261 anchors {
262@@ -189,7 +214,7 @@
263 id: __callHint
264 anchors {
265 top: parent.top
266- left: PanelState.buttonsVisible ? windowControlButtons.right : parent.left
267+ left: windowControlButtons.visible ? windowControlButtons.right : parent.left
268 }
269 height: indicators.minimizedPanelHeight
270 visible: active && indicators.state == "initial"
271
272=== modified file 'qml/Shell.qml'
273--- qml/Shell.qml 2015-10-16 17:11:54 +0000
274+++ qml/Shell.qml 2015-11-03 11:00:18 +0000
275@@ -57,6 +57,7 @@
276 property bool beingResized
277 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
278 property string mode: "full-greeter"
279+ property bool cursorVisible: false
280 function updateFocusedAppOrientation() {
281 applicationsDisplayLoader.item.updateFocusedAppOrientation();
282 }
283@@ -536,6 +537,7 @@
284
285 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
286 || greeter.hasLockedApp
287+ locked: greeter && greeter.active
288 }
289
290 Launcher {
291
292=== modified file 'qml/Stages/DecoratedWindow.qml'
293--- qml/Stages/DecoratedWindow.qml 2015-09-29 23:09:06 +0000
294+++ qml/Stages/DecoratedWindow.qml 2015-11-03 11:00:18 +0000
295@@ -26,6 +26,7 @@
296 property alias window: applicationWindow
297 property alias application: applicationWindow.application
298 property alias active: decoration.active
299+ property alias title: decoration.title
300
301 property bool decorationShown: true
302 property bool highlightShown: false
303@@ -66,7 +67,8 @@
304 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
305 anchors { left: parent.left; top: parent.top; right: parent.right }
306 height: units.gu(3)
307- title: window.title !== "" ? window.title : model.name
308+ width: root.width
309+ title: window.title !== "" ? window.title : ""
310 onClose: root.close();
311 onMaximize: root.maximize();
312 onMinimize: root.minimize();
313
314=== modified file 'qml/Stages/DesktopSpread.qml'
315--- qml/Stages/DesktopSpread.qml 2015-09-21 12:22:29 +0000
316+++ qml/Stages/DesktopSpread.qml 2015-11-03 11:00:18 +0000
317@@ -87,7 +87,7 @@
318 function focusSelected() {
319 if (spreadRepeater.highlightedIndex != -1) {
320 var application = ApplicationManager.get(spreadRepeater.highlightedIndex);
321- ApplicationManager.focusApplication(application.appId);
322+ ApplicationManager.requestFocusApplication(application.appId);
323 }
324 }
325
326
327=== modified file 'qml/Stages/DesktopSpreadDelegate.qml'
328--- qml/Stages/DesktopSpreadDelegate.qml 2015-09-18 11:03:48 +0000
329+++ qml/Stages/DesktopSpreadDelegate.qml 2015-11-03 11:00:18 +0000
330@@ -29,8 +29,8 @@
331 property bool highlightShown: false
332 property real shadowOpacity: 1
333
334- property int windowWidth: application.session && application.session.surface ? application.session.surface.size.width : 0
335- property int windowHeight: application.session && application.session.surface ? application.session.surface.size.height : 0
336+ property int windowWidth: application && application.session && application.session.surface ? application.session.surface.size.width : 0
337+ property int windowHeight: application && application.session && application.session.surface ? application.session.surface.size.height : 0
338
339 state: "normal"
340 states: [
341
342=== modified file 'qml/Stages/DesktopStage.qml'
343--- qml/Stages/DesktopStage.qml 2015-10-19 14:27:57 +0000
344+++ qml/Stages/DesktopStage.qml 2015-11-03 11:00:18 +0000
345@@ -24,6 +24,7 @@
346 import "../Components/PanelState"
347 import Utils 0.1
348 import Ubuntu.Gestures 0.1
349+import GlobalShortcut 1.0
350
351 Rectangle {
352 id: root
353@@ -63,21 +64,72 @@
354 spread.state = "";
355 }
356
357- ApplicationManager.requestFocusApplication(appId)
358+ ApplicationManager.focusApplication(appId);
359+ }
360+
361+ onApplicationRemoved: {
362+ priv.focusNext();
363 }
364
365 onFocusRequested: {
366 var appIndex = priv.indexOf(appId);
367 var appDelegate = appRepeater.itemAt(appIndex);
368- appDelegate.minimized = false;
369- ApplicationManager.focusApplication(appId)
370+ appDelegate.restoreFromMinimized();
371
372 if (spread.state == "altTab") {
373- spread.cancel()
374+ spread.cancel();
375 }
376 }
377 }
378
379+ GlobalShortcut {
380+ id: closeWindowShortcut
381+ shortcut: Qt.AltModifier|Qt.Key_F4
382+ onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
383+ active: priv.focusedAppId !== ""
384+ }
385+
386+ GlobalShortcut {
387+ id: showSpreadShortcut
388+ shortcut: Qt.MetaModifier|Qt.Key_W
389+ onTriggered: spread.state = "altTab"
390+ }
391+
392+ GlobalShortcut {
393+ id: minimizeAllShortcut
394+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
395+ onTriggered: priv.minimizeAllWindows()
396+ }
397+
398+ GlobalShortcut {
399+ id: maximizeWindowShortcut
400+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
401+ onTriggered: priv.focusedAppDelegate.maximize()
402+ active: priv.focusedAppDelegate !== null
403+ }
404+
405+ GlobalShortcut {
406+ id: maximizeWindowLeftShortcut
407+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
408+ onTriggered: priv.focusedAppDelegate.maximizeLeft()
409+ active: priv.focusedAppDelegate !== null
410+ }
411+
412+ GlobalShortcut {
413+ id: maximizeWindowRightShortcut
414+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
415+ onTriggered: priv.focusedAppDelegate.maximizeRight()
416+ active: priv.focusedAppDelegate !== null
417+ }
418+
419+ GlobalShortcut {
420+ id: minimizeRestoreShortcut
421+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
422+ onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight
423+ ? priv.focusedAppDelegate.restore() : priv.focusedAppDelegate.minimize(true)
424+ active: priv.focusedAppDelegate !== null
425+ }
426+
427 QtObject {
428 id: priv
429
430@@ -95,6 +147,28 @@
431 }
432 return -1;
433 }
434+
435+ function minimizeAllWindows() {
436+ for (var i = 0; i < appRepeater.count; i++) {
437+ var appDelegate = appRepeater.itemAt(i);
438+ if (appDelegate && !appDelegate.minimized) {
439+ appDelegate.minimize(false); // minimize but don't switch focus
440+ }
441+ }
442+
443+ ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
444+ }
445+
446+ function focusNext() {
447+ ApplicationManager.unfocusCurrentApplication();
448+ for (var i = 0; i < appRepeater.count; i++) {
449+ var appDelegate = appRepeater.itemAt(i);
450+ if (appDelegate && !appDelegate.minimized) {
451+ ApplicationManager.focusApplication(appDelegate.appId);
452+ return;
453+ }
454+ }
455+ }
456 }
457
458 Connections {
459@@ -102,15 +176,24 @@
460 onClose: {
461 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
462 }
463- onMinimize: appRepeater.itemAt(0).minimize();
464- onMaximize: appRepeater.itemAt(0).unmaximize();
465+ onMinimize: appRepeater.itemAt(0).minimize(true);
466+ onMaximize: appRepeater.itemAt(0).restore();
467 }
468
469 Binding {
470 target: PanelState
471 property: "buttonsVisible"
472- value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
473- }
474+ value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized
475+ }
476+
477+ Binding {
478+ target: PanelState
479+ property: "title"
480+ value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.title
481+ when: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
482+ }
483+
484+ Component.onDestruction: PanelState.buttonsVisible = false;
485
486 FocusScope {
487 id: appContainer
488@@ -132,18 +215,32 @@
489
490 delegate: FocusScope {
491 id: appDelegate
492+ objectName: "appDelegate_" + appId
493 z: ApplicationManager.count - index
494 y: units.gu(3)
495 width: units.gu(60)
496 height: units.gu(50)
497- focus: model.appId === priv.focusedAppId
498-
499- property bool maximized: false
500- property bool minimized: false
501+ focus: appId === priv.focusedAppId
502+
503+ QtObject {
504+ id: appDelegatePrivate
505+ property bool maximized: false
506+ property bool maximizedLeft: false
507+ property bool maximizedRight: false
508+ property bool minimized: false
509+ }
510+ readonly property alias maximized: appDelegatePrivate.maximized
511+ readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
512+ readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
513+ readonly property alias minimized: appDelegatePrivate.minimized
514+
515+ readonly property string appId: model.appId
516+ property bool animationsEnabled: true
517+ property alias title: decoratedWindow.title
518
519 onFocusChanged: {
520- if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
521- ApplicationManager.focusApplication(model.appId);
522+ if (focus && ApplicationManager.focusedApplicationId !== appId) {
523+ ApplicationManager.focusApplication(appId);
524 }
525 }
526
527@@ -158,44 +255,92 @@
528 value: ApplicationInfoInterface.RequestedRunning // Always running for now
529 }
530
531- function maximize() {
532- minimized = false;
533- maximized = true;
534- }
535- function minimize() {
536- maximized = false;
537- minimized = true;
538- }
539- function unmaximize() {
540- minimized = false;
541- maximized = false;
542+ function maximize(animated) {
543+ animationsEnabled = (animated === undefined) || animated;
544+ appDelegatePrivate.minimized = false;
545+ appDelegatePrivate.maximized = true;
546+ appDelegatePrivate.maximizedLeft = false;
547+ appDelegatePrivate.maximizedRight = false;
548+ }
549+ function maximizeLeft() {
550+ appDelegatePrivate.minimized = false;
551+ appDelegatePrivate.maximized = false;
552+ appDelegatePrivate.maximizedLeft = true;
553+ appDelegatePrivate.maximizedRight = false;
554+ }
555+ function maximizeRight() {
556+ appDelegatePrivate.minimized = false;
557+ appDelegatePrivate.maximized = false;
558+ appDelegatePrivate.maximizedLeft = false;
559+ appDelegatePrivate.maximizedRight = true;
560+ }
561+ function minimize(animated) {
562+ animationsEnabled = (animated === undefined) || animated;
563+ appDelegatePrivate.minimized = true;
564+ }
565+ function restore(animated) {
566+ animationsEnabled = (animated === undefined) || animated;
567+ appDelegatePrivate.minimized = false;
568+ appDelegatePrivate.maximized = false;
569+ appDelegatePrivate.maximizedLeft = false;
570+ appDelegatePrivate.maximizedRight = false;
571+ }
572+ function restoreFromMinimized(animated) {
573+ animationsEnabled = (animated === undefined) || animated;
574+ appDelegatePrivate.minimized = false;
575+ if (maximized)
576+ maximize();
577+ else if (maximizedLeft)
578+ maximizeLeft();
579+ else if (maximizedRight)
580+ maximizeRight();
581+ ApplicationManager.focusApplication(appId);
582 }
583
584 states: [
585 State {
586 name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized
587+ && !appDelegate.maximizedLeft && !appDelegate.maximizedRight
588 },
589 State {
590- name: "maximized"; when: appDelegate.maximized
591+ name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
592 PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
593 },
594 State {
595+ name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
596+ PropertyChanges { target: appDelegate; x: 0; y: units.gu(3); width: root.width/2; height: root.height - units.gu(3) }
597+ },
598+ State {
599+ name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
600+ PropertyChanges { target: appDelegate; x: root.width/2; y: units.gu(3); width: root.width/2; height: root.height - units.gu(3) }
601+ },
602+ State {
603 name: "minimized"; when: appDelegate.minimized
604 PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
605 }
606 ]
607 transitions: [
608 Transition {
609- from: "maximized,minimized,normal,"
610- to: "maximized,minimized,normal,"
611- PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
612+ from: "maximized,maximizedLeft,maximizedRight,minimized,normal,"
613+ to: "maximized,maximizedLeft,maximizedRight,minimized,normal,"
614+ enabled: appDelegate.animationsEnabled
615+ SequentialAnimation {
616+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale"; duration: UbuntuAnimation.FastDuration }
617+ ScriptAction {
618+ script: {
619+ if (animationsEnabled && state === "minimized" ) {
620+ priv.focusNext();
621+ }
622+ }
623+ }
624+ }
625 },
626 Transition {
627 from: ""
628 to: "altTab"
629 PropertyAction { target: appDelegate; properties: "y,angle,z,itemScale,itemScaleOriginY" }
630 PropertyAction { target: decoratedWindow; properties: "anchors.topMargin" }
631- PropertyAnimation {
632+ UbuntuNumberAnimation {
633 target: appDelegate; properties: "x"
634 from: root.width
635 duration: rightEdgePushArea.containsMouse ? UbuntuAnimation.FastDuration :0
636@@ -218,6 +363,8 @@
637 minHeight: units.gu(10)
638 borderThickness: units.gu(2)
639 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
640+ screenWidth: root.width
641+ screenHeight: root.height
642
643 onPressed: { ApplicationManager.focusApplication(model.appId) }
644 }
645@@ -234,8 +381,9 @@
646 focus: true
647
648 onClose: ApplicationManager.stopApplication(model.appId)
649- onMaximize: appDelegate.maximize()
650- onMinimize: appDelegate.minimize()
651+ onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
652+ ? appDelegate.restore() : appDelegate.maximize()
653+ onMinimize: appDelegate.minimize(true)
654 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
655 }
656 }
657
658=== modified file 'qml/Stages/WindowDecoration.qml'
659--- qml/Stages/WindowDecoration.qml 2015-10-19 19:05:23 +0000
660+++ qml/Stages/WindowDecoration.qml 2015-11-03 11:00:18 +0000
661@@ -18,6 +18,7 @@
662 import Unity.Application 0.1 // For Mir singleton
663 import Ubuntu.Components 1.1
664 import "../Components"
665+import "../Components/PanelState"
666
667 MouseArea {
668 id: root
669@@ -45,17 +46,18 @@
670 priv.distanceX = pos.x;
671 priv.distanceY = pos.y;
672 priv.dragging = true;
673- Mir.cursorName = "grabbing";
674 } else {
675 priv.dragging = false;
676 Mir.cursorName = "";
677 }
678 }
679+
680 onPositionChanged: {
681 if (priv.dragging) {
682+ Mir.cursorName = "grabbing";
683 var pos = mapToItem(root.target.parent, mouseX, mouseY);
684 root.target.x = pos.x - priv.distanceX;
685- root.target.y = pos.y - priv.distanceY;
686+ root.target.y = Math.max(pos.y - priv.distanceY, PanelState.panelHeight);
687 }
688 }
689
690@@ -70,11 +72,12 @@
691 }
692
693 Row {
694- anchors { left: parent.left; top: parent.top; bottom: parent.bottom; margins: units.gu(0.7) }
695+ anchors { fill: parent; margins: units.gu(0.7) }
696 spacing: units.gu(1)
697 opacity: root.active ? 1 : 0.5
698
699 WindowControlButtons {
700+ id: buttons
701 height: parent.height
702 onClose: root.close();
703 onMinimize: root.minimize();
704@@ -86,9 +89,11 @@
705 objectName: "windowDecorationTitle"
706 color: "#DFDBD2"
707 height: parent.height
708+ width: parent.width - buttons.width - parent.anchors.rightMargin - parent.anchors.leftMargin
709 verticalAlignment: Text.AlignVCenter
710 fontSize: "small"
711 font.bold: true
712+ elide: Text.ElideRight
713 }
714 }
715 }
716
717=== modified file 'qml/Stages/WindowResizeArea.qml'
718--- qml/Stages/WindowResizeArea.qml 2015-09-29 12:48:46 +0000
719+++ qml/Stages/WindowResizeArea.qml 2015-11-03 11:00:18 +0000
720@@ -18,6 +18,7 @@
721 import Ubuntu.Components 1.1
722 import Utils 0.1
723 import Unity.Application 0.1 // for Mir.cursorName
724+import "../Components/PanelState"
725
726 MouseArea {
727 id: root
728@@ -35,19 +36,52 @@
729 property int borderThickness: 0
730 property int minWidth: 0
731 property int minHeight: 0
732+ property int screenWidth: 0
733+ property int screenHeight: 0
734+
735+ QtObject {
736+ id: priv
737+
738+ property int normalX: 0
739+ property int normalY: 0
740+ property int normalWidth: 0
741+ property int normalHeight: 0
742+
743+ function updateNormalGeometry() {
744+ if (root.target.state == "normal") {
745+ normalX = root.target.x
746+ normalY = root.target.y
747+ normalWidth = root.target.width
748+ normalHeight = root.target.height
749+ }
750+ }
751+ }
752+
753+ Connections {
754+ target: root.target
755+ onXChanged: priv.updateNormalGeometry();
756+ onYChanged: priv.updateNormalGeometry();
757+ onWidthChanged: priv.updateNormalGeometry();
758+ onHeightChanged: priv.updateNormalGeometry();
759+ }
760
761 Component.onCompleted: {
762- var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
763- if (windowState !== undefined) {
764- target.x = windowState.x
765- target.y = windowState.y
766- target.width = windowState.width
767- target.height = windowState.height
768+ var windowGeometry = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
769+ if (windowGeometry !== undefined) {
770+ target.width = Math.min(windowGeometry.width, root.screenWidth)
771+ target.height = Math.min(windowGeometry.height, root.screenHeight - PanelState.panelHeight)
772+ target.x = Math.max(Math.min(windowGeometry.x, root.screenWidth - target.width), 0)
773+ target.y = Math.max(Math.min(windowGeometry.y, root.screenHeight - target.height), PanelState.panelHeight)
774+ }
775+ var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
776+ if (windowState === WindowStateStorage.WindowStateMaximized) {
777+ target.maximize(false)
778 }
779 }
780
781 Component.onDestruction: {
782- windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
783+ windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
784+ windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
785 }
786
787 QtObject {
788
789=== modified file 'tests/mocks/Utils/windowstatestorage.cpp'
790--- tests/mocks/Utils/windowstatestorage.cpp 2015-08-24 15:39:53 +0000
791+++ tests/mocks/Utils/windowstatestorage.cpp 2015-11-03 11:00:18 +0000
792@@ -44,3 +44,20 @@
793 if (!m_geometry.contains(windowId)) return defaultValue;
794 return m_geometry.value(windowId).toRect();
795 }
796+
797+void WindowStateStorage::clear()
798+{
799+ m_state.clear();
800+ m_geometry.clear();
801+}
802+
803+void WindowStateStorage::saveState(const QString &windowId, WindowState state)
804+{
805+ m_state[windowId] = state;
806+}
807+
808+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
809+{
810+ if (!m_state.contains(windowId)) return defaultValue;
811+ return m_state.value(windowId);
812+}
813
814=== modified file 'tests/mocks/Utils/windowstatestorage.h'
815--- tests/mocks/Utils/windowstatestorage.h 2015-08-24 15:39:53 +0000
816+++ tests/mocks/Utils/windowstatestorage.h 2015-11-03 11:00:18 +0000
817@@ -22,12 +22,23 @@
818 {
819 Q_OBJECT
820 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
821+ Q_ENUMS(WindowState)
822 public:
823+ enum WindowState {
824+ WindowStateNormal,
825+ WindowStateMaximized
826+ };
827 WindowStateStorage(QObject *parent = 0);
828
829+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
830+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
831+
832 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
833 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
834
835+ // Only in the mock, to easily restore a fresh state
836+ Q_INVOKABLE void clear();
837+
838 Q_SIGNALS:
839 void geometryChanged(const QVariantMap& geometry);
840
841@@ -35,5 +46,6 @@
842 void setGeometry(const QVariantMap& geometry);
843 QVariantMap geometry() const;
844
845+ QHash<QString, WindowState> m_state;
846 QVariantMap m_geometry;
847 };
848
849=== modified file 'tests/qmltests/Stages/tst_DesktopStage.qml'
850--- tests/qmltests/Stages/tst_DesktopStage.qml 2015-09-17 12:25:29 +0000
851+++ tests/qmltests/Stages/tst_DesktopStage.qml 2015-11-03 11:00:18 +0000
852@@ -53,14 +53,10 @@
853
854 focus: true
855
856- property bool itemDestroyed: false
857 sourceComponent: Component {
858 DesktopStage {
859 color: "darkblue"
860 anchors.fill: parent
861- Component.onDestruction: {
862- desktopStageLoader.itemDestroyed = true;
863- }
864 }
865 }
866 }
867@@ -95,16 +91,10 @@
868 property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null
869
870 function cleanup() {
871- desktopStageLoader.itemDestroyed = false;
872 desktopStageLoader.active = false;
873
874 tryCompare(desktopStageLoader, "status", Loader.Null);
875 tryCompare(desktopStageLoader, "item", null);
876- // Loader.status might be Loader.Null and Loader.item might be null but the Loader
877- // actually took place. Likely because Loader waits until the next event loop
878- // iteration to do its work. So to ensure the reload, we will wait until the
879- // Shell instance gets destroyed.
880- tryCompare(desktopStageLoader, "itemDestroyed", true);
881
882 killAllRunningApps();
883
884@@ -117,7 +107,7 @@
885 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
886 ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
887 }
888- compare(ApplicationManager.count, 1)
889+ compare(ApplicationManager.count, 1);
890 }
891
892 function waitUntilAppSurfaceShowsUp(appId) {
893@@ -147,10 +137,7 @@
894 }
895
896 function test_appFocusSwitch(data) {
897- var i;
898- for (i = 0; i < data.apps.length; i++) {
899- startApplication(data.apps[i]);
900- }
901+ data.apps.forEach(startApplication);
902
903 ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]);
904 tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
905@@ -167,10 +154,7 @@
906 }
907
908 function test_tappingOnWindowChangesFocusedApp(data) {
909- var i;
910- for (i = 0; i < data.apps.length; i++) {
911- startApplication(data.apps[i]);
912- }
913+ data.apps.forEach(startApplication);
914 var fromAppId = data.apps[data.focusfrom];
915 var toAppId = data.apps[data.focusTo]
916
917@@ -187,18 +171,37 @@
918 compare(ApplicationManager.focusedApplicationId, toAppId);
919 }
920
921+ function test_clickingOnWindowChangesFocusedApp_data() {
922+ return test_tappingOnWindowChangesFocusedApp_data(); // reuse test data
923+ }
924+
925+ function test_clickingOnWindowChangesFocusedApp(data) {
926+ data.apps.forEach(startApplication);
927+ var fromAppId = data.apps[data.focusfrom];
928+ var toAppId = data.apps[data.focusTo]
929+
930+ var fromAppWindow = findChild(desktopStage, "appWindow_" + fromAppId);
931+ verify(fromAppWindow);
932+ mouseClick(fromAppWindow);
933+ compare(fromAppWindow.application.session.surface.activeFocus, true);
934+ compare(ApplicationManager.focusedApplicationId, fromAppId);
935+
936+ var toAppWindow = findChild(desktopStage, "appWindow_" + toAppId);
937+ verify(toAppWindow);
938+ mouseClick(toAppWindow);
939+ compare(toAppWindow.application.session.surface.activeFocus, true);
940+ compare(ApplicationManager.focusedApplicationId, toAppId);
941+ }
942+
943 function test_tappingOnDecorationFocusesApplication_data() {
944 return [
945- {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
946- {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
947+ {tag: "dash to dialer", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
948+ {tag: "dialer to dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
949 ]
950 }
951
952 function test_tappingOnDecorationFocusesApplication(data) {
953- var i;
954- for (i = 0; i < data.apps.length; i++) {
955- startApplication(data.apps[i]);
956- }
957+ data.apps.forEach(startApplication);
958
959 var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
960 verify(fromAppDecoration);
961@@ -210,5 +213,109 @@
962 tap(toAppDecoration);
963 tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
964 }
965+
966+ function test_clickingOnDecorationFocusesApplication_data() {
967+ return test_tappingOnDecorationFocusesApplication_data(); // reuse test data
968+ }
969+
970+ function test_clickingOnDecorationFocusesApplication(data) {
971+ data.apps.forEach(startApplication);
972+
973+ var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
974+ verify(fromAppDecoration);
975+ mouseClick(fromAppDecoration);
976+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
977+
978+ var toAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusTo]);
979+ verify(toAppDecoration);
980+ mouseClick(toAppDecoration);
981+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
982+ }
983+
984+ function test_windowMaximize() {
985+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
986+ apps.forEach(startApplication);
987+ var appName = "dialer-app";
988+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
989+ verify(appDelegate);
990+ ApplicationManager.focusApplication(appName);
991+ keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
992+ tryCompare(appDelegate, "maximized", true);
993+ tryCompare(appDelegate, "minimized", false);
994+ }
995+
996+ function test_windowMaximizeLeft() {
997+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
998+ apps.forEach(startApplication);
999+ var appName = "dialer-app";
1000+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
1001+ verify(appDelegate);
1002+ ApplicationManager.focusApplication(appName);
1003+ keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
1004+ tryCompare(appDelegate, "maximized", false);
1005+ tryCompare(appDelegate, "minimized", false);
1006+ tryCompare(appDelegate, "maximizedLeft", true);
1007+ tryCompare(appDelegate, "maximizedRight", false);
1008+ }
1009+
1010+ function test_windowMaximizeRight() {
1011+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
1012+ apps.forEach(startApplication);
1013+ var appName = "dialer-app";
1014+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
1015+ verify(appDelegate);
1016+ ApplicationManager.focusApplication(appName);
1017+ keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
1018+ tryCompare(appDelegate, "maximized", false);
1019+ tryCompare(appDelegate, "minimized", false);
1020+ tryCompare(appDelegate, "maximizedLeft", false);
1021+ tryCompare(appDelegate, "maximizedRight", true);
1022+ }
1023+
1024+ function test_windowMinimize() {
1025+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
1026+ apps.forEach(startApplication);
1027+ var appName = "dialer-app";
1028+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
1029+ verify(appDelegate);
1030+ ApplicationManager.focusApplication(appName);
1031+ keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
1032+ tryCompare(appDelegate, "maximized", false);
1033+ tryCompare(appDelegate, "minimized", true);
1034+ verify(ApplicationManager.focusedApplicationId != ""); // verify we don't lose focus when minimizing an app
1035+ }
1036+
1037+ function test_windowMinimizeAll() {
1038+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
1039+ apps.forEach(startApplication);
1040+ verify(ApplicationManager.count == 3);
1041+ keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
1042+ tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify no app is focused
1043+ }
1044+
1045+ function test_windowClose() {
1046+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
1047+ apps.forEach(startApplication);
1048+ verify(ApplicationManager.count == 3);
1049+ var appName = "dialer-app";
1050+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
1051+ verify(appDelegate);
1052+ ApplicationManager.focusApplication(appName);
1053+ keyClick(Qt.Key_F4, Qt.AltModifier); // Alt+F4 shortcut to close
1054+ verify(ApplicationManager.count == 2); // verify the app is gone
1055+ verify(ApplicationManager.findApplication(appName) === null); // and it's not in running apps
1056+ }
1057+
1058+ function test_smashCursorKeys() {
1059+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
1060+ apps.forEach(startApplication);
1061+ verify(ApplicationManager.count == 3);
1062+ keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
1063+ tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify no app is focused
1064+
1065+ // now try pressing all 4 arrow keys + ctrl + meta
1066+ keyClick(Qt.Key_Up | Qt.Key_Down | Qt.Key_Left | Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // smash it!!!
1067+ tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify still no app is focused
1068+ }
1069 }
1070 }
1071
1072=== modified file 'tests/qmltests/Stages/tst_WindowResizeArea.qml'
1073--- tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-10-08 14:27:12 +0000
1074+++ tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-11-03 11:00:18 +0000
1075@@ -19,6 +19,7 @@
1076 import QtTest 1.0
1077 import Unity.Test 0.1
1078 import ".."
1079+import "../../../qml/Components/PanelState"
1080 import "../../../qml/Stages"
1081 import Ubuntu.Components 0.1
1082 import Ubuntu.Components.ListItems 1.0 as ListItem
1083@@ -31,6 +32,12 @@
1084
1085 property var fakeWindow: windowLoader.item
1086
1087+ Binding {
1088+ target: PanelState
1089+ property: "panelHeight"
1090+ value: units.gu(2)
1091+ }
1092+
1093 Component {
1094 id: fakeWindowComponent
1095
1096@@ -44,8 +51,11 @@
1097 width: units.gu(20)
1098 property int windowHeight: height
1099 property int windowWidth: width
1100- onWindowHeightChanged: height = windowHeight
1101- onWindowWidthChanged: width = windowWidth
1102+ state: "normal"
1103+
1104+ function maximize() {
1105+ state = "maximized"
1106+ }
1107
1108 WindowResizeArea {
1109 id: windowResizeArea
1110@@ -54,6 +64,8 @@
1111 minWidth: units.gu(15)
1112 minHeight: units.gu(10)
1113 windowId: "test-window-id"
1114+ screenWidth: root.width
1115+ screenHeight: root.height
1116 }
1117
1118 Rectangle {
1119@@ -195,5 +207,69 @@
1120 tryCompare(fakeWindow, "width", initialWindowWidth);
1121 tryCompare(fakeWindow, "height", initialWindowHeight);
1122 }
1123+
1124+ function test_saveRestoreMaximized() {
1125+ var initialWindowX = fakeWindow.x;
1126+ var initialWindowY = fakeWindow.y;
1127+
1128+ var moveDelta = units.gu(5);
1129+
1130+ fakeWindow.x = initialWindowX + moveDelta
1131+ fakeWindow.y = initialWindowY + moveDelta
1132+
1133+ // Now change the state to maximized. The window should not keep updating the stored values
1134+ fakeWindow.state = "maximized"
1135+ fakeWindow.x = 31415 // 0 is too risky to pass the test even when broken
1136+ fakeWindow.y = 31415
1137+
1138+ // This will destroy the window and recreate it
1139+ windowLoader.active = false;
1140+ waitForRendering(root);
1141+ windowLoader.active = true;
1142+
1143+ // Make sure it's again where we left it in normal state before destroying
1144+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
1145+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
1146+
1147+ // Make sure maximize() has been called after restoring
1148+ tryCompare(fakeWindow, "state", "maximized")
1149+
1150+ // clean up
1151+ fakeWindow.state = "normal"
1152+ }
1153+
1154+
1155+ function test_restoreMovesIntoBounds_data() {
1156+ return [
1157+ {tag: "left off", x: -units.gu(5), y: units.gu(5), w: units.gu(10), h: units.gu(10)},
1158+ {tag: "top off", x: units.gu(5), y: -units.gu(5), w: units.gu(10), h: units.gu(10)},
1159+ {tag: "right off", x: root.width - units.gu(5), y: units.gu(5), w: units.gu(10), h: units.gu(10)},
1160+ {tag: "bottom off", x: units.gu(5), y: root.height - units.gu(5), w: units.gu(10), h: units.gu(10)},
1161+ {tag: "width too large", x: units.gu(5), y: units.gu(5), w: root.width * 2, h: units.gu(10)},
1162+ {tag: "height too large", x: units.gu(5), y: units.gu(5), w: units.gu(10), h: root.height * 2}
1163+ ]
1164+ }
1165+
1166+ function test_restoreMovesIntoBounds(data) {
1167+ fakeWindow.x = data.x;
1168+ fakeWindow.y = data.y;
1169+ fakeWindow.width = data.w;
1170+ fakeWindow.height = data.h;
1171+ waitForRendering(root);
1172+
1173+ // This will destroy the window and recreate it
1174+ windowLoader.active = false;
1175+ waitForRendering(root);
1176+ windowLoader.active = true;
1177+ waitForRendering(root)
1178+
1179+ // Make sure it's again where we left it in normal state before destroying
1180+ compare(fakeWindow.x >= 0, true)
1181+ compare(fakeWindow.y >= PanelState.panelHeight, true)
1182+ compare(fakeWindow.x + fakeWindow.width <= root.width, true)
1183+ compare(fakeWindow.y + fakeWindow.height <= root.height, true)
1184+
1185+ waitForRendering(root)
1186+ }
1187 }
1188 }
1189
1190=== modified file 'tests/qmltests/tst_Shell.qml'
1191--- tests/qmltests/tst_Shell.qml 2015-10-20 08:10:03 +0000
1192+++ tests/qmltests/tst_Shell.qml 2015-11-03 11:00:18 +0000
1193@@ -29,8 +29,10 @@
1194 import Unity.Test 0.1
1195 import Powerd 0.1
1196 import Wizard 0.1 as Wizard
1197+import Utils 0.1
1198
1199 import "../../qml"
1200+import "../../qml/Components/PanelState"
1201 import "Stages"
1202
1203 Rectangle {
1204@@ -302,6 +304,7 @@
1205 mouseEmulation.checked = true;
1206 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
1207 tearDown();
1208+ WindowStateStorage.clear();
1209 }
1210
1211 function loadShell(formFactor) {
1212@@ -329,9 +332,9 @@
1213 var app2 = ApplicationManager.startApplication("webbrowser-app")
1214 var app3 = ApplicationManager.startApplication("camera-app")
1215 var app4 = ApplicationManager.startApplication("facebook-webapp")
1216+ var app7 = ApplicationManager.startApplication("camera-app")
1217+ var app6 = ApplicationManager.startApplication("gallery-app")
1218 var app5 = ApplicationManager.startApplication("calendar-app")
1219- var app6 = ApplicationManager.startApplication("gallery-app")
1220- var app7 = ApplicationManager.startApplication("camera-app")
1221 waitUntilAppWindowIsFullyLoaded(app7);
1222 }
1223
1224@@ -1693,11 +1696,11 @@
1225 keyPress(Qt.Key_Control)
1226 keyClick(Qt.Key_Tab);
1227
1228-
1229 tryCompare(desktopSpread, "state", "altTab")
1230
1231- mouseMove(shell, 0, 0);
1232+ mouseMove(shell, 0, shell.height / 2);
1233 tryCompare(launcher, "state", "visibleTemporary")
1234+ waitForRendering(shell)
1235
1236 mouseClick(bfb, bfb.width / 2, bfb.height / 2)
1237 tryCompare(launcher, "state", "")
1238@@ -1707,5 +1710,106 @@
1239
1240 keyRelease(Qt.Key_Control);
1241 }
1242+
1243+ // regression test for http://pad.lv/1443319
1244+ function test_closeMaximizedAndRestart() {
1245+ loadDesktopShellWithApps();
1246+
1247+ var appRepeater = findChild(shell, "appRepeater")
1248+ var appId = ApplicationManager.get(0).appId;
1249+ var appDelegate = appRepeater.itemAt(0);
1250+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1251+
1252+ wait(5000)
1253+ tryCompare(appDelegate, "state", "normal");
1254+ tryCompare(PanelState, "buttonsVisible", false)
1255+
1256+ wait(5000)
1257+ mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1258+ tryCompare(appDelegate, "state", "maximized");
1259+ tryCompare(PanelState, "buttonsVisible", true)
1260+
1261+ ApplicationManager.stopApplication(appId);
1262+ tryCompare(PanelState, "buttonsVisible", false)
1263+
1264+ ApplicationManager.startApplication(appId);
1265+ tryCompare(PanelState, "buttonsVisible", true)
1266+ }
1267+
1268+ // bug http://pad.lv/1431566
1269+ function test_switchToStagedHidesPanelButtons() {
1270+ loadDesktopShellWithApps();
1271+ var appRepeater = findChild(shell, "appRepeater")
1272+ var appId = ApplicationManager.get(0).appId;
1273+ var appDelegate = appRepeater.itemAt(0);
1274+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1275+
1276+ tryCompare(appDelegate, "state", "normal");
1277+ tryCompare(panelButtons, "visible", false);
1278+
1279+ appDelegate.maximize(false);
1280+ tryCompare(panelButtons, "visible", true);
1281+
1282+ shell.usageScenario = "phone";
1283+ waitForRendering(shell);
1284+ tryCompare(panelButtons, "visible", false);
1285+
1286+ shell.usageScenario = "desktop";
1287+ waitForRendering(shell);
1288+ tryCompare(panelButtons, "visible", true);
1289+ }
1290+
1291+ function test_lockingGreeterHidesPanelButtons() {
1292+ loadDesktopShellWithApps();
1293+ var appRepeater = findChild(shell, "appRepeater")
1294+ var appId = ApplicationManager.get(0).appId;
1295+ var appDelegate = appRepeater.itemAt(0);
1296+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1297+
1298+ tryCompare(appDelegate, "state", "normal");
1299+ tryCompare(panelButtons, "visible", false);
1300+
1301+ appDelegate.maximize(false);
1302+ tryCompare(panelButtons, "visible", true);
1303+
1304+ LightDM.Greeter.showGreeter();
1305+ waitForRendering(shell);
1306+ tryCompare(panelButtons, "visible", false);
1307+
1308+ LightDM.Greeter.hideGreeter();
1309+ waitForRendering(shell);
1310+ tryCompare(panelButtons, "visible", true);
1311+ }
1312+
1313+ function test_cantMoveWindowUnderPanel() {
1314+ loadDesktopShellWithApps();
1315+ var appRepeater = findChild(shell, "appRepeater")
1316+ var appDelegate = appRepeater.itemAt(0);
1317+
1318+ mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
1319+ mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
1320+
1321+ compare(appDelegate.y >= PanelState.panelHeight, true);
1322+ }
1323+
1324+ function test_restoreWindowStateFixesIfUnderPanel() {
1325+ loadDesktopShellWithApps();
1326+ var appRepeater = findChild(shell, "appRepeater")
1327+ var appId = ApplicationManager.get(0).appId;
1328+ var appDelegate = appRepeater.itemAt(0);
1329+
1330+ // Move it under the panel programmatically (might happen later with an alt+drag)
1331+ appDelegate.y = -units.gu(10)
1332+
1333+ ApplicationManager.stopApplication(appId)
1334+ ApplicationManager.startApplication(appId)
1335+ waitForRendering(shell)
1336+
1337+ // Make sure the newly started one is at index 0 again
1338+ tryCompare(ApplicationManager.get(0), "appId", appId);
1339+
1340+ appDelegate = appRepeater.itemAt(0);
1341+ compare(appDelegate.y >= PanelState.panelHeight, true);
1342+ }
1343 }
1344 }

Subscribers

People subscribed via source and target branches