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
}
}