2 * Copyright 2016 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
19 import Ubuntu.Components 1.3
20 import Ubuntu.Components.ListItems 1.3 as ListItems
21 import "../Components"
22 import "../Components/PanelState"
28 backgroundColor: theme.palette.normal.overlay
30 signal childActivated()
32 // true for submenus that need to show on the other side of their parent
33 // if they don't fit when growing right
34 property bool substractWidth: false
36 property bool selectFirstOnCountChange: true
38 property real desiredX
40 var dummy = visible; // force recalc when shown/hidden
41 var parentTopLeft = parent.mapToItem(null, 0, 0);
42 var farX = ApplicationMenusLimits.screenWidth;
43 if (parentTopLeft.x + width + desiredX <= farX) {
49 return farX - parentTopLeft.x - width;
54 property real desiredY
56 var dummy = visible; // force recalc when shown/hidden
57 var parentTopLeft = parent.mapToItem(null, 0, 0);
58 var bottomY = ApplicationMenusLimits.screenHeight;
59 if (parentTopLeft.y + height + desiredY <= bottomY) {
62 return bottomY - parentTopLeft.y - height;
66 property alias unityMenuModel: repeater.model
70 focusScope.forceActiveFocus();
78 function selectFirstIndex() {
91 implicitWidth: focusScope.width
92 implicitHeight: focusScope.height
99 property Item currentItem: null
100 property Item hoveredItem: null
101 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
103 property real __minimumWidth: units.gu(20)
104 property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
105 property real __minimumHeight: units.gu(2)
106 property real __maximumHeight: ApplicationMenusLimits.screenHeight - PanelState.panelHeight
110 onCurrentItemChanged: {
112 currentItem.item.forceActiveFocus();
117 submenuHoverTimer.stop();
121 currentItem = repeater.itemAt(index);
123 if (currentItem.y < listView.contentY) {
124 listView.contentY = currentItem.y;
125 } else if (currentItem.y + currentItem.height > listView.contentY + listView.height) {
126 listView.contentY = currentItem.y + currentItem.height - listView.height;
139 width: container.width
140 height: container.height
143 Keys.onUpPressed: d.selectPrevious(d.currentIndex)
144 Keys.onDownPressed: d.selectNext(d.currentIndex)
145 Keys.onRightPressed: {
146 // Don't let right keypresses fall through if the current item has a visible popup.
147 if (!d.currentItem || !d.currentItem.popup || !d.currentItem.popup.visible) {
148 event.accepted = false;
154 objectName: "container"
156 height: MathUtils.clamp(listView.contentHeight, d.__minimumHeight, d.__maximumHeight)
157 width: menuColumn.width
160 // Header - scroll up
162 Layout.fillWidth: true
164 visible: listView.contentHeight > root.height
165 enabled: !listView.atYBeginning
169 color: enabled ? theme.palette.normal.overlayText :
170 theme.palette.disabled.overlayText
173 bottom: parent.bottom
180 anchors.centerIn: parent
184 color: enabled ? theme.palette.normal.overlayText :
185 theme.palette.disabled.overlayText
191 hoverEnabled: enabled
192 onPressed: progress()
195 running: previousMA.containsMouse && !listView.atYBeginning
198 onTriggered: previousMA.progress()
201 function progress() {
202 var item = menuColumn.childAt(0, listView.contentY);
204 var previousItem = item;
206 previousItem = repeater.itemAt(previousItem.__ownIndex-1);
208 listView.contentY = 0;
211 } while (previousItem.__isSeparator);
213 listView.contentY = previousItem.y
224 Layout.fillHeight: true
225 Layout.fillWidth: true
226 contentHeight: menuColumn.height
227 interactive: height < contentHeight
230 id: submenuHoverTimer
231 interval: 225 // GTK MENU_POPUP_DELAY, Qt SH_Menu_SubMenuPopupDelay in QCommonStyle is 256
232 onTriggered: d.currentItem.item.trigger();
238 z: 1 // on top so we override any other hovers
239 onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
240 onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
242 function updateCurrentItemFromPosition(point) {
243 var pos = mapToItem(listView.contentItem, point.x, point.y);
245 if (!d.hoveredItem || !d.currentItem ||
246 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
247 submenuHoverTimer.stop();
249 d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
250 if (!d.hoveredItem || !d.hoveredItem.enabled)
252 d.currentItem = d.hoveredItem;
254 if (!d.currentItem.__isSeparator && d.currentItem.item.hasSubmenu && d.currentItem.item.enabled) {
255 submenuHoverTimer.start();
261 var pos = mapToItem(listView.contentItem, mouse.x, mouse.y);
262 var clickedItem = menuColumn.childAt(pos.x, pos.y);
263 if (clickedItem.enabled && !clickedItem.__isSeparator) {
264 clickedItem.item.trigger();
271 objectName: "menuContext"
273 if (!root.visible) return false;
274 if (d.currentItem && d.currentItem.popup && d.currentItem.popup.visible) {
282 id: separatorComponent
283 ListItems.ThinDivider {
284 // Parent will be loader
285 objectName: parent.objectName + "-separator"
286 implicitHeight: units.dp(2)
291 id: menuItemComponent
293 // Parent will be loader
295 menuData: parent.__menuData
296 objectName: parent.objectName + "-actionItem"
298 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
300 property Item popup: null
302 action.onTriggered: {
303 submenuHoverTimer.stop();
305 d.currentItem = parent;
309 root.unityMenuModel.aboutToShow(__ownIndex);
310 var model = root.unityMenuModel.submenu(__ownIndex);
311 popup = submenuComponent.createObject(focusScope, {
312 objectName: parent.objectName + "-",
313 unityMenuModel: model,
314 substractWidth: true,
315 desiredX: Qt.binding(function() { return root.width }),
316 desiredY: Qt.binding(function() {
317 var dummy = listView.contentY; // force a recalc on contentY change.
318 return mapToItem(container, 0, y).y;
321 popup.retreat.connect(function() {
324 menuItem.forceActiveFocus();
326 popup.childActivated.connect(function() {
329 root.childActivated();
331 } else if (!popup.visible) {
332 root.unityMenuModel.aboutToShow(__ownIndex);
333 popup.visible = true;
334 popup.item.selectFirstIndex();
337 root.unityMenuModel.activate(__ownIndex);
338 root.childActivated();
344 onCurrentIndexChanged: {
345 if (popup && d.currentIndex != __ownIndex) {
346 popup.visible = false;
357 Component.onDestruction: {
370 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
376 if (root.selectFirstOnCountChange && !d.currentItem && count > 0) {
377 root.selectFirstIndex();
383 objectName: root.objectName + "-item" + __ownIndex
385 readonly property var popup: item ? item.popup : null
386 property var __menuData: model
387 property int __ownIndex: index
388 property bool __isSeparator: model.isSeparator
390 enabled: __isSeparator ? false : model.sensitive
393 if (model.isSeparator) {
394 return separatorComponent;
396 return menuItemComponent;
399 Layout.fillWidth: true
408 border.width: units.dp(1)
409 border.color: UbuntuColors.orange
412 width: listView.width
413 height: d.currentItem ? d.currentItem.height : 0
414 y: d.currentItem ? d.currentItem.y : 0
415 visible: d.currentItem
420 // Header - scroll down
422 Layout.fillWidth: true
424 visible: listView.contentHeight > root.height
425 enabled: !listView.atYEnd
429 color: enabled ? theme.palette.normal.overlayText :
430 theme.palette.disabled.overlayText
440 anchors.centerIn: parent
444 color: enabled ? theme.palette.normal.overlayText :
445 theme.palette.disabled.overlayText
451 hoverEnabled: enabled
452 onPressed: progress()
455 running: nextMA.containsMouse && !listView.atYEnd
458 onTriggered: nextMA.progress()
461 function progress() {
462 var item = menuColumn.childAt(0, listView.contentY + listView.height);
466 nextItem = repeater.itemAt(nextItem.__ownIndex+1);
468 listView.contentY = listView.contentHeight - listView.height;
471 } while (nextItem.__isSeparator);
473 listView.contentY = nextItem.y - listView.height
484 source: "MenuPopup.qml"
486 property real desiredX
487 property real desiredY
488 property bool substractWidth
489 property var unityMenuModel: null
491 signal childActivated()
494 item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
495 item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
496 item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
497 item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
498 item.substractWidth = Qt.binding(function() { return submenuLoader.substractWidth; });
501 Keys.onLeftPressed: retreat()
505 onChildActivated: childActivated();