[vlc-devel] [PATCH 12/12] qt: playlist: implement Qt playlist list model

Romain Vimont rom1v at videolabs.io
Thu Oct 11 23:14:50 CEST 2018


Wrap the playlist, playlist items and media (input items) into C++
classes, for convenience and automatic memory management.

Use them to implement a list model to be used by a Qt list view.
---
 modules/gui/qt/Makefile.am                    |   8 +
 .../gui/qt/components/playlist_new/media.hpp  |  78 ++++++
 .../qt/components/playlist_new/playlist.cpp   | 261 ++++++++++++++++++
 .../qt/components/playlist_new/playlist.hpp   | 104 +++++++
 .../components/playlist_new/playlist_item.hpp |  96 +++++++
 .../playlist_new/playlist_model.cpp           | 184 ++++++++++++
 .../playlist_new/playlist_model.hpp           |  80 ++++++
 modules/gui/qt/qt.cpp                         |  16 ++
 8 files changed, 827 insertions(+)
 create mode 100644 modules/gui/qt/components/playlist_new/media.hpp
 create mode 100644 modules/gui/qt/components/playlist_new/playlist.cpp
 create mode 100644 modules/gui/qt/components/playlist_new/playlist.hpp
 create mode 100644 modules/gui/qt/components/playlist_new/playlist_item.hpp
 create mode 100644 modules/gui/qt/components/playlist_new/playlist_model.cpp
 create mode 100644 modules/gui/qt/components/playlist_new/playlist_model.hpp

diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index a37baeb1a9..d5476e7508 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -120,6 +120,14 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/components/playlist/selector.cpp \
 	gui/qt/components/playlist/selector.hpp \
 	gui/qt/components/playlist/sorting.h \
+	gui/qt/components/playlist_new/media.hpp \
+	gui/qt/components/playlist_new/playlist.cpp \
+	gui/qt/components/playlist_new/playlist.hpp \
+	gui/qt/components/playlist_new/playlist.moc.cpp \
+	gui/qt/components/playlist_new/playlist_item.hpp \
+	gui/qt/components/playlist_new/playlist_model.cpp \
+	gui/qt/components/playlist_new/playlist_model.hpp \
+	gui/qt/components/playlist_new/playlist_model.moc.cpp \
 	gui/qt/components/sout/profile_selector.cpp \
 	gui/qt/components/sout/profile_selector.hpp \
 	gui/qt/components/sout/sout_widgets.cpp \
diff --git a/modules/gui/qt/components/playlist_new/media.hpp b/modules/gui/qt/components/playlist_new/media.hpp
new file mode 100644
index 0000000000..03f2a4b47f
--- /dev/null
+++ b/modules/gui/qt/components/playlist_new/media.hpp
@@ -0,0 +1,78 @@
+/*****************************************************************************
+ * media_item.hpp
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLC_QT_MEDIA_HPP_
+#define VLC_QT_MEDIA_HPP_
+
+#include <vlc_cxx_helpers.hpp>
+#include <vlc_common.h>
+#include <vlc_input_item.h>
+#include <QString>
+
+namespace vlc {
+  namespace playlist {
+
+using InputItemPtr = vlc_shared_data_ptr_type(input_item_t,
+                                              input_item_Hold,
+                                              input_item_Release);
+
+class Media
+{
+public:
+    Media(input_item_t *media = nullptr)
+    {
+        if (media)
+        {
+            /* the media must be unique in the playlist */
+            ptr.reset(input_item_Copy(media), false);
+            if (!ptr)
+                throw std::bad_alloc();
+        }
+    }
+
+    Media(QString uri, QString name)
+    {
+        auto uUri = uri.toUtf8();
+        auto uName = name.toUtf8();
+        const char *rawUri = uUri.isNull() ? nullptr : uUri.constData();
+        const char *rawName = uName.isNull() ? nullptr : uName.constData();
+        ptr.reset(input_item_New(rawUri, rawName), false);
+        if (!ptr)
+            throw std::bad_alloc();
+    }
+
+    operator bool() const
+    {
+        return ptr;
+    }
+
+    input_item_t *raw() const
+    {
+        return ptr.get();
+    }
+
+private:
+    InputItemPtr ptr;
+};
+
+  } // namespace playlist
+} // namespace vlc
+
+#endif
diff --git a/modules/gui/qt/components/playlist_new/playlist.cpp b/modules/gui/qt/components/playlist_new/playlist.cpp
new file mode 100644
index 0000000000..08235cec5e
--- /dev/null
+++ b/modules/gui/qt/components/playlist_new/playlist.cpp
@@ -0,0 +1,261 @@
+/*****************************************************************************
+ * playlist.cpp
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "playlist.hpp"
+#include <algorithm>
+
+namespace vlc {
+  namespace playlist {
+
+static QVector<PlaylistItem> toVec(vlc_playlist_item_t *const items[],
+                                   size_t len)
+{
+    QVector<PlaylistItem> vec;
+    for (size_t i = 0; i < len; ++i)
+        vec.push_back(items[i]);
+    return vec;
+}
+
+extern "C" { // for C callbacks
+
+static void
+on_playlist_items_reset(vlc_playlist_t *playlist,
+                        vlc_playlist_item_t *const items[],
+                        size_t len, void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistItemsReset(toVec(items, len));
+}
+
+static void
+on_playlist_items_added(vlc_playlist_t *playlist, size_t index,
+                        vlc_playlist_item_t *const items[], size_t len,
+                        void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistItemsAdded(index, toVec(items, len));
+}
+
+static void
+on_playlist_items_moved(vlc_playlist_t *playlist, size_t index, size_t count,
+                        size_t target, void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistItemsMoved(index, count, target);
+}
+
+static void
+on_playlist_items_removed(vlc_playlist_t *playlist, size_t index, size_t count,
+                          void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistItemsRemoved(index, count);
+}
+
+static void
+on_playlist_items_updated(vlc_playlist_t *playlist, size_t index,
+                          vlc_playlist_item_t *const items[], size_t len,
+                          void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistItemsUpdated(index, toVec(items, len));
+}
+
+static void
+on_playlist_playback_repeat_changed(vlc_playlist_t *playlist,
+                                    enum vlc_playlist_playback_repeat repeat,
+                                    void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistPlaybackRepeatChanged(repeat);
+}
+
+static void
+on_playlist_playback_order_changed(vlc_playlist_t *playlist,
+                                   enum vlc_playlist_playback_order order,
+                                   void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistPlaybackOrderChanged(order);
+}
+
+static void
+on_playlist_current_item_changed(vlc_playlist_t *playlist, ssize_t index,
+                                 void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistCurrentItemChanged(index);
+}
+
+static void
+on_playlist_has_prev_changed(vlc_playlist_t *playlist, bool has_prev,
+                             void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistHasPrevChanged(has_prev);
+}
+
+static void
+on_playlist_has_next_changed(vlc_playlist_t *playlist, bool has_next,
+                             void *userdata)
+{
+    VLC_UNUSED(playlist);
+    Playlist *this_ = static_cast<Playlist *>(userdata);
+    emit this_->playlistHasNextChanged(has_next);
+}
+
+} // extern "C"
+
+static const struct vlc_playlist_callbacks playlist_callbacks = {
+    /* C++ (before C++20) does not support designated initializers */
+    on_playlist_items_reset,
+    on_playlist_items_added,
+    on_playlist_items_moved,
+    on_playlist_items_removed,
+    on_playlist_items_updated,
+    on_playlist_playback_repeat_changed,
+    on_playlist_playback_order_changed,
+    on_playlist_current_item_changed,
+    on_playlist_has_prev_changed,
+    on_playlist_has_next_changed,
+};
+
+Playlist::Playlist(vlc_playlist_t *playlist, QObject *parent)
+    : QObject(parent)
+    , playlist(playlist)
+{
+    PlaylistLocker locker(this);
+    listener = vlc_playlist_AddListener(playlist, &playlist_callbacks, this,
+                                        false);
+    if (!listener)
+        throw std::bad_alloc();
+}
+
+Playlist::~Playlist()
+{
+    PlaylistLocker locker(this);
+    vlc_playlist_RemoveListener(playlist, listener);
+}
+
+void Playlist::lock()
+{
+    vlc_playlist_Lock(playlist);
+}
+
+void Playlist::unlock()
+{
+    vlc_playlist_Unlock(playlist);
+}
+
+vlc_playlist_t *
+Playlist::raw()
+{
+    return playlist;
+}
+
+template <typename RAW, typename WRAPPER>
+static QVector<RAW> toRaw(const QVector<WRAPPER> &items)
+{
+    QVector<RAW> vec;
+    int count = items.size();
+    vec.reserve(count);
+    for (int i = 0; i < count; ++i)
+        vec.push_back(items[i].raw());
+    return vec;
+}
+
+void
+Playlist::append(const QVector<Media> &media)
+{
+    PlaylistLocker locker(this);
+
+    auto rawMedia = toRaw<input_item_t *>(media);
+    int ret = vlc_playlist_Append(playlist,
+                                  rawMedia.constData(), rawMedia.size());
+    if (ret != VLC_SUCCESS)
+        throw std::bad_alloc();
+}
+
+void
+Playlist::insert(size_t index, const QVector<Media> &media)
+{
+    PlaylistLocker locker(this);
+
+    auto rawMedia = toRaw<input_item_t *>(media);
+    int ret = vlc_playlist_RequestInsert(playlist, index,
+                                         rawMedia.constData(), rawMedia.size());
+    if (ret != VLC_SUCCESS)
+        throw std::bad_alloc();
+}
+
+void
+Playlist::move(const QVector<PlaylistItem> &items, size_t target,
+               ssize_t indexHint)
+{
+    PlaylistLocker locker(this);
+
+    auto rawItems = toRaw<vlc_playlist_item_t *>(items);
+    int ret = vlc_playlist_RequestMove(playlist, rawItems.constData(),
+                                       rawItems.size(), target, indexHint);
+    if (ret != VLC_SUCCESS)
+        throw std::bad_alloc();
+}
+
+void
+Playlist::remove(const QVector<PlaylistItem> &items, ssize_t indexHint)
+{
+    PlaylistLocker locker(this);
+
+    auto rawItems = toRaw<vlc_playlist_item_t *>(items);
+    int ret = vlc_playlist_RequestRemove(playlist, rawItems.constData(),
+                                         rawItems.size(), indexHint);
+    if (ret != VLC_SUCCESS)
+        throw std::bad_alloc();
+}
+
+void
+Playlist::shuffle()
+{
+    PlaylistLocker locker(this);
+    vlc_playlist_Shuffle(playlist);
+}
+
+void
+Playlist::sort(const QVector<vlc_playlist_sort_criterion> &criteria)
+{
+    PlaylistLocker locker(this);
+    vlc_playlist_Sort(playlist, criteria.constData(), criteria.size());
+}
+
+  } // namespace playlist
+} // namespace vlc
diff --git a/modules/gui/qt/components/playlist_new/playlist.hpp b/modules/gui/qt/components/playlist_new/playlist.hpp
new file mode 100644
index 0000000000..9e1dfab9af
--- /dev/null
+++ b/modules/gui/qt/components/playlist_new/playlist.hpp
@@ -0,0 +1,104 @@
+/*****************************************************************************
+ * playlist.hpp
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLC_QT_PLAYLIST_NEW_HPP_
+#define VLC_QT_PLAYLIST_NEW_HPP_
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <QObject>
+#include <QVector>
+#include <vlc_playlist_new.h>
+#include "media.hpp"
+#include "playlist_item.hpp"
+
+namespace vlc {
+  namespace playlist {
+
+/**
+ * Core playlist wrapper.
+ *
+ * This wrapper redispatches the core playlist events as Qt signals, and
+ * exposes functions to apply changes on the playlist.
+ *
+ * When a user requests to insert, move or remove items, before the core
+ * playlist lock is successfully acquired, another client may have changed the
+ * list. Therefore, this wrapper calls the vlc_playlist_Request*() functions
+ * from the core playlist to solve conflicts automatically.
+ *
+ * The actual changes applied are notified through the callbacks (signals).
+ */
+class Playlist : public QObject
+{
+    Q_OBJECT
+
+public:
+    Playlist(vlc_playlist_t *playlist, QObject *parent = nullptr);
+    ~Playlist();
+
+    vlc_playlist_t *raw();
+
+    void lock();
+    void unlock();
+
+    void append(const QVector<Media> &);
+    void insert(size_t index, const QVector<Media> &);
+    void move(const QVector<PlaylistItem> &, size_t target, ssize_t indexHint);
+    void remove(const QVector<PlaylistItem> &, ssize_t indexHint);
+
+    void shuffle();
+    void sort(const QVector<vlc_playlist_sort_criterion> &);
+
+signals:
+    void playlistItemsReset(QVector<PlaylistItem>);
+    void playlistItemsAdded(size_t index, QVector<PlaylistItem>);
+    void playlistItemsMoved(size_t index, size_t count, size_t target);
+    void playlistItemsRemoved(size_t index, size_t count);
+    void playlistItemsUpdated(size_t index, QVector<PlaylistItem>);
+    void playlistPlaybackRepeatChanged(enum vlc_playlist_playback_repeat);
+    void playlistPlaybackOrderChanged(enum vlc_playlist_playback_order);
+    void playlistCurrentItemChanged(ssize_t index);
+    void playlistHasPrevChanged(bool hasPrev);
+    void playlistHasNextChanged(bool hasNext);
+
+private:
+    vlc_playlist_t *playlist;
+    vlc_playlist_listener_id *listener;
+};
+
+class PlaylistLocker {
+    Playlist *playlist;
+public:
+    PlaylistLocker(Playlist *playlist) : playlist(playlist)
+    {
+        playlist->lock();
+    }
+    ~PlaylistLocker()
+    {
+        playlist->unlock();
+    }
+};
+
+  } // namespace playlist
+} // namespace vlc
+
+#endif
diff --git a/modules/gui/qt/components/playlist_new/playlist_item.hpp b/modules/gui/qt/components/playlist_new/playlist_item.hpp
new file mode 100644
index 0000000000..936cb1ec41
--- /dev/null
+++ b/modules/gui/qt/components/playlist_new/playlist_item.hpp
@@ -0,0 +1,96 @@
+/*****************************************************************************
+ * playlist_item.hpp
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLC_QT_PLAYLIST_NEW_ITEM_HPP_
+#define VLC_QT_PLAYLIST_NEW_ITEM_HPP_
+
+#include <vlc_cxx_helpers.hpp>
+#include <vlc_input_item.h>
+#include <vlc_playlist_new.h>
+#include <QExplicitlySharedDataPointer>
+
+namespace vlc {
+  namespace playlist {
+
+using PlaylistItemPtr = vlc_shared_data_ptr_type(vlc_playlist_item_t,
+                                                 vlc_playlist_item_Hold,
+                                                 vlc_playlist_item_Release);
+
+/**
+ * Playlist item wrapper.
+ *
+ * It contains both the PlaylistItemPtr and cached data saved while the playlist
+ * is locked, so that the fields may be read without synchronization or race
+ * conditions.
+ */
+class PlaylistItem
+{
+public:
+    PlaylistItem(vlc_playlist_item_t *item = nullptr)
+    {
+        if (item)
+        {
+            d = new Data();
+            d->item.reset(item);
+            sync();
+        }
+    }
+
+    operator bool() const
+    {
+        return d;
+    }
+
+    vlc_playlist_item_t *raw() const {
+        return d ? d->item.get() : nullptr;
+    }
+
+    QString getTitle() const
+    {
+        return d->title;
+    }
+
+    void sync() {
+        input_item_t *media = vlc_playlist_item_GetMedia(d->item.get());
+        vlc_mutex_lock(&media->lock);
+        d->title = media->psz_name;
+        vlc_mutex_unlock(&media->lock);
+    }
+
+private:
+    struct Data : public QSharedData {
+        PlaylistItemPtr item;
+
+        /* cached values */
+        QString title;
+    };
+
+    QExplicitlySharedDataPointer<Data> d;
+};
+
+/* PlaylistItem has the same size as a raw pointer */
+static_assert(sizeof(PlaylistItem) == sizeof(void *));
+
+  } // namespace playlist
+} // namespace vlc
+
+Q_DECLARE_METATYPE(vlc::playlist::PlaylistItem);
+
+#endif
diff --git a/modules/gui/qt/components/playlist_new/playlist_model.cpp b/modules/gui/qt/components/playlist_new/playlist_model.cpp
new file mode 100644
index 0000000000..be14f63273
--- /dev/null
+++ b/modules/gui/qt/components/playlist_new/playlist_model.cpp
@@ -0,0 +1,184 @@
+/*****************************************************************************
+ * playlist_model.cpp
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "playlist_model.hpp"
+#include <algorithm>
+#include <assert.h>
+
+namespace vlc {
+  namespace playlist {
+
+PlaylistModel::PlaylistModel(Playlist *playlist, QObject *parent)
+    : QAbstractListModel(parent)
+    , playlist(playlist)
+{
+    /* Do not use a Qt::AutoConnection, because in case the changes have been
+     * requested from the Qt UI thread, the slot will be executed directly, like
+     * with a Qt::DirectConnection, which would possibly break the order in
+     * which the events received by the core playlist are handled. In that case,
+     * the indices provided by the events would be invalid. */
+    connect(playlist, &Playlist::playlistItemsReset,
+            this, &PlaylistModel::onPlaylistItemsReset,
+            Qt::QueuedConnection);
+    connect(playlist, &Playlist::playlistItemsAdded,
+            this, &PlaylistModel::onPlaylistItemsAdded,
+            Qt::QueuedConnection);
+    connect(playlist, &Playlist::playlistItemsMoved,
+            this, &PlaylistModel::onPlaylistItemsMoved,
+            Qt::QueuedConnection);
+    connect(playlist, &Playlist::playlistItemsRemoved,
+            this, &PlaylistModel::onPlaylistItemsRemoved,
+            Qt::QueuedConnection);
+    connect(playlist, &Playlist::playlistItemsUpdated,
+            this, &PlaylistModel::onPlaylistItemsUpdated,
+            Qt::QueuedConnection);
+    connect(playlist, &Playlist::playlistCurrentItemChanged,
+            this, &PlaylistModel::onPlaylistCurrentItemChanged,
+            Qt::QueuedConnection);
+}
+
+void
+PlaylistModel::notifyItemsChanged(int idx, int count, const QVector<int> &roles)
+{
+    QModelIndex first = index(idx, 0);
+    QModelIndex last = index(idx + count - 1);
+    emit dataChanged(first, last, roles);
+}
+
+void
+PlaylistModel::onPlaylistItemsReset(QVector<PlaylistItem> newContent)
+{
+    beginResetModel();
+    items.swap(newContent);
+    endResetModel();
+}
+
+void
+PlaylistModel::onPlaylistItemsAdded(size_t index, QVector<PlaylistItem> added)
+{
+    int count = added.size();
+    beginInsertRows({}, index, index + count - 1);
+    items.insert(index, count, nullptr);
+    std::move(added.cbegin(), added.cend(), items.begin() + index);
+    endInsertRows();
+}
+
+void
+PlaylistModel::onPlaylistItemsMoved(size_t index, size_t count, size_t target)
+{
+    size_t qtTarget = target;
+    if (qtTarget > index)
+        /* Qt interprets the target index as the index of the insertion _before_
+         * the move, while the playlist core interprets it as the new index of
+         * the slice _after_ the move. */
+        qtTarget += count;
+
+    beginMoveRows({}, index, index + count - 1, {}, qtTarget);
+    if (index < target)
+        std::rotate(items.begin() + index,
+                    items.begin() + index + count,
+                    items.begin() + target + count);
+    else
+        std::rotate(items.begin() + target,
+                    items.begin() + index,
+                    items.begin() + index + count);
+    endMoveRows();
+}
+
+void
+PlaylistModel::onPlaylistItemsRemoved(size_t index, size_t count)
+{
+    beginRemoveRows({}, index, index + count - 1);
+    items.remove(index, count);
+    endRemoveRows();
+}
+
+void
+PlaylistModel::onPlaylistItemsUpdated(size_t index,
+                                      QVector<PlaylistItem> updated)
+{
+    int count = updated.size();
+    for (int i = 0; i < count; ++i)
+    {
+        assert(items[index + i].raw() == updated[i].raw());
+        items[index + i] = updated[i]; /* sync metadata */
+    }
+    notifyItemsChanged(index, count);
+}
+
+void
+PlaylistModel::onPlaylistCurrentItemChanged(ssize_t index)
+{
+    ssize_t oldCurrent = current;
+    current = index;
+    if (oldCurrent != -1)
+        notifyItemsChanged(oldCurrent, 1, {IsCurrentRole});
+    if (index != -1)
+        notifyItemsChanged(index, 1, {IsCurrentRole});
+}
+
+QHash<int, QByteArray>
+PlaylistModel::roleNames() const
+{
+    return {
+        { TitleRole, "title" },
+        { IsCurrentRole, "isCurrent" },
+    };
+}
+
+int
+PlaylistModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent);
+    return items.size();
+}
+
+const PlaylistItem &
+PlaylistModel::itemAt(int index) const
+{
+    return items[index];
+}
+
+int
+PlaylistModel::count() const
+{
+    return rowCount();
+}
+
+QVariant
+PlaylistModel::data(const QModelIndex &index, int role) const
+{
+    switch (role)
+    {
+        case TitleRole:
+            return items[index.row()].getTitle();
+        case IsCurrentRole:
+            return index.row() != -1 && index.row() == current;
+        default:
+            return {};
+    }
+}
+
+  } // namespace playlist
+} // namespace vlc
diff --git a/modules/gui/qt/components/playlist_new/playlist_model.hpp b/modules/gui/qt/components/playlist_new/playlist_model.hpp
new file mode 100644
index 0000000000..b7cafe3b78
--- /dev/null
+++ b/modules/gui/qt/components/playlist_new/playlist_model.hpp
@@ -0,0 +1,80 @@
+/*****************************************************************************
+ * playlist_model.hpp
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLC_QT_PLAYLIST_NEW_MODEL_HPP_
+#define VLC_QT_PLAYLIST_NEW_MODEL_HPP_
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <QAbstractListModel>
+#include <QVector>
+#include "playlist.hpp"
+
+namespace vlc {
+  namespace playlist {
+
+class PlaylistModel : public QAbstractListModel
+{
+    Q_OBJECT
+    Q_ENUMS(Roles)
+
+public:
+    enum Roles
+    {
+        TitleRole = Qt::UserRole,
+        IsCurrentRole,
+    };
+
+    PlaylistModel(Playlist *playlist, QObject *parent = nullptr);
+
+    QHash<int, QByteArray> roleNames() const override;
+    int rowCount(const QModelIndex &parent = {}) const override;
+    QVariant data(const QModelIndex &index,
+                  int role = Qt::DisplayRole) const override;
+
+    /* provided for convenience */
+    const PlaylistItem &itemAt(int index) const;
+    int count() const;
+
+private slots:
+    void onPlaylistItemsReset(QVector<PlaylistItem>);
+    void onPlaylistItemsAdded(size_t index, QVector<PlaylistItem>);
+    void onPlaylistItemsMoved(size_t index, size_t count, size_t target);
+    void onPlaylistItemsRemoved(size_t index, size_t count);
+    void onPlaylistItemsUpdated(size_t index, QVector<PlaylistItem>);
+    void onPlaylistCurrentItemChanged(ssize_t index);
+
+private:
+    void notifyItemsChanged(int index, int count,
+                            const QVector<int> &roles = {});
+
+    Playlist *playlist;
+
+    /* access only from the UI thread */
+    QVector<PlaylistItem> items;
+    ssize_t current = -1;
+};
+
+  } // namespace playlist
+} // namespace vlc
+
+#endif
diff --git a/modules/gui/qt/qt.cpp b/modules/gui/qt/qt.cpp
index 6fdbda06fa..42cb126d0a 100644
--- a/modules/gui/qt/qt.cpp
+++ b/modules/gui/qt/qt.cpp
@@ -60,6 +60,9 @@ extern "C" char **environ;
 #include "util/qvlcapp.hpp"     /* QVLCApplication definition */
 #include "components/playlist/playlist_model.hpp" /* for ~PLModel() */
 
+#include <QVector>
+#include "components/playlist_new/playlist_item.hpp"
+
 #include <vlc_plugin.h>
 #include <vlc_vout_window.h>
 #ifndef X_DISPLAY_MISSING
@@ -488,6 +491,17 @@ static void Close( vlc_object_t *p_this )
     busy = false;
 }
 
+static inline void qRegisterMetaTypes()
+{
+    // register all types used by signal/slots
+    qRegisterMetaType<size_t>("size_t");
+    qRegisterMetaType<ssize_t>("ssize_t");
+    qRegisterMetaType<vlc::playlist::PlaylistItem>("PlaylistItem");
+    qRegisterMetaType<QVector<vlc::playlist::PlaylistItem>>("QVector<PlaylistItem>");
+    qRegisterMetaType<enum vlc_playlist_playback_order>("vlc_playlist_playback_order");
+    qRegisterMetaType<enum vlc_playlist_playback_repeat>("vlc_playlist_playback_repeat");
+}
+
 static void *Thread( void *obj )
 {
     intf_thread_t *p_intf = (intf_thread_t *)obj;
@@ -624,6 +638,8 @@ static void *Thread( void *obj )
     if( s_style.compare("") != 0 )
         QApplication::setStyle( s_style );
 
+    qRegisterMetaTypes();
+
     /* Launch */
     app.exec();
 
-- 
2.19.1



More information about the vlc-devel mailing list