diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 46b11cea..f1a18562 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,441 +1,442 @@ enable_testing() configure_file(mediaplaylisttestconfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/mediaplaylisttestconfig.h @ONLY) include_directories(${elisa_CURRENT_BINARY_DIR}) include_directories(${elisa_BINARY_DIR}) include_directories(${elisa_BINARY_DIR}/src) set(databaseInterfaceTest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp databaseinterfacetest.cpp ) ecm_add_test(${databaseInterfaceTest_SOURCES} TEST_NAME "databaseInterfaceTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(databaseInterfaceTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(managemediaplayercontrolTest_SOURCES ../src/managemediaplayercontrol.cpp ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/musiclistenersmanager.cpp ../src/elisaapplication.cpp ../src/notificationitem.cpp ../src/trackslistener.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/elisautils.cpp ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp managemediaplayercontroltest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(managemediaplayercontrolTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(managemediaplayercontrolTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(managemediaplayercontrolTest_SOURCES ${managemediaplayercontrolTest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(managemediaplayercontrolTest_SOURCES ${managemediaplayercontrolTest_SOURCES} ../src/upnp/upnpcontrolcontentdirectory.cpp ../src/upnp/upnpcontentdirectorymodel.cpp ../src/upnp/upnpcontrolconnectionmanager.cpp ../src/upnp/upnpcontrolmediaserver.cpp ../src/upnp/didlparser.cpp ../src/upnp/upnplistener.cpp ../src/upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(managemediaplayercontrolTest_SOURCES ../src/elisa_settings.kcfgc ) set(managemediaplayercontrolTest_SOURCES ${managemediaplayercontrolTest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${managemediaplayercontrolTest_SOURCES} TEST_NAME "managemediaplayercontrolTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql Qt5::Widgets KF5::I18n Qt5::Quick Qt5::Multimedia KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData ) if (KF5Baloo_FOUND) target_link_libraries(managemediaplayercontrolTest KF5::Baloo Qt5::DBus) endif() if (KF5KCMUtils_FOUND) target_link_libraries(managemediaplayercontrolTest KF5::KCMUtils) endif() if (UPNPQT_FOUND) target_link_libraries(managemediaplayercontrolTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() if (KF5XmlGui_FOUND) target_link_libraries(managemediaplayercontrolTest KF5::XmlGui) endif() target_include_directories(managemediaplayercontrolTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(manageheaderbarTest_SOURCES ../src/manageheaderbar.cpp ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/musiclistenersmanager.cpp ../src/elisaapplication.cpp ../src/notificationitem.cpp ../src/trackslistener.cpp ../src/trackslistener.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/elisautils.cpp ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp manageheaderbartest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(manageheaderbarTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(manageheaderbarTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(manageheaderbarTest_SOURCES ${manageheaderbarTest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(manageheaderbarTest_SOURCES ${manageheaderbarTest_SOURCES} ../src/upnp/upnpcontrolcontentdirectory.cpp ../src/upnp/upnpcontentdirectorymodel.cpp ../src/upnp/upnpcontrolconnectionmanager.cpp ../src/upnp/upnpcontrolmediaserver.cpp ../src/upnp/didlparser.cpp ../src/upnp/upnplistener.cpp ../src/upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(manageheaderbarTest_SOURCES ../src/elisa_settings.kcfgc ) set(manageheaderbarTest_SOURCES ${manageheaderbarTest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${manageheaderbarTest_SOURCES} TEST_NAME "manageheaderbarTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui Qt5::Widgets KF5::I18n Qt5::Quick Qt5::Multimedia KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData ) if (KF5Baloo_FOUND) target_link_libraries(manageheaderbarTest KF5::Baloo Qt5::DBus) endif() if (KF5KCMUtils_FOUND) target_link_libraries(manageheaderbarTest KF5::KCMUtils) endif() if (UPNPQT_FOUND) target_link_libraries(manageheaderbarTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() if (KF5XmlGui_FOUND) target_link_libraries(manageheaderbarTest KF5::XmlGui) endif() target_include_directories(manageheaderbarTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(manageaudioplayerTest_SOURCES ../src/manageaudioplayer.cpp manageaudioplayertest.cpp ) ecm_add_test(${manageaudioplayerTest_SOURCES} TEST_NAME "manageaudioplayerTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Gui Qt5::Multimedia) target_include_directories(manageaudioplayerTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(mediaplaylistTest_SOURCES ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/trackslistener.cpp ../src/musiclistenersmanager.cpp ../src/elisaapplication.cpp ../src/notificationitem.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/elisautils.cpp ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp mediaplaylisttest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(mediaplaylistTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(mediaplaylistTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(mediaplaylistTest_SOURCES ${mediaplaylistTest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(mediaplaylistTest_SOURCES ${mediaplaylistTest_SOURCES} ../src/upnp/upnpcontrolcontentdirectory.cpp ../src/upnp/upnpcontentdirectorymodel.cpp ../src/upnp/upnpcontrolconnectionmanager.cpp ../src/upnp/upnpcontrolmediaserver.cpp ../src/upnp/didlparser.cpp ../src/upnp/upnplistener.cpp ../src/upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(mediaplaylistTest_SOURCES ../src/elisa_settings.kcfgc ) set(mediaplaylistTest_SOURCES ${mediaplaylistTest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${mediaplaylistTest_SOURCES} TEST_NAME "mediaplaylistTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui Qt5::Widgets KF5::I18n Qt5::Quick Qt5::Multimedia KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData ) if (KF5Baloo_FOUND) target_link_libraries(mediaplaylistTest KF5::Baloo Qt5::DBus) endif() if (KF5KCMUtils_FOUND) target_link_libraries(mediaplaylistTest KF5::KCMUtils) endif() if (UPNPQT_FOUND) target_link_libraries(mediaplaylistTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() if (KF5XmlGui_FOUND) target_link_libraries(mediaplaylistTest KF5::XmlGui) endif() target_include_directories(mediaplaylistTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(trackslistenertest_SOURCES ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/trackslistener.cpp ../src/musiclistenersmanager.cpp ../src/elisaapplication.cpp ../src/notificationitem.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/elisautils.cpp ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp trackslistenertest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(trackslistenertest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(trackslistenertest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(trackslistenertest_SOURCES ${trackslistenertest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(trackslistenertest_SOURCES ${trackslistenertest_SOURCES} ../src/upnp/upnpcontrolcontentdirectory.cpp ../src/upnp/upnpcontentdirectorymodel.cpp ../src/upnp/upnpcontrolconnectionmanager.cpp ../src/upnp/upnpcontrolmediaserver.cpp ../src/upnp/didlparser.cpp ../src/upnp/upnplistener.cpp ../src/upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(trackslistenertest_SOURCES ../src/elisa_settings.kcfgc ) set(trackslistenertest_SOURCES ${trackslistenertest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${trackslistenertest_SOURCES} TEST_NAME "trackslistenertest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui Qt5::Widgets KF5::I18n Qt5::Quick Qt5::Multimedia KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData ) if (KF5Baloo_FOUND) target_link_libraries(trackslistenertest KF5::Baloo Qt5::DBus) endif() if (KF5KCMUtils_FOUND) target_link_libraries(trackslistenertest KF5::KCMUtils) endif() if (UPNPQT_FOUND) target_link_libraries(trackslistenertest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() if (KF5XmlGui_FOUND) target_link_libraries(trackslistenertest KF5::XmlGui) endif() target_include_directories(trackslistenertest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(allalbumsmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/allalbumsmodel.cpp + ../src/albummodel.cpp allalbumsmodeltest.cpp ) ecm_add_test(${allalbumsmodeltest_SOURCES} TEST_NAME "allalbumsmodeltest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(allalbumsmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(albummodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/albummodel.cpp albummodeltest.cpp ) ecm_add_test(${albummodeltest_SOURCES} TEST_NAME "albummodeltest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(albummodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(allartistsmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/allartistsmodel.cpp allartistsmodeltest.cpp ) ecm_add_test(${allartistsmodeltest_SOURCES} TEST_NAME "allartistsmodeltest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(allartistsmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(alltracksmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/alltracksmodel.cpp alltracksmodeltest.cpp ) ecm_add_test(${alltracksmodeltest_SOURCES} TEST_NAME "alltracksmodeltest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(alltracksmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(localfilelistingtest_SOURCES ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelisting.cpp ../src/musicaudiotrack.cpp ../src/notificationitem.cpp ../src/elisautils.cpp localfilelistingtest.cpp ) kconfig_add_kcfg_files(localfilelistingtest_SOURCES ../src/elisa_settings.kcfgc ) set(localfilelistingtest_SOURCES ${localfilelistingtest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${localfilelistingtest_SOURCES} TEST_NAME "localfilelistingtest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n KF5::FileMetaData KF5::ConfigCore KF5::ConfigGui) target_include_directories(localfilelistingtest PRIVATE ${CMAKE_SOURCE_DIR}/src) if (KF5XmlGui_FOUND AND KF5KCMUtils_FOUND) set(elisaapplicationtest_SOURCES ../src/elisaapplication.cpp elisaapplicationtest.cpp ) kconfig_add_kcfg_files(elisaapplicationtest_SOURCES ../src/elisa_settings.kcfgc ) set(elisaapplicationtest_SOURCES ${elisaapplicationtest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${elisaapplicationtest_SOURCES} TEST_NAME "elisaapplicationtest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Widgets KF5::ConfigCore KF5::ConfigGui KF5::ConfigWidgets KF5::XmlGui KF5::KCMUtils) target_include_directories(elisaapplicationtest PRIVATE ${CMAKE_SOURCE_DIR}/src) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 54ba6f68..1029e427 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,225 +1,222 @@ include_directories(${elisa_BINARY_DIR}) if (Qt5Quick_FOUND AND Qt5Widgets_FOUND) set(elisa_SOURCES upnpControl.cpp mediaplaylist.cpp musicstatistics.cpp musicalbum.cpp musicaudiotrack.cpp musicartist.cpp progressindicator.cpp albummodel.cpp allalbumsmodel.cpp allartistsmodel.cpp databaseinterface.cpp musiclistenersmanager.cpp managemediaplayercontrol.cpp manageheaderbar.cpp manageaudioplayer.cpp albumfilterproxymodel.cpp trackslistener.cpp elisaapplication.cpp audiowrapper.cpp alltracksmodel.cpp notificationitem.cpp topnotificationmanager.cpp elisautils.cpp abstractfile/abstractfilelistener.cpp abstractfile/abstractfilelisting.cpp file/filelistener.cpp file/localfilelisting.cpp MediaServer.qml Theme.qml PlatformIntegration.qml LabelWithToolTip.qml windows/WindowsTheme.qml windows/PlatformIntegration.qml android/MediaServer.qml android/AndroidTheme.qml android/PlatformIntegration.qml RatingStar.qml PlayListEntry.qml - MediaAlbumDelegate.qml - MediaArtistDelegate.qml MediaBrowser.qml DraggableItem.qml PassiveNotification.qml TopNotification.qml TopNotificationItem.qml TrackImportNotification.qml HeaderBar.qml NavigationActionBar.qml FilterBar.qml MediaPlayerControl.qml ContextView.qml - MediaArtistAlbumView.qml MediaPlayListView.qml MediaAlbumView.qml - MediaAllAlbumView.qml - MediaAllArtistView.qml MediaAllTracksView.qml MediaTrackDelegate.qml MediaAlbumTrackDelegate.qml + GridBrowserView.qml + GridBrowserDelegate.qml ViewSelector.qml ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) set(elisa_SOURCES ${elisa_SOURCES} baloo/localbaloofilelisting.cpp baloo/baloolistener.cpp ) qt5_add_dbus_interface(elisa_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisa_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) endif() endif() if (Qt5DBus_FOUND) set(elisa_SOURCES ${elisa_SOURCES} mpris2/mpris2.cpp mpris2/mediaplayer2.cpp mpris2/mediaplayer2player.cpp ) endif() if (UPNPQT_FOUND) set(elisa_SOURCES ${elisa_SOURCES} upnp/upnpcontrolcontentdirectory.cpp upnp/upnpcontentdirectorymodel.cpp upnp/upnpcontrolconnectionmanager.cpp upnp/upnpcontrolmediaserver.cpp upnp/didlparser.cpp upnp/upnplistener.cpp upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(elisa_SOURCES elisa_settings.kcfgc) set(elisa_SOURCES ${elisa_SOURCES} elisa_core.kcfg ) qt5_add_resources(elisa_SOURCES upnpControl.qrc) set(elisa_ICONS_PNG ../icons/128-apps-elisa.png ../icons/64-apps-elisa.png ../icons/48-apps-elisa.png ../icons/32-apps-elisa.png ../icons/22-apps-elisa.png ../icons/16-apps-elisa.png ) # add icons to application sources, to have them bundled ecm_add_app_icon(elisa_SOURCES ICONS ${elisa_ICONS_PNG}) add_executable(elisa ${elisa_SOURCES}) set_target_properties(elisa PROPERTIES LINK_FLAGS "-Wl,--no-undefined") target_include_directories(elisa PRIVATE ${KDSoap_INCLUDE_DIRS}) target_link_libraries(elisa LINK_PRIVATE Qt5::Core Qt5::Quick Qt5::Gui Qt5::Widgets Qt5::Multimedia Qt5::Svg Qt5::Sql KF5::I18n KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData KF5::CoreAddons ) if (Qt5DBus_FOUND) target_link_libraries(elisa LINK_PRIVATE Qt5::DBus ) endif() if (KF5KCMUtils_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::KCMUtils ) endif() if (Qt5AndroidExtras_FOUND) target_link_libraries(elisa LINK_PRIVATE Qt5::AndroidExtras ) endif() if (KF5Baloo_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Baloo ) endif() if (KF5Declarative_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Declarative ) endif() if (KF5XmlGui_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::XmlGui ) endif() if (KF5Crash_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Crash ) endif() if (UPNPQT_FOUND) target_link_libraries(elisa LINK_PRIVATE UPNP::upnpQt Qt5::Network ) endif() if (KF5DBusAddons_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::DBusAddons ) endif() endif() install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS}) if (KF5ConfigWidgets_FOUND) add_subdirectory(localFileConfiguration) endif() diff --git a/src/MediaArtistDelegate.qml b/src/GridBrowserDelegate.qml similarity index 68% rename from src/MediaArtistDelegate.qml rename to src/GridBrowserDelegate.qml index 73ca346a..1b62a886 100644 --- a/src/MediaArtistDelegate.qml +++ b/src/GridBrowserDelegate.qml @@ -1,277 +1,290 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -import QtQuick 2.4 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 -FocusScope { - id: mediaServerEntry +Item { + id: gridEntry - property StackView stackView - property MediaPlayList playListModel - property var musicListener - property var playerControl - property var contentDirectoryModel - property var image - property alias name: nameLabel.text + property var imageUrl + property bool shadowForImage + property alias mainText: mainLabel.text + property alias secondaryText: secondaryLabel.text + property var containerData + property bool delegateDisplaySecondaryText: true - signal artistClicked() - signal openArtist(var name) - - SystemPalette { - id: myPalette - colorGroup: SystemPalette.Active - } - - Theme { - id: elisaTheme - } + signal enqueue(var data); + signal enqueueAndPlay(var data); + signal open(); + signal selected(); Action { id: enqueueAction - text: i18nc("Add all tracks from artist to play list", "Enqueue") - iconName: "media-track-add-amarok" - onTriggered: mediaServerEntry.playListModel.enqueue(mediaServerEntry.name) + text: i18nc("Add whole container to play list", "Enqueue") + iconName: 'media-track-add-amarok' + onTriggered: enqueue(containerData) } Action { id: openAction - text: i18nc("Open artist view", "Open Artist") + text: i18nc("Open view of the container", "Open") iconName: 'document-open-folder' - onTriggered: openArtist(name) + onTriggered: open() } Action { id: enqueueAndPlayAction - text: i18nc("Clear play list and add all tracks from artist to play list", "Play Now and Replace Play List") - iconName: "media-playback-start" - onTriggered: { - mediaServerEntry.playListModel.clearAndEnqueue(mediaServerEntry.name) - mediaServerEntry.playerControl.ensurePlay() - } + text: i18nc("Clear play list and add whole container to play list", "Play Now and Replace Play List") + iconName: 'media-playback-start' + onTriggered: enqueueAndPlay(containerData) } - Keys.onReturnPressed: openArtist(name) - Keys.onEnterPressed: openArtist(name) + Keys.onReturnPressed: open() + Keys.onEnterPressed: open() ColumnLayout { anchors.fill: parent spacing: 0 MouseArea { id: hoverHandle hoverEnabled: true acceptedButtons: Qt.LeftButton focus: true - Layout.preferredHeight: mediaServerEntry.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + nameSize.height + Layout.preferredHeight: gridEntry.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + mainLabelSize.height + secondaryLabelSize.height Layout.fillWidth: true onClicked: { hoverHandle.forceActiveFocus() - artistClicked() + gridEntry.selected() } - onDoubleClicked: openArtist(name) + onDoubleClicked: open() TextMetrics { - id: nameSize - font: nameLabel.font - text: nameLabel.text + 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.preferredWidth: mediaServerEntry.width * 0.85 - Layout.preferredHeight: mediaServerEntry.width * 0.85 + Layout.preferredHeight: gridEntry.width * 0.85 + Layout.preferredWidth: gridEntry.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + focus: true + Loader { id: hoverLoader active: false anchors.centerIn: parent z: 1 opacity: 0 sourceComponent: Row { ToolButton { id: enqueueButton action: enqueueAction width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } ToolButton { id: openButton action: openAction width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } ToolButton { id: enqueueAndPlayButton action: enqueueAndPlayAction width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } } } Image { - id: artistDecoration - - source: Qt.resolvedUrl(elisaTheme.defaultArtistImage) + id: coverImage anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height - fillMode: Image.PreserveAspectFit - smooth: true + source: gridEntry.imageUrl + asynchronous: true - layer.enabled: image === '' ? false : true + layer.enabled: shadowForImage layer.effect: DropShadow { + source: coverImage + radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } } LabelWithToolTip { - id: nameLabel + id: mainLabel font.weight: Font.Bold color: myPalette.text horizontalAlignment: Text.AlignLeft - Layout.preferredWidth: mediaServerEntry.width * 0.85 Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 + Layout.preferredWidth: gridEntry.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom elide: Text.ElideRight } + + LabelWithToolTip { + id: secondaryLabel + + font.weight: Font.Light + color: myPalette.text + + horizontalAlignment: Text.AlignLeft + + Layout.preferredWidth: gridEntry.width * 0.85 + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + + visible: delegateDisplaySecondaryText + + elide: Text.ElideRight + } } } Item { Layout.fillHeight: true } } states: [ State { name: 'notSelected' - when: !mediaServerEntry.activeFocus && !hoverHandle.containsMouse + when: !gridEntry.activeFocus && !hoverHandle.containsMouse PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { - target: artistDecoration + target: coverImage opacity: 1 } }, State { name: 'hoveredOrSelected' - when: mediaServerEntry.activeFocus || hoverHandle.containsMouse + when: gridEntry.activeFocus || hoverHandle.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { - target: artistDecoration + target: coverImage opacity: 0.2 } } ] transitions: [ Transition { to: 'hoveredOrSelected' SequentialAnimation { NumberAnimation { properties: "active" duration: 0 } NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 100 } } }, Transition { to: 'notSelected' SequentialAnimation { NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 100 } NumberAnimation { properties: "active" duration: 0 } } } ] } diff --git a/src/GridBrowserView.qml b/src/GridBrowserView.qml new file mode 100644 index 00000000..9b85ba0f --- /dev/null +++ b/src/GridBrowserView.qml @@ -0,0 +1,171 @@ +/* + * Copyright 2016-2017 Matthieu Gallien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Window 2.2 +import QtQml.Models 2.1 +import QtQuick.Layouts 1.2 +import QtGraphicalEffects 1.0 + +import org.kde.elisa 1.0 + +FocusScope { + id: gridView + + property bool isFirstPage: false + property string mainTitle + property string secondaryTitle + property url image + property alias model: proxyModel.model + property bool showRating: true + property bool delegateDisplaySecondaryText: true + + signal enqueue(var data); + signal enqueueAndPlay(var data); + signal open(var innerModel, var innerMainTitle, var innerSecondaryTitle, var innerImage); + signal goBack() + + SystemPalette { + id: myPalette + colorGroup: SystemPalette.Active + } + + Theme { + id: elisaTheme + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Loader { + sourceComponent: FilterBar { + id: filterBar + + labelText: mainTitle + showRating: gridView.showRating + + anchors.fill: parent + + Binding { + target: model + property: 'filterText' + value: filterBar.filterText + when: isFirstPage + } + + Binding { + target: model + property: 'filterRating' + value: filterBar.filterRating + when: isFirstPage + } + } + + height: elisaTheme.navigationBarHeight + Layout.preferredHeight: height + Layout.minimumHeight: height + Layout.maximumHeight: height + Layout.fillWidth: true + + active: isFirstPage + visible: isFirstPage + } + + Loader { + sourceComponent: NavigationActionBar { + id: navigationBar + + mainTitle: gridView.mainTitle + secondaryTitle: gridView.secondaryTitle + image: gridView.image + + anchors.fill: parent + + onEnqueue: gridView.enqueue(mainTitle) + onEnqueueAndPlay: gridView.enqueueAndPlay(mainTitle) + onGoBack: gridView.goBack() + } + + height: elisaTheme.navigationBarHeight + Layout.preferredHeight: height + Layout.minimumHeight: height + Layout.maximumHeight: height + Layout.fillWidth: true + + active: !isFirstPage + visible: !isFirstPage + } + + Rectangle { + color: myPalette.base + + Layout.fillHeight: true + Layout.fillWidth: true + + ScrollView { + anchors.fill: parent + flickableItem.boundsBehavior: Flickable.StopAtBounds + flickableItem.interactive: true + + GridView { + id: contentDirectoryView + + focus: true + + TextMetrics { + id: secondaryLabelSize + text: 'example' + } + + cellWidth: elisaTheme.gridDelegateWidth + cellHeight: (delegateDisplaySecondaryText ? elisaTheme.gridDelegateHeight : elisaTheme.gridDelegateHeight - secondaryLabelSize.height) + + model: DelegateModel { + id: proxyModel + + delegate: GridBrowserDelegate { + width: contentDirectoryView.cellWidth + height: contentDirectoryView.cellHeight + + focus: true + + mainText: model.display + secondaryText: model.secondaryText + imageUrl: model.imageUrl + shadowForImage: model.shadowForImage + containerData: model.containerData + delegateDisplaySecondaryText: gridView.delegateDisplaySecondaryText + + onEnqueue: gridView.enqueue(data) + onEnqueueAndPlay: gridView.enqueueAndPlay(data) + onOpen: gridView.open(model.childModel, model.display, model.secondaryText, model.imageUrl) + onSelected: { + forceActiveFocus() + contentDirectoryView.currentIndex = model.index + } + } + } + } + } + } + } +} diff --git a/src/MediaAlbumDelegate.qml b/src/MediaAlbumDelegate.qml deleted file mode 100644 index bbaf7bd3..00000000 --- a/src/MediaAlbumDelegate.qml +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright 2016-2017 Matthieu Gallien - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.4 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Window 2.2 -import QtQml.Models 2.1 -import QtQuick.Layouts 1.2 -import QtGraphicalEffects 1.0 - -import org.kde.elisa 1.0 - -FocusScope { - id: mediaServerEntry - - property StackView stackView - property MediaPlayList playListModel - property var musicListener - property var playerControl - property var image - property alias title: titleLabel.text - property alias artist: artistLabel.text - property string trackNumber - property bool isSingleDiscAlbum - property var albumData - property var albumId - - signal showArtist(var name) - signal albumClicked() - - Action { - id: enqueueAction - - text: i18nc("Add whole album to play list", "Enqueue") - iconName: 'media-track-add-amarok' - onTriggered: mediaServerEntry.playListModel.enqueue(mediaServerEntry.albumData) - } - - Action { - id: openAction - - text: i18nc("Open album view", "Open Album") - iconName: 'document-open-folder' - onTriggered: showAlbumTracks() - } - - Action { - id: enqueueAndPlayAction - - text: i18nc("Clear play list and add whole album to play list", "Play Now and Replace Play List") - iconName: 'media-playback-start' - onTriggered: { - mediaServerEntry.playListModel.clearAndEnqueue(mediaServerEntry.albumData) - mediaServerEntry.playerControl.ensurePlay() - } - } - - Keys.onReturnPressed: showAlbumTracks() - Keys.onEnterPressed: showAlbumTracks() - - Component { - id: albumViewComponent - - MediaAlbumView { - stackView: mediaServerEntry.stackView - playListModel: mediaServerEntry.playListModel - musicListener: mediaServerEntry.musicListener - playerControl: mediaServerEntry.playerControl - albumArtUrl: image - albumName: title - artistName: artist - tracksCount: count - isSingleDiscAlbum: mediaServerEntry.isSingleDiscAlbum - albumData: mediaServerEntry.albumData - albumId: mediaServerEntry.albumId - - onShowArtist: mediaServerEntry.showArtist(name) - } - } - - function showAlbumTracks() { - stackView.push(albumViewComponent) - } - - ColumnLayout { - anchors.fill: parent - - spacing: 0 - - MouseArea { - id: hoverHandle - - hoverEnabled: true - acceptedButtons: Qt.LeftButton - focus: true - - Layout.preferredHeight: mediaServerEntry.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + titleSize.height + artistSize.height - Layout.fillWidth: true - - onClicked: { - hoverHandle.forceActiveFocus() - albumClicked() - } - - onDoubleClicked: showAlbumTracks() - - TextMetrics { - id: titleSize - font: titleLabel.font - text: titleLabel.text - } - - TextMetrics { - id: artistSize - font: artistLabel.font - text: artistLabel.text - } - - ColumnLayout { - id: mainData - - spacing: 0 - anchors.fill: parent - - Item { - Layout.preferredHeight: mediaServerEntry.width * 0.85 - Layout.preferredWidth: mediaServerEntry.width * 0.85 - - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - focus: true - - Loader { - id: hoverLoader - active: false - - anchors.centerIn: parent - z: 1 - - opacity: 0 - - sourceComponent: Row { - - ToolButton { - id: enqueueButton - - action: enqueueAction - - width: elisaTheme.delegateToolButtonSize - height: elisaTheme.delegateToolButtonSize - } - - ToolButton { - id: openButton - - action: openAction - - width: elisaTheme.delegateToolButtonSize - height: elisaTheme.delegateToolButtonSize - } - - ToolButton { - id: enqueueAndPlayButton - - action: enqueueAndPlayAction - - width: elisaTheme.delegateToolButtonSize - height: elisaTheme.delegateToolButtonSize - } - } - } - - Image { - id: coverImage - - anchors.fill: parent - - sourceSize.width: parent.width - sourceSize.height: parent.height - fillMode: Image.PreserveAspectFit - smooth: true - - source: (mediaServerEntry.image ? mediaServerEntry.image : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) - - asynchronous: true - - layer.enabled: image === '' ? false : true - layer.effect: DropShadow { - source: coverImage - - radius: 10 - spread: 0.1 - samples: 21 - - color: myPalette.shadow - } - } - } - - LabelWithToolTip { - id: titleLabel - - font.weight: Font.Bold - color: myPalette.text - - horizontalAlignment: Text.AlignLeft - - Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 - Layout.preferredWidth: mediaServerEntry.width * 0.85 - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - - elide: Text.ElideRight - } - - LabelWithToolTip { - id: artistLabel - - font.weight: Font.Light - color: myPalette.text - - horizontalAlignment: Text.AlignLeft - - Layout.preferredWidth: mediaServerEntry.width * 0.85 - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - - elide: Text.ElideRight - } - } - } - - Item { - Layout.fillHeight: true - } - } - - states: [ - State { - name: 'notSelected' - when: !mediaServerEntry.activeFocus && !hoverHandle.containsMouse - PropertyChanges { - target: hoverLoader - active: false - } - PropertyChanges { - target: hoverLoader - opacity: 0.0 - } - PropertyChanges { - target: coverImage - opacity: 1 - } - }, - State { - name: 'hoveredOrSelected' - when: mediaServerEntry.activeFocus || hoverHandle.containsMouse - PropertyChanges { - target: hoverLoader - active: true - } - PropertyChanges { - target: hoverLoader - opacity: 1.0 - } - PropertyChanges { - target: coverImage - opacity: 0.2 - } - } - ] - - transitions: [ - Transition { - to: 'hoveredOrSelected' - SequentialAnimation { - NumberAnimation { - properties: "active" - duration: 0 - } - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - } - }, - Transition { - to: 'notSelected' - SequentialAnimation { - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - NumberAnimation { - properties: "active" - duration: 0 - } - } - } - ] -} diff --git a/src/MediaAlbumView.qml b/src/MediaAlbumView.qml index d541e2a5..51ac0080 100644 --- a/src/MediaAlbumView.qml +++ b/src/MediaAlbumView.qml @@ -1,210 +1,185 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import QtQuick.Window 2.2 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.2 import QtQml.Models 2.1 import org.kde.elisa 1.0 import QtQuick.Layouts 1.2 FocusScope { id: topListing - property StackView stackView - property MediaPlayList playListModel property var musicListener - property var playerControl property var albumName property var artistName - property var tracksCount property var albumArtUrl property bool isSingleDiscAlbum - property var albumData property var albumId + property alias albumModel: contentDirectoryView.model signal showArtist(var name) - - width: stackView.width - height: stackView.height + signal enqueueAlbum(var album) + signal clearPlayList() + signal enqueueTrack(var track) + signal ensurePlay() + signal goBack(); SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } - AlbumModel { - id: contentModel - - albumData: topListing.albumData - } - Connections { target: musicListener onAlbumRemoved: { if (albumId === removedAlbumId) { contentModel.albumRemoved(removedAlbum) } } } Connections { target: musicListener onAlbumModified: { if (albumId === modifiedAlbumId) { - albumData = modifiedAlbum + albumModel.albumData = modifiedAlbum contentModel.albumModified(modifiedAlbum) } } } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navBar height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true - parentStackView: topListing.stackView - playList: topListing.playListModel - playerControl: topListing.playerControl - artist: topListing.artistName - album: topListing.albumName + mainTitle: (topListing.albumModel ? topListing.albumModel.author : '') + secondaryTitle: topListing.albumName image: (topListing.albumArtUrl ? topListing.albumArtUrl : elisaTheme.defaultAlbumImage) - tracksCount: topListing.tracksCount + allowArtistNavigation: true - enqueueAction: Action { - text: i18nc("Add whole album to play list", "Enqueue") - iconName: "media-track-add-amarok" - onTriggered: topListing.playListModel.enqueue(topListing.albumData) - } + onEnqueue: topListing.enqueueAlbum(albumModel.albumData) - clearAndEnqueueAction: Action { - text: i18nc("Clear play list and play", "Replace and Play") - tooltip: i18nc("Clear play list and add whole album to play list", "Replace Play List and Play Now") - iconName: "media-playback-start" - onTriggered: { - topListing.playListModel.clearAndEnqueue(topListing.albumData) - topListing.playerControl.ensurePlay() - } + onEnqueueAndPlay: { + topListing.clearPlayList() + topListing.enqueueAlbum(albumModel.albumData) + topListing.ensurePlay() } - navigateToArtistAction: Action { - text: i18nc("Button to navigate to the artist of the album", "Display Artist") - iconName: "view-media-artist" - onTriggered: { - showArtist(topListing.artistName) - } - } + onGoBack: topListing.goBack() + + onShowArtist: topListing.showArtist((topListing.albumModel ? topListing.albumModel.author : '')) } ScrollView { flickableItem.boundsBehavior: Flickable.StopAtBounds flickableItem.interactive: true Layout.fillHeight: true Layout.fillWidth: true ListView { id: contentDirectoryView focus: true - model: contentModel - delegate: MediaAlbumTrackDelegate { id: entry height: ((model.isFirstTrackOfDisc && !isSingleDiscAlbum) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) width: contentDirectoryView.width focus: true mediaTrack.isAlternateColor: (index % 2) === 1 mediaTrack.title: if (model != undefined && model.title !== undefined) model.title else '' mediaTrack.artist: if (model != undefined && model.artist !== undefined) model.artist else '' mediaTrack.albumArtist: if (model != undefined && model.albumArtist !== undefined) model.albumArtist else '' mediaTrack.duration: if (model != undefined && model.duration !== undefined) model.duration else '' mediaTrack.trackNumber: if (model != undefined && model.trackNumber !== undefined) model.trackNumber else '' mediaTrack.discNumber: if (model != undefined && model.discNumber !== undefined) model.discNumber else '' mediaTrack.rating: if (model != undefined && model.rating !== undefined) model.rating else 0 mediaTrack.trackData: if (model != undefined && model.trackData !== undefined) model.trackData else '' mediaTrack.isFirstTrackOfDisc: if (model != undefined && model.isFirstTrackOfDisc !== undefined) model.isFirstTrackOfDisc else false mediaTrack.isSingleDiscAlbum: if (model != undefined && model.isSingleDiscAlbum !== undefined) model.isSingleDiscAlbum else true - mediaTrack.onClearPlaylist: topListing.playListModel.clearPlayList() + mediaTrack.onClearPlaylist: topListing.clearPlayList() - mediaTrack.onEnqueueToPlaylist: topListing.playListModel.enqueue(track) + mediaTrack.onEnqueueToPlaylist: topListing.enqueueTrack(track) - mediaTrack.onEnsurePlay: topListing.playerControl.ensurePlay() + mediaTrack.onEnsurePlay: topListing.ensurePlay() mediaTrack.onClicked: contentDirectoryView.currentIndex = index } } } } } diff --git a/src/MediaAllAlbumView.qml b/src/MediaAllAlbumView.qml deleted file mode 100644 index d5d83013..00000000 --- a/src/MediaAllAlbumView.qml +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2016-2017 Matthieu Gallien - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.5 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Window 2.2 -import QtQml.Models 2.1 -import QtQuick.Layouts 1.2 -import QtGraphicalEffects 1.0 - -import org.kde.elisa 1.0 - -FocusScope { - property var rootIndex - property StackView stackView - property MediaPlayList playListModel - property var musicListener - property var playerControl - property var contentDirectoryModel - - signal showArtist(var name) - - id: rootElement - - SystemPalette { - id: myPalette - colorGroup: SystemPalette.Active - } - - Theme { - id: elisaTheme - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - FilterBar { - id: filterBar - labelText: i18nc("Title of the view of all albums", "Albums") - - height: elisaTheme.navigationBarHeight - Layout.preferredHeight: height - Layout.minimumHeight: height - Layout.maximumHeight: height - Layout.fillWidth: true - } - - Rectangle { - color: myPalette.base - - Layout.fillHeight: true - Layout.fillWidth: true - - ScrollView { - anchors.fill: parent - flickableItem.boundsBehavior: Flickable.StopAtBounds - flickableItem.interactive: true - - GridView { - id: contentDirectoryView - - focus: true - - TextMetrics { - id: textLineHeight - text: 'Album' - } - - cellWidth: elisaTheme.gridDelegateWidth - cellHeight: elisaTheme.gridDelegateWidth + elisaTheme.layoutVerticalMargin + textLineHeight.height * 2 - - model: DelegateModel { - id: delegateContentModel - - model: AlbumFilterProxyModel { - sourceModel: rootElement.contentDirectoryModel - - filterText: filterBar.filterText - - filterRating: filterBar.filterRating - } - - delegate: MediaAlbumDelegate { - width: contentDirectoryView.cellWidth - height: contentDirectoryView.cellHeight - - focus: true - - musicListener: rootElement.musicListener - image: if (model.image) - model.image - else - "" - title: if (model.title) - model.title - else - "" - artist: if (model.artist) - model.artist - else - "" - trackNumber: if (model.count !== undefined) - i18np("1 track", "%1 tracks", model.count) - else - i18nc("Number of tracks for an album", "0 track") - - isSingleDiscAlbum: model.isSingleDiscAlbum - - albumData: model.albumData - albumId: model.albumId - - stackView: rootElement.stackView - - playListModel: rootElement.playListModel - playerControl: rootElement.playerControl - - onAlbumClicked: contentDirectoryView.currentIndex = index - - onShowArtist: rootElement.showArtist(name) - } - } - } - } - } - } -} diff --git a/src/MediaAllArtistView.qml b/src/MediaAllArtistView.qml deleted file mode 100644 index f433b54b..00000000 --- a/src/MediaAllArtistView.qml +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2016-2017 Matthieu Gallien - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.7 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Window 2.2 -import QtQml.Models 2.1 -import QtQuick.Layouts 1.2 -import QtGraphicalEffects 1.0 - -import org.kde.elisa 1.0 - -FocusScope { - property var playerControl - property var playListModel - property var artistsModel - property var stackView - property var contentDirectoryModel - property var musicListener - - id: rootElement - - SystemPalette { - id: myPalette - colorGroup: SystemPalette.Active - } - - Theme { - id: elisaTheme - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - FilterBar { - id: filterBar - labelText: i18nc("Title of the view of all artists", "Artists") - - showRating: false - height: elisaTheme.navigationBarHeight - Layout.preferredHeight: height - Layout.minimumHeight: height - Layout.maximumHeight: height - Layout.fillWidth: true - } - - Rectangle { - color: myPalette.base - - Layout.fillHeight: true - Layout.fillWidth: true - - ScrollView { - anchors.fill: parent - flickableItem.boundsBehavior: Flickable.StopAtBounds - flickableItem.interactive: true - - GridView { - id: contentDirectoryView - - focus: true - - TextMetrics { - id: textLineHeight - text: 'Artist' - } - - cellWidth: elisaTheme.gridDelegateWidth - cellHeight: elisaTheme.gridDelegateWidth + elisaTheme.layoutVerticalMargin + textLineHeight.height * 2 - - model: DelegateModel { - id: delegateContentModel - - model: SortFilterProxyModel { - sourceModel: artistsModel - - filterRole: AllArtistsModel.NameRole - - filterRegExp: new RegExp(filterBar.filterText, 'i') - } - - delegate: MediaArtistDelegate { - width: contentDirectoryView.cellWidth - height: contentDirectoryView.cellHeight - - focus: true - - musicListener: rootElement.musicListener - - image: if (model.image) - model.image - else - "" - - name: if (model.name) - model.name - else - "" - - stackView: rootElement.stackView - - playListModel: rootElement.playListModel - playerControl: rootElement.playerControl - contentDirectoryModel: rootElement.contentDirectoryModel - - onArtistClicked: contentDirectoryView.currentIndex = index - onOpenArtist: showArtistsAlbums(name) - } - } - } - } - } - } - - Component { - id: oneArtistView - - MediaArtistAlbumView { - playListModel: rootElement.playListModel - contentDirectoryModel: rootElement.contentDirectoryModel - playerControl: rootElement.playerControl - stackView: rootElement.stackView - musicListener: rootElement.musicListener - - onShowArtist: showArtistsAlbums(name) - } - } - - function showArtistsAlbums(name){ - if (stackView.depth === 3) { - stackView.pop() - } else { - stackView.push(oneArtistView, {artistName: name}) - } - } -} diff --git a/src/MediaArtistAlbumView.qml b/src/MediaArtistAlbumView.qml deleted file mode 100644 index 6e7e823f..00000000 --- a/src/MediaArtistAlbumView.qml +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2016-2017 Matthieu Gallien - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.5 -import QtQuick.Layouts 1.2 -import QtQuick.Window 2.2 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.2 -import QtQml.Models 2.1 -import QtGraphicalEffects 1.0 - -import org.kde.elisa 1.0 - -Item { - property var rootIndex - property StackView stackView - property MediaPlayList playListModel - property var musicListener - property var playerControl - property var contentDirectoryModel - - property alias artistName: navBar.artist - - signal showArtist(var name) - - id: rootElement - - SystemPalette { - id: myPalette - colorGroup: SystemPalette.Active - } - - Theme { - id: elisaTheme - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - NavigationActionBar { - id: navBar - - height: elisaTheme.navigationBarHeight - - Layout.preferredHeight: height - Layout.minimumHeight: height - Layout.maximumHeight: height - Layout.fillWidth: true - - parentStackView: rootElement.stackView - playList: rootElement.playListModel - playerControl: rootElement.playerControl - image: Qt.resolvedUrl(elisaTheme.defaultArtistImage) - - enqueueAction: Action { - text: i18nc("Add all tracks from artist to play list", "Enqueue") - iconName: "media-track-add-amarok" - onTriggered: rootElement.playListModel.enqueue(rootElement.artistName) - } - - clearAndEnqueueAction: Action { - text: i18nc("Clear play list and play", "Replace and Play") - tooltip: i18nc("Clear play list and add all tracks from artist to play list", "Replace Play List and Play Now") - iconName: "media-playback-start" - onTriggered: { - rootElement.playListModel.clearAndEnqueue(rootElement.artistName) - rootElement.playerControl.ensurePlay() - } - } - } - - Rectangle { - color: myPalette.base - - Layout.fillHeight: true - Layout.fillWidth: true - - ScrollView { - anchors.fill: parent - flickableItem.boundsBehavior: Flickable.StopAtBounds - flickableItem.interactive: true - - GridView { - id: contentDirectoryView - - TextMetrics { - id: textLineHeight - text: 'Album' - } - - cellWidth: elisaTheme.gridDelegateWidth - cellHeight: elisaTheme.gridDelegateWidth + elisaTheme.layoutVerticalMargin * 3 + textLineHeight.height * 2 - - model: DelegateModel { - id: delegateContentModel - - model: SortFilterProxyModel { - sourceModel: contentDirectoryModel - filterRole: AllAlbumsModel.AllArtistsRole - - filterRegExp: new RegExp('.*' + artistName + '.*', 'i') - } - - delegate: MediaAlbumDelegate { - width: contentDirectoryView.cellWidth - height: contentDirectoryView.cellHeight - - musicListener: rootElement.musicListener - image: if (model.image) - model.image - else - "" - title: if (model.title) - model.title - else - "" - artist: if (model.artist) - model.artist - else - "" - trackNumber: if (model.count !== undefined) - i18np("1 track", "%1 tracks", model.count) - else - i18nc("Number of tracks for an album", "0 track") - - isSingleDiscAlbum: model.isSingleDiscAlbum - - albumData: model.albumData - albumId: model.albumId - - stackView: rootElement.stackView - - playListModel: rootElement.playListModel - playerControl: rootElement.playerControl - - onAlbumClicked: contentDirectoryView.currentIndex = index - - onShowArtist: rootElement.showArtist(name) - } - } - - focus: true - } - } - } - } -} diff --git a/src/MediaBrowser.qml b/src/MediaBrowser.qml index a74b98f0..46c9b317 100644 --- a/src/MediaBrowser.qml +++ b/src/MediaBrowser.qml @@ -1,83 +1,81 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.4 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import org.kde.elisa 1.0 FocusScope { id: contentDirectoryRoot property MediaPlayList playListModel - property var firstPage + property alias firstPage: listingView.initialItem property alias stackView: listingView function goBack() { if (listingView.depth > 1) { listingView.pop() } } ColumnLayout { anchors.fill: parent spacing: 0 StackView { id: listingView Layout.fillHeight: true Layout.fillWidth: true focus: true delegate: StackViewDelegate { function transitionFinished(properties) { properties.exitItem.opacity = 1 } pushTransition: StackViewTransition { PropertyAnimation { target: enterItem property: "opacity" from: 0 to: 1 } PropertyAnimation { target: exitItem property: "opacity" from: 1 to: 0 } } } - - initialItem: firstPage } } MouseArea { anchors.fill: parent acceptedButtons: Qt.BackButton onClicked: goBack() } } diff --git a/src/MediaServer.qml b/src/MediaServer.qml index 6f036b1e..fd062af2 100644 --- a/src/MediaServer.qml +++ b/src/MediaServer.qml @@ -1,973 +1,1065 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import Qt.labs.platform 1.0 as PlatformDialog import org.kde.elisa 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 1000 minimumHeight: 600 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true x: persistentSettings.x y: persistentSettings.y width: persistentSettings.width height: persistentSettings.height title: 'Elisa' property var helpAction: elisa.action("help_contents") property var quitApplication: elisa.action("file_quit") property var reportBugAction: elisa.action("help_report_bug") property var aboutAppAction: elisa.action("help_about_app") property var configureShortcutsAction: elisa.action("options_configure_keybinding") property var configureAction: elisa.action("options_configure") property var goBackAction: elisa.action("go_back") SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 1000 property int height : 600 property var playListState property var playListControlerState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false } - Action { - text: goBackAction.text - shortcut: goBackAction.shortcut - iconName: elisa.iconName(goBackAction.icon) - onTriggered: { - localAlbums.goBack() - localArtists.goBack() - } - } + Action { + text: goBackAction.text + shortcut: goBackAction.shortcut + iconName: elisa.iconName(goBackAction.icon) + onTriggered: { + localAlbums.goBack() + localArtists.goBack() + } + } Action { id: qmlQuitAction text: quitApplication.text shortcut: quitApplication.shortcut iconName: elisa.iconName(quitApplication.icon) onTriggered: quitApplication.trigger() } property string globalBrowseFlag: 'BrowseDirectChildren' property string globalFilter: '*' property string globalSortCriteria: '' Connections { target: Qt.application onAboutToQuit: { persistentSettings.x = mainWindow.x; persistentSettings.y = mainWindow.y; persistentSettings.width = mainWindow.width; persistentSettings.height = mainWindow.height; persistentSettings.playListState = playListModelItem.persistentState; persistentSettings.playListControlerState = playListModelItem.persistentState; persistentSettings.audioPlayerState = manageAudioPlayer.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted } } PlatformIntegration { id: platformInterface playListModel: playListModelItem playListControler: playListModelItem audioPlayerManager: manageAudioPlayer headerBarManager: myHeaderBarManager manageMediaPlayerControl: myPlayControlManager player: audioPlayer onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } MusicListenersManager { id: allListeners elisaApplication: elisa - } + } AudioWrapper { id: audioPlayer muted: headerBar.playerControl.muted volume: headerBar.playerControl.volume onVolumeChanged: headerBar.playerControl.volume = volume onMutedChanged: headerBar.playerControl.muted = muted source: manageAudioPlayer.playerSource onPlaying: { myPlayControlManager.playerPlaying() } onPaused: { myPlayControlManager.playerPaused() } onStopped: { myPlayControlManager.playerStopped() } } MediaPlayList { id: playListModelItem persistentState: persistentSettings.playListState musicListenersManager: allListeners onPlayListFinished: manageAudioPlayer.playListFinished() Component.onCompleted: { var d = new Date(); var n = d.getMilliseconds(); seedRandomGenerator(n); playFiles(elisa.arguments) } onPlayListLoadFailed: { messageNotification.showNotification(i18nc("message of passive notification when playlist load failed", "Load of playlist failed"), 3000) } function playFiles(listFiles) { var previousTrackNumber = tracksCount enqueue(listFiles) switchTo(previousTrackNumber) manageAudioPlayer.ensurePlay() } } Connections { target: elisa onEnqueue: { playListModelItem.playFiles(files) } } ManageHeaderBar { id: myHeaderBarManager playListModel: playListModelItem currentTrack: playListModelItem.currentTrack artistRole: MediaPlayList.ArtistRole titleRole: MediaPlayList.TitleRole albumRole: MediaPlayList.AlbumRole imageRole: MediaPlayList.ImageRole isValidRole: MediaPlayList.IsValidRole } ManageAudioPlayer { id: manageAudioPlayer currentTrack: playListModelItem.currentTrack playListModel: playListModelItem urlRole: MediaPlayList.ResourceRole isPlayingRole: MediaPlayList.IsPlayingRole titleRole: MediaPlayList.TitleRole artistNameRole: MediaPlayList.ArtistRole albumNameRole: MediaPlayList.AlbumRole playerStatus: audioPlayer.status playerPlaybackState: audioPlayer.playbackState playerError: audioPlayer.error audioDuration: audioPlayer.duration playerIsSeekable: audioPlayer.seekable playerPosition: audioPlayer.position persistentState: persistentSettings.audioPlayerState onPlayerPlay: audioPlayer.play() onPlayerPause: audioPlayer.pause() onPlayerStop: audioPlayer.stop() onSkipNextTrack: playListModelItem.skipNextTrack() onSeek: audioPlayer.seek(position) onSourceInError: { playListModelItem.trackInError(source, playerError) allListeners.playBackError(source, playerError) } onDisplayTrackError: messageNotification.showNotification(i18n("Error when playing %1", "" + fileName), 3000) } ManageMediaPlayerControl { id: myPlayControlManager playListModel: playListModelItem currentTrack: playListModelItem.currentTrack } AllAlbumsModel { id: allAlbumsModel + + allArtists: allArtistsModel + } + + AllArtistsModel { + id: allArtistsModel + + allAlbums: allAlbumsModel } AllTracksModel { id: allTracksModel } Connections { target: allListeners onAlbumAdded: { busyScanningMusic.running = false allAlbumsModel.albumAdded(newAlbum) } } Connections { target: allListeners onAlbumRemoved: { allAlbumsModel.albumRemoved(removedAlbum) } } Connections { target: allListeners onAlbumModified: allAlbumsModel.albumModified(modifiedAlbum) } Connections { target: allListeners onTracksAdded: allTracksModel.tracksAdded(allTracks) } Connections { target: allListeners onTrackRemoved: allTracksModel.trackRemoved(id) } Connections { target: allListeners onTrackModified: allTracksModel.trackModified(modifiedTrack) } - AllArtistsModel { - id: allArtistsModel - } - Connections { target: allListeners onArtistAdded: allArtistsModel.artistAdded(newArtist) } Connections { target: allListeners onArtistRemoved: allArtistsModel.artistRemoved(removedArtist) } Connections { target: allListeners onArtistModified: allArtistsModel.artistModified(modifiedArtist) } Menu { id: applicationMenu title: i18nc("open application menu", "Application Menu") MenuItem { text: configureAction.text shortcut: configureAction.shortcut iconName: 'configure' onTriggered: configureAction.trigger() visible: configureAction.text !== "" } MenuItem { text: configureShortcutsAction.text shortcut: configureShortcutsAction.shortcut iconName: elisa.iconName(configureShortcutsAction.icon) onTriggered: configureShortcutsAction.trigger() visible: configureShortcutsAction.text !== "" } MenuSeparator { visible: reportBugAction.text !== "" } MenuItem { text: reportBugAction.text shortcut: reportBugAction.shortcut iconName: elisa.iconName(reportBugAction.icon) onTriggered: reportBugAction.trigger() visible: reportBugAction.text !== "" } MenuSeparator { visible: helpAction.text !== "" } MenuItem { text: helpAction.text shortcut: helpAction.shortcut iconName: elisa.iconName(helpAction.icon) onTriggered: helpAction.trigger() visible: helpAction.text !== "" } MenuItem { text: aboutAppAction.text shortcut: aboutAppAction.shortcut iconName: elisa.iconName(aboutAppAction.icon) onTriggered: aboutAppAction.trigger() visible: aboutAppAction.text !== "" } MenuSeparator { visible: qmlQuitAction.text !== "" } MenuItem { action: qmlQuitAction visible: qmlQuitAction.text !== "" } } Action { id: applicationMenuAction text: i18nc("open application menu", "Application Menu") iconName: "application-menu" onTriggered: applicationMenu.popup() } PassiveNotification { id: messageNotification } Rectangle { color: myPalette.base anchors.fill: parent ColumnLayout { anchors.fill: parent spacing: 0 Item { Layout.preferredHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.fillWidth: true HeaderBar { id: headerBar focus: true anchors.fill: parent tracksCount: myHeaderBarManager.remainingTracks album: myHeaderBarManager.album title: myHeaderBarManager.title artist: myHeaderBarManager.artist image: myHeaderBarManager.image ratingVisible: false playerControl.duration: audioPlayer.duration playerControl.seekable: audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: audioPlayer.position playerControl.skipBackwardEnabled: myPlayControlManager.skipBackwardControlEnabled playerControl.skipForwardEnabled: myPlayControlManager.skipForwardControlEnabled playerControl.playEnabled: myPlayControlManager.playControlEnabled playerControl.isPlaying: myPlayControlManager.musicPlaying playerControl.onSeek: audioPlayer.seek(position) playerControl.onPlay: manageAudioPlayer.playPause() playerControl.onPause: manageAudioPlayer.playPause() playerControl.onPlayPrevious: playListModelItem.skipPreviousTrack() playerControl.onPlayNext: playListModelItem.skipNextTrack() ToolButton { id: menuButton action: applicationMenuAction z: 2 anchors { right: parent.right top: parent.top rightMargin: elisaTheme.layoutHorizontalMargin * 3 topMargin: elisaTheme.layoutHorizontalMargin * 3 } } Rectangle { anchors.fill: menuButton z: 1 radius: width / 2 color: myPalette.window } TrackImportNotification { id: importedTracksCountNotification anchors { right: menuButton.left top: menuButton.top bottom: menuButton.bottom rightMargin: elisaTheme.layoutHorizontalMargin * 3 } indexingRunning: allListeners.indexingRunning importedTracksCount: allListeners.importedTracksCount musicManager: allListeners } } } RowLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 ViewSelector { id: listViews Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.15 Layout.maximumWidth: mainWindow.width * 0.15 } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 TopNotification { id: invalidBalooConfiguration Layout.fillWidth: true musicManager: allListeners focus: true } Item { Layout.fillHeight: true Layout.fillWidth: true RowLayout { anchors.fill: parent spacing: 0 id: contentZone FocusScope { id: mainContentView focus: true Layout.fillHeight: true Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 visible: Layout.minimumWidth != 0 Rectangle { border { color: (mainContentView.activeFocus ? myPalette.highlight : myPalette.base) width: 1 } radius: 3 color: myPalette.base anchors.fill: parent BusyIndicator { id: busyScanningMusic anchors.fill: parent anchors.leftMargin: parent.width / 3 anchors.rightMargin: parent.width / 3 anchors.topMargin: parent.height / 3 anchors.bottomMargin: parent.height / 3 opacity: 0.8 z: 2 running: true } MediaBrowser { id: localAlbums focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } - firstPage: MediaAllAlbumView { + firstPage: GridBrowserView { + id: allAlbumsView + focus: true - playListModel: playListModelItem - playerControl: manageAudioPlayer - stackView: localAlbums.stackView - musicListener: allListeners - contentDirectoryModel: allAlbumsModel - onShowArtist: { - listViews.currentIndex = 2 - allArtistsView.showArtistsAlbums(name) + isFirstPage: true + + model: AlbumFilterProxyModel { + sourceModel: allAlbumsModel } + + mainTitle: i18nc("Title of the view of all albums", "Albums") + + onEnqueue: playListModelItem.enqueue(data) + onEnqueueAndPlay: { + playListModelItem.clearAndEnqueue(data) + manageAudioPlayer.ensurePlay() + } + onOpen: { + localAlbums.stackView.push(albumView, { + stackView: localAlbums.stackView, + albumName: innerMainTitle, + albumModel: innerModel, + albumArtUrl: innerImage, + musicListener: allListeners + }) + } + onGoBack: localAlbums.stackView.pop() } visible: opacity > 0 } MediaBrowser { id: localArtists focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } - firstPage: MediaAllArtistView { + firstPage: GridBrowserView { id: allArtistsView - focus: true - playListModel: playListModelItem - artistsModel: allArtistsModel - playerControl: manageAudioPlayer - stackView: localArtists.stackView - musicListener: allListeners - contentDirectoryModel: allAlbumsModel + + isFirstPage: true + showRating: false + delegateDisplaySecondaryText: false + + model: AlbumFilterProxyModel { + sourceModel: allArtistsModel + } + + mainTitle: i18nc("Title of the view of all artists", "Artists") + + onEnqueue: playListModelItem.enqueue(data) + onEnqueueAndPlay: { + playListModelItem.clearAndEnqueue(data) + manageAudioPlayer.ensurePlay() + } + onOpen: { + localArtists.stackView.push(innerAlbumView, { + model: innerModel, + mainTitle: innerMainTitle, + secondaryTitle: innerSecondaryTitle, + image: innerImage, + stackView: localArtists.stackView + }) + } + onGoBack: localArtists.stackView.pop() } visible: opacity > 0 } MediaBrowser { id: localTracks focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: MediaAllTracksView { focus: true playListModel: playListModelItem tracksModel: allTracksModel playerControl: manageAudioPlayer stackView: localTracks.stackView musicListener: allListeners } visible: opacity > 0 } Behavior on border.color { ColorAnimation { duration: 300 } } } } Rectangle { id: firstViewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } MediaPlayListView { id: playList playListModel: playListModelItem playListControler: playListModelItem randomPlayChecked: playListModelItem.randomPlay repeatPlayChecked: playListModelItem.repeatPlay Layout.fillHeight: true Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width Component.onCompleted: { playListModelItem.randomPlay = Qt.binding(function() { return playList.randomPlayChecked }) playListModelItem.repeatPlay = Qt.binding(function() { return playList.repeatPlayChecked }) myPlayControlManager.randomOrContinuePlay = Qt.binding(function() { return playList.randomPlayChecked || playList.repeatPlayChecked }) } onStartPlayback: manageAudioPlayer.ensurePlay() onPausePlayback: manageAudioPlayer.playPause() onDisplayError: messageNotification.showNotification(errorText) } Rectangle { id: viewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: Layout.minimumWidth != 0 Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } ContextView { id: albumContext Layout.fillHeight: true Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width visible: Layout.minimumWidth != 0 artistName: myHeaderBarManager.artist albumName: myHeaderBarManager.album albumArtUrl: myHeaderBarManager.image } } } states: [ State { name: 'full' when: listViews.currentIndex === 0 PropertyChanges { target: mainContentView Layout.fillWidth: false Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: albumContext Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allAlbums' when: listViews.currentIndex === 1 StateChangeScript { script: { localAlbums.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 1 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allArtists' when: listViews.currentIndex === 2 StateChangeScript { script: { localArtists.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 1 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allTracks' when: listViews.currentIndex === 3 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 1 } } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" easing.type: Easing.InOutQuad duration: 300 } } } } } } + + Component { + id: innerAlbumView + + GridBrowserView { + property var stackView + + onEnqueue: playListModelItem.enqueue(data) + onEnqueueAndPlay: { + playListModelItem.clearAndEnqueue(data) + manageAudioPlayer.ensurePlay() + } + onOpen: { + localArtists.stackView.push(albumView, { + stackView: localArtists.stackView, + albumName: innerMainTitle, + albumModel: innerModel, + musicListener: allListeners + }) + } + onGoBack: stackView.pop() + } + } + + Component { + id: albumView + + MediaAlbumView { + property var stackView + + onEnsurePlay: manageAudioPlayer.ensurePlay() + onClearPlayList: playListModelItem.clearPlayList() + onEnqueueAlbum: playListModelItem.enqueue(album) + onEnqueueTrack: playListModelItem.enqueue(track) + onShowArtist: { + listViews.currentIndex = 2 + if (localArtists.stackView.depth === 3) { + localArtists.stackView.pop() + } + if (localArtists.stackView.depth === 2) { + var artistPage = localArtists.stackView.get(1) + if (artistPage.mainTitle === name) { + return + } else { + localArtists.stackView.pop() + } + } + + allArtistsView.open(allArtistsModel.itemModelForName(name), name, '', elisaTheme.defaultArtistImage) + } + onGoBack: stackView.pop() + } + } } diff --git a/src/NavigationActionBar.qml b/src/NavigationActionBar.qml index c8ac9486..95b65ce1 100644 --- a/src/NavigationActionBar.qml +++ b/src/NavigationActionBar.qml @@ -1,184 +1,191 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQml 2.2 import QtQuick 2.5 import QtQuick.Layouts 1.3 import QtQuick.Controls 1.4 import QtQuick.Window 2.0 Item { id: navigationBar - property var parentStackView - property var playList - property var playerControl - property string artist - property string album - property string image - property string tracksCount - property var enqueueAction - property var clearAndEnqueueAction - property var navigateToArtistAction: Action { } + property string mainTitle + property string secondaryTitle + property url image + property bool allowArtistNavigation: false + + signal enqueue(); + signal enqueueAndPlay(); + signal goBack(); + signal showArtist(); Action { id: goPreviousAction text: i18nc("navigate back in the views stack", "Back") iconName: (Qt.application.layoutDirection == Qt.RightToLeft) ? "go-next" : "go-previous" - onTriggered: - { - parentStackView.pop() - } + onTriggered: goBack() } RowLayout { anchors.fill: parent anchors.margins: { top: elisaTheme.layoutVerticalMargin bottom: elisaTheme.layoutVerticalMargin } spacing: 0 ToolButton { action: goPreviousAction Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } Image { id: mainIcon source: image asynchronous: true sourceSize.height: elisaTheme.coverImageSize / 2 sourceSize.width: elisaTheme.coverImageSize / 2 fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.preferredHeight: elisaTheme.coverImageSize / 2 Layout.minimumHeight: elisaTheme.coverImageSize / 2 Layout.maximumHeight: elisaTheme.coverImageSize / 2 Layout.preferredWidth: elisaTheme.coverImageSize / 2 Layout.minimumWidth: elisaTheme.coverImageSize / 2 Layout.maximumWidth: elisaTheme.coverImageSize / 2 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } ColumnLayout { Layout.fillHeight: true spacing: 0 Layout.fillWidth: true Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 TextMetrics { id: albumTextSize text: albumLabel.text font.pixelSize: albumLabel.font.pixelSize font.bold: albumLabel.font.bold } LabelWithToolTip { id: albumLabel - text: album + text: secondaryTitle Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter elide: Text.ElideRight color: myPalette.text font { pixelSize: elisaTheme.defaultFontPixelSize * 1.5 } - visible: album !== "" + visible: secondaryTitle !== "" } TextMetrics { id: authorTextSize text: authorLabel.text font.pixelSize: authorLabel.font.pixelSize font.bold: authorLabel.font.bold } LabelWithToolTip { id: authorLabel - text: artist + text: mainTitle color: myPalette.text Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter font { - pixelSize: (album !== "" ? elisaTheme.defaultFontPixelSize : elisaTheme.defaultFontPixelSize * 1.5) + pixelSize: (secondaryTitle !== "" ? elisaTheme.defaultFontPixelSize : elisaTheme.defaultFontPixelSize * 1.5) } elide: Text.ElideRight } Item { id: emptyBottomFiller Layout.fillWidth: true Layout.fillHeight: true } RowLayout { Layout.fillWidth: true spacing: 0 Button { - action: enqueueAction + text: i18nc("Add current list to playlist", "Enqueue") + iconName: "media-track-add-amarok" + + onClicked: enqueue() Layout.leftMargin: 0 Layout.rightMargin: 0 } Button { - action: clearAndEnqueueAction + text: i18nc("Clear playlist and play", "Replace and Play") + tooltip: i18nc("Clear playlist and add current list to it", "Replace PlayList and Play Now") + iconName: "media-playback-start" + + onClicked: enqueueAndPlay() Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } Button { - action: navigateToArtistAction + id: showArtistButton + + visible: allowArtistNavigation + text: i18nc("Button to navigate to the artist of the album", "Display Artist") + iconName: "view-media-artist" + + onClicked: showArtist() Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - - visible: album !== "" } } } } } diff --git a/src/albumfilterproxymodel.cpp b/src/albumfilterproxymodel.cpp index fc5c1a6a..38392ffd 100644 --- a/src/albumfilterproxymodel.cpp +++ b/src/albumfilterproxymodel.cpp @@ -1,118 +1,119 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "albumfilterproxymodel.h" #include "allalbumsmodel.h" +#include "allartistsmodel.h" AlbumFilterProxyModel::AlbumFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent), mFilterText() { setFilterCaseSensitivity(Qt::CaseInsensitive); } AlbumFilterProxyModel::~AlbumFilterProxyModel() = default; QString AlbumFilterProxyModel::filterText() const { return mFilterText; } int AlbumFilterProxyModel::filterRating() const { return mFilterRating; } void AlbumFilterProxyModel::setFilterText(const QString &filterText) { if (mFilterText == filterText) return; mFilterText = filterText; mFilterExpression.setPattern(mFilterText); mFilterExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); mFilterExpression.optimize(); invalidate(); Q_EMIT filterTextChanged(mFilterText); } void AlbumFilterProxyModel::setFilterRating(int filterRating) { if (mFilterRating == filterRating) { return; } mFilterRating = filterRating; invalidate(); Q_EMIT filterRatingChanged(filterRating); } bool AlbumFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { bool result = false; for (int column = 0, columnCount = sourceModel()->columnCount(source_parent); column < columnCount; ++column) { auto currentIndex = sourceModel()->index(source_row, column, source_parent); const auto &titleValue = sourceModel()->data(currentIndex, AllAlbumsModel::TitleRole).toString(); const auto &artistValue = sourceModel()->data(currentIndex, AllAlbumsModel::ArtistRole).toString(); const auto &allArtistsValue = sourceModel()->data(currentIndex, AllAlbumsModel::AllArtistsRole).toStringList(); const auto maximumRatingValue = sourceModel()->data(currentIndex, AllAlbumsModel::HighestTrackRating).toInt(); if (maximumRatingValue < mFilterRating) { result = false; continue; } if (mFilterExpression.match(titleValue).hasMatch()) { result = true; continue; } if (mFilterExpression.match(artistValue).hasMatch()) { result = true; continue; } for (const auto &oneArtist : allArtistsValue) { if (mFilterExpression.match(oneArtist).hasMatch()) { result = true; break; } } if (result) { continue; } if (!result) { break; } } return result; } #include "moc_albumfilterproxymodel.cpp" diff --git a/src/albummodel.cpp b/src/albummodel.cpp index 9eef5ed3..1931c5a5 100644 --- a/src/albummodel.cpp +++ b/src/albummodel.cpp @@ -1,417 +1,440 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "albummodel.h" #include "musicstatistics.h" #include "databaseinterface.h" #include #include #include #include class AlbumModelPrivate { public: AlbumModelPrivate() { } - QString mTitle; - - QString mAuthor; - MusicAlbum mCurrentAlbum; }; AlbumModel::AlbumModel(QObject *parent) : QAbstractItemModel(parent), d(std::make_unique()) { } AlbumModel::~AlbumModel() = default; int AlbumModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->mCurrentAlbum.tracksCount(); } QHash AlbumModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::TitleRole)] = "title"; roles[static_cast(ColumnsRoles::DurationRole)] = "duration"; roles[static_cast(ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(ColumnsRoles::AlbumRole)] = "album"; roles[static_cast(ColumnsRoles::AlbumArtistRole)] = "albumArtist"; roles[static_cast(ColumnsRoles::TrackNumberRole)] = "trackNumber"; roles[static_cast(ColumnsRoles::DiscNumberRole)] = "discNumber"; roles[static_cast(ColumnsRoles::RatingRole)] = "rating"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::DatabaseIdRole)] = "databaseId"; roles[static_cast(ColumnsRoles::DiscFirstTrackRole)] = "isFirstTrackOfDisc"; roles[static_cast(ColumnsRoles::IsSingleDiscAlbumRole)] = "isSingleDiscAlbum"; roles[static_cast(ColumnsRoles::TrackDataRole)] = "trackData"; roles[static_cast(ColumnsRoles::ResourceRole)] = "trackResource"; + roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; + roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; + roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; return roles; } Qt::ItemFlags AlbumModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant AlbumModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); if (!index.isValid()) { return result; } if (index.column() != 0) { return result; } if (index.row() < 0) { return result; } if (index.parent().isValid()) { return result; } if (index.row() >= d->mCurrentAlbum.tracksCount()) { return result; } const auto ¤tTrack = d->mCurrentAlbum.trackFromIndex(index.row()); if (!currentTrack.isValid()) { return result; } result = internalDataTrack(currentTrack, role, index.row()); return result; } QVariant AlbumModel::internalDataTrack(const MusicAudioTrack &track, int role, int rowIndex) const { auto result = QVariant(); - ColumnsRoles convertedRole = static_cast(role); - - switch(convertedRole) + switch(role) { case ColumnsRoles::TitleRole: result = track.title(); break; case ColumnsRoles::MilliSecondsDurationRole: result = track.duration().msecsSinceStartOfDay(); break; case ColumnsRoles::DurationRole: { QTime trackDuration = track.duration(); if (trackDuration.hour() == 0) { result = trackDuration.toString(QStringLiteral("mm:ss")); } else { result = trackDuration.toString(); } break; } case ColumnsRoles::CreatorRole: result = track.artist(); break; case ColumnsRoles::ArtistRole: result = track.artist(); break; case ColumnsRoles::AlbumRole: result = track.albumName(); break; case ColumnsRoles::AlbumArtistRole: result = track.albumArtist(); break; case ColumnsRoles::TrackNumberRole: result = track.trackNumber(); break; case ColumnsRoles::DiscNumberRole: if (track.discNumber() > 0) { result = track.discNumber(); } break; case ColumnsRoles::DiscFirstTrackRole: if (rowIndex == 0) { result = true; } else { auto previousTrack = d->mCurrentAlbum.trackFromIndex(rowIndex - 1); result = (previousTrack.discNumber() != track.discNumber()); } break; case ColumnsRoles::IsSingleDiscAlbumRole: result = track.isSingleDiscAlbum(); break; case ColumnsRoles::RatingRole: result = track.rating(); break; case ColumnsRoles::ImageRole: { if (d->mCurrentAlbum.albumArtURI().isValid()) { result = d->mCurrentAlbum.albumArtURI(); } break; } case ColumnsRoles::ResourceRole: result = track.resourceURI(); break; case ColumnsRoles::IdRole: result = track.title(); break; case ColumnsRoles::DatabaseIdRole: result = track.databaseId(); break; case ColumnsRoles::TrackDataRole: result = QVariant::fromValue(track); break; + case Qt::DisplayRole: + result = track.title(); + break; + case ColumnsRoles::SecondaryTextRole: + { + auto secondaryText = QString(); + secondaryText = QStringLiteral("%1 - %2%3"); + + secondaryText = secondaryText.arg(track.trackNumber()); + secondaryText = secondaryText.arg(track.title()); + + if (track.artist() == track.albumArtist()) { + secondaryText = secondaryText.arg(QString()); + } else { + auto artistText = QString(); + artistText = QStringLiteral(" - %1"); + artistText = artistText.arg(track.artist()); + secondaryText = secondaryText.arg(artistText); + } + + result = secondaryText; + break; + } + case ColumnsRoles::ImageUrlRole: + { + const auto &albumArtUri = d->mCurrentAlbum.albumArtURI(); + if (albumArtUri.isValid()) { + result = albumArtUri; + } else { + result = QUrl(QStringLiteral("image://icon/media-optical-audio")); + } + break; + } + case ColumnsRoles::ShadowForImageRole: + result = d->mCurrentAlbum.albumArtURI().isValid(); + break; } return result; } QModelIndex AlbumModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } if (row >= d->mCurrentAlbum.tracksCount()) { return result; } return createIndex(row, column); } QModelIndex AlbumModel::parent(const QModelIndex &child) const { Q_UNUSED(child) return {}; } int AlbumModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } MusicAlbum AlbumModel::albumData() const { return d->mCurrentAlbum; } QString AlbumModel::title() const { - return d->mTitle; + return d->mCurrentAlbum.title(); } QString AlbumModel::author() const { - return d->mAuthor; + return d->mCurrentAlbum.artist(); +} + +int AlbumModel::tracksCount() const +{ + return d->mCurrentAlbum.tracksCount(); } void AlbumModel::setAlbumData(const MusicAlbum &album) { if (d->mCurrentAlbum == album) { return; } if (d->mCurrentAlbum.tracksCount() > 0) { beginRemoveRows({}, 0, d->mCurrentAlbum.tracksCount() - 1); d->mCurrentAlbum = {}; endRemoveRows(); } beginInsertRows({}, 0, album.tracksCount() - 1); d->mCurrentAlbum = album; endInsertRows(); Q_EMIT albumDataChanged(); -} - -void AlbumModel::setTitle(const QString &title) -{ - if (d->mTitle == title) - return; - - d->mTitle = title; - emit titleChanged(); -} - -void AlbumModel::setAuthor(const QString &author) -{ - if (d->mAuthor == author) - return; - - d->mAuthor = author; - emit authorChanged(); + Q_EMIT tracksCountChanged(); + Q_EMIT authorChanged(); + Q_EMIT titleChanged(); } void AlbumModel::albumModified(const MusicAlbum &modifiedAlbum) { if (modifiedAlbum.databaseId() != d->mCurrentAlbum.databaseId()) { return; } auto removedTracks = QList(); for (auto i = 0; i < d->mCurrentAlbum.tracksCount(); ++i) { bool trackExist = false; for (auto j = 0; j < modifiedAlbum.tracksCount() && !trackExist; ++j) { trackExist = (d->mCurrentAlbum.trackIdFromIndex(i) == modifiedAlbum.trackIdFromIndex(j)); if (trackExist) { const auto &oldTrack = d->mCurrentAlbum.trackFromIndex(i); const auto &newTrack = modifiedAlbum.trackFromIndex(j); if (oldTrack != newTrack) { trackModified(newTrack); } } } if (!trackExist) { const auto &oldTrack = d->mCurrentAlbum.trackFromIndex(i); removedTracks.push_back(oldTrack); } } for (const auto &removedTrack : removedTracks) { trackRemoved(removedTrack); } for (auto j = 0; j < modifiedAlbum.tracksCount(); ++j) { bool trackExist = false; for (auto i = 0; i < d->mCurrentAlbum.tracksCount() && !trackExist; ++i) { trackExist = (d->mCurrentAlbum.trackIdFromIndex(i) == modifiedAlbum.trackIdFromIndex(j)); } if (!trackExist) { const auto &newTrack = modifiedAlbum.trackFromIndex(j); trackAdded(newTrack); } } } void AlbumModel::albumRemoved(const MusicAlbum &modifiedAlbum) { if (modifiedAlbum.databaseId() != d->mCurrentAlbum.databaseId()) { return; } for (int trackIndex = d->mCurrentAlbum.tracksCount() - 1; trackIndex >= 0 ; --trackIndex) { trackRemoved(d->mCurrentAlbum.trackFromIndex(trackIndex)); } } void AlbumModel::trackAdded(const MusicAudioTrack &newTrack) { if (newTrack.albumName() != d->mCurrentAlbum.title()) { return; } auto trackIndex = d->mCurrentAlbum.trackIndexFromId(newTrack.databaseId()); if (trackIndex != -1) { return; } bool trackInserted = false; for (int trackIndex = 0; trackIndex < d->mCurrentAlbum.tracksCount(); ++trackIndex) { const auto &oneTrack = d->mCurrentAlbum.trackFromIndex(trackIndex); if (oneTrack.discNumber() == newTrack.discNumber() && oneTrack.trackNumber() > newTrack.trackNumber()) { beginInsertRows({}, trackIndex, trackIndex); d->mCurrentAlbum.insertTrack(newTrack, trackIndex); endInsertRows(); trackInserted = true; break; } } if (!trackInserted) { beginInsertRows({}, d->mCurrentAlbum.tracksCount(), d->mCurrentAlbum.tracksCount()); d->mCurrentAlbum.insertTrack(newTrack, d->mCurrentAlbum.tracksCount()); endInsertRows(); } } void AlbumModel::trackModified(const MusicAudioTrack &modifiedTrack) { if (modifiedTrack.albumName() != d->mCurrentAlbum.title()) { return; } auto trackIndex = d->mCurrentAlbum.trackIndexFromId(modifiedTrack.databaseId()); if (trackIndex == -1) { return; } d->mCurrentAlbum.updateTrack(modifiedTrack, trackIndex); Q_EMIT dataChanged(index(trackIndex, 0), index(trackIndex, 0)); } void AlbumModel::trackRemoved(const MusicAudioTrack &removedTrack) { if (removedTrack.albumName() != d->mCurrentAlbum.title()) { return; } auto trackIndex = d->mCurrentAlbum.trackIndexFromId(removedTrack.databaseId()); if (trackIndex == -1) { return; } beginRemoveRows({}, trackIndex, trackIndex); d->mCurrentAlbum.removeTrackFromIndex(trackIndex); endRemoveRows(); } #include "moc_albummodel.cpp" diff --git a/src/albummodel.h b/src/albummodel.h index c40dcd91..dc6524c2 100644 --- a/src/albummodel.h +++ b/src/albummodel.h @@ -1,148 +1,153 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ALBUMMODEL_H #define ALBUMMODEL_H #include #include #include #include #include "musicalbum.h" #include "musicaudiotrack.h" #include class DatabaseInterface; class AlbumModelPrivate; class MusicStatistics; class QMutex; class AlbumModel : public QAbstractItemModel { Q_OBJECT Q_PROPERTY(MusicAlbum albumData READ albumData WRITE setAlbumData NOTIFY albumDataChanged) Q_PROPERTY(QString title READ title - WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(QString author READ author - WRITE setAuthor NOTIFY authorChanged) + Q_PROPERTY(int tracksCount + READ tracksCount + NOTIFY tracksCountChanged) + public: enum ItemClass { Container = 0, Album = 1, Artist = 2, AudioTrack = 3, }; enum ColumnsRoles { TitleRole = Qt::UserRole + 1, DurationRole, MilliSecondsDurationRole, CreatorRole, ArtistRole, AlbumRole, AlbumArtistRole, TrackNumberRole, DiscNumberRole, RatingRole, ImageRole, ResourceRole, IdRole, DatabaseIdRole, DiscFirstTrackRole, IsSingleDiscAlbumRole, TrackDataRole, + SecondaryTextRole, + ImageUrlRole, + ShadowForImageRole, }; Q_ENUM(ColumnsRoles) explicit AlbumModel(QObject *parent = nullptr); ~AlbumModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; MusicAlbum albumData() const; QString title() const; QString author() const; + int tracksCount() const; + Q_SIGNALS: void albumDataChanged(); void titleChanged(); void authorChanged(); + void tracksCountChanged(); + public Q_SLOTS: void setAlbumData(const MusicAlbum &album); - void setTitle(const QString &title); - - void setAuthor(const QString &author); - void albumModified(const MusicAlbum &modifiedAlbum); void albumRemoved(const MusicAlbum &modifiedAlbum); private: void trackAdded(const MusicAudioTrack &newTrack); void trackModified(const MusicAudioTrack &modifiedTrack); void trackRemoved(const MusicAudioTrack &removedTrack); QVariant internalDataTrack(const MusicAudioTrack &track, int role, int rowIndex) const; std::unique_ptr d; }; Q_DECLARE_METATYPE(AlbumModel::ItemClass) #endif // ALBUMMODEL_H diff --git a/src/allalbumsmodel.cpp b/src/allalbumsmodel.cpp index 44740b4a..27ff8bdb 100644 --- a/src/allalbumsmodel.cpp +++ b/src/allalbumsmodel.cpp @@ -1,259 +1,314 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "allalbumsmodel.h" #include "musicstatistics.h" #include "databaseinterface.h" +#include "allartistsmodel.h" +#include "albummodel.h" #include #include #include #include #include class AllAlbumsModelPrivate { public: AllAlbumsModelPrivate() { } QVector mAllAlbums; int mAlbumCount = 0; + AllArtistsModel *mAllArtistsModel = nullptr; + }; AllAlbumsModel::AllAlbumsModel(QObject *parent) : QAbstractItemModel(parent), d(std::make_unique()) { } AllAlbumsModel::~AllAlbumsModel() = default; int AllAlbumsModel::albumCount() const { return d->mAlbumCount; } int AllAlbumsModel::rowCount(const QModelIndex &parent) const { auto albumCount = 0; if (parent.isValid()) { return albumCount; } albumCount = d->mAlbumCount; return albumCount; } QHash AllAlbumsModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::TitleRole)] = "title"; roles[static_cast(ColumnsRoles::AllTracksTitleRole)] = "tracksTitle"; roles[static_cast(ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(ColumnsRoles::AllArtistsRole)] = "allArtists"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::CountRole)] = "count"; roles[static_cast(ColumnsRoles::IsSingleDiscAlbumRole)] = "isSingleDiscAlbum"; roles[static_cast(ColumnsRoles::AlbumDataRole)] = "albumData"; roles[static_cast(ColumnsRoles::HighestTrackRating)] = "highestTrackRating"; roles[static_cast(ColumnsRoles::AlbumDatabaseIdRole)] = "albumId"; + roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; + roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; + roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; + roles[static_cast(ColumnsRoles::ContainerDataRole)] = "containerData"; + roles[static_cast(ColumnsRoles::ChildModelRole)] = "childModel"; + roles[static_cast(ColumnsRoles::IsTracksContainerRole)] = "isTracksContainer"; return roles; } Qt::ItemFlags AllAlbumsModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant AllAlbumsModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); const auto albumCount = d->mAlbumCount; if (!index.isValid()) { return result; } if (index.column() != 0) { return result; } if (index.row() < 0) { return result; } if (index.parent().isValid()) { return result; } if (index.internalId() != 0) { return result; } if (index.row() < 0 || index.row() >= albumCount) { return result; } result = internalDataAlbum(index.row(), role); return result; } QVariant AllAlbumsModel::internalDataAlbum(int albumIndex, int role) const { auto result = QVariant(); - ColumnsRoles convertedRole = static_cast(role); - - switch(convertedRole) + switch(role) { case ColumnsRoles::TitleRole: result = d->mAllAlbums[albumIndex].title(); break; case ColumnsRoles::AllTracksTitleRole: result = d->mAllAlbums[albumIndex].allTracksTitle(); break; case ColumnsRoles::ArtistRole: result = d->mAllAlbums[albumIndex].artist(); break; case ColumnsRoles::AllArtistsRole: result = d->mAllAlbums[albumIndex].allArtists().join(QStringLiteral(", ")); break; case ColumnsRoles::ImageRole: { auto albumArt = d->mAllAlbums[albumIndex].albumArtURI(); if (albumArt.isValid()) { result = albumArt; } break; } case ColumnsRoles::CountRole: result = d->mAllAlbums[albumIndex].tracksCount(); break; case ColumnsRoles::IdRole: result = d->mAllAlbums[albumIndex].id(); break; case ColumnsRoles::IsSingleDiscAlbumRole: result = d->mAllAlbums[albumIndex].isSingleDiscAlbum(); break; case ColumnsRoles::AlbumDataRole: result = QVariant::fromValue(d->mAllAlbums[albumIndex]); break; case ColumnsRoles::AlbumDatabaseIdRole: result = QVariant::fromValue(d->mAllAlbums[albumIndex].databaseId()); break; case ColumnsRoles::HighestTrackRating: result = d->mAllAlbums[albumIndex].highestTrackRating(); break; + case Qt::DisplayRole: + result = d->mAllAlbums[albumIndex].title(); + break; + case ColumnsRoles::SecondaryTextRole: + result = d->mAllAlbums[albumIndex].artist(); + break; + case ColumnsRoles::ImageUrlRole: + { + auto albumArt = d->mAllAlbums[albumIndex].albumArtURI(); + if (albumArt.isValid()) { + result = albumArt; + } else { + result = QUrl(QStringLiteral("image://icon/media-optical-audio")); + } + break; + } + case ColumnsRoles::ShadowForImageRole: + result = d->mAllAlbums[albumIndex].albumArtURI().isValid(); + break; + case ColumnsRoles::ContainerDataRole: + result = QVariant::fromValue(d->mAllAlbums[albumIndex]); + break; + case ColumnsRoles::ChildModelRole: + { + auto newModel = new AlbumModel(); + newModel->setAlbumData(d->mAllAlbums[albumIndex]); + result = QVariant::fromValue(newModel); + break; + } + case ColumnsRoles::IsTracksContainerRole: + result = true; + break; } return result; } QModelIndex AllAlbumsModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } result = createIndex(row, column); return result; } QModelIndex AllAlbumsModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } int AllAlbumsModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } +AllArtistsModel *AllAlbumsModel::allArtists() const +{ + return d->mAllArtistsModel; +} + void AllAlbumsModel::albumAdded(const MusicAlbum &newAlbum) { if (newAlbum.isValid()) { beginInsertRows({}, d->mAllAlbums.size(), d->mAllAlbums.size()); d->mAllAlbums.push_back(newAlbum); ++d->mAlbumCount; endInsertRows(); Q_EMIT albumCountChanged(); } } void AllAlbumsModel::albumRemoved(const MusicAlbum &removedAlbum) { auto removedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), removedAlbum); if (removedAlbumIterator == d->mAllAlbums.end()) { return; } int albumIndex = removedAlbumIterator - d->mAllAlbums.begin(); beginRemoveRows({}, albumIndex, albumIndex); d->mAllAlbums.erase(removedAlbumIterator); --d->mAlbumCount; endRemoveRows(); Q_EMIT albumCountChanged(); } void AllAlbumsModel::albumModified(const MusicAlbum &modifiedAlbum) { auto modifiedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), modifiedAlbum); if (modifiedAlbumIterator == d->mAllAlbums.end()) { return; } int albumIndex = modifiedAlbumIterator - d->mAllAlbums.begin(); d->mAllAlbums[albumIndex] = modifiedAlbum; Q_EMIT dataChanged(index(albumIndex, 0), index(albumIndex, 0)); } +void AllAlbumsModel::setAllArtists(AllArtistsModel *model) +{ + if (d->mAllArtistsModel == model) { + return; + } + + d->mAllArtistsModel = model; + Q_EMIT allArtistsChanged(); +} + #include "moc_allalbumsmodel.cpp" diff --git a/src/allalbumsmodel.h b/src/allalbumsmodel.h index cd6c21fc..7678cc16 100644 --- a/src/allalbumsmodel.h +++ b/src/allalbumsmodel.h @@ -1,103 +1,120 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ALLALBUMSMODEL_H #define ALLALBUMSMODEL_H #include #include #include #include #include "musicalbum.h" #include "musicaudiotrack.h" #include class AllAlbumsModelPrivate; class MusicStatistics; -class QMutex; +class AllArtistsModel; class AllAlbumsModel : public QAbstractItemModel { Q_OBJECT Q_PROPERTY(int albumCount READ albumCount NOTIFY albumCountChanged) + Q_PROPERTY(AllArtistsModel* allArtists + READ allArtists + WRITE setAllArtists + NOTIFY allArtistsChanged) + public: enum ColumnsRoles { TitleRole = Qt::UserRole + 1, AllTracksTitleRole, ArtistRole, AllArtistsRole, ImageRole, CountRole, IdRole, IsSingleDiscAlbumRole, AlbumDataRole, HighestTrackRating, AlbumDatabaseIdRole, + SecondaryTextRole, + ImageUrlRole, + ShadowForImageRole, + ContainerDataRole, + ChildModelRole, + IsTracksContainerRole, }; Q_ENUM(ColumnsRoles) explicit AllAlbumsModel(QObject *parent = nullptr); ~AllAlbumsModel() override; Q_INVOKABLE int albumCount() const; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; + AllArtistsModel *allArtists() const; + public Q_SLOTS: void albumAdded(const MusicAlbum &newAlbum); void albumRemoved(const MusicAlbum &removedAlbum); void albumModified(const MusicAlbum &modifiedAlbum); + void setAllArtists(AllArtistsModel *model); + Q_SIGNALS: void albumCountChanged(); + void allArtistsChanged(); + private: QVariant internalDataAlbum(int albumIndex, int role) const; std::unique_ptr d; }; #endif // ALLALBUMSMODEL_H diff --git a/src/allartistsmodel.cpp b/src/allartistsmodel.cpp index c254f77a..432d2176 100644 --- a/src/allartistsmodel.cpp +++ b/src/allartistsmodel.cpp @@ -1,199 +1,262 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "allartistsmodel.h" #include "databaseinterface.h" #include "musicartist.h" +#include "allalbumsmodel.h" #include #include #include #include +#include class AllArtistsModelPrivate { public: AllArtistsModelPrivate() { } QVector mAllArtists; int mArtistsCount = 0; bool mUseLocalIcons = false; + AllAlbumsModel *mAllAlbumsModel = nullptr; + }; AllArtistsModel::AllArtistsModel(QObject *parent) : QAbstractItemModel(parent), d(std::make_unique()) { } AllArtistsModel::~AllArtistsModel() = default; int AllArtistsModel::rowCount(const QModelIndex &parent) const { auto artistCount = 0; if (parent.isValid()) { return artistCount; } artistCount = d->mArtistsCount; return artistCount; } QHash AllArtistsModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::NameRole)] = "name"; roles[static_cast(ColumnsRoles::ArtistsCountRole)] = "albumsCount"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::IdRole)] = "id"; + roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; + roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; + roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; + roles[static_cast(ColumnsRoles::ContainerDataRole)] = "containerData"; + roles[static_cast(ColumnsRoles::ChildModelRole)] = "childModel"; + roles[static_cast(ColumnsRoles::IsTracksContainerRole)] = "isTracksContainer"; return roles; } Qt::ItemFlags AllArtistsModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant AllArtistsModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); const auto artistsCount = d->mArtistsCount; if (!index.isValid()) { return result; } if (index.column() != 0) { return result; } if (index.row() < 0) { return result; } if (index.parent().isValid()) { return result; } if (index.internalId() != 0) { return result; } if (index.row() < 0 || index.row() >= artistsCount) { return result; } - ColumnsRoles convertedRole = static_cast(role); - - switch(convertedRole) + switch(role) { case ColumnsRoles::NameRole: result = d->mAllArtists[index.row()].name(); break; case ColumnsRoles::ArtistsCountRole: result = d->mAllArtists[index.row()].albumsCount(); break; case ColumnsRoles::ImageRole: break; case ColumnsRoles::IdRole: break; + case ColumnsRoles::SecondaryTextRole: + result = QString(); + break; + case Qt::DisplayRole: + result = d->mAllArtists[index.row()].name(); + break; + case ColumnsRoles::ImageUrlRole: + result = QUrl(QStringLiteral("image://icon/view-media-artist")); + break; + case ColumnsRoles::ShadowForImageRole: + result = false; + break; + case ColumnsRoles::ContainerDataRole: + result = QVariant::fromValue(d->mAllArtists[index.row()]); + break; + case ColumnsRoles::ChildModelRole: + { + auto newModel = new QSortFilterProxyModel; + newModel->setSourceModel(d->mAllAlbumsModel); + newModel->setFilterRole(AllAlbumsModel::AllArtistsRole); + newModel->setFilterRegExp(QRegExp(QStringLiteral(".*") + d->mAllArtists[index.row()].name() + QStringLiteral(".*"), + Qt::CaseInsensitive)); + result = QVariant::fromValue(newModel); + break; + } + case ColumnsRoles::IsTracksContainerRole: + result = false; + break; } return result; } QModelIndex AllArtistsModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } result = createIndex(row, column); return result; } QModelIndex AllArtistsModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } int AllArtistsModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } +AllAlbumsModel *AllArtistsModel::allAlbums() const +{ + return d->mAllAlbumsModel; +} + +QAbstractItemModel *AllArtistsModel::itemModelForName(const QString &name) const +{ + auto newModel = new QSortFilterProxyModel; + newModel->setSourceModel(d->mAllAlbumsModel); + newModel->setFilterRole(AllAlbumsModel::AllArtistsRole); + newModel->setFilterRegExp(QRegExp(QStringLiteral(".*") + name + QStringLiteral(".*"), + Qt::CaseInsensitive)); + + return newModel; +} + void AllArtistsModel::artistAdded(const MusicArtist &newArtist) { if (newArtist.isValid()) { beginInsertRows({}, d->mAllArtists.size(), d->mAllArtists.size()); d->mAllArtists.push_back(newArtist); ++d->mArtistsCount; endInsertRows(); } } void AllArtistsModel::artistRemoved(const MusicArtist &removedArtist) { auto removedArtistIterator = std::find(d->mAllArtists.begin(), d->mAllArtists.end(), removedArtist); if (removedArtistIterator == d->mAllArtists.end()) { return; } int artistIndex = removedArtistIterator - d->mAllArtists.begin(); beginRemoveRows({}, artistIndex, artistIndex); d->mAllArtists.erase(removedArtistIterator); --d->mArtistsCount; endRemoveRows(); } void AllArtistsModel::artistModified(const MusicArtist &modifiedArtist) { Q_UNUSED(modifiedArtist); } +void AllArtistsModel::setAllAlbums(AllAlbumsModel *model) +{ + if (d->mAllAlbumsModel == model) { + return; + } + + d->mAllAlbumsModel = model; + + Q_EMIT allAlbumsChanged(); +} + #include "moc_allartistsmodel.cpp" diff --git a/src/allartistsmodel.h b/src/allartistsmodel.h index 6b7acf05..8560b465 100644 --- a/src/allartistsmodel.h +++ b/src/allartistsmodel.h @@ -1,82 +1,104 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ALLARTISTSMODEL_H #define ALLARTISTSMODEL_H #include #include #include #include #include "musicartist.h" #include class DatabaseInterface; class AllArtistsModelPrivate; +class AllAlbumsModel; class AllArtistsModel : public QAbstractItemModel { Q_OBJECT + Q_PROPERTY(AllAlbumsModel* allAlbums + READ allAlbums + WRITE setAllAlbums + NOTIFY allAlbumsChanged) + public: enum ColumnsRoles { NameRole = Qt::UserRole + 1, ArtistsCountRole, ImageRole, IdRole, + SecondaryTextRole, + ImageUrlRole, + ShadowForImageRole, + ContainerDataRole, + ChildModelRole, + IsTracksContainerRole, }; Q_ENUM(ColumnsRoles) explicit AllArtistsModel(QObject *parent = nullptr); ~AllArtistsModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; + AllAlbumsModel* allAlbums() const; + + Q_INVOKABLE QAbstractItemModel* itemModelForName(const QString &name) const; + +Q_SIGNALS: + + void allAlbumsChanged(); + public Q_SLOTS: void artistAdded(const MusicArtist &newArtist); void artistRemoved(const MusicArtist &removedArtist); void artistModified(const MusicArtist &modifiedArtist); + void setAllAlbums(AllAlbumsModel *model); + private: std::unique_ptr d; }; #endif // ALLARTISTSMODEL_H diff --git a/src/alltracksmodel.cpp b/src/alltracksmodel.cpp index eb699325..91bef77d 100644 --- a/src/alltracksmodel.cpp +++ b/src/alltracksmodel.cpp @@ -1,284 +1,323 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "alltracksmodel.h" #include #include class AllTracksModelPrivate { public: QHash mAllTracks; QList mIds; }; AllTracksModel::AllTracksModel(QObject *parent) : QAbstractItemModel(parent), d(std::make_unique()) { } AllTracksModel::~AllTracksModel() = default; int AllTracksModel::rowCount(const QModelIndex &parent) const { auto tracksCount = 0; if (parent.isValid()) { return tracksCount; } tracksCount = d->mAllTracks.size(); return tracksCount; } QHash AllTracksModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::TitleRole)] = "title"; roles[static_cast(ColumnsRoles::DurationRole)] = "duration"; roles[static_cast(ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(ColumnsRoles::AlbumRole)] = "album"; roles[static_cast(ColumnsRoles::AlbumArtistRole)] = "albumArtist"; roles[static_cast(ColumnsRoles::TrackNumberRole)] = "trackNumber"; roles[static_cast(ColumnsRoles::DiscNumberRole)] = "discNumber"; roles[static_cast(ColumnsRoles::RatingRole)] = "rating"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::DatabaseIdRole)] = "databaseId"; roles[static_cast(ColumnsRoles::IsSingleDiscAlbumRole)] = "isSingleDiscAlbum"; roles[static_cast(ColumnsRoles::TrackDataRole)] = "trackData"; roles[static_cast(ColumnsRoles::ResourceRole)] = "trackResource"; + roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; + roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; + roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; return roles; } Qt::ItemFlags AllTracksModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant AllTracksModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); const auto tracksCount = d->mAllTracks.size(); if (!index.isValid()) { return result; } if (index.column() != 0) { return result; } if (index.row() < 0) { return result; } if (index.parent().isValid()) { return result; } if (index.internalId() != 0) { return result; } if (index.row() < 0 || index.row() >= tracksCount) { return result; } - ColumnsRoles convertedRole = static_cast(role); + const auto &track = d->mAllTracks[d->mIds[index.row()]]; - switch(convertedRole) + switch(role) { case ColumnsRoles::TitleRole: if (d->mAllTracks[d->mIds[index.row()]].title().isEmpty()) { result = {}; } result = d->mAllTracks[d->mIds[index.row()]].title(); break; case ColumnsRoles::MilliSecondsDurationRole: result = d->mAllTracks[d->mIds[index.row()]].duration().msecsSinceStartOfDay(); break; case ColumnsRoles::DurationRole: { QTime trackDuration = d->mAllTracks[d->mIds[index.row()]].duration(); if (trackDuration.hour() == 0) { result = trackDuration.toString(QStringLiteral("mm:ss")); } else { result = trackDuration.toString(); } break; } case ColumnsRoles::CreatorRole: result = d->mAllTracks[d->mIds[index.row()]].artist(); break; case ColumnsRoles::ArtistRole: result = d->mAllTracks[d->mIds[index.row()]].artist(); break; case ColumnsRoles::AlbumRole: result = d->mAllTracks[d->mIds[index.row()]].albumName(); break; case ColumnsRoles::AlbumArtistRole: result = d->mAllTracks[d->mIds[index.row()]].albumArtist(); break; case ColumnsRoles::TrackNumberRole: result = d->mAllTracks[d->mIds[index.row()]].trackNumber(); break; case ColumnsRoles::DiscNumberRole: { const auto discNumber = d->mAllTracks[d->mIds[index.row()]].discNumber(); if (discNumber > 0) { result = discNumber; } break; } case ColumnsRoles::IsSingleDiscAlbumRole: result = d->mAllTracks[d->mIds[index.row()]].isSingleDiscAlbum(); break; case ColumnsRoles::RatingRole: result = d->mAllTracks[d->mIds[index.row()]].rating(); break; case ColumnsRoles::ImageRole: { const auto &imageUrl = d->mAllTracks[d->mIds[index.row()]].albumCover(); if (imageUrl.isValid()) { result = imageUrl; } break; } case ColumnsRoles::ResourceRole: result = d->mAllTracks[d->mIds[index.row()]].resourceURI(); break; case ColumnsRoles::IdRole: result = d->mAllTracks[d->mIds[index.row()]].title(); break; case ColumnsRoles::DatabaseIdRole: result = d->mAllTracks[d->mIds[index.row()]].databaseId(); break; case ColumnsRoles::TrackDataRole: result = QVariant::fromValue(d->mAllTracks[d->mIds[index.row()]]); break; + case Qt::DisplayRole: + result = track.title(); + break; + case ColumnsRoles::SecondaryTextRole: + { + auto secondaryText = QString(); + secondaryText = QStringLiteral("%1 - %2%3"); + + secondaryText = secondaryText.arg(track.trackNumber()); + secondaryText = secondaryText.arg(track.title()); + + if (track.artist() == track.albumArtist()) { + secondaryText = secondaryText.arg(QString()); + } else { + auto artistText = QString(); + artistText = QStringLiteral(" - %1"); + artistText = artistText.arg(track.artist()); + secondaryText = secondaryText.arg(artistText); + } + + result = secondaryText; + break; + } + case ColumnsRoles::ImageUrlRole: + { + const auto &imageUrl = d->mAllTracks[d->mIds[index.row()]].albumCover(); + if (imageUrl.isValid()) { + result = imageUrl; + } else { + result = QUrl(QStringLiteral("image://icon/media-optical-audio")); + } + break; + } + case ColumnsRoles::ShadowForImageRole: + result = d->mAllTracks[d->mIds[index.row()]].albumCover().isValid(); + break; } return result; } QModelIndex AllTracksModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } if (row > d->mAllTracks.size() - 1) { return result; } result = createIndex(row, column); return result; } QModelIndex AllTracksModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } int AllTracksModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } void AllTracksModel::tracksAdded(const QList &allTracks) { auto newAllTracks = d->mAllTracks; auto newTracksIds = QList(); int countNewTracks = 0; for (const auto &oneTrack : allTracks) { if (newAllTracks.find(oneTrack.databaseId()) == newAllTracks.end()) { newAllTracks[oneTrack.databaseId()] = oneTrack; newTracksIds.push_back(oneTrack.databaseId()); ++countNewTracks; } } if (countNewTracks > 0) { beginInsertRows({}, d->mAllTracks.size(), d->mAllTracks.size() + countNewTracks - 1); d->mAllTracks = newAllTracks; d->mIds.append(newTracksIds); endInsertRows(); } } void AllTracksModel::trackRemoved(qulonglong removedTrackId) { auto itTrack = std::find(d->mIds.begin(), d->mIds.end(), removedTrackId); if (itTrack == d->mIds.end()) { return; } auto position = itTrack - d->mIds.begin(); beginRemoveRows({}, position, position); d->mIds.erase(itTrack); d->mAllTracks.remove(removedTrackId); endRemoveRows(); } void AllTracksModel::trackModified(const MusicAudioTrack &modifiedTrack) { auto itTrack = std::find(d->mIds.begin(), d->mIds.end(), modifiedTrack.databaseId()); if (itTrack == d->mIds.end()) { return; } auto position = itTrack - d->mIds.begin(); d->mAllTracks[modifiedTrack.databaseId()] = modifiedTrack; Q_EMIT dataChanged(index(position, 0), index(position, 0)); } #include "moc_alltracksmodel.cpp" diff --git a/src/alltracksmodel.h b/src/alltracksmodel.h index 6efb2f71..cb333c04 100644 --- a/src/alltracksmodel.h +++ b/src/alltracksmodel.h @@ -1,90 +1,93 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ALLTRACKSMODEL_H #define ALLTRACKSMODEL_H #include #include "musicaudiotrack.h" #include class AllTracksModelPrivate; class AllTracksModel : public QAbstractItemModel { Q_OBJECT public: enum ColumnsRoles { TitleRole = Qt::UserRole + 1, DurationRole, MilliSecondsDurationRole, CreatorRole, ArtistRole, AlbumRole, AlbumArtistRole, TrackNumberRole, DiscNumberRole, RatingRole, ImageRole, ResourceRole, IdRole, DatabaseIdRole, IsSingleDiscAlbumRole, TrackDataRole, + SecondaryTextRole, + ImageUrlRole, + ShadowForImageRole, }; Q_ENUM(ColumnsRoles) explicit AllTracksModel(QObject *parent = nullptr); ~AllTracksModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; public Q_SLOTS: void tracksAdded(const QList &allTracks); void trackRemoved(qulonglong removedTrackId); void trackModified(const MusicAudioTrack &modifiedTrack); private: std::unique_ptr d; }; #endif // ALLTRACKSMODEL_H diff --git a/src/mediaplaylist.cpp b/src/mediaplaylist.cpp index 269a1ea2..c8cb7889 100644 --- a/src/mediaplaylist.cpp +++ b/src/mediaplaylist.cpp @@ -1,1109 +1,1152 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "mediaplaylist.h" #include "databaseinterface.h" #include "musicaudiotrack.h" #include "musiclistenersmanager.h" #include #include #include #include #include #include #include class MediaPlayListPrivate { public: QList mData; QList mTrackData; MusicListenersManager* mMusicListenersManager = nullptr; QPersistentModelIndex mCurrentTrack; int mCurrentPlayListPosition = 0; bool mRandomPlay = false; bool mRepeatPlay = false; QVariantMap mPersistentState; QMediaPlaylist mLoadPlaylist; }; MediaPlayList::MediaPlayList(QObject *parent) : QAbstractListModel(parent), d(new MediaPlayListPrivate) { connect(&d->mLoadPlaylist, &QMediaPlaylist::loaded, this, &MediaPlayList::loadPlayListLoaded); connect(&d->mLoadPlaylist, &QMediaPlaylist::loadFailed, this, &MediaPlayList::loadPlayListLoadFailed); } MediaPlayList::~MediaPlayList() = default; int MediaPlayList::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->mData.size(); } QVariant MediaPlayList::data(const QModelIndex &index, int role) const { auto result = QVariant(); if (!index.isValid()) { return result; } if (index.row() < 0 || index.row() >= d->mData.size()) { return result; } - if (role < ColumnsRoles::IsValidRole || role > ColumnsRoles::IsSingleDiscAlbumHeader) { - return result; - } - - ColumnsRoles convertedRole = static_cast(role); - if (d->mData[index.row()].mIsValid) { - switch(convertedRole) + switch(role) { case ColumnsRoles::IsValidRole: result = d->mData[index.row()].mIsValid; break; case ColumnsRoles::TitleRole: if (!d->mTrackData[index.row()].title().isEmpty()) { result = d->mTrackData[index.row()].title(); } else { if (d->mData[index.row()].mTrackUrl.isLocalFile()) { auto localFile = QFileInfo(d->mData[index.row()].mTrackUrl.toLocalFile()); result = localFile.fileName(); } else { result = d->mData[index.row()].mTrackUrl.toString(); } } break; case ColumnsRoles::DurationRole: { const QTime &trackDuration = d->mTrackData[index.row()].duration(); if (trackDuration.hour() == 0) { result = trackDuration.toString(QStringLiteral("mm:ss")); } else { result = trackDuration.toString(); } break; } case ColumnsRoles::MilliSecondsDurationRole: result = d->mTrackData[index.row()].duration().msecsSinceStartOfDay(); break; case ColumnsRoles::ArtistRole: result = d->mTrackData[index.row()].artist(); break; case ColumnsRoles::AlbumArtistRole: result = d->mTrackData[index.row()].albumArtist(); break; case ColumnsRoles::AlbumRole: result = d->mTrackData[index.row()].albumName(); break; case ColumnsRoles::TrackNumberRole: result = d->mTrackData[index.row()].trackNumber(); break; case ColumnsRoles::DiscNumberRole: if (d->mTrackData[index.row()].discNumber() > 0) { result = d->mTrackData[index.row()].discNumber(); } break; case ColumnsRoles::IsSingleDiscAlbumHeader: result = d->mTrackData[index.row()].isSingleDiscAlbum(); break; case ColumnsRoles::ResourceRole: if (d->mTrackData[index.row()].resourceURI().isValid()) { result = d->mTrackData[index.row()].resourceURI(); } else { result = d->mData[index.row()].mTrackUrl; } break; case ColumnsRoles::ImageRole: { auto albumArt = d->mTrackData[index.row()].albumCover(); if (albumArt.isValid()) { result = albumArt; } break; } case ColumnsRoles::HasAlbumHeader: result = rowHasHeader(index.row()); break; case ColumnsRoles::RatingRole: result = d->mTrackData[index.row()].rating(); break; case ColumnsRoles::CountRole: break; case ColumnsRoles::CreatorRole: break; case ColumnsRoles::IsPlayingRole: result = d->mData[index.row()].mIsPlaying; break; + case Qt::DisplayRole: + { + const auto &track = d->mTrackData[index.row()]; + auto displayText = QString(); + displayText = QStringLiteral("%1 - %2"); + + if (track.isSingleDiscAlbum()) { + displayText = displayText.arg(track.trackNumber()); + } else { + auto numbersText = QString(); + numbersText = QStringLiteral("%1 - %2"); + numbersText = numbersText.arg(track.discNumber()); + numbersText = numbersText.arg(track.trackNumber()); + displayText = displayText.arg(numbersText); + } + + result = displayText.arg(track.title()); + break; + } + case ColumnsRoles::SecondaryTextRole: + break; + case ColumnsRoles::ImageUrlRole: + { + const auto &albumArt = d->mTrackData[index.row()].albumCover(); + if (albumArt.isValid()) { + result = albumArt; + } else { + result = QUrl(QStringLiteral("image://icon/media-optical-audio")); + } + break; + } + case ColumnsRoles::ShadowForImageRole: + result = d->mTrackData[index.row()].albumCover().isValid(); + break; } } else { - switch(convertedRole) + switch(role) { case ColumnsRoles::IsValidRole: result = d->mData[index.row()].mIsValid; break; case ColumnsRoles::TitleRole: if (!d->mData[index.row()].mTitle.isEmpty()) { result = d->mData[index.row()].mTitle; } else if (d->mData[index.row()].mTrackUrl.isValid()) { if (d->mData[index.row()].mTrackUrl.isLocalFile()) { auto localFile = QFileInfo(d->mData[index.row()].mTrackUrl.toLocalFile()); result = localFile.fileName(); } else { result = d->mData[index.row()].mTrackUrl.toString(); } } break; case ColumnsRoles::IsPlayingRole: result = d->mData[index.row()].mIsPlaying; break; case ColumnsRoles::ArtistRole: result = d->mData[index.row()].mArtist; break; case ColumnsRoles::AlbumArtistRole: result = d->mData[index.row()].mArtist; break; case ColumnsRoles::AlbumRole: result = d->mData[index.row()].mAlbum; break; case ColumnsRoles::TrackNumberRole: result = -1; break; case ColumnsRoles::HasAlbumHeader: result = rowHasHeader(index.row()); break; case ColumnsRoles::DurationRole: break; case ColumnsRoles::DiscNumberRole: break; case ColumnsRoles::IsSingleDiscAlbumHeader: break; case ColumnsRoles::MilliSecondsDurationRole: break; case ColumnsRoles::ResourceRole: break; case ColumnsRoles::RatingRole: break; case ColumnsRoles::CountRole: break; case ColumnsRoles::CreatorRole: break; case ColumnsRoles::ImageRole: result = QStringLiteral(""); break; + case Qt::DisplayRole: + result = d->mTrackData[index.row()].title(); + break; + case ColumnsRoles::SecondaryTextRole: + result = QString(); + break; + case ColumnsRoles::ImageUrlRole: + result = QUrl(QStringLiteral("image://icon/error")); + break; + case ColumnsRoles::ShadowForImageRole: + result = false; + break; } } return result; } bool MediaPlayList::setData(const QModelIndex &index, const QVariant &value, int role) { bool modelModified = false; if (!index.isValid()) { return modelModified; } if (index.row() < 0 || index.row() >= d->mData.size()) { return modelModified; } if (role < ColumnsRoles::IsValidRole || role > ColumnsRoles::HasAlbumHeader) { return modelModified; } ColumnsRoles convertedRole = static_cast(role); switch(convertedRole) { case ColumnsRoles::IsPlayingRole: { modelModified = true; PlayState newState = static_cast(value.toInt()); d->mData[index.row()].mIsPlaying = newState; Q_EMIT dataChanged(index, index, {role}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } break; } default: modelModified = false; } return modelModified; } QHash MediaPlayList::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::IsValidRole)] = "isValid"; roles[static_cast(ColumnsRoles::TitleRole)] = "title"; roles[static_cast(ColumnsRoles::DurationRole)] = "duration"; roles[static_cast(ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(ColumnsRoles::AlbumArtistRole)] = "albumArtist"; roles[static_cast(ColumnsRoles::AlbumRole)] = "album"; roles[static_cast(ColumnsRoles::TrackNumberRole)] = "trackNumber"; roles[static_cast(ColumnsRoles::DiscNumberRole)] = "discNumber"; roles[static_cast(ColumnsRoles::RatingRole)] = "rating"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::CountRole)] = "count"; roles[static_cast(ColumnsRoles::IsPlayingRole)] = "isPlaying"; roles[static_cast(ColumnsRoles::HasAlbumHeader)] = "hasAlbumHeader"; roles[static_cast(ColumnsRoles::IsSingleDiscAlbumHeader)] = "isSingleDiscAlbum"; + roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; + roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; + roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; return roles; } bool MediaPlayList::removeRows(int row, int count, const QModelIndex &parent) { beginRemoveRows(parent, row, row + count - 1); bool hadAlbumHeader = false; if (rowCount() > row + count) { hadAlbumHeader = rowHasHeader(row + count); } for (int i = row, cpt = 0; cpt < count; ++i, ++cpt) { d->mData.removeAt(i); d->mTrackData.removeAt(i); } endRemoveRows(); if (!d->mCurrentTrack.isValid()) { d->mCurrentTrack = index(d->mCurrentPlayListPosition, 0); if (d->mCurrentTrack.isValid()) { notifyCurrentTrackChanged(); } if (!d->mCurrentTrack.isValid()) { Q_EMIT playListFinished(); resetCurrentTrack(); if (!d->mCurrentTrack.isValid()) { notifyCurrentTrackChanged(); } } } if (!d->mCurrentTrack.isValid() && rowCount(parent) <= row) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); if (hadAlbumHeader != rowHasHeader(row)) { Q_EMIT dataChanged(index(row, 0), index(row, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } Q_EMIT persistentStateChanged(); return false; } void MediaPlayList::enqueue(qulonglong newTrackId) { enqueue(MediaPlayListEntry(newTrackId)); } void MediaPlayList::enqueue(const MusicAudioTrack &newTrack) { enqueue(MediaPlayListEntry(newTrack), newTrack); } void MediaPlayList::clearAndEnqueue(qulonglong newTrackId) { clearPlayList(); enqueue(MediaPlayListEntry(newTrackId)); } void MediaPlayList::clearAndEnqueue(const MusicAudioTrack &newTrack) { clearPlayList(); enqueue(newTrack); } void MediaPlayList::enqueue(const MediaPlayListEntry &newEntry, const MusicAudioTrack &audioTrack) { beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); d->mData.push_back(newEntry); if (audioTrack.isValid()) { d->mTrackData.push_back(audioTrack); } else { d->mTrackData.push_back({}); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT persistentStateChanged(); if (!newEntry.mIsValid) { if (newEntry.mTrackUrl.isValid()) { qDebug() << "MediaPlayList::enqueue" << "newTrackByFileNameInList" << newEntry.mTrackUrl; if (newEntry.mTrackUrl.isLocalFile()) { QFileInfo newTrackFile(newEntry.mTrackUrl.toLocalFile()); if (newTrackFile.exists()) { d->mData.last().mIsValid = true; } Q_EMIT newTrackByFileNameInList(newEntry.mTrackUrl); } } else { Q_EMIT newTrackByNameInList(newEntry.mTitle, newEntry.mArtist, newEntry.mAlbum, newEntry.mTrackNumber, newEntry.mDiscNumber); } } else { Q_EMIT newTrackByIdInList(newEntry.mId); } Q_EMIT trackHasBeenAdded(data(index(d->mData.size() - 1, 0), ColumnsRoles::TitleRole).toString(), data(index(d->mData.size() - 1, 0), ColumnsRoles::ImageRole).toUrl()); if (!newEntry.mIsValid) { Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } bool MediaPlayList::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) { if (sourceParent != destinationParent) { return false; } if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild)) { return false; } auto firstMovedTrackHasHeader = rowHasHeader(sourceRow); auto nextTrackHasHeader = rowHasHeader(sourceRow + count); auto futureNextTrackHasHeader = rowHasHeader(destinationChild); if (sourceRow < destinationChild) { nextTrackHasHeader = rowHasHeader(sourceRow + count); } for (auto cptItem = 0; cptItem < count; ++cptItem) { if (sourceRow < destinationChild) { d->mData.move(sourceRow, destinationChild - 1); d->mTrackData.move(sourceRow, destinationChild - 1); } else { d->mData.move(sourceRow, destinationChild); d->mTrackData.move(sourceRow, destinationChild); } } endMoveRows(); if (sourceRow < destinationChild) { if (firstMovedTrackHasHeader != rowHasHeader(destinationChild - count)) { Q_EMIT dataChanged(index(destinationChild - count, 0), index(destinationChild - count, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } else { if (firstMovedTrackHasHeader != rowHasHeader(destinationChild)) { Q_EMIT dataChanged(index(destinationChild, 0), index(destinationChild, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } if (sourceRow < destinationChild) { if (nextTrackHasHeader != rowHasHeader(sourceRow)) { Q_EMIT dataChanged(index(sourceRow, 0), index(sourceRow, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } else { if (nextTrackHasHeader != rowHasHeader(sourceRow + count)) { Q_EMIT dataChanged(index(sourceRow + count, 0), index(sourceRow + count, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } if (sourceRow < destinationChild) { if (futureNextTrackHasHeader != rowHasHeader(destinationChild + count - 1)) { Q_EMIT dataChanged(index(destinationChild + count - 1, 0), index(destinationChild + count - 1, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } else { if (futureNextTrackHasHeader != rowHasHeader(destinationChild + count)) { Q_EMIT dataChanged(index(destinationChild + count, 0), index(destinationChild + count, 0), {ColumnsRoles::HasAlbumHeader}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } Q_EMIT persistentStateChanged(); return true; } void MediaPlayList::move(int from, int to, int n) { if (from < to) { moveRows({}, from, n, {}, to + 1); } else { moveRows({}, from, n, {}, to); } } void MediaPlayList::enqueue(const MusicAlbum &album) { for (auto oneTrackIndex = 0; oneTrackIndex < album.tracksCount(); ++oneTrackIndex) { enqueue(album.trackFromIndex(oneTrackIndex)); } } void MediaPlayList::enqueue(const MusicArtist &artist) { enqueue(artist.name()); } void MediaPlayList::enqueue(const QString &artistName) { beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); d->mData.push_back(MediaPlayListEntry{artistName}); d->mTrackData.push_back({}); endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); Q_EMIT newArtistInList(artistName); Q_EMIT persistentStateChanged(); } void MediaPlayList::enqueue(const QUrl &fileName) { qDebug() << "MediaPlayList::enqueue" << fileName; enqueue(MediaPlayListEntry(fileName)); } void MediaPlayList::enqueue(const QStringList &files) { qDebug() << "MediaPlayList::enqueue" << files; for (const auto &oneFileName : files) { enqueue(QUrl::fromLocalFile(oneFileName)); } } void MediaPlayList::clearAndEnqueue(const MusicAlbum &album) { clearPlayList(); enqueue(album); } void MediaPlayList::clearAndEnqueue(const MusicArtist &artist) { clearAndEnqueue(artist.name()); } void MediaPlayList::clearAndEnqueue(const QString &artistName) { clearPlayList(); enqueue(artistName); } void MediaPlayList::clearAndEnqueue(const QUrl &fileName) { clearPlayList(); enqueue(fileName); } void MediaPlayList::clearPlayList() { if (d->mData.isEmpty()) { return; } beginRemoveRows({}, 0, d->mData.count()); d->mData.clear(); d->mTrackData.clear(); endRemoveRows(); Q_EMIT tracksCountChanged(); } void MediaPlayList::loadPlaylist(const QUrl &fileName) { d->mLoadPlaylist.clear(); d->mLoadPlaylist.load(fileName, "m3u"); } bool MediaPlayList::savePlaylist(const QUrl &fileName) { QMediaPlaylist savePlaylist; for (int i = 0; i < d->mData.size(); ++i) { const auto &oneTrack = d->mData.at(i); const auto &oneTrackData = d->mTrackData.at(i); if (oneTrack.mIsValid) { savePlaylist.addMedia(oneTrackData.resourceURI()); } } return savePlaylist.save(fileName, "m3u"); } QVariantMap MediaPlayList::persistentState() const { auto currentState = QVariantMap(); auto result = QList(); for (int trackIndex = 0; trackIndex < d->mData.size(); ++trackIndex) { auto oneData = QList(); const auto &oneEntry = d->mData[trackIndex]; if (oneEntry.mIsValid) { const auto &oneTrack = d->mTrackData[trackIndex]; oneData.push_back(oneTrack.title()); oneData.push_back(oneTrack.artist()); oneData.push_back(oneTrack.albumName()); oneData.push_back(QString::number(oneTrack.trackNumber())); oneData.push_back(QString::number(oneTrack.discNumber())); result.push_back(QVariant(oneData)); } } currentState[QStringLiteral("playList")] = result; currentState[QStringLiteral("currentTrack")] = d->mCurrentPlayListPosition; currentState[QStringLiteral("randomPlay")] = d->mRandomPlay; currentState[QStringLiteral("repeatPlay")] = d->mRepeatPlay; return currentState; } MusicListenersManager *MediaPlayList::musicListenersManager() const { return d->mMusicListenersManager; } int MediaPlayList::tracksCount() const { return rowCount(); } QPersistentModelIndex MediaPlayList::currentTrack() const { return d->mCurrentTrack; } int MediaPlayList::currentTrackRow() const { return d->mCurrentTrack.row(); } bool MediaPlayList::randomPlay() const { return d->mRandomPlay; } bool MediaPlayList::repeatPlay() const { return d->mRepeatPlay; } void MediaPlayList::setPersistentState(const QVariantMap &persistentStateValue) { if (d->mPersistentState == persistentStateValue) { return; } qDebug() << "MediaPlayList::setPersistentState" << persistentStateValue; d->mPersistentState = persistentStateValue; auto persistentState = d->mPersistentState[QStringLiteral("playList")].toList(); for (auto &oneData : persistentState) { auto trackData = oneData.toStringList(); if (trackData.size() != 5) { continue; } auto restoredTitle = trackData[0]; auto restoredArtist = trackData[1]; auto restoredAlbum = trackData[2]; auto restoredTrackNumber = trackData[3].toInt(); auto restoredDiscNumber = trackData[4].toInt(); enqueue({restoredTitle, restoredArtist, restoredAlbum, restoredTrackNumber, restoredDiscNumber}); } restorePlayListPosition(); restoreRandomPlay(); restoreRepeatPlay(); Q_EMIT persistentStateChanged(); } void MediaPlayList::removeSelection(QList selection) { std::sort(selection.begin(), selection.end()); std::reverse(selection.begin(), selection.end()); for (auto oneItem : selection) { removeRow(oneItem); } } void MediaPlayList::albumAdded(const QList &tracks) { for (int playListIndex = 0; playListIndex < d->mData.size(); ++playListIndex) { auto &oneEntry = d->mData[playListIndex]; if (!oneEntry.mIsArtist || oneEntry.mIsValid) { continue; } if (oneEntry.mArtist != tracks.first().artist()) { continue; } d->mTrackData[playListIndex] = tracks.first(); oneEntry.mId = tracks.first().databaseId(); oneEntry.mIsValid = true; oneEntry.mIsArtist = false; Q_EMIT dataChanged(index(playListIndex, 0), index(playListIndex, 0), {}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } if (tracks.size() > 1) { beginInsertRows(QModelIndex(), playListIndex + 1, playListIndex - 1 + tracks.size()); for (int trackIndex = 1; trackIndex < tracks.size(); ++trackIndex) { d->mData.push_back(MediaPlayListEntry{tracks[trackIndex].databaseId()}); d->mTrackData.push_back(tracks[trackIndex]); } endInsertRows(); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } Q_EMIT tracksCountChanged(); } Q_EMIT persistentStateChanged(); } } void MediaPlayList::trackChanged(const MusicAudioTrack &track) { for (int i = 0; i < d->mData.size(); ++i) { auto &oneEntry = d->mData[i]; if (!oneEntry.mIsArtist && oneEntry.mIsValid) { if (oneEntry.mTrackUrl.isValid() && track.resourceURI() != oneEntry.mTrackUrl) { continue; } if (!oneEntry.mTrackUrl.isValid() && (oneEntry.mId == 0 || track.databaseId() != oneEntry.mId)) { continue; } if (d->mTrackData[i] != track) { d->mTrackData[i] = track; Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } continue; } else if (!oneEntry.mIsArtist && !oneEntry.mIsValid && !oneEntry.mTrackUrl.isValid()) { if (track.title() != oneEntry.mTitle) { continue; } if (track.albumName() != oneEntry.mAlbum) { continue; } if (track.trackNumber() != oneEntry.mTrackNumber) { continue; } if (track.discNumber() != oneEntry.mDiscNumber) { continue; } d->mTrackData[i] = track; oneEntry.mId = track.databaseId(); oneEntry.mIsValid = true; Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } break; } else if (!oneEntry.mIsArtist && !oneEntry.mIsValid && oneEntry.mTrackUrl.isValid()) { qDebug() << "MediaPlayList::trackChanged" << oneEntry << track; qDebug() << "MediaPlayList::trackChanged" << track.resourceURI() << oneEntry.mTrackUrl; if (track.resourceURI() != oneEntry.mTrackUrl) { continue; } d->mTrackData[i] = track; oneEntry.mId = track.databaseId(); oneEntry.mIsValid = true; Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); restorePlayListPosition(); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } break; } } } void MediaPlayList::trackRemoved(qulonglong trackId) { for (int i = 0; i < d->mData.size(); ++i) { auto &oneEntry = d->mData[i]; if (oneEntry.mIsValid) { if (oneEntry.mId == trackId) { oneEntry.mIsValid = false; oneEntry.mTitle = d->mTrackData[i].title(); oneEntry.mArtist = d->mTrackData[i].artist(); oneEntry.mAlbum = d->mTrackData[i].albumName(); oneEntry.mTrackNumber = d->mTrackData[i].trackNumber(); oneEntry.mDiscNumber = d->mTrackData[i].discNumber(); Q_EMIT dataChanged(index(i, 0), index(i, 0), {}); if (!d->mCurrentTrack.isValid()) { resetCurrentTrack(); } } } } } void MediaPlayList::setMusicListenersManager(MusicListenersManager *musicListenersManager) { if (d->mMusicListenersManager == musicListenersManager) { return; } d->mMusicListenersManager = musicListenersManager; if (d->mMusicListenersManager) { d->mMusicListenersManager->subscribeForTracks(this); } Q_EMIT musicListenersManagerChanged(); } void MediaPlayList::setRandomPlay(bool value) { d->mRandomPlay = value; Q_EMIT randomPlayChanged(); } void MediaPlayList::setRepeatPlay(bool value) { d->mRepeatPlay = value; Q_EMIT repeatPlayChanged(); } void MediaPlayList::skipNextTrack() { if (!d->mCurrentTrack.isValid()) { return; } if (!d->mRandomPlay && (d->mCurrentTrack.row() >= (rowCount() - 1))) { if (!d->mRepeatPlay) { Q_EMIT playListFinished(); } if (rowCount() == 1) { d->mCurrentTrack = QPersistentModelIndex{}; notifyCurrentTrackChanged(); } resetCurrentTrack(); return; } if (d->mRandomPlay) { int randomValue = qrand(); randomValue = randomValue % (rowCount()); d->mCurrentTrack = index(randomValue, 0); } else { d->mCurrentTrack = index(d->mCurrentTrack.row() + 1, 0); } notifyCurrentTrackChanged(); } void MediaPlayList::skipPreviousTrack() { if (!d->mCurrentTrack.isValid()) { return; } if (!d->mRandomPlay && !d->mRepeatPlay && d->mCurrentTrack.row() <= 0) { return; } if (d->mRandomPlay) { int randomValue = qrand(); randomValue = randomValue % (rowCount()); d->mCurrentTrack = index(randomValue, 0); } else { if (d->mRepeatPlay) { if (d->mCurrentTrack.row() == 0) { d->mCurrentTrack = index(rowCount() - 1, 0); } else { d->mCurrentTrack = index(d->mCurrentTrack.row() - 1, 0); } } else { d->mCurrentTrack = index(d->mCurrentTrack.row() - 1, d->mCurrentTrack.column(), d->mCurrentTrack.parent()); } } notifyCurrentTrackChanged(); } void MediaPlayList::seedRandomGenerator(uint seed) { qsrand(seed); } void MediaPlayList::switchTo(int row) { if (!d->mCurrentTrack.isValid()) { return; } d->mCurrentTrack = index(row, 0); notifyCurrentTrackChanged(); } void MediaPlayList::trackInError(QUrl sourceInError, QMediaPlayer::Error playerError) { Q_UNUSED(playerError) for (int i = 0; i < d->mData.size(); ++i) { auto &oneTrack = d->mData[i]; if (oneTrack.mIsValid) { const auto &oneTrackData = d->mTrackData.at(i); if (oneTrackData.resourceURI() == sourceInError) { oneTrack.mIsValid = false; Q_EMIT dataChanged(index(i, 0), index(i, 0), {ColumnsRoles::IsValidRole}); } } } } bool MediaPlayList::rowHasHeader(int row) const { if (row >= rowCount()) { return false; } if (row < 0) { return false; } if (row - 1 < 0) { return true; } auto currentAlbumTitle = QString(); auto currentAlbumArtist = QString(); if (d->mData[row].mIsValid) { currentAlbumTitle = d->mTrackData[row].albumName(); currentAlbumArtist = d->mTrackData[row].albumArtist(); } else { currentAlbumTitle = d->mData[row].mAlbum; currentAlbumArtist = d->mData[row].mArtist; } auto previousAlbumTitle = QString(); auto previousAlbumArtist = QString(); if (d->mData[row - 1].mIsValid) { previousAlbumTitle = d->mTrackData[row - 1].albumName(); previousAlbumArtist = d->mTrackData[row - 1].albumArtist(); } else { previousAlbumTitle = d->mData[row - 1].mAlbum; previousAlbumArtist = d->mData[row - 1].mArtist; } if (currentAlbumTitle == previousAlbumTitle && currentAlbumArtist == previousAlbumArtist) { return false; } return true; } void MediaPlayList::loadPlayListLoaded() { clearPlayList(); for (int i = 0; i < d->mLoadPlaylist.mediaCount(); ++i) { enqueue(d->mLoadPlaylist.media(i).canonicalUrl()); } restorePlayListPosition(); restoreRandomPlay(); restoreRepeatPlay(); Q_EMIT persistentStateChanged(); d->mLoadPlaylist.clear(); Q_EMIT playListLoaded(); } void MediaPlayList::loadPlayListLoadFailed() { d->mLoadPlaylist.clear(); Q_EMIT playListLoadFailed(); } void MediaPlayList::resetCurrentTrack() { for(int row = 0; row < rowCount(); ++row) { auto candidateTrack = index(row, 0); if (candidateTrack.isValid() && candidateTrack.data(ColumnsRoles::IsValidRole).toBool()) { d->mCurrentTrack = candidateTrack; notifyCurrentTrackChanged(); break; } } } void MediaPlayList::notifyCurrentTrackChanged() { Q_EMIT currentTrackChanged(); Q_EMIT currentTrackRowChanged(); bool currentTrackIsValid = d->mCurrentTrack.isValid(); if (currentTrackIsValid) { d->mCurrentPlayListPosition = d->mCurrentTrack.row(); } } void MediaPlayList::restorePlayListPosition() { auto playerCurrentTrack = d->mPersistentState.find(QStringLiteral("currentTrack")); if (playerCurrentTrack != d->mPersistentState.end()) { auto newIndex = index(playerCurrentTrack->toInt(), 0); if (newIndex.isValid() && (newIndex != d->mCurrentTrack)) { d->mCurrentTrack = newIndex; notifyCurrentTrackChanged(); if (d->mCurrentTrack.isValid()) { d->mPersistentState.erase(playerCurrentTrack); } } } } void MediaPlayList::restoreRandomPlay() { auto randomPlayStoredValue = d->mPersistentState.find(QStringLiteral("randomPlay")); if (randomPlayStoredValue != d->mPersistentState.end()) { setRandomPlay(randomPlayStoredValue->toBool()); d->mPersistentState.erase(randomPlayStoredValue); } } void MediaPlayList::restoreRepeatPlay() { auto repeatPlayStoredValue = d->mPersistentState.find(QStringLiteral("repeatPlay")); if (repeatPlayStoredValue != d->mPersistentState.end()) { setRepeatPlay(repeatPlayStoredValue->toBool()); d->mPersistentState.erase(repeatPlayStoredValue); } } QDebug operator<<(QDebug stream, const MediaPlayListEntry &data) { stream << data.mTitle << data.mAlbum << data.mArtist << data.mTrackUrl << data.mTrackNumber << data.mDiscNumber << data.mId << data.mIsValid; return stream; } #include "moc_mediaplaylist.cpp" diff --git a/src/mediaplaylist.h b/src/mediaplaylist.h index 8ecbee6b..1fb3e88b 100644 --- a/src/mediaplaylist.h +++ b/src/mediaplaylist.h @@ -1,308 +1,311 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MEDIAPLAYLIST_H #define MEDIAPLAYLIST_H #include "musicaudiotrack.h" #include "musicalbum.h" #include "musicartist.h" #include #include #include #include class MediaPlayListPrivate; class DatabaseInterface; class MusicListenersManager; class MediaPlayListEntry; class QDebug; class MediaPlayList : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QVariantMap persistentState READ persistentState WRITE setPersistentState NOTIFY persistentStateChanged) Q_PROPERTY(MusicListenersManager* musicListenersManager READ musicListenersManager WRITE setMusicListenersManager NOTIFY musicListenersManagerChanged) Q_PROPERTY(int tracksCount READ tracksCount NOTIFY tracksCountChanged) Q_PROPERTY(QPersistentModelIndex currentTrack READ currentTrack NOTIFY currentTrackChanged) Q_PROPERTY(int currentTrackRow READ currentTrackRow NOTIFY currentTrackRowChanged) Q_PROPERTY(bool randomPlay READ randomPlay WRITE setRandomPlay NOTIFY randomPlayChanged) Q_PROPERTY(bool repeatPlay READ repeatPlay WRITE setRepeatPlay NOTIFY repeatPlayChanged) public: enum ColumnsRoles { IsValidRole = Qt::UserRole + 1, TitleRole, DurationRole, MilliSecondsDurationRole, CreatorRole, ArtistRole, AlbumArtistRole, AlbumRole, TrackNumberRole, DiscNumberRole, RatingRole, ImageRole, ResourceRole, CountRole, IsPlayingRole, HasAlbumHeader, IsSingleDiscAlbumHeader, + SecondaryTextRole, + ImageUrlRole, + ShadowForImageRole, }; Q_ENUM(ColumnsRoles) enum PlayState { NotPlaying, IsPlaying, IsPaused, }; Q_ENUM(PlayState) explicit MediaPlayList(QObject *parent = nullptr); ~MediaPlayList() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QHash roleNames() const override; Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; Q_INVOKABLE bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override; Q_INVOKABLE void move(int from, int to, int n); Q_INVOKABLE void clearPlayList(); Q_INVOKABLE bool savePlaylist(const QUrl &fileName); QVariantMap persistentState() const; MusicListenersManager* musicListenersManager() const; int tracksCount() const; QPersistentModelIndex currentTrack() const; int currentTrackRow() const; bool randomPlay() const; bool repeatPlay() const; Q_SIGNALS: void newTrackByNameInList(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber); void newTrackByFileNameInList(const QUrl &fileName); void newTrackByIdInList(qulonglong newTrackId); void newArtistInList(const QString &artist); void trackHasBeenAdded(const QString &title, const QUrl &image); void persistentStateChanged(); void musicListenersManagerChanged(); void tracksCountChanged(); void currentTrackChanged(); void currentTrackRowChanged(); void randomPlayChanged(); void repeatPlayChanged(); void playListFinished(); void playListLoaded(); void playListLoadFailed(); public Q_SLOTS: void setPersistentState(const QVariantMap &persistentState); void removeSelection(QList selection); void albumAdded(const QList &tracks); void trackChanged(const MusicAudioTrack &track); void trackRemoved(qulonglong trackId); void setMusicListenersManager(MusicListenersManager* musicListenersManager); void setRandomPlay(bool value); void setRepeatPlay(bool value); void skipNextTrack(); void skipPreviousTrack(); void seedRandomGenerator(uint seed); void switchTo(int row); void loadPlaylist(const QUrl &fileName); void enqueue(qulonglong newTrackId); void enqueue(const MusicAudioTrack &newTrack); void enqueue(const MediaPlayListEntry &newEntry, const MusicAudioTrack &audioTrack = {}); void enqueue(const MusicAlbum &album); void enqueue(const MusicArtist &artist); void enqueue(const QString &artistName); void enqueue(const QUrl &fileName); void enqueue(const QStringList &files); void clearAndEnqueue(qulonglong newTrackId); void clearAndEnqueue(const MusicAudioTrack &newTrack); void clearAndEnqueue(const MusicAlbum &album); void clearAndEnqueue(const MusicArtist &artist); void clearAndEnqueue(const QString &artistName); void clearAndEnqueue(const QUrl &fileName); void trackInError(QUrl sourceInError, QMediaPlayer::Error playerError); private Q_SLOTS: bool rowHasHeader(int row) const; void loadPlayListLoaded(); void loadPlayListLoadFailed(); private: void resetCurrentTrack(); void notifyCurrentTrackChanged(); void restorePlayListPosition(); void restoreRandomPlay(); void restoreRepeatPlay(); std::unique_ptr d; }; class MediaPlayListEntry { public: MediaPlayListEntry() { } explicit MediaPlayListEntry(qulonglong id) : mId(id), mIsValid(true) { } MediaPlayListEntry(QString title, QString artist, QString album, int trackNumber, int discNumber) : mTitle(std::move(title)), mAlbum(std::move(album)), mArtist(std::move(artist)), mTrackNumber(trackNumber), mDiscNumber(discNumber) { } explicit MediaPlayListEntry(const MusicAudioTrack &track) : mTitle(track.title()), mAlbum(track.albumName()), mTrackNumber(track.trackNumber()), mDiscNumber(track.discNumber()), mId(track.databaseId()), mIsValid(true) { } explicit MediaPlayListEntry(QString artist) : mArtist(std::move(artist)), mIsArtist(true) { } explicit MediaPlayListEntry(QUrl fileName) : mTrackUrl(std::move(fileName)) { } QString mTitle; QString mAlbum; QString mArtist; QUrl mTrackUrl; int mTrackNumber = -1; int mDiscNumber = -1; qulonglong mId = 0; bool mIsValid = false; bool mIsArtist = false; MediaPlayList::PlayState mIsPlaying = MediaPlayList::NotPlaying; }; QDebug operator<<(QDebug stream, const MediaPlayListEntry &data); #endif // MEDIAPLAYLIST_H diff --git a/src/upnpControl.qrc b/src/upnpControl.qrc index de0bbad6..9083862d 100644 --- a/src/upnpControl.qrc +++ b/src/upnpControl.qrc @@ -1,43 +1,40 @@ MediaPlayerControl.qml RatingStar.qml MediaPlayListView.qml MediaAlbumView.qml MediaServer.qml HeaderBar.qml background.jpg ContextView.qml DraggableItem.qml PassiveNotification.qml NavigationActionBar.qml PlayListEntry.qml MediaBrowser.qml - MediaAlbumDelegate.qml - MediaArtistDelegate.qml - MediaAllAlbumView.qml - MediaArtistAlbumView.qml - MediaAllArtistView.qml Theme.qml PlatformIntegration.qml LabelWithToolTip.qml MediaAllTracksView.qml TopNotification.qml TrackImportNotification.qml TopNotificationItem.qml ViewSelector.qml FilterBar.qml MediaTrackDelegate.qml MediaAlbumTrackDelegate.qml + GridBrowserView.qml + GridBrowserDelegate.qml windows/WindowsTheme.qml windows/PlatformIntegration.qml windows/LabelWithToolTip.qml android/MediaServer.qml android/AndroidTheme.qml android/PlatformIntegration.qml