Merge lp:~aacid/unity8/doubleClickMaximize into lp:unity8

Proposed by Albert Astals Cid
Status: Superseded
Proposed branch: lp:~aacid/unity8/doubleClickMaximize
Merge into: lp:unity8
Prerequisite: lp:~dandrader/unity8/simplifyWindowDecoration
Diff against target: 1463 lines (+829/-216)
16 files modified
plugins/Utils/CMakeLists.txt (+1/-0)
plugins/Utils/expressionfiltermodel.cpp (+49/-0)
plugins/Utils/expressionfiltermodel.h (+42/-0)
plugins/Utils/plugin.cpp (+2/-0)
qml/ApplicationMenus/ApplicationMenusLimits.qml (+24/-0)
qml/ApplicationMenus/MenuBar.qml (+320/-168)
qml/ApplicationMenus/MenuItem.qml (+3/-8)
qml/ApplicationMenus/MenuNavigator.qml (+23/-0)
qml/ApplicationMenus/MenuPopup.qml (+118/-27)
qml/ApplicationMenus/qmldir (+1/-0)
qml/Panel/Panel.qml (+3/-1)
tests/mocks/Utils/CMakeLists.txt (+1/-0)
tests/mocks/Utils/plugin.cpp (+2/-0)
tests/qmltests/ApplicationMenus/tst_MenuBar.qml (+49/-8)
tests/qmltests/ApplicationMenus/tst_MenuPopup.qml (+14/-0)
tests/qmltests/Stage/tst_DesktopStage.qml (+177/-4)
To merge this branch: bzr merge lp:~aacid/unity8/doubleClickMaximize
Reviewer Review Type Date Requested Status
Unity8 CI Bot continuous-integration Approve
Daniel d'Andrada (community) Approve
Review via email: mp+315147@code.launchpad.net

This proposal supersedes a proposal from 2017-01-19.

This proposal has been superseded by a proposal from 2017-01-31.

Commit message

Make double click on the window decoration maximize

And fix test and add a new one

Description of the change

 * Are there any related MPs required for this MP to build/function as expected?
No

 * Did you perform an exploratory manual test run of your code change and any related functionality?
Yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
N/A

 * If you changed the UI, has there been a design review?
N/A

To post a comment you must log in.
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2770
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2965/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3864
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2255
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2255
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3892
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3737/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2965/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2770
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2972/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3874
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2265
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2265
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3902
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3747/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2972/rebuild

review: Approve (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

With kate, after I double click to maximize. the cursor stays with the "closed hand" shape, as if I were dragging something.

In "make tryDesktopStage", when you double click on unity8-dash then move the mouse over the maximized tittle bar, things go crazy. Might be another symptom of the same bug. Some unresolved mouse press+release pair in some MouseArea.

Revision history for this message
Albert Astals Cid (aacid) wrote :

> In "make tryDesktopStage", when you double click on unity8-dash then move the mouse over the
> maximized tittle bar, things go crazy.

Interestingly doesn't seem to happen in tryShell

lp:~aacid/unity8/doubleClickMaximize updated
2771. By Albert Astals Cid

Dfiferent way of making double click work on window decoration

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2771
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2994/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3898
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2284
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2284
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3926
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3771/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2994/rebuild

review: Approve (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In test_hideMaximizeButtonWhenSizeConstrained():

"""
mouseMove(deco, deco.width - units.gu(1), deco.height/2);
"""

I think it deserves a comment on why are you clicking specifically near the right corner of the window title (ie, explain that you're evading the menu bar because double-clicking there doens't trigger maximization).

lp:~aacid/unity8/doubleClickMaximize updated
2772. By Albert Astals Cid

comment++

2773. By Albert Astals Cid

i'm tyopeing moar than usual

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

* Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

* Did CI run pass? If not, please explain why.
Yes.

review: Approve
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2772
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2997/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3901
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2287
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2287
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3929
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3774/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2997/rebuild

review: Approve (continuous-integration)
lp:~aacid/unity8/doubleClickMaximize updated
2774. By Albert Astals Cid

Merge ~nick-dedekind/unity8/menu.overflow

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/Utils/CMakeLists.txt'
2--- plugins/Utils/CMakeLists.txt 2016-12-21 10:20:36 +0000
3+++ plugins/Utils/CMakeLists.txt 2017-01-31 14:13:11 +0000
4@@ -35,6 +35,7 @@
5 globalfunctions.cpp
6 URLDispatcher.cpp
7 tabfocusfence.cpp
8+ expressionfiltermodel.cpp
9 plugin.cpp
10 )
11
12
13=== added file 'plugins/Utils/expressionfiltermodel.cpp'
14--- plugins/Utils/expressionfiltermodel.cpp 1970-01-01 00:00:00 +0000
15+++ plugins/Utils/expressionfiltermodel.cpp 2017-01-31 14:13:11 +0000
16@@ -0,0 +1,49 @@
17+/*
18+ * Copyright (C) 2017 Canonical, Ltd.
19+ *
20+ * This program is free software; you can redistribute it and/or modify
21+ * it under the terms of the GNU General Public License as published by
22+ * the Free Software Foundation; version 3.
23+ *
24+ * This program is distributed in the hope that it will be useful,
25+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
26+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27+ * GNU General Public License for more details.
28+ *
29+ * You should have received a copy of the GNU General Public License
30+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
31+ */
32+
33+#include "expressionfiltermodel.h"
34+
35+ExpressionFilterModel::ExpressionFilterModel(QObject *parent)
36+ : UnitySortFilterProxyModelQML(parent)
37+{
38+}
39+
40+QJSValue ExpressionFilterModel::matchExpression() const
41+{
42+ return m_matchExpression;
43+}
44+
45+void ExpressionFilterModel::setMatchExpression(const QJSValue &value)
46+{
47+ m_matchExpression = value;
48+ invalidateFilter();
49+}
50+
51+bool
52+ExpressionFilterModel::filterAcceptsRow(int sourceRow,
53+ const QModelIndex &sourceParent) const
54+{
55+ if (m_matchExpression.isCallable()) {
56+ QJSValueList args;
57+ args << sourceRow;
58+ QJSValue ret = m_matchExpression.call(args);
59+ if (ret.isBool()) {
60+ return ret.toBool();
61+ }
62+ }
63+
64+ return UnitySortFilterProxyModelQML::filterAcceptsRow(sourceRow, sourceParent);
65+}
66
67=== added file 'plugins/Utils/expressionfiltermodel.h'
68--- plugins/Utils/expressionfiltermodel.h 1970-01-01 00:00:00 +0000
69+++ plugins/Utils/expressionfiltermodel.h 2017-01-31 14:13:11 +0000
70@@ -0,0 +1,42 @@
71+/*
72+ * Copyright (C) 2017 Canonical, Ltd.
73+ *
74+ * This program is free software; you can redistribute it and/or modify
75+ * it under the terms of the GNU General Public License as published by
76+ * the Free Software Foundation; version 3.
77+ *
78+ * This program is distributed in the hope that it will be useful,
79+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
80+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
81+ * GNU General Public License for more details.
82+ *
83+ * You should have received a copy of the GNU General Public License
84+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
85+ */
86+
87+#ifndef EXPRESSIONFILTERMODEL_H
88+#define EXPRESSIONFILTERMODEL_H
89+
90+#include "unitysortfilterproxymodelqml.h"
91+#include <QJSValue>
92+
93+class ExpressionFilterModel : public UnitySortFilterProxyModelQML
94+{
95+ Q_OBJECT
96+ Q_PROPERTY(QJSValue matchExpression READ matchExpression WRITE setMatchExpression NOTIFY matchExpressionChanged)
97+public:
98+ explicit ExpressionFilterModel(QObject *parent = 0);
99+
100+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
101+
102+ QJSValue matchExpression() const;
103+ void setMatchExpression(const QJSValue& value);
104+
105+Q_SIGNALS:
106+ void matchExpressionChanged();
107+
108+private:
109+ mutable QJSValue m_matchExpression;
110+};
111+
112+#endif // EXPRESSIONFILTERMODEL_H
113
114=== modified file 'plugins/Utils/plugin.cpp'
115--- plugins/Utils/plugin.cpp 2017-01-03 12:16:00 +0000
116+++ plugins/Utils/plugin.cpp 2017-01-31 14:13:11 +0000
117@@ -41,6 +41,7 @@
118 #include "URLDispatcher.h"
119 #include "appdrawerproxymodel.h"
120 #include "tabfocusfence.h"
121+#include "expressionfiltermodel.h"
122
123 static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
124 {
125@@ -86,4 +87,5 @@
126 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
127 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
128 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
129+ qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel");
130 }
131
132=== added file 'qml/ApplicationMenus/ApplicationMenusLimits.qml'
133--- qml/ApplicationMenus/ApplicationMenusLimits.qml 1970-01-01 00:00:00 +0000
134+++ qml/ApplicationMenus/ApplicationMenusLimits.qml 2017-01-31 14:13:11 +0000
135@@ -0,0 +1,24 @@
136+/*
137+ * Copyright (C) 2017 Canonical, Ltd.
138+ *
139+ * This program is free software; you can redistribute it and/or modify
140+ * it under the terms of the GNU General Public License as published by
141+ * the Free Software Foundation; version 3.
142+ *
143+ * This program is distributed in the hope that it will be useful,
144+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
145+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
146+ * GNU General Public License for more details.
147+ *
148+ * You should have received a copy of the GNU General Public License
149+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
150+ */
151+
152+pragma Singleton
153+import QtQuick 2.4
154+import QtQuick.Window 2.2
155+
156+QtObject {
157+ property real screenWidth: Screen.width
158+ property real screenHeight: Screen.height
159+}
160
161=== modified file 'qml/ApplicationMenus/MenuBar.qml'
162--- qml/ApplicationMenus/MenuBar.qml 2017-01-17 09:45:22 +0000
163+++ qml/ApplicationMenus/MenuBar.qml 2017-01-31 14:13:11 +0000
164@@ -24,12 +24,13 @@
165 id: root
166 objectName: "menuBar"
167
168+ // set from outside
169 property alias unityMenuModel: rowRepeater.model
170+ property bool enableKeyFilter: false
171+ property real overflowWidth: width
172
173+ // read from outside
174 readonly property bool valid: rowRepeater.count > 0
175-
176- property bool enableKeyFilter: false
177-
178 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
179
180 implicitWidth: row.width
181@@ -75,189 +76,336 @@
182 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
183 anchors.fill: parent
184 enabled: d.currentItem != null
185+ hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
186 onPressed: d.dismissAll()
187 }
188
189- Item {
190- id: clippingItem
191-
192- height: root.height
193- width: root.width
194- clip: true
195-
196- Row {
197- id: row
198- spacing: units.gu(2)
199- height: parent.height
200-
201- ActionContext {
202- id: menuBarContext
203- objectName: "barContext"
204- active: !d.currentItem && enableKeyFilter
205- }
206-
207- Repeater {
208- id: rowRepeater
209-
210- Item {
211- id: visualItem
212- objectName: root.objectName + "-item" + __ownIndex
213-
214- readonly property int __ownIndex: index
215- property Item __popup: null;
216- property bool popupVisible: __popup && __popup.visible
217-
218- implicitWidth: column.implicitWidth
219- implicitHeight: row.height
220- enabled: model.sensitive
221-
222- function show() {
223- if (!__popup) {
224- __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
225- // force the current item to be the newly popped up menu
226- } else {
227- __popup.show();
228- }
229- d.currentItem = visualItem;
230- }
231- function hide() {
232- if (__popup) {
233- __popup.hide();
234-
235- if (d.currentItem === visualItem) {
236- d.currentItem = null;
237- }
238- }
239- }
240- function dismiss() {
241- if (__popup) {
242- __popup.destroy();
243- __popup = null;
244-
245- if (d.currentItem === visualItem) {
246- d.currentItem = null;
247- }
248- }
249- }
250-
251- Connections {
252- target: d
253- onDismissAll: visualItem.dismiss()
254- }
255-
256- Component {
257- id: menuComponent
258- MenuPopup {
259- x: visualItem.x - units.gu(1)
260- anchors.top: parent.bottom
261- unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
262-
263- Component.onCompleted: reset();
264- }
265- }
266-
267- RowLayout {
268- id: column
269- spacing: units.gu(1)
270- anchors {
271- centerIn: parent
272- }
273-
274- Icon {
275- Layout.preferredWidth: units.gu(2)
276- Layout.preferredHeight: units.gu(2)
277- Layout.alignment: Qt.AlignVCenter
278-
279- visible: model.icon || false
280- source: model.icon || ""
281- }
282-
283- ActionItem {
284- id: actionItem
285- width: _title.width
286- height: _title.height
287-
288- action: Action {
289- // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
290- text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
291-
292- onTriggered: {
293- visualItem.show();
294- }
295- }
296-
297- Label {
298- id: _title
299- text: actionItem.text
300- horizontalAlignment: Text.AlignLeft
301- color: enabled ? "white" : "#5d5d5d"
302- }
303- }
304- }
305- } // Item ( delegate )
306- } // Repeater
307- } // Row
308-
309- MouseArea {
310- anchors.fill: parent
311- hoverEnabled: d.currentItem
312-
313- onEntered: {
314- if (d.currentItem) {
315- updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
316- }
317- }
318- onPositionChanged: {
319- if (d.currentItem) {
320- updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
321- }
322- }
323- onClicked: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
324-
325- function updateCurrentItemFromPosition(point) {
326- var pos = mapToItem(row, point.x, point.y);
327-
328- if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
329- d.hoveredItem = row.childAt(pos.x, pos.y);
330- if (!d.hoveredItem || !d.hoveredItem.enabled)
331- return false;
332- if (d.currentItem != d.hoveredItem) {
333- d.currentItem = d.hoveredItem;
334- }
335- }
336- return true;
337- }
338- }
339-
340- Rectangle {
341- id: underline
342- anchors {
343- bottom: row.bottom
344- }
345- x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
346- width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
347- height: units.dp(4)
348- color: UbuntuColors.orange
349- visible: d.currentItem
350- }
351+ Row {
352+ id: row
353+ spacing: units.gu(2)
354+ height: parent.height
355+
356+ ActionContext {
357+ id: menuBarContext
358+ objectName: "barContext"
359+ active: !d.currentItem && enableKeyFilter
360+ }
361+
362+ Connections {
363+ target: root.unityMenuModel
364+ onModelReset: d.firstInvisibleIndex = undefined
365+ }
366+
367+ Repeater {
368+ id: rowRepeater
369+
370+ onItemAdded: d.recalcFirstInvisibleIndexAdded(index, item)
371+ onCountChanged: d.recalcFirstInvisibleIndex()
372+
373+ Item {
374+ id: visualItem
375+ objectName: root.objectName + "-item" + __ownIndex
376+
377+ readonly property int __ownIndex: index
378+ property Item __popup: null;
379+ property bool popupVisible: __popup && __popup.visible
380+ property bool shouldDisplay: x + width + ((__ownIndex < rowRepeater.count-1) ? units.gu(2) : 0) <
381+ root.overflowWidth - ((__ownIndex < rowRepeater.count-1) ? overflowButton.width : 0)
382+
383+ implicitWidth: column.implicitWidth
384+ implicitHeight: row.height
385+ enabled: model.sensitive && shouldDisplay
386+ opacity: shouldDisplay ? 1 : 0
387+
388+ function show() {
389+ if (!__popup) {
390+ __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
391+ __popup.childActivated.connect(dismiss);
392+ // force the current item to be the newly popped up menu
393+ } else {
394+ __popup.show();
395+ }
396+ d.currentItem = visualItem;
397+ }
398+ function hide() {
399+ if (__popup) {
400+ __popup.hide();
401+
402+ if (d.currentItem === visualItem) {
403+ d.currentItem = null;
404+ }
405+ }
406+ }
407+ function dismiss() {
408+ if (__popup) {
409+ __popup.destroy();
410+ __popup = null;
411+
412+ if (d.currentItem === visualItem) {
413+ d.currentItem = null;
414+ }
415+ }
416+ }
417+
418+ onVisibleChanged: {
419+ if (!visible && __popup) dismiss();
420+ }
421+
422+ Component.onCompleted: {
423+ shouldDisplayChanged.connect(function() {
424+ if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) {
425+ d.recalcFirstInvisibleIndex();
426+ }
427+ });
428+ }
429+
430+ Connections {
431+ target: d
432+ onDismissAll: visualItem.dismiss()
433+ }
434+
435+ Component {
436+ id: menuComponent
437+ MenuPopup {
438+ desiredX: visualItem.x - units.gu(1)
439+ desiredY: parent.height
440+ unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
441+
442+ Component.onCompleted: reset();
443+ }
444+ }
445+
446+ RowLayout {
447+ id: column
448+ spacing: units.gu(1)
449+ anchors {
450+ centerIn: parent
451+ }
452+
453+ Icon {
454+ Layout.preferredWidth: units.gu(2)
455+ Layout.preferredHeight: units.gu(2)
456+ Layout.alignment: Qt.AlignVCenter
457+
458+ visible: model.icon || false
459+ source: model.icon || ""
460+ }
461+
462+ ActionItem {
463+ id: actionItem
464+ width: _title.width
465+ height: _title.height
466+
467+ action: Action {
468+ enabled: visualItem.enabled
469+ // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
470+ text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
471+
472+ onTriggered: {
473+ visualItem.show();
474+ }
475+ }
476+
477+ Label {
478+ id: _title
479+ text: actionItem.text
480+ horizontalAlignment: Text.AlignLeft
481+ color: enabled ? "white" : "#5d5d5d"
482+ }
483+ }
484+ }
485+ } // Item ( delegate )
486+ } // Repeater
487+ } // Row
488+
489+ MouseArea {
490+ anchors.fill: row
491+ hoverEnabled: d.currentItem
492+
493+ onEntered: {
494+ if (d.currentItem) {
495+ updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
496+ }
497+ }
498+ onPositionChanged: {
499+ if (d.currentItem) {
500+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
501+ }
502+ }
503+ onClicked: {
504+ var prevItem = d.currentItem;
505+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
506+ if (prevItem && d.currentItem == prevItem) {
507+ prevItem.hide();
508+ }
509+ }
510+
511+ function updateCurrentItemFromPosition(point) {
512+ var pos = mapToItem(row, point.x, point.y);
513+
514+ if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
515+ d.hoveredItem = row.childAt(pos.x, pos.y);
516+ if (!d.hoveredItem || !d.hoveredItem.enabled)
517+ return;
518+ if (d.currentItem != d.hoveredItem) {
519+ d.currentItem = d.hoveredItem;
520+ }
521+ }
522+ }
523+ }
524+
525+ MouseArea {
526+ id: overflowButton
527+ objectName: "overflow"
528+
529+ hoverEnabled: d.currentItem
530+ onEntered: d.currentItem = this
531+ onPositionChanged: d.currentItem = this
532+ onClicked: d.currentItem = this
533+
534+ property Item __popup: null;
535+ property bool popupVisible: __popup && __popup.visible
536+ property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null
537+
538+ visible: d.firstInvisibleIndex != undefined
539+ x: firstInvisibleItem ? firstInvisibleItem.x : 0
540+
541+ height: parent.height
542+ width: units.gu(4)
543+
544+ onVisibleChanged: {
545+ if (!visible && __popup) dismiss();
546+ }
547+
548+ Icon {
549+ id: icon
550+ width: units.gu(2)
551+ height: units.gu(2)
552+ anchors.centerIn: parent
553+ color: theme.palette.normal.overlayText
554+ name: "toolkit_chevron-down_2gu"
555+ }
556+
557+ function show() {
558+ if (!__popup) {
559+ __popup = overflowComponent.createObject(root, { objectName: overflowButton.objectName + "-menu" });
560+ __popup.childActivated.connect(dismiss);
561+ // force the current item to be the newly popped up menu
562+ } else {
563+ __popup.show();
564+ }
565+ d.currentItem = overflowButton;
566+ }
567+ function hide() {
568+ if (__popup) {
569+ __popup.hide();
570+
571+ if (d.currentItem === overflowButton) {
572+ d.currentItem = null;
573+ }
574+ }
575+ }
576+ function dismiss() {
577+ if (__popup) {
578+ __popup.destroy();
579+ __popup = null;
580+
581+ if (d.currentItem === overflowButton) {
582+ d.currentItem = null;
583+ }
584+ }
585+ }
586+
587+ Connections {
588+ target: d
589+ onDismissAll: overflowButton.dismiss()
590+ }
591+
592+ Component {
593+ id: overflowComponent
594+ MenuPopup {
595+ id: overflowPopup
596+ desiredX: overflowButton.x - units.gu(1)
597+ desiredY: parent.height
598+ unityMenuModel: overflowModel
599+
600+ ExpressionFilterModel {
601+ id: overflowModel
602+ sourceModel: root.unityMenuModel
603+ matchExpression: function(index) {
604+ if (d.firstInvisibleIndex === undefined) return false;
605+ return index >= d.firstInvisibleIndex;
606+ }
607+
608+ function submenu(index) {
609+ return sourceModel.submenu(mapRowToSource(index));
610+ }
611+ function activate(index) {
612+ return sourceModel.activate(mapRowToSource(index));
613+ }
614+ }
615+
616+ Connections {
617+ target: d
618+ onFirstInvisibleIndexChanged: overflowModel.invalidate()
619+ }
620+ }
621+ }
622+ }
623+
624+ Rectangle {
625+ id: underline
626+ anchors {
627+ bottom: row.bottom
628+ }
629+ x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
630+ width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
631+ height: units.dp(4)
632+ color: UbuntuColors.orange
633+ visible: d.currentItem
634 }
635
636 MenuNavigator {
637 id: d
638 objectName: "d"
639 itemView: rowRepeater
640+ hasOverflow: overflowButton.visible
641
642 property Item currentItem: null
643 property Item hoveredItem: null
644 property Item prevCurrentItem: null
645-
646- readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
647-
648 property bool altPressed: false
649 property bool longAltPressed: false
650+ property var firstInvisibleIndex: undefined
651+
652+ readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1
653
654 signal dismissAll()
655
656+ function recalcFirstInvisibleIndexAdded(index, item) {
657+ if (firstInvisibleIndex === undefined) {
658+ if (!item.shouldDisplay) {
659+ firstInvisibleIndex = index;
660+ }
661+ } else if (index <= firstInvisibleIndex) {
662+ if (!item.shouldDisplay) {
663+ firstInvisibleIndex = index;
664+ } else {
665+ firstInvisibleIndex++;
666+ }
667+ }
668+ }
669+
670+ function recalcFirstInvisibleIndex() {
671+ for (var i = 0; i < rowRepeater.count; i++) {
672+ if (!rowRepeater.itemAt(i).shouldDisplay) {
673+ firstInvisibleIndex = i;
674+ return;
675+ }
676+ }
677+ firstInvisibleIndex = undefined;
678+ }
679+
680 onSelect: {
681 var delegate = rowRepeater.itemAt(index);
682 if (delegate) {
683@@ -265,6 +413,10 @@
684 }
685 }
686
687+ onOverflow: {
688+ d.currentItem = overflowButton;
689+ }
690+
691 onCurrentItemChanged: {
692 if (prevCurrentItem && prevCurrentItem != currentItem) {
693 if (currentItem) {
694
695=== modified file 'qml/ApplicationMenus/MenuItem.qml'
696--- qml/ApplicationMenus/MenuItem.qml 2016-12-13 09:56:20 +0000
697+++ qml/ApplicationMenus/MenuItem.qml 2017-01-31 14:13:11 +0000
698@@ -46,6 +46,8 @@
699 enabled: menuData ? menuData.sensitive : false
700
701 action: Action {
702+ enabled: root.enabled
703+
704 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
705 text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
706 checkable: menuData.isCheck || menuData.isRadio
707@@ -137,14 +139,7 @@
708 theme.palette.disabled.backgroundSecondaryText
709
710 visible: root.hasSubmenu
711- name: "chevron"
712- }
713- }
714-
715- MouseArea {
716- anchors.fill: parent
717- onClicked: {
718- root.trigger(action && action.checkable ? !action.checked : undefined);
719+ name: "toolkit_chevron-ltr_2gu"
720 }
721 }
722 }
723
724=== modified file 'qml/ApplicationMenus/MenuNavigator.qml'
725--- qml/ApplicationMenus/MenuNavigator.qml 2016-11-28 13:44:30 +0000
726+++ qml/ApplicationMenus/MenuNavigator.qml 2017-01-31 14:13:11 +0000
727@@ -18,8 +18,10 @@
728
729 QtObject {
730 property Item itemView: null
731+ property bool hasOverflow: false
732
733 signal select(int index)
734+ signal overflow()
735
736 function selectNext(currentIndex) {
737 var menu;
738@@ -32,6 +34,11 @@
739 break;
740 }
741 newIndex++;
742+
743+ if (hasOverflow && newIndex === itemView.count) {
744+ overflow()
745+ break;
746+ }
747 }
748 } else if (currentIndex !== -1 && itemView.count > 1) {
749 var startIndex = (currentIndex + 1) % itemView.count;
750@@ -42,6 +49,12 @@
751 select(newIndex);
752 break;
753 }
754+
755+ if (hasOverflow && newIndex + 1 === itemView.count) {
756+ overflow()
757+ break;
758+ }
759+
760 newIndex = (newIndex + 1) % itemView.count;
761 } while (newIndex !== startIndex)
762 }
763@@ -58,12 +71,21 @@
764 break;
765 }
766 newIndex--;
767+
768+ if (hasOverflow && newIndex < 0 ) {
769+ overflow();
770+ break;
771+ }
772 }
773 } else if (currentIndex !== -1 && itemView.count > 1) {
774 var startIndex = currentIndex - 1;
775 newIndex = startIndex;
776 do {
777 if (newIndex < 0) {
778+ if (hasOverflow) {
779+ overflow();
780+ break;
781+ }
782 newIndex = itemView.count - 1;
783 }
784 menu = itemView.itemAt(newIndex);
785@@ -72,6 +94,7 @@
786 break;
787 }
788 newIndex--;
789+
790 } while (newIndex !== startIndex)
791 }
792 }
793
794=== modified file 'qml/ApplicationMenus/MenuPopup.qml'
795--- qml/ApplicationMenus/MenuPopup.qml 2017-01-18 12:08:05 +0000
796+++ qml/ApplicationMenus/MenuPopup.qml 2017-01-31 14:13:11 +0000
797@@ -16,16 +16,50 @@
798
799 import QtQuick 2.4
800 import QtQuick.Layouts 1.1
801-import QtQuick.Window 2.2
802 import Ubuntu.Components 1.3
803 import Ubuntu.Components.ListItems 1.3 as ListItems
804 import "../Components"
805+import "."
806
807 UbuntuShape {
808 id: root
809 objectName: "menu"
810 backgroundColor: theme.palette.normal.overlay
811
812+ signal childActivated()
813+
814+ // true for submenus that need to show on the other side of their parent
815+ // if they don't fit when growing right
816+ property bool substractWidth: false
817+
818+ property real desiredX
819+ x: {
820+ var dummy = visible; // force recalc when shown/hidden
821+ var parentTopLeft = parent.mapToItem(null, 0, 0);
822+ var farX = ApplicationMenusLimits.screenWidth;
823+ if (parentTopLeft.x + width + desiredX <= farX) {
824+ return desiredX;
825+ } else {
826+ if (substractWidth) {
827+ return -width;
828+ } else {
829+ return farX - parentTopLeft.x - width;
830+ }
831+ }
832+ }
833+
834+ property real desiredY
835+ y: {
836+ var dummy = visible; // force recalc when shown/hidden
837+ var parentTopLeft = parent.mapToItem(null, 0, 0);
838+ var bottomY = ApplicationMenusLimits.screenHeight;
839+ if (parentTopLeft.y + height + desiredY <= bottomY) {
840+ return desiredY;
841+ } else {
842+ return bottomY - parentTopLeft.y - height;
843+ }
844+ }
845+
846 property alias unityMenuModel: repeater.model
847
848 function show() {
849@@ -64,9 +98,9 @@
850 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
851
852 property real __minimumWidth: units.gu(20)
853- property real __maximumWidth: Screen.width * 0.7
854+ property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
855 property real __minimumHeight: units.gu(2)
856- property real __maximumHeight: Screen.height * 0.7
857+ property real __maximumHeight: ApplicationMenusLimits.screenHeight
858
859 signal dismissAll()
860
861@@ -76,6 +110,8 @@
862 } else {
863 hoveredItem = null;
864 }
865+
866+ submenuHoverTimer.stop();
867 }
868
869 onSelect: {
870@@ -147,8 +183,20 @@
871 }
872
873 MouseArea {
874+ id: previousMA
875 anchors.fill: parent
876- onPressed: {
877+ hoverEnabled: enabled
878+ onPressed: progress()
879+
880+ Timer {
881+ running: previousMA.containsMouse && !listView.atYBeginning
882+ interval: 1000
883+ repeat: true
884+ onTriggered: previousMA.progress()
885+ }
886+
887+ function progress() {
888+ console.log("progress!")
889 var item = menuColumn.childAt(0, listView.contentY);
890 if (item) {
891 var previousItem = item;
892@@ -176,10 +224,15 @@
893 contentHeight: menuColumn.height
894 interactive: height < contentHeight
895
896+ Timer {
897+ id: submenuHoverTimer
898+ interval: 225 // GTK MENU_POPUP_DELAY, Qt SH_Menu_SubMenuPopupDelay in QCommonStyle is 256
899+ onTriggered: d.currentItem.item.trigger();
900+ }
901+
902 MouseArea {
903 anchors.fill: parent
904 hoverEnabled: true
905- propagateComposedEvents: true // propogate events so we send clicks to children.
906 z: 1 // on top so we override any other hovers
907 onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
908 onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
909@@ -189,12 +242,25 @@
910
911 if (!d.hoveredItem || !d.currentItem ||
912 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
913+ submenuHoverTimer.stop();
914+
915 d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
916 if (!d.hoveredItem || !d.hoveredItem.enabled)
917- return false;
918+ return;
919 d.currentItem = d.hoveredItem;
920- }
921- return true;
922+
923+ if (!d.currentItem.__isSeparator && d.currentItem.item.hasSubmenu && d.currentItem.item.enabled) {
924+ submenuHoverTimer.start();
925+ }
926+ }
927+ }
928+
929+ onClicked: {
930+ var pos = mapToItem(listView.contentItem, mouse.x, mouse.y);
931+ var clickedItem = menuColumn.childAt(pos.x, pos.y);
932+ if (clickedItem.enabled && !clickedItem.__isSeparator) {
933+ clickedItem.item.trigger();
934+ }
935 }
936 }
937
938@@ -249,6 +315,8 @@
939 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
940
941 action.onTriggered: {
942+ submenuHoverTimer.stop();
943+
944 d.currentItem = loader;
945
946 if (hasSubmenu) {
947@@ -257,22 +325,29 @@
948 popup = submenuComponent.createObject(focusScope, {
949 objectName: loader.objectName + "-",
950 unityMenuModel: model,
951- x: Qt.binding(function() { return root.width }),
952- y: Qt.binding(function() {
953+ substractWidth: true,
954+ desiredX: Qt.binding(function() { return root.width }),
955+ desiredY: Qt.binding(function() {
956 var dummy = listView.contentY; // force a recalc on contentY change.
957 return mapToItem(container, 0, y).y;
958 })
959 });
960+ popup.retreat.connect(function() {
961+ popup.destroy();
962+ popup = null;
963+ menuItem.forceActiveFocus();
964+ });
965+ popup.childActivated.connect(function() {
966+ popup.destroy();
967+ popup = null;
968+ root.childActivated();
969+ });
970 } else if (popup) {
971 popup.visible = true;
972 }
973- popup.retreat.connect(function() {
974- popup.destroy();
975- popup = null;
976- menuItem.forceActiveFocus();
977- })
978 } else {
979 root.unityMenuModel.activate(__ownIndex);
980+ root.childActivated();
981 }
982 }
983
984@@ -349,8 +424,19 @@
985 }
986
987 MouseArea {
988+ id: nextMA
989 anchors.fill: parent
990- onPressed: {
991+ hoverEnabled: enabled
992+ onPressed: progress()
993+
994+ Timer {
995+ running: nextMA.containsMouse && !listView.atYEnd
996+ interval: 1000
997+ repeat: true
998+ onTriggered: nextMA.progress()
999+ }
1000+
1001+ function progress() {
1002 var item = menuColumn.childAt(0, listView.contentY + listView.height);
1003 if (item) {
1004 var nextItem = item;
1005@@ -375,23 +461,28 @@
1006 id: submenuLoader
1007 source: "MenuPopup.qml"
1008
1009+ property real desiredX
1010+ property real desiredY
1011+ property bool substractWidth
1012 property var unityMenuModel: null
1013 signal retreat()
1014-
1015- Binding {
1016- target: item
1017- property: "unityMenuModel"
1018- value: submenuLoader.unityMenuModel
1019- }
1020-
1021- Binding {
1022- target: item
1023- property: "objectName"
1024- value: submenuLoader.objectName + "menu"
1025+ signal childActivated()
1026+
1027+ onLoaded: {
1028+ item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
1029+ item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
1030+ item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
1031+ item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
1032+ item.substractWidth = Qt.binding(function() { return submenuLoader.substractWidth; });
1033 }
1034
1035 Keys.onLeftPressed: retreat()
1036
1037+ Connections {
1038+ target: item
1039+ onChildActivated: childActivated();
1040+ }
1041+
1042 Component.onCompleted: item.select(0);
1043 onVisibleChanged: if (visible) { item.select(0); }
1044 }
1045
1046=== added file 'qml/ApplicationMenus/qmldir'
1047--- qml/ApplicationMenus/qmldir 1970-01-01 00:00:00 +0000
1048+++ qml/ApplicationMenus/qmldir 2017-01-31 14:13:11 +0000
1049@@ -0,0 +1,1 @@
1050+singleton ApplicationMenusLimits 0.1 ApplicationMenusLimits.qml
1051
1052=== modified file 'qml/Panel/Panel.qml'
1053--- qml/Panel/Panel.qml 2017-01-09 14:10:17 +0000
1054+++ qml/Panel/Panel.qml 2017-01-31 14:13:11 +0000
1055@@ -209,6 +209,8 @@
1056 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
1057 active: __applicationMenus.model
1058
1059+ width: parent.width - windowControlButtons.width - units.gu(2) - __indicators.barWidth
1060+
1061 property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
1062
1063 sourceComponent: MenuBar {
1064@@ -385,7 +387,7 @@
1065 }
1066
1067 enabled: !applicationMenus.expanded
1068- opacity: !applicationMenus.expanded ? 1 : 0
1069+ opacity: !callHint.visible && !applicationMenus.expanded ? 1 : 0
1070 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
1071
1072 onEnabledChanged: {
1073
1074=== modified file 'tests/mocks/Utils/CMakeLists.txt'
1075--- tests/mocks/Utils/CMakeLists.txt 2016-12-21 10:20:36 +0000
1076+++ tests/mocks/Utils/CMakeLists.txt 2017-01-31 14:13:11 +0000
1077@@ -26,6 +26,7 @@
1078 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
1079 ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp
1080 ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp
1081+ ${CMAKE_SOURCE_DIR}/plugins/Utils/expressionfiltermodel.cpp
1082 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
1083 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
1084 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
1085
1086=== modified file 'tests/mocks/Utils/plugin.cpp'
1087--- tests/mocks/Utils/plugin.cpp 2017-01-03 12:16:00 +0000
1088+++ tests/mocks/Utils/plugin.cpp 2017-01-31 14:13:11 +0000
1089@@ -42,6 +42,7 @@
1090 #include <globalfunctions.h>
1091 #include <appdrawerproxymodel.h>
1092 #include <tabfocusfence.h>
1093+#include <expressionfiltermodel.h>
1094
1095 static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
1096 {
1097@@ -86,4 +87,5 @@
1098 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
1099 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
1100 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
1101+ qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel");
1102 }
1103
1104=== modified file 'tests/qmltests/ApplicationMenus/tst_MenuBar.qml'
1105--- tests/qmltests/ApplicationMenus/tst_MenuBar.qml 2017-01-24 07:41:35 +0000
1106+++ tests/qmltests/ApplicationMenus/tst_MenuBar.qml 2017-01-31 14:13:11 +0000
1107@@ -28,8 +28,8 @@
1108
1109 Item {
1110 id: root
1111- width: units.gu(100)
1112- height: units.gu(50)
1113+ width: units.gu(120)
1114+ height: units.gu(70)
1115
1116 Component.onCompleted: {
1117 QuickUtils.keyboardAttached = true;
1118@@ -51,11 +51,11 @@
1119 Rectangle {
1120 anchors {
1121 left: parent.left
1122- right: parent.right
1123 top: parent.top
1124 margins: units.gu(1)
1125 }
1126 height: units.gu(3)
1127+ width: parent.width * 2/3
1128 color: "grey"
1129
1130 MenuBar {
1131@@ -65,7 +65,7 @@
1132
1133 unityMenuModel: UnityMenuModel {
1134 id: menuBackend
1135- modelData: appMenuData.generateTestData(17,5,2,3)
1136+ modelData: appMenuData.generateTestData(10,5,2,3)
1137 }
1138 }
1139 }
1140@@ -83,12 +83,12 @@
1141
1142 function init() {
1143 menuBar.dismiss();
1144- menuBackend.modelData = appMenuData.generateTestData(5,5,2,3)
1145+ menuBackend.modelData = appMenuData.generateTestData(5,5,2,3, "menu")
1146 activatedSpy.clear();
1147 }
1148
1149 function test_mouseNavigation() {
1150- menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
1151+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
1152 wait(50) // wait for row to build
1153 var priv = findInvisibleChild(menuBar, "d");
1154
1155@@ -114,7 +114,7 @@
1156 }
1157
1158 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
1159- menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
1160+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
1161 var priv = findInvisibleChild(menuBar, "d");
1162
1163 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
1164@@ -139,7 +139,7 @@
1165 }
1166
1167 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
1168- menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
1169+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
1170 var priv = findInvisibleChild(menuBar, "d");
1171
1172 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
1173@@ -192,5 +192,46 @@
1174 keyClick(Qt.Key_F10, Qt.AltModifier);
1175 compare(priv.currentItem, menuItem1, "First enabled item should be opened");
1176 }
1177+
1178+ function test_clickOpenMenuClosesMenu() {
1179+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
1180+ var priv = findInvisibleChild(menuBar, "d");
1181+
1182+ var menuItem = findChild(menuBar, "menuBar-item0");
1183+ waitForRendering(menuItem);
1184+ mouseClick(menuItem);
1185+ compare(priv.currentItem, menuItem, "CurrentItem should be set to item 0");
1186+ compare(priv.currentItem.popupVisible, true, "Popup should be visible");
1187+
1188+ waitForRendering(menuItem);
1189+ mouseClick(menuItem);
1190+ compare(priv.currentItem, null, "CurrentItem should be null");
1191+ }
1192+
1193+ function test_overfow() {
1194+ menuBackend.modelData = appMenuData.generateTestData(5,2,0,0,"menu");
1195+
1196+ var overflow = findChild(menuBar, "overflow");
1197+ compare(overflow.visible, false, "Overflow should not be visible");
1198+
1199+ var menu = { "rowData": { "label": "Short" } };
1200+ tryCompareFunction(function() {
1201+ menuBackend.insertRow(0, menu);
1202+ wait(1);
1203+ if (overflow.visible) {
1204+ return true;
1205+ }
1206+ return false;
1207+ }, true);
1208+
1209+ tryCompareFunction(function() {
1210+ menuBackend.removeRow(0);
1211+ wait(1);
1212+ if (!overflow.visible) {
1213+ return true;
1214+ }
1215+ return false;
1216+ }, true);
1217+ }
1218 }
1219 }
1220
1221=== modified file 'tests/qmltests/ApplicationMenus/tst_MenuPopup.qml'
1222--- tests/qmltests/ApplicationMenus/tst_MenuPopup.qml 2017-01-18 12:08:14 +0000
1223+++ tests/qmltests/ApplicationMenus/tst_MenuPopup.qml 2017-01-31 14:13:11 +0000
1224@@ -223,6 +223,20 @@
1225 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);
1226 }
1227
1228+ function test_mouseHoverOpensSubMenu() {
1229+ menu.unityMenuModel.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
1230+
1231+ var menuItem = findChild(menu, "menu-item0");
1232+
1233+ var priv = findInvisibleChild(menu, "d");
1234+ priv.currentItem = menuItem;
1235+
1236+ mouseMove(menuItem, menuItem.width/2, menuItem.height/2);
1237+ verify(!menuItem.popup);
1238+
1239+ tryCompareFunction(function() { return menuItem.popup != null; }, true);
1240+ }
1241+
1242 function test_differentSizes() {
1243 var differentSizesMenu = [{
1244 "rowData": { "label": "Short" }}, {
1245
1246=== modified file 'tests/qmltests/Stage/tst_DesktopStage.qml'
1247--- tests/qmltests/Stage/tst_DesktopStage.qml 2017-01-10 14:44:29 +0000
1248+++ tests/qmltests/Stage/tst_DesktopStage.qml 2017-01-31 14:13:11 +0000
1249@@ -29,6 +29,7 @@
1250 import "../../../qml/Stage"
1251 import "../../../qml/Components"
1252 import "../../../qml/Components/PanelState"
1253+import "../../../qml/ApplicationMenus"
1254
1255 Item {
1256 id: root
1257@@ -44,6 +45,8 @@
1258 }
1259
1260 Component.onCompleted: {
1261+ ApplicationMenusLimits.screenWidth = Qt.binding( function() { return stageLoader.width; } );
1262+ ApplicationMenusLimits.screenHeight = Qt.binding( function() { return stageLoader.height; } );
1263 QuickUtils.keyboardAttached = true;
1264 theme.name = "Ubuntu.Components.Themes.SuruDark";
1265 resetGeometry();
1266@@ -695,10 +698,18 @@
1267 var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);
1268 var deco = findChild(dialerDelegate, "appWindowDecoration");
1269 verify(deco);
1270- tryCompare(deco, "maximizeButtonShown", false);
1271- mouseDoubleClick(deco);
1272- var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
1273- tryCompareFunction(function(){ return sizeBefore; }, sizeAfter);
1274+ // deco.width - units.gu(1) to make sure we're outside the "menu" area of the decoration
1275+ mouseMove(deco, deco.width - units.gu(1), deco.height/2);
1276+ var menuBarLoader = findChild(deco, "menuBarLoader");
1277+ tryCompare(menuBarLoader.item, "visible", true);
1278+ mouseDoubleClick(deco, deco.width - units.gu(1), deco.height/2)
1279+ expectFail("", "Double click should not maximize in a size restricted window");
1280+ tryCompareFunction(function() {
1281+ var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
1282+ return sizeAfter.width > sizeBefore.width && sizeAfter.height > sizeBefore.height;
1283+ },
1284+ true
1285+ );
1286
1287 // remove restrictions, the maximize button should again be visible
1288 dialerDelegate.surface.setMaximumWidth(0);
1289@@ -706,6 +717,29 @@
1290 tryCompare(dialerMaximizeButton, "visible", true);
1291 }
1292
1293+ function test_doubleClickMaximizes() {
1294+ var dialerDelegate = startApplication("dialer-app");
1295+
1296+ var dialerMaximizeButton = findChild(dialerDelegate, "maximizeWindowButton");
1297+ tryCompare(dialerMaximizeButton, "visible", true);
1298+
1299+ // try double clicking the decoration, should maximize it
1300+ var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);
1301+ var deco = findChild(dialerDelegate, "appWindowDecoration");
1302+ verify(deco);
1303+ // deco.width - units.gu(1) to make sure we're outside the "menu" area of the decoration
1304+ mouseMove(deco, deco.width - units.gu(1), deco.height/2);
1305+ var menuBarLoader = findChild(deco, "menuBarLoader");
1306+ tryCompare(menuBarLoader.item, "visible", true);
1307+ mouseDoubleClick(deco, deco.width - units.gu(1), deco.height/2);
1308+ tryCompareFunction(function() {
1309+ var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
1310+ return sizeAfter.width > sizeBefore.width && sizeAfter.height > sizeBefore.height;
1311+ },
1312+ true
1313+ );
1314+ }
1315+
1316 function test_canMoveWindowWithLeftMouseButtonOnly_data() {
1317 return [
1318 {tag: "left mouse button", button: Qt.LeftButton },
1319@@ -830,5 +864,144 @@
1320 mouseRelease(decoration);
1321 tryCompare(Mir, "cursorName", "");
1322 }
1323+
1324+ function test_menuPositioning_data() {
1325+ return [
1326+ {tag: "good",
1327+ windowPosition: Qt.point(units.gu(10), units.gu(10))
1328+ },
1329+ {tag: "collides right",
1330+ windowPosition: Qt.point(units.gu(100), units.gu(10)),
1331+ minimumXDifference: units.gu(8)
1332+ },
1333+ {tag: "collides bottom",
1334+ windowPosition: Qt.point(units.gu(10), units.gu(80)),
1335+ minimumYDifference: units.gu(7)
1336+ },
1337+ ]
1338+ }
1339+
1340+ function test_menuPositioning(data) {
1341+ var appDelegate = startApplication("dialer-app");
1342+ appDelegate.windowedX = data.windowPosition.x;
1343+ appDelegate.windowedY = data.windowPosition.y;
1344+
1345+ var menuItem = findChild(appDelegate, "menuBar-item3");
1346+ menuItem.show();
1347+
1348+ var menu = findChild(appDelegate, "menuBar-item3-menu");
1349+ tryCompare(menu, "visible", true);
1350+
1351+ var normalPositioningX = menuItem.x - units.gu(1);
1352+ var normalPositioningY = menuItem.height;
1353+
1354+ // We do this fuzzy checking because otherwise we would be duplicating the code
1355+ // that calculates the coordinates and any bug it may have, what we want is really
1356+ // to check that on collision with the border the menu is shifted substantially
1357+ if (data.minimumXDifference) {
1358+ verify(menu.x < normalPositioningX - data.minimumXDifference);
1359+ } else {
1360+ compare(menu.x, normalPositioningX);
1361+ }
1362+
1363+ if (data.minimumYDifference) {
1364+ verify(menu.y < normalPositioningY - data.minimumYDifference);
1365+ } else {
1366+ compare(menu.y, normalPositioningY);
1367+ }
1368+ }
1369+
1370+ function test_submenuPositioning_data() {
1371+ return [
1372+ {tag: "good",
1373+ windowPosition: Qt.point(units.gu(10), units.gu(10))
1374+ },
1375+ {tag: "collides right",
1376+ windowPosition: Qt.point(units.gu(100), units.gu(10)),
1377+ minimumXDifference: units.gu(35)
1378+ },
1379+ {tag: "collides bottom",
1380+ windowPosition: Qt.point(units.gu(10), units.gu(80)),
1381+ minimumYDifference: units.gu(8)
1382+ },
1383+ ]
1384+ }
1385+
1386+ function test_submenuPositioning(data) {
1387+ var appDelegate = startApplication("dialer-app");
1388+ appDelegate.windowedX = data.windowPosition.x;
1389+ appDelegate.windowedY = data.windowPosition.y;
1390+
1391+ var menuItem = findChild(appDelegate, "menuBar-item3");
1392+ menuItem.show();
1393+
1394+ var menu = findChild(appDelegate, "menuBar-item3-menu");
1395+ menuItem = findChild(menu, "menuBar-item3-menu-item3-actionItem");
1396+ tryCompare(menuItem, "visible", true);
1397+ mouseMove(menuItem);
1398+ mouseClick(menuItem);
1399+
1400+ menu = findChild(appDelegate, "menuBar-item3-menu-item3-menu");
1401+
1402+ var normalPositioningX = menuItem.width;
1403+ var normalPositioningY = menuItem.parent.y;
1404+
1405+ // We do this fuzzy checking because otherwise we would be duplicating the code
1406+ // that calculates the coordinates and any bug it may have, what we want is really
1407+ // to check that on collision with the border the menu is shifted substantially
1408+ if (data.minimumXDifference) {
1409+ verify(menu.x < normalPositioningX - data.minimumXDifference);
1410+ } else {
1411+ compare(menu.x, normalPositioningX);
1412+ }
1413+
1414+ if (data.minimumYDifference) {
1415+ verify(menu.y < normalPositioningY - data.minimumYDifference);
1416+ } else {
1417+ compare(menu.y, normalPositioningY);
1418+ }
1419+ }
1420+
1421+ function test_menuDoubleClickNoMaximizeWindowBehind() {
1422+ var appDelegate1 = startApplication("dialer-app");
1423+ var appDelegate2 = startApplication("gmail-webapp");
1424+
1425+ // Open menu
1426+ var menuItem = findChild(appDelegate2, "menuBar-item3");
1427+ menuItem.show();
1428+ var menu = findChild(appDelegate2, "menuBar-item3-menu");
1429+ menuItem = findChild(menu, "menuBar-item3-menu-item3-actionItem");
1430+ tryCompare(menuItem, "visible", true);
1431+
1432+ // Place the other application window decoration under the menu
1433+ var pos = menuItem.mapToItem(null, menuItem.width / 2, menuItem.height / 2);
1434+ appDelegate1.windowedX = pos.x - appDelegate1.width / 2;
1435+ appDelegate1.windowedY = pos.y - units.gu(1);
1436+
1437+ var previousWindowState = appDelegate1.windowState;
1438+
1439+ mouseMove(menuItem);
1440+ mouseDoubleClickSequence(menuItem);
1441+
1442+ expectFail("", "Double clicking a menu should not change the window below");
1443+ tryCompareFunction(function() { return appDelegate1.windowState != previousWindowState; }, true);
1444+ }
1445+
1446+ function test_openMenuEatsHoverOutsideIt() {
1447+ var appDelegate = startApplication("gmail-webapp");
1448+
1449+ var wd = findChild(appDelegate, "appWindowDecoration");
1450+ var closeButton = findChild(wd, "closeWindowButton");
1451+
1452+ // Open menu
1453+ var menuItem = findChild(appDelegate, "menuBar-item3");
1454+ menuItem.show();
1455+ var menu = findChild(appDelegate, "menuBar-item3-menu");
1456+ tryCompare(menu, "visible", true);
1457+
1458+ mouseMove(closeButton, closeButton.width/2, closeButton.height/2);
1459+ expectFail("", "Hovering the window controls should be ignored when the menu is open");
1460+ tryCompare(closeButton, "containsMouse", true);
1461+ }
1462 }
1463 }

Subscribers

People subscribed via source and target branches