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); } }