diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index 6df2b4e0..869aabfe 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,899 +1,945 @@ /* * Copyright 2016-2018 Matthieu Gallien * * 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 1.3 import QtQuick.Controls.Styles 1.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import org.kde.elisa 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 1000 minimumHeight: 600 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true x: persistentSettings.x y: persistentSettings.y width: persistentSettings.width height: persistentSettings.height title: i18n("Elisa") property var goBackAction: elisa.action("go_back") Action { text: goBackAction.text shortcut: goBackAction.shortcut iconName: elisa.iconName(goBackAction.icon) onTriggered: { localAlbums.goBack() localArtists.goBack() } } Action { id: applicationMenuAction text: i18nc("open application menu", "Application Menu") iconName: "application-menu" onTriggered: applicationMenu.popup() } ApplicationMenu { id: applicationMenu } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 1000 property int height : 600 property var playListState property var playListControlerState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false - } + + property string filterState + + } Connections { target: Qt.application onAboutToQuit: { persistentSettings.x = mainWindow.x; persistentSettings.y = mainWindow.y; persistentSettings.width = mainWindow.width; persistentSettings.height = mainWindow.height; persistentSettings.playListState = elisa.mediaPlayList.persistentState; persistentSettings.playListControlerState = elisa.mediaPlayList.persistentState; persistentSettings.audioPlayerState = manageAudioPlayer.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted } } PlatformIntegration { id: platformInterface playListModel: elisa.mediaPlayList playListControler: elisa.mediaPlayList audioPlayerManager: manageAudioPlayer headerBarManager: myHeaderBarManager manageMediaPlayerControl: myPlayControlManager player: audioPlayer onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } AudioWrapper { id: audioPlayer muted: headerBar.playerControl.muted volume: headerBar.playerControl.volume onVolumeChanged: headerBar.playerControl.volume = volume onMutedChanged: headerBar.playerControl.muted = muted source: manageAudioPlayer.playerSource onPlaying: { myPlayControlManager.playerPlaying() } onPaused: { myPlayControlManager.playerPaused() } onStopped: { myPlayControlManager.playerStopped() } } Connections { target: elisa.mediaPlayList onPlayListLoadFailed: { messageNotification.showNotification(i18nc("message of passive notification when playlist load failed", "Load of playlist failed"), 3000) } onEnsurePlay: manageAudioPlayer.ensurePlay() onPlayListFinished: manageAudioPlayer.playListFinished() } ManageHeaderBar { id: myHeaderBarManager playListModel: elisa.mediaPlayList currentTrack: elisa.mediaPlayList.currentTrack artistRole: MediaPlayList.ArtistRole titleRole: MediaPlayList.TitleRole albumRole: MediaPlayList.AlbumRole imageRole: MediaPlayList.ImageRole isValidRole: MediaPlayList.IsValidRole } ManageAudioPlayer { id: manageAudioPlayer currentTrack: elisa.mediaPlayList.currentTrack playListModel: elisa.mediaPlayList urlRole: MediaPlayList.ResourceRole isPlayingRole: MediaPlayList.IsPlayingRole titleRole: MediaPlayList.TitleRole artistNameRole: MediaPlayList.ArtistRole albumNameRole: MediaPlayList.AlbumRole playerStatus: audioPlayer.status playerPlaybackState: audioPlayer.playbackState playerError: audioPlayer.error audioDuration: audioPlayer.duration playerIsSeekable: audioPlayer.seekable playerPosition: audioPlayer.position persistentState: persistentSettings.audioPlayerState onPlayerPlay: audioPlayer.play() onPlayerPause: audioPlayer.pause() onPlayerStop: audioPlayer.stop() onSkipNextTrack: elisa.mediaPlayList.skipNextTrack() onSeek: audioPlayer.seek(position) onSourceInError: { elisa.mediaPlayList.trackInError(source, playerError) allListeners.playBackError(source, playerError) } onDisplayTrackError: messageNotification.showNotification(i18n("Error when playing %1", "" + fileName), 3000) } ManageMediaPlayerControl { id: myPlayControlManager playListModel: elisa.mediaPlayList currentTrack: elisa.mediaPlayList.currentTrack } PassiveNotification { id: messageNotification } Rectangle { color: myPalette.base anchors.fill: parent ColumnLayout { anchors.fill: parent spacing: 0 Item { Layout.preferredHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.fillWidth: true HeaderBar { id: headerBar focus: true anchors.fill: parent tracksCount: myHeaderBarManager.remainingTracks album: myHeaderBarManager.album title: myHeaderBarManager.title artist: myHeaderBarManager.artist image: myHeaderBarManager.image ratingVisible: false playerControl.duration: audioPlayer.duration playerControl.seekable: audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: audioPlayer.position playerControl.skipBackwardEnabled: myPlayControlManager.skipBackwardControlEnabled playerControl.skipForwardEnabled: myPlayControlManager.skipForwardControlEnabled playerControl.playEnabled: myPlayControlManager.playControlEnabled playerControl.isPlaying: myPlayControlManager.musicPlaying playerControl.onSeek: audioPlayer.seek(position) playerControl.onPlay: manageAudioPlayer.playPause() playerControl.onPause: manageAudioPlayer.playPause() playerControl.onPlayPrevious: elisa.mediaPlayList.skipPreviousTrack() playerControl.onPlayNext: elisa.mediaPlayList.skipNextTrack() ToolButton { id: menuButton action: applicationMenuAction z: 2 anchors { right: parent.right top: parent.top rightMargin: elisaTheme.layoutHorizontalMargin * 3 topMargin: elisaTheme.layoutHorizontalMargin * 3 } } Rectangle { anchors.fill: menuButton z: 1 radius: width / 2 color: myPalette.window } TrackImportNotification { id: importedTracksCountNotification anchors { right: menuButton.left top: menuButton.top bottom: menuButton.bottom rightMargin: elisaTheme.layoutHorizontalMargin * 3 } } Binding { target: importedTracksCountNotification property: 'musicManager' value: elisa.musicManager when: elisa.musicManager !== undefined } Loader { sourceComponent: Binding { target: importedTracksCountNotification property: 'indexingRunning' value: elisa.musicManager.indexingRunning } active: elisa.musicManager !== undefined } Loader { sourceComponent: Binding { target: importedTracksCountNotification property: 'importedTracksCount' value: elisa.musicManager.importedTracksCount } active: elisa.musicManager !== undefined } } } RowLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 ViewSelector { id: listViews Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.15 Layout.maximumWidth: mainWindow.width * 0.15 } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 TopNotification { id: invalidBalooConfiguration Layout.fillWidth: true musicManager: elisa.musicManager focus: true } Item { Layout.fillHeight: true Layout.fillWidth: true RowLayout { anchors.fill: parent spacing: 0 id: contentZone FocusScope { id: mainContentView focus: true Layout.fillHeight: true Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 visible: Layout.minimumWidth != 0 Rectangle { border { color: (mainContentView.activeFocus ? myPalette.highlight : myPalette.base) width: 1 } radius: 3 color: myPalette.base anchors.fill: parent BusyIndicator { id: busyScanningMusic anchors.fill: parent anchors.leftMargin: parent.width / 3 anchors.rightMargin: parent.width / 3 anchors.topMargin: parent.height / 3 anchors.bottomMargin: parent.height / 3 opacity: 0.8 z: 2 } Loader { sourceComponent: Binding { target: busyScanningMusic property: 'running' value: elisa.musicManager.indexerBusy } active: elisa.musicManager !== undefined } MediaBrowser { id: localAlbums focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allAlbumsView focus: true contentModel: elisa.allAlbumsProxyModel image: elisaTheme.albumIcon mainTitle: i18nc("Title of the view of all albums", "Albums") onOpen: { elisa.singleAlbumProxyModel.sourceModel.loadAlbumData(databaseId) localAlbums.stackView.push(albumView, { stackView: localAlbums.stackView, albumName: innerMainTitle, artistName: innerSecondaryTitle, albumArtUrl: innerImage, }) } onGoBack: localAlbums.stackView.pop() + + Binding { + target: allAlbumsView + property: 'filterState' + value: persistentSettings.filterState + } + + onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } MediaBrowser { id: localArtists focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allArtistsView focus: true showRating: false delegateDisplaySecondaryText: false contentModel: elisa.allArtistsProxyModel image: elisaTheme.artistIcon mainTitle: i18nc("Title of the view of all artists", "Artists") onOpen: { elisa.singleArtistProxyModel.setArtistFilterText(innerMainTitle) localArtists.stackView.push(innerAlbumView, { mainTitle: innerMainTitle, secondaryTitle: innerSecondaryTitle, image: innerImage, stackView: localArtists.stackView }) } onGoBack: localArtists.stackView.pop() + + Binding { + target: allArtistsView + property: 'filterState' + value: persistentSettings.filterState + } + + onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } MediaBrowser { id: localTracks focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: MediaAllTracksView { + id: allTracksView focus: true stackView: localTracks.stackView contentModel: elisa.allTracksProxyModel + + Binding { + target: allTracksView + property: 'filterState' + value: persistentSettings.filterState + } + + onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } Behavior on border.color { ColorAnimation { duration: 300 } } } } Rectangle { id: firstViewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } MediaPlayListView { id: playList playListModel: elisa.mediaPlayList randomPlayChecked: elisa.mediaPlayList.randomPlay repeatPlayChecked: elisa.mediaPlayList.repeatPlay Layout.fillHeight: true Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width onStartPlayback: manageAudioPlayer.ensurePlay() onPausePlayback: manageAudioPlayer.playPause() onDisplayError: messageNotification.showNotification(errorText) } Rectangle { id: viewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: Layout.minimumWidth != 0 Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } ContextView { id: albumContext Layout.fillHeight: true Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width visible: Layout.minimumWidth != 0 artistName: myHeaderBarManager.artist albumName: myHeaderBarManager.album albumArtUrl: myHeaderBarManager.image } } } states: [ State { name: 'full' when: listViews.currentIndex === 0 PropertyChanges { target: mainContentView Layout.fillWidth: false Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: albumContext Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allAlbums' when: listViews.currentIndex === 1 StateChangeScript { script: { localAlbums.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 1 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allArtists' when: listViews.currentIndex === 2 StateChangeScript { script: { localArtists.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 1 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allTracks' when: listViews.currentIndex === 3 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 1 } } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" easing.type: Easing.InOutQuad duration: 300 } } } } } } Component { id: innerAlbumView GridBrowserView { + id: innerAlbumGridView property var stackView contentModel: elisa.singleArtistProxyModel isSubPage: true onOpen: { elisa.singleAlbumProxyModel.sourceModel.loadAlbumData(databaseId) localArtists.stackView.push(albumView, { stackView: localArtists.stackView, albumName: innerMainTitle, artistName: innerSecondaryTitle, albumArtUrl: innerImage, }) } onGoBack: stackView.pop() + + Binding { + target: innerAlbumGridView + property: 'filterState' + value: persistentSettings.filterState + } + + onFilterViewChanged: persistentSettings.filterState = filterState } } Component { id: albumView MediaAlbumView { + id: albumGridView property var stackView contentModel: elisa.singleAlbumProxyModel onShowArtist: { listViews.currentIndex = 2 if (localArtists.stackView.depth === 3) { localArtists.stackView.pop() } if (localArtists.stackView.depth === 2) { var artistPage = localArtists.stackView.get(1) if (artistPage.mainTitle === name) { return } else { localArtists.stackView.pop() } } allArtistsView.open(name, name, elisaTheme.defaultArtistImage, '') } onGoBack: stackView.pop() + + Binding { + target: albumGridView + property: 'filterState' + value: persistentSettings.filterState + } + + onFilterViewChanged: persistentSettings.filterState = filterState } } Component.onCompleted: { elisa.initialize() var d = new Date(); var n = d.getMilliseconds(); elisa.mediaPlayList.seedRandomGenerator(n); elisa.mediaPlayList.randomPlay = Qt.binding(function() { return playList.randomPlayChecked }) elisa.mediaPlayList.repeatPlay = Qt.binding(function() { return playList.repeatPlayChecked }) myPlayControlManager.randomOrContinuePlay = Qt.binding(function() { return playList.randomPlayChecked || playList.repeatPlayChecked }) if (persistentSettings.playListState) { elisa.mediaPlayList.persistentState = persistentSettings.playListState } elisa.mediaPlayList.enqueue(elisa.arguments) } } diff --git a/src/qml/GridBrowserView.qml b/src/qml/GridBrowserView.qml index 60e1bb9c..e57c9339 100644 --- a/src/qml/GridBrowserView.qml +++ b/src/qml/GridBrowserView.qml @@ -1,140 +1,144 @@ /* * Copyright 2016-2017 Matthieu Gallien * * 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 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 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 bool showRating: true property bool delegateDisplaySecondaryText: true + property alias filterState: navigationBar.state signal open(var innerMainTitle, var innerSecondaryTitle, var innerImage, var databaseId) signal goBack() + signal filterViewChanged(string filterState) SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: gridView.mainTitle secondaryTitle: gridView.secondaryTitle image: gridView.image enableGoBack: isSubPage height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay:contentModel.replaceAndPlayOfPlayList() onGoBack: gridView.goBack() + + onFilterViewChanged: gridView.filterViewChanged(filterState) } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true ScrollView { anchors.fill: parent flickableItem.boundsBehavior: Flickable.StopAtBounds flickableItem.interactive: true GridView { id: contentDirectoryView anchors.topMargin: 20 focus: true TextMetrics { id: secondaryLabelSize text: 'example' } cellWidth: elisaTheme.gridDelegateWidth cellHeight: (delegateDisplaySecondaryText ? elisaTheme.gridDelegateHeight : elisaTheme.gridDelegateHeight - secondaryLabelSize.height) delegate: GridBrowserDelegate { width: contentDirectoryView.cellWidth height: contentDirectoryView.cellHeight focus: true mainText: model.display secondaryText: model.secondaryText imageUrl: model.imageUrl shadowForImage: model.shadowForImage containerData: model.containerData delegateDisplaySecondaryText: gridView.delegateDisplaySecondaryText onEnqueue: elisa.mediaPlayList.enqueue(data) onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) onOpen: gridView.open(model.display, model.secondaryText, model.imageUrl, model.databaseId) onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index } } } } } } } diff --git a/src/qml/MediaAlbumView.qml b/src/qml/MediaAlbumView.qml index d8dcc8a9..f053cdeb 100644 --- a/src/qml/MediaAlbumView.qml +++ b/src/qml/MediaAlbumView.qml @@ -1,132 +1,136 @@ /* * Copyright 2016-2017 Matthieu Gallien * * 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.5 import QtQuick.Window 2.2 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.2 import QtQml.Models 2.1 import org.kde.elisa 1.0 import QtQuick.Layouts 1.2 FocusScope { id: topListing property var albumName property var artistName property var albumArtUrl property bool isSingleDiscAlbum + property alias filterState: navigationBar.state property var albumId property alias contentModel: contentDirectoryView.model signal showArtist(var name) signal goBack(); + signal filterViewChanged(string filterState) function loadAlbumData(id) { contentModel.sourceModel.loadAlbumData(id) } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true mainTitle: (topListing.artistName ? topListing.artistName : '') secondaryTitle: topListing.albumName image: (topListing.albumArtUrl ? topListing.albumArtUrl : elisaTheme.defaultAlbumImage) allowArtistNavigation: true Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } onGoBack: topListing.goBack() + onFilterViewChanged: topListing.filterViewChanged(filterState) + onShowArtist: topListing.showArtist(topListing.contentModel.sourceModel.author) onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay: contentModel.replaceAndPlayOfPlayList() } ScrollView { flickableItem.boundsBehavior: Flickable.StopAtBounds flickableItem.interactive: true Layout.fillHeight: true Layout.fillWidth: true ListView { id: contentDirectoryView focus: true delegate: MediaAlbumTrackDelegate { id: entry height: ((model.isFirstTrackOfDisc && !isSingleDiscAlbum) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) width: contentDirectoryView.width focus: true mediaTrack.isAlternateColor: (index % 2) === 1 mediaTrack.trackData: model.containerData mediaTrack.isFirstTrackOfDisc: model.isFirstTrackOfDisc mediaTrack.isSingleDiscAlbum: model.isSingleDiscAlbum mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(data) mediaTrack.onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) mediaTrack.onClicked: contentDirectoryView.currentIndex = index } } } } } diff --git a/src/qml/MediaAllTracksView.qml b/src/qml/MediaAllTracksView.qml index f7edda1f..f37eff71 100644 --- a/src/qml/MediaAllTracksView.qml +++ b/src/qml/MediaAllTracksView.qml @@ -1,123 +1,128 @@ /* * Copyright 2016-2017 Matthieu Gallien * * 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 1.2 import QtQuick.Controls.Styles 1.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: rootElement property var stackView property alias contentModel: contentDirectoryView.model + property alias filterState: navigationBar.state + + signal filterViewChanged(string filterState) SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: i18nc("Title of the view of all tracks", "Tracks") secondaryTitle: "" image: elisaTheme.tracksIcon enableGoBack: false height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } onEnqueue: contentModel.enqueueToPlayList() + onFilterViewChanged: rootElement.filterViewChanged(filterState) + onReplaceAndPlay: contentModel.replaceAndPlayOfPlayList() } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true ScrollView { anchors.fill: parent flickableItem.boundsBehavior: Flickable.StopAtBounds flickableItem.interactive: true ListView { id: contentDirectoryView focus: true delegate: MediaTrackDelegate { id: entry width: contentDirectoryView.width height: elisaTheme.trackDelegateHeight focus: true isAlternateColor: (index % 2) === 1 trackData: model.containerData isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum onEnqueue: rootElement.enqueue(data) onReplaceAndPlay: rootElement.replaceAndPlay(data) onClicked: contentDirectoryView.currentIndex = index } } } } } } diff --git a/src/qml/NavigationActionBar.qml b/src/qml/NavigationActionBar.qml index d087d7b2..c592c912 100644 --- a/src/qml/NavigationActionBar.qml +++ b/src/qml/NavigationActionBar.qml @@ -1,352 +1,353 @@ /* * Copyright 2016 Matthieu Gallien * * 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 QtQml 2.2 import QtQuick 2.5 import QtQuick.Layouts 1.3 import QtQuick.Controls 1.4 import QtQuick.Window 2.0 FocusScope { id: navigationBar 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 collapsed: true signal enqueue(); signal replaceAndPlay(); signal goBack(); signal showArtist(); + signal filterViewChanged(string filterState); Action { id: goPreviousAction text: i18nc("navigate back in the views stack", "Back") iconName: (Qt.application.layoutDirection == Qt.RightToLeft) ? "go-next" : "go-previous" onTriggered: goBack() } Action { id: showFilterAction - text: collapsed ? i18nc("Show filters in the navigation bar", "Show Search Options") : i18nc("Hide filters in the navigation bar", "Hide Search Options") - iconName: collapsed ? "go-down-search" : "go-up-search" - onTriggered: collapsed ? collapsed = false : collapsed = true + text: navigationBar.state === "" ? i18nc("Show filters in the navigation bar", "Show Search Options") : i18nc("Hide filters in the navigation bar", "Hide Search Options") + iconName: navigationBar.state === "" ? "go-down-search" : "go-up-search" + onTriggered: { + navigationBar.state === "" ? navigationBar.state = 'expanded' : navigationBar.state = "" + filterViewChanged(navigationBar.state) + } } ColumnLayout { anchors.fill: parent spacing: 0 anchors.margins: { top: elisaTheme.layoutVerticalMargin bottom: elisaTheme.layoutVerticalMargin } RowLayout { spacing: 0 Layout.alignment: Qt.AlignTop ToolButton { action: goPreviousAction Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 visible: enableGoBack } Image { id: mainIcon source: image asynchronous: true sourceSize.height: elisaTheme.coverImageSize / 2 sourceSize.width: elisaTheme.coverImageSize / 2 fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.preferredHeight: elisaTheme.coverImageSize / 2 Layout.minimumHeight: elisaTheme.coverImageSize / 2 Layout.maximumHeight: elisaTheme.coverImageSize / 2 Layout.preferredWidth: elisaTheme.coverImageSize / 2 Layout.minimumWidth: elisaTheme.coverImageSize / 2 Layout.maximumWidth: elisaTheme.coverImageSize / 2 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } ColumnLayout { Layout.preferredHeight: elisaTheme.coverImageSize / 2 Layout.minimumHeight: elisaTheme.coverImageSize / 2 Layout.maximumHeight: elisaTheme.coverImageSize / 2 spacing: 0 Layout.fillWidth: true Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 TextMetrics { id: albumTextSize text: albumLabel.text font.pixelSize: albumLabel.font.pixelSize font.bold: albumLabel.font.bold } LabelWithToolTip { id: albumLabel text: secondaryTitle Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter elide: Text.ElideRight color: myPalette.text font { pixelSize: elisaTheme.defaultFontPixelSize * 2 } visible: secondaryTitle !== "" } TextMetrics { id: authorTextSize text: authorLabel.text font.pixelSize: authorLabel.font.pixelSize font.bold: authorLabel.font.bold } LabelWithToolTip { id: authorLabel text: mainTitle color: myPalette.text Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter font { pixelSize: (secondaryTitle !== "" ? elisaTheme.defaultFontPixelSize : elisaTheme.defaultFontPixelSize * 2) } elide: Text.ElideRight } Item { id: emptyBottomFiller Layout.fillWidth: true Layout.fillHeight: true } RowLayout { Layout.fillWidth: true spacing: 0 Button { text: i18nc("Add current list to playlist", "Enqueue") iconName: "media-track-add-amarok" onClicked: enqueue() Layout.leftMargin: 0 Layout.rightMargin: 0 } Button { text: i18nc("Clear playlist and play", "Replace and Play") tooltip: i18nc("Clear playlist and add current list to it", "Replace PlayList and Play Now") iconName: "media-playback-start" onClicked: replaceAndPlay() Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } Button { id: showArtistButton visible: allowArtistNavigation text: i18nc("Button to navigate to the artist of the album", "Display Artist") iconName: "view-media-artist" onClicked: showArtist() Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } Item { Layout.fillWidth: true } ToolButton { action: showFilterAction Layout.alignment: Qt.AlignRight Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } } RowLayout { id: filterRow spacing: 0 visible: opacity > 0.0 Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin * 2 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.alignment: Qt.AlignTop LabelWithToolTip { text: i18nc("before the TextField input of the filter", "Search: ") font.bold: true Layout.bottomMargin: 0 color: myPalette.text } TextField { id: filterTextInput horizontalAlignment: TextInput.AlignLeft placeholderText: i18nc("Placeholder text in the filter text box", "Album name, artist, etc.") Layout.bottomMargin: 0 Layout.preferredWidth: navigationBar.width / 2 Image { anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right anchors.margins: elisaTheme.filterClearButtonMargin id: clearText fillMode: Image.PreserveAspectFit smooth: true visible: parent.text source: Qt.resolvedUrl(elisaTheme.clearIcon) height: parent.height width: parent.height sourceSize.width: parent.height sourceSize.height: parent.height mirror: LayoutMirroring.enabled MouseArea { id: clear anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } height: parent.parent.height width: parent.parent.height onClicked: { parent.parent.text = "" parent.parent.forceActiveFocus() } } } } LabelWithToolTip { text: i18nc("before the Rating widget input of the filter", "Rating: ") visible: showRating font.bold: true color: myPalette.text Layout.bottomMargin: 0 Layout.leftMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 Layout.rightMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 } RatingStar { id: ratingFilter visible: showRating readOnly: false starSize: elisaTheme.ratingStarSize Layout.bottomMargin: 0 } } } states: [ State { - name: 'collapsed' - when: collapsed + name: "" PropertyChanges { target: navigationBar height: elisaTheme.navigationBarHeight } PropertyChanges { target: filterRow opacity: 0.0 } }, State { name: 'expanded' - when: !collapsed PropertyChanges { target: navigationBar height: elisaTheme.navigationBarHeight + elisaTheme.navigationBarFilterHeight } PropertyChanges { target: filterRow opacity: 1.0 } } ] transitions: Transition { PropertyAnimation { properties: "height" easing.type: Easing.Linear duration: 250 } PropertyAnimation { properties: "opacity" easing.type: Easing.Linear duration: 250 } } }