on xenial help sign in
merge prereq
commit363f4f4effc6e587cf19396bc9c7e80afab4a6a4 authorMichael Zanetti <michael.zanetti@canonical.com> dateMon 2 Nov 2015, 21:42 (11 years ago) parent26855ad479b8b1
16 files changed+660−82
plugins/Utils/windowstatestorage.cpp+61−22 plugins/Utils/windowstatestorage.h+13−1 qml/Components/PanelState/PanelState.qml+2 qml/Components/WindowControlButtons.qml+3 qml/Panel/Panel.qml+28−3 qml/Shell.qml+2 qml/Stages/DecoratedWindow.qml+3−1 qml/Stages/DesktopSpreadDelegate.qml+2−2 qml/Stages/DesktopStage.qml+162−12 qml/Stages/WindowDecoration.qml+8−3 qml/Stages/WindowResizeArea.qml+41−7 tests/mocks/Utils/windowstatestorage.cpp+17 tests/mocks/Utils/windowstatestorage.h+12 tests/qmltests/Stages/tst_DesktopStage.qml+120−25 tests/qmltests/Stages/tst_WindowResizeArea.qml+78−2 tests/qmltests/tst_Shell.qml+108−4
plugins/Utils/windowstatestorage.cpp+61−22
@@ -47,26 +47,38 @@ WindowStateStorage::~WindowStateStorage()
4747 m_db.close();
4848}
4949
50−void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
50+void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
5151{
52− QMutexLocker mutexLocker(&s_mutex);
52+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
53+ .arg(windowId)
54+ .arg((int)state);
55+
56+ saveValue(queryString);
57+}
58+
59+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
60+{
61+ const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
62+ .arg(windowId);
5363
54− QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
64+ QSqlQuery query = getValue(queryString);
65+
66+ if (!query.first()) {
67+ return defaultValue;
68+ }
69+ return (WindowState)query.value("state").toInt();
70+}
71+
72+void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
73+{
74+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
5575 .arg(windowId)
5676 .arg(rect.x())
5777 .arg(rect.y())
5878 .arg(rect.width())
5979 .arg(rect.height());
6080
61− QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
62− m_asyncQueries.append(future);
63−
64− QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
65− futureWatcher->setFuture(future);
66− connect(futureWatcher, &QFutureWatcher<void>::finished,
67− this,
68− [=](){ m_asyncQueries.removeAll(futureWatcher->future());
69− futureWatcher->deleteLater(); });
81+ saveValue(queryString);
7082}
7183
7284void WindowStateStorage::executeAsyncQuery(const QString &queryString)
@@ -82,20 +94,13 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
8294 }
8395}
8496
85−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
97+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
8698{
87− QMutexLocker l(&s_mutex);
8899 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
89100 .arg(windowId);
90− QSqlQuery query;
91101
92− bool ok = query.exec(queryString);
93− if (!ok) {
94− qWarning() << "Error retrieving window state for" << windowId
95− << "Driver error:" << query.lastError().driverText()
96− << "Database error:" << query.lastError().databaseText();
97− return defaultValue;
98− }
102+ QSqlQuery query = getValue(queryString);
103+
99104 if (!query.first()) {
100105 return defaultValue;
101106 }
@@ -114,4 +119,38 @@ void WindowStateStorage::initdb()
114119 QSqlQuery query;
115120 query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
116121 }
122+
123+ if (!m_db.tables().contains("state")) {
124+ QSqlQuery query;
125+ query.exec("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
126+ }
127+}
128+
129+void WindowStateStorage::saveValue(const QString &queryString)
130+{
131+ QMutexLocker mutexLocker(&s_mutex);
132+
133+ QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
134+ m_asyncQueries.append(future);
135+
136+ QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
137+ futureWatcher->setFuture(future);
138+ connect(futureWatcher, &QFutureWatcher<void>::finished,
139+ this,
140+ [=](){ m_asyncQueries.removeAll(futureWatcher->future());
141+ futureWatcher->deleteLater(); });
142+}
143+
144+QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
145+{
146+ QMutexLocker l(&s_mutex);
147+ QSqlQuery query;
148+
149+ bool ok = query.exec(queryString);
150+ if (!ok) {
151+ qWarning() << "Error retrieving database query:" << queryString
152+ << "Driver error:" << query.lastError().driverText()
153+ << "Database error:" << query.lastError().databaseText();
154+ }
155+ return query;
117156}
plugins/Utils/windowstatestorage.h+13−1
@@ -22,16 +22,28 @@
2222class WindowStateStorage: public QObject
2323{
2424 Q_OBJECT
25+ Q_ENUMS(WindowState)
2526public:
27+ enum WindowState {
28+ WindowStateNormal,
29+ WindowStateMaximized
30+ };
31+
2632 WindowStateStorage(QObject *parent = 0);
2733 virtual ~WindowStateStorage();
2834
35+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
36+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
37+
2938 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
30− Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
39+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
3140
3241private:
3342 void initdb();
3443
44+ void saveValue(const QString &queryString);
45+ QSqlQuery getValue(const QString &queryString) const;
46+
3547 static void executeAsyncQuery(const QString &queryString);
3648 static QMutex s_mutex;
3749
qml/Components/PanelState/PanelState.qml+2
@@ -23,6 +23,8 @@ QtObject {
2323 property string title: ""
2424 property bool buttonsVisible: false
2525
26+ property int panelHeight: 0
27+
2628 signal close()
2729 signal minimize()
2830 signal maximize()
qml/Components/WindowControlButtons.qml+3
@@ -28,6 +28,7 @@ Row {
2828 signal maximize()
2929
3030 Rectangle {
31+ objectName: "closeWindowButton"
3132 height: parent.height; width: height; radius: height / 2
3233 gradient: Gradient {
3334 GradientStop { color: "#F49073"; position: 0 }
@@ -38,6 +39,7 @@ Row {
3839 MouseArea { anchors.fill: parent; onClicked: root.close() }
3940 }
4041 Rectangle {
42+ objectName: "minimizeWindowButton"
4143 height: parent.height; width: height; radius: height / 2
4244 gradient: Gradient {
4345 GradientStop { color: "#92918C"; position: 0 }
@@ -48,6 +50,7 @@ Row {
4850 MouseArea { anchors.fill: parent; onClicked: root.minimize() }
4951 }
5052 Rectangle {
53+ objectName: "maximizeWindowButton"
5154 height: parent.height; width: height; radius: height / 2
5255 gradient: Gradient {
5356 GradientStop { color: "#92918C"; position: 0 }
qml/Panel/Panel.qml+28−3
@@ -28,9 +28,16 @@ Item {
2828 property alias callHint: __callHint
2929 property bool fullscreenMode: false
3030 property real indicatorAreaShowProgress: 1.0
31+ property bool locked: false
3132
3233 opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.0
3334
35+ Binding {
36+ target: PanelState
37+ property: "panelHeight"
38+ value: root.panelHeight
39+ }
40+
3441 Rectangle {
3542 id: darkenedArea
3643 property real darkenedOpacity: 0.6
@@ -136,7 +143,7 @@ Item {
136143 }
137144
138145 shown: false
139− width: root.width - (PanelState.buttonsVisible ? windowControlButtons.width : 0)
146+ width: root.width - (windowControlButtons.visible ? windowControlButtons.width + titleLabel.width : 0)
140147 minimizedPanelHeight: units.gu(3)
141148 expandedPanelHeight: units.gu(7)
142149 openedHeight: root.height - indicatorOrangeLine.height
@@ -164,18 +171,36 @@ Item {
164171
165172 WindowControlButtons {
166173 id: windowControlButtons
174+ objectName: "panelWindowControlButtons"
167175 anchors {
168176 left: parent.left
169177 top: parent.top
170178 margins: units.gu(0.7)
171179 }
172180 height: indicators.minimizedPanelHeight - anchors.margins * 2
173− visible: PanelState.buttonsVisible
181+ visible: PanelState.buttonsVisible && !root.locked
174182 onClose: PanelState.close()
175183 onMinimize: PanelState.minimize()
176184 onMaximize: PanelState.maximize()
177185 }
178186
187+ Label {
188+ id: titleLabel
189+ objectName: "windowDecorationTitle"
190+ anchors {
191+ left: windowControlButtons.right
192+ top: parent.top
193+ margins: units.gu(0.7)
194+ }
195+ color: "#DFDBD2"
196+ height: windowControlButtons.height
197+ visible: windowControlButtons.visible
198+ verticalAlignment: Text.AlignVCenter
199+ fontSize: "small"
200+ font.bold: true
201+ text: PanelState.title
202+ }
203+
179204 PanelSeparatorLine {
180205 id: indicatorOrangeLine
181206 anchors {
@@ -189,7 +214,7 @@ Item {
189214 id: __callHint
190215 anchors {
191216 top: parent.top
192− left: PanelState.buttonsVisible ? windowControlButtons.right : parent.left
217+ left: windowControlButtons.visible ? windowControlButtons.right : parent.left
193218 }
194219 height: indicators.minimizedPanelHeight
195220 visible: active && indicators.state == "initial"
qml/Shell.qml+2
@@ -57,6 +57,7 @@ Item {
5757 property bool beingResized
5858 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
5959 property string mode: "full-greeter"
60+ property bool cursorVisible: false
6061 function updateFocusedAppOrientation() {
6162 applicationsDisplayLoader.item.updateFocusedAppOrientation();
6263 }
@@ -536,6 +537,7 @@ Item {
536537
537538 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
538539 || greeter.hasLockedApp
540+ locked: greeter && greeter.active
539541 }
540542
541543 Launcher {
qml/Stages/DecoratedWindow.qml+3−1
@@ -26,6 +26,7 @@ FocusScope {
2626 property alias window: applicationWindow
2727 property alias application: applicationWindow.application
2828 property alias active: decoration.active
29+ property alias title: decoration.title
2930
3031 property bool decorationShown: true
3132 property bool highlightShown: false
@@ -66,7 +67,8 @@ FocusScope {
6667 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
6768 anchors { left: parent.left; top: parent.top; right: parent.right }
6869 height: units.gu(3)
69− title: window.title !== "" ? window.title : model.name
70+ width: root.width
71+ title: window.title !== "" ? window.title : ""
7072 onClose: root.close();
7173 onMaximize: root.maximize();
7274 onMinimize: root.minimize();
qml/Stages/DesktopSpreadDelegate.qml+2−2
@@ -29,8 +29,8 @@ Item {
2929 property bool highlightShown: false
3030 property real shadowOpacity: 1
3131
32− property int windowWidth: application.session && application.session.surface ? application.session.surface.size.width : 0
33− property int windowHeight: application.session && application.session.surface ? application.session.surface.size.height : 0
32+ property int windowWidth: application && application.session && application.session.surface ? application.session.surface.size.width : 0
33+ property int windowHeight: application && application.session && application.session.surface ? application.session.surface.size.height : 0
3434
3535 state: "normal"
3636 states: [
qml/Stages/DesktopStage.qml+162−12
@@ -24,6 +24,7 @@ import "../Components"
2424import "../Components/PanelState"
2525import Utils 0.1
2626import Ubuntu.Gestures 0.1
27+import GlobalShortcut 1.0
2728
2829Rectangle {
2930 id: root
@@ -63,7 +64,15 @@ Rectangle {
6364 spread.state = "";
6465 }
6566
66− ApplicationManager.requestFocusApplication(appId)
67+ ApplicationManager.focusApplication(appId);
68+ }
69+
70+ onApplicationRemoved: {
71+ priv.removeAndFocusPreviousInStack(appId);
72+ }
73+
74+ onFocusedApplicationIdChanged: {
75+ priv.addToFocusStack(priv.focusedAppId);
6776 }
6877
6978 onFocusRequested: {
@@ -78,6 +87,53 @@ Rectangle {
7887 }
7988 }
8089
90+ GlobalShortcut {
91+ id: closeWindowShortcut
92+ shortcut: Qt.AltModifier|Qt.Key_F4
93+ onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
94+ active: priv.focusedAppId !== ""
95+ }
96+
97+ GlobalShortcut {
98+ id: showSpreadShortcut
99+ shortcut: Qt.MetaModifier|Qt.Key_W
100+ onTriggered: spread.state = "altTab"
101+ }
102+
103+ GlobalShortcut {
104+ id: minimizeAllShortcut
105+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
106+ onTriggered: priv.minimizeAllWindows()
107+ }
108+
109+ GlobalShortcut {
110+ id: maximizeWindowShortcut
111+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
112+ onTriggered: priv.focusedAppDelegate.minimized ? priv.focusedAppDelegate.restore() : priv.focusedAppDelegate.maximize()
113+ active: priv.focusedAppDelegate !== null
114+ }
115+
116+ GlobalShortcut {
117+ id: maximizeWindowLeftShortcut
118+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
119+ onTriggered: priv.focusedAppDelegate.maximizeLeft()
120+ active: priv.focusedAppDelegate !== null
121+ }
122+
123+ GlobalShortcut {
124+ id: maximizeWindowRightShortcut
125+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
126+ onTriggered: priv.focusedAppDelegate.maximizeRight()
127+ active: priv.focusedAppDelegate !== null
128+ }
129+
130+ GlobalShortcut {
131+ id: minimizeRestoreShortcut
132+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
133+ onTriggered: priv.focusedAppDelegate.maximized ? priv.focusedAppDelegate.restore() : priv.focusedAppDelegate.minimize()
134+ active: priv.focusedAppDelegate !== null
135+ }
136+
81137 QtObject {
82138 id: priv
83139
@@ -86,6 +142,11 @@ Rectangle {
86142 var index = indexOf(focusedAppId);
87143 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
88144 }
145+ onFocusedAppDelegateChanged: { // restore the window from minimization when we focus it (e.g. using spread)
146+ if (focusedAppDelegate && focusedAppDelegate.minimized) {
147+ focusedAppDelegate.restore();
148+ }
149+ }
89150
90151 function indexOf(appId) {
91152 for (var i = 0; i < ApplicationManager.count; i++) {
@@ -95,6 +156,45 @@ Rectangle {
95156 }
96157 return -1;
97158 }
159+
160+ property var focusStack: [] // focus stack of appIds
161+
162+ function addToFocusStack(appId) {
163+ var oldIndex = focusStack.indexOf(appId);
164+ if (oldIndex != -1) {
165+ // remove the old item
166+ focusStack.splice(oldIndex, 1);
167+ }
168+ // insert to the top of the focus stack
169+ focusStack.unshift(appId);
170+ }
171+
172+ function removeAndFocusPreviousInStack(appId) {
173+ var removedIndex = focusStack.indexOf(appId);
174+ if (removedIndex != -1) {
175+ focusStack.splice(removedIndex, 1); // remove one item from the focus stack
176+ focusFirstInStack(); // focus the first one
177+ }
178+ }
179+
180+ function focusFirstInStack() {
181+ var newHead = focusStack[0];
182+ if (newHead !== "") {
183+ ApplicationManager.focusApplication(newHead);
184+ }
185+ }
186+
187+ function minimizeAllWindows() {
188+ focusStack.forEach(function(appId) {
189+ var appDelegate = appRepeater.itemAt(indexOf(appId));
190+ if (appDelegate && !appDelegate.minimized) {
191+ // we don't want to change the focus to a different window
192+ appDelegate.minimized = true;
193+ }
194+ });
195+ ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
196+ focusStack = [];
197+ }
98198 }
99199
100200 Connections {
@@ -103,15 +203,24 @@ Rectangle {
103203 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
104204 }
105205 onMinimize: appRepeater.itemAt(0).minimize();
106− onMaximize: appRepeater.itemAt(0).unmaximize();
206+ onMaximize: appRepeater.itemAt(0).restore();
107207 }
108208
109209 Binding {
110210 target: PanelState
111211 property: "buttonsVisible"
112− value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
212+ value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized
213+ }
214+
215+ Binding {
216+ target: PanelState
217+ property: "title"
218+ value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.title
219+ when: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
113220 }
114221
222+ Component.onDestruction: PanelState.buttonsVisible = false;
223+
115224 FocusScope {
116225 id: appContainer
117226 objectName: "appContainer"
@@ -132,18 +241,24 @@ Rectangle {
132241
133242 delegate: FocusScope {
134243 id: appDelegate
244+ objectName: "appDelegate_" + appId
135245 z: ApplicationManager.count - index
136246 y: units.gu(3)
137247 width: units.gu(60)
138248 height: units.gu(50)
139− focus: model.appId === priv.focusedAppId
249+ focus: appId === priv.focusedAppId
140250
141251 property bool maximized: false
252+ property bool maximizedLeft: false
253+ property bool maximizedRight: false
142254 property bool minimized: false
255+ readonly property string appId: model.appId
256+ property bool animationsEnabled: true
257+ property alias title: decoratedWindow.title
143258
144259 onFocusChanged: {
145− if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
146− ApplicationManager.focusApplication(model.appId);
260+ if (focus && ApplicationManager.focusedApplicationId !== appId) {
261+ ApplicationManager.focusApplication(appId);
147262 }
148263 }
149264
@@ -158,27 +273,59 @@ Rectangle {
158273 value: ApplicationInfoInterface.RequestedRunning // Always running for now
159274 }
160275
161− function maximize() {
276+ function maximize(animated) {
277+ animationsEnabled = (animated === undefined) || animated;
162278 minimized = false;
163279 maximized = true;
280+ maximizedLeft = false;
281+ maximizedRight = false;
282+ }
283+ function maximizeLeft() {
284+ minimized = false;
285+ maximized = false;
286+ maximizedLeft = true;
287+ maximizedRight = false;
288+ }
289+ function maximizeRight() {
290+ minimized = false;
291+ maximized = false;
292+ maximizedLeft = false;
293+ maximizedRight = true;
164294 }
165− function minimize() {
295+ function minimize(animated) {
296+ animationsEnabled = (animated === undefined) || animated;
166297 maximized = false;
167298 minimized = true;
299+ maximizedLeft = false;
300+ maximizedRight = false;
301+ priv.removeAndFocusPreviousInStack(appId);
168302 }
169− function unmaximize() {
303+ function restore(animated) {
304+ animationsEnabled = (animated === undefined) || animated;
170305 minimized = false;
171306 maximized = false;
307+ maximizedLeft = false;
308+ maximizedRight = false;
309+ priv.addToFocusStack(appId);
172310 }
173311
174312 states: [
175313 State {
176314 name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized
315+ && !appDelegate.maximizedLeft && !appDelegate.maximizedRight
177316 },
178317 State {
179318 name: "maximized"; when: appDelegate.maximized
180319 PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
181320 },
321+ State {
322+ name: "maximized_left"; when: appDelegate.maximizedLeft
323+ PropertyChanges { target: appDelegate; x: 0; y: units.gu(3); width: root.width/2; height: root.height }
324+ },
325+ State {
326+ name: "maximized_right"; when: appDelegate.maximizedRight
327+ PropertyChanges { target: appDelegate; x: root.width/2; y: units.gu(3); width: root.width/2; height: root.height }
328+ },
182329 State {
183330 name: "minimized"; when: appDelegate.minimized
184331 PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
@@ -186,8 +333,9 @@ Rectangle {
186333 ]
187334 transitions: [
188335 Transition {
189− from: "maximized,minimized,normal,"
190− to: "maximized,minimized,normal,"
336+ from: "maximized,maximized_left,maximized_right,minimized,normal,"
337+ to: "maximized,maximized_left,maximized_right,minimized,normal,"
338+ enabled: appDelegate.animationsEnabled
191339 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
192340 },
193341 Transition {
@@ -218,6 +366,8 @@ Rectangle {
218366 minHeight: units.gu(10)
219367 borderThickness: units.gu(2)
220368 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
369+ screenWidth: root.width
370+ screenHeight: root.height
221371
222372 onPressed: { ApplicationManager.focusApplication(model.appId) }
223373 }
@@ -234,7 +384,7 @@ Rectangle {
234384 focus: true
235385
236386 onClose: ApplicationManager.stopApplication(model.appId)
237− onMaximize: appDelegate.maximize()
387+ onMaximize: appDelegate.maximized ? appDelegate.restore() : appDelegate.maximize()
238388 onMinimize: appDelegate.minimize()
239389 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
240390 }
qml/Stages/WindowDecoration.qml+8−3
@@ -18,6 +18,7 @@ import QtQuick 2.3
1818import Unity.Application 0.1 // For Mir singleton
1919import Ubuntu.Components 1.1
2020import "../Components"
21+import "../Components/PanelState"
2122
2223MouseArea {
2324 id: root
@@ -45,17 +46,18 @@ MouseArea {
4546 priv.distanceX = pos.x;
4647 priv.distanceY = pos.y;
4748 priv.dragging = true;
48− Mir.cursorName = "grabbing";
4949 } else {
5050 priv.dragging = false;
5151 Mir.cursorName = "";
5252 }
5353 }
54+
5455 onPositionChanged: {
5556 if (priv.dragging) {
57+ Mir.cursorName = "grabbing";
5658 var pos = mapToItem(root.target.parent, mouseX, mouseY);
5759 root.target.x = pos.x - priv.distanceX;
58− root.target.y = pos.y - priv.distanceY;
60+ root.target.y = Math.max(pos.y - priv.distanceY, PanelState.panelHeight);
5961 }
6062 }
6163
@@ -70,11 +72,12 @@ MouseArea {
7072 }
7173
7274 Row {
73− anchors { left: parent.left; top: parent.top; bottom: parent.bottom; margins: units.gu(0.7) }
75+ anchors { fill: parent; margins: units.gu(0.7) }
7476 spacing: units.gu(1)
7577 opacity: root.active ? 1 : 0.5
7678
7779 WindowControlButtons {
80+ id: buttons
7881 height: parent.height
7982 onClose: root.close();
8083 onMinimize: root.minimize();
@@ -86,9 +89,11 @@ MouseArea {
8689 objectName: "windowDecorationTitle"
8790 color: "#DFDBD2"
8891 height: parent.height
92+ width: parent.width - buttons.width - parent.anchors.rightMargin - parent.anchors.leftMargin
8993 verticalAlignment: Text.AlignVCenter
9094 fontSize: "small"
9195 font.bold: true
96+ elide: Text.ElideRight
9297 }
9398 }
9499}
qml/Stages/WindowResizeArea.qml+41−7
@@ -18,6 +18,7 @@ import QtQuick 2.3
1818import Ubuntu.Components 1.1
1919import Utils 0.1
2020import Unity.Application 0.1 // for Mir.cursorName
21+import "../Components/PanelState"
2122
2223MouseArea {
2324 id: root
@@ -35,19 +36,52 @@ MouseArea {
3536 property int borderThickness: 0
3637 property int minWidth: 0
3738 property int minHeight: 0
39+ property int screenWidth: 0
40+ property int screenHeight: 0
41+
42+ QtObject {
43+ id: priv
44+
45+ property int normalX: 0
46+ property int normalY: 0
47+ property int normalWidth: 0
48+ property int normalHeight: 0
49+
50+ function updateNormalGeometry() {
51+ if (root.target.state == "normal") {
52+ normalX = root.target.x
53+ normalY = root.target.y
54+ normalWidth = root.target.width
55+ normalHeight = root.target.height
56+ }
57+ }
58+ }
59+
60+ Connections {
61+ target: root.target
62+ onXChanged: priv.updateNormalGeometry();
63+ onYChanged: priv.updateNormalGeometry();
64+ onWidthChanged: priv.updateNormalGeometry();
65+ onHeightChanged: priv.updateNormalGeometry();
66+ }
3867
3968 Component.onCompleted: {
40− var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
41− if (windowState !== undefined) {
42− target.x = windowState.x
43− target.y = windowState.y
44− target.width = windowState.width
45− target.height = windowState.height
69+ var windowGeometry = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
70+ if (windowGeometry !== undefined) {
71+ target.width = Math.min(windowGeometry.width, root.screenWidth)
72+ target.height = Math.min(windowGeometry.height, root.screenHeight - PanelState.panelHeight)
73+ target.x = Math.max(Math.min(windowGeometry.x, root.screenWidth - target.width), 0)
74+ target.y = Math.max(Math.min(windowGeometry.y, root.screenHeight - target.height), PanelState.panelHeight)
75+ }
76+ var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
77+ if (windowState === WindowStateStorage.WindowStateMaximized) {
78+ target.maximize(false)
4679 }
4780 }
4881
4982 Component.onDestruction: {
50− windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
83+ windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
84+ windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
5185 }
5286
5387 QtObject {
tests/mocks/Utils/windowstatestorage.cpp+17
@@ -44,3 +44,20 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defa
4444 if (!m_geometry.contains(windowId)) return defaultValue;
4545 return m_geometry.value(windowId).toRect();
4646}
47+
48+void WindowStateStorage::clear()
49+{
50+ m_state.clear();
51+ m_geometry.clear();
52+}
53+
54+void WindowStateStorage::saveState(const QString &windowId, WindowState state)
55+{
56+ m_state[windowId] = state;
57+}
58+
59+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
60+{
61+ if (!m_state.contains(windowId)) return defaultValue;
62+ return m_state.value(windowId);
63+}
tests/mocks/Utils/windowstatestorage.h+12
@@ -22,12 +22,23 @@ class WindowStateStorage: public QObject
2222{
2323 Q_OBJECT
2424 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
25+ Q_ENUMS(WindowState)
2526public:
27+ enum WindowState {
28+ WindowStateNormal,
29+ WindowStateMaximized
30+ };
2631 WindowStateStorage(QObject *parent = 0);
2732
33+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
34+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
35+
2836 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
2937 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
3038
39+ // Only in the mock, to easily restore a fresh state
40+ Q_INVOKABLE void clear();
41+
3142Q_SIGNALS:
3243 void geometryChanged(const QVariantMap& geometry);
3344
@@ -35,5 +46,6 @@ private:
3546 void setGeometry(const QVariantMap& geometry);
3647 QVariantMap geometry() const;
3748
49+ QHash<QString, WindowState> m_state;
3850 QVariantMap m_geometry;
3951};
tests/qmltests/Stages/tst_DesktopStage.qml+120−25
@@ -53,14 +53,10 @@ Item {
5353
5454 focus: true
5555
56− property bool itemDestroyed: false
5756 sourceComponent: Component {
5857 DesktopStage {
5958 color: "darkblue"
6059 anchors.fill: parent
61− Component.onDestruction: {
62− desktopStageLoader.itemDestroyed = true;
63− }
6460 }
6561 }
6662 }
@@ -95,16 +91,10 @@ Item {
9591 property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null
9692
9793 function cleanup() {
98− desktopStageLoader.itemDestroyed = false;
9994 desktopStageLoader.active = false;
10095
10196 tryCompare(desktopStageLoader, "status", Loader.Null);
10297 tryCompare(desktopStageLoader, "item", null);
103− // Loader.status might be Loader.Null and Loader.item might be null but the Loader
104− // actually took place. Likely because Loader waits until the next event loop
105− // iteration to do its work. So to ensure the reload, we will wait until the
106− // Shell instance gets destroyed.
107− tryCompare(desktopStageLoader, "itemDestroyed", true);
10898
10999 killAllRunningApps();
110100
@@ -117,7 +107,7 @@ Item {
117107 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
118108 ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
119109 }
120− compare(ApplicationManager.count, 1)
110+ compare(ApplicationManager.count, 1);
121111 }
122112
123113 function waitUntilAppSurfaceShowsUp(appId) {
@@ -147,10 +137,7 @@ Item {
147137 }
148138
149139 function test_appFocusSwitch(data) {
150− var i;
151− for (i = 0; i < data.apps.length; i++) {
152− startApplication(data.apps[i]);
153− }
140+ data.apps.forEach(startApplication);
154141
155142 ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]);
156143 tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
@@ -167,10 +154,7 @@ Item {
167154 }
168155
169156 function test_tappingOnWindowChangesFocusedApp(data) {
170− var i;
171− for (i = 0; i < data.apps.length; i++) {
172− startApplication(data.apps[i]);
173− }
157+ data.apps.forEach(startApplication);
174158 var fromAppId = data.apps[data.focusfrom];
175159 var toAppId = data.apps[data.focusTo]
176160
@@ -187,18 +171,37 @@ Item {
187171 compare(ApplicationManager.focusedApplicationId, toAppId);
188172 }
189173
174+ function test_clickingOnWindowChangesFocusedApp_data() {
175+ return test_tappingOnWindowChangesFocusedApp_data(); // reuse test data
176+ }
177+
178+ function test_clickingOnWindowChangesFocusedApp(data) {
179+ data.apps.forEach(startApplication);
180+ var fromAppId = data.apps[data.focusfrom];
181+ var toAppId = data.apps[data.focusTo]
182+
183+ var fromAppWindow = findChild(desktopStage, "appWindow_" + fromAppId);
184+ verify(fromAppWindow);
185+ mouseClick(fromAppWindow);
186+ compare(fromAppWindow.application.session.surface.activeFocus, true);
187+ compare(ApplicationManager.focusedApplicationId, fromAppId);
188+
189+ var toAppWindow = findChild(desktopStage, "appWindow_" + toAppId);
190+ verify(toAppWindow);
191+ mouseClick(toAppWindow);
192+ compare(toAppWindow.application.session.surface.activeFocus, true);
193+ compare(ApplicationManager.focusedApplicationId, toAppId);
194+ }
195+
190196 function test_tappingOnDecorationFocusesApplication_data() {
191197 return [
192− {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
193− {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
198+ {tag: "dash to dialer", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
199+ {tag: "dialer to dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
194200 ]
195201 }
196202
197203 function test_tappingOnDecorationFocusesApplication(data) {
198− var i;
199− for (i = 0; i < data.apps.length; i++) {
200− startApplication(data.apps[i]);
201− }
204+ data.apps.forEach(startApplication);
202205
203206 var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
204207 verify(fromAppDecoration);
@@ -210,5 +213,97 @@ Item {
210213 tap(toAppDecoration);
211214 tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
212215 }
216+
217+ function test_clickingOnDecorationFocusesApplication_data() {
218+ return test_tappingOnDecorationFocusesApplication_data(); // reuse test data
219+ }
220+
221+ function test_clickingOnDecorationFocusesApplication(data) {
222+ data.apps.forEach(startApplication);
223+
224+ var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
225+ verify(fromAppDecoration);
226+ mouseClick(fromAppDecoration);
227+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
228+
229+ var toAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusTo]);
230+ verify(toAppDecoration);
231+ mouseClick(toAppDecoration);
232+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
233+ }
234+
235+ function test_windowMaximize() {
236+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
237+ apps.forEach(startApplication);
238+ var appName = "dialer-app";
239+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
240+ verify(appDelegate);
241+ ApplicationManager.focusApplication(appName);
242+ keyClick(Qt.Key_Up, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Up shortcut to maximize
243+ tryCompare(appDelegate, "maximized", true);
244+ tryCompare(appDelegate, "minimized", false);
245+ }
246+
247+ function test_windowMaximizeLeft() {
248+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
249+ apps.forEach(startApplication);
250+ var appName = "dialer-app";
251+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
252+ verify(appDelegate);
253+ ApplicationManager.focusApplication(appName);
254+ keyClick(Qt.Key_Left, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Left shortcut to maximizeLeft
255+ tryCompare(appDelegate, "maximized", false);
256+ tryCompare(appDelegate, "minimized", false);
257+ tryCompare(appDelegate, "maximizedLeft", true);
258+ tryCompare(appDelegate, "maximizedRight", false);
259+ }
260+
261+ function test_windowMaximizeRight() {
262+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
263+ apps.forEach(startApplication);
264+ var appName = "dialer-app";
265+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
266+ verify(appDelegate);
267+ ApplicationManager.focusApplication(appName);
268+ keyClick(Qt.Key_Right, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Right shortcut to maximizeRight
269+ tryCompare(appDelegate, "maximized", false);
270+ tryCompare(appDelegate, "minimized", false);
271+ tryCompare(appDelegate, "maximizedLeft", false);
272+ tryCompare(appDelegate, "maximizedRight", true);
273+ }
274+
275+ function test_windowMinimize() {
276+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
277+ apps.forEach(startApplication);
278+ var appName = "dialer-app";
279+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
280+ verify(appDelegate);
281+ ApplicationManager.focusApplication(appName);
282+ keyClick(Qt.Key_Down, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+Down shortcut to minimize
283+ tryCompare(appDelegate, "maximized", false);
284+ tryCompare(appDelegate, "minimized", true);
285+ verify(ApplicationManager.focusedApplicationId != ""); // verify we don't lose focus when minimizing an app
286+ }
287+
288+ function test_windowMinimizeAll() {
289+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
290+ apps.forEach(startApplication);
291+ verify(ApplicationManager.count == 3);
292+ keyClick(Qt.Key_D, Qt.MetaModifier|Qt.ControlModifier); // Ctrl+Super+D shortcut to minimize all
293+ tryCompare(ApplicationManager, "focusedApplicationId", ""); // verify no app is focused
294+ }
295+
296+ function test_windowClose() {
297+ var apps = ["unity8-dash", "dialer-app", "camera-app"];
298+ apps.forEach(startApplication);
299+ verify(ApplicationManager.count == 3);
300+ var appName = "dialer-app";
301+ var appDelegate = findChild(desktopStage, "appDelegate_" + appName);
302+ verify(appDelegate);
303+ ApplicationManager.focusApplication(appName);
304+ keyClick(Qt.Key_F4, Qt.AltModifier); // Alt+F4 shortcut to close
305+ verify(ApplicationManager.count == 2); // verify the app is gone
306+ verify(ApplicationManager.findApplication(appName) === null); // and it's not in running apps
307+ }
213308 }
214309}
tests/qmltests/Stages/tst_WindowResizeArea.qml+78−2
@@ -19,6 +19,7 @@ import QtQuick.Layouts 1.1
1919import QtTest 1.0
2020import Unity.Test 0.1
2121import ".."
22+import "../../../qml/Components/PanelState"
2223import "../../../qml/Stages"
2324import Ubuntu.Components 0.1
2425import Ubuntu.Components.ListItems 1.0 as ListItem
@@ -31,6 +32,12 @@ Item {
3132
3233 property var fakeWindow: windowLoader.item
3334
35+ Binding {
36+ target: PanelState
37+ property: "panelHeight"
38+ value: units.gu(2)
39+ }
40+
3441 Component {
3542 id: fakeWindowComponent
3643
@@ -44,8 +51,11 @@ Item {
4451 width: units.gu(20)
4552 property int windowHeight: height
4653 property int windowWidth: width
47− onWindowHeightChanged: height = windowHeight
48− onWindowWidthChanged: width = windowWidth
54+ state: "normal"
55+
56+ function maximize() {
57+ state = "maximized"
58+ }
4959
5060 WindowResizeArea {
5161 id: windowResizeArea
@@ -54,6 +64,8 @@ Item {
5464 minWidth: units.gu(15)
5565 minHeight: units.gu(10)
5666 windowId: "test-window-id"
67+ screenWidth: root.width
68+ screenHeight: root.height
5769 }
5870
5971 Rectangle {
@@ -195,5 +207,69 @@ Item {
195207 tryCompare(fakeWindow, "width", initialWindowWidth);
196208 tryCompare(fakeWindow, "height", initialWindowHeight);
197209 }
210+
211+ function test_saveRestoreMaximized() {
212+ var initialWindowX = fakeWindow.x;
213+ var initialWindowY = fakeWindow.y;
214+
215+ var moveDelta = units.gu(5);
216+
217+ fakeWindow.x = initialWindowX + moveDelta
218+ fakeWindow.y = initialWindowY + moveDelta
219+
220+ // Now change the state to maximized. The window should not keep updating the stored values
221+ fakeWindow.state = "maximized"
222+ fakeWindow.x = 31415 // 0 is too risky to pass the test even when broken
223+ fakeWindow.y = 31415
224+
225+ // This will destroy the window and recreate it
226+ windowLoader.active = false;
227+ waitForRendering(root);
228+ windowLoader.active = true;
229+
230+ // Make sure it's again where we left it in normal state before destroying
231+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
232+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
233+
234+ // Make sure maximize() has been called after restoring
235+ tryCompare(fakeWindow, "state", "maximized")
236+
237+ // clean up
238+ fakeWindow.state = "normal"
239+ }
240+
241+
242+ function test_restoreMovesIntoBounds_data() {
243+ return [
244+ {tag: "left off", x: -units.gu(5), y: units.gu(5), w: units.gu(10), h: units.gu(10)},
245+ {tag: "top off", x: units.gu(5), y: -units.gu(5), w: units.gu(10), h: units.gu(10)},
246+ {tag: "right off", x: root.width - units.gu(5), y: units.gu(5), w: units.gu(10), h: units.gu(10)},
247+ {tag: "bottom off", x: units.gu(5), y: root.height - units.gu(5), w: units.gu(10), h: units.gu(10)},
248+ {tag: "width too large", x: units.gu(5), y: units.gu(5), w: root.width * 2, h: units.gu(10)},
249+ {tag: "height too large", x: units.gu(5), y: units.gu(5), w: units.gu(10), h: root.height * 2}
250+ ]
251+ }
252+
253+ function test_restoreMovesIntoBounds(data) {
254+ fakeWindow.x = data.x;
255+ fakeWindow.y = data.y;
256+ fakeWindow.width = data.w;
257+ fakeWindow.height = data.h;
258+ waitForRendering(root);
259+
260+ // This will destroy the window and recreate it
261+ windowLoader.active = false;
262+ waitForRendering(root);
263+ windowLoader.active = true;
264+ waitForRendering(root)
265+
266+ // Make sure it's again where we left it in normal state before destroying
267+ compare(fakeWindow.x >= 0, true)
268+ compare(fakeWindow.y >= PanelState.panelHeight, true)
269+ compare(fakeWindow.x + fakeWindow.width <= root.width, true)
270+ compare(fakeWindow.y + fakeWindow.height <= root.height, true)
271+
272+ waitForRendering(root)
273+ }
198274 }
199275}
tests/qmltests/tst_Shell.qml+108−4
@@ -29,8 +29,10 @@ import Unity.Notifications 1.0
2929import Unity.Test 0.1
3030import Powerd 0.1
3131import Wizard 0.1 as Wizard
32+import Utils 0.1
3233
3334import "../../qml"
35+import "../../qml/Components/PanelState"
3436import "Stages"
3537
3638Rectangle {
@@ -302,6 +304,7 @@ Rectangle {
302304 mouseEmulation.checked = true;
303305 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
304306 tearDown();
307+ WindowStateStorage.clear();
305308 }
306309
307310 function loadShell(formFactor) {
@@ -329,9 +332,9 @@ Rectangle {
329332 var app2 = ApplicationManager.startApplication("webbrowser-app")
330333 var app3 = ApplicationManager.startApplication("camera-app")
331334 var app4 = ApplicationManager.startApplication("facebook-webapp")
332− var app5 = ApplicationManager.startApplication("calendar-app")
333− var app6 = ApplicationManager.startApplication("gallery-app")
334335 var app7 = ApplicationManager.startApplication("camera-app")
336+ var app6 = ApplicationManager.startApplication("gallery-app")
337+ var app5 = ApplicationManager.startApplication("calendar-app")
335338 waitUntilAppWindowIsFullyLoaded(app7);
336339 }
337340
@@ -1693,11 +1696,11 @@ Rectangle {
16931696 keyPress(Qt.Key_Control)
16941697 keyClick(Qt.Key_Tab);
16951698
1696−
16971699 tryCompare(desktopSpread, "state", "altTab")
16981700
1699− mouseMove(shell, 0, 0);
1701+ mouseMove(shell, 0, shell.height / 2);
17001702 tryCompare(launcher, "state", "visibleTemporary")
1703+ waitForRendering(shell)
17011704
17021705 mouseClick(bfb, bfb.width / 2, bfb.height / 2)
17031706 tryCompare(launcher, "state", "")
@@ -1707,5 +1710,106 @@ Rectangle {
17071710
17081711 keyRelease(Qt.Key_Control);
17091712 }
1713+
1714+ // regression test for http://pad.lv/1443319
1715+ function test_closeMaximizedAndRestart() {
1716+ loadDesktopShellWithApps();
1717+
1718+ var appRepeater = findChild(shell, "appRepeater")
1719+ var appId = ApplicationManager.get(0).appId;
1720+ var appDelegate = appRepeater.itemAt(0);
1721+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1722+
1723+ wait(5000)
1724+ tryCompare(appDelegate, "state", "normal");
1725+ tryCompare(PanelState, "buttonsVisible", false)
1726+
1727+ wait(5000)
1728+ mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1729+ tryCompare(appDelegate, "state", "maximized");
1730+ tryCompare(PanelState, "buttonsVisible", true)
1731+
1732+ ApplicationManager.stopApplication(appId);
1733+ tryCompare(PanelState, "buttonsVisible", false)
1734+
1735+ ApplicationManager.startApplication(appId);
1736+ tryCompare(PanelState, "buttonsVisible", true)
1737+ }
1738+
1739+ // bug http://pad.lv/1431566
1740+ function test_switchToStagedHidesPanelButtons() {
1741+ loadDesktopShellWithApps();
1742+ var appRepeater = findChild(shell, "appRepeater")
1743+ var appId = ApplicationManager.get(0).appId;
1744+ var appDelegate = appRepeater.itemAt(0);
1745+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1746+
1747+ tryCompare(appDelegate, "state", "normal");
1748+ tryCompare(panelButtons, "visible", false);
1749+
1750+ appDelegate.maximize(false);
1751+ tryCompare(panelButtons, "visible", true);
1752+
1753+ shell.usageScenario = "phone";
1754+ waitForRendering(shell);
1755+ tryCompare(panelButtons, "visible", false);
1756+
1757+ shell.usageScenario = "desktop";
1758+ waitForRendering(shell);
1759+ tryCompare(panelButtons, "visible", true);
1760+ }
1761+
1762+ function test_lockingGreeterHidesPanelButtons() {
1763+ loadDesktopShellWithApps();
1764+ var appRepeater = findChild(shell, "appRepeater")
1765+ var appId = ApplicationManager.get(0).appId;
1766+ var appDelegate = appRepeater.itemAt(0);
1767+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1768+
1769+ tryCompare(appDelegate, "state", "normal");
1770+ tryCompare(panelButtons, "visible", false);
1771+
1772+ appDelegate.maximize(false);
1773+ tryCompare(panelButtons, "visible", true);
1774+
1775+ LightDM.Greeter.showGreeter();
1776+ waitForRendering(shell);
1777+ tryCompare(panelButtons, "visible", false);
1778+
1779+ LightDM.Greeter.hideGreeter();
1780+ waitForRendering(shell);
1781+ tryCompare(panelButtons, "visible", true);
1782+ }
1783+
1784+ function test_cantMoveWindowUnderPanel() {
1785+ loadDesktopShellWithApps();
1786+ var appRepeater = findChild(shell, "appRepeater")
1787+ var appDelegate = appRepeater.itemAt(0);
1788+
1789+ mousePress(appDelegate, appDelegate.width / 2, units.gu(1))
1790+ mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100))
1791+
1792+ compare(appDelegate.y >= PanelState.panelHeight, true);
1793+ }
1794+
1795+ function test_restoreWindowStateFixesIfUnderPanel() {
1796+ loadDesktopShellWithApps();
1797+ var appRepeater = findChild(shell, "appRepeater")
1798+ var appId = ApplicationManager.get(0).appId;
1799+ var appDelegate = appRepeater.itemAt(0);
1800+
1801+ // Move it under the panel programmatically (might happen later with an alt+drag)
1802+ appDelegate.y = -units.gu(10)
1803+
1804+ ApplicationManager.stopApplication(appId)
1805+ ApplicationManager.startApplication(appId)
1806+ waitForRendering(shell)
1807+
1808+ // Make sure the newly started one is at index 0 again
1809+ tryCompare(ApplicationManager.get(0), "appId", appId);
1810+
1811+ appDelegate = appRepeater.itemAt(0);
1812+ compare(appDelegate.y >= PanelState.panelHeight, true);
1813+ }
17101814 }
17111815}