diff --git a/CMakeLists.txt b/CMakeLists.txt index a0602558..ba0bda1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,155 +1,160 @@ cmake_minimum_required(VERSION 3.5) project(elisa) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 14) set(REQUIRED_QT_VERSION "5.10.0") find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Network Qml Quick Test Sql Multimedia Svg Gui Widgets QuickTest Concurrent QuickControls2) set(REQUIRED_KF5_VERSION "5.45.0") find_package(ECM ${REQUIRED_KF5_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(FeatureSummary) include(ECMAddAppIcon) include(ECMAddTests) 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) endif() 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) find_package(KF5Baloo ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "Baloo provides file searching and indexing." TYPE RECOMMENDED) find_package(KF5FileMetaData ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5FileMetaData PROPERTIES DESCRIPTION "Provides a simple library for extracting metadata." TYPE REQUIRED) 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(KF5KCMUtils ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5KCMUtils PROPERTIES DESCRIPTION "KF5 Utilities for KDE System Settings modules library." TYPE RECOMMENDED) 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 REQUIRED) + 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() 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 ) 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} ) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cbc45d86..a79d6adb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,298 +1,302 @@ include_directories(${elisa_BINARY_DIR}) set(elisaLib_SOURCES mediaplaylist.cpp musicalbum.cpp musicaudiotrack.cpp musicartist.cpp musicaudiogenre.cpp progressindicator.cpp databaseinterface.cpp musiclistenersmanager.cpp managemediaplayercontrol.cpp manageheaderbar.cpp manageaudioplayer.cpp trackslistener.cpp elisaapplication.cpp audiowrapper.cpp notificationitem.cpp topnotificationmanager.cpp elisautils.cpp datatype.cpp trackdatahelper.cpp modeldatacache.cpp abstractfile/abstractfilelistener.cpp abstractfile/abstractfilelisting.cpp file/filelistener.cpp file/localfilelisting.cpp models/albummodel.cpp models/allalbumsmodel.cpp models/allartistsmodel.cpp models/alltracksmodel.cpp models/allgenresmodel.cpp models/abstractmediaproxymodel.cpp models/allalbumsproxymodel.cpp models/allartistsproxymodel.cpp models/alltracksproxymodel.cpp models/singleartistproxymodel.cpp models/singlealbumproxymodel.cpp models/genericdatamodel.cpp + models/filebrowsermodel.cpp + models/filebrowserproxymodel.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} baloo/localbaloofilelisting.cpp baloo/baloolistener.cpp ) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) endif() endif() if (Qt5DBus_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} mpris2/mpris2.cpp mpris2/mediaplayer2.cpp mpris2/mediaplayer2player.cpp ) endif() if (UPNPQT_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} upnp/upnpcontrolcontentdirectory.cpp upnp/upnpcontentdirectorymodel.cpp upnp/upnpcontrolconnectionmanager.cpp upnp/upnpcontrolmediaserver.cpp upnp/didlparser.cpp upnp/upnplistener.cpp upnp/upnpdiscoverallmusic.cpp ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(elisaLib_SOURCES ${elisaLib_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() kconfig_add_kcfg_files(elisaLib_SOURCES ../src/elisa_settings.kcfgc ) set(elisaLib_SOURCES ${elisaLib_SOURCES} ../src/elisa_core.kcfg ) add_library(elisaLib ${elisaLib_SOURCES}) target_link_libraries(elisaLib LINK_PUBLIC - Qt5::Multimedia + Qt5::Multimedia KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets LINK_PRIVATE Qt5::Core Qt5::Sql Qt5::Widgets KF5::I18n Qt5::Concurrent Qt5::Qml KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData) if (KF5XmlGui_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::XmlGui ) endif() if (KF5ConfigWidgets_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::ConfigWidgets ) endif() if (KF5KCMUtils_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::KCMUtils ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::Baloo ) endif() endif() if (Qt5DBus_FOUND) target_link_libraries(elisaLib LINK_PUBLIC Qt5::DBus ) if (KF5DBusAddons_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::DBusAddons ) endif() endif() generate_export_header(elisaLib BASE_NAME ElisaLib EXPORT_FILE_NAME elisaLib_export.h) set_target_properties(elisaLib PROPERTIES VERSION 0.1 SOVERSION 0 EXPORT_NAME ElisaLib ) install(TARGETS elisaLib ${INSTALL_TARGETS_DEFAULT_ARGS}) set(elisaqmlplugin_SOURCES elisaqmlplugin.cpp datatype.cpp elisautils.cpp ) add_library(elisaqmlplugin SHARED ${elisaqmlplugin_SOURCES}) target_link_libraries(elisaqmlplugin LINK_PRIVATE Qt5::Quick Qt5::Widgets KF5::FileMetaData KF5::ConfigCore KF5::ConfigGui elisaLib ) set_target_properties(elisaqmlplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa) install(TARGETS elisaqmlplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/elisa/) install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/elisa) add_custom_target(copy) add_custom_target(copy2) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa) add_custom_command(TARGET copy PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/qmldir ${CMAKE_BINARY_DIR}/bin/org/kde/elisa/) add_custom_command(TARGET copy2 PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugins.qmltypes ${CMAKE_BINARY_DIR}/bin/org/kde/elisa/) add_dependencies(elisaqmlplugin copy copy2) if (Qt5Quick_FOUND AND Qt5Widgets_FOUND AND KF5Declarative_FOUND) set(elisa_SOURCES main.cpp windows/WindowsTheme.qml windows/PlatformIntegration.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/RatingStar.qml qml/PlayListEntry.qml qml/MediaBrowser.qml qml/DraggableItem.qml qml/PassiveNotification.qml qml/TopNotification.qml qml/TopNotificationItem.qml qml/TrackImportNotification.qml qml/HeaderBar.qml qml/NavigationActionBar.qml qml/MediaPlayerControl.qml qml/ContextView.qml qml/ContentView.qml qml/ViewSelector.qml qml/ViewManager.qml qml/MediaPlayListView.qml qml/MediaTrackDelegate.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml qml/ListBrowserView.qml + qml/FileBrowserDelegate.qml + qml/FileBrowserView.qml ) qt5_add_resources(elisa_SOURCES resources.qrc) set_property(SOURCE qrc_resources.cpp PROPERTY SKIP_AUTOMOC ON) 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}) target_include_directories(elisa PRIVATE ${KDSoap_INCLUDE_DIRS}) target_link_libraries(elisa LINK_PRIVATE elisaLib Qt5::Widgets Qt5::QuickControls2 KF5::Declarative KF5::I18n KF5::Crash KF5::ConfigCore KF5::ConfigGui ) endif() install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS}) if (KF5ConfigWidgets_FOUND) add_subdirectory(localFileConfiguration) endif() set(elisaImport_SOURCES elisaimport.cpp elisaimportapplication.cpp ) kconfig_add_kcfg_files(elisaImport_SOURCES ../src/elisa_settings.kcfgc ) set(elisaImport_SOURCES ${elisaImport_SOURCES} ../src/elisa_core.kcfg ) add_executable(elisaImport ${elisaImport_SOURCES}) target_link_libraries(elisaImport LINK_PRIVATE KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData elisaLib ) set(QML_IMPORT_PATH ${CMAKE_BINARY_DIR}/bin CACHE INTERNAL "qml import path" FORCE) diff --git a/src/elisaapplication.cpp b/src/elisaapplication.cpp index aaff3888..4646723c 100644 --- a/src/elisaapplication.cpp +++ b/src/elisaapplication.cpp @@ -1,507 +1,522 @@ /* * Copyright 2017 Matthieu Gallien * Copyright (C) 2012 Aleix Pol Gonzalez * * 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 "elisaapplication.h" #include "musiclistenersmanager.h" #include "models/allalbumsproxymodel.h" #include "models/alltracksproxymodel.h" #include "models/allartistsproxymodel.h" #include "models/singleartistproxymodel.h" #include "models/singlealbumproxymodel.h" +#include "models/filebrowserproxymodel.h" #include "mediaplaylist.h" #include "audiowrapper.h" #include "manageaudioplayer.h" #include "managemediaplayercontrol.h" #include "manageheaderbar.h" #include "elisa_settings.h" #include #if defined KF5ConfigWidgets_FOUND && KF5ConfigWidgets_FOUND #include #endif #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND #include #include #include #include #include #endif #if defined KF5KCMUtils_FOUND && KF5KCMUtils_FOUND #include #endif #include #include #include #include #include #include #include #include #include #include #include #include class ElisaApplicationPrivate { public: explicit ElisaApplicationPrivate(QObject *parent) #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND : mCollection(parent) #endif { Q_UNUSED(parent) auto configurationFileName = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); configurationFileName += QStringLiteral("/elisarc"); Elisa::ElisaConfiguration::instance(configurationFileName); Elisa::ElisaConfiguration::self()->load(); Elisa::ElisaConfiguration::self()->save(); } #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND KActionCollection mCollection; #endif QStringList mArguments; std::unique_ptr mMusicManager; std::unique_ptr mAllAlbumsProxyModel; std::unique_ptr mAllArtistsProxyModel; std::unique_ptr mAllTracksProxyModel; std::unique_ptr mAllGenresProxyModel; std::unique_ptr mAllComposersProxyModel; std::unique_ptr mAllLyricistsProxyModel; std::unique_ptr mSingleArtistProxyModel; std::unique_ptr mSingleAlbumProxyModel; + std::unique_ptr mFileBrowserProxyModel; + std::unique_ptr mMediaPlayList; std::unique_ptr mAudioWrapper; std::unique_ptr mAudioControl; std::unique_ptr mPlayerControl; std::unique_ptr mManageHeaderBar; }; ElisaApplication::ElisaApplication(QObject *parent) : QObject(parent), d(std::make_unique(this)) { } ElisaApplication::~ElisaApplication() = default; void ElisaApplication::setupActions(const QString &actionName) { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND if (actionName == QStringLiteral("file_quit")) { auto quitAction = KStandardAction::quit(QCoreApplication::instance(), &QCoreApplication::quit, &d->mCollection); d->mCollection.addAction(actionName, quitAction); } if (actionName == QStringLiteral("help_contents") && KAuthorized::authorizeAction(actionName)) { auto handBookAction = KStandardAction::helpContents(this, &ElisaApplication::appHelpActivated, &d->mCollection); d->mCollection.addAction(handBookAction->objectName(), handBookAction); } if (actionName == QStringLiteral("help_report_bug") && KAuthorized::authorizeAction(actionName) && !KAboutData::applicationData().bugAddress().isEmpty()) { auto reportBugAction = KStandardAction::reportBug(this, &ElisaApplication::reportBug, &d->mCollection); d->mCollection.addAction(reportBugAction->objectName(), reportBugAction); } if (actionName == QStringLiteral("help_about_app") && KAuthorized::authorizeAction(actionName)) { auto aboutAppAction = KStandardAction::aboutApp(this, &ElisaApplication::aboutApplication, this); d->mCollection.addAction(aboutAppAction->objectName(), aboutAppAction); } if (actionName == QStringLiteral("options_configure") && KAuthorized::authorizeAction(actionName)) { auto preferencesAction = KStandardAction::preferences(this, &ElisaApplication::configureElisa, this); d->mCollection.addAction(preferencesAction->objectName(), preferencesAction); } if (actionName == QStringLiteral("options_configure_keybinding") && KAuthorized::authorizeAction(actionName)) { auto keyBindingsAction = KStandardAction::keyBindings(this, &ElisaApplication::configureShortcuts, this); d->mCollection.addAction(keyBindingsAction->objectName(), keyBindingsAction); } if (actionName == QStringLiteral("go_back") && KAuthorized::authorizeAction(actionName)) { auto goBackAction = KStandardAction::back(this, &ElisaApplication::goBack, this); d->mCollection.addAction(goBackAction->objectName(), goBackAction); } if (actionName == QStringLiteral("edit_find") && KAuthorized::authorizeAction(actionName)) { auto findAction = KStandardAction::find(this, &ElisaApplication::find, this); d->mCollection.addAction(findAction->objectName(), findAction); } d->mCollection.readSettings(); #endif } void ElisaApplication::setArguments(const QStringList &newArguments) { if (d->mArguments == newArguments) { return; } d->mArguments = checkFileListAndMakeAbsolute(newArguments, QDir::currentPath()); Q_EMIT argumentsChanged(); if (!d->mArguments.isEmpty()) { Q_EMIT enqueue(d->mArguments); } } void ElisaApplication::activateActionRequested(const QString &actionName, const QVariant ¶meter) { Q_UNUSED(actionName) Q_UNUSED(parameter) } void ElisaApplication::activateRequested(const QStringList &arguments, const QString &workingDirectory) { auto realArguments = arguments; if (realArguments.size() > 1) { realArguments.removeFirst(); Q_EMIT enqueue(checkFileListAndMakeAbsolute(realArguments, workingDirectory)); } } void ElisaApplication::openRequested(const QList &uris) { Q_UNUSED(uris) } void ElisaApplication::appHelpActivated() { QDesktopServices::openUrl(QUrl(QStringLiteral("help:/"))); } void ElisaApplication::aboutApplication() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND static QPointer dialog; if (!dialog) { dialog = new KAboutApplicationDialog(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); #endif } void ElisaApplication::reportBug() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND static QPointer dialog; if (!dialog) { dialog = new KBugReport(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); #endif } void ElisaApplication::configureShortcuts() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, nullptr); dlg.setModal(true); dlg.addCollection(&d->mCollection); dlg.configure(); #endif } void ElisaApplication::configureElisa() { #if defined KF5KCMUtils_FOUND && KF5KCMUtils_FOUND KCMultiDialog configurationDialog; configurationDialog.addModule(QStringLiteral("kcm_elisa_local_file")); configurationDialog.setModal(true); configurationDialog.exec(); #endif } void ElisaApplication::goBack() {} void ElisaApplication::find() {} QStringList ElisaApplication::checkFileListAndMakeAbsolute(const QStringList &filesList, const QString &workingDirectory) const { QStringList filesToOpen; for (const auto &oneFile : filesList) { auto newFile = QFileInfo(oneFile); if (newFile.isRelative()) { newFile = QFileInfo(workingDirectory + QStringLiteral("/") + oneFile); } if (newFile.exists()) { filesToOpen.push_back(newFile.canonicalFilePath()); } } return filesToOpen; } void ElisaApplication::initialize() { initializeModels(); initializePlayer(); } void ElisaApplication::initializeModels() { d->mMusicManager = std::make_unique(); Q_EMIT musicManagerChanged(); d->mAllAlbumsProxyModel = std::make_unique(); Q_EMIT allAlbumsProxyModelChanged(); d->mAllArtistsProxyModel = std::make_unique(); Q_EMIT allArtistsProxyModelChanged(); d->mAllGenresProxyModel = std::make_unique(); Q_EMIT allGenresProxyModelChanged(); d->mAllComposersProxyModel = std::make_unique(); Q_EMIT allComposersProxyModelChanged(); d->mAllLyricistsProxyModel = std::make_unique(); Q_EMIT allLyricistsProxyModelChanged(); d->mAllTracksProxyModel = std::make_unique(); Q_EMIT allTracksProxyModelChanged(); d->mSingleArtistProxyModel = std::make_unique(); Q_EMIT singleArtistProxyModelChanged(); d->mSingleAlbumProxyModel = std::make_unique(); Q_EMIT singleAlbumProxyModelChanged(); + d->mFileBrowserProxyModel = std::make_unique(); + Q_EMIT fileBrowserProxyModelChanged(); d->mMediaPlayList = std::make_unique(); Q_EMIT mediaPlayListChanged(); d->mMusicManager->setElisaApplication(this); d->mMediaPlayList->setMusicListenersManager(d->mMusicManager.get()); QObject::connect(this, &ElisaApplication::enqueue, d->mMediaPlayList.get(), &MediaPlayList::enqueueAndPlay); d->mAllAlbumsProxyModel->setSourceModel(d->mMusicManager->allAlbumsModel()); d->mAllArtistsProxyModel->setSourceModel(d->mMusicManager->allArtistsModel()); d->mAllGenresProxyModel->setSourceModel(d->mMusicManager->allGenresModel()); d->mAllComposersProxyModel->setSourceModel(d->mMusicManager->allComposersModel()); d->mAllLyricistsProxyModel->setSourceModel(d->mMusicManager->allLyricistsModel()); d->mAllTracksProxyModel->setSourceModel(d->mMusicManager->allTracksModel()); d->mSingleArtistProxyModel->setSourceModel(d->mMusicManager->allAlbumsModel()); d->mSingleAlbumProxyModel->setSourceModel(d->mMusicManager->albumModel()); QObject::connect(d->mAllAlbumsProxyModel.get(), &AllAlbumsProxyModel::albumToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); QObject::connect(d->mAllArtistsProxyModel.get(), &AllArtistsProxyModel::artistToEnqueue, d->mMediaPlayList.get(), &MediaPlayList::enqueueArtists); QObject::connect(d->mAllTracksProxyModel.get(), &AllTracksProxyModel::trackToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); QObject::connect(d->mSingleArtistProxyModel.get(), &SingleArtistProxyModel::albumToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); QObject::connect(d->mSingleAlbumProxyModel.get(), &SingleAlbumProxyModel::trackToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); + QObject::connect(d->mFileBrowserProxyModel.get(), &FileBrowserProxyModel::filesToEnqueue, + d->mMediaPlayList.get(), static_cast &, + ElisaUtils::PlayListEnqueueMode, + ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); + } void ElisaApplication::initializePlayer() { d->mAudioWrapper = std::make_unique(); Q_EMIT audioPlayerChanged(); d->mAudioControl = std::make_unique(); Q_EMIT audioControlChanged(); d->mPlayerControl = std::make_unique(); Q_EMIT playerControlChanged(); d->mManageHeaderBar = std::make_unique(); Q_EMIT manageHeaderBarChanged(); d->mAudioControl->setAlbumNameRole(MediaPlayList::AlbumRole); d->mAudioControl->setArtistNameRole(MediaPlayList::ArtistRole); d->mAudioControl->setTitleRole(MediaPlayList::TitleRole); d->mAudioControl->setUrlRole(MediaPlayList::ResourceRole); d->mAudioControl->setIsPlayingRole(MediaPlayList::IsPlayingRole); d->mAudioControl->setPlayListModel(d->mMediaPlayList.get()); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerPlay, d->mAudioWrapper.get(), &AudioWrapper::play); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerPause, d->mAudioWrapper.get(), &AudioWrapper::pause); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerStop, d->mAudioWrapper.get(), &AudioWrapper::stop); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::seek, d->mAudioWrapper.get(), &AudioWrapper::seek); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::skipNextTrack, d->mMediaPlayList.get(), &MediaPlayList::skipNextTrack); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::sourceInError, d->mMediaPlayList.get(), &MediaPlayList::trackInError); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::sourceInError, d->mMusicManager.get(), &MusicListenersManager::playBackError); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerSourceChanged, d->mAudioWrapper.get(), &AudioWrapper::setSource); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::ensurePlay, d->mAudioControl.get(), &ManageAudioPlayer::ensurePlay); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::playListFinished, d->mAudioControl.get(), &ManageAudioPlayer::playListFinished); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::currentTrackChanged, d->mAudioControl.get(), &ManageAudioPlayer::setCurrentTrack); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::playbackStateChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerPlaybackState); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::statusChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerStatus); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::errorChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerError); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::durationChanged, d->mAudioControl.get(), &ManageAudioPlayer::setAudioDuration); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::seekableChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerIsSeekable); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::positionChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerPosition); d->mPlayerControl->setPlayListModel(d->mMediaPlayList.get()); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::currentTrackChanged, d->mPlayerControl.get(), &ManageMediaPlayerControl::setCurrentTrack); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::playing, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerPlaying); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::paused, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerPaused); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::stopped, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerStopped); d->mManageHeaderBar->setTitleRole(MediaPlayList::TitleRole); d->mManageHeaderBar->setAlbumRole(MediaPlayList::AlbumRole); d->mManageHeaderBar->setArtistRole(MediaPlayList::ArtistRole); d->mManageHeaderBar->setImageRole(MediaPlayList::ImageRole); d->mManageHeaderBar->setIsValidRole(MediaPlayList::IsValidRole); d->mManageHeaderBar->setPlayListModel(d->mMediaPlayList.get()); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::currentTrackChanged, d->mManageHeaderBar.get(), &ManageHeaderBar::setCurrentTrack); if (!d->mArguments.isEmpty()) { Q_EMIT enqueue(d->mArguments); } } QAction * ElisaApplication::action(const QString& name) { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND auto resultAction = d->mCollection.action(name); if (!resultAction) { setupActions(name); resultAction = d->mCollection.action(name); } return resultAction; #else Q_UNUSED(name); return new QAction(); #endif } QString ElisaApplication::iconName(const QIcon& icon) { return icon.name(); } const QStringList &ElisaApplication::arguments() const { return d->mArguments; } MusicListenersManager *ElisaApplication::musicManager() const { return d->mMusicManager.get(); } AllAlbumsProxyModel *ElisaApplication::allAlbumsProxyModel() const { return d->mAllAlbumsProxyModel.get(); } AllArtistsProxyModel *ElisaApplication::allArtistsProxyModel() const { return d->mAllArtistsProxyModel.get(); } AllArtistsProxyModel *ElisaApplication::allGenresProxyModel() const { return d->mAllGenresProxyModel.get(); } AllArtistsProxyModel *ElisaApplication::allComposersProxyModel() const { return d->mAllComposersProxyModel.get(); } AllArtistsProxyModel *ElisaApplication::allLyricistsProxyModel() const { return d->mAllLyricistsProxyModel.get(); } AllTracksProxyModel *ElisaApplication::allTracksProxyModel() const { return d->mAllTracksProxyModel.get(); } SingleArtistProxyModel *ElisaApplication::singleArtistProxyModel() const { return d->mSingleArtistProxyModel.get(); } SingleAlbumProxyModel *ElisaApplication::singleAlbumProxyModel() const { return d->mSingleAlbumProxyModel.get(); } +FileBrowserProxyModel *ElisaApplication::fileBrowserProxyModel() const +{ + return d->mFileBrowserProxyModel.get(); +} + MediaPlayList *ElisaApplication::mediaPlayList() const { return d->mMediaPlayList.get(); } AudioWrapper *ElisaApplication::audioPlayer() const { return d->mAudioWrapper.get(); } ManageAudioPlayer *ElisaApplication::audioControl() const { return d->mAudioControl.get(); } ManageMediaPlayerControl *ElisaApplication::playerControl() const { return d->mPlayerControl.get(); } ManageHeaderBar *ElisaApplication::manageHeaderBar() const { return d->mManageHeaderBar.get(); } #include "moc_elisaapplication.cpp" diff --git a/src/elisaapplication.h b/src/elisaapplication.h index ae763f91..da49b50f 100644 --- a/src/elisaapplication.h +++ b/src/elisaapplication.h @@ -1,228 +1,237 @@ /* * 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 ELISAAPPLICATION_H #define ELISAAPPLICATION_H #include "elisaLib_export.h" #include "config-upnp-qt.h" #include #include #include class QIcon; class QAction; class MusicListenersManager; class AllAlbumsProxyModel; class AllArtistsProxyModel; class AllTracksProxyModel; class SingleArtistProxyModel; class SingleAlbumProxyModel; +class FileBrowserProxyModel; class MediaPlayList; class AudioWrapper; class ManageAudioPlayer; class ManageMediaPlayerControl; class ManageHeaderBar; class ElisaApplicationPrivate; class ELISALIB_EXPORT ElisaApplication : public QObject { Q_OBJECT Q_PROPERTY(QStringList arguments READ arguments WRITE setArguments NOTIFY argumentsChanged) Q_PROPERTY(MusicListenersManager *musicManager READ musicManager NOTIFY musicManagerChanged) Q_PROPERTY(AllAlbumsProxyModel *allAlbumsProxyModel READ allAlbumsProxyModel NOTIFY allAlbumsProxyModelChanged) Q_PROPERTY(AllArtistsProxyModel *allArtistsProxyModel READ allArtistsProxyModel NOTIFY allArtistsProxyModelChanged) Q_PROPERTY(AllTracksProxyModel *allTracksProxyModel READ allTracksProxyModel NOTIFY allTracksProxyModelChanged) Q_PROPERTY(AllArtistsProxyModel *allGenresProxyModel READ allGenresProxyModel NOTIFY allGenresProxyModelChanged) Q_PROPERTY(AllArtistsProxyModel *allComposersProxyModel READ allComposersProxyModel NOTIFY allComposersProxyModelChanged) Q_PROPERTY(AllArtistsProxyModel *allLyricistsProxyModel READ allLyricistsProxyModel NOTIFY allLyricistsProxyModelChanged) Q_PROPERTY(SingleArtistProxyModel *singleArtistProxyModel READ singleArtistProxyModel NOTIFY singleArtistProxyModelChanged) Q_PROPERTY(SingleAlbumProxyModel *singleAlbumProxyModel READ singleAlbumProxyModel NOTIFY singleAlbumProxyModelChanged) + Q_PROPERTY(FileBrowserProxyModel *fileBrowserProxyModel + READ fileBrowserProxyModel + NOTIFY fileBrowserProxyModelChanged) + Q_PROPERTY(MediaPlayList *mediaPlayList READ mediaPlayList NOTIFY mediaPlayListChanged) Q_PROPERTY(AudioWrapper *audioPlayer READ audioPlayer NOTIFY audioPlayerChanged) Q_PROPERTY(ManageAudioPlayer *audioControl READ audioControl NOTIFY audioControlChanged) Q_PROPERTY(ManageMediaPlayerControl *playerControl READ playerControl NOTIFY playerControlChanged) Q_PROPERTY(ManageHeaderBar *manageHeaderBar READ manageHeaderBar NOTIFY manageHeaderBarChanged) public: explicit ElisaApplication(QObject *parent = nullptr); ~ElisaApplication() override; Q_INVOKABLE QAction* action(const QString& name); Q_INVOKABLE QString iconName(const QIcon& icon); const QStringList &arguments() const; MusicListenersManager *musicManager() const; AllAlbumsProxyModel *allAlbumsProxyModel() const; AllArtistsProxyModel *allArtistsProxyModel() const; AllArtistsProxyModel *allGenresProxyModel() const; AllArtistsProxyModel *allComposersProxyModel() const; AllArtistsProxyModel *allLyricistsProxyModel() const; AllTracksProxyModel *allTracksProxyModel() const; SingleArtistProxyModel *singleArtistProxyModel() const; SingleAlbumProxyModel *singleAlbumProxyModel() const; + FileBrowserProxyModel *fileBrowserProxyModel() const; + MediaPlayList *mediaPlayList() const; AudioWrapper *audioPlayer() const; ManageAudioPlayer *audioControl() const; ManageMediaPlayerControl *playerControl() const; ManageHeaderBar *manageHeaderBar() const; Q_SIGNALS: void argumentsChanged(); void musicManagerChanged(); void allAlbumsProxyModelChanged(); void allArtistsProxyModelChanged(); void allGenresProxyModelChanged(); void allComposersProxyModelChanged(); void allLyricistsProxyModelChanged(); void allTracksProxyModelChanged(); void singleArtistProxyModelChanged(); void singleAlbumProxyModelChanged(); + void fileBrowserProxyModelChanged(); + void mediaPlayListChanged(); void audioPlayerChanged(); void audioControlChanged(); void playerControlChanged(); void manageHeaderBarChanged(); void enqueue(const QStringList &files); public Q_SLOTS: void appHelpActivated(); void aboutApplication(); void reportBug(); void configureShortcuts(); void configureElisa(); void setArguments(const QStringList &newArguments); void activateActionRequested(const QString &actionName, const QVariant ¶meter); void activateRequested(const QStringList &arguments, const QString &workingDirectory); void openRequested(const QList< QUrl > &uris); void initialize(); private Q_SLOTS: void goBack(); void find(); private: void initializeModels(); void initializePlayer(); void setupActions(const QString &actionName); QStringList checkFileListAndMakeAbsolute(const QStringList &filesList, const QString &workingDirectory) const; std::unique_ptr d; }; #endif // ELISAAPPLICATION_H diff --git a/src/elisaqmlplugin.cpp b/src/elisaqmlplugin.cpp index 81140cde..0cbb4e47 100644 --- a/src/elisaqmlplugin.cpp +++ b/src/elisaqmlplugin.cpp @@ -1,150 +1,152 @@ /* * Copyright 2018 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 "elisaqmlplugin.h" #if defined UPNPQT_FOUND && UPNPQT_FOUND #include "upnp/upnpcontrolconnectionmanager.h" #include "upnp/upnpcontrolmediaserver.h" #include "upnp/upnpcontrolcontentdirectory.h" #include "upnp/upnpcontentdirectorymodel.h" #include "upnpdevicedescription.h" #include "upnp/didlparser.h" #include "upnp/upnpdiscoverallmusic.h" #include "upnpssdpengine.h" #include "upnpabstractservice.h" #include "upnpcontrolabstractdevice.h" #include "upnpcontrolabstractservice.h" #include "upnpbasictypes.h" #endif #include "elisaapplication.h" #include "progressindicator.h" #include "mediaplaylist.h" #include "managemediaplayercontrol.h" #include "manageheaderbar.h" #include "manageaudioplayer.h" #include "musicaudiotrack.h" #include "musicaudiogenre.h" #include "musiclistenersmanager.h" #include "models/allalbumsmodel.h" #include "models/albummodel.h" #include "models/allartistsmodel.h" #include "models/alltracksmodel.h" #include "models/allalbumsproxymodel.h" #include "models/alltracksproxymodel.h" #include "models/allartistsproxymodel.h" #include "models/singleartistproxymodel.h" #include "models/singlealbumproxymodel.h" #include "models/genericdatamodel.h" +#include "models/filebrowserproxymodel.h" #include "audiowrapper.h" #include "notificationitem.h" #include "topnotificationmanager.h" #include "trackdatahelper.h" #include "elisautils.h" #include "datatype.h" #if defined Qt5DBus_FOUND && Qt5DBus_FOUND #include "mpris2/mpris2.h" #include "mpris2/mediaplayer2player.h" #endif #include #include #include #include #include void ElisaQmlTestPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { QQmlExtensionPlugin::initializeEngine(engine, uri); } void ElisaQmlTestPlugin::registerTypes(const char *uri) { #if defined UPNPQT_FOUND && UPNPQT_FOUND qmlRegisterType(uri, 1, 0, "UpnpSsdpEngine"); qmlRegisterType(uri, 1, 0, "UpnpDiscoverAllMusic"); qmlRegisterType(uri, 1, 0, "UpnpAbstractDevice"); qmlRegisterType(uri, 1, 0, "UpnpAbstractService"); qmlRegisterType(uri, 1, 0, "UpnpControlAbstractDevice"); qmlRegisterType(uri, 1, 0, "UpnpControlAbstractService"); qmlRegisterType(uri, 1, 0, "UpnpControlConnectionManager"); qmlRegisterType(uri, 1, 0, "UpnpControlMediaServer"); qmlRegisterType(uri, 1, 0, "UpnpContentDirectoryModel"); qmlRegisterType(uri, 1, 0, "DidlParser"); qmlRegisterType(uri, 1, 0, "UpnpControlContentDirectory"); qmlRegisterType(uri, 1, 0, "UpnpDeviceDescription"); qRegisterMetaType(); qRegisterMetaType >(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #endif qRegisterMetaType("DataUtils::DataType"); qmlRegisterType(uri, 1, 0, "MediaPlayList"); qmlRegisterType(uri, 1, 0, "ManageMediaPlayerControl"); qmlRegisterType(uri, 1, 0, "ManageHeaderBar"); qmlRegisterType(uri, 1, 0, "ManageAudioPlayer"); qmlRegisterType(uri, 1, 0, "ProgressIndicator"); qmlRegisterType(uri, 1, 0, "MusicListenersManager"); qmlRegisterType(uri, 1, 0, "AllAlbumsProxyModel"); qmlRegisterType(uri, 1, 0, "AllArtistsProxyModel"); qmlRegisterType(uri, 1, 0, "AllTracksProxyModel"); qmlRegisterType(uri, 1, 0, "SingleAlbumProxyModel"); qmlRegisterType(uri, 1, 0, "SingleArtistProxyModel"); qmlRegisterType(uri, 1, 0, "GenericDataModel"); + qmlRegisterType(uri, 1, 0, "FileBrowserProxyModel"); qmlRegisterType(uri, 1, 0, "AudioWrapper"); qmlRegisterType(uri, 1, 0, "TopNotificationManager"); qmlRegisterType(uri, 1, 0, "TrackDataHelper"); qmlRegisterUncreatableMetaObject(DataUtils::staticMetaObject, uri, 1, 0, "DataUtils", QStringLiteral("Only enums")); #if defined Qt5DBus_FOUND && Qt5DBus_FOUND qmlRegisterType(uri, 1, 0, "Mpris2"); qRegisterMetaType(); #endif qRegisterMetaType(); qRegisterMetaType>("QHash"); qRegisterMetaType>("QHash"); qRegisterMetaType>("QList"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QList"); qRegisterMetaType>("QList"); qRegisterMetaType>("QList"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QHash"); qRegisterMetaType("MusicAlbum"); qRegisterMetaType("MusicArtist"); qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType("NotificationItem"); qRegisterMetaType>("QMap"); qRegisterMetaType("ElisaUtils::PlayListEnqueueMode"); qRegisterMetaType("ElisaUtils::PlayListEnqueueTriggerPlay"); qRegisterMetaTypeStreamOperators("PlayListControler::PlayerState"); qmlRegisterUncreatableType(uri, 1, 0, "ElisaApplication", QStringLiteral("only one and done in c++")); } diff --git a/src/elisautils.cpp b/src/elisautils.cpp index e6beebb8..8df94181 100644 --- a/src/elisautils.cpp +++ b/src/elisautils.cpp @@ -1,180 +1,168 @@ /* * 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 "elisautils.h" #include #include #include #include #include MusicAudioTrack ElisaUtils::scanOneFile(const QUrl &scanFile, const QMimeDatabase &mimeDatabase, const KFileMetaData::ExtractorCollection &allExtractors) { MusicAudioTrack newTrack; auto localFileName = scanFile.toLocalFile(); QFileInfo scanFileInfo(localFileName); newTrack.setFileModificationTime(scanFileInfo.fileTime(QFile::FileModificationTime)); newTrack.setResourceURI(scanFile); const auto &fileMimeType = mimeDatabase.mimeTypeForFile(localFileName); if (!fileMimeType.name().startsWith(QStringLiteral("audio/"))) { return newTrack; } QString mimetype = fileMimeType.name(); QList exList = allExtractors.fetchExtractors(mimetype); if (exList.isEmpty()) { return newTrack; } KFileMetaData::Extractor* ex = exList.first(); KFileMetaData::SimpleExtractionResult result(localFileName, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData); ex->extract(&result); const auto &allProperties = result.properties(); ElisaUtils::scanProperties(localFileName, allProperties, newTrack); return newTrack; } void ElisaUtils::scanProperties(const QString localFileName, const KFileMetaData::PropertyMap &allProperties, MusicAudioTrack &trackData) { auto titleProperty = allProperties.find(KFileMetaData::Property::Title); auto durationProperty = allProperties.find(KFileMetaData::Property::Duration); auto artistProperty = allProperties.find(KFileMetaData::Property::Artist); auto albumProperty = allProperties.find(KFileMetaData::Property::Album); auto albumArtistProperty = allProperties.find(KFileMetaData::Property::AlbumArtist); auto trackNumberProperty = allProperties.find(KFileMetaData::Property::TrackNumber); auto discNumberProperty = allProperties.find(KFileMetaData::Property::DiscNumber); auto genreProperty = allProperties.find(KFileMetaData::Property::Genre); auto yearProperty = allProperties.find(KFileMetaData::Property::ReleaseYear); auto composerProperty = allProperties.find(KFileMetaData::Property::Composer); auto lyricistProperty = allProperties.find(KFileMetaData::Property::Lyricist); auto channelsProperty = allProperties.find(KFileMetaData::Property::Channels); auto bitRateProperty = allProperties.find(KFileMetaData::Property::BitRate); auto sampleRateProperty = allProperties.find(KFileMetaData::Property::SampleRate); auto commentProperty = allProperties.find(KFileMetaData::Property::Comment); #if defined Q_OS_LINUX && !defined Q_OS_ANDROID auto fileData = KFileMetaData::UserMetaData(localFileName); #endif if (albumProperty != allProperties.end()) { trackData.setAlbumName(albumProperty->toString()); } if (artistProperty != allProperties.end()) { trackData.setArtist(artistProperty->toStringList().join(QStringLiteral(", "))); } if (durationProperty != allProperties.end()) { trackData.setDuration(QTime::fromMSecsSinceStartOfDay(int(1000 * durationProperty->toDouble()))); } if (titleProperty != allProperties.end()) { trackData.setTitle(titleProperty->toString()); } if (trackNumberProperty != allProperties.end()) { trackData.setTrackNumber(trackNumberProperty->toInt()); } if (discNumberProperty != allProperties.end()) { trackData.setDiscNumber(discNumberProperty->toInt()); } else { trackData.setDiscNumber(1); } if (albumArtistProperty != allProperties.end()) { trackData.setAlbumArtist(albumArtistProperty->toStringList().join(QStringLiteral(", "))); } if (yearProperty != allProperties.end()) { trackData.setYear(yearProperty->toInt()); } if (channelsProperty != allProperties.end()) { trackData.setChannels(channelsProperty->toInt()); } if (bitRateProperty != allProperties.end()) { trackData.setBitRate(bitRateProperty->toInt()); } if (sampleRateProperty != allProperties.end()) { trackData.setSampleRate(sampleRateProperty->toInt()); } if (genreProperty != allProperties.end()) { trackData.setGenre(genreProperty->toStringList().join(QStringLiteral(", "))); } if (composerProperty != allProperties.end()) { trackData.setComposer(composerProperty->toStringList().join(QStringLiteral(", "))); } if (lyricistProperty != allProperties.end()) { trackData.setLyricist(lyricistProperty->toStringList().join(QStringLiteral(", "))); } if (commentProperty != allProperties.end()) { trackData.setComment(commentProperty->toString()); } if (trackData.artist().isEmpty()) { trackData.setArtist(trackData.albumArtist()); } #if defined Q_OS_LINUX && !defined Q_OS_ANDROID trackData.setRating(fileData.rating()); #else trackData.setRating(0); #endif - if (trackData.title().isEmpty()) { - return; - } - - if (trackData.artist().isEmpty()) { - return; - } - - if (trackData.albumName().isEmpty()) { - return; - } - if (!trackData.duration().isValid()) { return; } trackData.setValid(true); } #include "moc_elisautils.cpp" diff --git a/src/models/filebrowsermodel.cpp b/src/models/filebrowsermodel.cpp new file mode 100644 index 00000000..dbcd171c --- /dev/null +++ b/src/models/filebrowsermodel.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +#include "filebrowsermodel.h" + +FileBrowserModel::FileBrowserModel(QObject *parent) : KDirModel(parent) +{ + QMimeDatabase db; + QList mimeList = db.allMimeTypes(); + QStringList mimeTypes; + mimeTypes << QStringLiteral("inode/directory"); + foreach (const QMimeType &mime, mimeList) { + if (mime.name().startsWith(QStringLiteral("audio/"))) { + mimeTypes << mime.name(); + } + } + + dirLister()->setMimeFilter(mimeTypes); +} + +FileBrowserModel::~FileBrowserModel() += default; + +QString FileBrowserModel::url() const +{ + return dirLister()->url().toString(); +} + +void FileBrowserModel::setUrl(const QString &url) +{ + QString path = QUrl(url).path(); + path = QUrl::fromLocalFile(path).toString(); + + if (dirLister()->url().path() == QUrl(path).path()) { + dirLister()->updateDirectory(QUrl(path)); + return; + } + + beginResetModel(); + dirLister()->openUrl(QUrl(path)); + + endResetModel(); + emit urlChanged(); +} + +QHash FileBrowserModel::roleNames() const +{ + auto roles = KDirModel::roleNames(); + + roles[static_cast(ColumnsRoles::NameRole)] = "name"; + roles[static_cast(ColumnsRoles::ContainerDataRole)] = "containerData"; + roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; + roles[static_cast(ColumnsRoles::DirectoryRole)] = "directory"; + + return roles; +} + +QVariant FileBrowserModel::data(const QModelIndex &index, int role) const +{ + auto result = QVariant(); + + if (role < ColumnsRoles::NameRole) { + result = KDirModel::data(index,role); + } + + switch(role) + { + case ColumnsRoles::NameRole: + { + KFileItem item = itemForIndex(index); + result = item.name(); + break; + } + case ColumnsRoles::ContainerDataRole: + { + KFileItem item = itemForIndex(index); + result = item.url(); + break; + } + case ColumnsRoles::ImageUrlRole: + { + KFileItem item = itemForIndex(index); + if (item.isDir()) { + result = QUrl(QStringLiteral("image://icon/folder")); + } else { + result = QUrl(QStringLiteral("image://icon/audio-x-generic")); + } + break; + } + case ColumnsRoles::DirectoryRole: + KFileItem item = itemForIndex(index); + result = item.isDir(); + break; + } + + return result; +} + +#include "moc_filebrowsermodel.cpp" diff --git a/src/models/filebrowsermodel.h b/src/models/filebrowsermodel.h new file mode 100644 index 00000000..ca4bbcc4 --- /dev/null +++ b/src/models/filebrowsermodel.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FILEBROWSERMODEL_H +#define FILEBROWSERMODEL_H + +#include +#include +#include + +class MusicAudioTrack; + +class FileBrowserModel : public KDirModel +{ + Q_OBJECT + +public: + + enum ColumnsRoles { + NameRole = Qt::UserRole + 1, + ContainerDataRole = Qt::UserRole + 2, + ImageUrlRole = Qt::UserRole + 3, + DirectoryRole = Qt::UserRole + 4 + }; + + Q_ENUM(ColumnsRoles) + + explicit FileBrowserModel(QObject *parent = nullptr); + + ~FileBrowserModel() override; + + QString url() const; + + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void setUrl(const QString &url); + +Q_SIGNALS: + + void urlChanged(); + +}; + +#endif //FILEBROWSERMODEL_H diff --git a/src/models/filebrowserproxymodel.cpp b/src/models/filebrowserproxymodel.cpp new file mode 100644 index 00000000..94791811 --- /dev/null +++ b/src/models/filebrowserproxymodel.cpp @@ -0,0 +1,192 @@ +/* + * Copyright 2016-2017 Matthieu Gallien + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "filebrowserproxymodel.h" + +#include "filebrowsermodel.h" + +#include +#include +#include +#include +#include "elisautils.h" + +FileBrowserProxyModel::FileBrowserProxyModel(QObject *parent) : KDirSortFilterProxyModel(parent) +{ + setFilterCaseSensitivity(Qt::CaseInsensitive); + mThreadPool.setMaxThreadCount(1); + mFileModel = std::make_unique(); + setSourceModel(mFileModel.get()); + setSortFoldersFirst(true); + sort(Qt::AscendingOrder); + connect(mFileModel.get(), &FileBrowserModel::urlChanged,this, &FileBrowserProxyModel::urlChanged); + mTopFolder = QDir::homePath(); + openFolder(mTopFolder, true); +} + +FileBrowserProxyModel::~FileBrowserProxyModel() = default; + +QString FileBrowserProxyModel::filterText() const +{ + return mFilterText; +} + +void FileBrowserProxyModel::setFilterText(const QString &filterText) +{ + QWriteLocker writeLocker(&mDataLock); + + if (mFilterText == filterText) + return; + + mFilterText = filterText; + + mFilterExpression.setPattern(mFilterText); + mFilterExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + mFilterExpression.optimize(); + + invalidate(); + + Q_EMIT filterTextChanged(mFilterText); +} + +bool FileBrowserProxyModel::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 &nameValue = sourceModel()->data(currentIndex, FileBrowserModel::NameRole).toString(); + + if (mFilterExpression.match(nameValue).hasMatch()) { + result = true; + continue; + } + + if (result) { + continue; + } + + if (!result) { + break; + } + } + + return result; +} + +void FileBrowserProxyModel::enqueueToPlayList() +{ + qDebug() << "enqueue"; + QtConcurrent::run(&mThreadPool, [=] () { + QReadLocker locker(&mDataLock); + auto allTrackUrls = QList(); + for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) { + auto currentIndex = index(rowIndex, 0); + if (!data(currentIndex, FileBrowserModel::DirectoryRole).toBool()) { + allTrackUrls.push_back(data(currentIndex, FileBrowserModel::ContainerDataRole).toUrl()); + } + } + Q_EMIT filesToEnqueue(allTrackUrls, + ElisaUtils::AppendPlayList, + ElisaUtils::DoNotTriggerPlay); + }); +} + +void FileBrowserProxyModel::replaceAndPlayOfPlayList() +{ + qDebug() << "replace"; + QtConcurrent::run(&mThreadPool, [=] () { + QReadLocker locker(&mDataLock); + auto allTrackUrls = QList(); + for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) { + auto currentIndex = index(rowIndex, 0); + if (!data(currentIndex, FileBrowserModel::DirectoryRole).toBool()) { + allTrackUrls.push_back(data(currentIndex, FileBrowserModel::ContainerDataRole).toUrl()); + } + } + Q_EMIT filesToEnqueue(allTrackUrls, + ElisaUtils::ReplacePlayList, + ElisaUtils::TriggerPlay); + }); +} + +QString FileBrowserProxyModel::parentFolder() const +{ + //return to the top folder if parent directory does not exist + QDir dir(mFileModel->dirLister()->url().toLocalFile()); + if (dir.cdUp()) { + return dir.path(); + } else { + return mTopFolder; + } +} + +void FileBrowserProxyModel::openParentFolder() +{ + if (canGoBack()) { + QString parent = parentFolder(); + mFileModel->setUrl(parent); + if (parent == mTopFolder) { + Q_EMIT canGoBackChanged(); + } + } +} + +bool FileBrowserProxyModel::canGoBack() const +{ + return mFileModel->dirLister()->url().toLocalFile() != mTopFolder; +} + +void FileBrowserProxyModel::openFolder(const QString &folder, bool isDisplayRoot) +{ + if (folder.isEmpty()) { + return; + } + mFileModel->setUrl(folder); + if (!isDisplayRoot) { + Q_EMIT canGoBackChanged(); + } +} + +MusicAudioTrack FileBrowserProxyModel::loadMetaDataFromUrl(const QUrl &url) +{ + auto newTrack = ElisaUtils::scanOneFile(url,mMimeDb,mExtractors); + qDebug() << "loaded metadata " << url << newTrack; + return newTrack; +} + +QString FileBrowserProxyModel::url() const +{ + return mFileModel->dirLister()->url().toLocalFile(); +} + +bool FileBrowserProxyModel::sortedAscending() const +{ + return sortOrder() ? false : true; +} + +void FileBrowserProxyModel::sortModel(Qt::SortOrder order) +{ + this->sort(0,order); + Q_EMIT sortedAscendingChanged(); +} + +#include "moc_filebrowserproxymodel.cpp" diff --git a/src/models/filebrowserproxymodel.h b/src/models/filebrowserproxymodel.h new file mode 100644 index 00000000..6e509db4 --- /dev/null +++ b/src/models/filebrowserproxymodel.h @@ -0,0 +1,128 @@ +/* + * Copyright 2016-2018 Matthieu Gallien + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FILEBROWSERPROXYMODEL_H +#define FILEBROWSERPROXYMODEL_H + +#include "elisaLib_export.h" + +#include +#include +#include +#include +#include + +#include "filebrowsermodel.h" +#include "musicaudiotrack.h" +#include "elisautils.h" + +class ELISALIB_EXPORT FileBrowserProxyModel : public KDirSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(QString filterText + READ filterText + WRITE setFilterText + NOTIFY filterTextChanged) + + Q_PROPERTY(bool canGoBack + READ canGoBack + NOTIFY canGoBackChanged) + + Q_PROPERTY(QString url + READ url + NOTIFY urlChanged) + + Q_PROPERTY(bool sortedAscending + READ sortedAscending + NOTIFY sortedAscendingChanged) + +public: + + explicit FileBrowserProxyModel(QObject *parent = nullptr); + + ~FileBrowserProxyModel() override; + + QString filterText() const; + + QString url() const; + + bool canGoBack() const; + + Q_INVOKABLE MusicAudioTrack loadMetaDataFromUrl(const QUrl &url); + + bool sortedAscending() const; + +public Q_SLOTS: + + void enqueueToPlayList(); + + void replaceAndPlayOfPlayList(); + + void setFilterText(const QString &filterText); + + void openParentFolder(); + + void openFolder(const QString &folder, bool isDisplayRoot = false); + + void sortModel(Qt::SortOrder order); + +Q_SIGNALS: + + void filesToEnqueue(QList newFiles, + ElisaUtils::PlayListEnqueueMode enqueueMode, + ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); + + void urlChanged(); + + void canGoBackChanged(); + + void filterTextChanged(const QString &filterText); + + void sortedAscendingChanged(); + +protected: + + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + + QString parentFolder() const; + + QString mTopFolder; + + KFileMetaData::ExtractorCollection mExtractors; + + QMimeDatabase mMimeDb; + + QString mFilterText; + + QRegularExpression mFilterExpression; + + QReadWriteLock mDataLock; + + QThreadPool mThreadPool; + + std::unique_ptr mFileModel; + +}; + +#endif // FILEBROWSERPROXYMODEL_H diff --git a/src/qml/ContentView.qml b/src/qml/ContentView.qml index fca5966b..e7d8b12c 100644 --- a/src/qml/ContentView.qml +++ b/src/qml/ContentView.qml @@ -1,771 +1,829 @@ /* * Copyright 2016-2018 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 2.2 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import org.kde.elisa 1.0 RowLayout { id: contentViewContainer spacing: 0 signal toggleSearch() function goBack() { viewManager.goBack() } ViewManager { id: viewManager onSwitchAllAlbumsView: { elisa.allAlbumsProxyModel.genreFilterText = '' listViews.currentIndex = 1 localArtistsLoader.opacity = 0 localTracksLoader.opacity = 0 localAlbumsLoader.opacity = 1 localGenresLoader.opacity = 0 + localFilesLoader.opacity = 0 } onSwitchOneAlbumView: { elisa.singleAlbumProxyModel.loadAlbumData(databaseId) currentStackView.push(albumView, { mainTitle: mainTitle, secondaryTitle: secondaryTitle, image: imageUrl, stackView: currentStackView, }) oneAlbumViewIsLoaded() } onSwitchAllArtistsView: { elisa.allArtistsProxyModel.genreFilterText = '' listViews.currentIndex = 2 localArtistsLoader.opacity = 1 localTracksLoader.opacity = 0 localAlbumsLoader.opacity = 0 localGenresLoader.opacity = 0 + localFilesLoader.opacity = 0 } onSwitchOneArtistView: { elisa.singleArtistProxyModel.setArtistFilterText(mainTitle) elisa.singleArtistProxyModel.genreFilterText = '' currentStackView.push(innerAlbumView, { mainTitle: mainTitle, secondaryTitle: secondaryTitle, image: imageUrl, stackView: currentStackView, }) oneArtistViewIsLoaded() } onSwitchOneArtistFromGenreView: { elisa.singleArtistProxyModel.setArtistFilterText(mainTitle) elisa.singleArtistProxyModel.genreFilterText = genreName currentStackView.push(innerAlbumView, { mainTitle: mainTitle, secondaryTitle: secondaryTitle, image: imageUrl, stackView: currentStackView, }) oneArtistViewIsLoaded() } onSwitchAllTracksView: { elisa.allTracksProxyModel.genreFilterText = '' listViews.currentIndex = 3 localArtistsLoader.opacity = 0 localTracksLoader.opacity = 1 localAlbumsLoader.opacity = 0 localGenresLoader.opacity = 0 + localFilesLoader.opacity = 0 } onSwitchAllGenresView: { listViews.currentIndex = 4 localArtistsLoader.opacity = 0 localTracksLoader.opacity = 0 localAlbumsLoader.opacity = 0 localGenresLoader.opacity = 1 + localFilesLoader.opacity = 0 + } + + onSwitchFilesBrowserView: { + listViews.currentIndex = 5 + localArtistsLoader.opacity = 0 + localTracksLoader.opacity = 0 + localAlbumsLoader.opacity = 0 + localGenresLoader.opacity = 0 + localFilesLoader.opacity = 1 } onSwitchAllArtistsFromGenreView: { elisa.allArtistsProxyModel.genreFilterText = genreName currentStackView.push(innerArtistView, { contentModel: elisa.allArtistsProxyModel, mainTitle: genreName, secondaryTitle: '', image: elisaTheme.artistIcon, stackView: currentStackView, }) allArtistsFromGenreViewIsLoaded() } onSwitchOffAllViews: { localArtistsLoader.opacity = 0 localTracksLoader.opacity = 0 localAlbumsLoader.opacity = 0 localGenresLoader.opacity = 0 + localFilesLoader.opacity = 0 } } ViewSelector { id: listViews Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.11 Layout.maximumWidth: mainWindow.width * 0.11 onSwitchView: if (index === 1) { viewManager.openAllAlbums() } else if (index === 2) { viewManager.openAllArtists() } else if (index === 3) { viewManager.openAllTracks() } else if (index === 4) { viewManager.openAllGenres() + } else if (index === 5) { + viewManager.openFilesBrowser() } else { viewManager.closeAllViews() } } Rectangle { id: viewSelectorSeparatorItem 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 } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 TopNotification { id: invalidBalooConfiguration Layout.fillWidth: true musicManager: elisa.musicManager 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 Loader { anchors.fill: parent anchors.leftMargin: parent.width / 3 anchors.rightMargin: parent.width / 3 anchors.topMargin: parent.height / 3 anchors.bottomMargin: parent.height / 3 z: 2 sourceComponent: BusyIndicator { id: busyScanningMusic hoverEnabled: false anchors.fill: parent opacity: 0.8 visible: true running: true z: 2 } active: elisa.musicManager.indexerBusy } Loader { id: localAlbumsLoader active: opacity > 0 visible: opacity > 0 anchors.fill: parent onLoaded: viewManager.allAlbumsViewIsLoaded(item.stackView) sourceComponent: MediaBrowser { id: localAlbums focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allAlbumsView focus: true contentModel: elisa.allAlbumsProxyModel image: elisaTheme.albumIcon mainTitle: i18nc("Title of the view of all albums", "Albums") onOpen: { viewManager.openOneAlbum(localAlbums.stackView, innerMainTitle, innerSecondaryTitle, innerImage, databaseId) } onGoBack: viewManager.goBack() Binding { target: allAlbumsView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } Behavior on opacity { NumberAnimation { easing.type: Easing.InOutQuad duration: 300 } } } Loader { id: localArtistsLoader active: opacity > 0 visible: opacity > 0 opacity: 0 anchors.fill: parent onLoaded: viewManager.allArtistsViewIsLoaded(item.stackView) sourceComponent: MediaBrowser { id: localArtists focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allArtistsView focus: true showRating: false delegateDisplaySecondaryText: false contentModel: elisa.allArtistsProxyModel image: elisaTheme.artistIcon mainTitle: i18nc("Title of the view of all artists", "Artists") onOpen: { viewManager.openOneArtist(localArtists.stackView, innerMainTitle, innerImage, 0) } onGoBack: viewManager.goBack() Binding { target: allArtistsView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } Behavior on opacity { NumberAnimation { easing.type: Easing.InOutQuad duration: 300 } } } Loader { id: localTracksLoader active: opacity > 0 visible: opacity > 0 opacity: 0 anchors.fill: parent onLoaded: viewManager.allTracksViewIsLoaded(item) sourceComponent: MediaBrowser { id: localTracks focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: ListBrowserView { id: allTracksView focus: true contentModel: elisa.allTracksProxyModel delegate: MediaTrackDelegate { id: entry width: allTracksView.delegateWidth height: elisaTheme.trackDelegateHeight focus: true trackData: model.containerData isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum onEnqueue: elisa.mediaPlayList.enqueue(data) onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) onClicked: contentDirectoryView.currentIndex = index } image: elisaTheme.tracksIcon mainTitle: i18nc("Title of the view of all tracks", "Tracks") Binding { target: allTracksView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } Behavior on opacity { NumberAnimation { easing.type: Easing.InOutQuad duration: 300 } } } Loader { id: localGenresLoader active: opacity > 0 visible: opacity > 0 opacity: 0 - anchors.fill: parent onLoaded: viewManager.allGenresViewIsLoaded(item.stackView) sourceComponent: MediaBrowser { id: localGenres focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allGenresView focus: true showRating: false delegateDisplaySecondaryText: false contentModel: elisa.allGenresProxyModel image: elisaTheme.genresIcon mainTitle: i18nc("Title of the view of all genres", "Genres") onOpen: { viewManager.openAllArtistsFromGenre(localGenres.stackView, innerMainTitle) } onGoBack: viewManager.goBack() Binding { target: allGenresView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } Behavior on opacity { NumberAnimation { easing.type: Easing.InOutQuad duration: 300 } } } + Loader { + id: localFilesLoader + anchors.fill: parent + + active: opacity > 0 + + visible: opacity > 0 + + opacity: 0 + + anchors { + fill: parent + + leftMargin: elisaTheme.layoutHorizontalMargin + rightMargin: elisaTheme.layoutHorizontalMargin + } + + onLoaded: viewManager.filesBrowserViewIsLoaded(item) + + sourceComponent: FileBrowserView { + id: localFiles + + focus: true + + contentModel: elisa.fileBrowserProxyModel + + Binding { + target: localFiles + property: 'expandedFilterView' + value: persistentSettings.expandedFilterView + } + + onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView + } + + Behavior on opacity { + NumberAnimation { + easing.type: Easing.InOutQuad + duration: 300 + } + } + } + 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: elisa.mediaPlayList Layout.fillHeight: true Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width onStartPlayback: elisa.audioControl.ensurePlay() onPausePlayback: elisa.audioControl.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: elisa.manageHeaderBar.artist albumName: elisa.manageHeaderBar.album albumArtUrl: elisa.manageHeaderBar.image } } } states: [ State { name: 'playList' 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 } }, State { name: 'browsingViews' when: listViews.currentIndex !== 0 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 } } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" easing.type: Easing.InOutQuad duration: 300 } } } Component { id: innerAlbumView GridBrowserView { id: innerAlbumGridView contentModel: elisa.singleArtistProxyModel isSubPage: true onOpen: { viewManager.openOneAlbum(stackView, innerMainTitle, innerSecondaryTitle, innerImage, databaseId) } onGoBack: viewManager.goBack() Binding { target: innerAlbumGridView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } Component { id: innerArtistView GridBrowserView { id: innerAlbumGridView delegateDisplaySecondaryText: false isSubPage: true onOpen: { viewManager.openOneArtist(stackView, innerMainTitle, innerImage, 0) } onGoBack: viewManager.goBack() Binding { target: innerAlbumGridView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } Component { id: albumView ListBrowserView { id: albumGridView contentModel: elisa.singleAlbumProxyModel isSubPage: true delegate: MediaAlbumTrackDelegate { id: entry width: albumGridView.delegateWidth height: ((model.isFirstTrackOfDisc && !isSingleDiscAlbum) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) focus: true mediaTrack.trackData: model.containerData mediaTrack.isFirstTrackOfDisc: model.isFirstTrackOfDisc mediaTrack.isSingleDiscAlbum: model.isSingleDiscAlbum mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(data) mediaTrack.onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) mediaTrack.isAlternateColor: (index % 2) === 1 mediaTrack.onClicked: contentDirectoryView.currentIndex = index } allowArtistNavigation: true onShowArtist: { viewManager.openOneArtist(stackView, name, elisaTheme.artistIcon, 0) } onGoBack: viewManager.goBack() expandedFilterView: true Binding { target: albumGridView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } } diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index 04d12e7d..d0442ded 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,286 +1,286 @@ /* * Copyright 2016-2018 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 2.2 import QtQuick.Controls 1.4 as Controls1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import org.kde.elisa 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 1100 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: i18n("Elisa") property var goBackAction: elisa.action("go_back") property var findAction: elisa.action("edit_find") Controls1.Action { shortcut: findAction.shortcut onTriggered: { if ( persistentSettings.expandedFilterView == true) { persistentSettings.expandedFilterView = false } else { persistentSettings.expandedFilterView = true } } } Controls1.Action { shortcut: goBackAction.shortcut onTriggered: contentView.goBack() } Controls1.Action { id: applicationMenuAction text: i18nc("open application menu", "Application Menu") iconName: "application-menu" onTriggered: applicationMenu.popup() } ApplicationMenu { id: applicationMenu } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 1100 property int height : 600 property var playListState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false property bool playControlItemRepeat : false property bool playControlItemShuffle : false property bool expandedFilterView: false } Connections { target: Qt.application onAboutToQuit: { persistentSettings.x = mainWindow.x; persistentSettings.y = mainWindow.y; persistentSettings.width = mainWindow.width; persistentSettings.height = mainWindow.height; persistentSettings.playListState = elisa.mediaPlayList.persistentState; persistentSettings.audioPlayerState = elisa.audioControl.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted persistentSettings.playControlItemRepeat = headerBar.playerControl.repeat persistentSettings.playControlItemShuffle = headerBar.playerControl.shuffle } } Loader { id: mprisloader active: false sourceComponent: PlatformIntegration { id: platformInterface playListModel: elisa.mediaPlayList audioPlayerManager: elisa.audioControl player: elisa.audioPlayer headerBarManager: elisa.manageHeaderBar manageMediaPlayerControl: elisa.playerControl onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } } Connections { target: elisa.audioPlayer onVolumeChanged: headerBar.playerControl.volume = elisa.audioPlayer.volume onMutedChanged: headerBar.playerControl.muted = elisa.audioPlayer.muted } Connections { target: elisa.mediaPlayList onPlayListLoadFailed: { messageNotification.showNotification(i18nc("message of passive notification when playlist load failed", "Load of playlist failed"), 3000) } } 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: elisa.manageHeaderBar.remainingTracks album: elisa.manageHeaderBar.album title: elisa.manageHeaderBar.title artist: elisa.manageHeaderBar.artist image: elisa.manageHeaderBar.image ratingVisible: false playerControl.duration: elisa.audioPlayer.duration playerControl.seekable: elisa.audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: elisa.audioPlayer.position playerControl.skipBackwardEnabled: elisa.playerControl.skipBackwardControlEnabled playerControl.skipForwardEnabled: elisa.playerControl.skipForwardControlEnabled playerControl.playEnabled: elisa.playerControl.playControlEnabled playerControl.isPlaying: elisa.playerControl.musicPlaying playerControl.repeat: persistentSettings.playControlItemRepeat playerControl.shuffle: persistentSettings.playControlItemShuffle playerControl.onSeek: elisa.audioPlayer.seek(position) playerControl.onPlay: elisa.audioControl.playPause() playerControl.onPause: elisa.audioControl.playPause() playerControl.onPlayPrevious: elisa.mediaPlayList.skipPreviousTrack() playerControl.onPlayNext: elisa.mediaPlayList.skipNextTrack() TrackImportNotification { id: importedTracksCountNotification anchors { right: headerBar.right top: headerBar.top rightMargin: elisaTheme.layoutHorizontalMargin * 1.75 topMargin: elisaTheme.layoutHorizontalMargin * 3 } } Binding { target: importedTracksCountNotification property: 'musicManager' value: elisa.musicManager when: elisa.musicManager !== undefined } Loader { sourceComponent: Binding { target: importedTracksCountNotification property: 'indexingRunning' value: elisa.musicManager.indexingRunning } active: elisa.musicManager !== undefined } Loader { sourceComponent: Binding { target: importedTracksCountNotification property: 'importedTracksCount' value: elisa.musicManager.importedTracksCount } active: elisa.musicManager !== undefined } } } ContentView { id: contentView Layout.fillHeight: true Layout.fillWidth: true } } } Component.onCompleted: { elisa.initialize() elisa.mediaPlayList.randomPlay = Qt.binding(function() { return headerBar.playerControl.shuffle }) elisa.mediaPlayList.repeatPlay = Qt.binding(function() { return headerBar.playerControl.repeat }) - elisa.playerControl.randomOrContinuePlay = Qt.binding(function() { return headerBar.playerControl.shuffle || headerBar.playerControl.repeat }) + elisa.playerControl.randomOrContinuePlay = Qt.binding(function() { return headerBar.playerControl.shuffle || headerBar.playerControl.repeat}) if (persistentSettings.playListState) { elisa.mediaPlayList.persistentState = persistentSettings.playListState } if (persistentSettings.audioPlayerState) { elisa.audioControl.persistentState = persistentSettings.audioPlayerState } elisa.audioPlayer.muted = Qt.binding(function() { return headerBar.playerControl.muted }) elisa.audioPlayer.volume = Qt.binding(function() { return headerBar.playerControl.volume }) mprisloader.active = true } } diff --git a/src/qml/FileBrowserDelegate.qml b/src/qml/FileBrowserDelegate.qml new file mode 100644 index 00000000..d21e2733 --- /dev/null +++ b/src/qml/FileBrowserDelegate.qml @@ -0,0 +1,283 @@ +/* + * Copyright 2016-2018 Matthieu Gallien + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 +import QtQuick.Layouts 1.2 + +import org.kde.elisa 1.0 + +FocusScope { + id: fileDelegate + + property var fileName + property var fileUrl + property var imageUrl + property var contentModel + property bool isDirectory + + signal enqueue(var data) + signal replaceAndPlay(var data) + signal open(var data) + signal selected() + + Controls1.Action { + id: replaceAndPlayAction + text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") + iconName: "media-playback-start" + onTriggered: replaceAndPlay(fileUrl) + } + + Controls1.Action { + id: enqueueAction + text: i18nc("Enqueue current track", "Enqueue") + iconName: "media-track-add-amarok" + onTriggered: enqueue(fileUrl) + } + + Controls1.Action { + id: openAction + text: i18nc("Enqueue current track", "Enqueue") + iconName: 'document-open-folder' + onTriggered: open(fileUrl) + } + + Controls1.Action { + id: viewDetailsAction + text: i18nc("Show track metadata", "View Details") + iconName: "help-about" + onTriggered: { + if (metadataLoader.active === false) { + metadataLoader.active = true + metadataLoader.item.trackDataHelper.trackData = contentModel.loadMetaDataFromUrl(fileUrl) + } + else { + metadataLoader.item.close(); + metadataLoader.active = false + } + } + } + + Loader { + id: metadataLoader + active: false + onLoaded: item.show() + + sourceComponent: MediaTrackMetadataView { + trackDataHelper: TrackDataHelper { + id: dataHelper + } + + onRejected: metadataLoader.active = false; + } + } + + Keys.onReturnPressed: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) + Keys.onEnterPressed: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) + + ColumnLayout { + anchors.fill: parent + + spacing: 0 + + MouseArea { + id: hoverArea + + hoverEnabled: true + acceptedButtons: Qt.LeftButton + + Layout.preferredHeight: fileDelegate.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + mainLabelSize.height + Layout.fillWidth: true + + onClicked: fileDelegate.selected() + + onDoubleClicked: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) + + TextMetrics { + id: mainLabelSize + font: mainLabel.font + text: mainLabel.text + } + + ColumnLayout { + id: mainData + + spacing: 0 + anchors.fill: parent + + Item { + Layout.preferredHeight: fileDelegate.width * 0.85 + Layout.preferredWidth: fileDelegate.width * 0.85 + + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Loader { + id: hoverLoader + active: false + + anchors.centerIn: parent + z: 1 + + opacity: 0 + + sourceComponent: Row { + + Controls1.ToolButton { + id: detailsButton + + Layout.preferredHeight: elisaTheme.delegateHeight + Layout.preferredWidth: elisaTheme.delegateHeight + + action: viewDetailsAction + visible: !isDirectory + } + + Controls1.ToolButton { + id: enqueueOpenButton + + Layout.preferredHeight: elisaTheme.delegateHeight + Layout.preferredWidth: elisaTheme.delegateHeight + + action: isDirectory ? openAction : enqueueAction + } + + Controls1.ToolButton { + id: replaceAndPlayButton + + Layout.preferredHeight: elisaTheme.delegateHeight + Layout.preferredWidth: elisaTheme.delegateHeight + + scale: LayoutMirroring.enabled ? -1 : 1 + action: replaceAndPlayAction + visible: !isDirectory + } + } + } + + Image { + id: icon + anchors.fill: parent + + sourceSize.width: parent.width + sourceSize.height: parent.height + fillMode: Image.PreserveAspectFit + smooth: true + + source: imageUrl + + asynchronous: true + } + } + + LabelWithToolTip { + id: mainLabel + + font.weight: Font.Bold + color: myPalette.text + + horizontalAlignment: Text.AlignLeft + + Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 + Layout.preferredWidth: fileDelegate.width * 0.85 + Layout.maximumHeight: mainLabelSize.height * 2 + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + + text: fileName + wrapMode: Label.Wrap + elide: Text.ElideRight + } + + Item { + Layout.fillHeight: true + } + } + } + + Item { + Layout.fillHeight: true + } + } + + states: [ + State { + name: 'notSelected' + when: !fileDelegate.activeFocus && !hoverArea.containsMouse + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: icon + opacity: 1 + } + }, + State { + name: 'hoveredOrSelected' + when: fileDelegate.activeFocus || hoverArea.containsMouse + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + PropertyChanges { + target: icon + opacity: 0.2 + } + } + ] + + transitions: [ + Transition { + to: 'hoveredOrSelected' + SequentialAnimation { + PropertyAction { + properties: "active" + } + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 100 + } + } + }, + Transition { + to: 'notSelected' + SequentialAnimation { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 100 + } + PropertyAction { + properties: "active" + } + } + } + ] +} + diff --git a/src/qml/FileBrowserView.qml b/src/qml/FileBrowserView.qml new file mode 100644 index 00000000..0f5e4545 --- /dev/null +++ b/src/qml/FileBrowserView.qml @@ -0,0 +1,157 @@ +/* + * Copyright 2016-2018 Matthieu Gallien + * Copyright 2018 Alexander Stippich + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQml.Models 2.2 +import QtQuick.Layouts 1.2 + +import org.kde.elisa 1.0 + +FocusScope { + id: fileView + + property bool isSubPage: false + property alias contentModel: contentDirectoryView.model + property alias expandedFilterView: navigationBar.expandedFilterView + + signal filterViewChanged(bool expandedFilterView) + + function goBack() { + contentModel.openParentFolder() + } + + function loadFolderAndClear(data) { + contentModel.openFolder(data) + navigationBar.filterText = "" + } + + SystemPalette { + id: myPalette + colorGroup: SystemPalette.Active + } + + Theme { + id: elisaTheme + } + + MouseArea { + anchors.fill: parent + hoverEnabled: false + acceptedButtons: Qt.BackButton + onClicked: contentModel.openParentFolder() + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + NavigationActionBar { + id: navigationBar + + mainTitle: i18nc("Title of the file browser view", "Files") + image: elisaTheme.folderIcon + secondaryTitle: contentModel.url + enableGoBack: contentModel.canGoBack + sortOrder: contentModel.sortedAscending + showRating: false + + height: elisaTheme.navigationBarHeight + Layout.preferredHeight: height + Layout.minimumHeight: height + Layout.maximumHeight: height + Layout.fillWidth: true + + Binding { + target: contentModel + property: 'filterText' + value: navigationBar.filterText + } + + onEnqueue: contentModel.enqueueToPlayList() + onReplaceAndPlay: contentModel.replaceAndPlayOfPlayList() + onGoBack: contentModel.openParentFolder() + onFilterViewChanged: fileView.filterViewChanged(expandedFilterView) + onSort: contentModel.sortModel(order) + } + + Rectangle { + color: myPalette.base + + Layout.fillHeight: true + Layout.fillWidth: true + clip: true + + GridView { + id: contentDirectoryView + anchors.topMargin: 20 + anchors.fill: parent + + focus: true + + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + + add: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + } + + remove: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + } + + cellWidth: elisaTheme.gridDelegateWidth + cellHeight:elisaTheme.gridDelegateHeight + + delegate: FileBrowserDelegate { + width: contentDirectoryView.cellWidth + height: contentDirectoryView.cellHeight + focus: true + + isDirectory: model.directory + fileName: model.name + fileUrl: model.containerData + imageUrl: model.imageUrl + contentModel: fileView.contentModel + + onEnqueue: elisa.mediaPlayList.enqueue(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onSelected: { + forceActiveFocus() + contentDirectoryView.currentIndex = model.index + } + onOpen: loadFolderAndClear(data) + } + } + } + } +} diff --git a/src/qml/Theme.qml b/src/qml/Theme.qml index 6dc6a590..c1a3fcab 100644 --- a/src/qml/Theme.qml +++ b/src/qml/Theme.qml @@ -1,96 +1,97 @@ /* * 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. */ import QtQuick 2.7 import QtQuick.Controls 2.2 Item { function dp(pixel) { // 96 - common, "base" DPI value return Math.round(pixel * logicalDpi / 96); } property string defaultAlbumImage: 'image://icon/media-optical-audio' property string defaultArtistImage: 'image://icon/view-media-artist' property string defaultBackgroundImage: 'qrc:///background.png' property string artistIcon: 'image://icon/view-media-artist' property string albumIcon: 'image://icon/view-media-album-cover' 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 skipBackwardIcon: 'image://icon/media-skip-backward' property string pauseIcon: 'image://icon/media-playback-pause' property string playIcon: 'image://icon/media-playback-start' property string skipForwardIcon: 'image://icon/media-skip-forward' property string playerVolumeMutedIcon: 'image://icon/player-volume-muted' property string playerVolumeIcon: 'image://icon/player-volume' property string ratingIcon: 'image://icon/rating' property string ratingUnratedIcon: 'image://icon/rating-unrated' property string errorIcon: 'image://icon/error' property string playIndicatorIcon: 'image://icon/audio-volume-high' property string repeatIcon: 'image://icon/media-repeat-all' property string shuffleIcon: 'image://icon/media-playlist-shuffle' property string noRepeatIcon: 'image://icon/media-repeat-none' property string noShuffleIcon: 'image://icon/media-playlist-normal' + property string folderIcon: 'image://icon/document-open-folder' property int layoutHorizontalMargin: dp(8) property int layoutVerticalMargin: dp(6) property int delegateHeight: dp(28) property int delegateWithHeaderHeight: dp(68) property int trackDelegateHeight: dp(45) property int coverImageSize: dp(180) property int smallImageSize: dp(32) property int trackMetadataWidth: dp(300) property int tooltipRadius: dp(3) property int shadowOffset: dp(2) property int delegateToolButtonSize: dp(34) property int smallDelegateToolButtonSize: dp(20) property int ratingStarSize: dp(15) property int mediaPlayerControlHeight: dp(42) property int mediaPlayerHorizontalMargin: dp(10) property real mediaPlayerControlOpacity: 0.6 property int smallControlButtonSize: dp(22) property int volumeSliderWidth: dp(100) property int dragDropPlaceholderHeight: dp(28) property int navigationBarHeight: dp(100) property int navigationBarFilterHeight: dp(44) property int gridDelegateHeight: dp(100) + layoutVerticalMargin + fontSize.height * 2 property int gridDelegateWidth: dp(100) property int viewSelectorDelegateHeight: dp(24) property int filterClearButtonMargin: layoutVerticalMargin property alias defaultFontPointSize: fontSize.font.pointSize Label { id: fontSize } } diff --git a/src/qml/ViewManager.qml b/src/qml/ViewManager.qml index 2a2e4e58..d35e04a4 100644 --- a/src/qml/ViewManager.qml +++ b/src/qml/ViewManager.qml @@ -1,249 +1,264 @@ /* * 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 QtObject { enum ViewsType { NoViews, AllAlbums, OneAlbum, AllArtists, OneArtist, OneAlbumFromArtist, AllTracks, AllGenres, AllArtistsFromGenre, OneArtistFromGenre, - OneAlbumFromArtistAndGenre + OneAlbumFromArtistAndGenre, + FilesBrowser } property int currentView: ViewManager.ViewsType.NoViews property string currentAlbumTitle property string currentAlbumAuthor property string currentArtistName property string currentGenreName property var currentStackView property int targetView: ViewManager.ViewsType.NoViews property string targetAlbumTitle property string targetAlbumAuthor property string targetArtistName property string targetGenreName property url targetImageUrl property int targetDatabaseId signal switchAllAlbumsView() signal switchOneAlbumView(var currentStackView, string mainTitle, url imageUrl, string secondaryTitle, int databaseId) signal switchAllArtistsView() signal switchOneArtistView(var currentStackView, string mainTitle, url imageUrl, string secondaryTitle, int databaseId) signal switchAllTracksView() signal switchAllGenresView() signal switchAllArtistsFromGenreView(var currentStackView, string genreName) signal switchOneArtistFromGenreView(var currentStackView, string mainTitle, url imageUrl, string secondaryTitle, int databaseId, string genreName) + signal switchFilesBrowserView() signal switchOffAllViews() function closeAllViews() { currentView = ViewManager.ViewsType.NoViews targetView = ViewManager.ViewsType.NoViews switchOffAllViews() } function openAllAlbums() { targetView = ViewManager.ViewsType.AllAlbums if (currentView != targetView) { switchAllAlbumsView() } } function openOneAlbum(stackView, albumTitle, albumAuthor, albumCover, albumDatabaseId) { targetAlbumTitle = albumTitle targetAlbumAuthor = albumAuthor targetDatabaseId = albumDatabaseId targetImageUrl = albumCover currentStackView = stackView if (currentView == ViewManager.ViewsType.AllAlbums) { targetView = ViewManager.ViewsType.OneAlbum switchOneAlbumView(currentStackView, targetAlbumTitle, targetImageUrl, targetAlbumAuthor, targetDatabaseId) } else if (currentView == ViewManager.ViewsType.OneArtist) { targetView = ViewManager.ViewsType.OneAlbumFromArtist switchOneAlbumView(currentStackView, targetAlbumTitle, targetImageUrl, targetAlbumAuthor, targetDatabaseId) } else if (currentView == ViewManager.ViewsType.OneArtistFromGenre) { targetView = ViewManager.ViewsType.OneAlbumFromArtistAndGenre switchOneAlbumView(currentStackView, targetAlbumTitle, targetImageUrl, targetAlbumAuthor, targetDatabaseId) } else { switchAllAlbumsView() } } function openAllArtists() { targetView = ViewManager.ViewsType.AllArtists if (currentView != targetView) { switchAllArtistsView() } } function openOneArtist(stackView, artistName, artistImageUrl, artistDatabaseId) { targetArtistName = artistName targetDatabaseId = artistDatabaseId targetImageUrl = artistImageUrl currentStackView = stackView if (currentView == ViewManager.ViewsType.AllArtistsFromGenre) { targetView = ViewManager.ViewsType.OneArtistFromGenre } else { targetView = ViewManager.ViewsType.OneArtist } if (currentView == ViewManager.ViewsType.AllArtists && targetView == ViewManager.ViewsType.OneArtist) { switchOneArtistView(currentStackView, targetArtistName, targetImageUrl, '', targetDatabaseId) } else if (currentView == ViewManager.ViewsType.OneArtist && currentArtistName != targetArtistName && targetView == ViewManager.ViewsType.OneArtist) { currentStackView.pop() switchOneArtistView(currentStackView, targetArtistName, targetImageUrl, '', targetDatabaseId) } else if (currentView == ViewManager.ViewsType.OneAlbumFromArtist && currentArtistName != targetArtistName && targetView == ViewManager.ViewsType.OneArtist) { currentStackView.pop() currentStackView.pop() switchOneArtistView(currentStackView, targetArtistName, targetImageUrl, '', targetDatabaseId) } else if (currentView == ViewManager.ViewsType.AllArtistsFromGenre && targetView == ViewManager.ViewsType.OneArtistFromGenre) { switchOneArtistFromGenreView(currentStackView, targetArtistName, targetImageUrl, '', targetDatabaseId, targetGenreName) } else { switchAllArtistsView() } } function openAllTracks() { targetView = ViewManager.ViewsType.AllTracks if (currentView != targetView) { switchAllTracksView() } } function openAllGenres() { targetView = ViewManager.ViewsType.AllGenres if (currentView != targetView) { switchAllGenresView() } } function openAllArtistsFromGenre(stackView, genreName) { targetView = ViewManager.ViewsType.AllArtistsFromGenre targetGenreName = genreName currentStackView = stackView if (currentView == ViewManager.ViewsType.AllGenres) { switchAllArtistsFromGenreView(currentStackView, targetGenreName) } else { switchAllGenresView() } } + function openFilesBrowser() + { + targetView = ViewManager.ViewsType.FilesBrowser + if (currentView != targetView) { + switchFilesBrowserView() + } + } + function allAlbumsViewIsLoaded(stackView) { currentStackView = stackView currentView = ViewManager.ViewsType.AllAlbums if (targetView == ViewManager.ViewsType.OneAlbum) { switchOneAlbumView(currentStackView, targetAlbumTitle, targetImageUrl, targetArtistName, targetDatabaseId) } } function oneAlbumViewIsLoaded() { currentAlbumTitle = targetAlbumTitle currentAlbumAuthor = targetAlbumAuthor if (targetView == ViewManager.ViewsType.OneAlbum) { currentView = ViewManager.ViewsType.OneAlbum } else if (targetView == ViewManager.ViewsType.OneAlbumFromArtist) { currentView = ViewManager.ViewsType.OneAlbumFromArtist } else if (targetView == ViewManager.ViewsType.OneAlbumFromArtistAndGenre) { currentView = ViewManager.ViewsType.OneAlbumFromArtistAndGenre } } function allArtistsViewIsLoaded(stackView) { currentStackView = stackView currentView = ViewManager.ViewsType.AllArtists if (targetView == ViewManager.ViewsType.OneArtist) { switchOneArtistView(currentStackView, targetArtistName, targetImageUrl, '', targetDatabaseId) } } function oneArtistViewIsLoaded() { currentArtistName = targetArtistName if (targetView == ViewManager.ViewsType.OneArtist) { currentView = ViewManager.ViewsType.OneArtist } else { currentGenreName = targetGenreName currentView = ViewManager.ViewsType.OneArtistFromGenre } } function allTracksViewIsLoaded(allTracksView) { currentView = ViewManager.ViewsType.AllTracks } function allGenresViewIsLoaded(stackView) { currentStackView = stackView currentView = ViewManager.ViewsType.AllGenres } function allArtistsFromGenreViewIsLoaded() { currentGenreName = targetGenreName currentView = ViewManager.ViewsType.AllArtistsFromGenre } + function filesBrowserViewIsLoaded() + { + currentView = ViewManager.ViewsType.FilesBrowser + } + function goBack() { currentStackView.pop() if (currentView == ViewManager.ViewsType.OneAlbum) { currentView = ViewManager.ViewsType.AllAlbums } else if (currentView == ViewManager.ViewsType.OneArtist) { currentView = ViewManager.ViewsType.AllArtists } else if (currentView == ViewManager.ViewsType.OneAlbumFromArtist) { currentView = ViewManager.ViewsType.OneArtist } else if (currentView == ViewManager.ViewsType.AllArtistsFromGenre) { currentView = ViewManager.ViewsType.AllGenres } else if (currentView == ViewManager.ViewsType.OneArtistFromGenre) { currentView = ViewManager.ViewsType.AllArtistsFromGenre } else if (currentView == ViewManager.ViewsType.OneAlbumFromArtistAndGenre) { currentView = ViewManager.ViewsType.OneArtistFromGenre } } } diff --git a/src/qml/ViewSelector.qml b/src/qml/ViewSelector.qml index 25afe0e0..c5f64bbf 100644 --- a/src/qml/ViewSelector.qml +++ b/src/qml/ViewSelector.qml @@ -1,188 +1,189 @@ /* * 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 2.2 import QtQml.Models 2.2 import QtGraphicalEffects 1.0 FocusScope { id: rootFocusScope property alias currentIndex: viewModeView.currentIndex signal switchView(int index) Rectangle { anchors.fill: parent color: myPalette.base border { color: (rootFocusScope.activeFocus ? myPalette.highlight : "transparent") width: 1 } ScrollView { focus: true anchors.fill: parent clip: true ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ListView { id: viewModeView focus: true z: 2 anchors.topMargin: elisaTheme.layoutHorizontalMargin * 2 model: DelegateModel { id: pageDelegateModel model: ListModel { id: pageModel } delegate: MouseArea { id: item height: elisaTheme.viewSelectorDelegateHeight * 1.4 width: viewModeView.width hoverEnabled: true acceptedButtons: Qt.LeftButton Image { id: viewIcon z: 2 anchors { verticalCenter: parent.verticalCenter leftMargin: elisaTheme.layoutHorizontalMargin left: parent.left } height: elisaTheme.viewSelectorDelegateHeight width: elisaTheme.viewSelectorDelegateHeight sourceSize { width: elisaTheme.viewSelectorDelegateHeight height: elisaTheme.viewSelectorDelegateHeight } source: iconName visible: false } ColorOverlay { source: viewIcon z: 2 anchors { verticalCenter: parent.verticalCenter leftMargin: elisaTheme.layoutHorizontalMargin left: parent.left } height: elisaTheme.viewSelectorDelegateHeight width: elisaTheme.viewSelectorDelegateHeight color: (index === viewModeView.currentIndex || item.containsMouse ? myPalette.highlight : "transparent") Behavior on color { ColorAnimation { duration: 300 } } } LabelWithToolTip { id: nameLabel z: 2 anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: elisaTheme.layoutHorizontalMargin anchors.left: viewIcon.right anchors.right: parent.right anchors.rightMargin: elisaTheme.layoutHorizontalMargin verticalAlignment: "AlignVCenter" font.pointSize: elisaTheme.defaultFontPointSize * 1.4 text: model.name elide: Text.ElideRight color: (viewModeView.currentIndex === index || item.containsMouse ? myPalette.highlight : myPalette.text) Behavior on color { ColorAnimation { duration: 300 } } } onClicked: { viewModeView.currentIndex = index rootFocusScope.focus = true switchView(index) } } Component.onCompleted: { pageModel.insert(0, {"name": i18nc("Title of the view of the playlist", "Now Playing"), "iconName": elisaTheme.playlistIcon}) pageModel.insert(1, {"name": i18nc("Title of the view of all albums", "Albums"), "iconName": elisaTheme.albumIcon}) pageModel.insert(2, {"name": i18nc("Title of the view of all artists", "Artists"), "iconName": elisaTheme.artistIcon}) pageModel.insert(3, {"name": i18nc("Title of the view of all tracks", "Tracks"), "iconName": elisaTheme.tracksIcon}) pageModel.insert(4, {"name": i18nc("Title of the view of all genres", "Genres"), "iconName": elisaTheme.genresIcon}) + pageModel.insert(5, {"name": i18nc("Title of the file browser view", "Files"), "iconName": elisaTheme.folderIcon}) viewModeView.currentIndex = 1 switchView(1) } } footer: MouseArea { width: viewModeView.width height: viewModeView.height - y acceptedButtons: Qt.LeftButton onClicked: { rootFocusScope.focus = true } } } } Behavior on border.color { ColorAnimation { duration: 300 } } } } diff --git a/src/resources.qrc b/src/resources.qrc index 659a9831..f312a7c9 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,43 +1,45 @@ qml/MediaPlayerControl.qml qml/RatingStar.qml qml/MediaPlayListView.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/HeaderBar.qml qml/ContextView.qml qml/ContentView.qml qml/DraggableItem.qml qml/PassiveNotification.qml qml/NavigationActionBar.qml qml/PlayListEntry.qml qml/MediaBrowser.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/TopNotification.qml qml/TrackImportNotification.qml qml/TopNotificationItem.qml qml/ViewSelector.qml qml/MediaTrackDelegate.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml qml/ListBrowserView.qml + qml/FileBrowserDelegate.qml + qml/FileBrowserView.qml qtquickcontrols2.conf background.png qml/ViewManager.qml windows/WindowsTheme.qml windows/PlatformIntegration.qml windows/LabelWithToolTip.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml diff --git a/src/trackdatahelper.cpp b/src/trackdatahelper.cpp index 8d79f018..f11c1e6f 100644 --- a/src/trackdatahelper.cpp +++ b/src/trackdatahelper.cpp @@ -1,169 +1,183 @@ /* * Copyright 2018 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "trackdatahelper.h" TrackDataHelper::TrackDataHelper(QObject *parent) : QObject(parent) { } TrackDataHelper::~TrackDataHelper() = default; const MusicAudioTrack &TrackDataHelper::trackData() const { return *this; } void TrackDataHelper::setTrackData(const MusicAudioTrack &track) { MusicAudioTrack::operator=(track); Q_EMIT trackDataChanged(); } +QString TrackDataHelper::title() const +{ + if (MusicAudioTrack::title().isEmpty()) { + return fileName(); + } else { + return MusicAudioTrack::title(); + } +} + QString TrackDataHelper::trackNumber() const { return QString::number(MusicAudioTrack::trackNumber()); } QString TrackDataHelper::discNumber() const { return QString::number(MusicAudioTrack::discNumber()); } QString TrackDataHelper::channels() const { return QString::number(MusicAudioTrack::channels()); } QString TrackDataHelper::bitRate() const { return QString::number(MusicAudioTrack::bitRate()/1000); } QString TrackDataHelper::sampleRate() const { return QString::number(MusicAudioTrack::sampleRate()); } QString TrackDataHelper::year() const { return QString::number(MusicAudioTrack::year()); } QString TrackDataHelper::duration() const { QString result; const QTime &trackDuration = MusicAudioTrack::duration(); if (trackDuration.hour() == 0) { result = trackDuration.toString(QStringLiteral("mm:ss")); } else { result = trackDuration.toString(QStringLiteral("h:mm:ss")); } return result; } QString TrackDataHelper::resourceURI() const { return MusicAudioTrack::resourceURI().toString(); } +QString TrackDataHelper::fileName() const +{ + return MusicAudioTrack::resourceURI().fileName(); +} + bool TrackDataHelper::hasValidTrackNumber() const { return MusicAudioTrack::trackNumber() > -1; } bool TrackDataHelper::hasValidDiscNumber() const { return MusicAudioTrack::discNumber() > -1; } bool TrackDataHelper::hasValidChannels() const { return MusicAudioTrack::channels() > -1; } bool TrackDataHelper::hasValidBitRate() const { return MusicAudioTrack::bitRate() > -1; } bool TrackDataHelper::hasValidSampleRate() const { return MusicAudioTrack::sampleRate() > -1; } bool TrackDataHelper::hasValidYear() const { return MusicAudioTrack::year() != 0; } bool TrackDataHelper::hasValidRating() const { return MusicAudioTrack::rating() > -1; } bool TrackDataHelper::hasValidTitle() const { return !MusicAudioTrack::title().isEmpty(); } bool TrackDataHelper::hasValidArtist() const { return !MusicAudioTrack::artist().isEmpty(); } bool TrackDataHelper::hasValidAlbumArtist() const { return !MusicAudioTrack::albumArtist().isEmpty(); } bool TrackDataHelper::hasValidAlbumName() const { return !MusicAudioTrack::albumName().isEmpty(); } bool TrackDataHelper::hasValidGenre() const { return !MusicAudioTrack::genre().isEmpty(); } bool TrackDataHelper::hasValidComposer() const { return !MusicAudioTrack::composer().isEmpty(); } bool TrackDataHelper::hasValidLyricist() const { return !MusicAudioTrack::lyricist().isEmpty(); } bool TrackDataHelper::hasValidComment() const { return !MusicAudioTrack::comment().isEmpty(); } bool TrackDataHelper::hasValidAlbumCover() const { return !MusicAudioTrack::albumCover().isEmpty(); } #include "moc_trackdatahelper.cpp" diff --git a/src/trackdatahelper.h b/src/trackdatahelper.h index 24d497d3..b58bb4fe 100644 --- a/src/trackdatahelper.h +++ b/src/trackdatahelper.h @@ -1,181 +1,189 @@ /* * Copyright 2018 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TRACKDATAHELPER_H #define TRACKDATAHELPER_H #include "elisaLib_export.h" #include #include "musicaudiotrack.h" class ELISALIB_EXPORT TrackDataHelper : public QObject, public MusicAudioTrack { Q_OBJECT Q_PROPERTY(MusicAudioTrack trackData READ trackData WRITE setTrackData NOTIFY trackDataChanged) Q_PROPERTY(QString title READ title NOTIFY trackDataChanged) Q_PROPERTY(QString artist READ artist NOTIFY trackDataChanged) Q_PROPERTY(QString albumName READ albumName NOTIFY trackDataChanged) Q_PROPERTY(QString albumArtist READ albumArtist NOTIFY trackDataChanged) Q_PROPERTY(QString genre READ genre NOTIFY trackDataChanged) Q_PROPERTY(QString composer READ composer NOTIFY trackDataChanged) Q_PROPERTY(QString lyricist READ lyricist NOTIFY trackDataChanged) Q_PROPERTY(QString comment READ comment NOTIFY trackDataChanged) Q_PROPERTY(QString year READ year NOTIFY trackDataChanged) Q_PROPERTY(QString trackNumber READ trackNumber NOTIFY trackDataChanged) Q_PROPERTY(QString discNumber READ discNumber NOTIFY trackDataChanged) Q_PROPERTY(QString channels READ channels NOTIFY trackDataChanged) Q_PROPERTY(QString bitRate READ bitRate NOTIFY trackDataChanged) Q_PROPERTY(QString sampleRate READ sampleRate NOTIFY trackDataChanged) Q_PROPERTY(QString resourceURI READ resourceURI NOTIFY trackDataChanged) + Q_PROPERTY(QString fileName + READ fileName + NOTIFY trackDataChanged) + Q_PROPERTY(QString duration READ duration NOTIFY trackDataChanged) Q_PROPERTY(int rating READ rating NOTIFY trackDataChanged) Q_PROPERTY(QUrl albumCover READ albumCover NOTIFY trackDataChanged) Q_PROPERTY(qulonglong databaseId READ databaseId NOTIFY trackDataChanged) public: explicit TrackDataHelper(QObject *parent = nullptr); ~TrackDataHelper() override; + QString title() const; + QString trackNumber() const; QString discNumber() const; QString channels() const; QString bitRate() const; QString sampleRate() const; QString year() const; QString duration() const; QString resourceURI() const; + QString fileName() const; + const MusicAudioTrack& trackData() const; void setTrackData(const MusicAudioTrack &track); Q_SIGNALS: void trackDataChanged(); public: Q_INVOKABLE bool hasValidTitle() const; Q_INVOKABLE bool hasValidArtist() const; Q_INVOKABLE bool hasValidAlbumName() const; Q_INVOKABLE bool hasValidAlbumArtist() const; Q_INVOKABLE bool hasValidGenre() const; Q_INVOKABLE bool hasValidComposer() const; Q_INVOKABLE bool hasValidLyricist() const; Q_INVOKABLE bool hasValidComment() const; Q_INVOKABLE bool hasValidTrackNumber() const; Q_INVOKABLE bool hasValidDiscNumber() const; Q_INVOKABLE bool hasValidChannels() const; Q_INVOKABLE bool hasValidRating() const; Q_INVOKABLE bool hasValidBitRate() const; Q_INVOKABLE bool hasValidSampleRate() const; Q_INVOKABLE bool hasValidYear() const; Q_INVOKABLE bool hasValidAlbumCover() const; }; #endif // TRACKDATAHELPER_H diff --git a/src/windows/WindowsTheme.qml b/src/windows/WindowsTheme.qml index 6bc7f571..a6732dee 100644 --- a/src/windows/WindowsTheme.qml +++ b/src/windows/WindowsTheme.qml @@ -1,85 +1,86 @@ /* * 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. */ import QtQuick 2.7 import QtQuick.Controls 1.4 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 artistIcon: 'image://icon/view-media-artist' property string albumIcon: 'image://icon/view-media-album-cover' property string playlistIcon: 'image://icon/view-media-playlist' property string tracksIcon: 'image://icon/view-media-track' property string clearIcon: 'image://icon/edit-clear' property string skipBackwardIcon: 'image://icon/media-skip-backward' property string pauseIcon: 'image://icon/media-playback-pause' property string playIcon: 'image://icon/media-playback-start' property string skipForwardIcon: 'image://icon/media-skip-forward' property string playerVolumeMutedIcon: 'image://icon/player-volume-muted' property string playerVolumeIcon: 'image://icon/player-volume' property string ratingIcon: 'image://icon/rating' property string ratingUnratedIcon: 'image://icon/rating-unrated' property string errorIcon: 'image://icon/error' + property string folderIcon: 'image://icon/document-open-folder' property int layoutHorizontalMargin: 8 property int layoutVerticalMargin: 6 property int delegateHeight: 28 property int delegateWithHeaderHeight: 68 property int trackDelegateHeight: 45 property int coverImageSize: 180 property int smallImageSize: 32 property int trackMetadataWidth: 300 property int tooltipRadius: 3 property int shadowOffset: 2 property int delegateToolButtonSize: 34 property int smallDelegateToolButtonSize: 20 property int ratingStarSize: 15 property int mediaPlayerControlHeight: 48 property real mediaPlayerControlOpacity: 0.8 property int smallControlButtonHeight: 32 property int bigControlButtonHeight: 44 property int volumeSliderWidth: 140 property int dragDropPlaceholderHeight: 28 property int navigationBarHeight: 100 property int navigationBarFilterHeight: 44 property int gridDelegateHeight: 100 + layoutVerticalMargin + fontSize.height * 2 property int gridDelegateWidth: 100 property int viewSelectorDelegateHeight: 32 property int filterClearButtonMargin: 1 property alias defaultFontPointSize: fontSize.font.pointSize Label { id: fontSize } }