Changeset View
Standalone View
applets/taskmanager/package/contents/ui/ToolTipDelegate.qml
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
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 2017 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 | ||
8 | * published by the Free Software Foundation; either version 2, or | 9 | * published by the Free Software Foundation; either version 2, or | ||
9 | * (at your option) any later version. | 10 | * (at your option) any later version. | ||
10 | * | 11 | * | ||
11 | * This program is distributed in the hope that it will be useful, | 12 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU Library General Public License for more details | 15 | * GNU Library General Public License for more details | ||
15 | * | 16 | * | ||
16 | * You should have received a copy of the GNU Library General Public | 17 | * You should have received a copy of the GNU Library General Public | ||
17 | * License along with this program; if not, write to the | 18 | * License along with this program; if not, write to the | ||
18 | * Free Software Foundation, Inc., | 19 | * Free Software Foundation, Inc., | ||
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 | | |||
34 | property Item toolTip | 35 | PlasmaExtras.ScrollArea { | ||
35 | property var parentIndex | 36 | property Item parentTask | ||
36 | property var windows | 37 | property int parentIndex | ||
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 | 38 | | |||
43 | readonly property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(launcherUrl) | 39 | property string appName | ||
44 | readonly property bool hasPlayer: !!mprisSourceName && !!playerData | 40 | property int pidParent | ||
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 bool isGroup | ||||
45 | 42 | | |||
46 | readonly property var playerData: mpris2Source.data[mprisSourceName] | 43 | property var windows | ||
47 | readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" | 44 | readonly property bool isWin: windows !== undefined | ||
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 | 45 | | |||
74 | readonly property int thumbnailWidth: units.gridUnit * 15 | 46 | property variant icon | ||
75 | readonly property int thumbnailHeight: units.gridUnit * 10 | 47 | property url launcherUrl | ||
48 | property bool isLauncher | ||||
49 | property bool isMinimizedParent | ||||
76 | 50 | | |||
77 | property int preferredTextWidth: theme.mSize(theme.defaultFont).width * 30 | 51 | // Needed for generateSubtext() | ||
52 | property string displayParent | ||||
53 | property string genericName | ||||
54 | property int virtualDesktopParent | ||||
55 | property bool isOnAllVirtualDesktopsParent | ||||
56 | property var activitiesParent | ||||
57 | // | ||||
58 | readonly property bool isVerticalPanel: plasmoid.formFactor == PlasmaCore.Types.Vertical | ||||
78 | 59 | | |||
79 | Layout.minimumWidth: Math.max(thumbnailWidth, windowRow.width, appLabelRow.width) + units.largeSpacing / 2 | 60 | Layout.minimumWidth: contentItem.width | ||
80 | Layout.minimumHeight: childrenRect.height + units.largeSpacing | | |||
81 | Layout.maximumWidth: Layout.minimumWidth | 61 | Layout.maximumWidth: Layout.minimumWidth | ||
62 | | ||||
63 | Layout.minimumHeight: contentItem.height | ||||
82 | Layout.maximumHeight: Layout.minimumHeight | 64 | Layout.maximumHeight: Layout.minimumHeight | ||
83 | 65 | | |||
84 | LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft | 66 | LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft | ||
85 | LayoutMirroring.childrenInherit: true | 67 | LayoutMirroring.childrenInherit: true | ||
86 | 68 | | |||
87 | spacing: units.largeSpacing / 2 | 69 | property int textWidth: theme.mSize(theme.defaultFont).width * 20 | ||
88 | | ||||
89 | states: State { | | |||
90 | when: tooltipContentItem.hasPlayer | | |||
91 | | ||||
92 | PropertyChanges { | | |||
93 | target: thumbnailSourceItem | | |||
94 | opacity: 0 // cannot set visible to false or else WindowThumbnail won't provide thumbnail | | |||
95 | } | | |||
96 | PropertyChanges { | | |||
97 | target: playerControlsOpacityMask | | |||
98 | visible: true | | |||
99 | source: thumbnailSourceItem | | |||
100 | maskSource: playerControlsShadowMask | | |||
101 | } | | |||
102 | PropertyChanges { | | |||
103 | target: playerControlsRow | | |||
104 | visible: true | | |||
105 | } | | |||
106 | } | | |||
107 | | ||||
108 | Item { | | |||
109 | id: thumbnailContainer | | |||
110 | width: Math.max(parent.width, windowRow.width) | | |||
111 | height: albumArtImage.available ? albumArtImage.height : | | |||
112 | raisePlayerArea.visible ? raisePlayerArea.height : | | |||
113 | windowRow.height | | |||
114 | | ||||
115 | Item { | | |||
116 | id: thumbnailSourceItem | | |||
117 | anchors.fill: parent | | |||
118 | | ||||
119 | PlasmaExtras.ScrollArea { | | |||
120 | id: scrollArea | | |||
121 | anchors.horizontalCenter: parent.horizontalCenter | | |||
122 | width: Math.max(windowRow.width, thumbnailWidth) | | |||
123 | height: parent.height | | |||
124 | | ||||
125 | visible: !albumArtImage.available | | |||
126 | 70 | | |||
127 | verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff | 71 | verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff | ||
128 | horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff | 72 | horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff | ||
129 | 73 | | |||
130 | Component.onCompleted: { | 74 | Component.onCompleted: { | ||
131 | flickableItem.interactive = Qt.binding(function() { | 75 | flickableItem.interactive = Qt.binding(function() { | ||
132 | return contentItem.width > viewport.width; | 76 | return isVerticalPanel ? contentItem.height > viewport.height : contentItem.width > viewport.width | ||
133 | }); | 77 | }); | ||
134 | } | 78 | } | ||
135 | 79 | | |||
136 | Row { | | |||
137 | id: windowRow | | |||
138 | spacing: units.largeSpacing | | |||
139 | | ||||
140 | Repeater { | | |||
141 | model: plasmoid.configuration.showToolTips && !albumArtImage.available ? windows : null | | |||
142 | | ||||
143 | PlasmaCore.WindowThumbnail { | | |||
144 | id: windowThumbnail | | |||
145 | | ||||
146 | width: thumbnailWidth | | |||
147 | height: thumbnailHeight | | |||
148 | | ||||
149 | winId: modelData | | |||
150 | | ||||
151 | ToolTipWindowMouseArea { | | |||
152 | anchors.fill: parent | | |||
153 | modelIndex: tasksModel.makeModelIndex(parentIndex, group ? index : -1) | | |||
154 | winId: modelData | | |||
155 | thumbnailItem: parent | | |||
156 | } | | |||
157 | } | | |||
158 | } | | |||
159 | } | | |||
160 | } | | |||
161 | | ||||
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 | | ||||
183 | MouseArea { | | |||
184 | id: raisePlayerArea | | |||
185 | anchors.centerIn: parent | | |||
186 | width: thumbnailWidth | | |||
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 | | ||||
203 | Item { | 80 | Item { | ||
204 | id: playerControlsShadowMask | 81 | id: contentItem | ||
205 | anchors.fill: thumbnailSourceItem | 82 | width: childrenRect.width | ||
206 | visible: false // OpacityMask would render it | 83 | height: childrenRect.height | ||
207 | 84 | | |||
208 | Rectangle { | 85 | ToolTipInstance { | ||
209 | width: parent.width | 86 | visible: !isGroup | ||
210 | height: parent.height - playerControlsRow.height | | |||
211 | } | | |||
212 | | ||||
213 | Rectangle { | | |||
214 | anchors.bottom: parent.bottom | | |||
215 | width: parent.width | | |||
216 | height: playerControlsRow.height | | |||
217 | opacity: 0.2 | | |||
218 | } | | |||
219 | } | | |||
220 | | ||||
221 | OpacityMask { | | |||
222 | id: playerControlsOpacityMask | | |||
223 | anchors.fill: thumbnailSourceItem | | |||
224 | visible: false | | |||
225 | } | | |||
226 | | ||||
227 | // prevent accidental click-through when a control is disabled | | |||
228 | MouseArea { | | |||
229 | anchors.fill: playerControlsRow | | |||
230 | enabled: playerControlsRow.visible | | |||
231 | } | | |||
232 | | ||||
233 | RowLayout { | | |||
234 | id: playerControlsRow | | |||
235 | anchors { | | |||
236 | horizontalCenter: parent.horizontalCenter | | |||
237 | bottom: thumbnailSourceItem.bottom | | |||
238 | } | | |||
239 | width: thumbnailWidth | | |||
240 | spacing: 0 | | |||
241 | enabled: canControl | | |||
242 | visible: false | | |||
243 | | ||||
244 | ColumnLayout { | | |||
245 | Layout.fillWidth: true | | |||
246 | spacing: 0 | | |||
247 | | ||||
248 | PlasmaExtras.Heading { | | |||
249 | Layout.fillWidth: true | | |||
250 | level: 4 | | |||
251 | wrapMode: Text.NoWrap | | |||
252 | elide: Text.ElideRight | | |||
253 | text: track || "" | | |||
254 | } | | |||
255 | | ||||
256 | PlasmaExtras.Heading { | | |||
257 | Layout.fillWidth: true | | |||
258 | level: 5 | | |||
259 | wrapMode: Text.NoWrap | | |||
260 | elide: Text.ElideRight | | |||
261 | text: artist || "" | | |||
262 | } | | |||
263 | } | | |||
264 | | ||||
265 | PlasmaComponents.ToolButton { | | |||
266 | enabled: canGoBack | | |||
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 | | ||||
273 | PlasmaComponents.ToolButton { | | |||
274 | Layout.fillHeight: true | | |||
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 | | ||||
282 | PlasmaComponents.ToolButton { | | |||
283 | enabled: canGoNext | | |||
284 | iconName: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" | | |||
285 | tooltip: i18nc("Go to next song", "Next") | | |||
286 | Accessible.name: tooltip | | |||
287 | onClicked: mpris2Source.goNext(mprisSourceName) | | |||
288 | } | | |||
289 | } | | |||
290 | } | 87 | } | ||
291 | 88 | | |||
292 | Row { | 89 | Grid { | ||
293 | id: appLabelRow | 90 | rows: !isVerticalPanel | ||
294 | anchors.left: parent.left | 91 | columns: isVerticalPanel | ||
92 | flow: isVerticalPanel ? Grid.TopToBottom : Grid.LeftToRight | ||||
295 | spacing: units.largeSpacing | 93 | spacing: units.largeSpacing | ||
296 | 94 | | |||
297 | Item { | 95 | visible: isGroup | ||
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 | } | | |||
308 | width: units.iconSizes.desktop | | |||
309 | height: width | | |||
310 | animated: false | | |||
311 | usesPlasmaTheme: false | | |||
312 | source: icon | | |||
313 | } | | |||
314 | } | | |||
315 | 96 | | |||
316 | Column { | 97 | Repeater { | ||
317 | id: mainColumn | 98 | id: groupRepeater | ||
318 | spacing: units.smallSpacing | 99 | model: DelegateModel { | ||
319 | 100 | model: tasksModel | |||
320 | //This instance is purely for metrics | 101 | rootIndex: tasksModel.makeModelIndex(parentIndex, -1) | ||
321 | PlasmaExtras.Heading { | 102 | delegate: ToolTipInstance {} | ||
322 | id: tooltipMaintextPlaceholder | | |||
323 | visible: false | | |||
324 | level: 3 | | |||
325 | text: mainText | | |||
326 | textFormat: Text.PlainText | | |||
327 | } | | |||
328 | PlasmaExtras.Heading { | | |||
329 | id: tooltipMaintext | | |||
330 | level: 3 | | |||
331 | width: Math.min(tooltipMaintextPlaceholder.width, preferredTextWidth) | | |||
332 | height: undefined // unset stupid PlasmaComponents.Label default height | | |||
333 | //width: 400 | | |||
334 | elide: Text.ElideRight | | |||
335 | wrapMode: Text.WordWrap | | |||
336 | // if there's no subtext allow two lines of window title | | |||
337 | maximumLineCount: tooltipSubtext.visible ? 1 : 2 | | |||
338 | lineHeight: 0.95 | | |||
339 | text: mainText | | |||
340 | textFormat: Text.PlainText | | |||
341 | } | 103 | } | ||
342 | PlasmaComponents.Label { | | |||
343 | id: tooltipSubtext | | |||
344 | width: tooltipContentItem.preferredTextWidth | | |||
345 | height: Math.min(theme.mSize(theme.defaultFont), contentHeight) | | |||
346 | wrapMode: Text.WordWrap | | |||
347 | text: subText | | |||
348 | textFormat: Text.PlainText | | |||
349 | opacity: 0.5 | | |||
350 | visible: text !== "" | | |||
351 | } | 104 | } | ||
352 | } | 105 | } | ||
353 | } | 106 | } | ||
354 | } | 107 | } | ||
hein: Why 0.75? That's not an even number of pixels ... | |||||
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… | |||||
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! | |||||
hein: Needs a code comment explaining what it wants to achieve. | |||||
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. |
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?