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/>.
18import QtQuick.Layouts 1.1
19import Lomiri.Components 1.3
20import Lomiri.Components.ListItems 1.3 as ListItems
22import "../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 lomiriMenuModel: repeater.model
67 property PanelState panelState
71 focusScope.forceActiveFocus();
79 function selectFirstIndex() {
92 implicitWidth: focusScope.width
93 implicitHeight: focusScope.height
100 property Item currentItem: null
101 property Item hoveredItem: null
102 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
104 property real __minimumWidth: units.gu(20)
105 property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
106 property real __minimumHeight: units.gu(2)
107 property real __maximumHeight: panelState ? ApplicationMenusLimits.screenHeight - panelState.panelHeight : 0
111 onCurrentItemChanged: {
113 currentItem.item.forceActiveFocus();
118 submenuHoverTimer.stop();
122 currentItem = repeater.itemAt(index);
124 if (currentItem.y < listView.contentY) {
125 listView.contentY = currentItem.y;
126 } else if (currentItem.y + currentItem.height > listView.contentY + listView.height) {
127 listView.contentY = currentItem.y + currentItem.height - listView.height;
140 width: container.width
141 height: container.height
144 Keys.onUpPressed: d.selectPrevious(d.currentIndex)
145 Keys.onDownPressed: d.selectNext(d.currentIndex)
146 Keys.onRightPressed: {
147 // Don't let right keypresses fall through if the current item has a visible popup.
148 if (!d.currentItem || !d.currentItem.popup || !d.currentItem.popup.visible) {
149 event.accepted = false;
155 objectName: "container"
157 height: MathUtils.clamp(listView.contentHeight, d.__minimumHeight, d.__maximumHeight)
158 width: menuColumn.width
161 // Header - scroll up
163 Layout.fillWidth: true
165 visible: listView.contentHeight > root.height
166 enabled: !listView.atYBeginning
170 color: enabled ? theme.palette.normal.overlayText :
171 theme.palette.disabled.overlayText
174 bottom: parent.bottom
181 anchors.centerIn: parent
185 color: enabled ? theme.palette.normal.overlayText :
186 theme.palette.disabled.overlayText
192 hoverEnabled: enabled
193 onPressed: progress()
196 running: previousMA.containsMouse && !listView.atYBeginning
199 onTriggered: previousMA.progress()
202 function progress() {
203 var item = menuColumn.childAt(0, listView.contentY);
205 var previousItem = item;
207 previousItem = repeater.itemAt(previousItem.__ownIndex-1);
209 listView.contentY = 0;
212 } while (previousItem.__isSeparator);
214 listView.contentY = previousItem.y
225 Layout.fillHeight: true
226 Layout.fillWidth: true
227 contentHeight: menuColumn.height
228 interactive: height < contentHeight
231 id: submenuHoverTimer
232 interval: 225 // GTK MENU_POPUP_DELAY, Qt SH_Menu_SubMenuPopupDelay in QCommonStyle is 256
233 onTriggered: d.currentItem.item.trigger();
239 z: 1 // on top so we override any other hovers
240 onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
241 onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
243 function updateCurrentItemFromPosition(point) {
244 var pos = mapToItem(listView.contentItem, point.x, point.y);
246 if (!d.hoveredItem || !d.currentItem ||
247 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
248 submenuHoverTimer.stop();
250 d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
251 if (!d.hoveredItem || !d.hoveredItem.enabled)
253 d.currentItem = d.hoveredItem;
255 if (!d.currentItem.__isSeparator && d.currentItem.item.hasSubmenu && d.currentItem.item.enabled) {
256 submenuHoverTimer.start();
262 var pos = mapToItem(listView.contentItem, mouse.x, mouse.y);
263 var clickedItem = menuColumn.childAt(pos.x, pos.y);
264 if (clickedItem.enabled && !clickedItem.__isSeparator) {
265 clickedItem.item.trigger();
272 objectName: "menuContext"
274 if (!root.visible) return false;
275 if (d.currentItem && d.currentItem.popup && d.currentItem.popup.visible) {
283 id: separatorComponent
284 ListItems.ThinDivider {
285 // Parent will be loader
286 objectName: parent.objectName + "-separator"
287 implicitHeight: units.dp(2)
292 id: menuItemComponent
294 // Parent will be loader
296 menuData: parent.__menuData
297 objectName: parent.objectName + "-actionItem"
299 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
301 property Item popup: null
303 action.onTriggered: {
304 submenuHoverTimer.stop();
306 d.currentItem = parent;
310 root.lomiriMenuModel.aboutToShow(__ownIndex);
311 var model = root.lomiriMenuModel.submenu(__ownIndex);
312 popup = submenuComponent.createObject(focusScope, {
313 objectName: parent.objectName + "-",
314 lomiriMenuModel: model,
315 substractWidth: true,
316 desiredX: Qt.binding(function() { return root.width }),
317 desiredY: Qt.binding(function() {
318 var dummy = listView.contentY; // force a recalc on contentY change.
319 return mapToItem(container, 0, y).y;
322 popup.retreat.connect(function() {
325 menuItem.forceActiveFocus();
327 popup.childActivated.connect(function() {
330 root.childActivated();
332 } else if (!popup.visible) {
333 root.lomiriMenuModel.aboutToShow(__ownIndex);
334 popup.visible = true;
335 popup.item.selectFirstIndex();
338 root.lomiriMenuModel.activate(__ownIndex);
339 root.childActivated();
345 onCurrentIndexChanged: {
346 if (popup && d.currentIndex != __ownIndex) {
347 popup.visible = false;
358 Component.onDestruction: {
371 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
377 if (root.selectFirstOnCountChange && !d.currentItem && count > 0) {
378 root.selectFirstIndex();
384 objectName: root.objectName + "-item" + __ownIndex
386 readonly property var popup: item ? item.popup : null
387 property var __menuData: model
388 property int __ownIndex: index
389 property bool __isSeparator: model.isSeparator
391 enabled: __isSeparator ? false : model.sensitive
394 if (model.isSeparator) {
395 return separatorComponent;
397 return menuItemComponent;
400 Layout.fillWidth: true
409 border.width: units.dp(1)
410 border.color: LomiriColors.orange
413 width: listView.width
414 height: d.currentItem ? d.currentItem.height : 0
415 y: d.currentItem ? d.currentItem.y : 0
416 visible: d.currentItem
421 // Header - scroll down
423 Layout.fillWidth: true
425 visible: listView.contentHeight > root.height
426 enabled: !listView.atYEnd
430 color: enabled ? theme.palette.normal.overlayText :
431 theme.palette.disabled.overlayText
441 anchors.centerIn: parent
445 color: enabled ? theme.palette.normal.overlayText :
446 theme.palette.disabled.overlayText
452 hoverEnabled: enabled
453 onPressed: progress()
456 running: nextMA.containsMouse && !listView.atYEnd
459 onTriggered: nextMA.progress()
462 function progress() {
463 var item = menuColumn.childAt(0, listView.contentY + listView.height);
467 nextItem = repeater.itemAt(nextItem.__ownIndex+1);
469 listView.contentY = listView.contentHeight - listView.height;
472 } while (nextItem.__isSeparator);
474 listView.contentY = nextItem.y - listView.height
485 source: "MenuPopup.qml"
487 property real desiredX
488 property real desiredY
489 property bool substractWidth
490 property var lomiriMenuModel: null
492 signal childActivated()
495 item.lomiriMenuModel = Qt.binding(function() { return submenuLoader.lomiriMenuModel; });
496 item.panelState = Qt.binding(function() { return root.panelState; });
497 item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
498 item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
499 item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
500 item.substractWidth = Qt.binding(function() { return submenuLoader.substractWidth; });
503 Keys.onLeftPressed: retreat()
507 onChildActivated: childActivated();