on xenial help sign in
Merge pull request #239 from ubports/xenial_-_removeFocusFromApp
8762b72Marius Gripsgard
plugins/WindowManager/TopLevelWindowModel.cpp+19−2
@@ -38,7 +38,8 @@ Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
38namespace unityapi = unity::shell::application;
38namespace unityapi = unity::shell::application;
39
39
40TopLevelWindowModel::TopLevelWindowModel()
40TopLevelWindowModel::TopLevelWindowModel()
41− : m_nullWindow(createWindow(nullptr))
41+ : m_nullWindow(createWindow(nullptr)),
42+ m_surfaceManagerBusy(false)
42{
43{
43 connect(m_nullWindow, &Window::focusedChanged, this, [this] {
44 connect(m_nullWindow, &Window::focusedChanged, this, [this] {
44 Q_EMIT rootFocusChanged();
45 Q_EMIT rootFocusChanged();
@@ -439,6 +440,10 @@ void TopLevelWindowModel::removeAt(int index)
439 setFocusedWindow(nullptr);
440 setFocusedWindow(nullptr);
440 }
441 }
441
442
443+ if (m_previousWindow == window) {
444+ m_previousWindow = nullptr;
445+ }
446+
442 if (m_closingAllApps) {
447 if (m_closingAllApps) {
443 if (m_windowModel.isEmpty()) {
448 if (m_windowModel.isEmpty()) {
444 Q_EMIT closedAllWindows();
449 Q_EMIT closedAllWindows();
@@ -709,6 +714,7 @@ void TopLevelWindowModel::move(int from, int to)
709}
714}
710void TopLevelWindowModel::onModificationsStarted()
715void TopLevelWindowModel::onModificationsStarted()
711{
716{
717+ m_surfaceManagerBusy = true;
712}
718}
713
719
714void TopLevelWindowModel::onModificationsEnded()
720void TopLevelWindowModel::onModificationsEnded()
@@ -719,6 +725,7 @@ void TopLevelWindowModel::onModificationsEnded()
719 // reset
725 // reset
720 m_focusedWindowChanged = false;
726 m_focusedWindowChanged = false;
721 m_newlyFocusedWindow = nullptr;
727 m_newlyFocusedWindow = nullptr;
728+ m_surfaceManagerBusy = false;
722}
729}
723
730
724void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
731void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
@@ -754,7 +761,14 @@ bool TopLevelWindowModel::rootFocus()
754
761
755void TopLevelWindowModel::setRootFocus(bool focus)
762void TopLevelWindowModel::setRootFocus(bool focus)
756{
763{
757− INFO_MSG << "(" << focus << ")";
764+ INFO_MSG << "(" << focus << "), surfaceManagerBusy is " << m_surfaceManagerBusy;
765+
766+ if (m_surfaceManagerBusy) {
767+ // Something else is probably being focused already, let's not add to
768+ // the noise.
769+ return;
770+ }
771+
758 if (focus) {
772 if (focus) {
759 // Give focus back to previous focused window, only if null window is focused.
773 // Give focus back to previous focused window, only if null window is focused.
760 // If null window is not focused, a different app had taken the focus and we
774 // If null window is not focused, a different app had taken the focus and we
@@ -762,6 +776,9 @@ void TopLevelWindowModel::setRootFocus(bool focus)
762 if (m_previousWindow && !m_previousWindow->focused() && !m_pendingActivation &&
776 if (m_previousWindow && !m_previousWindow->focused() && !m_pendingActivation &&
763 m_nullWindow == m_focusedWindow && m_previousWindow != m_nullWindow) {
777 m_nullWindow == m_focusedWindow && m_previousWindow != m_nullWindow) {
764 m_previousWindow->activate();
778 m_previousWindow->activate();
779+ } else if (!m_pendingActivation) {
780+ // The previous window does not exist any more, focus top window.
781+ activateTopMostWindowWithoutId(-1);
765 }
782 }
766 } else {
783 } else {
767 if (!m_nullWindow->focused()) {
784 if (!m_nullWindow->focused()) {
plugins/WindowManager/TopLevelWindowModel.h+4
@@ -103,6 +103,9 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
103 * Setting rootFocus attempts to focus the Window which was focused last -
103 * Setting rootFocus attempts to focus the Window which was focused last -
104 * unless another app is attempting to gain focus (as determined by
104 * unless another app is attempting to gain focus (as determined by
105 * pendingActivation) and that's why we got rootFocus.
105 * pendingActivation) and that's why we got rootFocus.
106+ *
107+ * If the previously-focused Window was closed before rootFocus was set,
108+ * the next available window will be focused.
106 */
109 */
107 Q_PROPERTY(bool rootFocus READ rootFocus WRITE setRootFocus NOTIFY rootFocusChanged)
110 Q_PROPERTY(bool rootFocus READ rootFocus WRITE setRootFocus NOTIFY rootFocusChanged)
108
111
@@ -286,6 +289,7 @@ private:
286
289
287 unity::shell::application::ApplicationManagerInterface* m_applicationManager{nullptr};
290 unity::shell::application::ApplicationManagerInterface* m_applicationManager{nullptr};
288 unity::shell::application::SurfaceManagerInterface *m_surfaceManager{nullptr};
291 unity::shell::application::SurfaceManagerInterface *m_surfaceManager{nullptr};
292+ bool m_surfaceManagerBusy;
289
293
290 enum ModelState {
294 enum ModelState {
291 IdleState,
295 IdleState,
qml/Shell.qml+4−4
@@ -323,10 +323,10 @@ StyledItem {
323 nativeWidth: shell.nativeWidth
323 nativeWidth: shell.nativeWidth
324 nativeHeight: shell.nativeHeight
324 nativeHeight: shell.nativeHeight
325
325
326− interactive: (!greeter || !greeter.shown)
326+ allowInteractivity: (!greeter || !greeter.shown)
327− && panel.indicators.fullyClosed
327+ && panel.indicators.fullyClosed
328− && !notifications.useModal
328+ && !notifications.useModal
329− && !launcher.takesFocus
329+ && !launcher.takesFocus
330
330
331 suspended: greeter.shown
331 suspended: greeter.shown
332 altTabPressed: physicalKeysMapper.altTabPressed
332 altTabPressed: physicalKeysMapper.altTabPressed
qml/Stage/Stage.qml+11−10
@@ -36,7 +36,6 @@ FocusScope {
36 property bool altTabPressed
36 property bool altTabPressed
37 property url background
37 property url background
38 property int dragAreaWidth
38 property int dragAreaWidth
39− property bool interactive
40 property real nativeHeight
39 property real nativeHeight
41 property real nativeWidth
40 property real nativeWidth
42 property QtObject orientations
41 property QtObject orientations
@@ -49,6 +48,11 @@ FocusScope {
49 property real rightEdgePushProgress: 0
48 property real rightEdgePushProgress: 0
50 property Item availableDesktopArea
49 property Item availableDesktopArea
51
50
51+ // Whether outside forces say that the Stage may have focus
52+ property bool allowInteractivity
53+
54+ readonly property bool interactive: (state === "staged" || state === "stagedWithSideStage" || state === "windowed") && allowInteractivity
55+
52 // Configuration
56 // Configuration
53 property string mode: "staged"
57 property string mode: "staged"
54
58
@@ -89,18 +93,17 @@ FocusScope {
89 Qt.InvertedLandscapeOrientation;
93 Qt.InvertedLandscapeOrientation;
90 }
94 }
91
95
96+ Binding {
97+ target: topLevelSurfaceList
98+ property: "rootFocus"
99+ value: interactive
100+ }
101+
92 onInteractiveChanged: {
102 onInteractiveChanged: {
93 // Stage must have focus before activating windows, including null
103 // Stage must have focus before activating windows, including null
94 if (interactive) {
104 if (interactive) {
95 focus = true;
105 focus = true;
96 }
106 }
97−
98− // This will:
99− // - If interactive: Try to reactivate last focused application.
100− // this will not happen if a pending activation is going on
101− // - If not interactive: Activate nullWindow, this makes
102− // sure none of the apps have focus when stage is not active
103− topLevelSurfaceList.rootFocus = interactive;
104 }
107 }
105
108
106 onAltTabPressedChanged: {
109 onAltTabPressedChanged: {
@@ -1302,7 +1305,6 @@ FocusScope {
1302 showHighlight: spreadItem.highlightedIndex === index
1305 showHighlight: spreadItem.highlightedIndex === index
1303 darkening: spreadItem.highlightedIndex >= 0
1306 darkening: spreadItem.highlightedIndex >= 0
1304 anchors.topMargin: dragArea.distance
1307 anchors.topMargin: dragArea.distance
1305− interactive: false
1306 }
1308 }
1307 PropertyChanges {
1309 PropertyChanges {
1308 target: appDelegate
1310 target: appDelegate
@@ -1343,7 +1345,6 @@ FocusScope {
1343 scaleToPreviewSize: spreadItem.stackHeight
1345 scaleToPreviewSize: spreadItem.stackHeight
1344 scaleToPreviewProgress: stagedRightEdgeMaths.scaleToPreviewProgress
1346 scaleToPreviewProgress: stagedRightEdgeMaths.scaleToPreviewProgress
1345 shadowOpacity: .3
1347 shadowOpacity: .3
1346− interactive: false
1347 }
1348 }
1348 // make sure it's visible but transparent so it fades in when we transition to spread
1349 // make sure it's visible but transparent so it fades in when we transition to spread
1349 PropertyChanges { target: windowInfoItem; opacity: 0; visible: true }
1350 PropertyChanges { target: windowInfoItem; opacity: 0; visible: true }
tests/qmltests/Stage/tst_DesktopStage.qml+1−1
@@ -101,7 +101,7 @@ Item {
101 applicationManager: ApplicationManager
101 applicationManager: ApplicationManager
102 topLevelSurfaceList: topSurfaceList
102 topLevelSurfaceList: topSurfaceList
103 availableDesktopArea: availableDesktopAreaItem
103 availableDesktopArea: availableDesktopAreaItem
104− interactive: true
104+ allowInteractivity: true
105 mode: "windowed"
105 mode: "windowed"
106
106
107 Item {
107 Item {
tests/qmltests/Stage/tst_PhoneStage.qml+30−4
@@ -42,7 +42,7 @@ Item {
42 anchors { fill: parent; rightMargin: units.gu(30) }
42 anchors { fill: parent; rightMargin: units.gu(30) }
43 focus: true
43 focus: true
44 dragAreaWidth: units.gu(2)
44 dragAreaWidth: units.gu(2)
45− interactive: true
45+ allowInteractivity: true
46 shellOrientation: Qt.PortraitOrientation
46 shellOrientation: Qt.PortraitOrientation
47 orientations: Orientations {}
47 orientations: Orientations {}
48 applicationManager: ApplicationManager
48 applicationManager: ApplicationManager
@@ -210,7 +210,7 @@ Item {
210 return [
210 return [
211 {tag: "<breakPoint (trigger)", progress: .2, cancel: false, endState: "staged", newFocusedIndex: 1 },
211 {tag: "<breakPoint (trigger)", progress: .2, cancel: false, endState: "staged", newFocusedIndex: 1 },
212 {tag: "<breakPoint (cancel)", progress: .2, cancel: true, endState: "staged", newFocusedIndex: 0 },
212 {tag: "<breakPoint (cancel)", progress: .2, cancel: true, endState: "staged", newFocusedIndex: 0 },
213− {tag: ">breakPoint (trigger)", progress: .5, cancel: false, endState: "spread", newFocusedIndex: 0 },
213+ {tag: ">breakPoint (trigger)", progress: .5, cancel: false, endState: "spread", newFocusedIndex: null },
214 {tag: ">breakPoint (cancel)", progress: .8, cancel: true, endState: "staged", newFocusedIndex: 0 },
214 {tag: ">breakPoint (cancel)", progress: .8, cancel: true, endState: "staged", newFocusedIndex: 0 },
215 ];
215 ];
216 }
216 }
@@ -225,7 +225,6 @@ Item {
225 var endY = startY;
225 var endY = startY;
226 var endX = stage.width - (stage.width * data.progress) - stage.dragAreaWidth;
226 var endX = stage.width - (stage.width * data.progress) - stage.dragAreaWidth;
227
227
228− var oldFocusedApp = ApplicationManager.get(0);
229 var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
228 var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
230
229
231 touchFlick(stage, startX, startY, endX, endY,
230 touchFlick(stage, startX, startY, endX, endY,
@@ -238,7 +237,7 @@ Item {
238 touchRelease(stage, endX, endY); }
237 touchRelease(stage, endX, endY); }
239
238
240 tryCompare(stage, "state", data.endState);
239 tryCompare(stage, "state", data.endState);
241− tryCompare(ApplicationManager, "focusedApplicationId", data.endState == "spread" ? oldFocusedApp.appId : newFocusedApp.appId);
240+ tryCompare(ApplicationManager, "focusedApplicationId", data.endState == "spread" ? "" : newFocusedApp.appId);
242 }
241 }
243
242
244 function test_selectAppFromSpread_data() {
243 function test_selectAppFromSpread_data() {
@@ -583,5 +582,32 @@ Item {
583 compare(topLevelSurfaceList.idAt(0), webbrowserSurfaceId);
582 compare(topLevelSurfaceList.idAt(0), webbrowserSurfaceId);
584 compare(webbrowserApp.focused, true);
583 compare(webbrowserApp.focused, true);
585 }
584 }
585+
586+ /*
587+ Ensure that closing a surface while rootFocus is off focuses the
588+ next available surface when rootFocus is given back.
589+
590+ Regression test for https://github.com/ubports/unity8/issues/234
591+
592+ This cannot be tested in tst_TopLevelWindowModel.cpp, the mocks for
593+ it are not advanced enough.
594+ */
595+ function test_closeFocusedAppWithSpreadOpen()
596+ {
597+ var dashApp = ApplicationManager.findApplication("unity8-dash");
598+ var webbrowserSurfaceId = topLevelSurfaceList.nextId;
599+ var webbrowserApp = ApplicationManager.startApplication("morph-browser");
600+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
601+
602+ topLevelSurfaceList.rootFocus = false;
603+
604+ performEdgeSwipeToShowAppSpread();
605+
606+ swipeSurfaceUpwards(webbrowserSurfaceId);
607+ tryCompareFunction(function() { return topLevelSurfaceList.indexForId(webbrowserSurfaceId); }, -1);
608+
609+ topLevelSurfaceList.rootFocus = true;
610+ compare(dashApp.focused, true);
611+ }
586 }
612 }
587}
613}
tests/qmltests/Stage/tst_TabletStage.qml+1−1
@@ -45,7 +45,7 @@ Rectangle {
45 id: stage
45 id: stage
46 anchors { fill: parent; rightMargin: units.gu(30) }
46 anchors { fill: parent; rightMargin: units.gu(30) }
47 dragAreaWidth: units.gu(2)
47 dragAreaWidth: units.gu(2)
48− interactive: true
48+ allowInteractivity: true
49 shellOrientation: Qt.LandscapeOrientation
49 shellOrientation: Qt.LandscapeOrientation
50 nativeWidth: width
50 nativeWidth: width
51 nativeHeight: height
51 nativeHeight: height