[vlc-devel] [PATCH 8/9] qt: medialib: Add a bookmark model

Hugo Beauzée-Luyssen hugo at beauzee.fr
Thu Jun 18 17:40:22 CEST 2020


---
 modules/gui/qt/Makefile.am                    |   2 +
 .../gui/qt/medialibrary/mlbookmarkmodel.cpp   | 343 ++++++++++++++++++
 .../gui/qt/medialibrary/mlbookmarkmodel.hpp   |  93 +++++
 3 files changed, 438 insertions(+)
 create mode 100644 modules/gui/qt/medialibrary/mlbookmarkmodel.cpp
 create mode 100644 modules/gui/qt/medialibrary/mlbookmarkmodel.hpp

diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index 800b96b633..f0ea2f5749 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -140,6 +140,8 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/medialibrary/mlartistmodel.hpp \
 	gui/qt/medialibrary/mlbasemodel.cpp \
 	gui/qt/medialibrary/mlbasemodel.hpp \
+	gui/qt/medialibrary/mlbookmarkmodel.cpp \
+	gui/qt/medialibrary/mlbookmarkmodel.hpp \
 	gui/qt/medialibrary/mlfoldersmodel.cpp \
 	gui/qt/medialibrary/mlfoldersmodel.hpp \
 	gui/qt/medialibrary/mlgenre.cpp \
diff --git a/modules/gui/qt/medialibrary/mlbookmarkmodel.cpp b/modules/gui/qt/medialibrary/mlbookmarkmodel.cpp
new file mode 100644
index 0000000000..9c4a6ccca7
--- /dev/null
+++ b/modules/gui/qt/medialibrary/mlbookmarkmodel.cpp
@@ -0,0 +1,343 @@
+/*****************************************************************************
+ * Copyright (C) 2019 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.
+ *****************************************************************************/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "mlbookmarkmodel.hpp"
+#include "qt.hpp"
+
+#include <vlc_cxx_helpers.hpp>
+
+#include "mlhelper.hpp"
+
+MLBookmarkModel::MLBookmarkModel( vlc_medialibrary_t *ml, vlc_player_t *player,
+                                  QObject *parent )
+    : QAbstractListModel( parent )
+    , m_ml( ml )
+    , m_player( player )
+    , m_listener( nullptr )
+    , m_currentItem( nullptr, &input_item_Release )
+    , m_currentMediaId( 0 )
+    , m_sort( VLC_ML_SORTING_INSERTIONDATE )
+    , m_desc( false )
+{
+    static const vlc_player_cbs cbs {
+        &onCurrentMediaChanged,
+        &onPlaybackStateChanged,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+    };
+    {
+        vlc_player_locker lock{ m_player };
+        m_listener = vlc_player_AddListener( m_player, &cbs, this );
+        if ( m_listener == nullptr )
+            throw std::bad_alloc{};
+        auto currentItem = vlc_player_GetCurrentMedia( m_player );
+        m_currentItem = vlc::wrap_cptr( currentItem ? input_item_Hold( currentItem ) : nullptr,
+                                        &input_item_Release );
+    }
+    refresh( false );
+}
+
+MLBookmarkModel::~MLBookmarkModel()
+{
+    vlc_player_locker lock{ m_player };
+    vlc_player_RemoveListener( m_player, m_listener );
+}
+
+QVariant MLBookmarkModel::data( const QModelIndex &index, int role ) const
+{
+    if ( !index.isValid() || index.row() < 0 ||
+         m_bookmarks == nullptr ||
+         (uint32_t)index.row() >= m_bookmarks->i_nb_items )
+    {
+        return QVariant{};
+    }
+
+    const auto& bookmark = m_bookmarks->p_items[index.row()];
+    if ( role != Qt::DisplayRole )
+        return QVariant{};
+
+    switch ( index.column() )
+    {
+    case 0:
+        return QVariant::fromValue( QString::fromUtf8( bookmark.psz_name ) );
+    case 1:
+        return QVariant::fromValue( MsToString( bookmark.i_time ) );
+    case 2:
+        return QVariant::fromValue( QString::fromUtf8( bookmark.psz_description ) );
+    default:
+        return QVariant{};
+    }
+}
+
+bool MLBookmarkModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if ( index.isValid() == false )
+        return false;
+    if ( role != Qt::EditRole )
+        return false;
+    if ( index.column() == 1 )
+        /* Disable editing the Time value through the listing */
+        return false;
+    if ( value.canConvert<QString>() == false )
+        return false;
+    assert( index.column() == 0 || index.column() == 2 );
+    assert( (size_t)index.row() < m_bookmarks->i_nb_items );
+    auto& b = m_bookmarks->p_items[index.row()];
+    auto str = value.toString();
+    if ( index.column() == 0 )
+    {
+        if ( vlc_ml_media_update_bookmark( m_ml, b.i_media_id, b.i_time,
+                                           qtu( str ), nullptr ) != VLC_SUCCESS )
+            return false;
+        free( b.psz_name );
+        b.psz_name = strdup( qtu( str ) );
+        return true;
+    }
+    if ( vlc_ml_media_update_bookmark( m_ml, b.i_media_id, b.i_time,
+                                       nullptr, qtu( str ) ) != VLC_SUCCESS )
+        return false;
+    free( b.psz_description );
+    b.psz_description = strdup( qtu( str ) );
+    return true;
+}
+
+Qt::ItemFlags MLBookmarkModel::flags( const QModelIndex& index ) const
+{
+    auto f = QAbstractItemModel::flags( index );
+    if ( index.column() != 1 )
+        f |= Qt::ItemIsEditable;
+    return f;
+}
+
+int MLBookmarkModel::rowCount(const QModelIndex &) const
+{
+    if ( m_bookmarks == nullptr )
+        return 0;
+    return m_bookmarks->i_nb_items;
+}
+
+int MLBookmarkModel::columnCount(const QModelIndex &) const
+{
+    return 3;
+}
+
+QModelIndex MLBookmarkModel::index( int row, int column, const QModelIndex& ) const
+{
+    return createIndex( row, column, nullptr );
+}
+
+QModelIndex MLBookmarkModel::parent(const QModelIndex &) const
+{
+    return createIndex( -1, -1, nullptr );
+}
+
+QVariant MLBookmarkModel::headerData( int section, Qt::Orientation orientation,
+                                      int role ) const
+{
+    if ( role != Qt::DisplayRole )
+        return QVariant{};
+    if ( orientation == Qt::Vertical )
+        return QVariant{};
+    switch ( section )
+    {
+        case 0:
+            return QVariant::fromValue( qtr( "Name" ) );
+        case 1:
+            return QVariant::fromValue( qtr( "Time" ) );
+        case 2:
+            return QVariant::fromValue( qtr( "Description" ) );
+        default:
+            return QVariant{};
+    }
+}
+
+void MLBookmarkModel::sort( int column, Qt::SortOrder order )
+{
+    vlc::threads::mutex_locker lock{ m_mutex };
+
+    switch ( column )
+    {
+        case 0:
+            m_sort = VLC_ML_SORTING_ALPHA;
+            break;
+        case 1:
+            // For bookmarks, default sort order is their time in the media
+            m_sort = VLC_ML_SORTING_DEFAULT;
+            break;
+        case 2:
+            // We don't support ordering by description, just ignore it
+            return;
+        default:
+            // In all other cases, just list the items as they are inserted
+            m_sort = VLC_ML_SORTING_INSERTIONDATE;
+            break;
+    }
+    m_desc = order == Qt::DescendingOrder ? true : false;
+    refresh( false );
+}
+
+void MLBookmarkModel::add()
+{
+    vlc_tick_t currentTime;
+    {
+        vlc_player_locker lock{ m_player };
+        currentTime = vlc_player_GetTime( m_player );
+    }
+
+    {
+        vlc::threads::mutex_locker lock{ m_mutex };
+        if ( m_currentItem == nullptr )
+            return;
+        if ( m_currentMediaId == 0 )
+        {
+            auto mlMedia = vlc_ml_get_media_by_mrl( m_ml, m_currentItem->psz_uri );
+            if ( mlMedia == nullptr )
+                return;
+            m_currentMediaId = mlMedia->i_id;
+        }
+    }
+    vlc_ml_media_add_bookmark( m_ml, m_currentMediaId,
+                               MS_FROM_VLC_TICK( currentTime ) );
+    refresh( false );
+}
+
+void MLBookmarkModel::remove( const QModelIndexList &indexes )
+{
+    int64_t mediaId;
+    {
+        vlc::threads::mutex_locker lock{ m_mutex };
+        mediaId = m_currentMediaId;
+    }
+    for ( const auto& i : indexes )
+    {
+        if ( i.isValid() == false || (size_t)i.row() >= m_bookmarks->i_nb_items )
+            continue;
+        auto& b = m_bookmarks->p_items[i.row()];
+        vlc_ml_media_remove_bookmark( m_ml, mediaId, b.i_time );
+    }
+    refresh( false );
+}
+
+void MLBookmarkModel::clear()
+{
+    int64_t mediaId;
+    {
+        vlc::threads::mutex_locker lock{ m_mutex };
+        mediaId = m_currentMediaId;
+    }
+    beginResetModel();
+    vlc_ml_media_remove_all_bookmarks( m_ml, mediaId );
+    m_bookmarks.reset();
+    endResetModel();
+}
+
+void MLBookmarkModel::select(const QModelIndex &index)
+{
+    if ( index.isValid() == false )
+        return;
+    const auto& b = m_bookmarks->p_items[index.row()];
+    vlc_player_locker lock{ m_player };
+    vlc_player_SetTime( m_player, VLC_TICK_FROM_MS( b.i_time ) );
+}
+
+void MLBookmarkModel::onCurrentMediaChanged( vlc_player_t*, input_item_t* media,
+                                             void *data )
+{
+    auto self = static_cast<MLBookmarkModel*>( data );
+
+    {
+        vlc::threads::mutex_locker lock{ self->m_mutex };
+        self->m_currentItem.reset( media ? input_item_Hold( media ) : nullptr );
+        if ( media == nullptr )
+            self->m_currentMediaId = 0;
+    }
+    self->refresh( false );
+}
+
+void MLBookmarkModel::onPlaybackStateChanged( vlc_player_t *, vlc_player_state state,
+                                              void *data )
+{
+    auto self = static_cast<MLBookmarkModel*>( data );
+
+    if ( state == VLC_PLAYER_STATE_STARTED )
+        self->refresh( false );
+    else if ( state == VLC_PLAYER_STATE_STOPPING )
+        self->refresh( true );
+}
+
+void MLBookmarkModel::refresh( bool forceClear )
+{
+    callAsync([this, forceClear]() {
+        vlc::threads::mutex_locker lock( m_mutex );
+
+        if ( forceClear == false && m_currentMediaId == 0 && m_currentItem != nullptr )
+        {
+            auto mlMedia = vlc_ml_get_media_by_mrl( m_ml, m_currentItem->psz_uri );
+            if ( mlMedia != nullptr )
+            {
+                m_currentMediaId = mlMedia->i_id;
+                vlc_ml_release( mlMedia );
+            }
+        }
+        beginResetModel();
+        if ( m_currentMediaId == 0 || forceClear == true )
+            m_bookmarks.reset();
+        else
+        {
+            vlc_ml_query_params_t params{};
+            params.i_sort = m_sort;
+            params.b_desc = m_desc;
+            m_bookmarks.reset( vlc_ml_list_media_bookmarks( m_ml, &params, m_currentMediaId ) );
+        }
+        endResetModel();
+    });
+}
diff --git a/modules/gui/qt/medialibrary/mlbookmarkmodel.hpp b/modules/gui/qt/medialibrary/mlbookmarkmodel.hpp
new file mode 100644
index 0000000000..b36f65936d
--- /dev/null
+++ b/modules/gui/qt/medialibrary/mlbookmarkmodel.hpp
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * Copyright (C) 2019 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 MLBOOKMARKMODEL_HPP
+#define MLBOOKMARKMODEL_HPP
+
+#include <QAbstractListModel>
+#include <memory>
+
+#include <vlc_common.h>
+#include <vlc_media_library.h>
+#include <vlc_player.h>
+#include <vlc_threads.h>
+#include <vlc_cxx_helpers.hpp>
+
+#include "mlhelper.hpp"
+
+class MLBookmarkModel : public QAbstractListModel
+{
+public:
+    MLBookmarkModel( vlc_medialibrary_t* ml, vlc_player_t* player, QObject* parent );
+    virtual ~MLBookmarkModel();
+
+    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole ) const override;
+    bool setData( const QModelIndex& index, const QVariant& value, int role ) override;
+    Qt::ItemFlags flags( const QModelIndex & ) const override;
+    int rowCount( const QModelIndex& index ) const override;
+    int columnCount( const QModelIndex& index ) const override;
+    QModelIndex index( int row, int column,
+                       const QModelIndex& parent = QModelIndex() ) const override;
+    QModelIndex parent( const QModelIndex& ) const override;
+    QVariant headerData( int section, Qt::Orientation orientation, int role ) const override;
+    void sort( int column, Qt::SortOrder order ) override;
+
+    void add();
+    void remove( const QModelIndexList& indexes );
+    void clear();
+    void select( const QModelIndex& index );
+
+private:
+    static void onCurrentMediaChanged( vlc_player_t* player, input_item_t* media,
+                                       void* data );
+    static void onPlaybackStateChanged( vlc_player_t* player, vlc_player_state state,
+                                        void* data );
+
+    void refresh( bool forceClear );
+
+    template <typename Fun>
+    void callAsync(Fun&& fun)
+    {
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
+        QMetaObject::invokeMethod(this, std::forward<Fun>(fun), Qt::QueuedConnection, nullptr);
+#else
+        QObject src;
+        QObject::connect(&src, &QObject::destroyed, this, std::forward<Fun>(fun), Qt::QueuedConnection);
+#endif
+    }
+
+private:
+    using BookmarkListPtr = ml_unique_ptr<vlc_ml_bookmark_list_t>;
+    using InputItemPtr = std::unique_ptr<input_item_t, decltype(&input_item_Release)>;
+
+    vlc_medialibrary_t* m_ml;
+    vlc_player_t* m_player;
+    // Assume to be only used from the GUI thread
+    BookmarkListPtr m_bookmarks;
+    vlc_player_listener_id* m_listener;
+
+    vlc::threads::mutex m_mutex;
+    // current item & media id can be accessed by any thread and therefor
+    // must be accessed with m_mutex held
+    InputItemPtr m_currentItem;
+    int64_t m_currentMediaId;
+    vlc_ml_sorting_criteria_t m_sort;
+    bool m_desc;
+};
+
+#endif // MLBOOKMARKMODEL_HPP
-- 
2.20.1



More information about the vlc-devel mailing list