[vlc-devel] [RFC 16/82] qt: provide abstract list model for variables with VLC_VAR_HASCHOICE

Pierre Lamot pierre at videolabs.io
Fri Feb 1 14:01:20 CET 2019


 The VLCVarChoiceModel class contruct an Abstract List Model from a
 vlc_var with the VLC_VAR_HASCHOICE flag and a type amongst string, int, float, bool

 available roles are DisplayRole and CheckStateRole

 checking an entry from the model will set the variable to the corresponding value.
---
 modules/gui/qt/Makefile.am                   |   3 +
 modules/gui/qt/util/vlc_var_choice_model.cpp | 340 +++++++++++++++++++
 modules/gui/qt/util/vlc_var_choice_model.hpp |  97 ++++++
 3 files changed, 440 insertions(+)
 create mode 100644 modules/gui/qt/util/vlc_var_choice_model.cpp
 create mode 100644 modules/gui/qt/util/vlc_var_choice_model.hpp

diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index 1f51781643..ff8c46fa9a 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -148,6 +148,8 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/util/vlctick.hpp \
 	gui/qt/util/vlc_var_observer.cpp \
 	gui/qt/util/vlc_var_observer.hpp \
+	gui/qt/util/vlc_var_choice_model.cpp \
+	gui/qt/util/vlc_var_choice_model.hpp \
 	gui/qt/util/imagehelper.cpp gui/qt/util/imagehelper.hpp \
 	gui/qt/styles/seekstyle.cpp gui/qt/styles/seekstyle.hpp
 if HAVE_WIN32
@@ -235,6 +237,7 @@ nodist_libqt_plugin_la_SOURCES = \
 	gui/qt/util/qvlcapp.moc.cpp \
 	gui/qt/util/pictureflow.moc.cpp \
 	gui/qt/util/vlc_var_observer.moc.cpp \
+	gui/qt/util/vlc_var_choice_model.moc.cpp \
 	gui/qt/util/vlctick.moc.cpp \
 	gui/qt/util/validators.moc.cpp \
 	gui/qt/util/buttons/RoundButton.moc.cpp \
diff --git a/modules/gui/qt/util/vlc_var_choice_model.cpp b/modules/gui/qt/util/vlc_var_choice_model.cpp
new file mode 100644
index 0000000000..134789c4fd
--- /dev/null
+++ b/modules/gui/qt/util/vlc_var_choice_model.cpp
@@ -0,0 +1,340 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "vlc_var_choice_model.hpp"
+
+//C callbacks from the observed variable
+extern "C" {
+
+int VLCVarChoiceModel_on_variable_callback( vlc_object_t * object, char const * , vlc_value_t oldvalue, vlc_value_t newvalue, void * data)
+{
+    VLCVarChoiceModel* that = static_cast<VLCVarChoiceModel*>(data);
+    return that->updateData(object, oldvalue, newvalue);
+}
+
+int VLCVarChoiceModel_on_variable_list_callback( vlc_object_t * object, char const * , int action, vlc_value_t* value, void * data)
+{
+    VLCVarChoiceModel* that = static_cast<VLCVarChoiceModel*>(data);
+    return that->updateList(object, action, value);
+}
+
+}
+
+VLCVarChoiceModel::VLCVarChoiceModel(vlc_object_t *p_object, const char *varName, QObject * parent)
+    : QAbstractListModel(parent)
+    , m_varname(qfu(varName))
+{
+    connect(this, &VLCVarChoiceModel::dataUpdated, this, &VLCVarChoiceModel::onDataUpdated);
+    connect(this, &VLCVarChoiceModel::listUpdated, this, &VLCVarChoiceModel::onListUpdated);
+    resetObject(p_object);
+}
+
+VLCVarChoiceModel::~VLCVarChoiceModel()
+{
+    if (m_object)
+    {
+        var_DelCallback(m_object.get(), qtu(m_varname), VLCVarChoiceModel_on_variable_callback, this);
+        var_DelListCallback(m_object.get(), qtu(m_varname), VLCVarChoiceModel_on_variable_list_callback, this);
+    }
+    m_object.reset(nullptr);
+}
+
+Qt::ItemFlags VLCVarChoiceModel::flags(const QModelIndex &) const
+{
+    return Qt::ItemFlag::ItemIsUserCheckable;
+}
+
+QHash<int, QByteArray> VLCVarChoiceModel::roleNames() const
+{
+    QHash<int, QByteArray> roleNames = this->QAbstractListModel::roleNames();
+    roleNames[Qt::CheckStateRole] = "checked";
+    return roleNames;
+}
+
+int VLCVarChoiceModel::rowCount(const QModelIndex &) const
+{
+    if (!m_object)
+        return 0;
+    return m_values.count();
+}
+
+QVariant VLCVarChoiceModel::data(const QModelIndex &index, int role) const
+{
+    if (!m_object)
+        return {};
+
+    int row = index.row();
+    if (row < 0 || row >= m_values.count())
+        return {};
+
+    if (role == Qt::DisplayRole)
+        return m_titles[row];
+    else if (role == Qt::CheckStateRole)
+        return row == m_current;
+    return {};
+}
+
+bool VLCVarChoiceModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if (!m_object)
+        return false;
+
+    int row = index.row();
+    if (role == Qt::CheckStateRole && row >= 0 && row < m_values.count())
+    {
+        //only update the variable when we select an entry
+        if (!value.toBool())
+            return false;
+
+        int ret = VLC_EGENERIC;
+        switch (m_type & VLC_VAR_CLASS) {
+        case VLC_VAR_STRING:
+            ret = var_SetString(m_object.get(), qtu(m_varname), qtu(m_values[row].toString()) );
+            break;
+        case VLC_VAR_INTEGER:
+            ret = var_SetInteger(m_object.get(), qtu(m_varname), m_values[row].toInt() );
+            break;
+        case VLC_VAR_FLOAT:
+            ret = var_SetFloat(m_object.get(), qtu(m_varname), m_values[row].toFloat() );
+            break;
+        case VLC_VAR_BOOL:
+            ret = var_SetBool(m_object.get(), qtu(m_varname), m_values[row].toBool() );
+            break;
+        default:
+            break;
+        }
+        return ret == VLC_SUCCESS;
+    }
+    return false;
+}
+
+
+//update the choices of the variable, called on variable thread from the var_AddCallback callback
+//calling vlcValToVariant is safe here as m_type will only be modified when no callbacks is registred
+int VLCVarChoiceModel::updateData(const vlc_object_t* object, const vlc_value_t& oldvalue, const vlc_value_t& newvalue)
+{
+    QVariant oldvalueVariant = vlcValToVariant(oldvalue);
+    QVariant newvalueVariant = vlcValToVariant(newvalue);
+
+    emit dataUpdated(object, oldvalueVariant, newvalueVariant, QPrivateSignal{});
+    return VLC_SUCCESS;
+}
+
+//update the choices of the variable, called on variable thread from the var_AddListCallback callback
+//calling vlcValToVariant is safe here as m_type will only be modified when no callbacks is registred
+int VLCVarChoiceModel::updateList(const vlc_object_t* object, int action, const vlc_value_t* p_value)
+{
+    QVariant valueVariant = p_value ? vlcValToVariant(*p_value) : QVariant();
+    emit listUpdated(object, action, valueVariant, QPrivateSignal{});
+    return VLC_SUCCESS;
+}
+
+//update the current value of the variable, called on UI thread
+void VLCVarChoiceModel::onDataUpdated(const vlc_object_t* object, QVariant , QVariant newvalue)
+{
+    if (object != m_object.get())
+        return;
+
+    int oldCurrent = m_current;
+    for (int i = 0; i < m_values.count(); i++)
+        if( newvalue == m_values[i] )
+        {
+            m_current = i;
+            if (oldCurrent == -1)
+                emit hasCurrentChanged(true);
+            return;
+        }
+    m_current = -1;
+    if (oldCurrent != -1)
+        emit hasCurrentChanged(false);
+}
+
+//update the choices of the variable, called on UI thread
+void VLCVarChoiceModel::onListUpdated(const vlc_object_t* object, int action, QVariant newvalue)
+{
+    if (object != m_object.get())
+        return;
+
+    switch (action) {
+    case VLC_VAR_ADDCHOICE:
+        beginInsertRows( QModelIndex{}, m_values.count(), m_values.count() );
+        m_values.append( newvalue );
+        //we should probably rather get the whole choices list to get the associated text
+        m_titles.append( newvalue.toString() );
+        endInsertRows();
+        break;
+    case VLC_VAR_DELCHOICE:
+    {
+        int i = 0;
+        for (i = 0; i < m_values.count(); i++)
+            if (newvalue == m_values[i])
+                break;
+        if (i != m_values.count())
+        {
+            beginRemoveRows( QModelIndex{}, i, i);
+            m_values.removeAt(i);
+            m_titles.removeAt(i);
+            endRemoveRows();
+        }
+        break;
+    }
+    case VLC_VAR_CLEARCHOICES:
+        beginResetModel();
+        m_values.clear();
+        m_titles.clear();
+        endResetModel();
+        break;
+    default:
+        //N/A
+        break;
+    }
+}
+
+QString VLCVarChoiceModel::vlcValToString(const vlc_value_t &value)
+{
+    switch (m_type & VLC_VAR_CLASS) {
+    case VLC_VAR_INTEGER:
+        return QString::number(value.i_int);
+    case VLC_VAR_STRING:
+        return qfu(value.psz_string);
+    case VLC_VAR_FLOAT:
+        return QString::number(value.f_float);
+    default:
+        return {};
+    }
+}
+
+QVariant VLCVarChoiceModel::vlcValToVariant(const vlc_value_t &value)
+{
+    switch (m_type & VLC_VAR_CLASS) {
+    case VLC_VAR_INTEGER:
+        return QVariant::fromValue<qlonglong>(value.i_int);
+    case VLC_VAR_STRING:
+        return qfu(value.psz_string);
+    case VLC_VAR_FLOAT:
+        return value.f_float;
+    case VLC_VAR_BOOL:
+        return value.b_bool;
+    default:
+        return {};
+    }
+}
+
+bool VLCVarChoiceModel::resetObject(vlc_object_t *p_object)
+{
+    beginResetModel();
+
+    //clear the old model
+    if (m_object)
+    {
+        var_DelCallback(m_object.get(), qtu(m_varname), VLCVarChoiceModel_on_variable_callback, this);
+        var_DelListCallback(m_object.get(), qtu(m_varname), VLCVarChoiceModel_on_variable_list_callback, this);
+    }
+    m_object.reset(p_object, true);
+
+    m_values.clear();
+    m_titles.clear();
+
+    if (!m_object)
+    {
+        endResetModel();
+        return true;
+    }
+
+    m_type = var_Type( m_object.get(), qtu(m_varname) );
+    //we only handle variables with choice here
+    if ( ! (m_type & VLC_VAR_HASCHOICE) )
+    {
+        m_object.reset(nullptr, false);
+        endResetModel();
+        return false;
+    }
+
+
+    switch( m_type & VLC_VAR_CLASS )
+    {
+        case VLC_VAR_BOOL:
+        case VLC_VAR_STRING:
+        case VLC_VAR_INTEGER:
+        case VLC_VAR_FLOAT:
+            break;
+        default:
+            /* Variable doesn't exist or isn't handled */
+            endResetModel();
+            return false;
+    }
+
+    size_t count = 0;
+    vlc_value_t* val_list = nullptr;
+    char** val_title = nullptr;
+    if( var_Change( m_object.get(), qtu(m_varname), VLC_VAR_GETCHOICES, &count, &val_list, &val_title ) < 0 )
+    {
+        endResetModel();
+        return false;
+    }
+    vlc_value_t currentValue;
+    if (var_Get( m_object.get(), qtu(m_varname), &currentValue) != VLC_SUCCESS)
+    {
+        endResetModel();
+        return false;
+    }
+    QVariant currentValueVar = vlcValToVariant(currentValue);
+    if ( (m_type & VLC_VAR_CLASS) == VLC_VAR_STRING )
+        free( currentValue.psz_string );
+
+    int newCurrent = -1;
+    for (size_t i = 0; i < count; i++)
+    {
+        QVariant var = vlcValToVariant( val_list[i] );
+        if (currentValueVar == var)
+            newCurrent = i;
+        m_values.append( var );
+        if ( val_title[i] )
+        {
+            //display the title if present
+            m_titles.append( qfu(val_title[i]) );
+            free(val_title[i]);
+        }
+        else
+        {
+            //print the value otherwise
+            m_titles.append( vlcValToString(val_list[i]) );
+        }
+        if ( (m_type & VLC_VAR_CLASS) == VLC_VAR_STRING )
+            free( val_list[i].psz_string );
+    }
+    if (m_current != newCurrent)
+    {
+        m_current = newCurrent;
+        emit hasCurrentChanged(newCurrent != -1);
+    }
+
+    free(val_list);
+    free(val_title);
+
+    var_AddCallback(m_object.get(), qtu(m_varname), VLCVarChoiceModel_on_variable_callback, this);
+    var_AddListCallback(m_object.get(), qtu(m_varname), VLCVarChoiceModel_on_variable_list_callback, this);
+
+    endResetModel();
+    return true;
+}
+
+bool VLCVarChoiceModel::hasCurrent() const
+{
+    return m_current != -1;
+}
diff --git a/modules/gui/qt/util/vlc_var_choice_model.hpp b/modules/gui/qt/util/vlc_var_choice_model.hpp
new file mode 100644
index 0000000000..9dce90af21
--- /dev/null
+++ b/modules/gui/qt/util/vlc_var_choice_model.hpp
@@ -0,0 +1,97 @@
+/*****************************************************************************
+ * 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 VLC_VAR_CHOICE_MODEL_HPP
+#define VLC_VAR_CHOICE_MODEL_HPP
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "qt.hpp"
+#include <QAbstractListModel>
+#include <QMutex>
+#include <vlc_cxx_helpers.hpp>
+
+extern "C" {
+int VLCVarChoiceModel_on_variable_callback( vlc_object_t * object, char const * , vlc_value_t oldvalue, vlc_value_t newvalue, void * data);
+int VLCVarChoiceModel_on_variable_list_callback( vlc_object_t * object, char const * , int action, vlc_value_t* value, void * data);
+}
+
+/**
+ * @brief The VLCVarChoiceModel class contruct an Abstract List Model from a
+ * vlc_var with the VLC_VAR_HASCHOICE flag and a type amongst string, int, float, bool
+ *
+ * available roles are DisplayRole and CheckStateRole
+ */
+class VLCVarChoiceModel : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    Q_PROPERTY(bool hasCurrent READ hasCurrent NOTIFY hasCurrentChanged)
+
+    VLCVarChoiceModel(vlc_object_t *p_object, const char* varName, QObject *parent = nullptr);
+    ~VLCVarChoiceModel();
+
+    //QAbstractListModel overriden functions
+    virtual Qt::ItemFlags flags(const QModelIndex &) const  override;
+    QHash<int, QByteArray> roleNames() const override;
+    virtual int rowCount(const QModelIndex & = QModelIndex()) const override;
+    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+    virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+
+    /**
+     * @brief resetObject change the observed object.
+     * @param object the new object to observe (can be null), the incoming object will be hold.
+     * @return true if the object has the observed variable and if the variable has the right type.
+     */
+    bool resetObject(vlc_object_t *object);
+
+    bool hasCurrent() const;
+
+private slots:
+    void onDataUpdated(const vlc_object_t* object, QVariant oldvalue, QVariant newvalue);
+    void onListUpdated(const vlc_object_t* object, int action, QVariant newvalue);
+
+signals:
+    void dataUpdated(const vlc_object_t* object, QVariant oldvalue, QVariant newvalue, QPrivateSignal);
+    void listUpdated(const vlc_object_t* object, int action, QVariant newvalue, QPrivateSignal);
+    void hasCurrentChanged( bool );
+
+private:
+    int updateData(const vlc_object_t* object, const vlc_value_t& oldvalue, const vlc_value_t& newvalue);
+    int updateList(const vlc_object_t* object, int action, const vlc_value_t* p_newvalue);
+
+    QString vlcValToString(const vlc_value_t& value);
+    QVariant vlcValToVariant(const vlc_value_t& a);
+
+    typedef vlc_shared_data_ptr_type(vlc_object_t, vlc_object_hold, vlc_object_release) VlcObjectPtr;
+    //reference to the observed object. Can only be modified from the UI thread
+    VlcObjectPtr m_object;
+    int m_type;
+    QString m_varname;
+
+    QList< QVariant > m_values;
+    QStringList m_titles;
+    int m_current = -1;
+
+    friend int VLCVarChoiceModel_on_variable_callback( vlc_object_t * object, char const * , vlc_value_t oldvalue, vlc_value_t newvalue, void * data);
+    friend int VLCVarChoiceModel_on_variable_list_callback( vlc_object_t * object, char const * , int action, vlc_value_t* value, void * data);
+};
+
+#endif // VLC_VAR_CHOICE_MODEL_HPP
-- 
2.19.1



More information about the vlc-devel mailing list