on xenial help sign in
Merge pull request #239 from ubports/xenial_-_removeFocusFromApp

Remove focus from the topmost app when in spread

commit8762b72351cc459536433762b90eaf2c1e43dda8 authorMarius Gripsgard <marius@ubports.com> dateSat 14 Dec 2019, 18:26 (7 years ago) parent31d9e49922e8e8
7 files changed+70−22
plugins/WindowManager/TopLevelWindowModel.cpp+19−2 plugins/WindowManager/TopLevelWindowModel.h+4 qml/Shell.qml+4−4 qml/Stage/Stage.qml+11−10 tests/qmltests/Stage/tst_DesktopStage.qml+1−1 tests/qmltests/Stage/tst_PhoneStage.qml+30−4 tests/qmltests/Stage/tst_TabletStage.qml+1−1
plugins/WindowManager/TopLevelWindowModel.cpp+19−2
@@ -38,7 +38,8 @@ Q_LOGGING_CATEGORY(TOPLEVELWINDOWMODEL, "toplevelwindowmodel", QtInfoMsg)
3838namespace unityapi = unity::shell::application;
3939
4040TopLevelWindowModel::TopLevelWindowModel()
41− : m_nullWindow(createWindow(nullptr))
41+ : m_nullWindow(createWindow(nullptr)),
42+ m_surfaceManagerBusy(false)
4243{
4344 connect(m_nullWindow, &Window::focusedChanged, this, [this] {
4445 Q_EMIT rootFocusChanged();
@@ -439,6 +440,10 @@ void TopLevelWindowModel::removeAt(int index)
439440 setFocusedWindow(nullptr);
440441 }
441442
443+ if (m_previousWindow == window) {
444+ m_previousWindow = nullptr;
445+ }
446+
442447 if (m_closingAllApps) {
443448 if (m_windowModel.isEmpty()) {
444449 Q_EMIT closedAllWindows();
@@ -709,6 +714,7 @@ void TopLevelWindowModel::move(int from, int to)
709714}
710715void TopLevelWindowModel::onModificationsStarted()
711716{
717+ m_surfaceManagerBusy = true;
712718}
713719
714720void TopLevelWindowModel::onModificationsEnded()
@@ -719,6 +725,7 @@ void TopLevelWindowModel::onModificationsEnded()
719725 // reset
720726 m_focusedWindowChanged = false;
721727 m_newlyFocusedWindow = nullptr;
728+ m_surfaceManagerBusy = false;
722729}
723730
724731void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
@@ -754,7 +761,14 @@ bool TopLevelWindowModel::rootFocus()
754761
755762void TopLevelWindowModel::setRootFocus(bool focus)
756763{
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+
758772 if (focus) {
759773 // Give focus back to previous focused window, only if null window is focused.
760774 // If null window is not focused, a different app had taken the focus and we
@@ -762,6 +776,9 @@ void TopLevelWindowModel::setRootFocus(bool focus)
762776 if (m_previousWindow && !m_previousWindow->focused() && !m_pendingActivation &&
763777 m_nullWindow == m_focusedWindow && m_previousWindow != m_nullWindow) {
764778 m_previousWindow->activate();
779+ } else if (!m_pendingActivation) {
780+ // The previous window does not exist any more, focus top window.
781+ activateTopMostWindowWithoutId(-1);
765782 }
766783 } else {
767784 if (!m_nullWindow->focused()) {
plugins/WindowManager/TopLevelWindowModel.h+4
@@ -103,6 +103,9 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
103103 * Setting rootFocus attempts to focus the Window which was focused last -
104104 * unless another app is attempting to gain focus (as determined by
105105 * 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.
106109 */
107110 Q_PROPERTY(bool rootFocus READ rootFocus WRITE setRootFocus NOTIFY rootFocusChanged)
108111
@@ -286,6 +289,7 @@ private:
286289
287290 unity::shell::application::ApplicationManagerInterface* m_applicationManager{nullptr};
288291 unity::shell::application::SurfaceManagerInterface *m_surfaceManager{nullptr};
292+ bool m_surfaceManagerBusy;
289293
290294 enum ModelState {
291295 IdleState,
qml/Shell.qml+4−4
@@ -323,10 +323,10 @@ StyledItem {
323323 nativeWidth: shell.nativeWidth
324324 nativeHeight: shell.nativeHeight
325325
326− interactive: (!greeter || !greeter.shown)
327− && panel.indicators.fullyClosed
328− && !notifications.useModal
329− && !launcher.takesFocus
326+ allowInteractivity: (!greeter || !greeter.shown)
327+ && panel.indicators.fullyClosed
328+ && !notifications.useModal
329+ && !launcher.takesFocus
330330
331331 suspended: greeter.shown
332332 altTabPressed: physicalKeysMapper.altTabPressed
qml/Stage/Stage.qml+11−10
@@ -36,7 +36,6 @@ FocusScope {
3636 property bool altTabPressed
3737 property url background
3838 property int dragAreaWidth
39− property bool interactive
4039 property real nativeHeight
4140 property real nativeWidth
4241 property QtObject orientations
@@ -49,6 +48,11 @@ FocusScope {
4948 property real rightEdgePushProgress: 0
5049 property Item availableDesktopArea
5150
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+
5256 // Configuration
5357 property string mode: "staged"
5458
@@ -89,18 +93,17 @@ FocusScope {
8993 Qt.InvertedLandscapeOrientation;
9094 }
9195
96+ Binding {
97+ target: topLevelSurfaceList
98+ property: "rootFocus"
99+ value: interactive
100+ }
101+
92102 onInteractiveChanged: {
93103 // Stage must have focus before activating windows, including null
94104 if (interactive) {
95105 focus = true;
96106 }
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;
104107 }
105108
106109 onAltTabPressedChanged: {
@@ -1302,7 +1305,6 @@ FocusScope {
13021305 showHighlight: spreadItem.highlightedIndex === index
13031306 darkening: spreadItem.highlightedIndex >= 0
13041307 anchors.topMargin: dragArea.distance
1305− interactive: false
13061308 }
13071309 PropertyChanges {
13081310 target: appDelegate
@@ -1343,7 +1345,6 @@ FocusScope {
13431345 scaleToPreviewSize: spreadItem.stackHeight
13441346 scaleToPreviewProgress: stagedRightEdgeMaths.scaleToPreviewProgress
13451347 shadowOpacity: .3
1346− interactive: false
13471348 }
13481349 // make sure it's visible but transparent so it fades in when we transition to spread
13491350 PropertyChanges { target: windowInfoItem; opacity: 0; visible: true }
tests/qmltests/Stage/tst_DesktopStage.qml+1−1
@@ -101,7 +101,7 @@ Item {
101101 applicationManager: ApplicationManager
102102 topLevelSurfaceList: topSurfaceList
103103 availableDesktopArea: availableDesktopAreaItem
104− interactive: true
104+ allowInteractivity: true
105105 mode: "windowed"
106106
107107 Item {
tests/qmltests/Stage/tst_PhoneStage.qml+30−4
@@ -42,7 +42,7 @@ Item {
4242 anchors { fill: parent; rightMargin: units.gu(30) }
4343 focus: true
4444 dragAreaWidth: units.gu(2)
45− interactive: true
45+ allowInteractivity: true
4646 shellOrientation: Qt.PortraitOrientation
4747 orientations: Orientations {}
4848 applicationManager: ApplicationManager
@@ -210,7 +210,7 @@ Item {
210210 return [
211211 {tag: "<breakPoint (trigger)", progress: .2, cancel: false, endState: "staged", newFocusedIndex: 1 },
212212 {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 },
214214 {tag: ">breakPoint (cancel)", progress: .8, cancel: true, endState: "staged", newFocusedIndex: 0 },
215215 ];
216216 }
@@ -225,7 +225,6 @@ Item {
225225 var endY = startY;
226226 var endX = stage.width - (stage.width * data.progress) - stage.dragAreaWidth;
227227
228− var oldFocusedApp = ApplicationManager.get(0);
229228 var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
230229
231230 touchFlick(stage, startX, startY, endX, endY,
@@ -238,7 +237,7 @@ Item {
238237 touchRelease(stage, endX, endY); }
239238
240239 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);
242241 }
243242
244243 function test_selectAppFromSpread_data() {
@@ -583,5 +582,32 @@ Item {
583582 compare(topLevelSurfaceList.idAt(0), webbrowserSurfaceId);
584583 compare(webbrowserApp.focused, true);
585584 }
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+ }
586612 }
587613}
tests/qmltests/Stage/tst_TabletStage.qml+1−1
@@ -45,7 +45,7 @@ Rectangle {
4545 id: stage
4646 anchors { fill: parent; rightMargin: units.gu(30) }
4747 dragAreaWidth: units.gu(2)
48− interactive: true
48+ allowInteractivity: true
4949 shellOrientation: Qt.LandscapeOrientation
5050 nativeWidth: width
5151 nativeHeight: height