diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,6 +66,7 @@ MediaAllTracksView.qml MediaTrackDelegate.qml MediaAlbumTrackDelegate.qml + MediaTrackMetadataPopup.qml ViewSelector.qml ) diff --git a/src/MediaAlbumView.qml b/src/MediaAlbumView.qml --- a/src/MediaAlbumView.qml +++ b/src/MediaAlbumView.qml @@ -153,49 +153,54 @@ focus: true + mediaTrack.flickableObject: contentDirectoryView + mediaTrack.isAlternateColor: (index % 2) === 1 mediaTrack.title: if (model != undefined && model.title !== undefined) - model.title - else - '' + model.title + else + '' mediaTrack.artist: if (model != undefined && model.artist !== undefined) - model.artist - else - '' + model.artist + else + '' mediaTrack.albumArtist: if (model != undefined && model.albumArtist !== undefined) - model.albumArtist - else - '' + model.albumArtist + else + '' mediaTrack.duration: if (model != undefined && model.duration !== undefined) - model.duration - else - '' + model.duration + else + '' mediaTrack.trackNumber: if (model != undefined && model.trackNumber !== undefined) - model.trackNumber - else - '' + model.trackNumber + else + '' mediaTrack.discNumber: if (model != undefined && model.discNumber !== undefined) - model.discNumber - else - '' + model.discNumber + else + '' mediaTrack.rating: if (model != undefined && model.rating !== undefined) - model.rating - else - 0 + model.rating + else + 0 mediaTrack.trackData: if (model != undefined && model.trackData !== undefined) - model.trackData - else - '' + model.trackData + else + '' mediaTrack.isFirstTrackOfDisc: if (model != undefined && model.isFirstTrackOfDisc !== undefined) - model.isFirstTrackOfDisc - else - false + model.isFirstTrackOfDisc + else + false mediaTrack.isSingleDiscAlbum: if (model != undefined && model.isSingleDiscAlbum !== undefined) - model.isSingleDiscAlbum - else - true - + model.isSingleDiscAlbum + else + true + mediaTrack.trackResource: if (model != undefined && model.trackResource !== undefined) + model.trackResource + else + '' mediaTrack.onClearPlaylist: topListing.playListModel.clearPlayList() diff --git a/src/MediaAllTracksView.qml b/src/MediaAllTracksView.qml --- a/src/MediaAllTracksView.qml +++ b/src/MediaAllTracksView.qml @@ -96,6 +96,8 @@ focus: true + flickableObject: contentDirectoryView + isAlternateColor: (index % 2) === 1 title: if (model != undefined && model.title !== undefined) @@ -107,13 +109,13 @@ else '' albumName: if (model != undefined && model.album !== undefined) - model.album + model.album + else + '' + albumArtist: if (model != undefined && model.albumArtist !== undefined) + model.albumArtist else '' - albumArtist: if (model != undefined && model.albumArtist !== undefined) - model.albumArtist - else - '' duration: if (model != undefined && model.duration !== undefined) model.duration else @@ -123,13 +125,13 @@ else '' discNumber: if (model != undefined && model.discNumber !== undefined) - model.discNumber - else - '' + model.discNumber + else + '' rating: if (model != undefined && model.rating !== undefined) - model.rating - else - 0 + model.rating + else + 0 trackData: if (model != undefined && model.trackData !== undefined) model.trackData else diff --git a/src/MediaTrackDelegate.qml b/src/MediaTrackDelegate.qml --- a/src/MediaTrackDelegate.qml +++ b/src/MediaTrackDelegate.qml @@ -43,6 +43,7 @@ property var trackData property bool isAlternateColor property bool detailedView: true + property var flickableObject signal clicked() signal clearPlaylist() @@ -67,6 +68,44 @@ onTriggered: enqueueToPlaylist(trackData) } + Action { + id: showInfo + text: i18nc("Show track information", "Information") + iconName: "help-about" + onTriggered: { + if (metadataLoader.active === false) { + metadataLoader.active = true; + } + else { + metadataLoader.item.close(); + } + } + } + + Loader { + id: metadataLoader + active: false + onLoaded: item.open() + + sourceComponent: MediaTrackMetadataPopup { + parent: mediaTrack + + title: mediaTrack.title + artist: mediaTrack.artist + albumArtist: mediaTrack.albumArtist + albumName: mediaTrack.albumName + duration: mediaTrack.duration + resource: mediaTrack.trackResource + rating: mediaTrack.rating + trackNumber: mediaTrack.trackNumber + discNumber: mediaTrack.discNumber + + flickableObject: mediaTrack.flickableObject + onClosed: metadataLoader.active = false; + } + + } + Rectangle { id: rowRoot @@ -214,6 +253,18 @@ } } + ToolButton { + id: infoButton + + opacity: 0 + visible: opacity > 0.1 + action: showInfo + + Layout.preferredHeight: elisaTheme.delegateHeight + Layout.preferredWidth: elisaTheme.delegateHeight + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + ToolButton { id: enqueueButton @@ -273,6 +324,10 @@ State { name: 'notSelected' when: !hoverArea.containsMouse && !mediaTrack.activeFocus + PropertyChanges { + target: infoButton + opacity: 0 + } PropertyChanges { target: clearAndEnqueueButton opacity: 0 @@ -289,6 +344,10 @@ State { name: 'hoveredOrSelected' when: hoverArea.containsMouse || mediaTrack.activeFocus + PropertyChanges { + target: infoButton + opacity: 1 + } PropertyChanges { target: clearAndEnqueueButton opacity: 1 diff --git a/src/MediaTrackMetadataPopup.qml b/src/MediaTrackMetadataPopup.qml new file mode 100644 --- /dev/null +++ b/src/MediaTrackMetadataPopup.qml @@ -0,0 +1,230 @@ +/* + * Copyright 2017 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQml.Models 2.2 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 + +Popup { + id: trackMetadata + + property string title + property string artist + property string albumArtist + property string albumName + property string composer + property string duration + property string genre + property string resource + property int year + property int rating: -1 + property int trackNumber + property int discNumber + property bool isSingleDiscAlbum + + property var flickableObject + + padding: elisaTheme.layoutVerticalMargin + width: elisaTheme.trackPopupWidth + x: (parent.width - trackMetadata.width) / 2 + + onAboutToShow: { + if ( mapToItem(flickableObject, 0, parent.height + elisaTheme.layoutVerticalMargin + trackMetadata.height).y < flickableObject.height ) { + y = parent.height + elisaTheme.layoutVerticalMargin + } else { + y = - (trackMetadata.height + elisaTheme.layoutVerticalMargin) + } + } + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside | Popup.CloseOnReleaseOutside + + background: Rectangle { + color: myPalette.shadow + radius: elisaTheme.tooltipRadius + + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: elisaTheme.shadowOffset + verticalOffset: elisaTheme.shadowOffset + radius: 8 + samples: 17 + color: myPalette.shadow + } + } + + enter: Transition { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + from: 0 + to: 1 + duration: 250 + } + } + + exit: Transition { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + from: 1 + to: 0 + duration: 250 + } + } + + Component.onCompleted: { + if (title.length !== 0) + trackList.append({"name": i18nc("Track title for track information panel", "Title"), "content": title}) + if (artist.length !== 0) + trackList.append({"name": i18nc("Track artist for track information panel", "Artist"), "content": artist}) + if (albumName.length !== 0) + trackList.append({"name": i18nc("Album name for track information panel", "Album"), "content": albumName}) + if (composer.length !== 0) + trackList.append({"name": i18nc("Composer name for track information panel", "Composer"), "content": composer}) + if (trackNumber !== 0) + trackList.append({"name": i18nc("Track number for track information panel", "Track Number"), "content": trackNumber + ""}) + if (discNumber !== 0) + trackList.append({"name": i18nc("Disc number for track information panel", "Disc Number"), "content": discNumber + ""}) + if (albumArtist.length !== 0) + trackList.append({"name": i18nc("Album artist for itrack nformation panel", "Album Artist"), "content": albumArtist}) + if (duration.length !== 0) + trackList.append({"name": i18nc("Duration label for track information panel", "Duration"), "content": duration}) + if (year !== 0) + trackList.append({"name": i18nc("Year label for track information panel", "Year"), "content": year}) + if (genre.length !== 0) + trackList.append({"name": i18nc("Genre label for track information panel", "Genre"), "content": genre}) + trackData.Layout.preferredHeight = textSize.height * trackData.count + trackMetadata.height = textSize.height * (trackData.count + 1 + (rating > -1 ? 1 : 0)) + 4 * elisaTheme.layoutVerticalMargin + } + + Connections { + target: flickableObject + + onContentYChanged: + { + trackMetadata.close() + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 0 + + spacing: 0 + + ListView { + id: trackData + + interactive: false + Layout.fillWidth: true + + model: ListModel { + id: trackList + } + + delegate: RowLayout { + id: delegateRow + + spacing: 0 + + Text { + text: name + ": " + color: myPalette.highlightedText + horizontalAlignment: Text.AlignRight + + Layout.preferredWidth: trackData.width * 0.33 + Layout.minimumHeight: textSize.height + } + + Text { + text: content + color: myPalette.highlightedText + + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + + Layout.preferredWidth: trackData.width * 0.67 + Layout.minimumHeight: textSize.height + } + } + } + + RowLayout { + anchors.margins: 0 + spacing: 0 + + visible: rating > -1 + + Layout.minimumHeight: textSize.height + + Text { + id: ratingText + text: i18nc("Rating label for information panel", "Rating") + ": " + color: myPalette.highlightedText + + horizontalAlignment: Text.AlignRight + Layout.preferredWidth: trackData.width * 0.33 + } + + TextMetrics { + id: textSize + font: ratingText.font + text: ratingText.text + } + + RatingStar { + id: ratingWidget + starRating: rating + readOnly: true + + starSize: elisaTheme.ratingStarSize + + Layout.fillWidth: true + } + + ColorOverlay { + source: ratingWidget + + z: 2 + anchors.fill: ratingWidget + + color: myPalette.highlightedText + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + Text { + id: trackResource + text: trackMetadata.resource + color: myPalette.highlightedText + font.italic: true + + elide: Text.ElideLeft + + Layout.fillWidth: true + Layout.minimumHeight: textSize.height + } + } +} diff --git a/src/Theme.qml b/src/Theme.qml --- a/src/Theme.qml +++ b/src/Theme.qml @@ -44,6 +44,8 @@ property int coverImageSize: 180 property int smallImageSize: 32 + property int trackPopupWidth: 360 + property int tooltipRadius: 3 property int shadowOffset: 2 diff --git a/src/upnpControl.qrc b/src/upnpControl.qrc --- a/src/upnpControl.qrc +++ b/src/upnpControl.qrc @@ -43,6 +43,7 @@ FilterBar.qml MediaTrackDelegate.qml MediaAlbumTrackDelegate.qml + MediaTrackMetadataPopup.qml windows/WindowsTheme.qml diff --git a/src/windows/WindowsTheme.qml b/src/windows/WindowsTheme.qml --- a/src/windows/WindowsTheme.qml +++ b/src/windows/WindowsTheme.qml @@ -44,6 +44,8 @@ property int coverImageSize: 180 property int smallImageSize: 32 + property int trackPopupWidth: 360 + property int tooltipRadius: 3 property int shadowOffset: 2