[vlc-commits] [Git][videolan/vlc][master] 8 commits: libvlccore.sym: Add vlc_media_tree_Add and vlc_media_tree_Remove

Steve Lhomme (@robUx4) gitlab at videolan.org
Thu Nov 24 13:26:28 UTC 2022



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
df3c041e by Benjamin Arnaud at 2022-11-24T13:10:38+00:00
libvlccore.sym: Add vlc_media_tree_Add and vlc_media_tree_Remove

- - - - -
3318a931 by Benjamin Arnaud at 2022-11-24T13:10:38+00:00
qt/util: Create ClipListModel

This patch adds a helper template class for sorting and filtering QAbstractListModel
item(s). We tried implementing capping from a QSortFilterProxyModel and QML, but both
implementations had flaws:

- The QSortFilterProxyModel has a filterAcceptsRow function, but it does not work for
an item capping scenario.
- Regarding QML we found ways to make it work, but it felt hackish and brought
up issues on our items shadows.

Although a pure QML implementation could be done, a C++ implementation is probably
more elegant. Especially now that we are using a template that can fit several use
case scenarios.

Co-authored-by: Pierre Lamot <pierre at videolabs.io>

- - - - -
38196134 by Benjamin Arnaud at 2022-11-24T13:10:38+00:00
qt/network: Create StandardPathModel

- - - - -
616222e3 by Benjamin Arnaud at 2022-11-24T13:10:38+00:00
qml/BrowseDisplay: Add the 'browseFolders' component

- - - - -
1c89b040 by Benjamin Arnaud at 2022-11-24T13:10:38+00:00
qml/BrowseHomeDisplay: Add the standard paths section

- - - - -
d8dde8e8 by Benjamin Arnaud at 2022-11-24T13:10:38+00:00
qml/BrowseDeviceView: Add anchoring settings

Otherwise the ExpandGridView goes to an invalid height of -1 occasionaly.

- - - - -
1144ba36 by Benjamin Arnaud at 2022-11-24T13:10:38+00:00
qml/BrowseDeviceView: Update 'type' conditions for clarity

- - - - -
c49873e4 by Benjamin Arnaud at 2022-11-24T13:10:38+00:00
qt/networkdevicemodel: Update 'items' implementation

- - - - -


13 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/network/networkdevicemodel.cpp
- modules/gui/qt/network/networkdevicemodel.hpp
- modules/gui/qt/network/qml/BrowseDeviceView.qml
- modules/gui/qt/network/qml/BrowseDisplay.qml
- modules/gui/qt/network/qml/BrowseHomeDisplay.qml
- + modules/gui/qt/network/standardpathmodel.cpp
- + modules/gui/qt/network/standardpathmodel.hpp
- + modules/gui/qt/util/cliplistmodel.cpp
- + modules/gui/qt/util/cliplistmodel.hpp
- po/POTFILES.in
- src/libvlccore.sym


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -243,6 +243,8 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/network/networkmediamodel.hpp \
 	gui/qt/network/servicesdiscoverymodel.cpp \
 	gui/qt/network/servicesdiscoverymodel.hpp \
+	gui/qt/network/standardpathmodel.cpp \
+	gui/qt/network/standardpathmodel.hpp \
 	gui/qt/style/qtthemeprovider.hpp \
 	gui/qt/style/systempalette.cpp \
 	gui/qt/style/systempalette.hpp \
@@ -268,6 +270,8 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/util/asynctask.hpp \
 	gui/qt/util/audio_device_model.cpp  \
 	gui/qt/util/audio_device_model.hpp \
+	gui/qt/util/cliplistmodel.cpp \
+	gui/qt/util/cliplistmodel.hpp \
 	gui/qt/util/color_scheme_model.cpp gui/qt/util/color_scheme_model.hpp \
 	gui/qt/util/color_svg_image_provider.cpp gui/qt/util/color_svg_image_provider.hpp \
 	gui/qt/util/covergenerator.cpp \
@@ -456,6 +460,7 @@ nodist_libqt_plugin_la_SOURCES = \
 	gui/qt/network/networksourcesmodel.moc.cpp \
 	gui/qt/network/networkmediamodel.moc.cpp \
 	gui/qt/network/servicesdiscoverymodel.moc.cpp \
+	gui/qt/network/standardpathmodel.moc.cpp \
 	gui/qt/style/systempalette.moc.cpp \
 	gui/qt/player/input_models.moc.cpp \
 	gui/qt/player/player_controller.moc.cpp \
@@ -468,6 +473,7 @@ nodist_libqt_plugin_la_SOURCES = \
 	gui/qt/playlist/playlist_model.moc.cpp \
 	gui/qt/util/asynctask.moc.cpp \
 	gui/qt/util/audio_device_model.moc.cpp \
+	gui/qt/util/cliplistmodel.moc.cpp \
 	gui/qt/util/color_scheme_model.moc.cpp \
 	gui/qt/util/color_svg_image_provider.moc.cpp \
 	gui/qt/util/imageluminanceextractor.moc.cpp \


=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -53,6 +53,7 @@
 #include "network/networkdevicemodel.hpp"
 #include "network/networksourcesmodel.hpp"
 #include "network/servicesdiscoverymodel.hpp"
+#include "network/standardpathmodel.hpp"
 
 #include "menus/qml_menu_wrapper.hpp"
 
@@ -254,6 +255,7 @@ void MainUI::registerQMLTypes()
         qmlRegisterType<NetworkDeviceModel>( uri, versionMajor, versionMinor, "NetworkDeviceModel");
         qmlRegisterType<NetworkSourcesModel>( uri, versionMajor, versionMinor, "NetworkSourcesModel");
         qmlRegisterType<ServicesDiscoveryModel>( uri, versionMajor, versionMinor, "ServicesDiscoveryModel");
+        qmlRegisterType<StandardPathModel>( uri, versionMajor, versionMinor, "StandardPathModel");
         qmlRegisterType<MLFoldersModel>( uri, versionMajor, versionMinor, "MLFolderModel");
         qmlRegisterType<ImageLuminanceExtractor>( uri, versionMajor, versionMinor, "ImageLuminanceExtractor");
 


=====================================
modules/gui/qt/network/networkdevicemodel.cpp
=====================================
@@ -24,8 +24,9 @@
 #include <maininterface/mainctx.hpp>
 
 NetworkDeviceModel::NetworkDeviceModel( QObject* parent )
-    : QAbstractListModel( parent )
+    : ClipListModel(parent)
 {
+    m_comparator = ascendingName;
 }
 
 QVariant NetworkDeviceModel::data( const QModelIndex& index, int role ) const
@@ -33,7 +34,7 @@ QVariant NetworkDeviceModel::data( const QModelIndex& index, int role ) const
     if (!m_ctx)
         return {};
     auto idx = index.row();
-    if ( idx < 0 || (size_t)idx >= m_items.size() )
+    if ( idx < 0 || idx >= count() )
         return {};
     const auto& item = m_items[idx];
     switch ( role )
@@ -70,15 +71,6 @@ QHash<int, QByteArray> NetworkDeviceModel::roleNames() const
     };
 }
 
-
-int NetworkDeviceModel::rowCount(const QModelIndex& parent) const
-{
-    if ( parent.isValid() )
-        return 0;
-    return getCount();
-}
-
-
 void NetworkDeviceModel::setCtx(MainCtx* ctx)
 {
     if (ctx) {
@@ -108,109 +100,6 @@ void NetworkDeviceModel::setSourceName(const QString& sourceName)
     emit sourceNameChanged();
 }
 
-int NetworkDeviceModel::getCount() const
-{
-    return m_count;
-}
-
-int NetworkDeviceModel::maximumCount() const
-{
-    return m_maximumCount;
-}
-
-void NetworkDeviceModel::setMaximumCount(int count)
-{
-    if (m_maximumCount == count)
-        return;
-
-    if (count == -1 || m_maximumCount < count)
-    {
-        m_maximumCount = count;
-
-        expandItems();
-    }
-    else
-    {
-        m_maximumCount = count;
-
-        shrinkItems();
-    }
-
-    emit maximumCountChanged();
-}
-
-bool NetworkDeviceModel::hasMoreItems() const
-{
-    if (m_maximumCount == -1)
-        return false;
-    else
-        return ((size_t) m_count < m_items.size());
-}
-
-QString NetworkDeviceModel::searchPattern() const
-{
-    return m_searchPattern;
-}
-
-void NetworkDeviceModel::setSearchPattern(const QString & pattern)
-{
-    if (m_searchPattern == pattern)
-        return;
-
-    m_searchPattern = pattern;
-
-    emit searchPatternChanged();
-}
-
-QByteArray NetworkDeviceModel::searchRole() const
-{
-    return m_searchRole;
-}
-
-void NetworkDeviceModel::setSearchRole(const QByteArray & role)
-{
-    if (m_searchRole == role)
-        return;
-
-    m_searchRole = role;
-
-    emit searchRoleChanged();
-}
-
-QString NetworkDeviceModel::sortCriteria() const
-{
-    return m_sortCriteria;
-}
-
-void NetworkDeviceModel::setSortCriteria(const QString & criteria)
-{
-    if (m_sortCriteria == criteria)
-        return;
-
-    m_sortCriteria = criteria;
-
-    updateSort();
-
-    emit sortCriteriaChanged();
-}
-
-Qt::SortOrder NetworkDeviceModel::sortOrder() const
-{
-    return m_sortOrder;
-}
-
-void NetworkDeviceModel::setSortOrder(Qt::SortOrder order)
-{
-    if (m_sortOrder == order)
-        return;
-
-    m_sortOrder = order;
-
-    updateSort();
-
-    emit sortOrderChanged();
-}
-
 bool NetworkDeviceModel::insertIntoPlaylist(const QModelIndexList &itemIdList, ssize_t playlistIndex)
 {
     if (!(m_ctx && m_sdSource != CAT_MYCOMPUTER))
@@ -222,7 +111,7 @@ bool NetworkDeviceModel::insertIntoPlaylist(const QModelIndexList &itemIdList, s
         if ( !id.isValid() )
             continue;
         const int index = id.row();
-        if ( index < 0 || (size_t)index >= m_items.size() )
+        if ( index < 0 || index >= count() )
             continue;
 
         medias.append( vlc::playlist::Media {m_items[index].inputItem.get()} );
@@ -237,7 +126,7 @@ bool NetworkDeviceModel::addToPlaylist(int index)
 {
     if (!(m_ctx && m_sdSource != CAT_MYCOMPUTER))
         return false;
-    if (index < 0 || (size_t)index >= m_items.size() )
+    if (index < 0 || index >= count() )
         return false;
     auto item =  m_items[index];
     vlc::playlist::Media media{ item.inputItem.get() };
@@ -271,12 +160,11 @@ bool NetworkDeviceModel::addToPlaylist(const QModelIndexList &itemIdList)
     return ret;
 }
 
-
 bool NetworkDeviceModel::addAndPlay(int index)
 {
     if (!(m_ctx && m_sdSource != CAT_MYCOMPUTER))
         return false;
-    if (index < 0 || (size_t)index >= m_items.size() )
+    if (index < 0 || index >= count() )
         return false;
     auto item =  m_items[index];
     vlc::playlist::Media media{ item.inputItem.get() };
@@ -335,7 +223,7 @@ QVariantList NetworkDeviceModel::getItemsForIndexes(const QModelIndexList & inde
     {
         int index = modelIndex.row();
 
-        if (index < 0 || (size_t) index >= m_items.size())
+        if (index < 0 || index >= count())
             continue;
 
         QmlInputItem input(m_items[index].inputItem.get(), true);
@@ -346,22 +234,34 @@ QVariantList NetworkDeviceModel::getItemsForIndexes(const QModelIndexList & inde
     return items;
 }
 
+// Protected ClipListModel implementation
+
+void NetworkDeviceModel::onUpdateSort(const QString & criteria, Qt::SortOrder order) /* override */
+{
+    if (criteria == "mrl")
+    {
+        if (order == Qt::AscendingOrder)
+            m_comparator = ascendingMrl;
+        else
+            m_comparator = descendingMrl;
+    }
+    else
+    {
+        if (order == Qt::AscendingOrder)
+            m_comparator = ascendingName;
+        else
+            m_comparator = descendingName;
+    }
+}
+
 bool NetworkDeviceModel::initializeMediaSources()
 {
     auto libvlc = vlc_object_instance(m_ctx->getIntf());
 
     m_listeners.clear();
-    if (!m_items.empty()) {
-        beginResetModel();
-
-        m_items.clear();
-
-        m_count = 0;
 
-        endResetModel();
+    clearItems();
 
-        emit countChanged();
-    }
     m_name = QString {};
 
     auto provider = vlc_media_source_provider_Get( libvlc );
@@ -436,7 +336,8 @@ void NetworkDeviceModel::ListenerCb::onItemRemoved( MediaTreePtr tree, input_ite
         for (auto p_item : itemList)
         {
             QUrl itemUri = QUrl::fromEncoded(p_item->psz_uri);
-            auto it = std::find_if( begin( model->m_items ), end( model->m_items ), [p_item, itemUri](const Item& i) {
+            auto it = std::find_if( begin( model->m_items ), end( model->m_items ),
+                                   [p_item, itemUri](const NetworkDeviceItem & i) {
                 return QString::compare( qfu(p_item->psz_name), i.name, Qt::CaseInsensitive ) == 0 &&
                     itemUri.scheme() == i.mainMrl.scheme();
             });
@@ -455,11 +356,10 @@ void NetworkDeviceModel::ListenerCb::onItemRemoved( MediaTreePtr tree, input_ite
                 continue;
             auto idx = std::distance( begin( model->m_items ), it );
 
-            model->removeItem(it, idx, implicitCount);
+            model->eraseItem(it, idx, implicitCount);
         }
 
-        if (model->m_maximumCount != -1)
-            model->expandItems();
+       model->updateItems();
     }, Qt::QueuedConnection);
 }
 
@@ -475,14 +375,14 @@ void NetworkDeviceModel::refreshDeviceList(MediaSourcePtr mediaSource,
 
             int index = 0;
 
-            std::vector<Item>::iterator it = m_items.begin();
+            std::vector<NetworkDeviceItem>::iterator it = m_items.begin();
 
             while (it != m_items.end())
             {
                 if (it->mediaSource != mediaSource)
                     continue;
 
-                removeItem(it, index, implicitCount);
+                eraseItem(it, index, implicitCount);
 
                 index++;
             }
@@ -515,17 +415,17 @@ void NetworkDeviceModel::addItems(const std::vector<InputItemPtr> & inputList,
 
     for (const InputItemPtr & inputItem : inputList)
     {
-        Item item;
+        NetworkDeviceItem item;
 
         item.name = qfu(inputItem->psz_name);
 
         item.mainMrl = QUrl::fromEncoded(inputItem->psz_uri);
 
-        std::vector<Item>::iterator it;
+        std::vector<NetworkDeviceItem>::iterator it;
 
         if (checkDuplicate)
         {
-            it = std::find_if(begin(m_items), end(m_items), [item](const Item & i)
+            it = std::find_if(begin(m_items), end(m_items), [item](const NetworkDeviceItem & i)
             {
                 return matchItem(item, i);
             });
@@ -566,59 +466,18 @@ void NetworkDeviceModel::addItems(const std::vector<InputItemPtr> & inputList,
             free(artwork);
         }
 
-        int pos = std::distance(begin(m_items), it);
-
-        if (m_maximumCount != -1 && m_count >= m_maximumCount)
-        {
-            // NOTE: When the position is beyond the maximum count we don't notify the view.
-            if (pos >= m_maximumCount)
-            {
-                m_items.insert(it, std::move(item));
-
-                continue;
-            }
-
-            // NOTE: Removing the last item to make room for the new one.
-
-            int index = m_count - 1;
-
-            beginRemoveRows({}, index, index);
-
-            m_count--;
-
-            endRemoveRows();
-
-            emit countChanged();
-        }
-
-        beginInsertRows({}, pos, pos);
-
-        m_items.insert(it, std::move(item));
-
-        m_count++;
-
-        endInsertRows();
-
-        emit countChanged();
+        insertItem(it, std::move(item));
     }
 
-    if (m_maximumCount != -1)
-        expandItems();
+    updateItems();
 }
 
-void NetworkDeviceModel::removeItem(std::vector<Item>::iterator & it, int index, int count)
+void NetworkDeviceModel::eraseItem(std::vector<NetworkDeviceItem>::iterator & it,
+                                   int index, int count)
 {
     if (index < count)
     {
-        beginRemoveRows({}, index, index);
-
-        it = m_items.erase(it);
-
-        m_count--;
-
-        endRemoveRows();
-
-        emit countChanged();
+        removeItem(it, index);
     }
     // NOTE: We don't want to notify the view if the item's position is beyond the
     //       maximumCount.
@@ -626,82 +485,18 @@ void NetworkDeviceModel::removeItem(std::vector<Item>::iterator & it, int index,
         it = m_items.erase(it);
 }
 
-void NetworkDeviceModel::expandItems()
-{
-    int count = implicitCount();
-
-    if (m_count >= count)
-        return;
-
-    beginInsertRows({}, m_count, count - 1);
-
-    m_count = count;
-
-    endInsertRows();
-
-    emit countChanged();
-}
-
-void NetworkDeviceModel::shrinkItems()
-{
-    int count = implicitCount();
-
-    if (m_count <= count)
-        return;
-
-    beginRemoveRows({}, count, m_count - 1);
-
-    m_count = count;
-
-    endRemoveRows();
-
-    emit countChanged();
-}
-
-void NetworkDeviceModel::updateSort()
-{
-    if (m_sortCriteria == "mrl")
-    {
-        if (m_sortOrder == Qt::AscendingOrder)
-            m_comparator = ascendingMrl;
-        else
-            m_comparator = descendingMrl;
-    }
-    else
-    {
-        if (m_sortOrder == Qt::AscendingOrder)
-            m_comparator = ascendingName;
-        else
-            m_comparator = descendingName;
-    }
-
-    beginResetModel();
-
-    std::sort(m_items.begin(), m_items.end(), m_comparator);
-
-    endResetModel();
-}
-
-int NetworkDeviceModel::implicitCount() const
-{
-    assert(m_items.size() < INT32_MAX);
-
-    if (m_maximumCount == -1)
-        return (int) m_items.size();
-    else
-        return qMin((int) m_items.size(), m_maximumCount);
-}
-
 // Private static function
 
-/* static */ bool NetworkDeviceModel::matchItem(const Item & a, const Item & b)
+/* static */ bool NetworkDeviceModel::matchItem(const NetworkDeviceItem & a,
+                                                const NetworkDeviceItem & b)
 {
     return (QString::compare(a.name, b.name, Qt::CaseInsensitive) == 0
             &&
             QString::compare(a.mainMrl.scheme(), b.mainMrl.scheme(), Qt::CaseInsensitive) == 0);
 }
 
-/* static */ bool NetworkDeviceModel::ascendingName(const Item & a, const Item & b)
+/* static */ bool NetworkDeviceModel::ascendingName(const NetworkDeviceItem & a,
+                                                    const NetworkDeviceItem & b)
 {
     int result = QString::compare(a.name, b.name, Qt::CaseInsensitive);
 
@@ -711,13 +506,15 @@ int NetworkDeviceModel::implicitCount() const
     return (QString::compare(a.mainMrl.scheme(), b.mainMrl.scheme(), Qt::CaseInsensitive) <= 0);
 }
 
-/* static */ bool NetworkDeviceModel::ascendingMrl(const Item & a, const Item & b)
+/* static */ bool NetworkDeviceModel::ascendingMrl(const NetworkDeviceItem & a,
+                                                   const NetworkDeviceItem & b)
 {
     return (QString::compare(a.mainMrl.toString(),
                              b.mainMrl.toString(), Qt::CaseInsensitive) <= 0);
 }
 
-/* static */ bool NetworkDeviceModel::descendingName(const Item & a, const Item & b)
+/* static */ bool NetworkDeviceModel::descendingName(const NetworkDeviceItem & a,
+                                                     const NetworkDeviceItem & b)
 {
     int result = QString::compare(a.name, b.name, Qt::CaseInsensitive);
 
@@ -727,7 +524,8 @@ int NetworkDeviceModel::implicitCount() const
     return (QString::compare(a.mainMrl.scheme(), b.mainMrl.scheme(), Qt::CaseInsensitive) >= 0);
 }
 
-/* static */ bool NetworkDeviceModel::descendingMrl(const Item & a, const Item & b)
+/* static */ bool NetworkDeviceModel::descendingMrl(const NetworkDeviceItem & a,
+                                                    const NetworkDeviceItem & b)
 {
     return (QString::compare(a.mainMrl.toString(),
                              b.mainMrl.toString(), Qt::CaseInsensitive) >= 0);


=====================================
modules/gui/qt/network/networkdevicemodel.hpp
=====================================
@@ -32,15 +32,24 @@
 #include <vlc_cxx_helpers.hpp>
 
 #include "mediatreelistener.hpp"
+#include "util/cliplistmodel.hpp"
 
 #include <memory>
 
 class MainCtx;
-class NetworkDeviceModel : public QAbstractListModel
+
+struct NetworkDeviceItem;
+
+class NetworkDeviceModel : public ClipListModel<NetworkDeviceItem>
 {
     Q_OBJECT
-public:
 
+    Q_PROPERTY(MainCtx* ctx READ getCtx WRITE setCtx NOTIFY ctxChanged FINAL)
+    Q_PROPERTY(SDCatType sd_source READ getSdSource WRITE setSdSource NOTIFY sdSourceChanged FINAL)
+    Q_PROPERTY(QString name READ getName NOTIFY nameChanged FINAL)
+    Q_PROPERTY(QString source_name READ getSourceName WRITE setSourceName NOTIFY sourceNameChanged FINAL)
+
+public: // Enums
     enum Role {
         NETWORK_NAME = Qt::UserRole + 1,
         NETWORK_MRL,
@@ -74,36 +83,23 @@ public:
     };
     Q_ENUM( SDCatType )
 
+public: // Declarations
+    using MediaSourcePtr = vlc_shared_data_ptr_type(vlc_media_source_t,
+                                    vlc_media_source_Hold, vlc_media_source_Release);
 
-    Q_PROPERTY(MainCtx* ctx READ getCtx WRITE setCtx NOTIFY ctxChanged FINAL)
-    Q_PROPERTY(SDCatType sd_source READ getSdSource WRITE setSdSource NOTIFY sdSourceChanged FINAL)
-    Q_PROPERTY(QString name READ getName NOTIFY nameChanged FINAL)
-    Q_PROPERTY(QString source_name READ getSourceName WRITE setSourceName NOTIFY sourceNameChanged FINAL)
-    Q_PROPERTY(int count READ getCount NOTIFY countChanged FINAL)
-
-    Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged
-               FINAL)
-
-    Q_PROPERTY(bool hasMoreItems READ hasMoreItems NOTIFY countChanged FINAL)
-
-    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)
+    using MediaTreePtr = vlc_shared_data_ptr_type(vlc_media_tree_t,
+                                                  vlc_media_tree_Hold,
+                                                  vlc_media_tree_Release);
 
-    Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder
-               NOTIFY sortOrderChanged FINAL)
+    using InputItemPtr = vlc_shared_data_ptr_type(input_item_t,
+                                                  input_item_Hold,
+                                                  input_item_Release);
 
 public:
     NetworkDeviceModel( QObject* parent = nullptr );
 
     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);
     void setSdSource(SDCatType s);
@@ -114,25 +110,6 @@ public:
     inline QString getName() { return m_name; }
     inline QString getSourceName() { return m_sourceName; }
 
-    int getCount() const;
-
-    int maximumCount() const;
-    void setMaximumCount(int count);
-
-    bool hasMoreItems() const;
-
-    QString searchPattern() const;
-    void setSearchPattern(const QString & pattern);
-
-    QByteArray searchRole() const;
-    void setSearchRole(const QByteArray & role);
-
-    QString sortCriteria() const;
-    void setSortCriteria(const QString & criteria);
-
-    Qt::SortOrder sortOrder() const;
-    void setSortOrder(Qt::SortOrder order);
-
     Q_INVOKABLE bool insertIntoPlaylist( const QModelIndexList& itemIdList, ssize_t playlistIndex );
     Q_INVOKABLE bool addToPlaylist( int index );
     Q_INVOKABLE bool addToPlaylist(const QVariantList& itemIdList);
@@ -145,68 +122,32 @@ public:
 
     Q_INVOKABLE QVariantList getItemsForIndexes(const QModelIndexList & indexes) const;
 
+protected: // ClipListModel implementation
+    void onUpdateSort(const QString & criteria, Qt::SortOrder order) override;
+
 signals:
     void ctxChanged();
     void sdSourceChanged();
     void sourceNameChanged();
     void nameChanged();
-    void countChanged();
-
-    void maximumCountChanged();
-
-    void searchPatternChanged();
-    void searchRoleChanged();
-
-    void sortCriteriaChanged();
-    void sortOrderChanged();
 
 private:
-    using MediaSourcePtr = vlc_shared_data_ptr_type(vlc_media_source_t,
-                                    vlc_media_source_Hold, vlc_media_source_Release);
-
-    using MediaTreePtr = vlc_shared_data_ptr_type(vlc_media_tree_t,
-                                                  vlc_media_tree_Hold,
-                                                  vlc_media_tree_Release);
-
-    using InputItemPtr = vlc_shared_data_ptr_type(input_item_t,
-                                                  input_item_Hold,
-                                                  input_item_Release);
-
-    struct Item
-    {
-        QString name;
-        QUrl mainMrl;
-        std::vector<QUrl> mrls;
-        QString protocol;
-        ItemType type;
-        MediaSourcePtr mediaSource;
-        InputItemPtr inputItem;
-        QUrl artworkUrl;
-    };
-
     bool initializeMediaSources();
 
     void refreshDeviceList(MediaSourcePtr mediaSource, input_item_node_t* const children[], size_t count , bool clear);
 
     void addItems(const std::vector<InputItemPtr> & inputList, const MediaSourcePtr & mediaSource);
 
-    void removeItem(std::vector<Item>::iterator & it, int index, int count);
-
-    void expandItems();
-    void shrinkItems();
-
-    void updateSort();
-
-    int implicitCount() const;
+    void eraseItem(std::vector<NetworkDeviceItem>::iterator & it, int index, int count);
 
 private: // Static functions
-    static bool matchItem(const Item & a, const Item & b);
+    static bool matchItem(const NetworkDeviceItem & a, const NetworkDeviceItem & b);
 
-    static bool ascendingName(const Item & a, const Item & b);
-    static bool ascendingMrl (const Item & a, const Item & b);
+    static bool ascendingName(const NetworkDeviceItem & a, const NetworkDeviceItem & b);
+    static bool ascendingMrl (const NetworkDeviceItem & a, const NetworkDeviceItem & b);
 
-    static bool descendingName(const Item & a, const Item & b);
-    static bool descendingMrl (const Item & a, const Item & b);
+    static bool descendingName(const NetworkDeviceItem & a, const NetworkDeviceItem & b);
+    static bool descendingMrl (const NetworkDeviceItem & a, const NetworkDeviceItem & b);
 
 private:
     struct ListenerCb : public MediaTreeListener::MediaTreeListenerCb {
@@ -224,26 +165,24 @@ private:
         MediaSourcePtr mediaSource;
     };
 
-    std::vector<Item> m_items;
     MainCtx* m_ctx = nullptr;
     SDCatType m_sdSource = CAT_UNDEFINED;
     QString m_sourceName; // '*' -> all sources
     QString m_name; // source long name
 
-    int m_count = 0;
-
-    int m_maximumCount = -1;
-
-    QString m_searchPattern;
-    QByteArray m_searchRole;
-
-    QString m_sortCriteria = "name";
-
-    std::function<bool(const Item &, const Item &)> m_comparator = ascendingName;
-
-    Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
-
     std::vector<std::unique_ptr<MediaTreeListener>> m_listeners;
 };
 
+struct NetworkDeviceItem
+{
+    QString name;
+    QUrl mainMrl;
+    std::vector<QUrl> mrls;
+    QString protocol;
+    NetworkDeviceModel::ItemType type;
+    NetworkDeviceModel::MediaSourcePtr mediaSource;
+    NetworkDeviceModel::InputItemPtr inputItem;
+    QUrl artworkUrl;
+};
+
 #endif // MLNETWORKDEVICEMODEL_HPP


=====================================
modules/gui/qt/network/qml/BrowseDeviceView.qml
=====================================
@@ -162,10 +162,10 @@ FocusScope {
 
         var data = modelFilter.getDataAt(index)
 
-        if (data.type === NetworkMediaModel.TYPE_DIRECTORY
-            ||
-            data.type === NetworkMediaModel.TYPE_NODE)
-            browse(data.tree, Qt.TabFocusReason);
+        var type = data.type
+
+        if (type === NetworkMediaModel.TYPE_DIRECTORY || type === NetworkMediaModel.TYPE_NODE)
+            browse(data.tree, Qt.TabFocusReason)
         else
             playAt(index);
     }
@@ -179,10 +179,10 @@ FocusScope {
     }
 
     function onDoubleClicked(model, index) {
-        if (model.type === NetworkMediaModel.TYPE_NODE
-            ||
-            model.type === NetworkMediaModel.TYPE_DIRECTORY)
-            browse(model.tree, Qt.MouseFocusReason);
+        var type = model.type
+
+        if (type === NetworkMediaModel.TYPE_NODE || type === NetworkMediaModel.TYPE_DIRECTORY)
+            browse(model.tree, Qt.MouseFocusReason)
         else
             playAt(index);
     }
@@ -226,6 +226,8 @@ FocusScope {
                                                 ? -1
                                                 : root.maximumRows * nbItemPerRow
 
+            anchors.fill: parent
+
             cellWidth: VLCStyle.gridItem_network_width
             cellHeight: VLCStyle.gridItem_network_height
 
@@ -269,6 +271,8 @@ FocusScope {
 
             readonly property int _nameColSpan: Math.max((_nbCols - 1) / 2, 1)
 
+            anchors.fill: parent
+
             rowHeight: VLCStyle.tableCoverRow_height
 
             displayMarginEnd: root.displayMarginEnd


=====================================
modules/gui/qt/network/qml/BrowseDisplay.qml
=====================================
@@ -41,6 +41,9 @@ Widgets.PageLoader {
     pageModel: [{
         name: "home",
         url: "qrc:///network/BrowseHomeDisplay.qml"
+    }, {
+        name: "folders",
+        component: browseFolders,
     }, {
         name: "device",
         component: browseDevice,
@@ -76,7 +79,10 @@ Widgets.PageLoader {
                                                                      : null
 
         onSeeAll: {
-            History.push(["mc", "network", "device", { title: title, sd_source: sd_source }])
+            if (sd_source === -1)
+                History.push(["mc", "network", "folders", { title: title }])
+            else
+                History.push(["mc", "network", "device", { title: title, sd_source: sd_source }])
 
             stackView.currentItem.setCurrentItemFocus(reason)
         }
@@ -94,6 +100,23 @@ Widgets.PageLoader {
 
     // Children
 
+    Component {
+        id: browseFolders
+
+        BrowseDeviceView {
+            property var sortModel: [
+                { text: I18n.qtr("Alphabetic"), criteria: "name" },
+                { text: I18n.qtr("Url"),        criteria: "mrl"  }
+            ]
+
+            displayMarginEnd: g_mainDisplay.displayMargin
+
+            model: modelFilter
+
+            sourceModel: StandardPathModel {}
+        }
+    }
+
     Component {
         id: browseDevice
 


=====================================
modules/gui/qt/network/qml/BrowseHomeDisplay.qml
=====================================
@@ -38,7 +38,7 @@ FocusScope {
         { text: I18n.qtr("Url"),        criteria: "mrl" }
     ]
 
-    property alias model: deviceSection.model
+    property alias model: foldersSection.model
 
     focus: true
 
@@ -50,7 +50,12 @@ FocusScope {
     onActiveFocusChanged: resetFocus()
 
     function setCurrentItemFocus(reason) {
-        deviceSection.setCurrentItemFocus(reason);
+        if (foldersSection.visible)
+            foldersSection.setCurrentItemFocus(reason);
+        else if (deviceSection.visible)
+            deviceSection.setCurrentItemFocus(reason);
+        else if (lanSection.visible)
+            lanSection.setCurrentItemFocus(reason);
     }
 
     function _centerFlickableOnItem(item) {
@@ -84,7 +89,11 @@ FocusScope {
     //FIXME use the right xxxLabel class
     T.Label {
         anchors.centerIn: parent
-        visible: (deviceSection.model.count === 0 && lanSection.model.count === 0 )
+
+        visible: (foldersSection.model.count === 0 && deviceSection.model.count === 0
+                  &&
+                  lanSection.model.count === 0)
+
         font.pixelSize: VLCStyle.fontHeight_xxlarge
         color: root.activeFocus ? VLCStyle.colors.accent : VLCStyle.colors.text
         text: I18n.qtr("No network shares found")
@@ -101,6 +110,42 @@ FocusScope {
 
             spacing: VLCStyle.margin_small
 
+            BrowseDeviceView {
+                id: foldersSection
+
+                width: flickable.width
+                height: contentHeight
+
+                maximumRows: root.maximumRows
+
+                visible: (model.count !== 0)
+
+                model: StandardPathModel
+                {
+                    maximumCount: foldersSection.maximumCount
+                }
+
+                title: I18n.qtr("My Folders")
+
+                Navigation.parentItem: root
+
+                Navigation.downAction: function() {
+                    if (deviceSection.visible)
+                        deviceSection.setCurrentItemFocus(Qt.TabFocusReason)
+                    else if (lanSection.visible)
+                        lanSection.setCurrentItemFocus(Qt.TabFocusReason)
+                    else
+                        root.Navigation.defaultNavigationDown()
+                }
+
+                onBrowse: root.browse(tree, reason)
+
+                onSeeAll: root.seeAll(title, -1, reason)
+
+                onActiveFocusChanged: _centerFlickableOnItem(foldersSection)
+                onCurrentIndexChanged: _centerFlickableOnItem(foldersSection)
+            }
+
             BrowseDeviceView {
                 id: deviceSection
 
@@ -122,8 +167,17 @@ FocusScope {
 
                 title: I18n.qtr("My Machine")
 
+                parentFilter: foldersSection.model
+
                 Navigation.parentItem: root
 
+                Navigation.upAction: function() {
+                    if (foldersSection.visible)
+                        foldersSection.setCurrentItemFocus(Qt.TabFocusReason)
+                    else
+                        root.Navigation.defaultNavigationUp()
+                }
+
                 Navigation.downAction: function() {
                     if (lanSection.visible)
                         lanSection.setCurrentItemFocus(Qt.TabFocusReason)
@@ -160,13 +214,15 @@ FocusScope {
 
                 title: I18n.qtr("My LAN")
 
-                parentFilter: deviceSection.modelFilter
+                parentFilter: foldersSection.model
 
                 Navigation.parentItem: root
 
                 Navigation.upAction: function() {
                     if (deviceSection.visible)
                         deviceSection.setCurrentItemFocus(Qt.TabFocusReason)
+                    else if (foldersSection.visible)
+                        foldersSection.setCurrentItemFocus(Qt.TabFocusReason)
                     else
                         root.Navigation.defaultNavigationUp()
                 }
@@ -182,7 +238,7 @@ FocusScope {
     }
 
     function resetFocus() {
-        var widgetlist = [deviceSection, lanSection]
+        var widgetlist = [foldersSection, deviceSection, lanSection]
         var i;
         for (i in widgetlist) {
             if (widgetlist[i].activeFocus && widgetlist[i].visible)


=====================================
modules/gui/qt/network/standardpathmodel.cpp
=====================================
@@ -0,0 +1,167 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ *
+ * 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 "standardpathmodel.hpp"
+
+// VLC includes
+#include "networkmediamodel.hpp"
+
+// Ctor / dtor
+
+StandardPathModel::StandardPathModel(QObject * parent)
+    : ClipListModel(parent)
+{
+    m_comparator = ascendingName;
+
+#ifdef Q_OS_UNIX
+    addItem(QVLCUserDir(VLC_HOME_DIR), qtr("Home"), QUrl());
+#endif
+    addItem(QVLCUserDir(VLC_DESKTOP_DIR), qtr("Desktop"), QUrl());
+    addItem(QVLCUserDir(VLC_DOCUMENTS_DIR), qtr("Documents"), QUrl());
+    addItem(QVLCUserDir(VLC_MUSIC_DIR), qtr("Music"), QUrl());
+    addItem(QVLCUserDir(VLC_VIDEOS_DIR), qtr("Videos"), QUrl());
+    addItem(QVLCUserDir(VLC_DOWNLOAD_DIR), qtr("Download"), QUrl());
+
+    updateItems();
+}
+
+// QAbstractItemModel implementation
+
+QHash<int, QByteArray> StandardPathModel::roleNames() const /* override */
+{
+    return
+    {
+        { PATH_NAME, "name" },
+        { PATH_MRL, "mrl" },
+        { PATH_PROTOCOL, "protocol" },
+        { PATH_TYPE, "type" },
+        { PATH_SOURCE, "source" },
+        { PATH_TREE, "tree" },
+        { PATH_ARTWORK, "artwork" }
+    };
+}
+
+QVariant StandardPathModel::data(const QModelIndex & index, int role) const /* override */
+{
+    int row = index.row();
+
+    if (row < 0 || row >= count())
+        return QVariant();
+
+    const StandardPathItem & item = m_items[row];
+
+    switch (role)
+    {
+        case PATH_NAME:
+            return item.name;
+        case PATH_MRL:
+            return item.mrl;
+        case PATH_PROTOCOL:
+            return item.protocol;
+        case PATH_TYPE:
+            return item.type;
+        case PATH_TREE:
+            return QVariant::fromValue(NetworkTreeItem(item.tree, item.inputItem.get()));
+        case PATH_ARTWORK:
+            return item.artwork;
+        default:
+            return QVariant();
+    }
+}
+
+// Protected ClipListModel implementation
+
+void StandardPathModel::onUpdateSort(const QString & criteria, Qt::SortOrder order) /* override */
+{
+    if (criteria == "mrl")
+    {
+        if (order == Qt::AscendingOrder)
+            m_comparator = ascendingMrl;
+        else
+            m_comparator = descendingMrl;
+    }
+    else
+    {
+        if (order == Qt::AscendingOrder)
+            m_comparator = ascendingName;
+        else
+            m_comparator = descendingName;
+    }
+}
+
+// Private static function
+
+/* static */ bool StandardPathModel::ascendingName(const StandardPathItem & a,
+                                                   const StandardPathItem & b)
+{
+    return (QString::compare(a.name, b.name, Qt::CaseInsensitive) <= 0);
+}
+
+/* static */ bool StandardPathModel::ascendingMrl(const StandardPathItem & a,
+                                                  const StandardPathItem & b)
+{
+    return (QString::compare(a.mrl.toString(), b.mrl.toString(), Qt::CaseInsensitive) <= 0);
+}
+
+/* static */ bool StandardPathModel::descendingName(const StandardPathItem & a,
+                                                    const StandardPathItem & b)
+{
+    return (QString::compare(a.name, b.name, Qt::CaseInsensitive) >= 0);
+}
+
+/* static */ bool StandardPathModel::descendingMrl(const StandardPathItem & a,
+                                                   const StandardPathItem & b)
+{
+    return (QString::compare(a.mrl.toString(), b.mrl.toString(), Qt::CaseInsensitive) >= 0);
+}
+
+// Private functions
+
+void StandardPathModel::addItem(const QString & path, const QString & name, const QUrl & artwork)
+{
+    QUrl url = QUrl::fromLocalFile(path);
+
+    StandardPathItem item;
+
+    item.name = name;
+    item.mrl  = url;
+
+    item.protocol = url.scheme();
+
+    item.type = NetworkDeviceModel::TYPE_DIRECTORY;
+
+    input_item_t * inputItem = input_item_NewDirectory(qtu(url.toString()), qtu(name), ITEM_LOCAL);
+
+    item.inputItem = InputItemPtr(inputItem, false);
+
+    vlc_media_tree_t * tree = vlc_media_tree_New();
+
+    vlc_media_tree_Lock(tree);
+
+    vlc_media_tree_Add(tree, &(tree->root), inputItem);
+
+    vlc_media_tree_Unlock(tree);
+
+    item.tree = MediaTreePtr(tree, false);
+
+    item.artwork = artwork;
+
+    m_items.push_back(item);
+}


=====================================
modules/gui/qt/network/standardpathmodel.hpp
=====================================
@@ -0,0 +1,104 @@
+/*****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ *
+ * 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 STANDARDPATHMODEL_HPP
+#define STANDARDPATHMODEL_HPP
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+// VLC includes
+#include <vlc_media_source.h>
+#include <vlc_cxx_helpers.hpp>
+#include "networkdevicemodel.hpp"
+#include "util/cliplistmodel.hpp"
+
+// Qt includes
+#include <QAbstractListModel>
+#include <QStandardPaths>
+#include <QUrl>
+
+struct StandardPathItem;
+
+class StandardPathModel : public ClipListModel<StandardPathItem>
+{
+    Q_OBJECT
+
+public: // Enums
+    // NOTE: Roles should be aligned with the NetworkDeviceModel.
+    enum Role
+    {
+        PATH_NAME = Qt::UserRole + 1,
+        PATH_MRL,
+        PATH_TYPE,
+        PATH_PROTOCOL,
+        PATH_SOURCE,
+        PATH_TREE,
+        PATH_ARTWORK
+    };
+
+public: // Declarations
+    using InputItemPtr = vlc_shared_data_ptr_type(input_item_t,
+                                                  input_item_Hold,
+                                                  input_item_Release);
+
+    using MediaTreePtr = vlc_shared_data_ptr_type(vlc_media_tree_t,
+                                                  vlc_media_tree_Hold,
+                                                  vlc_media_tree_Release);
+
+public:
+    StandardPathModel(QObject * parent = nullptr);
+
+public: // QAbstractItemModel implementation
+    QHash<int, QByteArray> roleNames() const override;
+
+    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
+
+protected: // ClipListModel implementation
+    void onUpdateSort(const QString & criteria, Qt::SortOrder order) override;
+
+private: // Static functions
+    static bool ascendingName(const StandardPathItem & a, const StandardPathItem & b);
+    static bool ascendingMrl (const StandardPathItem & a, const StandardPathItem & b);
+
+    static bool descendingName(const StandardPathItem & a, const StandardPathItem & b);
+    static bool descendingMrl (const StandardPathItem & a, const StandardPathItem & b);
+
+private: // Functions
+    void addItem(const QString & path, const QString & name, const QUrl & artwork);
+};
+
+struct StandardPathItem
+{
+    QString name;
+    QUrl    mrl;
+
+    QString protocol;
+
+    NetworkDeviceModel::ItemType type;
+
+    StandardPathModel::InputItemPtr inputItem;
+    StandardPathModel::MediaTreePtr tree;
+
+    QUrl artwork;
+};
+
+#endif // STANDARDPATHMODEL_HPP


=====================================
modules/gui/qt/util/cliplistmodel.cpp
=====================================
@@ -0,0 +1,147 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ *
+ * 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 "cliplistmodel.hpp"
+
+// BaseClipListModel
+
+/* explicit */ BaseClipListModel::BaseClipListModel(QObject * parent)
+    : QAbstractListModel(parent) {}
+
+// Interface
+
+void BaseClipListModel::updateItems()
+{
+    int count = implicitCount();
+
+    if (m_count == count) return;
+
+    if (m_count < count)
+        expandItems(count);
+    else
+        shrinkItems(count);
+}
+
+// QAbstractItemModel implementation
+
+int BaseClipListModel::rowCount(const QModelIndex &) const /* override */
+{
+    return count();
+}
+
+// Properties
+
+int BaseClipListModel::count() const
+{
+    return m_count;
+}
+
+int BaseClipListModel::maximumCount() const
+{
+    return m_maximumCount;
+}
+
+void BaseClipListModel::setMaximumCount(int count)
+{
+    if (m_maximumCount == count)
+        return;
+
+    m_maximumCount = count;
+
+    count = implicitCount();
+
+    if (m_count == count)
+    {
+        emit maximumCountChanged();
+
+        return;
+    }
+
+    if (m_count < count)
+        expandItems(count);
+    else
+        shrinkItems(count);
+
+    emit maximumCountChanged();
+}
+
+QString BaseClipListModel::searchPattern() const
+{
+    return m_searchPattern;
+}
+
+void BaseClipListModel::setSearchPattern(const QString & pattern)
+{
+    if (m_searchPattern == pattern)
+        return;
+
+    m_searchPattern = pattern;
+
+    emit searchPatternChanged();
+}
+
+QByteArray BaseClipListModel::searchRole() const
+{
+    return m_searchRole;
+}
+
+void BaseClipListModel::setSearchRole(const QByteArray & role)
+{
+    if (m_searchRole == role)
+        return;
+
+    m_searchRole = role;
+
+    emit searchRoleChanged();
+}
+
+QString BaseClipListModel::sortCriteria() const
+{
+    return m_sortCriteria;
+}
+
+void BaseClipListModel::setSortCriteria(const QString & criteria)
+{
+    if (m_sortCriteria == criteria)
+        return;
+
+    m_sortCriteria = criteria;
+
+    updateSort();
+
+    emit sortCriteriaChanged();
+}
+
+Qt::SortOrder BaseClipListModel::sortOrder() const
+{
+    return m_sortOrder;
+}
+
+void BaseClipListModel::setSortOrder(Qt::SortOrder order)
+{
+    if (m_sortOrder == order)
+        return;
+
+    m_sortOrder = order;
+
+    updateSort();
+
+    emit sortOrderChanged();
+}


=====================================
modules/gui/qt/util/cliplistmodel.hpp
=====================================
@@ -0,0 +1,300 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ *
+ * 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 MODELITEMS_HPP
+#define MODELITEMS_HPP
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+// Qt includes
+#include <QAbstractListModel>
+
+// NOTE: Qt won't let us inherit from QAbstractListModel when declaring a template class. So we
+//       specify a base class for properties and signals.
+class BaseClipListModel : public QAbstractListModel
+{
+    Q_OBJECT
+
+    Q_PROPERTY(int count READ count NOTIFY countChanged)
+
+    Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged)
+
+    Q_PROPERTY(bool hasMoreItems READ hasMoreItems NOTIFY countChanged)
+
+    Q_PROPERTY(QByteArray searchRole READ searchRole WRITE setSearchRole NOTIFY searchRoleChanged)
+
+    Q_PROPERTY(QString searchPattern READ searchPattern WRITE setSearchPattern
+               NOTIFY searchPatternChanged)
+
+    Q_PROPERTY(QString sortCriteria READ sortCriteria WRITE setSortCriteria
+               NOTIFY sortCriteriaChanged)
+
+    Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
+
+public:
+    BaseClipListModel(QObject * parent = nullptr);
+
+public: // Interface
+    // Convenience function for showing or hiding items according to the implicitCount.
+    void updateItems();
+
+public: // Abstract functions
+    virtual int implicitCount() const = 0;
+
+    virtual bool hasMoreItems() const = 0;
+
+public: // QAbstractItemModel implementation
+    int rowCount(const QModelIndex & parent = QModelIndex()) const override;
+
+protected: // Abstract functions
+    virtual void expandItems(int count) = 0;
+    virtual void shrinkItems(int count) = 0;
+
+    virtual void updateSort() = 0;
+
+signals:
+    void countChanged();
+
+    void maximumCountChanged();
+
+    void searchPatternChanged();
+    void searchRoleChanged();
+
+    void sortCriteriaChanged();
+    void sortOrderChanged();
+
+public: // Properties
+    int count() const;
+
+    int maximumCount() const;
+    void setMaximumCount(int count);
+
+    QString searchPattern() const;
+    void setSearchPattern(const QString & pattern);
+
+    QByteArray searchRole() const;
+    void setSearchRole(const QByteArray & role);
+
+    QString sortCriteria() const;
+    void setSortCriteria(const QString & criteria);
+
+    Qt::SortOrder sortOrder() const;
+    void setSortOrder(Qt::SortOrder order);
+
+protected:
+    int m_count = 0;
+
+    int m_maximumCount = -1;
+
+    QString m_searchPattern;
+    QByteArray m_searchRole;
+
+    QString m_sortCriteria = "name";
+
+    Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
+};
+
+// NOTE: This helper adds support for capping, sorting and filtering QAbstractListModel item(s).
+//       We tried implementing capping from a QSortFilterProxyModel and QML, but both
+//       implementations had flaws.
+template <typename T>
+class ClipListModel : public BaseClipListModel
+{
+public:
+    typedef typename std::vector<T>::iterator iterator;
+
+public:
+    explicit ClipListModel(QObject * parent = nullptr);
+
+public: // Interface
+    // NOTE: Convenience function that inserts an item and 'clears' model row(s) depending on the
+    //       maximum count to preserve item capping.
+    void insertItem(iterator it, const T & item);
+
+    // NOTE: Convenience function that removes an item at a given index and updates the iterator.
+    void removeItem(iterator & it, int index);
+
+    // NOTE: Convenience function that clears the items and resets the model.
+    void clearItems();
+
+public: // BaseClipListModel implementation
+    int implicitCount() const override;
+
+    bool hasMoreItems() const override;
+
+protected: // Abstract functions
+    // NOTE: This function has to return a comparator for the current sorting parameters.
+    virtual void onUpdateSort(const QString & criteria, Qt::SortOrder order) = 0;
+
+protected: // BaseClipListModel implementation
+    void expandItems(int count) override;
+    void shrinkItems(int count) override;
+
+    void updateSort() override;
+
+protected:
+    std::vector<T> m_items;
+
+    std::function<bool(const T &, const T &)> m_comparator;
+};
+
+// Ctor / dtor
+
+template <typename T>
+/* explicit */ ClipListModel<T>::ClipListModel(QObject * parent) : BaseClipListModel(parent) {}
+
+// Interface
+
+template <typename T>
+void ClipListModel<T>::insertItem(iterator it, const T & item)
+{
+    int pos = std::distance(m_items.begin(), it);
+
+    if (m_maximumCount != -1 && m_count >= m_maximumCount)
+    {
+        // NOTE: When the position is beyond the maximum count we don't notify the view.
+        if (pos >= m_maximumCount)
+        {
+            m_items.insert(it, std::move(item));
+
+            return;
+        }
+
+        // NOTE: Removing the last item to make room for the new one.
+
+        int index = m_count - 1;
+
+        beginRemoveRows({}, index, index);
+
+        m_count--;
+
+        endRemoveRows();
+
+        emit countChanged();
+    }
+
+    beginInsertRows({}, pos, pos);
+
+    m_items.insert(it, std::move(item));
+
+    m_count++;
+
+    endInsertRows();
+
+    emit countChanged();
+}
+
+template <typename T>
+void ClipListModel<T>::removeItem(iterator & it, int index)
+{
+    if (index < 0 || index >= count())
+        return;
+
+    beginRemoveRows({}, index, index);
+
+    it = m_items.erase(it);
+
+    m_count--;
+
+    endRemoveRows();
+
+    emit countChanged();
+}
+
+template <typename T>
+void ClipListModel<T>::clearItems()
+{
+    if (m_items.empty())
+        return;
+
+    beginResetModel();
+
+    m_items.clear();
+
+    m_count = 0;
+
+    endResetModel();
+
+    emit countChanged();
+}
+
+// BaseClipListModel implementation
+
+template <typename T>
+int ClipListModel<T>::implicitCount() const /* override */
+{
+    assert(m_items.size() < INT32_MAX);
+
+    if (m_maximumCount == -1)
+        return (int) m_items.size();
+    else
+        return qMin((int) m_items.size(), m_maximumCount);
+}
+
+template <typename T>
+bool ClipListModel<T>::hasMoreItems() const /* override */
+{
+    if (m_maximumCount == -1)
+        return false;
+    else
+        return (m_count < (int) m_items.size());
+}
+
+// Protected BaseClipListModel implementation
+
+template <typename T>
+void ClipListModel<T>::expandItems(int count) /* override */
+{
+    beginInsertRows({}, m_count, count - 1);
+
+    m_count = count;
+
+    endInsertRows();
+
+    emit countChanged();
+}
+
+template <typename T>
+void ClipListModel<T>::shrinkItems(int count) /* override */
+{
+    beginRemoveRows({}, count, m_count - 1);
+
+    m_count = count;
+
+    endRemoveRows();
+
+    emit countChanged();
+}
+
+template <typename T>
+void ClipListModel<T>::updateSort() /* override */
+{
+    onUpdateSort(m_sortCriteria, m_sortOrder);
+
+    beginResetModel();
+
+    std::sort(m_items.begin(), m_items.end(), m_comparator);
+
+    endResetModel();
+}
+
+#endif // MODELITEMS_HPP


=====================================
po/POTFILES.in
=====================================
@@ -830,6 +830,8 @@ modules/gui/qt/menus/menus.hpp
 modules/gui/qt/menus/qml/Menubar.qml
 modules/gui/qt/menus/qml_menu_wrapper.cpp
 modules/gui/qt/menus/qml_menu_wrapper.hpp
+modules/gui/qt/network/standardpathmodel.cpp
+modules/gui/qt/network/standardpathmodel.hpp
 modules/gui/qt/network/qml/BrowseDeviceHeader.qml
 modules/gui/qt/network/qml/BrowseDeviceView.qml
 modules/gui/qt/network/qml/BrowseDisplay.qml


=====================================
src/libvlccore.sym
=====================================
@@ -1000,6 +1000,8 @@ vlc_media_tree_Hold
 vlc_media_tree_Release
 vlc_media_tree_Lock
 vlc_media_tree_Unlock
+vlc_media_tree_Add
+vlc_media_tree_Remove
 vlc_media_tree_Find
 vlc_media_tree_Preparse
 vlc_media_tree_PreparseCancel



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/2d151758edcd66a6e8cbc521e6d79abf0fb4ded1...c49873e4a409df7733d526295d3db8916776253d

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/2d151758edcd66a6e8cbc521e6d79abf0fb4ded1...c49873e4a409df7733d526295d3db8916776253d
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