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