You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
latte-dock/plasmoid/package/contents/ui/previews/ToolTipInstance.qml

556 lines
20 KiB
QML

/*
* 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.6
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.draganddrop 2.0
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.latte 0.2 as Latte
Column {
id: instance
property var submodelIndex
property int flatIndex: isGroup && itemIndex>=0 ? itemIndex : 0
property bool isActive: (typeof model !== 'undefined') && (typeof model.IsActive !== 'undefined') ? IsActive : false
property bool isMinimized: (typeof model !== 'undefined') && (typeof model.IsMinimized !== 'undefined') ? IsMinimized : false
property int appPid: (typeof model !== 'undefined') && (typeof model.AppPid !== 'undefined') ? AppPid : -1
property int itemIndex: (typeof model !== 'undefined') && (typeof model.index !== 'undefined') ? index : 0
property int virtualDesktop: (typeof model !== 'undefined') && (typeof model.VirtualDesktop !== 'undefined') ? VirtualDesktop : 0
property var activities : (typeof model !== 'undefined') && (typeof model.Activities !== 'undefined') ? Activities : []
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"] || ""
//
function containsMouse() {
return area1.containsMouse || area2.containsMouse
|| (playbackLoader.active && playbackLoader.item.containsMouse());
}
function isTaskActive() {
return (isGroup ? isActive : (parentTask ? parentTask.isActive : false));
}
// 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 {
Layout.preferredWidth: units.iconSizes.medium
Layout.preferredHeight: units.iconSizes.medium
source: icon
animated: false
usesPlasmaTheme: false
visible: !isWin
}
// all textlabels
Column {
PlasmaExtras.Heading {
level: 3
width: isWin ? textWidth : undefined
height: undefined
maximumLineCount: 1
elide: Text.ElideRight
text: appName
opacity: flatIndex == 0
textFormat: Text.PlainText
visible: text !== ""
}
// window title
PlasmaExtras.Heading {
id: winTitle
level: 5
width: isWin ? textWidth : undefined
height: undefined
maximumLineCount: 1
elide: Text.ElideRight
text: generateTitle()
textFormat: Text.PlainText
opacity: 0.75
visible: !hasPlayer
}
// subtext
PlasmaExtras.Heading {
level: 6
width: isWin ? textWidth : undefined
height: undefined
maximumLineCount: 1
elide: Text.ElideRight
text: isWin ? generateSubText() : ""
textFormat: Text.PlainText
opacity: 0.6
visible: text !== ""
}
}
// close button
MouseArea {
id: area1
Layout.alignment: Qt.AlignRight | Qt.AlignTop
height: units.iconSizes.smallMedium
width: height
visible: isWin && !hideCloseButtons
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: {
//NOTE: compatibility with plasma 5.8
if (backend.cancelHighlightWindows)
backend.cancelHighlightWindows()
tasksModel.requestClose(submodelIndex)
}
onContainsMouseChanged: mainToolTip.mouseIsInside();
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) + (!winTitle.visible? winTitle.height : 0)
anchors.horizontalCenter: parent.horizontalCenter
visible: isWin
Item {
id: thumbnailSourceItem
anchors.fill: parent
anchors.bottomMargin: 2
readonly property bool isMinimized: isGroup ? instance.isMinimized : mainToolTip.isMinimizedParent
// TODO: this causes XCB error message when being visible the first time
property int winId: isWin && windows[flatIndex] !== undefined ? windows[flatIndex] : 0
Loader{
id:previewThumbX11Loader
anchors.fill: parent
active: !Latte.WindowSystem.isPlatformWayland
visible: !albumArtImage.visible && !thumbnailSourceItem.isMinimized
sourceComponent: PlasmaCore.WindowThumbnail {
winId: thumbnailSourceItem.winId
}
}
ToolTipWindowMouseArea {
id: area2
anchors.fill: Latte.WindowSystem.isPlatformWayland ? parent : previewThumbX11Loader
rootTask: parentTask
modelIndex: submodelIndex
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
}
}
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
height: thumbnail.height - playbackLoader.realHeight
anchors.horizontalCenter: parent.horizontalCenter
sourceSize: Qt.size(parent.width, parent.height)
asynchronous: true
source: albumArt
fillMode: Image.PreserveAspectFit
visible: available
}
// when minimized, we don't have a preview, so show the icon
PlasmaCore.IconItem {
width: parent.width
height: thumbnail.height - playbackLoader.realHeight
anchors.horizontalCenter: parent.horizontalCenter
source: icon
animated: false
usesPlasmaTheme: false
visible: (thumbnailSourceItem.isMinimized && !albumArtImage.visible) //X11 case
|| (!previewThumbX11Loader.active && !albumArtImage.visible) //Wayland case
}
}
Loader {
id: playbackLoader
property real realHeight: item? item.realHeight : 0
anchors.fill: thumbnail
active: hasPlayer
sourceComponent: playerControlsComp
}
Component {
id: playerControlsComp
Item {
property real realHeight: playerControlsRow.height
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)
// }
function containsMouse() {
return area3.containsMouse || area4.containsMouse || area5.containsMouse || area6.containsMouse;
}
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
color: theme.backgroundColor
opacity: 0.8
}
}
OpacityMask {
id: playerControlsOpacityMask
anchors.fill: parent
source: playerControlsFrostedGlass
maskSource: thumbnailSourceItem
}
// prevent accidental click-through when a control is disabled
MouseArea {
id: area3
anchors.fill: playerControlsRow
hoverEnabled: true
onContainsMouseChanged: mainToolTip.mouseIsInside();
}
RowLayout {
id: playerControlsRow
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
}
width: parent.width
spacing: 0
enabled: canControl
ColumnLayout {
Layout.margins: 2
Layout.fillWidth: true
spacing: 0
PlasmaExtras.Heading {
Layout.fillWidth: true
level: 5
lineHeight: 1
maximumLineCount: artistText.visible? 1 : 2
wrapMode: artistText.visible? Text.NoWrap : Text.Wrap
elide: Text.ElideRight
text: track || ""
font.weight: Font.Bold
}
PlasmaExtras.Heading {
id: artistText
Layout.fillWidth: true
level: 5
wrapMode: Text.NoWrap
lineHeight: 1
elide: Text.ElideRight
text: artist || ""
visible: text != ""
opacity: 0.75
}
}
MouseArea {
id: area4
height: units.iconSizes.smallMedium
width: height
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: mpris2Source.goPrevious(mprisSourceName)
onContainsMouseChanged: mainToolTip.mouseIsInside();
PlasmaCore.IconItem {
anchors.fill: parent
enabled: canGoBack
active: parent.containsMouse
source: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward"
animated: false
}
}
MouseArea {
id: area5
height: units.iconSizes.medium
width: height
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: mpris2Source.playPause(mprisSourceName)
onContainsMouseChanged: mainToolTip.mouseIsInside();
PlasmaCore.IconItem {
anchors.fill: parent
active: parent.containsMouse
source: playing ? "media-playback-pause" : "media-playback-start"
animated: false
}
}
MouseArea {
id: area6
height: units.iconSizes.smallMedium
width: height
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: mpris2Source.goNext(mprisSourceName)
onContainsMouseChanged: mainToolTip.mouseIsInside();
PlasmaCore.IconItem {
anchors.fill: parent
enabled: canGoNext
active: parent.containsMouse
source: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward"
animated: false
}
}
}
}
}
//active window line
Rectangle{
width: parent.width
height: 2
color: isTaskActive() ? theme.buttonFocusColor : theme.buttonHoverColor
anchors.bottom: parent.bottom
visible: isTaskActive() || area2.containsMouse
}
}
function generateTitle() {
if (!isWin) {
return genericName != undefined ? genericName : "";
}
var text;
var modelExists = (typeof model !== 'undefined');
if (isGroup && modelExists) {
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 = [];
for (var i = 0; i < virtualDesktops.length; ++i) {
virtualDesktopNameList.push(virtualDesktopInfo.desktopNames[virtualDesktops[i] - 1]);
}
if (!root.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 (root.showOnlyCurrentActivity) {
if (activity !== activityInfo.currentActivity) {
activityNames.push(activityName);
}
} else if (activity !== activityInfo.currentActivity) {
activityNames.push(activityName);
}
}
if (root.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");
}
}