on xenial help sign in
save and restore window size/position
f4c608fMichael Zanetti
qml/Components/WindowPositionStorage/WindowPositionStorage.qmlnew file+67
@@ -0,0 +1,67 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+ */
18+
19+pragma Singleton
20+import QtQuick 2.2
21+import QtQuick.LocalStorage 2.0
22+
23+QtObject {
24+
25+ property var priv: QtObject {
26+ property var db: null
27+
28+ function openDB() {
29+ if(db !== null) return;
30+
31+ // db = LocalStorage.openDatabaseSync(identifier, version, description, estimated_size, callback(db))
32+ db = LocalStorage.openDatabaseSync("unity8", "0.1", "", 100000);
33+
34+ try {
35+ db.transaction(function(tx){
36+ tx.executeSql('CREATE TABLE IF NOT EXISTS settings(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER)');
37+ });
38+ } catch (err) {
39+ console.log("Error creating table in database: " + err);
40+ };
41+ }
42+ }
43+
44+ function savePosition(windowId, x, y, width, height) {
45+ priv.openDB();
46+ priv.db.transaction( function(tx){
47+ tx.executeSql('INSERT OR REPLACE INTO settings VALUES(?, ?, ?, ?, ?)', [windowId, x, y, width, height]);
48+ });
49+ }
50+
51+ function getPosition(windowId) {
52+ priv.openDB();
53+ var res = new Object();
54+ priv.db.transaction(function(tx) {
55+ var rs = tx.executeSql('SELECT x, y, width, height FROM settings WHERE windowId=?;', [windowId]);
56+ if (rs.rows.length === 0) {
57+ res = unefined;
58+ } else {
59+ res.x = rs.rows.item(0).x
60+ res.y = rs.rows.item(0).y
61+ res.width = rs.rows.item(0).width
62+ res.height = rs.rows.item(0).height
63+ }
64+ });
65+ return res;
66+ }
67+}
qml/Components/WindowPositionStorage/qmldirnew file+1
@@ -0,0 +1 @@
1+singleton WindowPositionStorage 1.0 WindowPositionStorage.qml
qml/Stages/DesktopStage.qml+2−2
@@ -38,8 +38,6 @@ Item {
38 Connections {
38 Connections {
39 target: ApplicationManager
39 target: ApplicationManager
40 onApplicationAdded: {
40 onApplicationAdded: {
41− // Initial placement to avoid having the window decoration behind the panel
42− appRepeater.itemAt(ApplicationManager.count-1).y = units.gu(3)
43 ApplicationManager.requestFocusApplication(ApplicationManager.get(ApplicationManager.count-1).appId)
41 ApplicationManager.requestFocusApplication(ApplicationManager.get(ApplicationManager.count-1).appId)
44 }
42 }
45
43
@@ -91,6 +89,7 @@ Item {
91 delegate: Item {
89 delegate: Item {
92 id: appDelegate
90 id: appDelegate
93 z: ApplicationManager.count - index
91 z: ApplicationManager.count - index
92+ y: units.gu(3)
94 width: units.gu(60)
93 width: units.gu(60)
95 height: units.gu(50)
94 height: units.gu(50)
96
95
@@ -121,6 +120,7 @@ Item {
121 minWidth: appDelegate.minWidth
120 minWidth: appDelegate.minWidth
122 minHeight: appDelegate.minHeight
121 minHeight: appDelegate.minHeight
123 resizeHandleWidth: units.gu(0.5)
122 resizeHandleWidth: units.gu(0.5)
123+ windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
124
124
125 onPressed: ApplicationManager.requestFocusApplication(model.appId)
125 onPressed: ApplicationManager.requestFocusApplication(model.appId)
126 }
126 }
qml/Stages/WindowMoveResizeArea.qml+17−1
@@ -18,6 +18,7 @@
18
18
19import QtQuick 2.3
19import QtQuick 2.3
20import Ubuntu.Components 1.1
20import Ubuntu.Components 1.1
21+import "../Components/WindowPositionStorage"
21
22
22MouseArea {
23MouseArea {
23 id: root
24 id: root
@@ -26,7 +27,8 @@ MouseArea {
26
27
27 // The target item managed by this. Must be a parent or a sibling
28 // The target item managed by this. Must be a parent or a sibling
28 // The area will anchor to it and manage move and resize events
29 // The area will anchor to it and manage move and resize events
29− property var target: null
30+ property Item target: null
31+ property string windowId: ""
30 property int resizeHandleWidth: 0
32 property int resizeHandleWidth: 0
31 property int minWidth: 0
33 property int minWidth: 0
32 property int minHeight: 0
34 property int minHeight: 0
@@ -47,6 +49,16 @@ MouseArea {
47
49
48 }
50 }
49
51
52+ Component.onCompleted: {
53+ var settings = WindowPositionStorage.getPosition(root.windowId)
54+ if (settings !== undefined) {
55+ target.x = settings.x
56+ target.y = settings.y
57+ target.width = settings.width
58+ target.height = settings.height
59+ }
60+ }
61+
50 onPressed: {
62 onPressed: {
51 priv.startPoint = mapToItem(null, Qt.point(mouse.x, mouse.y)).x;
63 priv.startPoint = mapToItem(null, Qt.point(mouse.x, mouse.y)).x;
52 priv.startWidth = root.width;
64 priv.startWidth = root.width;
@@ -95,4 +107,8 @@ MouseArea {
95 target.y += mouseDiff.y;
107 target.y += mouseDiff.y;
96 }
108 }
97 }
109 }
110+
111+ onReleased: {
112+ WindowPositionStorage.savePosition(root.windowId,target.x, target.y, target.width, target.height)
113+ }
98}
114}
tests/qmltests/Stages/tst_WindowMoveResizeArea.qml+76−17
@@ -29,20 +29,33 @@ Item {
29 height: units.gu(60)
29 height: units.gu(60)
30 width: units.gu(60)
30 width: units.gu(60)
31
31
32+ property var fakeWindow: windowLoader.item
33+
34+ Component {
35+ id: fakeWindowComponent
36+
32− Rectangle {
37+ Rectangle {
33− id: fakeWindow
38+ id: fakeWindow
34− height: units.gu(20)
35− width: units.gu(20)
36− color: "khaki"
39+ color: "khaki"
40+ property alias minWidth: moveResizeArea.minWidth
41+ property alias minHeight: moveResizeArea.minHeight
42+
37− WindowMoveResizeArea {
43+ WindowMoveResizeArea {
38− id: moveResizeArea
44+ id: moveResizeArea
39− target: fakeWindow
45+ target: fakeWindow
40− resizeHandleWidth: units.gu(0.5)
46+ resizeHandleWidth: units.gu(0.5)
41− minWidth: units.gu(15)
47+ minWidth: units.gu(15)
42− minHeight: units.gu(10)
48+ minHeight: units.gu(10)
49+ windowId: "test-window-id"
50+ }
43 }
51 }
44 }
52 }
45
53
54+ Loader {
55+ id: windowLoader
56+ sourceComponent: fakeWindowComponent
57+ }
58+
46 UT.UnityTestCase {
59 UT.UnityTestCase {
47 when: windowShown
60 when: windowShown
48
61
@@ -100,8 +113,8 @@ Item {
100 var startDragY = initialWindowY + initialWindowHeight + 1
113 var startDragY = initialWindowY + initialWindowHeight + 1
101 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
114 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
102
115
103− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + data.dx, moveResizeArea.minWidth));
116+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + data.dx, fakeWindow.minWidth));
104− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + data.dy, moveResizeArea.minHeight));
117+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + data.dy, fakeWindow.minHeight));
105
118
106 compare(fakeWindow.x, initialWindowX);
119 compare(fakeWindow.x, initialWindowX);
107 compare(fakeWindow.y, initialWindowY);
120 compare(fakeWindow.y, initialWindowY);
@@ -126,13 +139,59 @@ Item {
126 var startDragY = initialWindowY - 1
139 var startDragY = initialWindowY - 1
127 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
140 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
128
141
129− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - data.dx, moveResizeArea.minWidth));
142+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - data.dx, fakeWindow.minWidth));
130− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - data.dy, moveResizeArea.minHeight));
143+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - data.dy, fakeWindow.minHeight));
131
144
132− var maxMoveX = initialWindowWidth - moveResizeArea.minWidth;
145+ var maxMoveX = initialWindowWidth - fakeWindow.minWidth;
133− var maxMoveY = initialWindowHeight - moveResizeArea.minHeight;
146+ var maxMoveY = initialWindowHeight - fakeWindow.minHeight;
134 compare(fakeWindow.x, Math.min(initialWindowX + data.dx, initialWindowX + maxMoveX));
147 compare(fakeWindow.x, Math.min(initialWindowX + data.dx, initialWindowX + maxMoveX));
135 compare(fakeWindow.y, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
148 compare(fakeWindow.y, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
136 }
149 }
150+
151+ function test_saveRestorePosition() {
152+ var initialWindowX = fakeWindow.x;
153+ var initialWindowY = fakeWindow.y;
154+ var initialWindowWidth = fakeWindow.width;
155+ var initialWindowHeight = fakeWindow.height;
156+
157+ var moveDelta = units.gu(5);
158+ var startDragX = initialWindowX + fakeWindow.width / 2;
159+ var startDragY = initialWindowY + fakeWindow.height / 2;
160+ mouseFlick(root, startDragX, startDragY, startDragX + moveDelta, startDragY + moveDelta, true, true, units.gu(.5), 10)
161+
162+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
163+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
164+
165+ // This will destroy the window and recreate it
166+ windowLoader.active = false;
167+ windowLoader.active = true;
168+
169+ // Make sure it's again where we left it before destroying
170+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
171+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
172+ }
173+
174+ function test_saveRestoreSize() {
175+ var initialWindowX = fakeWindow.x;
176+ var initialWindowY = fakeWindow.y;
177+ var initialWindowWidth = fakeWindow.width
178+ var initialWindowHeight = fakeWindow.height
179+
180+ var resizeDelta = units.gu(5)
181+ var startDragX = initialWindowX + initialWindowWidth + 1
182+ var startDragY = initialWindowY + initialWindowHeight + 1
183+ mouseFlick(root, startDragX, startDragY, startDragX + resizeDelta, startDragY + resizeDelta, true, true, units.gu(.5), 10);
184+
185+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.minWidth));
186+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.minHeight));
187+
188+ // This will destroy the window and recreate it
189+ windowLoader.active = false;
190+ windowLoader.active = true;
191+
192+ // Make sure its size is again the same as before
193+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.minWidth));
194+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.minHeight));
195+ }
137 }
196 }
138}
197}