Merge lp:~lukas-kde/unity8/activateWindows into lp:unity8
- activateWindows
- Merge into trunk
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 |
Related bugs: |
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:/
* 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
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal | # |
Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal | # |
> Please follow the commit message format as explained here:
> https:/
Should be fine now
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).
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.
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal | # |
> > Please follow the commit message format as explained here:
> > https:/
>
> Should be fine now
Yes, thanks!
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:2005
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Michael Zanetti (mzanetti) : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:2020
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 2021. By Lukáš Tinkl
-
merge trunk
- 2022. By Lukáš Tinkl
- 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
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-10-26 13:53:58 +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-10-26 13:53:58 +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/WindowControlButtons.qml' |
155 | --- qml/Components/WindowControlButtons.qml 2014-11-24 11:21:38 +0000 |
156 | +++ qml/Components/WindowControlButtons.qml 2015-10-26 13:53:58 +0000 |
157 | @@ -28,6 +28,7 @@ |
158 | signal maximize() |
159 | |
160 | Rectangle { |
161 | + objectName: "closeWindowButton" |
162 | height: parent.height; width: height; radius: height / 2 |
163 | gradient: Gradient { |
164 | GradientStop { color: "#F49073"; position: 0 } |
165 | @@ -38,6 +39,7 @@ |
166 | MouseArea { anchors.fill: parent; onClicked: root.close() } |
167 | } |
168 | Rectangle { |
169 | + objectName: "minimizeWindowButton" |
170 | height: parent.height; width: height; radius: height / 2 |
171 | gradient: Gradient { |
172 | GradientStop { color: "#92918C"; position: 0 } |
173 | @@ -48,6 +50,7 @@ |
174 | MouseArea { anchors.fill: parent; onClicked: root.minimize() } |
175 | } |
176 | Rectangle { |
177 | + objectName: "maximizeWindowButton" |
178 | height: parent.height; width: height; radius: height / 2 |
179 | gradient: Gradient { |
180 | GradientStop { color: "#92918C"; position: 0 } |
181 | |
182 | === modified file 'qml/Panel/Panel.qml' |
183 | --- qml/Panel/Panel.qml 2015-09-29 12:48:46 +0000 |
184 | +++ qml/Panel/Panel.qml 2015-10-26 13:53:58 +0000 |
185 | @@ -28,6 +28,7 @@ |
186 | property alias callHint: __callHint |
187 | property bool fullscreenMode: false |
188 | property real indicatorAreaShowProgress: 1.0 |
189 | + property bool locked: false |
190 | |
191 | opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.0 |
192 | |
193 | @@ -136,7 +137,7 @@ |
194 | } |
195 | |
196 | shown: false |
197 | - width: root.width - (PanelState.buttonsVisible ? windowControlButtons.width : 0) |
198 | + width: root.width - (windowControlButtons.visible ? windowControlButtons.width : 0) |
199 | minimizedPanelHeight: units.gu(3) |
200 | expandedPanelHeight: units.gu(7) |
201 | openedHeight: root.height - indicatorOrangeLine.height |
202 | @@ -164,13 +165,14 @@ |
203 | |
204 | WindowControlButtons { |
205 | id: windowControlButtons |
206 | + objectName: "panelWindowControlButtons" |
207 | anchors { |
208 | left: parent.left |
209 | top: parent.top |
210 | margins: units.gu(0.7) |
211 | } |
212 | height: indicators.minimizedPanelHeight - anchors.margins * 2 |
213 | - visible: PanelState.buttonsVisible |
214 | + visible: PanelState.buttonsVisible && !root.locked |
215 | onClose: PanelState.close() |
216 | onMinimize: PanelState.minimize() |
217 | onMaximize: PanelState.maximize() |
218 | @@ -189,7 +191,7 @@ |
219 | id: __callHint |
220 | anchors { |
221 | top: parent.top |
222 | - left: PanelState.buttonsVisible ? windowControlButtons.right : parent.left |
223 | + left: windowControlButtons.visible ? windowControlButtons.right : parent.left |
224 | } |
225 | height: indicators.minimizedPanelHeight |
226 | visible: active && indicators.state == "initial" |
227 | |
228 | === modified file 'qml/Shell.qml' |
229 | --- qml/Shell.qml 2015-10-16 17:11:54 +0000 |
230 | +++ qml/Shell.qml 2015-10-26 13:53:58 +0000 |
231 | @@ -57,6 +57,7 @@ |
232 | property bool beingResized |
233 | property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop" |
234 | property string mode: "full-greeter" |
235 | + property bool cursorVisible: false |
236 | function updateFocusedAppOrientation() { |
237 | applicationsDisplayLoader.item.updateFocusedAppOrientation(); |
238 | } |
239 | @@ -536,6 +537,7 @@ |
240 | |
241 | fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0) |
242 | || greeter.hasLockedApp |
243 | + locked: greeter && greeter.active |
244 | } |
245 | |
246 | Launcher { |
247 | |
248 | === modified file 'qml/Stages/DesktopSpreadDelegate.qml' |
249 | --- qml/Stages/DesktopSpreadDelegate.qml 2015-09-18 11:03:48 +0000 |
250 | +++ qml/Stages/DesktopSpreadDelegate.qml 2015-10-26 13:53:58 +0000 |
251 | @@ -29,8 +29,8 @@ |
252 | property bool highlightShown: false |
253 | property real shadowOpacity: 1 |
254 | |
255 | - property int windowWidth: application.session && application.session.surface ? application.session.surface.size.width : 0 |
256 | - property int windowHeight: application.session && application.session.surface ? application.session.surface.size.height : 0 |
257 | + property int windowWidth: application && application.session && application.session.surface ? application.session.surface.size.width : 0 |
258 | + property int windowHeight: application && application.session && application.session.surface ? application.session.surface.size.height : 0 |
259 | |
260 | state: "normal" |
261 | states: [ |
262 | |
263 | === modified file 'qml/Stages/DesktopStage.qml' |
264 | --- qml/Stages/DesktopStage.qml 2015-10-19 14:27:57 +0000 |
265 | +++ qml/Stages/DesktopStage.qml 2015-10-26 13:53:58 +0000 |
266 | @@ -24,6 +24,7 @@ |
267 | import "../Components/PanelState" |
268 | import Utils 0.1 |
269 | import Ubuntu.Gestures 0.1 |
270 | +import GlobalShortcut 1.0 |
271 | |
272 | Rectangle { |
273 | id: root |
274 | @@ -63,7 +64,15 @@ |
275 | spread.state = ""; |
276 | } |
277 | |
278 | - ApplicationManager.requestFocusApplication(appId) |
279 | + ApplicationManager.focusApplication(appId); |
280 | + } |
281 | + |
282 | + onApplicationRemoved: { |
283 | + priv.removeAndFocusPreviousInStack(appId); |
284 | + } |
285 | + |
286 | + onFocusedApplicationIdChanged: { |
287 | + priv.addToFocusStack(priv.focusedAppId); |
288 | } |
289 | |
290 | onFocusRequested: { |
291 | @@ -78,6 +87,53 @@ |
292 | } |
293 | } |
294 | |
295 | + GlobalShortcut { |
296 | + id: closeWindowShortcut |
297 | + shortcut: Qt.AltModifier|Qt.Key_F4 |
298 | + onTriggered: ApplicationManager.stopApplication(priv.focusedAppId) |
299 | + active: priv.focusedAppId !== "" |
300 | + } |
301 | + |
302 | + GlobalShortcut { |
303 | + id: showSpreadShortcut |
304 | + shortcut: Qt.MetaModifier|Qt.Key_W |
305 | + onTriggered: spread.state = "altTab" |
306 | + } |
307 | + |
308 | + GlobalShortcut { |
309 | + id: minimizeAllShortcut |
310 | + shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D |
311 | + onTriggered: priv.minimizeAllWindows() |
312 | + } |
313 | + |
314 | + GlobalShortcut { |
315 | + id: maximizeWindowShortcut |
316 | + shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up |
317 | + onTriggered: priv.focusedAppDelegate.minimized ? priv.focusedAppDelegate.restore() : priv.focusedAppDelegate.maximize() |
318 | + active: priv.focusedAppDelegate !== null |
319 | + } |
320 | + |
321 | + GlobalShortcut { |
322 | + id: maximizeWindowLeftShortcut |
323 | + shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left |
324 | + onTriggered: priv.focusedAppDelegate.maximizeLeft() |
325 | + active: priv.focusedAppDelegate !== null |
326 | + } |
327 | + |
328 | + GlobalShortcut { |
329 | + id: maximizeWindowRightShortcut |
330 | + shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right |
331 | + onTriggered: priv.focusedAppDelegate.maximizeRight() |
332 | + active: priv.focusedAppDelegate !== null |
333 | + } |
334 | + |
335 | + GlobalShortcut { |
336 | + id: minimizeRestoreShortcut |
337 | + shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down |
338 | + onTriggered: priv.focusedAppDelegate.maximized ? priv.focusedAppDelegate.restore() : priv.focusedAppDelegate.minimize() |
339 | + active: priv.focusedAppDelegate !== null |
340 | + } |
341 | + |
342 | QtObject { |
343 | id: priv |
344 | |
345 | @@ -86,6 +142,11 @@ |
346 | var index = indexOf(focusedAppId); |
347 | return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null |
348 | } |
349 | + onFocusedAppDelegateChanged: { // restore the window from minimization when we focus it (e.g. using spread) |
350 | + if (focusedAppDelegate && focusedAppDelegate.minimized) { |
351 | + focusedAppDelegate.restore(); |
352 | + } |
353 | + } |
354 | |
355 | function indexOf(appId) { |
356 | for (var i = 0; i < ApplicationManager.count; i++) { |
357 | @@ -95,6 +156,45 @@ |
358 | } |
359 | return -1; |
360 | } |
361 | + |
362 | + property var focusStack: [] // focus stack of appIds |
363 | + |
364 | + function addToFocusStack(appId) { |
365 | + var oldIndex = focusStack.indexOf(appId); |
366 | + if (oldIndex != -1) { |
367 | + // remove the old item |
368 | + focusStack.splice(oldIndex, 1); |
369 | + } |
370 | + // insert to the top of the focus stack |
371 | + focusStack.unshift(appId); |
372 | + } |
373 | + |
374 | + function removeAndFocusPreviousInStack(appId) { |
375 | + var removedIndex = focusStack.indexOf(appId); |
376 | + if (removedIndex != -1) { |
377 | + focusStack.splice(removedIndex, 1); // remove one item from the focus stack |
378 | + focusFirstInStack(); // focus the first one |
379 | + } |
380 | + } |
381 | + |
382 | + function focusFirstInStack() { |
383 | + var newHead = focusStack[0]; |
384 | + if (newHead !== "") { |
385 | + ApplicationManager.focusApplication(newHead); |
386 | + } |
387 | + } |
388 | + |
389 | + function minimizeAllWindows() { |
390 | + focusStack.forEach(function(appId) { |
391 | + var appDelegate = appRepeater.itemAt(indexOf(appId)); |
392 | + if (appDelegate && !appDelegate.minimized) { |
393 | + // we don't want to change the focus to a different window |
394 | + appDelegate.minimized = true; |
395 | + } |
396 | + }); |
397 | + ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point |
398 | + focusStack = []; |
399 | + } |
400 | } |
401 | |
402 | Connections { |
403 | @@ -103,14 +203,15 @@ |
404 | ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId) |
405 | } |
406 | onMinimize: appRepeater.itemAt(0).minimize(); |
407 | - onMaximize: appRepeater.itemAt(0).unmaximize(); |
408 | + onMaximize: appRepeater.itemAt(0).restore(); |
409 | } |
410 | |
411 | Binding { |
412 | target: PanelState |
413 | property: "buttonsVisible" |
414 | - value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized" |
415 | + value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized |
416 | } |
417 | + Component.onDestruction: PanelState.buttonsVisible = false; |
418 | |
419 | FocusScope { |
420 | id: appContainer |
421 | @@ -132,18 +233,23 @@ |
422 | |
423 | delegate: FocusScope { |
424 | id: appDelegate |
425 | + objectName: "appDelegate_" + appId |
426 | z: ApplicationManager.count - index |
427 | y: units.gu(3) |
428 | width: units.gu(60) |
429 | height: units.gu(50) |
430 | - focus: model.appId === priv.focusedAppId |
431 | + focus: appId === priv.focusedAppId |
432 | |
433 | property bool maximized: false |
434 | + property bool maximizedLeft: false |
435 | + property bool maximizedRight: false |
436 | property bool minimized: false |
437 | + readonly property string appId: model.appId |
438 | + property bool animationsEnabled: true |
439 | |
440 | onFocusChanged: { |
441 | - if (focus && ApplicationManager.focusedApplicationId !== model.appId) { |
442 | - ApplicationManager.focusApplication(model.appId); |
443 | + if (focus && ApplicationManager.focusedApplicationId !== appId) { |
444 | + ApplicationManager.focusApplication(appId); |
445 | } |
446 | } |
447 | |
448 | @@ -158,36 +264,69 @@ |
449 | value: ApplicationInfoInterface.RequestedRunning // Always running for now |
450 | } |
451 | |
452 | - function maximize() { |
453 | + function maximize(animated) { |
454 | + animationsEnabled = (animated === undefined) || animated; |
455 | minimized = false; |
456 | maximized = true; |
457 | - } |
458 | - function minimize() { |
459 | + maximizedLeft = false; |
460 | + maximizedRight = false; |
461 | + } |
462 | + function maximizeLeft() { |
463 | + minimized = false; |
464 | + maximized = false; |
465 | + maximizedLeft = true; |
466 | + maximizedRight = false; |
467 | + } |
468 | + function maximizeRight() { |
469 | + minimized = false; |
470 | + maximized = false; |
471 | + maximizedLeft = false; |
472 | + maximizedRight = true; |
473 | + } |
474 | + function minimize(animated) { |
475 | + animationsEnabled = (animated === undefined) || animated; |
476 | maximized = false; |
477 | minimized = true; |
478 | + maximizedLeft = false; |
479 | + maximizedRight = false; |
480 | + priv.removeAndFocusPreviousInStack(appId); |
481 | } |
482 | - function unmaximize() { |
483 | + function restore(animated) { |
484 | + animationsEnabled = (animated === undefined) || animated; |
485 | minimized = false; |
486 | maximized = false; |
487 | + maximizedLeft = false; |
488 | + maximizedRight = false; |
489 | + priv.addToFocusStack(appId); |
490 | } |
491 | |
492 | states: [ |
493 | State { |
494 | name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized |
495 | + && !appDelegate.maximizedLeft && !appDelegate.maximizedRight |
496 | }, |
497 | State { |
498 | name: "maximized"; when: appDelegate.maximized |
499 | PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height } |
500 | }, |
501 | State { |
502 | + name: "maximized_left"; when: appDelegate.maximizedLeft |
503 | + PropertyChanges { target: appDelegate; x: 0; y: units.gu(3); width: root.width/2; height: root.height } |
504 | + }, |
505 | + State { |
506 | + name: "maximized_right"; when: appDelegate.maximizedRight |
507 | + PropertyChanges { target: appDelegate; x: root.width/2; y: units.gu(3); width: root.width/2; height: root.height } |
508 | + }, |
509 | + State { |
510 | name: "minimized"; when: appDelegate.minimized |
511 | PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 } |
512 | } |
513 | ] |
514 | transitions: [ |
515 | Transition { |
516 | - from: "maximized,minimized,normal," |
517 | - to: "maximized,minimized,normal," |
518 | + from: "maximized,maximized_left,maximized_right,minimized,normal," |
519 | + to: "maximized,maximized_left,maximized_right,minimized,normal," |
520 | + enabled: appDelegate.animationsEnabled |
521 | PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" } |
522 | }, |
523 | Transition { |
524 | @@ -234,7 +373,7 @@ |
525 | focus: true |
526 | |
527 | onClose: ApplicationManager.stopApplication(model.appId) |
528 | - onMaximize: appDelegate.maximize() |
529 | + onMaximize: appDelegate.maximized ? appDelegate.restore() : appDelegate.maximize() |
530 | onMinimize: appDelegate.minimize() |
531 | onDecorationPressed: { ApplicationManager.focusApplication(model.appId) } |
532 | } |
533 | |
534 | === modified file 'qml/Stages/WindowDecoration.qml' |
535 | --- qml/Stages/WindowDecoration.qml 2015-10-19 19:05:23 +0000 |
536 | +++ qml/Stages/WindowDecoration.qml 2015-10-26 13:53:58 +0000 |
537 | @@ -45,14 +45,15 @@ |
538 | priv.distanceX = pos.x; |
539 | priv.distanceY = pos.y; |
540 | priv.dragging = true; |
541 | - Mir.cursorName = "grabbing"; |
542 | } else { |
543 | priv.dragging = false; |
544 | Mir.cursorName = ""; |
545 | } |
546 | } |
547 | + |
548 | onPositionChanged: { |
549 | if (priv.dragging) { |
550 | + Mir.cursorName = "grabbing"; |
551 | var pos = mapToItem(root.target.parent, mouseX, mouseY); |
552 | root.target.x = pos.x - priv.distanceX; |
553 | root.target.y = pos.y - priv.distanceY; |
554 | |
555 | === modified file 'qml/Stages/WindowResizeArea.qml' |
556 | --- qml/Stages/WindowResizeArea.qml 2015-09-29 12:48:46 +0000 |
557 | +++ qml/Stages/WindowResizeArea.qml 2015-10-26 13:53:58 +0000 |
558 | @@ -36,18 +36,49 @@ |
559 | property int minWidth: 0 |
560 | property int minHeight: 0 |
561 | |
562 | + QtObject { |
563 | + id: priv |
564 | + |
565 | + property int normalX: 0 |
566 | + property int normalY: 0 |
567 | + property int normalWidth: 0 |
568 | + property int normalHeight: 0 |
569 | + |
570 | + function updateNormalGeometry() { |
571 | + if (root.target.state == "normal") { |
572 | + normalX = root.target.x |
573 | + normalY = root.target.y |
574 | + normalWidth = root.target.width |
575 | + normalHeight = root.target.height |
576 | + } |
577 | + } |
578 | + } |
579 | + |
580 | + Connections { |
581 | + target: root.target |
582 | + onXChanged: priv.updateNormalGeometry(); |
583 | + onYChanged: priv.updateNormalGeometry(); |
584 | + onWidthChanged: priv.updateNormalGeometry(); |
585 | + onHeightChanged: priv.updateNormalGeometry(); |
586 | + } |
587 | + |
588 | Component.onCompleted: { |
589 | - var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height)) |
590 | - if (windowState !== undefined) { |
591 | - target.x = windowState.x |
592 | - target.y = windowState.y |
593 | - target.width = windowState.width |
594 | - target.height = windowState.height |
595 | + var windowGeometry = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height)) |
596 | + if (windowGeometry !== undefined) { |
597 | + target.x = windowGeometry.x |
598 | + target.y = windowGeometry.y |
599 | + target.width = windowGeometry.width |
600 | + target.height = windowGeometry.height |
601 | + } |
602 | + var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal) |
603 | + if (windowState === WindowStateStorage.WindowStateMaximized) { |
604 | + target.maximize(false) |
605 | } |
606 | } |
607 | |
608 | Component.onDestruction: { |
609 | - windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height)) |
610 | + windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal) |
611 | + windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight)) |
612 | } |
613 | |
614 | QtObject { |
615 | |
616 | === modified file 'tests/mocks/Utils/windowstatestorage.cpp' |
617 | --- tests/mocks/Utils/windowstatestorage.cpp 2015-08-24 15:39:53 +0000 |
618 | +++ tests/mocks/Utils/windowstatestorage.cpp 2015-10-26 13:53:58 +0000 |
619 | @@ -44,3 +44,14 @@ |
620 | if (!m_geometry.contains(windowId)) return defaultValue; |
621 | return m_geometry.value(windowId).toRect(); |
622 | } |
623 | + |
624 | +void WindowStateStorage::saveState(const QString &windowId, WindowState state) |
625 | +{ |
626 | + m_state[windowId] = state; |
627 | +} |
628 | + |
629 | +WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) |
630 | +{ |
631 | + if (!m_state.contains(windowId)) return defaultValue; |
632 | + return m_state.value(windowId); |
633 | +} |
634 | |
635 | === modified file 'tests/mocks/Utils/windowstatestorage.h' |
636 | --- tests/mocks/Utils/windowstatestorage.h 2015-08-24 15:39:53 +0000 |
637 | +++ tests/mocks/Utils/windowstatestorage.h 2015-10-26 13:53:58 +0000 |
638 | @@ -22,9 +22,17 @@ |
639 | { |
640 | Q_OBJECT |
641 | Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged) |
642 | + Q_ENUMS(WindowState) |
643 | public: |
644 | + enum WindowState { |
645 | + WindowStateNormal, |
646 | + WindowStateMaximized |
647 | + }; |
648 | WindowStateStorage(QObject *parent = 0); |
649 | |
650 | + Q_INVOKABLE void saveState(const QString &windowId, WindowState state); |
651 | + Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue); |
652 | + |
653 | Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect); |
654 | Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue); |
655 | |
656 | @@ -35,5 +43,6 @@ |
657 | void setGeometry(const QVariantMap& geometry); |
658 | QVariantMap geometry() const; |
659 | |
660 | + QHash<QString, WindowState> m_state; |
661 | QVariantMap m_geometry; |
662 | }; |
663 | |
664 | === modified file 'tests/qmltests/Stages/tst_DesktopStage.qml' |
665 | --- tests/qmltests/Stages/tst_DesktopStage.qml 2015-09-17 12:25:29 +0000 |
666 | +++ tests/qmltests/Stages/tst_DesktopStage.qml 2015-10-26 13:53:58 +0000 |
667 | @@ -53,14 +53,10 @@ |
668 | |
669 | focus: true |
670 | |
671 | - property bool itemDestroyed: false |
672 | sourceComponent: Component { |
673 | DesktopStage { |
674 | color: "darkblue" |
675 | anchors.fill: parent |
676 | - Component.onDestruction: { |
677 | - desktopStageLoader.itemDestroyed = true; |
678 | - } |
679 | } |
680 | } |
681 | } |
682 | @@ -95,16 +91,10 @@ |
683 | property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null |
684 | |
685 | function cleanup() { |
686 | - desktopStageLoader.itemDestroyed = false; |
687 | desktopStageLoader.active = false; |
688 | |
689 | tryCompare(desktopStageLoader, "status", Loader.Null); |
690 | tryCompare(desktopStageLoader, "item", null); |
691 | - // Loader.status might be Loader.Null and Loader.item might be null but the Loader |
692 | - // actually took place. Likely because Loader waits until the next event loop |
693 | - // iteration to do its work. So to ensure the reload, we will wait until the |
694 | - // Shell instance gets destroyed. |
695 | - tryCompare(desktopStageLoader, "itemDestroyed", true); |
696 | |
697 | killAllRunningApps(); |
698 | |
699 | @@ -117,7 +107,7 @@ |
700 | var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0 |
701 | ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId); |
702 | } |
703 | - compare(ApplicationManager.count, 1) |
704 | + compare(ApplicationManager.count, 1); |
705 | } |
706 | |
707 | function waitUntilAppSurfaceShowsUp(appId) { |
708 | @@ -147,10 +137,7 @@ |
709 | } |
710 | |
711 | function test_appFocusSwitch(data) { |
712 | - var i; |
713 | - for (i = 0; i < data.apps.length; i++) { |
714 | - startApplication(data.apps[i]); |
715 | - } |
716 | + data.apps.forEach(startApplication); |
717 | |
718 | ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]); |
719 | tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true); |
720 | @@ -167,10 +154,7 @@ |
721 | } |
722 | |
723 | function test_tappingOnWindowChangesFocusedApp(data) { |
724 | - var i; |
725 | - for (i = 0; i < data.apps.length; i++) { |
726 | - startApplication(data.apps[i]); |
727 | - } |
728 | + data.apps.forEach(startApplication); |
729 | var fromAppId = data.apps[data.focusfrom]; |
730 | var toAppId = data.apps[data.focusTo] |
731 | |
732 | @@ -187,18 +171,37 @@ |
733 | compare(ApplicationManager.focusedApplicationId, toAppId); |
734 | } |
735 | |
736 | + function test_clickingOnWindowChangesFocusedApp_data() { |
737 | + return test_tappingOnWindowChangesFocusedApp_data(); // reuse test data |
738 | + } |
739 | + |
740 | + function test_clickingOnWindowChangesFocusedApp(data) { |
741 | + data.apps.forEach(startApplication); |
742 | + var fromAppId = data.apps[data.focusfrom]; |
743 | + var toAppId = data.apps[data.focusTo] |
744 | + |
745 | + var fromAppWindow = findChild(desktopStage, "appWindow_" + fromAppId); |
746 | + verify(fromAppWindow); |
747 | + mouseClick(fromAppWindow); |
748 | + compare(fromAppWindow.application.session.surface.activeFocus, true); |
749 | + compare(ApplicationManager.focusedApplicationId, fromAppId); |
750 | + |
751 | + var toAppWindow = findChild(desktopStage, "appWindow_" + toAppId); |
752 | + verify(toAppWindow); |
753 | + mouseClick(toAppWindow); |
754 | + compare(toAppWindow.application.session.surface.activeFocus, true); |
755 | + compare(ApplicationManager.focusedApplicationId, toAppId); |
756 | + } |
757 | + |
758 | function test_tappingOnDecorationFocusesApplication_data() { |
759 | return [ |
760 | - {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 }, |
761 | - {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 }, |
762 | + {tag: "dash to dialer", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 }, |
763 | + {tag: "dialer to dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 }, |
764 | ] |
765 | } |
766 | |
767 | function test_tappingOnDecorationFocusesApplication(data) { |
768 | - var i; |
769 | - for (i = 0; i < data.apps.length; i++) { |
770 | - startApplication(data.apps[i]); |
771 | - } |
772 | + data.apps.forEach(startApplication); |
773 | |
774 | var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]); |
775 | verify(fromAppDecoration); |
776 | @@ -210,5 +213,97 @@ |
777 | tap(toAppDecoration); |
778 | tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true); |
779 | } |
780 | + |
781 | + function test_clickingOnDecorationFocusesApplication_data() { |
782 | + return test_tappingOnDecorationFocusesApplication_data(); // reuse test data |
783 | + } |
784 | + |
785 | + function test_clickingOnDecorationFocusesApplication(data) { |
786 | + data.apps.forEach(startApplication); |
787 | + |
788 | + var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]); |
789 | + verify(fromAppDecoration); |
790 | + mouseClick(fromAppDecoration); |
791 | + tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true); |
792 | + |
793 | + var toAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusTo]); |
794 | + verify(toAppDecoration); |
795 | + mouseClick(toAppDecoration); |
796 | + tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true); |
797 | + } |
798 | + |
799 | + function test_windowMaximize() { |
800 | + var apps = ["unity8-dash", "dialer-app", "camera-app"]; |
801 | + apps.forEach(startApplication); |
802 | + var appName = "dialer-app"; |
803 | + var appDelegate = findChild(desktopStage, "appDelegate_" + appName); |
804 | + verify(appDelegate); |
805 | + ApplicationManager.focusApplication(appName); |
806 | + keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize |
807 | + tryCompare(appDelegate, "maximized", true); |
808 | + tryCompare(appDelegate, "minimized", false); |
809 | + } |
810 | + |
811 | + function test_windowMaximizeLeft() { |
812 | + var apps = ["unity8-dash", "dialer-app", "camera-app"]; |
813 | + apps.forEach(startApplication); |
814 | + var appName = "dialer-app"; |
815 | + var appDelegate = findChild(desktopStage, "appDelegate_" + appName); |
816 | + verify(appDelegate); |
817 | + ApplicationManager.focusApplication(appName); |
818 | + keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft |
819 | + tryCompare(appDelegate, "maximized", false); |
820 | + tryCompare(appDelegate, "minimized", false); |
821 | + tryCompare(appDelegate, "maximizedLeft", true); |
822 | + tryCompare(appDelegate, "maximizedRight", false); |
823 | + } |
824 | + |
825 | + function test_windowMaximizeRight() { |
826 | + var apps = ["unity8-dash", "dialer-app", "camera-app"]; |
827 | + apps.forEach(startApplication); |
828 | + var appName = "dialer-app"; |
829 | + var appDelegate = findChild(desktopStage, "appDelegate_" + appName); |
830 | + verify(appDelegate); |
831 | + ApplicationManager.focusApplication(appName); |
832 | + keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight |
833 | + tryCompare(appDelegate, "maximized", false); |
834 | + tryCompare(appDelegate, "minimized", false); |
835 | + tryCompare(appDelegate, "maximizedLeft", false); |
836 | + tryCompare(appDelegate, "maximizedRight", true); |
837 | + } |
838 | + |
839 | + function test_windowMinimize() { |
840 | + var apps = ["unity8-dash", "dialer-app", "camera-app"]; |
841 | + apps.forEach(startApplication); |
842 | + var appName = "dialer-app"; |
843 | + var appDelegate = findChild(desktopStage, "appDelegate_" + appName); |
844 | + verify(appDelegate); |
845 | + ApplicationManager.focusApplication(appName); |
846 | + keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize |
847 | + tryCompare(appDelegate, "maximized", false); |
848 | + tryCompare(appDelegate, "minimized", true); |
849 | + verify(ApplicationManager.focusedApplicationId != ""); // verify we don't lose focus when minimizing an app |
850 | + } |
851 | + |
852 | + function test_windowMinimizeAll() { |
853 | + var apps = ["unity8-dash", "dialer-app", "camera-app"]; |
854 | + apps.forEach(startApplication); |
855 | + verify(ApplicationManager.count == 3); |
856 | + keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all |
857 | + tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify no app is focused |
858 | + } |
859 | + |
860 | + function test_windowClose() { |
861 | + var apps = ["unity8-dash", "dialer-app", "camera-app"]; |
862 | + apps.forEach(startApplication); |
863 | + verify(ApplicationManager.count == 3); |
864 | + var appName = "dialer-app"; |
865 | + var appDelegate = findChild(desktopStage, "appDelegate_" + appName); |
866 | + verify(appDelegate); |
867 | + ApplicationManager.focusApplication(appName); |
868 | + keyClick(Qt.Key_F4, Qt.AltModifier); // Alt+F4 shortcut to close |
869 | + verify(ApplicationManager.count == 2); // verify the app is gone |
870 | + verify(ApplicationManager.findApplication(appName) === null); // and it's not in running apps |
871 | + } |
872 | } |
873 | } |
874 | |
875 | === modified file 'tests/qmltests/Stages/tst_WindowResizeArea.qml' |
876 | --- tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-10-08 14:27:12 +0000 |
877 | +++ tests/qmltests/Stages/tst_WindowResizeArea.qml 2015-10-26 13:53:58 +0000 |
878 | @@ -44,8 +44,11 @@ |
879 | width: units.gu(20) |
880 | property int windowHeight: height |
881 | property int windowWidth: width |
882 | - onWindowHeightChanged: height = windowHeight |
883 | - onWindowWidthChanged: width = windowWidth |
884 | + state: "normal" |
885 | + |
886 | + function maximize() { |
887 | + state = "maximized" |
888 | + } |
889 | |
890 | WindowResizeArea { |
891 | id: windowResizeArea |
892 | @@ -195,5 +198,35 @@ |
893 | tryCompare(fakeWindow, "width", initialWindowWidth); |
894 | tryCompare(fakeWindow, "height", initialWindowHeight); |
895 | } |
896 | + |
897 | + function test_saveRestoreMaximized() { |
898 | + var initialWindowX = fakeWindow.x; |
899 | + var initialWindowY = fakeWindow.y; |
900 | + |
901 | + var moveDelta = units.gu(5); |
902 | + |
903 | + fakeWindow.x = initialWindowX + moveDelta |
904 | + fakeWindow.y = initialWindowY + moveDelta |
905 | + |
906 | + // Now change the state to maximized. The window should not keep updating the stored values |
907 | + fakeWindow.state = "maximized" |
908 | + fakeWindow.x = 31415 // 0 is too risky to pass the test even when broken |
909 | + fakeWindow.y = 31415 |
910 | + |
911 | + // This will destroy the window and recreate it |
912 | + windowLoader.active = false; |
913 | + waitForRendering(root); |
914 | + windowLoader.active = true; |
915 | + |
916 | + // Make sure it's again where we left it in normal state before destroying |
917 | + tryCompare(fakeWindow, "x", initialWindowX + moveDelta) |
918 | + tryCompare(fakeWindow, "y", initialWindowX + moveDelta) |
919 | + |
920 | + // Make sure maximize() has been called after restoring |
921 | + tryCompare(fakeWindow, "state", "maximized") |
922 | + |
923 | + // clean up |
924 | + fakeWindow.state = "normal" |
925 | + } |
926 | } |
927 | } |
928 | |
929 | === modified file 'tests/qmltests/tst_Shell.qml' |
930 | --- tests/qmltests/tst_Shell.qml 2015-10-20 08:10:03 +0000 |
931 | +++ tests/qmltests/tst_Shell.qml 2015-10-26 13:53:58 +0000 |
932 | @@ -31,6 +31,7 @@ |
933 | import Wizard 0.1 as Wizard |
934 | |
935 | import "../../qml" |
936 | +import "../../qml/Components/PanelState" |
937 | import "Stages" |
938 | |
939 | Rectangle { |
940 | @@ -1707,5 +1708,73 @@ |
941 | |
942 | keyRelease(Qt.Key_Control); |
943 | } |
944 | + |
945 | + // regression test for http://pad.lv/1443319 |
946 | + function test_closeMaximizedAndRestart() { |
947 | + loadDesktopShellWithApps(); |
948 | + |
949 | + var appRepeater = findChild(shell, "appRepeater") |
950 | + var appId = ApplicationManager.get(0).appId; |
951 | + var appDelegate = appRepeater.itemAt(0); |
952 | + var maximizeButton = findChild(appDelegate, "maximizeWindowButton"); |
953 | + |
954 | + tryCompare(appDelegate, "state", "normal"); |
955 | + tryCompare(PanelState, "buttonsVisible", false) |
956 | + |
957 | + mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2); |
958 | + tryCompare(appDelegate, "state", "maximized"); |
959 | + tryCompare(PanelState, "buttonsVisible", true) |
960 | + |
961 | + ApplicationManager.stopApplication(appId); |
962 | + tryCompare(PanelState, "buttonsVisible", false) |
963 | + |
964 | + ApplicationManager.startApplication(appId); |
965 | + tryCompare(PanelState, "buttonsVisible", true) |
966 | + } |
967 | + |
968 | + // bug http://pad.lv/1431566 |
969 | + function test_switchToStagedHidesPanelButtons() { |
970 | + loadDesktopShellWithApps(); |
971 | + var appRepeater = findChild(shell, "appRepeater") |
972 | + var appId = ApplicationManager.get(0).appId; |
973 | + var appDelegate = appRepeater.itemAt(0); |
974 | + var panelButtons = findChild(shell, "panelWindowControlButtons") |
975 | + |
976 | + tryCompare(appDelegate, "state", "normal"); |
977 | + tryCompare(panelButtons, "visible", false); |
978 | + |
979 | + appDelegate.maximize(false); |
980 | + tryCompare(panelButtons, "visible", true); |
981 | + |
982 | + shell.usageScenario = "phone"; |
983 | + waitForRendering(shell); |
984 | + tryCompare(panelButtons, "visible", false); |
985 | + |
986 | + shell.usageScenario = "desktop"; |
987 | + waitForRendering(shell); |
988 | + tryCompare(panelButtons, "visible", true); |
989 | + } |
990 | + |
991 | + function test_lockingGreeterHidesPanelButtons() { |
992 | + loadDesktopShellWithApps(); |
993 | + var appRepeater = findChild(shell, "appRepeater") |
994 | + var appId = ApplicationManager.get(0).appId; |
995 | + var appDelegate = appRepeater.itemAt(0); |
996 | + var panelButtons = findChild(shell, "panelWindowControlButtons") |
997 | + |
998 | + tryCompare(appDelegate, "state", "normal"); |
999 | + tryCompare(panelButtons, "visible", false); |
1000 | + |
1001 | + appDelegate.maximize(false); |
1002 | + tryCompare(panelButtons, "visible", true); |
1003 | + |
1004 | + LightDM.Greeter.showGreeter(); |
1005 | + waitForRendering(shell); |
1006 | + tryCompare(panelButtons, "visible", false); |
1007 | + |
1008 | + LightDM.Greeter.hideGreeter(); |
1009 | + waitForRendering(shell); |
1010 | + tryCompare(panelButtons, "visible", true); |
1011 | + } |
1012 | } |
1013 | } |
Please follow the commit message format as explained here: https:/ /wiki.ubuntu. com/Process/ Merges/ Checklists/ Unity8