on xenial help sign in
Merge lp:~mzanetti/unity8/panel-button-fixes
commit6e4498b74049dacc165f4006362c583dbd6276cc authorMichał Sawicz <michal.sawicz@canonical.com> dateMon 26 Oct 2015, 12:10 (11 years ago) parent6309ccc33d7eba
11 files changed+255−38
plugins/Utils/windowstatestorage.cpp+61−22 plugins/Utils/windowstatestorage.h+13−1 qml/Components/WindowControlButtons.qml+3 qml/Panel/Panel.qml+5−3 qml/Shell.qml+2 qml/Stages/DesktopStage.qml+9−3 qml/Stages/WindowResizeArea.qml+38−7 tests/mocks/Utils/windowstatestorage.cpp+11 tests/mocks/Utils/windowstatestorage.h+9 tests/qmltests/Stages/tst_WindowResizeArea.qml+35−2 tests/qmltests/tst_Shell.qml+69
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/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+5−3
@@ -28,6 +28,7 @@ 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
@@ -136,7 +137,7 @@ Item {
136137 }
137138
138139 shown: false
139− width: root.width - (PanelState.buttonsVisible ? windowControlButtons.width : 0)
140+ width: root.width - (windowControlButtons.visible ? windowControlButtons.width : 0)
140141 minimizedPanelHeight: units.gu(3)
141142 expandedPanelHeight: units.gu(7)
142143 openedHeight: root.height - indicatorOrangeLine.height
@@ -164,13 +165,14 @@ Item {
164165
165166 WindowControlButtons {
166167 id: windowControlButtons
168+ objectName: "panelWindowControlButtons"
167169 anchors {
168170 left: parent.left
169171 top: parent.top
170172 margins: units.gu(0.7)
171173 }
172174 height: indicators.minimizedPanelHeight - anchors.margins * 2
173− visible: PanelState.buttonsVisible
175+ visible: PanelState.buttonsVisible && !root.locked
174176 onClose: PanelState.close()
175177 onMinimize: PanelState.minimize()
176178 onMaximize: PanelState.maximize()
@@ -189,7 +191,7 @@ Item {
189191 id: __callHint
190192 anchors {
191193 top: parent.top
192− left: PanelState.buttonsVisible ? windowControlButtons.right : parent.left
194+ left: windowControlButtons.visible ? windowControlButtons.right : parent.left
193195 }
194196 height: indicators.minimizedPanelHeight
195197 visible: active && indicators.state == "initial"
qml/Shell.qml+2
@@ -56,6 +56,7 @@ Item {
5656 property bool beingResized
5757 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
5858 property string mode: "full-greeter"
59+ property bool cursorVisible: false
5960 function updateFocusedAppOrientation() {
6061 applicationsDisplayLoader.item.updateFocusedAppOrientation();
6162 }
@@ -530,6 +531,7 @@ Item {
530531
531532 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
532533 || greeter.hasLockedApp
534+ locked: greeter && greeter.active
533535 }
534536
535537 Launcher {
qml/Stages/DesktopStage.qml+9−3
@@ -111,6 +111,7 @@ Rectangle {
111111 property: "buttonsVisible"
112112 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
113113 }
114+ Component.onDestruction: PanelState.buttonsVisible = false;
114115
115116 FocusScope {
116117 id: appContainer
@@ -140,6 +141,7 @@ Rectangle {
140141
141142 property bool maximized: false
142143 property bool minimized: false
144+ property bool animationsEnabled: true
143145
144146 onFocusChanged: {
145147 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
@@ -158,15 +160,18 @@ Rectangle {
158160 value: ApplicationInfoInterface.RequestedRunning // Always running for now
159161 }
160162
161− function maximize() {
163+ function maximize(animated) {
164+ animationsEnabled = (animated === undefined) || animated;
162165 minimized = false;
163166 maximized = true;
164167 }
165− function minimize() {
168+ function minimize(animated) {
169+ animationsEnabled = (animated === undefined) || animated;
166170 maximized = false;
167171 minimized = true;
168172 }
169− function unmaximize() {
173+ function unmaximize(animated) {
174+ animationsEnabled = (animated === undefined) || animated;
170175 minimized = false;
171176 maximized = false;
172177 }
@@ -188,6 +193,7 @@ Rectangle {
188193 Transition {
189194 from: "maximized,minimized,normal,"
190195 to: "maximized,minimized,normal,"
196+ enabled: appDelegate.animationsEnabled
191197 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
192198 },
193199 Transition {
qml/Stages/WindowResizeArea.qml+38−7
@@ -36,18 +36,49 @@ MouseArea {
3636 property int minWidth: 0
3737 property int minHeight: 0
3838
39+ QtObject {
40+ id: priv
41+
42+ property int normalX: 0
43+ property int normalY: 0
44+ property int normalWidth: 0
45+ property int normalHeight: 0
46+
47+ function updateNormalGeometry() {
48+ if (root.target.state == "normal") {
49+ normalX = root.target.x
50+ normalY = root.target.y
51+ normalWidth = root.target.width
52+ normalHeight = root.target.height
53+ }
54+ }
55+ }
56+
57+ Connections {
58+ target: root.target
59+ onXChanged: priv.updateNormalGeometry();
60+ onYChanged: priv.updateNormalGeometry();
61+ onWidthChanged: priv.updateNormalGeometry();
62+ onHeightChanged: priv.updateNormalGeometry();
63+ }
64+
3965 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
66+ var windowGeometry = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
67+ if (windowGeometry !== undefined) {
68+ target.x = windowGeometry.x
69+ target.y = windowGeometry.y
70+ target.width = windowGeometry.width
71+ target.height = windowGeometry.height
72+ }
73+ var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
74+ if (windowState === WindowStateStorage.WindowStateMaximized) {
75+ target.maximize(false)
4676 }
4777 }
4878
4979 Component.onDestruction: {
50− windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
80+ windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
81+ windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
5182 }
5283
5384 QtObject {
tests/mocks/Utils/windowstatestorage.cpp+11
@@ -44,3 +44,14 @@ 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::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
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
@@ -35,5 +43,6 @@ private:
3543 void setGeometry(const QVariantMap& geometry);
3644 QVariantMap geometry() const;
3745
46+ QHash<QString, WindowState> m_state;
3847 QVariantMap m_geometry;
3948};
tests/qmltests/Stages/tst_WindowResizeArea.qml+35−2
@@ -44,8 +44,11 @@ Item {
4444 width: units.gu(20)
4545 property int windowHeight: height
4646 property int windowWidth: width
47− onWindowHeightChanged: height = windowHeight
48− onWindowWidthChanged: width = windowWidth
47+ state: "normal"
48+
49+ function maximize() {
50+ state = "maximized"
51+ }
4952
5053 WindowResizeArea {
5154 id: windowResizeArea
@@ -195,5 +198,35 @@ Item {
195198 tryCompare(fakeWindow, "width", initialWindowWidth);
196199 tryCompare(fakeWindow, "height", initialWindowHeight);
197200 }
201+
202+ function test_saveRestoreMaximized() {
203+ var initialWindowX = fakeWindow.x;
204+ var initialWindowY = fakeWindow.y;
205+
206+ var moveDelta = units.gu(5);
207+
208+ fakeWindow.x = initialWindowX + moveDelta
209+ fakeWindow.y = initialWindowY + moveDelta
210+
211+ // Now change the state to maximized. The window should not keep updating the stored values
212+ fakeWindow.state = "maximized"
213+ fakeWindow.x = 31415 // 0 is too risky to pass the test even when broken
214+ fakeWindow.y = 31415
215+
216+ // This will destroy the window and recreate it
217+ windowLoader.active = false;
218+ waitForRendering(root);
219+ windowLoader.active = true;
220+
221+ // Make sure it's again where we left it in normal state before destroying
222+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
223+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
224+
225+ // Make sure maximize() has been called after restoring
226+ tryCompare(fakeWindow, "state", "maximized")
227+
228+ // clean up
229+ fakeWindow.state = "normal"
230+ }
198231 }
199232}
tests/qmltests/tst_Shell.qml+69
@@ -32,6 +32,7 @@ import Wizard 0.1 as Wizard
3232
3333import "../../qml"
3434import "../../qml/Components"
35+import "../../qml/Components/PanelState"
3536import "Stages"
3637
3738Rectangle {
@@ -1707,5 +1708,73 @@ Rectangle {
17071708
17081709 keyRelease(Qt.Key_Control);
17091710 }
1711+
1712+ // regression test for http://pad.lv/1443319
1713+ function test_closeMaximizedAndRestart() {
1714+ loadDesktopShellWithApps();
1715+
1716+ var appRepeater = findChild(shell, "appRepeater")
1717+ var appId = ApplicationManager.get(0).appId;
1718+ var appDelegate = appRepeater.itemAt(0);
1719+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1720+
1721+ tryCompare(appDelegate, "state", "normal");
1722+ tryCompare(PanelState, "buttonsVisible", false)
1723+
1724+ mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1725+ tryCompare(appDelegate, "state", "maximized");
1726+ tryCompare(PanelState, "buttonsVisible", true)
1727+
1728+ ApplicationManager.stopApplication(appId);
1729+ tryCompare(PanelState, "buttonsVisible", false)
1730+
1731+ ApplicationManager.startApplication(appId);
1732+ tryCompare(PanelState, "buttonsVisible", true)
1733+ }
1734+
1735+ // bug http://pad.lv/1431566
1736+ function test_switchToStagedHidesPanelButtons() {
1737+ loadDesktopShellWithApps();
1738+ var appRepeater = findChild(shell, "appRepeater")
1739+ var appId = ApplicationManager.get(0).appId;
1740+ var appDelegate = appRepeater.itemAt(0);
1741+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1742+
1743+ tryCompare(appDelegate, "state", "normal");
1744+ tryCompare(panelButtons, "visible", false);
1745+
1746+ appDelegate.maximize(false);
1747+ tryCompare(panelButtons, "visible", true);
1748+
1749+ shell.usageScenario = "phone";
1750+ waitForRendering(shell);
1751+ tryCompare(panelButtons, "visible", false);
1752+
1753+ shell.usageScenario = "desktop";
1754+ waitForRendering(shell);
1755+ tryCompare(panelButtons, "visible", true);
1756+ }
1757+
1758+ function test_lockingGreeterHidesPanelButtons() {
1759+ loadDesktopShellWithApps();
1760+ var appRepeater = findChild(shell, "appRepeater")
1761+ var appId = ApplicationManager.get(0).appId;
1762+ var appDelegate = appRepeater.itemAt(0);
1763+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1764+
1765+ tryCompare(appDelegate, "state", "normal");
1766+ tryCompare(panelButtons, "visible", false);
1767+
1768+ appDelegate.maximize(false);
1769+ tryCompare(panelButtons, "visible", true);
1770+
1771+ LightDM.Greeter.showGreeter();
1772+ waitForRendering(shell);
1773+ tryCompare(panelButtons, "visible", false);
1774+
1775+ LightDM.Greeter.hideGreeter();
1776+ waitForRendering(shell);
1777+ tryCompare(panelButtons, "visible", true);
1778+ }
17101779 }
17111780}