diff --git a/src/models/viewsmodel.cpp b/src/models/viewsmodel.cpp index d0ac8f50..af15b4ab 100644 --- a/src/models/viewsmodel.cpp +++ b/src/models/viewsmodel.cpp @@ -1,209 +1,209 @@ /* * Copyright 2018 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 . */ #include "viewsmodel.h" #include "viewmanager.h" #include #include class ViewsModelPrivate { public: QList mTypes; QHash mNames; QHash mIcons; ViewsModelPrivate() { mTypes = {ViewManager::Context, ViewManager::RecentlyPlayedTracks, ViewManager::FrequentlyPlayedTracks, ViewManager::AllAlbums, ViewManager::AllArtists, ViewManager::AllTracks, ViewManager::AllGenres, ViewManager::FilesBrowser}; mNames = {{ViewManager::Context, {i18nc("Title of the view of the playlist", "Now Playing")}}, {ViewManager::RecentlyPlayedTracks, {i18nc("Title of the view of recently played tracks", "Recently Played")}}, {ViewManager::FrequentlyPlayedTracks, {i18nc("Title of the view of frequently played tracks", "Frequently Played")}}, {ViewManager::AllAlbums, {i18nc("Title of the view of all albums", "Albums")}}, {ViewManager::AllArtists, {i18nc("Title of the view of all artists", "Artists")}}, {ViewManager::AllTracks, {i18nc("Title of the view of all tracks", "Tracks")}}, {ViewManager::AllGenres, {i18nc("Title of the view of all genres", "Genres")}}, {ViewManager::FilesBrowser, {i18nc("Title of the file browser view", "Files")}}}; mIcons = {{ViewManager::Context, QUrl{QStringLiteral("image://icon/view-media-lyrics")}}, {ViewManager::RecentlyPlayedTracks, QUrl{QStringLiteral("image://icon/media-playlist-play")}}, - {ViewManager::FrequentlyPlayedTracks, QUrl{QStringLiteral("image://icon/amarok_playcount")}}, + {ViewManager::FrequentlyPlayedTracks, QUrl{QStringLiteral("image://icon/view-media-playcount")}}, {ViewManager::AllAlbums, QUrl{QStringLiteral("image://icon/view-media-album-cover")}}, {ViewManager::AllArtists, QUrl{QStringLiteral("image://icon/view-media-artist")}}, {ViewManager::AllTracks, QUrl{QStringLiteral("image://icon/view-media-track")}}, {ViewManager::AllGenres, QUrl{QStringLiteral("image://icon/view-media-genre")}}, {ViewManager::FilesBrowser, QUrl{QStringLiteral("image://icon/document-open-folder")}}}; } }; ViewsModel::ViewsModel(QObject *parent) : QAbstractListModel(parent), d(std::make_unique()) { } ViewsModel::~ViewsModel() = default; QHash ViewsModel::roleNames() const { auto result = QAbstractListModel::roleNames(); result[ItemType] = "type"; result[ImageName] = "image"; return result; } Qt::ItemFlags ViewsModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } int ViewsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->mTypes.count(); } QVariant ViewsModel::data(const QModelIndex &index, int role) const { auto result = QVariant{}; if (!index.isValid()) { return result; } switch(role) { case Qt::DisplayRole: result = d->mNames[d->mTypes[index.row()]]; break; case ColumnRoles::ImageName: result = d->mIcons[d->mTypes[index.row()]]; break; case ColumnRoles::ItemType: result = d->mTypes[index.row()]; break; } return result; } QModelIndex ViewsModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } result = createIndex(row, column); return result; } QModelIndex ViewsModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } int ViewsModel::indexFromViewType(ViewManager::ViewsType type) { switch(type) { case ViewManager::Context: return 0; case ViewManager::RecentlyPlayedTracks: return 1; case ViewManager::FrequentlyPlayedTracks: return 2; case ViewManager::AllAlbums: return 3; case ViewManager::AllArtists: return 4; case ViewManager::AllTracks: return 5; case ViewManager::AllGenres: return 6; case ViewManager::FilesBrowser: return 7; case ViewManager::OneAlbum: case ViewManager::OneArtist: case ViewManager::OneAlbumFromArtist: case ViewManager::OneArtistFromGenre: case ViewManager::OneAlbumFromArtistAndGenre: case ViewManager::AllArtistsFromGenre: break; } return -1; } QString ViewsModel::viewMainTitle(ViewManager::ViewsType type, QString suggestedMainTitle) const { auto result = std::move(suggestedMainTitle); if (!result.isEmpty()) { return result; } result = d->mNames[type]; return result; } QUrl ViewsModel::viewImageUrl(ViewManager::ViewsType type, QUrl suggestedImageUrl) const { auto result = std::move(suggestedImageUrl); if (!result.isEmpty()) { return result; } result = d->mIcons[type]; return result; } #include "moc_viewsmodel.cpp" diff --git a/src/qml/ApplicationMenu.qml b/src/qml/ApplicationMenu.qml index 7d3a3219..35a6819a 100644 --- a/src/qml/ApplicationMenu.qml +++ b/src/qml/ApplicationMenu.qml @@ -1,112 +1,112 @@ /* * 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 //explore menu from Qt 5.10 once we can require it, but it is item-based import QtQuick.Controls 1.4 import org.kde.elisa 1.0 Menu { id: applicationMenu title: i18nc("open application menu", "Application Menu") property var helpAction: elisa.action("help_contents") property var quitApplication: elisa.action("file_quit") property var reportBugAction: elisa.action("help_report_bug") property var aboutAppAction: elisa.action("help_about_app") property var configureShortcutsAction: elisa.action("options_configure_keybinding") property var configureAction: elisa.action("options_configure") property var togglePlaylistAction: elisa.action("toggle_playlist") MenuItem { text: i18nc("Refresh Music Collection application menu entry", "Refresh Music Collection") - iconName: "collection-rescan-amarok" + iconName: "view-refresh" onTriggered: elisa.musicManager.resetMusicData() } MenuSeparator { } MenuItem { text: configureAction.text shortcut: configureAction.shortcut iconName: elisa.iconName(configureAction.icon) onTriggered: configureAction.trigger() visible: configureAction.text !== "" } MenuItem { text: configureShortcutsAction.text shortcut: configureShortcutsAction.shortcut iconName: elisa.iconName(configureShortcutsAction.icon) onTriggered: configureShortcutsAction.trigger() visible: configureShortcutsAction.text !== "" } MenuItem { shortcut: togglePlaylistAction.shortcut text: contentView.showPlaylist ? i18nc("Hide playlist", "Hide Playlist") : i18nc("Show playlist", "Show Playlist") iconName: "view-media-playlist" onTriggered: contentView.showPlaylist = !contentView.showPlaylist visible: togglePlaylistAction.text !== "" } MenuSeparator { visible: reportBugAction.text !== "" } MenuItem { text: reportBugAction.text shortcut: reportBugAction.shortcut iconName: elisa.iconName(reportBugAction.icon) onTriggered: reportBugAction.trigger() visible: reportBugAction.text !== "" } MenuSeparator { visible: helpAction.text !== "" } MenuItem { text: helpAction.text shortcut: helpAction.shortcut iconName: elisa.iconName(helpAction.icon) onTriggered: helpAction.trigger() visible: helpAction.text !== "" } MenuItem { text: aboutAppAction.text shortcut: aboutAppAction.shortcut iconName: elisa.iconName(aboutAppAction.icon) onTriggered: aboutAppAction.trigger() visible: aboutAppAction.text !== "" } MenuSeparator { visible: quitApplication.text !== "" } MenuItem { text: quitApplication.text shortcut: quitApplication.shortcut iconName: elisa.iconName(quitApplication.icon) onTriggered: quitApplication.trigger() visible: quitApplication.text !== "" } } diff --git a/src/qml/BaseTheme.qml b/src/qml/BaseTheme.qml index fe900b34..b45f2e13 100644 --- a/src/qml/BaseTheme.qml +++ b/src/qml/BaseTheme.qml @@ -1,120 +1,120 @@ /* * Copyright 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 Item { function dp(pixel) { // 96 - common, "base" DPI value return Math.round(pixel * logicalDpi / 96); } property string defaultAlbumImage: 'image://icon/media-optical-audio' property string defaultArtistImage: 'image://icon/view-media-artist' property string defaultBackgroundImage: 'qrc:///background.png' property string nowPlayingIcon: 'image://icon/view-media-lyrics' property string artistIcon: 'image://icon/view-media-artist' property string albumIcon: 'image://icon/view-media-album-cover' property string albumCoverIcon: 'image://icon/media-optical-audio' property string playlistIcon: 'image://icon/view-media-playlist' property string tracksIcon: 'image://icon/view-media-track' property string genresIcon: 'image://icon/view-media-genre' property string clearIcon: 'image://icon/edit-clear' property string recentlyPlayedTracksIcon: 'image://icon/media-playlist-play' - property string frequentlyPlayedTracksIcon: 'image://icon/amarok_playcount' + property string frequentlyPlayedTracksIcon: 'image://icon/view-media-playcount' property string pausedIndicatorIcon: 'image://icon/media-playback-paused' property string playingIndicatorIcon: 'image://icon/media-playback-playing' property string ratingIcon: 'image://icon/rating' property string ratingUnratedIcon: 'image://icon/rating-unrated' property string errorIcon: 'image://icon/error' property string folderIcon: 'image://icon/document-open-folder' property int layoutHorizontalMargin: dp(8) property int layoutVerticalMargin: dp(6) property int delegateHeight: dp(28) FontMetrics { id: playListAuthorTextHeight font.weight: Font.Light } FontMetrics { id: playListAlbumTextHeight font.weight: Font.Bold font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.4) } FontMetrics { id: playListTrackTextHeight font.weight: Font.Bold } property int playListDelegateHeight: (playListTrackTextHeight.height > dp(28)) ? playListTrackTextHeight.height : dp(28) property int playListHeaderHeight: elisaTheme.layoutVerticalMargin * 5 + playListAuthorTextHeight.height + playListAlbumTextHeight.height property int trackDelegateHeight: dp(45) property int coverImageSize: dp(180) property int contextCoverImageSize: dp(100) property int smallImageSize: dp(32) property int maximumMetadataWidth: dp(300) property int tooltipRadius: dp(3) property int shadowOffset: dp(2) property int delegateToolButtonSize: dp(34) property int smallDelegateToolButtonSize: dp(20) property int ratingStarSize: dp(15) property int mediaPlayerControlHeight: dp(42) property int mediaPlayerHorizontalMargin: dp(10) property real mediaPlayerControlOpacity: 0.6 property int smallControlButtonSize: dp(22) property int volumeSliderWidth: dp(100) property int dragDropPlaceholderHeight: dp(28) property int navigationBarHeight: dp(100) property int navigationBarFilterHeight: dp(44) property int gridDelegateHeight: dp(170) + layoutVerticalMargin + fontSize.height * 2 property int gridDelegateWidth: dp(170) property int viewSelectorDelegateHeight: dp(24) property int filterClearButtonMargin: layoutVerticalMargin property alias defaultFontPointSize: fontSize.font.pointSize property int headerTitleFontSize: defaultFontPointSize * 2 property int headerToolbarHeight: dp(48) property int footerToolbarHeight: dp(30) property int viewSelectorSmallSizeThreshold: 800 Label { id: fontSize } } diff --git a/src/qml/GridBrowserDelegate.qml b/src/qml/GridBrowserDelegate.qml index 9f0dc3e8..bc238182 100644 --- a/src/qml/GridBrowserDelegate.qml +++ b/src/qml/GridBrowserDelegate.qml @@ -1,437 +1,437 @@ /* * 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 var imageUrl property bool shadowForImage property string fileUrl 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 showOpenButton: true property bool showPlayButton: true property bool showEnqueueButton: true signal enqueue(var databaseId, var name) signal replaceAndPlay(var databaseId, var name) signal open() signal selected() Loader { id: metadataLoader active: false && gridEntry.fileUrl onLoaded: item.show() sourceComponent: MediaTrackMetadataView { fileName: gridEntry.fileUrl ? gridEntry.fileUrl : '' 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 } ColumnLayout { anchors.fill: parent z: 2 spacing: 0 MouseArea { id: hoverHandle hoverEnabled: true acceptedButtons: Qt.LeftButton Layout.preferredHeight: gridEntry.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) + (secondaryLabelSize.boundingRect.height - secondaryLabelSize.boundingRect.y) Layout.fillWidth: true onClicked: { gridEntry.selected() } onDoubleClicked: 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.preferredHeight: gridEntry.width * 0.85 Layout.preferredWidth: gridEntry.width * 0.85 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) Keys.onReturnPressed: replaceAndPlay(databaseId, mainText) Keys.onEnterPressed: replaceAndPlay(databaseId, mainText) visible: showPlayButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } Button { id: enqueueButton objectName: 'enqueueButton' - icon.name: 'media-track-add-amarok' + 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) Keys.onReturnPressed: enqueue(databaseId, mainText) Keys.onEnterPressed: enqueue(databaseId, mainText) visible: showEnqueueButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } Button { id: openButton objectName: 'openButton' icon.name: 'go-next-view-page' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Open view of the container", "Open") Accessible.role: Accessible.Button Accessible.name: ToolTip.text Accessible.description: ToolTip.text Accessible.onPressAction: onClicked onClicked: open() visible: showOpenButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } } } Loader { id: coverImageLoader active: !isPartial anchors.fill: parent sourceComponent: Image { id: coverImage anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height fillMode: Image.PreserveAspectFit smooth: true source: (gridEntry.imageUrl !== undefined ? gridEntry.imageUrl : "") asynchronous: true layer.enabled: shadowForImage 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 font.weight: Font.Bold 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.topMargin: elisaTheme.layoutVerticalMargin * 0.5 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.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.bottomMargin: delegateDisplaySecondaryText ? 0 : elisaTheme.layoutVerticalMargin wrapMode: delegateDisplaySecondaryText ? Label.NoWrap : Label.Wrap elide: Text.ElideRight } LabelWithToolTip { id: secondaryLabel font.weight: Font.Light 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: elisaTheme.layoutVerticalMargin 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/ListBrowserDelegate.qml b/src/qml/ListBrowserDelegate.qml index 500f0e0f..d611c8b4 100644 --- a/src/qml/ListBrowserDelegate.qml +++ b/src/qml/ListBrowserDelegate.qml @@ -1,460 +1,460 @@ /* * 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.elisa 1.0 FocusScope { id: mediaTrack property var databaseId 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 isSingleDiscAlbum property bool isSelected property bool isAlternateColor property bool detailedView: true signal clicked() signal enqueue(var databaseId, var name) signal replaceAndPlay(var databaseId, var name) Accessible.role: Accessible.ListItem Accessible.name: title Accessible.description: title Action { id: enqueueAction text: i18nc("Enqueue current track", "Enqueue") - icon.name: "media-track-add-amarok" + icon.name: "list-add" onTriggered: enqueue(databaseId, title) } Action { id: viewDetailsAction text: i18nc("Show track metadata", "View Details") icon.name: "help-about" onTriggered: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } } Action { id: replaceAndPlayAction text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") icon.name: "media-playback-start" onTriggered: replaceAndPlay(databaseId, title) } Keys.onReturnPressed: enqueue(databaseId, title) Keys.onEnterPressed: enqueue(databaseId, title) Loader { id: metadataLoader active: false onLoaded: item.show() sourceComponent: MediaTrackMetadataView { databaseId: mediaTrack.databaseId onRejected: metadataLoader.active = false; } } 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(databaseId, title) RowLayout { anchors.fill: parent spacing: 0 LabelWithToolTip { id: mainLabel visible: !detailedView text: { if (trackNumber !== 0) { if (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 (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 (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) else return 0 } Layout.rightMargin: { if (LayoutMirroring.enabled) return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) else return 0 } } Item { Layout.preferredHeight: mediaTrack.height * 0.9 Layout.preferredWidth: mediaTrack.height * 0.9 Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter visible: detailedView Image { id: coverImageElement anchors.fill: parent sourceSize.width: mediaTrack.height * 0.9 sourceSize.height: mediaTrack.height * 0.9 fillMode: Image.PreserveAspectFit smooth: true source: (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) asynchronous: true layer.enabled: imageUrl != '' 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 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 font.weight: Font.Bold color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 elide: Text.ElideRight } Item { Layout.fillHeight: true } LabelWithToolTip { id: artistLabel text: { var labelText = "" if (artist) { labelText += artist } if (album !== '') { labelText += ' - ' + album if (!isSingleDiscAlbum) { labelText += ' - CD ' + discNumber } } return labelText; } horizontalAlignment: Text.AlignLeft font.weight: Font.Light font.italic: true color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignBottom Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 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: elisaTheme.delegateHeight width: elisaTheme.delegateHeight icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize action: viewDetailsAction Accessible.onPressAction: action.trigger() } FlatButtonWithToolTip { id: enqueueButton height: elisaTheme.delegateHeight width: elisaTheme.delegateHeight icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize action: enqueueAction Accessible.onPressAction: action.trigger() } FlatButtonWithToolTip { id: clearAndEnqueueButton scale: LayoutMirroring.enabled ? -1 : 1 height: elisaTheme.delegateHeight width: elisaTheme.delegateHeight icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize action: replaceAndPlayAction Accessible.onPressAction: action.trigger() } } } RatingStar { id: ratingWidget starSize: elisaTheme.ratingStarSize starRating: rating Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin } 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 ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 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/NavigationActionBar.qml b/src/qml/NavigationActionBar.qml index 9985c4e0..bbaf1ae8 100644 --- a/src/qml/NavigationActionBar.qml +++ b/src/qml/NavigationActionBar.qml @@ -1,278 +1,278 @@ /* * 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 QtQml 2.2 import QtQuick 2.7 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.3 import org.kde.kirigami 2.8 as Kirigami ColumnLayout { id: navigationBar spacing: 0 property string mainTitle property string secondaryTitle property url image property bool allowArtistNavigation: false property string labelText property bool showRating: true property alias filterText: filterTextInput.text property alias filterRating: ratingFilter.starRating property bool enableGoBack: true property bool expandedFilterView property bool enableSorting: true property bool sortOrder signal enqueue(); signal replaceAndPlay(); signal goBack(); signal showArtist(); signal sort(var order); Action { id: goPreviousAction text: i18nc("navigate back in the views stack", "Back") icon.name: (Qt.application.layoutDirection == Qt.RightToLeft) ? "go-next" : "go-previous" onTriggered: goBack() enabled: enableGoBack } Action { id: replaceAndPlayAction text: i18n("Play now, replacing contents of Playlist") icon.name: "media-playback-start" onTriggered: replaceAndPlay() } Action { id: enqueueAction text: i18nc("Add current list to playlist", "Enqueue") - icon.name: "media-track-add-amarok" + icon.name: "list-add" onTriggered: enqueue() } Action { id: showFilterAction text: !navigationBar.expandedFilterView ? i18nc("Show filters in the navigation bar", "Show Search Options") : i18nc("Hide filters in the navigation bar", "Hide Search Options") icon.name: 'search' checkable: true checked: expandedFilterView onTriggered: persistentSettings.expandedFilterView = !persistentSettings.expandedFilterView } Action { id: sortAction text: i18nc("Toggle between ascending and descending order", "Toggle sort order") icon.name: sortOrder ? "view-sort-ascending" : "view-sort-descending" onTriggered: sortOrder ? sort(Qt.DescendingOrder) : sort(Qt.AscendingOrder) } Action { id: showArtistAction text: i18nc("Button to navigate to the artist of the album", "Display Artist") icon.name: "view-media-artist" onTriggered: showArtist() } HeaderFooterToolbar { type: filterRow.visible? "other" : "header" contentItems: [ FlatButtonWithToolTip { action: goPreviousAction id: goPreviousButton objectName: 'goPreviousButton' icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize focus: enableGoBack visible: enableGoBack }, Item { id: spacer width: elisaTheme.layoutHorizontalMargin visible: goPreviousButton.visible }, Image { id: mainIcon source: image height: authorAndAlbumLayout.height width: height sourceSize.height: height sourceSize.width: width fillMode: Image.PreserveAspectFit asynchronous: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft }, Item { width: elisaTheme.layoutHorizontalMargin visible: mainIcon.visible }, ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true id: authorAndAlbumLayout LabelWithToolTip { id: albumLabel Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true text: mainTitle font.pointSize: authorLabel.visible ? Math.round(elisaTheme.defaultFontPointSize * 1.2) : elisaTheme.headerTitleFontSize font.weight: authorLabel.visible ? Font.Bold : Font.Normal elide: Text.ElideRight } LabelWithToolTip { id: authorLabel Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true text: secondaryTitle font.pointSize: elisaTheme.defaultFontPointSize elide: Text.ElideRight visible: secondaryTitle !== "" } }, FlatButtonWithToolTip { action: enqueueAction objectName: 'enqueueButton' icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize focus: true }, FlatButtonWithToolTip { action: replaceAndPlayAction objectName: 'replaceAndPlayButton' icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize }, FlatButtonWithToolTip { action: showArtistAction objectName: 'showArtistButton' icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize visible: allowArtistNavigation }, FlatButtonWithToolTip { action: sortAction objectName: 'sortAscendingButton' icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize visible: enableSorting }, FlatButtonWithToolTip { action: showFilterAction objectName: 'showFilterButton' icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize } ] } HeaderFooterToolbar { type: "header" id: filterRow visible: opacity > 0.0 opacity: 0 contentItems: [ Kirigami.SearchField { id: filterTextInput objectName: 'filterTextInput' Layout.fillWidth: true focusSequence: null Accessible.role: Accessible.EditableText placeholderText: i18n("Search for album name, artist, etc.") }, Item { width: elisaTheme.layoutHorizontalMargin }, LabelWithToolTip { text: i18n("Filter by rating: ") visible: showRating }, RatingStar { id: ratingFilter objectName: 'ratingFilter' visible: showRating hoverWidgetOpacity: 1 readOnly: false starSize: elisaTheme.ratingStarSize } ] } states: [ State { name: 'collapsed' when: !expandedFilterView PropertyChanges { target: filterRow opacity: 0.0 } }, State { name: 'expanded' when: expandedFilterView PropertyChanges { target: filterRow opacity: 1.0 } StateChangeScript { script: filterTextInput.forceActiveFocus() } } ] transitions: Transition { from: "expanded,collapsed" PropertyAnimation { properties: "opacity" easing.type: Easing.Linear duration: 250 } } }