aerothemeplasma/Plasma/Plasma_Widgets/User/io.gitgud.wackyideas.seventasks/contents/ui/TasksMenu.qml

1133 lines
47 KiB
QML
Raw Normal View History

2023-08-24 22:32:11 +00:00
/*
SPDX-FileCopyrightText: 2012-2016 Eike Hein <hein@kde.org>
SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.taskmanager 0.1 as TaskManager
import "code/layout.js" as LayoutManager
import org.kde.plasma.extras 2.0 as PlasmaExtras
/*
* This is the custom context menu control for SevenTasks.
* It is designed to look and feel like the context menu from Windows Vista and onwards,
* while making sure that it behaves just like a normal context menu under KDE. This means:
*
* 1. The context menu grabs *all* mouse and key inputs.
* 2. The context menu must disappear if an event outside of it causes it to 'lose focus'.
* 3. The context menu must disappear if a menu item has been activated either with the mouse or the keyboard.
* 4. The context menu must disappear if the user clicks away from it or the Escape key is pressed on the keyboard.
*
* As PlasmaCore.Dialog inherits QWindow, we can use QWindow::setMouseGrabEnabled(bool) and QWindow::setKeyboardGrabEnabled(bool)
* to steal all mouse and keyboard events from the system and direct it towards the context menu. This is done through C++, more
* info in the C++ source files.
*
*/
PlasmaCore.Dialog {
id: tasksMenu
// Properties passed by the task when the context menu is created dynamically.
// Context menu specific stuff.
property QtObject backend
property QtObject mpris2Source
property var modelIndex
readonly property var atm: TaskManager.AbstractTasksModel
property var menuDecoration: "exec"
property QtObject currentItem: null
property int currentItemIndex: -1
readonly property int menuItemHeight: PlasmaCore.Units.smallSpacing*5
readonly property int menuWidth: 263
property bool showAllPlaces: false
property bool alsoCloseTask: false
property bool secondaryColumn: false
property color backgroundColorStatic: "#f1f6fb"
property color backgroundColorGradient: "white"
property color borderColor: "#ccd9ea"
// Functions inherited from the original ContextMenu
function get(modelProp) {
return tasksModel.data(modelIndex, modelProp)
}
function showContextMenuWithAllPlaces() {
visualParent.showContextMenu({showAllPlaces: true});
}
function newPlasmaMenuItem(parent) {
return Qt.createQmlObject(`
import org.kde.plasma.components 2.0 as PlasmaComponents
PlasmaComponents.MenuItem {}
`, parent);
}
function newPlasmaSeparator(parent) {
return Qt.createQmlObject(`
import org.kde.plasma.components 2.0 as PlasmaComponents
PlasmaComponents.MenuItem { separator: true }
`, parent);
}
function newMenuItem(parent) {
return Qt.createQmlObject(`
TasksMenuItemWrapper {}
`, parent);
}
function newSeparator(parent) {
return Qt.createQmlObject(`
TasksMenuItemSeparator {}
`, parent);
}
function addItemToMenu(obj) {
obj.Layout.fillWidth = true;
obj.Layout.preferredHeight = menuItemHeight;
menuitems.height += obj.Layout.preferredHeight + menuitems.spacing;
}
function clearIndices() {
if(currentItem !== null) {
currentItem.selected = false;
currentItem = null;
}
currentItemIndex = -1;
}
function setCurrentItem(obj) {
clearIndices();
var i = Array.prototype.indexOf.call(menuitems.children, obj);
if(i === -1) {
i = menuitems.children.length + Array.prototype.indexOf.call(staticMenuItems.children, obj);
}
currentItemIndex = i;
currentItem = obj;
currentItem.selected = true;
}
// Tasksmenu specific stuff
property alias tMenu: tasksMenu
property int xpos: -1 // Variable is used to keep track of the original x position which sometimes gets changed for no reason.
visible: false
opacity: 0
objectName: "tasksMenuDialog"
hideOnWindowDeactivate: true // Makes it so that the context menu disappears if it gets forcibly out of focus by an external event.
flags: Qt.WindowStaysOnTopHint | Qt.Dialog
// Used to animate the context menu appearing and disappearing.
Behavior on opacity {
NumberAnimation { duration: 125; }
}
Behavior on y {
NumberAnimation {duration: 125; }
}
// Tries to detect when the x position resets to 0.
onXChanged: {
if(tasksMenu.x !== xpos) {
tasksMenu.x = xpos;
}
}
// If the context menu is no longer visible (most often when it loses focus), close the menu.
onVisibleChanged: {
if(visible) {
var diff = parent.mapToGlobal(tasksMenu.x, tasksMenu.y).x - tasksMenu.x;
xpos = visualParent.x + diff + visualParent.width/2 - PlasmaCore.Units.smallSpacing;
xpos -= menuWidth / 2;
if(xpos < 0) xpos = 0;
tasksMenu.x = xpos;
plasmoid.nativeInterface.setMouseGrab(true, tasksMenu);
}
else if(!visible) {
tasksMenu.closeMenu();
}
}
onActiveChanged: {
if(!active) tasksMenu.close();
}
// Set to Floating so that the borders are visible all the time, even when it is right next to another object.
location: {
return PlasmaCore.Types.Floating;
}
// Used publicly by other objects to show the dynamically created context menu.
function show() {
loadDynamicLauncherActions(get(atm.LauncherUrlWithoutIcon));
visible = true;
opacity = 1;
tasksMenu.y -= PlasmaCore.Units.smallSpacing*2;
openTimer.start();
}
// Closes the menu gracefully, by first showing a fade out animation before freeing the object from memory.
function closeMenu() {
plasmoid.nativeInterface.disableBlurBehind(tasksMenu);
tasksMenu.y += PlasmaCore.Units.smallSpacing*2;
opacity = 0;
closeTimer.start();
}
function loadDynamicLauncherActions(launcherUrl) {
var sections = [
{
title: i18n("Frequent"),
group: "places",
actions: backend.placesActions(launcherUrl, showAllPlaces, tasksMenu)
},
{
title: i18n("Recent"),
group: "recents",
actions: backend.recentDocumentActions(launcherUrl, tasksMenu)
},
{
title: i18n("Tasks"),
group: "actions",
actions: backend.jumpListActions(launcherUrl, tasksMenu)
}
]
// C++ can override section heading by returning a QString as first action
sections.forEach((section) => {
if (typeof section.actions[0] === "string") {
section.title = section.actions.shift(); // take first
}
});
// QMenu does not limit its width automatically. Even if we set a maximumWidth
// it would just cut off text rather than eliding. So we do this manually.
var textMetrics = Qt.createQmlObject("import QtQuick 2.4; TextMetrics {}", menuitems);
var maximumWidth = LayoutManager.maximumContextMenuTextWidth() + PlasmaCore.Units.smallSpacing*2;
for(var i = 0; i < sections.length; i++) {
var section = sections[i];
if(section["actions"].length == 0) continue;
// Make a separator header
var sepHeader = tasksMenu.newSeparator(menuitems);
sepHeader.menuText = section["title"];
addItemToMenu(sepHeader);
for(var j = 0; j < section["actions"].length; j++) {
if(section["group"] == "recents" && j == section["actions"].length-2) continue;
var mAction = section["actions"][j];
var mItem = tasksMenu.newMenuItem(menuitems);
// Crude way of manually eliding...
var elided = false;
textMetrics.text = Qt.binding(function() {
return mAction.text;
});
while (textMetrics.width > maximumWidth) {
mAction.text = mAction.text.slice(0, -1);
elided = true;
}
if (elided) {
mAction.text += "…";
}
mItem.text = mAction.text;
mItem.icon = mAction.icon;
mItem.clicked.connect(mAction.trigger);
addItemToMenu(mItem);
secondaryColumn = true;
}
}
// Add Media Player control actions
var sourceName = mpris2Source.sourceNameForLauncherUrl(launcherUrl, get(atm.AppPid));
if (sourceName && !(get(atm.WinIdList) !== undefined && get(atm.WinIdList).length > 1)) {
var playerData = mpris2Source.data[sourceName]
var sepHeader = tasksMenu.newSeparator(menuitems);
sepHeader.menuText = "Media controls";
addItemToMenu(sepHeader);
secondaryColumn = true;
if (playerData.CanControl) {
var playing = (playerData.PlaybackStatus === "Playing");
var menuItem = tasksMenu.newMenuItem(menuitems);
menuItem.text = i18nc("Play previous track", "Previous Track");
menuItem.icon = "media-skip-backward";
menuItem.enabled = Qt.binding(function() {
return playerData.CanGoPrevious;
});
menuItem.clicked.connect(function() {
mpris2Source.goPrevious(sourceName);
tasksMenu.closeMenu();
});
tasksMenu.addItemToMenu(menuItem);
menuItem = tasksMenu.newMenuItem(menuitems);
// PlasmaCore Menu doesn't actually handle icons or labels changing at runtime...
menuItem.text = Qt.binding(function() {
// if CanPause, toggle the menu entry between Play & Pause, otherwise always use Play
return playing && playerData.CanPause ? i18nc("Pause playback", "Pause") : i18nc("Start playback", "Play");
});
menuItem.icon = Qt.binding(function() {
return playing && playerData.CanPause ? "media-playback-pause" : "media-playback-start";
});
menuItem.enabled = Qt.binding(function() {
return playing ? playerData.CanPause : playerData.CanPlay;
});
menuItem.clicked.connect(function() {
if (playing) {
mpris2Source.pause(sourceName);
} else {
mpris2Source.play(sourceName);
}
tasksMenu.closeMenu();
});
tasksMenu.addItemToMenu(menuItem);
menuItem = tasksMenu.newMenuItem(menuitems);
menuItem.text = i18nc("Play next track", "Next Track");
menuItem.icon = "media-skip-forward";
menuItem.enabled = Qt.binding(function() {
return playerData.CanGoNext;
});
menuItem.clicked.connect(function() {
mpris2Source.goNext(sourceName);
tasksMenu.closeMenu();
});
tasksMenu.addItemToMenu(menuItem);
menuItem = tasksMenu.newMenuItem(menuitems);
menuItem.text = i18nc("Stop playback", "Stop");
menuItem.icon = "media-playback-stop";
menuItem.enabled = Qt.binding(function() {
return playerData.PlaybackStatus !== "Stopped";
});
menuItem.clicked.connect(function() {
mpris2Source.stop(sourceName);
tasksMenu.closeMenu();
});
tasksMenu.addItemToMenu(menuItem);
// If we don't have a window associated with the player but we can quit
// it through MPRIS we'll offer a "Quit" option instead of "Close"
if (!closeWindowItem.visible && playerData.CanQuit) {
menuItem = tasksMenu.newMenuItem(menuitems);
menuItem.text = i18nc("Quit media player app", "Quit");
menuItem.icon = "application-exit";
menuItem.visible = Qt.binding(function() {
return !closeWindowItem.visible;
});
menuItem.clicked.connect(function() {
mpris2Source.quit(sourceName);
tasksMenu.closeMenu();
});
tasksMenu.addItemToMenu(menuItem);
}
// If we don't have a window associated with the player but we can raise
// it through MPRIS we'll offer a "Restore" option
if (get(atm.IsLauncher) === true && !startNewInstanceItem.visible && playerData.CanRaise) {
menuItem = tasksMenu.newMenuItem(menuitems);
menuItem.text = i18nc("Open or bring to the front window of media player app", "Restore");
menuItem.icon = playerData["Desktop Icon Name"];
menuItem.visible = Qt.binding(function() {
return !startNewInstanceItem.visible;
});
menuItem.clicked.connect(function() {
mpris2Source.raise(sourceName);
tasksMenu.closeMenu();
});
tasksMenu.addItemToMenu(menuItem);
}
}
}
// We allow mute/unmute whenever an application has a stream, regardless of whether it
// is actually playing sound.
// This way you can unmute, e.g. a telephony app, even after the conversation has ended,
// so you still have it ringing later on.
if (tasksMenu.visualParent.hasAudioStream) {
var muteItem = tasksMenu.newMenuItem(menuitems);
muteItem.checkable = true;
muteItem.checked = Qt.binding(function() {
return tasksMenu.visualParent && tasksMenu.visualParent.muted;
});
muteItem.clicked.connect(function() {
tasksMenu.visualParent.toggleMuted();
muteItem.text = !muteItem.checked ? "Unmute" : "Mute";
muteItem.icon = !muteItem.checked ? "audio-volume-muted" : "audio-volume-high";
});
muteItem.text = muteItem.checked ? "Unmute" : "Mute";
muteItem.icon = muteItem.checked ? "audio-volume-muted" : "audio-volume-high";
tasksMenu.addItemToMenu(muteItem);
secondaryColumn = true;
}
}
function delayedMenu(delay, func) {
plasmoid.nativeInterface.disableBlurBehind(tasksMenu);
tasksMenu.y += PlasmaCore.Units.smallSpacing*2;
opacity = 0;
delayTimer.interval = delay;
delayTimer.repeat = false;
delayTimer.triggered.connect(func);
delayTimer.start();
}
FocusScope {
id: fscope
focus: true
Layout.minimumWidth: menuWidth
Layout.maximumWidth: menuWidth
Layout.minimumHeight: staticMenuItems.height + menuitems.height + PlasmaCore.Units.smallSpacing*3 - (secondaryColumn ? 0 : PlasmaCore.Units.smallSpacing*2)
Layout.maximumHeight: staticMenuItems.height + menuitems.height + PlasmaCore.Units.smallSpacing*3 - (secondaryColumn ? 0 : PlasmaCore.Units.smallSpacing*2)
// This is the last resort to avoiding the dialog displacement bug. It's set to correct the x position at a delay of 18ms.
// This may result in a brief but noticeable jump in position when the context menu is shown.
Timer {
id: delayTimer
}
Timer {
id: openTimer
interval: 20
repeat: false
onTriggered: {
tasksMenu.x = xpos;
plasmoid.nativeInterface.setMouseGrab(true, tasksMenu);
}
}
// Timer used to free the object from memory after the fade out animation has finished.
Timer {
id: closeTimer
interval: 150
onTriggered: {
tasksMenu.destroy();
}
}
ColumnLayout {
id: menuitems
z: 1
spacing: PlasmaCore.Units.smallSpacing/2
anchors {
left: parent.left
right: parent.right
bottom: staticMenuItems.top
leftMargin: PlasmaCore.Units.smallSpacing*2
rightMargin: PlasmaCore.Units.smallSpacing*2
bottomMargin: PlasmaCore.Units.smallSpacing
}
Item {
Layout.fillHeight: true
}
Item {
height: PlasmaCore.Units.smallSpacing
}
}
ColumnLayout {
id: staticMenuItems
z: 1
spacing: PlasmaCore.Units.smallSpacing/2
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: PlasmaCore.Units.smallSpacing*2
Item {
Layout.fillHeight: true
}
Item {
height: PlasmaCore.Units.smallSpacing
}
TasksMenuItemWrapper {
id: startNewInstanceItem
visible: get(atm.CanLaunchNewInstance)
text: get(atm.AppName)
icon: menuDecoration
onClicked: tasksModel.requestNewInstance(modelIndex)
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
}
TasksMenuItemWrapper {
id: virtualDesktopsMenuItem
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
text: i18n("Move to Desktop...")
icon: "virtual-desktops"
visible: virtualDesktopInfo.numberOfDesktops > 1 && (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true && get(atm.IsVirtualDesktopsChangeable) === true)
onClicked: virtualDesktopsMenu.openRelative()
Connections {
target: virtualDesktopInfo
function onNumberOfDesktopsChanged() {Qt.callLater(virtualDesktopsMenu.refresh)}
function onDesktopIdsChanged() {Qt.callLater(virtualDesktopsMenu.refresh)}
function onDesktopNamesChanged() {Qt.callLater(virtualDesktopsMenu.refresh)}
}
PlasmaComponents.ContextMenu {
id: virtualDesktopsMenu
visualParent: virtualDesktopsMenuItem
onTriggered: {
tasksMenu.closeMenu();
}
function refresh() {
clearMenuItems();
if (virtualDesktopInfo.numberOfDesktops <= 1) {
return;
}
var menuItem = tasksMenu.newPlasmaMenuItem(virtualDesktopsMenu);
menuItem.text = i18n("Move &To Current Desktop");
menuItem.enabled = Qt.binding(function() {
return tasksMenu.visualParent && tasksMenu.get(atm.VirtualDesktops).indexOf(virtualDesktopInfo.currentDesktop) === -1;
});
menuItem.clicked.connect(function() {
tasksModel.requestVirtualDesktops(tasksMenu.modelIndex, [virtualDesktopInfo.currentDesktop]);
});
menuItem = tasksMenu.newPlasmaMenuItem(virtualDesktopsMenu);
menuItem.text = i18n("&All Desktops");
menuItem.checkable = true;
menuItem.checked = Qt.binding(function() {
return tasksMenu.visualParent && tasksMenu.get(atm.IsOnAllVirtualDesktops) === true;
});
menuItem.clicked.connect(function() {
tasksModel.requestVirtualDesktops(tasksMenu.modelIndex, []);
});
backend.setActionGroup(menuItem.action);
tasksMenu.newPlasmaSeparator(virtualDesktopsMenu);
for (var i = 0; i < virtualDesktopInfo.desktopNames.length; ++i) {
menuItem = tasksMenu.newPlasmaMenuItem(virtualDesktopsMenu);
menuItem.text = i18nc("1 = number of desktop, 2 = desktop name", "&%1 %2", i + 1, virtualDesktopInfo.desktopNames[i]);
menuItem.checkable = true;
menuItem.checked = Qt.binding((function(i) {
return function() { return tasksMenu.visualParent && tasksMenu.get(atm.VirtualDesktops).indexOf(virtualDesktopInfo.desktopIds[i]) > -1 };
})(i));
menuItem.clicked.connect((function(i) {
return function() { return tasksModel.requestVirtualDesktops(tasksMenu.modelIndex, [virtualDesktopInfo.desktopIds[i]]); };
})(i));
backend.setActionGroup(menuItem.action);
}
tasksMenu.newPlasmaSeparator(virtualDesktopsMenu);
menuItem = tasksMenu.newPlasmaMenuItem(virtualDesktopsMenu);
menuItem.text = i18n("&New Desktop");
menuItem.clicked.connect(function() {
tasksModel.requestNewVirtualDesktop(tasksMenu.modelIndex);
});
}
// Return mouse grabbing to the original context menu when the context menu closes
onStatusChanged: {
if(status == 3) {
plasmoid.nativeInterface.setMouseGrab(true, tasksMenu);
}
}
Component.onCompleted: {
if(virtualDesktopsMenuItem.visible) refresh()
}
}
}
TasksMenuItemWrapper {
id: activitiesDesktopsMenuItem
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
visible: activityInfo.numberOfRunningActivities > 1
&& (visualParent && !get(atm.IsLauncher)
&& !get(atm.IsStartup))
enabled: visible
text: i18n("Show in Activities...")
icon: "activities"
onClicked: activitiesDesktopsMenu.openRelative()
Connections {
target: activityInfo
function onNumberOfRunningActivitiesChanged() {
activitiesDesktopsMenu.refresh()
}
}
PlasmaComponents.ContextMenu {
id: activitiesDesktopsMenu
visualParent: activitiesDesktopsMenuItem
onTriggered: {
tasksMenu.closeMenu();
}
function refresh() {
clearMenuItems();
if (activityInfo.numberOfRunningActivities <= 1) {
return;
}
var menuItem = tasksMenu.newPlasmaMenuItem(activitiesDesktopsMenu);
menuItem.text = i18n("Add To Current Activity");
menuItem.enabled = Qt.binding(function() {
return tasksMenu.visualParent && tasksMenu.get(atm.Activities).length > 0 &&
tasksMenu.get(atm.Activities).indexOf(activityInfo.currentActivity) < 0;
});
menuItem.clicked.connect(function() {
tasksModel.requestActivities(tasksMenu.modelIndex, tasksMenu.get(atm.Activities).concat(activityInfo.currentActivity));
});
menuItem = tasksMenu.newPlasmaMenuItem(activitiesDesktopsMenu);
menuItem.text = i18n("All Activities");
menuItem.checkable = true;
menuItem.checked = Qt.binding(function() {
return tasksMenu.visualParent && tasksMenu.get(atm.Activities).length === 0;
});
menuItem.toggled.connect(function(checked) {
let newActivities = []; // will cast to an empty QStringList i.e all activities
if (!checked) {
newActivities = new Array(activityInfo.currentActivity);
}
tasksModel.requestActivities(tasksMenu.modelIndex, newActivities);
});
tasksMenu.newPlasmaSeparator(activitiesDesktopsMenu);
var runningActivities = activityInfo.runningActivities();
for (var i = 0; i < runningActivities.length; ++i) {
var activityId = runningActivities[i];
menuItem = tasksMenu.newPlasmaMenuItem(activitiesDesktopsMenu);
menuItem.text = activityInfo.activityName(runningActivities[i]);
menuItem.checkable = true;
menuItem.checked = Qt.binding( (function(activityId) {
return function() {
return tasksMenu.visualParent && tasksMenu.get(atm.Activities).indexOf(activityId) >= 0;
};
})(activityId));
menuItem.toggled.connect((function(activityId) {
return function (checked) {
var newActivities = tasksMenu.get(atm.Activities);
if (checked) {
newActivities = newActivities.concat(activityId);
} else {
var index = newActivities.indexOf(activityId)
if (index < 0) {
return;
}
newActivities.splice(index, 1);
}
return tasksModel.requestActivities(tasksMenu.modelIndex, newActivities);
};
})(activityId));
}
tasksMenu.newPlasmaSeparator(activitiesDesktopsMenu);
for (var i = 0; i < runningActivities.length; ++i) {
var activityId = runningActivities[i];
var onActivities = tasksMenu.get(atm.Activities);
// if the task is on a single activity, don't insert a "move to" item for that activity
if(onActivities.length == 1 && onActivities[0] == activityId) {
continue;
}
menuItem = tasksMenu.newPlasmaMenuItem(activitiesDesktopsMenu);
menuItem.text = i18n("Move to %1", activityInfo.activityName(activityId))
menuItem.clicked.connect((function(activityId) {
return function () {
return tasksModel.requestActivities(tasksMenu.modelIndex, [activityId]);
};
})(activityId));
}
tasksMenu.newPlasmaSeparator(activitiesDesktopsMenu);
}
onStatusChanged: {
if(status == 3) {
plasmoid.nativeInterface.setMouseGrab(true, tasksMenu);
}
}
Component.onCompleted: {
if(activitiesDesktopsMenuItem.visible) refresh()
}
}
}
TasksMenuItemWrapper {
id: launcherToggleAction
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
text: i18n("Pin this program to taskbar")
icon: "window-pin"
visible: visualParent
&& get(atm.IsLauncher) !== true
&& get(atm.IsStartup) !== true
&& plasmoid.immutability !== PlasmaCore.Types.SystemImmutable
&& (activityInfo.numberOfRunningActivities < 2)
&& !doesBelongToCurrentActivity()
function doesBelongToCurrentActivity() {
return tasksModel.launcherActivities(get(atm.LauncherUrlWithoutIcon)).some(function(activity) {
return activity === activityInfo.currentActivity || activity === activityInfo.nullUuid;
});
}
onClicked: {
tasksModel.requestAddLauncher(get(atm.LauncherUrl));
tasksMenu.closeMenu();
}
}
TasksMenuItemWrapper {
id: showLauncherInActivitiesItem
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
text: i18n("Pin this program to taskbar") + "..."
icon: "window-pin"
visible: visualParent
&& get(atm.IsStartup) !== true
&& plasmoid.immutability !== PlasmaCore.Types.SystemImmutable
&& (activityInfo.numberOfRunningActivities >= 2)
onClicked: activitiesLaunchersMenu.openRelative()
Connections {
target: activityInfo
function onNumberOfRunningActivitiesChanged() {
activitiesDesktopsMenu.refresh()
}
}
PlasmaComponents.ContextMenu {
id: activitiesLaunchersMenu
visualParent: showLauncherInActivitiesItem
function refresh() {
clearMenuItems();
if (tasksMenu.visualParent === null) return;
var createNewItem = function(id, title, url, activities) {
var result = tasksMenu.newPlasmaMenuItem(activitiesLaunchersMenu);
result.text = title;
result.visible = true;
result.checkable = true;
result.checked = activities.some(function(activity) { return activity === id });
result.clicked.connect(
function() {
delayedMenu(150, function() {
if (result.checked) {
tasksModel.requestAddLauncherToActivity(url, id);
} else {
tasksModel.requestRemoveLauncherFromActivity(url, id);
}
tasksMenu.destroy();
});
}
);
return result;
}
if (tasksMenu.visualParent === null) return;
var url = tasksMenu.get(atm.LauncherUrlWithoutIcon);
var activities = tasksModel.launcherActivities(url);
createNewItem(activityInfo.nullUuid, i18n("On All Activities"), url, activities);
if (activityInfo.numberOfRunningActivities <= 1) {
return;
}
createNewItem(activityInfo.currentActivity, i18n("On The Current Activity"), url, activities);
tasksMenu.newPlasmaSeparator(activitiesLaunchersMenu);
var runningActivities = activityInfo.runningActivities();
runningActivities.forEach(function(id) {
createNewItem(id, activityInfo.activityName(id), url, activities);
});
}
onStatusChanged: {
if(status == 3) {
plasmoid.nativeInterface.setMouseGrab(true, tasksMenu);
}
}
Component.onCompleted: {
tasksMenu.onVisualParentChanged.connect(refresh);
refresh();
}
}
}
TasksMenuItemWrapper {
id: unpinFromTaskMan
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
visible: (visualParent
&& plasmoid.immutability !== PlasmaCore.Types.SystemImmutable
&& !launcherToggleAction.visible
&& activityInfo.numberOfRunningActivities < 2)
text: i18n("Unpin this program from taskbar")
icon: "window-unpin"
onClicked: {
delayedMenu(150, function() {
tasksModel.requestRemoveLauncher(get(atm.LauncherUrlWithoutIcon));
tasksMenu.destroy();
});
}
}
TasksMenuItemWrapper {
id: moreActionsMenuItem
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: visible
text: i18n("More") + "..."
icon: "view-more-symbolic"
onClicked: moreActionsMenu.openRelative();
PlasmaComponents.ContextMenu {
id: moreActionsMenu
visualParent: moreActionsMenuItem
onTriggered: {
plasmoid.nativeInterface.setMouseGrab(false, tasksMenu);
tasksMenu.closeMenu();
}
onStatusChanged: {
if(status == 3) {
plasmoid.nativeInterface.setMouseGrab(true, tasksMenu);
}
}
PlasmaComponents.MenuItem {
enabled: tasksMenu.visualParent && tasksMenu.get(atm.IsMovable) === true
text: i18n("&Move")
icon: "transform-move"
onClicked: tasksModel.requestMove(tasksMenu.modelIndex)
}
PlasmaComponents.MenuItem {
enabled: tasksMenu.visualParent && tasksMenu.get(atm.IsResizable) === true
text: i18n("Re&size")
icon: "transform-scale"
onClicked: tasksModel.requestResize(tasksMenu.modelIndex)
}
PlasmaComponents.MenuItem {
visible: (tasksMenu.visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: tasksMenu.visualParent && get(atm.IsMaximizable) === true
checkable: true
checked: tasksMenu.visualParent && get(atm.IsMaximized) === true
text: i18n("Ma&ximize")
icon: "window-maximize"
onClicked: tasksModel.requestToggleMaximized(modelIndex)
}
PlasmaComponents.MenuItem {
visible: (tasksMenu.visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: tasksMenu.visualParent && get(atm.IsMinimizable) === true
checkable: true
checked: tasksMenu.visualParent && get(atm.IsMinimized) === true
text: i18n("Mi&nimize")
icon: "window-minimize"
onClicked: tasksModel.requestToggleMinimized(modelIndex)
}
PlasmaComponents.MenuItem {
checkable: true
checked: tasksMenu.visualParent && tasksMenu.get(atm.IsKeepAbove) === true
text: i18n("Keep &Above Others")
icon: "window-keep-above"
onClicked: tasksModel.requestToggleKeepAbove(tasksMenu.modelIndex)
}
PlasmaComponents.MenuItem {
checkable: true
checked: tasksMenu.visualParent && tasksMenu.get(atm.IsKeepBelow) === true
text: i18n("Keep &Below Others")
icon: "window-keep-below"
onClicked: tasksModel.requestToggleKeepBelow(tasksMenu.modelIndex)
}
PlasmaComponents.MenuItem {
enabled: tasksMenu.visualParent && tasksMenu.get(atm.IsFullScreenable) === true
checkable: true
checked: tasksMenu.visualParent && tasksMenu.get(atm.IsFullScreen) === true
text: i18n("&Fullscreen")
icon: "view-fullscreen"
onClicked: tasksModel.requestToggleFullScreen(tasksMenu.modelIndex)
}
PlasmaComponents.MenuItem {
enabled: tasksMenu.visualParent && tasksMenu.get(atm.IsShadeable) === true
checkable: true
checked: tasksMenu.visualParent && tasksMenu.get(atm.IsShaded) === true
text: i18n("&Shade")
icon: "window-shade"
onClicked: tasksModel.requestToggleShaded(tasksMenu.modelIndex)
}
PlasmaComponents.MenuItem {
separator: true
}
PlasmaComponents.MenuItem {
visible: (plasmoid.configuration.groupingStrategy !== 0) && tasksMenu.get(atm.IsWindow) === true
checkable: true
checked: tasksMenu.visualParent && tasksMenu.get(atm.IsGroupable) === true
text: i18n("Allow this program to be grouped")
icon: "view-group"
onClicked: tasksModel.requestToggleGrouping(menu.modelIndex)
}
PlasmaComponents.MenuItem {
separator: true
}
PlasmaComponents.MenuItem {
property QtObject configureAction: null
enabled: configureAction && configureAction.enabled
visible: configureAction && configureAction.visible
text: configureAction ? configureAction.text : ""
icon: configureAction ? configureAction.icon : ""
onClicked: configureAction.trigger()
Component.onCompleted: configureAction = plasmoid.action("configure")
}
PlasmaComponents.MenuItem {
property QtObject alternativesAction: null
enabled: alternativesAction && alternativesAction.enabled
visible: alternativesAction && alternativesAction.visible
text: alternativesAction ? alternativesAction.text : ""
icon: alternativesAction ? alternativesAction.icon : ""
onClicked: alternativesAction.trigger()
Component.onCompleted: alternativesAction = plasmoid.action("alternatives")
}
}
}
TasksMenuItemWrapper {
id: closeWindowItem
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: visualParent && get(atm.IsClosable) === true
text: get(atm.IsGroupParent) ? "Close all windows" : i18n("Close window")
icon: "window-close"
onClicked: {
alsoCloseTask = true;
closeMenu();
}
}
/*TasksMenuItemWrapper {
id: testItem
Layout.fillWidth: true
Layout.preferredHeight: menuItemHeight
text: "Test"
icon: "window-close"
onClicked: {
}
}*/
}
Rectangle {
id: bgRect
visible: secondaryColumn
anchors {
top: parent.top
bottom: bgStatic.top
left: parent.left
right: parent.right
leftMargin: 2
rightMargin: 2
topMargin: 2
}
gradient: Gradient {
GradientStop { position: 0; color: backgroundColorStatic }
GradientStop { position: 0.5; color: backgroundColorGradient }
GradientStop { position: 1; color: backgroundColorStatic }
}
z: -2
}
Rectangle {
id: bgStatic
anchors {
top: staticMenuItems.top
bottom: parent.bottom
left: parent.left
right: parent.right
leftMargin: 2
rightMargin: 2
bottomMargin: 2
}
Rectangle {
id: bgStaticBorderLine
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: PlasmaCore.Units.smallSpacing
gradient: Gradient {
GradientStop { position: 0; color: borderColor }
GradientStop { position: 1; color: "transparent"}
}
}
z: -1
color: backgroundColorStatic
}
function decreaseItemIndex() {
currentItemIndex--;
if(currentItemIndex < 0) {
currentItemIndex = menuitems.children.length + staticMenuItems.children.length - 1;
}
var temp = currentItemIndex;
var container = menuitems.children;
if(currentItemIndex >= menuitems.children.length) {
temp -= menuitems.children.length;
container = staticMenuItems.children;
}
if(container[temp].objectName !== "menuitemwrapper" || (container[temp].objectName === "menuitemwrapper" && (!container[temp].enabled || !container[temp].visible))) {
decreaseItemIndex();
} else {
if(currentItem !== null) currentItem.selected = false;
container[temp].selected = true;
currentItem = container[temp];
}
}
function increaseItemIndex() {
currentItemIndex++;
if(currentItemIndex == menuitems.children.length + staticMenuItems.children.length) {
currentItemIndex = 0;
}
var temp = currentItemIndex;
var container = menuitems.children;
if(currentItemIndex >= menuitems.children.length) {
temp -= menuitems.children.length;
container = staticMenuItems.children;
}
if(container[temp].objectName !== "menuitemwrapper" || (container[temp].objectName === "menuitemwrapper" && (!container[temp].enabled || !container[temp].visible))) {
increaseItemIndex();
} else {
if(currentItem !== null) currentItem.selected = false;
container[temp].selected = true;
currentItem = container[temp];
}
}
Keys.onPressed: {
if(event.key == Qt.Key_Up) {
decreaseItemIndex();
}
else if(event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
increaseItemIndex();
}
else if(event.key == Qt.Key_Escape) {
tasksMenu.closeMenu();
}
else if(event.key == Qt.Key_Enter || event.key == Qt.Key_Return) {
if(currentItem !== null) {
currentItem.clicked();
}
}
}
/*
* Connects the context menu with the C++ part of the plasmoid.
* The native interface installs itself onto this dialog as an event filter, upon which
* all mouse click events are captured. By checking if the mouse has been clicked outside of
* the context menu, we can then safely close it.
*
* This works because right after creating the context menu, we have set this dialog window to
* grab all mouse events, which mimicks the way context menus work under Linux.
*
*/
Connections {
target: plasmoid.nativeInterface;
function onMouseEventDetected(mouse) {
if(!fscope.contains(plasmoid.nativeInterface.getPosition(tasksMenu))) {
tasksMenu.closeMenu();
}
}
/*onMouseEventDetected: {
}*/
}
}
Component.onCompleted: {
backend.showAllPlaces.connect(showContextMenuWithAllPlaces)
tasksMenu.backgroundHints = 2; // Sets the dialog background to the solid SVG variant.
}
Component.onDestruction: {
backend.showAllPlaces.disconnect(showContextMenuWithAllPlaces)
if(alsoCloseTask)
tasksModel.requestClose(modelIndex);
}
}