diff --git a/autotests/qmltests/tst_GridBrowserDelegate.qml b/autotests/qmltests/tst_GridBrowserDelegate.qml index 94961c0d..775af92f 100644 --- a/autotests/qmltests/tst_GridBrowserDelegate.qml +++ b/autotests/qmltests/tst_GridBrowserDelegate.qml @@ -1,314 +1,313 @@ /* * 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" FocusScope { GridBrowserDelegate { id: delegateItem height: 160 width: 100 mainText: "hello" secondaryText: "hello secondary" databaseId: 1 function i18nc(string1,string2) { return string2 } Item { id: myPalette property color text property color shadow property color highlight } Item { id: elisaTheme property int delegateToolButtonSize: 34 - property int layoutVerticalMargin: 6 } } Item { id: otherItem focus: true width: 50 height: 50 anchors.top: delegateItem.bottom } TestCase { name: "TestGridBrowserDelegate" SignalSpy { id: enqueueSpy target: delegateItem signalName: "enqueue" } SignalSpy { id: replaceAndPlaySpy target: delegateItem signalName: "replaceAndPlay" } SignalSpy { id: openSpy target: delegateItem signalName: "open" } SignalSpy { id: selectedSpy target: delegateItem signalName: "selected" } when: windowShown function init() { otherItem.focus = true enqueueSpy.clear(); replaceAndPlaySpy.clear(); openSpy.clear(); selectedSpy.clear(); } function test_focus() { compare(enqueueSpy.count, 0); compare(replaceAndPlaySpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, false, "delegateItem.focus"); delegateItem.forceActiveFocus(); compare(enqueueSpy.count, 0); compare(replaceAndPlaySpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); var replaceAndPlayButtonItem = findChild(delegateItem, "replaceAndPlayButton"); var enqueueButtonItem = findChild(delegateItem, "enqueueButton"); verify(replaceAndPlayButtonItem !== null, "valid replaceAndPlayButton") verify(enqueueButtonItem !== null, "valid enqueueButton") compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); keyClick(Qt.Key_Tab) compare(enqueueSpy.count, 0) compare(replaceAndPlaySpy.count, 0) compare(openSpy.count, 0) compare(selectedSpy.count, 0) compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); keyClick(Qt.Key_Tab) compare(enqueueSpy.count, 0) compare(replaceAndPlaySpy.count, 0) compare(openSpy.count, 0) compare(selectedSpy.count, 0) compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, true, "enqueueButton.focus"); enqueueButtonItem.focus = false; replaceAndPlayButtonItem.focus = false; otherItem.focus = true; compare(enqueueSpy.count, 0) compare(replaceAndPlaySpy.count, 0) compare(openSpy.count, 0) compare(selectedSpy.count, 0) compare(delegateItem.focus, false, "delegateItem.focus"); } function test_mouse_clicks() { compare(delegateItem.focus, false, "delegateItem.focus"); compare(enqueueSpy.count, 0); compare(replaceAndPlaySpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); mouseMove(delegateItem, 0, 0); compare(delegateItem.focus, false, "delegateItem.focus"); compare(enqueueSpy.count, 0); compare(replaceAndPlaySpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); var replaceAndPlayButtonItem = findChild(delegateItem, "replaceAndPlayButton"); var enqueueButtonItem = findChild(delegateItem, "enqueueButton"); verify(replaceAndPlayButtonItem !== null, "valid replaceAndPlayButton") verify(enqueueButtonItem !== null, "valid enqueueButton") compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); mouseClick(delegateItem, 0, 0); compare(delegateItem.focus, false, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueSpy.count, 0); compare(replaceAndPlaySpy.count, 0); compare(openSpy.count, 1); compare(selectedSpy.count, 0); mouseMove(enqueueButtonItem); mouseClick(enqueueButtonItem); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueSpy.count, 1); compare(replaceAndPlaySpy.count, 0); compare(openSpy.count, 1); compare(selectedSpy.count, 0); mouseMove(replaceAndPlayButtonItem); mouseClick(replaceAndPlayButtonItem); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueSpy.count, 1); compare(replaceAndPlaySpy.count, 1); compare(openSpy.count, 1); compare(selectedSpy.count, 0); mouseMove(otherItem, 0, 0); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueSpy.count, 1); compare(replaceAndPlaySpy.count, 1); compare(openSpy.count, 1); compare(selectedSpy.count, 0); mouseClick(delegateItem, 0, 0); openSpy.wait(150); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueSpy.count, 1); compare(replaceAndPlaySpy.count, 1); compare(openSpy.count, 2); compare(selectedSpy.count, 0); enqueueButtonItem.focus = false; replaceAndPlayButtonItem.focus = false; otherItem.focus = true; } function test_keyboard() { compare(enqueueSpy.count, 0); compare(replaceAndPlaySpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, false, "delegateItem.focus"); delegateItem.forceActiveFocus(); compare(enqueueSpy.count, 0); compare(replaceAndPlaySpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); var replaceAndPlayButtonItem = findChild(delegateItem, "replaceAndPlayButton"); var enqueueButtonItem = findChild(delegateItem, "enqueueButton"); verify(replaceAndPlayButtonItem !== null, "valid replaceAndPlayButton") verify(enqueueButtonItem !== null, "valid enqueueButton") compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); keyClick(Qt.Key_Tab); compare(replaceAndPlaySpy.count, 0); compare(enqueueSpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); keyClick(Qt.Key_Enter); compare(replaceAndPlaySpy.count, 1); compare(enqueueSpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); keyClick(Qt.Key_Return); compare(replaceAndPlaySpy.count, 2); compare(enqueueSpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); keyClick(Qt.Key_Tab); compare(replaceAndPlaySpy.count, 2); compare(enqueueSpy.count, 0); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, true, "enqueueButton.focus"); keyClick(Qt.Key_Enter); compare(replaceAndPlaySpy.count, 2); compare(enqueueSpy.count, 1); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, true, "enqueueButton.focus"); keyClick(Qt.Key_Return); compare(replaceAndPlaySpy.count, 2); compare(enqueueSpy.count, 2); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, false, "enqueueButton.focus"); compare(enqueueButtonItem.focus, true, "enqueueButton.focus"); keyClick(Qt.Key_Tab); compare(replaceAndPlaySpy.count, 2); compare(enqueueSpy.count, 2); compare(openSpy.count, 0); compare(selectedSpy.count, 0); compare(delegateItem.focus, true, "delegateItem.focus"); compare(replaceAndPlayButtonItem.focus, true, "enqueueButton.focus"); compare(enqueueButtonItem.focus, false, "enqueueButton.focus"); enqueueButtonItem.focus = false; replaceAndPlayButtonItem.focus = false; otherItem.focus = true; } } } diff --git a/autotests/qmltests/tst_NavigationActionBar.qml b/autotests/qmltests/tst_NavigationActionBar.qml index da2a3fdc..eaaf43c4 100644 --- a/autotests/qmltests/tst_NavigationActionBar.qml +++ b/autotests/qmltests/tst_NavigationActionBar.qml @@ -1,298 +1,297 @@ /* * 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 ratingStarSize: 16 } 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/src/qml/BaseTheme.qml b/src/qml/BaseTheme.qml index a51e3dab..339a1cd1 100644 --- a/src/qml/BaseTheme.qml +++ b/src/qml/BaseTheme.qml @@ -1,74 +1,66 @@ /* * 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 playListAlbumArtSize: 60 property int coverImageSize: 180 property int contextCoverImageSize: 100 property int smallImageSize: 32 property int tooltipRadius: 3 property int shadowOffset: 2 property int delegateToolButtonSize: 34 property int mediaPlayerControlHeight: 42 property real mediaPlayerControlOpacity: 0.6 property int volumeSliderWidth: 100 property int dragDropPlaceholderHeight: 28 property int gridDelegateSize: 170 property int viewSelectorDelegateHeight: 24 property int headerToolbarHeight: 48 property int footerToolbarHeight: 30 property int viewSelectorSmallSizeThreshold: 800 - - Label { - id: fontSize - } } diff --git a/src/qml/BasicPlayListAlbumHeader.qml b/src/qml/BasicPlayListAlbumHeader.qml index 1493f6fa..9887eb28 100644 --- a/src/qml/BasicPlayListAlbumHeader.qml +++ b/src/qml/BasicPlayListAlbumHeader.qml @@ -1,106 +1,107 @@ /* * 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 import QtQuick.Controls 2.3 +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 import QtQuick 2.0 Rectangle { id: background property var headerData property string album: headerData[0] property string albumArtist: headerData[1] property url imageUrl: headerData[2] property alias textColor: mainLabel.color property alias backgroundColor: background.color implicitHeight: contentLayout.implicitHeight color: myPalette.midlight RowLayout { id: contentLayout anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top // No bottom anchor so it can grow vertically - spacing: elisaTheme.layoutHorizontalMargin / 4 + spacing: Kirigami.Units.smallSpacing ImageWithFallback { Layout.preferredWidth: elisaTheme.playListAlbumArtSize Layout.preferredHeight: elisaTheme.playListAlbumArtSize - Layout.margins: elisaTheme.layoutHorizontalMargin + Layout.margins: Kirigami.Units.largeSpacing source: imageUrl fallback: elisaTheme.defaultAlbumImage sourceSize.width: elisaTheme.playListAlbumArtSize sourceSize.height: elisaTheme.playListAlbumArtSize fillMode: Image.PreserveAspectFit asynchronous: true } ColumnLayout { id: albumHeaderTextColumn Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: !LayoutMirroring.enabled ? - elisaTheme.layoutHorizontalMargin / 4 : 0 - Layout.rightMargin: LayoutMirroring.enabled ? - elisaTheme.layoutHorizontalMargin / 4 : 0 - Layout.topMargin: elisaTheme.layoutVerticalMargin - Layout.bottomMargin: elisaTheme.layoutVerticalMargin + Layout.leftMargin: !LayoutMirroring.enabled ? - Kirigami.Units.smallSpacing : 0 + Layout.rightMargin: LayoutMirroring.enabled ? - Kirigami.Units.smallSpacing : 0 + Layout.topMargin: Kirigami.Units.smallSpacing + Layout.bottomMargin: Kirigami.Units.smallSpacing - spacing: elisaTheme.layoutVerticalMargin + spacing: Kirigami.Units.smallSpacing LabelWithToolTip { id: mainLabel Layout.fillWidth: true Layout.alignment: Qt.AlignBottom | Qt.AlignLeft text: album level: 2 font.weight: Font.Bold elide: Text.ElideRight wrapMode: Text.WordWrap maximumLineCount: 2 } LabelWithToolTip { id: authorLabel Layout.fillWidth: true Layout.alignment: Qt.AlignTop | Qt.AlignLeft text: albumArtist color: mainLabel.color elide: Text.ElideRight wrapMode: Text.WordWrap maximumLineCount: 2 } } } } diff --git a/src/qml/ContextView.qml b/src/qml/ContextView.qml index b14bfa46..0435f99b 100644 --- a/src/qml/ContextView.qml +++ b/src/qml/ContextView.qml @@ -1,278 +1,277 @@ /* * Copyright 2016 Matthieu Gallien * Copyright 2019 Nate Graham * * 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.Window 2.2 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 - +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: topItem property var viewType property int databaseId: 0 property var trackType property alias title: titleLabel.text property string albumName: '' property string artistName: '' property url albumArtUrl: '' property url fileUrl: '' TrackContextMetaDataModel { id: metaDataModel manager: elisa.musicManager } ColumnLayout { anchors.fill: parent spacing: 0 TextMetrics { id: titleHeight text: viewTitle.text font: viewTitle.font } // Header with title HeaderFooterToolbar { type: "header" contentItems: [ Image { id: mainIcon source: elisaTheme.nowPlayingIcon height: viewTitle.height width: height sourceSize.height: height sourceSize.width: width fillMode: Image.PreserveAspectFit asynchronous: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft }, Item { id: spacer - width: elisaTheme.layoutHorizontalMargin + width: Kirigami.Units.largeSpacing }, LabelWithToolTip { id: viewTitle Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter text: i18nc("Title of the context view related to the currently playing track", "Now Playing") level: 1 } ] } // Container to hold both the blurred background and the scrollview // We can't make the scrollview a child of the background item since then // everything in the scrollview will be blurred and transparent too! Item { Layout.fillWidth: true Layout.fillHeight: true // Blurred album art background Image { id: albumArtBackground anchors.fill: parent source: albumArtUrl.toString() === '' ? Qt.resolvedUrl(elisaTheme.defaultAlbumImage) : albumArtUrl sourceSize.width: topItem.width sourceSize.height: topItem.height asynchronous: true fillMode: Image.PreserveAspectCrop layer.enabled: true opacity: 0.2 layer.effect: FastBlur { source: albumArtBackground // anchors.fill: parent radius: 40 } } // Scrollview to hold all the content ScrollView { id: scrollView anchors.fill: parent clip: true contentWidth: content.width contentHeight: content.height // Title + metadata + lyrics ColumnLayout { id: content width: scrollView.width - scrollView.ScrollBar.vertical.width // Song title LabelWithToolTip { id: titleLabel level: 1 horizontalAlignment: Label.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignTop Layout.topMargin: elisaTheme.layoutVerticalMargin } LabelWithToolTip { id: subtitleLabel text: { if (artistName !== '' && albumName !== '') { return i18nc("display of artist and album in context view", "by %1 from %2", artistName, albumName) } else if (artistName === '' && albumName !== '') { return i18nc("display of album in context view", "from %1", albumName) } else if (artistName !== '' && albumName === '') { i18nc("display of artist in context view", "by %1", artistName) } } level: 3 opacity: 0.6 horizontalAlignment: Label.AlignHCenter visible: artistName !== '' && albumName !== '' Layout.fillWidth: true Layout.alignment: Qt.AlignTop } // Horizontal line separating title and subtitle from metadata Rectangle { Layout.fillWidth: true - Layout.leftMargin: elisaTheme.layoutHorizontalMargin * 5 - Layout.rightMargin: elisaTheme.layoutHorizontalMargin * 5 - Layout.topMargin: elisaTheme.layoutVerticalMargin * 2 - Layout.bottomMargin: elisaTheme.layoutVerticalMargin * 2 + Layout.leftMargin: Kirigami.Units.largeSpacing* 5 + Layout.rightMargin: Kirigami.Units.largeSpacing * 5 + Layout.topMargin: Kirigami.Units.largeSpacing + Layout.bottomMargin: Kirigami.Units.largeSpacing height: 1 color: myPalette.mid } // Metadata ColumnLayout { id: allMetaData spacing: 0 Layout.fillWidth: true Repeater { id: trackData model: metaDataModel delegate: MetaDataDelegate { Layout.fillWidth: true } } } // Horizontal line separating metadata from lyrics Rectangle { Layout.fillWidth: true - Layout.leftMargin: elisaTheme.layoutHorizontalMargin * 5 - Layout.rightMargin: elisaTheme.layoutHorizontalMargin * 5 - Layout.topMargin: elisaTheme.layoutVerticalMargin * 2 - Layout.bottomMargin: elisaTheme.layoutVerticalMargin * 2 + Layout.leftMargin: Kirigami.Units.largeSpacing * 5 + Layout.rightMargin: Kirigami.Units.largeSpacing * 5 + Layout.topMargin: Kirigami.Units.largeSpacing + Layout.bottomMargin: Kirigami.Units.largeSpacing height: 1 color: myPalette.mid visible: metaDataModel.lyrics !== "" } // Lyrics Label { text: metaDataModel.lyrics wrapMode: Text.WordWrap horizontalAlignment: Label.AlignHCenter Layout.fillWidth: true - Layout.bottomMargin: elisaTheme.layoutVerticalMargin - + Layout.bottomMargin: Kirigami.Units.smallSpacing visible: metaDataModel.lyrics !== "" } } } } // Footer with file path label HeaderFooterToolbar { type: "footer" - contentLayoutSpacing: elisaTheme.layoutHorizontalMargin + contentLayoutSpacing: Kirigami.Units.largeSpacing contentItems: [ Image { sourceSize.width: fileNameLabel.height sourceSize.height: fileNameLabel.height source: elisaTheme.folderIcon }, LabelWithToolTip { id: fileNameLabel Layout.fillWidth: true text: metaDataModel.fileUrl elide: Text.ElideLeft } ] } } onDatabaseIdChanged: { metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) } Connections { target: elisa onMusicManagerChanged: { metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) } } Component.onCompleted: { if (elisa.musicManager) { metaDataModel.initializeByIdAndUrl(trackType, databaseId, fileUrl) } } } diff --git a/src/qml/EditableMetaDataDelegate.qml b/src/qml/EditableMetaDataDelegate.qml index f21cebe3..10a5569c 100644 --- a/src/qml/EditableMetaDataDelegate.qml +++ b/src/qml/EditableMetaDataDelegate.qml @@ -1,79 +1,79 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 - +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 RowLayout { id: delegateRow spacing: 0 TextMetrics { id: metaDataLabelMetric text: 'Metadata Name' } Label { id: metaDataLabels text: { if (model.name !== undefined) { return i18nc("Label for a piece of metadata, e.g. 'Album Artist:'", "%1:", model.name) } return "" } font.weight: Font.Bold horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignCenter Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 + Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 } Loader { id: textDisplayLoader active: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.UrlEntry || model.type === EditableTrackMetadataModel.IntegerEntry visible: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.UrlEntry || model.type === EditableTrackMetadataModel.IntegerEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: TextField { text: model.display horizontalAlignment: Text.AlignLeft selectByMouse: true anchors.fill: parent onTextEdited: { if (model.display !== text) { model.display = text } } } } } diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index c819a87c..417df56a 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,335 +1,336 @@ /* * Copyright 2016-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.7 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 590 property int minHeight: 320 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") Accessible.role: Accessible.Application Accessible.name: title property var goBackAction: elisa.action("go_back") property var seekAction: elisa.action("Seek") property var scrubAction: elisa.action("Scrub") property var playPauseAction: elisa.action("Play-Pause") property var findAction: elisa.action("edit_find") Action { shortcut: goBackAction.shortcut onTriggered: contentView.goBack() } Action { shortcut: seekAction.shortcut onTriggered: elisa.audioControl.seek(headerBar.playerControl.position + 10000) } Action { shortcut: scrubAction.shortcut onTriggered: elisa.audioControl.seek(headerBar.playerControl.position - 10000) } Action { shortcut: playPauseAction.shortcut onTriggered: elisa.audioControl.playPause() } Action { shortcut: findAction.shortcut onTriggered: persistentSettings.expandedFilterView = !persistentSettings.expandedFilterView } ApplicationMenu { id: applicationMenu } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 900 property int height : 650 property var playListState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false property bool expandedFilterView: false property bool showPlaylist: true property bool headerBarIsMaximized: false } Connections { target: headerBar.playerControl onOpenMenu: { if (applicationMenu.visible) { applicationMenu.close() } else { applicationMenu.popup(mainWindow.width - applicationMenu.width, headerBar.height) } } } 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.audioPlayerState = elisa.audioControl.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted persistentSettings.showPlaylist = contentView.showPlaylist persistentSettings.headerBarIsMaximized = headerBar.isMaximized } } Loader { id: mprisloader active: false sourceComponent: PlatformIntegration { id: platformInterface playListModel: elisa.mediaPlayList audioPlayerManager: elisa.audioControl player: elisa.audioPlayer headerBarManager: elisa.manageHeaderBar manageMediaPlayerControl: elisa.playerControl showProgressOnTaskBar: elisa.showProgressOnTaskBar onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } } Connections { target: elisa.audioPlayer onVolumeChanged: headerBar.playerControl.volume = elisa.audioPlayer.volume onMutedChanged: headerBar.playerControl.muted = elisa.audioPlayer.muted } Rectangle { color: myPalette.base anchors.fill: parent ColumnLayout { anchors.fill: parent spacing: 0 HeaderBar { id: headerBar focus: true Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.fillWidth: true tracksCount: elisa.mediaPlayList.remainingTracks album: (elisa.manageHeaderBar.album !== undefined ? elisa.manageHeaderBar.album : '') title: elisa.manageHeaderBar.title artist: (elisa.manageHeaderBar.artist !== undefined ? elisa.manageHeaderBar.artist : '') albumArtist: (elisa.manageHeaderBar.albumArtist !== undefined ? elisa.manageHeaderBar.albumArtist : '') image: elisa.manageHeaderBar.image albumID: elisa.manageHeaderBar.albumId ratingVisible: false playerControl.duration: elisa.audioPlayer.duration playerControl.seekable: elisa.audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: elisa.audioPlayer.position playerControl.skipBackwardEnabled: elisa.playerControl.skipBackwardControlEnabled playerControl.skipForwardEnabled: elisa.playerControl.skipForwardControlEnabled playerControl.playEnabled: elisa.playerControl.playControlEnabled playerControl.isPlaying: elisa.playerControl.musicPlaying playerControl.repeat: elisa.mediaPlayList.repeatPlay playerControl.shuffle: elisa.mediaPlayList.randomPlay playerControl.onSeek: elisa.audioPlayer.seek(position) playerControl.onPlay: elisa.audioControl.playPause() playerControl.onPause: elisa.audioControl.playPause() playerControl.onPlayPrevious: elisa.mediaPlayList.skipPreviousTrack() playerControl.onPlayNext: elisa.mediaPlayList.skipNextTrack() playerControl.isMaximized: persistentSettings.headerBarIsMaximized onOpenArtist: { contentView.openArtist(artist) } onOpenNowPlaying: { contentView.openNowPlaying() } onOpenAlbum: { contentView.openAlbum(album, albumArtist, image, albumID) } TrackImportNotification { id: importedTracksCountNotification anchors { right: headerBar.right top: headerBar.top - rightMargin: elisaTheme.layoutHorizontalMargin * 1.75 - topMargin: elisaTheme.layoutHorizontalMargin * 3 + rightMargin: Kirigami.Units.largeSpacing * 2 + topMargin: Kirigami.Units.largeSpacing * 3 } } Binding { id: indexerBusyBinding target: importedTracksCountNotification property: 'indexingRunning' value: elisa.musicManager.indexerBusy when: elisa.musicManager !== undefined } Binding { target: importedTracksCountNotification property: 'importedTracksCount' value: elisa.musicManager.importedTracksCount when: elisa.musicManager !== undefined } } Rectangle { Layout.fillWidth: true height: 1 color: myPalette.mid } ContentView { id: contentView Layout.fillHeight: true Layout.fillWidth: true showPlaylist: persistentSettings.showPlaylist showExpandedFilterView: persistentSettings.expandedFilterView } } } StateGroup { id: mainWindowState states: [ State { name: "headerBarIsNormal" when: !headerBar.isMaximized changes: [ PropertyChanges { target: mainWindow minimumHeight: mainWindow.minHeight * 1.5 explicit: true }, PropertyChanges { target: headerBar Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight } ] }, State { name: "headerBarIsMaximized" when: headerBar.isMaximized changes: [ PropertyChanges { target: mainWindow minimumHeight: mainWindow.minHeight explicit: true }, PropertyChanges { target: headerBar Layout.minimumHeight: mainWindow.height Layout.maximumHeight: mainWindow.height } ] } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumHeight, Layout.maximumHeight, minimumHeight" easing.type: Easing.InOutQuad duration: 300 } } } Component.onCompleted: { elisa.initialize() if (persistentSettings.playListState) { elisa.mediaPlayList.persistentState = persistentSettings.playListState } if (persistentSettings.audioPlayerState) { elisa.audioControl.persistentState = persistentSettings.audioPlayerState } elisa.mediaPlayList.randomPlay = Qt.binding(function() { return headerBar.playerControl.shuffle }) elisa.mediaPlayList.repeatPlay = Qt.binding(function() { return headerBar.playerControl.repeat }) elisa.audioPlayer.muted = Qt.binding(function() { return headerBar.playerControl.muted }) elisa.audioPlayer.volume = Qt.binding(function() { return headerBar.playerControl.volume }) mprisloader.active = true } } diff --git a/src/qml/FileBrowserView.qml b/src/qml/FileBrowserView.qml index 2da9421c..920a2bb3 100644 --- a/src/qml/FileBrowserView.qml +++ b/src/qml/FileBrowserView.qml @@ -1,187 +1,187 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 - +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: fileView property var viewType property bool isSubPage: false property alias expandedFilterView: navigationBar.expandedFilterView function goBack() { proxyModel.openParentFolder() } function loadFolderAndClear(data) { proxyModel.openFolder(data) navigationBar.filterText = "" } FileBrowserModel { id: realModel } FileBrowserProxyModel { id: proxyModel sourceModel: realModel playList: elisa.mediaPlayList } MouseArea { anchors.fill: parent hoverEnabled: false acceptedButtons: Qt.BackButton onClicked: proxyModel.openParentFolder() } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: i18nc("Title of the file browser view", "Files") image: elisaTheme.folderIcon secondaryTitle: proxyModel.url enableGoBack: proxyModel.canGoBack sortOrder: proxyModel.sortedAscending showRating: false Layout.fillWidth: true Binding { target: proxyModel property: 'filterText' value: navigationBar.filterText } onEnqueue: proxyModel.enqueueToPlayList() onReplaceAndPlay: proxyModel.replaceAndPlayOfPlayList() onGoBack: proxyModel.openParentFolder() onSort: proxyModel.sortModel(order) } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true clip: true GridView { id: contentDirectoryView anchors.fill: parent activeFocusOnTab: true keyNavigationEnabled: true ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds currentIndex: -1 Accessible.role: Accessible.List Accessible.name: proxyModel.url model: proxyModel TextMetrics { id: secondaryLabelSize text: 'example' } ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } add: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 100 } } remove: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 100 } } cellWidth: Math.floor(availableWidth / Math.max(Math.floor(availableWidth / elisaTheme.gridDelegateSize), 2)) - cellHeight: elisaTheme.gridDelegateSize + secondaryLabelSize.boundingRect.height * 2 + elisaTheme.layoutVerticalMargin * 2 + cellHeight: elisaTheme.gridDelegateSize + secondaryLabelSize.boundingRect.height * 2 + Kirigami.Units.largeSpacing delegate: GridBrowserDelegate { width: elisaTheme.gridDelegateSize height: contentDirectoryView.cellHeight focus: true isSelected: contentDirectoryView.currentIndex === index mainText: model.name delegateDisplaySecondaryText: false fileUrl: model.fileUrl entryType: ElisaUtils.FileName imageUrl: model.imageUrl showDetailsButton: !model.isDirectory && !model.isPlaylist showEnqueueButton: !model.isDirectory && !model.isPlaylist showPlayButton: !model.isDirectory onEnqueue: elisa.mediaPlayList.enqueue(url, ElisaUtils.FileName, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: { if (model.isPlaylist) { elisa.mediaPlayList.loadPlaylist(url) } else { elisa.mediaPlayList.enqueue(url, ElisaUtils.FileName, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) } } onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index } onActiveFocusChanged: { if (activeFocus && contentDirectoryView.currentIndex !== model.index) { contentDirectoryView.currentIndex = model.index } } onOpen: isDirectory ? loadFolderAndClear(model.fileUrl) : elisa.mediaPlayList.enqueue(model.fileUrl, ElisaUtils.FileName, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) } } } } } diff --git a/src/qml/FlatButtonWithToolTip.qml b/src/qml/FlatButtonWithToolTip.qml index 0e8ba635..565c580d 100644 --- a/src/qml/FlatButtonWithToolTip.qml +++ b/src/qml/FlatButtonWithToolTip.qml @@ -1,47 +1,47 @@ /* * Copyright 2018 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.Layouts 1.2 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.3 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 Button { id: flatButtonWithToolTip activeFocusOnTab: true Keys.onReturnPressed: flatButtonWithToolTip.clicked() Accessible.onPressAction: flatButtonWithToolTip.clicked() flat: true display: AbstractButton.IconOnly icon.width: Kirigami.Units.iconSizes.smallMedium icon.height: Kirigami.Units.iconSizes.smallMedium - implicitWidth: icon.width + 16 - implicitHeight: icon.height + 16 + implicitWidth: icon.width + Kirigami.Units.largeSpacing * 2 + implicitHeight: icon.height + Kirigami.Units.largeSpacing * 2 ToolTip.visible: hovered ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: flatButtonWithToolTip.text } diff --git a/src/qml/GridBrowserDelegate.qml b/src/qml/GridBrowserDelegate.qml index 13488364..656e9668 100644 --- a/src/qml/GridBrowserDelegate.qml +++ b/src/qml/GridBrowserDelegate.qml @@ -1,415 +1,415 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.5 as Kirigami FocusScope { id: gridEntry property url imageUrl property url imageFallbackUrl property url fileUrl property var entryType property alias mainText: mainLabel.text property alias secondaryText: secondaryLabel.text property var databaseId property bool delegateDisplaySecondaryText: true property bool isPartial property bool isSelected property bool showDetailsButton: false property bool showPlayButton: true property bool showEnqueueButton: true signal enqueue(var databaseId, var name, var url) signal replaceAndPlay(var databaseId, var name, var url) signal open() signal selected() Loader { id: metadataLoader active: false && gridEntry.fileUrl onLoaded: item.show() sourceComponent: MediaTrackMetadataView { fileName: gridEntry.fileUrl ? gridEntry.fileUrl : '' showImage: true modelType: gridEntry.entryType showTrackFileName: true showDeleteButton: false showApplyButton: false editableMetadata: false onRejected: metadataLoader.active = false; } } Keys.onReturnPressed: open() Keys.onEnterPressed: open() Accessible.role: Accessible.ListItem Accessible.name: mainText Rectangle { id: stateIndicator anchors.fill: parent z: 1 color: "transparent" opacity: 0.4 radius: 3 } MouseArea { id: hoverHandle anchors.fill: parent z: 2 hoverEnabled: true acceptedButtons: Qt.LeftButton cursorShape: Qt.PointingHandCursor Layout.preferredHeight: gridEntry.height Layout.fillWidth: true onClicked: open() TextMetrics { id: mainLabelSize font: mainLabel.font text: mainLabel.text } TextMetrics { id: secondaryLabelSize font: secondaryLabel.font text: secondaryLabel.text } ColumnLayout { id: mainData spacing: 0 anchors.fill: parent Item { - Layout.margins: 2 * elisaTheme.layoutVerticalMargin - Layout.preferredHeight: gridEntry.width - 4 * elisaTheme.layoutVerticalMargin - Layout.preferredWidth: gridEntry.width - 4 * elisaTheme.layoutVerticalMargin + Layout.margins: Kirigami.Units.largeSpacing + Layout.preferredHeight: gridEntry.width - 2 * Kirigami.Units.largeSpacing + Layout.preferredWidth: gridEntry.width - 2 * Kirigami.Units.largeSpacing Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Loader { id: hoverLoader active: false anchors { bottom: parent.bottom bottomMargin: 2 left: parent.left leftMargin: 2 } z: 1 opacity: 0 sourceComponent: Row { spacing: 2 Button { id: detailsButton objectName: 'detailsButton' icon.name: 'help-about' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Show track metadata", "View Details") Accessible.role: Accessible.Button Accessible.name: ToolTip.text Accessible.description: ToolTip.text Accessible.onPressAction: clicked() onClicked: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } Keys.onReturnPressed: clicked() Keys.onEnterPressed: clicked() visible: showDetailsButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } Button { id: replaceAndPlayButton objectName: 'replaceAndPlayButton' icon.name: 'media-playback-start' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Clear play list and add whole container to play list", "Play now, replacing current playlist") Accessible.role: Accessible.Button Accessible.name: ToolTip.text Accessible.description: ToolTip.text Accessible.onPressAction: onClicked onClicked: replaceAndPlay(databaseId, mainText, fileUrl) Keys.onReturnPressed: replaceAndPlay(databaseId, mainText, fileUrl) Keys.onEnterPressed: replaceAndPlay(databaseId, mainText, fileUrl) visible: showPlayButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } Button { id: enqueueButton objectName: 'enqueueButton' icon.name: 'list-add' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Add whole container to play list", "Add to playlist") Accessible.role: Accessible.Button Accessible.name: ToolTip.text Accessible.description: ToolTip.text Accessible.onPressAction: onClicked onClicked: enqueue(databaseId, mainText, fileUrl) Keys.onReturnPressed: enqueue(databaseId, mainText, fileUrl) Keys.onEnterPressed: enqueue(databaseId, mainText, fileUrl) visible: showEnqueueButton width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } } } Loader { id: coverImageLoader active: !isPartial anchors.fill: parent sourceComponent: ImageWithFallback { id: coverImage anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height fillMode: Image.PreserveAspectFit smooth: true source: gridEntry.imageUrl fallback: gridEntry.imageFallbackUrl asynchronous: true layer.enabled: !coverImage.usingFallback layer.effect: DropShadow { source: coverImage radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } } Loader { active: isPartial anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height sourceComponent: BusyIndicator { anchors.centerIn: parent running: true } } } LabelWithToolTip { id: mainLabel level: 4 color: myPalette.text // FIXME: Center-aligned text looks better overall, but // sometimes results in font kerning issues // See https://bugreports.qt.io/browse/QTBUG-49646 horizontalAlignment: Text.AlignHCenter Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.maximumHeight: delegateDisplaySecondaryText ? (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) : (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) * 2 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - Layout.bottomMargin: delegateDisplaySecondaryText ? 0 : elisaTheme.layoutVerticalMargin + Layout.bottomMargin: delegateDisplaySecondaryText ? 0 : Kirigami.Units.smallSpacing wrapMode: delegateDisplaySecondaryText ? Label.NoWrap : Label.Wrap maximumLineCount: 2 elide: Text.ElideRight } LabelWithToolTip { id: secondaryLabel opacity: 0.6 color: myPalette.text // FIXME: Center-aligned text looks better overall, but // sometimes results in font kerning issues // See https://bugreports.qt.io/browse/QTBUG-49646 horizontalAlignment: Text.AlignHCenter - Layout.bottomMargin: elisaTheme.layoutVerticalMargin + Layout.bottomMargin: Kirigami.Units.smallSpacing Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom visible: delegateDisplaySecondaryText elide: Text.ElideRight } Item { Layout.fillHeight: true } } } states: [ State { name: 'notSelected' when: !gridEntry.activeFocus && !hoverHandle.containsMouse && !gridEntry.isSelected PropertyChanges { target: stateIndicator color: 'transparent' } PropertyChanges { target: stateIndicator opacity: 1.0 } PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } }, State { name: 'hovered' when: hoverHandle.containsMouse && !gridEntry.activeFocus PropertyChanges { target: stateIndicator color: myPalette.highlight } PropertyChanges { target: stateIndicator opacity: 0.2 } PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } }, State { name: 'selected' when: gridEntry.isSelected && !gridEntry.activeFocus PropertyChanges { target: stateIndicator color: myPalette.mid } PropertyChanges { target: stateIndicator opacity: 0.6 } PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0. } }, State { name: 'hoveredOrSelected' when: gridEntry.activeFocus PropertyChanges { target: stateIndicator color: myPalette.highlight } PropertyChanges { target: stateIndicator opacity: 0.6 } PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } } ] } diff --git a/src/qml/GridBrowserView.qml b/src/qml/GridBrowserView.qml index 73497ae0..b5c47fee 100644 --- a/src/qml/GridBrowserView.qml +++ b/src/qml/GridBrowserView.qml @@ -1,169 +1,169 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 - +import org.kde.kirigami 2.5 as Kirigami 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 alias showRating: navigationBar.showRating property bool delegateDisplaySecondaryText: true property alias expandedFilterView: navigationBar.expandedFilterView property var stackView property url defaultIcon signal enqueue(int databaseId, string name) signal replaceAndPlay(int databaseId, string name) signal open(string innerMainTitle, string innerSecondaryTitle, url innerImage, int databaseId, var dataType, var showDiscHeader) signal goBack() ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: gridView.mainTitle secondaryTitle: gridView.secondaryTitle image: gridView.image enableGoBack: isSubPage sortOrder: if (contentModel) {contentModel.sortedAscending} else true Layout.fillWidth: true Loader { active: contentModel !== undefined sourceComponent: Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } } Loader { active: contentModel sourceComponent: Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } } onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay:contentModel.replaceAndPlayOfPlayList() onGoBack: gridView.goBack() onSort: contentModel.sortModel(order) } FocusScope { Layout.fillHeight: true Layout.fillWidth: true clip: true GridView { id: contentDirectoryView property int availableWidth: width - scrollBar.width activeFocusOnTab: true keyNavigationEnabled: true anchors.fill: parent - anchors.margins: elisaTheme.layoutHorizontalMargin + anchors.margins: Kirigami.Units.largeSpacing ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds currentIndex: -1 Accessible.role: Accessible.List Accessible.name: mainTitle TextMetrics { id: secondaryLabelSize text: 'example' } ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } cellWidth: Math.floor(availableWidth / Math.max(Math.floor(availableWidth / elisaTheme.gridDelegateSize), 2)) - cellHeight: elisaTheme.gridDelegateSize + secondaryLabelSize.boundingRect.height * 2 + elisaTheme.layoutVerticalMargin * 2 + cellHeight: elisaTheme.gridDelegateSize + secondaryLabelSize.boundingRect.height * 2 + Kirigami.Units.largeSpacing delegate: GridBrowserDelegate { width: elisaTheme.gridDelegateSize height: contentDirectoryView.cellHeight focus: true isSelected: contentDirectoryView.currentIndex === index isPartial: false mainText: model.display fileUrl: model.url secondaryText: if (gridView.delegateDisplaySecondaryText) {model.secondaryText} else {""} imageUrl: model.imageUrl ? model.imageUrl : '' imageFallbackUrl: defaultIcon databaseId: model.databaseId delegateDisplaySecondaryText: gridView.delegateDisplaySecondaryText entryType: model.dataType onEnqueue: gridView.enqueue(databaseId, name) onReplaceAndPlay: gridView.replaceAndPlay(databaseId, name) onOpen: gridView.open(model.display, model.secondaryText, (model && model.imageUrl && model.imageUrl.toString() !== "" ? model.imageUrl : defaultIcon), model.databaseId, model.dataType, (model.isSingleDiscAlbum ? ViewManager.NoDiscHeaders : ViewManager.DiscHeaders)) onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index } onActiveFocusChanged: { if (activeFocus && contentDirectoryView.currentIndex !== model.index) { contentDirectoryView.currentIndex = model.index } } } } } } } diff --git a/src/qml/HeaderBar.qml b/src/qml/HeaderBar.qml index 9b3de9a0..4819ca35 100644 --- a/src/qml/HeaderBar.qml +++ b/src/qml/HeaderBar.qml @@ -1,427 +1,428 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 +import org.kde.kirigami 2.5 as Kirigami FocusScope { id: headerBar property string title property string artist property string albumArtist property string album property string image property string newImage property string oldImage property string tracksCount property int trackRating property int albumID property bool ratingVisible property alias playerControl: playControlItem property alias isMaximized: playControlItem.isMaximized property int imageSourceSize: 512 signal openArtist() signal openAlbum() signal openNowPlaying() onImageChanged: { if (changeBackgroundTransition.running) { changeBackgroundTransition.complete() } newImage = image changeBackgroundTransition.start() } Item { id: background anchors.fill: parent ImageWithFallback { id: oldBackground source: oldImage fallback: elisaTheme.defaultBackgroundImage asynchronous: true anchors.fill: parent fillMode: Image.PreserveAspectCrop sourceSize.width: imageSourceSize opacity: 1 layer.enabled: true layer.effect: HueSaturation { cached: true lightness: -0.5 saturation: 0.9 layer.enabled: true layer.effect: GaussianBlur { cached: true radius: 256 deviation: 12 samples: 129 transparentBorder: false } } } ImageWithFallback { id: newBackground source: newImage fallback: Qt.resolvedUrl(elisaTheme.defaultBackgroundImage) asynchronous: true anchors.fill: parent fillMode: Image.PreserveAspectCrop sourceSize.width: imageSourceSize visible: false opacity: 0 layer.enabled: true layer.effect: HueSaturation { cached: true lightness: -0.5 saturation: 0.9 layer.enabled: true layer.effect: GaussianBlur { cached: true radius: 256 deviation: 12 samples: 129 transparentBorder: false } } } } MediaPlayerControl { id: playControlItem focus: true anchors.left: background.left anchors.right: background.right anchors.bottom: background.bottom height: elisaTheme.mediaPlayerControlHeight } ColumnLayout { id: contentZone anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: playControlItem.top spacing: 0 RowLayout { spacing: 0 Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.fillHeight: true Item { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.preferredHeight: contentZone.height * 0.9 Layout.minimumHeight: contentZone.height * 0.9 Layout.maximumHeight: contentZone.height * 0.9 Layout.preferredWidth: contentZone.height * 0.9 Layout.minimumWidth: contentZone.height * 0.9 Layout.maximumWidth: contentZone.height * 0.9 Layout.leftMargin: !LayoutMirroring.enabled ? contentZone.width * 0.15 : 0 Layout.rightMargin: LayoutMirroring.enabled ? contentZone.width * 0.15 : 0 ImageWithFallback { id: oldMainIcon anchors.fill: parent asynchronous: true mipmap: true source: oldImage fallback: Qt.resolvedUrl(elisaTheme.defaultAlbumImage) sourceSize { width: imageSourceSize height: imageSourceSize } fillMode: Image.PreserveAspectFit } ImageWithFallback { id: newMainIcon anchors.fill: parent asynchronous: true mipmap: true source: newImage fallback: Qt.resolvedUrl(elisaTheme.defaultAlbumImage) visible: false opacity: 0 sourceSize { width: imageSourceSize height: imageSourceSize } fillMode: Image.PreserveAspectFit } } ColumnLayout { spacing: 0 - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 + Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.preferredHeight: contentZone.height * 0.9 Layout.minimumHeight: contentZone.height * 0.9 Layout.maximumHeight: contentZone.height * 0.9 Layout.fillWidth: true LabelWithToolTip { id: mainLabel text: title Layout.alignment: Qt.AlignLeft elide: Text.ElideRight // Hardcoded because the headerbar blur always makes a dark-ish // background, so we don't want to use a color scheme color that // might also be dark color: "white" level: 1 font.bold: true Layout.bottomMargin: albumLabel.height * 0.5 MouseArea { id: titleMouseArea hoverEnabled: true anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { openNowPlaying() } } } LabelWithToolTip { id: authorLabel text: artist Layout.alignment: Qt.AlignLeft elide: Text.ElideRight // Hardcoded because the headerbar blur always makes a dark-ish // background, so we don't want to use a color scheme color that // might also be dark color: "white" level: 3 MouseArea { id: authorMouseArea hoverEnabled: true anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { openArtist() } } } LabelWithToolTip { id: albumLabel text: album Layout.alignment: Qt.AlignLeft elide: Text.ElideRight // Hardcoded because the headerbar blur always makes a dark-ish // background, so we don't want to use a color scheme color that // might also be dark color: "white" level: 3 MouseArea { id: albumMouseArea hoverEnabled: true anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { openAlbum() } } } RatingStar { id: mainRating visible: ratingVisible starRating: trackRating Layout.alignment: Qt.AlignLeft } Loader { active: headerBar.isMaximized visible: headerBar.isMaximized sourceComponent: SimplePlayListView { id: playList playListModel: elisa.mediaPlayList anchors.fill: parent } Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignRight | Qt.AlignTop - Layout.topMargin: elisaTheme.layoutHorizontalMargin * 6 - Layout.bottomMargin: elisaTheme.layoutHorizontalMargin * 2 - Layout.leftMargin: elisaTheme.layoutHorizontalMargin * 7 - Layout.rightMargin: elisaTheme.layoutHorizontalMargin * 2 + Layout.topMargin: Kirigami.Units.largeSpacing * 6 + Layout.bottomMargin: Kirigami.Units.largeSpacing * 2 + Layout.leftMargin: Kirigami.Units.largeSpacing * 7 + Layout.rightMargin: Kirigami.Units.largeSpacing * 2 } } } } LabelWithToolTip { id: remainingTracksLabel text: tracksCount > 0 ? i18np("1 track remaining", "%1 tracks remaining", tracksCount) : i18n("No remaining tracks") elide: Text.ElideRight visible: tracksCount >= 0 // Hardcoded because the headerbar blur always makes a dark-ish // background, so we don't want to use a color scheme color that // might also be dark color: "white" anchors.right: contentZone.right anchors.bottom: contentZone.bottom - anchors.rightMargin: elisaTheme.layoutHorizontalMargin * 2 - anchors.bottomMargin: elisaTheme.layoutHorizontalMargin * 2 + anchors.rightMargin: Kirigami.Units.largeSpacing * 2 + anchors.bottomMargin: Kirigami.Units.largeSpacing * 2 } SequentialAnimation { id: changeBackgroundTransition PropertyAction { targets: [newBackground, newMainIcon] property: 'opacity' value: 0 } PropertyAction { targets: [newBackground, newMainIcon] property: 'visible' value: true } PropertyAction { target: newBackground property: "source" value: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) } PropertyAction { target: newMainIcon property: "source" value: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) } ParallelAnimation { NumberAnimation { targets: [newBackground, newMainIcon, mainLabel, authorLabel, remainingTracksLabel, albumLabel] property: 'opacity' from: 0 to: 1 duration: 250 easing.type: Easing.Linear } NumberAnimation { targets: [oldBackground, oldMainIcon] property: 'opacity' from: 1 to: 0 duration: 250 easing.type: Easing.Linear } } PropertyAction { target: headerBar property: "oldImage" value: image } PropertyAction { target: oldBackground property: 'source' value: (headerBar.oldImage ? headerBar.oldImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) } PropertyAction { target: oldMainIcon property: 'source' value: (headerBar.oldImage ? headerBar.oldImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) } PropertyAction { targets: [oldBackground, oldMainIcon] property: 'opacity' value: 1 } PropertyAction { targets: [newBackground, newMainIcon] property: 'visible' value: false } onStopped: { oldImage = newImage } } } diff --git a/src/qml/HeaderFooterToolbar.qml b/src/qml/HeaderFooterToolbar.qml index e4953721..319da60c 100644 --- a/src/qml/HeaderFooterToolbar.qml +++ b/src/qml/HeaderFooterToolbar.qml @@ -1,87 +1,87 @@ /* * Copyright 2016 Matthieu Gallien * Copyright 2019 Nate Graham * * 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.2 import QtQuick.Layouts 1.2 - +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 ColumnLayout { spacing: 0 Layout.fillWidth: true // Is this a header or a footer? Acceptable values are // "header" (separator drawn on bottom) // "footer" (separator drawn on top) // "other" (no separator drawn) property string type // A list of items to be shown within the header or footer property alias contentItems: contentLayout.children // Spacing of content items. Defaults to 0 property alias contentLayoutSpacing: contentLayout.spacing // Separator line above the header Rectangle { visible: type == "footer" && type != "other" Layout.fillWidth: true height: 1 color: myPalette.mid } // Background rectangle + content layout Rectangle { id: headerBackground color: myPalette.window Layout.fillWidth: true height: type == "footer" ? elisaTheme.footerToolbarHeight : elisaTheme.headerToolbarHeight // Content layout RowLayout { id: contentLayout anchors { left: parent.left - leftMargin: elisaTheme.layoutHorizontalMargin + leftMargin: Kirigami.Units.largeSpacing right: parent.right - rightMargin: elisaTheme.layoutHorizontalMargin + rightMargin: Kirigami.Units.largeSpacing verticalCenter: parent.verticalCenter } spacing: 0 // Items provided by the contentItems property will go here } } // Separator line under the header Rectangle { visible: type == "header" && type != "other" Layout.fillWidth: true height: 1 color: myPalette.mid } } diff --git a/src/qml/ListBrowserDelegate.qml b/src/qml/ListBrowserDelegate.qml index 20de6400..712f7310 100644 --- a/src/qml/ListBrowserDelegate.qml +++ b/src/qml/ListBrowserDelegate.qml @@ -1,421 +1,422 @@ /* * 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.kirigami 2.5 as Kirigami 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 + property int singleLineHeight: 3 * Kirigami.Units.smallSpacing + 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) + return (!hideDiscNumber ? Kirigami.Units.largeSpacing * 4 : Kirigami.Units.largeSpacing) else return 0 } Layout.rightMargin: { if (LayoutMirroring.enabled) - return (!hideDiscNumber ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) + return (!hideDiscNumber ? Kirigami.Units.largeSpacing * 4 : Kirigami.Units.largeSpacing) else return 0 } } ImageWithFallback { id: coverImageElement - 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 + Layout.preferredHeight: mediaTrack.height - Kirigami.Units.smallSpacing + Layout.preferredWidth: mediaTrack.height - Kirigami.Units.smallSpacing + Layout.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 + Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 Layout.alignment: Qt.AlignCenter visible: detailedView - sourceSize.width: mediaTrack.height - elisaTheme.layoutVerticalMargin - sourceSize.height: mediaTrack.height - elisaTheme.layoutVerticalMargin + sourceSize.width: mediaTrack.height - Kirigami.Units.smallSpacing + sourceSize.height: mediaTrack.height - Kirigami.Units.smallSpacing fillMode: Image.PreserveAspectFit smooth: true source: imageUrl fallback: elisaTheme.defaultAlbumImage asynchronous: true layer.enabled: !usingFallback layer.effect: DropShadow { source: coverImageElement radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } ColumnLayout { visible: detailedView Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignLeft spacing: 0 LabelWithToolTip { id: mainLabelDetailed 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.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 + Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.fillWidth: true - Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 + Layout.topMargin: Kirigami.Units.smallSpacing / 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.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 + Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 Layout.fillWidth: true - Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 2 + Layout.bottomMargin: Kirigami.Units.smallSpacing / 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: singleLineHeight width: singleLineHeight text: i18nc("Show track metadata", "View Details") icon.name: "help-about" onClicked: callOpenMetaDataView(trackUrl, dataType) } FlatButtonWithToolTip { id: enqueueButton 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: 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 starRating: rating Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.leftMargin: elisaTheme.layoutHorizontalMargin - Layout.rightMargin: elisaTheme.layoutHorizontalMargin + Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.rightMargin: Kirigami.Units.largeSpacing } 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 + Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 + Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 } } } states: [ State { name: 'notSelected' when: !mediaTrack.activeFocus && !hoverArea.containsMouse && !mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } PropertyChanges { target: rowRoot color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } PropertyChanges { target: rowRoot opacity: 1 } }, State { name: 'hovered' when: !mediaTrack.activeFocus && hoverArea.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.highlight } PropertyChanges { target: rowRoot opacity: 0.2 } }, State { name: 'selected' when: !mediaTrack.activeFocus && mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.mid } PropertyChanges { target: rowRoot opacity: 1. } }, State { name: 'focused' when: mediaTrack.activeFocus PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.highlight } PropertyChanges { target: rowRoot opacity: 0.6 } } ] } diff --git a/src/qml/MediaPlayListView.qml b/src/qml/MediaPlayListView.qml index 682b2bb0..00f3c235 100644 --- a/src/qml/MediaPlayListView.qml +++ b/src/qml/MediaPlayListView.qml @@ -1,319 +1,319 @@ /* * Copyright 2016-2017 Matthieu Gallien * Copyright 2019 Nate Graham * * 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.5 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import Qt.labs.platform 1.0 as PlatformDialog import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { property StackView parentStackView property int placeholderHeight: elisaTheme.dragDropPlaceholderHeight signal startPlayback() signal pausePlayback() function showPlayListNotification(message, type, action) { if (!message) { return; } if (type) { playListNotification.type = type; } else { playListNotification.type = Kirigami.MessageType.Information; } if (action) { playListNotification.actions = action; } else { playListNotification.actions = []; } playListNotification.text = message ? message : ""; playListNotification.visible = true; } function hideNotification() { playListNotification.visible = false; } Kirigami.Action { id: undoAction text: i18nc("Undo", "Undo") icon.name: "dialog-cancel" onTriggered: elisa.mediaPlayList.undoClearPlayList() } Kirigami.Action { id: retryLoadAction text: i18nc("Retry", "Retry") icon.name: "edit-redo" onTriggered: loadPlaylistButton.clicked() } Kirigami.Action { id: retrySaveAction text: i18nc("Retry", "Retry") icon.name: "edit-redo" onTriggered: savePlaylistButton.clicked() } Connections { target: elisa.mediaPlayList onPlayListLoadFailed: { showPlayListNotification(i18nc("Message when playlist load failed", "Loading failed"), Kirigami.MessageType.Error, retryLoadAction) } } Connections { target: elisa.mediaPlayList onDisplayUndoNotification: { showPlayListNotification(i18nc("Playlist cleared", "Playlist cleared"), Kirigami.MessageType.Information, undoAction) } } Connections { target: elisa.mediaPlayList onHideUndoNotification: hideNotification() } id: topItem Accessible.role: Accessible.Pane Accessible.name: viewTitle.text 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 (!elisa.mediaPlayList.savePlaylist(fileDialog.file)) { showPlayListNotification(i18nc("Message when saving a playlist failed", "Saving failed"), Kirigami.MessageType.Error, retrySaveAction) } } else { elisa.mediaPlayList.loadPlaylist(fileDialog.file) } } } ColumnLayout { anchors.fill: parent spacing: 0 // Header with title and toolbar buttons HeaderFooterToolbar { type: "header" contentItems: [ // Header title LabelWithToolTip { id: viewTitle Layout.fillWidth: true text: i18nc("Title of the view of the playlist", "Playlist") level: 1 Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter }, // Toolbar buttons FlatButtonWithToolTip { text: i18nc("Show currently played track inside playlist", "Show Current Track") icon.name: 'media-show-active-track-amarok' enabled: elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount > 0 : false onClicked: { playListView.positionViewAtIndex(elisa.mediaPlayList.currentTrackRow, ListView.Contain) playListView.currentIndex = elisa.mediaPlayList.currentTrackRow playListView.currentItem.forceActiveFocus() } }, FlatButtonWithToolTip { id: savePlaylistButton text: i18nc("Save a playlist file", "Save Playlist...") icon.name: 'document-save' enabled: elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount > 0 : false onClicked: { fileDialog.fileMode = PlatformDialog.FileDialog.SaveFile fileDialog.file = '' fileDialog.open() } }, FlatButtonWithToolTip { id: loadPlaylistButton text: i18nc("Load a playlist file", "Load Playlist...") icon.name: 'document-open' onClicked: { fileDialog.fileMode = PlatformDialog.FileDialog.OpenFile fileDialog.file = '' fileDialog.open() } }, FlatButtonWithToolTip { text: i18nc("Remove all tracks from play list", "Clear Playlist") icon.name: 'edit-clear-all' enabled: elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount > 0 : false onClicked: elisa.mediaPlayList.clearPlayList() } ] } ColumnLayout { id: emptyPlaylistText spacing: 0 visible: true Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.fillHeight: true Layout.fillWidth: true Item { id: emptyVisible visible: elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount === 0 : true Layout.preferredHeight: (emptyPlaylistText.height-emptyImage.height-emptyLabel0.height-emptyLabel1.height)/2 } Image { id: emptyImage visible: emptyVisible.visible Layout.alignment: Qt.AlignHCenter width: elisaTheme.gridDelegateWidth * 5 height: elisaTheme.gridDelegateWidth * 5 source: elisaTheme.playlistIcon opacity: 0.25 sourceSize { width: elisaTheme.viewSelectorDelegateHeight * 5 height: elisaTheme.viewSelectorDelegateHeight * 5 } } LabelWithToolTip { id: emptyLabel0 visible: emptyVisible.visible Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter - Layout.rightMargin: elisaTheme.layoutHorizontalMargin - Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.rightMargin: Kirigami.Units.largeSpacing + Layout.leftMargin: Kirigami.Units.largeSpacing level: 1 wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter text: i18nc("Your playlist is empty", "Your playlist is empty") } Label { id: emptyLabel1 visible: emptyVisible.visible - Layout.topMargin: 5 + Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter - Layout.rightMargin: elisaTheme.layoutHorizontalMargin - Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.rightMargin: Kirigami.Units.largeSpacing + Layout.leftMargin: Kirigami.Units.largeSpacing wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter text: i18nc("Text shown when play list is empty", "Add some songs to get started. You can browse your music using the views on the left.") } Item { visible: emptyVisible.visible Layout.fillHeight: true } PlayListBasicView { id: playListView visible: !emptyVisible.visible Layout.fillWidth: true Layout.fillHeight: true title: viewTitle.text playListModel: elisa.mediaPlayList focus: true onStartPlayback: topItem.startPlayback() onPausePlayback: topItem.pausePlayback() onDisplayError: showPlayListNotification(errorText, Kirigami.MessageType.Error) } Kirigami.InlineMessage { id: playListNotification Timer { id: autoHideNotificationTimer interval: 7000 onTriggered: playListNotification.visible = false } type: Kirigami.MessageType.Information showCloseButton: true - Layout.topMargin: 5 + Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true - Layout.rightMargin: elisaTheme.layoutHorizontalMargin - Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.rightMargin: Kirigami.Units.largeSpacing + Layout.leftMargin: Kirigami.Units.largeSpacing onVisibleChanged: { if (visible) { autoHideNotificationTimer.start() } else { autoHideNotificationTimer.stop() } } } } // Footer with number of tracks label HeaderFooterToolbar { type: "footer" contentItems: [ LabelWithToolTip { id: trackCountLabel Layout.fillWidth: true text: i18np("1 track", "%1 tracks", (elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount : 0)) elide: Text.ElideLeft } ] } } } diff --git a/src/qml/MediaPlayerControl.qml b/src/qml/MediaPlayerControl.qml index 853c9b66..fea4582d 100644 --- a/src/qml/MediaPlayerControl.qml +++ b/src/qml/MediaPlayerControl.qml @@ -1,403 +1,404 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.3 +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { property alias volume: volumeSlider.value property int position property int duration property bool muted property bool isPlaying property bool seekable property bool playEnabled property bool skipForwardEnabled property bool skipBackwardEnabled property bool isMaximized property bool shuffle property bool repeat signal play() signal pause() signal playPrevious() signal playNext() signal seek(int position) signal openMenu() signal maximize() signal minimize() id: musicWidget SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Rectangle { anchors.fill: parent color: myPalette.midlight opacity: elisaTheme.mediaPlayerControlOpacity } RowLayout { anchors.fill: parent spacing: 0 FlatButtonWithToolTip { id: minimizeMaximizeButton text: i18nc("toggle between maximized and minimized ivre", "Toggle Maximize") icon.name: musicWidget.isMaximized ? "draw-arrow-up" : "draw-arrow-down" onClicked: musicWidget.isMaximized = !musicWidget.isMaximized } FlatButtonWithToolTip { id: skipBackwardButton enabled: skipBackwardEnabled text: i18nc("skip backward in playlists", "Skip Backward") icon.name: musicWidget.LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" onClicked: musicWidget.playPrevious() } FlatButtonWithToolTip { id: playPauseButton enabled: playEnabled text: i18nc("toggle play and pause for the audio player", "Toggle Play and Pause") icon.name: musicWidget.isPlaying? "media-playback-pause" : "media-playback-start" onClicked: musicWidget.isPlaying ? musicWidget.pause() : musicWidget.play() } FlatButtonWithToolTip { id: skipForwardButton enabled: skipForwardEnabled text: i18nc("skip forward in playlists", "Skip Forward") icon.name: musicWidget.LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" onClicked: musicWidget.playNext() } TextMetrics { id: durationTextMetrics text: i18nc("This is used to preserve a fixed width for the duration text.", "00:00:00") } LabelWithToolTip { id: positionLabel text: timeIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : elisaTheme.layoutHorizontalMargin * 2 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : elisaTheme.layoutHorizontalMargin * 2 + Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2 + Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2 Layout.preferredWidth: (durationTextMetrics.boundingRect.width - durationTextMetrics.boundingRect.x) + 5 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight ProgressIndicator { id: timeIndicator position: musicWidget.position } } MouseArea { id: seekWheelHandler Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true Layout.fillWidth: true - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 + Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : 0 acceptedButtons: Qt.NoButton onWheel: { if (wheel.angleDelta.y > 0) { musicWidget.seek(position + 10000) } else { musicWidget.seek(position - 10000) } } // Synthesized slider background that's not actually a part of the // slider. This is done so the slider's own background can be full // height yet transparent, for easier clicking Rectangle { x: musicProgress.leftPadding y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: seekWheelHandler.width implicitHeight: 6 color: myPalette.dark radius: 3 } Slider { property bool seekStarted: false property int seekValue id: musicProgress from: 0 to: musicWidget.duration width: parent.width anchors.centerIn: parent enabled: musicWidget.seekable && musicWidget.playEnabled live: true onValueChanged: { if (seekStarted) { seekValue = value } } onPressedChanged: { if (pressed) { seekStarted = true; seekValue = value } else { musicWidget.seek(seekValue) seekStarted = false; } } // This only provides a full-height area for clicking; see // https://bugs.kde.org/show_bug.cgi?id=408703. The actual visual // background is generated above ^^ background: Rectangle { x: musicProgress.leftPadding y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 anchors.fill: parent implicitWidth: seekWheelHandler.width implicitHeight: seekWheelHandler.height color: "transparent" Rectangle { anchors.verticalCenter: parent.verticalCenter x: (LayoutMirroring.enabled ? musicProgress.visualPosition * parent.width : 0) width: LayoutMirroring.enabled ? parent.width - musicProgress.visualPosition * parent.width: musicProgress.handle.x + radius height: 6 color: myPalette.text radius: 3 } } handle: Rectangle { x: musicProgress.leftPadding + musicProgress.visualPosition * (musicProgress.availableWidth - width) y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 color: myPalette.base border.width: 1 border.color: musicProgress.pressed ? myPalette.text : myPalette.dark } } } LabelWithToolTip { id: durationLabel text: durationIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true - Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 - Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? (Kirigami.Units.largeSpacing* 2) : 0 + Layout.leftMargin: LayoutMirroring.enabled ? (Kirigami.Units.largeSpacing* 2) : 0 Layout.preferredWidth: (durationTextMetrics.boundingRect.width - durationTextMetrics.boundingRect.x) verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft ProgressIndicator { id: durationIndicator position: musicWidget.duration } } FlatButtonWithToolTip { id: muteButton text: i18nc("toggle mute mode for player", "Toggle Mute") icon.name: musicWidget.muted ? "player-volume-muted" : "player-volume" onClicked: musicWidget.muted = !musicWidget.muted } MouseArea { id: audioWheelHandler Layout.preferredWidth: elisaTheme.volumeSliderWidth Layout.maximumWidth: elisaTheme.volumeSliderWidth Layout.minimumWidth: elisaTheme.volumeSliderWidth Layout.fillHeight: true acceptedButtons: Qt.NoButton onWheel: { if (wheel.angleDelta.y > 0) { volumeSlider.increase() } else { volumeSlider.decrease() } } // Synthesized slider background that's not actually a part of the // slider. This is done so the slider's own background can be full // height yet transparent, for easier clicking Rectangle { x: volumeSlider.leftPadding y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: audioWheelHandler.width implicitHeight: 6 radius: 3 color: myPalette.dark opacity: muted ? 0.5 : 1 } Slider { id: volumeSlider from: 0 to: 100 stepSize: 5 enabled: !muted anchors.centerIn: parent width: elisaTheme.volumeSliderWidth // This only provides a full-height area for clicking; see // https://bugs.kde.org/show_bug.cgi?id=408703. The actual visual // background is generated above ^^ background: Rectangle { anchors.fill: parent implicitWidth: audioWheelHandler.width implicitHeight: audioWheelHandler.height color: "transparent" Rectangle { anchors.verticalCenter: parent.verticalCenter x: (LayoutMirroring.enabled ? volumeSlider.visualPosition * parent.width : 0) width: (LayoutMirroring.enabled ? parent.width - volumeSlider.visualPosition * parent.width : volumeSlider.visualPosition * parent.width) height: 6 color: myPalette.text radius: 3 opacity: muted ? 0.5 : 1 } } handle: Rectangle { x: volumeSlider.leftPadding + volumeSlider.visualPosition * (volumeSlider.availableWidth - width) y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 color: myPalette.base border.width: 1 border.color: volumeSlider.pressed ? myPalette.text : (muted ? myPalette.mid : myPalette.dark) } } } FlatButtonWithToolTip { id: shuffleButton text: i18nc("toggle shuffle mode for playlist", "Toggle Shuffle") icon.name: musicWidget.shuffle ? "media-playlist-shuffle" : "media-playlist-normal" onClicked: musicWidget.shuffle = !musicWidget.shuffle } FlatButtonWithToolTip { id: repeatButton text: i18nc("toggle repeat mode for playlist", "Toggle Repeat") icon.name: musicWidget.repeat ? "media-repeat-all" : "media-repeat-none" onClicked: musicWidget.repeat = !musicWidget.repeat } // Not a FlatButtonWithToolTip because we want text Button { id: showHidePlaylistAction action: Action { shortcut: elisa.action("toggle_playlist").shortcut onTriggered: contentView.showPlaylist = !contentView.showPlaylist } visible: mainWindow.width >= elisaTheme.viewSelectorSmallSizeThreshold flat: true text: i18n("Show Playlist") icon.name: "view-media-playlist" checkable: true checked: contentView.showPlaylist activeFocusOnTab: true Keys.onReturnPressed: action.trigger() Accessible.onPressAction: action.trigger() } FlatButtonWithToolTip { id: menuButton text: i18nc("open application menu", "Application Menu") icon.name: "application-menu" onClicked: openMenu() } } onPositionChanged: { if (!musicProgress.seekStarted) { musicProgress.value = position } } onIsMaximizedChanged: { if (musicWidget.isMaximized) { musicWidget.maximize() } else { musicWidget.minimize() } } Component.onCompleted: { var elementList = [menuButton, repeatButton, shuffleButton, muteButton, skipForwardButton, skipBackwardButton, playPauseButton, minimizeMaximizeButton] for (var i=0; i * 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.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.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 Window { id: trackMetadata property var modelType property url fileName property bool editableMetadata property bool isCreation: false property alias showImage: metadataImage.visible property alias showTrackFileName: fileNameRow.visible property alias showDeleteButton: deleteButtonBox.visible property alias showApplyButton: applyButton.visible property double widthIndex: 2.8 signal rejected() LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true title: isCreation ? i18nc("Window title for track metadata", "Create a Radio") : i18nc("Window title for track metadata", "View Details") EditableTrackMetadataModel { id: realModel manager: elisa.musicManager } modality: Qt.NonModal flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint color: myPalette.window minimumHeight: elisaTheme.coverImageSize * 1.8 minimumWidth: elisaTheme.coverImageSize * trackMetadata.widthIndex ColumnLayout { anchors.fill: parent - anchors.margins: elisaTheme.layoutVerticalMargin + anchors.margins: Kirigami.Units.smallSpacing - spacing: elisaTheme.layoutVerticalMargin + spacing: Kirigami.Units.smallSpacing RowLayout { id: metadataView Layout.fillHeight: true Layout.fillWidth: true spacing: 0 ImageWithFallback { id: metadataImage source: realModel.coverUrl fallback: elisaTheme.defaultAlbumImage sourceSize.width: elisaTheme.coverImageSize sourceSize.height: elisaTheme.coverImageSize fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Layout.preferredHeight: elisaTheme.coverImageSize Layout.preferredWidth: elisaTheme.coverImageSize Layout.minimumHeight: elisaTheme.coverImageSize Layout.minimumWidth: elisaTheme.coverImageSize Layout.maximumHeight: elisaTheme.coverImageSize Layout.maximumWidth: elisaTheme.coverImageSize } ListView { id: trackData Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: 2 * elisaTheme.layoutHorizontalMargin + Layout.leftMargin: 2 * Kirigami.Units.largeSpacing focus: true ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds clip: true ScrollHelper { id: scrollHelper flickable: trackData anchors.fill: trackData } model: realModel Component { id: metaDataDelegate MetaDataDelegate { width: scrollBar.visible ? (!LayoutMirroring.enabled ? trackData.width - scrollBar.width : trackData.width) : trackData.width } } Component { id: editableMetaDataDelegate EditableMetaDataDelegate { width: scrollBar.visible ? (!LayoutMirroring.enabled ? trackData.width - scrollBar.width : trackData.width) : trackData.width } } delegate: editableMetadata ? editableMetaDataDelegate: metaDataDelegate } } RowLayout { id: fileNameRow Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - Layout.topMargin: elisaTheme.layoutVerticalMargin - Layout.bottomMargin: elisaTheme.layoutVerticalMargin + Layout.topMargin: Kirigami.Units.smallSpacing + Layout.bottomMargin: Kirigami.Units.smallSpacing - spacing: elisaTheme.layoutHorizontalMargin + spacing: Kirigami.Units.largeSpacing Image { Layout.preferredWidth: fileNameLabel.height Layout.preferredHeight: fileNameLabel.height sourceSize.width: fileNameLabel.height sourceSize.height: fileNameLabel.height source: elisaTheme.folderIcon } LabelWithToolTip { id: fileNameLabel Layout.fillWidth: true text: realModel.fileUrl elide: Text.ElideRight } } Kirigami.InlineMessage { id: formInvalidNotification text: i18nc("Form validation error message for track data", "Data are not valid. %1", realModel.errorMessage) type: Kirigami.MessageType.Error showCloseButton: false visible: !realModel.isDataValid && realModel.isDirty Layout.topMargin: 5 Layout.fillWidth: true - Layout.rightMargin: elisaTheme.layoutHorizontalMargin - Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.rightMargin: Kirigami.Units.largeSpacing + Layout.leftMargin: Kirigami.Units.largeSpacing } RowLayout { - spacing: elisaTheme.layoutVerticalMargin + spacing: Kirigami.Units.smallSpacing DialogButtonBox { id: deleteButtonBox Layout.minimumHeight: implicitHeight alignment: Qt.AlignLeft Button { id: deleteButton text: i18n("Delete") DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole onClicked: { elisa.musicManager.deleteElementById(modelType, realModel.databaseId) trackMetadata.close() } } } DialogButtonBox { id: buttons Layout.fillWidth: true Layout.minimumHeight: implicitHeight alignment: Qt.AlignRight Button { id: applyButton enabled: realModel.isDataValid && realModel.isDirty text: i18n("Apply") DialogButtonBox.buttonRole: DialogButtonBox.ApplyRole onClicked: { realModel.saveData() if (!deleteButtonBox.visible && editableMetadata) { deleteButtonBox.visible = true } } } Button { text: i18n("Close") DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole onClicked: trackMetadata.close() } } } } Connections { target: elisa onMusicManagerChanged: { if (isCreation) { realModel.initializeForNewRadio() } else { realModel.initializeByUrl(modelType, fileName) } } } Component.onCompleted: { if (elisa.musicManager) { if (isCreation) { realModel.initializeForNewRadio() } else { realModel.initializeByUrl(modelType, fileName) } } } } diff --git a/src/qml/MetaDataDelegate.qml b/src/qml/MetaDataDelegate.qml index 4a6f9e0a..74c5b2e5 100644 --- a/src/qml/MetaDataDelegate.qml +++ b/src/qml/MetaDataDelegate.qml @@ -1,131 +1,131 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 - +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 RowLayout { id: delegateRow spacing: 0 - height: (model.type === EditableTrackMetadataModel.LongTextEntry ? longTextDisplayLoader.height : (metaDataLabelMetric.boundingRect.height + elisaTheme.layoutVerticalMargin / 2)) + height: (model.type === EditableTrackMetadataModel.LongTextEntry ? longTextDisplayLoader.height : (metaDataLabelMetric.boundingRect.height + Kirigami.Units.smallSpacing / 2)) TextMetrics { id: metaDataLabelMetric text: 'Metadata Name' } Label { id: metaDataLabels text: i18nc("Label for a piece of metadata, e.g. 'Album Artist:'", "%1:", model.name) font.weight: Font.Bold horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignTop Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 + Layout.leftMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 } Loader { id: textDisplayLoader active: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.IntegerEntry visible: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.IntegerEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: LabelWithToolTip { text: model.display horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent } } Loader { id: longTextDisplayLoader active: model.type === EditableTrackMetadataModel.LongTextEntry visible: model.type === EditableTrackMetadataModel.LongTextEntry Layout.fillWidth: true - Layout.maximumWidth: delegateRow.width - (0.8 * elisaTheme.coverImageSize + elisaTheme.layoutHorizontalMargin * 2) + Layout.maximumWidth: delegateRow.width - (0.8 * elisaTheme.coverImageSize + Kirigami.Units.largeSpacing * 2) Layout.alignment: Qt.AlignTop sourceComponent: Label { text: model.display horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent wrapMode: Text.WordWrap } } Loader { active: model.type === EditableTrackMetadataModel.DateEntry visible: model.type === EditableTrackMetadataModel.DateEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: LabelWithToolTip { text: rawDate.toLocaleDateString() horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent property date rawDate: new Date(model.display) } } Loader { active: model.type === EditableTrackMetadataModel.RatingEntry visible: model.type === EditableTrackMetadataModel.RatingEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: RatingStar { starRating: model.display readOnly: true anchors { left: parent.left top: parent.top bottom: parent.bottom } } } } diff --git a/src/qml/NavigationActionBar.qml b/src/qml/NavigationActionBar.qml index fc218de0..1fca79b3 100644 --- a/src/qml/NavigationActionBar.qml +++ b/src/qml/NavigationActionBar.qml @@ -1,241 +1,241 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQml 2.2 import QtQuick 2.7 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.3 import org.kde.kirigami 2.8 as Kirigami ColumnLayout { id: navigationBar spacing: 0 property string mainTitle property string secondaryTitle property url image property bool allowArtistNavigation: false property bool showEnqueueButton: true property bool showCreateRadioButton property string labelText property bool showRating: true property alias filterText: filterTextInput.text property alias filterRating: ratingFilter.starRating property bool enableGoBack: true property bool expandedFilterView property bool enableSorting: true property bool sortOrder signal enqueue(); signal replaceAndPlay(); signal createRadio(); signal goBack(); signal showArtist(); signal sort(var order); HeaderFooterToolbar { type: filterRow.visible? "other" : "header" contentItems: [ FlatButtonWithToolTip { id: goPreviousButton objectName: 'goPreviousButton' visible: enableGoBack text: i18nc("navigate back in the views stack", "Back") icon.name: (Qt.application.layoutDirection == Qt.RightToLeft) ? "go-next" : "go-previous" onClicked: goBack() }, Item { id: spacer - Layout.preferredWidth: elisaTheme.layoutHorizontalMargin + Layout.preferredWidth: Kirigami.Units.largeSpacing visible: goPreviousButton.visible }, Image { id: mainIcon source: image Layout.preferredHeight: authorAndAlbumLayout.height Layout.preferredWidth: height sourceSize.height: height sourceSize.width: width fillMode: Image.PreserveAspectFit asynchronous: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft }, Item { - Layout.preferredWidth: elisaTheme.layoutHorizontalMargin + Layout.preferredWidth: Kirigami.Units.largeSpacing visible: mainIcon.visible }, ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true id: authorAndAlbumLayout LabelWithToolTip { id: albumLabel Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true text: mainTitle level: authorLabel.visible ? 4 : 1 font.weight: authorLabel.visible ? Font.Bold : Font.Normal elide: Text.ElideRight } LabelWithToolTip { id: authorLabel Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true text: secondaryTitle elide: Text.ElideRight visible: secondaryTitle !== "" } }, FlatButtonWithToolTip { objectName: 'createRadioButton' visible: showCreateRadioButton text: i18nc("Create a new radio", "Create a radio") icon.name: "media-track-add-amarok" onClicked: createRadio() }, FlatButtonWithToolTip { objectName: 'enqueueButton' visible: !showCreateRadioButton text: i18nc("Add current list to playlist", "Enqueue") icon.name: "list-add" onClicked: enqueue() }, FlatButtonWithToolTip { objectName: 'replaceAndPlayButton' visible: !showCreateRadioButton text: i18n("Play now, replacing contents of Playlist") icon.name: "media-playback-start" onClicked: replaceAndPlay() }, FlatButtonWithToolTip { objectName: 'showArtistButton' visible: allowArtistNavigation && !showCreateRadioButton text: i18nc("Button to navigate to the artist of the album", "Display Artist") icon.name: "view-media-artist" onClicked: showArtist() }, FlatButtonWithToolTip { objectName: 'sortAscendingButton' visible: enableSorting && !showCreateRadioButton text: i18nc("Toggle between ascending and descending order", "Toggle sort order") icon.name: sortOrder ? "view-sort-ascending" : "view-sort-descending" onClicked: sortOrder ? sort(Qt.DescendingOrder) : sort(Qt.AscendingOrder) }, FlatButtonWithToolTip { objectName: 'showFilterButton' visible: !showCreateRadioButton text: !navigationBar.expandedFilterView ? i18nc("Show filters in the navigation bar", "Show Search Options") : i18nc("Hide filters in the navigation bar", "Hide Search Options") icon.name: 'search' checkable: true checked: expandedFilterView onClicked: persistentSettings.expandedFilterView = !persistentSettings.expandedFilterView } ] } HeaderFooterToolbar { type: "header" id: filterRow visible: opacity > 0.0 opacity: 0 contentItems: [ Kirigami.SearchField { id: filterTextInput objectName: 'filterTextInput' Layout.fillWidth: true focusSequence: "" selectByMouse: true Accessible.role: Accessible.EditableText placeholderText: i18n("Search for album name, artist, etc.") Keys.onEscapePressed: persistentSettings.expandedFilterView = false; }, Item { - width: elisaTheme.layoutHorizontalMargin + width: Kirigami.Units.largeSpacing }, LabelWithToolTip { text: i18n("Filter by rating: ") visible: showRating }, RatingStar { id: ratingFilter objectName: 'ratingFilter' visible: showRating hoverWidgetOpacity: 1 readOnly: false } ] } states: [ State { name: 'collapsed' when: !expandedFilterView PropertyChanges { target: filterRow opacity: 0.0 } StateChangeScript { // Focus main content view since that's probably what the user // wants to interact with next script: contentDirectoryView.forceActiveFocus(); } }, State { name: 'expanded' when: expandedFilterView PropertyChanges { target: filterRow opacity: 1.0 } StateChangeScript { script: filterTextInput.forceActiveFocus() } } ] transitions: Transition { from: "expanded,collapsed" PropertyAnimation { properties: "opacity" easing.type: Easing.Linear duration: 250 } } } diff --git a/src/qml/PlayListEntry.qml b/src/qml/PlayListEntry.qml index bdccaebe..bb83f7c4 100644 --- a/src/qml/PlayListEntry.qml +++ b/src/qml/PlayListEntry.qml @@ -1,393 +1,393 @@ /* * 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 import QtQuick.Controls 2.3 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: playListEntry property var index property bool isSingleDiscAlbum property int isPlaying property bool isSelected property bool isValid property bool isAlternateColor property bool containsMouse property int databaseId: 0 property var entryType property string title property string artist property string album property string albumArtist property string duration property url fileName property url imageUrl property int trackNumber property int discNumber property int rating property bool hasValidDiscNumber: true property int scrollBarWidth property bool simpleMode: false signal startPlayback() signal pausePlayback() signal removeFromPlaylist(var trackIndex) signal switchToTrack(var trackIndex) Accessible.role: Accessible.ListItem Accessible.name: title + ' ' + album + ' ' + artist TextMetrics { id: mainCompactLabelSize font: mainCompactLabel.font text: mainCompactLabel.text } Keys.onReturnPressed: { playListEntry.switchToTrack(playListEntry.index) playListEntry.startPlayback() } - height: mainCompactLabelSize.height + 2 * elisaTheme.layoutVerticalMargin + height: mainCompactLabelSize.height + 3 * Kirigami.Units.smallSpacing Loader { id: metadataLoader active: false onLoaded: item.show() sourceComponent: MediaTrackMetadataView { fileName: playListEntry.fileName showImage: entryType !== ElisaUtils.Radio modelType: entryType showTrackFileName: entryType !== ElisaUtils.Radio showDeleteButton: entryType === ElisaUtils.Radio showApplyButton: entryType === ElisaUtils.Radio editableMetadata: entryType === ElisaUtils.Radio onRejected: metadataLoader.active = false } } Rectangle { id: entryBackground anchors.fill: parent anchors.rightMargin: LayoutMirroring.enabled ? scrollBarWidth : 0 z: 1 color: simpleMode ? "transparent" : myPalette.base height: playListEntry.height } RowLayout { id: trackRow z: 2 anchors.fill: parent - anchors.leftMargin: elisaTheme.layoutHorizontalMargin + anchors.leftMargin: Kirigami.Units.largeSpacing anchors.rightMargin: LayoutMirroring.enabled ? scrollBarWidth : 0 - spacing: elisaTheme.layoutHorizontalMargin / 4 + spacing: Kirigami.Units.smallSpacing / 2 // Container for the play/pause icon and the track/disc label Item { TextMetrics { id: fakeLabel text: '99/9' } Layout.minimumWidth: fakeLabel.width Layout.preferredWidth: fakeLabel.width Layout.maximumWidth: fakeLabel.width Layout.preferredHeight: playListEntry.height Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 + Layout.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 + Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0 Image { id: playIcon anchors.centerIn: parent source: (isPlaying === MediaPlayList.IsPlaying ? Qt.resolvedUrl(elisaTheme.playingIndicatorIcon) : Qt.resolvedUrl(elisaTheme.pausedIndicatorIcon)) width: Kirigami.Units.iconSizes.smallMedium height: Kirigami.Units.iconSizes.smallMedium sourceSize.width: Kirigami.Units.iconSizes.smallMedium sourceSize.height: Kirigami.Units.iconSizes.smallMedium fillMode: Image.PreserveAspectFit mirror: LayoutMirroring.enabled layer.enabled: simpleMode layer.effect: ColorOverlay { cached: true color: myPalette.highlightedText } visible: isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused } Label { id: trackAndDiscNumberLabel anchors.fill: parent - anchors.rightMargin: LayoutMirroring.enabled ? 0 : elisaTheme.layoutHorizontalMargin - anchors.leftMargin: !LayoutMirroring.enabled ? 0 : elisaTheme.layoutHorizontalMargin + anchors.rightMargin: LayoutMirroring.enabled ? 0 : Kirigami.Units.largeSpacing + anchors.leftMargin: !LayoutMirroring.enabled ? 0 : Kirigami.Units.largeSpacing horizontalAlignment: Text.AlignRight text: { var trackNumberString; if (trackNumber !== -1) { trackNumberString = Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0); } else { trackNumberString = '' } if (!isSingleDiscAlbum && discNumber !== 0 ) { return trackNumberString + "/" + Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) } else { return trackNumberString } } font.weight: (isPlaying ? Font.Bold : Font.Light) color: simpleMode ? myPalette.highlightedText : myPalette.text visible: isValid && !playIcon.visible } } LabelWithToolTip { id: mainCompactLabel text: title font.weight: (isPlaying ? Font.Bold : Font.Normal) color: simpleMode ? myPalette.highlightedText : myPalette.text Layout.maximumWidth: mainCompactLabel.implicitWidth + 1 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: isValid elide: Text.ElideRight horizontalAlignment: Text.AlignLeft } LabelWithToolTip { id: mainInvalidCompactLabel text: title color: simpleMode ? myPalette.highlightedText : myPalette.text Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: !isValid elide: Text.ElideRight } Item { Layout.fillWidth: true Layout.preferredWidth: 0 } Loader { id: hoverLoader active: false visible: active Layout.alignment: Qt.AlignVCenter | Qt.AlignRight sourceComponent: Row { anchors.centerIn: parent enabled: isValid FlatButtonWithToolTip { id: infoButton objectName: 'infoButton' implicitHeight: playListEntry.height implicitWidth: playListEntry.height text: i18nc("Show track metadata", "View Details") icon.name: "help-about" onClicked: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } } FlatButtonWithToolTip { id: playPauseButton objectName: 'playPauseButton' implicitHeight: playListEntry.height implicitWidth: playListEntry.height scale: LayoutMirroring.enabled ? -1 : 1 // We can mirror the symmetrical pause icon text: (isPlaying === MediaPlayList.IsPlaying) ? i18nc("Pause current track from play list", "Pause") : i18nc("Play this track from play list", "Play") icon.name: (isPlaying === MediaPlayList.IsPlaying) ? "media-playback-pause" : "media-playback-start" onClicked: if (isPlaying === MediaPlayList.IsPlaying) { playListEntry.pausePlayback() } else { playListEntry.switchToTrack(playListEntry.index) playListEntry.startPlayback() } } FlatButtonWithToolTip { id: removeButton objectName: 'removeButton' implicitHeight: playListEntry.height implicitWidth: playListEntry.height text: i18nc("Remove current track from play list", "Remove") icon.name: "error" onClicked: playListEntry.removeFromPlaylist(playListEntry.index) } } } RatingStar { id: ratingWidget starRating: rating visible: rating > 0 } LabelWithToolTip { id: durationLabel text: duration font.weight: (isPlaying ? Font.Bold : Font.Normal) color: simpleMode ? myPalette.highlightedText : myPalette.text Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.leftMargin: elisaTheme.layoutHorizontalMargin - Layout.rightMargin: elisaTheme.layoutHorizontalMargin + Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.rightMargin: Kirigami.Units.largeSpacing horizontalAlignment: Text.AlignRight } } states: [ State { name: 'notSelected' when: !containsMouse && !isSelected && !playListEntry.activeFocus && !simpleMode PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: entryBackground color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } PropertyChanges { target: entryBackground opacity: 1. } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } }, State { name: 'hovered' when: containsMouse && !playListEntry.activeFocus && !simpleMode PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: entryBackground color: myPalette.highlight } PropertyChanges { target: entryBackground opacity: 0.2 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } }, State { name: 'selected' when: !playListEntry.activeFocus && isSelected && !simpleMode PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: entryBackground color: myPalette.mid } PropertyChanges { target: entryBackground opacity: 1. } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } }, State { name: 'focused' when: playListEntry.activeFocus && !simpleMode PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: entryBackground color: myPalette.highlight } PropertyChanges { target: entryBackground opacity: 0.6 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } } ] } diff --git a/src/qml/TrackImportNotification.qml b/src/qml/TrackImportNotification.qml index c8753d46..ed31f71a 100644 --- a/src/qml/TrackImportNotification.qml +++ b/src/qml/TrackImportNotification.qml @@ -1,125 +1,125 @@ /* * Copyright 2017-2019 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.Layouts 1.1 import QtQuick.Controls 2.4 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 /* * This visually mimics the Kirigami.InlineMessage due to the * BusyIndicator, which is not supported by the InlineMessage. * Consider implementing support for the BusyIndicator within * the InlineMessage in the future. */ Rectangle { id: rootComponent property bool indexingRunning property int importedTracksCount color: Kirigami.Theme.activeTextColor - width: (labelWidth.boundingRect.width - labelWidth.boundingRect.x) + 3 * elisaTheme.layoutHorizontalMargin + + width: (labelWidth.boundingRect.width - labelWidth.boundingRect.x) + 3 * Kirigami.Units.largeSpacing + indicator.width height: indicator.height visible: opacity > 0 opacity: 0 radius: Kirigami.Units.smallSpacing / 2 Rectangle { id: bgFillRect anchors.fill: parent anchors.margins: Kirigami.Units.devicePixelRatio color: Kirigami.Theme.backgroundColor radius: rootComponent.radius * 0.60 } Rectangle { anchors.fill: bgFillRect color: rootComponent.color opacity: 0.20 radius: bgFillRect.radius } RowLayout { anchors.fill: parent - spacing: elisaTheme.layoutHorizontalMargin + spacing: Kirigami.Units.largeSpacing BusyIndicator{ id: indicator Layout.alignment: Qt.AlignVCenter } Label { id: importedTracksCountLabel text: (importedTracksCount ? i18ncp("number of imported tracks", "Imported one track", "Imported %1 tracks", importedTracksCount) : i18nc("message to show when Elisa is scanning music files", "Scanning music")) color: Kirigami.Theme.textColor Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignVCenter } } TextMetrics { id: labelWidth text: i18ncp("number of imported tracks", "Imported one track", "Imported %1 tracks", 999999) } Timer { id: hideTimer interval: 6000 repeat: false onTriggered: { rootComponent.opacity = 0 } } Behavior on opacity { NumberAnimation { easing.type: Easing.InOutQuad duration: 300 } } onIndexingRunningChanged: if (indexingRunning) { hideTimer.stop() opacity = 1 } else { hideTimer.start() } } diff --git a/src/qml/TracksDiscHeader.qml b/src/qml/TracksDiscHeader.qml index 96267932..895a9c5c 100644 --- a/src/qml/TracksDiscHeader.qml +++ b/src/qml/TracksDiscHeader.qml @@ -1,49 +1,50 @@ /* * 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 +import org.kde.kirigami 2.5 as Kirigami Rectangle { property int discNumber color: myPalette.mid - height: discHeaderSize.height + elisaTheme.layoutVerticalMargin * 2 + height: discHeaderSize.height + Kirigami.Units.largeSpacing 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 + anchors.leftMargin: Kirigami.Units.largeSpacing elide: Text.ElideRight } } diff --git a/src/qml/ViewSelector.qml b/src/qml/ViewSelector.qml index 59e292a3..584a1377 100644 --- a/src/qml/ViewSelector.qml +++ b/src/qml/ViewSelector.qml @@ -1,143 +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 QtQml.Models 2.2 import QtGraphicalEffects 1.0 - +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: rootFocusScope readonly property alias currentIndex: viewModeView.currentIndex property double textOpacity property alias model: pageDelegateModel.model signal switchView(var viewType) function setCurrentIndex(index) { viewModeView.ignoreCurrentItemChanges = true viewModeView.currentIndex = index viewModeView.ignoreCurrentItemChanges = false } implicitWidth: 225 ScrollView { focus: true anchors.fill: parent z: 2 clip: true ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ListView { id: viewModeView Accessible.role: Accessible.List focus: true activeFocusOnTab: true keyNavigationEnabled: true property bool ignoreCurrentItemChanges: false z: 2 - anchors.topMargin: elisaTheme.layoutHorizontalMargin * 2 + anchors.topMargin: Kirigami.Units.largeSpacing * 2 model: DelegateModel { id: pageDelegateModel delegate: ViewSelectorDelegate { id: entry height: Math.round(elisaTheme.viewSelectorDelegateHeight * 1.4) width: viewModeView.width focus: true isSelected: viewModeView.currentIndex === index onClicked: { viewModeView.currentIndex = index entry.forceActiveFocus() } } } footer: MouseArea { width: viewModeView.width height: viewModeView.height - y acceptedButtons: Qt.LeftButton onClicked: { rootFocusScope.focus = true } } onCurrentItemChanged: if (!ignoreCurrentItemChanges) switchView(currentItem.viewType) } } Connections { target: elisa onInitializationDone: { viewModeView.currentIndex = 3 } } Behavior on implicitWidth { NumberAnimation { duration: 150 } } Behavior on width { NumberAnimation { duration: 150 } } states: [ State { name: 'iconsAndText' when: mainWindow.width >= elisaTheme.viewSelectorSmallSizeThreshold PropertyChanges { target: rootFocusScope textOpacity: 1 implicitWidth: 225 } }, State { name: 'iconsOnly' when: mainWindow.width < elisaTheme.viewSelectorSmallSizeThreshold PropertyChanges { target: rootFocusScope textOpacity: 0 - implicitWidth: elisaTheme.viewSelectorDelegateHeight + 2 * elisaTheme.layoutHorizontalMargin + implicitWidth: elisaTheme.viewSelectorDelegateHeight + 2 * Kirigami.Units.largeSpacing } } ] } diff --git a/src/qml/ViewSelectorDelegate.qml b/src/qml/ViewSelectorDelegate.qml index 4e274349..6e1b026a 100644 --- a/src/qml/ViewSelectorDelegate.qml +++ b/src/qml/ViewSelectorDelegate.qml @@ -1,238 +1,239 @@ /* * Copyright 2016-2019 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 QtGraphicalEffects 1.0 +import org.kde.kirigami 2.5 as Kirigami FocusScope { id: rootItem property var viewType: model.type property bool isSelected signal clicked() Rectangle { id: backgroundHighlight anchors.fill: parent z: 1 color: "transparent" } Accessible.role: Accessible.ListItem Accessible.description: model.display Accessible.name: model.display MouseArea { id: hoverArea anchors.fill: parent z: 2 hoverEnabled: true acceptedButtons: Qt.LeftButton onClicked: { rootItem.clicked() } Loader { id: hoverLoader anchors.fill: parent active: false sourceComponent: ToolTip { delay: Qt.styleHints.mousePressAndHoldInterval text: model.display visible: hoverArea && hoverArea.containsMouse && !nameLabel.visible contentItem: Label { text: model.display color: myPalette.highlightedText } enter: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 0.0; to: 1.0; duration: 300; } } exit: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 1.0; to: 0.0; duration: 300; } } background: Rectangle { color: myPalette.shadow radius: elisaTheme.tooltipRadius layer.enabled: true layer.effect: DropShadow { horizontalOffset: elisaTheme.shadowOffset verticalOffset: elisaTheme.shadowOffset radius: 8 samples: 17 color: myPalette.shadow } } } } Image { id: viewIcon z: 2 anchors { verticalCenter: parent.verticalCenter - leftMargin: elisaTheme.layoutHorizontalMargin + leftMargin: Kirigami.Units.largeSpacing left: parent.left } height: elisaTheme.viewSelectorDelegateHeight width: elisaTheme.viewSelectorDelegateHeight sourceSize { width: elisaTheme.viewSelectorDelegateHeight height: elisaTheme.viewSelectorDelegateHeight } source: model.image layer.enabled: true layer.effect: ColorOverlay { color: nameLabel.color } } LabelWithToolTip { id: nameLabel z: 2 anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: elisaTheme.layoutHorizontalMargin + anchors.leftMargin: Kirigami.Units.largeSpacing anchors.left: viewIcon.right anchors.right: parent.right - anchors.rightMargin: elisaTheme.layoutHorizontalMargin + anchors.rightMargin: Kirigami.Units.largeSpacing verticalAlignment: "AlignVCenter" text: model.display elide: Text.ElideRight opacity: textOpacity visible: opacity > 0 color: (viewModeView.currentIndex === index || hoverArea.containsMouse ? myPalette.highlight : myPalette.text) } } states: [ State { name: 'notSelected' when: !rootItem.activeFocus && !hoverArea.containsMouse && !rootItem.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: viewIcon opacity: 1 } PropertyChanges { target: nameLabel color: myPalette.buttonText } PropertyChanges { target: backgroundHighlight color: 'transparent' } }, State { name: 'hovered' when: !rootItem.activeFocus && hoverArea.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: viewIcon opacity: 0.4 } PropertyChanges { target: nameLabel color: myPalette.buttonText } PropertyChanges { target: backgroundHighlight color: Qt.rgba(myPalette.highlight.r, myPalette.highlight.g, myPalette.highlight.b, 0.2) } }, State { name: 'selected' when: !rootItem.activeFocus && rootItem.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: viewIcon opacity: 1 } PropertyChanges { target: nameLabel color: myPalette.buttonText } PropertyChanges { target: backgroundHighlight color: myPalette.mid } }, State { name: 'focused' when: rootItem.activeFocus PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: viewIcon opacity: 1. } PropertyChanges { target: nameLabel color: myPalette.highlightedText } PropertyChanges { target: backgroundHighlight color: myPalette.highlight } } ] }