/** Copyright 2013 by Sebastian KΓΌgler <sebas@kde.org>* Copyright 2014 by Martin GrΓ€Γlin <mgraesslin@kde.org>* Copyright 2016 by Kai Uwe Broulik <kde@privat.broulik.de>* Copyright 2017 by Roman Gilg <subdiff@gmail.com>** 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.6import QtQuick.Layouts 1.1import QtGraphicalEffects 1.0import QtQml.Models 2.2import org.kde.plasma.core 2.0 as PlasmaCoreimport org.kde.plasma.components 2.0 as PlasmaComponentsimport org.kde.plasma.extras 2.0 as PlasmaExtrasimport org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddonsimport org.kde.taskmanager 0.1 as TaskManagerColumn {readonly property var submodelIndex: tasksModel.makeModelIndex(parentIndex, isGroup ? index : -1)readonly property int flatIndex: isGroup && index != undefined ? index : 0spacing: units.smallSpacingreadonly property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(toolTipDelegate.launcherUrl, isGroup ? AppPid : pidParent)readonly property var playerData: mprisSourceName != "" ? mpris2Source.data[mprisSourceName] : 0readonly property bool hasPlayer: !!mprisSourceName && !!playerDatareadonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing"readonly property bool canControl: hasPlayer && playerData.CanControlreadonly property bool canPlay: hasPlayer && playerData.CanPlayreadonly property bool canPause: hasPlayer && playerData.CanPausereadonly property bool canGoBack: hasPlayer && playerData.CanGoPreviousreadonly property bool canGoNext: hasPlayer && playerData.CanGoNextreadonly property bool canRaise: hasPlayer && playerData.CanRaisereadonly 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 namevar 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 buttonRowLayout {id: headerLayout.minimumWidth: childrenRect.widthLayout.maximumWidth: Layout.minimumWidthLayout.minimumHeight: childrenRect.heightLayout.maximumHeight: Layout.minimumHeightanchors.horizontalCenter: parent.horizontalCenter// launcher iconPlasmaCore.IconItem {Layout.preferredWidth: units.iconSizes.mediumLayout.preferredHeight: units.iconSizes.mediumsource: !isWin ? icon : ""animated: falseusesPlasmaTheme: falsevisible: !isWin}// all textlabelsColumn {PlasmaExtras.Heading {level: 3width: isWin ? textWidth : undefinedheight: undefinedmaximumLineCount: 1elide: Text.ElideRighttext: appNameopacity: flatIndex == 0textFormat: Text.PlainTextvisible: text !== ""}// window titlePlasmaExtras.Heading {level: 5width: isWin ? textWidth : undefinedheight: undefinedmaximumLineCount: 1elide: Text.ElideRighttext: generateTitle()textFormat: Text.PlainTextopacity: 0.75}// subtextPlasmaExtras.Heading {level: 6width: isWin ? textWidth : undefinedheight: undefinedmaximumLineCount: 1elide: Text.ElideRighttext: isWin ? generateSubText() : ""textFormat: Text.PlainTextopacity: 0.6visible: text !== ""}}// Count badge.Badge {Layout.alignment: Qt.AlignRight | Qt.AlignTopheight: units.iconSizes.smallMediumvisible: flatIndex === 0 && smartLauncherCountVisiblenumber: smartLauncherCount}// close buttonMouseArea {Layout.alignment: Qt.AlignRight | Qt.AlignTopheight: units.iconSizes.smallMediumwidth: heightvisible: isWinacceptedButtons: Qt.LeftButtonhoverEnabled: trueonClicked: {backend.cancelHighlightWindows();tasksModel.requestClose(submodelIndex);}PlasmaCore.IconItem {anchors.fill: parentactive: parent.containsMousesource: "window-close"animated: false}}}// thumbnail containerItem {id: thumbnailwidth: 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.horizontalCentervisible: isWinItem {id: thumbnailSourceItemanchors.fill: parentreadonly property bool isMinimized: isGroup ? IsMinimized == true : isMinimizedParent// TODO: this causes XCB error message when being visible the first timeproperty int winId: isWin && windows[flatIndex] != undefined ? windows[flatIndex] : 0PlasmaCore.WindowThumbnail {anchors.fill: parentvisible: !albumArtImage.visible && !thumbnailSourceItem.isMinimizedwinId: thumbnailSourceItem.winIdToolTipWindowMouseArea {anchors.fill: parentrootTask: parentTaskmodelIndex: submodelIndexwinId: thumbnailSourceItem.winId}}Image {id: albumArtImage// also Image.Loading to prevent loading thumbnails just because the album art takes a split second to loadreadonly property bool available: status === Image.Ready || status === Image.Loadinganchors.fill: parentsourceSize: Qt.size(parent.width, parent.height)asynchronous: truesource: albumArtfillMode: Image.PreserveAspectCropvisible: availableToolTipWindowMouseArea {anchors.fill: parentrootTask: parentTaskmodelIndex: submodelIndexwinId: thumbnailSourceItem.winId}}// when minimized, we don't have a preview, so show the iconPlasmaCore.IconItem {anchors.fill: parentsource: thumbnailSourceItem.isMinimized && !albumArtImage.visible ? icon : ""animated: falseusesPlasmaTheme: falsevisible: validToolTipWindowMouseArea {anchors.fill: parentrootTask: parentTaskmodelIndex: submodelIndexwinId: thumbnailSourceItem.winId}}}Loader {anchors.fill: thumbnailsourceComponent: hasPlayer ? playerControlsComp : undefined}Component {id: playerControlsCompItem {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: playerControlsFrostedGlassanchors.fill: parentvisible: false // OpacityMask would render itRectangle {width: parent.widthheight: parent.height - playerControlsRow.heightopacity: 0}Rectangle {anchors.bottom: parent.bottomwidth: parent.widthheight: playerControlsRow.heightcolor: theme.backgroundColoropacity: 0.8}}OpacityMask {id: playerControlsOpacityMaskanchors.fill: parentsource: playerControlsFrostedGlassmaskSource: thumbnailSourceItem}// prevent accidental click-through when a control is disabledMouseArea {anchors.fill: playerControlsRow}RowLayout {id: playerControlsRowanchors {horizontalCenter: parent.horizontalCenterbottom: parent.bottom}width: parent.widthspacing: 0enabled: canControlColumnLayout {Layout.fillWidth: truespacing: 0PlasmaExtras.Heading {Layout.fillWidth: truelevel: 4wrapMode: Text.NoWrapelide: Text.ElideRighttext: track || ""}PlasmaExtras.Heading {Layout.fillWidth: truelevel: 5wrapMode: Text.NoWrapelide: Text.ElideRighttext: artist || ""}}MouseArea {height: units.iconSizes.smallMediumwidth: heightenabled: canGoBackacceptedButtons: Qt.LeftButtonhoverEnabled: trueonClicked: mpris2Source.goPrevious(mprisSourceName)PlasmaCore.IconItem {anchors.fill: parentenabled: canGoBackactive: parent.containsMousesource: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward"animated: false}}MouseArea {height: units.iconSizes.mediumwidth: heightenabled: playing ? canPause : canPlayacceptedButtons: Qt.LeftButtonhoverEnabled: trueonClicked: {if (!playing) {mpris2Source.play(mprisSourceName);} else {mpris2Source.pause(mprisSourceName);}}PlasmaCore.IconItem {anchors.fill: parentenabled: playing ? canPause : canPlayactive: parent.containsMousesource: playing ? "media-playback-pause" : "media-playback-start"animated: false}}MouseArea {height: units.iconSizes.smallMediumwidth: heightenabled: canGoNextacceptedButtons: Qt.LeftButtonhoverEnabled: trueonClicked: mpris2Source.goNext(mprisSourceName)PlasmaCore.IconItem {anchors.fill: parentenabled: canGoNextactive: parent.containsMousesource: 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(", ")));}}return subTextEntries.join("\n");}}