on xenial help sign in
merge trunk
commitf694c4f28e0450ad6c9912b62c00a6248b16107a authorLukáš Tinkl <lukas.tinkl@canonical.com> dateTue 21 Feb 2017, 14:36 (10 years ago) parented27e9e7af3e1d
42 files changed+1172−289
CMakeLists.txt+1 debian/changelog+48 plugins/Greeter/Unity/Launcher/CMakeLists.txt−1 plugins/Ubuntu/Gestures/TouchGestureArea.cpp−5 plugins/Unity/Launcher/CMakeLists.txt−1 plugins/Utils/CMakeLists.txt+1 plugins/Utils/appdrawerproxymodel.cpp+2−2 plugins/Utils/expressionfiltermodel.cpp+49 plugins/Utils/expressionfiltermodel.h+42 plugins/Utils/plugin.cpp+2 po/unity8.pot+3−3 qml/ApplicationMenus/ApplicationMenusLimits.qml+24 qml/ApplicationMenus/MenuBar.qml+279−129 qml/ApplicationMenus/MenuItem.qml+8−12 qml/ApplicationMenus/MenuNavigator.qml+23 qml/ApplicationMenus/MenuPopup.qml+116−25 qml/ApplicationMenus/qmldir+1 qml/Components/EdgeBarrier.qml+1 qml/Components/EdgeBarrierController.qml+1 qml/Launcher/Drawer.qml+8−1 qml/Launcher/Launcher.qml+3−2 qml/Launcher/LauncherPanel.qml+3 qml/Panel/Panel.qml+4−2 qml/Shell.qml+31−2 qml/Stage/Stage.qml+63−56 qml/Tutorial/TutorialLeftLong.qml+1−1 tests/mocks/Unity/Application/SurfaceManager.cpp+4−5 tests/mocks/Unity/CMakeLists.txt+6 tests/mocks/Unity/Launcher/CMakeLists.txt−2 tests/mocks/Utils/CMakeLists.txt+1 tests/mocks/Utils/plugin.cpp+2 tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt−2 tests/plugins/Unity/Launcher/CMakeLists.txt−1 tests/qmltests/ApplicationMenus/tst_MenuBar.qml+85−9 tests/qmltests/ApplicationMenus/tst_MenuPopup.qml+14 tests/qmltests/Launcher/tst_Launcher.qml+13 tests/qmltests/Stage/tst_DesktopStage.qml+193−10 tests/qmltests/Stage/tst_PhoneStage.qml+4−5 tests/qmltests/Tutorial/tst_Tutorial.qml+2−2 tests/qmltests/tst_OrientedShell.qml+2−1 tests/qmltests/tst_Shell.qml+132−1 tests/utils/modules/Unity/Test/UnityTestCase.qml−9
CMakeLists.txt+1
@@ -74,6 +74,7 @@ pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=26)
7474pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
7575pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
7676pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
77+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
7778pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
7879pkg_check_modules(GD3 REQUIRED gnome-desktop-3.0)
7980pkg_check_modules(UAL REQUIRED ubuntu-app-launch-2)
debian/changelog+48
@@ -1,3 +1,51 @@
1+unity8 (8.15+17.04.20170216.1-0ubuntu1) zesty; urgency=medium
2+
3+ [ Albert Astals Cid ]
4+ * Make menus items close the menu when clicking on them (LP: #1657082,
5+ #1657399)
6+ * Clicking on an open menu closes it + test
7+ * Make the menus and submenus do not go outside the screen when
8+ popping out
9+ * Auto open submenus on hover (LP: #1657085)
10+ * Make the double click on a menu not be maximize the window below
11+ (LP: #1657079)
12+ * Make the MenuBar InverseMouseArea eat hover events when there's a
13+ popup open (LP: #1657763)
14+ * Make double click on the window decoration maximize (LP: #1657758)
15+ * Eat hover events when the launcher quicklist is open (LP: #1640776)
16+ * Remove TODO now that we require Qt > 5.5
17+ * Serialize two make targets that want to cp to the same target file
18+ (LP: #1660576)
19+ * Remove variable that is always false (LP: #1585910)
20+
21+ [ Daniel d'Andrada ]
22+ * Consolidate launcher API checks
23+
24+ [ Lukáš Tinkl ]
25+ * Fixes for activating minimized windows and restoring fullscreen ones
26+ (LP: #1656808, #1658937, #1658938)
27+
28+ [ Michael Zanetti ]
29+ * move right edge push area up some levels so it can be used on top of
30+ the indicators
31+ * fix minimizing/restoring transitions (LP: #1658936)
32+ * fix some glitches with spread positioning
33+ * fix jumpy animation when closing an item from the spread
34+ * Show the wallpaper while the focused app is moving in staged mode
35+ (LP: #1648251)
36+ * Update the tutorial to mention the app drawer instead of the dash on
37+ long left edge swipe (LP: #1658932)
38+ * prevent hiding the launcher when the mouse hover event is eaten by
39+ the EdgePushArea (LP: #1657045)
40+ * properly invalidate the whole model (LP: #1658190)
41+ * stabilize OrientedShell test (LP: #1658994)
42+
43+ [ Nick Dedekind ]
44+ * Added overflow support to application menus.
45+ * Updated menu colors
46+
47+ -- Michał Sawicz <michal.sawicz@canonical.com> Thu, 16 Feb 2017 13:49:18 +0000
48+
149unity8 (8.15+17.04.20170206-0ubuntu1) zesty; urgency=medium
250
351 [ Daniel d'Andrada ]
plugins/Greeter/Unity/Launcher/CMakeLists.txt−1
@@ -1,4 +1,3 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
21pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
32
43add_definitions(-DSM_BUSNAME=systemBus)
plugins/Ubuntu/Gestures/TouchGestureArea.cpp−5
@@ -735,7 +735,6 @@ void TouchGestureArea::resyncCachedTouchPoints()
735735 bool added = false;
736736 bool ended = false;
737737 bool moved = false;
738− bool wantsDrag = false;
739738
740739 // list of deletes
741740 QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
@@ -765,10 +764,6 @@ void TouchGestureArea::resyncCachedTouchPoints()
765764 }
766765 }
767766
768− if (wantsDrag && !dragging()) {
769− setDragging(true);
770− }
771−
772767 if (ended) {
773768 if (m_cachedTouchPoints.isEmpty()) {
774769 if (!dragging()) Q_EMIT clicked();
plugins/Unity/Launcher/CMakeLists.txt−1
@@ -1,4 +1,3 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
21pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
32
43add_definitions(-DSM_BUSNAME=systemBus)
plugins/Utils/CMakeLists.txt+1
@@ -35,6 +35,7 @@ set(QMLPLUGIN_SRC
3535 globalfunctions.cpp
3636 URLDispatcher.cpp
3737 tabfocusfence.cpp
38+ expressionfiltermodel.cpp
3839 plugin.cpp
3940 )
4041
plugins/Utils/appdrawerproxymodel.cpp+2−2
@@ -43,8 +43,8 @@ void AppDrawerProxyModel::setSource(QAbstractItemModel *source)
4343 m_source = source;
4444 setSourceModel(m_source);
4545 setSortRole(m_sortBy == SortByAToZ ? AppDrawerModelInterface::RoleName : AppDrawerModelInterface::RoleUsage);
46− connect(m_source, &QAbstractItemModel::rowsRemoved, this, &AppDrawerProxyModel::invalidateFilter);
47− connect(m_source, &QAbstractItemModel::rowsInserted, this, &AppDrawerProxyModel::invalidateFilter);
46+ connect(m_source, &QAbstractItemModel::rowsRemoved, this, &AppDrawerProxyModel::invalidate);
47+ connect(m_source, &QAbstractItemModel::rowsInserted, this, &AppDrawerProxyModel::invalidate);
4848 Q_EMIT sourceChanged();
4949 }
5050}
plugins/Utils/expressionfiltermodel.cppnew file+49
@@ -0,0 +1,49 @@
1+/*
2+ * Copyright (C) 2017 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+#include "expressionfiltermodel.h"
18+
19+ExpressionFilterModel::ExpressionFilterModel(QObject *parent)
20+ : UnitySortFilterProxyModelQML(parent)
21+{
22+}
23+
24+QJSValue ExpressionFilterModel::matchExpression() const
25+{
26+ return m_matchExpression;
27+}
28+
29+void ExpressionFilterModel::setMatchExpression(const QJSValue &value)
30+{
31+ m_matchExpression = value;
32+ invalidateFilter();
33+}
34+
35+bool
36+ExpressionFilterModel::filterAcceptsRow(int sourceRow,
37+ const QModelIndex &sourceParent) const
38+{
39+ if (m_matchExpression.isCallable()) {
40+ QJSValueList args;
41+ args << sourceRow;
42+ QJSValue ret = m_matchExpression.call(args);
43+ if (ret.isBool()) {
44+ return ret.toBool();
45+ }
46+ }
47+
48+ return UnitySortFilterProxyModelQML::filterAcceptsRow(sourceRow, sourceParent);
49+}
plugins/Utils/expressionfiltermodel.hnew file+42
@@ -0,0 +1,42 @@
1+/*
2+ * Copyright (C) 2017 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+#ifndef EXPRESSIONFILTERMODEL_H
18+#define EXPRESSIONFILTERMODEL_H
19+
20+#include "unitysortfilterproxymodelqml.h"
21+#include <QJSValue>
22+
23+class ExpressionFilterModel : public UnitySortFilterProxyModelQML
24+{
25+ Q_OBJECT
26+ Q_PROPERTY(QJSValue matchExpression READ matchExpression WRITE setMatchExpression NOTIFY matchExpressionChanged)
27+public:
28+ explicit ExpressionFilterModel(QObject *parent = 0);
29+
30+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
31+
32+ QJSValue matchExpression() const;
33+ void setMatchExpression(const QJSValue& value);
34+
35+Q_SIGNALS:
36+ void matchExpressionChanged();
37+
38+private:
39+ mutable QJSValue m_matchExpression;
40+};
41+
42+#endif // EXPRESSIONFILTERMODEL_H
plugins/Utils/plugin.cpp+2
@@ -41,6 +41,7 @@
4141#include "URLDispatcher.h"
4242#include "appdrawerproxymodel.h"
4343#include "tabfocusfence.h"
44+#include "expressionfiltermodel.h"
4445
4546static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4647{
@@ -86,4 +87,5 @@ void UtilsPlugin::registerTypes(const char *uri)
8687 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
8788 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
8889 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
90+ qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel");
8991}
po/unity8.pot+3−3
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2017-02-06 09:12+0000\n"
11+"POT-Creation-Date: 2017-02-16 13:49+0000\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -521,7 +521,7 @@ msgstr ""
521521msgid "Search…"
522522msgstr ""
523523
524−#: qml/Launcher/Drawer.qml:123
524+#: qml/Launcher/Drawer.qml:127
525525msgctxt "Apps sorted alphabetically"
526526msgid "A-Z"
527527msgstr ""
@@ -622,7 +622,7 @@ msgid "Drag using 3 fingers any application from one window to the other"
622622msgstr ""
623623
624624#: qml/Tutorial/TutorialLeftLong.qml:49
625−msgid "Long swipe from the left edge to open the Today scope"
625+msgid "Long swipe from the left edge to open the Application Drawer"
626626msgstr ""
627627
628628#: qml/Tutorial/TutorialLeft.qml:47
qml/ApplicationMenus/ApplicationMenusLimits.qmlnew file+24
@@ -0,0 +1,24 @@
1+/*
2+ * Copyright (C) 2017 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+pragma Singleton
18+import QtQuick 2.4
19+import QtQuick.Window 2.2
20+
21+QtObject {
22+ property real screenWidth: Screen.width
23+ property real screenHeight: Screen.height
24+}
qml/ApplicationMenus/MenuBar.qml+279−129
@@ -24,12 +24,13 @@ Item {
2424 id: root
2525 objectName: "menuBar"
2626
27+ // set from outside
2728 property alias unityMenuModel: rowRepeater.model
28−
29− readonly property bool valid: rowRepeater.count > 0
30−
3129 property bool enableKeyFilter: false
30+ property real overflowWidth: width
3231
32+ // read from outside
33+ readonly property bool valid: rowRepeater.count > 0
3334 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
3435
3536 implicitWidth: row.width
@@ -75,189 +76,334 @@ Item {
7576 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
7677 anchors.fill: parent
7778 enabled: d.currentItem != null
79+ hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
7880 onPressed: d.dismissAll()
7981 }
8082
81− Item {
82− id: clippingItem
83−
84− height: root.height
85− width: root.width
86− clip: true
87−
88− Row {
89− id: row
90− spacing: units.gu(2)
91− height: parent.height
83+ Row {
84+ id: row
85+ spacing: units.gu(2)
86+ height: parent.height
9287
93− ActionContext {
94− id: menuBarContext
95− objectName: "barContext"
96− active: !d.currentItem && enableKeyFilter
97− }
98−
99− Repeater {
100− id: rowRepeater
101−
102− Item {
103− id: visualItem
104− objectName: root.objectName + "-item" + __ownIndex
105−
106− readonly property int __ownIndex: index
107− property Item __popup: null;
108− property bool popupVisible: __popup && __popup.visible
88+ ActionContext {
89+ id: menuBarContext
90+ objectName: "barContext"
91+ active: !d.currentItem && enableKeyFilter
92+ }
10993
110− implicitWidth: column.implicitWidth
111− implicitHeight: row.height
112− enabled: model.sensitive
94+ Connections {
95+ target: root.unityMenuModel
96+ onModelReset: d.firstInvisibleIndex = undefined
97+ }
11398
114− function show() {
115− if (!__popup) {
116− __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
117− // force the current item to be the newly popped up menu
118− } else {
119− __popup.show();
120− }
121− d.currentItem = visualItem;
99+ Repeater {
100+ id: rowRepeater
101+
102+ onItemAdded: d.recalcFirstInvisibleIndexAdded(index, item)
103+ onCountChanged: d.recalcFirstInvisibleIndex()
104+
105+ Item {
106+ id: visualItem
107+ objectName: root.objectName + "-item" + __ownIndex
108+
109+ readonly property int __ownIndex: index
110+ property Item __popup: null;
111+ readonly property bool popupVisible: __popup && __popup.visible
112+ readonly property bool shouldDisplay: x + width + ((__ownIndex < rowRepeater.count-1) ? units.gu(2) : 0) <
113+ root.overflowWidth - ((__ownIndex < rowRepeater.count-1) ? overflowButton.width : 0)
114+
115+ implicitWidth: column.implicitWidth
116+ implicitHeight: row.height
117+ enabled: model.sensitive && shouldDisplay
118+ opacity: shouldDisplay ? 1 : 0
119+
120+ function show() {
121+ if (!__popup) {
122+ __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
123+ __popup.childActivated.connect(dismiss);
124+ // force the current item to be the newly popped up menu
125+ } else {
126+ __popup.show();
122127 }
123− function hide() {
124− if (__popup) {
125− __popup.hide();
128+ d.currentItem = visualItem;
129+ }
130+ function hide() {
131+ if (__popup) {
132+ __popup.hide();
126133
127− if (d.currentItem === visualItem) {
128− d.currentItem = null;
129− }
134+ if (d.currentItem === visualItem) {
135+ d.currentItem = null;
130136 }
131137 }
132− function dismiss() {
133− if (__popup) {
134− __popup.destroy();
135− __popup = null;
138+ }
139+ function dismiss() {
140+ if (__popup) {
141+ __popup.destroy();
142+ __popup = null;
136143
137− if (d.currentItem === visualItem) {
138− d.currentItem = null;
139− }
144+ if (d.currentItem === visualItem) {
145+ d.currentItem = null;
140146 }
141147 }
148+ }
142149
143− Connections {
144− target: d
145− onDismissAll: visualItem.dismiss()
150+ onVisibleChanged: {
151+ if (!visible && __popup) dismiss();
152+ }
153+
154+ onShouldDisplayChanged: {
155+ if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) {
156+ d.recalcFirstInvisibleIndex();
146157 }
158+ }
147159
148− Component {
149− id: menuComponent
150− MenuPopup {
151− x: visualItem.x - units.gu(1)
152− anchors.top: parent.bottom
153− unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
160+ Connections {
161+ target: d
162+ onDismissAll: visualItem.dismiss()
163+ }
154164
155− Component.onCompleted: reset();
156− }
165+ Component {
166+ id: menuComponent
167+ MenuPopup {
168+ desiredX: visualItem.x - units.gu(1)
169+ desiredY: parent.height
170+ unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
171+
172+ Component.onCompleted: reset();
157173 }
174+ }
158175
159− RowLayout {
160− id: column
161− spacing: units.gu(1)
162− anchors {
163− centerIn: parent
164− }
176+ RowLayout {
177+ id: column
178+ spacing: units.gu(1)
179+ anchors {
180+ centerIn: parent
181+ }
165182
166− Icon {
167− Layout.preferredWidth: units.gu(2)
168− Layout.preferredHeight: units.gu(2)
169− Layout.alignment: Qt.AlignVCenter
183+ Icon {
184+ Layout.preferredWidth: units.gu(2)
185+ Layout.preferredHeight: units.gu(2)
186+ Layout.alignment: Qt.AlignVCenter
170187
171− visible: model.icon || false
172− source: model.icon || ""
173− }
188+ visible: model.icon || false
189+ source: model.icon || ""
190+ }
174191
175− ActionItem {
176− id: actionItem
177− width: _title.width
178− height: _title.height
192+ ActionItem {
193+ id: actionItem
194+ width: _title.width
195+ height: _title.height
179196
180− action: Action {
181− // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
182− text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
197+ action: Action {
198+ enabled: visualItem.enabled
199+ // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
200+ text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
183201
184− onTriggered: {
185− visualItem.show();
186− }
202+ onTriggered: {
203+ visualItem.show();
187204 }
205+ }
188206
189− Label {
190− id: _title
191− text: actionItem.text
192− horizontalAlignment: Text.AlignLeft
193− color: enabled ? "white" : "#5d5d5d"
194− }
207+ Label {
208+ id: _title
209+ text: actionItem.text
210+ horizontalAlignment: Text.AlignLeft
211+ color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
195212 }
196213 }
197− } // Item ( delegate )
198− } // Repeater
199− } // Row
214+ }
215+ } // Item ( delegate )
216+ } // Repeater
217+ } // Row
218+
219+ MouseArea {
220+ anchors.fill: row
221+ hoverEnabled: d.currentItem
222+
223+ onEntered: {
224+ if (d.currentItem) {
225+ updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
226+ }
227+ }
228+ onPositionChanged: {
229+ if (d.currentItem) {
230+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
231+ }
232+ }
233+ onClicked: {
234+ var prevItem = d.currentItem;
235+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
236+ if (prevItem && d.currentItem == prevItem) {
237+ prevItem.hide();
238+ }
239+ }
240+
241+ function updateCurrentItemFromPosition(point) {
242+ var pos = mapToItem(row, point.x, point.y);
243+
244+ if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
245+ d.hoveredItem = row.childAt(pos.x, pos.y);
246+ if (!d.hoveredItem || !d.hoveredItem.enabled)
247+ return;
248+ if (d.currentItem != d.hoveredItem) {
249+ d.currentItem = d.hoveredItem;
250+ }
251+ }
252+ }
253+ }
254+
255+ MouseArea {
256+ id: overflowButton
257+ objectName: "overflow"
258+
259+ hoverEnabled: d.currentItem
260+ onEntered: d.currentItem = this
261+ onPositionChanged: d.currentItem = this
262+ onClicked: d.currentItem = this
200263
201− MouseArea {
202− anchors.fill: parent
203− hoverEnabled: d.currentItem
264+ property Item __popup: null;
265+ readonly property bool popupVisible: __popup && __popup.visible
266+ readonly property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null
204267
205− onEntered: {
206− if (d.currentItem) {
207− updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
268+ visible: d.firstInvisibleIndex != undefined
269+ x: firstInvisibleItem ? firstInvisibleItem.x : 0
270+
271+ height: parent.height
272+ width: units.gu(4)
273+
274+ onVisibleChanged: {
275+ if (!visible && __popup) dismiss();
276+ }
277+
278+ Icon {
279+ id: icon
280+ width: units.gu(2)
281+ height: units.gu(2)
282+ anchors.centerIn: parent
283+ color: theme.palette.normal.backgroundText
284+ name: "toolkit_chevron-down_2gu"
285+ }
286+
287+ function show() {
288+ if (!__popup) {
289+ __popup = overflowComponent.createObject(root, { objectName: overflowButton.objectName + "-menu" });
290+ __popup.childActivated.connect(dismiss);
291+ // force the current item to be the newly popped up menu
292+ } else {
293+ __popup.show();
294+ }
295+ d.currentItem = overflowButton;
296+ }
297+ function hide() {
298+ if (__popup) {
299+ __popup.hide();
300+
301+ if (d.currentItem === overflowButton) {
302+ d.currentItem = null;
208303 }
209304 }
210− onPositionChanged: {
211− if (d.currentItem) {
212− updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
305+ }
306+ function dismiss() {
307+ if (__popup) {
308+ __popup.destroy();
309+ __popup = null;
310+
311+ if (d.currentItem === overflowButton) {
312+ d.currentItem = null;
213313 }
214314 }
215− onClicked: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
315+ }
216316
217− function updateCurrentItemFromPosition(point) {
218− var pos = mapToItem(row, point.x, point.y);
317+ Connections {
318+ target: d
319+ onDismissAll: overflowButton.dismiss()
320+ }
321+
322+ Component {
323+ id: overflowComponent
324+ MenuPopup {
325+ id: overflowPopup
326+ desiredX: overflowButton.x - units.gu(1)
327+ desiredY: parent.height
328+ unityMenuModel: overflowModel
329+
330+ ExpressionFilterModel {
331+ id: overflowModel
332+ sourceModel: root.unityMenuModel
333+ matchExpression: function(index) {
334+ if (d.firstInvisibleIndex === undefined) return false;
335+ return index >= d.firstInvisibleIndex;
336+ }
219337
220− if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
221− d.hoveredItem = row.childAt(pos.x, pos.y);
222− if (!d.hoveredItem || !d.hoveredItem.enabled)
223− return false;
224− if (d.currentItem != d.hoveredItem) {
225− d.currentItem = d.hoveredItem;
338+ function submenu(index) {
339+ return sourceModel.submenu(mapRowToSource(index));
226340 }
341+ function activate(index) {
342+ return sourceModel.activate(mapRowToSource(index));
343+ }
344+ }
345+
346+ Connections {
347+ target: d
348+ onFirstInvisibleIndexChanged: overflowModel.invalidate()
227349 }
228− return true;
229350 }
230351 }
352+ }
231353
232− Rectangle {
233− id: underline
234− anchors {
235− bottom: row.bottom
236− }
237− x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
238− width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
239− height: units.dp(4)
240− color: UbuntuColors.orange
241− visible: d.currentItem
354+ Rectangle {
355+ id: underline
356+ anchors {
357+ bottom: row.bottom
242358 }
359+ x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
360+ width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
361+ height: units.dp(4)
362+ color: UbuntuColors.orange
363+ visible: d.currentItem
243364 }
244365
245366 MenuNavigator {
246367 id: d
247368 objectName: "d"
248369 itemView: rowRepeater
370+ hasOverflow: overflowButton.visible
249371
250372 property Item currentItem: null
251373 property Item hoveredItem: null
252374 property Item prevCurrentItem: null
253−
254− readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
255−
256375 property bool altPressed: false
257376 property bool longAltPressed: false
377+ property var firstInvisibleIndex: undefined
378+
379+ readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1
258380
259381 signal dismissAll()
260382
383+ function recalcFirstInvisibleIndexAdded(index, item) {
384+ if (firstInvisibleIndex === undefined) {
385+ if (!item.shouldDisplay) {
386+ firstInvisibleIndex = index;
387+ }
388+ } else if (index <= firstInvisibleIndex) {
389+ if (!item.shouldDisplay) {
390+ firstInvisibleIndex = index;
391+ } else {
392+ firstInvisibleIndex++;
393+ }
394+ }
395+ }
396+
397+ function recalcFirstInvisibleIndex() {
398+ for (var i = 0; i < rowRepeater.count; i++) {
399+ if (!rowRepeater.itemAt(i).shouldDisplay) {
400+ firstInvisibleIndex = i;
401+ return;
402+ }
403+ }
404+ firstInvisibleIndex = undefined;
405+ }
406+
261407 onSelect: {
262408 var delegate = rowRepeater.itemAt(index);
263409 if (delegate) {
@@ -265,6 +411,10 @@ Item {
265411 }
266412 }
267413
414+ onOverflow: {
415+ d.currentItem = overflowButton;
416+ }
417+
268418 onCurrentItemChanged: {
269419 if (prevCurrentItem && prevCurrentItem != currentItem) {
270420 if (currentItem) {
qml/ApplicationMenus/MenuItem.qml+8−12
@@ -46,6 +46,8 @@ ActionItem {
4646 enabled: menuData ? menuData.sensitive : false
4747
4848 action: Action {
49+ enabled: root.enabled
50+
4951 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
5052 text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
5153 checkable: menuData.isCheck || menuData.isRadio
@@ -111,6 +113,7 @@ ActionItem {
111113 elide: Text.ElideNone
112114 wrapMode: Text.NoWrap
113115 clip: true
116+ color: enabled ? theme.palette.normal.overlayText : theme.palette.disabled.overlayText
114117 Layout.fillWidth: true
115118
116119 text: root.text ? root.text : ""
@@ -121,8 +124,8 @@ ActionItem {
121124 elide: Text.ElideNone
122125 wrapMode: Text.NoWrap
123126 clip: true
124− color: enabled ? theme.palette.normal.backgroundSecondaryText :
125− theme.palette.disabled.backgroundSecondaryText
127+ color: enabled ? theme.palette.normal.overlaySecondaryText :
128+ theme.palette.disabled.overlaySecondaryText
126129
127130 visible: menuData.shortcut != undefined && !root.hasSubmenu && QuickUtils.keyboardAttached
128131 text: menuData.shortcut ? menuData.shortcut : ""
@@ -133,18 +136,11 @@ ActionItem {
133136 id: chevronIcon
134137 width: units.gu(2)
135138 height: units.gu(2)
136− color: enabled ? theme.palette.normal.backgroundSecondaryText :
137− theme.palette.disabled.backgroundSecondaryText
139+ color: enabled ? theme.palette.normal.overlayText :
140+ theme.palette.disabled.overlayText
138141
139142 visible: root.hasSubmenu
140− name: "chevron"
141− }
142− }
143−
144− MouseArea {
145− anchors.fill: parent
146− onClicked: {
147− root.trigger(action && action.checkable ? !action.checked : undefined);
143+ name: "toolkit_chevron-ltr_2gu"
148144 }
149145 }
150146}
qml/ApplicationMenus/MenuNavigator.qml+23
@@ -18,8 +18,10 @@ import QtQuick 2.4
1818
1919QtObject {
2020 property Item itemView: null
21+ property bool hasOverflow: false
2122
2223 signal select(int index)
24+ signal overflow()
2325
2426 function selectNext(currentIndex) {
2527 var menu;
@@ -32,6 +34,11 @@ QtObject {
3234 break;
3335 }
3436 newIndex++;
37+
38+ if (hasOverflow && newIndex === itemView.count) {
39+ overflow()
40+ break;
41+ }
3542 }
3643 } else if (currentIndex !== -1 && itemView.count > 1) {
3744 var startIndex = (currentIndex + 1) % itemView.count;
@@ -42,6 +49,12 @@ QtObject {
4249 select(newIndex);
4350 break;
4451 }
52+
53+ if (hasOverflow && newIndex + 1 === itemView.count) {
54+ overflow()
55+ break;
56+ }
57+
4558 newIndex = (newIndex + 1) % itemView.count;
4659 } while (newIndex !== startIndex)
4760 }
@@ -58,12 +71,21 @@ QtObject {
5871 break;
5972 }
6073 newIndex--;
74+
75+ if (hasOverflow && newIndex < 0 ) {
76+ overflow();
77+ break;
78+ }
6179 }
6280 } else if (currentIndex !== -1 && itemView.count > 1) {
6381 var startIndex = currentIndex - 1;
6482 newIndex = startIndex;
6583 do {
6684 if (newIndex < 0) {
85+ if (hasOverflow) {
86+ overflow();
87+ break;
88+ }
6789 newIndex = itemView.count - 1;
6890 }
6991 menu = itemView.itemAt(newIndex);
@@ -72,6 +94,7 @@ QtObject {
7294 break;
7395 }
7496 newIndex--;
97+
7598 } while (newIndex !== startIndex)
7699 }
77100 }
qml/ApplicationMenus/MenuPopup.qml+116−25
@@ -16,16 +16,51 @@
1616
1717import QtQuick 2.4
1818import QtQuick.Layouts 1.1
19−import QtQuick.Window 2.2
2019import Ubuntu.Components 1.3
2120import Ubuntu.Components.ListItems 1.3 as ListItems
2221import "../Components"
22+import "../Components/PanelState"
23+import "."
2324
2425UbuntuShape {
2526 id: root
2627 objectName: "menu"
2728 backgroundColor: theme.palette.normal.overlay
2829
30+ signal childActivated()
31+
32+ // true for submenus that need to show on the other side of their parent
33+ // if they don't fit when growing right
34+ property bool substractWidth: false
35+
36+ property real desiredX
37+ x: {
38+ var dummy = visible; // force recalc when shown/hidden
39+ var parentTopLeft = parent.mapToItem(null, 0, 0);
40+ var farX = ApplicationMenusLimits.screenWidth;
41+ if (parentTopLeft.x + width + desiredX <= farX) {
42+ return desiredX;
43+ } else {
44+ if (substractWidth) {
45+ return -width;
46+ } else {
47+ return farX - parentTopLeft.x - width;
48+ }
49+ }
50+ }
51+
52+ property real desiredY
53+ y: {
54+ var dummy = visible; // force recalc when shown/hidden
55+ var parentTopLeft = parent.mapToItem(null, 0, 0);
56+ var bottomY = ApplicationMenusLimits.screenHeight;
57+ if (parentTopLeft.y + height + desiredY <= bottomY) {
58+ return desiredY;
59+ } else {
60+ return bottomY - parentTopLeft.y - height;
61+ }
62+ }
63+
2964 property alias unityMenuModel: repeater.model
3065
3166 function show() {
@@ -64,9 +99,9 @@ UbuntuShape {
6499 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
65100
66101 property real __minimumWidth: units.gu(20)
67− property real __maximumWidth: Screen.width * 0.7
102+ property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
68103 property real __minimumHeight: units.gu(2)
69− property real __maximumHeight: Screen.height * 0.7
104+ property real __maximumHeight: ApplicationMenusLimits.screenHeight - PanelState.panelHeight
70105
71106 signal dismissAll()
72107
@@ -76,6 +111,8 @@ UbuntuShape {
76111 } else {
77112 hoveredItem = null;
78113 }
114+
115+ submenuHoverTimer.stop();
79116 }
80117
81118 onSelect: {
@@ -147,8 +184,19 @@ UbuntuShape {
147184 }
148185
149186 MouseArea {
187+ id: previousMA
150188 anchors.fill: parent
151− onPressed: {
189+ hoverEnabled: enabled
190+ onPressed: progress()
191+
192+ Timer {
193+ running: previousMA.containsMouse && !listView.atYBeginning
194+ interval: 1000
195+ repeat: true
196+ onTriggered: previousMA.progress()
197+ }
198+
199+ function progress() {
152200 var item = menuColumn.childAt(0, listView.contentY);
153201 if (item) {
154202 var previousItem = item;
@@ -176,10 +224,15 @@ UbuntuShape {
176224 contentHeight: menuColumn.height
177225 interactive: height < contentHeight
178226
227+ Timer {
228+ id: submenuHoverTimer
229+ interval: 225 // GTK MENU_POPUP_DELAY, Qt SH_Menu_SubMenuPopupDelay in QCommonStyle is 256
230+ onTriggered: d.currentItem.item.trigger();
231+ }
232+
179233 MouseArea {
180234 anchors.fill: parent
181235 hoverEnabled: true
182− propagateComposedEvents: true // propogate events so we send clicks to children.
183236 z: 1 // on top so we override any other hovers
184237 onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
185238 onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
@@ -189,12 +242,25 @@ UbuntuShape {
189242
190243 if (!d.hoveredItem || !d.currentItem ||
191244 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
245+ submenuHoverTimer.stop();
246+
192247 d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
193248 if (!d.hoveredItem || !d.hoveredItem.enabled)
194− return false;
249+ return;
195250 d.currentItem = d.hoveredItem;
251+
252+ if (!d.currentItem.__isSeparator && d.currentItem.item.hasSubmenu && d.currentItem.item.enabled) {
253+ submenuHoverTimer.start();
254+ }
255+ }
256+ }
257+
258+ onClicked: {
259+ var pos = mapToItem(listView.contentItem, mouse.x, mouse.y);
260+ var clickedItem = menuColumn.childAt(pos.x, pos.y);
261+ if (clickedItem.enabled && !clickedItem.__isSeparator) {
262+ clickedItem.item.trigger();
196263 }
197− return true;
198264 }
199265 }
200266
@@ -249,6 +315,8 @@ UbuntuShape {
249315 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
250316
251317 action.onTriggered: {
318+ submenuHoverTimer.stop();
319+
252320 d.currentItem = loader;
253321
254322 if (hasSubmenu) {
@@ -257,22 +325,29 @@ UbuntuShape {
257325 popup = submenuComponent.createObject(focusScope, {
258326 objectName: loader.objectName + "-",
259327 unityMenuModel: model,
260− x: Qt.binding(function() { return root.width }),
261− y: Qt.binding(function() {
328+ substractWidth: true,
329+ desiredX: Qt.binding(function() { return root.width }),
330+ desiredY: Qt.binding(function() {
262331 var dummy = listView.contentY; // force a recalc on contentY change.
263332 return mapToItem(container, 0, y).y;
264333 })
265334 });
335+ popup.retreat.connect(function() {
336+ popup.destroy();
337+ popup = null;
338+ menuItem.forceActiveFocus();
339+ });
340+ popup.childActivated.connect(function() {
341+ popup.destroy();
342+ popup = null;
343+ root.childActivated();
344+ });
266345 } else if (popup) {
267346 popup.visible = true;
268347 }
269− popup.retreat.connect(function() {
270− popup.destroy();
271− popup = null;
272− menuItem.forceActiveFocus();
273− })
274348 } else {
275349 root.unityMenuModel.activate(__ownIndex);
350+ root.childActivated();
276351 }
277352 }
278353
@@ -349,8 +424,19 @@ UbuntuShape {
349424 }
350425
351426 MouseArea {
427+ id: nextMA
352428 anchors.fill: parent
353− onPressed: {
429+ hoverEnabled: enabled
430+ onPressed: progress()
431+
432+ Timer {
433+ running: nextMA.containsMouse && !listView.atYEnd
434+ interval: 1000
435+ repeat: true
436+ onTriggered: nextMA.progress()
437+ }
438+
439+ function progress() {
354440 var item = menuColumn.childAt(0, listView.contentY + listView.height);
355441 if (item) {
356442 var nextItem = item;
@@ -375,23 +461,28 @@ UbuntuShape {
375461 id: submenuLoader
376462 source: "MenuPopup.qml"
377463
464+ property real desiredX
465+ property real desiredY
466+ property bool substractWidth
378467 property var unityMenuModel: null
379468 signal retreat()
380−
381− Binding {
382− target: item
383− property: "unityMenuModel"
384− value: submenuLoader.unityMenuModel
469+ signal childActivated()
470+
471+ onLoaded: {
472+ item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
473+ item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
474+ item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
475+ item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
476+ item.substractWidth = Qt.binding(function() { return submenuLoader.substractWidth; });
385477 }
386478
387− Binding {
479+ Keys.onLeftPressed: retreat()
480+
481+ Connections {
388482 target: item
389− property: "objectName"
390− value: submenuLoader.objectName + "menu"
483+ onChildActivated: childActivated();
391484 }
392485
393− Keys.onLeftPressed: retreat()
394−
395486 Component.onCompleted: item.select(0);
396487 onVisibleChanged: if (visible) { item.select(0); }
397488 }
qml/ApplicationMenus/qmldirnew file+1
@@ -0,0 +1 @@
1+singleton ApplicationMenusLimits 0.1 ApplicationMenusLimits.qml
qml/Components/EdgeBarrier.qml+1
@@ -30,6 +30,7 @@ Item {
3030 property int edge: Qt.LeftEdge
3131
3232 readonly property alias progress: controller.progress
33+ readonly property alias containsMouse: controller.containsMouse
3334
3435 property Item target: parent
3536 function push(amount) { controller.push(amount); }
qml/Components/EdgeBarrierController.qml+1
@@ -34,6 +34,7 @@ MouseArea {
3434
3535 function push(amount) {
3636 if (!root._containsMouse) {
37+ console.warn("pushing right edge without mouse actually being at right edge!")
3738 return;
3839 }
3940
qml/Launcher/Drawer.qml+8−1
@@ -95,7 +95,11 @@ FocusScope {
9595 KeyNavigation.down: sections
9696
9797 onAccepted: {
98− if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
98+ if (searchField.displayText != "" && listLoader.item) {
99+ // In case there is no currentItem (it might have been filtered away) lets reset it to the first item
100+ if (!listLoader.item.currentItem) {
101+ listLoader.item.currentIndex = 0;
102+ }
99103 root.applicationSelected(listLoader.item.getFirstAppId());
100104 }
101105 }
@@ -238,6 +242,7 @@ FocusScope {
238242 source: sortProxyModel
239243 group: AppDrawerProxyModel.GroupByAll
240244 sortBy: AppDrawerProxyModel.SortByUsage
245+ dynamicSortFilter: false
241246 }
242247
243248 delegate: UbuntuShape {
@@ -287,6 +292,7 @@ FocusScope {
287292 source: sortProxyModel
288293 sortBy: AppDrawerProxyModel.SortByAToZ
289294 group: AppDrawerProxyModel.GroupByAToZ
295+ dynamicSortFilter: false
290296 }
291297
292298 delegate: UbuntuShape {
@@ -320,6 +326,7 @@ FocusScope {
320326 id: categoryModel
321327 source: sortProxyModel
322328 filterLetter: model.letter
329+ dynamicSortFilter: false
323330 }
324331 delegateWidth: units.gu(8)
325332 delegateHeight: units.gu(10)
qml/Launcher/Launcher.qml+3−2
@@ -307,7 +307,8 @@ FocusScope {
307307 InverseMouseArea {
308308 id: closeMouseArea
309309 anchors.fill: panel
310− enabled: root.state == "visible" || root.state == "drawer"
310+ enabled: (root.state == "visible" && !root.lockedVisible) || root.state == "drawer" || hoverEnabled
311+ hoverEnabled: panel.quickListOpen
311312 visible: enabled
312313 onPressed: {
313314 mouse.accepted = false;
@@ -414,7 +415,7 @@ FocusScope {
414415 target: panel.dismissTimer
415416 onTriggered: {
416417 if (root.autohideEnabled && !root.lockedVisible) {
417− if (!panel.preventHiding) {
418+ if (!edgeBarrier.containsMouse && !panel.preventHiding) {
418419 root.state = ""
419420 } else {
420421 panel.dismissTimer.restart()
qml/Launcher/LauncherPanel.qml+3
@@ -35,6 +35,7 @@ Rectangle {
3535 || dndArea.containsMouse || dashItem.hovered
3636 property int highlightIndex: -2
3737 property bool shortcutHintsShown: false
38+ readonly property bool quickListOpen: quickList.state === "open"
3839
3940 signal applicationSelected(string appId)
4041 signal showDashHome()
@@ -686,6 +687,8 @@ Rectangle {
686687 InverseMouseArea {
687688 anchors.fill: quickListShape
688689 enabled: quickList.state == "open" || pressed
690+ hoverEnabled: enabled
691+ visible: enabled
689692
690693 onClicked: {
691694 quickList.state = "";
qml/Panel/Panel.qml+4−2
@@ -207,6 +207,8 @@ Item {
207207 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
208208 active: __applicationMenus.model
209209
210+ width: parent.width - windowControlButtons.width - units.gu(2) - __indicators.barWidth
211+
210212 property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
211213
212214 sourceComponent: MenuBar {
@@ -281,7 +283,7 @@ Item {
281283 anchors.centerIn: parent
282284 text: actionItem.text
283285 horizontalAlignment: Text.AlignLeft
284− color: enabled ? "white" : "#5d5d5d"
286+ color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
285287 }
286288 }
287289
@@ -383,7 +385,7 @@ Item {
383385 }
384386
385387 enabled: !applicationMenus.expanded
386− opacity: !applicationMenus.expanded ? 1 : 0
388+ opacity: !callHint.visible && !applicationMenus.expanded ? 1 : 0
387389 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
388390
389391 onEnabledChanged: {
qml/Shell.qml+31−2
@@ -288,6 +288,7 @@ StyledItem {
288288 applicationManager: ApplicationManager
289289 topLevelSurfaceList: topLevelSurfaceList
290290 inputMethodRect: inputMethod.visibleRect
291+ rightEdgePushProgress: rightEdgeBarrier.progress
291292
292293 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
293294 ? "phone"
@@ -523,12 +524,13 @@ StyledItem {
523524 hides: [launcher]
524525 available: (!greeter || !greeter.shown)
525526 && !shell.waitingOnGreeter
527+ && !stage.spreadShown
526528 }
527529
528530 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
529531 ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
530532 : false
531− fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
533+ fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
532534 || greeter.hasLockedApp
533535 greeterShown: greeter && greeter.shown
534536 hasKeyboard: shell.hasKeyboard
@@ -694,6 +696,33 @@ StyledItem {
694696 }
695697 ]
696698 }
699+
700+ EdgeBarrier {
701+ id: rightEdgeBarrier
702+ enabled: !greeter.shown
703+
704+ // NB: it does its own positioning according to the specified edge
705+ edge: Qt.RightEdge
706+
707+ onPassed: {
708+ panel.indicators.hide()
709+ }
710+
711+ material: Component {
712+ Item {
713+ Rectangle {
714+ width: parent.height
715+ height: parent.width
716+ rotation: 90
717+ anchors.centerIn: parent
718+ gradient: Gradient {
719+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
720+ GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
721+ }
722+ }
723+ }
724+ }
725+ }
697726 }
698727
699728 Dialogs {
@@ -777,7 +806,7 @@ StyledItem {
777806
778807 onPushedRightBoundary: {
779808 if (buttons === Qt.NoButton) {
780− stage.pushRightEdge(amount);
809+ rightEdgeBarrier.push(amount);
781810 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
782811 previewRectangle.maximizeRight(amount);
783812 }
qml/Stage/Stage.qml+63−56
@@ -46,6 +46,7 @@ FocusScope {
4646 property int leftMargin: 0
4747 property bool oskEnabled: false
4848 property rect inputMethodRect
49+ property real rightEdgePushProgress: 0
4950
5051 // Configuration
5152 property string mode: "staged"
@@ -101,6 +102,8 @@ FocusScope {
101102 // No we didn't, do a quick alt-tab
102103 if (appRepeater.count > 1) {
103104 appRepeater.itemAt(1).activate();
105+ } else if (appRepeater.count > 0) {
106+ appRepeater.itemAt(0).activate(); // quick alt-tab to the only (minimized) window should still activate it
104107 }
105108 }
106109 }
@@ -125,11 +128,6 @@ FocusScope {
125128 // functions to be called from outside
126129 function updateFocusedAppOrientation() { /* TODO */ }
127130 function updateFocusedAppOrientationAnimated() { /* TODO */}
128− function pushRightEdge(amount) {
129− if (root.spreadEnabled) {
130− edgeBarrier.push(amount);
131− }
132− }
133131
134132 function closeSpread() {
135133 priv.goneToSpread = false;
@@ -141,6 +139,12 @@ FocusScope {
141139 }
142140 }
143141
142+ onRightEdgePushProgressChanged: {
143+ if (spreadEnabled && rightEdgePushProgress >= 1) {
144+ priv.goneToSpread = true
145+ }
146+ }
147+
144148 GSettings {
145149 id: lifecycleExceptions
146150 schema.id: "com.canonical.qtmir"
@@ -452,7 +456,7 @@ FocusScope {
452456 PropertyChanges { target: wallpaper; visible: false }
453457 },
454458 State {
455− name: "stagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "staged"
459+ name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
456460 PropertyChanges {
457461 target: blurLayer;
458462 visible: true;
@@ -462,7 +466,7 @@ FocusScope {
462466 }
463467 },
464468 State {
465− name: "sideStagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "stagedWithSideStage"
469+ name: "sideStagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "stagedWithSideStage"
466470 extend: "stagedRightEdge"
467471 PropertyChanges {
468472 target: sideStage
@@ -471,18 +475,18 @@ FocusScope {
471475 }
472476 },
473477 State {
474− name: "windowedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "windowed"
478+ name: "windowedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "windowed"
475479 PropertyChanges {
476480 target: blurLayer;
477481 visible: true
478482 blurRadius: 32
479483 brightness: .65
480− opacity: MathUtils.linearAnimation(spreadItem.rightEdgeBreakPoint, 1, 0, 1, Math.max(rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0, edgeBarrier.progress))
484+ opacity: MathUtils.linearAnimation(spreadItem.rightEdgeBreakPoint, 1, 0, 1, Math.max(rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0, rightEdgePushProgress))
481485 }
482486 },
483487 State {
484488 name: "staged"; when: root.mode === "staged"
485− PropertyChanges { target: wallpaper; visible: false }
489+ PropertyChanges { target: wallpaper; visible: !priv.focusedAppDelegate || priv.focusedAppDelegate.x !== 0 }
486490 },
487491 State {
488492 name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
@@ -502,7 +506,6 @@ FocusScope {
502506 Transition {
503507 to: "spread"
504508 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
505− PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
506509 },
507510 Transition {
508511 from: "spread"
@@ -517,6 +520,7 @@ FocusScope {
517520 }
518521 }
519522 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
523+ PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
520524 }
521525 },
522526 Transition {
@@ -811,7 +815,7 @@ FocusScope {
811815 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
812816
813817 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
814− readonly property bool fullscreen: window.state === Mir.FullscreenState
818+ readonly property bool fullscreen: windowState === WindowStateStorage.WindowStateFullscreen
815819
816820 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
817821 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
@@ -822,7 +826,10 @@ FocusScope {
822826 readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
823827 readonly property alias orientationChangesEnabled: decoratedWindow.orientationChangesEnabled
824828
829+ // TODO drop our own windowType once Mir/Miral/Qtmir gets in sync with ours
825830 property int windowState: WindowStateStorage.WindowStateNormal
831+ property int prevWindowState: WindowStateStorage.WindowStateRestored
832+
826833 property bool animationsEnabled: true
827834 property alias title: decoratedWindow.title
828835 readonly property string appName: model.application ? model.application.name : ""
@@ -877,9 +884,6 @@ FocusScope {
877884 }
878885 priv.updateMainAndSideStageIndexes();
879886 }
880− if (root.mode == "windowed") {
881− appDelegate.restore(true /* animated */, appDelegate.windowState);
882− }
883887 appDelegate.focus = true;
884888 }
885889
@@ -929,7 +933,14 @@ FocusScope {
929933 } else if (model.window.state === Mir.MaximizedBottomRightState) {
930934 appDelegate.maximizeBottomRight();
931935 } else if (model.window.state === Mir.RestoredState) {
932− appDelegate.restore();
936+ if (appDelegate.fullscreen && appDelegate.prevWindowState != WindowStateStorage.WindowStateRestored) {
937+ model.window.requestState(WindowStateStorage.toMirState(appDelegate.prevWindowState));
938+ } else {
939+ appDelegate.restore();
940+ }
941+ } else if (model.window.state === Mir.FullscreenState) {
942+ appDelegate.prevWindowState = appDelegate.windowState;
943+ appDelegate.windowState = WindowStateStorage.WindowStateFullscreen;
933944 }
934945 }
935946 }
@@ -1031,6 +1042,7 @@ FocusScope {
10311042 animationsEnabled = (animated === undefined) || animated;
10321043 windowState = state || WindowStateStorage.WindowStateRestored;
10331044 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1045+ prevWindowState = windowState;
10341046 }
10351047
10361048 function playFocusAnimation() {
@@ -1193,7 +1205,7 @@ FocusScope {
11931205 when: (root.mode == "staged" || root.mode == "stagedWithSideStage") && (root.state == "sideStagedRightEdge" || root.state == "stagedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running)
11941206 PropertyChanges {
11951207 target: stagedRightEdgeMaths
1196− progress: Math.max(edgeBarrier.progress, rightEdgeDragArea.draggedProgress)
1208+ progress: Math.max(rightEdgePushProgress, rightEdgeDragArea.draggedProgress)
11971209 }
11981210 PropertyChanges {
11991211 target: appDelegate
@@ -1220,11 +1232,11 @@ FocusScope {
12201232 },
12211233 State {
12221234 name: "windowedRightEdge"
1223− when: root.mode == "windowed" && (root.state == "windowedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running || edgeBarrier.progress > 0)
1235+ when: root.mode == "windowed" && (root.state == "windowedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running || rightEdgePushProgress > 0)
12241236 PropertyChanges {
12251237 target: windowedRightEdgeMaths
12261238 swipeProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0
1227− pushProgress: edgeBarrier.progress
1239+ pushProgress: rightEdgePushProgress
12281240 }
12291241 PropertyChanges {
12301242 target: appDelegate
@@ -1418,11 +1430,12 @@ FocusScope {
14181430 name: "minimized"; when: appDelegate.minimized
14191431 PropertyChanges {
14201432 target: appDelegate
1421− requestedX: -appDelegate.width / 2
14221433 scale: units.gu(5) / appDelegate.width
14231434 opacity: 0;
14241435 visuallyMinimized: true
14251436 visuallyMaximized: false
1437+ x: -appDelegate.width / 2
1438+ y: root.height / 2
14261439 }
14271440 }
14281441 ]
@@ -1438,15 +1451,6 @@ FocusScope {
14381451 to: "staged,stagedWithSideStage"
14391452 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
14401453 },
1441− Transition {
1442− to: "minimized"
1443− enabled: appDelegate.animationsEnabled
1444− PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1445− SequentialAnimation {
1446− UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
1447− PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1448− }
1449− },
14501454 Transition {
14511455 to: "spread"
14521456 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
@@ -1495,6 +1499,31 @@ FocusScope {
14951499 PropertyAction { target: appDelegate; property: "visible" }
14961500 }
14971501 },
1502+ Transition {
1503+ from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1504+ to: "minimized"
1505+ SequentialAnimation {
1506+ ScriptAction { script: print("transitioning:", appDelegate.x, appDelegate.y, appDelegate.scale) }
1507+ ScriptAction { script: { fakeRectangle.stop(); } }
1508+ PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1509+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,scale,opacity"; duration: priv.animationDuration }
1510+ PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1511+ }
1512+ },
1513+ Transition {
1514+ from: "minimized"
1515+ to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1516+ SequentialAnimation {
1517+ ScriptAction { script: print("transitioning:", appDelegate.x, appDelegate.y, appDelegate.scale) }
1518+ PropertyAction { target: appDelegate; property: "visuallyMinimized,z" }
1519+ ParallelAnimation {
1520+ UbuntuNumberAnimation { target: appDelegate; properties: "x"; from: -appDelegate.width / 2; duration: priv.animationDuration }
1521+ UbuntuNumberAnimation { target: appDelegate; properties: "y,opacity"; duration: priv.animationDuration }
1522+ UbuntuNumberAnimation { target: appDelegate; properties: "scale"; from: 0; duration: priv.animationDuration }
1523+ }
1524+ PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1525+ }
1526+ },
14981527 Transition {
14991528 id: windowedTransition
15001529 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen,minimized"
@@ -1586,7 +1615,10 @@ FocusScope {
15861615 onDecorationReleased: fakeRectangle.commit();
15871616
15881617 property real angle: 0
1618+ Behavior on angle { enabled: priv.closingIndex >= 0; UbuntuNumberAnimation {} }
15891619 property real itemScale: 1
1620+ Behavior on itemScale { enabled: priv.closingIndex >= 0; UbuntuNumberAnimation {} }
1621+
15901622 transform: [
15911623 Scale {
15921624 origin.x: 0
@@ -1648,7 +1680,6 @@ FocusScope {
16481680 onClicked: {
16491681 spreadItem.highlightedIndex = index;
16501682 if (distance == 0) {
1651− model.window.activate();
16521683 priv.goneToSpread = false;
16531684 }
16541685 }
@@ -1756,30 +1787,6 @@ FocusScope {
17561787 appContainerHeight: appContainer.height
17571788 }
17581789
1759− EdgeBarrier {
1760− id: edgeBarrier
1761−
1762− // NB: it does its own positioning according to the specified edge
1763− edge: Qt.RightEdge
1764−
1765− onPassed: priv.goneToSpread = true;
1766−
1767− material: Component {
1768− Item {
1769− Rectangle {
1770− width: parent.height
1771− height: parent.width
1772− rotation: 90
1773− anchors.centerIn: parent
1774− gradient: Gradient {
1775− GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
1776− GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
1777− }
1778− }
1779− }
1780− }
1781− }
1782−
17831790 MouseArea {
17841791 id: hoverMouseArea
17851792 objectName: "hoverMouseArea"
@@ -1846,11 +1853,11 @@ FocusScope {
18461853 var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
18471854 if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
18481855 var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1849− snapAnimation.to = floatingFlickable.contentX - offset;
1856+ snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
18501857 snapAnimation.start();
18511858 } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
18521859 var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1853− snapAnimation.to = floatingFlickable.contentX - offset;
1860+ snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
18541861 snapAnimation.start();
18551862 }
18561863 }
qml/Tutorial/TutorialLeftLong.qml+1−1
@@ -46,7 +46,7 @@ TutorialPage {
4646 }
4747
4848 label {
49− text: i18n.tr("Long swipe from the left edge to open the Today scope")
49+ text: i18n.tr("Long swipe from the left edge to open the Application Drawer")
5050 anchors.left: arrow.right
5151 anchors.leftMargin: units.gu(3)
5252 anchors.right: root.right
tests/mocks/Unity/Application/SurfaceManager.cpp+4−5
@@ -211,8 +211,6 @@ void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
211211
212212void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
213213{
214− Q_ASSERT(!m_underModification);
215−
216214 DEBUG_MSG("("<<surface<<","<<state<<") started");
217215 Q_EMIT modificationsStarted();
218216 m_underModification = true;
@@ -266,9 +264,10 @@ void SurfaceManager::focusFirstAvailableSurface()
266264 if (m_focusedSurface) {
267265 m_focusedSurface->setFocused(false);
268266 }
269− if (chosenSurface) {
270− chosenSurface->setFocused(true);
271− }
267+
268+ chosenSurface->setFocused(true);
269+ doRaise(chosenSurface);
270+
272271 m_focusedSurface = chosenSurface;
273272}
274273
tests/mocks/Unity/CMakeLists.txt+6
@@ -73,3 +73,9 @@ add_unity8_mock(Unity 0.2 Unity TARGETS FakeUnityQml)
7373
7474# and one copy in a separate directory for autopilot
7575add_unity8_mock(Unity 0.2 Unity PREFIX scopefakes TARGETS FakeUnityQml)
76+
77+# Since on build both copy the same file to the same location (not great)
78+# make sure one depends on the other, otherwise when running with make -jX
79+# we can get errors of cp complaining the file has been created while it
80+# was trying to create it
81+add_dependencies(scopefakes-MockUnity-qmlfiles mocks-MockUnity-qmlfiles)
tests/mocks/Unity/Launcher/CMakeLists.txt−2
@@ -1,5 +1,3 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
2−
31include_directories(
42 ${CMAKE_CURRENT_SOURCE_DIR}
53 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/
tests/mocks/Utils/CMakeLists.txt+1
@@ -26,6 +26,7 @@ set(QMLPLUGIN_SRC
2626 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
2727 ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp
2828 ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp
29+ ${CMAKE_SOURCE_DIR}/plugins/Utils/expressionfiltermodel.cpp
2930 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
3031 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
3132 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
tests/mocks/Utils/plugin.cpp+2
@@ -42,6 +42,7 @@
4242#include <globalfunctions.h>
4343#include <appdrawerproxymodel.h>
4444#include <tabfocusfence.h>
45+#include <expressionfiltermodel.h>
4546
4647static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4748{
@@ -86,4 +87,5 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
8687 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
8788 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
8889 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
90+ qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel");
8991}
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt−2
@@ -1,5 +1,3 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
2−
31include_directories(
42 ${CMAKE_CURRENT_SOURCE_DIR}
53 ${CMAKE_CURRENT_BINARY_DIR}
tests/plugins/Unity/Launcher/CMakeLists.txt−1
@@ -1,5 +1,4 @@
11pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
32
43include_directories(
54 ${CMAKE_CURRENT_SOURCE_DIR}
tests/qmltests/ApplicationMenus/tst_MenuBar.qml+85−9
@@ -28,8 +28,8 @@ import ".."
2828
2929Item {
3030 id: root
31− width: units.gu(100)
32− height: units.gu(50)
31+ width: units.gu(120)
32+ height: units.gu(70)
3333
3434 Component.onCompleted: {
3535 QuickUtils.keyboardAttached = true;
@@ -51,12 +51,12 @@ Item {
5151 Rectangle {
5252 anchors {
5353 left: parent.left
54− right: parent.right
5554 top: parent.top
5655 margins: units.gu(1)
5756 }
5857 height: units.gu(3)
59− color: "grey"
58+ width: parent.width * 2/3
59+ color: theme.palette.normal.background
6060
6161 MenuBar {
6262 id: menuBar
@@ -65,7 +65,7 @@ Item {
6565
6666 unityMenuModel: UnityMenuModel {
6767 id: menuBackend
68− modelData: appMenuData.generateTestData(17,5,2,3)
68+ modelData: appMenuData.generateTestData(10,5,2,3)
6969 }
7070 }
7171 }
@@ -83,12 +83,12 @@ Item {
8383
8484 function init() {
8585 menuBar.dismiss();
86− menuBackend.modelData = appMenuData.generateTestData(5,5,2,3)
86+ menuBackend.modelData = appMenuData.generateTestData(5,5,2,3, "menu")
8787 activatedSpy.clear();
8888 }
8989
9090 function test_mouseNavigation() {
91− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
91+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
9292 wait(50) // wait for row to build
9393 var priv = findInvisibleChild(menuBar, "d");
9494
@@ -114,7 +114,7 @@ Item {
114114 }
115115
116116 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
117− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
117+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
118118 var priv = findInvisibleChild(menuBar, "d");
119119
120120 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -139,7 +139,7 @@ Item {
139139 }
140140
141141 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
142− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
142+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
143143 var priv = findInvisibleChild(menuBar, "d");
144144
145145 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -180,6 +180,41 @@ Item {
180180 tryCompare(priv, "currentItem", menuItem);
181181 }
182182
183+ function test_menuActivateClosesMenu() {
184+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
185+ var priv = findInvisibleChild(menuBar, "d");
186+
187+ var menuItem = findChild(menuBar, "menuBar-item0");
188+ menuItem.show();
189+ compare(priv.currentItem, menuItem, "CurrentItem should be set to item 0");
190+ compare(priv.currentItem.popupVisible, true, "Popup should be visible");
191+
192+ var actionItem = findChild(menuBar, "menuBar-item0-menu-item0-actionItem");
193+ mouseClick(actionItem);
194+ compare(priv.currentItem, null, "CurrentItem should be null");
195+ }
196+
197+ function test_subMenuActivateClosesMenu() {
198+ menuBackend.modelData = appMenuData.generateTestData(3,4,1,0,"menu");
199+ var priv = findInvisibleChild(menuBar, "d");
200+
201+ var menuItem = findChild(menuBar, "menuBar-item0");
202+ menuItem.show();
203+ compare(priv.currentItem, menuItem, "CurrentItem should be set to item 0");
204+ compare(priv.currentItem.popupVisible, true, "Popup should be visible");
205+
206+ var actionItem = findChild(menuBar, "menuBar-item0-menu-item0-actionItem");
207+ mouseClick(actionItem);
208+
209+ actionItem = findChild(menuBar, "menuBar-item0-menu-item0-menu-item0-actionItem");
210+ mouseClick(actionItem);
211+
212+ actionItem = findChild(menuBar, "menuBar-item0-menu-item0-menu-item0-menu-item0-actionItem");
213+ mouseClick(actionItem);
214+
215+ compare(priv.currentItem, null, "CurrentItem should be null");
216+ }
217+
183218 function test_openAppMenuShortcut() {
184219 var priv = findInvisibleChild(menuBar, "d");
185220
@@ -192,5 +227,46 @@ Item {
192227 keyClick(Qt.Key_F10, Qt.AltModifier);
193228 compare(priv.currentItem, menuItem1, "First enabled item should be opened");
194229 }
230+
231+ function test_clickOpenMenuClosesMenu() {
232+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
233+ var priv = findInvisibleChild(menuBar, "d");
234+
235+ var menuItem = findChild(menuBar, "menuBar-item0");
236+ waitForRendering(menuItem);
237+ mouseClick(menuItem);
238+ compare(priv.currentItem, menuItem, "CurrentItem should be set to item 0");
239+ compare(priv.currentItem.popupVisible, true, "Popup should be visible");
240+
241+ waitForRendering(menuItem);
242+ mouseClick(menuItem);
243+ compare(priv.currentItem, null, "CurrentItem should be null");
244+ }
245+
246+ function test_overfow() {
247+ menuBackend.modelData = appMenuData.generateTestData(5,2,0,0,"menu");
248+
249+ var overflow = findChild(menuBar, "overflow");
250+ compare(overflow.visible, false, "Overflow should not be visible");
251+
252+ var menu = { "rowData": { "label": "Short" } };
253+ tryCompareFunction(function() {
254+ menuBackend.insertRow(0, menu);
255+ wait(1);
256+ if (overflow.visible) {
257+ return true;
258+ }
259+ return false;
260+ }, true);
261+
262+ tryCompareFunction(function() {
263+ menuBackend.removeRow(0);
264+ wait(1);
265+ if (!overflow.visible) {
266+ return true;
267+ }
268+ return false;
269+ }, true);
270+ }
195271 }
196272}
tests/qmltests/ApplicationMenus/tst_MenuPopup.qml+14
@@ -223,6 +223,20 @@ Item {
223223 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);
224224 }
225225
226+ function test_mouseHoverOpensSubMenu() {
227+ menu.unityMenuModel.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
228+
229+ var menuItem = findChild(menu, "menu-item0");
230+
231+ var priv = findInvisibleChild(menu, "d");
232+ priv.currentItem = menuItem;
233+
234+ mouseMove(menuItem, menuItem.width/2, menuItem.height/2);
235+ verify(!menuItem.popup);
236+
237+ tryCompareFunction(function() { return menuItem.popup != null; }, true);
238+ }
239+
226240 function test_differentSizes() {
227241 var differentSizesMenu = [{
228242 "rowData": { "label": "Short" }}, {
tests/qmltests/Launcher/tst_Launcher.qml+13
@@ -1480,6 +1480,19 @@ Rectangle {
14801480 launcher.available = true;
14811481 }
14821482
1483+ function test_hoverOnEdgeBarrerPreventsHiding() {
1484+ revealByEdgePush();
1485+
1486+ // Make sure the mouse is on the very left edge (on top of the barrier)
1487+ mouseMove(root, 1, root.height / 2);
1488+
1489+ // trigger the hide timer
1490+ compare(fakeDismissTimer.running, true);
1491+ fakeDismissTimer.triggered();
1492+
1493+ compare(launcher.state, "visibleTemporary");
1494+ }
1495+
14831496 function test_hintOnSizeChange() {
14841497 var oldSize = launcher.panelWidth;
14851498 launcher.maxPanelX = -launcher.panelWidth;
tests/qmltests/Stage/tst_DesktopStage.qml+193−10
@@ -29,6 +29,7 @@ import ".." // For EdgeBarrierControls
2929import "../../../qml/Stage"
3030import "../../../qml/Components"
3131import "../../../qml/Components/PanelState"
32+import "../../../qml/ApplicationMenus"
3233
3334Item {
3435 id: root
@@ -44,6 +45,8 @@ Item {
4445 }
4546
4647 Component.onCompleted: {
48+ ApplicationMenusLimits.screenWidth = Qt.binding( function() { return stageLoader.width; } );
49+ ApplicationMenusLimits.screenHeight = Qt.binding( function() { return stageLoader.height; } );
4750 QuickUtils.keyboardAttached = true;
4851 theme.name = "Ubuntu.Components.Themes.SuruDark";
4952 resetGeometry();
@@ -89,7 +92,6 @@ Item {
8992 focus: true
9093
9194 Component.onCompleted: {
92− edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
9395 ApplicationManager.startApplication("unity8-dash");
9496 }
9597 Component.onDestruction: {
@@ -132,11 +134,22 @@ Item {
132134 }
133135 }
134136
135− EdgeBarrierControls {
136− id: edgeBarrierControls
137− text: "Drag here to pull out spread"
138− backgroundColor: "blue"
139− onDragged: { stageLoader.item.pushRightEdge(amount); }
137+ Label {
138+ text: "Right edge push progress"
139+ }
140+
141+ Slider {
142+ id: rightEdgePushSlider
143+ width: parent.width
144+ live: true
145+ minimumValue: 0.0
146+ maximumValue: 1.0
147+ onPressedChanged: {
148+ if (!pressed) {
149+ value = 0;
150+ }
151+ }
152+ Binding { target: stageLoader.item; property: "rightEdgePushProgress"; value: rightEdgePushSlider.value }
140153 }
141154
142155 Divider {}
@@ -695,10 +708,18 @@ Item {
695708 var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);
696709 var deco = findChild(dialerDelegate, "appWindowDecoration");
697710 verify(deco);
698− tryCompare(deco, "maximizeButtonShown", false);
699− mouseDoubleClick(deco);
700− var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
701− tryCompareFunction(function(){ return sizeBefore; }, sizeAfter);
711+ // deco.width - units.gu(1) to make sure we're outside the "menu" area of the decoration
712+ mouseMove(deco, deco.width - units.gu(1), deco.height/2);
713+ var menuBarLoader = findChild(deco, "menuBarLoader");
714+ tryCompare(menuBarLoader.item, "visible", true);
715+ mouseDoubleClick(deco, deco.width - units.gu(1), deco.height/2)
716+ expectFail("", "Double click should not maximize in a size restricted window");
717+ tryCompareFunction(function() {
718+ var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
719+ return sizeAfter.width > sizeBefore.width && sizeAfter.height > sizeBefore.height;
720+ },
721+ true
722+ );
702723
703724 // remove restrictions, the maximize button should again be visible
704725 dialerDelegate.surface.setMaximumWidth(0);
@@ -706,6 +727,29 @@ Item {
706727 tryCompare(dialerMaximizeButton, "visible", true);
707728 }
708729
730+ function test_doubleClickMaximizes() {
731+ var dialerDelegate = startApplication("dialer-app");
732+
733+ var dialerMaximizeButton = findChild(dialerDelegate, "maximizeWindowButton");
734+ tryCompare(dialerMaximizeButton, "visible", true);
735+
736+ // try double clicking the decoration, should maximize it
737+ var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);
738+ var deco = findChild(dialerDelegate, "appWindowDecoration");
739+ verify(deco);
740+ // deco.width - units.gu(1) to make sure we're outside the "menu" area of the decoration
741+ mouseMove(deco, deco.width - units.gu(1), deco.height/2);
742+ var menuBarLoader = findChild(deco, "menuBarLoader");
743+ tryCompare(menuBarLoader.item, "visible", true);
744+ mouseDoubleClick(deco, deco.width - units.gu(1), deco.height/2);
745+ tryCompareFunction(function() {
746+ var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
747+ return sizeAfter.width > sizeBefore.width && sizeAfter.height > sizeBefore.height;
748+ },
749+ true
750+ );
751+ }
752+
709753 function test_canMoveWindowWithLeftMouseButtonOnly_data() {
710754 return [
711755 {tag: "left mouse button", button: Qt.LeftButton },
@@ -830,5 +874,144 @@ Item {
830874 mouseRelease(decoration);
831875 tryCompare(Mir, "cursorName", "");
832876 }
877+
878+ function test_menuPositioning_data() {
879+ return [
880+ {tag: "good",
881+ windowPosition: Qt.point(units.gu(10), units.gu(10))
882+ },
883+ {tag: "collides right",
884+ windowPosition: Qt.point(units.gu(100), units.gu(10)),
885+ minimumXDifference: units.gu(8)
886+ },
887+ {tag: "collides bottom",
888+ windowPosition: Qt.point(units.gu(10), units.gu(80)),
889+ minimumYDifference: units.gu(7)
890+ },
891+ ]
892+ }
893+
894+ function test_menuPositioning(data) {
895+ var appDelegate = startApplication("dialer-app");
896+ appDelegate.windowedX = data.windowPosition.x;
897+ appDelegate.windowedY = data.windowPosition.y;
898+
899+ var menuItem = findChild(appDelegate, "menuBar-item3");
900+ menuItem.show();
901+
902+ var menu = findChild(appDelegate, "menuBar-item3-menu");
903+ tryCompare(menu, "visible", true);
904+
905+ var normalPositioningX = menuItem.x - units.gu(1);
906+ var normalPositioningY = menuItem.height;
907+
908+ // We do this fuzzy checking because otherwise we would be duplicating the code
909+ // that calculates the coordinates and any bug it may have, what we want is really
910+ // to check that on collision with the border the menu is shifted substantially
911+ if (data.minimumXDifference) {
912+ verify(menu.x < normalPositioningX - data.minimumXDifference);
913+ } else {
914+ compare(menu.x, normalPositioningX);
915+ }
916+
917+ if (data.minimumYDifference) {
918+ verify(menu.y < normalPositioningY - data.minimumYDifference);
919+ } else {
920+ compare(menu.y, normalPositioningY);
921+ }
922+ }
923+
924+ function test_submenuPositioning_data() {
925+ return [
926+ {tag: "good",
927+ windowPosition: Qt.point(units.gu(10), units.gu(10))
928+ },
929+ {tag: "collides right",
930+ windowPosition: Qt.point(units.gu(100), units.gu(10)),
931+ minimumXDifference: units.gu(35)
932+ },
933+ {tag: "collides bottom",
934+ windowPosition: Qt.point(units.gu(10), units.gu(80)),
935+ minimumYDifference: units.gu(8)
936+ },
937+ ]
938+ }
939+
940+ function test_submenuPositioning(data) {
941+ var appDelegate = startApplication("dialer-app");
942+ appDelegate.windowedX = data.windowPosition.x;
943+ appDelegate.windowedY = data.windowPosition.y;
944+
945+ var menuItem = findChild(appDelegate, "menuBar-item3");
946+ menuItem.show();
947+
948+ var menu = findChild(appDelegate, "menuBar-item3-menu");
949+ menuItem = findChild(menu, "menuBar-item3-menu-item3-actionItem");
950+ tryCompare(menuItem, "visible", true);
951+ mouseMove(menuItem);
952+ mouseClick(menuItem);
953+
954+ menu = findChild(appDelegate, "menuBar-item3-menu-item3-menu");
955+
956+ var normalPositioningX = menuItem.width;
957+ var normalPositioningY = menuItem.parent.y;
958+
959+ // We do this fuzzy checking because otherwise we would be duplicating the code
960+ // that calculates the coordinates and any bug it may have, what we want is really
961+ // to check that on collision with the border the menu is shifted substantially
962+ if (data.minimumXDifference) {
963+ verify(menu.x < normalPositioningX - data.minimumXDifference);
964+ } else {
965+ compare(menu.x, normalPositioningX);
966+ }
967+
968+ if (data.minimumYDifference) {
969+ verify(menu.y < normalPositioningY - data.minimumYDifference);
970+ } else {
971+ compare(menu.y, normalPositioningY);
972+ }
973+ }
974+
975+ function test_menuDoubleClickNoMaximizeWindowBehind() {
976+ var appDelegate1 = startApplication("dialer-app");
977+ var appDelegate2 = startApplication("gmail-webapp");
978+
979+ // Open menu
980+ var menuItem = findChild(appDelegate2, "menuBar-item3");
981+ menuItem.show();
982+ var menu = findChild(appDelegate2, "menuBar-item3-menu");
983+ menuItem = findChild(menu, "menuBar-item3-menu-item3-actionItem");
984+ tryCompare(menuItem, "visible", true);
985+
986+ // Place the other application window decoration under the menu
987+ var pos = menuItem.mapToItem(null, menuItem.width / 2, menuItem.height / 2);
988+ appDelegate1.windowedX = pos.x - appDelegate1.width / 2;
989+ appDelegate1.windowedY = pos.y - units.gu(1);
990+
991+ var previousWindowState = appDelegate1.windowState;
992+
993+ mouseMove(menuItem);
994+ mouseDoubleClickSequence(menuItem);
995+
996+ expectFail("", "Double clicking a menu should not change the window below");
997+ tryCompareFunction(function() { return appDelegate1.windowState != previousWindowState; }, true);
998+ }
999+
1000+ function test_openMenuEatsHoverOutsideIt() {
1001+ var appDelegate = startApplication("gmail-webapp");
1002+
1003+ var wd = findChild(appDelegate, "appWindowDecoration");
1004+ var closeButton = findChild(wd, "closeWindowButton");
1005+
1006+ // Open menu
1007+ var menuItem = findChild(appDelegate, "menuBar-item3");
1008+ menuItem.show();
1009+ var menu = findChild(appDelegate, "menuBar-item3-menu");
1010+ tryCompare(menu, "visible", true);
1011+
1012+ mouseMove(closeButton, closeButton.width/2, closeButton.height/2);
1013+ expectFail("", "Hovering the window controls should be ignored when the menu is open");
1014+ tryCompare(closeButton, "containsMouse", true);
1015+ }
8331016 }
8341017}
tests/qmltests/Stage/tst_PhoneStage.qml+4−5
@@ -418,11 +418,10 @@ Item {
418418
419419 function test_mouseEdgePush() {
420420 addApps(1);
421− mouseMove(stage, stage.width - 1, units.gu(10));
422− for (var i = 0; i < units.gu(10); i++) {
423− stage.pushRightEdge(1);
424− }
425− mouseMove(stage, stage.width - units.gu(5), units.gu(10));
421+ // When progress goes to 1 it should switch to spread, but stay there even if progress goes back
422+ stage.rightEdgePushProgress = 1;
423+ compare(stage.state, "spread");
424+ stage.rightEdgePushProgress = 0;
426425 compare(stage.state, "spread");
427426 }
428427
tests/qmltests/Tutorial/tst_Tutorial.qml+2−2
@@ -719,9 +719,9 @@ Rectangle {
719719 ApplicationManager.startApplication("facebook-webapp");
720720 tryCompare(tutorialRightLoader, "shown", true);
721721
722− var stage = findChild(shell, "stage");
722+ var cursor = findChild(shell, "cursor");
723723 mouseMove(shell, shell.width, shell.height / 2);
724− stage.pushRightEdge(units.gu(8));
724+ cursor.pushedRightBoundary(units.gu(8), 0);
725725 tryCompare(tutorialRightLoader, "shown", false);
726726
727727 tryCompare(AccountsService, "demoEdges", false);
tests/qmltests/tst_OrientedShell.qml+2−1
@@ -1431,8 +1431,9 @@ Rectangle {
14311431 function swipeAwayGreeter() {
14321432 var greeter = findChild(shell, "greeter");
14331433 tryCompare(greeter, "fullyShown", true);
1434+ waitForRendering(greeter)
14341435
1435− var touchX = shell.width - (shell.edgeSize / 2);
1436+ var touchX = shell.width * .75;
14361437 var touchY = shell.height / 2;
14371438 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
14381439
tests/qmltests/tst_Shell.qml+132−1
@@ -335,6 +335,8 @@ Rectangle {
335335 Row {
336336 CheckBox {
337337 id: fullscreeAppCheck
338+ activeFocusOnPress: false
339+ activeFocusOnTab: false
338340
339341 onTriggered: {
340342 if (!topLevelSurfaceList.focusedWindow) return;
@@ -2585,6 +2587,55 @@ Rectangle {
25852587 tryCompareFunction(function() { return ApplicationManager.focusedApplicationId; }, "calendar-app");
25862588 }
25872589
2590+ function test_rightEdgePushWithOpenIndicators() {
2591+ loadShell("desktop");
2592+ shell.usageScenario = "desktop";
2593+ waitForRendering(shell);
2594+ swipeAwayGreeter();
2595+
2596+ var stage = findChild(shell, "stage");
2597+ var cursor = findChild(shell, "cursor");
2598+ var indicators = findChild(shell, "indicators");
2599+
2600+ // Open indicators
2601+ var touchX = shell.width - units.gu(5);
2602+ touchFlick(shell,
2603+ touchX /* fromX */, indicators.minimizedPanelHeight * 0.5 /* fromY */,
2604+ touchX /* toX */, shell.height * 0.9 /* toY */,
2605+ true /* beginTouch */, true /* endTouch */);
2606+ tryCompare(indicators, "fullyOpened", true);
2607+
2608+ // push the right edge
2609+ mouseMove(shell, shell.width - 1, units.gu(10));
2610+ for (var i = 0; i < units.gu(10); i++) {
2611+ cursor.pushedRightBoundary(1, 0);
2612+ }
2613+ tryCompare(stage, "rightEdgePushProgress", 1);
2614+ tryCompare(stage, "state", "spread");
2615+ tryCompare(indicators, "fullyOpened", false);
2616+
2617+ mouseMove(shell, shell.width - units.gu(5), units.gu(10));
2618+
2619+ tryCompare(stage, "rightEdgePushProgress", 0);
2620+ }
2621+
2622+ function test_rightEdgePushOnGreeter() {
2623+ loadShell("desktop");
2624+ shell.usageScenario = "desktop";
2625+ waitForRendering(shell);
2626+
2627+ var stage = findChild(shell, "stage");
2628+ var cursor = findChild(shell, "cursor");
2629+
2630+ // push the right edge, but verify it doesn't emit any progress
2631+ mouseMove(shell, shell.width - 1, units.gu(10));
2632+ for (var i = 0; i < units.gu(10); i++) {
2633+ cursor.pushedRightBoundary(1, 0);
2634+ compare(stage.rightEdgePushProgress, 0);
2635+ }
2636+ compare(stage.rightEdgePushProgress, 0);
2637+ }
2638+
25882639 function test_oskDisplacesWindow_data() {
25892640 return [
25902641 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
@@ -2707,9 +2758,10 @@ Rectangle {
27072758 tryCompare(stage, "state", "staged");
27082759
27092760 // Try by edge push
2761+ var cursor = findChild(shell, "cursor");
27102762 mouseMove(stage, stage.width - 1, units.gu(10));
27112763 for (var i = 0; i < units.gu(10); i++) {
2712− stage.pushRightEdge(1);
2764+ cursor.pushedRightBoundary(1, 0);
27132765 }
27142766 mouseMove(stage, stage.width - units.gu(5), units.gu(10));
27152767 tryCompare(stage, "state", data.spreadEnabled ? "spread" : "staged");
@@ -2732,6 +2784,28 @@ Rectangle {
27322784 keyRelease(Qt.Key_W, Qt.MetaModifier)
27332785 }
27342786
2787+ function test_launcherWindowResizeInteraction()
2788+ {
2789+ loadShell("desktop");
2790+ waitForRendering(shell)
2791+ shell.usageScenario = "desktop"
2792+ waitForRendering(shell)
2793+ swipeAwayGreeter();
2794+
2795+ var app1SurfaceId = topLevelSurfaceList.nextId;
2796+ var app1 = ApplicationManager.startApplication("dialer-app")
2797+ waitUntilAppWindowIsFullyLoaded(app1SurfaceId);
2798+
2799+ var launcherDelegate1 = findChild(shell, "launcherDelegate1");
2800+ mouseClick(launcherDelegate1, launcherDelegate1.width / 2, launcherDelegate1.height / 2, Qt.RightButton);
2801+
2802+ var appDelegate = findChild(shell, "appDelegate_" + app1SurfaceId);
2803+ mouseMove(shell, appDelegate.mapToItem(shell, 0, 0).x, launcherDelegate1.mapToItem(shell, 0, 0).y);
2804+
2805+ expectFail("", "Cursor should not change while launcher menu is open");
2806+ tryCompare(Mir, "cursorName", "left_side");
2807+ }
2808+
27352809 function test_panelTitleShowsWhenGreeterNotShown_data() {
27362810 return [
27372811 {tag: "phone" },
@@ -2779,5 +2853,62 @@ Rectangle {
27792853 }
27802854 tryCompareFunction(function() { return drawer.visible; }, false);
27812855 }
2856+
2857+ function test_restoreFromFullscreen() {
2858+ loadShell("desktop");
2859+ shell.usageScenario = "desktop";
2860+ waitForRendering(shell);
2861+ swipeAwayGreeter();
2862+
2863+ var appSurfaceId = topLevelSurfaceList.nextId;
2864+ var app = ApplicationManager.startApplication("dialer-app")
2865+ waitUntilAppWindowIsFullyLoaded(appSurfaceId);
2866+
2867+ // start dialer
2868+ var appContainer = findChild(shell, "appContainer");
2869+ var appDelegate = findChild(appContainer, "appDelegate_" + appSurfaceId);
2870+ verify(appDelegate);
2871+ tryCompare(appDelegate, "state", "normal");
2872+
2873+ // now maximize to right
2874+ appDelegate.requestMaximizeRight();
2875+ tryCompare(appDelegate, "state", "maximizedRight");
2876+
2877+ // switch to fullscreen
2878+ app.surfaceList.get(0).requestState(Mir.FullscreenState);
2879+ tryCompare(appDelegate, "state", "fullscreen");
2880+
2881+ // restore, should go back to maximizedRight, not restored
2882+ appDelegate.requestRestore();
2883+ tryCompare(appDelegate, "state", "maximizedRight");
2884+ }
2885+
2886+ function test_altTabToMinimizedApp() {
2887+ loadShell("desktop");
2888+ shell.usageScenario = "desktop";
2889+ waitForRendering(shell);
2890+ swipeAwayGreeter();
2891+
2892+ var appSurfaceId = topLevelSurfaceList.nextId;
2893+ var app = ApplicationManager.startApplication("dialer-app")
2894+ waitUntilAppWindowIsFullyLoaded(appSurfaceId);
2895+
2896+ // start dialer
2897+ var appContainer = findChild(shell, "appContainer");
2898+ var appDelegate = findChild(appContainer, "appDelegate_" + appSurfaceId);
2899+ verify(appDelegate);
2900+ tryCompare(appDelegate, "state", "normal");
2901+
2902+ // minimize dialer
2903+ appDelegate.requestMinimize();
2904+ tryCompare(appDelegate, "state", "minimized");
2905+
2906+ // try to bring dialer back from minimized by doing alt-tab
2907+ keyClick(Qt.Key_Tab, Qt.AltModifier);
2908+ tryCompare(appDelegate, "visible", true);
2909+ tryCompare(appDelegate, "focus", true);
2910+ tryCompare(topLevelSurfaceList.focusedWindow, "surface", appDelegate.surface);
2911+ tryCompare(topLevelSurfaceList.applicationAt(0), "appId", "dialer-app");
2912+ }
27822913 }
27832914}
tests/utils/modules/Unity/Test/UnityTestCase.qml−9
@@ -617,15 +617,6 @@ TestCase {
617617 }
618618 }
619619
620− // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
621− function waitForRendering(item, timeout) {
622− if (timeout === undefined)
623− timeout = 5000;
624− if (!item)
625− qtest_fail("No item given to waitForRendering", 1);
626− return qtest_results.waitForRendering(item, timeout);
627− }
628−
629620 /*
630621 Wait until any transition animation has finished for the given StateGroup or Item
631622 */