Merge lp:~lukas-kde/unity8/activateWindows into lp:unity8

Proposed by Lukáš Tinkl
Status: Superseded
Proposed branch: lp:~lukas-kde/unity8/activateWindows
Merge into: lp:unity8
Prerequisite: lp:~unity-team/unity8/mousePointer
Diff against target: 1013 lines (+523/-77)
14 files modified
plugins/Utils/windowstatestorage.cpp (+62/-23)
plugins/Utils/windowstatestorage.h (+13/-1)
qml/Components/WindowControlButtons.qml (+3/-0)
qml/Panel/Panel.qml (+5/-3)
qml/Shell.qml (+2/-0)
qml/Stages/DesktopSpreadDelegate.qml (+2/-2)
qml/Stages/DesktopStage.qml (+152/-13)
qml/Stages/WindowDecoration.qml (+2/-1)
qml/Stages/WindowResizeArea.qml (+38/-7)
tests/mocks/Utils/windowstatestorage.cpp (+11/-0)
tests/mocks/Utils/windowstatestorage.h (+9/-0)
tests/qmltests/Stages/tst_DesktopStage.qml (+120/-25)
tests/qmltests/Stages/tst_WindowResizeArea.qml (+35/-2)
tests/qmltests/tst_Shell.qml (+69/-0)
To merge this branch: bzr merge lp:~lukas-kde/unity8/activateWindows
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Michael Zanetti Pending
Daniel d'Andrada Pending
Review via email: mp+274903@code.launchpad.net

This proposal supersedes a proposal from 2015-10-19.

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

Commit message

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

Description of the change

Raise/restore windows when activating from the spread, maintain a focus stack in case we minimize/close an app.

Stop displaying the "grabbing" icon when we merely click to focus the app's decoration.

Provide keyboard shortcuts for common window operations

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

https://code.launchpad.net/~unity-team/unity8/mousePointer/+merge/273369 as a 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?

Yes

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

N/A

To post a comment you must log in.
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Please follow the commit message format as explained here: https://wiki.ubuntu.com/Process/Merges/Checklists/Unity8

Revision history for this message
Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal

> Please follow the commit message format as explained here:
> https://wiki.ubuntu.com/Process/Merges/Checklists/Unity8

Should be fine now

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Could you please add qml tests to cover those use cases you mention (ie active focus when clicking on decoration and raise/restore when activation from spread)?

I believe we already have a test for the first one (focus when clicking decoration). We had similar problems in the past before. Should investigate why it passes now even though there's a bug there (maybe it tests only with touches and not with mouse clicks, don't know).

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Oh, and it's worth making lp:~unity-team/unity8/mousePointer from silo 022 a prerequisite as it makes a lot of changes in this code.

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

> > Please follow the commit message format as explained here:
> > https://wiki.ubuntu.com/Process/Merges/Checklists/Unity8
>
> Should be fine now

Yes, thanks!

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2005
http://jenkins.qa.ubuntu.com/job/unity8-ci/6488/
Executed test runs:
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/4701
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-wily-touch/870
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-vivid/1200
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-wily/516
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-vivid-amd64-ci/1095
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-vivid-i386-ci/1096
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-wily-amd64-ci/727
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-wily-i386-ci/728
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-vivid-mako/3794
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/4698
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/4698/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/24339
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-wily-mako/514
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-wily-armhf/870
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-wily-armhf/870/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/24336

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/unity8-ci/6488/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) :
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~lukas-kde/unity8/activateWindows updated
2021. By Lukáš Tinkl

merge trunk

2022. By Lukáš Tinkl

merge lp:~mzanetti/unity8/panel-button-fixes

2023. By Lukáš Tinkl

display the maximized window's title in the panel, next to the buttons

2024. By Lukáš Tinkl

merge prereq to fix failing tests

2025. By Lukáš Tinkl

elide the window title in the title bar

2026. By Lukáš Tinkl

silence warnings

2027. By Lukáš Tinkl

fix issues found by mzanetti

additionally properly restore windows from minimized state to the correct
previous state (maximized, maximizedLeft/Right)

2028. By Lukáš Tinkl

fix restoring the apps from spread to the correct state and size

potential fix for the Alt+F4 shortcut problem

2029. By Lukáš Tinkl

fix click to focus

2030. By Lukáš Tinkl

take the panel height into account when semimaximizing windows

2031. By Lukáš Tinkl

add a test to prove smashing all the 4 cursor keys together does nothing :)

2032. By Lukáš Tinkl

make sure to play the minimized animation before switching focus to next

2033. By Lukáš Tinkl

merge trunk

2034. By Lukáš Tinkl

cleanup

2035. By Lukáš Tinkl

cleanup

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'plugins/Utils/windowstatestorage.cpp'
--- plugins/Utils/windowstatestorage.cpp 2015-09-14 09:11:08 +0000
+++ plugins/Utils/windowstatestorage.cpp 2015-10-26 13:53:58 +0000
@@ -47,26 +47,38 @@
47 m_db.close();47 m_db.close();
48}48}
4949
50void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
51{
52 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
53 .arg(windowId)
54 .arg((int)state);
55
56 saveValue(queryString);
57}
58
59WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
60{
61 const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
62 .arg(windowId);
63
64 QSqlQuery query = getValue(queryString);
65
66 if (!query.first()) {
67 return defaultValue;
68 }
69 return (WindowState)query.value("state").toInt();
70}
71
50void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)72void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
51{73{
52 QMutexLocker mutexLocker(&s_mutex);74 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
53
54 QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
55 .arg(windowId)75 .arg(windowId)
56 .arg(rect.x())76 .arg(rect.x())
57 .arg(rect.y())77 .arg(rect.y())
58 .arg(rect.width())78 .arg(rect.width())
59 .arg(rect.height());79 .arg(rect.height());
6080
61 QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);81 saveValue(queryString);
62 m_asyncQueries.append(future);
63
64 QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
65 futureWatcher->setFuture(future);
66 connect(futureWatcher, &QFutureWatcher<void>::finished,
67 this,
68 [=](){ m_asyncQueries.removeAll(futureWatcher->future());
69 futureWatcher->deleteLater(); });
70}82}
7183
72void WindowStateStorage::executeAsyncQuery(const QString &queryString)84void WindowStateStorage::executeAsyncQuery(const QString &queryString)
@@ -82,20 +94,13 @@
82 }94 }
83}95}
8496
85QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)97QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
86{98{
87 QMutexLocker l(&s_mutex);
88 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")99 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
89 .arg(windowId);100 .arg(windowId);
90 QSqlQuery query;101
91102 QSqlQuery query = getValue(queryString);
92 bool ok = query.exec(queryString);103
93 if (!ok) {
94 qWarning() << "Error retrieving window state for" << windowId
95 << "Driver error:" << query.lastError().driverText()
96 << "Database error:" << query.lastError().databaseText();
97 return defaultValue;
98 }
99 if (!query.first()) {104 if (!query.first()) {
100 return defaultValue;105 return defaultValue;
101 }106 }
@@ -114,4 +119,38 @@
114 QSqlQuery query;119 QSqlQuery query;
115 query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));120 query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
116 }121 }
122
123 if (!m_db.tables().contains("state")) {
124 QSqlQuery query;
125 query.exec("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
126 }
127}
128
129void WindowStateStorage::saveValue(const QString &queryString)
130{
131 QMutexLocker mutexLocker(&s_mutex);
132
133 QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
134 m_asyncQueries.append(future);
135
136 QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
137 futureWatcher->setFuture(future);
138 connect(futureWatcher, &QFutureWatcher<void>::finished,
139 this,
140 [=](){ m_asyncQueries.removeAll(futureWatcher->future());
141 futureWatcher->deleteLater(); });
142}
143
144QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
145{
146 QMutexLocker l(&s_mutex);
147 QSqlQuery query;
148
149 bool ok = query.exec(queryString);
150 if (!ok) {
151 qWarning() << "Error retrieving database query:" << queryString
152 << "Driver error:" << query.lastError().driverText()
153 << "Database error:" << query.lastError().databaseText();
154 }
155 return query;
117}156}
118157
=== modified file 'plugins/Utils/windowstatestorage.h'
--- plugins/Utils/windowstatestorage.h 2015-03-13 19:01:32 +0000
+++ plugins/Utils/windowstatestorage.h 2015-10-26 13:53:58 +0000
@@ -22,16 +22,28 @@
22class WindowStateStorage: public QObject22class WindowStateStorage: public QObject
23{23{
24 Q_OBJECT24 Q_OBJECT
25 Q_ENUMS(WindowState)
25public:26public:
27 enum WindowState {
28 WindowStateNormal,
29 WindowStateMaximized
30 };
31
26 WindowStateStorage(QObject *parent = 0);32 WindowStateStorage(QObject *parent = 0);
27 virtual ~WindowStateStorage();33 virtual ~WindowStateStorage();
2834
35 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
36 Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
37
29 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);38 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
30 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);39 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
3140
32private:41private:
33 void initdb();42 void initdb();
3443
44 void saveValue(const QString &queryString);
45 QSqlQuery getValue(const QString &queryString) const;
46
35 static void executeAsyncQuery(const QString &queryString);47 static void executeAsyncQuery(const QString &queryString);
36 static QMutex s_mutex;48 static QMutex s_mutex;
3749
3850
=== modified file 'qml/Components/WindowControlButtons.qml'
--- qml/Components/WindowControlButtons.qml 2014-11-24 11:21:38 +0000
+++ qml/Components/WindowControlButtons.qml 2015-10-26 13:53:58 +0000
@@ -28,6 +28,7 @@
28 signal maximize()28 signal maximize()
2929
30 Rectangle {30 Rectangle {
31 objectName: "closeWindowButton"
31 height: parent.height; width: height; radius: height / 232 height: parent.height; width: height; radius: height / 2
32 gradient: Gradient {33 gradient: Gradient {
33 GradientStop { color: "#F49073"; position: 0 }34 GradientStop { color: "#F49073"; position: 0 }
@@ -38,6 +39,7 @@
38 MouseArea { anchors.fill: parent; onClicked: root.close() }39 MouseArea { anchors.fill: parent; onClicked: root.close() }
39 }40 }
40 Rectangle {41 Rectangle {
42 objectName: "minimizeWindowButton"
41 height: parent.height; width: height; radius: height / 243 height: parent.height; width: height; radius: height / 2
42 gradient: Gradient {44 gradient: Gradient {
43 GradientStop { color: "#92918C"; position: 0 }45 GradientStop { color: "#92918C"; position: 0 }
@@ -48,6 +50,7 @@
48 MouseArea { anchors.fill: parent; onClicked: root.minimize() }50 MouseArea { anchors.fill: parent; onClicked: root.minimize() }
49 }51 }
50 Rectangle {52 Rectangle {
53 objectName: "maximizeWindowButton"
51 height: parent.height; width: height; radius: height / 254 height: parent.height; width: height; radius: height / 2
52 gradient: Gradient {55 gradient: Gradient {
53 GradientStop { color: "#92918C"; position: 0 }56 GradientStop { color: "#92918C"; position: 0 }
5457
=== modified file 'qml/Panel/Panel.qml'
--- qml/Panel/Panel.qml 2015-09-29 12:48:46 +0000
+++ qml/Panel/Panel.qml 2015-10-26 13:53:58 +0000
@@ -28,6 +28,7 @@
28 property alias callHint: __callHint28 property alias callHint: __callHint
29 property bool fullscreenMode: false29 property bool fullscreenMode: false
30 property real indicatorAreaShowProgress: 1.030 property real indicatorAreaShowProgress: 1.0
31 property bool locked: false
3132
32 opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.033 opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.0
3334
@@ -136,7 +137,7 @@
136 }137 }
137138
138 shown: false139 shown: false
139 width: root.width - (PanelState.buttonsVisible ? windowControlButtons.width : 0)140 width: root.width - (windowControlButtons.visible ? windowControlButtons.width : 0)
140 minimizedPanelHeight: units.gu(3)141 minimizedPanelHeight: units.gu(3)
141 expandedPanelHeight: units.gu(7)142 expandedPanelHeight: units.gu(7)
142 openedHeight: root.height - indicatorOrangeLine.height143 openedHeight: root.height - indicatorOrangeLine.height
@@ -164,13 +165,14 @@
164165
165 WindowControlButtons {166 WindowControlButtons {
166 id: windowControlButtons167 id: windowControlButtons
168 objectName: "panelWindowControlButtons"
167 anchors {169 anchors {
168 left: parent.left170 left: parent.left
169 top: parent.top171 top: parent.top
170 margins: units.gu(0.7)172 margins: units.gu(0.7)
171 }173 }
172 height: indicators.minimizedPanelHeight - anchors.margins * 2174 height: indicators.minimizedPanelHeight - anchors.margins * 2
173 visible: PanelState.buttonsVisible175 visible: PanelState.buttonsVisible && !root.locked
174 onClose: PanelState.close()176 onClose: PanelState.close()
175 onMinimize: PanelState.minimize()177 onMinimize: PanelState.minimize()
176 onMaximize: PanelState.maximize()178 onMaximize: PanelState.maximize()
@@ -189,7 +191,7 @@
189 id: __callHint191 id: __callHint
190 anchors {192 anchors {
191 top: parent.top193 top: parent.top
192 left: PanelState.buttonsVisible ? windowControlButtons.right : parent.left194 left: windowControlButtons.visible ? windowControlButtons.right : parent.left
193 }195 }
194 height: indicators.minimizedPanelHeight196 height: indicators.minimizedPanelHeight
195 visible: active && indicators.state == "initial"197 visible: active && indicators.state == "initial"
196198
=== modified file 'qml/Shell.qml'
--- qml/Shell.qml 2015-10-16 17:11:54 +0000
+++ qml/Shell.qml 2015-10-26 13:53:58 +0000
@@ -57,6 +57,7 @@
57 property bool beingResized57 property bool beingResized
58 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"58 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
59 property string mode: "full-greeter"59 property string mode: "full-greeter"
60 property bool cursorVisible: false
60 function updateFocusedAppOrientation() {61 function updateFocusedAppOrientation() {
61 applicationsDisplayLoader.item.updateFocusedAppOrientation();62 applicationsDisplayLoader.item.updateFocusedAppOrientation();
62 }63 }
@@ -536,6 +537,7 @@
536537
537 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)538 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
538 || greeter.hasLockedApp539 || greeter.hasLockedApp
540 locked: greeter && greeter.active
539 }541 }
540542
541 Launcher {543 Launcher {
542544
=== modified file 'qml/Stages/DesktopSpreadDelegate.qml'
--- qml/Stages/DesktopSpreadDelegate.qml 2015-09-18 11:03:48 +0000
+++ qml/Stages/DesktopSpreadDelegate.qml 2015-10-26 13:53:58 +0000
@@ -29,8 +29,8 @@
29 property bool highlightShown: false29 property bool highlightShown: false
30 property real shadowOpacity: 130 property real shadowOpacity: 1
3131
32 property int windowWidth: application.session && application.session.surface ? application.session.surface.size.width : 032 property int windowWidth: application && 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 : 033 property int windowHeight: application && application.session && application.session.surface ? application.session.surface.size.height : 0
3434
35 state: "normal"35 state: "normal"
36 states: [36 states: [
3737
=== modified file 'qml/Stages/DesktopStage.qml'
--- qml/Stages/DesktopStage.qml 2015-10-19 14:27:57 +0000
+++ qml/Stages/DesktopStage.qml 2015-10-26 13:53:58 +0000
@@ -24,6 +24,7 @@
24import "../Components/PanelState"24import "../Components/PanelState"
25import Utils 0.125import Utils 0.1
26import Ubuntu.Gestures 0.126import Ubuntu.Gestures 0.1
27import GlobalShortcut 1.0
2728
28Rectangle {29Rectangle {
29 id: root30 id: root
@@ -63,7 +64,15 @@
63 spread.state = "";64 spread.state = "";
64 }65 }
6566
66 ApplicationManager.requestFocusApplication(appId)67 ApplicationManager.focusApplication(appId);
68 }
69
70 onApplicationRemoved: {
71 priv.removeAndFocusPreviousInStack(appId);
72 }
73
74 onFocusedApplicationIdChanged: {
75 priv.addToFocusStack(priv.focusedAppId);
67 }76 }
6877
69 onFocusRequested: {78 onFocusRequested: {
@@ -78,6 +87,53 @@
78 }87 }
79 }88 }
8089
90 GlobalShortcut {
91 id: closeWindowShortcut
92 shortcut: Qt.AltModifier|Qt.Key_F4
93 onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
94 active: priv.focusedAppId !== ""
95 }
96
97 GlobalShortcut {
98 id: showSpreadShortcut
99 shortcut: Qt.MetaModifier|Qt.Key_W
100 onTriggered: spread.state = "altTab"
101 }
102
103 GlobalShortcut {
104 id: minimizeAllShortcut
105 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
106 onTriggered: priv.minimizeAllWindows()
107 }
108
109 GlobalShortcut {
110 id: maximizeWindowShortcut
111 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
112 onTriggered: priv.focusedAppDelegate.minimized ? priv.focusedAppDelegate.restore() : priv.focusedAppDelegate.maximize()
113 active: priv.focusedAppDelegate !== null
114 }
115
116 GlobalShortcut {
117 id: maximizeWindowLeftShortcut
118 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
119 onTriggered: priv.focusedAppDelegate.maximizeLeft()
120 active: priv.focusedAppDelegate !== null
121 }
122
123 GlobalShortcut {
124 id: maximizeWindowRightShortcut
125 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
126 onTriggered: priv.focusedAppDelegate.maximizeRight()
127 active: priv.focusedAppDelegate !== null
128 }
129
130 GlobalShortcut {
131 id: minimizeRestoreShortcut
132 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
133 onTriggered: priv.focusedAppDelegate.maximized ? priv.focusedAppDelegate.restore() : priv.focusedAppDelegate.minimize()
134 active: priv.focusedAppDelegate !== null
135 }
136
81 QtObject {137 QtObject {
82 id: priv138 id: priv
83139
@@ -86,6 +142,11 @@
86 var index = indexOf(focusedAppId);142 var index = indexOf(focusedAppId);
87 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null143 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
88 }144 }
145 onFocusedAppDelegateChanged: { // restore the window from minimization when we focus it (e.g. using spread)
146 if (focusedAppDelegate && focusedAppDelegate.minimized) {
147 focusedAppDelegate.restore();
148 }
149 }
89150
90 function indexOf(appId) {151 function indexOf(appId) {
91 for (var i = 0; i < ApplicationManager.count; i++) {152 for (var i = 0; i < ApplicationManager.count; i++) {
@@ -95,6 +156,45 @@
95 }156 }
96 return -1;157 return -1;
97 }158 }
159
160 property var focusStack: [] // focus stack of appIds
161
162 function addToFocusStack(appId) {
163 var oldIndex = focusStack.indexOf(appId);
164 if (oldIndex != -1) {
165 // remove the old item
166 focusStack.splice(oldIndex, 1);
167 }
168 // insert to the top of the focus stack
169 focusStack.unshift(appId);
170 }
171
172 function removeAndFocusPreviousInStack(appId) {
173 var removedIndex = focusStack.indexOf(appId);
174 if (removedIndex != -1) {
175 focusStack.splice(removedIndex, 1); // remove one item from the focus stack
176 focusFirstInStack(); // focus the first one
177 }
178 }
179
180 function focusFirstInStack() {
181 var newHead = focusStack[0];
182 if (newHead !== "") {
183 ApplicationManager.focusApplication(newHead);
184 }
185 }
186
187 function minimizeAllWindows() {
188 focusStack.forEach(function(appId) {
189 var appDelegate = appRepeater.itemAt(indexOf(appId));
190 if (appDelegate && !appDelegate.minimized) {
191 // we don't want to change the focus to a different window
192 appDelegate.minimized = true;
193 }
194 });
195 ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
196 focusStack = [];
197 }
98 }198 }
99199
100 Connections {200 Connections {
@@ -103,14 +203,15 @@
103 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)203 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
104 }204 }
105 onMinimize: appRepeater.itemAt(0).minimize();205 onMinimize: appRepeater.itemAt(0).minimize();
106 onMaximize: appRepeater.itemAt(0).unmaximize();206 onMaximize: appRepeater.itemAt(0).restore();
107 }207 }
108208
109 Binding {209 Binding {
110 target: PanelState210 target: PanelState
111 property: "buttonsVisible"211 property: "buttonsVisible"
112 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"212 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized
113 }213 }
214 Component.onDestruction: PanelState.buttonsVisible = false;
114215
115 FocusScope {216 FocusScope {
116 id: appContainer217 id: appContainer
@@ -132,18 +233,23 @@
132233
133 delegate: FocusScope {234 delegate: FocusScope {
134 id: appDelegate235 id: appDelegate
236 objectName: "appDelegate_" + appId
135 z: ApplicationManager.count - index237 z: ApplicationManager.count - index
136 y: units.gu(3)238 y: units.gu(3)
137 width: units.gu(60)239 width: units.gu(60)
138 height: units.gu(50)240 height: units.gu(50)
139 focus: model.appId === priv.focusedAppId241 focus: appId === priv.focusedAppId
140242
141 property bool maximized: false243 property bool maximized: false
244 property bool maximizedLeft: false
245 property bool maximizedRight: false
142 property bool minimized: false246 property bool minimized: false
247 readonly property string appId: model.appId
248 property bool animationsEnabled: true
143249
144 onFocusChanged: {250 onFocusChanged: {
145 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {251 if (focus && ApplicationManager.focusedApplicationId !== appId) {
146 ApplicationManager.focusApplication(model.appId);252 ApplicationManager.focusApplication(appId);
147 }253 }
148 }254 }
149255
@@ -158,36 +264,69 @@
158 value: ApplicationInfoInterface.RequestedRunning // Always running for now264 value: ApplicationInfoInterface.RequestedRunning // Always running for now
159 }265 }
160266
161 function maximize() {267 function maximize(animated) {
268 animationsEnabled = (animated === undefined) || animated;
162 minimized = false;269 minimized = false;
163 maximized = true;270 maximized = true;
164 }271 maximizedLeft = false;
165 function minimize() {272 maximizedRight = false;
273 }
274 function maximizeLeft() {
275 minimized = false;
276 maximized = false;
277 maximizedLeft = true;
278 maximizedRight = false;
279 }
280 function maximizeRight() {
281 minimized = false;
282 maximized = false;
283 maximizedLeft = false;
284 maximizedRight = true;
285 }
286 function minimize(animated) {
287 animationsEnabled = (animated === undefined) || animated;
166 maximized = false;288 maximized = false;
167 minimized = true;289 minimized = true;
290 maximizedLeft = false;
291 maximizedRight = false;
292 priv.removeAndFocusPreviousInStack(appId);
168 }293 }
169 function unmaximize() {294 function restore(animated) {
295 animationsEnabled = (animated === undefined) || animated;
170 minimized = false;296 minimized = false;
171 maximized = false;297 maximized = false;
298 maximizedLeft = false;
299 maximizedRight = false;
300 priv.addToFocusStack(appId);
172 }301 }
173302
174 states: [303 states: [
175 State {304 State {
176 name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized305 name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized
306 && !appDelegate.maximizedLeft && !appDelegate.maximizedRight
177 },307 },
178 State {308 State {
179 name: "maximized"; when: appDelegate.maximized309 name: "maximized"; when: appDelegate.maximized
180 PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }310 PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
181 },311 },
182 State {312 State {
313 name: "maximized_left"; when: appDelegate.maximizedLeft
314 PropertyChanges { target: appDelegate; x: 0; y: units.gu(3); width: root.width/2; height: root.height }
315 },
316 State {
317 name: "maximized_right"; when: appDelegate.maximizedRight
318 PropertyChanges { target: appDelegate; x: root.width/2; y: units.gu(3); width: root.width/2; height: root.height }
319 },
320 State {
183 name: "minimized"; when: appDelegate.minimized321 name: "minimized"; when: appDelegate.minimized
184 PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }322 PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
185 }323 }
186 ]324 ]
187 transitions: [325 transitions: [
188 Transition {326 Transition {
189 from: "maximized,minimized,normal,"327 from: "maximized,maximized_left,maximized_right,minimized,normal,"
190 to: "maximized,minimized,normal,"328 to: "maximized,maximized_left,maximized_right,minimized,normal,"
329 enabled: appDelegate.animationsEnabled
191 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }330 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
192 },331 },
193 Transition {332 Transition {
@@ -234,7 +373,7 @@
234 focus: true373 focus: true
235374
236 onClose: ApplicationManager.stopApplication(model.appId)375 onClose: ApplicationManager.stopApplication(model.appId)
237 onMaximize: appDelegate.maximize()376 onMaximize: appDelegate.maximized ? appDelegate.restore() : appDelegate.maximize()
238 onMinimize: appDelegate.minimize()377 onMinimize: appDelegate.minimize()
239 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }378 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
240 }379 }
241380
=== modified file 'qml/Stages/WindowDecoration.qml'
--- qml/Stages/WindowDecoration.qml 2015-10-19 19:05:23 +0000
+++ qml/Stages/WindowDecoration.qml 2015-10-26 13:53:58 +0000
@@ -45,14 +45,15 @@
45 priv.distanceX = pos.x;45 priv.distanceX = pos.x;
46 priv.distanceY = pos.y;46 priv.distanceY = pos.y;
47 priv.dragging = true;47 priv.dragging = true;
48 Mir.cursorName = "grabbing";
49 } else {48 } else {
50 priv.dragging = false;49 priv.dragging = false;
51 Mir.cursorName = "";50 Mir.cursorName = "";
52 }51 }
53 }52 }
53
54 onPositionChanged: {54 onPositionChanged: {
55 if (priv.dragging) {55 if (priv.dragging) {
56 Mir.cursorName = "grabbing";
56 var pos = mapToItem(root.target.parent, mouseX, mouseY);57 var pos = mapToItem(root.target.parent, mouseX, mouseY);
57 root.target.x = pos.x - priv.distanceX;58 root.target.x = pos.x - priv.distanceX;
58 root.target.y = pos.y - priv.distanceY;59 root.target.y = pos.y - priv.distanceY;
5960
=== modified file 'qml/Stages/WindowResizeArea.qml'
--- qml/Stages/WindowResizeArea.qml 2015-09-29 12:48:46 +0000
+++ qml/Stages/WindowResizeArea.qml 2015-10-26 13:53:58 +0000
@@ -36,18 +36,49 @@
36 property int minWidth: 036 property int minWidth: 0
37 property int minHeight: 037 property int minHeight: 0
3838
39 QtObject {
40 id: priv
41
42 property int normalX: 0
43 property int normalY: 0
44 property int normalWidth: 0
45 property int normalHeight: 0
46
47 function updateNormalGeometry() {
48 if (root.target.state == "normal") {
49 normalX = root.target.x
50 normalY = root.target.y
51 normalWidth = root.target.width
52 normalHeight = root.target.height
53 }
54 }
55 }
56
57 Connections {
58 target: root.target
59 onXChanged: priv.updateNormalGeometry();
60 onYChanged: priv.updateNormalGeometry();
61 onWidthChanged: priv.updateNormalGeometry();
62 onHeightChanged: priv.updateNormalGeometry();
63 }
64
39 Component.onCompleted: {65 Component.onCompleted: {
40 var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))66 var windowGeometry = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
41 if (windowState !== undefined) {67 if (windowGeometry !== undefined) {
42 target.x = windowState.x68 target.x = windowGeometry.x
43 target.y = windowState.y69 target.y = windowGeometry.y
44 target.width = windowState.width70 target.width = windowGeometry.width
45 target.height = windowState.height71 target.height = windowGeometry.height
72 }
73 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
74 if (windowState === WindowStateStorage.WindowStateMaximized) {
75 target.maximize(false)
46 }76 }
47 }77 }
4878
49 Component.onDestruction: {79 Component.onDestruction: {
50 windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))80 windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
81 windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
51 }82 }
5283
53 QtObject {84 QtObject {
5485
=== modified file 'tests/mocks/Utils/windowstatestorage.cpp'
--- tests/mocks/Utils/windowstatestorage.cpp 2015-08-24 15:39:53 +0000
+++ tests/mocks/Utils/windowstatestorage.cpp 2015-10-26 13:53:58 +0000
@@ -44,3 +44,14 @@
44 if (!m_geometry.contains(windowId)) return defaultValue;44 if (!m_geometry.contains(windowId)) return defaultValue;
45 return m_geometry.value(windowId).toRect();45 return m_geometry.value(windowId).toRect();
46}46}
47
48void WindowStateStorage::saveState(const QString &windowId, WindowState state)
49{
50 m_state[windowId] = state;
51}
52
53WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
54{
55 if (!m_state.contains(windowId)) return defaultValue;
56 return m_state.value(windowId);
57}
4758
=== modified file 'tests/mocks/Utils/windowstatestorage.h'
--- tests/mocks/Utils/windowstatestorage.h 2015-08-24 15:39:53 +0000
+++ tests/mocks/Utils/windowstatestorage.h 2015-10-26 13:53:58 +0000
@@ -22,9 +22,17 @@
22{22{
23 Q_OBJECT23 Q_OBJECT
24 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)24 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
25 Q_ENUMS(WindowState)
25public:26public:
27 enum WindowState {
28 WindowStateNormal,
29 WindowStateMaximized
30 };
26 WindowStateStorage(QObject *parent = 0);31 WindowStateStorage(QObject *parent = 0);
2732
33 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
34 Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
35
28 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);36 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
29 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);37 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
3038
@@ -35,5 +43,6 @@
35 void setGeometry(const QVariantMap& geometry);43 void setGeometry(const QVariantMap& geometry);
36 QVariantMap geometry() const;44 QVariantMap geometry() const;
3745
46 QHash<QString, WindowState> m_state;
38 QVariantMap m_geometry;47 QVariantMap m_geometry;
39};48};
4049
=== modified file 'tests/qmltests/Stages/tst_DesktopStage.qml'
--- tests/qmltests/Stages/tst_DesktopStage.qml 2015-09-17 12:25:29 +0000
+++ tests/qmltests/Stages/tst_DesktopStage.qml 2015-10-26 13:53:58 +0000
@@ -53,14 +53,10 @@
5353
54 focus: true54 focus: true
5555
56 property bool itemDestroyed: false
57 sourceComponent: Component {56 sourceComponent: Component {
58 DesktopStage {57 DesktopStage {
59 color: "darkblue"58 color: "darkblue"
60 anchors.fill: parent59 anchors.fill: parent
61 Component.onDestruction: {
62 desktopStageLoader.itemDestroyed = true;
63 }
64 }60 }
65 }61 }
66 }62 }
@@ -95,16 +91,10 @@
95 property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null91 property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null
9692
97 function cleanup() {93 function cleanup() {
98 desktopStageLoader.itemDestroyed = false;
99 desktopStageLoader.active = false;94 desktopStageLoader.active = false;
10095
101 tryCompare(desktopStageLoader, "status", Loader.Null);96 tryCompare(desktopStageLoader, "status", Loader.Null);
102 tryCompare(desktopStageLoader, "item", null);97 tryCompare(desktopStageLoader, "item", null);
103 // Loader.status might be Loader.Null and Loader.item might be null but the Loader
104 // actually took place. Likely because Loader waits until the next event loop
105 // iteration to do its work. So to ensure the reload, we will wait until the
106 // Shell instance gets destroyed.
107 tryCompare(desktopStageLoader, "itemDestroyed", true);
10898
109 killAllRunningApps();99 killAllRunningApps();
110100
@@ -117,7 +107,7 @@
117 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0107 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
118 ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);108 ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
119 }109 }
120 compare(ApplicationManager.count, 1)110 compare(ApplicationManager.count, 1);
121 }111 }
122112
123 function waitUntilAppSurfaceShowsUp(appId) {113 function waitUntilAppSurfaceShowsUp(appId) {
@@ -147,10 +137,7 @@
147 }137 }
148138
149 function test_appFocusSwitch(data) {139 function test_appFocusSwitch(data) {
150 var i;140 data.apps.forEach(startApplication);
151 for (i = 0; i < data.apps.length; i++) {
152 startApplication(data.apps[i]);
153 }
154141
155 ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]);142 ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]);
156 tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);143 tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
@@ -167,10 +154,7 @@
167 }154 }
168155
169 function test_tappingOnWindowChangesFocusedApp(data) {156 function test_tappingOnWindowChangesFocusedApp(data) {
170 var i;157 data.apps.forEach(startApplication);
171 for (i = 0; i < data.apps.length; i++) {
172 startApplication(data.apps[i]);
173 }
174 var fromAppId = data.apps[data.focusfrom];158 var fromAppId = data.apps[data.focusfrom];
175 var toAppId = data.apps[data.focusTo]159 var toAppId = data.apps[data.focusTo]
176160
@@ -187,18 +171,37 @@
187 compare(ApplicationManager.focusedApplicationId, toAppId);171 compare(ApplicationManager.focusedApplicationId, toAppId);
188 }172 }
189173
174 function test_clickingOnWindowChangesFocusedApp_data() {
175 return test_tappingOnWindowChangesFocusedApp_data(); // reuse test data
176 }
177
178 function test_clickingOnWindowChangesFocusedApp(data) {
179 data.apps.forEach(startApplication);
180 var fromAppId = data.apps[data.focusfrom];
181 var toAppId = data.apps[data.focusTo]
182
183 var fromAppWindow = findChild(desktopStage, "appWindow_" + fromAppId);
184 verify(fromAppWindow);
185 mouseClick(fromAppWindow);
186 compare(fromAppWindow.application.session.surface.activeFocus, true);
187 compare(ApplicationManager.focusedApplicationId, fromAppId);
188
189 var toAppWindow = findChild(desktopStage, "appWindow_" + toAppId);
190 verify(toAppWindow);
191 mouseClick(toAppWindow);
192 compare(toAppWindow.application.session.surface.activeFocus, true);
193 compare(ApplicationManager.focusedApplicationId, toAppId);
194 }
195
190 function test_tappingOnDecorationFocusesApplication_data() {196 function test_tappingOnDecorationFocusesApplication_data() {
191 return [197 return [
192 {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },198 {tag: "dash to dialer", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
193 {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },199 {tag: "dialer to dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
194 ]200 ]
195 }201 }
196202
197 function test_tappingOnDecorationFocusesApplication(data) {203 function test_tappingOnDecorationFocusesApplication(data) {
198 var i;204 data.apps.forEach(startApplication);
199 for (i = 0; i < data.apps.length; i++) {
200 startApplication(data.apps[i]);
201 }
202205
203 var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);206 var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
204 verify(fromAppDecoration);207 verify(fromAppDecoration);
@@ -210,5 +213,97 @@
210 tap(toAppDecoration);213 tap(toAppDecoration);
211 tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);214 tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
212 }215 }
216
217 function test_clickingOnDecorationFocusesApplication_data() {
218 return test_tappingOnDecorationFocusesApplication_data(); // reuse test data
219 }
220
221 function test_clickingOnDecorationFocusesApplication(data) {
222 data.apps.forEach(startApplication);
223
224 var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
225 verify(fromAppDecoration);
226 mouseClick(fromAppDecoration);
227 tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
228
229 var toAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusTo]);
230 verify(toAppDecoration);
231 mouseClick(toAppDecoration);
232 tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
233 }
234
235 function test_windowMaximize() {
236 var apps = ["unity8-dash", "dialer-app", "camera-app"];
237 apps.forEach(startApplication);
238 var appName = "dialer-app";
239 var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
240 verify(appDelegate);
241 ApplicationManager.focusApplication(appName);
242 keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
243 tryCompare(appDelegate, "maximized", true);
244 tryCompare(appDelegate, "minimized", false);
245 }
246
247 function test_windowMaximizeLeft() {
248 var apps = ["unity8-dash", "dialer-app", "camera-app"];
249 apps.forEach(startApplication);
250 var appName = "dialer-app";
251 var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
252 verify(appDelegate);
253 ApplicationManager.focusApplication(appName);
254 keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
255 tryCompare(appDelegate, "maximized", false);
256 tryCompare(appDelegate, "minimized", false);
257 tryCompare(appDelegate, "maximizedLeft", true);
258 tryCompare(appDelegate, "maximizedRight", false);
259 }
260
261 function test_windowMaximizeRight() {
262 var apps = ["unity8-dash", "dialer-app", "camera-app"];
263 apps.forEach(startApplication);
264 var appName = "dialer-app";
265 var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
266 verify(appDelegate);
267 ApplicationManager.focusApplication(appName);
268 keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
269 tryCompare(appDelegate, "maximized", false);
270 tryCompare(appDelegate, "minimized", false);
271 tryCompare(appDelegate, "maximizedLeft", false);
272 tryCompare(appDelegate, "maximizedRight", true);
273 }
274
275 function test_windowMinimize() {
276 var apps = ["unity8-dash", "dialer-app", "camera-app"];
277 apps.forEach(startApplication);
278 var appName = "dialer-app";
279 var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
280 verify(appDelegate);
281 ApplicationManager.focusApplication(appName);
282 keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
283 tryCompare(appDelegate, "maximized", false);
284 tryCompare(appDelegate, "minimized", true);
285 verify(ApplicationManager.focusedApplicationId != ""); // verify we don't lose focus when minimizing an app
286 }
287
288 function test_windowMinimizeAll() {
289 var apps = ["unity8-dash", "dialer-app", "camera-app"];
290 apps.forEach(startApplication);
291 verify(ApplicationManager.count == 3);
292 keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
293 tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify no app is focused
294 }
295
296 function test_windowClose() {
297 var apps = ["unity8-dash", "dialer-app", "camera-app"];
298 apps.forEach(startApplication);
299 verify(ApplicationManager.count == 3);
300 var appName = "dialer-app";
301 var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
302 verify(appDelegate);
303 ApplicationManager.focusApplication(appName);
304 keyClick(Qt.Key_F4, Qt.AltModifier); // Alt+F4 shortcut to close
305 verify(ApplicationManager.count == 2); // verify the app is gone
306 verify(ApplicationManager.findApplication(appName) === null); // and it's not in running apps
307 }
213 }308 }
214}309}
215310
=== modified file 'tests/qmltests/Stages/tst_WindowResizeArea.qml'
--- tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-10-08 14:27:12 +0000
+++ tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-10-26 13:53:58 +0000
@@ -44,8 +44,11 @@
44 width: units.gu(20)44 width: units.gu(20)
45 property int windowHeight: height45 property int windowHeight: height
46 property int windowWidth: width46 property int windowWidth: width
47 onWindowHeightChanged: height = windowHeight47 state: "normal"
48 onWindowWidthChanged: width = windowWidth48
49 function maximize() {
50 state = "maximized"
51 }
4952
50 WindowResizeArea {53 WindowResizeArea {
51 id: windowResizeArea54 id: windowResizeArea
@@ -195,5 +198,35 @@
195 tryCompare(fakeWindow, "width", initialWindowWidth);198 tryCompare(fakeWindow, "width", initialWindowWidth);
196 tryCompare(fakeWindow, "height", initialWindowHeight);199 tryCompare(fakeWindow, "height", initialWindowHeight);
197 }200 }
201
202 function test_saveRestoreMaximized() {
203 var initialWindowX = fakeWindow.x;
204 var initialWindowY = fakeWindow.y;
205
206 var moveDelta = units.gu(5);
207
208 fakeWindow.x = initialWindowX + moveDelta
209 fakeWindow.y = initialWindowY + moveDelta
210
211 // Now change the state to maximized. The window should not keep updating the stored values
212 fakeWindow.state = "maximized"
213 fakeWindow.x = 31415 // 0 is too risky to pass the test even when broken
214 fakeWindow.y = 31415
215
216 // This will destroy the window and recreate it
217 windowLoader.active = false;
218 waitForRendering(root);
219 windowLoader.active = true;
220
221 // Make sure it's again where we left it in normal state before destroying
222 tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
223 tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
224
225 // Make sure maximize() has been called after restoring
226 tryCompare(fakeWindow, "state", "maximized")
227
228 // clean up
229 fakeWindow.state = "normal"
230 }
198 }231 }
199}232}
200233
=== modified file 'tests/qmltests/tst_Shell.qml'
--- tests/qmltests/tst_Shell.qml 2015-10-20 08:10:03 +0000
+++ tests/qmltests/tst_Shell.qml 2015-10-26 13:53:58 +0000
@@ -31,6 +31,7 @@
31import Wizard 0.1 as Wizard31import Wizard 0.1 as Wizard
3232
33import "../../qml"33import "../../qml"
34import "../../qml/Components/PanelState"
34import "Stages"35import "Stages"
3536
36Rectangle {37Rectangle {
@@ -1707,5 +1708,73 @@
17071708
1708 keyRelease(Qt.Key_Control);1709 keyRelease(Qt.Key_Control);
1709 }1710 }
1711
1712 // regression test for http://pad.lv/1443319
1713 function test_closeMaximizedAndRestart() {
1714 loadDesktopShellWithApps();
1715
1716 var appRepeater = findChild(shell, "appRepeater")
1717 var appId = ApplicationManager.get(0).appId;
1718 var appDelegate = appRepeater.itemAt(0);
1719 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1720
1721 tryCompare(appDelegate, "state", "normal");
1722 tryCompare(PanelState, "buttonsVisible", false)
1723
1724 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1725 tryCompare(appDelegate, "state", "maximized");
1726 tryCompare(PanelState, "buttonsVisible", true)
1727
1728 ApplicationManager.stopApplication(appId);
1729 tryCompare(PanelState, "buttonsVisible", false)
1730
1731 ApplicationManager.startApplication(appId);
1732 tryCompare(PanelState, "buttonsVisible", true)
1733 }
1734
1735 // bug http://pad.lv/1431566
1736 function test_switchToStagedHidesPanelButtons() {
1737 loadDesktopShellWithApps();
1738 var appRepeater = findChild(shell, "appRepeater")
1739 var appId = ApplicationManager.get(0).appId;
1740 var appDelegate = appRepeater.itemAt(0);
1741 var panelButtons = findChild(shell, "panelWindowControlButtons")
1742
1743 tryCompare(appDelegate, "state", "normal");
1744 tryCompare(panelButtons, "visible", false);
1745
1746 appDelegate.maximize(false);
1747 tryCompare(panelButtons, "visible", true);
1748
1749 shell.usageScenario = "phone";
1750 waitForRendering(shell);
1751 tryCompare(panelButtons, "visible", false);
1752
1753 shell.usageScenario = "desktop";
1754 waitForRendering(shell);
1755 tryCompare(panelButtons, "visible", true);
1756 }
1757
1758 function test_lockingGreeterHidesPanelButtons() {
1759 loadDesktopShellWithApps();
1760 var appRepeater = findChild(shell, "appRepeater")
1761 var appId = ApplicationManager.get(0).appId;
1762 var appDelegate = appRepeater.itemAt(0);
1763 var panelButtons = findChild(shell, "panelWindowControlButtons")
1764
1765 tryCompare(appDelegate, "state", "normal");
1766 tryCompare(panelButtons, "visible", false);
1767
1768 appDelegate.maximize(false);
1769 tryCompare(panelButtons, "visible", true);
1770
1771 LightDM.Greeter.showGreeter();
1772 waitForRendering(shell);
1773 tryCompare(panelButtons, "visible", false);
1774
1775 LightDM.Greeter.hideGreeter();
1776 waitForRendering(shell);
1777 tryCompare(panelButtons, "visible", true);
1778 }
1710 }1779 }
1711}1780}

Subscribers

People subscribed via source and target branches