Changeset View
Standalone View
applets/taskmanager/package/contents/ui/ToolTipDelegate.qml
Context not available. | |||||
2 | * Copyright 2013 by Sebastian Kügler <sebas@kde.org> | 2 | * Copyright 2013 by Sebastian Kügler <sebas@kde.org> | ||
---|---|---|---|---|---|
3 | * Copyright 2014 by Martin Gräßlin <mgraesslin@kde.org> | 3 | * Copyright 2014 by Martin Gräßlin <mgraesslin@kde.org> | ||
4 | * Copyright 2016 by Kai Uwe Broulik <kde@privat.broulik.de> | 4 | * Copyright 2016 by Kai Uwe Broulik <kde@privat.broulik.de> | ||
5 | * Copyright 2016 by Roman Gilg <subdiff@gmail.com> | ||||
5 | * | 6 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | 7 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Library General Public License as | 8 | * it under the terms of the GNU Library General Public License as | ||
Context not available. | |||||
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. | 20 | * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. | ||
20 | */ | 21 | */ | ||
21 | 22 | | |||
22 | import QtQuick 2.0 | 23 | import QtQuick 2.7 | ||
23 | import QtQuick.Layouts 1.1 | 24 | import QtQuick.Layouts 1.1 | ||
24 | import QtGraphicalEffects 1.0 | 25 | import QtGraphicalEffects 1.0 | ||
26 | import QtQml.Models 2.2 | ||||
25 | 27 | | |||
26 | import org.kde.plasma.core 2.0 as PlasmaCore | 28 | import org.kde.plasma.core 2.0 as PlasmaCore | ||
27 | import org.kde.plasma.components 2.0 as PlasmaComponents | 29 | import org.kde.plasma.components 2.0 as PlasmaComponents | ||
28 | import org.kde.plasma.extras 2.0 as PlasmaExtras | 30 | import org.kde.plasma.extras 2.0 as PlasmaExtras | ||
29 | import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons | 31 | import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons | ||
30 | 32 | | |||
31 | Column { | 33 | import org.kde.taskmanager 0.1 as TaskManager | ||
32 | id: tooltipContentItem | | |||
33 | | ||||
34 | property Item toolTip | | |||
35 | property var parentIndex | | |||
36 | property var windows | | |||
37 | property string mainText | | |||
38 | property string subText | | |||
39 | property variant icon | | |||
40 | property url launcherUrl | | |||
41 | property bool group: (windows !== undefined && windows.length > 1) | | |||
42 | | ||||
43 | readonly property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(launcherUrl) | | |||
44 | readonly property bool hasPlayer: !!mprisSourceName && !!playerData | | |||
45 | | ||||
46 | readonly property var playerData: mpris2Source.data[mprisSourceName] | | |||
47 | readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" | | |||
48 | readonly property bool canControl: hasPlayer && playerData.CanControl | | |||
49 | readonly property bool canGoBack: hasPlayer && playerData.CanGoPrevious | | |||
50 | readonly property bool canGoNext: hasPlayer && playerData.CanGoNext | | |||
51 | readonly property bool canRaise: hasPlayer && playerData.CanRaise | | |||
52 | readonly property var currentMetadata: hasPlayer ? playerData.Metadata : ({}) | | |||
53 | | ||||
54 | readonly property string track: { | | |||
55 | var xesamTitle = currentMetadata["xesam:title"] | | |||
56 | if (xesamTitle) { | | |||
57 | return xesamTitle | | |||
58 | } | | |||
59 | // if no track title is given, print out the file name | | |||
60 | var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" | | |||
61 | if (!xesamUrl) { | | |||
62 | return "" | | |||
63 | } | | |||
64 | var lastSlashPos = xesamUrl.lastIndexOf('/') | | |||
65 | if (lastSlashPos < 0) { | | |||
66 | return "" | | |||
67 | } | | |||
68 | var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) | | |||
69 | return decodeURIComponent(lastUrlPart) | | |||
70 | } | | |||
71 | readonly property string artist: currentMetadata["xesam:artist"] || "" | | |||
72 | readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" | | |||
73 | | ||||
74 | readonly property int thumbnailWidth: units.gridUnit * 15 | | |||
75 | readonly property int thumbnailHeight: units.gridUnit * 10 | | |||
76 | 34 | | |||
77 | property int preferredTextWidth: theme.mSize(theme.defaultFont).width * 30 | 35 | PlasmaExtras.ScrollArea { | ||
36 | id: tooltipContentItem | ||||
78 | 37 | | |||
79 | Layout.minimumWidth: Math.max(thumbnailWidth, windowRow.width, appLabelRow.width) + units.largeSpacing / 2 | 38 | property Item parentTask | ||
80 | Layout.minimumHeight: childrenRect.height + units.largeSpacing | 39 | property int parentIndex: parentTask.itemIndex | ||
40 | property bool isGroup: 1 < parentTask.m.ChildCount | ||||
hein: Sanity check: Why count children instead of the IsGroupParent role?
Also:
(1) For performance… | |||||
(1) Done romangg: (1) Done
(2) Was carried over from old tooltips. Otherwise we get lots of "can not assign… | |||||
hein: Hrm ... ok, but somehow still feels wrong :) | |||||
41 | property var windows: parentTask.m.LegacyWinIdList | ||||
42 | property string appName: parentTask.m.AppName | ||||
43 | property string genericName: parentTask.m.GenericName | ||||
44 | property int pid: parentTask.m.AppPid ? parentTask.m.AppPid : -1 | ||||
45 | property variant icon: parentTask.m.decoration | ||||
46 | property url launcherUrl: parentTask.m.LauncherUrlWithoutIcon | ||||
47 | property bool isLauncher: parentTask.m.IsLauncher == true | ||||
48 | property bool isWin: windows !== undefined | ||||
49 | | ||||
50 | readonly property bool isVerticalPanel: plasmoid.formFactor == PlasmaCore.Types.Vertical | ||||
51 | | ||||
52 | Layout.minimumWidth: contentLoader.width | ||||
81 | Layout.maximumWidth: Layout.minimumWidth | 53 | Layout.maximumWidth: Layout.minimumWidth | ||
54 | | ||||
55 | Layout.minimumHeight: contentLoader.height | ||||
82 | Layout.maximumHeight: Layout.minimumHeight | 56 | Layout.maximumHeight: Layout.minimumHeight | ||
83 | 57 | | |||
84 | LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft | 58 | LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft | ||
85 | LayoutMirroring.childrenInherit: true | 59 | LayoutMirroring.childrenInherit: true | ||
86 | 60 | | |||
87 | spacing: units.largeSpacing / 2 | 61 | property int textWidth: theme.mSize(theme.defaultFont).width * 20 | ||
88 | 62 | | |||
89 | states: State { | 63 | verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff | ||
90 | when: tooltipContentItem.hasPlayer | 64 | horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff | ||
65 | | ||||
66 | Component.onCompleted: { | ||||
67 | flickableItem.interactive = Qt.binding(function() { | ||||
68 | return isVerticalPanel ? contentLoader.height > viewport.height : contentLoader.width > viewport.width | ||||
69 | }); | ||||
70 | } | ||||
91 | 71 | | |||
92 | PropertyChanges { | 72 | Item { | ||
93 | target: thumbnailSourceItem | 73 | width: contentLoader.status == Loader.Ready ? contentLoader.width : 1 | ||
94 | opacity: 0 // cannot set visible to false or else WindowThumbnail won't provide thumbnail | 74 | height: contentLoader.status == Loader.Ready ? contentLoader.height : 1 | ||
75 | | ||||
76 | Loader { | ||||
77 | id: contentLoader | ||||
78 | sourceComponent: isWin && !isGroup || isLauncher ? tooltipInstance : contentGrid | ||||
95 | } | 79 | } | ||
96 | PropertyChanges { | 80 | | ||
97 | target: playerControlsOpacityMask | 81 | Component { | ||
98 | visible: true | 82 | id: contentGrid | ||
99 | source: thumbnailSourceItem | 83 | Grid { | ||
100 | maskSource: playerControlsShadowMask | 84 | rows: !isVerticalPanel | ||
85 | columns: isVerticalPanel | ||||
86 | flow: isVerticalPanel ? Grid.TopToBottom : Grid.LeftToRight | ||||
87 | spacing: units.largeSpacing | ||||
88 | | ||||
89 | Repeater { | ||||
90 | id: groupRepeater | ||||
91 | model: groupModel | ||||
92 | } | ||||
93 | } | ||||
101 | } | 94 | } | ||
102 | PropertyChanges { | 95 | | ||
103 | target: playerControlsRow | 96 | DelegateModel { | ||
104 | visible: true | 97 | id: groupModel | ||
98 | model: tasksModel | ||||
99 | rootIndex: tasksModel.makeModelIndex(parentIndex, -1) | ||||
100 | delegate: tooltipInstance | ||||
105 | } | 101 | } | ||
106 | } | | |||
107 | 102 | | |||
108 | Item { | 103 | // | ||
109 | id: thumbnailContainer | 104 | // text labels + thumbnail | ||
110 | width: Math.max(parent.width, windowRow.width) | 105 | Component { | ||
111 | height: albumArtImage.available ? albumArtImage.height : | 106 | id: tooltipInstance | ||
112 | raisePlayerArea.visible ? raisePlayerArea.height : | 107 | Column { | ||
113 | windowRow.height | 108 | property var submodelIndex: index != undefined ? tasksModel.makeModelIndex(parentIndex, isGroup ? index : -1) : 0 | ||
109 | property int flatIndex: isGroup && index != undefined ? index : 0 | ||||
110 | spacing: units.smallSpacing | ||||
111 | | ||||
112 | property url launcherUrl: tooltipContentItem.launcherUrl ? tooltipContentItem.launcherUrl : "" | ||||
113 | | ||||
114 | property bool mpris2SourceDefined: mpris2Source != undefined | ||||
115 | property string mprisSourceName: "" | ||||
116 | property var playerData: mprisSourceName != "" ? mpris2Source.data[mprisSourceName] : 0 | ||||
117 | property bool hasPlayer: !!mprisSourceName && !!playerData | ||||
118 | property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" | ||||
119 | property bool canControl: hasPlayer && playerData.CanControl | ||||
120 | property bool canGoBack: hasPlayer && playerData.CanGoPrevious | ||||
121 | property bool canGoNext: hasPlayer && playerData.CanGoNext | ||||
122 | property bool canRaise: hasPlayer && playerData.CanRaise | ||||
123 | property var currentMetadata: hasPlayer ? playerData.Metadata : ({}) | ||||
124 | | ||||
125 | readonly property string track: { | ||||
126 | var xesamTitle = currentMetadata["xesam:title"] | ||||
127 | if (xesamTitle) { | ||||
128 | return xesamTitle | ||||
129 | } | ||||
130 | // if no track title is given, print out the file name | ||||
131 | var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" | ||||
132 | if (!xesamUrl) { | ||||
133 | return "" | ||||
134 | } | ||||
135 | var lastSlashPos = xesamUrl.lastIndexOf('/') | ||||
136 | if (lastSlashPos < 0) { | ||||
137 | return "" | ||||
138 | } | ||||
139 | var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) | ||||
140 | return decodeURIComponent(lastUrlPart) | ||||
141 | } | ||||
142 | readonly property string artist: currentMetadata["xesam:artist"] || "" | ||||
143 | readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" | ||||
114 | 144 | | |||
115 | Item { | 145 | function checkPlayer() { | ||
116 | id: thumbnailSourceItem | 146 | if (!mpris2SourceDefined) { | ||
117 | anchors.fill: parent | 147 | return | ||
148 | } | ||||
149 | // set mprisSourceName here, otherwise we call sourceNameForLauncherUrl on every service update again (currentMetaData!) | ||||
150 | mprisSourceName = mpris2Source.sourceNameForLauncherUrl(launcherUrl, isGroup == true ? AppPid : pid) | ||||
151 | } | ||||
118 | 152 | | |||
119 | PlasmaExtras.ScrollArea { | 153 | // | ||
120 | id: scrollArea | 154 | // launcher icon + text labels + close button | ||
121 | anchors.horizontalCenter: parent.horizontalCenter | 155 | RowLayout { | ||
122 | width: Math.max(windowRow.width, thumbnailWidth) | 156 | id: header | ||
123 | height: parent.height | 157 | Layout.minimumWidth: childrenRect.width | ||
158 | Layout.maximumWidth: Layout.minimumWidth | ||||
124 | 159 | | |||
125 | visible: !albumArtImage.available | 160 | Layout.minimumHeight: childrenRect.height | ||
161 | Layout.maximumHeight: Layout.minimumHeight | ||||
126 | 162 | | |||
127 | verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff | 163 | anchors.horizontalCenter: parent.horizontalCenter | ||
128 | horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff | | |||
129 | 164 | | |||
130 | Component.onCompleted: { | 165 | // launcher icon | ||
131 | flickableItem.interactive = Qt.binding(function() { | 166 | PlasmaCore.IconItem { | ||
132 | return contentItem.width > viewport.width; | 167 | source: icon | ||
133 | }); | 168 | animated: false | ||
169 | usesPlasmaTheme: false | ||||
170 | visible: !isWin | ||||
171 | } | ||||
172 | // all textlabels | ||||
173 | Column { | ||||
174 | spacing: 0.75 * units.smallSpacing | ||||
hein: Why 0.75? That's not an even number of pixels ... | |||||
175 | PlasmaComponents.Label { | ||||
176 | width: isWin ? textWidth : undefined | ||||
177 | height: theme.mSize(theme.defaultFont).height | ||||
178 | font.pointSize: -1 | ||||
179 | font.pixelSize: height | ||||
180 | elide: Text.ElideRight | ||||
181 | text: appName | ||||
182 | opacity: flatIndex == 0 | ||||
183 | textFormat: Text.PlainText | ||||
184 | } | ||||
185 | // window title | ||||
186 | PlasmaComponents.Label { | ||||
187 | width: isWin ? textWidth : undefined | ||||
188 | height: 0.75 * theme.mSize(theme.defaultFont).height | ||||
189 | font.pointSize: -1 | ||||
190 | font.pixelSize: height | ||||
191 | elide: Text.ElideRight | ||||
192 | text: isWin && model.display != undefined ? generateTitle() : genericName | ||||
193 | textFormat: Text.PlainText | ||||
194 | opacity: 0.75 | ||||
195 | } | ||||
196 | // subtext | ||||
197 | PlasmaComponents.Label { | ||||
198 | width: isWin ? textWidth : undefined | ||||
199 | height: 0.6 * theme.mSize(theme.defaultFont).height | ||||
Why 0.6? Is that in the HIG somewhere? Maybe instead of Label we could use a Heading of a particular level? We can't have consistent typography across applets if applets contain magic numbers for sizes. hein: Why 0.6? Is that in the HIG somewhere? Maybe instead of Label we could use a Heading of a… | |||||
I wanted to use Headings with different levels. The problem is, that they always automatically create huge margins. Since we wanted to minimize the size of the tooltips as much as possible, I used instead relative sizes, which worked way better for me, because they give a more direct control over the look in the end. They are not really magic numbers, are they? Read the 0.75 value for example as: the window title font size is 75% as large as the font size of the app name. Of course on the other side you raise a valid point with the consistency. Though I've seen relative font sizes also elsewhere (digital clock). romangg: I wanted to use Headings with different levels. The problem is, that they always automatically… | |||||
The problem is that the next time someone else does this in another UI they might decide 0.70 is right-er than 0.75 - we need to discuss this at the next Monday hangout maybe. hein: The problem is that the next time someone else does this in another UI they might decide 0.70… | |||||
Why we want to make tooltip smaller? Your patch will effect all screens with higher resolution than your i.e. will be smaller to read. Do not use magic number, get it pro rata of screen resolution. You must apply your patch to FullHD or bigger to see the difference. anthonyfieroni: Why we want to make tooltip smaller? Your patch will effect all screens with higher resolution… | |||||
The font sizes are directly based on theme.mSize(theme.defaultFont).height, which scales with default font size and screen DPI. romangg: The font sizes are directly based on `theme.mSize(theme.defaultFont).height`, which scales with… | |||||
200 | font.pointSize: -1 | ||||
201 | font.pixelSize: height | ||||
202 | elide: Text.ElideRight | ||||
203 | text: isWin ? generateSubText() : "" | ||||
204 | textFormat: Text.PlainText | ||||
205 | opacity: 0.6 | ||||
206 | visible: text !== "" | ||||
207 | } | ||||
208 | } | ||||
209 | // close button | ||||
210 | MouseArea { | ||||
211 | Layout.alignment: Qt.AlignRight | Qt.AlignTop | ||||
212 | | ||||
213 | height: units.iconSizes.smallMedium | ||||
214 | width: height | ||||
215 | | ||||
216 | visible: isWin | ||||
217 | | ||||
218 | acceptedButtons: Qt.LeftButton | ||||
219 | hoverEnabled: true | ||||
220 | onClicked: { | ||||
221 | backend.cancelHighlightWindows(); | ||||
222 | tasksModel.requestClose(submodelIndex); | ||||
223 | } | ||||
224 | | ||||
225 | PlasmaCore.IconItem { | ||||
226 | anchors.fill: parent | ||||
227 | active: parent.containsMouse | ||||
228 | | ||||
229 | source: "window-close" | ||||
230 | animated: false | ||||
231 | } | ||||
232 | } | ||||
134 | } | 233 | } | ||
135 | 234 | | |||
136 | Row { | 235 | // thumbnail container | ||
137 | id: windowRow | 236 | Item { | ||
138 | spacing: units.largeSpacing | 237 | id: thumbnail | ||
238 | width: header.width | ||||
239 | // similar to 0.5625 = 1 / (16:9) as most screens are | ||||
Again, do not make a bad decisions, use Screen width, height and pixel ratio for HiDPI. import QtQuick.Window 2.2 property int maxWidth: Screen.width / Screen.devicePixelRatio property int maxHeight: Screen.height / Screen.devicePixelRatio anthonyfieroni: Again, do not make a bad decisions, use Screen width, height and pixel ratio for HiDPI.
```… | |||||
header.width, i.e. thumbnail.width is controlled by a constant factor of theme.mSize(theme.defaultFont).width, which again is font and screen DPI dependent, such that the thumbnail will always have the width of the maximal possible font and DPI dependent width of the app name in the header plus close the button width. Directly using Screen.width / ... doesn't make much sense to me. We want the thumbnail to fill out the available space defined by the header above it. The height variable in contrast is arbitrary and could be any value, which would always make somehow sense since windows can have any format by resizing them. Using the Screen height-width ratio on heider.width could be also a solution, but I'm not sure if people, who use their monitor in potrait mode want their thumbnails to be suddenly a lot higher than on the normal screen next to it, which also means that the tooltip needs more space on the portrait mode screen, since we cannot reduce the width on the other hand (needs horizontal space for app name / window title). romangg: `header.width`, i.e. `thumbnail.width` is controlled by a constant factor of `theme.mSize(theme. | |||||
240 | // round necessary, otherwise shadow mask for players has gap! | ||||
241 | height: Math.round(0.5 * width) | ||||
242 | | ||||
243 | visible: isWin | ||||
244 | | ||||
245 | anchors.horizontalCenter: parent.horizontalCenter | ||||
139 | 246 | | |||
140 | Repeater { | 247 | Item { | ||
141 | model: plasmoid.configuration.showToolTips && !albumArtImage.available ? windows : null | 248 | id: thumbnailSourceItem | ||
249 | anchors.fill: parent | ||||
250 | | ||||
251 | property int winId: isWin && windows[flatIndex] != undefined ? windows[flatIndex] : 0 | ||||
142 | 252 | | |||
143 | PlasmaCore.WindowThumbnail { | 253 | PlasmaCore.WindowThumbnail { | ||
144 | id: windowThumbnail | 254 | id: windowThumbnail | ||
255 | anchors.fill: parent | ||||
256 | | ||||
257 | visible: !albumArtImage.visible && IsMinimized == false | ||||
145 | 258 | | |||
146 | width: thumbnailWidth | 259 | // TODO: this causes XCB error message on first emergence | ||
147 | height: thumbnailHeight | 260 | winId: thumbnailSourceItem.winId | ||
261 | | ||||
262 | ToolTipWindowMouseArea { | ||||
263 | anchors.fill: parent | ||||
264 | rootTask: parentTask | ||||
265 | modelIndex: submodelIndex | ||||
266 | winId: thumbnailSourceItem.winId | ||||
267 | } | ||||
268 | } | ||||
269 | | ||||
270 | Image { | ||||
271 | id: albumArtImage | ||||
272 | // also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load | ||||
273 | readonly property bool available: status === Image.Ready || status === Image.Loading | ||||
274 | | ||||
275 | anchors.fill: parent | ||||
276 | sourceSize: Qt.size(parent.width, parent.height) | ||||
277 | asynchronous: true | ||||
278 | source: albumArt | ||||
279 | fillMode: Image.PreserveAspectCrop | ||||
280 | visible: available | ||||
281 | | ||||
282 | ToolTipWindowMouseArea { | ||||
283 | anchors.fill: parent | ||||
284 | rootTask: parentTask | ||||
285 | modelIndex: submodelIndex | ||||
286 | winId: thumbnailSourceItem.winId | ||||
287 | } | ||||
288 | } | ||||
148 | 289 | | |||
149 | winId: modelData | 290 | // when minimized, we don't have a preview, so show the icon | ||
291 | PlasmaCore.IconItem { | ||||
292 | anchors.fill: parent | ||||
293 | source: icon | ||||
294 | animated: false | ||||
295 | usesPlasmaTheme: false | ||||
296 | visible: IsMinimized == true && !albumArtImage.visible | ||||
150 | 297 | | |||
151 | ToolTipWindowMouseArea { | 298 | ToolTipWindowMouseArea { | ||
152 | anchors.fill: parent | 299 | anchors.fill: parent | ||
153 | modelIndex: tasksModel.makeModelIndex(parentIndex, group ? index : -1) | 300 | rootTask: parentTask | ||
154 | winId: modelData | 301 | modelIndex: submodelIndex | ||
155 | thumbnailItem: parent | 302 | winId: thumbnailSourceItem.winId | ||
156 | } | 303 | } | ||
157 | } | 304 | } | ||
158 | } | 305 | } | ||
159 | } | | |||
160 | } | | |||
161 | 306 | | |||
162 | Image { | | |||
163 | id: albumArtImage | | |||
164 | // also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load | | |||
165 | readonly property bool available: status === Image.Ready || status === Image.Loading | | |||
166 | | ||||
167 | anchors.centerIn: parent | | |||
168 | width: parent.width | | |||
169 | height: thumbnailHeight | | |||
170 | sourceSize: Qt.size(thumbnailWidth, thumbnailHeight) | | |||
171 | asynchronous: true | | |||
172 | source: albumArt | | |||
173 | fillMode: Image.PreserveAspectCrop | | |||
174 | visible: available | | |||
175 | | ||||
176 | ToolTipWindowMouseArea { | | |||
177 | anchors.fill: parent | | |||
178 | modelIndex: tasksModel.makeModelIndex(parentIndex, group ? index : -1) | | |||
179 | winId: windows != undefined ? (windows[0] || 0) : 0 | | |||
180 | } | | |||
181 | } | | |||
182 | 307 | | |||
183 | MouseArea { | 308 | Loader { | ||
184 | id: raisePlayerArea | 309 | anchors.fill: thumbnail | ||
185 | anchors.centerIn: parent | 310 | sourceComponent: hasPlayer ? playerControlsComp : undefined | ||
186 | width: thumbnailWidth | 311 | } | ||
187 | height: thumbnailHeight | | |||
188 | | ||||
189 | // if there's no window associated with this task, we might still be able to raise the player | | |||
190 | visible: windows == undefined || !windows[0] && canRaise | | |||
191 | onClicked: mpris2Source.raise(mprisSourceName) | | |||
192 | | ||||
193 | PlasmaCore.IconItem { | | |||
194 | anchors.fill: parent | | |||
195 | source: icon | | |||
196 | animated: false | | |||
197 | usesPlasmaTheme: false | | |||
198 | visible: !albumArtImage.available | | |||
199 | } | | |||
200 | } | | |||
201 | } | | |||
202 | 312 | | |||
203 | Item { | 313 | Component { | ||
204 | id: playerControlsShadowMask | 314 | id: playerControlsComp | ||
205 | anchors.fill: thumbnailSourceItem | | |||
206 | visible: false // OpacityMask would render it | | |||
207 | 315 | | |||
208 | Rectangle { | 316 | Item { | ||
209 | width: parent.width | 317 | anchors.fill: parent | ||
210 | height: parent.height - playerControlsRow.height | | |||
211 | } | | |||
212 | 318 | | |||
213 | Rectangle { | 319 | // TODO: When could this really be the case? A not-launcher-task always has a window!? | ||
214 | anchors.bottom: parent.bottom | 320 | // if there's no window associated with this task, we might still be able to raise the player | ||
215 | width: parent.width | 321 | // MouseArea { | ||
216 | height: playerControlsRow.height | 322 | // id: raisePlayerArea | ||
217 | opacity: 0.2 | 323 | // anchors.fill: parent | ||
218 | } | | |||
219 | } | | |||
220 | 324 | | |||
221 | OpacityMask { | 325 | // visible: !isWin || !windows[0] && canRaise | ||
222 | id: playerControlsOpacityMask | 326 | // onClicked: mpris2Source.raise(mprisSourceName) | ||
223 | anchors.fill: thumbnailSourceItem | 327 | // } | ||
224 | visible: false | | |||
225 | } | | |||
226 | 328 | | |||
227 | // prevent accidental click-through when a control is disabled | 329 | Item { | ||
228 | MouseArea { | 330 | id: playerControlsFrostedGlass | ||
229 | anchors.fill: playerControlsRow | 331 | anchors.fill: parent | ||
230 | enabled: playerControlsRow.visible | 332 | visible: false // OpacityMask would render it | ||
231 | } | 333 | | ||
334 | Rectangle { | ||||
335 | width: parent.width | ||||
336 | height: parent.height - playerControlsRow.height | ||||
337 | opacity: 0 | ||||
338 | } | ||||
339 | | ||||
340 | Rectangle { | ||||
341 | anchors.bottom: parent.bottom | ||||
342 | width: parent.width | ||||
343 | height: playerControlsRow.height | ||||
344 | opacity: 0.8 | ||||
345 | } | ||||
346 | } | ||||
232 | 347 | | |||
233 | RowLayout { | 348 | OpacityMask { | ||
234 | id: playerControlsRow | 349 | id: playerControlsOpacityMask | ||
235 | anchors { | 350 | anchors.fill: parent | ||
236 | horizontalCenter: parent.horizontalCenter | 351 | source: playerControlsFrostedGlass | ||
237 | bottom: thumbnailSourceItem.bottom | 352 | maskSource: thumbnailSourceItem | ||
238 | } | 353 | } | ||
239 | width: thumbnailWidth | 354 | | ||
240 | spacing: 0 | 355 | // prevent accidental click-through when a control is disabled | ||
241 | enabled: canControl | 356 | MouseArea { | ||
242 | visible: false | 357 | anchors.fill: playerControlsRow | ||
243 | 358 | } | |||
244 | ColumnLayout { | 359 | | ||
245 | Layout.fillWidth: true | 360 | RowLayout { | ||
246 | spacing: 0 | 361 | id: playerControlsRow | ||
247 | 362 | anchors { | |||
248 | PlasmaExtras.Heading { | 363 | horizontalCenter: parent.horizontalCenter | ||
249 | Layout.fillWidth: true | 364 | bottom: parent.bottom | ||
250 | level: 4 | 365 | } | ||
251 | wrapMode: Text.NoWrap | 366 | width: parent.width | ||
252 | elide: Text.ElideRight | 367 | spacing: 0 | ||
253 | text: track || "" | 368 | enabled: canControl | ||
369 | | ||||
370 | ColumnLayout { | ||||
371 | Layout.fillWidth: true | ||||
372 | spacing: 0 | ||||
373 | | ||||
374 | PlasmaExtras.Heading { | ||||
375 | Layout.fillWidth: true | ||||
376 | level: 4 | ||||
377 | wrapMode: Text.NoWrap | ||||
378 | elide: Text.ElideRight | ||||
379 | text: track || "" | ||||
380 | } | ||||
381 | | ||||
382 | PlasmaExtras.Heading { | ||||
383 | Layout.fillWidth: true | ||||
384 | level: 5 | ||||
385 | wrapMode: Text.NoWrap | ||||
386 | elide: Text.ElideRight | ||||
387 | text: artist || "" | ||||
388 | } | ||||
389 | } | ||||
390 | | ||||
391 | MouseArea { | ||||
392 | height: units.iconSizes.smallMedium | ||||
393 | width: height | ||||
394 | enabled: canGoBack | ||||
395 | | ||||
396 | acceptedButtons: Qt.LeftButton | ||||
397 | hoverEnabled: true | ||||
398 | onClicked: mpris2Source.goPrevious(mprisSourceName) | ||||
399 | | ||||
400 | PlasmaCore.IconItem { | ||||
401 | anchors.fill: parent | ||||
402 | enabled: canGoBack | ||||
403 | active: parent.containsMouse | ||||
404 | | ||||
405 | source: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" | ||||
406 | animated: false | ||||
407 | } | ||||
408 | } | ||||
409 | | ||||
410 | MouseArea { | ||||
411 | height: units.iconSizes.medium | ||||
412 | width: height | ||||
413 | | ||||
414 | acceptedButtons: Qt.LeftButton | ||||
415 | hoverEnabled: true | ||||
416 | onClicked: mpris2Source.playPause(mprisSourceName) | ||||
417 | | ||||
418 | PlasmaCore.IconItem { | ||||
419 | anchors.fill: parent | ||||
420 | active: parent.containsMouse | ||||
421 | | ||||
422 | source: playing ? "media-playback-pause" : "media-playback-start" | ||||
423 | animated: false | ||||
424 | } | ||||
425 | } | ||||
426 | | ||||
427 | MouseArea { | ||||
428 | height: units.iconSizes.smallMedium | ||||
429 | width: height | ||||
430 | enabled: canGoNext | ||||
431 | | ||||
432 | acceptedButtons: Qt.LeftButton | ||||
433 | hoverEnabled: true | ||||
434 | onClicked: mpris2Source.goNext(mprisSourceName) | ||||
435 | | ||||
436 | PlasmaCore.IconItem { | ||||
437 | anchors.fill: parent | ||||
438 | enabled: canGoNext | ||||
439 | active: parent.containsMouse | ||||
440 | | ||||
441 | source: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" | ||||
442 | animated: false | ||||
443 | } | ||||
444 | } | ||||
445 | | ||||
446 | // // TODO: These buttons produce error messages on emergence, | ||||
hein: Is this your code? What's "emergence"? | |||||
No, not my code. These were the buttons from the old revision. I would remove this code portion, if there is no problem with my rewrite of them as IconItems directly above, which don't produce error messages on emergence (when the tooltip of a player opens) and which imo look better here in this context. I'll just remove it in the second diff to minimize confusion. romangg: No, not my code. These were the buttons from the old revision. I would remove this code portion… | |||||
hein: Aye, thx! | |||||
447 | // // see: https://bugreports.qt.io/browse/QTBUG-55844 | ||||
448 | // // remove? their design is also not fitting very well here | ||||
449 | // PlasmaComponents.ToolButton { | ||||
450 | // enabled: canGoBack | ||||
451 | // iconName: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" | ||||
452 | // tooltip: i18nc("Go to previous song", "Previous") | ||||
453 | // Accessible.name: tooltip | ||||
454 | // onClicked: mpris2Source.goPrevious(mprisSourceName) | ||||
455 | // } | ||||
456 | | ||||
457 | // PlasmaComponents.ToolButton { | ||||
458 | // Layout.fillHeight: true | ||||
459 | // Layout.preferredWidth: height // make this button bigger | ||||
460 | // iconName: playing ? "media-playback-pause" : "media-playback-start" | ||||
461 | // tooltip: playing ? i18nc("Pause player", "Pause") : i18nc("Start player", "Play") | ||||
462 | // Accessible.name: tooltip | ||||
463 | // onClicked: mpris2Source.playPause(mprisSourceName) | ||||
464 | // } | ||||
465 | | ||||
466 | // PlasmaComponents.ToolButton { | ||||
467 | // enabled: canGoNext | ||||
468 | // iconName: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" | ||||
469 | // tooltip: i18nc("Go to next song", "Next") | ||||
470 | // Accessible.name: tooltip | ||||
471 | // onClicked: mpris2Source.goNext(mprisSourceName) | ||||
472 | // } | ||||
473 | } | ||||
474 | } | ||||
475 | } | ||||
254 | } | 476 | } | ||
255 | 477 | | |||
256 | PlasmaExtras.Heading { | 478 | Component.onCompleted: { | ||
257 | Layout.fillWidth: true | 479 | checkPlayer() | ||
258 | level: 5 | 480 | mpris2Source.sourceAdded.connect(checkPlayer) | ||
259 | wrapMode: Text.NoWrap | | |||
260 | elide: Text.ElideRight | | |||
261 | text: artist || "" | | |||
262 | } | 481 | } | ||
263 | } | | |||
264 | 482 | | |||
265 | PlasmaComponents.ToolButton { | 483 | function generateTitle() { | ||
266 | enabled: canGoBack | 484 | var text = model.display.toString() | ||
267 | iconName: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" | | |||
268 | tooltip: i18nc("Go to previous song", "Previous") | | |||
269 | Accessible.name: tooltip | | |||
270 | onClicked: mpris2Source.goPrevious(mprisSourceName) | | |||
271 | } | | |||
272 | 485 | | |||
273 | PlasmaComponents.ToolButton { | 486 | var counter = text.match(/<\d+>\W*$/) | ||
274 | Layout.fillHeight: true | 487 | text = text.replace(/\s*<\d+>\W*$/, "") | ||
275 | Layout.preferredWidth: height // make this button bigger | | |||
276 | iconName: playing ? "media-playback-pause" : "media-playback-start" | | |||
277 | tooltip: playing ? i18nc("Pause player", "Pause") : i18nc("Start player", "Play") | | |||
278 | Accessible.name: tooltip | | |||
279 | onClicked: mpris2Source.playPause(mprisSourceName) | | |||
280 | } | | |||
281 | 488 | | |||
282 | PlasmaComponents.ToolButton { | 489 | var appNameRegex = new RegExp(appName + "$", "i") | ||
hein: Needs a code comment explaining what it wants to achieve. | |||||
283 | enabled: canGoNext | 490 | text = text.replace(appNameRegex, "") | ||
284 | iconName: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" | 491 | text = text.replace(/\s*(?:-|—)*\s*$/, "") | ||
285 | tooltip: i18nc("Go to next song", "Next") | 492 | | ||
286 | Accessible.name: tooltip | 493 | if (counter != null) { | ||
287 | onClicked: mpris2Source.goNext(mprisSourceName) | 494 | if (text == "") { | ||
288 | } | 495 | text = counter | ||
289 | } | 496 | } else { | ||
290 | } | 497 | text = text + " " + counter | ||
498 | } | ||||
499 | } | ||||
291 | 500 | | |||
292 | Row { | 501 | if (text == "") { | ||
293 | id: appLabelRow | 502 | text = "—" | ||
294 | anchors.left: parent.left | 503 | } | ||
295 | spacing: units.largeSpacing | 504 | return text.toString() | ||
296 | | ||||
297 | Item { | | |||
298 | id: imageContainer | | |||
299 | width: tooltipIcon.width | | |||
300 | height: tooltipIcon.height | | |||
301 | | ||||
302 | PlasmaCore.IconItem { | | |||
303 | id: tooltipIcon | | |||
304 | anchors { | | |||
305 | left: parent.left | | |||
306 | leftMargin: units.largeSpacing / 2 | | |||
307 | } | 505 | } | ||
308 | width: units.iconSizes.desktop | | |||
309 | height: width | | |||
310 | animated: false | | |||
311 | usesPlasmaTheme: false | | |||
312 | source: icon | | |||
313 | } | | |||
314 | } | | |||
315 | 506 | | |||
316 | Column { | 507 | function generateSubText() { | ||
317 | id: mainColumn | 508 | var subTextEntries = new Array(); | ||
318 | spacing: units.smallSpacing | 509 | | ||
319 | 510 | var vd = isGroup == true ? VirtualDesktop : parentTask.m.VirtualDesktop | |||
320 | //This instance is purely for metrics | 511 | | ||
321 | PlasmaExtras.Heading { | 512 | if (!plasmoid.configuration.showOnlyCurrentDesktop | ||
322 | id: tooltipMaintextPlaceholder | 513 | && virtualDesktopInfo.numberOfDesktops > 1 | ||
323 | visible: false | 514 | && (isGroup == true ? IsOnAllVirtualDesktops : parentTask.m.IsOnAllVirtualDesktops) !== true | ||
324 | level: 3 | 515 | && vd != -1 | ||
325 | text: mainText | 516 | && vd != undefined | ||
326 | textFormat: Text.PlainText | 517 | && virtualDesktopInfo.desktopNames[vd - 1] != undefined) { | ||
327 | } | 518 | subTextEntries.push(i18n("On %1", virtualDesktopInfo.desktopNames[vd - 1])); | ||
328 | PlasmaExtras.Heading { | 519 | } | ||
329 | id: tooltipMaintext | 520 | | ||
330 | level: 3 | 521 | var act = isGroup == true ? Activities : parentTask.m.Activities | ||
331 | width: Math.min(tooltipMaintextPlaceholder.width, preferredTextWidth) | 522 | if (act == undefined) { | ||
332 | height: undefined // unset stupid PlasmaComponents.Label default height | 523 | return subTextEntries.join("\n"); | ||
333 | //width: 400 | 524 | } | ||
334 | elide: Text.ElideRight | 525 | | ||
335 | wrapMode: Text.WordWrap | 526 | if (act.length == 0 && activityInfo.numberOfRunningActivities > 1) { | ||
336 | // if there's no subtext allow two lines of window title | 527 | subTextEntries.push(i18nc("Which virtual desktop a window is currently on", | ||
337 | maximumLineCount: tooltipSubtext.visible ? 1 : 2 | 528 | "Available on all activities")); | ||
338 | lineHeight: 0.95 | 529 | } else if (act.length > 0) { | ||
339 | text: mainText | 530 | var activityNames = new Array(); | ||
340 | textFormat: Text.PlainText | 531 | | ||
341 | } | 532 | for (var i = 0; i < act.length; i++) { | ||
342 | PlasmaComponents.Label { | 533 | var activity = act[i]; | ||
343 | id: tooltipSubtext | 534 | | ||
344 | width: tooltipContentItem.preferredTextWidth | 535 | if (plasmoid.configuration.showOnlyCurrentActivity) { | ||
345 | height: Math.min(theme.mSize(theme.defaultFont), contentHeight) | 536 | if (activity != activityInfo.currentActivity) { | ||
346 | wrapMode: Text.WordWrap | 537 | activityNames.push(activityInfo.activityName(act[i])); | ||
347 | text: subText | 538 | } | ||
348 | textFormat: Text.PlainText | 539 | } else if (activity != activityInfo.currentActivity) { | ||
349 | opacity: 0.5 | 540 | activityNames.push(activityInfo.activityName(act[i])); | ||
350 | visible: text !== "" | 541 | } | ||
542 | } | ||||
543 | | ||||
544 | if (plasmoid.configuration.showOnlyCurrentActivity) { | ||||
545 | if (activityNames.length > 0) { | ||||
546 | subTextEntries.push(i18nc("Activities a window is currently on (apart from the current one)", | ||||
547 | "Also available on %1", activityNames.join(", "))); | ||||
548 | } | ||||
549 | } else if (activityNames.length > 0) { | ||||
550 | subTextEntries.push(i18nc("Which activities a window is currently on", | ||||
551 | "Available on %1", activityNames.join(", "))); | ||||
552 | } | ||||
553 | } | ||||
554 | | ||||
555 | return subTextEntries.join("\n"); | ||||
556 | } | ||||
351 | } | 557 | } | ||
352 | } | 558 | } | ||
353 | } | 559 | } | ||
Context not available. |
Sanity check: Why count children instead of the IsGroupParent role?
Also:
(1) For performance and code hygiene reasons please mark properties readonly if you won't assign them.
(2) Why copy tons of data roles into local props in the first place instead of just accessing the model directly?