diff --git a/crates/qbz-i18n/translations/de/LC_MESSAGES/qbz-ui.po b/crates/qbz-i18n/translations/de/LC_MESSAGES/qbz-ui.po index c1976202e..cf041026a 100644 --- a/crates/qbz-i18n/translations/de/LC_MESSAGES/qbz-ui.po +++ b/crates/qbz-i18n/translations/de/LC_MESSAGES/qbz-ui.po @@ -7639,3 +7639,11 @@ msgstr "Playlist löschen?" #: qbz-qt/qml/controls/PlaylistDeleteConfirm.qml msgid "“{}” will be deleted. This cannot be undone." msgstr "„{}“ wird gelöscht. Das lässt sich nicht rückgängig machen." + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy basic data" +msgstr "Basisdaten kopieren" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy full" +msgstr "Alles kopieren" diff --git a/crates/qbz-i18n/translations/en/LC_MESSAGES/qbz-ui.po b/crates/qbz-i18n/translations/en/LC_MESSAGES/qbz-ui.po index 508f47229..072413aa4 100644 --- a/crates/qbz-i18n/translations/en/LC_MESSAGES/qbz-ui.po +++ b/crates/qbz-i18n/translations/en/LC_MESSAGES/qbz-ui.po @@ -3294,3 +3294,11 @@ msgstr "Delete" #: crates/qbz-qt/qml/settings/NavigationSettings.qml msgid "Always use the icon-only section navigation in the header. When off, the text tabs turn into icons only when they no longer fit next to the search box." msgstr "Always use the icon-only section navigation in the header. When off, the text tabs turn into icons only when they no longer fit next to the search box." + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy basic data" +msgstr "Copy basic data" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy full" +msgstr "Copy full" diff --git a/crates/qbz-i18n/translations/es/LC_MESSAGES/qbz-ui.po b/crates/qbz-i18n/translations/es/LC_MESSAGES/qbz-ui.po index a972860db..cfad174dc 100644 --- a/crates/qbz-i18n/translations/es/LC_MESSAGES/qbz-ui.po +++ b/crates/qbz-i18n/translations/es/LC_MESSAGES/qbz-ui.po @@ -7645,3 +7645,11 @@ msgstr "¿Eliminar la lista de reproducción?" #: qbz-qt/qml/controls/PlaylistDeleteConfirm.qml msgid "“{}” will be deleted. This cannot be undone." msgstr "«{}» se eliminará. Esto no se puede deshacer." + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy basic data" +msgstr "Copiar datos básicos" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy full" +msgstr "Copiar todo" diff --git a/crates/qbz-i18n/translations/fr/LC_MESSAGES/qbz-ui.po b/crates/qbz-i18n/translations/fr/LC_MESSAGES/qbz-ui.po index 9cd37d41d..4088a0c07 100644 --- a/crates/qbz-i18n/translations/fr/LC_MESSAGES/qbz-ui.po +++ b/crates/qbz-i18n/translations/fr/LC_MESSAGES/qbz-ui.po @@ -7637,3 +7637,11 @@ msgstr "Supprimer la playlist ?" #: qbz-qt/qml/controls/PlaylistDeleteConfirm.qml msgid "“{}” will be deleted. This cannot be undone." msgstr "« {} » sera supprimée. Cette action est irréversible." + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy basic data" +msgstr "Copier les données de base" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy full" +msgstr "Tout copier" diff --git a/crates/qbz-i18n/translations/ja/LC_MESSAGES/qbz-ui.po b/crates/qbz-i18n/translations/ja/LC_MESSAGES/qbz-ui.po index 33c3c0140..9088277b5 100644 --- a/crates/qbz-i18n/translations/ja/LC_MESSAGES/qbz-ui.po +++ b/crates/qbz-i18n/translations/ja/LC_MESSAGES/qbz-ui.po @@ -7620,3 +7620,11 @@ msgstr "プレイリストを削除しますか?" #: qbz-qt/qml/controls/PlaylistDeleteConfirm.qml msgid "“{}” will be deleted. This cannot be undone." msgstr "「{}」を削除します。この操作は取り消せません。" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy basic data" +msgstr "基本情報をコピー" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy full" +msgstr "すべてコピー" diff --git a/crates/qbz-i18n/translations/nl/LC_MESSAGES/qbz-ui.po b/crates/qbz-i18n/translations/nl/LC_MESSAGES/qbz-ui.po index 8be41d166..f1dee7b1e 100644 --- a/crates/qbz-i18n/translations/nl/LC_MESSAGES/qbz-ui.po +++ b/crates/qbz-i18n/translations/nl/LC_MESSAGES/qbz-ui.po @@ -7627,3 +7627,11 @@ msgstr "Afspeellijst verwijderen?" #: qbz-qt/qml/controls/PlaylistDeleteConfirm.qml msgid "“{}” will be deleted. This cannot be undone." msgstr "“{}” wordt verwijderd. Dit kan niet ongedaan worden gemaakt." + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy basic data" +msgstr "Basisgegevens kopiëren" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy full" +msgstr "Alles kopiëren" diff --git a/crates/qbz-i18n/translations/pt/LC_MESSAGES/qbz-ui.po b/crates/qbz-i18n/translations/pt/LC_MESSAGES/qbz-ui.po index e85bb7b37..5d9d6a1a1 100644 --- a/crates/qbz-i18n/translations/pt/LC_MESSAGES/qbz-ui.po +++ b/crates/qbz-i18n/translations/pt/LC_MESSAGES/qbz-ui.po @@ -7637,3 +7637,11 @@ msgstr "Excluir a playlist?" #: qbz-qt/qml/controls/PlaylistDeleteConfirm.qml msgid "“{}” will be deleted. This cannot be undone." msgstr "“{}” será excluída. Isso não pode ser desfeito." + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy basic data" +msgstr "Copiar dados básicos" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy full" +msgstr "Copiar tudo" diff --git a/crates/qbz-i18n/translations/ru/LC_MESSAGES/qbz-ui.po b/crates/qbz-i18n/translations/ru/LC_MESSAGES/qbz-ui.po index b8e11a4e5..bc54bdd8b 100644 --- a/crates/qbz-i18n/translations/ru/LC_MESSAGES/qbz-ui.po +++ b/crates/qbz-i18n/translations/ru/LC_MESSAGES/qbz-ui.po @@ -7658,3 +7658,11 @@ msgstr "Удалить плейлист?" #: qbz-qt/qml/controls/PlaylistDeleteConfirm.qml msgid "“{}” will be deleted. This cannot be undone." msgstr "«{}» будет удалён. Это действие нельзя отменить." + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy basic data" +msgstr "Копировать основные данные" + +#: crates/qbz-qt/qml/shell/TrackInfoBody.qml +msgid "Copy full" +msgstr "Копировать всё" diff --git a/crates/qbz-qt/qml/shell/AlbumInfoModal.qml b/crates/qbz-qt/qml/shell/AlbumInfoModal.qml index 5ad6c8041..d4a6a47e1 100644 --- a/crates/qbz-qt/qml/shell/AlbumInfoModal.qml +++ b/crates/qbz-qt/qml/shell/AlbumInfoModal.qml @@ -17,6 +17,7 @@ import QtQuick import QtQuick.Controls import com.blitzfc.qbz +import "../controls" import "../theme" Popup { @@ -60,6 +61,64 @@ Popup { // that to the API's id: any divergence blanks this card with no log. property string requestedId: "" + // --- Copy (2026-09-13) -------------------------------------------------- + // Basic = "{Album} - {Artist}"; full = the header lines plus the ACTIVE + // tab as plain text: every track with its performers (Credits) or the + // review. The glyph turns into a check for a moment as the receipt. + QbzClipboard { id: clipboard } + property bool justCopied: false + Timer { + id: copiedTimer + interval: 1400 + onTriggered: root.justCopied = false + } + function nonEmpty(s) { return (s || "") !== "" } + function basicText() { + return [root.doc.title, root.doc.artist].filter(root.nonEmpty).join(" - ") + } + function fullText() { + var d = root.doc + var lines = [d.title, d.artist].filter(root.nonEmpty) + var meta = [d.label, d.releaseDate, d.metaLine, d.quality].filter(root.nonEmpty) + if (meta.length > 0) lines.push(meta.join(" · ")) + if (root.activeTab === "review") { + if (root.nonEmpty(d.review)) { lines.push(""); lines.push(d.review) } + return lines.join("\n") + } + var tracks = d.tracks || [] + if (tracks.length > 0) lines.push("") + for (var i = 0; i < tracks.length; i++) { + var tr = tracks[i] + var num = (tr.number === undefined || tr.number === null) ? "" : String(tr.number) + var head = (num !== "" ? num + ". " : "") + (tr.title || "") + if (root.nonEmpty(tr.artist)) head += " — " + tr.artist + lines.push(head) + var perf = tr.performers || [] + for (var j = 0; j < perf.length; j++) { + var p = perf[j] + lines.push(" " + (p.name || "") + (root.nonEmpty(p.roles) ? ": " + p.roles : "")) + } + if (root.nonEmpty(tr.copyright)) lines.push(" " + tr.copyright) + } + return lines.join("\n") + } + function copy(which) { + var text = which === "full" ? root.fullText() : root.basicText() + if (clipboard.copy(text)) { + root.justCopied = true + copiedTimer.restart() + } + } + CardMenu { + id: copyMenu + menuWidth: 200 + entries: [ + { "label": QbzSession.tr("Copy basic data", QbzSession.trRev), "icon": "copy", "action": "basic" }, + { "label": QbzSession.tr("Copy full", QbzSession.trRev), "icon": "clipboard", "action": "full" } + ] + onPicked: function (a) { root.copy(a) } + } + // --- Actions --------------------------------------------------------- /// Header info button. The view gates local/Plex albums (no button); @@ -220,26 +279,20 @@ Popup { spacing: 0 topPadding: 4 bottomPadding: 4 - Text { + QbzSelectableText { id: perfName text: modelData.name - color: perfArea.containsMouse ? theme.accent : theme.textPrimary - font.pixelSize: 13 - font.weight: theme.weightMedium - MouseArea { - id: perfArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: root.openMusician(modelData.name, modelData.primaryRole) - } + color: theme.textPrimary + linkHref: "musician" + pixelSize: 13 + weight: theme.weightMedium + onLinkActivated: root.openMusician(modelData.name, modelData.primaryRole) } - Text { - width: parent.width - perfName.implicitWidth - text: modelData.roles + QbzSelectableText { + width: Math.max(0, parent.width - perfName.width) + text: String(modelData.roles || "") color: theme.textMuted - font.pixelSize: 13 - wrapMode: Text.WordWrap + pixelSize: 13 } } } @@ -303,23 +356,45 @@ Popup { spacing: 16 Item { width: 8; height: 1 } Column { - width: parent.width - 82 + width: parent.width - 130 anchors.verticalCenter: parent.verticalCenter spacing: 4 - Text { + // Selectable (2026-09-13), like every field below. + QbzSelectableText { width: parent.width text: root.doc.title || "" color: theme.textPrimary - font.pixelSize: 16 - font.weight: theme.weightSemibold - elide: Text.ElideRight + pixelSize: 16 + weight: theme.weightSemibold } - Text { + QbzSelectableText { width: parent.width text: root.doc.artist || "" color: theme.textMuted - font.pixelSize: theme.fontLegal - elide: Text.ElideRight + pixelSize: theme.fontLegal + } + } + // Copy: basic line or the active tab, from the flyout. + Rectangle { + id: copyBtn + width: 32 + height: 32 + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + QbzIcon { + anchors.centerIn: parent + name: root.justCopied ? "check" : "copy" + width: 17 + height: 17 + tintName: root.justCopied ? "accent" + : (copyArea.containsMouse ? "primary" : "muted") + } + MouseArea { + id: copyArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: copyMenu.openBelowLeft(copyBtn) } } Rectangle { @@ -376,43 +451,38 @@ Popup { color: theme.textMuted font.pixelSize: 13 } - Text { + QbzSelectableText { + width: parent.width text: root.doc.label || "" - color: (labelArea.containsMouse && (root.doc.labelId || "") !== "") - ? theme.accent : theme.textPrimary - font.pixelSize: 13 - font.weight: theme.weightSemibold - MouseArea { - id: labelArea - anchors.fill: parent - hoverEnabled: true - cursorShape: (root.doc.labelId || "") !== "" - ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: root.openLabel(root.doc.labelId || "") - } + color: theme.textPrimary + linkHref: (root.doc.labelId || "") !== "" ? "label" : "" + pixelSize: 13 + weight: theme.weightSemibold + onLinkActivated: root.openLabel(root.doc.labelId || "") } - Text { + QbzSelectableText { visible: (root.doc.releaseDate || "") !== "" + width: parent.width text: QbzSession.tr("on", QbzSession.trRev) + " " + (root.doc.releaseDate || "") color: theme.textMuted - font.pixelSize: 13 + pixelSize: 13 } } Item { visible: (root.doc.label || "") !== ""; width: 1; height: 8 } - Text { + QbzSelectableText { visible: (root.doc.metaLine || "") !== "" width: parent.width text: root.doc.metaLine || "" color: theme.textMuted - font.pixelSize: 13 - wrapMode: Text.WordWrap + pixelSize: 13 } Item { visible: (root.doc.quality || "") !== ""; width: 1; height: 12 } - Text { + QbzSelectableText { visible: (root.doc.quality || "") !== "" + width: parent.width text: root.doc.quality || "" color: theme.textSecondary - font.pixelSize: 13 + pixelSize: 13 } } @@ -502,13 +572,12 @@ Popup { } } - Text { + QbzSelectableText { visible: root.activeTab === "review" width: parent.width text: root.doc.review || "" color: theme.textSecondary - font.pixelSize: 14 - wrapMode: Text.WordWrap + pixelSize: 14 } } } diff --git a/crates/qbz-qt/qml/shell/InfoCreditCell.qml b/crates/qbz-qt/qml/shell/InfoCreditCell.qml index e76e7f7a0..590cbc482 100644 --- a/crates/qbz-qt/qml/shell/InfoCreditCell.qml +++ b/crates/qbz-qt/qml/shell/InfoCreditCell.qml @@ -9,6 +9,7 @@ // { "role": "PRODUCER", "roleRaw": "Producer", "names": ["A", "B"] } import QtQuick +import "../controls" import "../theme" Column { @@ -33,46 +34,34 @@ Column { width: colW spacing: 6 - Text { + QbzSelectableText { width: cc.colW text: cc.cell ? (cc.cell.role || "") : "" color: cc.overAmbient ? "#b3ffffff" : theme.textMuted - style: cc.overAmbient ? Text.Raised : Text.Normal - styleColor: "#b0000000" - font.pixelSize: 11 - font.weight: theme.weightSemibold - font.letterSpacing: 0.5 - wrapMode: Text.WordWrap + raised: cc.overAmbient + pixelSize: 11 + weight: theme.weightSemibold + letterSpacing: 0.5 } Column { spacing: 2 Repeater { model: cc.cell ? (cc.cell.names || []) : [] - delegate: Item { + // Selectable AND a link: a click lands on the musician, a drag + // selects the name (QbzSelectableText, rich-text anchor). + delegate: QbzSelectableText { id: nameRow required property string modelData width: cc.colW - height: nameText.implicitHeight - Text { - id: nameText - width: cc.colW - text: nameRow.modelData - color: nameArea.containsMouse ? cc.accentColor - : (cc.overAmbient ? "#f2ffffff" : theme.textPrimary) - style: cc.overAmbient ? Text.Raised : Text.Normal - styleColor: "#b0000000" - font.pixelSize: 14 - wrapMode: Text.WordWrap - } - MouseArea { - id: nameArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: cc.nameClicked(nameRow.modelData, - cc.cell ? (cc.cell.roleRaw || "") : "") - } + text: nameRow.modelData + color: cc.overAmbient ? "#f2ffffff" : theme.textPrimary + linkHref: "musician" + linkHoverColor: cc.accentColor + raised: cc.overAmbient + pixelSize: 14 + onLinkActivated: cc.nameClicked(nameRow.modelData, + cc.cell ? (cc.cell.roleRaw || "") : "") } } } diff --git a/crates/qbz-qt/qml/shell/InfoMetaCell.qml b/crates/qbz-qt/qml/shell/InfoMetaCell.qml index 6ae2bef0b..b39c93bac 100644 --- a/crates/qbz-qt/qml/shell/InfoMetaCell.qml +++ b/crates/qbz-qt/qml/shell/InfoMetaCell.qml @@ -13,6 +13,7 @@ // } import QtQuick +import "../controls" import "../theme" Column { @@ -28,15 +29,13 @@ Column { width: cellWidth spacing: 6 - Text { + QbzSelectableText { width: mc.cellWidth text: mc.label color: mc.overAmbient ? "#b3ffffff" : theme.textMuted - style: mc.overAmbient ? Text.Raised : Text.Normal - styleColor: "#b0000000" - font.pixelSize: 11 - font.weight: theme.weightSemibold - font.letterSpacing: 0.5 - elide: Text.ElideRight + raised: mc.overAmbient + pixelSize: 11 + weight: theme.weightSemibold + letterSpacing: 0.5 } } diff --git a/crates/qbz-qt/qml/shell/TrackInfoBody.qml b/crates/qbz-qt/qml/shell/TrackInfoBody.qml index 11c9d3a11..9173a6024 100644 --- a/crates/qbz-qt/qml/shell/TrackInfoBody.qml +++ b/crates/qbz-qt/qml/shell/TrackInfoBody.qml @@ -15,6 +15,7 @@ import QtQuick import com.blitzfc.qbz +import "../controls" import "../theme" Column { @@ -42,6 +43,59 @@ Column { readonly property var doc: host ? host.doc : ({}) + // --- Copy (2026-09-13) ------------------------------------------------- + // Basic = "{Track} - {Album} - {Artist}"; full = everything the modal + // shows, as plain text. The glyph turns into a check for a moment. + QbzClipboard { id: clipboard } + property bool justCopied: false + Timer { + id: copiedTimer + interval: 1400 + onTriggered: body.justCopied = false + } + function nonEmpty(s) { return (s || "") !== "" } + function basicText() { + return [body.doc.title, body.doc.album, body.doc.artist].filter(body.nonEmpty).join(" - ") + } + function fullText() { + var t = QbzSession.tr + var r = QbzSession.trRev + var d = body.doc + var lines = [d.title, d.album, d.artist].filter(body.nonEmpty) + var meta = [] + if (body.nonEmpty(d.duration)) meta.push(t("Duration", r) + ": " + d.duration) + if (body.nonEmpty(d.quality)) meta.push(t("Quality", r) + ": " + d.quality) + if (body.nonEmpty(d.isrc)) meta.push("ISRC: " + d.isrc) + if (body.nonEmpty(d.label)) meta.push(t("Label", r) + ": " + d.label) + if (meta.length > 0) { lines.push(""); lines = lines.concat(meta) } + var credits = body.host ? body.host.credits : [] + if (credits.length > 0) { + lines.push("") + for (var i = 0; i < credits.length; i++) { + var c = credits[i] + lines.push((c.role || c.roleRaw || "") + ": " + (c.names || []).join(", ")) + } + } + if (body.nonEmpty(d.copyright)) { lines.push(""); lines.push(d.copyright) } + return lines.join("\n") + } + function copy(which) { + var text = which === "full" ? body.fullText() : body.basicText() + if (clipboard.copy(text)) { + body.justCopied = true + copiedTimer.restart() + } + } + CardMenu { + id: copyMenu + menuWidth: 200 + entries: [ + { "label": QbzSession.tr("Copy basic data", QbzSession.trRev), "icon": "copy", "action": "basic" }, + { "label": QbzSession.tr("Copy full", QbzSession.trRev), "icon": "clipboard", "action": "full" } + ] + onPicked: function (a) { body.copy(a) } + } + // ---- Loading --------------------------------------------------------- Item { visible: body.host && body.host.loading && body.host.errorText === "" @@ -115,53 +169,71 @@ Column { x: 24 y: 16 // 24 L + 24 R padding, 16 gap, 32 close X. - width: Math.max(0, parent.width - 24 - 24 - 16 - 32) + width: Math.max(0, parent.width - 24 - 24 - 16 - 32 - 8 - 32) spacing: 4 - Text { + // Every field is selectable (2026-09-13): QbzSelectableText, + // raised (plain, shadowed) on the ambient host. + QbzSelectableText { width: parent.width text: body.doc.title || "" color: body.cPrimary - style: body.cStyle - styleColor: body.cShadow + raised: body.overAmbient + raisedShadow: body.cShadow // The immersive panel reads at a distance — its header // steps up (owner, 2026-08-31 round 2). - font.pixelSize: body.overAmbient ? 24 : 16 - font.weight: theme.weightSemibold - wrapMode: Text.WordWrap + pixelSize: body.overAmbient ? 24 : 16 + weight: theme.weightSemibold } - Text { + QbzSelectableText { visible: (body.doc.album || "") !== "" width: parent.width text: body.doc.album || "" color: body.cMuted - style: body.cStyle - styleColor: body.cShadow - font.pixelSize: body.overAmbient ? 15 : theme.fontLegal - elide: Text.ElideRight + raised: body.overAmbient + raisedShadow: body.cShadow + pixelSize: body.overAmbient ? 15 : theme.fontLegal } // Artist — a link only when an id exists. - Item { + QbzSelectableText { visible: (body.doc.artist || "") !== "" width: parent.width - height: visible ? artistText.implicitHeight : 0 - Text { - id: artistText - text: body.doc.artist || "" - color: (body.doc.artistId || "") !== "" - ? body.cAccent : body.cPrimary - style: body.cStyle - styleColor: body.cShadow - font.pixelSize: body.overAmbient ? 20 : 16 - font.weight: theme.weightSemibold - } - MouseArea { - width: Math.min(artistText.implicitWidth, parent.width) - height: artistText.implicitHeight - cursorShape: (body.doc.artistId || "") !== "" - ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: body.host.openArtist(body.doc.artistId || "") - } + text: body.doc.artist || "" + color: (body.doc.artistId || "") !== "" ? body.cAccent : body.cPrimary + linkHref: (body.doc.artistId || "") !== "" ? "artist" : "" + linkHoverColor: body.cAccent + raised: body.overAmbient + raisedShadow: body.cShadow + pixelSize: body.overAmbient ? 20 : 16 + weight: theme.weightSemibold + onLinkActivated: body.host.openArtist(body.doc.artistId || "") + } + } + + // Copy — basic line or the full sheet, from the flyout; the + // glyph turns into a check for a moment as the receipt. + Item { + id: copyBtn + width: 32 + height: 32 + x: parent.width - 24 - 32 - 8 - 32 + y: 16 + QbzIcon { + name: body.justCopied ? "check" : "copy" + width: 17 + height: 17 + anchors.centerIn: parent + tintName: body.justCopied ? "accent" + : body.overAmbient + ? (copyArea.containsMouse ? "white" : "muted") + : (copyArea.containsMouse ? "textPrimary" : "muted") + } + MouseArea { + id: copyArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: copyMenu.openBelowLeft(copyBtn) } } @@ -211,28 +283,26 @@ Column { cellWidth: loadedCol.metaColW overAmbient: body.overAmbient label: QbzSession.tr("Duration", QbzSession.trRev) - Text { + QbzSelectableText { width: loadedCol.metaColW text: body.doc.duration || "" color: body.cPrimary - style: body.cStyle - styleColor: body.cShadow - font.pixelSize: 14 - elide: Text.ElideRight + raised: body.overAmbient + raisedShadow: body.cShadow + pixelSize: 14 } } InfoMetaCell { cellWidth: loadedCol.metaColW overAmbient: body.overAmbient label: QbzSession.tr("Quality", QbzSession.trRev) - Text { + QbzSelectableText { width: loadedCol.metaColW text: body.doc.quality || "" color: body.cPrimary - style: body.cStyle - styleColor: body.cShadow - font.pixelSize: 14 - elide: Text.ElideRight + raised: body.overAmbient + raisedShadow: body.cShadow + pixelSize: 14 } } // ISRC — the cell is dropped (not blanked) when absent; @@ -242,14 +312,13 @@ Column { cellWidth: loadedCol.metaColW overAmbient: body.overAmbient label: "ISRC" - Text { + QbzSelectableText { width: loadedCol.metaColW text: body.doc.isrc || "" color: body.cMuted - style: body.cStyle - styleColor: body.cShadow - font.pixelSize: 14 - elide: Text.ElideRight + raised: body.overAmbient + raisedShadow: body.cShadow + pixelSize: 14 } } Item { @@ -271,30 +340,16 @@ Column { cellWidth: loadedCol.metaColW overAmbient: body.overAmbient label: QbzSession.tr("Label", QbzSession.trRev) - Item { + QbzSelectableText { width: loadedCol.metaColW - height: labelText.implicitHeight - Text { - id: labelText - width: loadedCol.metaColW - text: body.doc.label || "" - color: (labelArea.containsMouse - && (body.doc.labelId || "") !== "") - ? body.cAccent : body.cPrimary - style: body.cStyle - styleColor: body.cShadow - font.pixelSize: 14 - elide: Text.ElideRight - } - MouseArea { - id: labelArea - width: Math.min(labelText.implicitWidth, loadedCol.metaColW) - height: labelText.implicitHeight - hoverEnabled: true - cursorShape: (body.doc.labelId || "") !== "" - ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: body.host.openLabel(body.doc.labelId || "") - } + text: body.doc.label || "" + color: body.cPrimary + linkHref: (body.doc.labelId || "") !== "" ? "label" : "" + linkHoverColor: body.cAccent + raised: body.overAmbient + raisedShadow: body.cShadow + pixelSize: 14 + onLinkActivated: body.host.openLabel(body.doc.labelId || "") } } } @@ -369,15 +424,14 @@ Column { width: 1 height: 20 } - Text { + QbzSelectableText { visible: (body.doc.copyright || "") !== "" width: parent.width text: body.doc.copyright || "" color: body.cMuted - style: body.cStyle - styleColor: body.cShadow - font.pixelSize: 12 - wrapMode: Text.WordWrap + raised: body.overAmbient + raisedShadow: body.cShadow + pixelSize: 12 } } }