diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d8e77e54..2d12cac0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,496 +1,497 @@ include_directories(${elisa_BINARY_DIR}) set(elisaLib_SOURCES mediaplaylist.cpp progressindicator.cpp databaseinterface.cpp datatypes.cpp musiclistenersmanager.cpp managemediaplayercontrol.cpp manageheaderbar.cpp manageaudioplayer.cpp trackslistener.cpp elisaapplication.cpp modeldataloader.cpp elisautils.cpp abstractfile/abstractfilelistener.cpp abstractfile/abstractfilelisting.cpp filescanner.cpp viewmanager.cpp powermanagementinterface.cpp file/filelistener.cpp file/localfilelisting.cpp models/datamodel.cpp models/abstractmediaproxymodel.cpp models/gridviewproxymodel.cpp models/alltracksproxymodel.cpp models/singlealbumproxymodel.cpp models/trackmetadatamodel.cpp + models/editabletrackmetadatamodel.cpp models/trackcontextmetadatamodel.cpp models/viewsmodel.cpp localFileConfiguration/elisaconfigurationdialog.cpp ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "indexersManager.h" IDENTIFIER "orgKdeElisaIndexersManager" CATEGORY_NAME "org.kde.elisa.indexers.manager" DEFAULT_SEVERITY Info ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "databaseLogging.h" IDENTIFIER "orgKdeElisaDatabase" CATEGORY_NAME "org.kde.elisa.database" DEFAULT_SEVERITY Info ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "abstractfile/indexercommon.h" IDENTIFIER "orgKdeElisaIndexer" CATEGORY_NAME "org.kde.elisa.indexer" DEFAULT_SEVERITY Info ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "models/modelLogging.h" IDENTIFIER "orgKdeElisaModel" CATEGORY_NAME "org.kde.elisa.model" DEFAULT_SEVERITY Info ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "playListLogging.h" IDENTIFIER "orgKdeElisaPlayList" CATEGORY_NAME "org.kde.elisa.playlist" DEFAULT_SEVERITY Info ) if (LIBVLC_FOUND) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "vlcLogging.h" IDENTIFIER "orgKdeElisaPlayerVlc" CATEGORY_NAME "org.kde.elisa.player.vlc" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} audiowrapper_libvlc.cpp ) else() ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "qtMultimediaLogging.h" IDENTIFIER "orgKdeElisaPlayerQtMultimedia" CATEGORY_NAME "org.kde.elisa.player.qtMultimedia" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} audiowrapper_qtmultimedia.cpp ) endif() if (ANDROID) set(elisaLib_SOURCES ${elisaLib_SOURCES} android/androidmusiclistener.cpp ) endif() if (KF5KIO_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} models/filebrowsermodel.cpp models/filebrowserproxymodel.cpp ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "baloo/baloocommon.h" IDENTIFIER "orgKdeElisaBaloo" CATEGORY_NAME "org.kde.elisa.baloo" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} baloo/localbaloofilelisting.cpp baloo/baloolistener.cpp baloo/baloodetector.cpp ) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.main.xml baloo/main) 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) qt5_add_dbus_adaptor(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.BalooWatcherApplication.xml baloo/localbaloofilelisting.h LocalBalooFileListing) 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 LINK_PRIVATE Qt5::Core Qt5::Sql Qt5::Widgets Qt5::Concurrent Qt5::Qml KF5::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui) if (KF5FileMetaData_FOUND) target_link_libraries(elisaLib LINK_PRIVATE KF5::FileMetaData ) endif() if (KF5KIO_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets ) endif() 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() if (LIBVLC_FOUND) target_include_directories(elisaLib PRIVATE ${LIBVLC_INCLUDE_DIR} ) target_link_libraries(elisaLib LINK_PRIVATE ${LIBVLC_LIBRARY} ) endif() if (ANDROID) target_link_libraries(elisaLib LINK_PUBLIC Qt5::AndroidExtras ) 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 ) if (NOT APPLE AND NOT WIN32) install(TARGETS elisaLib LIBRARY DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa NAMELINK_SKIP RUNTIME DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa BUNDLE DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa ) else() install(TARGETS elisaLib ${INSTALL_TARGETS_DEFAULT_ARGS}) endif() set(elisaqmlplugin_SOURCES elisaqmlplugin.cpp elisautils.cpp ) if (KF5FileMetaData_FOUND) set(elisaqmlplugin_SOURCES ${elisaqmlplugin_SOURCES} embeddedcoverageimageprovider.cpp ) endif() add_library(elisaqmlplugin SHARED ${elisaqmlplugin_SOURCES}) target_link_libraries(elisaqmlplugin LINK_PRIVATE Qt5::Quick Qt5::Widgets KF5::ConfigCore KF5::ConfigGui elisaLib ) if (KF5FileMetaData_FOUND) target_link_libraries(elisaqmlplugin LINK_PRIVATE KF5::FileMetaData ) endif() set_target_properties(elisaqmlplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa ) if (NOT APPLE AND NOT WIN32) set_target_properties(elisaqmlplugin PROPERTIES INSTALL_RPATH "${KDE_INSTALL_FULL_LIBDIR}/elisa;${CMAKE_INSTALL_RPATH}" ) endif() 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) set(elisa_SOURCES main.cpp windows/WindowsTheme.qml windows/PlatformIntegration.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml android/AlbumsView.qml android/ArtistsView.qml android/TracksView.qml android/GenresView.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/NativeApplicationMenu.qml qml/BaseTheme.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/RatingStar.qml qml/DraggableItem.qml qml/TrackImportNotification.qml qml/HeaderBar.qml qml/NavigationActionBar.qml qml/MediaPlayerControl.qml qml/ContextView.qml qml/ContentView.qml qml/ViewSelector.qml qml/ViewSelectorDelegate.qml qml/DataGridView.qml qml/DataListView.qml qml/MediaPlayListView.qml qml/PlayListBasicView.qml qml/PlayListEntry.qml qml/SimplePlayListView.qml qml/BasicPlayListAlbumHeader.qml qml/MetaDataDelegate.qml qml/EditableMetaDataDelegate.qml qml/TracksDiscHeader.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml qml/ListBrowserView.qml qml/ListBrowserDelegate.qml qml/FileBrowserView.qml qml/ScrollHelper.qml qml/FlatButtonWithToolTip.qml qml/HeaderFooterToolbar.qml qml/ElisaConfigurationDialog.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::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui ) if (ANDROID) target_link_libraries(elisa LINK_PRIVATE Qt5::AndroidExtras Qt5::Svg Qt5::Sql Qt5::Concurrent KF5::Kirigami2 ) endif() if (KF5Crash_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Crash ) endif() if (KF5Declarative_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Declarative KF5::QuickAddons ) endif() if (NOT APPLE AND NOT WIN32) set_target_properties(elisa PROPERTIES INSTALL_RPATH "${KDE_INSTALL_FULL_LIBDIR}/elisa;${CMAKE_INSTALL_RPATH}" ) endif() install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS}) 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 elisaLib ) if (KF5FileMetaData_FOUND) target_link_libraries(elisaImport LINK_PRIVATE KF5::FileMetaData ) endif() set(QML_IMPORT_PATH ${CMAKE_BINARY_DIR}/bin CACHE INTERNAL "qml import path" FORCE) if (ANDROID) kirigami_package_breeze_icons(ICONS elisa) endif() diff --git a/src/elisaqmlplugin.cpp b/src/elisaqmlplugin.cpp index 4cf5e0e0..5f0c2025 100644 --- a/src/elisaqmlplugin.cpp +++ b/src/elisaqmlplugin.cpp @@ -1,184 +1,186 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #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 "elisautils.h" #include "elisaapplication.h" #include "progressindicator.h" #include "mediaplaylist.h" #include "managemediaplayercontrol.h" #include "manageheaderbar.h" #include "manageaudioplayer.h" #include "musiclistenersmanager.h" #include "trackslistener.h" #include "viewmanager.h" #include "databaseinterface.h" #include "datatypes.h" #include "models/datamodel.h" #include "models/trackmetadatamodel.h" #include "models/trackcontextmetadatamodel.h" +#include "models/editabletrackmetadatamodel.h" #include "models/viewsmodel.h" #include "models/gridviewproxymodel.h" #include "models/alltracksproxymodel.h" #include "models/singlealbumproxymodel.h" #include "localFileConfiguration/elisaconfigurationdialog.h" #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND #include "embeddedcoverageimageprovider.h" #endif #if defined KF5KIO_FOUND && KF5KIO_FOUND #include "models/filebrowsermodel.h" #include "models/filebrowserproxymodel.h" #endif #include "audiowrapper.h" #if defined Qt5DBus_FOUND && Qt5DBus_FOUND #include "mpris2/mpris2.h" #include "mpris2/mediaplayer2player.h" #endif #include #include #include #include ElisaQmlTestPlugin::ElisaQmlTestPlugin(QObject *aParent) : QQmlExtensionPlugin(aParent) { } void ElisaQmlTestPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { QQmlExtensionPlugin::initializeEngine(engine, uri); #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND engine->addImageProvider(QStringLiteral("cover"), new EmbeddedCoverageImageProvider); #endif } 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 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, "ViewManager"); qmlRegisterType(uri, 1, 0, "DataModel"); qmlRegisterType(uri, 1, 0, "TrackMetadataModel"); qmlRegisterType(uri, 1, 0, "TrackContextMetaDataModel"); + qmlRegisterType(uri, 1, 0, "EditableTrackMetadataModel"); qmlRegisterType(uri, 1, 0, "ViewsModel"); qmlRegisterType(uri, 1, 0, "GridViewProxyModel"); qmlRegisterType(uri, 1, 0, "AllTracksProxyModel"); qmlRegisterType(uri, 1, 0, "SingleAlbumProxyModel"); #if defined KF5KIO_FOUND && KF5KIO_FOUND qmlRegisterType(uri, 1, 0, "FileBrowserModel"); qmlRegisterType(uri, 1, 0, "FileBrowserProxyModel"); #endif qmlRegisterType(uri, 1, 0, "AudioWrapper"); qmlRegisterUncreatableType(uri, 1, 0, "DatabaseInterface", QStringLiteral("Only created in c++")); #if defined Qt5DBus_FOUND && Qt5DBus_FOUND qmlRegisterType(uri, 1, 0, "Mpris2"); qRegisterMetaType(); #endif qRegisterMetaType(); qRegisterMetaType>("QHash"); qRegisterMetaType>("QHash"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QHash"); qRegisterMetaType("DataTypes::ListTrackDataType"); qRegisterMetaType("DataTypes::ListRadioDataType"); qRegisterMetaType("DataTypes::ListAlbumDataType"); qRegisterMetaType("DataTypes::ListArtistDataType"); qRegisterMetaType("DataTypes::ListGenreDataType"); qRegisterMetaType("ModelDataLoader::ListTrackDataType"); qRegisterMetaType("ModelDataLoader::ListRadioDataType"); qRegisterMetaType("ModelDataLoader::ListAlbumDataType"); qRegisterMetaType("ModelDataLoader::ListArtistDataType"); qRegisterMetaType("ModelDataLoader::ListGenreDataType"); qRegisterMetaType("ModelDataLoader::AlbumDataType"); qRegisterMetaType("TracksListener::ListTrackDataType"); qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType>("QMap"); qRegisterMetaType("ElisaUtils::PlayListEnqueueMode"); qRegisterMetaType("ElisaUtils::PlayListEnqueueTriggerPlay"); qRegisterMetaType("ElisaUtils::PlayListEntryType"); qRegisterMetaType("ElisaUtils::EntryData"); qRegisterMetaType("ElisaUtils::EntryDataList"); qRegisterMetaType("ElisaUtils::FilterType"); qRegisterMetaType("DataTypes::TrackDataType"); qRegisterMetaType("DataTypes::AlbumDataType"); qRegisterMetaType("DataTypes::ArtistDataType"); qRegisterMetaType("DataTypes::GenreDataType"); qRegisterMetaType("DataTypes::ColumnsRoles"); qRegisterMetaType("ModelDataLoader::TrackDataType"); qRegisterMetaType("TracksListener::TrackDataType"); qRegisterMetaType("ViewManager::ViewsType"); qRegisterMetaType("ViewManager::SortOrder"); qmlRegisterUncreatableType(uri, 1, 0, "ElisaConfigurationDialog", QStringLiteral("only one and done in c++")); qmlRegisterUncreatableType(uri, 1, 0, "ElisaApplication", QStringLiteral("only one and done in c++")); qmlRegisterUncreatableMetaObject(ElisaUtils::staticMetaObject, uri, 1, 0, "ElisaUtils", QStringLiteral("Namespace ElisaUtils")); } diff --git a/src/models/editabletrackmetadatamodel.cpp b/src/models/editabletrackmetadatamodel.cpp new file mode 100644 index 00000000..f82bd721 --- /dev/null +++ b/src/models/editabletrackmetadatamodel.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "editabletrackmetadatamodel.h" + +#include + +EditableTrackMetadataModel::EditableTrackMetadataModel(QObject *parent) + : TrackMetadataModel(parent) +{ +} + +bool EditableTrackMetadataModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + auto result = TrackMetadataModel::setData(index, value, role); + + if (result) { + if (!mIsDirty) { + mIsDirty = true; + Q_EMIT isDirtyChanged(); + } + + validData(); + } + + return result; +} + +void EditableTrackMetadataModel::saveData() +{ + mIsDirty = false; + Q_EMIT isDirtyChanged(); + + QString imageUrl = dataFromType(DataTypes::ImageUrlRole).toString(); + if (!imageUrl.isEmpty() + && !imageUrl.startsWith(QStringLiteral("http://")) + && !imageUrl.startsWith(QStringLiteral("https://")) + && !imageUrl.startsWith(QStringLiteral("file://"))) { + auto newTrackData = allTrackData(); + newTrackData[DataTypes::ImageUrlRole] = QStringLiteral("file:/").append(imageUrl); + Q_EMIT saveRadioData(newTrackData); + } else { + Q_EMIT saveRadioData(allTrackData()); + } +} + +void EditableTrackMetadataModel::filterDataFromTrackData() +{ + TrackMetadataModel::filterDataFromTrackData(); + validData(); +} + +void EditableTrackMetadataModel::fillLyricsDataFromTrack() +{ + TrackMetadataModel::fillLyricsDataFromTrack(); + validData(); +} + +void EditableTrackMetadataModel::validData() +{ + bool newValidState = true; + + const auto &resourceData = dataFromType(TrackDataType::key_type::ResourceRole); + if (resourceData.canConvert()) { + const auto resourceUrl = resourceData.toUrl(); + + newValidState = !resourceUrl.scheme().isEmpty() && resourceUrl.isValid() && !resourceUrl.isRelative(); + } else { + newValidState = false; + } + + if (newValidState) { + const auto &titleData = dataFromType(TrackDataType::key_type::TitleRole); + + newValidState = newValidState && !titleData.toString().isEmpty(); + } + + if (mIsDataValid != newValidState) { + mIsDataValid = newValidState; + + Q_EMIT isDataValidChanged(); + } +} + + +#include "moc_editabletrackmetadatamodel.cpp" diff --git a/src/models/editabletrackmetadatamodel.h b/src/models/editabletrackmetadatamodel.h new file mode 100644 index 00000000..3c8277e0 --- /dev/null +++ b/src/models/editabletrackmetadatamodel.h @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef EDITABLETRACKMETADATAMODEL_H +#define EDITABLETRACKMETADATAMODEL_H + +#include "elisaLib_export.h" + +#include "trackmetadatamodel.h" + +class ELISALIB_EXPORT EditableTrackMetadataModel : public TrackMetadataModel +{ + Q_OBJECT + + Q_PROPERTY(bool isDataValid + READ isDataValid + NOTIFY isDataValidChanged) + + Q_PROPERTY(bool isDirty + READ isDirty + NOTIFY isDirtyChanged) + +public: + explicit EditableTrackMetadataModel(QObject *parent = nullptr); + + bool isDataValid() const + { + return mIsDataValid; + } + + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override; + + bool isDirty() const + { + return mIsDirty; + } + +Q_SIGNALS: + void isDataValidChanged(); + + void isDirtyChanged(); + +public Q_SLOTS: + + void saveData(); + +protected: + + void filterDataFromTrackData() override; + + void fillLyricsDataFromTrack() override; + +private: + + void validData(); + + bool mIsDataValid = false; + + bool mIsDirty = false; +}; + + + +#endif // EDITABLETRACKMETADATAMODEL_H diff --git a/src/models/trackmetadatamodel.cpp b/src/models/trackmetadatamodel.cpp index fa9a77cd..9f9bbc57 100644 --- a/src/models/trackmetadatamodel.cpp +++ b/src/models/trackmetadatamodel.cpp @@ -1,582 +1,571 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "trackmetadatamodel.h" #include "musiclistenersmanager.h" #include #include TrackMetadataModel::TrackMetadataModel(QObject *parent) : QAbstractListModel(parent) { connect(&mLyricsValueWatcher, &QFutureWatcher::finished, this, &TrackMetadataModel::lyricsValueIsReady); } TrackMetadataModel::~TrackMetadataModel() { if (mLyricsValueWatcher.isRunning() && !mLyricsValueWatcher.isFinished()) { mLyricsValueWatcher.waitForFinished(); } } int TrackMetadataModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return mTrackData.count(); } QVariant TrackMetadataModel::data(const QModelIndex &index, int role) const { auto result = QVariant{}; const auto currentKey = mTrackKeys[index.row()]; switch (role) { case Qt::DisplayRole: switch (currentKey) { case DataTypes::TrackNumberRole: { auto trackNumber = mTrackData.trackNumber(); if (trackNumber > 0) { result = trackNumber; } break; } case DataTypes::DiscNumberRole: { auto discNumber = mTrackData.discNumber(); if (discNumber > 0) { result = discNumber; } break; } case DataTypes::ChannelsRole: { auto channels = mTrackData.channels(); if (channels > 0) { result = channels; } break; } case DataTypes::BitRateRole: { auto bitRate = mTrackData.bitRate(); if (bitRate > 0) { result = bitRate; } break; } case DataTypes::SampleRateRole: { auto sampleRate = mTrackData.sampleRate(); if (sampleRate > 0) { result = sampleRate; } break; } default: result = mTrackData[currentKey]; break; } break; case ItemNameRole: switch (currentKey) { case DataTypes::TitleRole: result = i18nc("Track title for track metadata view", "Title"); break; case DataTypes::DurationRole: result = i18nc("Duration label for track metadata view", "Duration"); break; case DataTypes::ArtistRole: result = i18nc("Track artist for track metadata view", "Artist"); break; case DataTypes::AlbumRole: result = i18nc("Album name for track metadata view", "Album"); break; case DataTypes::AlbumArtistRole: result = i18nc("Album artist for track metadata view", "Album Artist"); break; case DataTypes::TrackNumberRole: result = i18nc("Track number for track metadata view", "Track Number"); break; case DataTypes::DiscNumberRole: result = i18nc("Disc number for track metadata view", "Disc Number"); break; case DataTypes::RatingRole: result = i18nc("Rating label for information panel", "Rating"); break; case DataTypes::GenreRole: result = i18nc("Genre label for track metadata view", "Genre"); break; case DataTypes::LyricistRole: result = i18nc("Lyricist label for track metadata view", "Lyricist"); break; case DataTypes::ComposerRole: result = i18nc("Composer name for track metadata view", "Composer"); break; case DataTypes::CommentRole: result = i18nc("Comment label for track metadata view", "Comment"); break; case DataTypes::YearRole: result = i18nc("Year label for track metadata view", "Year"); break; case DataTypes::ChannelsRole: result = i18nc("Channels label for track metadata view", "Channels"); break; case DataTypes::BitRateRole: result = i18nc("Bit rate label for track metadata view", "Bit Rate"); break; case DataTypes::SampleRateRole: result = i18nc("Sample Rate label for track metadata view", "Sample Rate"); break; case DataTypes::LastPlayDate: result = i18nc("Last play date label for track metadata view", "Last played"); break; case DataTypes::PlayCounter: result = i18nc("Play counter label for track metadata view", "Play count"); break; case DataTypes::LyricsRole: result = i18nc("Lyrics label for track metadata view", "Lyrics"); break; case DataTypes::ResourceRole: result = i18nc("Radio HTTP address for radio metadata view", "Stream Http Address"); break; case DataTypes::ImageUrlRole: result = i18nc("Image address for radio metadata view", "Image Address"); break; case DataTypes::SecondaryTextRole: case DataTypes::ShadowForImageRole: case DataTypes::ChildModelRole: case DataTypes::StringDurationRole: case DataTypes::IsValidAlbumArtistRole: case DataTypes::AllArtistsRole: case DataTypes::HighestTrackRating: case DataTypes::IdRole: case DataTypes::ParentIdRole: case DataTypes::DatabaseIdRole: case DataTypes::IsSingleDiscAlbumRole: case DataTypes::ContainerDataRole: case DataTypes::IsPartialDataRole: case DataTypes::AlbumIdRole: case DataTypes::HasEmbeddedCover: case DataTypes::FileModificationTime: case DataTypes::FirstPlayDate: case DataTypes::PlayFrequency: case DataTypes::ElementTypeRole: case DataTypes::FullDataRole: break; } break; case ItemTypeRole: switch (currentKey) { case DataTypes::TitleRole: result = TextEntry; break; case DataTypes::ResourceRole: case DataTypes::ImageUrlRole: result = UrlEntry; break; case DataTypes::ArtistRole: result = TextEntry; break; case DataTypes::AlbumRole: result = TextEntry; break; case DataTypes::AlbumArtistRole: result = TextEntry; break; case DataTypes::TrackNumberRole: result = IntegerEntry; break; case DataTypes::DiscNumberRole: result = IntegerEntry; break; case DataTypes::RatingRole: result = RatingEntry; break; case DataTypes::GenreRole: result = TextEntry; break; case DataTypes::LyricistRole: result = TextEntry; break; case DataTypes::ComposerRole: result = TextEntry; break; case DataTypes::CommentRole: result = TextEntry; break; case DataTypes::YearRole: result = IntegerEntry; break; case DataTypes::LastPlayDate: result = DateEntry; break; case DataTypes::PlayCounter: result = IntegerEntry; break; case DataTypes::LyricsRole: result = LongTextEntry; break; case DataTypes::DurationRole: case DataTypes::SampleRateRole: case DataTypes::BitRateRole: case DataTypes::ChannelsRole: case DataTypes::SecondaryTextRole: case DataTypes::ShadowForImageRole: case DataTypes::ChildModelRole: case DataTypes::StringDurationRole: case DataTypes::IsValidAlbumArtistRole: case DataTypes::AllArtistsRole: case DataTypes::HighestTrackRating: case DataTypes::IdRole: case DataTypes::ParentIdRole: case DataTypes::DatabaseIdRole: case DataTypes::IsSingleDiscAlbumRole: case DataTypes::ContainerDataRole: case DataTypes::IsPartialDataRole: case DataTypes::AlbumIdRole: case DataTypes::HasEmbeddedCover: case DataTypes::FileModificationTime: case DataTypes::FirstPlayDate: case DataTypes::PlayFrequency: case DataTypes::ElementTypeRole: case DataTypes::FullDataRole: break; } break; } return result; } bool TrackMetadataModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (data(index, role) != value) { - const auto dataType = mTrackKeys[index.row()]; + auto dataType = mTrackKeys[index.row()]; mTrackData[dataType] = value; mFullData[dataType] = value; Q_EMIT dataChanged(index, index, QVector() << role); return true; } return false; } QHash TrackMetadataModel::roleNames() const { auto names = QAbstractListModel::roleNames(); names[ItemNameRole] = "name"; names[ItemTypeRole] = "type"; return names; } QString TrackMetadataModel::fileUrl() const { return mFileUrl; } QUrl TrackMetadataModel::coverUrl() const { if (mCoverImage.isEmpty()) { return QUrl(QStringLiteral("image://icon/media-optical-audio")); } else { return mCoverImage; } } MusicListenersManager *TrackMetadataModel::manager() const { return mManager; } QString TrackMetadataModel::lyrics() const { return mFullData[TrackDataType::key_type::LyricsRole].toString(); } qulonglong TrackMetadataModel::databaseId() const { return mDatabaseId; } void TrackMetadataModel::trackData(const TrackMetadataModel::TrackDataType &trackData) { if (!mFullData.isEmpty() && trackData.databaseId() != mFullData.databaseId()) { return; } const QList fieldsForTrack({DataTypes::TitleRole, DataTypes::ArtistRole, DataTypes::AlbumRole, DataTypes::AlbumArtistRole, DataTypes::TrackNumberRole, DataTypes::DiscNumberRole, DataTypes::RatingRole, DataTypes::GenreRole, DataTypes::LyricistRole, DataTypes::ComposerRole, DataTypes::CommentRole, DataTypes::YearRole, DataTypes::LastPlayDate, DataTypes::PlayCounter}); fillDataFromTrackData(trackData, fieldsForTrack); } void TrackMetadataModel::fillDataFromTrackData(const TrackMetadataModel::TrackDataType &trackData, const QList &fieldsForTrack) { beginResetModel(); mFullData = trackData; mTrackData.clear(); mTrackKeys.clear(); for (DataTypes::ColumnsRoles role : fieldsForTrack) { if (trackData.constFind(role) != trackData.constEnd()) { if (role == DataTypes::RatingRole) { if (trackData[role].toInt() == 0) { continue; } } mTrackKeys.push_back(role); mTrackData[role] = trackData[role]; } } filterDataFromTrackData(); endResetModel(); fetchLyrics(); mDatabaseId = trackData[DataTypes::DatabaseIdRole].toULongLong(); Q_EMIT databaseIdChanged(); mCoverImage = trackData[DataTypes::ImageUrlRole].toUrl(); Q_EMIT coverUrlChanged(); auto rawFileUrl = trackData[DataTypes::ResourceRole].toUrl(); if (rawFileUrl.isLocalFile()) { mFileUrl = rawFileUrl.toLocalFile(); } else { mFileUrl = rawFileUrl.toString(); } Q_EMIT fileUrlChanged(); } void TrackMetadataModel::filterDataFromTrackData() { } void TrackMetadataModel::removeMetaData(DataTypes::ColumnsRoles metaData) { auto itMetaData = std::find(mTrackKeys.begin(), mTrackKeys.end(), metaData); if (itMetaData == mTrackKeys.end()) { return; } mTrackKeys.erase(itMetaData); mTrackData.remove(metaData); } TrackMetadataModel::TrackDataType::mapped_type TrackMetadataModel::dataFromType(TrackDataType::key_type metaData) const { return mFullData[metaData]; } void TrackMetadataModel::fillLyricsDataFromTrack() { beginInsertRows({}, mTrackData.size(), mTrackData.size()); mTrackKeys.push_back(DataTypes::LyricsRole); mTrackData[DataTypes::LyricsRole] = mLyricsValueWatcher.result(); endInsertRows(); } +const TrackMetadataModel::TrackDataType &TrackMetadataModel::allTrackData() const +{ + return mTrackData; +} + void TrackMetadataModel::lyricsValueIsReady() { if (!mLyricsValueWatcher.result().isEmpty()) { fillLyricsDataFromTrack(); mFullData[DataTypes::LyricsRole] = mLyricsValueWatcher.result(); Q_EMIT lyricsChanged(); } } void TrackMetadataModel::initializeByIdAndUrl(ElisaUtils::PlayListEntryType type, qulonglong databaseId, const QUrl &url) { mFullData.clear(); mTrackData.clear(); mCoverImage.clear(); mFileUrl.clear(); Q_EMIT lyricsChanged(); Q_EMIT needDataByDatabaseIdAndUrl(type, databaseId, url); } void TrackMetadataModel::initialize(MusicListenersManager *newManager, DatabaseInterface *trackDatabase) { mManager = newManager; Q_EMIT managerChanged(); if (mManager) { mDataLoader.setDatabase(mManager->viewDatabase()); connect(this, &TrackMetadataModel::needDataByUrl, mManager->tracksListener(), &TracksListener::trackByFileNameInList); connect(mManager->tracksListener(), &TracksListener::trackHasChanged, this, &TrackMetadataModel::trackData); } else if (trackDatabase) { mDataLoader.setDatabase(trackDatabase); } if (mManager) { mManager->connectModel(&mDataLoader); } connect(this, &TrackMetadataModel::needDataByDatabaseIdAndUrl, &mDataLoader, &ModelDataLoader::loadDataByDatabaseIdAndUrl); connect(this, &TrackMetadataModel::needDataByUrl, &mDataLoader, &ModelDataLoader::loadDataByUrl); connect(this, &TrackMetadataModel::saveRadioData, &mDataLoader, &ModelDataLoader::saveRadioModified); connect(this, &TrackMetadataModel::deleteRadioData, &mDataLoader, &ModelDataLoader::removeRadio); connect(&mDataLoader, &ModelDataLoader::trackModified, this, &TrackMetadataModel::trackData); connect(&mDataLoader, &ModelDataLoader::allTrackData, this, &TrackMetadataModel::trackData); connect(&mDataLoader, &ModelDataLoader::allRadioData, this, &TrackMetadataModel::radioData); connect(&mDataLoader, &ModelDataLoader::radioAdded, this, &TrackMetadataModel::radioData); connect(&mDataLoader, &ModelDataLoader::radioModified, this, &TrackMetadataModel::radioData); } void TrackMetadataModel::fetchLyrics() { auto lyricicsValue = QtConcurrent::run(QThreadPool::globalInstance(), [=]() { auto trackData = mFileScanner.scanOneFile(mFullData[DataTypes::ResourceRole].toUrl()); if (!trackData.lyrics().isEmpty()) { return trackData.lyrics(); } return QString{}; }); mLyricsValueWatcher.setFuture(lyricicsValue); } void TrackMetadataModel::initializeForNewRadio() { mFullData.clear(); mTrackData.clear(); fillDataForNewRadio(); } void TrackMetadataModel::fillDataForNewRadio() { beginResetModel(); mTrackData.clear(); mTrackKeys.clear(); - for (auto role : { - DataTypes::TitleRole, - DataTypes::ResourceRole, - DataTypes::CommentRole, - DataTypes::ImageUrlRole, - DataTypes::DatabaseIdRole + auto allRoles = {DataTypes::TitleRole, DataTypes::ResourceRole, + DataTypes::CommentRole, DataTypes::ImageUrlRole, + DataTypes::DatabaseIdRole}; -}) { + for (auto role : allRoles) { mTrackKeys.push_back(role); if (role == DataTypes::DatabaseIdRole) { mTrackData[role] = -1; } else { mTrackData[role] = QString(); } } filterDataFromTrackData(); endResetModel(); } void TrackMetadataModel::initializeByUrl(ElisaUtils::PlayListEntryType type, const QUrl &url) { mFullData.clear(); mTrackData.clear(); mCoverImage.clear(); mFileUrl.clear(); Q_EMIT lyricsChanged(); Q_EMIT needDataByUrl(type, url); } void TrackMetadataModel::setManager(MusicListenersManager *newManager) { initialize(newManager, nullptr); } void TrackMetadataModel::setDatabase(DatabaseInterface *trackDatabase) { initialize(nullptr, trackDatabase); } -void TrackMetadataModel::saveData() -{ - QString imageUrl = mTrackData[DataTypes::ImageUrlRole].toString(); - if (!imageUrl.isEmpty() - && !imageUrl.startsWith(QStringLiteral("http://")) - && !imageUrl.startsWith(QStringLiteral("https://")) - && !imageUrl.startsWith(QStringLiteral("file://"))) { - mTrackData[DataTypes::ImageUrlRole] = QStringLiteral("file:/").append(imageUrl); - } - - Q_EMIT saveRadioData(mTrackData); -} - void TrackMetadataModel::deleteRadio() { if (mTrackData[DataTypes::DatabaseIdRole]>=0) { Q_EMIT deleteRadioData(mTrackData[DataTypes::DatabaseIdRole].toULongLong()); } } void TrackMetadataModel::radioData(const TrackDataType &radiosData) { if (!mFullData.isEmpty() && mFullData[DataTypes::DatabaseIdRole].toInt() != -1 && mFullData.databaseId() != radiosData.databaseId()) { return; } const QList fieldsForTrack({DataTypes::TitleRole, DataTypes::ResourceRole, DataTypes::CommentRole, DataTypes::ImageUrlRole, DataTypes::DatabaseIdRole}); fillDataFromTrackData(radiosData, fieldsForTrack); } #include "moc_trackmetadatamodel.cpp" diff --git a/src/models/trackmetadatamodel.h b/src/models/trackmetadatamodel.h index d04b243d..fa5a7540 100644 --- a/src/models/trackmetadatamodel.h +++ b/src/models/trackmetadatamodel.h @@ -1,192 +1,192 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef TRACKMETADATAMODEL_H #define TRACKMETADATAMODEL_H #include "elisaLib_export.h" #include "elisautils.h" #include "trackslistener.h" #include "datatypes.h" #include "modeldataloader.h" #include "filescanner.h" #include #include #include class MusicListenersManager; class ELISALIB_EXPORT TrackMetadataModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QUrl coverUrl READ coverUrl NOTIFY coverUrlChanged) Q_PROPERTY(QString fileUrl READ fileUrl NOTIFY fileUrlChanged) Q_PROPERTY(qulonglong databaseId READ databaseId NOTIFY databaseIdChanged) Q_PROPERTY(MusicListenersManager* manager READ manager WRITE setManager NOTIFY managerChanged) Q_PROPERTY(QString lyrics READ lyrics NOTIFY lyricsChanged) public: enum ColumnRoles { ItemNameRole = Qt::UserRole + 1, ItemTypeRole, }; enum ItemType { TextEntry, UrlEntry, IntegerEntry, RatingEntry, DateEntry, LongTextEntry, }; Q_ENUM(ItemType) using TrackDataType = DataTypes::TrackDataType; explicit TrackMetadataModel(QObject *parent = nullptr); ~TrackMetadataModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QHash roleNames() const override; QUrl coverUrl() const; QString fileUrl() const; MusicListenersManager* manager() const; QString lyrics() const; qulonglong databaseId() const; Q_SIGNALS: void needDataByDatabaseIdAndUrl(ElisaUtils::PlayListEntryType dataType, qulonglong databaseId, const QUrl &url); void needDataByUrl(ElisaUtils::PlayListEntryType dataType, const QUrl &fileName); void coverUrlChanged(); void fileUrlChanged(); void managerChanged(); void lyricsChanged(); void saveRadioData(const DataTypes::TrackDataType &trackDataType); void deleteRadioData(qulonglong radioId); void databaseIdChanged(); public Q_SLOTS: void trackData(const TrackMetadataModel::TrackDataType &trackData); void initializeByIdAndUrl(ElisaUtils::PlayListEntryType type, qulonglong databaseId, const QUrl &url); void initializeByUrl(ElisaUtils::PlayListEntryType type, const QUrl &url); void initializeForNewRadio(); void setManager(MusicListenersManager *newManager); void setDatabase(DatabaseInterface *trackDatabase); - void saveData(); - void deleteRadio(); void radioData(const TrackMetadataModel::TrackDataType &radiosData); protected: void fillDataFromTrackData(const TrackMetadataModel::TrackDataType &trackData, const QList &fieldsForTrack); void fillDataForNewRadio(); virtual void filterDataFromTrackData(); void removeMetaData(DataTypes::ColumnsRoles metaData); TrackDataType::mapped_type dataFromType(TrackDataType::key_type metaData) const; virtual void fillLyricsDataFromTrack(); + const TrackDataType& allTrackData() const; + private Q_SLOTS: void lyricsValueIsReady(); private: void initialize(MusicListenersManager *newManager, DatabaseInterface *trackDatabase); void fetchLyrics(); TrackDataType mFullData; TrackDataType mTrackData; QUrl mCoverImage; QString mFileUrl; qulonglong mDatabaseId = 0; QList mTrackKeys; ModelDataLoader mDataLoader; MusicListenersManager *mManager = nullptr; FileScanner mFileScanner; QFutureWatcher mLyricsValueWatcher; }; #endif // TRACKMETADATAMODEL_H diff --git a/src/qml/EditableMetaDataDelegate.qml b/src/qml/EditableMetaDataDelegate.qml index 29260ce6..6858dbc4 100644 --- a/src/qml/EditableMetaDataDelegate.qml +++ b/src/qml/EditableMetaDataDelegate.qml @@ -1,94 +1,80 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import org.kde.elisa 1.0 RowLayout { id: delegateRow spacing: 0 signal radioEdited() TextMetrics { id: metaDataLabelMetric text: 'Metadata Name' } Label { id: metaDataLabels text: { if (model.name !== undefined) { return i18nc("Label for a piece of metadata, e.g. 'Album Artist:'", "%1:", model.name) } return "" } font.weight: Font.Bold horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignCenter Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 } Loader { id: textDisplayLoader - active: model.type === TrackMetadataModel.TextEntry || model.type === TrackMetadataModel.UrlEntry || model.type === TrackMetadataModel.IntegerEntry - visible: model.type === TrackMetadataModel.TextEntry || model.type === TrackMetadataModel.UrlEntry || model.type === TrackMetadataModel.IntegerEntry + active: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.UrlEntry || model.type === EditableTrackMetadataModel.IntegerEntry + visible: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.UrlEntry || model.type === EditableTrackMetadataModel.IntegerEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: TextField { text: model.display horizontalAlignment: Text.AlignLeft anchors.fill: parent - validator: ((model.type === TrackMetadataModel.UrlEntry) ? urlValidator : allValidator) - placeholderText: ((model.type === TrackMetadataModel.UrlEntry) ? 'https://' : '') - color: acceptableInput ? myPalette.text : 'red' - onTextEdited: { if (model.display !== text) { model.display = text delegateRow.radioEdited() } } - - RegExpValidator { - id: allValidator - regExp: /.*/ - } - - RegExpValidator { - id: urlValidator - regExp: /http[s]?:\/\/.+/ - } } } } diff --git a/src/qml/MediaTrackMetadataView.qml b/src/qml/MediaTrackMetadataView.qml index 795e6da1..f84535d8 100644 --- a/src/qml/MediaTrackMetadataView.qml +++ b/src/qml/MediaTrackMetadataView.qml @@ -1,256 +1,270 @@ /* * Copyright 2017 Alexander Stippich * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 Window { id: trackMetadata property var modelType property url fileName property bool editableMetadata property bool isCreation: false property alias showImage: metadataImage.visible property alias showTrackFileName: fileNameRow.visible property alias showDeleteButton: deleteButtonBox.visible property alias showApplyButton: applyButton.visible property double widthIndex: 2.8 signal rejected() LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true title: isCreation ? i18nc("Window title for track metadata", "Create a Radio") : i18nc("Window title for track metadata", "View Details") - TrackMetadataModel { + EditableTrackMetadataModel { id: realModel manager: elisa.musicManager } modality: Qt.NonModal flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint color: myPalette.window minimumHeight: elisaTheme.coverImageSize * 1.8 minimumWidth: elisaTheme.coverImageSize * trackMetadata.widthIndex ColumnLayout { anchors.fill: parent anchors.margins: elisaTheme.layoutVerticalMargin spacing: elisaTheme.layoutVerticalMargin RowLayout { id: metadataView Layout.fillHeight: true Layout.fillWidth: true spacing: 0 Image { id: metadataImage source: realModel.coverUrl sourceSize.width: elisaTheme.coverImageSize sourceSize.height: elisaTheme.coverImageSize fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Layout.preferredHeight: elisaTheme.coverImageSize Layout.preferredWidth: elisaTheme.coverImageSize Layout.minimumHeight: elisaTheme.coverImageSize Layout.minimumWidth: elisaTheme.coverImageSize Layout.maximumHeight: elisaTheme.coverImageSize Layout.maximumWidth: elisaTheme.coverImageSize onStatusChanged: { if (metadataImage.status === Image.Error) { source = Qt.resolvedUrl(elisaTheme.defaultAlbumImage) } } } ListView { id: trackData Layout.fillWidth: true Layout.fillHeight: true Layout.leftMargin: 2 * elisaTheme.layoutHorizontalMargin focus: true ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds clip: true ScrollHelper { id: scrollHelper flickable: trackData anchors.fill: trackData } model: realModel Component { id: metaDataDelegate MetaDataDelegate { width: scrollBar.visible ? (!LayoutMirroring.enabled ? trackData.width - scrollBar.width : trackData.width) : trackData.width } } Component { id: editableMetaDataDelegate EditableMetaDataDelegate { width: scrollBar.visible ? (!LayoutMirroring.enabled ? trackData.width - scrollBar.width : trackData.width) : trackData.width - onRadioEdited: applyButton.enabled = true } } delegate: editableMetadata ? editableMetaDataDelegate: metaDataDelegate } } RowLayout { id: fileNameRow Layout.alignment: Qt.AlignLeft | Qt.AlignBottom Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.bottomMargin: elisaTheme.layoutVerticalMargin spacing: elisaTheme.layoutHorizontalMargin Image { Layout.preferredWidth: fileNameLabel.height Layout.preferredHeight: fileNameLabel.height sourceSize.width: fileNameLabel.height sourceSize.height: fileNameLabel.height source: elisaTheme.folderIcon } LabelWithToolTip { id: fileNameLabel Layout.fillWidth: true text: realModel.fileUrl elide: Text.ElideRight } } + Kirigami.InlineMessage { + id: formInvalidNotification + + text: i18n("Data are not valid. Radio cannot be created or modified.") + type: Kirigami.MessageType.Error + showCloseButton: false + visible: !realModel.isDataValid + + Layout.topMargin: 5 + Layout.fillWidth: true + Layout.rightMargin: elisaTheme.layoutHorizontalMargin + Layout.leftMargin: elisaTheme.layoutHorizontalMargin + } + RowLayout { spacing: elisaTheme.layoutVerticalMargin DialogButtonBox { id: deleteButtonBox Layout.minimumHeight: implicitHeight alignment: Qt.AlignLeft Button { id: deleteButton text: i18n("Delete") DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole onClicked: { elisa.musicManager.deleteElementById(modelType, realModel.databaseId) trackMetadata.close() } } } DialogButtonBox { id: buttons Layout.fillWidth: true Layout.minimumHeight: implicitHeight alignment: Qt.AlignRight Button { id: applyButton + enabled: realModel.isDataValid && realModel.isDirty + text: i18n("Apply") DialogButtonBox.buttonRole: DialogButtonBox.ApplyRole onClicked: { realModel.saveData() - enabled = false if (!deleteButtonBox.visible && editableMetadata) { deleteButtonBox.visible = true } } - enabled: false } Button { text: i18n("Close") DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole onClicked: trackMetadata.close() } } } } Connections { target: elisa onMusicManagerChanged: { if (isCreation) { realModel.initializeForNewRadio() } else { realModel.initializeByUrl(modelType, fileName) } } } Connections { target: realModel onCoverUrlChanged: { metadataImage.source = realModel.coverUrl } } Component.onCompleted: { if (elisa.musicManager) { if (isCreation) { realModel.initializeForNewRadio() } else { realModel.initializeByUrl(modelType, fileName) } } } } diff --git a/src/qml/MetaDataDelegate.qml b/src/qml/MetaDataDelegate.qml index 250e9907..dbc21a9d 100644 --- a/src/qml/MetaDataDelegate.qml +++ b/src/qml/MetaDataDelegate.qml @@ -1,132 +1,132 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import org.kde.elisa 1.0 RowLayout { id: delegateRow spacing: 0 - height: (model.type === TrackMetadataModel.LongTextEntry ? longTextDisplayLoader.height : (metaDataLabelMetric.boundingRect.height + elisaTheme.layoutVerticalMargin / 2)) + height: (model.type === EditableTrackMetadataModel.LongTextEntry ? longTextDisplayLoader.height : (metaDataLabelMetric.boundingRect.height + elisaTheme.layoutVerticalMargin / 2)) TextMetrics { id: metaDataLabelMetric text: 'Metadata Name' } Label { id: metaDataLabels text: i18nc("Label for a piece of metadata, e.g. 'Album Artist:'", "%1:", model.name) font.weight: Font.Bold horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignTop Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 } Loader { id: textDisplayLoader - active: model.type === TrackMetadataModel.TextEntry || model.type === TrackMetadataModel.IntegerEntry - visible: model.type === TrackMetadataModel.TextEntry || model.type === TrackMetadataModel.IntegerEntry + active: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.IntegerEntry + visible: model.type === EditableTrackMetadataModel.TextEntry || model.type === EditableTrackMetadataModel.IntegerEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: LabelWithToolTip { text: model.display horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent } } Loader { id: longTextDisplayLoader - active: model.type === TrackMetadataModel.LongTextEntry - visible: model.type === TrackMetadataModel.LongTextEntry + active: model.type === EditableTrackMetadataModel.LongTextEntry + visible: model.type === EditableTrackMetadataModel.LongTextEntry Layout.fillWidth: true Layout.maximumWidth: delegateRow.width - (0.8 * elisaTheme.coverImageSize + elisaTheme.layoutHorizontalMargin * 2) Layout.alignment: Qt.AlignTop sourceComponent: Label { text: model.display horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent wrapMode: Text.WordWrap } } Loader { - active: model.type === TrackMetadataModel.DateEntry - visible: model.type === TrackMetadataModel.DateEntry + active: model.type === EditableTrackMetadataModel.DateEntry + visible: model.type === EditableTrackMetadataModel.DateEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: LabelWithToolTip { text: rawDate.toLocaleDateString() horizontalAlignment: Text.AlignLeft elide: Text.ElideRight anchors.fill: parent property date rawDate: new Date(model.display) } } Loader { - active: model.type === TrackMetadataModel.RatingEntry - visible: model.type === TrackMetadataModel.RatingEntry + active: model.type === EditableTrackMetadataModel.RatingEntry + visible: model.type === EditableTrackMetadataModel.RatingEntry Layout.fillWidth: true Layout.alignment: Qt.AlignTop sourceComponent: RatingStar { starRating: model.display starSize: elisaTheme.ratingStarSize readOnly: true anchors { left: parent.left top: parent.top bottom: parent.bottom } } } }