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