on xenial help sign in
save/restore state of windows too, not just geometry
8930f96Michael Zanetti
plugins/Utils/windowstatestorage.cpp+59−20
@@ -47,10 +47,30 @@ WindowStateStorage::~WindowStateStorage()
47 m_db.close();
47 m_db.close();
48}
48}
49
49
50−void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
50+void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
51{
51{
52− QMutexLocker mutexLocker(&s_mutex);
52+ QString queryString = QString("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)
60+{
61+ QString queryString = QString("SELECT * FROM state WHERE windowId = '%1';")
62+ .arg(windowId);
53
63
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+{
54 QString queryString = QString("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
74 QString queryString = QString("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
55 .arg(windowId)
75 .arg(windowId)
56 .arg(rect.x())
76 .arg(rect.x())
@@ -58,15 +78,7 @@ void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect
58 .arg(rect.width())
78 .arg(rect.width())
59 .arg(rect.height());
79 .arg(rect.height());
60
80
61− QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
81+ saveValue(queryString);
62− m_asyncQueries.append(future);
63−
64− QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
65− futureWatcher->setFuture(future);
66− connect(futureWatcher, &QFutureWatcher<void>::finished,
67− this,
68− [=](){ m_asyncQueries.removeAll(futureWatcher->future());
69− futureWatcher->deleteLater(); });
70}
82}
71
83
72void WindowStateStorage::executeAsyncQuery(const QString &queryString)
84void WindowStateStorage::executeAsyncQuery(const QString &queryString)
@@ -84,18 +96,11 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
84
96
85QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
97QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
86{
98{
87− QMutexLocker l(&s_mutex);
88 QString queryString = QString("SELECT * FROM geometry WHERE windowId = '%1';")
99 QString queryString = QString("SELECT * FROM geometry WHERE windowId = '%1';")
89 .arg(windowId);
100 .arg(windowId);
90− QSqlQuery query;
91
101
92− bool ok = query.exec(queryString);
102+ QSqlQuery query = getValue(queryString);
93− if (!ok) {
103+
94− qWarning() << "Error retrieving window state for" << windowId
95− << "Driver error:" << query.lastError().driverText()
96− << "Database error:" << query.lastError().databaseText();
97− return defaultValue;
98− }
99 if (!query.first()) {
104 if (!query.first()) {
100 return defaultValue;
105 return defaultValue;
101 }
106 }
@@ -114,4 +119,38 @@ void WindowStateStorage::initdb()
114 QSqlQuery query;
119 QSqlQuery query;
115 query.exec("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);");
120 query.exec("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);");
116 }
121 }
122+
123+ if (!m_db.tables().contains("state")) {
124+ QSqlQuery query;
125+ query.exec("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
126+ }
127+}
128+
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)
145+{
146+ QMutexLocker l(&s_mutex);
147+ QSqlQuery query;
148+
149+ bool ok = query.exec(queryString);
150+ if (!ok) {
151+ qWarning() << "Error retrieving database query:" << queryString
152+ << "Driver error:" << query.lastError().driverText()
153+ << "Database error:" << query.lastError().databaseText();
154+ }
155+ return query;
117}
156}
plugins/Utils/windowstatestorage.h+12
@@ -22,16 +22,28 @@
22class WindowStateStorage: public QObject
22class WindowStateStorage: public QObject
23{
23{
24 Q_OBJECT
24 Q_OBJECT
25+ Q_ENUMS(WindowState)
25public:
26public:
27+ enum WindowState {
28+ WindowStateNormal,
29+ WindowStateMaximized
30+ };
31+
26 WindowStateStorage(QObject *parent = 0);
32 WindowStateStorage(QObject *parent = 0);
27 virtual ~WindowStateStorage();
33 virtual ~WindowStateStorage();
28
34
35+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
36+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
37+
29 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
38 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
30 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
39 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
31
40
32private:
41private:
33 void initdb();
42 void initdb();
34
43
44+ void saveValue(const QString &queryString);
45+ QSqlQuery getValue(const QString &queryString);
46+
35 static void executeAsyncQuery(const QString &queryString);
47 static void executeAsyncQuery(const QString &queryString);
36 static QMutex s_mutex;
48 static QMutex s_mutex;
37
49
qml/Components/WindowControlButtons.qml+3
@@ -28,6 +28,7 @@ Row {
28 signal maximize()
28 signal maximize()
29
29
30 Rectangle {
30 Rectangle {
31+ objectName: "closeWindowButton"
31 height: parent.height; width: height; radius: height / 2
32 height: parent.height; width: height; radius: height / 2
32 gradient: Gradient {
33 gradient: Gradient {
33 GradientStop { color: "#F49073"; position: 0 }
34 GradientStop { color: "#F49073"; position: 0 }
@@ -38,6 +39,7 @@ Row {
38 MouseArea { anchors.fill: parent; onClicked: root.close() }
39 MouseArea { anchors.fill: parent; onClicked: root.close() }
39 }
40 }
40 Rectangle {
41 Rectangle {
42+ objectName: "minimizeWindowButton"
41 height: parent.height; width: height; radius: height / 2
43 height: parent.height; width: height; radius: height / 2
42 gradient: Gradient {
44 gradient: Gradient {
43 GradientStop { color: "#92918C"; position: 0 }
45 GradientStop { color: "#92918C"; position: 0 }
@@ -48,6 +50,7 @@ Row {
48 MouseArea { anchors.fill: parent; onClicked: root.minimize() }
50 MouseArea { anchors.fill: parent; onClicked: root.minimize() }
49 }
51 }
50 Rectangle {
52 Rectangle {
53+ objectName: "maximizeWindowButton"
51 height: parent.height; width: height; radius: height / 2
54 height: parent.height; width: height; radius: height / 2
52 gradient: Gradient {
55 gradient: Gradient {
53 GradientStop { color: "#92918C"; position: 0 }
56 GradientStop { color: "#92918C"; position: 0 }
qml/Stages/DesktopStage.qml+8−3
@@ -143,6 +143,7 @@ Rectangle {
143
143
144 property bool maximized: false
144 property bool maximized: false
145 property bool minimized: false
145 property bool minimized: false
146+ property bool animationsEnabled: true
146
147
147 onFocusChanged: {
148 onFocusChanged: {
148 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
149 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
@@ -161,15 +162,18 @@ Rectangle {
161 value: ApplicationInfoInterface.RequestedRunning // Always running for now
162 value: ApplicationInfoInterface.RequestedRunning // Always running for now
162 }
163 }
163
164
164− function maximize() {
165+ function maximize(animated) {
166+ animationsEnabled = (animated === undefined) | animated;
165 minimized = false;
167 minimized = false;
166 maximized = true;
168 maximized = true;
167 }
169 }
168− function minimize() {
170+ function minimize(animated) {
171+ animationsEnabled = (animated === undefined) | animated;
169 maximized = false;
172 maximized = false;
170 minimized = true;
173 minimized = true;
171 }
174 }
172− function unmaximize() {
175+ function unmaximize(animated) {
176+ animationsEnabled = (animated === undefined) | animated;
173 minimized = false;
177 minimized = false;
174 maximized = false;
178 maximized = false;
175 }
179 }
@@ -191,6 +195,7 @@ Rectangle {
191 Transition {
195 Transition {
192 from: "maximized,minimized,normal,"
196 from: "maximized,minimized,normal,"
193 to: "maximized,minimized,normal,"
197 to: "maximized,minimized,normal,"
198+ enabled: appDelegate.animationsEnabled
194 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
199 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
195 },
200 },
196 Transition {
201 Transition {
qml/Stages/WindowMoveResizeArea.qml+33−8
@@ -47,15 +47,40 @@ MouseArea {
47 property bool resizeLeft: false
47 property bool resizeLeft: false
48 property bool resizeRight: false
48 property bool resizeRight: false
49
49
50+ property int normalX: 0
51+ property int normalY: 0
52+ property int normalWidth: 0
53+ property int normalHeight: 0
54+
55+ function updateNormalGeometry() {
56+ if (root.target.state == "normal") {
57+ normalX = root.target.x
58+ normalY = root.target.y
59+ normalWidth = root.target.width
60+ normalHeight = root.target.height
61+ }
62+ }
63+ }
64+
65+ Connections {
66+ target: root.target
67+ onXChanged: priv.updateNormalGeometry();
68+ onYChanged: priv.updateNormalGeometry();
69+ onWidthChanged: priv.updateNormalGeometry();
70+ onHeightChanged: priv.updateNormalGeometry();
50 }
71 }
51
72
52 Component.onCompleted: {
73 Component.onCompleted: {
53− var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
74+ var windowGeometry = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
54− if (windowState !== undefined) {
75+ if (windowGeometry !== undefined) {
55− target.x = windowState.x
76+ target.x = windowGeometry.x
56− target.y = windowState.y
77+ target.y = windowGeometry.y
57− target.width = windowState.width
78+ target.width = windowGeometry.width
58− target.height = windowState.height
79+ target.height = windowGeometry.height
80+ }
81+ var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
82+ if (windowState === WindowStateStorage.WindowStateMaximized) {
83+ target.maximize(false)
59 }
84 }
60 }
85 }
61
86
@@ -100,10 +125,10 @@ MouseArea {
100 target.x += mouseDiff.x;
125 target.x += mouseDiff.x;
101 target.y += mouseDiff.y;
126 target.y += mouseDiff.y;
102 }
127 }
103−
104 }
128 }
105
129
106 Component.onDestruction: {
130 Component.onDestruction: {
107− windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
131+ windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
132+ windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
108 }
133 }
109}
134}
tests/mocks/Utils/windowstatestorage.cpp+11
@@ -44,3 +44,14 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defa
44 if (!m_geometry.contains(windowId)) return defaultValue;
44 if (!m_geometry.contains(windowId)) return defaultValue;
45 return m_geometry.value(windowId).toRect();
45 return m_geometry.value(windowId).toRect();
46}
46}
47+
48+void WindowStateStorage::saveState(const QString &windowId, WindowState state)
49+{
50+ m_state[windowId] = state;
51+}
52+
53+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
54+{
55+ if (!m_state.contains(windowId)) return defaultValue;
56+ return m_state.value(windowId);
57+}
tests/mocks/Utils/windowstatestorage.h+9
@@ -22,9 +22,17 @@ class WindowStateStorage: public QObject
22{
22{
23 Q_OBJECT
23 Q_OBJECT
24 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
24 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
25+ Q_ENUMS(WindowState)
25public:
26public:
27+ enum WindowState {
28+ WindowStateNormal,
29+ WindowStateMaximized
30+ };
26 WindowStateStorage(QObject *parent = 0);
31 WindowStateStorage(QObject *parent = 0);
27
32
33+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
34+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
35+
28 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
36 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
29 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
37 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
30
38
@@ -35,5 +43,6 @@ private:
35 void setGeometry(const QVariantMap& geometry);
43 void setGeometry(const QVariantMap& geometry);
36 QVariantMap geometry() const;
44 QVariantMap geometry() const;
37
45
46+ QHash<QString, WindowState> m_state;
38 QVariantMap m_geometry;
47 QVariantMap m_geometry;
39};
48};
tests/qmltests/tst_Shell.qml+24
@@ -31,6 +31,7 @@ import Powerd 0.1
31import Wizard 0.1 as Wizard
31import Wizard 0.1 as Wizard
32
32
33import "../../qml"
33import "../../qml"
34+import "../../qml/Components/PanelState"
34import "Stages"
35import "Stages"
35
36
36Rectangle {
37Rectangle {
@@ -1731,5 +1732,28 @@ Rectangle {
1731
1732
1732 keyRelease(Qt.Key_Control);
1733 keyRelease(Qt.Key_Control);
1733 }
1734 }
1735+
1736+ // regression test for http://pad.lv/1443319
1737+ function test_closeMaximizedAndRestart() {
1738+ loadDesktopShellWithApps();
1739+
1740+ var appRepeater = findChild(shell, "appRepeater")
1741+ var appId = ApplicationManager.get(0).appId;
1742+ var appDelegate = appRepeater.itemAt(0);
1743+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1744+
1745+ tryCompare(appDelegate, "state", "normal");
1746+ tryCompare(PanelState, "buttonsVisible", false)
1747+
1748+ mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1749+ tryCompare(appDelegate, "state", "maximized");
1750+ tryCompare(PanelState, "buttonsVisible", true)
1751+
1752+ ApplicationManager.stopApplication(appId);
1753+ tryCompare(PanelState, "buttonsVisible", false)
1754+
1755+ ApplicationManager.startApplication(appId);
1756+ tryCompare(PanelState, "buttonsVisible", true)
1757+ }
1734 }
1758 }
1735}
1759}