[vlc-commits] [Git][videolan/vlc][master] 8 commits: qt: make ServicesDiscoveryModel use BaseModel

Steve Lhomme (@robUx4) gitlab at videolan.org
Fri Sep 15 13:32:40 UTC 2023



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
db55b347 by Pierre Lamot at 2023-09-15T13:04:19+00:00
qt: make ServicesDiscoveryModel use BaseModel

- - - - -
444abdd3 by Pierre Lamot at 2023-09-15T13:04:19+00:00
qml: fix wrong callback called to uninstall service

- - - - -
8ff15cee by Pierre Lamot at 2023-09-15T13:04:19+00:00
qml: remove unecessary SortFilterProxyModel in ServicesManage

- - - - -
7f4cf7b0 by Pierre Lamot at 2023-09-15T13:04:19+00:00
qt: make NetworkSourcesModel use BaseModel

- - - - -
54a31e54 by Pierre Lamot at 2023-09-15T13:04:19+00:00
qml: remove unecessary SortFilterProxyModel in ServicesSources

- - - - -
24d808fc by Pierre Lamot at 2023-09-15T13:04:19+00:00
qt: remove obsolete SortfilterProxyModel

- - - - -
97aeaa45 by Pierre Lamot at 2023-09-15T13:04:19+00:00
qt: fix class/struct mismatch

- - - - -
dd683943 by Pierre Lamot at 2023-09-15T13:04:19+00:00
qt: fix loading state in NetworkMediaModel

- - - - -


13 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/medialibrary/mlbasemodel.hpp
- modules/gui/qt/meson.build
- modules/gui/qt/network/networkmediamodel.cpp
- modules/gui/qt/network/networksourcesmodel.cpp
- modules/gui/qt/network/networksourcesmodel.hpp
- modules/gui/qt/network/qml/ServicesManage.qml
- modules/gui/qt/network/qml/ServicesSources.qml
- modules/gui/qt/network/servicesdiscoverymodel.cpp
- modules/gui/qt/network/servicesdiscoverymodel.hpp
- − modules/gui/qt/util/sortfilterproxymodel.cpp
- − modules/gui/qt/util/sortfilterproxymodel.hpp


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -314,8 +314,6 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/util/selectable_list_model.cpp \
 	gui/qt/util/selectable_list_model.hpp \
 	gui/qt/util/singleton.hpp \
-	gui/qt/util/sortfilterproxymodel.cpp \
-	gui/qt/util/sortfilterproxymodel.hpp \
 	gui/qt/util/soutchain.cpp gui/qt/util/soutchain.hpp \
 	gui/qt/util/validators.cpp gui/qt/util/validators.hpp \
 	gui/qt/util/varcommon_p.hpp \
@@ -509,7 +507,6 @@ nodist_libqt_plugin_la_SOURCES = \
 	gui/qt/util/flickable_scroll_handler.moc.cpp \
 	gui/qt/util/renderer_manager.moc.cpp \
 	gui/qt/util/selectable_list_model.moc.cpp \
-	gui/qt/util/sortfilterproxymodel.moc.cpp \
 	gui/qt/util/validators.moc.cpp \
 	gui/qt/util/varchoicemodel.moc.cpp \
 	gui/qt/util/variables.moc.cpp \


=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -38,7 +38,6 @@
 #include "util/i18n.hpp"
 #include "util/keyhelper.hpp"
 #include "style/systempalette.hpp"
-#include "util/sortfilterproxymodel.hpp"
 #include "util/navigation_history.hpp"
 #include "util/flickable_scroll_handler.hpp"
 #include "util/color_svg_image_provider.hpp"
@@ -306,7 +305,6 @@ void MainUI::registerQMLTypes()
         qmlRegisterType<NetworkMediaContextMenu>( uri, versionMajor, versionMinor, "NetworkMediaContextMenu" );
         qmlRegisterType<NetworkDeviceContextMenu>( uri, versionMajor, versionMinor, "NetworkDeviceContextMenu" );
         qmlRegisterType<PlaylistContextMenu>( uri, versionMajor, versionMinor, "PlaylistContextMenu" );
-        qmlRegisterType<SortFilterProxyModel>( uri, versionMajor, versionMinor, "SortFilterProxyModel" );
 
         qmlRegisterUncreatableType<NavigationAttached>( uri, versionMajor, versionMinor, "Navigation", "Navigation is only available via attached properties");
 


=====================================
modules/gui/qt/medialibrary/mlbasemodel.hpp
=====================================
@@ -42,7 +42,7 @@ class MediaLib;
 template<typename T>
 class ListCache;
 using MLListCache = ListCache<std::unique_ptr<MLItem>>;
-struct MLListCacheLoader;
+class MLListCacheLoader;
 
 /* Medialib data loader for the cache */
 class MLBaseModelPrivate;
@@ -149,7 +149,7 @@ private:
 };
 
 
-struct MLListCacheLoader: public QObject, public ListCacheLoader<std::unique_ptr<MLItem>>
+class MLListCacheLoader: public QObject, public ListCacheLoader<std::unique_ptr<MLItem>>
 {
     Q_OBJECT
 public:


=====================================
modules/gui/qt/meson.build
=====================================
@@ -131,7 +131,6 @@ moc_headers = files(
     'util/flickable_scroll_handler.hpp',
     'util/renderer_manager.hpp',
     'util/selectable_list_model.hpp',
-    'util/sortfilterproxymodel.hpp',
     'util/validators.hpp',
     'util/varchoicemodel.hpp',
     'util/variables.hpp',
@@ -438,8 +437,6 @@ some_sources = files(
     'util/selectable_list_model.cpp',
     'util/selectable_list_model.hpp',
     'util/singleton.hpp',
-    'util/sortfilterproxymodel.cpp',
-    'util/sortfilterproxymodel.hpp',
     'util/soutchain.cpp',
     'util/soutchain.hpp',
     'util/validators.cpp',


=====================================
modules/gui/qt/network/networkmediamodel.cpp
=====================================
@@ -224,7 +224,7 @@ public:
 public:
     bool loading() const override
     {
-        return m_parsing && BaseModelPrivateT<NetworkMediaItemPtr>::loading();
+        return m_parsing || BaseModelPrivateT<NetworkMediaItemPtr>::loading();
     }
 
     LocalListCacheLoader<NetworkMediaItemPtr>::ItemCompare getSortFunction() const


=====================================
modules/gui/qt/network/networksourcesmodel.cpp
=====================================
@@ -16,136 +16,229 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
-#include <QQmlFile>
 
 #include "networksourcesmodel.hpp"
 #include "networkmediamodel.hpp"
 
+#include "util/base_model_p.hpp"
+#include "util/locallistcacheloader.hpp"
+
 #include "playlist/media.hpp"
 #include "playlist/playlist_controller.hpp"
 
-NetworkSourcesModel::NetworkSourcesModel( QObject* parent )
-    : QAbstractListModel( parent )
-{
-}
+#include <QQmlFile>
 
-QVariant NetworkSourcesModel::data( const QModelIndex& index, int role ) const
+#include <memory>
+
+#include <vlc_media_source.h>
+#include <vlc_cxx_helpers.hpp>
+
+namespace {
+
+struct SourceItem
 {
-    if (!m_ctx)
-        return {};
-    auto idx = index.row();
-    if ( idx < 0 || (size_t)idx >= m_items.size() )
-        return {};
-    const auto& item = m_items[idx];
-    switch ( role )
+    SourceItem()
     {
-        case SOURCE_NAME:
-            return item.name;
-        case SOURCE_LONGNAME:
-            return item.longName;
-        case SOURCE_TYPE:
-            return idx == 0 ? TYPE_DUMMY : TYPE_SOURCE;
-        case SOURCE_ARTWORK:
-            return item.artworkUrl;
-        default:
-            return {};
+        isDummy = true;
     }
-}
 
-QHash<int, QByteArray> NetworkSourcesModel::roleNames() const
-{
-    return {
-        { SOURCE_NAME, "name" },
-        { SOURCE_LONGNAME, "long_name" },
-        { SOURCE_TYPE, "type" },
-        { SOURCE_ARTWORK, "artwork" }
-    };
-}
+    explicit SourceItem(const vlc_media_source_meta* meta)
+        : isDummy(false)
+        , name(qfu(meta->name))
+        , longName(qfu(meta->longname))
+    {
+        if ( name.startsWith( "podcast" ) )
+        {
+            artworkUrl = QUrl("qrc:///sd/podcast.svg");
+        }
+        else if ( name.startsWith("lua{") )
+        {
+            int i_head = name.indexOf( "sd='" ) + 4;
+            int i_tail = name.indexOf( '\'', i_head );
+            const QString iconName = QString( "qrc:///sd/%1.svg" ).arg( name.mid( i_head, i_tail - i_head ) );
+            artworkUrl = QFileInfo::exists( QQmlFile::urlToLocalFileOrQrc(iconName) ) ? QUrl(iconName)
+                                                                                          : QUrl("qrc:///sd/network.svg");
+        }
+    }
 
-int NetworkSourcesModel::rowCount(const QModelIndex& parent) const
-{
-    if ( parent.isValid() )
-        return 0;
-    return getCount();
-}
+    bool isDummy;
+    QString name;
+    QString longName;
+    QUrl artworkUrl;
+};
 
+using SourceItemPtr = std::shared_ptr<SourceItem>;
+using SourceItemLists = std::vector<SourceItemPtr>;
 
-void NetworkSourcesModel::setCtx(MainCtx* ctx)
-{
-    if (ctx) {
-        m_ctx = ctx;
-    }
-    if (m_ctx) {
-        initializeMediaTree();
-    }
-    emit ctxChanged();
 }
 
-int NetworkSourcesModel::getCount() const
+class NetworkSourcesModelPrivate
+    : public BaseModelPrivateT<SourceItemPtr>
+    , public LocalListCacheLoader<SourceItemPtr>::ModelSource
 {
-    assert( m_items.size() < INT32_MAX );
-    return static_cast<int>( m_items.size() );
-}
+    Q_DECLARE_PUBLIC(NetworkSourcesModel);
 
-QMap<QString, QVariant> NetworkSourcesModel::getDataAt(int idx)
-{
-    QMap<QString, QVariant> dataDict;
-    QHash<int,QByteArray> roles = roleNames();
-    for (auto role: roles.keys()) {
-        dataDict[roles[role]] = data(index(idx), role);
+public: //Ctor/Dtor
+    NetworkSourcesModelPrivate(NetworkSourcesModel* pub)
+        : BaseModelPrivateT<SourceItemPtr>(pub)
+    {}
+
+public:
+    const SourceItem* getItemForRow(int row) const
+    {
+        const SourceItemPtr* ref = item(row);
+        if (ref)
+            return ref->get();
+        return nullptr;
     }
-    return dataDict;
-}
 
-bool NetworkSourcesModel::initializeMediaTree()
-{
-    auto libvlc = vlc_object_instance(m_ctx->getIntf());
+public: // BaseModelPrivate implementation
 
-    if (!m_items.empty()) {
-        beginResetModel();
-        endResetModel();
-        emit countChanged();
+    LocalListCacheLoader<SourceItemPtr>::ItemCompare getSortFunction() const
+    {
+        if (m_sortOrder == Qt::DescendingOrder)
+            return [](const SourceItemPtr& a, const SourceItemPtr& b) {
+                //always put our dummy item first
+                if (a->isDummy)
+                    return true;
+                if (b->isDummy)
+                    return false;
+                return QString::compare(a->name, b->name, Qt::CaseInsensitive) > 0;
+            };
+        else
+            return [](const SourceItemPtr& a, const SourceItemPtr& b) {
+                //always put our dummy item first
+                if (a->isDummy)
+                    return true;
+                if (b->isDummy)
+                    return false;
+                return QString::compare(a->name, b->name, Qt::CaseInsensitive) < 0;
+            };
+    }
+
+    std::unique_ptr<ListCacheLoader<SourceItemPtr>> createLoader() const override
+    {
+        return std::make_unique<LocalListCacheLoader<SourceItemPtr>>(
+            this, m_searchPattern, getSortFunction());
     }
-    m_items = {Item{}}; // dummy item that UI uses to add entry for "add a service"
 
-    auto provider = vlc_media_source_provider_Get( libvlc );
+    bool initializeModel() override
+    {
+        Q_Q(NetworkSourcesModel);
+        if (m_qmlInitializing || !q->m_ctx)
+            return false;
 
-    using SourceMetaPtr = std::unique_ptr<vlc_media_source_meta_list_t,
-                                          decltype( &vlc_media_source_meta_list_Delete )>;
+        auto libvlc = vlc_object_instance(q->m_ctx->getIntf());
 
-    SourceMetaPtr providerList( vlc_media_source_provider_List( provider, static_cast<services_discovery_category_e>(m_sdSource) ),
-                                &vlc_media_source_meta_list_Delete );
-    if ( providerList == nullptr )
-        return false;
+        if (!m_items.empty())
+            m_items.clear();
 
-    auto nbProviders = vlc_media_source_meta_list_Count( providerList.get() );
+        auto provider = vlc_media_source_provider_Get( libvlc );
+        //add a dummy item
+        m_items.push_back(std::make_shared<SourceItem>());
 
-    beginResetModel();
-    for ( auto i = 0u; i < nbProviders; ++i )
-    {
-        auto meta = vlc_media_source_meta_list_Get( providerList.get(), i );
+        using SourceMetaPtr = std::unique_ptr<vlc_media_source_meta_list_t,
+                                              decltype( &vlc_media_source_meta_list_Delete )>;
 
-        Item item;
-        item.name = qfu(meta->name);
-        item.longName = qfu(meta->longname);
+        SourceMetaPtr providerList( vlc_media_source_provider_List( provider, static_cast<services_discovery_category_e>(m_sdSource) ),
+                                   &vlc_media_source_meta_list_Delete );
+        if ( providerList == nullptr )
+            return false;
 
-        if ( item.name.startsWith( "podcast" ) )
-        {
-            item.artworkUrl = QUrl("qrc:///sd/podcast.svg");
-        }
-        else if ( item.name.startsWith("lua{") )
+        auto nbProviders = vlc_media_source_meta_list_Count( providerList.get() );
+
+        for ( auto i = 0u; i < nbProviders; ++i )
         {
-            int i_head = item.name.indexOf( "sd='" ) + 4;
-            int i_tail = item.name.indexOf( '\'', i_head );
-            const QString iconName = QString( "qrc:///sd/%1.svg" ).arg( item.name.mid( i_head, i_tail - i_head ) );
-            item.artworkUrl = QFileInfo::exists( QQmlFile::urlToLocalFileOrQrc(iconName) ) ? QUrl(iconName)
-                                                            : QUrl("qrc:///sd/network.svg");
+            auto meta = vlc_media_source_meta_list_Get( providerList.get(), i );
+            SourceItemPtr item = std::make_shared<SourceItem>(meta);
+            m_items.push_back( item );
         }
 
-        m_items.push_back( std::move(item) );
+        return true;
+    }
+
+public: // LocalListCacheLoader::ModelSource implementation
+    size_t getModelRevision() const override
+    {
+        return 1;
+    }
+
+    std::vector<SourceItemPtr> getModelData(const QString& pattern) const override
+    {
+        if (pattern.isEmpty())
+            return m_items;
+
+        std::vector<SourceItemPtr> items;
+        std::copy_if(
+            m_items.cbegin(), m_items.cend(),
+            std::back_inserter(items),
+            [&pattern](const SourceItemPtr& item){
+                return item->name.contains(pattern, Qt::CaseInsensitive);
+            });
+        return items;
     }
-    endResetModel();
-    emit countChanged();
 
-    return m_items.empty() == false;
+public: // Data
+    services_discovery_category_e m_sdSource = services_discovery_category_e::SD_CAT_INTERNET;
+    std::vector<SourceItemPtr> m_items;
+};
+
+// ListCache specialisation
+
+template<>
+bool ListCache<SourceItemPtr>::compareItems(const SourceItemPtr& a, const SourceItemPtr& b)
+{
+    //just compare the pointers here
+    return a == b;
+}
+
+NetworkSourcesModel::NetworkSourcesModel( QObject* parent )
+    : BaseModel( new NetworkSourcesModelPrivate(this), parent )
+{
+}
+
+QVariant NetworkSourcesModel::data( const QModelIndex& index, int role ) const
+{
+    Q_D(const NetworkSourcesModel);
+    if (!m_ctx)
+        return {};
+
+    const SourceItem* item = d->getItemForRow(index.row());
+    if (!item)
+        return {};
+
+    switch ( role )
+    {
+    case SOURCE_NAME:
+        return item->name;
+    case SOURCE_LONGNAME:
+        return item->longName;
+    case SOURCE_TYPE:
+        return item->isDummy ? TYPE_DUMMY : TYPE_SOURCE;
+    case SOURCE_ARTWORK:
+        return item->artworkUrl;
+    default:
+        return {};
+    }
+}
+
+QHash<int, QByteArray> NetworkSourcesModel::roleNames() const
+{
+    return {
+        { SOURCE_NAME, "name" },
+        { SOURCE_LONGNAME, "long_name" },
+        { SOURCE_TYPE, "type" },
+        { SOURCE_ARTWORK, "artwork" }
+    };
+}
+
+void NetworkSourcesModel::setCtx(MainCtx* ctx)
+{
+    Q_D(NetworkSourcesModel);
+    if (ctx == m_ctx)
+        return;
+
+    m_ctx = ctx;
+    d->initializeModel();
+    emit ctxChanged();
 }


=====================================
modules/gui/qt/network/networksourcesmodel.hpp
=====================================
@@ -23,21 +23,16 @@
 #include "config.h"
 #endif
 
-#include <QAbstractListModel>
 
-#include <vlc_media_source.h>
-#include <vlc_cxx_helpers.hpp>
+#include "util/base_model.hpp"
+#include "maininterface/mainctx.hpp"
 
-#include <maininterface/mainctx.hpp>
-
-#include <memory>
-
-class NetworkSourcesModel : public QAbstractListModel
+class NetworkSourcesModelPrivate;
+class NetworkSourcesModel : public BaseModel
 {
     Q_OBJECT
 
     Q_PROPERTY(MainCtx* ctx READ getCtx WRITE setCtx NOTIFY ctxChanged FINAL)
-    Q_PROPERTY(int count READ getCount NOTIFY countChanged FINAL)
 
 public:
     enum Role {
@@ -58,34 +53,18 @@ public:
 
     QVariant data(const QModelIndex& index, int role) const override;
     QHash<int, QByteArray> roleNames() const override;
-    int rowCount(const QModelIndex& parent = {}) const override;
 
+public: // properties
     void setCtx(MainCtx* ctx);
-
     inline MainCtx* getCtx() { return m_ctx; }
 
-    int getCount() const;
-
-    Q_INVOKABLE QMap<QString, QVariant> getDataAt(int index);
-
 signals:
     void ctxChanged();
-    void countChanged();
 
 private:
-    struct Item
-    {
-        QString name;
-        QString longName;
-        QUrl artworkUrl;
-    };
-
-    bool initializeMediaTree();
-
-private:
-    std::vector<Item> m_items;
     MainCtx* m_ctx = nullptr;
-    services_discovery_category_e m_sdSource = services_discovery_category_e::SD_CAT_INTERNET;
+
+    Q_DECLARE_PRIVATE(NetworkSourcesModel);
 };
 
 #endif // MLNetworkSourcesModel_HPP


=====================================
modules/gui/qt/network/qml/ServicesManage.qml
=====================================
@@ -29,7 +29,11 @@ Widgets.KeyNavigableListView {
     // required by g_root to indicate view with 'grid' or 'list' mode
     readonly property bool isViewMultiView: false
 
-    model: discoveryFilterModel
+    model: ServicesDiscoveryModel {
+        id: discoveryModel
+        ctx: MainCtx
+    }
+
     topMargin: VLCStyle.margin_large
     leftMargin: VLCStyle.margin_large
     rightMargin: VLCStyle.margin_large
@@ -115,9 +119,9 @@ Widgets.KeyNavigableListView {
 
                         onClicked: {
                             if (model.state === ServicesDiscoveryModel.NOTINSTALLED)
-                                discoveryModel.installService(discoveryFilterModel.mapIndexToSource(index))
+                                discoveryModel.installService(index)
                             else if (model.state === ServicesDiscoveryModel.INSTALLED)
-                                discoveryModel.installService(discoveryFilterModel.mapIndexToSource(index))
+                                discoveryModel.removeService(index)
                         }
                     }
                 }
@@ -148,17 +152,4 @@ Widgets.KeyNavigableListView {
         color: servicesView.colorContext.fg.primary
         z: 1
     }
-
-    ServicesDiscoveryModel {
-        id: discoveryModel
-
-        ctx: MainCtx
-    }
-
-    SortFilterProxyModel {
-        id: discoveryFilterModel
-
-        sourceModel: discoveryModel
-        searchRole: "name"
-    }
 }


=====================================
modules/gui/qt/network/qml/ServicesSources.qml
=====================================
@@ -32,7 +32,7 @@ MainInterface.MainGridView {
     readonly property bool isViewMultiView: false
 
     selectionDelegateModel: selectionModel
-    model: sourcesFilterModel
+    model: sourcesModel
     topMargin: VLCStyle.margin_large
     cellWidth: VLCStyle.gridItem_network_width
     cellHeight: VLCStyle.gridCover_network_height + VLCStyle.margin_xsmall + VLCStyle.fontHeight_normal
@@ -96,7 +96,7 @@ MainInterface.MainGridView {
     }
 
     onActionAtIndex: {
-        const itemData = sourcesFilterModel.getDataAt(index);
+        const itemData = sourcesModel.getDataAt(index);
 
         if (itemData.type === NetworkSourcesModel.TYPE_DUMMY)
             History.push(["mc", "discover", "services", "services_manage"], Qt.TabFocusReason)
@@ -120,13 +120,6 @@ MainInterface.MainGridView {
     Util.SelectableDelegateModel {
         id: selectionModel
 
-        model: sourcesFilterModel
-    }
-
-    SortFilterProxyModel {
-        id: sourcesFilterModel
-
-        sourceModel: sourcesModel
-        searchRole: "name"
+        model: sourcesModel
     }
 }


=====================================
modules/gui/qt/network/servicesdiscoverymodel.cpp
=====================================
@@ -17,54 +17,224 @@
  *****************************************************************************/
 
 #include "servicesdiscoverymodel.hpp"
-#include <vlc_addons.h>
+
+#include "util/base_model_p.hpp"
+#include "util/locallistcacheloader.hpp"
 
 #include "medialibrary/mlhelper.hpp"
 
 #include "playlist/media.hpp"
 #include "playlist/playlist_controller.hpp"
 
+#include <memory>
 #include <QPixmap>
 
-ServicesDiscoveryModel::ServicesDiscoveryModel( QObject* parent )
-    : QAbstractListModel( parent )
+#include <vlc_media_source.h>
+#include <vlc_addons.h>
+#include <vlc_cxx_helpers.hpp>
+#include <vlc_addons.h>
+
+namespace {
+
+using AddonPtr = vlc_shared_data_ptr_type(addon_entry_t,
+                                          addon_entry_Hold, addon_entry_Release);
+
+class SDItem {
+public:
+    SDItem( AddonPtr addon )
+    {
+        name = qfu( addon->psz_name );
+        summery = qfu( addon->psz_summary ).trimmed();
+        description = qfu( addon->psz_description ).trimmed();
+        author = qfu( addon->psz_author );
+        sourceUrl = QUrl( addon->psz_source_uri );
+        entry = addon;
+
+        if ( addon->psz_image_data ) {
+            char *cDir = config_GetUserDir( VLC_CACHE_DIR );
+            if (likely(cDir != nullptr))
+            {
+                QDir dir( cDir );
+                free(cDir);
+                dir.mkdir("art");
+                dir.cd("art");
+                dir.mkdir("qt-addon-covers");
+                dir.cd("qt-addon-covers");
+
+                QString id = addons_uuid_to_psz( &addon->uuid );
+                QString filename = QString("addon_thumbnail_%1.png").arg(id);
+                QString absoluteFilePath =  dir.absoluteFilePath(filename);
+
+                if ( !QFileInfo::exists( absoluteFilePath )) {
+                    QPixmap pixmap;
+                    pixmap.loadFromData( QByteArray::fromBase64( QByteArray( addon->psz_image_data ) ),
+                                        0,
+                                        Qt::AutoColor
+                                        );
+                    pixmap.save(absoluteFilePath);
+                }
+                artworkUrl = QUrl::fromLocalFile( absoluteFilePath );
+            }
+        }
+        else if ( addon->e_flags & ADDON_BROKEN )
+            artworkUrl = QUrl( ":/addons/addon_broken.svg" );
+        else
+            artworkUrl = QUrl( ":/addons/addon_default.svg" );
+    }
+
+public:
+    QString name;
+    QString summery;
+    QString description;
+    QString author;
+    QUrl sourceUrl;
+    QUrl artworkUrl;
+    AddonPtr entry;
+};
+
+} //namespace
+
+using SDItemPtr = std::shared_ptr<SDItem> ;
+using SDItemList = std::vector<SDItemPtr> ;
+
+// ListCache specialisation
+
+template<>
+bool ListCache<SDItemPtr>::compareItems(const SDItemPtr& a, const SDItemPtr& b)
 {
+    //just compare the pointers here
+    return a == b;
 }
 
-ServicesDiscoveryModel::~ServicesDiscoveryModel()
+// ServicesDiscoveryModelPrivate
+
+class ServicesDiscoveryModelPrivate
+    : public BaseModelPrivateT<SDItemPtr>
+    , public LocalListCacheLoader<SDItemPtr>::ModelSource
 {
-    if ( m_manager )
+public:
+    Q_DECLARE_PUBLIC(ServicesDiscoveryModel)
+
+public: //ctor/dtor
+    ServicesDiscoveryModelPrivate(ServicesDiscoveryModel* pub)
+        : BaseModelPrivateT<SDItemPtr>(pub)
     {
-        addons_manager_Delete( m_manager );
     }
+
+    ~ServicesDiscoveryModelPrivate()
+    {
+        if ( m_manager )
+            addons_manager_Delete( m_manager );
+    }
+
+public:
+    const SDItem* getItemForRow(int row) const
+    {
+        const SDItemPtr* ref = item(row);
+        if (ref)
+            return ref->get();
+        return nullptr;
+    }
+
+public: //BaseModelPrivateT implementation
+    bool initializeModel() override;
+
+    bool loading() const override
+    {
+        return m_loading || BaseModelPrivateT<SDItemPtr>::loading();
+    }
+
+    std::unique_ptr<ListCacheLoader<SDItemPtr>> createLoader() const override
+    {
+        return std::make_unique<LocalListCacheLoader<SDItemPtr>>(
+            this, m_searchPattern,
+            getSortFunction()
+            );
+    }
+
+    LocalListCacheLoader<SDItemPtr>::ItemCompare getSortFunction() const
+    {
+        if (m_sortOrder == Qt::SortOrder::DescendingOrder)
+            return [](const SDItemPtr& a, const SDItemPtr& b){
+                return QString::compare(a->name, b->name) > 0;
+            };
+        else
+            return [](const SDItemPtr& a, const SDItemPtr& b) {
+                return QString::compare(a->name, b->name) < 0;
+            };
+    }
+
+
+
+public: //discovery callbacks
+    void addonFound( AddonPtr addon );
+    void addonChanged( AddonPtr addon );
+    void discoveryEnded();
+
+public: //LocalListCacheLoader implementation
+
+    virtual size_t getModelRevision() const override
+    {
+        return m_revision;
+    }
+
+    //return the data matching the pattern
+    virtual SDItemList getModelData(const QString& pattern) const override
+    {
+        if (pattern.isEmpty())
+            return m_items;
+
+        SDItemList items;
+        std::copy_if(
+            m_items.cbegin(), m_items.cend(),
+            std::back_inserter(items),
+            [&pattern](const SDItemPtr& item) {
+                return item->name.contains(pattern, Qt::CaseInsensitive);
+            });
+        return items;
+    }
+
+public: // data
+    bool m_loading = true;
+    addons_manager_t* m_manager = nullptr;
+
+    size_t m_revision = 0;
+    SDItemList m_items;
+};
+
+ServicesDiscoveryModel::ServicesDiscoveryModel( QObject* parent )
+    : BaseModel( new ServicesDiscoveryModelPrivate(this), parent )
+{
 }
 
 QVariant ServicesDiscoveryModel::data( const QModelIndex& index, int role ) const
 {
+    Q_D(const ServicesDiscoveryModel);
     if (!m_ctx)
         return {};
-    auto idx = index.row();
-    if ( idx < 0 || (size_t)idx >= m_items.size() )
+
+    const SDItem* item = d->getItemForRow(index.row());
+    if (!item)
         return {};
-    const auto& item = m_items[idx];
+
     switch ( role )
     {
         case Role::SERVICE_NAME:
-            return item.name;
+            return item->name;
         case Role::SERVICE_AUTHOR:
-            return item.author;
+            return item->author;
         case Role::SERVICE_SUMMARY:
-            return item.summery;
+            return item->summery;
         case Role::SERVICE_DESCRIPTION:
-            return item.description;
+            return item->description;
         case Role::SERVICE_DOWNLOADS:
-            return QVariant::fromValue( item.entry->i_downloads );
+            return QVariant::fromValue( item->entry->i_downloads );
         case Role::SERVICE_SCORE:
-            return item.entry->i_score / 100;
+            return item->entry->i_score / 100;
         case Role::SERVICE_STATE:
-            return item.entry->e_state;
+            return item->entry->e_state;
         case Role::SERVICE_ARTWORK:
-            return item.artworkUrl;
+            return item->artworkUrl;
         default:
             return {};
     }
@@ -84,62 +254,83 @@ QHash<int, QByteArray> ServicesDiscoveryModel::roleNames() const
     };
 }
 
-QMap<QString, QVariant> ServicesDiscoveryModel::getDataAt(int idx)
-{
-    QMap<QString, QVariant> dataDict;
-    QHash<int,QByteArray> roles = roleNames();
-    for (auto role: roles.keys()) {
-        dataDict[roles[role]] = data(index(idx), role);
-    }
-    return dataDict;
-}
-
 void ServicesDiscoveryModel::installService(int idx)
 {
-    if ( idx < 0 || idx >= (int)m_items.size() )
+    Q_D(ServicesDiscoveryModel);
+
+    const SDItem* item = d->getItemForRow(idx);
+    if (!item)
         return;
 
     addon_uuid_t uuid;
-    memcpy( uuid, m_items[idx].entry->uuid, sizeof( uuid ) );
-    addons_manager_Install( m_manager, uuid );
+    memcpy( uuid, item->entry->uuid, sizeof( uuid ) );
+    addons_manager_Install( d->m_manager, uuid );
 }
 
 void ServicesDiscoveryModel::removeService(int idx)
 {
-    if ( idx < 0 || idx >= (int)m_items.size() )
+    Q_D(ServicesDiscoveryModel);
+
+    const SDItem* item = d->getItemForRow(idx);
+    if (!item)
         return;
 
     addon_uuid_t uuid;
-    memcpy( uuid, m_items[idx].entry->uuid, sizeof( uuid ) );
-    addons_manager_Remove( m_manager, uuid );
+    memcpy( uuid, item->entry->uuid, sizeof( uuid ) );
+    addons_manager_Remove( d->m_manager, uuid );
 }
 
-int ServicesDiscoveryModel::rowCount(const QModelIndex& parent) const
+
+void ServicesDiscoveryModel::setCtx(MainCtx* ctx)
 {
-    if ( parent.isValid() )
-        return 0;
-    return getCount();
+    Q_D(ServicesDiscoveryModel);
+
+    if (ctx == m_ctx)
+        return;
+
+    assert(ctx);
+    m_ctx = ctx;
+    d->initializeModel();
+    emit ctxChanged();
 }
 
-int ServicesDiscoveryModel::getCount() const
+static void addonFoundCallback( addons_manager_t *manager, addon_entry_t *entry )
 {
-    assert( m_items.size() < INT32_MAX );
-    return static_cast<int>( m_items.size() );
+    if (entry->e_type != ADDON_SERVICE_DISCOVERY)
+        return;
+    ServicesDiscoveryModelPrivate* d = (ServicesDiscoveryModelPrivate*) manager->owner.sys;
+    QMetaObject::invokeMethod( d->q_func(), [d, entryPtr = AddonPtr(entry)]()
+        {
+            d->addonFound( std::move( entryPtr ) );
+        }, Qt::QueuedConnection);
 }
 
-void ServicesDiscoveryModel::setCtx(MainCtx* ctx)
+static void addonsDiscoveryEndedCallback( addons_manager_t *manager )
 {
-    if (ctx) {
-        m_ctx = ctx;
-    }
-    if (m_ctx) {
-        initializeManager();
-    }
-    emit ctxChanged();
+    ServicesDiscoveryModelPrivate* d = (ServicesDiscoveryModelPrivate*) manager->owner.sys;
+    QMetaObject::invokeMethod( d->q_func(), [d]()
+        {
+            d->discoveryEnded();
+        }, Qt::QueuedConnection);
 }
 
-void ServicesDiscoveryModel::initializeManager()
+static void addonChangedCallback( addons_manager_t *manager, addon_entry_t *entry )
 {
+    if (entry->e_type != ADDON_SERVICE_DISCOVERY)
+        return;
+    ServicesDiscoveryModelPrivate* d = (ServicesDiscoveryModelPrivate*) manager->owner.sys;
+    QMetaObject::invokeMethod( d->q_func(), [d, entryPtr = AddonPtr(entry)]()
+        {
+            d->addonChanged( std::move( entryPtr ) );
+        }, Qt::QueuedConnection);
+}
+
+bool ServicesDiscoveryModelPrivate::initializeModel()
+{
+    Q_Q(ServicesDiscoveryModel);
+    if (m_qmlInitializing || !q->m_ctx)
+        return false;
+
     if ( m_manager )
         addons_manager_Delete( m_manager );
 
@@ -151,119 +342,42 @@ void ServicesDiscoveryModel::initializeManager()
         addonChangedCallback,
     };
 
-    m_manager = addons_manager_New( VLC_OBJECT( m_ctx->getIntf() ), &owner );
+    m_manager = addons_manager_New( VLC_OBJECT( q->m_ctx->getIntf() ), &owner );
     assert( m_manager );
 
     m_loading = true;
-    emit loadingChanged();
+    emit q->loadingChanged();
     addons_manager_LoadCatalog( m_manager );
     addons_manager_Gather( m_manager, "repo://" );
+    return true;
 }
 
-void ServicesDiscoveryModel::addonFoundCallback( addons_manager_t *manager,
-                                        addon_entry_t *entry )
-{
-    if (entry->e_type != ADDON_SERVICE_DISCOVERY)
-        return;
-    ServicesDiscoveryModel *me = (ServicesDiscoveryModel *) manager->owner.sys;
-    QMetaObject::invokeMethod( me, [me, entryPtr = AddonPtr(entry)]()
-    {
-        me->addonFound( std::move( entryPtr ) );
-    }, Qt::QueuedConnection);
-}
-
-void ServicesDiscoveryModel::addonsDiscoveryEndedCallback( addons_manager_t *manager )
-{
-    ServicesDiscoveryModel *me = (ServicesDiscoveryModel *) manager->owner.sys;
-    QMetaObject::invokeMethod( me, [me]()
-    {
-        me->discoveryEnded();
-    }, Qt::QueuedConnection);
-}
-
-void ServicesDiscoveryModel::addonChangedCallback( addons_manager_t *manager,
-                                          addon_entry_t *entry )
-{
-    if (entry->e_type != ADDON_SERVICE_DISCOVERY)
-        return;
-    ServicesDiscoveryModel *me = (ServicesDiscoveryModel *) manager->owner.sys;
-    QMetaObject::invokeMethod( me, [me, entryPtr = AddonPtr(entry)]()
-    {
-        me->addonChanged( std::move( entryPtr ) );
-    }, Qt::QueuedConnection);
-}
 
-void ServicesDiscoveryModel::addonFound( ServicesDiscoveryModel::AddonPtr addon )
+void ServicesDiscoveryModelPrivate::addonFound( AddonPtr addon )
 {
-    beginInsertRows( QModelIndex(), getCount(), getCount() );
-    m_items.emplace_back(addon);
-    endInsertRows();
-    emit countChanged();
+    m_items.emplace_back(std::make_shared<SDItem>(addon));
+    m_revision++;
+    invalidateCache();
 }
 
-void ServicesDiscoveryModel::addonChanged( ServicesDiscoveryModel::AddonPtr addon )
+void ServicesDiscoveryModelPrivate::addonChanged( AddonPtr addon )
 {
-    for ( int r = 0; r < getCount(); ++r )
+    for ( size_t r = 0; r < m_items.size(); ++r )
     {
-        if ( memcmp( m_items[r].entry->uuid, addon->uuid, sizeof( addon->uuid ) ) )
+        if ( memcmp( m_items[r]->entry->uuid, addon->uuid, sizeof( addon->uuid ) ) )
             continue;
 
-        m_items[r] = addon;
-        emit dataChanged( index( r, 0 ), index( r, 0 ) );
+        m_items[r] = std::make_shared<SDItem>(addon);
+        break;
     }
+    m_revision++;
+    invalidateCache();
 }
 
-void ServicesDiscoveryModel::discoveryEnded()
+void ServicesDiscoveryModelPrivate::discoveryEnded()
 {
+    Q_Q(ServicesDiscoveryModel);
     assert( m_loading );
     m_loading = false;
-    emit loadingChanged();
-}
-
-ServicesDiscoveryModel::Item::Item( ServicesDiscoveryModel::AddonPtr addon )
-{
-    *this = addon;
-}
-
-ServicesDiscoveryModel::Item &ServicesDiscoveryModel::Item::operator=( ServicesDiscoveryModel::AddonPtr addon )
-{
-    name = qfu( addon->psz_name );
-    summery = qfu( addon->psz_summary ).trimmed();
-    description = qfu( addon->psz_description ).trimmed();
-    author = qfu( addon->psz_author );
-    sourceUrl = QUrl( addon->psz_source_uri );
-    entry = addon;
-
-    if ( addon->psz_image_data ) {
-        char *cDir = config_GetUserDir( VLC_CACHE_DIR );
-        if (likely(cDir != nullptr))
-        {
-            QDir dir( cDir );
-            free(cDir);
-            dir.mkdir("art");
-            dir.cd("art");
-            dir.mkdir("qt-addon-covers");
-            dir.cd("qt-addon-covers");
-
-            QString id = addons_uuid_to_psz( &addon->uuid );
-            QString filename = QString("addon_thumbnail_%1.png").arg(id);
-            QString absoluteFilePath =  dir.absoluteFilePath(filename);
-
-            if ( !QFileInfo::exists( absoluteFilePath )) {
-                QPixmap pixmap;
-                pixmap.loadFromData( QByteArray::fromBase64( QByteArray( addon->psz_image_data ) ),
-                    0,
-                    Qt::AutoColor
-                );
-                pixmap.save(absoluteFilePath);
-            }
-            artworkUrl = QUrl::fromLocalFile( absoluteFilePath );
-        }
-    }
-    else if ( addon->e_flags & ADDON_BROKEN )
-        artworkUrl = QUrl( ":/addons/addon_broken.svg" );
-    else
-        artworkUrl = QUrl( ":/addons/addon_default.svg" );
-
-    return *this;
+    emit q->loadingChanged();
 }


=====================================
modules/gui/qt/network/servicesdiscoverymodel.hpp
=====================================
@@ -23,25 +23,17 @@
 #include "config.h"
 #endif
 
-#include <QAbstractListModel>
+#include "util/base_model.hpp"
+#include "maininterface/mainctx.hpp"
 
-#include <vlc_media_source.h>
-#include <vlc_addons.h>
-#include <vlc_cxx_helpers.hpp>
-
-#include <maininterface/mainctx.hpp>
-
-#include <memory>
-
-class ServicesDiscoveryModel : public QAbstractListModel
+struct ServicesDiscoveryModelPrivate;
+class ServicesDiscoveryModel : public BaseModel
 {
     Q_OBJECT
 
 public:
 
     Q_PROPERTY(MainCtx* ctx READ getCtx WRITE setCtx NOTIFY ctxChanged FINAL)
-    Q_PROPERTY(bool loading READ isLoading NOTIFY loadingChanged FINAL)
-    Q_PROPERTY(int count READ getCount NOTIFY countChanged FINAL)
 
     enum State // equivalent to addon_state_t
     {
@@ -65,58 +57,26 @@ public:
     Q_ENUM(Role)
 
     explicit ServicesDiscoveryModel(QObject* parent = nullptr);
-    ~ServicesDiscoveryModel() override;
 
+public: //QAbstractListModel override
     QVariant data(const QModelIndex& index, int role) const override;
     QHash<int, QByteArray> roleNames() const override;
-    int rowCount(const QModelIndex& parent = {}) const override;
-
-    void setCtx(MainCtx* ctx);
 
-    inline MainCtx* getCtx() const { return m_ctx; }
-    inline bool isLoading() const { return m_loading; }
-    int getCount() const;
-
-    Q_INVOKABLE QMap<QString, QVariant> getDataAt(int idx);
+public: //invokable functions
     Q_INVOKABLE void installService(int idx);
     Q_INVOKABLE void removeService(int idx);
 
+public: // properties
+    void setCtx(MainCtx* ctx);
+    inline MainCtx* getCtx() const { return m_ctx; }
+
 signals:
-    void loadingChanged();
-    void countChanged();
     void ctxChanged();
 
 private:
-    using AddonPtr = vlc_shared_data_ptr_type(addon_entry_t,
-                                addon_entry_Hold, addon_entry_Release);
-
-    void initializeManager();
-    static void addonFoundCallback( addons_manager_t *, struct addon_entry_t * );
-    static void addonsDiscoveryEndedCallback( addons_manager_t * );
-    static void addonChangedCallback( addons_manager_t *, struct addon_entry_t * );
-
-    void addonFound( AddonPtr addon );
-    void addonChanged( AddonPtr addon );
-    void discoveryEnded();
-
-    struct Item
-    {
-        QString name;
-        QString summery;
-        QString description;
-        QString author;
-        QUrl sourceUrl;
-        QUrl artworkUrl;
-        AddonPtr entry;
-
-        Item( AddonPtr addon );
-        Item& operator =( AddonPtr addon );
-    };
-
-    std::vector<Item> m_items;
     MainCtx* m_ctx = nullptr;
-    addons_manager_t* m_manager = nullptr;
-    bool m_loading = false;
+
+    Q_DECLARE_PRIVATE(ServicesDiscoveryModel);
 };
 
 #endif // MLServicesDiscoveryModel_HPP


=====================================
modules/gui/qt/util/sortfilterproxymodel.cpp deleted
=====================================
@@ -1,129 +0,0 @@
-/*****************************************************************************
- * Copyright (C) 2020 VLC authors and VideoLAN
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-#include "sortfilterproxymodel.hpp"
-
-#include <QRegularExpression>
-
-#include <cassert>
-
-
-SortFilterProxyModel::SortFilterProxyModel( QObject *parent )
-    : QSortFilterProxyModel( parent )
-{
-    setFilterCaseSensitivity(Qt::CaseInsensitive);
-
-    connect( this, &QAbstractListModel::rowsInserted, this, &SortFilterProxyModel::countChanged );
-    connect( this, &QAbstractListModel::rowsRemoved, this, &SortFilterProxyModel::countChanged );
-    connect( this, &QAbstractItemModel::modelReset, this, &SortFilterProxyModel::countChanged );
-    connect( this, &QAbstractItemModel::layoutChanged, this, &SortFilterProxyModel::countChanged );
-
-    connect( this, &QAbstractProxyModel::sourceModelChanged, this, &SortFilterProxyModel::updateFilterRole );
-    connect( this, &QAbstractProxyModel::sourceModelChanged, this, &SortFilterProxyModel::updateSortRole );
-}
-
-QString SortFilterProxyModel::searchPattern() const
-{
-    return filterRegularExpression().pattern();
-}
-
-void SortFilterProxyModel::setSearchPattern( const QString &searchPattern )
-{
-    setFilterRegularExpression(searchPattern);
-}
-
-QByteArray SortFilterProxyModel::searchRole() const
-{
-    return m_searchRole;
-}
-
-void SortFilterProxyModel::setSearchRole( const QByteArray &searchRole )
-{
-    m_searchRole = searchRole;
-    emit searchRoleChanged();
-    updateFilterRole();
-}
-
-QString SortFilterProxyModel::sortCriteria() const
-{
-    return m_sortCriteria;
-}
-
-void SortFilterProxyModel::setSortCriteria(const QString &sortCriteria)
-{
-    if (m_sortCriteria == sortCriteria)
-        return;
-
-    m_sortCriteria = sortCriteria;
-    updateSortRole();
-    emit sortCriteriaChanged();
-}
-
-Qt::SortOrder SortFilterProxyModel::sortOrder() const
-{
-    return QSortFilterProxyModel::sortOrder();
-}
-
-void SortFilterProxyModel::setSortOrder(Qt::SortOrder sortOrder)
-{
-    if (this->sortOrder() == sortOrder)
-        return;
-
-    this->sort(0, sortOrder);
-    emit sortOrderChanged();
-}
-
-int SortFilterProxyModel::count() const
-{
-    return rowCount();
-}
-
-QMap<QString, QVariant> SortFilterProxyModel::getDataAt( int idx )
-{
-    QMap<QString, QVariant> dataDict;
-    QHash<int,QByteArray> roles = roleNames();
-    for( const auto role: roles.keys() ) {
-        dataDict[roles[role]] = data( index( idx, 0 ), role );
-    }
-    return dataDict;
-}
-
-QModelIndexList SortFilterProxyModel::mapIndexesToSource( const QModelIndexList &indexes )
-{
-    QModelIndexList sourceIndexes;
-    sourceIndexes.reserve( indexes.size() );
-    for( const auto &proxyIndex : indexes ) {
-        sourceIndexes.push_back( mapToSource(proxyIndex) );
-    }
-    return sourceIndexes;
-}
-
-int SortFilterProxyModel::mapIndexToSource(int idx)
-{
-    return mapToSource( index( idx, 0 ) ).row();
-}
-
-void SortFilterProxyModel::updateFilterRole()
-{
-    setFilterRole( roleNames().key( m_searchRole ) );
-}
-
-void SortFilterProxyModel::updateSortRole()
-{
-    setSortRole( roleNames().key( m_sortCriteria.toUtf8() ) );
-}


=====================================
modules/gui/qt/util/sortfilterproxymodel.hpp deleted
=====================================
@@ -1,76 +0,0 @@
-/*****************************************************************************
- * Copyright (C) 2020 VLC authors and VideoLAN
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-#ifndef SORT_FILTER_PROXY_MODEL
-#define SORT_FILTER_PROXY_MODEL
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "qt.hpp"
-#include <QSortFilterProxyModel>
-
-class SortFilterProxyModel : public QSortFilterProxyModel
-{
-    Q_OBJECT
-
-    Q_PROPERTY( QByteArray searchRole READ searchRole WRITE setSearchRole NOTIFY searchRoleChanged  FINAL)
-    Q_PROPERTY( QString searchPattern READ searchPattern WRITE setSearchPattern NOTIFY searchPatternChanged  FINAL)
-    Q_PROPERTY( QString sortCriteria READ sortCriteria WRITE setSortCriteria NOTIFY sortCriteriaChanged  FINAL)
-    Q_PROPERTY( Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged  FINAL)
-    Q_PROPERTY( int count READ count NOTIFY countChanged  FINAL)
-
-public:
-    SortFilterProxyModel( QObject * parent = nullptr );
-
-    QString searchPattern() const;
-    void setSearchPattern( const QString &searchPattern );
-
-    QByteArray searchRole() const;
-    void setSearchRole( const QByteArray &searchRole );
-
-    QString sortCriteria() const;
-    void setSortCriteria( const QString &sortCriteria );
-
-    Qt::SortOrder sortOrder() const;
-    void setSortOrder(Qt::SortOrder sortOrder);
-
-    int count() const;
-
-    Q_INVOKABLE QMap<QString, QVariant> getDataAt( int idx );
-    Q_INVOKABLE QModelIndexList mapIndexesToSource( const QModelIndexList &indexes );
-    Q_INVOKABLE int mapIndexToSource( int idx );
-
-signals:
-    void searchPatternChanged();
-    void searchRoleChanged();
-    void countChanged();
-    void sortCriteriaChanged();
-    void sortOrderChanged();
-
-private slots:
-    void updateFilterRole();
-    void updateSortRole();
-
-private:
-    QByteArray m_searchRole;
-    QString m_sortCriteria;
-};
-
-#endif // SORT_FILTER_PROXY_MODEL



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/4ea5d3b9ac4cc7938cadd3a849303bf8bd5fcfda...dd683943be31612ea9c48d64a8b9f21e44ecb971

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/4ea5d3b9ac4cc7938cadd3a849303bf8bd5fcfda...dd683943be31612ea9c48d64a8b9f21e44ecb971
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list