Merge lp:~nick-dedekind/unity8/menu.overflow into lp:unity8
- menu.overflow
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~nick-dedekind/unity8/menu.overflow |
Merge into: | lp:unity8 |
Prerequisite: | lp:~nick-dedekind/unity8/menu.width.fix |
Diff against target: |
1066 lines (+553/-188) 12 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/MenuBar.qml (+312/-168) qml/ApplicationMenus/MenuItem.qml (+3/-1) qml/ApplicationMenus/MenuNavigator.qml (+23/-0) qml/ApplicationMenus/MenuPopup.qml (+46/-10) 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 (+69/-8) |
To merge this branch: | bzr merge lp:~nick-dedekind/unity8/menu.overflow |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Unity8 CI Bot | continuous-integration | Needs Fixing | |
Lukáš Tinkl (community) | Needs Fixing | ||
Review via email: mp+315449@code.launchpad.net |
This proposal has been superseded by a proposal from 2017-01-30.
Commit message
Added overflow support to application menus.
Hover timer for auto-scrolling menu popup overflow.
Description of the change
* Are there any related MPs required for this MP to build/function as expected? Please list.
N/A
* 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?
Yes
Unity8 CI Bot (unity8-ci-bot) wrote : | # |
Lukáš Tinkl (lukas-kde) wrote : | # |
/<<BUILDDIR>
Unity8 CI Bot (unity8-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:2778
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Unity8 CI Bot (unity8-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:2779
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Unity8 CI Bot (unity8-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:2780
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Unity8 CI Bot (unity8-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:2781
https:/
Executed test runs:
SUCCESS: https:/
UNSTABLE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Michał Sawicz (saviq) wrote : | # |
MenuNavigator.qml: bad whitespace in line 83
Albert Astals Cid (aacid) wrote : | # |
Text conflict in plugins/
Text conflict in plugins/
Text conflict in tests/mocks/
Text conflict in tests/mocks/
Text conflict in tests/qmltests/
5 conflicts encountered.
Nick Dedekind (nick-dedekind) wrote : | # |
> Text conflict in plugins/
> Text conflict in plugins/
> Text conflict in tests/mocks/
> Text conflict in tests/mocks/
> Text conflict in tests/qmltests/
> 5 conflicts encountered.
merged
Unity8 CI Bot (unity8-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:2782
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
UNSTABLE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Unity8 CI Bot (unity8-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:2782
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Unity8 CI Bot (unity8-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:2785
https:/
Executed test runs:
SUCCESS: https:/
UNSTABLE: https:/
UNSTABLE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
- 2786. By Nick Dedekind
-
removed height change
- 2787. By Nick Dedekind
-
moved shouldDisplay connection
- 2788. By Nick Dedekind
-
include panel height in max height calc
- 2789. By Nick Dedekind
-
readd tests
- 2790. By Nick Dedekind
-
review comments
- 2791. By Nick Dedekind
-
merged with parent
Unmerged revisions
Preview Diff
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-30 14:46:20 +0000 | |||
4 | @@ -35,6 +35,7 @@ | |||
5 | 35 | globalfunctions.cpp | 35 | globalfunctions.cpp |
6 | 36 | URLDispatcher.cpp | 36 | URLDispatcher.cpp |
7 | 37 | tabfocusfence.cpp | 37 | tabfocusfence.cpp |
8 | 38 | expressionfiltermodel.cpp | ||
9 | 38 | plugin.cpp | 39 | plugin.cpp |
10 | 39 | ) | 40 | ) |
11 | 40 | 41 | ||
12 | 41 | 42 | ||
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-30 14:46:20 +0000 | |||
16 | @@ -0,0 +1,49 @@ | |||
17 | 1 | /* | ||
18 | 2 | * Copyright (C) 2017 Canonical, Ltd. | ||
19 | 3 | * | ||
20 | 4 | * This program is free software; you can redistribute it and/or modify | ||
21 | 5 | * it under the terms of the GNU General Public License as published by | ||
22 | 6 | * the Free Software Foundation; version 3. | ||
23 | 7 | * | ||
24 | 8 | * This program is distributed in the hope that it will be useful, | ||
25 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
26 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
27 | 11 | * GNU General Public License for more details. | ||
28 | 12 | * | ||
29 | 13 | * You should have received a copy of the GNU General Public License | ||
30 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
31 | 15 | */ | ||
32 | 16 | |||
33 | 17 | #include "expressionfiltermodel.h" | ||
34 | 18 | |||
35 | 19 | ExpressionFilterModel::ExpressionFilterModel(QObject *parent) | ||
36 | 20 | : UnitySortFilterProxyModelQML(parent) | ||
37 | 21 | { | ||
38 | 22 | } | ||
39 | 23 | |||
40 | 24 | QJSValue ExpressionFilterModel::matchExpression() const | ||
41 | 25 | { | ||
42 | 26 | return m_matchExpression; | ||
43 | 27 | } | ||
44 | 28 | |||
45 | 29 | void ExpressionFilterModel::setMatchExpression(const QJSValue &value) | ||
46 | 30 | { | ||
47 | 31 | m_matchExpression = value; | ||
48 | 32 | invalidateFilter(); | ||
49 | 33 | } | ||
50 | 34 | |||
51 | 35 | bool | ||
52 | 36 | ExpressionFilterModel::filterAcceptsRow(int sourceRow, | ||
53 | 37 | const QModelIndex &sourceParent) const | ||
54 | 38 | { | ||
55 | 39 | if (m_matchExpression.isCallable()) { | ||
56 | 40 | QJSValueList args; | ||
57 | 41 | args << sourceRow; | ||
58 | 42 | QJSValue ret = m_matchExpression.call(args); | ||
59 | 43 | if (ret.isBool()) { | ||
60 | 44 | return ret.toBool(); | ||
61 | 45 | } | ||
62 | 46 | } | ||
63 | 47 | |||
64 | 48 | return UnitySortFilterProxyModelQML::filterAcceptsRow(sourceRow, sourceParent); | ||
65 | 49 | } | ||
66 | 0 | 50 | ||
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-30 14:46:20 +0000 | |||
70 | @@ -0,0 +1,42 @@ | |||
71 | 1 | /* | ||
72 | 2 | * Copyright (C) 2017 Canonical, Ltd. | ||
73 | 3 | * | ||
74 | 4 | * This program is free software; you can redistribute it and/or modify | ||
75 | 5 | * it under the terms of the GNU General Public License as published by | ||
76 | 6 | * the Free Software Foundation; version 3. | ||
77 | 7 | * | ||
78 | 8 | * This program is distributed in the hope that it will be useful, | ||
79 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
80 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
81 | 11 | * GNU General Public License for more details. | ||
82 | 12 | * | ||
83 | 13 | * You should have received a copy of the GNU General Public License | ||
84 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
85 | 15 | */ | ||
86 | 16 | |||
87 | 17 | #ifndef EXPRESSIONFILTERMODEL_H | ||
88 | 18 | #define EXPRESSIONFILTERMODEL_H | ||
89 | 19 | |||
90 | 20 | #include "unitysortfilterproxymodelqml.h" | ||
91 | 21 | #include <QJSValue> | ||
92 | 22 | |||
93 | 23 | class ExpressionFilterModel : public UnitySortFilterProxyModelQML | ||
94 | 24 | { | ||
95 | 25 | Q_OBJECT | ||
96 | 26 | Q_PROPERTY(QJSValue matchExpression READ matchExpression WRITE setMatchExpression NOTIFY matchExpressionChanged) | ||
97 | 27 | public: | ||
98 | 28 | explicit ExpressionFilterModel(QObject *parent = 0); | ||
99 | 29 | |||
100 | 30 | bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; | ||
101 | 31 | |||
102 | 32 | QJSValue matchExpression() const; | ||
103 | 33 | void setMatchExpression(const QJSValue& value); | ||
104 | 34 | |||
105 | 35 | Q_SIGNALS: | ||
106 | 36 | void matchExpressionChanged(); | ||
107 | 37 | |||
108 | 38 | private: | ||
109 | 39 | mutable QJSValue m_matchExpression; | ||
110 | 40 | }; | ||
111 | 41 | |||
112 | 42 | #endif // EXPRESSIONFILTERMODEL_H | ||
113 | 0 | 43 | ||
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-30 14:46:20 +0000 | |||
117 | @@ -41,6 +41,7 @@ | |||
118 | 41 | #include "URLDispatcher.h" | 41 | #include "URLDispatcher.h" |
119 | 42 | #include "appdrawerproxymodel.h" | 42 | #include "appdrawerproxymodel.h" |
120 | 43 | #include "tabfocusfence.h" | 43 | #include "tabfocusfence.h" |
121 | 44 | #include "expressionfiltermodel.h" | ||
122 | 44 | 45 | ||
123 | 45 | static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine) | 46 | static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine) |
124 | 46 | { | 47 | { |
125 | @@ -86,4 +87,5 @@ | |||
126 | 86 | qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher"); | 87 | qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher"); |
127 | 87 | qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel"); | 88 | qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel"); |
128 | 88 | qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence"); | 89 | qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence"); |
129 | 90 | qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel"); | ||
130 | 89 | } | 91 | } |
131 | 90 | 92 | ||
132 | === modified file 'qml/ApplicationMenus/MenuBar.qml' | |||
133 | --- qml/ApplicationMenus/MenuBar.qml 2017-01-17 09:45:22 +0000 | |||
134 | +++ qml/ApplicationMenus/MenuBar.qml 2017-01-30 14:46:20 +0000 | |||
135 | @@ -24,12 +24,13 @@ | |||
136 | 24 | id: root | 24 | id: root |
137 | 25 | objectName: "menuBar" | 25 | objectName: "menuBar" |
138 | 26 | 26 | ||
139 | 27 | // set from outside | ||
140 | 27 | property alias unityMenuModel: rowRepeater.model | 28 | property alias unityMenuModel: rowRepeater.model |
141 | 29 | property bool enableKeyFilter: false | ||
142 | 30 | property real overflowWidth: width | ||
143 | 28 | 31 | ||
144 | 32 | // read from outside | ||
145 | 29 | readonly property bool valid: rowRepeater.count > 0 | 33 | readonly property bool valid: rowRepeater.count > 0 |
146 | 30 | |||
147 | 31 | property bool enableKeyFilter: false | ||
148 | 32 | |||
149 | 33 | readonly property bool showRequested: d.longAltPressed || d.currentItem != null | 34 | readonly property bool showRequested: d.longAltPressed || d.currentItem != null |
150 | 34 | 35 | ||
151 | 35 | implicitWidth: row.width | 36 | implicitWidth: row.width |
152 | @@ -78,186 +79,325 @@ | |||
153 | 78 | onPressed: d.dismissAll() | 79 | onPressed: d.dismissAll() |
154 | 79 | } | 80 | } |
155 | 80 | 81 | ||
318 | 81 | Item { | 82 | Row { |
319 | 82 | id: clippingItem | 83 | id: row |
320 | 83 | 84 | spacing: units.gu(2) | |
321 | 84 | height: root.height | 85 | height: parent.height |
322 | 85 | width: root.width | 86 | |
323 | 86 | clip: true | 87 | ActionContext { |
324 | 87 | 88 | id: menuBarContext | |
325 | 88 | Row { | 89 | objectName: "barContext" |
326 | 89 | id: row | 90 | active: !d.currentItem && enableKeyFilter |
327 | 90 | spacing: units.gu(2) | 91 | } |
328 | 91 | height: parent.height | 92 | |
329 | 92 | 93 | Connections { | |
330 | 93 | ActionContext { | 94 | target: root.unityMenuModel |
331 | 94 | id: menuBarContext | 95 | onModelReset: d.firstInvisibleIndex = undefined |
332 | 95 | objectName: "barContext" | 96 | } |
333 | 96 | active: !d.currentItem && enableKeyFilter | 97 | |
334 | 97 | } | 98 | Repeater { |
335 | 98 | 99 | id: rowRepeater | |
336 | 99 | Repeater { | 100 | |
337 | 100 | id: rowRepeater | 101 | onItemAdded: d.recalcFirstInvisibleIndexAdded(index, item) |
338 | 101 | 102 | onCountChanged: d.recalcFirstInvisibleIndex() | |
339 | 102 | Item { | 103 | |
340 | 103 | id: visualItem | 104 | Item { |
341 | 104 | objectName: root.objectName + "-item" + __ownIndex | 105 | id: visualItem |
342 | 105 | 106 | objectName: root.objectName + "-item" + __ownIndex | |
343 | 106 | readonly property int __ownIndex: index | 107 | |
344 | 107 | property Item __popup: null; | 108 | readonly property int __ownIndex: index |
345 | 108 | property bool popupVisible: __popup && __popup.visible | 109 | property Item __popup: null; |
346 | 109 | 110 | property bool popupVisible: __popup && __popup.visible | |
347 | 110 | implicitWidth: column.implicitWidth | 111 | property bool shouldDisplay: x + width + ((__ownIndex < rowRepeater.count-1) ? units.gu(2) : 0) < |
348 | 111 | implicitHeight: row.height | 112 | root.overflowWidth - ((__ownIndex < rowRepeater.count-1) ? overflowButton.width : 0) |
349 | 112 | enabled: model.sensitive | 113 | |
350 | 113 | 114 | implicitWidth: column.implicitWidth | |
351 | 114 | function show() { | 115 | implicitHeight: row.height |
352 | 115 | if (!__popup) { | 116 | enabled: model.sensitive && shouldDisplay |
353 | 116 | __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" }); | 117 | opacity: shouldDisplay ? 1 : 0 |
354 | 117 | // force the current item to be the newly popped up menu | 118 | |
355 | 118 | } else { | 119 | function show() { |
356 | 119 | __popup.show(); | 120 | if (!__popup) { |
357 | 120 | } | 121 | __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" }); |
358 | 121 | d.currentItem = visualItem; | 122 | __popup.childActivated.connect(dismiss); |
359 | 122 | } | 123 | // force the current item to be the newly popped up menu |
360 | 123 | function hide() { | 124 | } else { |
361 | 124 | if (__popup) { | 125 | __popup.show(); |
362 | 125 | __popup.hide(); | 126 | } |
363 | 126 | 127 | d.currentItem = visualItem; | |
364 | 127 | if (d.currentItem === visualItem) { | 128 | } |
365 | 128 | d.currentItem = null; | 129 | function hide() { |
366 | 129 | } | 130 | if (__popup) { |
367 | 130 | } | 131 | __popup.hide(); |
368 | 131 | } | 132 | |
369 | 132 | function dismiss() { | 133 | if (d.currentItem === visualItem) { |
370 | 133 | if (__popup) { | 134 | d.currentItem = null; |
371 | 134 | __popup.destroy(); | 135 | } |
372 | 135 | __popup = null; | 136 | } |
373 | 136 | 137 | } | |
374 | 137 | if (d.currentItem === visualItem) { | 138 | function dismiss() { |
375 | 138 | d.currentItem = null; | 139 | if (__popup) { |
376 | 139 | } | 140 | __popup.destroy(); |
377 | 140 | } | 141 | __popup = null; |
378 | 141 | } | 142 | |
379 | 142 | 143 | if (d.currentItem === visualItem) { | |
380 | 143 | Connections { | 144 | d.currentItem = null; |
381 | 144 | target: d | 145 | } |
382 | 145 | onDismissAll: visualItem.dismiss() | 146 | } |
383 | 146 | } | 147 | } |
384 | 147 | 148 | ||
385 | 148 | Component { | 149 | onVisibleChanged: { |
386 | 149 | id: menuComponent | 150 | if (!visible && __popup) dismiss(); |
387 | 150 | MenuPopup { | 151 | } |
388 | 151 | x: visualItem.x - units.gu(1) | 152 | |
389 | 152 | anchors.top: parent.bottom | 153 | Component.onCompleted: { |
390 | 153 | unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex) | 154 | shouldDisplayChanged.connect(function() { |
391 | 154 | 155 | if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) { | |
392 | 155 | Component.onCompleted: reset(); | 156 | d.recalcFirstInvisibleIndex(); |
393 | 156 | } | 157 | } |
394 | 157 | } | 158 | }); |
395 | 158 | 159 | } | |
396 | 159 | RowLayout { | 160 | |
397 | 160 | id: column | 161 | Connections { |
398 | 161 | spacing: units.gu(1) | 162 | target: d |
399 | 162 | anchors { | 163 | onDismissAll: visualItem.dismiss() |
400 | 163 | centerIn: parent | 164 | } |
401 | 164 | } | 165 | |
402 | 165 | 166 | Component { | |
403 | 166 | Icon { | 167 | id: menuComponent |
404 | 167 | Layout.preferredWidth: units.gu(2) | 168 | MenuPopup { |
405 | 168 | Layout.preferredHeight: units.gu(2) | 169 | x: visualItem.x - units.gu(1) |
406 | 169 | Layout.alignment: Qt.AlignVCenter | 170 | anchors.top: parent.bottom |
407 | 170 | 171 | unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex) | |
408 | 171 | visible: model.icon || false | 172 | |
409 | 172 | source: model.icon || "" | 173 | Component.onCompleted: reset(); |
410 | 173 | } | 174 | } |
411 | 174 | 175 | } | |
412 | 175 | ActionItem { | 176 | |
413 | 176 | id: actionItem | 177 | RowLayout { |
414 | 177 | width: _title.width | 178 | id: column |
415 | 178 | height: _title.height | 179 | spacing: units.gu(1) |
416 | 179 | 180 | anchors { | |
417 | 180 | action: Action { | 181 | centerIn: parent |
418 | 181 | // FIXME - SDK Action:text modifies menu text with html underline for mnemonic | 182 | } |
419 | 182 | text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "") | 183 | |
420 | 183 | 184 | Icon { | |
421 | 184 | onTriggered: { | 185 | Layout.preferredWidth: units.gu(2) |
422 | 185 | visualItem.show(); | 186 | Layout.preferredHeight: units.gu(2) |
423 | 186 | } | 187 | Layout.alignment: Qt.AlignVCenter |
424 | 187 | } | 188 | |
425 | 188 | 189 | visible: model.icon || false | |
426 | 189 | Label { | 190 | source: model.icon || "" |
427 | 190 | id: _title | 191 | } |
428 | 191 | text: actionItem.text | 192 | |
429 | 192 | horizontalAlignment: Text.AlignLeft | 193 | ActionItem { |
430 | 193 | color: enabled ? "white" : "#5d5d5d" | 194 | id: actionItem |
431 | 194 | } | 195 | width: _title.width |
432 | 195 | } | 196 | height: _title.height |
433 | 196 | } | 197 | |
434 | 197 | } // Item ( delegate ) | 198 | action: Action { |
435 | 198 | } // Repeater | 199 | enabled: visualItem.enabled |
436 | 199 | } // Row | 200 | // FIXME - SDK Action:text modifies menu text with html underline for mnemonic |
437 | 200 | 201 | text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "") | |
438 | 201 | MouseArea { | 202 | |
439 | 202 | anchors.fill: parent | 203 | onTriggered: { |
440 | 203 | hoverEnabled: d.currentItem | 204 | visualItem.show(); |
441 | 204 | 205 | } | |
442 | 205 | onEntered: { | 206 | } |
443 | 206 | if (d.currentItem) { | 207 | |
444 | 207 | updateCurrentItemFromPosition(Qt.point(mouseX, mouseY)) | 208 | Label { |
445 | 208 | } | 209 | id: _title |
446 | 209 | } | 210 | text: actionItem.text |
447 | 210 | onPositionChanged: { | 211 | horizontalAlignment: Text.AlignLeft |
448 | 211 | if (d.currentItem) { | 212 | color: enabled ? "white" : "#5d5d5d" |
449 | 212 | updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y)) | 213 | } |
450 | 213 | } | 214 | } |
451 | 214 | } | 215 | } |
452 | 215 | onClicked: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y)) | 216 | } // Item ( delegate ) |
453 | 216 | 217 | } // Repeater | |
454 | 217 | function updateCurrentItemFromPosition(point) { | 218 | } // Row |
455 | 218 | var pos = mapToItem(row, point.x, point.y); | 219 | |
456 | 219 | 220 | MouseArea { | |
457 | 220 | if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) { | 221 | anchors.fill: parent |
458 | 221 | d.hoveredItem = row.childAt(pos.x, pos.y); | 222 | hoverEnabled: d.currentItem |
459 | 222 | if (!d.hoveredItem || !d.hoveredItem.enabled) | 223 | |
460 | 223 | return false; | 224 | onEntered: { |
461 | 224 | if (d.currentItem != d.hoveredItem) { | 225 | if (d.currentItem) { |
462 | 225 | d.currentItem = d.hoveredItem; | 226 | updateCurrentItemFromPosition(Qt.point(mouseX, mouseY)) |
463 | 226 | } | 227 | } |
464 | 227 | } | 228 | } |
465 | 228 | return true; | 229 | onPositionChanged: { |
466 | 229 | } | 230 | if (d.currentItem) { |
467 | 230 | } | 231 | updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y)) |
468 | 231 | 232 | } | |
469 | 232 | Rectangle { | 233 | } |
470 | 233 | id: underline | 234 | onClicked: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y)) |
471 | 234 | anchors { | 235 | |
472 | 235 | bottom: row.bottom | 236 | function updateCurrentItemFromPosition(point) { |
473 | 236 | } | 237 | var pos = mapToItem(row, point.x, point.y); |
474 | 237 | x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0 | 238 | |
475 | 238 | width: d.currentItem ? d.currentItem.width + units.gu(2) : 0 | 239 | if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) { |
476 | 239 | height: units.dp(4) | 240 | d.hoveredItem = row.childAt(pos.x, pos.y); |
477 | 240 | color: UbuntuColors.orange | 241 | if (!d.hoveredItem || !d.hoveredItem.enabled) |
478 | 241 | visible: d.currentItem | 242 | return; |
479 | 242 | } | 243 | if (d.currentItem != d.hoveredItem) { |
480 | 244 | d.currentItem = d.hoveredItem; | ||
481 | 245 | } | ||
482 | 246 | } | ||
483 | 247 | } | ||
484 | 248 | } | ||
485 | 249 | |||
486 | 250 | MouseArea { | ||
487 | 251 | id: overflowButton | ||
488 | 252 | objectName: "overflow" | ||
489 | 253 | |||
490 | 254 | hoverEnabled: d.currentItem | ||
491 | 255 | onEntered: d.currentItem = this | ||
492 | 256 | onPositionChanged: d.currentItem = this | ||
493 | 257 | onClicked: d.currentItem = this | ||
494 | 258 | |||
495 | 259 | property Item __popup: null; | ||
496 | 260 | property bool popupVisible: __popup && __popup.visible | ||
497 | 261 | property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null | ||
498 | 262 | |||
499 | 263 | visible: d.firstInvisibleIndex != undefined | ||
500 | 264 | x: firstInvisibleItem ? firstInvisibleItem.x : 0 | ||
501 | 265 | |||
502 | 266 | height: parent.height | ||
503 | 267 | width: units.gu(4) | ||
504 | 268 | |||
505 | 269 | onVisibleChanged: { | ||
506 | 270 | if (!visible && __popup) dismiss(); | ||
507 | 271 | } | ||
508 | 272 | |||
509 | 273 | Icon { | ||
510 | 274 | id: icon | ||
511 | 275 | width: units.gu(2) | ||
512 | 276 | height: units.gu(2) | ||
513 | 277 | anchors.centerIn: parent | ||
514 | 278 | color: theme.palette.normal.overlayText | ||
515 | 279 | name: "toolkit_chevron-down_2gu" | ||
516 | 280 | } | ||
517 | 281 | |||
518 | 282 | function show() { | ||
519 | 283 | if (!__popup) { | ||
520 | 284 | __popup = overflowComponent.createObject(root, { objectName: overflowButton.objectName + "-menu" }); | ||
521 | 285 | // force the current item to be the newly popped up menu | ||
522 | 286 | } else { | ||
523 | 287 | __popup.show(); | ||
524 | 288 | } | ||
525 | 289 | d.currentItem = overflowButton; | ||
526 | 290 | } | ||
527 | 291 | function hide() { | ||
528 | 292 | if (__popup) { | ||
529 | 293 | __popup.hide(); | ||
530 | 294 | |||
531 | 295 | if (d.currentItem === overflowButton) { | ||
532 | 296 | d.currentItem = null; | ||
533 | 297 | } | ||
534 | 298 | } | ||
535 | 299 | } | ||
536 | 300 | function dismiss() { | ||
537 | 301 | if (__popup) { | ||
538 | 302 | __popup.destroy(); | ||
539 | 303 | __popup = null; | ||
540 | 304 | |||
541 | 305 | if (d.currentItem === overflowButton) { | ||
542 | 306 | d.currentItem = null; | ||
543 | 307 | } | ||
544 | 308 | } | ||
545 | 309 | } | ||
546 | 310 | |||
547 | 311 | Connections { | ||
548 | 312 | target: d | ||
549 | 313 | onDismissAll: overflowButton.dismiss() | ||
550 | 314 | } | ||
551 | 315 | |||
552 | 316 | Component { | ||
553 | 317 | id: overflowComponent | ||
554 | 318 | MenuPopup { | ||
555 | 319 | id: overflowPopup | ||
556 | 320 | x: overflowButton.x - units.gu(1) | ||
557 | 321 | anchors.top: parent.bottom | ||
558 | 322 | unityMenuModel: overflowModel | ||
559 | 323 | |||
560 | 324 | ExpressionFilterModel { | ||
561 | 325 | id: overflowModel | ||
562 | 326 | sourceModel: root.unityMenuModel | ||
563 | 327 | matchExpression: function(index) { | ||
564 | 328 | if (d.firstInvisibleIndex === undefined) return false; | ||
565 | 329 | return index >= d.firstInvisibleIndex; | ||
566 | 330 | } | ||
567 | 331 | |||
568 | 332 | function submenu(index) { | ||
569 | 333 | return sourceModel.submenu(mapRowToSource(index)); | ||
570 | 334 | } | ||
571 | 335 | function activate(index) { | ||
572 | 336 | return sourceModel.activate(mapRowToSource(index)); | ||
573 | 337 | } | ||
574 | 338 | } | ||
575 | 339 | |||
576 | 340 | Connections { | ||
577 | 341 | target: d | ||
578 | 342 | onFirstInvisibleIndexChanged: overflowModel.invalidate() | ||
579 | 343 | } | ||
580 | 344 | } | ||
581 | 345 | } | ||
582 | 346 | } | ||
583 | 347 | |||
584 | 348 | Rectangle { | ||
585 | 349 | id: underline | ||
586 | 350 | anchors { | ||
587 | 351 | bottom: row.bottom | ||
588 | 352 | } | ||
589 | 353 | x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0 | ||
590 | 354 | width: d.currentItem ? d.currentItem.width + units.gu(2) : 0 | ||
591 | 355 | height: units.dp(4) | ||
592 | 356 | color: UbuntuColors.orange | ||
593 | 357 | visible: d.currentItem | ||
594 | 243 | } | 358 | } |
595 | 244 | 359 | ||
596 | 245 | MenuNavigator { | 360 | MenuNavigator { |
597 | 246 | id: d | 361 | id: d |
598 | 247 | objectName: "d" | 362 | objectName: "d" |
599 | 248 | itemView: rowRepeater | 363 | itemView: rowRepeater |
600 | 364 | hasOverflow: overflowButton.visible | ||
601 | 249 | 365 | ||
602 | 250 | property Item currentItem: null | 366 | property Item currentItem: null |
603 | 251 | property Item hoveredItem: null | 367 | property Item hoveredItem: null |
604 | 252 | property Item prevCurrentItem: null | 368 | property Item prevCurrentItem: null |
605 | 253 | |||
606 | 254 | readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1 | ||
607 | 255 | |||
608 | 256 | property bool altPressed: false | 369 | property bool altPressed: false |
609 | 257 | property bool longAltPressed: false | 370 | property bool longAltPressed: false |
610 | 371 | property var firstInvisibleIndex: undefined | ||
611 | 372 | |||
612 | 373 | readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1 | ||
613 | 258 | 374 | ||
614 | 259 | signal dismissAll() | 375 | signal dismissAll() |
615 | 260 | 376 | ||
616 | 377 | function recalcFirstInvisibleIndexAdded(index, item) { | ||
617 | 378 | if (firstInvisibleIndex === undefined) { | ||
618 | 379 | if (!item.shouldDisplay) { | ||
619 | 380 | firstInvisibleIndex = index; | ||
620 | 381 | } | ||
621 | 382 | } else if (index <= firstInvisibleIndex) { | ||
622 | 383 | if (!item.shouldDisplay) { | ||
623 | 384 | firstInvisibleIndex = index; | ||
624 | 385 | } else { | ||
625 | 386 | firstInvisibleIndex++; | ||
626 | 387 | } | ||
627 | 388 | } | ||
628 | 389 | } | ||
629 | 390 | |||
630 | 391 | function recalcFirstInvisibleIndex() { | ||
631 | 392 | for (var i = 0; i < rowRepeater.count; i++) { | ||
632 | 393 | if (!rowRepeater.itemAt(i).shouldDisplay) { | ||
633 | 394 | firstInvisibleIndex = i; | ||
634 | 395 | return; | ||
635 | 396 | } | ||
636 | 397 | } | ||
637 | 398 | firstInvisibleIndex = undefined; | ||
638 | 399 | } | ||
639 | 400 | |||
640 | 261 | onSelect: { | 401 | onSelect: { |
641 | 262 | var delegate = rowRepeater.itemAt(index); | 402 | var delegate = rowRepeater.itemAt(index); |
642 | 263 | if (delegate) { | 403 | if (delegate) { |
643 | @@ -265,6 +405,10 @@ | |||
644 | 265 | } | 405 | } |
645 | 266 | } | 406 | } |
646 | 267 | 407 | ||
647 | 408 | onOverflow: { | ||
648 | 409 | d.currentItem = overflowButton; | ||
649 | 410 | } | ||
650 | 411 | |||
651 | 268 | onCurrentItemChanged: { | 412 | onCurrentItemChanged: { |
652 | 269 | if (prevCurrentItem && prevCurrentItem != currentItem) { | 413 | if (prevCurrentItem && prevCurrentItem != currentItem) { |
653 | 270 | if (currentItem) { | 414 | if (currentItem) { |
654 | 271 | 415 | ||
655 | === modified file 'qml/ApplicationMenus/MenuItem.qml' | |||
656 | --- qml/ApplicationMenus/MenuItem.qml 2016-12-13 09:56:20 +0000 | |||
657 | +++ qml/ApplicationMenus/MenuItem.qml 2017-01-30 14:46:20 +0000 | |||
658 | @@ -46,6 +46,8 @@ | |||
659 | 46 | enabled: menuData ? menuData.sensitive : false | 46 | enabled: menuData ? menuData.sensitive : false |
660 | 47 | 47 | ||
661 | 48 | action: Action { | 48 | action: Action { |
662 | 49 | enabled: root.enabled | ||
663 | 50 | |||
664 | 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 |
665 | 50 | text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "") | 52 | text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "") |
666 | 51 | checkable: menuData.isCheck || menuData.isRadio | 53 | checkable: menuData.isCheck || menuData.isRadio |
667 | @@ -137,7 +139,7 @@ | |||
668 | 137 | theme.palette.disabled.backgroundSecondaryText | 139 | theme.palette.disabled.backgroundSecondaryText |
669 | 138 | 140 | ||
670 | 139 | visible: root.hasSubmenu | 141 | visible: root.hasSubmenu |
672 | 140 | name: "chevron" | 142 | name: "toolkit_chevron-ltr_2gu" |
673 | 141 | } | 143 | } |
674 | 142 | } | 144 | } |
675 | 143 | 145 | ||
676 | 144 | 146 | ||
677 | === modified file 'qml/ApplicationMenus/MenuNavigator.qml' | |||
678 | --- qml/ApplicationMenus/MenuNavigator.qml 2016-11-28 13:44:30 +0000 | |||
679 | +++ qml/ApplicationMenus/MenuNavigator.qml 2017-01-30 14:46:20 +0000 | |||
680 | @@ -18,8 +18,10 @@ | |||
681 | 18 | 18 | ||
682 | 19 | QtObject { | 19 | QtObject { |
683 | 20 | property Item itemView: null | 20 | property Item itemView: null |
684 | 21 | property bool hasOverflow: false | ||
685 | 21 | 22 | ||
686 | 22 | signal select(int index) | 23 | signal select(int index) |
687 | 24 | signal overflow() | ||
688 | 23 | 25 | ||
689 | 24 | function selectNext(currentIndex) { | 26 | function selectNext(currentIndex) { |
690 | 25 | var menu; | 27 | var menu; |
691 | @@ -32,6 +34,11 @@ | |||
692 | 32 | break; | 34 | break; |
693 | 33 | } | 35 | } |
694 | 34 | newIndex++; | 36 | newIndex++; |
695 | 37 | |||
696 | 38 | if (hasOverflow && newIndex === itemView.count) { | ||
697 | 39 | overflow() | ||
698 | 40 | break; | ||
699 | 41 | } | ||
700 | 35 | } | 42 | } |
701 | 36 | } else if (currentIndex !== -1 && itemView.count > 1) { | 43 | } else if (currentIndex !== -1 && itemView.count > 1) { |
702 | 37 | var startIndex = (currentIndex + 1) % itemView.count; | 44 | var startIndex = (currentIndex + 1) % itemView.count; |
703 | @@ -42,6 +49,12 @@ | |||
704 | 42 | select(newIndex); | 49 | select(newIndex); |
705 | 43 | break; | 50 | break; |
706 | 44 | } | 51 | } |
707 | 52 | |||
708 | 53 | if (hasOverflow && newIndex + 1 === itemView.count) { | ||
709 | 54 | overflow() | ||
710 | 55 | break; | ||
711 | 56 | } | ||
712 | 57 | |||
713 | 45 | newIndex = (newIndex + 1) % itemView.count; | 58 | newIndex = (newIndex + 1) % itemView.count; |
714 | 46 | } while (newIndex !== startIndex) | 59 | } while (newIndex !== startIndex) |
715 | 47 | } | 60 | } |
716 | @@ -58,12 +71,21 @@ | |||
717 | 58 | break; | 71 | break; |
718 | 59 | } | 72 | } |
719 | 60 | newIndex--; | 73 | newIndex--; |
720 | 74 | |||
721 | 75 | if (hasOverflow && newIndex < 0 ) { | ||
722 | 76 | overflow(); | ||
723 | 77 | break; | ||
724 | 78 | } | ||
725 | 61 | } | 79 | } |
726 | 62 | } else if (currentIndex !== -1 && itemView.count > 1) { | 80 | } else if (currentIndex !== -1 && itemView.count > 1) { |
727 | 63 | var startIndex = currentIndex - 1; | 81 | var startIndex = currentIndex - 1; |
728 | 64 | newIndex = startIndex; | 82 | newIndex = startIndex; |
729 | 65 | do { | 83 | do { |
730 | 66 | if (newIndex < 0) { | 84 | if (newIndex < 0) { |
731 | 85 | if (hasOverflow) { | ||
732 | 86 | overflow(); | ||
733 | 87 | break; | ||
734 | 88 | } | ||
735 | 67 | newIndex = itemView.count - 1; | 89 | newIndex = itemView.count - 1; |
736 | 68 | } | 90 | } |
737 | 69 | menu = itemView.itemAt(newIndex); | 91 | menu = itemView.itemAt(newIndex); |
738 | @@ -72,6 +94,7 @@ | |||
739 | 72 | break; | 94 | break; |
740 | 73 | } | 95 | } |
741 | 74 | newIndex--; | 96 | newIndex--; |
742 | 97 | |||
743 | 75 | } while (newIndex !== startIndex) | 98 | } while (newIndex !== startIndex) |
744 | 76 | } | 99 | } |
745 | 77 | } | 100 | } |
746 | 78 | 101 | ||
747 | === modified file 'qml/ApplicationMenus/MenuPopup.qml' | |||
748 | --- qml/ApplicationMenus/MenuPopup.qml 2017-01-18 12:08:05 +0000 | |||
749 | +++ qml/ApplicationMenus/MenuPopup.qml 2017-01-30 14:46:20 +0000 | |||
750 | @@ -26,6 +26,8 @@ | |||
751 | 26 | objectName: "menu" | 26 | objectName: "menu" |
752 | 27 | backgroundColor: theme.palette.normal.overlay | 27 | backgroundColor: theme.palette.normal.overlay |
753 | 28 | 28 | ||
754 | 29 | signal childActivated() | ||
755 | 30 | |||
756 | 29 | property alias unityMenuModel: repeater.model | 31 | property alias unityMenuModel: repeater.model |
757 | 30 | 32 | ||
758 | 31 | function show() { | 33 | function show() { |
759 | @@ -66,7 +68,7 @@ | |||
760 | 66 | property real __minimumWidth: units.gu(20) | 68 | property real __minimumWidth: units.gu(20) |
761 | 67 | property real __maximumWidth: Screen.width * 0.7 | 69 | property real __maximumWidth: Screen.width * 0.7 |
762 | 68 | property real __minimumHeight: units.gu(2) | 70 | property real __minimumHeight: units.gu(2) |
764 | 69 | property real __maximumHeight: Screen.height * 0.7 | 71 | property real __maximumHeight: Screen.height - mapToItem(null, 0, y).y |
765 | 70 | 72 | ||
766 | 71 | signal dismissAll() | 73 | signal dismissAll() |
767 | 72 | 74 | ||
768 | @@ -147,8 +149,20 @@ | |||
769 | 147 | } | 149 | } |
770 | 148 | 150 | ||
771 | 149 | MouseArea { | 151 | MouseArea { |
772 | 152 | id: previousMA | ||
773 | 150 | anchors.fill: parent | 153 | anchors.fill: parent |
775 | 151 | onPressed: { | 154 | hoverEnabled: enabled |
776 | 155 | onPressed: progress() | ||
777 | 156 | |||
778 | 157 | Timer { | ||
779 | 158 | running: previousMA.containsMouse && !listView.atYBeginning | ||
780 | 159 | interval: 1000 | ||
781 | 160 | repeat: true | ||
782 | 161 | onTriggered: previousMA.progress() | ||
783 | 162 | } | ||
784 | 163 | |||
785 | 164 | function progress() { | ||
786 | 165 | console.log("progress!") | ||
787 | 152 | var item = menuColumn.childAt(0, listView.contentY); | 166 | var item = menuColumn.childAt(0, listView.contentY); |
788 | 153 | if (item) { | 167 | if (item) { |
789 | 154 | var previousItem = item; | 168 | var previousItem = item; |
790 | @@ -191,10 +205,9 @@ | |||
791 | 191 | !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) { | 205 | !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) { |
792 | 192 | d.hoveredItem = menuColumn.childAt(pos.x, pos.y) | 206 | d.hoveredItem = menuColumn.childAt(pos.x, pos.y) |
793 | 193 | if (!d.hoveredItem || !d.hoveredItem.enabled) | 207 | if (!d.hoveredItem || !d.hoveredItem.enabled) |
795 | 194 | return false; | 208 | return; |
796 | 195 | d.currentItem = d.hoveredItem; | 209 | d.currentItem = d.hoveredItem; |
797 | 196 | } | 210 | } |
798 | 197 | return true; | ||
799 | 198 | } | 211 | } |
800 | 199 | } | 212 | } |
801 | 200 | 213 | ||
802 | @@ -263,16 +276,22 @@ | |||
803 | 263 | return mapToItem(container, 0, y).y; | 276 | return mapToItem(container, 0, y).y; |
804 | 264 | }) | 277 | }) |
805 | 265 | }); | 278 | }); |
806 | 279 | popup.retreat.connect(function() { | ||
807 | 280 | popup.destroy(); | ||
808 | 281 | popup = null; | ||
809 | 282 | menuItem.forceActiveFocus(); | ||
810 | 283 | }); | ||
811 | 284 | popup.childActivated.connect(function() { | ||
812 | 285 | popup.destroy(); | ||
813 | 286 | popup = null; | ||
814 | 287 | root.childActivated(); | ||
815 | 288 | }); | ||
816 | 266 | } else if (popup) { | 289 | } else if (popup) { |
817 | 267 | popup.visible = true; | 290 | popup.visible = true; |
818 | 268 | } | 291 | } |
819 | 269 | popup.retreat.connect(function() { | ||
820 | 270 | popup.destroy(); | ||
821 | 271 | popup = null; | ||
822 | 272 | menuItem.forceActiveFocus(); | ||
823 | 273 | }) | ||
824 | 274 | } else { | 292 | } else { |
825 | 275 | root.unityMenuModel.activate(__ownIndex); | 293 | root.unityMenuModel.activate(__ownIndex); |
826 | 294 | root.childActivated(); | ||
827 | 276 | } | 295 | } |
828 | 277 | } | 296 | } |
829 | 278 | 297 | ||
830 | @@ -349,8 +368,19 @@ | |||
831 | 349 | } | 368 | } |
832 | 350 | 369 | ||
833 | 351 | MouseArea { | 370 | MouseArea { |
834 | 371 | id: nextMA | ||
835 | 352 | anchors.fill: parent | 372 | anchors.fill: parent |
837 | 353 | onPressed: { | 373 | hoverEnabled: enabled |
838 | 374 | onPressed: progress() | ||
839 | 375 | |||
840 | 376 | Timer { | ||
841 | 377 | running: nextMA.containsMouse && !listView.atYEnd | ||
842 | 378 | interval: 1000 | ||
843 | 379 | repeat: true | ||
844 | 380 | onTriggered: nextMA.progress() | ||
845 | 381 | } | ||
846 | 382 | |||
847 | 383 | function progress() { | ||
848 | 354 | var item = menuColumn.childAt(0, listView.contentY + listView.height); | 384 | var item = menuColumn.childAt(0, listView.contentY + listView.height); |
849 | 355 | if (item) { | 385 | if (item) { |
850 | 356 | var nextItem = item; | 386 | var nextItem = item; |
851 | @@ -377,6 +407,7 @@ | |||
852 | 377 | 407 | ||
853 | 378 | property var unityMenuModel: null | 408 | property var unityMenuModel: null |
854 | 379 | signal retreat() | 409 | signal retreat() |
855 | 410 | signal childActivated() | ||
856 | 380 | 411 | ||
857 | 381 | Binding { | 412 | Binding { |
858 | 382 | target: item | 413 | target: item |
859 | @@ -392,6 +423,11 @@ | |||
860 | 392 | 423 | ||
861 | 393 | Keys.onLeftPressed: retreat() | 424 | Keys.onLeftPressed: retreat() |
862 | 394 | 425 | ||
863 | 426 | Connections { | ||
864 | 427 | target: item | ||
865 | 428 | onChildActivated: childActivated(); | ||
866 | 429 | } | ||
867 | 430 | |||
868 | 395 | Component.onCompleted: item.select(0); | 431 | Component.onCompleted: item.select(0); |
869 | 396 | onVisibleChanged: if (visible) { item.select(0); } | 432 | onVisibleChanged: if (visible) { item.select(0); } |
870 | 397 | } | 433 | } |
871 | 398 | 434 | ||
872 | === modified file 'qml/Panel/Panel.qml' | |||
873 | --- qml/Panel/Panel.qml 2017-01-09 14:10:17 +0000 | |||
874 | +++ qml/Panel/Panel.qml 2017-01-30 14:46:20 +0000 | |||
875 | @@ -209,6 +209,8 @@ | |||
876 | 209 | Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } } | 209 | Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } } |
877 | 210 | active: __applicationMenus.model | 210 | active: __applicationMenus.model |
878 | 211 | 211 | ||
879 | 212 | width: parent.width - windowControlButtons.width - units.gu(2) - __indicators.barWidth | ||
880 | 213 | |||
881 | 212 | property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false | 214 | property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false |
882 | 213 | 215 | ||
883 | 214 | sourceComponent: MenuBar { | 216 | sourceComponent: MenuBar { |
884 | @@ -385,7 +387,7 @@ | |||
885 | 385 | } | 387 | } |
886 | 386 | 388 | ||
887 | 387 | enabled: !applicationMenus.expanded | 389 | enabled: !applicationMenus.expanded |
889 | 388 | opacity: !applicationMenus.expanded ? 1 : 0 | 390 | opacity: !callHint.visible && !applicationMenus.expanded ? 1 : 0 |
890 | 389 | Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } } | 391 | Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } } |
891 | 390 | 392 | ||
892 | 391 | onEnabledChanged: { | 393 | onEnabledChanged: { |
893 | 392 | 394 | ||
894 | === modified file 'tests/mocks/Utils/CMakeLists.txt' | |||
895 | --- tests/mocks/Utils/CMakeLists.txt 2016-12-21 10:20:36 +0000 | |||
896 | +++ tests/mocks/Utils/CMakeLists.txt 2017-01-30 14:46:20 +0000 | |||
897 | @@ -26,6 +26,7 @@ | |||
898 | 26 | ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp | 26 | ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp |
899 | 27 | ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp | 27 | ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp |
900 | 28 | ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp | 28 | ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp |
901 | 29 | ${CMAKE_SOURCE_DIR}/plugins/Utils/expressionfiltermodel.cpp | ||
902 | 29 | ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h | 30 | ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h |
903 | 30 | ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h | 31 | ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h |
904 | 31 | ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h | 32 | ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h |
905 | 32 | 33 | ||
906 | === modified file 'tests/mocks/Utils/plugin.cpp' | |||
907 | --- tests/mocks/Utils/plugin.cpp 2017-01-03 12:16:00 +0000 | |||
908 | +++ tests/mocks/Utils/plugin.cpp 2017-01-30 14:46:20 +0000 | |||
909 | @@ -42,6 +42,7 @@ | |||
910 | 42 | #include <globalfunctions.h> | 42 | #include <globalfunctions.h> |
911 | 43 | #include <appdrawerproxymodel.h> | 43 | #include <appdrawerproxymodel.h> |
912 | 44 | #include <tabfocusfence.h> | 44 | #include <tabfocusfence.h> |
913 | 45 | #include <expressionfiltermodel.h> | ||
914 | 45 | 46 | ||
915 | 46 | static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine) | 47 | static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine) |
916 | 47 | { | 48 | { |
917 | @@ -86,4 +87,5 @@ | |||
918 | 86 | qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher"); | 87 | qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher"); |
919 | 87 | qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel"); | 88 | qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel"); |
920 | 88 | qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence"); | 89 | qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence"); |
921 | 90 | qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel"); | ||
922 | 89 | } | 91 | } |
923 | 90 | 92 | ||
924 | === modified file 'tests/qmltests/ApplicationMenus/tst_MenuBar.qml' | |||
925 | --- tests/qmltests/ApplicationMenus/tst_MenuBar.qml 2017-01-24 07:41:35 +0000 | |||
926 | +++ tests/qmltests/ApplicationMenus/tst_MenuBar.qml 2017-01-30 14:46:20 +0000 | |||
927 | @@ -28,8 +28,8 @@ | |||
928 | 28 | 28 | ||
929 | 29 | Item { | 29 | Item { |
930 | 30 | id: root | 30 | id: root |
933 | 31 | width: units.gu(100) | 31 | width: units.gu(120) |
934 | 32 | height: units.gu(50) | 32 | height: units.gu(70) |
935 | 33 | 33 | ||
936 | 34 | Component.onCompleted: { | 34 | Component.onCompleted: { |
937 | 35 | QuickUtils.keyboardAttached = true; | 35 | QuickUtils.keyboardAttached = true; |
938 | @@ -51,11 +51,11 @@ | |||
939 | 51 | Rectangle { | 51 | Rectangle { |
940 | 52 | anchors { | 52 | anchors { |
941 | 53 | left: parent.left | 53 | left: parent.left |
942 | 54 | right: parent.right | ||
943 | 55 | top: parent.top | 54 | top: parent.top |
944 | 56 | margins: units.gu(1) | 55 | margins: units.gu(1) |
945 | 57 | } | 56 | } |
946 | 58 | height: units.gu(3) | 57 | height: units.gu(3) |
947 | 58 | width: parent.width * 2/3 | ||
948 | 59 | color: "grey" | 59 | color: "grey" |
949 | 60 | 60 | ||
950 | 61 | MenuBar { | 61 | MenuBar { |
951 | @@ -65,7 +65,7 @@ | |||
952 | 65 | 65 | ||
953 | 66 | unityMenuModel: UnityMenuModel { | 66 | unityMenuModel: UnityMenuModel { |
954 | 67 | id: menuBackend | 67 | id: menuBackend |
956 | 68 | modelData: appMenuData.generateTestData(17,5,2,3) | 68 | modelData: appMenuData.generateTestData(10,5,2,3) |
957 | 69 | } | 69 | } |
958 | 70 | } | 70 | } |
959 | 71 | } | 71 | } |
960 | @@ -83,12 +83,12 @@ | |||
961 | 83 | 83 | ||
962 | 84 | function init() { | 84 | function init() { |
963 | 85 | menuBar.dismiss(); | 85 | menuBar.dismiss(); |
965 | 86 | menuBackend.modelData = appMenuData.generateTestData(5,5,2,3) | 86 | menuBackend.modelData = appMenuData.generateTestData(5,5,2,3, "menu") |
966 | 87 | activatedSpy.clear(); | 87 | activatedSpy.clear(); |
967 | 88 | } | 88 | } |
968 | 89 | 89 | ||
969 | 90 | function test_mouseNavigation() { | 90 | function test_mouseNavigation() { |
971 | 91 | menuBackend.modelData = appMenuData.generateTestData(3,3,0,0); | 91 | menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu"); |
972 | 92 | wait(50) // wait for row to build | 92 | wait(50) // wait for row to build |
973 | 93 | var priv = findInvisibleChild(menuBar, "d"); | 93 | var priv = findInvisibleChild(menuBar, "d"); |
974 | 94 | 94 | ||
975 | @@ -114,7 +114,7 @@ | |||
976 | 114 | } | 114 | } |
977 | 115 | 115 | ||
978 | 116 | function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) { | 116 | function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) { |
980 | 117 | menuBackend.modelData = appMenuData.generateTestData(3,3,0,0); | 117 | menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu"); |
981 | 118 | var priv = findInvisibleChild(menuBar, "d"); | 118 | var priv = findInvisibleChild(menuBar, "d"); |
982 | 119 | 119 | ||
983 | 120 | var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0); | 120 | var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0); |
984 | @@ -139,7 +139,7 @@ | |||
985 | 139 | } | 139 | } |
986 | 140 | 140 | ||
987 | 141 | function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) { | 141 | function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) { |
989 | 142 | menuBackend.modelData = appMenuData.generateTestData(3,3,0,0); | 142 | menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu"); |
990 | 143 | var priv = findInvisibleChild(menuBar, "d"); | 143 | var priv = findInvisibleChild(menuBar, "d"); |
991 | 144 | 144 | ||
992 | 145 | var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0); | 145 | var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0); |
993 | @@ -180,6 +180,41 @@ | |||
994 | 180 | tryCompare(priv, "currentItem", menuItem); | 180 | tryCompare(priv, "currentItem", menuItem); |
995 | 181 | } | 181 | } |
996 | 182 | 182 | ||
997 | 183 | function test_menuActivateClosesMenu() { | ||
998 | 184 | menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu"); | ||
999 | 185 | var priv = findInvisibleChild(menuBar, "d"); | ||
1000 | 186 | |||
1001 | 187 | var menuItem = findChild(menuBar, "menuBar-item0"); | ||
1002 | 188 | menuItem.show(); | ||
1003 | 189 | compare(priv.currentItem, menuItem, "CurrentItem should be set to item 0"); | ||
1004 | 190 | compare(priv.currentItem.popupVisible, true, "Popup should be visible"); | ||
1005 | 191 | |||
1006 | 192 | var actionItem = findChild(menuBar, "menuBar-item0-menu-item0-actionItem"); | ||
1007 | 193 | mouseClick(actionItem); | ||
1008 | 194 | compare(priv.currentItem, null, "CurrentItem should be null"); | ||
1009 | 195 | } | ||
1010 | 196 | |||
1011 | 197 | function test_subMenuActivateClosesMenu() { | ||
1012 | 198 | menuBackend.modelData = appMenuData.generateTestData(3,4,1,0,"menu"); | ||
1013 | 199 | var priv = findInvisibleChild(menuBar, "d"); | ||
1014 | 200 | |||
1015 | 201 | var menuItem = findChild(menuBar, "menuBar-item0"); | ||
1016 | 202 | menuItem.show(); | ||
1017 | 203 | compare(priv.currentItem, menuItem, "CurrentItem should be set to item 0"); | ||
1018 | 204 | compare(priv.currentItem.popupVisible, true, "Popup should be visible"); | ||
1019 | 205 | |||
1020 | 206 | var actionItem = findChild(menuBar, "menuBar-item0-menu-item0-actionItem"); | ||
1021 | 207 | mouseClick(actionItem); | ||
1022 | 208 | |||
1023 | 209 | actionItem = findChild(menuBar, "menuBar-item0-menu-item0-menu-item0-actionItem"); | ||
1024 | 210 | mouseClick(actionItem); | ||
1025 | 211 | |||
1026 | 212 | actionItem = findChild(menuBar, "menuBar-item0-menu-item0-menu-item0-menu-item0-actionItem"); | ||
1027 | 213 | mouseClick(actionItem); | ||
1028 | 214 | |||
1029 | 215 | compare(priv.currentItem, null, "CurrentItem should be null"); | ||
1030 | 216 | } | ||
1031 | 217 | |||
1032 | 183 | function test_openAppMenuShortcut() { | 218 | function test_openAppMenuShortcut() { |
1033 | 184 | var priv = findInvisibleChild(menuBar, "d"); | 219 | var priv = findInvisibleChild(menuBar, "d"); |
1034 | 185 | 220 | ||
1035 | @@ -192,5 +227,31 @@ | |||
1036 | 192 | keyClick(Qt.Key_F10, Qt.AltModifier); | 227 | keyClick(Qt.Key_F10, Qt.AltModifier); |
1037 | 193 | compare(priv.currentItem, menuItem1, "First enabled item should be opened"); | 228 | compare(priv.currentItem, menuItem1, "First enabled item should be opened"); |
1038 | 194 | } | 229 | } |
1039 | 230 | |||
1040 | 231 | function test_overfow() { | ||
1041 | 232 | menuBackend.modelData = appMenuData.generateTestData(5,2,0,0,"menu"); | ||
1042 | 233 | |||
1043 | 234 | var overflow = findChild(menuBar, "overflow"); | ||
1044 | 235 | compare(overflow.visible, false, "Overflow should not be visible"); | ||
1045 | 236 | |||
1046 | 237 | var menu = { "rowData": { "label": "Short" } }; | ||
1047 | 238 | tryCompareFunction(function() { | ||
1048 | 239 | menuBackend.insertRow(0, menu); | ||
1049 | 240 | wait(1); | ||
1050 | 241 | if (overflow.visible) { | ||
1051 | 242 | return true; | ||
1052 | 243 | } | ||
1053 | 244 | return false; | ||
1054 | 245 | }, true); | ||
1055 | 246 | |||
1056 | 247 | tryCompareFunction(function() { | ||
1057 | 248 | menuBackend.removeRow(0); | ||
1058 | 249 | wait(1); | ||
1059 | 250 | if (!overflow.visible) { | ||
1060 | 251 | return true; | ||
1061 | 252 | } | ||
1062 | 253 | return false; | ||
1063 | 254 | }, true); | ||
1064 | 255 | } | ||
1065 | 195 | } | 256 | } |
1066 | 196 | } | 257 | } |
FAILED: Continuous integration, rev:2777 /unity8- jenkins. ubuntu. com/job/ lp-unity8- ci/3007/ /unity8- jenkins. ubuntu. com/job/ build/3912/ console /unity8- jenkins. ubuntu. com/job/ build-0- fetch/3940 /unity8- jenkins. ubuntu. com/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 3785/console /unity8- jenkins. ubuntu. com/job/ build-2- binpkg/ arch=amd64, release= zesty/3785/ console /unity8- jenkins. ubuntu. com/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 3785/console /unity8- jenkins. ubuntu. com/job/ build-2- binpkg/ arch=armhf, release= zesty/3785/ console /unity8- jenkins. ubuntu. com/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 3785/console /unity8- jenkins. ubuntu. com/job/ build-2- binpkg/ arch=i386, release= zesty/3785/ console
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild: /unity8- jenkins. ubuntu. com/job/ lp-unity8- ci/3007/ rebuild
https:/