on xenial help sign in
merge trunk
f694c4fLukáš Tinkl
CMakeLists.txt+1
@@ -74,6 +74,7 @@ pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=26)
74pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
74pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
75pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
75pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
76pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
76pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
77+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
77pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
78pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
78pkg_check_modules(GD3 REQUIRED gnome-desktop-3.0)
79pkg_check_modules(GD3 REQUIRED gnome-desktop-3.0)
79pkg_check_modules(UAL REQUIRED ubuntu-app-launch-2)
80pkg_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+
1unity8 (8.15+17.04.20170206-0ubuntu1) zesty; urgency=medium
49unity8 (8.15+17.04.20170206-0ubuntu1) zesty; urgency=medium
2
50
3 [ Daniel d'Andrada ]
51 [ 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)
2pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3
2
4add_definitions(-DSM_BUSNAME=systemBus)
3add_definitions(-DSM_BUSNAME=systemBus)
plugins/Ubuntu/Gestures/TouchGestureArea.cpp−5
@@ -735,7 +735,6 @@ void TouchGestureArea::resyncCachedTouchPoints()
735 bool added = false;
735 bool added = false;
736 bool ended = false;
736 bool ended = false;
737 bool moved = false;
737 bool moved = false;
738− bool wantsDrag = false;
739
738
740 // list of deletes
739 // list of deletes
741 QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
740 QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
@@ -765,10 +764,6 @@ void TouchGestureArea::resyncCachedTouchPoints()
765 }
764 }
766 }
765 }
767
766
768− if (wantsDrag && !dragging()) {
769− setDragging(true);
770− }
771−
772 if (ended) {
767 if (ended) {
773 if (m_cachedTouchPoints.isEmpty()) {
768 if (m_cachedTouchPoints.isEmpty()) {
774 if (!dragging()) Q_EMIT clicked();
769 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)
2pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3
2
4add_definitions(-DSM_BUSNAME=systemBus)
3add_definitions(-DSM_BUSNAME=systemBus)
plugins/Utils/CMakeLists.txt+1
@@ -35,6 +35,7 @@ set(QMLPLUGIN_SRC
35 globalfunctions.cpp
35 globalfunctions.cpp
36 URLDispatcher.cpp
36 URLDispatcher.cpp
37 tabfocusfence.cpp
37 tabfocusfence.cpp
38+ expressionfiltermodel.cpp
38 plugin.cpp
39 plugin.cpp
39 )
40 )
40
41
plugins/Utils/appdrawerproxymodel.cpp+2−2
@@ -43,8 +43,8 @@ void AppDrawerProxyModel::setSource(QAbstractItemModel *source)
43 m_source = source;
43 m_source = source;
44 setSourceModel(m_source);
44 setSourceModel(m_source);
45 setSortRole(m_sortBy == SortByAToZ ? AppDrawerModelInterface::RoleName : AppDrawerModelInterface::RoleUsage);
45 setSortRole(m_sortBy == SortByAToZ ? AppDrawerModelInterface::RoleName : AppDrawerModelInterface::RoleUsage);
46− connect(m_source, &QAbstractItemModel::rowsRemoved, this, &AppDrawerProxyModel::invalidateFilter);
46+ connect(m_source, &QAbstractItemModel::rowsRemoved, this, &AppDrawerProxyModel::invalidate);
47− connect(m_source, &QAbstractItemModel::rowsInserted, this, &AppDrawerProxyModel::invalidateFilter);
47+ connect(m_source, &QAbstractItemModel::rowsInserted, this, &AppDrawerProxyModel::invalidate);
48 Q_EMIT sourceChanged();
48 Q_EMIT sourceChanged();
49 }
49 }
50}
50}
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 @@
41#include "URLDispatcher.h"
41#include "URLDispatcher.h"
42#include "appdrawerproxymodel.h"
42#include "appdrawerproxymodel.h"
43#include "tabfocusfence.h"
43#include "tabfocusfence.h"
44+#include "expressionfiltermodel.h"
44
45
45static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
46static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
46{
47{
@@ -86,4 +87,5 @@ void UtilsPlugin::registerTypes(const char *uri)
86 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
87 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
87 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
88 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
88 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
89 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
90+ qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel");
89}
91}
po/unity8.pot+3−3
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"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"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -521,7 +521,7 @@ msgstr ""
521msgid "Search…"
521msgid "Search…"
522msgstr ""
522msgstr ""
523
523
524−#: qml/Launcher/Drawer.qml:123
524+#: qml/Launcher/Drawer.qml:127
525msgctxt "Apps sorted alphabetically"
525msgctxt "Apps sorted alphabetically"
526msgid "A-Z"
526msgid "A-Z"
527msgstr ""
527msgstr ""
@@ -622,7 +622,7 @@ msgid "Drag using 3 fingers any application from one window to the other"
622msgstr ""
622msgstr ""
623
623
624#: qml/Tutorial/TutorialLeftLong.qml:49
624#: 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"
626msgstr ""
626msgstr ""
627
627
628#: qml/Tutorial/TutorialLeft.qml:47
628#: 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 {
24 id: root
24 id: root
25 objectName: "menuBar"
25 objectName: "menuBar"
26
26
27+ // set from outside
27 property alias unityMenuModel: rowRepeater.model
28 property alias unityMenuModel: rowRepeater.model
28−
29− readonly property bool valid: rowRepeater.count > 0
30−
31 property bool enableKeyFilter: false
29 property bool enableKeyFilter: false
30+ property real overflowWidth: width
32
31
32+ // read from outside
33+ readonly property bool valid: rowRepeater.count > 0
33 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
34 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
34
35
35 implicitWidth: row.width
36 implicitWidth: row.width
@@ -75,189 +76,334 @@ Item {
75 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
76 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
76 anchors.fill: parent
77 anchors.fill: parent
77 enabled: d.currentItem != null
78 enabled: d.currentItem != null
79+ hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
78 onPressed: d.dismissAll()
80 onPressed: d.dismissAll()
79 }
81 }
80
82
81− Item {
82− id: clippingItem
83−
84− height: root.height
85− width: root.width
86− clip: true
87−
88− Row {
83+ Row {
89− id: row
84+ id: row
90− spacing: units.gu(2)
85+ spacing: units.gu(2)
91− height: parent.height
86+ height: parent.height
92
87
93− ActionContext {
88+ ActionContext {
94− id: menuBarContext
89+ id: menuBarContext
95− objectName: "barContext"
90+ objectName: "barContext"
96− active: !d.currentItem && enableKeyFilter
91+ active: !d.currentItem && enableKeyFilter
97− }
92+ }
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
109
93
110− implicitWidth: column.implicitWidth
94+ Connections {
111− implicitHeight: row.height
95+ target: root.unityMenuModel
112− enabled: model.sensitive
96+ onModelReset: d.firstInvisibleIndex = undefined
97+ }
113
98
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+
114− function show() {
120+ function show() {
115− if (!__popup) {
121+ if (!__popup) {
116− __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
122+ __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
123+ __popup.childActivated.connect(dismiss);
117− // force the current item to be the newly popped up menu
124+ // force the current item to be the newly popped up menu
118− } else {
125+ } else {
119− __popup.show();
126+ __popup.show();
120− }
121− d.currentItem = visualItem;
122 }
127 }
128+ d.currentItem = visualItem;
129+ }
123− function hide() {
130+ function hide() {
124− if (__popup) {
131+ if (__popup) {
125− __popup.hide();
132+ __popup.hide();
126
133
127− if (d.currentItem === visualItem) {
134+ if (d.currentItem === visualItem) {
128− d.currentItem = null;
135+ d.currentItem = null;
129− }
130 }
136 }
131 }
137 }
138+ }
132− function dismiss() {
139+ function dismiss() {
133− if (__popup) {
140+ if (__popup) {
134− __popup.destroy();
141+ __popup.destroy();
135− __popup = null;
142+ __popup = null;
136
143
137− if (d.currentItem === visualItem) {
144+ if (d.currentItem === visualItem) {
138− d.currentItem = null;
145+ d.currentItem = null;
139− }
140 }
146 }
141 }
147 }
148+ }
142
149
143− Connections {
150+ onVisibleChanged: {
144− target: d
151+ if (!visible && __popup) dismiss();
145− onDismissAll: visualItem.dismiss()
152+ }
153+
154+ onShouldDisplayChanged: {
155+ if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) {
156+ d.recalcFirstInvisibleIndex();
146 }
157 }
158+ }
147
159
148− Component {
160+ Connections {
149− id: menuComponent
161+ target: d
150− MenuPopup {
151− x: visualItem.x - units.gu(1)
162+ onDismissAll: visualItem.dismiss()
152− anchors.top: parent.bottom
163+ }
153− unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
154
164
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+
155− Component.onCompleted: reset();
172+ Component.onCompleted: reset();
156− }
157 }
173 }
174+ }
158
175
159− RowLayout {
176+ RowLayout {
160− id: column
177+ id: column
161− spacing: units.gu(1)
178+ spacing: units.gu(1)
162− anchors {
179+ anchors {
163− centerIn: parent
180+ centerIn: parent
164− }
181+ }
165
182
166− Icon {
183+ Icon {
167− Layout.preferredWidth: units.gu(2)
184+ Layout.preferredWidth: units.gu(2)
168− Layout.preferredHeight: units.gu(2)
185+ Layout.preferredHeight: units.gu(2)
169− Layout.alignment: Qt.AlignVCenter
186+ Layout.alignment: Qt.AlignVCenter
170
187
171− visible: model.icon || false
188+ visible: model.icon || false
172− source: model.icon || ""
189+ source: model.icon || ""
173− }
190+ }
174
191
175− ActionItem {
192+ ActionItem {
176− id: actionItem
193+ id: actionItem
177− width: _title.width
194+ width: _title.width
178− height: _title.height
195+ height: _title.height
179
196
180− action: Action {
197+ action: Action {
198+ enabled: visualItem.enabled
181− // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
199+ // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
182− text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
200+ text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
183
201
184− onTriggered: {
202+ onTriggered: {
185− visualItem.show();
203+ visualItem.show();
186− }
187 }
204 }
205+ }
188
206
189− Label {
207+ Label {
190− id: _title
208+ id: _title
191− text: actionItem.text
209+ text: actionItem.text
192− horizontalAlignment: Text.AlignLeft
210+ horizontalAlignment: Text.AlignLeft
193− color: enabled ? "white" : "#5d5d5d"
211+ color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
194− }
195 }
212 }
196 }
213 }
214+ }
197− } // Item ( delegate )
215+ } // Item ( delegate )
198− } // Repeater
216+ } // Repeater
199− } // Row
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
200
263
201− MouseArea {
264+ property Item __popup: null;
202− anchors.fill: parent
265+ readonly property bool popupVisible: __popup && __popup.visible
203− hoverEnabled: d.currentItem
266+ readonly property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null
204
267
205− onEntered: {
268+ visible: d.firstInvisibleIndex != undefined
206− if (d.currentItem) {
269+ x: firstInvisibleItem ? firstInvisibleItem.x : 0
207− updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
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;
208 }
303 }
209 }
304 }
210− onPositionChanged: {
305+ }
306+ function dismiss() {
307+ if (__popup) {
308+ __popup.destroy();
309+ __popup = null;
310+
211− if (d.currentItem) {
311+ if (d.currentItem === overflowButton) {
212− updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
312+ d.currentItem = null;
213 }
313 }
214 }
314 }
215− onClicked: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
315+ }
216
316
217− function updateCurrentItemFromPosition(point) {
317+ Connections {
218− var pos = mapToItem(row, point.x, point.y);
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+ }
219
337
220− if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
338+ function submenu(index) {
221− d.hoveredItem = row.childAt(pos.x, pos.y);
339+ return sourceModel.submenu(mapRowToSource(index));
222− if (!d.hoveredItem || !d.hoveredItem.enabled)
223− return false;
224− if (d.currentItem != d.hoveredItem) {
225− d.currentItem = d.hoveredItem;
226 }
340 }
341+ function activate(index) {
342+ return sourceModel.activate(mapRowToSource(index));
343+ }
344+ }
345+
346+ Connections {
347+ target: d
348+ onFirstInvisibleIndexChanged: overflowModel.invalidate()
227 }
349 }
228− return true;
229 }
350 }
230 }
351 }
352+ }
231
353
232− Rectangle {
354+ Rectangle {
233− id: underline
355+ id: underline
234− anchors {
356+ anchors {
235− bottom: row.bottom
357+ 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
242 }
358 }
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
243 }
364 }
244
365
245 MenuNavigator {
366 MenuNavigator {
246 id: d
367 id: d
247 objectName: "d"
368 objectName: "d"
248 itemView: rowRepeater
369 itemView: rowRepeater
370+ hasOverflow: overflowButton.visible
249
371
250 property Item currentItem: null
372 property Item currentItem: null
251 property Item hoveredItem: null
373 property Item hoveredItem: null
252 property Item prevCurrentItem: null
374 property Item prevCurrentItem: null
253−
254− readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
255−
256 property bool altPressed: false
375 property bool altPressed: false
257 property bool longAltPressed: false
376 property bool longAltPressed: false
377+ property var firstInvisibleIndex: undefined
378+
379+ readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1
258
380
259 signal dismissAll()
381 signal dismissAll()
260
382
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+
261 onSelect: {
407 onSelect: {
262 var delegate = rowRepeater.itemAt(index);
408 var delegate = rowRepeater.itemAt(index);
263 if (delegate) {
409 if (delegate) {
@@ -265,6 +411,10 @@ Item {
265 }
411 }
266 }
412 }
267
413
414+ onOverflow: {
415+ d.currentItem = overflowButton;
416+ }
417+
268 onCurrentItemChanged: {
418 onCurrentItemChanged: {
269 if (prevCurrentItem && prevCurrentItem != currentItem) {
419 if (prevCurrentItem && prevCurrentItem != currentItem) {
270 if (currentItem) {
420 if (currentItem) {
qml/ApplicationMenus/MenuItem.qml+8−12
@@ -46,6 +46,8 @@ ActionItem {
46 enabled: menuData ? menuData.sensitive : false
46 enabled: menuData ? menuData.sensitive : false
47
47
48 action: Action {
48 action: Action {
49+ enabled: root.enabled
50+
49 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
51 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
50 text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
52 text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
51 checkable: menuData.isCheck || menuData.isRadio
53 checkable: menuData.isCheck || menuData.isRadio
@@ -111,6 +113,7 @@ ActionItem {
111 elide: Text.ElideNone
113 elide: Text.ElideNone
112 wrapMode: Text.NoWrap
114 wrapMode: Text.NoWrap
113 clip: true
115 clip: true
116+ color: enabled ? theme.palette.normal.overlayText : theme.palette.disabled.overlayText
114 Layout.fillWidth: true
117 Layout.fillWidth: true
115
118
116 text: root.text ? root.text : ""
119 text: root.text ? root.text : ""
@@ -121,8 +124,8 @@ ActionItem {
121 elide: Text.ElideNone
124 elide: Text.ElideNone
122 wrapMode: Text.NoWrap
125 wrapMode: Text.NoWrap
123 clip: true
126 clip: true
124− color: enabled ? theme.palette.normal.backgroundSecondaryText :
127+ color: enabled ? theme.palette.normal.overlaySecondaryText :
125− theme.palette.disabled.backgroundSecondaryText
128+ theme.palette.disabled.overlaySecondaryText
126
129
127 visible: menuData.shortcut != undefined && !root.hasSubmenu && QuickUtils.keyboardAttached
130 visible: menuData.shortcut != undefined && !root.hasSubmenu && QuickUtils.keyboardAttached
128 text: menuData.shortcut ? menuData.shortcut : ""
131 text: menuData.shortcut ? menuData.shortcut : ""
@@ -133,18 +136,11 @@ ActionItem {
133 id: chevronIcon
136 id: chevronIcon
134 width: units.gu(2)
137 width: units.gu(2)
135 height: units.gu(2)
138 height: units.gu(2)
136− color: enabled ? theme.palette.normal.backgroundSecondaryText :
139+ color: enabled ? theme.palette.normal.overlayText :
137− theme.palette.disabled.backgroundSecondaryText
140+ theme.palette.disabled.overlayText
138
141
139 visible: root.hasSubmenu
142 visible: root.hasSubmenu
140− name: "chevron"
143+ name: "toolkit_chevron-ltr_2gu"
141− }
142− }
143−
144− MouseArea {
145− anchors.fill: parent
146− onClicked: {
147− root.trigger(action && action.checkable ? !action.checked : undefined);
148 }
144 }
149 }
145 }
150}
146}
qml/ApplicationMenus/MenuNavigator.qml+23
@@ -18,8 +18,10 @@ import QtQuick 2.4
18
18
19QtObject {
19QtObject {
20 property Item itemView: null
20 property Item itemView: null
21+ property bool hasOverflow: false
21
22
22 signal select(int index)
23 signal select(int index)
24+ signal overflow()
23
25
24 function selectNext(currentIndex) {
26 function selectNext(currentIndex) {
25 var menu;
27 var menu;
@@ -32,6 +34,11 @@ QtObject {
32 break;
34 break;
33 }
35 }
34 newIndex++;
36 newIndex++;
37+
38+ if (hasOverflow && newIndex === itemView.count) {
39+ overflow()
40+ break;
41+ }
35 }
42 }
36 } else if (currentIndex !== -1 && itemView.count > 1) {
43 } else if (currentIndex !== -1 && itemView.count > 1) {
37 var startIndex = (currentIndex + 1) % itemView.count;
44 var startIndex = (currentIndex + 1) % itemView.count;
@@ -42,6 +49,12 @@ QtObject {
42 select(newIndex);
49 select(newIndex);
43 break;
50 break;
44 }
51 }
52+
53+ if (hasOverflow && newIndex + 1 === itemView.count) {
54+ overflow()
55+ break;
56+ }
57+
45 newIndex = (newIndex + 1) % itemView.count;
58 newIndex = (newIndex + 1) % itemView.count;
46 } while (newIndex !== startIndex)
59 } while (newIndex !== startIndex)
47 }
60 }
@@ -58,12 +71,21 @@ QtObject {
58 break;
71 break;
59 }
72 }
60 newIndex--;
73 newIndex--;
74+
75+ if (hasOverflow && newIndex < 0 ) {
76+ overflow();
77+ break;
78+ }
61 }
79 }
62 } else if (currentIndex !== -1 && itemView.count > 1) {
80 } else if (currentIndex !== -1 && itemView.count > 1) {
63 var startIndex = currentIndex - 1;
81 var startIndex = currentIndex - 1;
64 newIndex = startIndex;
82 newIndex = startIndex;
65 do {
83 do {
66 if (newIndex < 0) {
84 if (newIndex < 0) {
85+ if (hasOverflow) {
86+ overflow();
87+ break;
88+ }
67 newIndex = itemView.count - 1;
89 newIndex = itemView.count - 1;
68 }
90 }
69 menu = itemView.itemAt(newIndex);
91 menu = itemView.itemAt(newIndex);
@@ -72,6 +94,7 @@ QtObject {
72 break;
94 break;
73 }
95 }
74 newIndex--;
96 newIndex--;
97+
75 } while (newIndex !== startIndex)
98 } while (newIndex !== startIndex)
76 }
99 }
77 }
100 }
qml/ApplicationMenus/MenuPopup.qml+116−25
@@ -16,16 +16,51 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19−import QtQuick.Window 2.2
20import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
21import Ubuntu.Components.ListItems 1.3 as ListItems
20import Ubuntu.Components.ListItems 1.3 as ListItems
22import "../Components"
21import "../Components"
22+import "../Components/PanelState"
23+import "."
23
24
24UbuntuShape {
25UbuntuShape {
25 id: root
26 id: root
26 objectName: "menu"
27 objectName: "menu"
27 backgroundColor: theme.palette.normal.overlay
28 backgroundColor: theme.palette.normal.overlay
28
29
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+
29 property alias unityMenuModel: repeater.model
64 property alias unityMenuModel: repeater.model
30
65
31 function show() {
66 function show() {
@@ -64,9 +99,9 @@ UbuntuShape {
64 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
99 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
65
100
66 property real __minimumWidth: units.gu(20)
101 property real __minimumWidth: units.gu(20)
67− property real __maximumWidth: Screen.width * 0.7
102+ property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
68 property real __minimumHeight: units.gu(2)
103 property real __minimumHeight: units.gu(2)
69− property real __maximumHeight: Screen.height * 0.7
104+ property real __maximumHeight: ApplicationMenusLimits.screenHeight - PanelState.panelHeight
70
105
71 signal dismissAll()
106 signal dismissAll()
72
107
@@ -76,6 +111,8 @@ UbuntuShape {
76 } else {
111 } else {
77 hoveredItem = null;
112 hoveredItem = null;
78 }
113 }
114+
115+ submenuHoverTimer.stop();
79 }
116 }
80
117
81 onSelect: {
118 onSelect: {
@@ -147,8 +184,19 @@ UbuntuShape {
147 }
184 }
148
185
149 MouseArea {
186 MouseArea {
187+ id: previousMA
150 anchors.fill: parent
188 anchors.fill: parent
189+ hoverEnabled: enabled
151− onPressed: {
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() {
152 var item = menuColumn.childAt(0, listView.contentY);
200 var item = menuColumn.childAt(0, listView.contentY);
153 if (item) {
201 if (item) {
154 var previousItem = item;
202 var previousItem = item;
@@ -176,10 +224,15 @@ UbuntuShape {
176 contentHeight: menuColumn.height
224 contentHeight: menuColumn.height
177 interactive: height < contentHeight
225 interactive: height < contentHeight
178
226
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+
179 MouseArea {
233 MouseArea {
180 anchors.fill: parent
234 anchors.fill: parent
181 hoverEnabled: true
235 hoverEnabled: true
182− propagateComposedEvents: true // propogate events so we send clicks to children.
183 z: 1 // on top so we override any other hovers
236 z: 1 // on top so we override any other hovers
184 onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
237 onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
185 onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
238 onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
@@ -189,12 +242,25 @@ UbuntuShape {
189
242
190 if (!d.hoveredItem || !d.currentItem ||
243 if (!d.hoveredItem || !d.currentItem ||
191 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
244 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
245+ submenuHoverTimer.stop();
246+
192 d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
247 d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
193 if (!d.hoveredItem || !d.hoveredItem.enabled)
248 if (!d.hoveredItem || !d.hoveredItem.enabled)
194− return false;
249+ return;
195 d.currentItem = d.hoveredItem;
250 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();
196 }
263 }
197− return true;
198 }
264 }
199 }
265 }
200
266
@@ -249,6 +315,8 @@ UbuntuShape {
249 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
315 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
250
316
251 action.onTriggered: {
317 action.onTriggered: {
318+ submenuHoverTimer.stop();
319+
252 d.currentItem = loader;
320 d.currentItem = loader;
253
321
254 if (hasSubmenu) {
322 if (hasSubmenu) {
@@ -257,22 +325,29 @@ UbuntuShape {
257 popup = submenuComponent.createObject(focusScope, {
325 popup = submenuComponent.createObject(focusScope, {
258 objectName: loader.objectName + "-",
326 objectName: loader.objectName + "-",
259 unityMenuModel: model,
327 unityMenuModel: model,
328+ substractWidth: true,
260− x: Qt.binding(function() { return root.width }),
329+ desiredX: Qt.binding(function() { return root.width }),
261− y: Qt.binding(function() {
330+ desiredY: Qt.binding(function() {
262 var dummy = listView.contentY; // force a recalc on contentY change.
331 var dummy = listView.contentY; // force a recalc on contentY change.
263 return mapToItem(container, 0, y).y;
332 return mapToItem(container, 0, y).y;
264 })
333 })
265 });
334 });
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+ });
266 } else if (popup) {
345 } else if (popup) {
267 popup.visible = true;
346 popup.visible = true;
268 }
347 }
269− popup.retreat.connect(function() {
270− popup.destroy();
271− popup = null;
272− menuItem.forceActiveFocus();
273− })
274 } else {
348 } else {
275 root.unityMenuModel.activate(__ownIndex);
349 root.unityMenuModel.activate(__ownIndex);
350+ root.childActivated();
276 }
351 }
277 }
352 }
278
353
@@ -349,8 +424,19 @@ UbuntuShape {
349 }
424 }
350
425
351 MouseArea {
426 MouseArea {
427+ id: nextMA
352 anchors.fill: parent
428 anchors.fill: parent
429+ hoverEnabled: enabled
353− onPressed: {
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() {
354 var item = menuColumn.childAt(0, listView.contentY + listView.height);
440 var item = menuColumn.childAt(0, listView.contentY + listView.height);
355 if (item) {
441 if (item) {
356 var nextItem = item;
442 var nextItem = item;
@@ -375,23 +461,28 @@ UbuntuShape {
375 id: submenuLoader
461 id: submenuLoader
376 source: "MenuPopup.qml"
462 source: "MenuPopup.qml"
377
463
464+ property real desiredX
465+ property real desiredY
466+ property bool substractWidth
378 property var unityMenuModel: null
467 property var unityMenuModel: null
379 signal retreat()
468 signal retreat()
380−
469+ signal childActivated()
381− Binding {
470+
382− target: item
471+ onLoaded: {
383− property: "unityMenuModel"
472+ item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
384− value: 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; });
385 }
477 }
386
478
387− Binding {
479+ Keys.onLeftPressed: retreat()
480+
481+ Connections {
388 target: item
482 target: item
389− property: "objectName"
483+ onChildActivated: childActivated();
390− value: submenuLoader.objectName + "menu"
391 }
484 }
392
485
393− Keys.onLeftPressed: retreat()
394−
395 Component.onCompleted: item.select(0);
486 Component.onCompleted: item.select(0);
396 onVisibleChanged: if (visible) { item.select(0); }
487 onVisibleChanged: if (visible) { item.select(0); }
397 }
488 }
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 {
30 property int edge: Qt.LeftEdge
30 property int edge: Qt.LeftEdge
31
31
32 readonly property alias progress: controller.progress
32 readonly property alias progress: controller.progress
33+ readonly property alias containsMouse: controller.containsMouse
33
34
34 property Item target: parent
35 property Item target: parent
35 function push(amount) { controller.push(amount); }
36 function push(amount) { controller.push(amount); }
qml/Components/EdgeBarrierController.qml+1
@@ -34,6 +34,7 @@ MouseArea {
34
34
35 function push(amount) {
35 function push(amount) {
36 if (!root._containsMouse) {
36 if (!root._containsMouse) {
37+ console.warn("pushing right edge without mouse actually being at right edge!")
37 return;
38 return;
38 }
39 }
39
40
qml/Launcher/Drawer.qml+8−1
@@ -95,7 +95,11 @@ FocusScope {
95 KeyNavigation.down: sections
95 KeyNavigation.down: sections
96
96
97 onAccepted: {
97 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+ }
99 root.applicationSelected(listLoader.item.getFirstAppId());
103 root.applicationSelected(listLoader.item.getFirstAppId());
100 }
104 }
101 }
105 }
@@ -238,6 +242,7 @@ FocusScope {
238 source: sortProxyModel
242 source: sortProxyModel
239 group: AppDrawerProxyModel.GroupByAll
243 group: AppDrawerProxyModel.GroupByAll
240 sortBy: AppDrawerProxyModel.SortByUsage
244 sortBy: AppDrawerProxyModel.SortByUsage
245+ dynamicSortFilter: false
241 }
246 }
242
247
243 delegate: UbuntuShape {
248 delegate: UbuntuShape {
@@ -287,6 +292,7 @@ FocusScope {
287 source: sortProxyModel
292 source: sortProxyModel
288 sortBy: AppDrawerProxyModel.SortByAToZ
293 sortBy: AppDrawerProxyModel.SortByAToZ
289 group: AppDrawerProxyModel.GroupByAToZ
294 group: AppDrawerProxyModel.GroupByAToZ
295+ dynamicSortFilter: false
290 }
296 }
291
297
292 delegate: UbuntuShape {
298 delegate: UbuntuShape {
@@ -320,6 +326,7 @@ FocusScope {
320 id: categoryModel
326 id: categoryModel
321 source: sortProxyModel
327 source: sortProxyModel
322 filterLetter: model.letter
328 filterLetter: model.letter
329+ dynamicSortFilter: false
323 }
330 }
324 delegateWidth: units.gu(8)
331 delegateWidth: units.gu(8)
325 delegateHeight: units.gu(10)
332 delegateHeight: units.gu(10)
qml/Launcher/Launcher.qml+3−2
@@ -307,7 +307,8 @@ FocusScope {
307 InverseMouseArea {
307 InverseMouseArea {
308 id: closeMouseArea
308 id: closeMouseArea
309 anchors.fill: panel
309 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
311 visible: enabled
312 visible: enabled
312 onPressed: {
313 onPressed: {
313 mouse.accepted = false;
314 mouse.accepted = false;
@@ -414,7 +415,7 @@ FocusScope {
414 target: panel.dismissTimer
415 target: panel.dismissTimer
415 onTriggered: {
416 onTriggered: {
416 if (root.autohideEnabled && !root.lockedVisible) {
417 if (root.autohideEnabled && !root.lockedVisible) {
417− if (!panel.preventHiding) {
418+ if (!edgeBarrier.containsMouse && !panel.preventHiding) {
418 root.state = ""
419 root.state = ""
419 } else {
420 } else {
420 panel.dismissTimer.restart()
421 panel.dismissTimer.restart()
qml/Launcher/LauncherPanel.qml+3
@@ -35,6 +35,7 @@ Rectangle {
35 || dndArea.containsMouse || dashItem.hovered
35 || dndArea.containsMouse || dashItem.hovered
36 property int highlightIndex: -2
36 property int highlightIndex: -2
37 property bool shortcutHintsShown: false
37 property bool shortcutHintsShown: false
38+ readonly property bool quickListOpen: quickList.state === "open"
38
39
39 signal applicationSelected(string appId)
40 signal applicationSelected(string appId)
40 signal showDashHome()
41 signal showDashHome()
@@ -686,6 +687,8 @@ Rectangle {
686 InverseMouseArea {
687 InverseMouseArea {
687 anchors.fill: quickListShape
688 anchors.fill: quickListShape
688 enabled: quickList.state == "open" || pressed
689 enabled: quickList.state == "open" || pressed
690+ hoverEnabled: enabled
691+ visible: enabled
689
692
690 onClicked: {
693 onClicked: {
691 quickList.state = "";
694 quickList.state = "";
qml/Panel/Panel.qml+4−2
@@ -207,6 +207,8 @@ Item {
207 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
207 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
208 active: __applicationMenus.model
208 active: __applicationMenus.model
209
209
210+ width: parent.width - windowControlButtons.width - units.gu(2) - __indicators.barWidth
211+
210 property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
212 property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
211
213
212 sourceComponent: MenuBar {
214 sourceComponent: MenuBar {
@@ -281,7 +283,7 @@ Item {
281 anchors.centerIn: parent
283 anchors.centerIn: parent
282 text: actionItem.text
284 text: actionItem.text
283 horizontalAlignment: Text.AlignLeft
285 horizontalAlignment: Text.AlignLeft
284− color: enabled ? "white" : "#5d5d5d"
286+ color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
285 }
287 }
286 }
288 }
287
289
@@ -383,7 +385,7 @@ Item {
383 }
385 }
384
386
385 enabled: !applicationMenus.expanded
387 enabled: !applicationMenus.expanded
386− opacity: !applicationMenus.expanded ? 1 : 0
388+ opacity: !callHint.visible && !applicationMenus.expanded ? 1 : 0
387 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
389 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
388
390
389 onEnabledChanged: {
391 onEnabledChanged: {
qml/Shell.qml+31−2
@@ -288,6 +288,7 @@ StyledItem {
288 applicationManager: ApplicationManager
288 applicationManager: ApplicationManager
289 topLevelSurfaceList: topLevelSurfaceList
289 topLevelSurfaceList: topLevelSurfaceList
290 inputMethodRect: inputMethod.visibleRect
290 inputMethodRect: inputMethod.visibleRect
291+ rightEdgePushProgress: rightEdgeBarrier.progress
291
292
292 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
293 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
293 ? "phone"
294 ? "phone"
@@ -523,12 +524,13 @@ StyledItem {
523 hides: [launcher]
524 hides: [launcher]
524 available: (!greeter || !greeter.shown)
525 available: (!greeter || !greeter.shown)
525 && !shell.waitingOnGreeter
526 && !shell.waitingOnGreeter
527+ && !stage.spreadShown
526 }
528 }
527
529
528 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
530 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
529 ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
531 ? topLevelSurfaceList.focusedWindow.state === Mir.FullscreenState
530 : false
532 : false
531− fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
533+ fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0 && !stage.spreadShown)
532 || greeter.hasLockedApp
534 || greeter.hasLockedApp
533 greeterShown: greeter && greeter.shown
535 greeterShown: greeter && greeter.shown
534 hasKeyboard: shell.hasKeyboard
536 hasKeyboard: shell.hasKeyboard
@@ -694,6 +696,33 @@ StyledItem {
694 }
696 }
695 ]
697 ]
696 }
698 }
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+ }
697 }
726 }
698
727
699 Dialogs {
728 Dialogs {
@@ -777,7 +806,7 @@ StyledItem {
777
806
778 onPushedRightBoundary: {
807 onPushedRightBoundary: {
779 if (buttons === Qt.NoButton) {
808 if (buttons === Qt.NoButton) {
780− stage.pushRightEdge(amount);
809+ rightEdgeBarrier.push(amount);
781 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
810 } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
782 previewRectangle.maximizeRight(amount);
811 previewRectangle.maximizeRight(amount);
783 }
812 }
qml/Stage/Stage.qml+63−56
@@ -46,6 +46,7 @@ FocusScope {
46 property int leftMargin: 0
46 property int leftMargin: 0
47 property bool oskEnabled: false
47 property bool oskEnabled: false
48 property rect inputMethodRect
48 property rect inputMethodRect
49+ property real rightEdgePushProgress: 0
49
50
50 // Configuration
51 // Configuration
51 property string mode: "staged"
52 property string mode: "staged"
@@ -101,6 +102,8 @@ FocusScope {
101 // No we didn't, do a quick alt-tab
102 // No we didn't, do a quick alt-tab
102 if (appRepeater.count > 1) {
103 if (appRepeater.count > 1) {
103 appRepeater.itemAt(1).activate();
104 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
104 }
107 }
105 }
108 }
106 }
109 }
@@ -125,11 +128,6 @@ FocusScope {
125 // functions to be called from outside
128 // functions to be called from outside
126 function updateFocusedAppOrientation() { /* TODO */ }
129 function updateFocusedAppOrientation() { /* TODO */ }
127 function updateFocusedAppOrientationAnimated() { /* TODO */}
130 function updateFocusedAppOrientationAnimated() { /* TODO */}
128− function pushRightEdge(amount) {
129− if (root.spreadEnabled) {
130− edgeBarrier.push(amount);
131− }
132− }
133
131
134 function closeSpread() {
132 function closeSpread() {
135 priv.goneToSpread = false;
133 priv.goneToSpread = false;
@@ -141,6 +139,12 @@ FocusScope {
141 }
139 }
142 }
140 }
143
141
142+ onRightEdgePushProgressChanged: {
143+ if (spreadEnabled && rightEdgePushProgress >= 1) {
144+ priv.goneToSpread = true
145+ }
146+ }
147+
144 GSettings {
148 GSettings {
145 id: lifecycleExceptions
149 id: lifecycleExceptions
146 schema.id: "com.canonical.qtmir"
150 schema.id: "com.canonical.qtmir"
@@ -452,7 +456,7 @@ FocusScope {
452 PropertyChanges { target: wallpaper; visible: false }
456 PropertyChanges { target: wallpaper; visible: false }
453 },
457 },
454 State {
458 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"
456 PropertyChanges {
460 PropertyChanges {
457 target: blurLayer;
461 target: blurLayer;
458 visible: true;
462 visible: true;
@@ -462,7 +466,7 @@ FocusScope {
462 }
466 }
463 },
467 },
464 State {
468 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"
466 extend: "stagedRightEdge"
470 extend: "stagedRightEdge"
467 PropertyChanges {
471 PropertyChanges {
468 target: sideStage
472 target: sideStage
@@ -471,18 +475,18 @@ FocusScope {
471 }
475 }
472 },
476 },
473 State {
477 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"
475 PropertyChanges {
479 PropertyChanges {
476 target: blurLayer;
480 target: blurLayer;
477 visible: true
481 visible: true
478 blurRadius: 32
482 blurRadius: 32
479 brightness: .65
483 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))
481 }
485 }
482 },
486 },
483 State {
487 State {
484 name: "staged"; when: root.mode === "staged"
488 name: "staged"; when: root.mode === "staged"
485− PropertyChanges { target: wallpaper; visible: false }
489+ PropertyChanges { target: wallpaper; visible: !priv.focusedAppDelegate || priv.focusedAppDelegate.x !== 0 }
486 },
490 },
487 State {
491 State {
488 name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
492 name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
@@ -502,7 +506,6 @@ FocusScope {
502 Transition {
506 Transition {
503 to: "spread"
507 to: "spread"
504 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
508 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
505− PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
506 },
509 },
507 Transition {
510 Transition {
508 from: "spread"
511 from: "spread"
@@ -517,6 +520,7 @@ FocusScope {
517 }
520 }
518 }
521 }
519 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
522 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
523+ PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
520 }
524 }
521 },
525 },
522 Transition {
526 Transition {
@@ -811,7 +815,7 @@ FocusScope {
811 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
815 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
812
816
813 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
817 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
814− readonly property bool fullscreen: window.state === Mir.FullscreenState
818+ readonly property bool fullscreen: windowState === WindowStateStorage.WindowStateFullscreen
815
819
816 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
820 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
817 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
821 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
@@ -822,7 +826,10 @@ FocusScope {
822 readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
826 readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
823 readonly property alias orientationChangesEnabled: decoratedWindow.orientationChangesEnabled
827 readonly property alias orientationChangesEnabled: decoratedWindow.orientationChangesEnabled
824
828
829+ // TODO drop our own windowType once Mir/Miral/Qtmir gets in sync with ours
825 property int windowState: WindowStateStorage.WindowStateNormal
830 property int windowState: WindowStateStorage.WindowStateNormal
831+ property int prevWindowState: WindowStateStorage.WindowStateRestored
832+
826 property bool animationsEnabled: true
833 property bool animationsEnabled: true
827 property alias title: decoratedWindow.title
834 property alias title: decoratedWindow.title
828 readonly property string appName: model.application ? model.application.name : ""
835 readonly property string appName: model.application ? model.application.name : ""
@@ -877,9 +884,6 @@ FocusScope {
877 }
884 }
878 priv.updateMainAndSideStageIndexes();
885 priv.updateMainAndSideStageIndexes();
879 }
886 }
880− if (root.mode == "windowed") {
881− appDelegate.restore(true /* animated */, appDelegate.windowState);
882− }
883 appDelegate.focus = true;
887 appDelegate.focus = true;
884 }
888 }
885
889
@@ -929,7 +933,14 @@ FocusScope {
929 } else if (model.window.state === Mir.MaximizedBottomRightState) {
933 } else if (model.window.state === Mir.MaximizedBottomRightState) {
930 appDelegate.maximizeBottomRight();
934 appDelegate.maximizeBottomRight();
931 } else if (model.window.state === Mir.RestoredState) {
935 } else if (model.window.state === Mir.RestoredState) {
936+ if (appDelegate.fullscreen && appDelegate.prevWindowState != WindowStateStorage.WindowStateRestored) {
937+ model.window.requestState(WindowStateStorage.toMirState(appDelegate.prevWindowState));
938+ } else {
932− appDelegate.restore();
939+ appDelegate.restore();
940+ }
941+ } else if (model.window.state === Mir.FullscreenState) {
942+ appDelegate.prevWindowState = appDelegate.windowState;
943+ appDelegate.windowState = WindowStateStorage.WindowStateFullscreen;
933 }
944 }
934 }
945 }
935 }
946 }
@@ -1031,6 +1042,7 @@ FocusScope {
1031 animationsEnabled = (animated === undefined) || animated;
1042 animationsEnabled = (animated === undefined) || animated;
1032 windowState = state || WindowStateStorage.WindowStateRestored;
1043 windowState = state || WindowStateStorage.WindowStateRestored;
1033 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1044 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1045+ prevWindowState = windowState;
1034 }
1046 }
1035
1047
1036 function playFocusAnimation() {
1048 function playFocusAnimation() {
@@ -1193,7 +1205,7 @@ FocusScope {
1193 when: (root.mode == "staged" || root.mode == "stagedWithSideStage") && (root.state == "sideStagedRightEdge" || root.state == "stagedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running)
1205 when: (root.mode == "staged" || root.mode == "stagedWithSideStage") && (root.state == "sideStagedRightEdge" || root.state == "stagedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running)
1194 PropertyChanges {
1206 PropertyChanges {
1195 target: stagedRightEdgeMaths
1207 target: stagedRightEdgeMaths
1196− progress: Math.max(edgeBarrier.progress, rightEdgeDragArea.draggedProgress)
1208+ progress: Math.max(rightEdgePushProgress, rightEdgeDragArea.draggedProgress)
1197 }
1209 }
1198 PropertyChanges {
1210 PropertyChanges {
1199 target: appDelegate
1211 target: appDelegate
@@ -1220,11 +1232,11 @@ FocusScope {
1220 },
1232 },
1221 State {
1233 State {
1222 name: "windowedRightEdge"
1234 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)
1224 PropertyChanges {
1236 PropertyChanges {
1225 target: windowedRightEdgeMaths
1237 target: windowedRightEdgeMaths
1226 swipeProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0
1238 swipeProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0
1227− pushProgress: edgeBarrier.progress
1239+ pushProgress: rightEdgePushProgress
1228 }
1240 }
1229 PropertyChanges {
1241 PropertyChanges {
1230 target: appDelegate
1242 target: appDelegate
@@ -1418,11 +1430,12 @@ FocusScope {
1418 name: "minimized"; when: appDelegate.minimized
1430 name: "minimized"; when: appDelegate.minimized
1419 PropertyChanges {
1431 PropertyChanges {
1420 target: appDelegate
1432 target: appDelegate
1421− requestedX: -appDelegate.width / 2
1422 scale: units.gu(5) / appDelegate.width
1433 scale: units.gu(5) / appDelegate.width
1423 opacity: 0;
1434 opacity: 0;
1424 visuallyMinimized: true
1435 visuallyMinimized: true
1425 visuallyMaximized: false
1436 visuallyMaximized: false
1437+ x: -appDelegate.width / 2
1438+ y: root.height / 2
1426 }
1439 }
1427 }
1440 }
1428 ]
1441 ]
@@ -1438,15 +1451,6 @@ FocusScope {
1438 to: "staged,stagedWithSideStage"
1451 to: "staged,stagedWithSideStage"
1439 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1452 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1440 },
1453 },
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− },
1450 Transition {
1454 Transition {
1451 to: "spread"
1455 to: "spread"
1452 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1456 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
@@ -1495,6 +1499,31 @@ FocusScope {
1495 PropertyAction { target: appDelegate; property: "visible" }
1499 PropertyAction { target: appDelegate; property: "visible" }
1496 }
1500 }
1497 },
1501 },
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+ },
1498 Transition {
1527 Transition {
1499 id: windowedTransition
1528 id: windowedTransition
1500 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen,minimized"
1529 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen,minimized"
@@ -1586,7 +1615,10 @@ FocusScope {
1586 onDecorationReleased: fakeRectangle.commit();
1615 onDecorationReleased: fakeRectangle.commit();
1587
1616
1588 property real angle: 0
1617 property real angle: 0
1618+ Behavior on angle { enabled: priv.closingIndex >= 0; UbuntuNumberAnimation {} }
1589 property real itemScale: 1
1619 property real itemScale: 1
1620+ Behavior on itemScale { enabled: priv.closingIndex >= 0; UbuntuNumberAnimation {} }
1621+
1590 transform: [
1622 transform: [
1591 Scale {
1623 Scale {
1592 origin.x: 0
1624 origin.x: 0
@@ -1648,7 +1680,6 @@ FocusScope {
1648 onClicked: {
1680 onClicked: {
1649 spreadItem.highlightedIndex = index;
1681 spreadItem.highlightedIndex = index;
1650 if (distance == 0) {
1682 if (distance == 0) {
1651− model.window.activate();
1652 priv.goneToSpread = false;
1683 priv.goneToSpread = false;
1653 }
1684 }
1654 }
1685 }
@@ -1756,30 +1787,6 @@ FocusScope {
1756 appContainerHeight: appContainer.height
1787 appContainerHeight: appContainer.height
1757 }
1788 }
1758
1789
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−
1783 MouseArea {
1790 MouseArea {
1784 id: hoverMouseArea
1791 id: hoverMouseArea
1785 objectName: "hoverMouseArea"
1792 objectName: "hoverMouseArea"
@@ -1846,11 +1853,11 @@ FocusScope {
1846 var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1853 var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1847 if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1854 if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1848 var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1855 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);
1850 snapAnimation.start();
1857 snapAnimation.start();
1851 } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1858 } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1852 var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1859 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);
1854 snapAnimation.start();
1861 snapAnimation.start();
1855 }
1862 }
1856 }
1863 }
qml/Tutorial/TutorialLeftLong.qml+1−1
@@ -46,7 +46,7 @@ TutorialPage {
46 }
46 }
47
47
48 label {
48 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")
50 anchors.left: arrow.right
50 anchors.left: arrow.right
51 anchors.leftMargin: units.gu(3)
51 anchors.leftMargin: units.gu(3)
52 anchors.right: root.right
52 anchors.right: root.right
tests/mocks/Unity/Application/SurfaceManager.cpp+4−5
@@ -211,8 +211,6 @@ void SurfaceManager::activate(unityapi::MirSurfaceInterface *apiSurface)
211
211
212void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
212void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
213{
213{
214− Q_ASSERT(!m_underModification);
215−
216 DEBUG_MSG("("<<surface<<","<<state<<") started");
214 DEBUG_MSG("("<<surface<<","<<state<<") started");
217 Q_EMIT modificationsStarted();
215 Q_EMIT modificationsStarted();
218 m_underModification = true;
216 m_underModification = true;
@@ -266,9 +264,10 @@ void SurfaceManager::focusFirstAvailableSurface()
266 if (m_focusedSurface) {
264 if (m_focusedSurface) {
267 m_focusedSurface->setFocused(false);
265 m_focusedSurface->setFocused(false);
268 }
266 }
269− if (chosenSurface) {
267+
270− chosenSurface->setFocused(true);
268+ chosenSurface->setFocused(true);
271− }
269+ doRaise(chosenSurface);
270+
272 m_focusedSurface = chosenSurface;
271 m_focusedSurface = chosenSurface;
273}
272}
274
273
tests/mocks/Unity/CMakeLists.txt+6
@@ -73,3 +73,9 @@ add_unity8_mock(Unity 0.2 Unity TARGETS FakeUnityQml)
73
73
74# and one copy in a separate directory for autopilot
74# and one copy in a separate directory for autopilot
75add_unity8_mock(Unity 0.2 Unity PREFIX scopefakes TARGETS FakeUnityQml)
75add_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−
3include_directories(
1include_directories(
4 ${CMAKE_CURRENT_SOURCE_DIR}
2 ${CMAKE_CURRENT_SOURCE_DIR}
5 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/
3 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/
tests/mocks/Utils/CMakeLists.txt+1
@@ -26,6 +26,7 @@ set(QMLPLUGIN_SRC
26 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
26 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
27 ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp
27 ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp
28 ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp
28 ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp
29+ ${CMAKE_SOURCE_DIR}/plugins/Utils/expressionfiltermodel.cpp
29 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
30 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
30 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
31 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
31 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
32 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
tests/mocks/Utils/plugin.cpp+2
@@ -42,6 +42,7 @@
42#include <globalfunctions.h>
42#include <globalfunctions.h>
43#include <appdrawerproxymodel.h>
43#include <appdrawerproxymodel.h>
44#include <tabfocusfence.h>
44#include <tabfocusfence.h>
45+#include <expressionfiltermodel.h>
45
46
46static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
47static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
47{
48{
@@ -86,4 +87,5 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
86 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
87 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
87 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
88 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
88 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
89 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
90+ qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel");
89}
91}
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt−2
@@ -1,5 +1,3 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
2−
3include_directories(
1include_directories(
4 ${CMAKE_CURRENT_SOURCE_DIR}
2 ${CMAKE_CURRENT_SOURCE_DIR}
5 ${CMAKE_CURRENT_BINARY_DIR}
3 ${CMAKE_CURRENT_BINARY_DIR}
tests/plugins/Unity/Launcher/CMakeLists.txt−1
@@ -1,5 +1,4 @@
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=11)
3
2
4include_directories(
3include_directories(
5 ${CMAKE_CURRENT_SOURCE_DIR}
4 ${CMAKE_CURRENT_SOURCE_DIR}
tests/qmltests/ApplicationMenus/tst_MenuBar.qml+85−9
@@ -28,8 +28,8 @@ import ".."
28
28
29Item {
29Item {
30 id: root
30 id: root
31− width: units.gu(100)
31+ width: units.gu(120)
32− height: units.gu(50)
32+ height: units.gu(70)
33
33
34 Component.onCompleted: {
34 Component.onCompleted: {
35 QuickUtils.keyboardAttached = true;
35 QuickUtils.keyboardAttached = true;
@@ -51,12 +51,12 @@ Item {
51 Rectangle {
51 Rectangle {
52 anchors {
52 anchors {
53 left: parent.left
53 left: parent.left
54− right: parent.right
55 top: parent.top
54 top: parent.top
56 margins: units.gu(1)
55 margins: units.gu(1)
57 }
56 }
58 height: units.gu(3)
57 height: units.gu(3)
59− color: "grey"
58+ width: parent.width * 2/3
59+ color: theme.palette.normal.background
60
60
61 MenuBar {
61 MenuBar {
62 id: menuBar
62 id: menuBar
@@ -65,7 +65,7 @@ Item {
65
65
66 unityMenuModel: UnityMenuModel {
66 unityMenuModel: UnityMenuModel {
67 id: menuBackend
67 id: menuBackend
68− modelData: appMenuData.generateTestData(17,5,2,3)
68+ modelData: appMenuData.generateTestData(10,5,2,3)
69 }
69 }
70 }
70 }
71 }
71 }
@@ -83,12 +83,12 @@ Item {
83
83
84 function init() {
84 function init() {
85 menuBar.dismiss();
85 menuBar.dismiss();
86− menuBackend.modelData = appMenuData.generateTestData(5,5,2,3)
86+ menuBackend.modelData = appMenuData.generateTestData(5,5,2,3, "menu")
87 activatedSpy.clear();
87 activatedSpy.clear();
88 }
88 }
89
89
90 function test_mouseNavigation() {
90 function test_mouseNavigation() {
91− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
91+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
92 wait(50) // wait for row to build
92 wait(50) // wait for row to build
93 var priv = findInvisibleChild(menuBar, "d");
93 var priv = findInvisibleChild(menuBar, "d");
94
94
@@ -114,7 +114,7 @@ Item {
114 }
114 }
115
115
116 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
116 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
117− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
117+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
118 var priv = findInvisibleChild(menuBar, "d");
118 var priv = findInvisibleChild(menuBar, "d");
119
119
120 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
120 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -139,7 +139,7 @@ Item {
139 }
139 }
140
140
141 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
141 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
142− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
142+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
143 var priv = findInvisibleChild(menuBar, "d");
143 var priv = findInvisibleChild(menuBar, "d");
144
144
145 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
145 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -180,6 +180,41 @@ Item {
180 tryCompare(priv, "currentItem", menuItem);
180 tryCompare(priv, "currentItem", menuItem);
181 }
181 }
182
182
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+
183 function test_openAppMenuShortcut() {
218 function test_openAppMenuShortcut() {
184 var priv = findInvisibleChild(menuBar, "d");
219 var priv = findInvisibleChild(menuBar, "d");
185
220
@@ -192,5 +227,46 @@ Item {
192 keyClick(Qt.Key_F10, Qt.AltModifier);
227 keyClick(Qt.Key_F10, Qt.AltModifier);
193 compare(priv.currentItem, menuItem1, "First enabled item should be opened");
228 compare(priv.currentItem, menuItem1, "First enabled item should be opened");
194 }
229 }
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+ }
195 }
271 }
196}
272}
tests/qmltests/ApplicationMenus/tst_MenuPopup.qml+14
@@ -223,6 +223,20 @@ Item {
223 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);
223 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);
224 }
224 }
225
225
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+
226 function test_differentSizes() {
240 function test_differentSizes() {
227 var differentSizesMenu = [{
241 var differentSizesMenu = [{
228 "rowData": { "label": "Short" }}, {
242 "rowData": { "label": "Short" }}, {
tests/qmltests/Launcher/tst_Launcher.qml+13
@@ -1480,6 +1480,19 @@ Rectangle {
1480 launcher.available = true;
1480 launcher.available = true;
1481 }
1481 }
1482
1482
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+
1483 function test_hintOnSizeChange() {
1496 function test_hintOnSizeChange() {
1484 var oldSize = launcher.panelWidth;
1497 var oldSize = launcher.panelWidth;
1485 launcher.maxPanelX = -launcher.panelWidth;
1498 launcher.maxPanelX = -launcher.panelWidth;
tests/qmltests/Stage/tst_DesktopStage.qml+193−10
@@ -29,6 +29,7 @@ import ".." // For EdgeBarrierControls
29import "../../../qml/Stage"
29import "../../../qml/Stage"
30import "../../../qml/Components"
30import "../../../qml/Components"
31import "../../../qml/Components/PanelState"
31import "../../../qml/Components/PanelState"
32+import "../../../qml/ApplicationMenus"
32
33
33Item {
34Item {
34 id: root
35 id: root
@@ -44,6 +45,8 @@ Item {
44 }
45 }
45
46
46 Component.onCompleted: {
47 Component.onCompleted: {
48+ ApplicationMenusLimits.screenWidth = Qt.binding( function() { return stageLoader.width; } );
49+ ApplicationMenusLimits.screenHeight = Qt.binding( function() { return stageLoader.height; } );
47 QuickUtils.keyboardAttached = true;
50 QuickUtils.keyboardAttached = true;
48 theme.name = "Ubuntu.Components.Themes.SuruDark";
51 theme.name = "Ubuntu.Components.Themes.SuruDark";
49 resetGeometry();
52 resetGeometry();
@@ -89,7 +92,6 @@ Item {
89 focus: true
92 focus: true
90
93
91 Component.onCompleted: {
94 Component.onCompleted: {
92− edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
93 ApplicationManager.startApplication("unity8-dash");
95 ApplicationManager.startApplication("unity8-dash");
94 }
96 }
95 Component.onDestruction: {
97 Component.onDestruction: {
@@ -132,11 +134,22 @@ Item {
132 }
134 }
133 }
135 }
134
136
135− EdgeBarrierControls {
137+ Label {
136− id: edgeBarrierControls
138+ text: "Right edge push progress"
137− text: "Drag here to pull out spread"
139+ }
138− backgroundColor: "blue"
140+
139− onDragged: { stageLoader.item.pushRightEdge(amount); }
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 }
140 }
153 }
141
154
142 Divider {}
155 Divider {}
@@ -695,10 +708,18 @@ Item {
695 var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);
708 var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);
696 var deco = findChild(dialerDelegate, "appWindowDecoration");
709 var deco = findChild(dialerDelegate, "appWindowDecoration");
697 verify(deco);
710 verify(deco);
698− tryCompare(deco, "maximizeButtonShown", false);
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);
699− mouseDoubleClick(deco);
715+ mouseDoubleClick(deco, deco.width - units.gu(1), deco.height/2)
700− var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
716+ expectFail("", "Double click should not maximize in a size restricted window");
701− tryCompareFunction(function(){ return sizeBefore; }, sizeAfter);
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+ );
702
723
703 // remove restrictions, the maximize button should again be visible
724 // remove restrictions, the maximize button should again be visible
704 dialerDelegate.surface.setMaximumWidth(0);
725 dialerDelegate.surface.setMaximumWidth(0);
@@ -706,6 +727,29 @@ Item {
706 tryCompare(dialerMaximizeButton, "visible", true);
727 tryCompare(dialerMaximizeButton, "visible", true);
707 }
728 }
708
729
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+
709 function test_canMoveWindowWithLeftMouseButtonOnly_data() {
753 function test_canMoveWindowWithLeftMouseButtonOnly_data() {
710 return [
754 return [
711 {tag: "left mouse button", button: Qt.LeftButton },
755 {tag: "left mouse button", button: Qt.LeftButton },
@@ -830,5 +874,144 @@ Item {
830 mouseRelease(decoration);
874 mouseRelease(decoration);
831 tryCompare(Mir, "cursorName", "");
875 tryCompare(Mir, "cursorName", "");
832 }
876 }
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+ }
833 }
1016 }
834}
1017}
tests/qmltests/Stage/tst_PhoneStage.qml+4−5
@@ -418,11 +418,10 @@ Item {
418
418
419 function test_mouseEdgePush() {
419 function test_mouseEdgePush() {
420 addApps(1);
420 addApps(1);
421− mouseMove(stage, stage.width - 1, units.gu(10));
421+ // When progress goes to 1 it should switch to spread, but stay there even if progress goes back
422− for (var i = 0; i < units.gu(10); i++) {
422+ stage.rightEdgePushProgress = 1;
423− stage.pushRightEdge(1);
423+ compare(stage.state, "spread");
424− }
424+ stage.rightEdgePushProgress = 0;
425− mouseMove(stage, stage.width - units.gu(5), units.gu(10));
426 compare(stage.state, "spread");
425 compare(stage.state, "spread");
427 }
426 }
428
427
tests/qmltests/Tutorial/tst_Tutorial.qml+2−2
@@ -719,9 +719,9 @@ Rectangle {
719 ApplicationManager.startApplication("facebook-webapp");
719 ApplicationManager.startApplication("facebook-webapp");
720 tryCompare(tutorialRightLoader, "shown", true);
720 tryCompare(tutorialRightLoader, "shown", true);
721
721
722− var stage = findChild(shell, "stage");
722+ var cursor = findChild(shell, "cursor");
723 mouseMove(shell, shell.width, shell.height / 2);
723 mouseMove(shell, shell.width, shell.height / 2);
724− stage.pushRightEdge(units.gu(8));
724+ cursor.pushedRightBoundary(units.gu(8), 0);
725 tryCompare(tutorialRightLoader, "shown", false);
725 tryCompare(tutorialRightLoader, "shown", false);
726
726
727 tryCompare(AccountsService, "demoEdges", false);
727 tryCompare(AccountsService, "demoEdges", false);
tests/qmltests/tst_OrientedShell.qml+2−1
@@ -1431,8 +1431,9 @@ Rectangle {
1431 function swipeAwayGreeter() {
1431 function swipeAwayGreeter() {
1432 var greeter = findChild(shell, "greeter");
1432 var greeter = findChild(shell, "greeter");
1433 tryCompare(greeter, "fullyShown", true);
1433 tryCompare(greeter, "fullyShown", true);
1434+ waitForRendering(greeter)
1434
1435
1435− var touchX = shell.width - (shell.edgeSize / 2);
1436+ var touchX = shell.width * .75;
1436 var touchY = shell.height / 2;
1437 var touchY = shell.height / 2;
1437 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
1438 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
1438
1439
tests/qmltests/tst_Shell.qml+132−1
@@ -335,6 +335,8 @@ Rectangle {
335 Row {
335 Row {
336 CheckBox {
336 CheckBox {
337 id: fullscreeAppCheck
337 id: fullscreeAppCheck
338+ activeFocusOnPress: false
339+ activeFocusOnTab: false
338
340
339 onTriggered: {
341 onTriggered: {
340 if (!topLevelSurfaceList.focusedWindow) return;
342 if (!topLevelSurfaceList.focusedWindow) return;
@@ -2585,6 +2587,55 @@ Rectangle {
2585 tryCompareFunction(function() { return ApplicationManager.focusedApplicationId; }, "calendar-app");
2587 tryCompareFunction(function() { return ApplicationManager.focusedApplicationId; }, "calendar-app");
2586 }
2588 }
2587
2589
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+
2588 function test_oskDisplacesWindow_data() {
2639 function test_oskDisplacesWindow_data() {
2589 return [
2640 return [
2590 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
2641 {tag: "no need to displace", windowHeight: units.gu(10), windowY: units.gu(5), targetDisplacement: units.gu(5), oskEnabled: true},
@@ -2707,9 +2758,10 @@ Rectangle {
2707 tryCompare(stage, "state", "staged");
2758 tryCompare(stage, "state", "staged");
2708
2759
2709 // Try by edge push
2760 // Try by edge push
2761+ var cursor = findChild(shell, "cursor");
2710 mouseMove(stage, stage.width - 1, units.gu(10));
2762 mouseMove(stage, stage.width - 1, units.gu(10));
2711 for (var i = 0; i < units.gu(10); i++) {
2763 for (var i = 0; i < units.gu(10); i++) {
2712− stage.pushRightEdge(1);
2764+ cursor.pushedRightBoundary(1, 0);
2713 }
2765 }
2714 mouseMove(stage, stage.width - units.gu(5), units.gu(10));
2766 mouseMove(stage, stage.width - units.gu(5), units.gu(10));
2715 tryCompare(stage, "state", data.spreadEnabled ? "spread" : "staged");
2767 tryCompare(stage, "state", data.spreadEnabled ? "spread" : "staged");
@@ -2732,6 +2784,28 @@ Rectangle {
2732 keyRelease(Qt.Key_W, Qt.MetaModifier)
2784 keyRelease(Qt.Key_W, Qt.MetaModifier)
2733 }
2785 }
2734
2786
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+
2735 function test_panelTitleShowsWhenGreeterNotShown_data() {
2809 function test_panelTitleShowsWhenGreeterNotShown_data() {
2736 return [
2810 return [
2737 {tag: "phone" },
2811 {tag: "phone" },
@@ -2779,5 +2853,62 @@ Rectangle {
2779 }
2853 }
2780 tryCompareFunction(function() { return drawer.visible; }, false);
2854 tryCompareFunction(function() { return drawer.visible; }, false);
2781 }
2855 }
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+ }
2782 }
2913 }
2783}
2914}
tests/utils/modules/Unity/Test/UnityTestCase.qml−9
@@ -617,15 +617,6 @@ TestCase {
617 }
617 }
618 }
618 }
619
619
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−
629 /*
620 /*
630 Wait until any transition animation has finished for the given StateGroup or Item
621 Wait until any transition animation has finished for the given StateGroup or Item
631 */
622 */