diff --git a/CMakeLists.txt b/CMakeLists.txt index fdb7e25e..339527bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,191 +1,191 @@ # SPDX-FileCopyrightText: 2015 (c) Matthieu Gallien # # SPDX-License-Identifier: LGPL-3.0-or-later cmake_minimum_required(VERSION 3.8) # KDE Applications version, managed by release script. set(RELEASE_SERVICE_VERSION_MAJOR "20") set(RELEASE_SERVICE_VERSION_MINOR "07") set(RELEASE_SERVICE_VERSION_MICRO "70") set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") project(elisa VERSION ${RELEASE_SERVICE_VERSION} LANGUAGES CXX) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 17) set(REQUIRED_QT_VERSION "5.11.0") find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Network Qml Quick Test Sql Multimedia Svg Gui Widgets QuickTest Concurrent QuickControls2) find_package(Qt5Core ${REQUIRED_QT_VERSION} CONFIG REQUIRED Private) -set(REQUIRED_KF5_VERSION "5.64.0") +set(REQUIRED_KF5_VERSION "5.70.0") find_package(ECM ${REQUIRED_KF5_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(FeatureSummary) include(ECMAddAppIcon) include(ECMAddTests) include(ECMQtDeclareLoggingCategory) if (NOT WIN32) find_package(Qt5DBus ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt5 DBus is needed to provide MPris2 interface to allow remote control by the desktop workspace." TYPE OPTIONAL) endif() find_package(Qt5QuickWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "Qt5 Quick Widgets is needed at runtime to provide the interface." TYPE RUNTIME) find_package(Qt5QuickControls2 ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5QuickControls2 PROPERTIES DESCRIPTION "Qt5 Quick Controls version 2 is needed at runtime to provide the interface." TYPE RUNTIME) if (ANDROID) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG QUIET OPTIONAL_COMPONENTS AndroidExtras) set_package_properties(Qt5AndroidExtras PROPERTIES DESCRIPTION "Qt5 AndroidExtras is needed to provide the Android integration." TYPE REQUIRED) endif() find_package(KF5Kirigami2 ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "KF5 Kirigami 2 is needed to provide the mobile UI components." TYPE REQUIRED) find_package(KF5I18n ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5I18n PROPERTIES DESCRIPTION "KF5 text internationalization library." TYPE REQUIRED) find_package(KF5Declarative ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Declarative PROPERTIES DESCRIPTION "Integration of QML and KDE work spaces." TYPE RECOMMENDED) find_package(KF5CoreAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5CoreAddons PROPERTIES DESCRIPTION "Qt addon library with a collection of non-GUI utilities." TYPE REQUIRED) if (NOT WIN32) find_package(KF5Baloo ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "Baloo provides file searching and indexing." TYPE RECOMMENDED) endif() find_package(KF5FileMetaData ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5FileMetaData PROPERTIES DESCRIPTION "Provides a simple library for extracting metadata." TYPE RECOMMENDED) find_package(KF5DocTools ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Create documentation from DocBook library." TYPE OPTIONAL) find_package(KF5XmlGui ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5XmlGui PROPERTIES DESCRIPTION "Framework for managing menu and toolbar actions." TYPE RECOMMENDED) find_package(KF5Config ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Config PROPERTIES DESCRIPTION "Persistent platform-independent application settings." TYPE REQUIRED) find_package(KF5ConfigWidgets ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5ConfigWidgets PROPERTIES DESCRIPTION "Widgets for configuration dialogs." TYPE RECOMMENDED) find_package(KF5Crash ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "Graceful handling of application crashes." TYPE OPTIONAL) if (NOT WIN32) find_package(KF5DBusAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5DBusAddons PROPERTIES DESCRIPTION "Convenience classes for D-Bus." TYPE OPTIONAL) endif() find_package(KF5Package ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Package PROPERTIES DESCRIPTION "KF5 package management library needed to get the configuration dialogs." TYPE RECOMMENDED) find_package(KF5KIO ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "File management libraries used for file browsing." TYPE RECOMMENDED) find_package(UPNPQT CONFIG QUIET) set_package_properties(UPNPQT PROPERTIES DESCRIPTION "UPNP layer build with Qt 5. UPnP support is currently broken. You should probably avoid this dependency." URL "https://gitlab.com/homeautomationqt/upnp-player-qt" TYPE OPTIONAL) if (UPNPQT_FOUND) message(WARNING "UPnP support is experimental and may not work.") endif() find_package(LIBVLC QUIET) set_package_properties(LIBVLC PROPERTIES DESCRIPTION "libvlc allows to play music in Elisa (otherwise it will use QtMultimedia)" URL "https://www.videolan.org/vlc/libvlc.html" TYPE RECOMMENDED) include(FeatureSummary) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) if (CMAKE_SYSTEM_NAME STREQUAL Android) set(QT_QMAKE_EXECUTABLE "$ENV{Qt5_android}/bin/qmake") endif() configure_file(config-upnp-qt.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-upnp-qt.h ) ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX ELISA VERSION_HEADER elisa-version.h) add_subdirectory(src) add_subdirectory(icons) if (BUILD_TESTING) add_subdirectory(autotests) endif() add_subdirectory(doc) install( PROGRAMS org.kde.elisa.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES org.kde.elisa.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) ki18n_install(po) install(FILES elisa.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/qml/BaseTheme.qml b/src/qml/BaseTheme.qml index 54f59f23..39c93056 100644 --- a/src/qml/BaseTheme.qml +++ b/src/qml/BaseTheme.qml @@ -1,53 +1,52 @@ /* SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien SPDX-License-Identifier: LGPL-3.0-or-later */ import QtQuick 2.7 Item { property string defaultAlbumImage: 'image://icon/media-optical-audio' property string defaultArtistImage: 'image://icon/view-media-artist' property string defaultBackgroundImage: 'qrc:///background.png' property string nowPlayingIcon: 'image://icon/view-media-lyrics' property string artistIcon: 'image://icon/view-media-artist' property string albumIcon: 'image://icon/view-media-album-cover' property string albumCoverIcon: 'image://icon/media-optical-audio' - property string playlistIcon: 'image://icon/view-media-playlist' property string tracksIcon: 'image://icon/view-media-track' property string genresIcon: 'image://icon/view-media-genre' property string clearIcon: 'image://icon/edit-clear' property string recentlyPlayedTracksIcon: 'image://icon/media-playlist-play' property string frequentlyPlayedTracksIcon: 'image://icon/view-media-playcount' property string pausedIndicatorIcon: 'image://icon/media-playback-paused' property string playingIndicatorIcon: 'image://icon/media-playback-playing' property string ratingIcon: 'image://icon/rating' property string ratingUnratedIcon: 'image://icon/rating-unrated' property string errorIcon: 'image://icon/error' property string folderIcon: 'image://icon/document-open-folder' property int playListAlbumArtSize: 60 property int coverImageSize: 180 property int contextCoverImageSize: 100 property int smallImageSize: 32 property int tooltipRadius: 3 property int shadowOffset: 2 property int delegateToolButtonSize: 34 property int mediaPlayerControlHeight: 42 property real mediaPlayerControlOpacity: 0.6 property int volumeSliderWidth: 100 property int dragDropPlaceholderHeight: 28 property int gridDelegateSize: 170 property int headerToolbarHeight: 48 property int footerToolbarHeight: 30 property int viewSelectorSmallSizeThreshold: 800 } diff --git a/src/qml/MediaPlayListView.qml b/src/qml/MediaPlayListView.qml index dee12de0..8da2ff49 100644 --- a/src/qml/MediaPlayListView.qml +++ b/src/qml/MediaPlayListView.qml @@ -1,306 +1,255 @@ /* SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien SPDX-FileCopyrightText: 2019 (c) Nate Graham SPDX-License-Identifier: LGPL-3.0-or-later */ import QtQuick 2.5 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import Qt.labs.platform 1.0 as PlatformDialog -import org.kde.kirigami 2.5 as Kirigami +import org.kde.kirigami 2.12 as Kirigami import org.kde.elisa 1.0 FocusScope { property StackView parentStackView property int placeholderHeight: elisaTheme.dragDropPlaceholderHeight signal startPlayback() signal pausePlayback() function showPlayListNotification(message, type, action) { if (!message) { return; } if (type) { playListNotification.type = type; } else { playListNotification.type = Kirigami.MessageType.Information; } if (action) { playListNotification.actions = action; } else { playListNotification.actions = []; } playListNotification.text = message ? message : ""; playListNotification.visible = true; } function hideNotification() { playListNotification.visible = false; } Kirigami.Action { id: undoAction text: i18nc("Undo", "Undo") icon.name: "dialog-cancel" onTriggered: elisa.mediaPlayListProxyModel.undoClearPlayList() } Kirigami.Action { id: retryLoadAction text: i18nc("Retry", "Retry") icon.name: "edit-redo" onTriggered: loadPlaylistButton.clicked() } Kirigami.Action { id: retrySaveAction text: i18nc("Retry", "Retry") icon.name: "edit-redo" onTriggered: savePlaylistButton.clicked() } Connections { target: elisa.mediaPlayListProxyModel onPlayListLoadFailed: { showPlayListNotification(i18nc("Message when playlist load failed", "Loading failed"), Kirigami.MessageType.Error, retryLoadAction) } } Connections { target: elisa.mediaPlayListProxyModel onDisplayUndoNotification: { showPlayListNotification(i18nc("Playlist cleared", "Playlist cleared"), Kirigami.MessageType.Information, undoAction) } } Connections { target: elisa.mediaPlayListProxyModel onHideUndoNotification: hideNotification() } id: topItem Accessible.role: Accessible.Pane Accessible.name: viewTitle.text PlatformDialog.FileDialog { id: fileDialog defaultSuffix: 'm3u' folder: PlatformDialog.StandardPaths.writableLocation(PlatformDialog.StandardPaths.MusicLocation) nameFilters: [i18nc("file type (mime type) for m3u playlist", "Playlist (*.m3u)")] onAccepted: { if (fileMode === PlatformDialog.FileDialog.SaveFile) { if (!elisa.mediaPlayListProxyModel.savePlayList(fileDialog.file)) { showPlayListNotification(i18nc("Message when saving a playlist failed", "Saving failed"), Kirigami.MessageType.Error, retrySaveAction) } } else { elisa.mediaPlayListProxyModel.loadPlayList(fileDialog.file) } } } ColumnLayout { anchors.fill: parent spacing: 0 // Header with title and toolbar buttons HeaderFooterToolbar { type: "header" contentItems: [ // Header title LabelWithToolTip { id: viewTitle Layout.fillWidth: true text: i18nc("Title of the view of the playlist", "Playlist") level: 1 Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter }, // Toolbar buttons FlatButtonWithToolTip { text: i18nc("Show currently played track inside playlist", "Show Current Track") icon.name: 'media-track-show-active' enabled: elisa.mediaPlayListProxyModel ? elisa.mediaPlayListProxyModel.tracksCount > 0 : false onClicked: { playListView.positionViewAtIndex(elisa.mediaPlayListProxyModel.currentTrackRow, ListView.Contain) playListView.currentIndex = elisa.mediaPlayListProxyModel.currentTrackRow playListView.currentItem.forceActiveFocus() } }, FlatButtonWithToolTip { id: savePlaylistButton text: i18nc("Save a playlist file", "Save Playlist...") icon.name: 'document-save' enabled: elisa.mediaPlayListProxyModel ? elisa.mediaPlayListProxyModel.tracksCount > 0 : false onClicked: { fileDialog.fileMode = PlatformDialog.FileDialog.SaveFile fileDialog.file = '' fileDialog.open() } }, FlatButtonWithToolTip { id: loadPlaylistButton text: i18nc("Load a playlist file", "Load Playlist...") icon.name: 'document-open' onClicked: { fileDialog.fileMode = PlatformDialog.FileDialog.OpenFile fileDialog.file = '' fileDialog.open() } }, FlatButtonWithToolTip { text: i18nc("Remove all tracks from play list", "Clear Playlist") icon.name: 'edit-clear-all' enabled: elisa.mediaPlayListProxyModel ? elisa.mediaPlayListProxyModel.tracksCount > 0 : false onClicked: elisa.mediaPlayListProxyModel.clearPlayList() } ] } - ColumnLayout { - id: emptyPlaylistText - spacing: 0 - visible: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - Layout.fillHeight: true - Layout.fillWidth: true - - Item { - id: emptyVisible - visible: elisa.mediaPlayListProxyModel ? elisa.mediaPlayListProxyModel.tracksCount === 0 : true - Layout.fillHeight: true - } - - Image { - id: emptyImage - visible: emptyVisible.visible - Layout.alignment: Qt.AlignHCenter - - width: elisaTheme.gridDelegateSize - height: elisaTheme.gridDelegateSize + Item { + id: emptyPlaylistMessage - source: elisaTheme.playlistIcon - opacity: 0.25 + visible: elisa.mediaPlayListProxyModel ? elisa.mediaPlayListProxyModel.tracksCount === 0 : true - sourceSize { - width: elisaTheme.gridDelegateSize - height: elisaTheme.gridDelegateSize - } - } - - LabelWithToolTip { - id: emptyLabel0 - visible: emptyVisible.visible - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - Layout.rightMargin: Kirigami.Units.largeSpacing - Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.fillHeight: true + Layout.fillWidth: true - level: 1 - wrapMode: Text.WordWrap + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + width: parent.width - (Kirigami.Units.largeSpacing * 4) - horizontalAlignment: Text.AlignHCenter - text: i18nc("Your playlist is empty", "Your playlist is empty") + text: xi18nc("@info", "Your playlist is empty.Add some songs to get started. You can browse your music using the views on the left.") } + } - Label { - id: emptyLabel1 - visible: emptyVisible.visible - Layout.topMargin: Kirigami.Units.largeSpacing - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - Layout.rightMargin: Kirigami.Units.largeSpacing - Layout.leftMargin: Kirigami.Units.largeSpacing + PlayListBasicView { + id: playListView - wrapMode: Text.WordWrap + visible: !emptyPlaylistMessage.visible - horizontalAlignment: Text.AlignHCenter - text: i18nc("Text shown when play list is empty", "Add some songs to get started. You can browse your music using the views on the left.") - } + Layout.fillWidth: true + Layout.fillHeight: true - Item { - visible: emptyVisible.visible - Layout.fillHeight: true - } + title: viewTitle.text + playListModel: elisa.mediaPlayListProxyModel - PlayListBasicView { - id: playListView + focus: true - visible: !emptyVisible.visible + onStartPlayback: topItem.startPlayback() - Layout.fillWidth: true - Layout.fillHeight: true + onPausePlayback: topItem.pausePlayback() - title: viewTitle.text - playListModel: elisa.mediaPlayListProxyModel + onDisplayError: showPlayListNotification(errorText, Kirigami.MessageType.Error) - focus: true + } - onStartPlayback: topItem.startPlayback() + Kirigami.InlineMessage { + id: playListNotification - onPausePlayback: topItem.pausePlayback() + Timer { + id: autoHideNotificationTimer - onDisplayError: showPlayListNotification(errorText, Kirigami.MessageType.Error) + interval: 7000 + onTriggered: playListNotification.visible = false } - Kirigami.InlineMessage { - id: playListNotification - - Timer { - id: autoHideNotificationTimer - - interval: 7000 - - onTriggered: playListNotification.visible = false - } - - type: Kirigami.MessageType.Information - showCloseButton: true - Layout.fillWidth: true - Layout.margins: Kirigami.Units.largeSpacing - - onVisibleChanged: - { - if (visible) { - autoHideNotificationTimer.start() - } else { - autoHideNotificationTimer.stop() - } + type: Kirigami.MessageType.Information + showCloseButton: true + Layout.fillWidth: true + Layout.margins: Kirigami.Units.largeSpacing + + onVisibleChanged: + { + if (visible) { + autoHideNotificationTimer.start() + } else { + autoHideNotificationTimer.stop() } } } // Footer with number of tracks label HeaderFooterToolbar { type: "footer" contentItems: [ LabelWithToolTip { id: trackCountLabel Layout.fillWidth: true text: i18np("1 track", "%1 tracks", (elisa.mediaPlayListProxyModel ? elisa.mediaPlayListProxyModel.tracksCount : 0)) elide: Text.ElideLeft } ] } } }