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