/*
* Copyright 2016 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import Utils 0.1
import Ubuntu.Components 1.3
import GlobalShortcut 1.0
Item {
id: root
objectName: "menuBar"
// set from outside
property alias unityMenuModel: rowRepeater.model
property bool enableKeyFilter: false
property real overflowWidth: width
// read from outside
readonly property bool valid: rowRepeater.count > 0
readonly property bool showRequested: d.longAltPressed || d.currentItem != null
implicitWidth: row.width
height: parent.height
function select(index) {
d.select(index);
}
function dismiss() {
d.dismissAll();
}
GlobalShortcut {
shortcut: Qt.Key_Alt|Qt.AltModifier
active: enableKeyFilter
onTriggered: d.startShortcutTimer()
onReleased: d.stopSHortcutTimer()
}
// On an actual keyboard, the AltModifier is not supplied on release.
GlobalShortcut {
shortcut: Qt.Key_Alt
active: enableKeyFilter
onTriggered: d.startShortcutTimer()
onReleased: d.stopSHortcutTimer()
}
GlobalShortcut {
shortcut: Qt.AltModifier | Qt.Key_F10
active: enableKeyFilter && d.currentItem == null
onTriggered: {
for (var i = 0; i < rowRepeater.count; i++) {
var item = rowRepeater.itemAt(i);
if (item.enabled) {
item.show();
break;
}
}
}
}
InverseMouseArea {
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
anchors.fill: parent
enabled: d.currentItem != null
hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
onPressed: d.dismissAll()
}
Row {
id: row
spacing: units.gu(2)
height: parent.height
ActionContext {
id: menuBarContext
objectName: "barContext"
active: !d.currentItem && enableKeyFilter
}
Connections {
target: root.unityMenuModel
onModelReset: d.firstInvisibleIndex = undefined
}
Repeater {
id: rowRepeater
onItemAdded: d.recalcFirstInvisibleIndexAdded(index, item)
onCountChanged: d.recalcFirstInvisibleIndex()
Item {
id: visualItem
objectName: root.objectName + "-item" + __ownIndex
readonly property int __ownIndex: index
property Item __popup: null;
readonly property bool popupVisible: __popup && __popup.visible
readonly property bool shouldDisplay: x + width + ((__ownIndex < rowRepeater.count-1) ? units.gu(2) : 0) <
root.overflowWidth - ((__ownIndex < rowRepeater.count-1) ? overflowButton.width : 0)
implicitWidth: column.implicitWidth
implicitHeight: row.height
enabled: model.sensitive && shouldDisplay
opacity: shouldDisplay ? 1 : 0
function show() {
if (!__popup) {
__popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
__popup.childActivated.connect(dismiss);
// force the current item to be the newly popped up menu
} else {
__popup.show();
}
d.currentItem = visualItem;
}
function hide() {
if (__popup) {
__popup.hide();
if (d.currentItem === visualItem) {
d.currentItem = null;
}
}
}
function dismiss() {
if (__popup) {
__popup.destroy();
__popup = null;
if (d.currentItem === visualItem) {
d.currentItem = null;
}
}
}
onVisibleChanged: {
if (!visible && __popup) dismiss();
}
onShouldDisplayChanged: {
if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) {
d.recalcFirstInvisibleIndex();
}
}
Connections {
target: d
onDismissAll: visualItem.dismiss()
}
Component {
id: menuComponent
MenuPopup {
desiredX: visualItem.x - units.gu(1)
desiredY: parent.height
unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
Component.onCompleted: reset();
}
}
RowLayout {
id: column
spacing: units.gu(1)
anchors {
centerIn: parent
}
Icon {
Layout.preferredWidth: units.gu(2)
Layout.preferredHeight: units.gu(2)
Layout.alignment: Qt.AlignVCenter
visible: model.icon || false
source: model.icon || ""
}
ActionItem {
id: actionItem
width: _title.width
height: _title.height
action: Action {
enabled: visualItem.enabled
// FIXME - SDK Action:text modifies menu text with html underline for mnemonic
text: model.label.replace("_", "&").replace("", "&").replace("", "")
onTriggered: {
visualItem.show();
}
}
Label {
id: _title
text: actionItem.text
horizontalAlignment: Text.AlignLeft
color: enabled ? theme.palette.normal.backgroundText : theme.palette.disabled.backgroundText
}
}
}
} // Item ( delegate )
} // Repeater
} // Row
MouseArea {
anchors.fill: row
hoverEnabled: d.currentItem
onEntered: {
if (d.currentItem) {
updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
}
}
onPositionChanged: {
if (d.currentItem) {
updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
}
}
onClicked: {
var prevItem = d.currentItem;
updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
if (prevItem && d.currentItem == prevItem) {
prevItem.hide();
}
}
function updateCurrentItemFromPosition(point) {
var pos = mapToItem(row, point.x, point.y);
if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
d.hoveredItem = row.childAt(pos.x, pos.y);
if (!d.hoveredItem || !d.hoveredItem.enabled)
return;
if (d.currentItem != d.hoveredItem) {
d.currentItem = d.hoveredItem;
}
}
}
}
MouseArea {
id: overflowButton
objectName: "overflow"
hoverEnabled: d.currentItem
onEntered: d.currentItem = this
onPositionChanged: d.currentItem = this
onClicked: d.currentItem = this
property Item __popup: null;
readonly property bool popupVisible: __popup && __popup.visible
readonly property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null
visible: d.firstInvisibleIndex != undefined
x: firstInvisibleItem ? firstInvisibleItem.x : 0
height: parent.height
width: units.gu(4)
onVisibleChanged: {
if (!visible && __popup) dismiss();
}
Icon {
id: icon
width: units.gu(2)
height: units.gu(2)
anchors.centerIn: parent
color: theme.palette.normal.backgroundText
name: "toolkit_chevron-down_2gu"
}
function show() {
if (!__popup) {
__popup = overflowComponent.createObject(root, { objectName: overflowButton.objectName + "-menu" });
__popup.childActivated.connect(dismiss);
// force the current item to be the newly popped up menu
} else {
__popup.show();
}
d.currentItem = overflowButton;
}
function hide() {
if (__popup) {
__popup.hide();
if (d.currentItem === overflowButton) {
d.currentItem = null;
}
}
}
function dismiss() {
if (__popup) {
__popup.destroy();
__popup = null;
if (d.currentItem === overflowButton) {
d.currentItem = null;
}
}
}
Connections {
target: d
onDismissAll: overflowButton.dismiss()
}
Component {
id: overflowComponent
MenuPopup {
id: overflowPopup
desiredX: overflowButton.x - units.gu(1)
desiredY: parent.height
unityMenuModel: overflowModel
ExpressionFilterModel {
id: overflowModel
sourceModel: root.unityMenuModel
matchExpression: function(index) {
if (d.firstInvisibleIndex === undefined) return false;
return index >= d.firstInvisibleIndex;
}
function submenu(index) {
return sourceModel.submenu(mapRowToSource(index));
}
function activate(index) {
return sourceModel.activate(mapRowToSource(index));
}
}
Connections {
target: d
onFirstInvisibleIndexChanged: overflowModel.invalidate()
}
}
}
}
Rectangle {
id: underline
anchors {
bottom: row.bottom
}
x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
height: units.dp(4)
color: UbuntuColors.orange
visible: d.currentItem
}
MenuNavigator {
id: d
objectName: "d"
itemView: rowRepeater
hasOverflow: overflowButton.visible
property Item currentItem: null
property Item hoveredItem: null
property Item prevCurrentItem: null
property bool altPressed: false
property bool longAltPressed: false
property var firstInvisibleIndex: undefined
readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1
signal dismissAll()
function recalcFirstInvisibleIndexAdded(index, item) {
if (firstInvisibleIndex === undefined) {
if (!item.shouldDisplay) {
firstInvisibleIndex = index;
}
} else if (index <= firstInvisibleIndex) {
if (!item.shouldDisplay) {
firstInvisibleIndex = index;
} else {
firstInvisibleIndex++;
}
}
}
function recalcFirstInvisibleIndex() {
for (var i = 0; i < rowRepeater.count; i++) {
if (!rowRepeater.itemAt(i).shouldDisplay) {
firstInvisibleIndex = i;
return;
}
}
firstInvisibleIndex = undefined;
}
onSelect: {
var delegate = rowRepeater.itemAt(index);
if (delegate) {
d.currentItem = delegate;
}
}
onOverflow: {
d.currentItem = overflowButton;
}
onCurrentItemChanged: {
if (prevCurrentItem && prevCurrentItem != currentItem) {
if (currentItem) {
prevCurrentItem.hide();
} else {
prevCurrentItem.dismiss();
}
}
if (currentItem) currentItem.show();
prevCurrentItem = currentItem;
}
function startShortcutTimer() {
d.altPressed = true;
menuBarShortcutTimer.start();
}
function stopSHortcutTimer() {
menuBarShortcutTimer.stop();
d.altPressed = false;
d.longAltPressed = false;
}
}
Timer {
id: menuBarShortcutTimer
interval: 200
repeat: false
onTriggered: {
d.longAltPressed = true;
}
}
Keys.onEscapePressed: {
d.dismissAll();
event.accepted = true;
}
Keys.onLeftPressed: {
if (d.currentItem) {
d.selectPrevious(d.currentIndex);
}
}
Keys.onRightPressed: {
if (d.currentItem) {
d.selectNext(d.currentIndex);
}
}
}