diff --git a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml index 8b41577e9..27a2333c8 100644 --- a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml +++ b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml @@ -1,475 +1,408 @@ /* * Copyright 2013 by Sebastian Kügler * Copyright 2014 by Martin Gräßlin * Copyright 2016 by Kai Uwe Broulik * Copyright 2017 by Roman Gilg +* Copyright 2020 by Nate Graham * * 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.6 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 import QtQml.Models 2.2 import org.kde.plasma.core 2.0 as PlasmaCore +// for Highlight import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.components 3.0 as PlasmaComponents3 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 { +ColumnLayout { property var submodelIndex property int flatIndex: isGroup && index != undefined ? index : 0 - spacing: units.smallSpacing - readonly property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(toolTipDelegate.launcherUrl, isGroup ? AppPid : pidParent) readonly property var playerData: mprisSourceName != "" ? mpris2Source.data[mprisSourceName] : 0 readonly property bool hasPlayer: !!mprisSourceName && !!playerData readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" readonly property bool canControl: hasPlayer && playerData.CanControl readonly property bool canPlay: hasPlayer && playerData.CanPlay readonly property bool canPause: hasPlayer && playerData.CanPause readonly property bool canGoBack: hasPlayer && playerData.CanGoPrevious readonly property bool canGoNext: hasPlayer && playerData.CanGoNext readonly property bool canRaise: hasPlayer && playerData.CanRaise readonly 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"] || "" - // + spacing: units.smallSpacing + // launcher icon + text labels + close button RowLayout { id: header - Layout.minimumWidth: childrenRect.width - Layout.maximumWidth: Layout.minimumWidth + spacing: units.smallSpacing - Layout.minimumHeight: childrenRect.height - Layout.maximumHeight: Layout.minimumHeight - - anchors.horizontalCenter: parent.horizontalCenter + // This number controls the overall size of the window tooltips + Layout.maximumWidth: units.gridUnit * 16 + Layout.minimumWidth: isWin ? Layout.maximumWidth : 0 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter // launcher icon PlasmaCore.IconItem { Layout.preferredWidth: units.iconSizes.medium Layout.preferredHeight: units.iconSizes.medium + Layout.leftMargin: units.smallSpacing + Layout.rightMargin: units.smallSpacing source: !isWin ? icon : "" animated: false usesPlasmaTheme: false visible: !isWin } + // all textlabels - Column { + ColumnLayout { + // app name PlasmaExtras.Heading { level: 3 - width: isWin ? textWidth : undefined - height: undefined maximumLineCount: 1 + Layout.fillWidth: true elide: Text.ElideRight text: appName opacity: flatIndex == 0 - textFormat: Text.PlainText - visible: text !== "" + visible: text.length !== 0 } // window title PlasmaExtras.Heading { id: winTitle level: 5 - width: isWin ? textWidth : undefined - height: undefined maximumLineCount: 1 + Layout.fillWidth: true elide: Text.ElideRight text: generateTitle() - textFormat: Text.PlainText opacity: 0.75 - visible: !hasPlayer + visible: !hasPlayer && text.length !== 0 } // subtext PlasmaExtras.Heading { level: 6 - width: isWin ? textWidth : undefined - height: undefined maximumLineCount: 1 + Layout.fillWidth: true elide: Text.ElideRight text: isWin ? generateSubText() : "" - textFormat: Text.PlainText opacity: 0.6 - visible: text !== "" + visible: text.length !== 0 } } // Count badge. + // The badge itself is inside an item to better center the text in the bubble Item { Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.preferredHeight: closeButton.height Layout.preferredWidth: closeButton.width + visible: flatIndex === 0 && smartLauncherCountVisible Badge { anchors.centerIn: parent height: units.iconSizes.smallMedium - visible: flatIndex === 0 && smartLauncherCountVisible number: smartLauncherCount } } // close button - PlasmaComponents.ToolButton { + PlasmaComponents3.ToolButton { id: closeButton Layout.alignment: Qt.AlignRight | Qt.AlignTop visible: isWin - iconSource: "window-close" + icon.name: "window-close" + icon.width: units.iconSizes.small + icon.height: units.iconSizes.small onClicked: { backend.cancelHighlightWindows(); tasksModel.requestClose(submodelIndex); } - } } // 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) + (!winTitle.visible? Math.round(winTitle.height) : 0) - 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 - - PlasmaComponents.Highlight { - anchors.fill: hoverHandler - visible: hoverHandler.containsMouse - pressed: hoverHandler.containsPress - } + id: thumbnailSourceItem - PlasmaCore.WindowThumbnail { - anchors.fill: hoverHandler - // Indent by one pixel to make sure we never cover up the entire highlight - anchors.margins: 1 + Layout.minimumWidth: header.width + Layout.preferredHeight: header.width / 2 - visible: !albumArtImage.visible && !thumbnailSourceItem.isMinimized - winId: thumbnailSourceItem.winId - } - - Image { - id: albumArtBackground - source: albumArt - anchors.fill: parent - fillMode: Image.PreserveAspectCrop - visible: albumArtImage.available - layer.enabled: true - opacity: 0.25 - layer.effect: FastBlur { - source: albumArtBackground - anchors.fill: parent - radius: 30 - } - } + visible: isWin - 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 + 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 - anchors.fill: hoverHandler - // Indent by one pixel to make sure we never cover up the entire highlight - anchors.margins: 1 - sourceSize: Qt.size(parent.width, parent.height) + // There's no PlasmaComponents3 version + PlasmaComponents.Highlight { + anchors.fill: hoverHandler + visible: hoverHandler.containsMouse + pressed: hoverHandler.containsPress + } - asynchronous: true - source: albumArt - fillMode: Image.PreserveAspectFit - visible: available - } + PlasmaCore.WindowThumbnail { + anchors.fill: hoverHandler + // Indent by one pixel to make sure we never cover up the entire highlight + anchors.margins: 1 - // when minimized, we don't have a preview, so show the icon - PlasmaCore.IconItem { - width: parent.width - height: thumbnail.height - playerControlsLoader.realHeight - anchors.horizontalCenter: parent.horizontalCenter - source: thumbnailSourceItem.isMinimized && !albumArtImage.visible ? icon : "" - animated: false - usesPlasmaTheme: false - visible: valid - } + visible: !albumArtImage.visible && !thumbnailSourceItem.isMinimized + winId: thumbnailSourceItem.winId + } - ToolTipWindowMouseArea { - id: hoverHandler + Image { + id: albumArtBackground + source: albumArt + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + visible: albumArtImage.available + layer.enabled: true + opacity: 0.25 + layer.effect: FastBlur { + source: albumArtBackground anchors.fill: parent - // Don't go under the player controls bar, when it's visible - anchors.bottomMargin: playerControlsLoader.realHeight - rootTask: parentTask - modelIndex: submodelIndex - winId: thumbnailSourceItem.winId + radius: 30 } } + 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 - Loader { - id: playerControlsLoader + anchors.fill: hoverHandler + // Indent by one pixel to make sure we never cover up the entire highlight + anchors.margins: 1 + sourceSize: Qt.size(parent.width, parent.height) - property real realHeight: item? item.realHeight : 0 - - anchors.fill: thumbnail - sourceComponent: hasPlayer ? playerControlsComp : undefined + asynchronous: true + source: albumArt + fillMode: Image.PreserveAspectFit + visible: available } - Component { - id: playerControlsComp - - Item { - property real realHeight: playerControlsRow.height - - anchors.fill: parent + // when minimized, we don't have a preview, so show the icon + PlasmaCore.IconItem { + width: parent.width + height: thumbnailSourceItem.height + anchors.horizontalCenter: parent.horizontalCenter + source: thumbnailSourceItem.isMinimized && !albumArtImage.visible ? icon : "" + animated: false + usesPlasmaTheme: false + visible: valid + } - // 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 - } + ToolTipWindowMouseArea { + id: hoverHandler + anchors.fill: parent + rootTask: parentTask + modelIndex: submodelIndex + winId: thumbnailSourceItem.winId + } + } - Rectangle { - anchors.bottom: parent.bottom - width: parent.width - height: playerControlsRow.height - color: theme.backgroundColor - opacity: 0.8 - } - } + // Player controls row + RowLayout { + Layout.maximumWidth: header.width - OpacityMask { - id: playerControlsOpacityMask - anchors.fill: parent - source: playerControlsFrostedGlass - maskSource: thumbnailSourceItem - } + visible: hasPlayer + enabled: canControl - // prevent accidental click-through when a control is disabled - MouseArea { - anchors.fill: playerControlsRow - } + ColumnLayout { + Layout.fillWidth: true + spacing: 0 - RowLayout { - id: playerControlsRow - anchors { - horizontalCenter: parent.horizontalCenter - bottom: parent.bottom - } - width: parent.width - spacing: 0 - enabled: canControl - - ColumnLayout { - Layout.leftMargin: 2 - Layout.fillWidth: true - spacing: 0 - - PlasmaComponents.Label { - Layout.fillWidth: true - lineHeight: 1 - maximumLineCount: artistText.visible? 1 : 2 - wrapMode: artistText.visible? Text.NoWrap : Text.Wrap - elide: Text.ElideRight - text: track || "" - } - - PlasmaExtras.DescriptiveLabel { - id: artistText - Layout.fillWidth: true - wrapMode: Text.NoWrap - lineHeight: 1 - elide: Text.ElideRight - text: artist || "" - visible: text != "" - font.pointSize: theme.smallestFont.pointSize - } - } + PlasmaComponents3.Label { + Layout.fillWidth: true + lineHeight: 1 + maximumLineCount: artistText.visible? 1 : 2 + wrapMode: artistText.visible? Text.NoWrap : Text.Wrap + elide: Text.ElideRight + text: track || "" + } - PlasmaComponents.ToolButton { - enabled: canGoBack - iconSource: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" - onClicked: mpris2Source.goPrevious(mprisSourceName) - } + PlasmaExtras.DescriptiveLabel { + id: artistText + Layout.fillWidth: true + wrapMode: Text.NoWrap + lineHeight: 1 + elide: Text.ElideRight + text: artist || "" + visible: text != "" + font.pointSize: theme.smallestFont.pointSize + } + } - PlasmaComponents.ToolButton { - enabled: playing ? canPause : canPlay - iconSource: playing ? "media-playback-pause" : "media-playback-start" - onClicked: { - if (!playing) { - mpris2Source.play(mprisSourceName); - } else { - mpris2Source.pause(mprisSourceName); - } - } - } + PlasmaComponents3.ToolButton { + enabled: canGoBack + icon.name: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" + icon.height: units.iconSizes.small + icon.width: units.iconSizes.small + onClicked: mpris2Source.goPrevious(mprisSourceName) + } - PlasmaComponents.ToolButton { - enabled: canGoNext - iconSource: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" - onClicked: mpris2Source.goNext(mprisSourceName) - } + PlasmaComponents3.ToolButton { + enabled: playing ? canPause : canPlay + icon.name: playing ? "media-playback-pause" : "media-playback-start" + icon.height: units.iconSizes.small + icon.width: units.iconSizes.small + onClicked: { + if (!playing) { + mpris2Source.play(mprisSourceName); + } else { + mpris2Source.pause(mprisSourceName); } } } + + PlasmaComponents3.ToolButton { + enabled: canGoNext + icon.name: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" + icon.height: units.iconSizes.small + icon.width: units.iconSizes.small + onClicked: mpris2Source.goNext(mprisSourceName) + } } 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 information (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 virtualDesktops = isGroup ? VirtualDesktops : virtualDesktopParent; var virtualDesktopNameList = new Array(); for (var i = 0; i < virtualDesktops.length; ++i) { virtualDesktopNameList.push(virtualDesktopInfo.desktopNames[virtualDesktops[i] - 1]); } if (!plasmoid.configuration.showOnlyCurrentDesktop && virtualDesktopInfo.numberOfDesktops > 1 && (isGroup ? IsOnAllVirtualDesktops : isOnAllVirtualDesktopsParent) !== true && virtualDesktops.length > 0) { subTextEntries.push(i18nc("Comma-separated list of desktops", "On %1", virtualDesktopNameList.join())); } 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"); } }