on xenial help sign in
Take save/restore functions out of WindowResizeArea
b5167ffDaniel d'Andrada
qml/Stage/MoveHandler.qml+2−2
@@ -90,8 +90,8 @@ QtObject {
90 if (target.anyMaximized) {
90 if (target.anyMaximized) {
91 // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
91 // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
92 // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
92 // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
93− priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.resizeArea.normalWidth);
93+ priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.normalWidth);
94− priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.resizeArea.normalHeight);
94+ priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.normalHeight);
95 } else {
95 } else {
96 priv.distanceX = pos.x;
96 priv.distanceX = pos.x;
97 priv.distanceY = pos.y;
97 priv.distanceY = pos.y;
qml/Stage/Stage.qml+39−9
@@ -710,6 +710,29 @@ FocusScope {
710 property real restoredX
710 property real restoredX
711 property real restoredY
711 property real restoredY
712
712
713+ // Keeps track of the window geometry while in normal or restored state
714+ // Useful when returning from some maxmized state or when saving the geometry while maximized
715+ // FIXME: find a better solution
716+ property real normalX: 0
717+ property real normalY: 0
718+ property real normalWidth: 0
719+ property real normalHeight: 0
720+ function updateNormalGeometry() {
721+ if (appDelegate.state == "normal" || appDelegate.state == "restored") {
722+ normalX = appDelegate.requestedX;
723+ normalY = appDelegate.requestedY;
724+ normalWidth = appDelegate.width;
725+ normalHeight = appDelegate.height;
726+ }
727+ }
728+ Connections {
729+ target: appDelegate
730+ onXChanged: appDelegate.updateNormalGeometry();
731+ onYChanged: appDelegate.updateNormalGeometry();
732+ onWidthChanged: appDelegate.updateNormalGeometry();
733+ onHeightChanged: appDelegate.updateNormalGeometry();
734+ }
735+
713 Binding {
736 Binding {
714 target: appDelegate
737 target: appDelegate
715 property: "y"
738 property: "y"
@@ -850,6 +873,16 @@ FocusScope {
850 MirFocusController.focusedSurface = null;
873 MirFocusController.focusedSurface = null;
851 }
874 }
852 }
875 }
876+
877+ WindowStateSaver {
878+ id: windowStateSaver
879+ target: appDelegate
880+ screenWidth: appContainer.width
881+ screenHeight: appContainer.height
882+ leftMargin: root.leftMargin
883+ minimumY: PanelState.panelHeight
884+ }
885+
853 Component.onCompleted: {
886 Component.onCompleted: {
854 if (application && application.rotatesWindowContents) {
887 if (application && application.rotatesWindowContents) {
855 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
888 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
@@ -861,7 +894,7 @@ FocusScope {
861 windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
894 windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
862 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
895 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
863 // Now load any saved state. This needs to happen *after* the cascading!
896 // Now load any saved state. This needs to happen *after* the cascading!
864− resizeArea.loadWindowState();
897+ windowStateSaver.load();
865
898
866 // NB: We're differentiating if this delegate was created in response to a new entry in the model
899 // NB: We're differentiating if this delegate was created in response to a new entry in the model
867 // or if the Repeater is just populating itself with delegates to match the model it received.
900 // or if the Repeater is just populating itself with delegates to match the model it received.
@@ -878,6 +911,8 @@ FocusScope {
878 _constructing = false;
911 _constructing = false;
879 }
912 }
880 Component.onDestruction: {
913 Component.onDestruction: {
914+ windowStateSaver.save();
915+
881 if (!root.parent) {
916 if (!root.parent) {
882 // This stage is about to be destroyed. Don't mess up with the model at this point
917 // This stage is about to be destroyed. Don't mess up with the model at this point
883 return;
918 return;
@@ -1480,27 +1515,22 @@ FocusScope {
1480 id: resizeArea
1515 id: resizeArea
1481 objectName: "windowResizeArea"
1516 objectName: "windowResizeArea"
1482
1517
1518+ anchors.fill: appDelegate
1519+
1483 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
1520 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
1484 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
1521 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
1485
1522
1486 target: appDelegate
1523 target: appDelegate
1524+ minimumY: PanelState.panelHeight // disallow resizing up past Panel
1487 minWidth: units.gu(10)
1525 minWidth: units.gu(10)
1488 minHeight: units.gu(10)
1526 minHeight: units.gu(10)
1489 borderThickness: units.gu(2)
1527 borderThickness: units.gu(2)
1490− windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
1491− screenWidth: appContainer.width
1492− screenHeight: appContainer.height
1493− leftMargin: root.leftMargin
1494 enabled: false
1528 enabled: false
1495 visible: enabled
1529 visible: enabled
1496
1530
1497 onPressed: {
1531 onPressed: {
1498 appDelegate.focus = true;
1532 appDelegate.focus = true;
1499 }
1533 }
1500−
1501− Component.onDestruction: {
1502− saveWindowState();
1503− }
1504 }
1534 }
1505
1535
1506 DecoratedWindow {
1536 DecoratedWindow {
qml/Stage/WindowResizeArea.qml+2−70
@@ -18,91 +18,23 @@ import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19import Utils 0.1
19import Utils 0.1
20import Unity.Application 0.1 // for Mir.cursorName
20import Unity.Application 0.1 // for Mir.cursorName
21−import "../Components/PanelState"
22
21
23MouseArea {
22MouseArea {
24 id: root
23 id: root
25
24
26− anchors.fill: target
27 anchors.margins: -borderThickness
25 anchors.margins: -borderThickness
28
26
29 hoverEnabled: target && !target.maximized // don't grab the resize under the panel
27 hoverEnabled: target && !target.maximized // don't grab the resize under the panel
30
28
31− property var windowStateStorage: WindowStateStorage
32 readonly property alias dragging: d.dragging
29 readonly property alias dragging: d.dragging
33− readonly property alias normalWidth: priv.normalWidth
34− readonly property alias normalHeight: priv.normalHeight
35
30
36 // The target item managed by this. Must be a parent or a sibling
31 // The target item managed by this. Must be a parent or a sibling
37 // The area will anchor to it and manage resize events
32 // The area will anchor to it and manage resize events
38 property Item target: null
33 property Item target: null
39− property string windowId: ""
40 property int borderThickness: 0
34 property int borderThickness: 0
35+ property real minimumY: -100000000 // By default, impose no limit
41 property int minWidth: 0
36 property int minWidth: 0
42 property int minHeight: 0
37 property int minHeight: 0
43− property int defaultWidth: units.gu(60)
44− property int defaultHeight: units.gu(50)
45− property int screenWidth: 0
46− property int screenHeight: 0
47− property int leftMargin: 0
48−
49− QtObject {
50− id: priv
51− objectName: "priv"
52−
53− property int normalX: 0
54− property int normalY: 0
55− property int normalWidth: 0
56− property int normalHeight: 0
57−
58− function updateNormalGeometry() {
59− if (root.target.state == "normal" || root.target.state == "restored") {
60− normalX = root.target.requestedX
61− normalY = root.target.requestedY
62− normalWidth = root.target.width
63− normalHeight = root.target.height
64− }
65− }
66− }
67−
68− Connections {
69− target: root.target
70− onXChanged: priv.updateNormalGeometry();
71− onYChanged: priv.updateNormalGeometry();
72− onWidthChanged: priv.updateNormalGeometry();
73− onHeightChanged: priv.updateNormalGeometry();
74− }
75−
76− function loadWindowState() {
77− var windowGeometry = windowStateStorage.getGeometry(root.windowId,
78− Qt.rect(target.windowedX, target.windowedY, defaultWidth, defaultHeight));
79−
80−
81− target.windowedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth - root.leftMargin); });
82− target.windowedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, d.minimumHeight),
83− root.screenHeight - (target.fullscreen ? 0 : PanelState.panelHeight)); });
84− target.windowedX = Qt.binding(function() { return Math.max(Math.min(windowGeometry.x, root.screenWidth - root.leftMargin - target.windowedWidth),
85− (target.fullscreen ? 0 : root.leftMargin)); });
86− target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.windowedHeight), PanelState.panelHeight); });
87−
88− var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
89− target.restore(false /* animated */, windowState);
90−
91− priv.updateNormalGeometry();
92−
93− // initialize the x/y to restore to
94− target.restoredX = priv.normalX;
95− target.restoredY = priv.normalY;
96− }
97−
98− function saveWindowState() {
99− var state = target.windowState;
100− if (state === WindowStateStorage.WindowStateRestored) {
101− state = WindowStateStorage.WindowStateNormal;
102− }
103− windowStateStorage.saveState(root.windowId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
104− windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight));
105− }
106
38
107 QtObject {
39 QtObject {
108 id: d
40 id: d
@@ -311,7 +243,7 @@ MouseArea {
311 }
243 }
312
244
313 if (d.topBorder) {
245 if (d.topBorder) {
314− var newTargetY = Math.max(d.startY + deltaY, PanelState.panelHeight); // disallow resizing up past Panel
246+ var newTargetY = Math.max(d.startY + deltaY, root.minimumY);
315 var bottomBorderY = target.windowedY + target.height;
247 var bottomBorderY = target.windowedY + target.height;
316 if (bottomBorderY > newTargetY + d.minimumHeight) {
248 if (bottomBorderY > newTargetY + d.minimumHeight) {
317 if (bottomBorderY < newTargetY + d.maximumHeight) {
249 if (bottomBorderY < newTargetY + d.maximumHeight) {
qml/Stage/WindowStateSaver.qmlnew file+63
@@ -0,0 +1,63 @@
1+/*
2+ * Copyright (C) 2016 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+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Utils 0.1
20+
21+QtObject {
22+ id: root
23+
24+ // set from outside
25+ property var target
26+ property int screenWidth: 0
27+ property int screenHeight: 0
28+ property int leftMargin: 0
29+ property int minimumY: 0
30+
31+ function load() {
32+ var defaultWidth = units.gu(60);
33+ var defaultHeight = units.gu(50);
34+ var windowGeometry = WindowStateStorage.getGeometry(target.appId,
35+ Qt.rect(target.windowedX, target.windowedY, defaultWidth, defaultHeight));
36+
37+ target.windowedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, target.minimumWidth), screenWidth - root.leftMargin); });
38+ target.windowedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, target.minimumHeight),
39+ screenHeight - (target.fullscreen ? 0 : minimumY)); });
40+ target.windowedX = Qt.binding(function() { return Math.max(Math.min(windowGeometry.x, screenWidth - root.leftMargin - target.windowedWidth),
41+ (target.fullscreen ? 0 : root.leftMargin)); });
42+ target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, screenHeight - target.windowedHeight), minimumY); });
43+
44+ var windowState = WindowStateStorage.getState(target.appId, WindowStateStorage.WindowStateNormal)
45+ target.restore(false /* animated */, windowState);
46+
47+ target.updateNormalGeometry();
48+
49+ // initialize the x/y to restore to
50+ target.restoredX = target.normalX;
51+ target.restoredY = target.normalY;
52+ }
53+
54+ function save() {
55+ var state = target.windowState;
56+ if (state === WindowStateStorage.WindowStateRestored) {
57+ state = WindowStateStorage.WindowStateNormal;
58+ }
59+
60+ WindowStateStorage.saveState(target.appId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
61+ WindowStateStorage.saveGeometry(target.appId, Qt.rect(target.normalX, target.normalY, target.normalWidth, target.normalHeight));
62+ }
63+}
tests/qmltests/Stage/tst_DesktopStage.qml+71
@@ -790,6 +790,77 @@ Item {
790 tryCompare(dialerAppDelegate, "state", "maximized");
790 tryCompare(dialerAppDelegate, "state", "maximized");
791 }
791 }
792
792
793+ function test_saveRestoreSize() {
794+ var originalWindowCount = topSurfaceList.count;
795+ var appDelegate = startApplication("dialer-app");
796+ compare(topSurfaceList.count, originalWindowCount + 1);
797+
798+ var initialWindowX = appDelegate.windowedX;
799+ var initialWindowY = appDelegate.windowedY;
800+ var initialWindowWidth = appDelegate.width
801+ var initialWindowHeight = appDelegate.height
802+
803+ var resizeDelta = units.gu(5)
804+ var startDragX = initialWindowX + initialWindowWidth + 1
805+ var startDragY = initialWindowY + initialWindowHeight + 1
806+ mouseFlick(root, startDragX, startDragY, startDragX + resizeDelta, startDragY + resizeDelta, true, true, units.gu(.5), 10);
807+
808+ tryCompare(appDelegate, "width", initialWindowWidth + resizeDelta);
809+ tryCompare(appDelegate, "height", initialWindowHeight + resizeDelta);
810+
811+ // Close the window and restart the application
812+ var closeButton = findChild(appDelegate, "closeWindowButton");
813+ appDelegate = null;
814+ verify(closeButton);
815+ mouseClick(closeButton);
816+ tryCompare(topSurfaceList, "count", originalWindowCount);
817+ wait(100); // plus some spare room
818+ appDelegate = startApplication("dialer-app");
819+
820+ // Make sure its size is again the same as before
821+ tryCompare(appDelegate, "width", initialWindowWidth + resizeDelta);
822+ tryCompare(appDelegate, "height", initialWindowHeight + resizeDelta);
823+ }
824+
825+ function test_saveRestoreMaximized() {
826+ var originalWindowCount = topSurfaceList.count;
827+ var appDelegate = startApplication("dialer-app");
828+ compare(topSurfaceList.count, originalWindowCount + 1);
829+
830+ var initialWindowX = appDelegate.windowedX;
831+ var initialWindowY = appDelegate.windowedY;
832+
833+ var moveDelta = units.gu(5);
834+
835+ appDelegate.windowedX = initialWindowX + moveDelta
836+ appDelegate.windowedY = initialWindowY + moveDelta
837+
838+ // Now change the state to maximized. The window should not keep updating the stored values
839+ maximizeAppDelegate(appDelegate);
840+
841+ // Close the window and restart the application
842+ var closeButton = findChild(appDelegate, "closeWindowButton");
843+ appDelegate = null;
844+ verify(closeButton);
845+ mouseClick(closeButton);
846+ closeButton = null;
847+ tryCompare(topSurfaceList, "count", originalWindowCount);
848+ wait(100); // plus some spare room
849+ appDelegate = startApplication("dialer-app");
850+
851+ // Make sure it's again where we left it in normal state before destroying
852+ tryCompare(appDelegate, "windowedX", initialWindowX + moveDelta)
853+ tryCompare(appDelegate, "windowedY", initialWindowY + moveDelta)
854+
855+ // Make sure maximize() has been called after restoring
856+ tryCompare(appDelegate, "state", "maximized")
857+
858+ // clean up
859+ // click on restore button (same one as maximize)
860+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
861+ mouseClick(maximizeButton);
862+ }
863+
793 function test_grabbingCursorOnDecorationPress() {
864 function test_grabbingCursorOnDecorationPress() {
794 var appDelegate = startApplication("dialer-app");
865 var appDelegate = startApplication("dialer-app");
795 verify(appDelegate);
866 verify(appDelegate);
tests/qmltests/Stage/tst_WindowResizeArea.qml+1−66
@@ -88,22 +88,11 @@ Item {
88
88
89 WindowResizeArea {
89 WindowResizeArea {
90 id: windowResizeArea
90 id: windowResizeArea
91+ anchors.fill: parent
91 target: fakeWindow
92 target: fakeWindow
92 borderThickness: units.gu(2)
93 borderThickness: units.gu(2)
93 minWidth: units.gu(15)
94 minWidth: units.gu(15)
94 minHeight: units.gu(10)
95 minHeight: units.gu(10)
95− defaultWidth: units.gu(20)
96− defaultHeight: units.gu(20)
97− windowId: "test-window-id"
98− screenWidth: root.width
99− screenHeight: root.height
100−
101− Component.onCompleted: {
102− loadWindowState();
103− }
104− Component.onDestruction: {
105− saveWindowState();
106− }
107 }
96 }
108
97
109 Rectangle {
98 Rectangle {
@@ -312,30 +301,6 @@ Item {
312 compare(fakeWindow.requestedY, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
301 compare(fakeWindow.requestedY, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
313 }
302 }
314
303
315− function test_saveRestoreSize() {
316− var initialWindowX = fakeWindow.windowedX;
317− var initialWindowY = fakeWindow.windowedY;
318− var initialWindowWidth = fakeWindow.width
319− var initialWindowHeight = fakeWindow.height
320−
321− var resizeDelta = units.gu(5)
322− var startDragX = initialWindowX + initialWindowWidth + 1
323− var startDragY = initialWindowY + initialWindowHeight + 1
324− mouseFlick(root, startDragX, startDragY, startDragX + resizeDelta, startDragY + resizeDelta, true, true, units.gu(.5), 10);
325−
326− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.resizeAreaMinWidth));
327− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.resizeAreaMinHeight));
328−
329− // This will destroy the window and recreate it
330− windowLoader.active = false;
331− waitForRendering(root);
332− windowLoader.active = true;
333−
334− // Make sure its size is again the same as before
335− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.resizeAreaMinWidth));
336− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.resizeAreaMinHeight));
337− }
338−
339 // This tests if dragging smaller than minSize and then larger again, will keep the edge sticking
304 // This tests if dragging smaller than minSize and then larger again, will keep the edge sticking
340 // to the mouse, instead of immediately making the window grow again when switching direction
305 // to the mouse, instead of immediately making the window grow again when switching direction
341 function test_resizeSmallerAndLarger_data() {
306 function test_resizeSmallerAndLarger_data() {
@@ -361,36 +326,6 @@ Item {
361 tryCompare(fakeWindow, "height", initialWindowHeight);
326 tryCompare(fakeWindow, "height", initialWindowHeight);
362 }
327 }
363
328
364− function test_saveRestoreMaximized() {
365− var initialWindowX = fakeWindow.windowedX;
366− var initialWindowY = fakeWindow.windowedY;
367−
368− var moveDelta = units.gu(5);
369−
370− fakeWindow.windowedX = initialWindowX + moveDelta
371− fakeWindow.windowedY = initialWindowY + moveDelta
372−
373− // Now change the state to maximized. The window should not keep updating the stored values
374− fakeWindow.maximize()
375− fakeWindow.windowedX = 31415 // 0 is too risky to pass the test even when broken
376− fakeWindow.windowedY = 31415
377−
378− // This will destroy the window and recreate it
379− windowLoader.active = false;
380− waitForRendering(root);
381− windowLoader.active = true;
382−
383− // Make sure it's again where we left it in normal state before destroying
384− tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
385− tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
386−
387− // Make sure maximize() has been called after restoring
388− tryCompare(fakeWindow, "state", "maximized")
389−
390− // clean up
391− fakeWindow.restoreFromMaximized()
392− }
393−
394 function test_restoreMovesIntoBounds_data() {
329 function test_restoreMovesIntoBounds_data() {
395 return [
330 return [
396 {tag: "left off", x: -units.gu(5), y: units.gu(5), w: units.gu(10), h: units.gu(10)},
331 {tag: "left off", x: -units.gu(5), y: units.gu(5), w: units.gu(10), h: units.gu(10)},
tests/qmltests/tst_Shell.qml+4−7
@@ -1857,14 +1857,11 @@ Rectangle {
1857 var appRepeater = findChild(shell, "appRepeater");
1857 var appRepeater = findChild(shell, "appRepeater");
1858 var appDelegate = appRepeater.itemAt(0);
1858 var appDelegate = appRepeater.itemAt(0);
1859
1859
1860− var resizeArea = findChild(appDelegate, "windowResizeArea");
1861− var priv = findInvisibleChild(resizeArea, "priv");
1862−
1863 // Make sure windows are at 0,0 or greater and they have a size that's > 0
1860 // Make sure windows are at 0,0 or greater and they have a size that's > 0
1864− compare(priv.normalX >= 0, true)
1861+ compare(appDelegate.normalX >= 0, true)
1865− compare(priv.normalY >= 0, true)
1862+ compare(appDelegate.normalY >= 0, true)
1866− compare(priv.normalWidth > 0, true)
1863+ compare(appDelegate.normalWidth > 0, true)
1867− compare(priv.normalHeight > 0, true)
1864+ compare(appDelegate.normalHeight > 0, true)
1868 }
1865 }
1869
1866
1870 // bug http://pad.lv/1431566
1867 // bug http://pad.lv/1431566