diff --git a/src/qml/ContextView.qml b/src/qml/ContextView.qml index 0435f99b..ae9b0783 100644 --- a/src/qml/ContextView.qml +++ b/src/qml/ContextView.qml @@ -1,277 +1,271 @@ /* * Copyright 2016 Matthieu Gallien * Copyright 2019 Nate Graham * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Window 2.2 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: topItem property var viewType property int databaseId: 0 property var trackType property alias title: titleLabel.text property string albumName: '' property string artistName: '' property url albumArtUrl: '' property url fileUrl: '' TrackContextMetaDataModel { id: metaDataModel manager: elisa.musicManager } ColumnLayout { anchors.fill: parent spacing: 0 - TextMetrics { - id: titleHeight - text: viewTitle.text - font: viewTitle.font - } - // Header with title HeaderFooterToolbar { type: "header" contentItems: [ Image { id: mainIcon source: elisaTheme.nowPlayingIcon height: viewTitle.height width: height sourceSize.height: height sourceSize.width: width fillMode: Image.PreserveAspectFit asynchronous: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft }, Item { id: spacer width: Kirigami.Units.largeSpacing }, LabelWithToolTip { id: viewTitle Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter text: i18nc("Title of the context view related to the currently playing track", "Now Playing") level: 1 } ] } // Container to hold both the blurred background and the scrollview // We can't make the scrollview a child of the background item since then // everything in the scrollview will be blurred and transparent too! Item { Layout.fillWidth: true Layout.fillHeight: true // Blurred album art background Image { id: albumArtBackground anchors.fill: parent source: albumArtUrl.toString() === '' ? Qt.resolvedUrl(elisaTheme.defaultAlbumImage) : albumArtUrl sourceSize.width: topItem.width sourceSize.height: topItem.height asynchronous: true fillMode: Image.PreserveAspectCrop layer.enabled: true opacity: 0.2 layer.effect: FastBlur { source: albumArtBackground // anchors.fill: parent radius: 40 } } // Scrollview to hold all the content ScrollView { id: scrollView anchors.fill: parent clip: true contentWidth: content.width contentHeight: content.height // Title + metadata + lyrics ColumnLayout { id: content width: scrollView.width - scrollView.ScrollBar.vertical.width // Song title LabelWithToolTip { id: titleLabel level: 1 horizontalAlignment: Label.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignTop Layout.topMargin: elisaTheme.layoutVerticalMargin } LabelWithToolTip { id: subtitleLabel text: { if (artistName !== '' && albumName !== '') { return i18nc("display of artist and album in context view", "by %1 from %2", artistName, albumName) } else if (artistName === '' && albumName !== '') { return i18nc("display of album in context view", "from %1", albumName) } else if (artistName !== '' && albumName === '') { i18nc("display of artist in context view", "by %1", artistName) } } level: 3 opacity: 0.6 horizontalAlignment: Label.AlignHCenter visible: artistName !== '' && albumName !== '' Layout.fillWidth: true Layout.alignment: Qt.AlignTop } // Horizontal line separating title and subtitle from metadata Rectangle { Layout.fillWidth: true Layout.leftMargin: Kirigami.Units.largeSpacing* 5 Layout.rightMargin: Kirigami.Units.largeSpacing * 5 Layout.topMargin: Kirigami.Units.largeSpacing Layout.bottomMargin: Kirigami.Units.largeSpacing height: 1 color: myPalette.mid } // Metadata ColumnLayout { id: allMetaData spacing: 0 Layout.fillWidth: true Repeater { id: trackData model: metaDataModel delegate: MetaDataDelegate { Layout.fillWidth: true } } } // Horizontal line separating metadata from lyrics Rectangle { Layout.fillWidth: true Layout.leftMargin: Kirigami.Units.largeSpacing * 5 Layout.rightMargin: Kirigami.Units.largeSpacing * 5 Layout.topMargin: Kirigami.Units.largeSpacing Layout.bottomMargin: Kirigami.Units.largeSpacing height: 1 color: myPalette.mid visible: metaDataModel.lyrics !== "" } // Lyrics Label { text: metaDataModel.lyrics wrapMode: Text.WordWrap horizontalAlignment: Label.AlignHCenter Layout.fillWidth: true Layout.bottomMargin: Kirigami.Units.smallSpacing visible: metaDataModel.lyrics !== "" } } } } // Footer with file path label HeaderFooterToolbar { type: "footer" contentLayoutSpacing: Kirigami.Units.largeSpacing contentItems: [ Image { sourceSize.width: fileNameLabel.height sourceSize.height: fileNameLabel.height source: elisaTheme.folderIcon }, LabelWithToolTip { id: fileNameLabel Layout.fillWidth: true text: metaDataModel.fileUrl elide: Text.ElideLeft } ] } } onDatabaseIdChanged: { metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) } Connections { target: elisa onMusicManagerChanged: { metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) } } Component.onCompleted: { if (elisa.musicManager) { metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) } } } diff --git a/src/qml/EditableMetaDataDelegate.qml b/src/qml/EditableMetaDataDelegate.qml index 10a5569c..5b27b0b9 100644 --- a/src/qml/EditableMetaDataDelegate.qml +++ b/src/qml/EditableMetaDataDelegate.qml @@ -1,79 +1,73 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 RowLayout { id: delegateRow spacing: 0 - TextMetrics { - id: metaDataLabelMetric - - text: 'Metadata Name' - } - Label { id: metaDataLabels text: { if (model.name !== undefined) { return i18nc("Label for a piece of metadata, e.g. 'Album Artist:'", "%1:", model.name) } return "" } font.weight: Font.Bold horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignCenter Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 } Loader { id: textDisplayLoader active: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.UrlEntry || model.type === EditableTrackMetadataModel.IntegerEntry visible: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.UrlEntry || model.type === EditableTrackMetadataModel.IntegerEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: TextField { text: model.display horizontalAlignment: Text.AlignLeft selectByMouse: true anchors.fill: parent onTextEdited: { if (model.display !== text) { model.display = text } } } } } diff --git a/src/qml/FileBrowserView.qml b/src/qml/FileBrowserView.qml index 920a2bb3..39bd5352 100644 --- a/src/qml/FileBrowserView.qml +++ b/src/qml/FileBrowserView.qml @@ -1,187 +1,182 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: fileView property var viewType property bool isSubPage: false property alias expandedFilterView: navigationBar.expandedFilterView function goBack() { proxyModel.openParentFolder() } function loadFolderAndClear(data) { proxyModel.openFolder(data) navigationBar.filterText = "" } FileBrowserModel { id: realModel } FileBrowserProxyModel { id: proxyModel sourceModel: realModel playList: elisa.mediaPlayList } MouseArea { anchors.fill: parent hoverEnabled: false acceptedButtons: Qt.BackButton onClicked: proxyModel.openParentFolder() } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: i18nc("Title of the file browser view", "Files") image: elisaTheme.folderIcon secondaryTitle: proxyModel.url enableGoBack: proxyModel.canGoBack sortOrder: proxyModel.sortedAscending showRating: false Layout.fillWidth: true Binding { target: proxyModel property: 'filterText' value: navigationBar.filterText } onEnqueue: proxyModel.enqueueToPlayList() onReplaceAndPlay: proxyModel.replaceAndPlayOfPlayList() onGoBack: proxyModel.openParentFolder() onSort: proxyModel.sortModel(order) } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true clip: true GridView { id: contentDirectoryView anchors.fill: parent activeFocusOnTab: true keyNavigationEnabled: true ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds currentIndex: -1 Accessible.role: Accessible.List Accessible.name: proxyModel.url model: proxyModel - TextMetrics { - id: secondaryLabelSize - text: 'example' - } - ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } add: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 100 } } remove: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 100 } } cellWidth: Math.floor(availableWidth / Math.max(Math.floor(availableWidth / elisaTheme.gridDelegateSize), 2)) - cellHeight: elisaTheme.gridDelegateSize + secondaryLabelSize.boundingRect.height * 2 + Kirigami.Units.largeSpacing + cellHeight: elisaTheme.gridDelegateSize + Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing delegate: GridBrowserDelegate { width: elisaTheme.gridDelegateSize height: contentDirectoryView.cellHeight focus: true isSelected: contentDirectoryView.currentIndex === index mainText: model.name delegateDisplaySecondaryText: false fileUrl: model.fileUrl entryType: ElisaUtils.FileName imageUrl: model.imageUrl showDetailsButton: !model.isDirectory && !model.isPlaylist showEnqueueButton: !model.isDirectory && !model.isPlaylist showPlayButton: !model.isDirectory onEnqueue: elisa.mediaPlayList.enqueue(url, ElisaUtils.FileName, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: { if (model.isPlaylist) { elisa.mediaPlayList.loadPlaylist(url) } else { elisa.mediaPlayList.enqueue(url, ElisaUtils.FileName, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) } } onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index } onActiveFocusChanged: { if (activeFocus && contentDirectoryView.currentIndex !== model.index) { contentDirectoryView.currentIndex = model.index } } onOpen: isDirectory ? loadFolderAndClear(model.fileUrl) : elisa.mediaPlayList.enqueue(model.fileUrl, ElisaUtils.FileName, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) } } } } } diff --git a/src/qml/GridBrowserDelegate.qml b/src/qml/GridBrowserDelegate.qml index 656e9668..758978d7 100644 --- a/src/qml/GridBrowserDelegate.qml +++ b/src/qml/GridBrowserDelegate.qml @@ -1,415 +1,407 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.5 as Kirigami FocusScope { id: gridEntry property url imageUrl property url imageFallbackUrl property url fileUrl property var entryType property alias mainText: mainLabel.text property alias secondaryText: secondaryLabel.text property var databaseId property bool delegateDisplaySecondaryText: true property bool isPartial property bool isSelected property bool showDetailsButton: false property bool showPlayButton: true property bool showEnqueueButton: true signal enqueue(var databaseId, var name, var url) signal replaceAndPlay(var databaseId, var name, var url) signal open() signal selected() Loader { id: metadataLoader active: false && gridEntry.fileUrl onLoaded: item.show() sourceComponent: MediaTrackMetadataView { fileName: gridEntry.fileUrl ? gridEntry.fileUrl : '' showImage: true modelType: gridEntry.entryType showTrackFileName: true showDeleteButton: false showApplyButton: false editableMetadata: false onRejected: metadataLoader.active = false; } } Keys.onReturnPressed: open() Keys.onEnterPressed: open() Accessible.role: Accessible.ListItem Accessible.name: mainText Rectangle { id: stateIndicator anchors.fill: parent z: 1 color: "transparent" opacity: 0.4 radius: 3 } MouseArea { id: hoverHandle anchors.fill: parent z: 2 hoverEnabled: true acceptedButtons: Qt.LeftButton cursorShape: Qt.PointingHandCursor Layout.preferredHeight: gridEntry.height Layout.fillWidth: true onClicked: open() TextMetrics { id: mainLabelSize font: mainLabel.font text: mainLabel.text } - TextMetrics { - id: secondaryLabelSize - font: secondaryLabel.font - text: secondaryLabel.text - } - ColumnLayout { id: mainData spacing: 0 anchors.fill: parent Item { Layout.margins: Kirigami.Units.largeSpacing Layout.preferredHeight: gridEntry.width - 2 * Kirigami.Units.largeSpacing Layout.preferredWidth: gridEntry.width - 2 * Kirigami.Units.largeSpacing Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Loader { id: hoverLoader active: false anchors { bottom: parent.bottom bottomMargin: 2 left: parent.left leftMargin: 2 } z: 1 opacity: 0 sourceComponent: Row { spacing: 2 Button { id: detailsButton objectName: 'detailsButton' icon.name: 'help-about' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Show track metadata", "View Details") Accessible.role: Accessible.Button Accessible.name: ToolTip.text Accessible.description: ToolTip.text Accessible.onPressAction: clicked() onClicked: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } Keys.onReturnPressed: clicked() Keys.onEnterPressed: clicked() visible: showDetailsButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } Button { id: replaceAndPlayButton objectName: 'replaceAndPlayButton' icon.name: 'media-playback-start' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Clear play list and add whole container to play list", "Play now, replacing current playlist") Accessible.role: Accessible.Button Accessible.name: ToolTip.text Accessible.description: ToolTip.text Accessible.onPressAction: onClicked onClicked: replaceAndPlay(databaseId, mainText, fileUrl) Keys.onReturnPressed: replaceAndPlay(databaseId, mainText, fileUrl) Keys.onEnterPressed: replaceAndPlay(databaseId, mainText, fileUrl) visible: showPlayButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } Button { id: enqueueButton objectName: 'enqueueButton' icon.name: 'list-add' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Add whole container to play list", "Add to playlist") Accessible.role: Accessible.Button Accessible.name: ToolTip.text Accessible.description: ToolTip.text Accessible.onPressAction: onClicked onClicked: enqueue(databaseId, mainText, fileUrl) Keys.onReturnPressed: enqueue(databaseId, mainText, fileUrl) Keys.onEnterPressed: enqueue(databaseId, mainText, fileUrl) visible: showEnqueueButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } } } Loader { id: coverImageLoader active: !isPartial anchors.fill: parent sourceComponent: ImageWithFallback { id: coverImage anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height fillMode: Image.PreserveAspectFit smooth: true source: gridEntry.imageUrl fallback: gridEntry.imageFallbackUrl asynchronous: true layer.enabled: !coverImage.usingFallback layer.effect: DropShadow { source: coverImage radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } } Loader { active: isPartial anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height sourceComponent: BusyIndicator { anchors.centerIn: parent running: true } } } LabelWithToolTip { id: mainLabel level: 4 color: myPalette.text // FIXME: Center-aligned text looks better overall, but // sometimes results in font kerning issues // See https://bugreports.qt.io/browse/QTBUG-49646 horizontalAlignment: Text.AlignHCenter Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth - Layout.maximumHeight: delegateDisplaySecondaryText - ? (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) - : (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) * 2 + Layout.maximumHeight: delegateDisplaySecondaryText ? mainLabelSize.boundingRect.height : mainLabelSize.boundingRect.height * 2 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.bottomMargin: delegateDisplaySecondaryText ? 0 : Kirigami.Units.smallSpacing wrapMode: delegateDisplaySecondaryText ? Label.NoWrap : Label.Wrap maximumLineCount: 2 elide: Text.ElideRight } LabelWithToolTip { id: secondaryLabel opacity: 0.6 color: myPalette.text // FIXME: Center-aligned text looks better overall, but // sometimes results in font kerning issues // See https://bugreports.qt.io/browse/QTBUG-49646 horizontalAlignment: Text.AlignHCenter Layout.bottomMargin: Kirigami.Units.smallSpacing Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom visible: delegateDisplaySecondaryText elide: Text.ElideRight } Item { Layout.fillHeight: true } } } states: [ State { name: 'notSelected' when: !gridEntry.activeFocus && !hoverHandle.containsMouse && !gridEntry.isSelected PropertyChanges { target: stateIndicator color: 'transparent' } PropertyChanges { target: stateIndicator opacity: 1.0 } PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } }, State { name: 'hovered' when: hoverHandle.containsMouse && !gridEntry.activeFocus PropertyChanges { target: stateIndicator color: myPalette.highlight } PropertyChanges { target: stateIndicator opacity: 0.2 } PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } }, State { name: 'selected' when: gridEntry.isSelected && !gridEntry.activeFocus PropertyChanges { target: stateIndicator color: myPalette.mid } PropertyChanges { target: stateIndicator opacity: 0.6 } PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0. } }, State { name: 'hoveredOrSelected' when: gridEntry.activeFocus PropertyChanges { target: stateIndicator color: myPalette.highlight } PropertyChanges { target: stateIndicator opacity: 0.6 } PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } } ] } diff --git a/src/qml/GridBrowserView.qml b/src/qml/GridBrowserView.qml index b5c47fee..190397ac 100644 --- a/src/qml/GridBrowserView.qml +++ b/src/qml/GridBrowserView.qml @@ -1,169 +1,164 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: gridView property bool isSubPage: false property string mainTitle property string secondaryTitle property url image property alias contentModel: contentDirectoryView.model property alias showRating: navigationBar.showRating property bool delegateDisplaySecondaryText: true property alias expandedFilterView: navigationBar.expandedFilterView property var stackView property url defaultIcon signal enqueue(int databaseId, string name) signal replaceAndPlay(int databaseId, string name) signal open(string innerMainTitle, string innerSecondaryTitle, url innerImage, int databaseId, var dataType, var showDiscHeader) signal goBack() ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: gridView.mainTitle secondaryTitle: gridView.secondaryTitle image: gridView.image enableGoBack: isSubPage sortOrder: if (contentModel) {contentModel.sortedAscending} else true Layout.fillWidth: true Loader { active: contentModel !== undefined sourceComponent: Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } } Loader { active: contentModel sourceComponent: Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } } onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay:contentModel.replaceAndPlayOfPlayList() onGoBack: gridView.goBack() onSort: contentModel.sortModel(order) } FocusScope { Layout.fillHeight: true Layout.fillWidth: true clip: true GridView { id: contentDirectoryView property int availableWidth: width - scrollBar.width activeFocusOnTab: true keyNavigationEnabled: true anchors.fill: parent anchors.margins: Kirigami.Units.largeSpacing ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds currentIndex: -1 Accessible.role: Accessible.List Accessible.name: mainTitle - TextMetrics { - id: secondaryLabelSize - text: 'example' - } - ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } cellWidth: Math.floor(availableWidth / Math.max(Math.floor(availableWidth / elisaTheme.gridDelegateSize), 2)) - cellHeight: elisaTheme.gridDelegateSize + secondaryLabelSize.boundingRect.height * 2 + Kirigami.Units.largeSpacing + cellHeight: elisaTheme.gridDelegateSize + Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing delegate: GridBrowserDelegate { width: elisaTheme.gridDelegateSize height: contentDirectoryView.cellHeight focus: true isSelected: contentDirectoryView.currentIndex === index isPartial: false mainText: model.display fileUrl: model.url secondaryText: if (gridView.delegateDisplaySecondaryText) {model.secondaryText} else {""} imageUrl: model.imageUrl ? model.imageUrl : '' imageFallbackUrl: defaultIcon databaseId: model.databaseId delegateDisplaySecondaryText: gridView.delegateDisplaySecondaryText entryType: model.dataType onEnqueue: gridView.enqueue(databaseId, name) onReplaceAndPlay: gridView.replaceAndPlay(databaseId, name) onOpen: gridView.open(model.display, model.secondaryText, (model && model.imageUrl && model.imageUrl.toString() !== "" ? model.imageUrl : defaultIcon), model.databaseId, model.dataType, (model.isSingleDiscAlbum ? ViewManager.NoDiscHeaders : ViewManager.DiscHeaders)) onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index } onActiveFocusChanged: { if (activeFocus && contentDirectoryView.currentIndex !== model.index) { contentDirectoryView.currentIndex = model.index } } } } } } } diff --git a/src/qml/ListBrowserDelegate.qml b/src/qml/ListBrowserDelegate.qml index 712f7310..80af596c 100644 --- a/src/qml/ListBrowserDelegate.qml +++ b/src/qml/ListBrowserDelegate.qml @@ -1,422 +1,416 @@ /* * Copyright 2016-2017 Matthieu Gallien * Copyright 2017 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.3 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: mediaTrack property url trackUrl property var dataType property string title property string artist property string album property string albumArtist property string duration property url imageUrl property int trackNumber property int discNumber property int rating property bool hideDiscNumber property bool isSelected property bool isAlternateColor property bool detailedView: true signal clicked() signal enqueue(var url, var entryType, var name) signal replaceAndPlay(var url, var entryType, var name) signal callOpenMetaDataView(var url, var entryType) Accessible.role: Accessible.ListItem Accessible.name: title Accessible.description: title Keys.onReturnPressed: enqueue(trackUrl, dataType, title) Keys.onEnterPressed: enqueue(trackUrl, dataType, title) - TextMetrics { - id: mainLabelSize - font: mainLabel.font - text: mainLabel.text - } - - property int singleLineHeight: 3 * Kirigami.Units.smallSpacing + mainLabelSize.height - height: singleLineHeight + (detailedView ? mainLabelSize.height : 0) + property int singleLineHeight: 3 * Kirigami.Units.smallSpacing + Kirigami.Units.gridUnit + height: singleLineHeight + (detailedView ? Kirigami.Units.gridUnit : 0) Rectangle { id: rowRoot anchors.fill: parent z: 1 color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } MouseArea { id: hoverArea anchors.fill: parent z: 2 hoverEnabled: true acceptedButtons: Qt.LeftButton onClicked: { mediaTrack.clicked() } onDoubleClicked: enqueue(trackUrl, dataType, title) RowLayout { anchors.fill: parent spacing: 0 LabelWithToolTip { id: mainLabel visible: !detailedView text: { if (trackNumber !== 0 && trackNumber !== -1 && trackNumber !== undefined) { if (albumArtist !== undefined && artist !== albumArtist) return i18nc("%1: track number. %2: track title. %3: artist name", "%1 - %2 - %3", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title, artist); else return i18nc("%1: track number. %2: track title.", "%1 - %2", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); } else { if (albumArtist !== undefined && artist !== albumArtist) return i18nc("%1: track title. %2: artist name", "%1 - %2", title, artist); else return i18nc("%1: track title", "%1", title); } } elide: Text.ElideRight horizontalAlignment: Text.AlignLeft color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true Layout.leftMargin: { if (!LayoutMirroring.enabled) return (!hideDiscNumber ? Kirigami.Units.largeSpacing * 4 : Kirigami.Units.largeSpacing) else return 0 } Layout.rightMargin: { if (LayoutMirroring.enabled) return (!hideDiscNumber ? Kirigami.Units.largeSpacing * 4 : Kirigami.Units.largeSpacing) else return 0 } } ImageWithFallback { id: coverImageElement Layout.preferredHeight: mediaTrack.height - Kirigami.Units.smallSpacing Layout.preferredWidth: mediaTrack.height - Kirigami.Units.smallSpacing Layout.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 Layout.alignment: Qt.AlignCenter visible: detailedView sourceSize.width: mediaTrack.height - Kirigami.Units.smallSpacing sourceSize.height: mediaTrack.height - Kirigami.Units.smallSpacing fillMode: Image.PreserveAspectFit smooth: true source: imageUrl fallback: elisaTheme.defaultAlbumImage asynchronous: true layer.enabled: !usingFallback layer.effect: DropShadow { source: coverImageElement radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } ColumnLayout { visible: detailedView Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignLeft spacing: 0 LabelWithToolTip { id: mainLabelDetailed level: 4 text: { if (trackNumber >= 0) { return i18nc("%1: track number. %2: track title", "%1 - %2", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); } else { return title; } } horizontalAlignment: Text.AlignLeft color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.fillWidth: true Layout.topMargin: Kirigami.Units.smallSpacing / 2 elide: Text.ElideRight } Item { Layout.fillHeight: true } LabelWithToolTip { id: artistLabel text: { var labelText = "" if (artist) { labelText += artist } if (album !== '') { labelText += ' - ' + album if (!hideDiscNumber && discNumber !== -1) { labelText += ' - CD ' + discNumber } } return labelText; } horizontalAlignment: Text.AlignLeft opacity: 0.6 color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignBottom Layout.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.fillWidth: true Layout.bottomMargin: Kirigami.Units.smallSpacing / 2 elide: Text.ElideRight } } Loader { id: hoverLoader active: false Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: 10 z: 1 opacity: 0 sourceComponent: Row { anchors.centerIn: parent FlatButtonWithToolTip { id: detailsButton height: singleLineHeight width: singleLineHeight text: i18nc("Show track metadata", "View Details") icon.name: "help-about" onClicked: callOpenMetaDataView(trackUrl, dataType) } FlatButtonWithToolTip { id: enqueueButton height: singleLineHeight width: singleLineHeight text: i18nc("Enqueue current track", "Enqueue") icon.name: "list-add" onClicked: enqueue(trackUrl, dataType, title) } FlatButtonWithToolTip { id: clearAndEnqueueButton scale: LayoutMirroring.enabled ? -1 : 1 height: singleLineHeight width: singleLineHeight text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") icon.name: "media-playback-start" onClicked: replaceAndPlay(trackUrl, dataType, title) } } } RatingStar { id: ratingWidget starRating: rating Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.leftMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing } LabelWithToolTip { id: durationLabel text: duration font.weight: Font.Light color: myPalette.text horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 } } } states: [ State { name: 'notSelected' when: !mediaTrack.activeFocus && !hoverArea.containsMouse && !mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } PropertyChanges { target: rowRoot color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } PropertyChanges { target: rowRoot opacity: 1 } }, State { name: 'hovered' when: !mediaTrack.activeFocus && hoverArea.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.highlight } PropertyChanges { target: rowRoot opacity: 0.2 } }, State { name: 'selected' when: !mediaTrack.activeFocus && mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.mid } PropertyChanges { target: rowRoot opacity: 1. } }, State { name: 'focused' when: mediaTrack.activeFocus PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.highlight } PropertyChanges { target: rowRoot opacity: 0.6 } } ] } diff --git a/src/qml/MediaPlayerControl.qml b/src/qml/MediaPlayerControl.qml index fea4582d..257889d2 100644 --- a/src/qml/MediaPlayerControl.qml +++ b/src/qml/MediaPlayerControl.qml @@ -1,404 +1,404 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.3 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { property alias volume: volumeSlider.value property int position property int duration property bool muted property bool isPlaying property bool seekable property bool playEnabled property bool skipForwardEnabled property bool skipBackwardEnabled property bool isMaximized property bool shuffle property bool repeat signal play() signal pause() signal playPrevious() signal playNext() signal seek(int position) signal openMenu() signal maximize() signal minimize() id: musicWidget SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Rectangle { anchors.fill: parent color: myPalette.midlight opacity: elisaTheme.mediaPlayerControlOpacity } RowLayout { anchors.fill: parent spacing: 0 FlatButtonWithToolTip { id: minimizeMaximizeButton text: i18nc("toggle between maximized and minimized ivre", "Toggle Maximize") icon.name: musicWidget.isMaximized ? "draw-arrow-up" : "draw-arrow-down" onClicked: musicWidget.isMaximized = !musicWidget.isMaximized } FlatButtonWithToolTip { id: skipBackwardButton enabled: skipBackwardEnabled text: i18nc("skip backward in playlists", "Skip Backward") icon.name: musicWidget.LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" onClicked: musicWidget.playPrevious() } FlatButtonWithToolTip { id: playPauseButton enabled: playEnabled text: i18nc("toggle play and pause for the audio player", "Toggle Play and Pause") icon.name: musicWidget.isPlaying? "media-playback-pause" : "media-playback-start" onClicked: musicWidget.isPlaying ? musicWidget.pause() : musicWidget.play() } FlatButtonWithToolTip { id: skipForwardButton enabled: skipForwardEnabled text: i18nc("skip forward in playlists", "Skip Forward") icon.name: musicWidget.LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" onClicked: musicWidget.playNext() } TextMetrics { id: durationTextMetrics text: i18nc("This is used to preserve a fixed width for the duration text.", "00:00:00") } LabelWithToolTip { id: positionLabel text: timeIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2 Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2 - Layout.preferredWidth: (durationTextMetrics.boundingRect.width - durationTextMetrics.boundingRect.x) + 5 + Layout.preferredWidth: (durationTextMetrics.boundingRect.width - durationTextMetrics.boundingRect.x) + Kirigami.Units.smallSpacing verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight ProgressIndicator { id: timeIndicator position: musicWidget.position } } MouseArea { id: seekWheelHandler Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true Layout.fillWidth: true Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 acceptedButtons: Qt.NoButton onWheel: { if (wheel.angleDelta.y > 0) { musicWidget.seek(position + 10000) } else { musicWidget.seek(position - 10000) } } // Synthesized slider background that's not actually a part of the // slider. This is done so the slider's own background can be full // height yet transparent, for easier clicking Rectangle { x: musicProgress.leftPadding y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: seekWheelHandler.width implicitHeight: 6 color: myPalette.dark radius: 3 } Slider { property bool seekStarted: false property int seekValue id: musicProgress from: 0 to: musicWidget.duration width: parent.width anchors.centerIn: parent enabled: musicWidget.seekable && musicWidget.playEnabled live: true onValueChanged: { if (seekStarted) { seekValue = value } } onPressedChanged: { if (pressed) { seekStarted = true; seekValue = value } else { musicWidget.seek(seekValue) seekStarted = false; } } // This only provides a full-height area for clicking; see // https://bugs.kde.org/show_bug.cgi?id=408703. The actual visual // background is generated above ^^ background: Rectangle { x: musicProgress.leftPadding y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 anchors.fill: parent implicitWidth: seekWheelHandler.width implicitHeight: seekWheelHandler.height color: "transparent" Rectangle { anchors.verticalCenter: parent.verticalCenter x: (LayoutMirroring.enabled ? musicProgress.visualPosition * parent.width : 0) width: LayoutMirroring.enabled ? parent.width - musicProgress.visualPosition * parent.width: musicProgress.handle.x + radius height: 6 color: myPalette.text radius: 3 } } handle: Rectangle { x: musicProgress.leftPadding + musicProgress.visualPosition * (musicProgress.availableWidth - width) y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 color: myPalette.base border.width: 1 border.color: musicProgress.pressed ? myPalette.text : myPalette.dark } } } LabelWithToolTip { id: durationLabel text: durationIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true Layout.rightMargin: !LayoutMirroring.enabled ? (Kirigami.Units.largeSpacing* 2) : 0 Layout.leftMargin: LayoutMirroring.enabled ? (Kirigami.Units.largeSpacing* 2) : 0 Layout.preferredWidth: (durationTextMetrics.boundingRect.width - durationTextMetrics.boundingRect.x) verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft ProgressIndicator { id: durationIndicator position: musicWidget.duration } } FlatButtonWithToolTip { id: muteButton text: i18nc("toggle mute mode for player", "Toggle Mute") icon.name: musicWidget.muted ? "player-volume-muted" : "player-volume" onClicked: musicWidget.muted = !musicWidget.muted } MouseArea { id: audioWheelHandler Layout.preferredWidth: elisaTheme.volumeSliderWidth Layout.maximumWidth: elisaTheme.volumeSliderWidth Layout.minimumWidth: elisaTheme.volumeSliderWidth Layout.fillHeight: true acceptedButtons: Qt.NoButton onWheel: { if (wheel.angleDelta.y > 0) { volumeSlider.increase() } else { volumeSlider.decrease() } } // Synthesized slider background that's not actually a part of the // slider. This is done so the slider's own background can be full // height yet transparent, for easier clicking Rectangle { x: volumeSlider.leftPadding y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: audioWheelHandler.width implicitHeight: 6 radius: 3 color: myPalette.dark opacity: muted ? 0.5 : 1 } Slider { id: volumeSlider from: 0 to: 100 stepSize: 5 enabled: !muted anchors.centerIn: parent width: elisaTheme.volumeSliderWidth // This only provides a full-height area for clicking; see // https://bugs.kde.org/show_bug.cgi?id=408703. The actual visual // background is generated above ^^ background: Rectangle { anchors.fill: parent implicitWidth: audioWheelHandler.width implicitHeight: audioWheelHandler.height color: "transparent" Rectangle { anchors.verticalCenter: parent.verticalCenter x: (LayoutMirroring.enabled ? volumeSlider.visualPosition * parent.width : 0) width: (LayoutMirroring.enabled ? parent.width - volumeSlider.visualPosition * parent.width : volumeSlider.visualPosition * parent.width) height: 6 color: myPalette.text radius: 3 opacity: muted ? 0.5 : 1 } } handle: Rectangle { x: volumeSlider.leftPadding + volumeSlider.visualPosition * (volumeSlider.availableWidth - width) y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 color: myPalette.base border.width: 1 border.color: volumeSlider.pressed ? myPalette.text : (muted ? myPalette.mid : myPalette.dark) } } } FlatButtonWithToolTip { id: shuffleButton text: i18nc("toggle shuffle mode for playlist", "Toggle Shuffle") icon.name: musicWidget.shuffle ? "media-playlist-shuffle" : "media-playlist-normal" onClicked: musicWidget.shuffle = !musicWidget.shuffle } FlatButtonWithToolTip { id: repeatButton text: i18nc("toggle repeat mode for playlist", "Toggle Repeat") icon.name: musicWidget.repeat ? "media-repeat-all" : "media-repeat-none" onClicked: musicWidget.repeat = !musicWidget.repeat } // Not a FlatButtonWithToolTip because we want text Button { id: showHidePlaylistAction action: Action { shortcut: elisa.action("toggle_playlist").shortcut onTriggered: contentView.showPlaylist = !contentView.showPlaylist } visible: mainWindow.width >= elisaTheme.viewSelectorSmallSizeThreshold flat: true text: i18n("Show Playlist") icon.name: "view-media-playlist" checkable: true checked: contentView.showPlaylist activeFocusOnTab: true Keys.onReturnPressed: action.trigger() Accessible.onPressAction: action.trigger() } FlatButtonWithToolTip { id: menuButton text: i18nc("open application menu", "Application Menu") icon.name: "application-menu" onClicked: openMenu() } } onPositionChanged: { if (!musicProgress.seekStarted) { musicProgress.value = position } } onIsMaximizedChanged: { if (musicWidget.isMaximized) { musicWidget.maximize() } else { musicWidget.minimize() } } Component.onCompleted: { var elementList = [menuButton, repeatButton, shuffleButton, muteButton, skipForwardButton, skipBackwardButton, playPauseButton, minimizeMaximizeButton] for (var i=0; i * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 RowLayout { id: delegateRow spacing: 0 - height: (model.type === EditableTrackMetadataModel.LongTextEntry ? longTextDisplayLoader.height : (metaDataLabelMetric.boundingRect.height + Kirigami.Units.smallSpacing / 2)) - - TextMetrics { - id: metaDataLabelMetric - - text: 'Metadata Name' - } + height: (model.type === EditableTrackMetadataModel.LongTextEntry ? longTextDisplayLoader.height : (Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing / 2)) Label { id: metaDataLabels text: i18nc("Label for a piece of metadata, e.g. 'Album Artist:'", "%1:", model.name) font.weight: Font.Bold horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignTop Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 } Loader { id: textDisplayLoader active: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.IntegerEntry visible: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.IntegerEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: LabelWithToolTip { text: model.display horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent } } Loader { id: longTextDisplayLoader active: model.type === EditableTrackMetadataModel.LongTextEntry visible: model.type === EditableTrackMetadataModel.LongTextEntry Layout.fillWidth: true Layout.maximumWidth: delegateRow.width - (0.8 * elisaTheme.coverImageSize + Kirigami.Units.largeSpacing * 2) Layout.alignment: Qt.AlignTop sourceComponent: Label { text: model.display horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent wrapMode: Text.WordWrap } } Loader { active: model.type === EditableTrackMetadataModel.DateEntry visible: model.type === EditableTrackMetadataModel.DateEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: LabelWithToolTip { text: rawDate.toLocaleDateString() horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent property date rawDate: new Date(model.display) } } Loader { active: model.type === EditableTrackMetadataModel.RatingEntry visible: model.type === EditableTrackMetadataModel.RatingEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: RatingStar { starRating: model.display readOnly: true anchors { left: parent.left top: parent.top bottom: parent.bottom } } } } diff --git a/src/qml/PlayListEntry.qml b/src/qml/PlayListEntry.qml index bb83f7c4..00d9ea12 100644 --- a/src/qml/PlayListEntry.qml +++ b/src/qml/PlayListEntry.qml @@ -1,393 +1,387 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.3 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: playListEntry property var index property bool isSingleDiscAlbum property int isPlaying property bool isSelected property bool isValid property bool isAlternateColor property bool containsMouse property int databaseId: 0 property var entryType property string title property string artist property string album property string albumArtist property string duration property url fileName property url imageUrl property int trackNumber property int discNumber property int rating property bool hasValidDiscNumber: true property int scrollBarWidth property bool simpleMode: false signal startPlayback() signal pausePlayback() signal removeFromPlaylist(var trackIndex) signal switchToTrack(var trackIndex) Accessible.role: Accessible.ListItem Accessible.name: title + ' ' + album + ' ' + artist - TextMetrics { - id: mainCompactLabelSize - font: mainCompactLabel.font - text: mainCompactLabel.text - } - Keys.onReturnPressed: { playListEntry.switchToTrack(playListEntry.index) playListEntry.startPlayback() } - height: mainCompactLabelSize.height + 3 * Kirigami.Units.smallSpacing + height: Kirigami.Units.gridUnit + 3 * Kirigami.Units.smallSpacing Loader { id: metadataLoader active: false onLoaded: item.show() sourceComponent: MediaTrackMetadataView { fileName: playListEntry.fileName showImage: entryType !== ElisaUtils.Radio modelType: entryType showTrackFileName: entryType !== ElisaUtils.Radio showDeleteButton: entryType === ElisaUtils.Radio showApplyButton: entryType === ElisaUtils.Radio editableMetadata: entryType === ElisaUtils.Radio onRejected: metadataLoader.active = false } } Rectangle { id: entryBackground anchors.fill: parent anchors.rightMargin: LayoutMirroring.enabled ? scrollBarWidth : 0 z: 1 color: simpleMode ? "transparent" : myPalette.base height: playListEntry.height } RowLayout { id: trackRow z: 2 anchors.fill: parent anchors.leftMargin: Kirigami.Units.largeSpacing anchors.rightMargin: LayoutMirroring.enabled ? scrollBarWidth : 0 spacing: Kirigami.Units.smallSpacing / 2 // Container for the play/pause icon and the track/disc label Item { TextMetrics { id: fakeLabel text: '99/9' } Layout.minimumWidth: fakeLabel.width Layout.preferredWidth: fakeLabel.width Layout.maximumWidth: fakeLabel.width Layout.preferredHeight: playListEntry.height Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 Image { id: playIcon anchors.centerIn: parent source: (isPlaying === MediaPlayList.IsPlaying ? Qt.resolvedUrl(elisaTheme.playingIndicatorIcon) : Qt.resolvedUrl(elisaTheme.pausedIndicatorIcon)) width: Kirigami.Units.iconSizes.smallMedium height: Kirigami.Units.iconSizes.smallMedium sourceSize.width: Kirigami.Units.iconSizes.smallMedium sourceSize.height: Kirigami.Units.iconSizes.smallMedium fillMode: Image.PreserveAspectFit mirror: LayoutMirroring.enabled layer.enabled: simpleMode layer.effect: ColorOverlay { cached: true color: myPalette.highlightedText } visible: isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused } Label { id: trackAndDiscNumberLabel anchors.fill: parent anchors.rightMargin: LayoutMirroring.enabled ? 0 : Kirigami.Units.largeSpacing anchors.leftMargin: !LayoutMirroring.enabled ? 0 : Kirigami.Units.largeSpacing horizontalAlignment: Text.AlignRight text: { var trackNumberString; if (trackNumber !== -1) { trackNumberString = Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0); } else { trackNumberString = '' } if (!isSingleDiscAlbum && discNumber !== 0 ) { return trackNumberString + "/" + Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) } else { return trackNumberString } } font.weight: (isPlaying ? Font.Bold : Font.Light) color: simpleMode ? myPalette.highlightedText : myPalette.text visible: isValid && !playIcon.visible } } LabelWithToolTip { id: mainCompactLabel text: title font.weight: (isPlaying ? Font.Bold : Font.Normal) color: simpleMode ? myPalette.highlightedText : myPalette.text Layout.maximumWidth: mainCompactLabel.implicitWidth + 1 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: isValid elide: Text.ElideRight horizontalAlignment: Text.AlignLeft } LabelWithToolTip { id: mainInvalidCompactLabel text: title color: simpleMode ? myPalette.highlightedText : myPalette.text Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: !isValid elide: Text.ElideRight } Item { Layout.fillWidth: true Layout.preferredWidth: 0 } Loader { id: hoverLoader active: false visible: active Layout.alignment: Qt.AlignVCenter | Qt.AlignRight sourceComponent: Row { anchors.centerIn: parent enabled: isValid FlatButtonWithToolTip { id: infoButton objectName: 'infoButton' implicitHeight: playListEntry.height implicitWidth: playListEntry.height text: i18nc("Show track metadata", "View Details") icon.name: "help-about" onClicked: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } } FlatButtonWithToolTip { id: playPauseButton objectName: 'playPauseButton' implicitHeight: playListEntry.height implicitWidth: playListEntry.height scale: LayoutMirroring.enabled ? -1 : 1 // We can mirror the symmetrical pause icon text: (isPlaying === MediaPlayList.IsPlaying) ? i18nc("Pause current track from play list", "Pause") : i18nc("Play this track from play list", "Play") icon.name: (isPlaying === MediaPlayList.IsPlaying) ? "media-playback-pause" : "media-playback-start" onClicked: if (isPlaying === MediaPlayList.IsPlaying) { playListEntry.pausePlayback() } else { playListEntry.switchToTrack(playListEntry.index) playListEntry.startPlayback() } } FlatButtonWithToolTip { id: removeButton objectName: 'removeButton' implicitHeight: playListEntry.height implicitWidth: playListEntry.height text: i18nc("Remove current track from play list", "Remove") icon.name: "error" onClicked: playListEntry.removeFromPlaylist(playListEntry.index) } } } RatingStar { id: ratingWidget starRating: rating visible: rating > 0 } LabelWithToolTip { id: durationLabel text: duration font.weight: (isPlaying ? Font.Bold : Font.Normal) color: simpleMode ? myPalette.highlightedText : myPalette.text Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.leftMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing horizontalAlignment: Text.AlignRight } } states: [ State { name: 'notSelected' when: !containsMouse && !isSelected && !playListEntry.activeFocus && !simpleMode PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: entryBackground color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } PropertyChanges { target: entryBackground opacity: 1. } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } }, State { name: 'hovered' when: containsMouse && !playListEntry.activeFocus && !simpleMode PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: entryBackground color: myPalette.highlight } PropertyChanges { target: entryBackground opacity: 0.2 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } }, State { name: 'selected' when: !playListEntry.activeFocus && isSelected && !simpleMode PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: entryBackground color: myPalette.mid } PropertyChanges { target: entryBackground opacity: 1. } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } }, State { name: 'focused' when: playListEntry.activeFocus && !simpleMode PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: entryBackground color: myPalette.highlight } PropertyChanges { target: entryBackground opacity: 0.6 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } } ] } diff --git a/src/qml/TracksDiscHeader.qml b/src/qml/TracksDiscHeader.qml index 895a9c5c..1267c650 100644 --- a/src/qml/TracksDiscHeader.qml +++ b/src/qml/TracksDiscHeader.qml @@ -1,50 +1,44 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.5 as Kirigami Rectangle { property int discNumber color: myPalette.mid - height: discHeaderSize.height + Kirigami.Units.largeSpacing - - TextMetrics { - id: discHeaderSize - font: discHeaderLabel.font - text: discHeaderLabel.text - } + height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing LabelWithToolTip { id: discHeaderLabel text: i18nc("disc header text when showing an album", "Disc %1", discNumber) font.weight: Font.Bold font.italic: true color: myPalette.text verticalAlignment: Text.AlignVCenter anchors.fill: parent anchors.leftMargin: Kirigami.Units.largeSpacing elide: Text.ElideRight } }