diff --git a/autotests/qmltests/tst_NavigationActionBar.qml b/autotests/qmltests/tst_NavigationActionBar.qml index 021fd6d3..245c0100 100644 --- a/autotests/qmltests/tst_NavigationActionBar.qml +++ b/autotests/qmltests/tst_NavigationActionBar.qml @@ -1,302 +1,300 @@ /* * Copyright 2018 Alexander Stippich . */ import QtQuick 2.3 import QtTest 1.0 import "../../src/qml" FocusScope { Item { id: persistentSettings property bool expandedFilterView: false } function i18nc(string1,string2) { return string2 } function i18n(string) { return string; } Item { id: elisaTheme property int layoutHorizontalMargin: 8 property int layoutVerticalMargin: 6 - property int filterClearButtonMargin: layoutVerticalMargin property int ratingStarSize: 15 property int navigationBarHeight: 100 - property int navigationBarFilterHeight: 44 property int smallControlButtonSize: 22 } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } NavigationActionBar { id: navigationActionBar1 mainTitle: 'testTitle1' secondaryTitle: 'secondaryTitle1' enableGoBack: true allowArtistNavigation: true showRating: true expandedFilterView: persistentSettings.expandedFilterView height: 100 } NavigationActionBar { id: navigationActionBar2 mainTitle: 'testTitle2' secondaryTitle: 'secondaryTitle2' enableGoBack: false allowArtistNavigation: false showRating: false expandedFilterView: persistentSettings.expandedFilterView height: 100 y: 200 } TestCase { name: "TestNavigationActionBar" SignalSpy { id: enqueueSpy1 target: navigationActionBar1 signalName: "enqueue" } SignalSpy { id: enqueueSpy2 target: navigationActionBar2 signalName: "enqueue" } SignalSpy { id: replaceAndPlaySpy1 target: navigationActionBar1 signalName: "replaceAndPlay" } SignalSpy { id: replaceAndPlaySpy2 target: navigationActionBar2 signalName: "replaceAndPlay" } SignalSpy { id: goBackSpy1 target: navigationActionBar1 signalName: "goBack" } SignalSpy { id: goBackSpy2 target: navigationActionBar2 signalName: "goBack" } SignalSpy { id: showArtistSpy1 target: navigationActionBar1 signalName: "showArtist" } SignalSpy { id: showArtistSpy2 target: navigationActionBar2 signalName: "showArtist" } when: windowShown function init() { enqueueSpy1.clear(); enqueueSpy2.clear(); replaceAndPlaySpy1.clear(); replaceAndPlaySpy2.clear(); goBackSpy1.clear(); goBackSpy2.clear(); showArtistSpy1.clear(); showArtistSpy2.clear(); persistentSettings.expandedFilterView = false; } function test_goBack() { compare(enqueueSpy1.count, 0); compare(enqueueSpy2.count, 0); compare(replaceAndPlaySpy1.count, 0); compare(replaceAndPlaySpy2.count, 0); compare(goBackSpy1.count, 0); compare(goBackSpy2.count, 0); compare(showArtistSpy1.count, 0); compare(showArtistSpy2.count, 0); var goPreviousButtonItem1 = findChild(navigationActionBar1, "goPreviousButton"); verify(goPreviousButtonItem1 !== null, "valid goPreviousButton") mouseClick(goPreviousButtonItem1); wait(200) compare(goBackSpy1.count, 1); var goPreviousButtonItem2 = findChild(navigationActionBar2, "goPreviousButton"); verify(goPreviousButtonItem2 !== null, "valid goPreviousButton") mouseClick(goPreviousButtonItem2); wait(200) compare(goBackSpy2.count, 0); } function test_enqueue() { compare(enqueueSpy1.count, 0); compare(enqueueSpy2.count, 0); compare(replaceAndPlaySpy1.count, 0); compare(replaceAndPlaySpy2.count, 0); compare(goBackSpy1.count, 0); compare(goBackSpy2.count, 0); compare(showArtistSpy1.count, 0); compare(showArtistSpy2.count, 0); var enqueueButtonItem = findChild(navigationActionBar1, "enqueueButton"); verify(enqueueButtonItem !== null, "valid enqueueButton") mouseClick(enqueueButtonItem); compare(enqueueSpy1.count, 1); } function test_filterState() { compare(enqueueSpy1.count, 0); compare(enqueueSpy2.count, 0); compare(replaceAndPlaySpy1.count, 0); compare(replaceAndPlaySpy2.count, 0); compare(goBackSpy1.count, 0); compare(goBackSpy2.count, 0); compare(showArtistSpy1.count, 0); compare(showArtistSpy2.count, 0); var showFilterButtonItem1 = findChild(navigationActionBar1, "showFilterButton"); verify(showFilterButtonItem1 !== null, "valid showFilterButton") mouseClick(showFilterButtonItem1); compare(navigationActionBar1.state,'expanded') var showFilterButtonItem2 = findChild(navigationActionBar2, "showFilterButton"); verify(showFilterButtonItem2 !== null, "valid showFilterButton") mouseClick(showFilterButtonItem2); compare(navigationActionBar2.expandedFilterView, false) compare(navigationActionBar2.state, 'collapsed') } function test_replaceAndPlay() { compare(enqueueSpy1.count, 0); compare(enqueueSpy2.count, 0); compare(replaceAndPlaySpy1.count, 0); compare(replaceAndPlaySpy2.count, 0); compare(goBackSpy1.count, 0); compare(goBackSpy2.count, 0); compare(showArtistSpy1.count, 0); compare(showArtistSpy2.count, 0); var replaceAndPlayButtonItem = findChild(navigationActionBar1, "replaceAndPlayButton"); verify(replaceAndPlayButtonItem !== null, "valid replaceAndPlayButton") mouseClick(replaceAndPlayButtonItem); compare(replaceAndPlaySpy1.count, 1); } function test_showArtist() { compare(enqueueSpy1.count, 0); compare(enqueueSpy2.count, 0); compare(replaceAndPlaySpy1.count, 0); compare(replaceAndPlaySpy2.count, 0); compare(goBackSpy1.count, 0); compare(goBackSpy2.count, 0); compare(showArtistSpy1.count, 0); compare(showArtistSpy2.count, 0); var showArtistButtonItem1 = findChild(navigationActionBar1, "showArtistButton"); verify(showArtistButtonItem1 !== null, "valid showArtistButton") mouseClick(showArtistButtonItem1); compare(showArtistSpy1.count, 1); var showArtistButtonItem2 = findChild(navigationActionBar2, "showArtistButton"); verify(showArtistButtonItem2 !== null, "valid showArtistButton") mouseClick(showArtistButtonItem2); compare(showArtistSpy2.count, 0); } function test_filterRating() { persistentSettings.expandedFilterView = true; wait(200); var ratingFilterItem1 = findChild(navigationActionBar1, "ratingFilter"); verify(ratingFilterItem1 !== null, "valid ratingFilter") mouseClick(ratingFilterItem1,1); compare(navigationActionBar1.filterRating, 2); mouseClick(ratingFilterItem1,1); compare(navigationActionBar1.filterRating, 0); mouseClick(ratingFilterItem1,1 + elisaTheme.ratingStarSize); compare(navigationActionBar1.filterRating, 4); mouseClick(ratingFilterItem1,1 + elisaTheme.ratingStarSize); compare(navigationActionBar1.filterRating, 0); mouseClick(ratingFilterItem1,1 + 2 * elisaTheme.ratingStarSize); compare(navigationActionBar1.filterRating, 6); mouseClick(ratingFilterItem1,1 + 2 * elisaTheme.ratingStarSize); compare(navigationActionBar1.filterRating, 0); mouseClick(ratingFilterItem1,1 + 3 * elisaTheme.ratingStarSize); compare(navigationActionBar1.filterRating, 8); mouseClick(ratingFilterItem1,1 + 3 * elisaTheme.ratingStarSize); compare(navigationActionBar1.filterRating, 0); mouseClick(ratingFilterItem1,1 + 4 * elisaTheme.ratingStarSize); compare(navigationActionBar1.filterRating, 10); mouseClick(ratingFilterItem1,1 + 4 * elisaTheme.ratingStarSize); compare(navigationActionBar1.filterRating, 0); var ratingFilterItem2 = findChild(navigationActionBar2, "ratingFilter"); verify(ratingFilterItem2 !== null, "valid ratingFilter") mouseClick(ratingFilterItem2,1); compare(navigationActionBar2.filterRating, 0); mouseClick(ratingFilterItem2,1 + elisaTheme.ratingStarSize); compare(navigationActionBar2.filterRating, 0); mouseClick(ratingFilterItem2,1 + 2 * elisaTheme.ratingStarSize); compare(navigationActionBar2.filterRating, 0); mouseClick(ratingFilterItem2,1 + 3 * elisaTheme.ratingStarSize); compare(navigationActionBar2.filterRating, 0); mouseClick(ratingFilterItem2,1 + 4 * elisaTheme.ratingStarSize); compare(navigationActionBar2.filterRating, 0); } function test_filterText() { navigationActionBar1.expandedFilterView = true navigationActionBar2.expandedFilterView = false wait(300) var textsFilterItem1 = findChild(navigationActionBar1, "filterTextInput"); verify(textsFilterItem1 !== null, "valid filterTextInput") textsFilterItem1.focus = false compare(textsFilterItem1.focus, false); mouseClick(textsFilterItem1); compare(textsFilterItem1.focus, true); keyClick(Qt.Key_T); keyClick(Qt.Key_E); keyClick(Qt.Key_S); keyClick(Qt.Key_T); compare(navigationActionBar1.filterText, 'test'); wait(300) mouseClick(textsFilterItem1,textsFilterItem1.width - 20); compare(navigationActionBar1.filterText, ""); } } } diff --git a/autotests/qmltests/tst_PlayListEntry.qml b/autotests/qmltests/tst_PlayListEntry.qml index 4e4aeaad..2a1e7083 100644 --- a/autotests/qmltests/tst_PlayListEntry.qml +++ b/autotests/qmltests/tst_PlayListEntry.qml @@ -1,125 +1,124 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.3 import QtTest 1.0 import "../../src/qml" import org.kde.elisa 1.0 Item { height: 50 width: 300 PlayListEntry { id: playListEntry height: 50 width: 300 isAlternateColor: false isSingleDiscAlbum: false title: "hello" isValid: true isPlaying: MediaPlayList.IsPaused isSelected: true containsMouse: false function i18nc() { return "" } SystemPalette { id: myPalette } Item { id: elisaTheme property int layoutHorizontalMargin: 8 - property int smallDelegateToolButtonSize: 20 property int ratingStarSize: 15 property int playListDelegateHeight: 28 property int smallControlButtonSize: 22 } } TestCase { name: "TestPlayListEntry" SignalSpy { id: startPlaybackSpy target: playListEntry signalName: "startPlayback" } SignalSpy { id: pausePlaybackSpy target: playListEntry signalName: "pausePlayback" } SignalSpy { id: removeFromPlaylistSpy target: playListEntry signalName: "removeFromPlaylist" } SignalSpy { id: switchToTrackSpy target: playListEntry signalName: "switchToTrack" } when: windowShown function init() { startPlaybackSpy.clear(); pausePlaybackSpy.clear(); removeFromPlaylistSpy.clear(); switchToTrackSpy.clear(); } function test_playnow_click() { compare(startPlaybackSpy.count, 0); compare(pausePlaybackSpy.count, 0); compare(removeFromPlaylistSpy.count, 0); compare(switchToTrackSpy.count, 0); playListEntry.forceActiveFocus(); compare(startPlaybackSpy.count, 0); compare(pausePlaybackSpy.count, 0); compare(removeFromPlaylistSpy.count, 0); compare(switchToTrackSpy.count, 0); var playPauseButtonItem = findChild(playListEntry, "playPauseButton"); verify(playPauseButtonItem !== null, "valid playPauseButton") mouseMove(playPauseButtonItem, playPauseButtonItem.width / 2, playPauseButtonItem.height / 2); wait(300); mouseClick(playPauseButtonItem); compare(startPlaybackSpy.count, 1); compare(pausePlaybackSpy.count, 0); compare(removeFromPlaylistSpy.count, 0); compare(switchToTrackSpy.count, 1); } } } diff --git a/src/qml/BaseTheme.qml b/src/qml/BaseTheme.qml index 00bd6f54..3f4f60f7 100644 --- a/src/qml/BaseTheme.qml +++ b/src/qml/BaseTheme.qml @@ -1,106 +1,85 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 Item { property string defaultAlbumImage: 'image://icon/media-optical-audio' property string defaultArtistImage: 'image://icon/view-media-artist' property string defaultBackgroundImage: 'qrc:///background.png' property string nowPlayingIcon: 'image://icon/view-media-lyrics' property string artistIcon: 'image://icon/view-media-artist' property string albumIcon: 'image://icon/view-media-album-cover' property string albumCoverIcon: 'image://icon/media-optical-audio' property string playlistIcon: 'image://icon/view-media-playlist' property string tracksIcon: 'image://icon/view-media-track' property string genresIcon: 'image://icon/view-media-genre' property string clearIcon: 'image://icon/edit-clear' property string recentlyPlayedTracksIcon: 'image://icon/media-playlist-play' property string frequentlyPlayedTracksIcon: 'image://icon/view-media-playcount' property string pausedIndicatorIcon: 'image://icon/media-playback-paused' property string playingIndicatorIcon: 'image://icon/media-playback-playing' property string ratingIcon: 'image://icon/rating' property string ratingUnratedIcon: 'image://icon/rating-unrated' property string errorIcon: 'image://icon/error' property string folderIcon: 'image://icon/document-open-folder' property int layoutHorizontalMargin: 8 property int layoutVerticalMargin: 6 - property int delegateHeight: 28 - // FIXME: don't hardcode these; derive them from the layouts they're used in - FontMetrics { - id: playListAuthorTextHeight - font.weight: Font.Light - } - FontMetrics { - id: playListAlbumTextHeight - font.weight: Font.Bold - font.pointSize: Math.round(fontSize.font.pointSize * 1.4) - } FontMetrics { id: playListTrackTextHeight font.weight: Font.Bold } property int playListDelegateHeight: (playListTrackTextHeight.height > 28) ? playListTrackTextHeight.height : 28 - property int trackDelegateHeight: elisaTheme.layoutVerticalMargin + fontSize.height * 2 // END FIXME property int playListAlbumArtSize: 70 property int coverImageSize: 180 property int contextCoverImageSize: 100 property int smallImageSize: 32 - property int maximumMetadataWidth: 300 - property int tooltipRadius: 3 property int shadowOffset: 2 property int delegateToolButtonSize: 34 - property int smallDelegateToolButtonSize: 20 property int ratingStarSize: 15 property int mediaPlayerControlHeight: 42 - property int mediaPlayerHorizontalMargin: 10 property real mediaPlayerControlOpacity: 0.6 property int smallControlButtonSize: 22 property int volumeSliderWidth: 100 property int dragDropPlaceholderHeight: 28 - property int navigationBarHeight: 100 - property int navigationBarFilterHeight: 44 - property int gridDelegateSize: 170 property int viewSelectorDelegateHeight: 24 - property int filterClearButtonMargin: layoutVerticalMargin - property int headerToolbarHeight: 48 property int footerToolbarHeight: 30 property int viewSelectorSmallSizeThreshold: 800 Label { id: fontSize } } diff --git a/src/qml/DataListView.qml b/src/qml/DataListView.qml index c0a91552..66a3874e 100644 --- a/src/qml/DataListView.qml +++ b/src/qml/DataListView.qml @@ -1,247 +1,245 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.3 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: viewHeader property var viewType property var filterType property alias isSubPage: listView.isSubPage property alias mainTitle: listView.mainTitle property alias secondaryTitle: listView.secondaryTitle property int databaseId property alias showSection: listView.showSection property alias expandedFilterView: listView.expandedFilterView property alias image: listView.image property var modelType property alias sortRole: proxyModel.sortRole property var sortAscending property bool displaySingleAlbum: false property alias radioCase: listView.showCreateRadioButton function openMetaDataView(databaseId, url, entryType) { metadataLoader.setSource("MediaTrackMetadataView.qml", { "fileName": url, "modelType": entryType, "showImage": entryType !== ElisaUtils.Radio, "showTrackFileName": entryType !== ElisaUtils.Radio, "showDeleteButton": entryType === ElisaUtils.Radio, "showApplyButton": entryType === ElisaUtils.Radio, "editableMetadata": entryType === ElisaUtils.Radio, "widthIndex": (entryType === ElisaUtils.Radio ? 4.5 : 2.8), }); metadataLoader.active = true } function openCreateRadioView() { metadataLoader.setSource("MediaTrackMetadataView.qml", { "modelType": ElisaUtils.Radio, "isCreation": true, "showImage": false, "showTrackFileName": false, "showDeleteButton": false, "showApplyButton": true, "editableMetadata": true, "widthIndex": 4.5, }); metadataLoader.active = true } DataModel { id: realModel } AllTracksProxyModel { id: proxyModel sourceModel: realModel playList: elisa.mediaPlayList } Loader { id: metadataLoader active: false onLoaded: item.show() } Component { id: albumDelegate ListBrowserDelegate { id: entry width: listView.delegateWidth - height: elisaTheme.delegateHeight focus: true trackUrl: model.url dataType: model.dataType title: model.display ? model.display : '' artist: model.artist ? model.artist : '' album: model.album ? model.album : '' albumArtist: model.albumArtist ? model.albumArtist : '' duration: model.duration ? model.duration : '' imageUrl: model.imageUrl ? model.imageUrl : '' trackNumber: model.trackNumber ? model.trackNumber : -1 discNumber: model.discNumber ? model.discNumber : -1 rating: model.rating isSelected: listView.currentIndex === index isAlternateColor: (index % 2) === 1 detailedView: false onEnqueue: elisa.mediaPlayList.enqueue(url, entryType, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: elisa.mediaPlayList.enqueue(url, entryType, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) onClicked: listView.currentIndex = index onActiveFocusChanged: { if (activeFocus && listView.currentIndex !== index) { listView.currentIndex = index } } onCallOpenMetaDataView: { openMetaDataView(databaseId, url, entryType) } } } Component { id: detailedTrackDelegate ListBrowserDelegate { id: entry width: listView.delegateWidth - height: elisaTheme.trackDelegateHeight focus: true trackUrl: model.url dataType: model.dataType title: model.display ? model.display : '' artist: model.artist ? model.artist : '' album: model.album ? model.album : '' albumArtist: model.albumArtist ? model.albumArtist : '' duration: model.duration ? model.duration : '' imageUrl: model.imageUrl ? model.imageUrl : '' trackNumber: model.trackNumber ? model.trackNumber : -1 discNumber: model.discNumber ? model.discNumber : -1 rating: model.rating hideDiscNumber: model.isSingleDiscAlbum isSelected: listView.currentIndex === index isAlternateColor: (index % 2) === 1 onEnqueue: elisa.mediaPlayList.enqueue(url, entryType, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: elisa.mediaPlayList.enqueue(url, entryType, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) onClicked: { listView.currentIndex = index entry.forceActiveFocus() } onCallOpenMetaDataView: { openMetaDataView(databaseId, url, entryType) } } } ListBrowserView { id: listView focus: true anchors.fill: parent contentModel: proxyModel delegate: (displaySingleAlbum ? albumDelegate : detailedTrackDelegate) enableSorting: !displaySingleAlbum allowArtistNavigation: isSubPage showCreateRadioButton: modelType === ElisaUtils.Radio showEnqueueButton: modelType !== ElisaUtils.Radio onShowArtist: { viewManager.openChildView(secondaryTitle, '', elisaTheme.artistIcon, 0, ElisaUtils.Artist, ViewManager.NoDiscHeaders) } onGoBack: viewManager.goBack() Loader { anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height visible: realModel.isBusy active: realModel.isBusy sourceComponent: BusyIndicator { anchors.centerIn: parent } } } Connections { target: elisa onMusicManagerChanged: realModel.initialize(elisa.musicManager, elisa.musicManager.viewDatabase, modelType) } Connections { target: listView.navigationBar onCreateRadio: { openCreateRadioView() } } Component.onCompleted: { if (elisa.musicManager) { realModel.initialize(elisa.musicManager, elisa.musicManager.viewDatabase, modelType, filterType, mainTitle, secondaryTitle, databaseId) } if (sortAscending === ViewManager.SortAscending) { proxyModel.sortModel(Qt.AscendingOrder) } else if (sortAscending === ViewManager.SortDescending) { proxyModel.sortModel(Qt.DescendingOrder) } } } diff --git a/src/qml/ListBrowserDelegate.qml b/src/qml/ListBrowserDelegate.qml index 3c9f0e68..32186ea8 100644 --- a/src/qml/ListBrowserDelegate.qml +++ b/src/qml/ListBrowserDelegate.qml @@ -1,431 +1,438 @@ /* * Copyright 2016-2017 Matthieu Gallien * Copyright 2017 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.3 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: mediaTrack property url trackUrl property var dataType property string title property string artist property string album property string albumArtist property string duration property url imageUrl property int trackNumber property int discNumber property int rating property bool hideDiscNumber property bool isSelected property bool isAlternateColor property bool detailedView: true signal clicked() signal enqueue(var url, var entryType, var name) signal replaceAndPlay(var url, var entryType, var name) signal callOpenMetaDataView(var url, var entryType) Accessible.role: Accessible.ListItem Accessible.name: title Accessible.description: title Keys.onReturnPressed: enqueue(trackUrl, dataType, title) Keys.onEnterPressed: enqueue(trackUrl, dataType, title) + TextMetrics { + id: mainLabelSize + font: mainLabel.font + text: mainLabel.text + } + + property int singleLineHeight: elisaTheme.layoutVerticalMargin * 2 + mainLabelSize.height + height: singleLineHeight + (detailedView ? mainLabelSize.height : 0) + Rectangle { id: rowRoot anchors.fill: parent z: 1 color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } MouseArea { id: hoverArea anchors.fill: parent z: 2 hoverEnabled: true acceptedButtons: Qt.LeftButton onClicked: { mediaTrack.clicked() } onDoubleClicked: enqueue(trackUrl, dataType, title) RowLayout { anchors.fill: parent spacing: 0 LabelWithToolTip { id: mainLabel visible: !detailedView text: { if (trackNumber !== 0 && trackNumber !== -1 && trackNumber !== undefined) { if (albumArtist !== undefined && artist !== albumArtist) return i18nc("%1: track number. %2: track title. %3: artist name", "%1 - %2 - %3", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title, artist); else return i18nc("%1: track number. %2: track title.", "%1 - %2", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); } else { if (albumArtist !== undefined && artist !== albumArtist) return i18nc("%1: track title. %2: artist name", "%1 - %2", title, artist); else return i18nc("%1: track title", "%1", title); } } elide: Text.ElideRight horizontalAlignment: Text.AlignLeft color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true Layout.leftMargin: { if (!LayoutMirroring.enabled) return (!hideDiscNumber ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) else return 0 } Layout.rightMargin: { if (LayoutMirroring.enabled) return (!hideDiscNumber ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) else return 0 } } - Item { - Layout.preferredHeight: mediaTrack.height * 0.9 - Layout.preferredWidth: mediaTrack.height * 0.9 - - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Image { + id: coverImageElement - visible: detailedView + Layout.preferredHeight: mediaTrack.height - elisaTheme.layoutVerticalMargin + Layout.preferredWidth: mediaTrack.height - elisaTheme.layoutVerticalMargin + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutVerticalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutVerticalMargin : 0 - Image { - id: coverImageElement + Layout.alignment: Qt.AlignCenter - anchors.fill: parent + visible: detailedView - sourceSize.width: mediaTrack.height * 0.9 - sourceSize.height: mediaTrack.height * 0.9 - fillMode: Image.PreserveAspectFit - smooth: true + sourceSize.width: mediaTrack.height - elisaTheme.layoutVerticalMargin + sourceSize.height: mediaTrack.height - elisaTheme.layoutVerticalMargin + fillMode: Image.PreserveAspectFit + smooth: true - source: (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) + source: (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) - asynchronous: true + asynchronous: true - layer.enabled: imageUrl != '' + layer.enabled: imageUrl != '' - layer.effect: DropShadow { - source: coverImageElement + layer.effect: DropShadow { + source: coverImageElement - radius: 10 - spread: 0.1 - samples: 21 + radius: 10 + spread: 0.1 + samples: 21 - color: myPalette.shadow - } + color: myPalette.shadow + } - onStatusChanged: { - if (coverImageElement.status === Image.Error) { - source = 'image://icon/media-optical-audio' - } + onStatusChanged: { + if (coverImageElement.status === Image.Error) { + source = 'image://icon/media-optical-audio' } } } ColumnLayout { visible: detailedView Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignLeft spacing: 0 LabelWithToolTip { id: mainLabelDetailed level: 4 text: { if (trackNumber >= 0) { return i18nc("%1: track number. %2: track title", "%1 - %2", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); } else { return title; } } horizontalAlignment: Text.AlignLeft color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 elide: Text.ElideRight } Item { Layout.fillHeight: true } LabelWithToolTip { id: artistLabel text: { var labelText = "" if (artist) { labelText += artist } if (album !== '') { labelText += ' - ' + album if (!hideDiscNumber && discNumber !== -1) { labelText += ' - CD ' + discNumber } } return labelText; } horizontalAlignment: Text.AlignLeft opacity: 0.6 color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignBottom Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 2 elide: Text.ElideRight } } Loader { id: hoverLoader active: false Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: 10 z: 1 opacity: 0 sourceComponent: Row { anchors.centerIn: parent FlatButtonWithToolTip { id: detailsButton - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: singleLineHeight + width: singleLineHeight text: i18nc("Show track metadata", "View Details") icon.name: "help-about" onClicked: callOpenMetaDataView(trackUrl, dataType) } FlatButtonWithToolTip { id: enqueueButton - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: singleLineHeight + width: singleLineHeight text: i18nc("Enqueue current track", "Enqueue") icon.name: "list-add" onClicked: enqueue(trackUrl, dataType, title) } FlatButtonWithToolTip { id: clearAndEnqueueButton scale: LayoutMirroring.enabled ? -1 : 1 - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: singleLineHeight + width: singleLineHeight text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") icon.name: "media-playback-start" onClicked: replaceAndPlay(trackUrl, dataType, title) } } } RatingStar { id: ratingWidget starSize: elisaTheme.ratingStarSize starRating: rating Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin } LabelWithToolTip { id: durationLabel text: duration font.weight: Font.Light color: myPalette.text horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } Connections { target: mediaTrack onImageUrlChanged: { if (coverImageElement.source !== imageUrl) { coverImageElement.source = (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) } } } states: [ State { name: 'notSelected' when: !mediaTrack.activeFocus && !hoverArea.containsMouse && !mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } PropertyChanges { target: rowRoot color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } PropertyChanges { target: rowRoot opacity: 1 } }, State { name: 'hovered' when: !mediaTrack.activeFocus && hoverArea.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.highlight } PropertyChanges { target: rowRoot opacity: 0.2 } }, State { name: 'selected' when: !mediaTrack.activeFocus && mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.mid } PropertyChanges { target: rowRoot opacity: 1. } }, State { name: 'focused' when: mediaTrack.activeFocus PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.highlight } PropertyChanges { target: rowRoot opacity: 0.6 } } ] } diff --git a/src/qml/ListBrowserView.qml b/src/qml/ListBrowserView.qml index 2a3eeb0d..b508270a 100644 --- a/src/qml/ListBrowserView.qml +++ b/src/qml/ListBrowserView.qml @@ -1,144 +1,143 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: listView property bool isSubPage: false property alias mainTitle: navigationBar.mainTitle property alias secondaryTitle: navigationBar.secondaryTitle property alias image: navigationBar.image property int databaseId property alias delegate: contentDirectoryView.delegate property bool showSection: false property alias contentModel: contentDirectoryView.model property alias expandedFilterView: navigationBar.expandedFilterView property alias showRating: navigationBar.showRating property alias allowArtistNavigation: navigationBar.allowArtistNavigation property var delegateWidth: scrollBar.visible ? contentDirectoryView.width - scrollBar.width : contentDirectoryView.width property alias currentIndex: contentDirectoryView.currentIndex property alias enableSorting: navigationBar.enableSorting property var stackView property alias showEnqueueButton: navigationBar.showEnqueueButton property alias showCreateRadioButton: navigationBar.showCreateRadioButton property alias navigationBar: navigationBar signal goBack() signal showArtist(var name) SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar enableGoBack: listView.isSubPage sortOrder: contentModel.sortedAscending 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: listView.goBack() onShowArtist: listView.showArtist(listView.contentModel.sourceModel.author) onSort: contentModel.sortModel(order) } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true Layout.margins: 2 ListView { id: contentDirectoryView anchors.fill: parent Accessible.role: Accessible.List Accessible.name: mainTitle Accessible.description: mainTitle activeFocusOnTab: true keyNavigationEnabled: true currentIndex: -1 section.property: (showSection ? 'discNumber' : '') section.criteria: ViewSection.FullString section.labelPositioning: ViewSection.InlineLabels section.delegate: TracksDiscHeader { discNumber: section width: scrollBar.visible ? (!LayoutMirroring.enabled ? contentDirectoryView.width - scrollBar.width : contentDirectoryView.width) : contentDirectoryView.width - height: elisaTheme.delegateHeight } ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds clip: true ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } onCountChanged: if (count === 0) { currentIndex = -1; } } } } } diff --git a/src/qml/TracksDiscHeader.qml b/src/qml/TracksDiscHeader.qml index afcd7f6e..96267932 100644 --- a/src/qml/TracksDiscHeader.qml +++ b/src/qml/TracksDiscHeader.qml @@ -1,42 +1,49 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 Rectangle { property int discNumber color: myPalette.mid + height: discHeaderSize.height + elisaTheme.layoutVerticalMargin * 2 + + TextMetrics { + id: discHeaderSize + font: discHeaderLabel.font + text: discHeaderLabel.text + } LabelWithToolTip { id: discHeaderLabel text: i18nc("disc header text when showing an album", "Disc %1", discNumber) font.weight: Font.Bold font.italic: true color: myPalette.text verticalAlignment: Text.AlignVCenter anchors.fill: parent anchors.leftMargin: elisaTheme.layoutHorizontalMargin elide: Text.ElideRight } }