diff --git a/applets/taskmanager/package/contents/config/main.xml b/applets/taskmanager/package/contents/config/main.xml
index 5fece6dce..0e104bd15 100644
--- a/applets/taskmanager/package/contents/config/main.xml
+++ b/applets/taskmanager/package/contents/config/main.xml
@@ -1,95 +1,99 @@
false
false
true
false
1
true
true
2
true
2
1
false
true
true
false
0
true
+
+
+ true
+
diff --git a/applets/taskmanager/package/contents/ui/ConfigGeneral.qml b/applets/taskmanager/package/contents/ui/ConfigGeneral.qml
index de12afc62..efa960a75 100644
--- a/applets/taskmanager/package/contents/ui/ConfigGeneral.qml
+++ b/applets/taskmanager/package/contents/ui/ConfigGeneral.qml
@@ -1,236 +1,243 @@
/***************************************************************************
* Copyright (C) 2013 by Eike Hein *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
import QtQuick 2.0
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
width: childrenRect.width
height: childrenRect.height
property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical)
property alias cfg_forceStripes: forceStripes.checked
property alias cfg_showToolTips: showToolTips.checked
property alias cfg_wheelEnabled: wheelEnabled.checked
property alias cfg_highlightWindows: highlightWindows.checked
property alias cfg_smartLaunchersEnabled: smartLaunchers.checked
+ property alias cfg_indicateAudioStreams: indicateAudioStreams.checked
property alias cfg_maxStripes: maxStripes.value
property alias cfg_groupingStrategy: groupingStrategy.currentIndex
property alias cfg_middleClickAction: middleClickAction.currentIndex
property alias cfg_groupPopups: groupPopups.checked
property alias cfg_onlyGroupWhenFull: onlyGroupWhenFull.checked
property alias cfg_sortingStrategy: sortingStrategy.currentIndex
property alias cfg_separateLaunchers: separateLaunchers.checked
property alias cfg_showOnlyCurrentScreen: showOnlyCurrentScreen.checked
property alias cfg_showOnlyCurrentDesktop: showOnlyCurrentDesktop.checked
property alias cfg_showOnlyCurrentActivity: showOnlyCurrentActivity.checked
property alias cfg_showOnlyMinimized: showOnlyMinimized.checked
ColumnLayout {
anchors.left: parent.left
GroupBox {
Layout.fillWidth: true
title: i18n("Arrangement")
flat: true
GridLayout {
columns: 2
Layout.fillWidth: true
Label {
text: vertical ? i18n("Maximum columns:") : i18n("Maximum rows:")
}
SpinBox {
id: maxStripes
minimumValue: 1
}
CheckBox {
id: forceStripes
Layout.column: 1
Layout.row: 1
text: vertical ? i18n("Always arrange tasks in rows of as many columns") : i18n("Always arrange tasks in columns of as many rows")
enabled: maxStripes.value > 1
}
}
}
GroupBox {
Layout.fillWidth: true
title: i18n("Behavior")
flat: true
ColumnLayout {
Layout.fillWidth: true
CheckBox {
id: showToolTips
text: i18n("Show tooltips")
}
CheckBox {
id: wheelEnabled
text: i18n("Cycle through tasks with mouse wheel")
}
CheckBox {
id: highlightWindows
text: i18n("Highlight windows")
}
CheckBox {
id: smartLaunchers
Layout.fillWidth: true
text: i18n("Show progress and status information in task buttons")
}
+ CheckBox {
+ id: indicateAudioStreams
+ Layout.fillWidth: true
+ text: i18n("Denote applications that play audio")
+ }
+
RowLayout {
Label {
text: i18n("On middle-click:")
}
ComboBox {
id: middleClickAction
Layout.fillWidth: true
model: [i18nc("The click action", "None"), i18n("Close Window or Group"), i18n("New Instance"), i18n("Minimize/Restore Window or Group")]
}
}
}
}
GroupBox {
Layout.fillWidth: true
title: i18n("Grouping and Sorting")
flat: true
visible: (plasmoid.pluginName != "org.kde.plasma.icontasks")
ColumnLayout {
GridLayout {
columns: 3
Label {
Layout.fillWidth: true
text: i18n("Sorting:")
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: sortingStrategy
Layout.fillWidth: true
model: [i18n("Do Not Sort"), i18n("Manually"), i18n("Alphabetically"), i18n("By Desktop"), i18n("By Activity")]
}
CheckBox {
id: separateLaunchers
Layout.column: 1
Layout.row: 1
Layout.columnSpan: 2
text: i18n("Keep launchers separate")
enabled: sortingStrategy.currentIndex == 1
}
Label {
Layout.fillWidth: true
Layout.row: 2
Layout.column: 0
text: i18n("Grouping:")
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: groupingStrategy
Layout.row: 2
Layout.column: 1
Layout.fillWidth: true
model: [i18n("Do Not Group"), i18n("By Program Name")]
}
CheckBox {
id: groupPopups
Layout.column: 1
Layout.row: 3
Layout.columnSpan: 2
text: i18n("Open groups in popups")
enabled: groupingStrategy.currentIndex > 0
}
Item {
width: childrenRect.width
height: childrenRect.height
Layout.column: 1
Layout.row: 4
Layout.columnSpan: 2
CheckBox {
id: onlyGroupWhenFull
anchors.left: parent.left
anchors.leftMargin: units.gridUnit
text: i18n("Only group when the task manager is full")
enabled: groupingStrategy.currentIndex > 0 && groupPopups.checked
}
}
}
}
}
GroupBox {
Layout.fillWidth: true
title: i18n("Filters")
flat: true
ColumnLayout {
Layout.fillWidth: true
CheckBox {
id: showOnlyCurrentScreen
text: i18n("Show only tasks from the current screen")
}
CheckBox {
id: showOnlyCurrentDesktop
text: i18n("Show only tasks from the current desktop")
}
CheckBox {
id: showOnlyCurrentActivity
text: i18n("Show only tasks from the current activity")
}
CheckBox {
id: showOnlyMinimized
visible: (plasmoid.pluginName != "org.kde.plasma.icontasks")
text: i18n("Show only tasks that are minimized")
}
}
}
}
}
diff --git a/applets/taskmanager/package/contents/ui/ContextMenu.qml b/applets/taskmanager/package/contents/ui/ContextMenu.qml
index b86ced941..a09a429ff 100644
--- a/applets/taskmanager/package/contents/ui/ContextMenu.qml
+++ b/applets/taskmanager/package/contents/ui/ContextMenu.qml
@@ -1,633 +1,653 @@
/***************************************************************************
* Copyright (C) 2012-2016 by Eike Hein *
* Copyright (C) 2016 by Kai Uwe Broulik *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
import QtQuick 2.0
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
PlasmaComponents.ContextMenu {
id: menu
property QtObject mpris2Source
property var modelIndex
readonly property var atm: TaskManager.AbstractTasksModel
placement: {
if (plasmoid.location == PlasmaCore.Types.LeftEdge) {
return PlasmaCore.Types.RightPosedTopAlignedPopup;
} else if (plasmoid.location == PlasmaCore.Types.TopEdge) {
return PlasmaCore.Types.BottomPosedLeftAlignedPopup;
} else {
return PlasmaCore.Types.TopPosedLeftAlignedPopup;
}
}
minimumWidth: visualParent.width
onStatusChanged: {
if (visualParent && get(atm.LauncherUrlWithoutIcon) != null && status == PlasmaComponents.DialogStatus.Open) {
launcherToggleAction.checked = (tasksModel.launcherPosition(get(atm.LauncherUrlWithoutIcon)) != -1);
activitiesDesktopsMenu.refresh();
} else if (status == PlasmaComponents.DialogStatus.Closed) {
menu.destroy();
}
}
function get(modelProp) {
return tasksModel.data(modelIndex, modelProp)
}
function show() {
loadDynamicLaunchActions(get(atm.LauncherUrlWithoutIcon));
backend.ungrabMouse(visualParent);
openRelative();
}
function newMenuItem(parent) {
return Qt.createQmlObject(
"import org.kde.plasma.components 2.0 as PlasmaComponents;" +
"PlasmaComponents.MenuItem {}",
parent);
}
function newSeparator(parent) {
return Qt.createQmlObject(
"import org.kde.plasma.components 2.0 as PlasmaComponents;" +
"PlasmaComponents.MenuItem { separator: true }",
parent);
}
function loadDynamicLaunchActions(launcherUrl) {
var actionList = backend.jumpListActions(launcherUrl, menu);
for (var i = 0; i < actionList.length; ++i) {
var item = newMenuItem(menu);
item.action = actionList[i];
menu.addMenuItem(item, virtualDesktopsMenuItem);
}
if (actionList.length > 0) {
menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem);
}
var actionList = backend.recentDocumentActions(launcherUrl, menu);
for (var i = 0; i < actionList.length; ++i) {
var item = newMenuItem(menu);
item.action = actionList[i];
menu.addMenuItem(item, virtualDesktopsMenuItem);
}
if (actionList.length > 0) {
menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem);
}
// Add Media Player control actions
var sourceName = mpris2Source.sourceNameForLauncherUrl(launcherUrl, get(atm.AppPid));
if (sourceName && !(get(atm.LegacyWinIdList) != undefined && get(atm.LegacyWinIdList).length > 1)) {
var playerData = mpris2Source.data[sourceName]
if (playerData.CanControl) {
var menuItem = menu.newMenuItem(menu);
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);
});
menu.addMenuItem(menuItem, virtualDesktopsMenuItem);
menuItem = menu.newMenuItem(menu);
// PlasmaCore Menu doesn't actually handle icons or labels changing at runtime...
menuItem.text = Qt.binding(function() {
return playerData.PlaybackStatus === "Playing" ? i18nc("Pause playback", "Pause") : i18nc("Start playback", "Play");
});
menuItem.icon = Qt.binding(function() {
return playerData.PlaybackStatus === "Playing" ? "media-playback-pause" : "media-playback-start";
});
menuItem.clicked.connect(function() {
mpris2Source.playPause(sourceName);
});
menu.addMenuItem(menuItem, virtualDesktopsMenuItem);
menuItem = menu.newMenuItem(menu);
menuItem.text = i18nc("Stop playback", "Stop");
menuItem.icon = "media-playback-stop";
menuItem.clicked.connect(function() {
mpris2Source.stop(sourceName);
});
menu.addMenuItem(menuItem, virtualDesktopsMenuItem);
menuItem = menu.newMenuItem(menu);
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);
});
menu.addMenuItem(menuItem, virtualDesktopsMenuItem);
menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem);
// 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 = menu.newMenuItem(menu);
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);
});
menu.addMenuItem(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 (!startNewInstanceItem.visible && playerData.CanRaise) {
menuItem = menu.newMenuItem(menu);
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);
});
menu.addMenuItem(menuItem, startNewInstanceItem);
}
}
}
+
+ // 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 (menu.visualParent.hasAudioStream) {
+ var muteItem = menu.newMenuItem(menu);
+ muteItem.checkable = true;
+ muteItem.checked = Qt.binding(function() {
+ return menu.visualParent && menu.visualParent.muted;
+ });
+ muteItem.clicked.connect(function() {
+ menu.visualParent.toggleMuted();
+ });
+ muteItem.text = i18n("Mute");
+ muteItem.icon = "audio-volume-muted";
+ menu.addMenuItem(muteItem, virtualDesktopsMenuItem);
+
+ menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem);
+ }
}
PlasmaComponents.MenuItem {
id: virtualDesktopsMenuItem
visible: virtualDesktopInfo.numberOfDesktops > 1
&& (visualParent && get(atm.IsLauncher) !== true
&& get(atm.IsStartup) !== true
&& get(atm.IsVirtualDesktopChangeable) === true)
enabled: visible
text: i18n("Move To &Desktop")
Connections {
target: virtualDesktopInfo
onNumberOfDesktopsChanged: virtualDesktopsMenu.refresh()
onDesktopNamesChanged: virtualDesktopsMenu.refresh()
}
PlasmaComponents.ContextMenu {
id: virtualDesktopsMenu
visualParent: virtualDesktopsMenuItem.action
function refresh() {
clearMenuItems();
if (virtualDesktopInfo.numberOfDesktops <= 1) {
return;
}
var menuItem = menu.newMenuItem(virtualDesktopsMenu);
menuItem.text = i18n("Move &To Current Desktop");
menuItem.enabled = Qt.binding(function() {
return menu.visualParent && menu.get(atm.VirtualDesktop) != virtualDesktopInfo.currentDesktop;
});
menuItem.clicked.connect(function() {
tasksModel.requestVirtualDesktop(menu.modelIndex, virtualDesktopInfo.currentDesktop);
});
menuItem = menu.newMenuItem(virtualDesktopsMenu);
menuItem.text = i18n("&All Desktops");
menuItem.checkable = true;
menuItem.checked = Qt.binding(function() {
return menu.visualParent && menu.get(atm.IsOnAllVirtualDesktops) === true;
});
menuItem.clicked.connect(function() {
tasksModel.requestVirtualDesktop(menu.modelIndex, 0);
});
backend.setActionGroup(menuItem.action);
menu.newSeparator(virtualDesktopsMenu);
for (var i = 0; i < virtualDesktopInfo.desktopNames.length; ++i) {
menuItem = menu.newMenuItem(virtualDesktopsMenu);
menuItem.text = i18nc("1 = number of desktop, 2 = desktop name", "&%1 Desktop %2", i + 1, virtualDesktopInfo.desktopNames[i]);
menuItem.checkable = true;
menuItem.checked = Qt.binding((function(i) {
return function() { return menu.visualParent && menu.get(atm.VirtualDesktop) == (i + 1) };
})(i));
menuItem.clicked.connect((function(i) {
return function() { return tasksModel.requestVirtualDesktop(menu.modelIndex, i + 1); };
})(i));
backend.setActionGroup(menuItem.action);
}
menu.newSeparator(virtualDesktopsMenu);
menuItem = menu.newMenuItem(virtualDesktopsMenu);
menuItem.text = i18n("&New Desktop");
menuItem.clicked.connect(function() {
tasksModel.requestVirtualDesktop(menu.modelIndex, virtualDesktopInfo.numberOfDesktops + 1)
});
}
Component.onCompleted: refresh()
}
}
PlasmaComponents.MenuItem {
id: activitiesDesktopsMenuItem
visible: activityInfo.numberOfRunningActivities > 1
&& (visualParent && !get(atm.IsLauncher)
&& !get(atm.IsStartup))
enabled: visible
text: i18n("Move To &Activity")
Connections {
target: activityInfo
onNumberOfRunningActivitiesChanged: activitiesDesktopsMenu.refresh()
}
PlasmaComponents.ContextMenu {
id: activitiesDesktopsMenu
visualParent: activitiesDesktopsMenuItem.action
function refresh() {
clearMenuItems();
if (activityInfo.numberOfRunningActivities <= 1) {
return;
}
var menuItem = menu.newMenuItem(activitiesDesktopsMenu);
menuItem.text = i18n("Add To Current Activity");
menuItem.enabled = Qt.binding(function() {
return menu.visualParent && menu.get(atm.Activities).length > 0 &&
menu.get(atm.Activities).indexOf(activityInfo.currentActivity) < 0;
});
menuItem.clicked.connect(function() {
tasksModel.requestActivities(menu.modelIndex, menu.get(atm.Activities).concat(activityInfo.currentActivity));
});
menuItem = menu.newMenuItem(activitiesDesktopsMenu);
menuItem.text = i18n("All Activities");
menuItem.checkable = true;
menuItem.checked = Qt.binding(function() {
return menu.visualParent && menu.get(atm.Activities).length === 0;
});
menuItem.clicked.connect(function() {
var checked = menuItem.checked;
var newActivities = undefined; // will cast to an empty QStringList i.e all activities
if (!checked) {
newActivities = new Array(activityInfo.currentActivity);
}
tasksModel.requestActivities(menu.modelIndex, newActivities);
});
menu.newSeparator(activitiesDesktopsMenu);
var runningActivities = activityInfo.runningActivities();
for (var i = 0; i < runningActivities.length; ++i) {
var activityId = runningActivities[i];
menuItem = menu.newMenuItem(activitiesDesktopsMenu);
menuItem.text = activityInfo.activityName(runningActivities[i]);
menuItem.checkable = true;
menuItem.checked = Qt.binding( (function(activityId) {
return function() {
return menu.visualParent && menu.get(atm.Activities).indexOf(activityId) >= 0;
};
})(activityId));
menuItem.clicked.connect((function(activityId) {
return function () {
var checked = menuItem.checked;
var newActivities = menu.get(atm.Activities);
if (checked) {
newActivities = newActivities.concat(activityId);
} else {
var index = newActivities.indexOf(activityId)
if (index < 0) {
return;
}
newActivities = newActivities.splice(index, 1);
}
return tasksModel.requestActivities(menu.modelIndex, newActivities);
};
})(activityId));
}
menu.newSeparator(activitiesDesktopsMenu);
}
Component.onCompleted: refresh()
}
}
PlasmaComponents.MenuItem {
visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: visualParent && get(atm.IsMinimizable) === true
checkable: true
checked: visualParent && get(atm.IsMinimized) === true
text: i18n("Mi&nimize")
onClicked: tasksModel.requestToggleMinimized(modelIndex)
}
PlasmaComponents.MenuItem {
visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: visualParent && get(atm.IsMaximizable) === true
checkable: true
checked: visualParent && get(atm.IsMaximized) === true
text: i18n("Ma&ximize")
onClicked: tasksModel.requestToggleMaximized(modelIndex)
}
PlasmaComponents.MenuItem {
id: startNewInstanceItem
visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: visualParent && get(atm.LauncherUrlWithoutIcon) != null
text: i18n("Start New Instance")
icon: "system-run"
onClicked: tasksModel.requestNewInstance(modelIndex)
}
PlasmaComponents.MenuItem {
id: launcherToggleAction
visible: visualParent
&& get(atm.IsLauncher) !== true
&& get(atm.IsStartup) !== true
&& plasmoid.immutability !== PlasmaCore.Types.SystemImmutable
&& (activityInfo.numberOfRunningActivities < 2)
enabled: visualParent && get(atm.LauncherUrlWithoutIcon) != ""
checkable: true
text: i18nc("Toggle action for showing a launcher button while the application is not running", "&Pin")
onClicked: {
if (tasksModel.launcherPosition(get(atm.LauncherUrlWithoutIcon)) != -1) {
tasksModel.requestRemoveLauncher(get(atm.LauncherUrlWithoutIcon));
} else {
tasksModel.requestAddLauncher(get(atm.LauncherUrl));
}
}
}
PlasmaComponents.MenuItem {
id: showLauncherInActivitiesItem
text: i18n("&Pin")
visible: visualParent
&& get(atm.IsLauncher) !== true
&& get(atm.IsStartup) !== true
&& plasmoid.immutability !== PlasmaCore.Types.SystemImmutable
&& (activityInfo.numberOfRunningActivities >= 2)
Connections {
target: activityInfo
onNumberOfRunningActivitiesChanged: activitiesDesktopsMenu.refresh()
}
PlasmaComponents.ContextMenu {
id: activitiesLaunchersMenu
visualParent: showLauncherInActivitiesItem.action
function refresh() {
clearMenuItems();
if (menu.visualParent === null) return;
var createNewItem = function(id, title, url, activities) {
var result = menu.newMenuItem(activitiesLaunchersMenu);
result.text = title;
result.visible = true;
result.checkable = true;
result.checked = activities.some(function(activity) { return activity === id });
result.clicked.connect(
function() {
if (result.checked) {
tasksModel.requestAddLauncherToActivity(url, id);
} else {
tasksModel.requestRemoveLauncherFromActivity(url, id);
}
}
);
return result;
}
if (menu.visualParent === null) return;
var url = menu.get(atm.LauncherUrlWithoutIcon);
var activities = tasksModel.launcherActivities(url);
var NULL_UUID = "00000000-0000-0000-0000-000000000000";
createNewItem(NULL_UUID, i18n("On All Activities"), url, activities);
if (activityInfo.numberOfRunningActivities <= 1) {
return;
}
createNewItem(activityInfo.currentActivity, i18n("On The Current Activity"), url, activities);
menu.newSeparator(activitiesLaunchersMenu);
var runningActivities = activityInfo.runningActivities();
runningActivities.forEach(function(id) {
createNewItem(id, activityInfo.activityName(id), url, activities);
});
}
Component.onCompleted: {
menu.onVisualParentChanged.connect(refresh);
refresh();
}
}
}
PlasmaComponents.MenuItem {
visible: (visualParent && get(atm.IsLauncher) === true) && plasmoid.immutability !== PlasmaCore.Types.SystemImmutable
text: i18nc("Remove launcher button for application shown while it is not running", "Unpin")
onClicked: tasksModel.requestRemoveLauncher(get(atm.LauncherUrlWithoutIcon));
}
PlasmaComponents.MenuItem {
id: moreActionsMenuItem
visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: visible
text: i18n("More Actions")
PlasmaComponents.ContextMenu {
visualParent: moreActionsMenuItem.action
PlasmaComponents.MenuItem {
enabled: menu.visualParent && menu.get(atm.IsMovable) === true
text: i18n("&Move")
icon: "transform-move"
onClicked: tasksModel.requestMove(menu.modelIndex)
}
PlasmaComponents.MenuItem {
enabled: menu.visualParent && menu.get(atm.IsResizable) === true
text: i18n("Re&size")
onClicked: tasksModel.requestResize(menu.modelIndex)
}
PlasmaComponents.MenuItem {
checkable: true
checked: menu.visualParent && menu.get(atm.IsKeepAbove) === true
text: i18n("Keep &Above Others")
icon: "go-up"
onClicked: tasksModel.requestToggleKeepAbove(menu.modelIndex)
}
PlasmaComponents.MenuItem {
checkable: true
checked: menu.visualParent && menu.get(atm.IsKeepBelow) === true
text: i18n("Keep &Below Others")
icon: "go-down"
onClicked: tasksModel.requestToggleKeepBelow(menu.modelIndex)
}
PlasmaComponents.MenuItem {
enabled: menu.visualParent && menu.get(atm.IsFullScreenable) === true
checkable: true
checked: menu.visualParent && menu.get(atm.IsFullScreen) === true
text: i18n("&Fullscreen")
icon: "view-fullscreen"
onClicked: tasksModel.requestToggleFullScreen(menu.modelIndex)
}
PlasmaComponents.MenuItem {
enabled: menu.visualParent && menu.get(atm.IsShadeable) === true
checkable: true
checked: menu.visualParent && menu.get(atm.IsShaded) === true
text: i18n("&Shade")
onClicked: tasksModel.requestToggleShaded(menu.modelIndex)
}
PlasmaComponents.MenuItem {
separator: true
}
PlasmaComponents.MenuItem {
visible: (plasmoid.configuration.groupingStrategy != 0) && menu.get(atm.IsWindow) === true
checkable: true
checked: menu.visualParent && menu.get(atm.IsGroupable) === true
text: i18n("Allow this program to be grouped")
onClicked: tasksModel.requestToggleGrouping(menu.modelIndex)
}
}
}
PlasmaComponents.MenuItem {
property QtObject configureAction: null
enabled: configureAction && configureAction.enabled
text: configureAction ? configureAction.text : ""
icon: configureAction ? configureAction.icon : ""
onClicked: configureAction.trigger()
Component.onCompleted: configureAction = plasmoid.action("configure")
}
PlasmaComponents.MenuItem {
separator: true
}
PlasmaComponents.MenuItem {
id: closeWindowItem
visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true)
enabled: visualParent && get(atm.IsClosable) === true
text: i18n("&Close")
icon: "window-close"
onClicked: tasksModel.requestClose(modelIndex)
}
}
diff --git a/applets/taskmanager/package/contents/ui/Task.qml b/applets/taskmanager/package/contents/ui/Task.qml
index 03fd3e011..4975f9a8d 100644
--- a/applets/taskmanager/package/contents/ui/Task.qml
+++ b/applets/taskmanager/package/contents/ui/Task.qml
@@ -1,462 +1,529 @@
/***************************************************************************
* Copyright (C) 2012-2013 by Eike Hein *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
import QtQuick 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.draganddrop 2.0
import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet
import "../code/layout.js" as LayoutManager
import "../code/tools.js" as TaskTools
MouseArea {
id: task
width: groupDialog.mainItem.width
height: Math.max(theme.mSize(theme.defaultFont).height, units.iconSizes.medium) + LayoutManager.verticalMargins()
visible: false
LayoutMirroring.enabled: (Qt.application.layoutDirection == Qt.RightToLeft)
LayoutMirroring.childrenInherit: (Qt.application.layoutDirection == Qt.RightToLeft)
readonly property var m: model
+ readonly property int pid: model.AppPid
property int itemIndex: index
property bool inPopup: false
property bool isWindow: model.IsWindow === true
property int childCount: model.ChildCount != undefined ? model.ChildCount : 0
property int previousChildCount: 0
property alias textWidth: label.implicitWidth
property bool pressed: false
property int pressX: -1
property int pressY: -1
property QtObject contextMenu: null
property int wheelDelta: 0
readonly property bool smartLauncherEnabled: plasmoid.configuration.smartLaunchersEnabled && !inPopup && model.IsStartup !== true
property QtObject smartLauncherItem: null
+ property Item audioStreamOverlay
+ property var audioStreams: []
+ readonly property bool hasAudioStream: plasmoid.configuration.indicateAudioStreams && audioStreams.length > 0
+ readonly property bool playingAudio: hasAudioStream && audioStreams.some(function (item) {
+ return !item.corked
+ })
+ readonly property bool muted: hasAudioStream && audioStreams.every(function (item) {
+ return item.muted
+ })
+
readonly property bool highlighted: (inPopup && activeFocus) || (!inPopup && containsMouse)
function hideToolTipTemporarily() {
toolTipArea.hideToolTip();
}
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MidButton
+ onPidChanged: updateAudioStreams()
+
onIsWindowChanged: {
if (isWindow) {
taskInitComponent.createObject(task);
}
}
onChildCountChanged: {
if (childCount > previousChildCount) {
tasksModel.requestPublishDelegateGeometry(modelIndex(), backend.globalRect(task), task);
}
previousChildCount = childCount;
}
onItemIndexChanged: {
hideToolTipTemporarily();
if (!inPopup && !tasks.vertical
&& (LayoutManager.calculateStripes() > 1 || !plasmoid.configuration.separateLaunchers)) {
tasks.requestLayout();
}
}
onContainsMouseChanged: {
if (containsMouse) {
if (inPopup) {
forceActiveFocus();
}
} else {
pressed = false;
}
if (model.IsWindow === true) {
tasks.windowsHovered(model.LegacyWinIdList, containsMouse);
}
}
onPressed: {
if (mouse.button == Qt.LeftButton || mouse.button == Qt.MidButton) {
pressed = true;
pressX = mouse.x;
pressY = mouse.y;
} else if (mouse.button == Qt.RightButton) {
tasks.createContextMenu(task, modelIndex()).show();
}
}
onReleased: {
if (pressed) {
if (mouse.button == Qt.MidButton) {
if (plasmoid.configuration.middleClickAction == TaskManagerApplet.Backend.NewInstance) {
tasksModel.requestNewInstance(modelIndex());
} else if (plasmoid.configuration.middleClickAction == TaskManagerApplet.Backend.Close) {
tasksModel.requestClose(modelIndex());
} else if (plasmoid.configuration.middleClickAction == TaskManagerApplet.Backend.ToggleMinimized) {
tasksModel.requestToggleMinimized(modelIndex());
}
} else if (mouse.button == Qt.LeftButton) {
TaskTools.activateTask(modelIndex(), model, mouse.modifiers, task);
if (plasmoid.configuration.showToolTips) {
hideToolTipTemporarily();
}
}
}
pressed = false;
pressX = -1;
pressY = -1;
}
onPositionChanged: {
// mouse.button is always 0 here, hence checking with mouse.buttons
if (pressX != -1 && mouse.buttons == Qt.LeftButton && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) {
tasks.dragSource = task;
dragHelper.startDrag(task, model.MimeType, model.MimeData,
model.LauncherUrlWithoutIcon, model.decoration);
pressX = -1;
pressY = -1;
return;
}
}
onWheel: {
if (plasmoid.configuration.wheelEnabled) {
wheelDelta = TaskTools.wheelActivateNextPrevTask(task, wheelDelta, wheel.angleDelta.y);
} else {
wheel.accepted = false;
}
}
onSmartLauncherEnabledChanged: {
if (smartLauncherEnabled && !smartLauncherItem) {
var smartLauncher = Qt.createQmlObject("
import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet;
TaskManagerApplet.SmartLauncherItem { }", task);
smartLauncher.launcherUrl = Qt.binding(function() { return model.LauncherUrlWithoutIcon; });
smartLauncherItem = smartLauncher;
}
}
+ onHasAudioStreamChanged: {
+ if (hasAudioStream) {
+ audioStreamIconLoader.active = true
+ }
+ }
+
Keys.onReturnPressed: TaskTools.activateTask(modelIndex(), model, event.modifiers, task)
Keys.onEnterPressed: Keys.onReturnPressed(event);
function modelIndex() {
return (inPopup ? tasksModel.makeModelIndex(groupDialog.visualParent.itemIndex, index)
: tasksModel.makeModelIndex(index));
}
+ function updateAudioStreams() {
+ if (!pid) {
+ task.audioStreams = [];
+ return;
+ }
+
+ var pa = pulseAudio.item;
+ if (!pa) {
+ task.audioStreams = [];
+ return;
+ }
+
+ task.audioStreams = pa.streamsForPid(pid);
+ }
+
+ function toggleMuted() {
+ if (muted) {
+ task.audioStreams.forEach(function (item) { item.unmute(); });
+ } else {
+ task.audioStreams.forEach(function (item) { item.mute(); });
+ }
+ }
+
+ Connections {
+ target: pulseAudio.item
+ ignoreUnknownSignals: true // Plasma-PA might not be available
+ onStreamsChanged: task.updateAudioStreams()
+ }
+
Component {
id: taskInitComponent
Timer {
id: timer
interval: units.longDuration * 2
repeat: false
onTriggered: {
parent.hoverEnabled = true;
if (parent.isWindow) {
tasksModel.requestPublishDelegateGeometry(parent.modelIndex(),
backend.globalRect(parent), parent);
}
timer.destroy();
}
Component.onCompleted: timer.start()
}
}
PlasmaCore.FrameSvgItem {
id: frame
anchors {
fill: parent
topMargin: (!tasks.vertical && taskList.rows > 1) ? units.smallSpacing / 4 : 0
bottomMargin: (!tasks.vertical && taskList.rows > 1) ? units.smallSpacing / 4 : 0
leftMargin: ((inPopup || tasks.vertical) && taskList.columns > 1) ? units.smallSpacing / 4 : 0
rightMargin: ((inPopup || tasks.vertical) && taskList.columns > 1) ? units.smallSpacing / 4 : 0
}
imagePath: "widgets/tasks"
property string basePrefix: "normal"
prefix: TaskTools.taskPrefix(basePrefix)
onRepaintNeeded: updatePrefix()
function updatePrefix() {
prefix = Qt.binding(function() {
return TaskTools.taskPrefix(basePrefix);
});
}
PlasmaCore.ToolTipArea {
id: toolTipArea
anchors.fill: parent
location: plasmoid.location
active: !inPopup && !groupDialog.visible && plasmoid.configuration.showToolTips
interactive: true
mainItem: toolTipDelegate
onContainsMouseChanged: {
if (containsMouse) {
toolTipDelegate.parentTask = task;
toolTipDelegate.parentIndex = itemIndex;
toolTipDelegate.appName = Qt.binding(function() {
return model.AppName;
});
toolTipDelegate.pidParent = Qt.binding(function() {
return model.AppPid;
});
toolTipDelegate.windows = Qt.binding(function() {
return model.LegacyWinIdList;
});
toolTipDelegate.isGroup = Qt.binding(function() {
return model.IsGroupParent == true;
});
toolTipDelegate.icon = Qt.binding(function() {
return model.decoration;
});
toolTipDelegate.launcherUrl = Qt.binding(function() {
return model.LauncherUrlWithoutIcon;
});
toolTipDelegate.isLauncher = Qt.binding(function() {
return model.IsLauncher == true;
});
toolTipDelegate.isMinimizedParent = Qt.binding(function() {
return model.IsMinimized == true;
});
toolTipDelegate.displayParent = Qt.binding(function() {
return model.display;
});
toolTipDelegate.genericName = Qt.binding(function() {
return model.GenericName;
});
toolTipDelegate.virtualDesktopParent = Qt.binding(function() {
return model.VirtualDesktop != undefined ? model.VirtualDesktop : 0;
});
toolTipDelegate.isOnAllVirtualDesktopsParent = Qt.binding(function() {
return model.IsOnAllVirtualDesktops == true;
});
toolTipDelegate.activitiesParent = Qt.binding(function() {
return model.Activities;
});
}
}
}
}
Loader {
anchors.fill: frame
asynchronous: true
source: "TaskProgressOverlay.qml"
active: plasmoid.configuration.smartLaunchersEnabled && task.smartLauncherItem && task.smartLauncherItem.progressVisible
}
Item {
id: iconBox
anchors {
left: parent.left
leftMargin: adjustMargin(true, parent.width, taskFrame.margins.left)
top: parent.top
topMargin: adjustMargin(false, parent.height, taskFrame.margins.top)
}
- width: (label.visible ? height
- : parent.width - adjustMargin(true, parent.width, taskFrame.margins.left)
- - adjustMargin(true, parent.width, taskFrame.margins.right))
+ width: height
height: (parent.height - adjustMargin(false, parent.height, taskFrame.margins.top)
- adjustMargin(false, parent.height, taskFrame.margins.bottom))
function adjustMargin(vert, size, margin) {
if (!size) {
return margin;
}
var margins = vert ? LayoutManager.horizontalMargins() : LayoutManager.verticalMargins();
if ((size - margins) < units.iconSizes.small) {
return Math.ceil((margin * (units.iconSizes.small / size)) / 2);
}
return margin;
}
//width: inPopup ? units.iconSizes.small : Math.min(height, parent.width - LayoutManager.horizontalMargins())
PlasmaCore.IconItem {
id: icon
anchors.fill: parent
active: task.highlighted || (task.contextMenu && task.contextMenu.status == PlasmaComponents.DialogStatus.Open)
enabled: true
usesPlasmaTheme: false
source: model.decoration
}
Loader {
// QTBUG anchors.fill in conjunction with the Loader doesn't reliably work on creation:
// have a window with a badge, move it from one screen to another, the new task item on the
// other screen will now have a glitched out badge mask.
width: parent.width
height: parent.height
asynchronous: true
source: "TaskBadgeOverlay.qml"
active: plasmoid.configuration.smartLaunchersEnabled && height >= units.iconSizes.small
&& task.smartLauncherItem && task.smartLauncherItem.countVisible
}
states: [
// Using a state transition avoids a binding loop between label.visible and
// the text label margin, which derives from the icon width.
State {
name: "standalone"
- when: !label.visible
+ when: !label.visible && !audioStreamIconLoader.shown
AnchorChanges {
target: iconBox
anchors.left: undefined
anchors.horizontalCenter: parent.horizontalCenter
}
PropertyChanges {
target: iconBox
anchors.leftMargin: 0
+ width: parent.width - adjustMargin(true, task.width, taskFrame.margins.left)
+ - adjustMargin(true, task.width, taskFrame.margins.right)
}
}
]
Loader {
anchors.fill: parent
active: model.IsStartup === true
sourceComponent: busyIndicator
}
Component {
id: busyIndicator
PlasmaComponents.BusyIndicator { anchors.fill: parent }
}
}
+ Loader {
+ id: audioStreamIconLoader
+
+ readonly property bool shown: item && item.visible
+
+ source: "AudioStream.qml"
+ width: Math.min(units.iconSizes.medium, iconBox.width)
+ height: Math.min(units.iconSizes.medium, iconBox.height)
+
+ anchors {
+ right: parent.right
+ rightMargin: iconBox.adjustMargin(true, parent.width, taskFrame.margins.right)
+ top: parent.top
+ topMargin: iconBox.adjustMargin(false, parent.height, taskFrame.margins.top)
+ }
+ }
+
PlasmaComponents.Label {
id: label
visible: (inPopup || !iconsOnly && model.IsLauncher !== true
&& (parent.width - iconBox.height - units.smallSpacing) >= (theme.mSize(theme.defaultFont).width * 7))
anchors {
fill: parent
leftMargin: taskFrame.margins.left + iconBox.width + units.smallSpacing
topMargin: taskFrame.margins.top
- rightMargin: taskFrame.margins.right
+ rightMargin: taskFrame.margins.right + (audioStreamIconLoader.shown ? (audioStreamIconLoader.width + units.smallSpacing) : 0)
bottomMargin: taskFrame.margins.bottom
}
text: model.display
wrapMode: Text.Wrap
elide: Text.ElideRight
textFormat: Text.PlainText
verticalAlignment: Text.AlignVCenter
}
states: [
State {
name: "launcher"
when: model.IsLauncher === true
PropertyChanges {
target: frame
basePrefix: ""
}
},
State {
name: "hovered"
when: task.highlighted || (contextMenu.status == PlasmaComponents.DialogStatus.Open && contextMenu.visualParent == task)
PropertyChanges {
target: frame
basePrefix: "hover"
}
},
State {
name: "attention"
when: model.IsDemandingAttention === true || (task.smartLauncherItem && task.smartLauncherItem.urgent)
PropertyChanges {
target: frame
basePrefix: "attention"
}
},
State {
name: "minimized"
when: model.IsMinimized === true && !(groupDialog.visible && groupDialog.visualParent == task)
PropertyChanges {
target: frame
basePrefix: "minimized"
}
},
State {
name: "active"
when: model.IsActive === true || groupDialog.visible && groupDialog.visualParent == task
PropertyChanges {
target: frame
basePrefix: "focus"
}
}
]
Component.onCompleted: {
if (!inPopup && model.IsWindow === true) {
var component = Qt.createComponent("GroupExpanderOverlay.qml");
component.createObject(task);
}
if (!inPopup && model.IsWindow !== true) {
taskInitComponent.createObject(task);
}
+
+ updateAudioStreams()
}
}
diff --git a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml
index 0496e0f21..d6f3f20f5 100644
--- a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml
+++ b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml
@@ -1,465 +1,473 @@
/*
* Copyright 2013 by Sebastian Kügler
* Copyright 2014 by Martin Gräßlin
* Copyright 2016 by Kai Uwe Broulik
* Copyright 2017 by Roman Gilg
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* 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 Library General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.7
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.0
import QtQml.Models 2.2
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons
import org.kde.taskmanager 0.1 as TaskManager
Column {
property var submodelIndex: tasksModel.makeModelIndex(parentIndex, isGroup ? index : -1)
property int flatIndex: isGroup && index != undefined ? index : 0
spacing: units.smallSpacing
property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(toolTipDelegate.launcherUrl, isGroup ? AppPid : pidParent)
property var playerData: mprisSourceName != "" ? mpris2Source.data[mprisSourceName] : 0
property bool hasPlayer: !!mprisSourceName && !!playerData
property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing"
property bool canControl: hasPlayer && playerData.CanControl
property bool canGoBack: hasPlayer && playerData.CanGoPrevious
property bool canGoNext: hasPlayer && playerData.CanGoNext
property bool canRaise: hasPlayer && playerData.CanRaise
property var currentMetadata: hasPlayer ? playerData.Metadata : ({})
readonly property string track: {
var xesamTitle = currentMetadata["xesam:title"]
if (xesamTitle) {
return xesamTitle;
}
// if no track title is given, print out the file name
var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : ""
if (!xesamUrl) {
return "";
}
var lastSlashPos = xesamUrl.lastIndexOf('/')
if (lastSlashPos < 0) {
return "";
}
var lastUrlPart = xesamUrl.substring(lastSlashPos + 1)
return decodeURIComponent(lastUrlPart);
}
readonly property string artist: currentMetadata["xesam:artist"] || ""
readonly property string albumArt: currentMetadata["mpris:artUrl"] || ""
//
// launcher icon + text labels + close button
RowLayout {
id: header
Layout.minimumWidth: childrenRect.width
Layout.maximumWidth: Layout.minimumWidth
Layout.minimumHeight: childrenRect.height
Layout.maximumHeight: Layout.minimumHeight
anchors.horizontalCenter: parent.horizontalCenter
// launcher icon
PlasmaCore.IconItem {
source: icon
animated: false
usesPlasmaTheme: false
visible: !isWin
}
// all textlabels
Column {
spacing: 0.75 * units.smallSpacing
PlasmaComponents.Label {
width: isWin ? textWidth : undefined
height: theme.mSize(theme.defaultFont).height
font.pointSize: -1
font.pixelSize: height
elide: Text.ElideRight
text: appName
opacity: flatIndex == 0
textFormat: Text.PlainText
}
// window title
PlasmaComponents.Label {
width: isWin ? textWidth : undefined
height: 0.75 * theme.mSize(theme.defaultFont).height
font.pointSize: -1
font.pixelSize: height
elide: Text.ElideRight
text: generateTitle()
textFormat: Text.PlainText
opacity: 0.75
}
// subtext
PlasmaComponents.Label {
width: isWin ? textWidth : undefined
height: 0.6 * theme.mSize(theme.defaultFont).height
font.pointSize: -1
font.pixelSize: height
elide: Text.ElideRight
text: isWin ? generateSubText() : ""
textFormat: Text.PlainText
opacity: 0.6
visible: text !== ""
}
}
// close button
MouseArea {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
height: units.iconSizes.smallMedium
width: height
visible: isWin
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: {
backend.cancelHighlightWindows();
tasksModel.requestClose(submodelIndex);
}
PlasmaCore.IconItem {
anchors.fill: parent
active: parent.containsMouse
source: "window-close"
animated: false
}
}
}
// thumbnail container
Item {
id: thumbnail
width: header.width
// similar to 0.5625 = 1 / (16:9) as most screens are
// round necessary, otherwise shadow mask for players has gap!
height: Math.round(0.5 * width)
anchors.horizontalCenter: parent.horizontalCenter
visible: isWin
Item {
id: thumbnailSourceItem
anchors.fill: parent
readonly property bool isMinimized: isGroup ? IsMinimized == true : isMinimizedParent
// TODO: this causes XCB error message when being visible the first time
property int winId: isWin && windows[flatIndex] != undefined ? windows[flatIndex] : 0
PlasmaCore.WindowThumbnail {
anchors.fill: parent
visible: !albumArtImage.visible && !thumbnailSourceItem.isMinimized
winId: thumbnailSourceItem.winId
ToolTipWindowMouseArea {
anchors.fill: parent
rootTask: parentTask
modelIndex: submodelIndex
winId: thumbnailSourceItem.winId
}
}
Image {
id: albumArtImage
// also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load
readonly property bool available: status === Image.Ready || status === Image.Loading
anchors.fill: parent
sourceSize: Qt.size(parent.width, parent.height)
asynchronous: true
source: albumArt
fillMode: Image.PreserveAspectCrop
visible: available
ToolTipWindowMouseArea {
anchors.fill: parent
rootTask: parentTask
modelIndex: submodelIndex
winId: thumbnailSourceItem.winId
}
}
// when minimized, we don't have a preview, so show the icon
PlasmaCore.IconItem {
anchors.fill: parent
source: icon
animated: false
usesPlasmaTheme: false
visible: thumbnailSourceItem.isMinimized && !albumArtImage.visible
ToolTipWindowMouseArea {
anchors.fill: parent
rootTask: parentTask
modelIndex: submodelIndex
winId: thumbnailSourceItem.winId
}
}
}
Loader {
anchors.fill: thumbnail
sourceComponent: hasPlayer ? playerControlsComp : undefined
}
Component {
id: playerControlsComp
Item {
anchors.fill: parent
// TODO: When could this really be the case? A not-launcher-task always has a window!?
// if there's no window associated with this task, we might still be able to raise the player
// MouseArea {
// id: raisePlayerArea
// anchors.fill: parent
// visible: !isWin || !windows[0] && canRaise
// onClicked: mpris2Source.raise(mprisSourceName)
// }
Item {
id: playerControlsFrostedGlass
anchors.fill: parent
visible: false // OpacityMask would render it
Rectangle {
width: parent.width
height: parent.height - playerControlsRow.height
opacity: 0
}
Rectangle {
anchors.bottom: parent.bottom
width: parent.width
height: playerControlsRow.height
opacity: 0.8
}
}
OpacityMask {
id: playerControlsOpacityMask
anchors.fill: parent
source: playerControlsFrostedGlass
maskSource: thumbnailSourceItem
}
// prevent accidental click-through when a control is disabled
MouseArea {
anchors.fill: playerControlsRow
}
RowLayout {
id: playerControlsRow
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
}
width: parent.width
spacing: 0
enabled: canControl
ColumnLayout {
Layout.fillWidth: true
spacing: 0
PlasmaExtras.Heading {
Layout.fillWidth: true
level: 4
wrapMode: Text.NoWrap
elide: Text.ElideRight
text: track || ""
}
PlasmaExtras.Heading {
Layout.fillWidth: true
level: 5
wrapMode: Text.NoWrap
elide: Text.ElideRight
text: artist || ""
}
}
MouseArea {
height: units.iconSizes.smallMedium
width: height
enabled: canGoBack
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: mpris2Source.goPrevious(mprisSourceName)
PlasmaCore.IconItem {
anchors.fill: parent
enabled: canGoBack
active: parent.containsMouse
source: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward"
animated: false
}
}
MouseArea {
height: units.iconSizes.medium
width: height
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: mpris2Source.playPause(mprisSourceName)
PlasmaCore.IconItem {
anchors.fill: parent
active: parent.containsMouse
source: playing ? "media-playback-pause" : "media-playback-start"
animated: false
}
}
MouseArea {
height: units.iconSizes.smallMedium
width: height
enabled: canGoNext
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: mpris2Source.goNext(mprisSourceName)
PlasmaCore.IconItem {
anchors.fill: parent
enabled: canGoNext
active: parent.containsMouse
source: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward"
animated: false
}
}
}
}
}
}
function generateTitle() {
if (!isWin) {
return genericName != undefined ? genericName : "";
}
var text;
if (isGroup) {
if (model.display == undefined) {
return "";
}
text = model.display.toString();
} else {
text = displayParent;
}
// KWin appends increasing integers in between pointy brackets to otherwise equal window titles.
// In this case save <#number> as counter and delete it at the end of text.
var counter = text.match(/<\d+>\W*$/);
text = text.replace(/\s*<\d+>\W*$/, "");
// Remove appName from the end of text.
var appNameRegex = new RegExp(appName + "$", "i");
text = text.replace(appNameRegex, "");
text = text.replace(/\s*(?:-|—)*\s*$/, "");
// Add counter back at the end.
if (counter != null) {
if (text == "") {
text = counter;
} else {
text = text + " " + counter;
}
}
// In case the window title had only redundant informations (i.e. appName), text is now empty.
// Add a hyphen to indicate that and avoid empty space.
if (text == "") {
text = "—";
}
return text.toString();
}
function generateSubText() {
if (activitiesParent == undefined) {
return "";
}
var subTextEntries = [];
var vd = isGroup ? VirtualDesktop : virtualDesktopParent;
if (!plasmoid.configuration.showOnlyCurrentDesktop
&& virtualDesktopInfo.numberOfDesktops > 1
&& (isGroup ? IsOnAllVirtualDesktops : isOnAllVirtualDesktopsParent) !== true
&& vd != -1
&& vd != undefined
&& virtualDesktopInfo.desktopNames[vd - 1] != undefined) {
subTextEntries.push(i18n("On %1", virtualDesktopInfo.desktopNames[vd - 1]));
}
var act = isGroup ? Activities : activitiesParent;
if (act == undefined) {
return subTextEntries.join("\n");
}
if (act.length == 0 && activityInfo.numberOfRunningActivities > 1) {
subTextEntries.push(i18nc("Which virtual desktop a window is currently on",
"Available on all activities"));
} else if (act.length > 0) {
var activityNames = [];
for (var i = 0; i < act.length; i++) {
var activity = act[i];
var activityName = activityInfo.activityName(act[i]);
if (activityName == "") {
continue;
}
if (plasmoid.configuration.showOnlyCurrentActivity) {
if (activity != activityInfo.currentActivity) {
activityNames.push(activityName);
}
} else if (activity != activityInfo.currentActivity) {
activityNames.push(activityName);
}
}
if (plasmoid.configuration.showOnlyCurrentActivity) {
if (activityNames.length > 0) {
subTextEntries.push(i18nc("Activities a window is currently on (apart from the current one)",
"Also available on %1", activityNames.join(", ")));
}
} else if (activityNames.length > 0) {
subTextEntries.push(i18nc("Which activities a window is currently on",
"Available on %1", activityNames.join(", ")));
}
}
+ if (parentTask.playingAudio) {
+ if (parentTask.muted) {
+ subTextEntries.push(i18nc("The application this window belongs to is muted", "Currently Muted"));
+ } else {
+ subTextEntries.push(i18nc("The application this window belongs to is playing audio", "Currently Playing Audio"));
+ }
+ }
+
return subTextEntries.join("\n");
}
}
diff --git a/applets/taskmanager/package/contents/ui/main.qml b/applets/taskmanager/package/contents/ui/main.qml
index 85b3563ab..0df4f389e 100644
--- a/applets/taskmanager/package/contents/ui/main.qml
+++ b/applets/taskmanager/package/contents/ui/main.qml
@@ -1,424 +1,430 @@
/***************************************************************************
* Copyright (C) 2012-2016 by Eike Hein *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet
import "../code/layout.js" as LayoutManager
import "../code/tools.js" as TaskTools
Item {
id: tasks
anchors.fill: parent
property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical)
property bool iconsOnly: (plasmoid.pluginName == "org.kde.plasma.icontasks")
property QtObject contextMenuComponent: Qt.createComponent("ContextMenu.qml");
Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation
Layout.fillWidth: true
Layout.fillHeight:true
Layout.minimumWidth: tasks.vertical ? 0 : LayoutManager.preferredMinWidth()
Layout.minimumHeight: !tasks.vertical ? 0 : LayoutManager.preferredMinHeight()
//BEGIN TODO: this is not precise enough: launchers are smaller than full tasks
Layout.preferredWidth: tasks.vertical ? units.gridUnit * 10 : ((LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxWidth()) / LayoutManager.calculateStripes());
Layout.preferredHeight: tasks.vertical ? ((LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxHeight()) / LayoutManager.calculateStripes()) : units.gridUnit * 2;
//END TODO
property Item dragSource: null
signal requestLayout
signal windowsHovered(variant winIds, bool hovered)
signal presentWindows(variant winIds)
onWidthChanged: {
taskList.width = LayoutManager.layoutWidth();
if (plasmoid.configuration.forceStripes) {
taskList.height = LayoutManager.layoutHeight();
}
}
onHeightChanged: {
if (plasmoid.configuration.forceStripes) {
taskList.width = LayoutManager.layoutWidth();
}
taskList.height = LayoutManager.layoutHeight();
}
onDragSourceChanged: {
if (dragSource == null) {
tasksModel.syncLaunchers();
}
}
TaskManager.TasksModel {
id: tasksModel
virtualDesktop: virtualDesktopInfo.currentDesktop
screenGeometry: plasmoid.screenGeometry
activity: activityInfo.currentActivity
filterByVirtualDesktop: plasmoid.configuration.showOnlyCurrentDesktop
filterByScreen: plasmoid.configuration.showOnlyCurrentScreen
filterByActivity: plasmoid.configuration.showOnlyCurrentActivity
filterNotMinimized: plasmoid.configuration.showOnlyMinimized
sortMode: iconsOnly ? TaskManager.TasksModel.SortManual
: sortModeEnumValue(plasmoid.configuration.sortingStrategy)
launchInPlace: iconsOnly
separateLaunchers: {
if (!iconsOnly && !plasmoid.configuration.separateLaunchers
&& plasmoid.configuration.sortingStrategy == 1) {
return false;
}
return true;
}
groupMode: iconsOnly ? TaskManager.TasksModel.GroupApplication
: sortModeEnumValue(plasmoid.configuration.groupingStrategy)
groupInline: !plasmoid.configuration.groupPopups
groupingWindowTasksThreshold: (plasmoid.configuration.onlyGroupWhenFull && !iconsOnly
? LayoutManager.optimumCapacity(width, height) + 1 : -1)
onLauncherListChanged: {
layoutTimer.restart();
plasmoid.configuration.launchers = launcherList;
}
onGroupingAppIdBlacklistChanged: {
plasmoid.configuration.groupingAppIdBlacklist = groupingAppIdBlacklist;
}
onGroupingLauncherUrlBlacklistChanged: {
plasmoid.configuration.groupingLauncherUrlBlacklist = groupingLauncherUrlBlacklist;
}
function sortModeEnumValue(index) {
switch (index) {
case 0:
return TaskManager.TasksModel.SortDisabled;
case 1:
return TaskManager.TasksModel.SortManual;
case 2:
return TaskManager.TasksModel.SortAlpha;
case 3:
return TaskManager.TasksModel.SortVirtualDesktop;
case 4:
return TaskManager.TasksModel.SortActivity;
default:
return TaskManager.TasksModel.SortDisabled;
}
}
function groupModeEnumValue(index) {
switch (index) {
case 0:
return TaskManager.TasksModel.GroupDisabled;
case 1:
return TaskManager.TasksModel.GroupApplications;
}
}
Component.onCompleted: {
launcherList = plasmoid.configuration.launchers;
groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist;
groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist;
// Only hook up view only after the above churn is done.
taskRepeater.model = tasksModel;
}
}
TaskManager.VirtualDesktopInfo {
id: virtualDesktopInfo
}
TaskManager.ActivityInfo {
id: activityInfo
}
TaskManagerApplet.Backend {
id: backend
taskManagerItem: groupDialog.visible ? null : tasks
toolTipItem: toolTipDelegate
highlightWindows: plasmoid.configuration.highlightWindows
onAddLauncher: {
tasks.addLauncher(url);
}
}
PlasmaCore.DataSource {
id: mpris2Source
engine: "mpris2"
connectedSources: sources
function sourceNameForLauncherUrl(launcherUrl, pid) {
if (!launcherUrl || launcherUrl == "") {
return "";
}
// MPRIS spec explicitly mentions that "DesktopEntry" is with .desktop extension trimmed
// Moreover, remove URL parameters, like wmClass (part after the question mark)
var desktopFileName = launcherUrl.toString().split('/').pop().split('?')[0].replace(".desktop", "")
for (var i = 0, length = connectedSources.length; i < length; ++i) {
var source = connectedSources[i];
// we intend to connect directly, otherwise the multiplexer steals the connection away
if (source === "@multiplex") {
continue;
}
var sourceData = data[source];
if (sourceData && sourceData.DesktopEntry === desktopFileName && sourceData.InstancePid === pid) {
return source;
}
}
return ""
}
function startOperation(source, op) {
var service = serviceForSource(source)
var operation = service.operationDescription(op)
return service.startOperationCall(operation)
}
function goPrevious(source) {
startOperation(source, "Previous");
}
function goNext(source) {
startOperation(source, "Next");
}
function playPause(source) {
startOperation(source, "PlayPause");
}
function stop(source) {
startOperation(source, "Stop");
}
function raise(source) {
startOperation(source, "Raise");
}
function quit(source) {
startOperation(source, "Quit");
}
}
+ Loader {
+ id: pulseAudio
+ source: "PulseAudio.qml"
+ active: plasmoid.configuration.indicateAudioStreams
+ }
+
Timer {
id: iconGeometryTimer
interval: 500
repeat: false
onTriggered: {
TaskTools.publishIconGeometries(taskList.children);
}
}
Binding {
target: plasmoid
property: "status"
value: (tasksModel.anyTaskDemandsAttention
? PlasmaCore.Types.NeedsAttentionStatus : PlasmaCore.Types.PassiveStatus)
}
Connections {
target: plasmoid
onLocationChanged: {
// This is on a timer because the panel may not have
// settled into position yet when the location prop-
// erty updates.
iconGeometryTimer.start();
}
}
Connections {
target: plasmoid.configuration
onLaunchersChanged: tasksModel.launcherList = plasmoid.configuration.launchers
onGroupingAppIdBlacklistChanged: tasksModel.groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist;
onGroupingLauncherUrlBlacklistChanged: tasksModel.groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist;
}
TaskManagerApplet.DragHelper {
id: dragHelper
dragIconSize: units.iconSizes.medium
}
PlasmaCore.FrameSvgItem {
id: taskFrame
visible: false;
imagePath: "widgets/tasks";
prefix: "normal"
}
PlasmaCore.Svg {
id: taskSvg
imagePath: "widgets/tasks"
}
MouseHandler {
id: mouseHandler
anchors.fill: parent
target: taskList
onUrlsDropped: {
// If all dropped URLs point to application desktop files, we'll add a launcher for each of them.
var createLaunchers = urls.every(function (item) {
return backend.isApplication(item)
});
if (createLaunchers) {
urls.forEach(function (item) {
addLauncher(item);
});
return;
}
if (!hoveredItem) {
return;
}
// DeclarativeMimeData urls is a QJsonArray but requestOpenUrls expects a proper QList.
var urlsList = backend.jsonArrayToUrlList(urls);
// Otherwise we'll just start a new instance of the application with the URLs as argument,
// as you probably don't expect some of your files to open in the app and others to spawn launchers.
tasksModel.requestOpenUrls(hoveredItem.modelIndex(), urlsList);
}
}
ToolTipDelegate {
id: toolTipDelegate
visible: false
}
TaskList {
id: taskList
anchors {
left: parent.left
top: parent.top
}
onWidthChanged: LayoutManager.layout(taskRepeater)
onHeightChanged: LayoutManager.layout(taskRepeater)
flow: {
if (tasks.vertical) {
return plasmoid.configuration.forceStripes ? Flow.LeftToRight : Flow.TopToBottom
}
return plasmoid.configuration.forceStripes ? Flow.TopToBottom : Flow.LeftToRight
}
onAnimatingChanged: {
if (!animating) {
TaskTools.publishIconGeometries(children);
}
}
function layout() {
taskList.width = LayoutManager.layoutWidth();
taskList.height = LayoutManager.layoutHeight();
LayoutManager.layout(taskRepeater);
}
Timer {
id: layoutTimer
interval: 0
repeat: false
onTriggered: taskList.layout()
}
Repeater {
id: taskRepeater
delegate: Task {}
onItemAdded: taskList.layout()
onItemRemoved: taskList.layout()
}
}
GroupDialog { id: groupDialog }
function hasLauncher(url) {
return tasksModel.launcherPosition(url) != -1;
}
function addLauncher(url) {
if (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) {
tasksModel.requestAddLauncher(url);
}
}
// This is called by plasmashell in response to a Meta+number shortcut.
function activateTaskAtIndex(index) {
if (typeof index !== "number") {
return;
}
var task = taskRepeater.itemAt(index);
if (task) {
TaskTools.activateTask(task.modelIndex(), task.m, null, task);
}
}
function resetDragSource() {
dragSource = null;
}
function createContextMenu(rootTask, modelIndex) {
var menu = tasks.contextMenuComponent.createObject(rootTask);
menu.visualParent = rootTask;
menu.modelIndex = modelIndex;
menu.mpris2Source = mpris2Source;
return menu;
}
Component.onCompleted: {
tasks.requestLayout.connect(layoutTimer.restart);
tasks.requestLayout.connect(iconGeometryTimer.restart);
tasks.windowsHovered.connect(backend.windowsHovered);
tasks.presentWindows.connect(backend.presentWindows);
dragHelper.dropped.connect(resetDragSource);
}
}