Changeset View
Standalone View
applets/mediacontroller/contents/ui/ExpandedRepresentation.qml
Show All 18 Lines | |||||
19 | ***************************************************************************/ | 19 | ***************************************************************************/ | ||
20 | 20 | | |||
21 | import QtQuick 2.4 | 21 | import QtQuick 2.4 | ||
22 | import QtQuick.Layouts 1.1 | 22 | import QtQuick.Layouts 1.1 | ||
23 | import org.kde.plasma.core 2.0 as PlasmaCore | 23 | import org.kde.plasma.core 2.0 as PlasmaCore | ||
24 | import org.kde.plasma.components 3.0 as PlasmaComponents3 | 24 | import org.kde.plasma.components 3.0 as PlasmaComponents3 | ||
25 | import org.kde.plasma.extras 2.0 as PlasmaExtras | 25 | import org.kde.plasma.extras 2.0 as PlasmaExtras | ||
26 | import org.kde.kcoreaddons 1.0 as KCoreAddons | 26 | import org.kde.kcoreaddons 1.0 as KCoreAddons | ||
27 | import QtGraphicalEffects 1.0 | ||||
27 | 28 | | |||
28 | Item { | 29 | Item { | ||
29 | id: expandedRepresentation | 30 | id: expandedRepresentation | ||
30 | 31 | | |||
31 | Layout.minimumWidth: Layout.minimumHeight * 1.333 | 32 | Layout.minimumWidth: units.gridUnit * 15 | ||
32 | Layout.minimumHeight: units.gridUnit * 10 | 33 | Layout.minimumHeight: units.gridUnit * 23 | ||
33 | Layout.preferredWidth: Layout.minimumWidth * 1.5 | 34 | Layout.preferredWidth: Layout.minimumWidth * 1.5 | ||
34 | Layout.preferredHeight: Layout.minimumHeight * 1.5 | 35 | Layout.preferredHeight: Layout.minimumHeight * 1.5 | ||
35 | 36 | | |||
37 | Layout.margins: units.largeSpacing | ||||
38 | | ||||
36 | readonly property int controlSize: units.iconSizes.large | 39 | readonly property int controlSize: units.iconSizes.large | ||
37 | 40 | | |||
38 | property double position: mpris2Source.currentData.Position || 0 | 41 | property double position: mpris2Source.currentData.Position || 0 | ||
39 | readonly property real rate: mpris2Source.currentData.Rate || 1 | 42 | readonly property real rate: mpris2Source.currentData.Rate || 1 | ||
40 | readonly property double length: currentMetadata ? currentMetadata["mpris:length"] || 0 : 0 | 43 | readonly property double length: currentMetadata ? currentMetadata["mpris:length"] || 0 : 0 | ||
41 | readonly property bool canSeek: mpris2Source.currentData.CanSeek || false | 44 | readonly property bool canSeek: mpris2Source.currentData.CanSeek || false | ||
45 | readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software | ||||
42 | 46 | | |||
43 | // only show hours (the default for KFormat) when track is actually longer than an hour | 47 | // only show hours (the default for KFormat) when track is actually longer than an hour | ||
44 | readonly property int durationFormattingOptions: length >= 60*60*1000*1000 ? 0 : KCoreAddons.FormatTypes.FoldHours | 48 | readonly property int durationFormattingOptions: length >= 60*60*1000*1000 ? 0 : KCoreAddons.FormatTypes.FoldHours | ||
45 | 49 | | |||
46 | property bool disablePositionUpdate: false | 50 | property bool disablePositionUpdate: false | ||
47 | property bool keyPressed: false | 51 | property bool keyPressed: false | ||
48 | 52 | | |||
49 | function retrievePosition() { | 53 | function retrievePosition() { | ||
▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Line(s) | 121 | } else if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) { | |||
119 | seekSlider.value = seekSlider.to * (event.key - Qt.Key_0) / 10 | 123 | seekSlider.value = seekSlider.to * (event.key - Qt.Key_0) / 10 | ||
120 | seekSlider.moved(); | 124 | seekSlider.moved(); | ||
121 | } else { | 125 | } else { | ||
122 | event.accepted = false | 126 | event.accepted = false | ||
123 | } | 127 | } | ||
124 | } | 128 | } | ||
125 | } | 129 | } | ||
126 | 130 | | |||
127 | PlasmaComponents3.ComboBox { | 131 | Image { | ||
132 | id: backgroundImage | ||||
133 | | ||||
134 | source: root.albumArt | ||||
135 | sourceSize.width: 512 /* Setting a source size means the item doesn't need | ||||
136 | to recompute blur as the user resizes the plasmoid | ||||
broulik: Superfluous since you fill both width and height? | |||||
137 | Additionally, it puts a bit of a cap on how large the | ||||
138 | buffer getting blurred can be, saving resources. | ||||
139 | */ | ||||
140 | | ||||
141 | anchors.fill: parent | ||||
142 | anchors.margins: -units.smallSpacing*2 | ||||
143 | fillMode: Image.PreserveAspectCrop | ||||
144 | | ||||
145 | asynchronous: true | ||||
146 | visible: !!root.track && status === Image.Ready && !softwareRendering | ||||
147 | | ||||
Do you have a source for this claim? From what I know blur merely blurs the actual item texture which is rendered, no the raw image source? broulik: Do you have a source for this claim? From what I know blur merely blurs the actual item texture… | |||||
cblack: https://github. | |||||
That says the opposite of what you want. We want image's texture provider to be used directly. Only then will this sourcesize matter. This is saying when layer effect is enabled it uses the base blitting path. We don't want to use layer here, we want the image as a source to the blur directly davidedmundson: That says the opposite of what you want.
We want image's texture provider to be used directly. | |||||
148 | layer.enabled: !softwareRendering | ||||
149 | layer.effect: HueSaturation { | ||||
150 | cached: true | ||||
151 | | ||||
152 | lightness: -0.5 | ||||
153 | saturation: 0.9 | ||||
154 | | ||||
155 | layer.enabled: true | ||||
156 | layer.effect: GaussianBlur { | ||||
157 | cached: true | ||||
158 | | ||||
159 | radius: 256 | ||||
160 | deviation: 12 | ||||
161 | samples: 129 | ||||
162 | | ||||
163 | transparentBorder: false | ||||
164 | } | ||||
165 | } | ||||
166 | } | ||||
167 | | ||||
168 | ColumnLayout { // Main Column Layout | ||||
169 | id: mainCol | ||||
170 | anchors.fill: parent | ||||
171 | | ||||
172 | PlasmaComponents3.ComboBox { // Media Source Selector | ||||
128 | id: playerCombo | 173 | id: playerCombo | ||
174 | visible: model.length > 2 // more than one player, @multiplex is always there | ||||
175 | | ||||
129 | width: Math.round(0.6 * parent.width) | 176 | width: Math.round(0.6 * parent.width) | ||
130 | height: visible ? undefined : 0 | 177 | height: visible ? undefined : 0 | ||
131 | anchors.horizontalCenter: parent.horizontalCenter | 178 | | ||
ngraham: whitespace | |||||
cblack: Whitespace is intentional to help with code legibility | |||||
132 | textRole: "text" | 179 | textRole: "text" | ||
133 | visible: model.length > 2 // more than one player, @multiplex is always there | 180 | | ||
ngraham: whitespace | |||||
181 | Layout.alignment: Qt.AlignHCenter | ||||
182 | Layout.preferredWidth: expandedRepresentation.width * (1/3) | ||||
183 | | ||||
134 | model: { | 184 | model: { | ||
135 | var model = [{ | 185 | var model = [{ | ||
136 | text: i18n("Choose player automatically"), | 186 | text: i18n("Choose player automatically"), | ||
137 | source: mpris2Source.multiplexSource | 187 | source: mpris2Source.multiplexSource | ||
138 | }] | 188 | }] | ||
139 | 189 | | |||
140 | var sources = mpris2Source.sources | 190 | var sources = mpris2Source.sources | ||
141 | for (var i = 0, length = sources.length; i < length; ++i) { | 191 | for (var i = 0, length = sources.length; i < length; ++i) { | ||
Show All 23 Lines | |||||
165 | onActivated: { | 215 | onActivated: { | ||
166 | disablePositionUpdate = true | 216 | disablePositionUpdate = true | ||
167 | // ComboBox has currentIndex and currentText, why doesn't it have currentItem/currentModelValue? | 217 | // ComboBox has currentIndex and currentText, why doesn't it have currentItem/currentModelValue? | ||
168 | mpris2Source.current = model[index].source | 218 | mpris2Source.current = model[index].source | ||
169 | disablePositionUpdate = false | 219 | disablePositionUpdate = false | ||
170 | } | 220 | } | ||
171 | } | 221 | } | ||
172 | 222 | | |||
173 | Item { | 223 | RowLayout { // Album Art + Details | ||
174 | anchors { | 224 | spacing: units.largeSpacing | ||
175 | horizontalCenter: parent.horizontalCenter | 225 | | ||
Does assigning undefined work? I don't see a RESET property for it. Probably want to assign color scope text color instead? broulik: Does assigning `undefined` work? I don't see a `RESET` property for it. Probably want to assign… | |||||
176 | top: playerCombo.bottom | 226 | Layout.margins: units.smallSpacing | ||
177 | bottom: controlCol.top | 227 | Layout.alignment: Qt.AlignHCenter | ||
178 | margins: units.smallSpacing | 228 | | ||
229 | Image { // Album Art | ||||
230 | id: albumArt | ||||
231 | | ||||
232 | visible: !!root.track && status === Image.Ready | ||||
233 | | ||||
234 | asynchronous: true | ||||
235 | fillMode: Image.PreserveAspectFit | ||||
236 | | ||||
237 | source: root.albumArt | ||||
238 | | ||||
239 | Layout.maximumWidth: expandedRepresentation.width * (1/3) | ||||
240 | Layout.maximumHeight: units.gridUnit * 10 | ||||
179 | } | 241 | } | ||
180 | 242 | | |||
181 | PlasmaCore.IconItem { | 243 | ColumnLayout { // Details Column | ||
182 | anchors { | 244 | | ||
183 | horizontalCenter: parent.horizontalCenter | 245 | PlasmaExtras.Heading { // Song Title | ||
184 | verticalCenter: parent.verticalCenter | 246 | id: songTitle | ||
247 | level: 1 | ||||
248 | | ||||
249 | color: softwareRendering ? undefined : "white" | ||||
Is a hardcoded white color always guaranteed to look good even when the album art is very light? Is the blurred background always going to be dark enough? ngraham: Is a hardcoded white color always guaranteed to look good even when the album art is very light? | |||||
cblack: {F8085988} | |||||
ngraham: All right, looks good! | |||||
gvgeo: With a white theme? | |||||
gvgeo: You are limiting `Details Column`'s width, while `Album Art` is not always visible. | |||||
broulik: Make sure to `Math.round` random factors of a size | |||||
250 | | ||||
251 | textFormat: Text.PlainText | ||||
ngraham: I think that's the default, no? | |||||
252 | wrapMode: Text.Wrap | ||||
253 | | ||||
254 | text: root.track || i18n("No media playing") | ||||
255 | | ||||
256 | Layout.maximumWidth: expandedRepresentation.width * (2/5) | ||||
185 | } | 257 | } | ||
258 | PlasmaExtras.Heading { // Song Artist | ||||
259 | id: songArtist | ||||
260 | visible: root.track && root.artist | ||||
there's a massive binding loop somewhere here: file:///home/nate/kde/usr/share/plasma/plasmoids/org.kde.plasma.mediacontroller/contents/ui/ExpandedRepresentation.qml:261:21: QML Heading: Binding loop detected for property "height" ngraham: there's a massive binding loop somewhere here:
```
file… | |||||
cblack: I can't seem to reproduce a binding loop here. | |||||
I have not seen your code yet, I will check tomorrow. Probably because you have something like this: Layout.preferredWidth: parent.width / 2 Layout.preferredHeight: parent.height / 2 Check the last answer from: How to design a multi-level fluid layout in QML. In short words: when possible, do not use Layout.preferredWidth, prefer implicitWidth instead. If you want to have 50-50 ratio between items, set implicitWidth in both items to the same value. kmaterka: I have not seen your code yet, I will check tomorrow. Probably because you have something like… | |||||
My last comment was not precise. Just check this for all details :) kmaterka: My last comment was not precise. Just check [[ https://stackoverflow.com/a/52754479 | this ]]… | |||||
As a general rule, you shouldn't refer to parents width in Layouts, including: Layout.maximumWidth: parent.width. It a (very) common mistake :) kmaterka: As a general rule, you shouldn't refer to parents width in Layouts, including: `Layout. | |||||
This advice doesn't seem to be applicable here, as the implicitWidth of the ColumnLayout child isn't able to be mutated due to the fact that all of its child items (text) have fixed implicitWidths. cblack: This advice doesn't seem to be applicable here, as the `implicitWidth` of the ColumnLayout… | |||||
True, I didn't want to copy whole answer from Stack Overflow :) In this case Layout.preferredWidth should not be set to parent.width / 2, better to use Layout.preferredWidth = 50. I sometimes add % in a comment: Layout.preferredWidth = 50//%. kmaterka: True, I didn't want to copy whole answer from Stack Overflow :) In this case `Layout. | |||||
261 | level: 2 | ||||
262 | | ||||
263 | color: softwareRendering ? undefined : "white" | ||||
186 | 264 | | |||
187 | height: Math.round(parent.height / 2) | 265 | textFormat: Text.PlainText | ||
188 | width: height | 266 | wrapMode: Text.Wrap | ||
189 | 267 | | |||
190 | source: mpris2Source.currentData["Desktop Icon Name"] | 268 | text: root.artist | ||
191 | visible: !albumArt.visible | | |||
192 | 269 | | |||
193 | usesPlasmaTheme: false | 270 | Layout.maximumWidth: expandedRepresentation.width * (2/5) | ||
271 | } | ||||
272 | PlasmaExtras.Heading { // Song Album | ||||
273 | color: softwareRendering ? undefined : "white" | ||||
274 | | ||||
275 | level: 3 | ||||
276 | opacity: 0.6 | ||||
277 | | ||||
278 | textFormat: Text.PlainText | ||||
279 | wrapMode: Text.Wrap | ||||
280 | | ||||
281 | visible: text !== "" | ||||
ngraham: `text.length !== 0` is a bit faster | |||||
282 | text: { | ||||
283 | var metadata = root.currentMetadata | ||||
284 | if (!metadata) { | ||||
285 | return "" | ||||
194 | } | 286 | } | ||
287 | var xesamAlbum = metadata["xesam:album"] | ||||
288 | if (xesamAlbum) { | ||||
289 | return xesamAlbum | ||||
195 | } | 290 | } | ||
196 | 291 | | |||
197 | Image { | 292 | // if we play a local file without title and artist, show its containing folder instead | ||
198 | id: albumArt | 293 | if (metadata["xesam:title"] || root.artist) { | ||
199 | anchors { | 294 | return "" | ||
200 | left: parent.left | | |||
201 | right: parent.right | | |||
202 | top: playerCombo.bottom | | |||
203 | bottom: controlCol.top | | |||
204 | margins: units.smallSpacing | | |||
205 | } | 295 | } | ||
206 | source: root.albumArt | 296 | | ||
207 | asynchronous: true | 297 | var xesamUrl = (metadata["xesam:url"] || "").toString() | ||
208 | fillMode: Image.PreserveAspectFit | 298 | if (xesamUrl.indexOf("file:///") !== 0) { // "!startsWith()" | ||
209 | sourceSize: Qt.size(height, height) | 299 | return "" | ||
210 | visible: !!root.track && status === Image.Ready | | |||
211 | } | 300 | } | ||
212 | 301 | | |||
213 | Column { | 302 | var urlParts = xesamUrl.split("/") | ||
214 | id: controlCol | 303 | if (urlParts.length < 3) { | ||
215 | width: parent.width | 304 | return "" | ||
216 | anchors.bottom: parent.bottom | 305 | } | ||
217 | 306 | | |||
218 | spacing: units.smallSpacing | 307 | var lastFolderPath = urlParts[urlParts.length - 2] // last would be filename | ||
308 | if (lastFolderPath) { | ||||
309 | return lastFolderPath | ||||
310 | } | ||||
311 | | ||||
312 | return "" | ||||
313 | } | ||||
219 | 314 | | |||
220 | RowLayout { | 315 | Layout.maximumWidth: expandedRepresentation.width * (2/5) | ||
221 | anchors { | 316 | } | ||
222 | left: parent.left | 317 | } | ||
223 | right: parent.right | | |||
224 | margins: units.smallSpacing | | |||
225 | } | 318 | } | ||
226 | 319 | | |||
320 | RowLayout { // Seek Bar | ||||
227 | spacing: units.smallSpacing | 321 | spacing: units.smallSpacing | ||
228 | 322 | | |||
229 | // if there's no "mpris:length" in the metadata, we cannot seek, so hide it in that case | 323 | // if there's no "mpris:length" in the metadata, we cannot seek, so hide it in that case | ||
230 | enabled: !root.noPlayer && root.track && expandedRepresentation.length > 0 ? true : false | 324 | enabled: !root.noPlayer && root.track && expandedRepresentation.length > 0 ? true : false | ||
231 | opacity: enabled ? 1 : 0 | 325 | opacity: enabled ? 1 : 0 | ||
232 | Behavior on opacity { | 326 | Behavior on opacity { | ||
233 | NumberAnimation { duration: units.longDuration } | 327 | NumberAnimation { duration: units.longDuration } | ||
234 | } | 328 | } | ||
235 | 329 | | |||
330 | Layout.alignment: Qt.AlignHCenter | ||||
331 | Layout.fillWidth: true | ||||
332 | Layout.maximumWidth: Math.min(800, expandedRepresentation.width*(7/10)) | ||||
If the user does something a bit silly like making the applet as big as the whole screen, they might prefer if the seek bar is really long ngraham: If the user does something a bit silly like making the applet as big as the whole screen, they… | |||||
broulik: No magic numbers, please | |||||
333 | | ||||
236 | // ensure the layout doesn't shift as the numbers change and measure roughly the longest text that could occur with the current song | 334 | // ensure the layout doesn't shift as the numbers change and measure roughly the longest text that could occur with the current song | ||
237 | TextMetrics { | 335 | TextMetrics { | ||
238 | id: timeMetrics | 336 | id: timeMetrics | ||
239 | text: i18nc("Remaining time for song e.g -5:42", "-%1", | 337 | text: i18nc("Remaining time for song e.g -5:42", "-%1", | ||
240 | KCoreAddons.Format.formatDuration(seekSlider.to / 1000, expandedRepresentation.durationFormattingOptions)) | 338 | KCoreAddons.Format.formatDuration(seekSlider.to / 1000, expandedRepresentation.durationFormattingOptions)) | ||
241 | font: theme.smallestFont | 339 | font: theme.smallestFont | ||
242 | } | 340 | } | ||
243 | 341 | | |||
244 | PlasmaComponents3.Label { | 342 | PlasmaComponents3.Label { // Time Elapsed | ||
245 | Layout.preferredWidth: timeMetrics.width | 343 | Layout.preferredWidth: timeMetrics.width | ||
246 | verticalAlignment: Text.AlignVCenter | 344 | verticalAlignment: Text.AlignVCenter | ||
247 | horizontalAlignment: Text.AlignRight | 345 | horizontalAlignment: Text.AlignRight | ||
248 | text: KCoreAddons.Format.formatDuration(seekSlider.value / 1000, expandedRepresentation.durationFormattingOptions) | 346 | text: KCoreAddons.Format.formatDuration(seekSlider.value / 1000, expandedRepresentation.durationFormattingOptions) | ||
249 | opacity: 0.9 | 347 | opacity: 0.9 | ||
250 | font: theme.smallestFont | 348 | font: theme.smallestFont | ||
349 | color: softwareRendering ? undefined : "white" | ||||
broulik: The labels are shown outside the album art, so they are unreadable now | |||||
251 | } | 350 | } | ||
252 | 351 | | |||
253 | PlasmaComponents3.Slider { | 352 | PlasmaComponents3.Slider { // Slider | ||
254 | id: seekSlider | 353 | id: seekSlider | ||
255 | Layout.fillWidth: true | 354 | Layout.fillWidth: true | ||
256 | z: 999 | 355 | z: 999 | ||
257 | value: 0 | 356 | value: 0 | ||
258 | visible: canSeek | 357 | visible: canSeek | ||
259 | 358 | | |||
260 | onMoved: { | 359 | onMoved: { | ||
261 | if (!disablePositionUpdate) { | 360 | if (!disablePositionUpdate) { | ||
Show All 18 Lines | 378 | } else { | |||
280 | seekSlider.value += 1000000 | 379 | seekSlider.value += 1000000 | ||
281 | } | 380 | } | ||
282 | disablePositionUpdate = false | 381 | disablePositionUpdate = false | ||
283 | } | 382 | } | ||
284 | } | 383 | } | ||
285 | } | 384 | } | ||
286 | } | 385 | } | ||
287 | 386 | | |||
288 | PlasmaComponents3.ProgressBar { | 387 | PlasmaComponents3.ProgressBar { // Time Remaining | ||
289 | Layout.fillWidth: true | 388 | Layout.fillWidth: true | ||
290 | value: seekSlider.value | 389 | value: seekSlider.value | ||
291 | from: seekSlider.from | 390 | from: seekSlider.from | ||
292 | to: seekSlider.to | 391 | to: seekSlider.to | ||
293 | visible: !canSeek | 392 | visible: !canSeek | ||
294 | } | 393 | } | ||
295 | 394 | | |||
296 | PlasmaComponents3.Label { | 395 | PlasmaComponents3.Label { | ||
297 | Layout.preferredWidth: timeMetrics.width | 396 | Layout.preferredWidth: timeMetrics.width | ||
298 | verticalAlignment: Text.AlignVCenter | 397 | verticalAlignment: Text.AlignVCenter | ||
299 | horizontalAlignment: Text.AlignLeft | 398 | horizontalAlignment: Text.AlignLeft | ||
300 | text: i18nc("Remaining time for song e.g -5:42", "-%1", | 399 | text: i18nc("Remaining time for song e.g -5:42", "-%1", | ||
301 | KCoreAddons.Format.formatDuration((seekSlider.to - seekSlider.value) / 1000, expandedRepresentation.durationFormattingOptions)) | 400 | KCoreAddons.Format.formatDuration((seekSlider.to - seekSlider.value) / 1000, expandedRepresentation.durationFormattingOptions)) | ||
302 | opacity: 0.9 | 401 | opacity: 0.9 | ||
303 | font: theme.smallestFont | 402 | font: theme.smallestFont | ||
403 | color: softwareRendering ? undefined : "white" | ||||
304 | } | 404 | } | ||
305 | } | 405 | } | ||
306 | 406 | | |||
307 | Column { | 407 | Row { // Player Controls | ||
308 | width: parent.width | | |||
309 | | ||||
310 | PlasmaExtras.Heading { | | |||
311 | id: song | | |||
312 | width: parent.width | | |||
313 | height: undefined | | |||
314 | level: 4 | | |||
315 | horizontalAlignment: Text.AlignHCenter | | |||
316 | | ||||
317 | maximumLineCount: 1 | | |||
318 | elide: Text.ElideRight | | |||
319 | text: { | | |||
320 | if (!root.track) { | | |||
321 | return i18n("No media playing") | | |||
322 | } | | |||
323 | return root.artist ? i18nc("artist – track", "%1 – %2", root.artist, root.track) : root.track | | |||
324 | } | | |||
325 | textFormat: Text.PlainText | | |||
326 | } | | |||
327 | | ||||
328 | PlasmaExtras.Heading { | | |||
329 | width: parent.width | | |||
330 | height: undefined | | |||
331 | level: 5 | | |||
332 | opacity: 0.6 | | |||
333 | horizontalAlignment: Text.AlignHCenter | | |||
334 | wrapMode: Text.NoWrap | | |||
335 | elide: Text.ElideRight | | |||
336 | visible: text !== "" | | |||
337 | text: { | | |||
338 | var metadata = root.currentMetadata | | |||
339 | if (!metadata) { | | |||
340 | return "" | | |||
341 | } | | |||
342 | var xesamAlbum = metadata["xesam:album"] | | |||
343 | if (xesamAlbum) { | | |||
344 | return xesamAlbum | | |||
345 | } | | |||
346 | | ||||
347 | // if we play a local file without title and artist, show its containing folder instead | | |||
348 | if (metadata["xesam:title"] || root.artist) { | | |||
349 | return "" | | |||
350 | } | | |||
351 | | ||||
352 | var xesamUrl = (metadata["xesam:url"] || "").toString() | | |||
353 | if (xesamUrl.indexOf("file:///") !== 0) { // "!startsWith()" | | |||
354 | return "" | | |||
355 | } | | |||
356 | | ||||
357 | var urlParts = xesamUrl.split("/") | | |||
358 | if (urlParts.length < 3) { | | |||
359 | return "" | | |||
360 | } | | |||
361 | | ||||
362 | var lastFolderPath = urlParts[urlParts.length - 2] // last would be filename | | |||
363 | if (lastFolderPath) { | | |||
364 | return lastFolderPath | | |||
365 | } | | |||
366 | | ||||
367 | return "" | | |||
368 | } | | |||
369 | textFormat: Text.PlainText | | |||
370 | } | | |||
371 | } | | |||
372 | | ||||
373 | Item { | | |||
374 | width: parent.width | | |||
375 | height: playerControls.height | | |||
376 | | ||||
377 | Row { | | |||
378 | id: playerControls | 408 | id: playerControls | ||
409 | | ||||
379 | property bool enabled: root.canControl | 410 | property bool enabled: root.canControl | ||
380 | property int controlsSize: theme.mSize(theme.defaultFont).height * 3 | 411 | property int controlsSize: theme.mSize(theme.defaultFont).height * 3 | ||
381 | 412 | | |||
382 | anchors.horizontalCenter: parent.horizontalCenter | 413 | Layout.alignment: Qt.AlignHCenter | ||
383 | spacing: units.largeSpacing | 414 | spacing: units.largeSpacing | ||
384 | 415 | | |||
385 | PlasmaComponents3.ToolButton { | 416 | PlasmaComponents3.ToolButton { // Previous | ||
386 | anchors.verticalCenter: parent.verticalCenter | 417 | anchors.verticalCenter: parent.verticalCenter | ||
387 | width: expandedRepresentation.controlSize | 418 | width: expandedRepresentation.controlSize | ||
388 | height: width | 419 | height: width | ||
389 | enabled: playerControls.enabled && root.canGoPrevious | 420 | enabled: playerControls.enabled && root.canGoPrevious | ||
390 | icon.name: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" | 421 | icon.name: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" | ||
391 | onClicked: { | 422 | onClicked: { | ||
392 | seekSlider.value = 0 // Let the media start from beginning. Bug 362473 | 423 | seekSlider.value = 0 // Let the media start from beginning. Bug 362473 | ||
393 | root.action_previous() | 424 | root.action_previous() | ||
394 | } | 425 | } | ||
395 | } | 426 | } | ||
396 | 427 | | |||
397 | PlasmaComponents3.ToolButton { | 428 | PlasmaComponents3.ToolButton { // Pause/Play | ||
398 | width: Math.round(expandedRepresentation.controlSize * 1.5) | 429 | width: Math.round(expandedRepresentation.controlSize * 1.5) | ||
399 | height: width | 430 | height: width | ||
400 | enabled: root.state == "playing" ? root.canPause : root.canPlay | 431 | enabled: root.state == "playing" ? root.canPause : root.canPlay | ||
davidedmundson: Items in a layout shouldn't specify a width | |||||
401 | icon.name: root.state == "playing" ? "media-playback-pause" : "media-playback-start" | 432 | icon.name: root.state == "playing" ? "media-playback-pause" : "media-playback-start" | ||
402 | onClicked: root.togglePlaying() | 433 | onClicked: root.togglePlaying() | ||
403 | } | 434 | } | ||
404 | 435 | | |||
405 | PlasmaComponents3.ToolButton { | 436 | PlasmaComponents3.ToolButton { // Next | ||
406 | anchors.verticalCenter: parent.verticalCenter | 437 | anchors.verticalCenter: parent.verticalCenter | ||
407 | width: expandedRepresentation.controlSize | 438 | width: expandedRepresentation.controlSize | ||
408 | height: width | 439 | height: width | ||
409 | enabled: playerControls.enabled && root.canGoNext | 440 | enabled: playerControls.enabled && root.canGoNext | ||
410 | icon.name: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" | 441 | icon.name: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" | ||
411 | onClicked: { | 442 | onClicked: { | ||
412 | seekSlider.value = 0 // Let the media start from beginning. Bug 362473 | 443 | seekSlider.value = 0 // Let the media start from beginning. Bug 362473 | ||
413 | root.action_next() | 444 | root.action_next() | ||
414 | } | 445 | } | ||
415 | } | 446 | } | ||
416 | } | 447 | } | ||
417 | } | 448 | } | ||
418 | } | | |||
419 | 449 | | |||
420 | Timer { | 450 | Timer { | ||
421 | id: queuedPositionUpdate | 451 | id: queuedPositionUpdate | ||
422 | interval: 100 | 452 | interval: 100 | ||
423 | onTriggered: { | 453 | onTriggered: { | ||
424 | if (position == seekSlider.value) { | 454 | if (position == seekSlider.value) { | ||
425 | return; | 455 | return; | ||
426 | } | 456 | } | ||
427 | var service = mpris2Source.serviceForSource(mpris2Source.current) | 457 | var service = mpris2Source.serviceForSource(mpris2Source.current) | ||
428 | var operation = service.operationDescription("SetPosition") | 458 | var operation = service.operationDescription("SetPosition") | ||
429 | operation.microseconds = seekSlider.value | 459 | operation.microseconds = seekSlider.value | ||
430 | service.startOperationCall(operation) | 460 | service.startOperationCall(operation) | ||
431 | } | 461 | } | ||
432 | } | 462 | } | ||
433 | } | 463 | } |
Superfluous since you fill both width and height?