[vlc-devel] [RFC 32/82] qt: provide helper class to build menus

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


---
 modules/gui/qt/components/custom_menus.cpp | 119 +++++++++++++++++++++
 modules/gui/qt/components/custom_menus.hpp |  54 ++++++++++
 2 files changed, 173 insertions(+)

diff --git a/modules/gui/qt/components/custom_menus.cpp b/modules/gui/qt/components/custom_menus.cpp
index 232a7aea7a..ff9c596bb0 100644
--- a/modules/gui/qt/components/custom_menus.cpp
+++ b/modules/gui/qt/components/custom_menus.cpp
@@ -35,6 +35,9 @@
 #include <QVBoxLayout>
 #include <QLabel>
 #include <QProgressBar>
+#include <QMetaObject>
+#include <QMetaProperty>
+#include <QMetaMethod>
 
 RendererAction::RendererAction( vlc_renderer_item_t *p_item_ )
     : QAction()
@@ -196,3 +199,119 @@ void RendererMenu::RendererSelected(QAction *action)
     else
         RendererManager::getInstance( p_intf )->SelectRenderer( NULL );
 }
+
+/*   CheckableListMenu   */
+
+CheckableListMenu::CheckableListMenu(QString title, QAbstractListModel* model , QWidget *parent)
+    : QMenu(parent)
+    , m_model(model)
+{
+    this->setTitle(title);
+    m_actionGroup = new QActionGroup( this );
+
+    connect(m_model, &QAbstractListModel::rowsAboutToBeRemoved, this, &CheckableListMenu::onRowsAboutToBeRemoved);
+    connect(m_model, &QAbstractListModel::rowsInserted, this, &CheckableListMenu::onRowInserted);
+    connect(m_model, &QAbstractListModel::dataChanged, this, &CheckableListMenu::onDataChanged);
+    connect(m_model, &QAbstractListModel::modelAboutToBeReset, this, &CheckableListMenu::onModelAboutToBeReset);
+    connect(m_model, &QAbstractListModel::modelReset, this, &CheckableListMenu::onModelReset);
+    onModelReset();
+}
+
+void CheckableListMenu::onRowsAboutToBeRemoved(const QModelIndex &, int first, int last)
+{
+    for (int i = last; i >= first; i--)
+    {
+        QAction* action = actions()[i];
+        delete action;
+    }
+    if (actions().count() == 0)
+        setEnabled(false);
+}
+
+void CheckableListMenu::onRowInserted(const QModelIndex &, int first, int last)
+{
+    for (int i = first; i <= last; i++)
+    {
+        QModelIndex index = m_model->index(i);
+        QString title = m_model->data(index, Qt::DisplayRole).toString();
+        bool checked = m_model->data(index, Qt::CheckStateRole).toBool();
+
+        QAction *choiceAction = new QAction(title, this);
+        m_actionGroup->addAction(choiceAction);
+        addAction(choiceAction);
+        connect(choiceAction, &QAction::triggered, [this, i](bool checked){
+            QModelIndex dataIndex = m_model->index(i);
+            m_model->setData(dataIndex, QVariant::fromValue<bool>(checked), Qt::CheckStateRole);
+        });
+        choiceAction->setCheckable(true);
+        choiceAction->setChecked(checked);
+        setEnabled(true);
+    }
+}
+
+void CheckableListMenu::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> & )
+{
+    for (int i = topLeft.row(); i <= bottomRight.row(); i++)
+    {
+        if (i >= actions().size())
+            break;
+        QAction *choiceAction = actions()[i];
+
+        QModelIndex index = m_model->index(i);
+        QString title = m_model->data(index, Qt::DisplayRole).toString();
+        bool checked = m_model->data(index, Qt::CheckStateRole).toBool();
+
+        choiceAction->setText(title);
+        choiceAction->setChecked(checked);
+    }
+}
+
+void CheckableListMenu::onModelAboutToBeReset()
+{
+    for (QAction* action  :actions())
+        delete action;
+    setEnabled(false);
+}
+
+void CheckableListMenu::onModelReset()
+{
+    int nb_rows = m_model->rowCount();
+    if (nb_rows == 0)
+        setEnabled(false);
+    else
+        onRowInserted({}, 0, nb_rows - 1);
+}
+
+/*     BooleanPropertyAction    */
+
+BooleanPropertyAction::BooleanPropertyAction(QString title, QObject *model, QString propertyName, QWidget *parent)
+    : QAction(parent)
+    , m_model(model)
+    , m_propertyName(propertyName)
+{
+    setText(title);
+    assert(model);
+    const QMetaObject* meta = model->metaObject();
+    int propertyId = meta->indexOfProperty(qtu(propertyName));
+    assert(propertyId != -1);
+    QMetaProperty property = meta->property(propertyId);
+    assert(property.type() ==  QVariant::Bool);
+    const QMetaObject* selfMeta = this->metaObject();
+    if (property.hasNotifySignal())
+    {
+        QMetaMethod checkedSlot = selfMeta->method(selfMeta->indexOfSlot( "setChecked(bool)" ));
+        connect( model, property.notifySignal(), this, checkedSlot );
+        connect( this, &BooleanPropertyAction::toggled, this, &BooleanPropertyAction::setModelChecked );
+        setCheckable(true);
+    }
+    else
+    {
+        connect( this, &BooleanPropertyAction::triggered, this, &BooleanPropertyAction::setModelChecked );
+        setCheckable(true);
+    }
+}
+
+void BooleanPropertyAction::setModelChecked(bool checked)
+{
+    m_model->setProperty(qtu(m_propertyName), QVariant::fromValue<bool>(checked) );
+}
diff --git a/modules/gui/qt/components/custom_menus.hpp b/modules/gui/qt/components/custom_menus.hpp
index 45c8c9039c..80316fdc6a 100644
--- a/modules/gui/qt/components/custom_menus.hpp
+++ b/modules/gui/qt/components/custom_menus.hpp
@@ -24,6 +24,7 @@
 #include "qt.hpp"
 
 #include <QMenu>
+#include <QAbstractListModel>
 
 class RendererAction : public QAction
 {
@@ -64,4 +65,57 @@ private:
 };
 
 
+/*
+ * Construct a menu from a QAbstractListModel with Qt::DisplayRole and Qt::CheckStateRole
+ */
+class CheckableListMenu : public QMenu
+{
+    Q_OBJECT
+public:
+    /**
+     * @brief CheckableListMenu
+     * @param title the title of the menu
+     * @param model the model to observe, the model should provide at least Qt::DisplayRole and Qt::CheckStateRole
+     * @param parent QObject parent
+     */
+    CheckableListMenu(QString title, QAbstractListModel* model , QWidget *parent = nullptr);
+
+private slots:
+    void onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
+    void onRowInserted(const QModelIndex &parent, int first, int last);
+    void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
+    void onModelAboutToBeReset();
+    void onModelReset();
+
+private:
+    QAbstractListModel* m_model;
+    QMenu * m_submenu;
+
+    QActionGroup* m_actionGroup;
+};
+
+
+/**
+ * @brief The BooleanPropertyAction class allows to bind a boolean Q_PROPRERTY to a QAction
+ */
+class BooleanPropertyAction: public QAction
+{
+    Q_OBJECT
+public:
+    /**
+     * @brief BooleanPropertyAction
+     * @param title the title of the menu
+     * @param model the object to observe
+     * @param propertyName the name of the property on the @a model
+     * @param parent QObject parent
+     */
+    BooleanPropertyAction(QString title, QObject* model , QString propertyName, QWidget *parent = nullptr);
+
+private slots:
+    void setModelChecked(bool checked);
+private:
+    QObject* m_model;
+    QString m_propertyName;
+};
+
 #endif // CUSTOM_MENUS_HPP
-- 
2.19.1



More information about the vlc-devel mailing list