diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index 2a9cf1f5..9f0627c4 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,943 +1,943 @@ /* * 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 2.2 import QtQuick.Controls 1.4 as Controls1 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") - Shortcut { - sequence: goBackAction.shortcut - onActivated: { + Controls1.Action { + shortcut: goBackAction.shortcut + onTriggered: { localAlbums.goBack() localArtists.goBack() } } Controls1.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() Controls1.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 - + hoverEnabled: false 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 9841edf4..21c2f567 100644 --- a/src/qml/GridBrowserView.qml +++ b/src/qml/GridBrowserView.qml @@ -1,141 +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 2.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: 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 { + GridView { + id: contentDirectoryView + anchors.topMargin: 20 + + focus: true anchors.fill: parent - GridView { - id: contentDirectoryView - anchors.topMargin: 20 + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true - focus: true + TextMetrics { + id: secondaryLabelSize + text: 'example' + } - 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 - 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 - } + 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 dfe20617..d5548051 100644 --- a/src/qml/MediaAlbumView.qml +++ b/src/qml/MediaAlbumView.qml @@ -1,132 +1,135 @@ /* * 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.Window 2.2 import QtQuick.Controls 2.2 import QtQml.Models 2.2 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 { + ListView { + id: contentDirectoryView Layout.fillHeight: true Layout.fillWidth: true + contentWidth: parent.width + focus: true - ListView { - id: contentDirectoryView - - focus: true + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true - delegate: MediaAlbumTrackDelegate { - id: entry + delegate: MediaAlbumTrackDelegate { + id: entry - height: ((model.isFirstTrackOfDisc && !isSingleDiscAlbum) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) - width: contentDirectoryView.width + height: ((model.isFirstTrackOfDisc && !isSingleDiscAlbum) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) + width: scrollBar.visible ? contentDirectoryView.width - scrollBar.width : contentDirectoryView.width - focus: true + focus: true - mediaTrack.isAlternateColor: (index % 2) === 1 + mediaTrack.trackData: model.containerData - mediaTrack.trackData: model.containerData + mediaTrack.isFirstTrackOfDisc: model.isFirstTrackOfDisc - mediaTrack.isFirstTrackOfDisc: model.isFirstTrackOfDisc + mediaTrack.isSingleDiscAlbum: model.isSingleDiscAlbum - mediaTrack.isSingleDiscAlbum: model.isSingleDiscAlbum + mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(data) - mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(data) + mediaTrack.onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) - mediaTrack.onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + mediaTrack.isAlternateColor: (index % 2) === 1 - mediaTrack.onClicked: contentDirectoryView.currentIndex = index - } + mediaTrack.onClicked: contentDirectoryView.currentIndex = index } } } } diff --git a/src/qml/MediaAllTracksView.qml b/src/qml/MediaAllTracksView.qml index 173cc8ba..7abbb323 100644 --- a/src/qml/MediaAllTracksView.qml +++ b/src/qml/MediaAllTracksView.qml @@ -1,125 +1,126 @@ /* * 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 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.2 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 { + ListView { + id: contentDirectoryView anchors.fill: parent - ListView { - id: contentDirectoryView + focus: true - focus: true - - delegate: MediaTrackDelegate { - id: entry + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true - width: contentDirectoryView.width - height: elisaTheme.trackDelegateHeight + delegate: MediaTrackDelegate { + id: entry - focus: true + width: scrollBar.visible ? contentDirectoryView.width - scrollBar.width : contentDirectoryView.width + height: elisaTheme.trackDelegateHeight - isAlternateColor: (index % 2) === 1 + focus: true - trackData: model.containerData + trackData: model.containerData - isFirstTrackOfDisc: false + isFirstTrackOfDisc: false - isSingleDiscAlbum: model.isSingleDiscAlbum + isSingleDiscAlbum: model.isSingleDiscAlbum - onEnqueue: elisa.mediaPlayList.enqueue(data) + onEnqueue: elisa.mediaPlayList.enqueue(data) - onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) - onClicked: contentDirectoryView.currentIndex = index - } + onClicked: contentDirectoryView.currentIndex = index } } } } } diff --git a/src/qml/MediaBrowser.qml b/src/qml/MediaBrowser.qml index 9a138d07..5651bd68 100644 --- a/src/qml/MediaBrowser.qml +++ b/src/qml/MediaBrowser.qml @@ -1,90 +1,89 @@ /* * 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 QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import org.kde.elisa 1.0 FocusScope { id: contentDirectoryRoot - property MediaPlayList playListModel property alias firstPage: listingView.initialItem property alias stackView: listingView function goBack() { if (listingView.depth > 1) { listingView.pop() } } ColumnLayout { anchors.fill: parent spacing: 0 StackView { id: listingView Layout.fillHeight: true Layout.fillWidth: true focus: true pushEnter: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 } } pushExit: Transition { PropertyAnimation { property: "opacity" from: 1 to: 0 } } popEnter: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 } } popExit: Transition { PropertyAnimation { property: "opacity" from: 1 to: 0 } } } } MouseArea { anchors.fill: parent acceptedButtons: Qt.BackButton onClicked: goBack() } } diff --git a/src/qml/MediaPlayListView.qml b/src/qml/MediaPlayListView.qml index 593f4be9..3d0b7c8f 100644 --- a/src/qml/MediaPlayListView.qml +++ b/src/qml/MediaPlayListView.qml @@ -1,364 +1,366 @@ /* * 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.Controls 2.2 import QtQuick.Controls 1.3 as Controls1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import QtQml.Models 2.1 import Qt.labs.platform 1.0 as PlatformDialog import org.kde.elisa 1.0 FocusScope { property StackView parentStackView property MediaPlayList playListModel property alias randomPlayChecked: shuffleOption.checked property alias repeatPlayChecked: repeatOption.checked property int placeholderHeight: elisaTheme.dragDropPlaceholderHeight signal startPlayback() signal pausePlayback() signal displayError(var errorText) id: topItem Controls1.Action { id: clearPlayList text: i18nc("Remove all tracks from play list", "Clear Play List") iconName: "list-remove" enabled: playListModelDelegate.items.count > 0 onTriggered: playListModel.clearPlayList() } Controls1.Action { id: showCurrentTrack text: i18nc("Show currently played track inside playlist", "Show Current Track") iconName: 'media-show-active-track-amarok' enabled: playListModelDelegate.items.count > 0 onTriggered: { playListView.positionViewAtIndex(playListModel.currentTrackRow, ListView.Contain) playListView.currentIndex = playListModel.currentTrackRow playListView.currentItem.forceActiveFocus() } } Controls1.Action { id: loadPlaylist text: i18nc("Load a playlist file", "Load a Playlist") iconName: 'document-open' onTriggered: { fileDialog.fileMode = PlatformDialog.FileDialog.OpenFile fileDialog.file = '' fileDialog.open() } } Controls1.Action { id: savePlaylist text: i18nc("Save a playlist file", "Save a Playlist") iconName: 'document-save' enabled: playListModelDelegate.items.count > 0 onTriggered: { fileDialog.fileMode = PlatformDialog.FileDialog.SaveFile fileDialog.file = '' fileDialog.open() } } PlatformDialog.FileDialog { id: fileDialog defaultSuffix: 'm3u' folder: PlatformDialog.StandardPaths.writableLocation(PlatformDialog.StandardPaths.MusicLocation) nameFilters: [i18nc("file type (mime type) for m3u playlist", "Playlist (*.m3u)")] onAccepted: { if (fileMode === PlatformDialog.FileDialog.SaveFile) { if (!playListModel.savePlaylist(fileDialog.file)) { displayError(i18nc("message of passive notification when playlist load failed", "Save of playlist failed")) } } else { playListModel.loadPlaylist(fileDialog.file) } } } ColumnLayout { anchors.fill: parent spacing: 0 RowLayout { height: elisaTheme.navigationBarHeight Layout.preferredHeight: elisaTheme.navigationBarHeight Layout.minimumHeight: elisaTheme.navigationBarHeight Layout.maximumHeight: elisaTheme.navigationBarHeight Image { id: mainIcon source: elisaTheme.playlistIcon 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 { spacing: 0 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 TextMetrics { id: titleHeight text: viewTitleHeight.text font { pointSize: viewTitleHeight.font.pointSize bold: viewTitleHeight.font.bold } } LabelWithToolTip { id: viewTitleHeight text: i18nc("Title of the view of the playlist", "Now Playing") color: myPalette.text font.pointSize: elisaTheme.defaultFontPointSize * 2 Layout.topMargin: elisaTheme.layoutVerticalMargin - } + } LabelWithToolTip { id: playListInfo text: i18np("1 track", "%1 tracks", playListModel.tracksCount) color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter } Item { Layout.fillHeight: true } RowLayout { Layout.bottomMargin: elisaTheme.layoutVerticalMargin CheckBox { id: shuffleOption text: i18n("Shuffle") } CheckBox { id: repeatOption text: i18n("Repeat") } } } } - ScrollView { + ListView { + id: playListView Layout.fillWidth: true Layout.fillHeight: true focus: true - ListView { - id: playListView - - focus: true + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true - TextEdit { - readOnly: true - visible: playListModelDelegate.count === 0 - wrapMode: TextEdit.Wrap + TextEdit { + readOnly: true + visible: playListModelDelegate.count === 0 + wrapMode: TextEdit.Wrap - color: myPalette.text + color: myPalette.text - font.weight: Font.ExtraLight - font.pixelSize: elisaTheme.defaultFontPixelSize * 1.5 + font.weight: Font.ExtraLight + font.pixelSize: elisaTheme.defaultFontPixelSize * 1.5 - text: i18nc("Text shown when play list is empty", "Your play list is empty.\nIn order to start, you can explore your music library with the views on the left.\nUse the available buttons to add your selection.") - anchors.fill: parent - anchors.margins: elisaTheme.layoutHorizontalMargin - } + text: i18nc("Text shown when play list is empty", "Your play list is empty.\nIn order to start, you can explore your music library with the views on the left.\nUse the available buttons to add your selection.") + anchors.fill: parent + anchors.margins: elisaTheme.layoutHorizontalMargin + } - add: Transition { - NumberAnimation { - property: "opacity"; - from: 0; - to: 1; - duration: 100 } - } + add: Transition { + NumberAnimation { + property: "opacity"; + from: 0; + to: 1; + duration: 100 } + } - populate: Transition { - NumberAnimation { - property: "opacity"; - from: 0; - to: 1; - duration: 100 } - } + populate: Transition { + NumberAnimation { + property: "opacity"; + from: 0; + to: 1; + duration: 100 } + } - remove: Transition { - NumberAnimation { - property: "opacity"; - from: 1.0; - to: 0; - duration: 100 } - } + remove: Transition { + NumberAnimation { + property: "opacity"; + from: 1.0; + to: 0; + duration: 100 } + } - displaced: Transition { - NumberAnimation { - properties: "x,y"; - duration: 100; - easing.type: Easing.InOutQuad} - } + displaced: Transition { + NumberAnimation { + properties: "x,y"; + duration: 100; + easing.type: Easing.InOutQuad} + } - model: DelegateModel { - id: playListModelDelegate - model: playListModel + model: DelegateModel { + id: playListModelDelegate + model: playListModel - groups: [ - DelegateModelGroup { name: "selected" } - ] + groups: [ + DelegateModelGroup { name: "selected" } + ] - delegate: DraggableItem { - id: item - placeholderHeight: topItem.placeholderHeight + delegate: DraggableItem { + id: item + placeholderHeight: topItem.placeholderHeight - focus: true + focus: true - PlayListEntry { - id: entry + PlayListEntry { + id: entry - focus: true + focus: true - width: playListView.width + width: playListView.width - index: model.index + index: model.index - isAlternateColor: item.DelegateModel.itemsIndex % 2 + isAlternateColor: item.DelegateModel.itemsIndex % 2 - hasAlbumHeader: model.hasAlbumHeader + hasAlbumHeader: model.hasAlbumHeader - isSingleDiscAlbum: model.isSingleDiscAlbum + isSingleDiscAlbum: model.isSingleDiscAlbum - trackData: model.trackData + trackData: model.trackData - titleDisplay: model.title + titleDisplay: model.title - isValid: model.isValid + isValid: model.isValid - isPlaying: model.isPlaying + isPlaying: model.isPlaying - isSelected: playListView.currentIndex === index + isSelected: playListView.currentIndex === index - containsMouse: item.containsMouse + containsMouse: item.containsMouse - onStartPlayback: topItem.startPlayback() + onStartPlayback: topItem.startPlayback() - onPausePlayback: topItem.pausePlayback() + onPausePlayback: topItem.pausePlayback() - onRemoveFromPlaylist: topItem.playListModel.removeRows(trackIndex, 1) + onRemoveFromPlaylist: topItem.playListModel.removeRows(trackIndex, 1) - onSwitchToTrack: topItem.playListModel.switchTo(trackIndex) - } + onSwitchToTrack: topItem.playListModel.switchTo(trackIndex) + } - draggedItemParent: topItem + draggedItemParent: topItem - onClicked: { - playListView.currentIndex = index - entry.forceActiveFocus() - } + onClicked: { + playListView.currentIndex = index + entry.forceActiveFocus() + } - onDoubleClicked: { - if (model.isValid) { - topItem.playListModel.switchTo(model.index) - topItem.startPlayback() - } + onDoubleClicked: { + if (model.isValid) { + topItem.playListControler.switchTo(model.index) + topItem.startPlayback() } + } - onMoveItemRequested: { - playListModel.move(from, to, 1); - } + onMoveItemRequested: { + playListModel.move(from, to, 1); } } } } Rectangle { id: actionBar Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.preferredHeight: elisaTheme.delegateToolButtonSize color: myPalette.mid RowLayout { id: actionBarLayout anchors.fill: parent Controls1.ToolButton { action: clearPlayList Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Controls1.ToolButton { action: showCurrentTrack Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Controls1.ToolButton { action: loadPlaylist Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Controls1.ToolButton { action: savePlaylist Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Item { Layout.fillWidth: true } } } } } + diff --git a/src/qml/MediaTrackMetadataView.qml b/src/qml/MediaTrackMetadataView.qml index ba7d290a..d8ba602d 100644 --- a/src/qml/MediaTrackMetadataView.qml +++ b/src/qml/MediaTrackMetadataView.qml @@ -1,206 +1,210 @@ /* * 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 QtQuick.Window 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 Dialog { id: trackMetadata property var trackDataHelper + modal: false + closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape + padding: elisaTheme.layoutVerticalMargin + standardButtons: Dialog.Close parent: ApplicationWindow.overlay + width: elisaTheme.trackMetadataWidth + x: (parent.width - width) / 2 y: (parent.height - height) / 2 - modal: false - - standardButtons: Dialog.Close Component.onCompleted: { if (trackDataHelper.hasValidTitle()) trackList.append({"name": i18nc("Track title for track metadata view", "Title:"), "content": trackDataHelper.title}) if (trackDataHelper.hasValidArtist()) trackList.append({"name": i18nc("Track artist for track metadata view", "Artist:"), "content": trackDataHelper.artist}) if (trackDataHelper.hasValidAlbumName()) trackList.append({"name": i18nc("Album name for track metadata view", "Album:"), "content": trackDataHelper.albumName}) if (trackDataHelper.hasValidTrackNumber()) trackList.append({"name": i18nc("Track number for track metadata view", "Track Number:"), "content": trackDataHelper.trackNumber}) if (trackDataHelper.hasValidDiscNumber()) trackList.append({"name": i18nc("Disc number for track metadata view", "Disc Number:"), "content": trackDataHelper.discNumber}) if (trackDataHelper.hasValidAlbumArtist()) trackList.append({"name": i18nc("Album artist for track metadata view", "Album Artist:"), "content": trackDataHelper.albumArtist}) trackList.append({"name": i18nc("Duration label for track metadata view", "Duration:"), "content": trackDataHelper.duration}) if (trackDataHelper.hasValidYear()) trackList.append({"name": i18nc("Year label for track metadata view", "Year:"), "content": trackDataHelper.year}) if (trackDataHelper.hasValidGenre()) trackList.append({"name": i18nc("Genre label for track metadata view", "Genre:"), "content": trackDataHelper.genre}) if (trackDataHelper.hasValidComposer()) trackList.append({"name": i18nc("Composer name for track metadata view", "Composer:"), "content": trackDataHelper.composer}) if (trackDataHelper.hasValidLyricist()) trackList.append({"name": i18nc("Lyricist label for track metadata view", "Lyricist:"), "content": trackDataHelper.lyricist}) if (trackDataHelper.hasValidBitRate()) trackList.append({"name": i18nc("Bit rate label for track metadata view", "Bit Rate:"), "content": trackDataHelper.bitRate + " " + i18nc("Unit of the bit rate in thousand", "kbit/s")}) if (trackDataHelper.hasValidSampleRate()) trackList.append({"name": i18nc("Sample Rate label for track metadata view", "Sample Rate:"), "content": trackDataHelper.sampleRate + " " + i18nc("Unit of the sample rate", "Hz")}) if (trackDataHelper.hasValidChannels()) trackList.append({"name": i18nc("Channels label for track metadata view", "Channels:"), "content": trackDataHelper.channels}) if (trackDataHelper.hasValidComment()) trackList.append({"name": i18nc("Comment label for track metadata view", "Comment:"), "content": trackDataHelper.comment}) trackData.Layout.preferredHeight = textSize.height * trackData.count + trackMetadata.height = textSize.height * (trackData.count + 1 + (rating > -1 ? 1 : 0)) + 3 * elisaTheme.layoutVerticalMargin + footer.height } ColumnLayout { anchors.fill: parent spacing: 0 // This is needed since Dialog doesn't inherit from Item LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true RowLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 Image { source: trackDataHelper.albumCover visible: trackDataHelper.hasValidAlbumCover() width: elisaTheme.coverImageSize height: elisaTheme.coverImageSize fillMode: Image.PreserveAspectFit Layout.preferredHeight: height Layout.preferredWidth: width Layout.minimumHeight: height Layout.minimumWidth: width Layout.maximumHeight: height Layout.maximumWidth: width } ColumnLayout { Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.fillHeight: true Layout.preferredWidth: elisaTheme.trackMetadataWidth spacing: 0 ListView { id: trackData interactive: false Layout.fillWidth: true model: ListModel { id: trackList } delegate: RowLayout { id: delegateRow spacing: 0 LabelWithToolTip { text: name color: myPalette.text horizontalAlignment: Text.AlignRight Layout.preferredWidth: trackData.width * 0.3 Layout.minimumHeight: textSize.height Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } LabelWithToolTip { text: content color: myPalette.text horizontalAlignment: Text.AlignLeft elide: Text.ElideRight Layout.preferredWidth: trackData.width * 0.66 Layout.fillWidth: true Layout.minimumHeight: textSize.height } } } RowLayout { anchors.margins: 0 spacing: 0 visible: trackDataHelper.hasValidRating() Layout.minimumHeight: textSize.height LabelWithToolTip { id: ratingText text: i18nc("Rating label for information panel", "Rating:") color: myPalette.text horizontalAlignment: Text.AlignRight Layout.preferredWidth: trackData.width * 0.3 Layout.rightMargin: elisaTheme.layoutHorizontalMargin } TextMetrics { id: textSize font: ratingText.font text: ratingText.text } RatingStar { id: ratingWidget starRating: trackDataHelper.rating readOnly: true starSize: elisaTheme.ratingStarSize Layout.fillWidth: true } ColorOverlay { source: ratingWidget z: 2 anchors.fill: ratingWidget color: myPalette.text } } } } LabelWithToolTip { id: trackResource text: trackDataHelper.resourceURI color: myPalette.text font.italic: true elide: Text.ElideLeft horizontalAlignment: Text.AlignRight Layout.minimumHeight: textSize.height Layout.preferredWidth: trackDataHelper.hasValidAlbumCover() ? elisaTheme.coverImageSize + elisaTheme.trackMetadataWidth : elisaTheme.trackMetadataWidth + 2 * elisaTheme.layoutHorizontalMargin Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin } } } diff --git a/src/qml/PassiveNotification.qml b/src/qml/PassiveNotification.qml index e67dafac..55111124 100644 --- a/src/qml/PassiveNotification.qml +++ b/src/qml/PassiveNotification.qml @@ -1,152 +1,152 @@ /* * Copyright 2015 Marco Martin * * This program 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 2, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; 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.Controls 2.0 as QQC2 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 MouseArea { id: root z: 9999999 width: background.width height: background.height opacity: 0 enabled: appearAnimation.appear anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 3 * 4 } function showNotification(message, timeout, actionText, callBack) { if (!message) { return; } appearAnimation.running = false; appearAnimation.appear = true; appearAnimation.running = true; if (timeout == "short") { timer.interval = 1000; } else if (timeout == "long") { timer.interval = 4500; } else if (timeout > 0) { timer.interval = timeout; } else { timer.interval = 4500; } messageLabel.text = message ? message : ""; actionButton.text = actionText ? actionText : ""; actionButton.callBack = callBack ? callBack : ""; timer.restart(); } function hideNotification() { appearAnimation.running = false; appearAnimation.appear = false; appearAnimation.running = true; } onClicked: { appearAnimation.appear = false; appearAnimation.running = true; } transform: Translate { id: transform y: root.height } Timer { id: timer interval: 4000 onTriggered: { appearAnimation.appear = false; appearAnimation.running = true; } } ParallelAnimation { id: appearAnimation property bool appear: true NumberAnimation { target: root properties: "opacity" to: appearAnimation.appear ? 1 : 0 duration: 1000 easing.type: Easing.InOutQuad } NumberAnimation { target: transform properties: "y" to: appearAnimation.appear ? 0 : background.height duration: 1000 easing.type: appearAnimation.appear ? Easing.OutQuad : Easing.InQuad } } Item { id: background width: backgroundRect.width + 3 height: backgroundRect.height + 3 Rectangle { id: backgroundRect anchors.centerIn: parent radius: 5 color: myPalette.button opacity: 0.6 width: mainLayout.width + Math.round((height - mainLayout.height)) height: Math.max(mainLayout.height + 5*2, 3*2) } RowLayout { id: mainLayout anchors.centerIn: parent - QQC2.Label { + Label { id: messageLabel Layout.maximumWidth: Math.min(root.parent.width - 20*2, implicitWidth) elide: Text.ElideRight wrapMode: Text.WordWrap maximumLineCount: 4 color: myPalette.buttonText } - QQC2.Button { + Button { id: actionButton property var callBack visible: text != "" onClicked: { appearAnimation.appear = false; appearAnimation.running = true; if (callBack) { callBack(); } } } } layer.enabled: true layer.effect: DropShadow { horizontalOffset: 0 verticalOffset: 0 radius: 3 samples: 32 color: Qt.rgba(0, 0, 0, 0.5) } } } diff --git a/src/qml/TopNotification.qml b/src/qml/TopNotification.qml index 1db11dfb..c3616c45 100644 --- a/src/qml/TopNotification.qml +++ b/src/qml/TopNotification.qml @@ -1,333 +1,342 @@ /* * Copyright 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.Layouts 1.2 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import org.kde.elisa 1.0 FocusScope { id: topItem property var musicManager property bool isViewExpanded: false property int rowHeight: elisaTheme.delegateHeight * 2 visible: Layout.preferredHeight > 0 Rectangle { anchors.fill: parent color: myPalette.mid } Component { id: highlightBar Rectangle { width: notificationColumn.width height: rowHeight color: (topItem.state === 'expanded' ? myPalette.highlight : myPalette.mid) Behavior on color { ColorAnimation { duration: 300 } } } } Rectangle { id: notificationCounter anchors { top: parent.top left: parent.left margins: elisaTheme.layoutVerticalMargin } width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize radius: width / 2 color: myPalette.window opacity: 0.0 visible: opacity > 0 z: 3 ToolButton { anchors.centerIn: parent text: manager.countNotifications onClicked: topItem.isViewExpanded = !topItem.isViewExpanded } } ScrollView { id: expandedView + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + anchors { top: parent.top right: parent.right left: notificationCounter.right bottom: parent.bottom } opacity: 0 visible: opacity > 0 focus: true z: 2 ListView { id: notificationColumn focus: true populate: Transition { NumberAnimation { properties: "opacity"; from: 0; to: 1.0; duration: 300 } } add: Transition { NumberAnimation { properties: "opacity"; from: 0; to: 1.0; duration: 300 } } displaced: Transition { NumberAnimation { properties: "x,y"; duration: 300 } } highlight: highlightBar model: DelegateModel { model: manager delegate: TopNotificationItem { id: currentDelegate focus: true width: ListView.view.width height: rowHeight onEntered: notificationColumn.currentIndex = index onClose: manager.closeNotification(index) onMainButtonClicked: manager.triggerMainButton(index) onSecondaryButtonClicked: manager.triggerSecondaryButton(index) itemMessage: message itemMainButtonText: mainButtonText itemMainButtonIconName: mainButtonIconName itemSecondaryButtonText: secondaryButtonText itemSecondaryButtonIconName: secondaryButtonIconName ListView.onRemove: SequentialAnimation { PropertyAction { target: currentDelegate; property: "ListView.delayRemove"; value: true } ParallelAnimation { NumberAnimation { target: currentDelegate; properties: "height"; from: elisaTheme.delegateHeight * 2; to: 0; duration: 300 } NumberAnimation { target: currentDelegate; properties: "opacity"; from: 1.0; to: 0; duration: 300 } } PropertyAction { target: currentDelegate; property: "ListView.delayRemove"; value: false } } } } function gotoBeginning() { anim.from = notificationColumn.contentY; anim.to = 0; anim.running = true; } NumberAnimation { id: anim; target: notificationColumn; property: "contentY"; duration: 300 } } } TopNotificationManager { id: manager } Connections { target: elisa.musicManager onNewNotification: manager.addNotification(notification) onCloseNotification: manager.closeNotificationById(notificationId) } states: [ State { name: "empty" when: manager.countNotifications === 0 PropertyChanges { target: topItem Layout.preferredHeight: 0 } PropertyChanges { target: notificationCounter opacity: 0.0 } PropertyChanges { target: notificationCounter width: 0 } PropertyChanges { target: notificationColumn interactive: false } PropertyChanges { target: expandedView opacity: 0.0 + ScrollBar.vertical.policy: ScrollBar.AlwaysOff } StateChangeScript { script: notificationColumn.gotoBeginning() } }, State { name: "oneNotification" when: manager.countNotifications === 1 PropertyChanges { target: topItem Layout.preferredHeight: elisaTheme.delegateHeight * 2 } PropertyChanges { target: notificationCounter opacity: 0.0 } PropertyChanges { target: notificationCounter width: 0 } PropertyChanges { target: notificationColumn interactive: false } PropertyChanges { target: expandedView opacity: 1.0 + ScrollBar.vertical.policy: ScrollBar.AlwaysOff } StateChangeScript { script: notificationColumn.gotoBeginning() } }, State { name: "multipleNotifications" when: manager.countNotifications > 1 && !isViewExpanded PropertyChanges { target: topItem Layout.preferredHeight: elisaTheme.delegateHeight * 2 } PropertyChanges { target: notificationCounter opacity: 1.0 } PropertyChanges { target: notificationCounter width: elisaTheme.delegateToolButtonSize } PropertyChanges { target: notificationColumn interactive: false } PropertyChanges { target: expandedView opacity: 1.0 - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded } StateChangeScript { script: notificationColumn.gotoBeginning() } }, State { name: "expanded" when: manager.countNotifications > 1 && isViewExpanded PropertyChanges { target: topItem Layout.preferredHeight: elisaTheme.delegateHeight * 4 } PropertyChanges { target: notificationCounter opacity: 1.0 } PropertyChanges { target: notificationCounter width: elisaTheme.delegateToolButtonSize } PropertyChanges { target: notificationColumn interactive: true } PropertyChanges { target: expandedView opacity: 1.0 + ScrollBar.vertical.policy: ScrollBar.AsNeeded } } ] transitions: [ Transition { SequentialAnimation { - + PropertyAction { + target: expandedView + property: "ScrollBar.vertical.policy" + value: ScrollBar.AlwaysOff + } ParallelAnimation { NumberAnimation { target: topItem duration: 300 property: "Layout.preferredHeight" } NumberAnimation { target: notificationCounter duration: 300 property: "opacity" } NumberAnimation { target: notificationCounter duration: 300 property: "width" } NumberAnimation { target: expandedView duration: 300 property: "opacity" } } } } ] }