2 * Copyright 2016, 2017 Canonical Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import QtQuick.Layouts 1.1
20 import Ubuntu.Components 1.3
21 import GlobalShortcut 1.0
28 property alias unityMenuModel: rowRepeater.model
29 property bool enableKeyFilter: false
30 property real overflowWidth: width
31 property bool windowMoving: false
34 readonly property bool valid: rowRepeater.count > 0
35 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
37 // MoveHandler API for DecoratedWindow
38 signal pressed(var mouse)
39 signal pressedChangedEx(bool pressed, var pressedButtons, real mouseX, real mouseY)
40 signal positionChanged(var mouse)
41 signal released(var mouse)
42 signal doubleClicked(var mouse)
44 implicitWidth: row.width
51 function invokeMenu(mouseEvent) {
52 mouseArea.onClicked(mouseEvent);
56 shortcut: Qt.Key_Alt|Qt.AltModifier
57 active: enableKeyFilter
58 onTriggered: d.startShortcutTimer()
59 onReleased: d.stopSHortcutTimer()
61 // On an actual keyboard, the AltModifier is not supplied on release.
64 active: enableKeyFilter
65 onTriggered: d.startShortcutTimer()
66 onReleased: d.stopSHortcutTimer()
70 shortcut: Qt.AltModifier | Qt.Key_F10
71 active: enableKeyFilter && d.currentItem == null
73 for (var i = 0; i < rowRepeater.count; i++) {
74 var item = rowRepeater.itemAt(i);
84 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
86 enabled: d.currentItem != null
87 hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
88 onPressed: { mouse.accepted = false; d.dismissAll(); }
98 objectName: "barContext"
99 active: !d.currentItem && enableKeyFilter
103 target: root.unityMenuModel
104 onModelReset: d.firstInvisibleIndex = undefined
115 onItemAdded: d.recalcFirstInvisibleIndexAdded(index, item)
116 onCountChanged: d.recalcFirstInvisibleIndex()
120 objectName: root.objectName + "-item" + __ownIndex
122 readonly property int __ownIndex: index
123 property Item __popup: null;
124 readonly property bool popupVisible: __popup && __popup.visible
125 readonly property bool shouldDisplay: x + width + ((__ownIndex < rowRepeater.count-1) ? units.gu(2) : 0) <
126 root.overflowWidth - ((__ownIndex < rowRepeater.count-1) ? overflowButton.width : 0)
128 // First item is not centered, it has 0 gu on the left and 1 on the right
129 // so needs different width and anchors
130 readonly property bool isFirstItem: __ownIndex == 0
132 implicitWidth: column.implicitWidth + (isFirstItem ? units.gu(1) : units.gu(2))
133 implicitHeight: row.height
134 enabled: (model.sensitive === true) && shouldDisplay
135 opacity: shouldDisplay ? 1 : 0
139 root.unityMenuModel.aboutToShow(visualItem.__ownIndex);
140 __popup = menuComponent.createObject(root,
142 objectName: visualItem.objectName + "-menu",
143 desiredX: Qt.binding(function() { return visualItem.x - units.gu(1); }),
144 desiredY: Qt.binding(function() { return root.height; }),
145 unityMenuModel: Qt.binding(function() { return root.unityMenuModel.submenu(visualItem.__ownIndex); }),
146 selectFirstOnCountChange: false
149 __popup.childActivated.connect(dismiss);
150 // force the current item to be the newly popped up menu
151 } else if (!__popup.visible) {
152 root.unityMenuModel.aboutToShow(visualItem.__ownIndex);
155 d.currentItem = visualItem;
161 if (d.currentItem === visualItem) {
162 d.currentItem = null;
171 if (d.currentItem === visualItem) {
172 d.currentItem = null;
178 if (!visible && __popup) dismiss();
181 onShouldDisplayChanged: {
182 if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) {
183 d.recalcFirstInvisibleIndex();
189 onDismissAll: visualItem.dismiss()
196 verticalCenter: parent.verticalCenter
197 horizontalCenter: !visualItem.isFirstItem ? parent.horizontalCenter : undefined
198 left: visualItem.isFirstItem ? parent.left : undefined
202 Layout.preferredWidth: units.gu(2)
203 Layout.preferredHeight: units.gu(2)
204 Layout.alignment: Qt.AlignVCenter
206 visible: model.icon || false
207 source: model.icon || ""
213 height: _title.height
216 enabled: visualItem.enabled
217 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
218 text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
227 text: actionItem.text
228 horizontalAlignment: Text.AlignLeft
229 color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
234 Component.onDestruction: {
240 } // Item ( delegate )
247 hoverEnabled: d.currentItem
249 property bool moved: false
253 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
259 var prevItem = d.currentItem;
260 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY));
261 if (prevItem && d.currentItem == prevItem) {
268 // for the MoveHandler
269 onPressed: root.pressed(mouse)
270 onPressedChanged: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
271 onReleased: root.released(mouse)
272 onDoubleClicked: root.doubleClicked(mouse)
274 Mouse.ignoreSynthesizedEvents: true
275 Mouse.onPositionChanged: {
276 root.positionChanged(mouse);
277 moved = root.windowMoving;
279 updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
283 function updateCurrentItemFromPosition(point) {
284 var pos = mapToItem(row, point.x, point.y);
286 if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
287 d.hoveredItem = row.childAt(pos.x, pos.y);
288 if (!d.hoveredItem || !d.hoveredItem.enabled)
290 if (d.currentItem != d.hoveredItem) {
291 d.currentItem = d.hoveredItem;
299 objectName: "overflow"
301 hoverEnabled: d.currentItem
302 onEntered: d.currentItem = this
303 onPositionChanged: d.currentItem = this
304 onPressed: d.currentItem = this
306 property Item __popup: null;
307 readonly property bool popupVisible: __popup && __popup.visible
308 readonly property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null
310 visible: d.firstInvisibleIndex != undefined
311 x: firstInvisibleItem ? firstInvisibleItem.x : 0
313 height: parent.height
317 if (!visible && __popup) dismiss();
324 anchors.centerIn: parent
325 color: theme.palette.normal.backgroundText
326 name: "toolkit_chevron-down_2gu"
331 __popup = overflowComponent.createObject(root, { objectName: overflowButton.objectName + "-menu" });
332 __popup.childActivated.connect(dismiss);
333 // force the current item to be the newly popped up menu
337 d.currentItem = overflowButton;
343 if (d.currentItem === overflowButton) {
344 d.currentItem = null;
353 if (d.currentItem === overflowButton) {
354 d.currentItem = null;
361 onDismissAll: overflowButton.dismiss()
365 id: overflowComponent
368 desiredX: overflowButton.x - units.gu(1)
369 desiredY: parent.height
370 unityMenuModel: overflowModel
372 ExpressionFilterModel {
374 sourceModel: root.unityMenuModel
375 matchExpression: function(index) {
376 if (d.firstInvisibleIndex === undefined) return false;
377 return index >= d.firstInvisibleIndex;
380 function submenu(index) {
381 return sourceModel.submenu(mapRowToSource(index));
383 function activate(index) {
384 return sourceModel.activate(mapRowToSource(index));
386 function aboutToShow(index) {
387 return sourceModel.aboutToShow(mapRowToSource(index));
393 onFirstInvisibleIndexChanged: overflowModel.invalidate()
404 x: d.currentItem ? row.x + d.currentItem.x : 0
405 width: d.currentItem ? d.currentItem.width : 0
407 color: UbuntuColors.orange
408 visible: d.currentItem
414 itemView: rowRepeater
415 hasOverflow: overflowButton.visible
417 property Item currentItem: null
418 property Item hoveredItem: null
419 property Item prevCurrentItem: null
420 property bool altPressed: false
421 property bool longAltPressed: false
422 property var firstInvisibleIndex: undefined
424 readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1
428 function recalcFirstInvisibleIndexAdded(index, item) {
429 if (firstInvisibleIndex === undefined) {
430 if (!item.shouldDisplay) {
431 firstInvisibleIndex = index;
433 } else if (index <= firstInvisibleIndex) {
434 if (!item.shouldDisplay) {
435 firstInvisibleIndex = index;
437 firstInvisibleIndex++;
442 function recalcFirstInvisibleIndex() {
443 for (var i = 0; i < rowRepeater.count; i++) {
444 if (!rowRepeater.itemAt(i).shouldDisplay) {
445 firstInvisibleIndex = i;
449 firstInvisibleIndex = undefined;
453 var delegate = rowRepeater.itemAt(index);
455 d.currentItem = delegate;
460 d.currentItem = overflowButton;
463 onCurrentItemChanged: {
464 if (prevCurrentItem && prevCurrentItem != currentItem) {
466 prevCurrentItem.hide();
468 prevCurrentItem.dismiss();
472 if (currentItem) currentItem.show();
473 prevCurrentItem = currentItem;
476 function startShortcutTimer() {
478 menuBarShortcutTimer.start();
481 function stopSHortcutTimer() {
482 menuBarShortcutTimer.stop();
483 d.altPressed = false;
484 d.longAltPressed = false;
489 id: menuBarShortcutTimer
493 d.longAltPressed = true;
497 Keys.onEscapePressed: {
499 event.accepted = true;
502 Keys.onLeftPressed: {
504 d.selectPrevious(d.currentIndex);
508 Keys.onRightPressed: {
510 d.selectNext(d.currentIndex);