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/>.
18import QtQuick.Layouts 1.1
20import Lomiri.Components 1.3
21import GlobalShortcut 1.0
22import "../Components/PanelState"
29 property alias lomiriMenuModel: rowRepeater.model
30 property bool enableKeyFilter: false
31 property real overflowWidth: width
32 property bool windowMoving: false
33 property PanelState panelState
36 readonly property bool valid: rowRepeater.count > 0
37 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
39 // MoveHandler API for DecoratedWindow
40 signal pressed(var mouse)
41 signal pressedChangedEx(bool pressed, var pressedButtons, real mouseX, real mouseY)
42 signal positionChanged(var mouse)
43 signal released(var mouse)
44 signal doubleClicked(var mouse)
46 implicitWidth: row.width
53 function invokeMenu(mouseEvent) {
54 mouseArea.onClicked(mouseEvent);
58 shortcut: Qt.Key_Alt|Qt.AltModifier
59 active: enableKeyFilter
60 onTriggered: d.startShortcutTimer()
61 onReleased: d.stopSHortcutTimer()
63 // On an actual keyboard, the AltModifier is not supplied on release.
66 active: enableKeyFilter
67 onTriggered: d.startShortcutTimer()
68 onReleased: d.stopSHortcutTimer()
72 shortcut: Qt.AltModifier | Qt.Key_F10
73 active: enableKeyFilter && d.currentItem == null
75 for (var i = 0; i < rowRepeater.count; i++) {
76 var item = rowRepeater.itemAt(i);
86 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
88 enabled: d.currentItem != null
89 hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
90 onPressed: { mouse.accepted = false; d.dismissAll(); }
100 objectName: "barContext"
101 active: !d.currentItem && enableKeyFilter
105 target: root.lomiriMenuModel
106 onModelReset: d.firstInvisibleIndex = undefined
112 panelState: root.panelState
119 onItemAdded: d.recalcFirstInvisibleIndexAdded(index, item)
120 onCountChanged: d.recalcFirstInvisibleIndex()
124 objectName: root.objectName + "-item" + __ownIndex
126 readonly property int __ownIndex: index
127 property Item __popup: null;
128 readonly property bool popupVisible: __popup && __popup.visible
129 readonly property bool shouldDisplay: x + width + ((__ownIndex < rowRepeater.count-1) ? units.gu(2) : 0) <
130 root.overflowWidth - ((__ownIndex < rowRepeater.count-1) ? overflowButton.width : 0)
132 // First item is not centered, it has 0 gu on the left and 1 on the right
133 // so needs different width and anchors
134 readonly property bool isFirstItem: __ownIndex == 0
136 implicitWidth: column.implicitWidth + (isFirstItem ? units.gu(1) : units.gu(2))
137 implicitHeight: row.height
138 enabled: (model.sensitive === true) && shouldDisplay
139 opacity: shouldDisplay ? 1 : 0
143 root.lomiriMenuModel.aboutToShow(visualItem.__ownIndex);
144 __popup = menuComponent.createObject(root,
146 objectName: visualItem.objectName + "-menu",
147 desiredX: Qt.binding(function() { return visualItem.x - units.gu(1); }),
148 desiredY: Qt.binding(function() { return root.height; }),
149 lomiriMenuModel: Qt.binding(function() { return root.lomiriMenuModel.submenu(visualItem.__ownIndex); }),
150 selectFirstOnCountChange: false
153 __popup.childActivated.connect(dismiss);
154 // force the current item to be the newly popped up menu
155 } else if (!__popup.visible) {
156 root.lomiriMenuModel.aboutToShow(visualItem.__ownIndex);
159 d.currentItem = visualItem;
165 if (d.currentItem === visualItem) {
166 d.currentItem = null;
175 if (d.currentItem === visualItem) {
176 d.currentItem = null;
182 if (!visible && __popup) dismiss();
185 onShouldDisplayChanged: {
186 if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) {
187 d.recalcFirstInvisibleIndex();
193 onDismissAll: visualItem.dismiss()
200 verticalCenter: parent.verticalCenter
201 horizontalCenter: !visualItem.isFirstItem ? parent.horizontalCenter : undefined
202 left: visualItem.isFirstItem ? parent.left : undefined
206 Layout.preferredWidth: units.gu(2)
207 Layout.preferredHeight: units.gu(2)
208 Layout.alignment: Qt.AlignVCenter
210 visible: model.icon || false
211 source: model.icon || ""
217 height: _title.height
220 enabled: visualItem.enabled
221 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
222 text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
231 text: actionItem.text
232 horizontalAlignment: Text.AlignLeft
233 color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
238 Component.onDestruction: {
244 } // Item ( delegate )
251 hoverEnabled: d.currentItem
253 property bool moved: false
257 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
263 var prevItem = d.currentItem;
264 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY));
265 if (prevItem && d.currentItem == prevItem) {
272 // for the MoveHandler
273 onPressed: root.pressed(mouse)
274 onPressedChanged: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
275 onReleased: root.released(mouse)
276 onDoubleClicked: root.doubleClicked(mouse)
278 Mouse.ignoreSynthesizedEvents: true
279 Mouse.onPositionChanged: {
280 root.positionChanged(mouse);
281 moved = root.windowMoving;
283 updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
287 function updateCurrentItemFromPosition(point) {
288 var pos = mapToItem(row, point.x, point.y);
290 if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
291 d.hoveredItem = row.childAt(pos.x, pos.y);
292 if (!d.hoveredItem || !d.hoveredItem.enabled)
294 if (d.currentItem != d.hoveredItem) {
295 d.currentItem = d.hoveredItem;
303 objectName: "overflow"
305 hoverEnabled: d.currentItem
306 onEntered: d.currentItem = this
307 onPositionChanged: d.currentItem = this
308 onPressed: d.currentItem = this
310 property Item __popup: null;
311 readonly property bool popupVisible: __popup && __popup.visible
312 readonly property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null
314 visible: d.firstInvisibleIndex != undefined
315 x: firstInvisibleItem ? firstInvisibleItem.x : 0
317 height: parent.height
321 if (!visible && __popup) dismiss();
328 anchors.centerIn: parent
329 color: theme.palette.normal.backgroundText
330 name: "toolkit_chevron-down_2gu"
335 __popup = overflowComponent.createObject(root, { objectName: overflowButton.objectName + "-menu" });
336 __popup.childActivated.connect(dismiss);
337 // force the current item to be the newly popped up menu
341 d.currentItem = overflowButton;
347 if (d.currentItem === overflowButton) {
348 d.currentItem = null;
357 if (d.currentItem === overflowButton) {
358 d.currentItem = null;
365 onDismissAll: overflowButton.dismiss()
369 id: overflowComponent
372 desiredX: overflowButton.x - units.gu(1)
373 desiredY: parent.height
374 lomiriMenuModel: overflowModel
376 ExpressionFilterModel {
378 sourceModel: root.lomiriMenuModel
379 matchExpression: function(index) {
380 if (d.firstInvisibleIndex === undefined) return false;
381 return index >= d.firstInvisibleIndex;
384 function submenu(index) {
385 return sourceModel.submenu(mapRowToSource(index));
387 function activate(index) {
388 return sourceModel.activate(mapRowToSource(index));
390 function aboutToShow(index) {
391 return sourceModel.aboutToShow(mapRowToSource(index));
397 onFirstInvisibleIndexChanged: overflowModel.invalidate()
408 x: d.currentItem ? row.x + d.currentItem.x : 0
409 width: d.currentItem ? d.currentItem.width : 0
411 color: LomiriColors.orange
412 visible: d.currentItem
418 itemView: rowRepeater
419 hasOverflow: overflowButton.visible
421 property Item currentItem: null
422 property Item hoveredItem: null
423 property Item prevCurrentItem: null
424 property bool altPressed: false
425 property bool longAltPressed: false
426 property var firstInvisibleIndex: undefined
428 readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1
432 function recalcFirstInvisibleIndexAdded(index, item) {
433 if (firstInvisibleIndex === undefined) {
434 if (!item.shouldDisplay) {
435 firstInvisibleIndex = index;
437 } else if (index <= firstInvisibleIndex) {
438 if (!item.shouldDisplay) {
439 firstInvisibleIndex = index;
441 firstInvisibleIndex++;
446 function recalcFirstInvisibleIndex() {
447 for (var i = 0; i < rowRepeater.count; i++) {
448 if (!rowRepeater.itemAt(i).shouldDisplay) {
449 firstInvisibleIndex = i;
453 firstInvisibleIndex = undefined;
457 var delegate = rowRepeater.itemAt(index);
459 d.currentItem = delegate;
464 d.currentItem = overflowButton;
467 onCurrentItemChanged: {
468 if (prevCurrentItem && prevCurrentItem != currentItem) {
470 prevCurrentItem.hide();
472 prevCurrentItem.dismiss();
476 if (currentItem) currentItem.show();
477 prevCurrentItem = currentItem;
480 function startShortcutTimer() {
482 menuBarShortcutTimer.start();
485 function stopSHortcutTimer() {
486 menuBarShortcutTimer.stop();
487 d.altPressed = false;
488 d.longAltPressed = false;
493 id: menuBarShortcutTimer
497 d.longAltPressed = true;
501 Keys.onEscapePressed: {
503 event.accepted = true;
506 Keys.onLeftPressed: {
508 d.selectPrevious(d.currentIndex);
512 Keys.onRightPressed: {
514 d.selectNext(d.currentIndex);