[vlc-commits] [Git][videolan/vlc][master] 13 commits: qt: expose the inner ActionGroup of ListMenuHelper
Steve Lhomme (@robUx4)
gitlab at videolan.org
Fri Oct 11 13:08:20 UTC 2024
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
5b5cace7 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: expose the inner ActionGroup of ListMenuHelper
- - - - -
1aa580f4 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: forward the checked state on select events in ListMenuHelper
- - - - -
a06cd634 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: ensure CheckStateRole is defined in ListMenuHelper models
- - - - -
1c5d7b47 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: allow to create an VLCAccessImageProvider image response directly from URL
this can be used by Qt GUI classes that don't need to pass through the QML image
provider scheme
- - - - -
411fb08c by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: support icons in ListMenuHelper
- - - - -
c8807eab by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: selectively update Actions in ListMenuHelper
- - - - -
83b7c8d9 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: make CheckableListMenu use Qt enum instead of a custom one
- - - - -
5b749e59 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: make CheckableListMenu use ListMenuHelper
- - - - -
28df34f9 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: make RecentMenu use ListMenuHelper
- - - - -
4327da49 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: update RendererManager model
* make it implement QAbstractListModel
* RendererManager being tied to the player instance make it a submodel of the
player controller
* react on player renderer changes
* factorize RendererMenu to use ListMenuHelper as RendererManager is now a QAbstractListModel
- - - - -
26ea26a1 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: add configure check to detect QTest
- - - - -
b3dfa3f7 by Pierre Lamot at 2024-10-11T12:38:52+00:00
meson: allow qt tests to moc .cpp files
- - - - -
e32174b3 by Pierre Lamot at 2024-10-11T12:38:52+00:00
qt: add unit test for RendererManager model
- - - - -
18 changed files:
- configure.ac
- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainctx.cpp
- modules/gui/qt/medialibrary/mlrecentsmodel.cpp
- modules/gui/qt/menus/custom_menus.cpp
- modules/gui/qt/menus/custom_menus.hpp
- modules/gui/qt/menus/menus.cpp
- modules/gui/qt/menus/qml_menu_wrapper.cpp
- modules/gui/qt/meson.build
- modules/gui/qt/player/player_controller.cpp
- modules/gui/qt/player/player_controller.hpp
- modules/gui/qt/player/player_controller_p.hpp
- + modules/gui/qt/tests/test_renderer_manager_model.cpp
- modules/gui/qt/util/renderer_manager.cpp
- modules/gui/qt/util/renderer_manager.hpp
- modules/gui/qt/util/vlcaccess_image_provider.cpp
- modules/gui/qt/util/vlcaccess_image_provider.hpp
- test/meson.build
Changes:
=====================================
configure.ac
=====================================
@@ -3986,6 +3986,7 @@ AC_ARG_ENABLE([qt],
])
])
have_qt_gtk="no"
+have_qt_qtest="no"
have_qt_quick_test="no"
have_qt_declarative_private="no"
have_qt_gui_private="no"
@@ -4092,6 +4093,24 @@ AS_IF([test "${enable_qt}" != "no"], [
have_qt="no"
])
+ AC_MSG_CHECKING([for Qt QTest])
+ (${QMAKE} ${srcdir}/modules/gui/qt/qtest.pro -o ${ac_pwd}/modules/gui/qt/qmake-qtest) 2>/dev/null
+ ac_status=$?
+ AS_IF([test $ac_status = 0 && test -f ${ac_pwd}/modules/gui/qt/qmake-qtest],[
+ echo "include ${ac_pwd}/modules/gui/qt/qmake-common.mk" >> ${ac_pwd}/modules/gui/qt/qmake-qtest
+ AC_MSG_RESULT([yes])
+ QT_QTEST_LIBS=$(cd ${ac_pwd}/modules/gui/qt && make -s -f qmake-qtest get_libs)
+ QT_QTEST_CFLAGS=$(cd ${ac_pwd}/modules/gui/qt && make -s -f qmake-qtest get_cflags)
+ QT_QTEST_LDFLAGS=$(cd ${ac_pwd}/modules/gui/qt && make -s -f qmake-qtest get_cflags)
+ AC_SUBST([QT_QTEST_LIBS])
+ AC_SUBST([QT_QTEST_CFLAGS])
+ AC_SUBST([QT_QTEST_LDFLAGS])
+ have_qt_qtest="yes"
+ ],[
+ AC_MSG_RESULT([no])
+ ])
+ rm -f ${ac_pwd}/modules/gui/qt/qmake-qtest
+
AC_MSG_CHECKING([for QuickTest])
(${QMAKE} ${srcdir}/modules/gui/qt/quicktest.pro -o ${ac_pwd}/modules/gui/qt/qmake-quicktest) 2>/dev/null
ac_status=$?
@@ -4217,6 +4236,7 @@ AS_IF([test "${enable_qt}" != "no"], [
AC_SUBST([QT_INDIRECT_LIBS])
AM_CONDITIONAL([ENABLE_QT], [test "$enable_qt" != "no"])
AM_CONDITIONAL([HAVE_QT_GTK], [test "${have_qt_gtk}" = "yes"])
+AM_CONDITIONAL([HAVE_QT_QTEST], [test "${have_qt_qtest}" = "yes"])
AM_CONDITIONAL([HAVE_QT_QUICK_TEST], [test "${have_qt_quick_test}" = "yes"])
AM_CONDITIONAL([HAVE_QT_DECLARATIVE_PRIVATE], [test "${have_qt_declarative_private}" = "yes"])
AM_CONDITIONAL([HAVE_QT_GUI_PRIVATE], [test "${have_qt_gui_private}" = "yes"])
=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -1538,6 +1538,28 @@ base_model_test_LDFLAGS = $(AM_LDFLAGS) $(QT_LDFLAGS)
check_PROGRAMS = base_model_test
TESTS = base_model_test
+if HAVE_QT_QTEST
+
+LIBVLC = -L../../../lib -lvlc
+
+test_renderer_manager_model_SOURCES = \
+ tests/test_renderer_manager_model.cpp \
+ util/renderer_manager.hpp util/renderer_manager.cpp
+
+nodist_test_renderer_manager_model_SOURCES = \
+ tests/test_renderer_manager_model.moc \
+ util/renderer_manager.moc.cpp
+
+BUILT_SOURCES += tests/test_renderer_manager_model.moc
+CLEANFILES += tests/test_renderer_manager_model.moc
+test_renderer_manager_model_CPPFLAGS = $(libqt_plugin_la_CPPFLAGS) -I$(builddir)/tests -DTOP_BUILDDIR=\"$(abs_top_builddir)\"
+test_renderer_manager_model_CXXFLAGS = $(AM_CXXFLAGS) $(QT_CFLAGS) $(QT_QTEST_CFLAGS) -fPIC $(CXXFLAGS_qt)
+test_renderer_manager_model_LDADD = $(QT_LIBS) $(LIBS_qt) $(QT_QTEST_LIBS) $(LIBVLCCORE) $(LIBVLC)
+test_renderer_manager_model_LDFLAGS = $(AM_LDFLAGS) $(QT_LDFLAGS) $(QT_QTEST_LDFLAGS)
+check_PROGRAMS += test_renderer_manager_model
+TESTS += test_renderer_manager_model
+
+endif
QML_LOG_COMPILER = $(builddir)/qml_test -input
=====================================
modules/gui/qt/maininterface/mainctx.cpp
=====================================
@@ -260,8 +260,6 @@ MainCtx::MainCtx(qt_intf_t *_p_intf)
MainCtx::~MainCtx()
{
- RendererManager::killInstance();
-
/* Save states */
settings->beginGroup("MainWindow");
=====================================
modules/gui/qt/medialibrary/mlrecentsmodel.cpp
=====================================
@@ -53,6 +53,7 @@ QVariant MLRecentsModel::itemRoleData(MLItem *item , int role ) const
{
case RECENT_MEDIA_ID:
return QVariant::fromValue( media->getId() );
+ case Qt::DisplayRole:
case RECENT_MEDIA_URL:
return QVariant::fromValue( media->getUrl().toString(QUrl::PreferLocalFile | QUrl::RemovePassword));
case RECENT_MEDIA_LAST_PLAYED_DATE:
=====================================
modules/gui/qt/menus/custom_menus.cpp
=====================================
@@ -36,6 +36,7 @@
// Menus includes
#include "menus/menus.hpp"
+#include "player/player_controller.hpp"
// Qt includes
#include <QMenu>
@@ -49,260 +50,117 @@
#include <QMetaProperty>
#include <QMetaMethod>
-RendererAction::RendererAction( vlc_renderer_item_t *p_item_ )
- : QAction()
-{
- p_item = p_item_;
- vlc_renderer_item_hold( p_item );
- if( vlc_renderer_item_flags( p_item ) & VLC_RENDERER_CAN_VIDEO )
- setIcon( QIcon( ":/menu/movie.svg" ) );
- else
- setIcon( QIcon( ":/menu/music.svg" ) );
- setText( vlc_renderer_item_name( p_item ) );
- setCheckable(true);
-}
-
-RendererAction::~RendererAction()
-{
- vlc_renderer_item_release( p_item );
-}
-
-vlc_renderer_item_t * RendererAction::getItem()
-{
- return p_item;
-}
+#include "util/vlcaccess_image_provider.hpp"
-RendererMenu::RendererMenu( QMenu *parent, qt_intf_t *p_intf_ )
- : QMenu( parent ), p_intf( p_intf_ )
+RendererMenu::RendererMenu( QMenu* parent, qt_intf_t* intf, PlayerController* player )
+ : QMenu( parent)
+ , p_intf( intf )
+ , m_renderManager(player->getRendererManager())
{
setTitle( qtr("&Renderer") );
- group = new QActionGroup( this );
-
QAction *action = new QAction( qtr("<Local>"), this );
action->setCheckable(true);
+ action->setChecked(!m_renderManager->useRenderer());
+ connect(action, &QAction::triggered, this, [this](bool checked){
+ if (checked) {
+ m_renderManager->disableRenderer();
+ }
+ });
addAction( action );
- group->addAction(action);
+ connect(m_renderManager, &RendererManager::useRendererChanged, action,
+ [action, this](){
+ action->setChecked(!m_renderManager->useRenderer());
+ });
- vlc_player_Lock( p_intf_->p_player );
- if ( vlc_player_GetRenderer( p_intf->p_player ) == nullptr )
- action->setChecked( true );
- vlc_player_Unlock( p_intf_->p_player );
+ QAction* separator = addSeparator();
- addSeparator();
+ ListMenuHelper* helper = new ListMenuHelper(this, m_renderManager, separator, this);
+ connect(helper, &ListMenuHelper::select, this, [this](int row, bool checked){
+ m_renderManager->setData(m_renderManager->index(row), checked, Qt::CheckStateRole);
+ });
+
+ QActionGroup* actionGroup = helper->getActionGroup();
+ actionGroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::Exclusive);
+ //the <Local> node is part of the group
+ actionGroup->addAction(action);
QWidget *statusWidget = new QWidget();
statusWidget->setLayout( new QVBoxLayout );
- QLabel *label = new QLabel();
- label->setObjectName( "statuslabel" );
- statusWidget->layout()->addWidget( label );
- QProgressBar *pb = new QProgressBar();
- pb->setObjectName( "statusprogressbar" );
- pb->setMaximumHeight( 10 );
- pb->setStyleSheet( QString("\
- QProgressBar:horizontal {\
- border: none;\
- background: transparent;\
- padding: 1px;\
- }\
- QProgressBar::chunk:horizontal {\
- background: qlineargradient(x1: 0, y1: 0.5, x2: 1, y2: 0.5, \
- stop: 0 white, stop: 0.4 orange, stop: 0.6 orange, stop: 1 white);\
- }") );
- pb->setRange( 0, 0 );
- pb->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum );
- statusWidget->layout()->addWidget( pb );
+ m_statusLabel = new QLabel();
+ statusWidget->layout()->addWidget( m_statusLabel );
+ m_statusProgressBar = new QProgressBar();
+ m_statusProgressBar->setMaximumHeight( 10 );
+ m_statusProgressBar->setStyleSheet( QString(R"RAW(
+ QProgressBar:horizontal {
+ border: none;
+ background: transparent;
+ padding: 1px;
+ }
+ QProgressBar::chunk:horizontal {
+ background: qlineargradient(x1: 0, y1: 0.5, x2: 1, y2: 0.5,
+ stop: 0 white, stop: 0.4 orange, stop: 0.6 orange, stop: 1 white);
+ })RAW") );
+ m_statusProgressBar->setRange( 0, 0 );
+ m_statusProgressBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum );
+ statusWidget->layout()->addWidget( m_statusProgressBar );
+
QWidgetAction *qwa = new QWidgetAction( this );
qwa->setDefaultWidget( statusWidget );
qwa->setDisabled( true );
addAction( qwa );
- status = qwa;
-
- RendererManager *manager = RendererManager::getInstance( p_intf );
- connect( this, &RendererMenu::aboutToShow, manager, &RendererManager::StartScan );
- connect( group, &QActionGroup::triggered, this, &RendererMenu::RendererSelected );
- connect( manager, SIGNAL(rendererItemAdded( vlc_renderer_item_t * )),
- this, SLOT(addRendererItem( vlc_renderer_item_t * )), Qt::DirectConnection );
- connect( manager, SIGNAL(rendererItemRemoved( vlc_renderer_item_t * )),
- this, SLOT(removeRendererItem( vlc_renderer_item_t * )), Qt::DirectConnection );
- connect( manager, &RendererManager::statusUpdated, this, &RendererMenu::updateStatus );
+ m_statusAction = qwa;
+
+ connect( this, &RendererMenu::aboutToShow, m_renderManager, &RendererManager::StartScan );
+ connect( m_renderManager, &RendererManager::statusChanged, this, &RendererMenu::updateStatus );
+ connect( m_renderManager, &RendererManager::scanRemainChanged, this, &RendererMenu::updateStatus );
+ updateStatus();
}
RendererMenu::~RendererMenu()
-{
- reset();
-}
+{}
-void RendererMenu::updateStatus( int val )
+void RendererMenu::updateStatus()
{
- QProgressBar *pb = findChild<QProgressBar *>("statusprogressbar");
- QLabel *label = findChild<QLabel *>("statuslabel");
- if( val >= RendererManager::RendererStatus::RUNNING )
+
+ switch (m_renderManager->getStatus())
{
- label->setText( qtr("Scanning...").
- append( QString(" (%1s)").arg( val ) ) );
- pb->setVisible( true );
- status->setVisible( true );
- }
- else if( val == RendererManager::RendererStatus::FAILED )
+ case RendererManager::RendererStatus::RUNNING:
{
- label->setText( "Failed (no discovery module available)" );
- pb->setVisible( false );
- status->setVisible( true );
+ int scanRemain = m_renderManager->getScanRemain();
+ m_statusLabel->setText( qtr("Scanning...").
+ append( QString(" (%1s)").arg( scanRemain ) ) );
+ m_statusProgressBar->setVisible( true );
+ m_statusAction->setVisible( true );
+ break;
}
- else status->setVisible( false );
-}
-
-void RendererMenu::addRendererItem( vlc_renderer_item_t *p_item )
-{
- QAction *action = new RendererAction( p_item );
- insertAction( status, action );
- group->addAction( action );
-}
-
-void RendererMenu::removeRendererItem( vlc_renderer_item_t *p_item )
-{
- foreach (QAction* action, group->actions())
+ case RendererManager::RendererStatus::FAILED:
{
- RendererAction *ra = qobject_cast<RendererAction *>( action );
- if( !ra || ra->getItem() != p_item )
- continue;
- removeRendererAction( ra );
- delete ra;
+ m_statusLabel->setText( "Failed (no discovery module available)" );
+ m_statusProgressBar->setVisible( false );
+ m_statusAction->setVisible( true );
break;
}
-}
-
-void RendererMenu::addRendererAction(QAction *action)
-{
- insertAction( status, action );
- group->addAction( action );
-}
-
-void RendererMenu::removeRendererAction(QAction *action)
-{
- removeAction( action );
- group->removeAction( action );
-}
-
-void RendererMenu::reset()
-{
- /* reset the list of renderers */
- foreach (QAction* action, group->actions())
- {
- RendererAction *ra = qobject_cast<RendererAction *>( action );
- if( ra )
- {
- removeRendererAction( ra );
- delete ra;
- }
+ case RendererManager::RendererStatus::IDLE:
+ m_statusAction->setVisible( false );
+ break;
}
}
-void RendererMenu::RendererSelected(QAction *action)
-{
- RendererAction *ra = qobject_cast<RendererAction *>( action );
- if( ra )
- RendererManager::getInstance( p_intf )->SelectRenderer( ra->getItem() );
- else
- RendererManager::getInstance( p_intf )->SelectRenderer( NULL );
-}
/* CheckableListMenu */
-CheckableListMenu::CheckableListMenu(QString title, QAbstractListModel* model , GroupingMode grouping, QWidget *parent)
+CheckableListMenu::CheckableListMenu(QString title, QAbstractListModel* model , QActionGroup::ExclusionPolicy grouping, QWidget *parent)
: QMenu(parent)
, m_model(model)
- , m_grouping(grouping)
{
this->setTitle(title);
- if (m_grouping != UNGROUPED)
- {
- m_actionGroup = new QActionGroup(this);
- if (m_grouping == GROUPED_OPTIONAL)
- {
- m_actionGroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional);
- }
- }
-
- 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];
- if (m_actionGroup)
- m_actionGroup->removeAction(action);
- 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);
- addAction(choiceAction);
- if (m_actionGroup)
- m_actionGroup->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())
- {
- if (m_actionGroup)
- m_actionGroup->removeAction(action);
- 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);
+ ListMenuHelper* helper = new ListMenuHelper(this, model, nullptr, this);
+ helper->getActionGroup()->setExclusionPolicy(grouping);
+ connect(helper, &ListMenuHelper::select, this, [this](int row, bool checked){
+ m_model->setData(m_model->index(row), checked, Qt::CheckStateRole);
+ });
}
// ListMenuHelper
@@ -323,6 +181,9 @@ ListMenuHelper::ListMenuHelper(QMenu * menu, QAbstractListModel * model, QAction
connect(m_model, &QAbstractListModel::modelReset, this, &ListMenuHelper::onModelReset);
}
+
+ListMenuHelper::~ListMenuHelper()
+{}
// Interface
int ListMenuHelper::count() const
@@ -330,6 +191,40 @@ int ListMenuHelper::count() const
return m_actions.count();
}
+QActionGroup* ListMenuHelper::getActionGroup() const
+{
+ return m_group;
+}
+
+
+void ListMenuHelper::setIcon(QAction* action, const QUrl& iconUrl)
+{
+
+ if (!iconUrl.isValid())
+ {
+ action->setIcon({});
+ return;
+ }
+
+ if (m_iconLoader)
+ {
+ disconnect(m_iconLoader.get(), nullptr, this, nullptr);
+ m_iconLoader->cancel();
+ }
+ m_iconLoader.reset(VLCAccessImageProvider::requestImageResponseUnWrapped(iconUrl, {64,64}));
+ connect(m_iconLoader.get(), &QQuickImageResponse::finished, this, [this, action] {
+ std::unique_ptr<QQuickTextureFactory> factory(m_iconLoader->textureFactory());
+ if (!factory)
+ {
+ action->setIcon({});
+ return;
+ }
+ QImage img = factory->image();
+ action->setIcon(QIcon(QPixmap::fromImage(img)));
+ });
+}
+
+
// Private slots
void ListMenuHelper::onRowsInserted(const QModelIndex &, int first, int last)
@@ -349,11 +244,24 @@ void ListMenuHelper::onRowsInserted(const QModelIndex &, int first, int last)
QAction * action = new QAction(name, this);
- action->setCheckable(true);
+ QVariant checked = m_model->data(index, Qt::CheckStateRole);
+ if (checked.isValid() && checked.canConvert<bool>())
+ {
+ action->setCheckable(true);
+ action->setChecked(checked.toBool());
+ }
- bool checked = m_model->data(index, Qt::CheckStateRole).toBool();
+ QVariant iconPath = m_model->data(index, Qt::DecorationRole);
+ if (iconPath.isValid())
+ {
+ QUrl iconUrl;
+ if (iconPath.canConvert<QUrl>())
+ iconUrl = iconPath.toUrl();
+ else if (iconPath.canConvert<QString>())
+ iconUrl = QUrl::fromEncoded(iconPath.toString().toUtf8());
- action->setChecked(checked);
+ setIcon(action, iconUrl);
+ }
// NOTE: We are adding sequentially *before* the next action in the list.
m_menu->insertAction(before, action);
@@ -387,21 +295,44 @@ void ListMenuHelper::onRowsRemoved(const QModelIndex &, int first, int last)
}
void ListMenuHelper::onDataChanged(const QModelIndex & topLeft,
- const QModelIndex & bottomRight, const QVector<int> &)
+ const QModelIndex & bottomRight, const QVector<int> & roles)
{
+ const bool updateDisplay = roles.contains(Qt::DisplayRole);
+ const bool updateChecked = roles.contains(Qt::CheckStateRole);
+ const bool udpateIcon = roles.contains(Qt::DecorationRole);
+
for (int i = topLeft.row(); i <= bottomRight.row(); i++)
{
QAction * action = m_actions.at(i);
-
QModelIndex index = m_model->index(i, 0);
- QString name = m_model->data(index, Qt::DisplayRole).toString();
-
- action->setText(name);
+ if (updateDisplay)
+ {
+ QString name = m_model->data(index, Qt::DisplayRole).toString();
+ action->setText(name);
+ }
- bool checked = m_model->data(index, Qt::CheckStateRole).toBool();
+ if (updateChecked)
+ {
+ QVariant checked = m_model->data(index, Qt::CheckStateRole);
+ if (checked.isValid() && checked.canConvert<bool>())
+ action->setChecked(checked.toBool());
+ }
- action->setChecked(checked);
+ if (udpateIcon)
+ {
+ QVariant iconPath = m_model->data(index, Qt::DecorationRole);
+ if (iconPath.isValid())
+ {
+ QUrl iconUrl;
+ if (iconPath.canConvert<QUrl>())
+ iconUrl = iconPath.toUrl();
+ else if (iconPath.canConvert<QString>())
+ iconUrl = QUrl::fromEncoded(iconPath.toString().toUtf8());
+
+ setIcon(action, iconUrl);
+ }
+ }
}
}
@@ -422,11 +353,11 @@ void ListMenuHelper::onModelReset()
onRowsInserted(QModelIndex(), 0, count - 1);
}
-void ListMenuHelper::onTriggered(bool)
+void ListMenuHelper::onTriggered(bool checked)
{
QAction * action = static_cast<QAction *> (sender());
- emit select(m_actions.indexOf(action));
+ emit select(m_actions.indexOf(action), checked);
}
/* BooleanPropertyAction */
@@ -465,83 +396,17 @@ RecentMenu::RecentMenu(MLRecentsModel* model, MediaLib* ml, QWidget* parent)
, m_model(model)
, m_ml(ml)
{
- connect(m_model, &MLRecentsModel::rowsRemoved, this, &RecentMenu::onRowsRemoved);
- connect(m_model, &MLRecentsModel::rowsInserted, this, &RecentMenu::onRowInserted);
- connect(m_model, &MLRecentsModel::dataChanged, this, &RecentMenu::onDataChanged);
- connect(m_model, &MLRecentsModel::modelReset, this, &RecentMenu::onModelReset);
- m_separator = addSeparator();
- addAction( qtr("&Clear"), m_model, &MLRecentsModel::clearHistory );
- onModelReset();
-}
-
-void RecentMenu::onRowsRemoved(const QModelIndex&, int first, int last)
-{
- for (int i = first; i <= last; i++)
- {
- delete m_actions.at(i);
- }
-
- QList<QAction *>::iterator begin = m_actions.begin();
-
- m_actions.erase(begin + first, begin + last + 1);
-
- if (m_actions.isEmpty())
- setEnabled(false);
-}
-
-void RecentMenu::onRowInserted(const QModelIndex&, int first, int last)
-{
- QAction * before;
-
- if (first < m_actions.count())
- before = m_actions.at(first);
- else
- // NOTE: In that case we insert *before* the 'Clear' separator.
- before = m_separator;
+ QAction* separator = addSeparator();
- for (int i = first; i <= last; i++)
- {
- QModelIndex index = m_model->index(i);
- QString url = m_model->data(index, MLRecentsModel::RECENT_MEDIA_URL).toString();
+ ListMenuHelper* helper = new ListMenuHelper(this, model, separator, this);
+ connect(helper, &ListMenuHelper::select, this, [this](int row, bool){
+ QModelIndex index = m_model->index(row);
- QAction *choiceAction = new QAction(url, this);
-
- // NOTE: We are adding sequentially *before* the next action in the list.
- insertAction(before, choiceAction);
-
- m_actions.insert(i, choiceAction);
-
- connect(choiceAction, &QAction::triggered, [this, choiceAction](){
- QModelIndex index = m_model->index(m_actions.indexOf(choiceAction));
-
- MLItemId id = m_model->data(index, MLRecentsModel::RECENT_MEDIA_ID).value<MLItemId>();
- m_ml->addAndPlay(id);
- });
- setEnabled(true);
- }
-}
-
-void RecentMenu::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& )
-{
- for (int i = topLeft.row(); i <= bottomRight.row(); i++)
- {
- QModelIndex index = m_model->index(i);
- QString title = m_model->data(index, MLRecentsModel::RECENT_MEDIA_URL).toString();
-
- m_actions.at(i)->setText(title);
- }
-}
-
-void RecentMenu::onModelReset()
-{
- qDeleteAll(m_actions);
- m_actions.clear();
+ MLItemId id = m_model->data(index, MLRecentsModel::RECENT_MEDIA_ID).value<MLItemId>();
+ m_ml->addAndPlay(id);
+ });
- int nb_rows = m_model->rowCount();
- if (nb_rows == 0 || nb_rows == -1)
- setEnabled(false);
- else
- onRowInserted({}, 0, nb_rows - 1);
+ addAction( qtr("&Clear"), model, &MLRecentsModel::clearHistory );
}
// BookmarkMenu
@@ -560,7 +425,7 @@ BookmarkMenu::BookmarkMenu(MediaLib * mediaLib, vlc_player_t * player, QWidget *
ListMenuHelper * helper = new ListMenuHelper(this, model, nullptr, this);
- connect(helper, &ListMenuHelper::select, [model](int index)
+ connect(helper, &ListMenuHelper::select, [model](int index, bool )
{
model->select(model->index(index, 0));
});
=====================================
modules/gui/qt/menus/custom_menus.hpp
=====================================
@@ -24,49 +24,11 @@
#include "qt.hpp"
#include <QMenu>
+#include <QActionGroup>
#include "medialibrary/mlrecentsmodel.hpp"
class QAbstractListModel;
-class RendererAction : public QAction
-{
- Q_OBJECT
-
- public:
- RendererAction( vlc_renderer_item_t * );
- ~RendererAction();
- vlc_renderer_item_t *getItem();
-
- private:
- vlc_renderer_item_t *p_item;
-};
-
-class RendererMenu : public QMenu
-{
- Q_OBJECT
-
-public:
- RendererMenu( QMenu *, qt_intf_t * );
- virtual ~RendererMenu();
- void reset();
-
-private slots:
- void addRendererItem( vlc_renderer_item_t * );
- void removeRendererItem( vlc_renderer_item_t * );
- void updateStatus( int );
- void RendererSelected( QAction* );
-
-private:
- void addRendererAction( QAction * );
- void removeRendererAction( QAction * );
- static vlc_renderer_item_t* getMatchingRenderer( const QVariant &,
- vlc_renderer_item_t* );
- QAction *status;
- QActionGroup *group;
- qt_intf_t *p_intf;
-};
-
-
/*
* Construct a menu from a QAbstractListModel with Qt::DisplayRole and Qt::CheckStateRole
*/
@@ -74,12 +36,6 @@ class CheckableListMenu : public QMenu
{
Q_OBJECT
public:
- enum GroupingMode {
- GROUPED_EXLUSIVE,
- GROUPED_OPTIONAL,
- UNGROUPED
- };
-
/**
* @brief CheckableListMenu
* @param title the title of the menu
@@ -87,21 +43,14 @@ public:
* @param grouping whether the menu should use an ActionGroup or not
* @param parent QObject parent
*/
- CheckableListMenu(QString title, QAbstractListModel* model , GroupingMode grouping = UNGROUPED, 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();
+ CheckableListMenu(QString title, QAbstractListModel* model, QActionGroup::ExclusionPolicy grouping = QActionGroup::ExclusionPolicy::None, QWidget *parent = nullptr);
-private:
- QAbstractListModel* m_model;
- GroupingMode m_grouping;
- QActionGroup* m_actionGroup = nullptr;
+protected:
+ QAbstractListModel* m_model = nullptr;
};
+class QQuickImageResponse;
+
// NOTE: This class is a helper to populate and maintain a QMenu from an QAbstractListModel.
class ListMenuHelper : public QObject
{
@@ -111,10 +60,13 @@ public:
// NOTE: The model actions will be inserted before 'before' or at the end if it's NULL.
ListMenuHelper(QMenu * menu, QAbstractListModel * model, QAction * before = nullptr,
QObject * parent = nullptr);
+ virtual ~ListMenuHelper();
public: // Interface
int count() const;
+ QActionGroup* getActionGroup() const;
+
private slots:
void onRowsInserted(const QModelIndex & parent, int first, int last);
void onRowsRemoved (const QModelIndex & parent, int first, int last);
@@ -127,11 +79,13 @@ private slots:
void onTriggered(bool checked);
signals:
- void select(int index);
+ void select(int index, bool checked);
void countChanged(int count);
private:
+ void setIcon(QAction* action, const QUrl& iconUrl);
+
QMenu * m_menu = nullptr;
QActionGroup * m_group = nullptr;
@@ -141,6 +95,8 @@ private:
QList<QAction *> m_actions;
QAction * m_before = nullptr;
+
+ std::unique_ptr<QQuickImageResponse> m_iconLoader;
};
/**
@@ -166,24 +122,41 @@ private:
QString m_propertyName;
};
-class RecentMenu : public QMenu
+class RendererManager;
+class QProgressBar;
+class QLabel;
+class PlayerController;
+class RendererMenu : public QMenu
{
Q_OBJECT
+
public:
- RecentMenu(MLRecentsModel* model, MediaLib* ml, QWidget *parent = nullptr);
+ RendererMenu( QMenu* parent, qt_intf_t* intf, PlayerController* playerController );
+ virtual ~RendererMenu();
private slots:
- void onRowsRemoved(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 onModelReset();
+ void updateStatus();
+
+protected:
+ QProgressBar* m_statusProgressBar;
+ QLabel* m_statusLabel;
+
+ QAction *m_statusAction;
+ QActionGroup *group;
+ qt_intf_t *p_intf;
+ RendererManager* m_renderManager = nullptr;
+};
+
+
+class RecentMenu : public QMenu
+{
+ Q_OBJECT
+public:
+ RecentMenu(MLRecentsModel* model, MediaLib* ml, QWidget *parent = nullptr);
private:
MLRecentsModel* m_model = nullptr;
- QAction* m_separator = nullptr;
MediaLib* m_ml = nullptr;
-
- QList<QAction *> m_actions;
};
class BookmarkMenu : public QMenu
=====================================
modules/gui/qt/menus/menus.cpp
=====================================
@@ -359,7 +359,7 @@ void VLCMenuBar::ViewMenu(qt_intf_t *p_intf, QMenu *menu, std::optional<bool> pl
action->setCheckable( true );
action->setChecked( mi->hasGridView() );
- menu->addMenu( new CheckableListMenu(qtr( "&Color Scheme" ), mi->getColorScheme(), CheckableListMenu::GROUPED_EXLUSIVE, menu) );
+ menu->addMenu( new CheckableListMenu(qtr( "&Color Scheme" ), mi->getColorScheme(), QActionGroup::ExclusionPolicy::Exclusive, menu) );
menu->addSeparator();
@@ -377,7 +377,7 @@ void VLCMenuBar::InterfacesMenu( qt_intf_t *p_intf, QMenu *current )
{
assert(current);
VLCVarChoiceModel* model = new VLCVarChoiceModel(VLC_OBJECT(p_intf->intf), "intf-add", current);
- CheckableListMenu* submenu = new CheckableListMenu(qtr("Interfaces"), model, CheckableListMenu::UNGROUPED, current);
+ CheckableListMenu* submenu = new CheckableListMenu(qtr("Interfaces"), model, QActionGroup::ExclusionPolicy::None, current);
current->addMenu(submenu);
}
@@ -421,7 +421,7 @@ void VLCMenuBar::AudioMenu( qt_intf_t *p_intf, QMenu * current )
{
if( current->isEmpty() )
{
- current->addMenu(new CheckableListMenu(qtr( "Audio &Track" ), THEMIM->getAudioTracks(), CheckableListMenu::GROUPED_OPTIONAL, current));
+ current->addMenu(new CheckableListMenu(qtr( "Audio &Track" ), THEMIM->getAudioTracks(), QActionGroup::ExclusionPolicy::ExclusiveOptional, current));
QAction *audioDeviceAction = new QAction( qtr( "&Audio Device" ), current );
QMenu *audioDeviceSubmenu = new QMenu( current );
@@ -433,12 +433,12 @@ void VLCMenuBar::AudioMenu( qt_intf_t *p_intf, QMenu * current )
VLCVarChoiceModel *mix_mode = THEMIM->getAudioMixMode();
if (mix_mode->rowCount() == 0)
- current->addMenu( new CheckableListMenu(qtr( "&Stereo Mode" ), THEMIM->getAudioStereoMode(), CheckableListMenu::GROUPED_EXLUSIVE, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Stereo Mode" ), THEMIM->getAudioStereoMode(), QActionGroup::ExclusionPolicy::Exclusive, current) );
else
- current->addMenu( new CheckableListMenu(qtr( "&Mix Mode" ), mix_mode, CheckableListMenu::GROUPED_EXLUSIVE, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Mix Mode" ), mix_mode, QActionGroup::ExclusionPolicy::Exclusive, current) );
current->addSeparator();
- current->addMenu( new CheckableListMenu(qtr( "&Visualizations" ), THEMIM->getAudioVisualizations(), CheckableListMenu::GROUPED_EXLUSIVE, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Visualizations" ), THEMIM->getAudioVisualizations(), QActionGroup::ExclusionPolicy::Exclusive, current) );
VolumeEntries( p_intf, current );
}
}
@@ -450,7 +450,7 @@ void VLCMenuBar::SubtitleMenu( qt_intf_t *p_intf, QMenu *current, bool b_popup )
{
addDPStaticEntry( current, qtr( "Add &Subtitle File..." ), "",
&DialogsProvider::loadSubtitlesFile);
- current->addMenu(new CheckableListMenu(qtr( "Sub &Track" ), THEMIM->getSubtitleTracks(), CheckableListMenu::GROUPED_OPTIONAL, current));
+ current->addMenu(new CheckableListMenu(qtr( "Sub &Track" ), THEMIM->getSubtitleTracks(), QActionGroup::ExclusionPolicy::ExclusiveOptional, current));
current->addSeparator();
}
}
@@ -463,7 +463,7 @@ void VLCMenuBar::VideoMenu( qt_intf_t *p_intf, QMenu *current )
{
if( current->isEmpty() )
{
- current->addMenu(new CheckableListMenu(qtr( "Video &Track" ), THEMIM->getVideoTracks(), CheckableListMenu::GROUPED_OPTIONAL, current));
+ current->addMenu(new CheckableListMenu(qtr( "Video &Track" ), THEMIM->getVideoTracks(), QActionGroup::ExclusionPolicy::ExclusiveOptional, current));
current->addSeparator();
/* Surface modifiers */
@@ -476,15 +476,15 @@ void VLCMenuBar::VideoMenu( qt_intf_t *p_intf, QMenu *current )
current->addSeparator();
/* Size modifiers */
- current->addMenu( new CheckableListMenu(qtr( "&Zoom" ), THEMIM->getZoom(), CheckableListMenu::GROUPED_EXLUSIVE, current) );
- current->addMenu( new CheckableListMenu(qtr( "&Aspect Ratio" ), THEMIM->getAspectRatio(), CheckableListMenu::GROUPED_EXLUSIVE, current) );
- current->addMenu( new CheckableListMenu(qtr( "&Crop" ), THEMIM->getCrop(), CheckableListMenu::GROUPED_EXLUSIVE, current) );
- current->addMenu( new CheckableListMenu(qtr( "&Fit" ), THEMIM->getFit(), CheckableListMenu::GROUPED_EXLUSIVE, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Zoom" ), THEMIM->getZoom(), QActionGroup::ExclusionPolicy::Exclusive, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Aspect Ratio" ), THEMIM->getAspectRatio(), QActionGroup::ExclusionPolicy::Exclusive, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Crop" ), THEMIM->getCrop(), QActionGroup::ExclusionPolicy::Exclusive, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Fit" ), THEMIM->getFit(), QActionGroup::ExclusionPolicy::Exclusive, current) );
current->addSeparator();
/* Rendering modifiers */
- current->addMenu( new CheckableListMenu(qtr( "&Deinterlace" ), THEMIM->getDeinterlace(), CheckableListMenu::GROUPED_EXLUSIVE, current) );
- current->addMenu( new CheckableListMenu(qtr( "&Deinterlace mode" ), THEMIM->getDeinterlaceMode(), CheckableListMenu::GROUPED_EXLUSIVE, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Deinterlace" ), THEMIM->getDeinterlace(), QActionGroup::ExclusionPolicy::Exclusive, current) );
+ current->addMenu( new CheckableListMenu(qtr( "&Deinterlace mode" ), THEMIM->getDeinterlaceMode(), QActionGroup::ExclusionPolicy::Exclusive, current) );
current->addSeparator();
/* Other actions */
@@ -503,11 +503,11 @@ void VLCMenuBar::NavigMenu( qt_intf_t *p_intf, QMenu *menu )
QAction *action;
QMenu *submenu;
- menu->addMenu( new CheckableListMenu( qtr( "T&itle" ), THEMIM->getTitles(), CheckableListMenu::GROUPED_EXLUSIVE, menu) );
- submenu = new CheckableListMenu( qtr( "&Chapter" ), THEMIM->getChapters(), CheckableListMenu::GROUPED_EXLUSIVE, menu );
+ menu->addMenu( new CheckableListMenu( qtr( "T&itle" ), THEMIM->getTitles(), QActionGroup::ExclusionPolicy::Exclusive, menu) );
+ submenu = new CheckableListMenu( qtr( "&Chapter" ), THEMIM->getChapters(), QActionGroup::ExclusionPolicy::Exclusive, menu );
submenu->setTearOffEnabled( true );
menu->addMenu( submenu );
- menu->addMenu( new CheckableListMenu( qtr("&Program") , THEMIM->getPrograms(), CheckableListMenu::GROUPED_EXLUSIVE, menu) );
+ menu->addMenu( new CheckableListMenu( qtr("&Program") , THEMIM->getPrograms(), QActionGroup::ExclusionPolicy::Exclusive, menu) );
MainCtx * mi = p_intf->p_mi;
@@ -527,7 +527,7 @@ void VLCMenuBar::NavigMenu( qt_intf_t *p_intf, QMenu *menu )
menu->addSeparator();
if ( !VLCMenuBar::rendererMenu )
- VLCMenuBar::rendererMenu = new RendererMenu( NULL, p_intf );
+ VLCMenuBar::rendererMenu = new RendererMenu( NULL, p_intf, p_intf->p_mainPlayerController );
menu->addMenu( VLCMenuBar::rendererMenu );
menu->addSeparator();
@@ -833,7 +833,7 @@ QMenu* VLCMenuBar::PopupMenu( qt_intf_t *p_intf, bool show )
}
VLCVarChoiceModel* skinmodel = new VLCVarChoiceModel(p_object, "intf-skins", submenu);
- CheckableListMenu* skinsubmenu = new CheckableListMenu(qtr("Interface"), skinmodel, CheckableListMenu::GROUPED_OPTIONAL, submenu);
+ CheckableListMenu* skinsubmenu = new CheckableListMenu(qtr("Interface"), skinmodel, QActionGroup::ExclusionPolicy::ExclusiveOptional, submenu);
submenu->addMenu(skinsubmenu);
submenu->addSeparator();
=====================================
modules/gui/qt/menus/qml_menu_wrapper.cpp
=====================================
@@ -467,7 +467,7 @@ bool QmlMenuPositioner::eventFilter(QObject * object, QEvent * event)
ListMenuHelper * helper = new ListMenuHelper(m_menu.get(), titles, sectionChapters, m_menu.get());
- connect(helper, &ListMenuHelper::select, [titles](int index)
+ connect(helper, &ListMenuHelper::select, [titles](int index, bool)
{
titles->setData(titles->index(index), true, Qt::CheckStateRole);
});
@@ -486,7 +486,7 @@ bool QmlMenuPositioner::eventFilter(QObject * object, QEvent * event)
helper = new ListMenuHelper(m_menu.get(), chapters, sectionBookmarks, m_menu.get());
- connect(helper, &ListMenuHelper::select, [chapters](int index)
+ connect(helper, &ListMenuHelper::select, [chapters](int index, bool)
{
chapters->setData(chapters->index(index), true, Qt::CheckStateRole);
});
@@ -511,7 +511,7 @@ bool QmlMenuPositioner::eventFilter(QObject * object, QEvent * event)
helper = new ListMenuHelper(m_menu.get(), bookmarks, nullptr, m_menu.get());
- connect(helper, &ListMenuHelper::select, [bookmarks](int index)
+ connect(helper, &ListMenuHelper::select, [bookmarks](int index, bool)
{
bookmarks->select(bookmarks->index(index, 0));
});
@@ -551,7 +551,7 @@ void QmlBookmarkMenu::close()
ListMenuHelper * helper = new ListMenuHelper(m_menu.get(), programs, nullptr, m_menu.get());
- connect(helper, &ListMenuHelper::select, [programs](int index)
+ connect(helper, &ListMenuHelper::select, [programs](int index, bool)
{
programs->setData(programs->index(index), true, Qt::CheckStateRole);
});
@@ -578,7 +578,7 @@ void QmlProgramMenu::close()
if (m_ctx == nullptr)
return;
- m_menu = std::make_unique<RendererMenu>(nullptr, m_ctx->getIntf());
+ m_menu = std::make_unique<RendererMenu>(nullptr, m_ctx->getIntf(), m_ctx->getIntf()->p_mainPlayerController);
connect(m_menu.get(), &QMenu::aboutToHide, this, &QmlRendererMenu::aboutToHide);
connect(m_menu.get(), &QMenu::aboutToShow, this, &QmlRendererMenu::aboutToShow);
=====================================
modules/gui/qt/meson.build
=====================================
@@ -1066,18 +1066,18 @@ if qt6_dep.found()
'link_with' : qmllibs,
}
- test_qt6_dep = dependency('qt6', version: '=' + qt6_dep.version(), modules: ['QuickTest'], required: false)
- if test_qt6_dep.found()
+ quicktest_qt6_dep = dependency('qt6', version: '=' + qt6_dep.version(), modules: ['QuickTest'], required: false)
+ if quicktest_qt6_dep.found()
qml_test_moc = qt6.compile_moc(
headers : files('tests/qml_test.hpp'),
- dependencies : [test_qt6_dep, qt6_dep]
+ dependencies : [quicktest_qt6_dep, qt6_dep]
)
qml_test = executable(
'qml_test',
[files('tests/qml_test.cpp', 'tests/qml_test.hpp'), qml_test_moc],
qt6pre_qrc,
build_by_default: false,
- dependencies: [test_qt6_dep, qt6_dep],
+ dependencies: [quicktest_qt6_dep, qt6_dep],
link_with: qmllibs,
cpp_args: ['-DQUICK_TEST_SOURCE_DIR="' + meson.current_source_dir() + '/tests"']
)
@@ -1101,4 +1101,26 @@ if qt6_dep.found()
'link_with': [libvlccore],
'dependencies': [qt6_dep, qt_extra_deps],
}
+
+ qtest_qt6_dep = dependency('qt6', version: '=' + qt6_dep.version(), modules: ['Test'], required: false)
+ if qtest_qt6_dep.found()
+ vlc_tests += {
+ 'name': 'test_qt_renderer_manager',
+ 'sources': files(
+ 'tests/test_renderer_manager_model.cpp',
+ 'util/renderer_manager.hpp',
+ 'util/renderer_manager.cpp'
+ ),
+ 'moc_sources': files(
+ 'tests/test_renderer_manager_model.cpp'
+ ),
+ 'moc_headers': files(
+ 'util/renderer_manager.hpp'
+ ),
+ 'suite': ['qt'],
+ 'include_directories' : qt_include_dir,
+ 'link_with': [libvlccore, libvlc],
+ 'dependencies': [qt6_dep, qt_extra_deps, qtest_qt6_dep],
+ }
+ endif
endif
=====================================
modules/gui/qt/player/player_controller.cpp
=====================================
@@ -1125,6 +1125,7 @@ PlayerControllerPrivate::PlayerControllerPrivate(PlayerController *playercontrol
, m_audioMixMode((audio_output_t*)nullptr, "mix-mode")
, m_audioDeviceList(m_player)
, m_audioVisualization((audio_output_t*)nullptr, "visual")
+ , m_rendererManager(p_intf, m_player)
{
{
vlc_player_locker locker{m_player};
@@ -2067,7 +2068,7 @@ QABSTRACTLIST_GETTER( VLCVarChoiceModel, getDeinterlaceMode, m_deinterlaceMode)
QABSTRACTLIST_GETTER( VLCVarChoiceModel, getAudioStereoMode, m_audioStereoMode)
QABSTRACTLIST_GETTER( VLCVarChoiceModel, getAudioMixMode, m_audioMixMode)
QABSTRACTLIST_GETTER( VLCVarChoiceModel, getAudioVisualizations, m_audioVisualization)
-
+QABSTRACTLIST_GETTER( RendererManager, getRendererManager, m_rendererManager)
#undef QABSTRACTLIST_GETTER
=====================================
modules/gui/qt/player/player_controller.hpp
=====================================
@@ -34,12 +34,14 @@
#include "util/varchoicemodel.hpp"
#include "util/vlctick.hpp"
+Q_MOC_INCLUDE("util/renderer_manager.hpp")
using vlc_player_locker = vlc_locker<vlc_player_t, vlc_player_Lock, vlc_player_Unlock>;
using SharedVOutThread = vlc_shared_data_ptr_type(vout_thread_t, vout_Hold, vout_Release);
using SharedAOut = vlc_shared_data_ptr_type(audio_output_t, aout_Hold, aout_Release);
+class RendererManager;
class QSignalMapper;
class IMEvent : public QEvent
@@ -191,6 +193,7 @@ public:
Q_PROPERTY(VLCTick ABLoopA READ getABLoopA NOTIFY ABLoopAChanged FINAL)
Q_PROPERTY(VLCTick ABLoopB READ getABLoopB NOTIFY ABLoopBChanged FINAL)
Q_PROPERTY(bool recording READ isRecording WRITE setRecording NOTIFY recordingChanged FINAL)
+ Q_PROPERTY(RendererManager* rendererManager READ getRendererManager CONSTANT FINAL)
// High resolution time fed by SMPTE Timer
Q_PROPERTY(QString highResolutionTime READ highResolutionTime NOTIFY highResolutionTimeChanged FINAL)
@@ -394,6 +397,9 @@ public slots:
QString getAlbum() const;
QUrl getArtwork() const;
+ //Renderer
+ RendererManager* getRendererManager();
+
signals:
//playback
void playingStateChanged( PlayingState state );
=====================================
modules/gui/qt/player/player_controller_p.hpp
=====================================
@@ -24,6 +24,7 @@
#include "input_models.hpp"
#include "util/varchoicemodel.hpp"
#include "util/shared_input_item.hpp"
+#include "util/renderer_manager.hpp"
#include <QTimer>
#include <QUrl>
@@ -173,6 +174,8 @@ public:
QString m_artUrl;
struct input_stats_t m_stats;
+ RendererManager m_rendererManager;
+
// meta
QString m_title;
QString m_artist;
=====================================
modules/gui/qt/tests/test_renderer_manager_model.cpp
=====================================
@@ -0,0 +1,492 @@
+/*****************************************************************************
+ * Copyright (C) 2024 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.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Define a builtin module for mocked parts */
+#define MODULE_NAME renderer_manager_test
+#undef VLC_DYNAMIC_PLUGIN
+
+#include "../../../../test/libvlc/test.h"
+
+#include <vlc/vlc.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_playlist.h>
+#include <vlc_services_discovery.h>
+#include <vlc_renderer_discovery.h>
+#include <vlc_probe.h>
+#include <vlc_interface.h>
+#include <vlc_player.h>
+
+#include "../../../../lib/libvlc_internal.h"
+
+#include "qt.hpp"
+
+namespace vlc {
+class Compositor {};
+}
+
+static vlc_renderer_discovery_t* g_rd = nullptr;
+static bool g_rd_probe_enabled = false;
+
+static int OpenRD( vlc_object_t* p_this )
+{
+ g_rd = (vlc_renderer_discovery_t *)p_this;
+ return VLC_SUCCESS;
+}
+
+static void CloseRD( vlc_object_t* )
+{
+ g_rd = nullptr;
+
+}
+
+static int vlc_rd_probe_open(vlc_object_t *obj) {
+ auto probe = (struct vlc_probe_t *)obj;
+
+ if (g_rd_probe_enabled)
+ vlc_rd_probe_add(probe, "rd", "a fake renderer for testing purpose");
+ //only probe ourself
+ return VLC_PROBE_STOP;
+}
+
+static qt_intf_t* g_intf = nullptr;
+
+static int OpenIntf(vlc_object_t* p_this)
+{
+ auto intfThread = reinterpret_cast<intf_thread_t*>(p_this);
+ libvlc_int_t* libvlc = vlc_object_instance( p_this );
+
+ /* Ensure initialization of objects in qt_intf_t. */
+ g_intf = vlc_object_create<qt_intf_t>( libvlc );
+ if (!g_intf)
+ return VLC_ENOMEM;
+
+ g_intf->obj.logger = vlc_LogHeaderCreate(libvlc->obj.logger, "qt");
+ if (!g_intf->obj.logger)
+ {
+ vlc_object_delete(g_intf);
+ return VLC_EGENERIC;
+ }
+ g_intf->intf = intfThread;
+ intfThread->p_sys = reinterpret_cast<intf_sys_t*>(g_intf);
+ return VLC_SUCCESS;
+}
+
+static void CloseIntf( vlc_object_t *p_this )
+{
+ intf_thread_t* intfThread = (intf_thread_t*)(p_this);
+ auto p_intf = reinterpret_cast<qt_intf_t*>(intfThread->p_sys);
+ if (!p_intf)
+ return;
+ vlc_LogDestroy(p_intf->obj.logger);
+ vlc_object_delete(p_intf);
+}
+
+vlc_module_begin()
+ set_callbacks(OpenIntf, CloseIntf)
+ set_capability("interface", 0)
+add_submodule()
+ set_capability("renderer_discovery", 0)
+ add_shortcut("rd")
+ set_callbacks(OpenRD, CloseRD)
+add_submodule()
+ set_capability("renderer probe", 10000)
+ set_callback(vlc_rd_probe_open)
+vlc_module_end()
+
+extern "C" {
+
+const char vlc_module_name[] = MODULE_STRING;
+VLC_EXPORT vlc_plugin_cb vlc_static_modules[] = {
+ VLC_SYMBOL(vlc_entry),
+ NULL
+};
+}
+
+#include <QTest>
+#include <QAbstractItemModelTester>
+#include "../util/renderer_manager.hpp"
+#include <vlc_cxx_helpers.hpp>
+
+using RendererItemPtr = vlc_shared_data_ptr_type(vlc_renderer_item_t,
+ vlc_renderer_item_hold,
+ vlc_renderer_item_release);
+
+
+class TestClass : public QObject
+{
+ Q_OBJECT
+
+private:
+ RendererItemPtr pushDummyRDItem(int i) {
+ QString name = QString("name%1").arg(i);
+ QString sout = QString("dummy://%1.%1.%1.%1:%1").arg(i);
+ RendererItemPtr item(vlc_renderer_item_new(
+ "type", qtu(name), qtu(sout), "extra sout",
+ nullptr, "icon://", i ));
+ vlc_rd_add_item( g_rd, item.get() );
+ return item;
+ }
+
+ void checkDummyRDItem(int row, int id = -1) {
+ if (id == -1)
+ id = row;
+ QModelIndex idx = m_model->index(row, 0);
+ QCOMPARE(m_model->data(idx, RendererManager::TYPE), "type");
+ QCOMPARE(m_model->data(idx, RendererManager::NAME), QString("name%1").arg(id));
+ QCOMPARE(m_model->data(idx, RendererManager::SOUT), QString("dummy{ip=%1.%1.%1.%1,port=%1,extra sout}").arg(id));
+ QCOMPARE(m_model->data(idx, RendererManager::ICON_URI), QString("icon://"));
+ QCOMPARE(m_model->data(idx, RendererManager::FLAGS), id);
+ }
+
+private slots:
+ void initTestCase() {
+ test_init();
+
+ m_vlc = libvlc_new(test_defaults_nargs, test_defaults_args);
+ libvlc_InternalAddIntf(m_vlc->p_libvlc_int, MODULE_STRING);
+ libvlc_InternalPlay(m_vlc->p_libvlc_int);
+
+ m_playlist = vlc_intf_GetMainPlaylist(g_intf->intf);
+ m_player = vlc_playlist_GetPlayer( m_playlist );
+ }
+
+ void cleanupTestCase() {
+ libvlc_release(m_vlc);
+ }
+
+ void init() {
+ g_rd_probe_enabled = true;
+ m_model = new RendererManager(g_intf, m_player);
+ //QAbstractItemModelTester checks that QAbstractItemModel events are coherents
+ m_modelTester = new QAbstractItemModelTester(m_model);
+ QVERIFY(g_rd == nullptr);
+ }
+
+ void cleanup() {
+ delete m_modelTester;
+ delete m_model;
+ QVERIFY(g_rd == nullptr);
+ }
+
+ void testEmpty() {
+ QVERIFY(g_rd == nullptr);
+ //model is empty before scan
+ QCOMPARE(m_model->rowCount(), 0);
+ m_model->StartScan();
+ QVERIFY(g_rd != nullptr);
+ QCOMPARE(m_model->rowCount(), 0);
+ QCOMPARE(m_model->getStatus(), RendererManager::RUNNING);
+ //scan didn't find anything
+ m_model->StopScan();
+ QVERIFY(g_rd == nullptr);
+ QCOMPARE(m_model->rowCount(), 0);
+ QCOMPARE(m_model->getStatus(), RendererManager::IDLE);
+ }
+
+ void testStatic() {
+ QCOMPARE(m_model->rowCount(), 0);
+ m_model->StartScan();
+ QCOMPARE(m_model->rowCount(), 0);
+ QCOMPARE(m_model->getStatus(), RendererManager::RUNNING);
+
+ QVERIFY(g_rd != nullptr);
+
+ for (int i = 0; i < 5; ++i) {
+ pushDummyRDItem(i);
+ QCOMPARE(m_model->rowCount(), i+1);
+ checkDummyRDItem(i);
+ }
+ m_model->StopScan();
+
+ //items are still present
+ QCOMPARE(m_model->rowCount(), 5);
+ QCOMPARE(m_model->getStatus(), RendererManager::IDLE);
+
+ for (int i = 0; i < 5; ++i) {
+ checkDummyRDItem(i);
+ }
+
+ //module is closed
+ QVERIFY(g_rd == nullptr);
+ }
+
+ void testTwoPassesIdentical() {
+ m_model->StartScan();
+ QVERIFY(g_rd != nullptr);
+ QCOMPARE(m_model->rowCount(), 0);
+ for (int i = 0; i < 5; ++i) {
+ pushDummyRDItem(i);
+ }
+ QCOMPARE(m_model->rowCount(), 5);
+ m_model->StopScan();
+ QCOMPARE(m_model->rowCount(), 5);
+
+ //second pass will yield the same items
+ m_model->StartScan();
+ QCOMPARE(m_model->rowCount(), 5);
+ for (int i = 0; i < 5; ++i) {
+ pushDummyRDItem(i);
+ }
+ QCOMPARE(m_model->rowCount(), 5);
+ m_model->StopScan();
+ QCOMPARE(m_model->rowCount(), 5);
+ QCOMPARE(m_model->getStatus(), RendererManager::IDLE);
+
+ for (int i = 0; i < 5; ++i) {
+ checkDummyRDItem(i);
+ }
+ }
+
+ void testTwoPassesDifferent() {
+ m_model->StartScan();
+ for (int i = 0; i < 5; ++i) {
+ pushDummyRDItem(i);
+ }
+ m_model->StopScan();
+
+ //second pass will yield the same items
+ m_model->StartScan();
+ QCOMPARE(m_model->rowCount(), 5);
+ for (int i = 0; i < 3; ++i) {
+ pushDummyRDItem(i);
+ }
+ QCOMPARE(m_model->rowCount(), 5);
+ m_model->StopScan();
+ //items from former passes have been removed
+ QCOMPARE(m_model->rowCount(), 3);
+ QCOMPARE(m_model->getStatus(), RendererManager::IDLE);
+ for (int i = 0; i < 3; ++i) {
+ checkDummyRDItem(i);
+ }
+ }
+
+ void testRemovedItem() {
+ m_model->StartScan();
+ for (int i = 0; i < 3; ++i) {
+ pushDummyRDItem(i);
+ }
+ auto item = pushDummyRDItem(3);
+ for (int i = 4; i <= 6; ++i) {
+ pushDummyRDItem(i);
+ }
+ QCOMPARE(m_model->rowCount(), 7);
+ vlc_rd_remove_item(g_rd, item.get());
+ QCOMPARE(m_model->rowCount(), 6);
+ m_model->StopScan();
+ QCOMPARE(m_model->rowCount(), 6);
+
+ for (int i = 0; i < 3; ++i) {
+ checkDummyRDItem(i, i);
+ }
+ for (int i = 3; i < 6; ++i) {
+ checkDummyRDItem(i, i+1);
+ }
+ }
+
+ void testRendererSelection() {
+ m_model->StartScan();
+
+ auto r0 = pushDummyRDItem(0);
+ auto r1 = pushDummyRDItem(1);
+ auto r2 = pushDummyRDItem(2);
+ auto r3 = pushDummyRDItem(3);
+ auto r4 = pushDummyRDItem(4);
+
+ //item are not selected by default
+ QCOMPARE(m_model->useRenderer(), false);
+ for (int i = 0; i < 5; ++i) {
+ QModelIndex idx = m_model->index(i, 0);
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), false);
+ }
+
+ //select a first renderer
+ QModelIndex selidx = m_model->index(3);
+ m_model->setData(selidx, true, RendererManager::SELECTED);
+ QCOMPARE(m_model->useRenderer(), true);
+ for (int i = 0; i < 5; ++i) {
+ QModelIndex idx = m_model->index(i, 0);
+ if (i == 3)
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), true);
+ else
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), false);
+ }
+
+ //player renderer is actually set
+ {
+ vlc_player_Lock(m_player);
+ QCOMPARE(vlc_player_GetRenderer(m_player), r3.get());
+ vlc_player_Unlock(m_player);
+ }
+
+ //select another renderer
+ selidx = m_model->index(2);
+ m_model->setData(selidx, true, RendererManager::SELECTED);
+ QCOMPARE(m_model->useRenderer(), true);
+ for (int i = 0; i < 5; ++i) {
+ QModelIndex idx = m_model->index(i, 0);
+ if (i == 2)
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), true);
+ else
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), false);
+ }
+
+ //player renderer has actually changed
+ {
+ vlc_player_Lock(m_player);
+ QCOMPARE(vlc_player_GetRenderer(m_player), r2.get());
+ vlc_player_Unlock(m_player);
+ }
+
+ //deselect renderer from index
+ m_model->setData(selidx, false, RendererManager::SELECTED);
+ QCOMPARE(m_model->useRenderer(), false);
+ for (int i = 0; i < 5; ++i) {
+ QModelIndex idx = m_model->index(i, 0);
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), false);
+ }
+
+ //player renderer is actually unset
+ {
+ vlc_player_Lock(m_player);
+ QCOMPARE(vlc_player_GetRenderer(m_player), nullptr);
+ vlc_player_Unlock(m_player);
+ }
+
+ //re-select renderer then deselect it using disableRenderer
+ m_model->setData(selidx, true, RendererManager::SELECTED);
+ m_model->disableRenderer();
+ QCOMPARE(m_model->useRenderer(), false);
+ for (int i = 0; i < 5; ++i) {
+ QModelIndex idx = m_model->index(i, 0);
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), false);
+ }
+
+
+ //make the player select the renderer itself
+ {
+ vlc_player_Lock(m_player);
+ vlc_player_SetRenderer(m_player, r4.get());
+ vlc_player_Unlock(m_player);
+ }
+ QCOMPARE(m_model->useRenderer(), true);
+ for (int i = 0; i < 5; ++i) {
+ QModelIndex idx = m_model->index(i, 0);
+ if (i == 4)
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), true);
+ else
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), false);
+ }
+ //the make the player unselect the renderer
+ {
+ vlc_player_Lock(m_player);
+ vlc_player_SetRenderer(m_player, nullptr);
+ vlc_player_Unlock(m_player);
+ }
+ QCOMPARE(m_model->useRenderer(), false);
+ for (int i = 0; i < 5; ++i) {
+ QModelIndex idx = m_model->index(i, 0);
+ QCOMPARE(m_model->data(idx, RendererManager::SELECTED), false);
+ }
+ }
+
+ void testSelectionLost() {
+ m_model->StartScan();
+
+ auto r0 = pushDummyRDItem(0);
+ auto r1 = pushDummyRDItem(1);
+ auto r2 = pushDummyRDItem(2);
+ auto r3 = pushDummyRDItem(3);
+ auto r4 = pushDummyRDItem(4);
+
+ QModelIndex selidx = m_model->index(3);
+ m_model->setData(selidx, true, RendererManager::SELECTED);
+
+ //selected item is removed
+ QCOMPARE(m_model->rowCount(), 5);
+ vlc_rd_remove_item( g_rd, r3.get() );
+
+ //item is held by model
+ QCOMPARE(m_model->useRenderer(), true);
+ QCOMPARE(m_model->rowCount(), 5);
+
+ //disable the renderer
+ m_model->setData(selidx, false, RendererManager::SELECTED);
+ QCOMPARE(m_model->useRenderer(), false);
+ //item is kept by model until next scan
+ QCOMPARE(m_model->rowCount(), 5);
+ m_model->StopScan();
+ QCOMPARE(m_model->rowCount(), 5);
+
+ //item should be gone after next scan
+ m_model->StartScan();
+ vlc_rd_add_item( g_rd, r0.get() );
+ vlc_rd_add_item( g_rd, r1.get() );
+ vlc_rd_add_item( g_rd, r2.get() );
+ //no r3
+ vlc_rd_add_item( g_rd, r4.get() );
+ m_model->StopScan();
+ QCOMPARE(m_model->rowCount(), 4);
+
+ //not finding the selected item again should keep it in the model
+ selidx = m_model->index(2);
+ m_model->setData(selidx, true, RendererManager::SELECTED);
+ m_model->StartScan();
+ vlc_rd_add_item( g_rd, r0.get() );
+ vlc_rd_add_item( g_rd, r1.get() );
+ m_model->StopScan();
+ QCOMPARE(m_model->rowCount(), 3);
+ QCOMPARE(m_model->data(selidx, RendererManager::SELECTED), true);
+ QCOMPARE(m_model->useRenderer(), true);
+ m_model->StartScan();
+ m_model->StopScan();
+ selidx = m_model->index(0);
+ QCOMPARE(m_model->rowCount(), 1);
+ QCOMPARE(m_model->data(selidx, RendererManager::SELECTED), true);
+ QCOMPARE(m_model->useRenderer(), true);
+ }
+
+ //deleting the model without stopping the scan
+ void testNoStopScanDeletion() {
+ m_model->StartScan();
+ }
+
+ //failed state when no renderer manager is found
+ void testNoProbes() {
+ g_rd_probe_enabled = false;
+ QCOMPARE(m_model->getStatus(), RendererManager::IDLE);
+ m_model->StartScan();
+ QCOMPARE(m_model->getStatus(), RendererManager::FAILED);
+ QCOMPARE(m_model->rowCount(), 0);
+ QVERIFY(g_rd == nullptr);
+ g_rd_probe_enabled = true;
+ }
+private:
+ libvlc_instance_t* m_vlc = nullptr;
+ vlc_playlist_t* m_playlist = nullptr;
+ vlc_player_t* m_player = nullptr;
+ QAbstractItemModelTester* m_modelTester = nullptr;
+ RendererManager* m_model;
+};
+
+QTEST_GUILESS_MAIN(TestClass)
+#include "test_renderer_manager_model.moc"
=====================================
modules/gui/qt/util/renderer_manager.cpp
=====================================
@@ -1,108 +1,412 @@
+/*****************************************************************************
+ * Copyright (C) 2024 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.
+ *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "renderer_manager.hpp"
-#include "player/player_controller.hpp"
#include <QApplication>
+#include <QTimer>
+#include <QVector>
+#include <QHash>
#include <vlc_common.h>
#include <vlc_renderer_discovery.h>
#include <vlc_player.h>
+#include <vlc_cxx_helpers.hpp>
-const QEvent::Type RendererManagerEvent::AddedEvent =
- (QEvent::Type)QEvent::registerEventType();
-const QEvent::Type RendererManagerEvent::RemovedEvent =
- (QEvent::Type)QEvent::registerEventType();
+static void player_renderer_changed(vlc_player_t *, vlc_renderer_item_t *new_item, void *data);
-RendererManagerEvent::RendererManagerEvent(Type type, vlc_renderer_item_t* p_item_)
- : QEvent( type ), p_item( p_item_ )
-{
- vlc_renderer_item_hold( p_item );
-}
+using RendererItemPtr = vlc_shared_data_ptr_type(vlc_renderer_item_t, vlc_renderer_item_hold, vlc_renderer_item_release);
+using vlc_player_locker = vlc_locker<vlc_player_t, vlc_player_Lock, vlc_player_Unlock>;
-RendererManagerEvent::~RendererManagerEvent()
+struct ItemEntry
{
- vlc_renderer_item_release( p_item );
-}
+ ItemEntry(const RendererItemPtr& renderItem, bool current = true)
+ : renderItem(renderItem)
+ , currentScan(current)
+ {}
+ ItemEntry(){}
-RendererManager::RendererManager( qt_intf_t *p_intf_ ) :
- p_intf( p_intf_ ), p_selected_item( NULL )
-{
- connect( &m_stop_scan_timer, &QTimer::timeout, this, &RendererManager::RendererMenuCountdown );
-}
+ RendererItemPtr renderItem;
+ /**
+ * we scan for 20 seconds, when the scan ends render discoverer are destroyed but
+ * items are kept in the model, at this moment the items are marked as "old", if
+ * a new scan starts and finish without finding them again, then they will be removed
+ * from the model
+ */
+ bool currentScan = true;
+};
-RendererManager::~RendererManager()
+struct RendererManagerPrivate
{
- StopScan();
- foreach( ItemEntry entry, m_items )
+ RendererManagerPrivate(RendererManager* pub, qt_intf_t* const intf, vlc_player_t* const player)
+ : p_intf(intf)
+ , m_player(player)
+ , q_ptr(pub)
+ {
+ assert(m_player);
+ QObject::connect( &m_stop_scan_timer, &QTimer::timeout, q_ptr, [this](){ timerCountdown(); });
+
+ static struct vlc_player_cbs cbs = {};
+ cbs.on_renderer_changed = &player_renderer_changed;
+ {
+ vlc_player_locker lock{ m_player };
+ m_playerListener = vlc_player_AddListener(m_player, &cbs, this);
+ }
+ }
+
+ ~RendererManagerPrivate()
{
- emit rendererItemRemoved( entry.second );
- vlc_renderer_item_release( entry.second );
+ if (m_playerListener)
+ {
+ vlc_player_locker lock{ m_player };
+ vlc_player_RemoveListener(m_player, m_playerListener);
+ }
}
-}
-void RendererManager::customEvent( QEvent *event )
-{
- if( event->type() == RendererManagerEvent::AddedEvent ||
- event->type() == RendererManagerEvent::RemovedEvent )
+ void timerCountdown()
+ {
+ Q_Q(RendererManager);
+ if( m_stop_scan_timer.isActive() && m_scan_remain > 0 )
+ {
+ m_scan_remain--;
+ emit q->scanRemainChanged();
+ }
+ else
+ {
+ q->StopScan();
+ }
+ }
+
+ void setStatus(RendererManager::RendererStatus status)
{
- RendererManagerEvent *ev = static_cast<RendererManagerEvent *>(event);
- QString souturi( vlc_renderer_item_sout( ev->getItem() ) );
- vlc_renderer_item_t *p_item = ev->getItem();
+ Q_Q(RendererManager);
+ if (status == m_status)
+ return;
+ m_status = status;
+ emit q->statusChanged();
+ }
+
- if( event->type() == RendererManagerEvent::AddedEvent )
+ void onRendererAdded(const RendererItemPtr& renderItem) {
+ Q_Q(RendererManager);
+ QString key = getItemKey(renderItem);
+ if( !m_items.contains( key ) )
{
- if( !m_items.contains( souturi ) )
+ emit q->beginInsertRows({}, m_itemsIndex.size(), m_itemsIndex.size());
+ m_items.emplace( key, renderItem );
+ m_itemsIndex.push_back(key);
+ emit q->endInsertRows();
+ }
+ else
+ {
+ /* mark the item as seen during this scan */
+ ItemEntry& entry = m_items[key];
+ entry.currentScan = true;
+ }
+ }
+
+ void onRendererRemoved(const RendererItemPtr& renderItem) {
+ Q_Q(RendererManager);
+
+ QString key = getItemKey(renderItem);
+ if( m_items.contains( key ) )
+ {
+ //only remove the item if we are using it
+ //and if we are actively scanning
+ if( m_selectedItem != renderItem && m_status == RendererManager::RUNNING )
{
- vlc_renderer_item_hold( p_item );
- ItemEntry entry( true, p_item );
- m_items.insert( souturi, entry );
- emit rendererItemAdded( p_item );
+ qsizetype index = m_itemsIndex.indexOf(key);
+ emit q->beginRemoveRows({}, index, index);
+ m_items.remove(key);
+ m_itemsIndex.remove(index);
+ emit q->endRemoveRows();
}
- else /* incref for now */
+ }
+ }
+
+ void onPlayerRendererChanged(const RendererItemPtr& renderItem)
+ {
+ Q_Q(RendererManager);
+
+ if (m_selectedItem == renderItem)
+ return;
+
+ if (m_selectedItem)
+ {
+ int oldIndexRow = getItemRow(m_selectedItem);
+ m_selectedItem.reset();
+ if (oldIndexRow >= 0)
{
- ItemEntry &entry = m_items[ souturi ];
- entry.first = true; /* to be kept */
+ auto oldIndex = q->index(oldIndexRow);
+ emit q->dataChanged(oldIndex, oldIndex, { Qt::CheckStateRole, RendererManager::SELECTED });
}
}
- else /* remove event */
+
+ if (renderItem)
{
- if( m_items.contains( souturi ) )
+ m_selectedItem = renderItem;
+
+ int newIndexRow= getItemRow(m_selectedItem);
+ if (newIndexRow >= 0)
+ {
+ auto newIndex = q->index(newIndexRow);
+ emit q->dataChanged(newIndex, newIndex, { Qt::CheckStateRole, RendererManager::SELECTED });
+ }
+ else
{
- p_item = m_items[ souturi ].second;
- if( p_selected_item != p_item )
- {
- m_items.remove( souturi );
- emit rendererItemRemoved( p_item );
- vlc_renderer_item_release( p_item );
- }
- else m_items[ souturi ].first = true; /* keep */
+ QString key = getItemKey(m_selectedItem);
+ emit q->beginInsertRows({}, m_itemsIndex.size(), m_itemsIndex.size());
+ //renderer is not part of the current scan, mark it like it
+ m_items.emplace( key, renderItem, false );
+ m_itemsIndex.push_back(key);
+ emit q->endInsertRows();
}
- /* else ignore */
}
+ emit q->useRendererChanged();
+ }
+
+ QString getItemKey(const RendererItemPtr& item) const
+ {
+ const char* type = vlc_renderer_item_type(item.get());
+ const char* sout = vlc_renderer_item_sout(item.get());
+ return QString("%1-%2").arg(type).arg(sout);
+ }
+
+ int getItemRow(const RendererItemPtr& item) const
+ {
+ QString newKey = getItemKey(item);
+ return m_itemsIndex.indexOf(newKey);
+ }
+
+ qt_intf_t* const p_intf;
+ vlc_player_t* const m_player;
+
+ vlc_player_listener_id* m_playerListener = nullptr;
+
+ RendererManager::RendererStatus m_status = RendererManager::IDLE;
+
+ std::vector<vlc_renderer_discovery_t*> m_rds;
+
+
+ //the elements of the model (in model order), the string is the key of m_items
+ QVector<QString> m_itemsIndex;
+
+ QHash<QString, ItemEntry> m_items;
+
+ //renderer currently used by the player
+ RendererItemPtr m_selectedItem;
+
+ //model automatically stops scanning after a while
+ QTimer m_stop_scan_timer;
+ //remaining time in seconds
+ unsigned m_scan_remain = 0;
+
+ RendererManager* q_ptr;
+ Q_DECLARE_PUBLIC(RendererManager);
+};
+
+void RendererManager::disableRenderer()
+{
+ Q_D(RendererManager);
+ if (!d->m_selectedItem)
+ return;
+
+ {
+ vlc_player_locker lock{ d->m_player };
+ //vlc_player synchronously report the renderer change
+ vlc_player_SetRenderer( d->m_player, nullptr );
}
}
+QVariant RendererManager::data(const QModelIndex &index, int role) const
+{
+ Q_D(const RendererManager);
+ if (index.row() >= d->m_itemsIndex.size())
+ return {};
+
+ const ItemEntry& entry = d->m_items.value(d->m_itemsIndex[index.row()]);
+ vlc_renderer_item_t* item = entry.renderItem.get();
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ case NAME:
+ return QString(vlc_renderer_item_name(item));
+ case TYPE:
+ return QString(vlc_renderer_item_type(item));
+ case DEMUX_FILTER:
+ return QString(vlc_renderer_item_demux_filter(item));
+ case SOUT:
+ return QString(vlc_renderer_item_sout(item));
+ case ICON_URI:
+ case Qt::DecorationRole:
+ {
+ const char* iconUri = vlc_renderer_item_icon_uri(item);
+ if (!iconUri)
+ {
+ if (vlc_renderer_item_flags( item ) & VLC_RENDERER_CAN_VIDEO)
+ return QString("qrc://menu/movie.svg");
+ else
+ return QString("qrc://menu/music.svg");
+ }
+ return QString(iconUri);
+ }
+ case FLAGS:
+ return vlc_renderer_item_flags(entry.renderItem.get());
+ case SELECTED:
+ return entry.renderItem == d->m_selectedItem;
+ case Qt::CheckStateRole:
+ return entry.renderItem == d->m_selectedItem ? Qt::Checked : Qt::Unchecked;
+ }
+ return {};
+}
+
+bool RendererManager::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ Q_D(RendererManager);
+ if (index.row() >= d->m_itemsIndex.size())
+ return false;
+
+ QString sout = d->m_itemsIndex[index.row()];
+ ItemEntry& entry = d->m_items[sout];
+ switch (role)
+ {
+ case Qt::CheckStateRole:
+ case SELECTED:
+ {
+ bool enableRenderer;
+
+ if ( value.canConvert<bool>() )
+ enableRenderer = value.toBool();
+ else
+ return false;
+
+ {
+ vlc_player_locker lock{ d->m_player };
+ //vlc_player synchronously report the renderer change
+ vlc_player_SetRenderer(
+ d->m_player,
+ enableRenderer ? entry.renderItem.get() : nullptr
+ );
+ }
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+QHash<int,QByteArray> RendererManager::roleNames() const
+{
+ return {
+ { NAME, "name" },
+ { TYPE, "type" },
+ { DEMUX_FILTER, "demuxFilter" },
+ { SOUT, "sour" },
+ { ICON_URI, "iconUri" },
+ { FLAGS, "flags" },
+ };
+}
+
+int RendererManager::rowCount(const QModelIndex&) const
+{
+ Q_D(const RendererManager);
+ return d->m_itemsIndex.size();
+}
+
+
+static void renderer_event_item_added( vlc_renderer_discovery_t* p_rd, vlc_renderer_item_t *p_item )
+{
+ RendererManagerPrivate *self = reinterpret_cast<RendererManagerPrivate*>( p_rd->owner.sys );
+ RendererItemPtr renderItem(p_item);
+ QMetaObject::invokeMethod(self->q_func(), [self, renderItem = std::move(renderItem)](){
+ self->onRendererAdded(renderItem);
+ });
+}
+
+static void renderer_event_item_removed( vlc_renderer_discovery_t *p_rd,
+ vlc_renderer_item_t *p_item )
+{
+ RendererManagerPrivate *self = reinterpret_cast<RendererManagerPrivate*>( p_rd->owner.sys );
+ RendererItemPtr renderItem(p_item);
+ QMetaObject::invokeMethod(self->q_func(), [self, renderItem = std::move(renderItem)](){
+ self->onRendererRemoved(renderItem);
+ });
+}
+
+static void player_renderer_changed(vlc_player_t *, vlc_renderer_item_t *new_item, void *data)
+{
+ RendererManagerPrivate *self = reinterpret_cast<RendererManagerPrivate*>( data);
+ RendererItemPtr renderItem(new_item);
+ QMetaObject::invokeMethod(self->q_func(), [self, renderItem = std::move(renderItem)](){
+ self->onPlayerRendererChanged(renderItem);
+ });
+}
+
+RendererManager::RendererManager( qt_intf_t *p_intf_, vlc_player_t* player )
+ : d_ptr(new RendererManagerPrivate(this, p_intf_, player))
+{
+}
+
+RendererManager::~RendererManager()
+{
+ StopScan();
+}
+
+int RendererManager::getScanRemain() const
+{
+ Q_D(const RendererManager);
+ return d->m_scan_remain;
+}
+
+RendererManager::RendererStatus RendererManager::getStatus() const
+{
+ Q_D(const RendererManager);
+ return d->m_status;
+}
+
+bool RendererManager::useRenderer() const
+{
+ Q_D(const RendererManager);
+ return d->m_selectedItem.get() != nullptr;
+}
+
void RendererManager::StartScan()
{
- if( m_stop_scan_timer.isActive() )
+ Q_D(RendererManager);
+ if( d->m_stop_scan_timer.isActive() )
return;
/* SD subnodes */
char **ppsz_longnames;
char **ppsz_names;
- if( vlc_rd_get_names( p_intf, &ppsz_names, &ppsz_longnames ) != VLC_SUCCESS )
+ if( vlc_rd_get_names( d->p_intf, &ppsz_names, &ppsz_longnames ) != VLC_SUCCESS )
{
- emit statusUpdated( RendererManager::RendererStatus::FAILED );
+ d->setStatus( RendererManager::RendererStatus::FAILED );
return;
}
struct vlc_renderer_discovery_owner owner =
{
- this,
+ d,
renderer_event_item_added,
renderer_event_item_removed,
};
@@ -110,83 +414,50 @@ void RendererManager::StartScan()
char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
for( ; *ppsz_name; ppsz_name++, ppsz_longname++ )
{
- msg_Dbg( p_intf, "starting renderer discovery service %s", *ppsz_longname );
- vlc_renderer_discovery_t* p_rd = vlc_rd_new( VLC_OBJECT(p_intf), *ppsz_name, &owner );
+ msg_Dbg( d->p_intf, "starting renderer discovery service %s", *ppsz_longname );
+ vlc_renderer_discovery_t* p_rd = vlc_rd_new( VLC_OBJECT(d->p_intf), *ppsz_name, &owner );
if( p_rd != NULL )
- m_rds.push_back( p_rd );
+ d->m_rds.push_back( p_rd );
free( *ppsz_name );
free( *ppsz_longname );
}
free( ppsz_names );
free( ppsz_longnames );
- emit statusUpdated( RendererManager::RendererStatus::RUNNING );
- m_scan_remain = 20000;
- m_stop_scan_timer.setInterval( 1000 );
- m_stop_scan_timer.start();
+ d->m_scan_remain = 20;
+ d->m_stop_scan_timer.setInterval( 1000 );
+ d->m_stop_scan_timer.start();
+ d->setStatus( RendererManager::RendererStatus::RUNNING );
+ emit scanRemainChanged();
}
void RendererManager::StopScan()
{
- m_stop_scan_timer.stop();
- foreach ( vlc_renderer_discovery_t* p_rd, m_rds )
+ Q_D(RendererManager);
+ d->m_stop_scan_timer.stop();
+
+ for ( vlc_renderer_discovery_t* p_rd : d->m_rds )
vlc_rd_release( p_rd );
+ d->m_rds.clear();
+
/* Cleanup of outdated items, and notify removal */
- QHash<QString, ItemEntry>::iterator it = m_items.begin();
- while ( it != m_items.end() )
+ for (int i = d->m_itemsIndex.size() - 1; i >= 0; --i)
{
- ItemEntry &entry = it.value();
- if( !entry.first /* don't keep */ && entry.second != p_selected_item )
+ QString key = d->m_itemsIndex[i];
+ ItemEntry& entry = d->m_items[key];
+ if( !entry.currentScan && entry.renderItem != d->m_selectedItem )
{
- emit rendererItemRemoved( entry.second );
- vlc_renderer_item_release( entry.second );
- it = m_items.erase( it );
+ //remove items from previous scans
+ emit beginRemoveRows({}, i, i);
+ d->m_items.remove(key);
+ d->m_itemsIndex.remove(i);
+ emit endRemoveRows();
}
else
{
- entry.first = false; /* don't keep if not updated by new detect */
- assert( it.value().first == false );
- ++it;
+ /* don't keep if not updated by next detect */
+ entry.currentScan = false;
}
}
- m_rds.clear();
- emit statusUpdated( RendererManager::RendererStatus::IDLE );
-}
-
-void RendererManager::RendererMenuCountdown()
-{
- if( m_stop_scan_timer.isActive() && m_scan_remain > 0 )
- {
- m_scan_remain -= 1000;
- emit statusUpdated( RendererManager::RendererStatus::RUNNING + m_scan_remain / 1000 );
- }
- else
- {
- StopScan();
- }
-}
-
-void RendererManager::SelectRenderer( vlc_renderer_item_t *p_item )
-{
- p_selected_item = p_item;
- vlc_player_locker lock{ p_intf->p_player };
- vlc_player_SetRenderer( p_intf->p_player, p_item );
-}
-
-void RendererManager::renderer_event_item_added( vlc_renderer_discovery_t* p_rd,
- vlc_renderer_item_t *p_item )
-{
- RendererManager *self = reinterpret_cast<RendererManager*>( p_rd->owner.sys );
- QEvent *ev = new RendererManagerEvent( RendererManagerEvent::AddedEvent,
- p_item );
- QApplication::postEvent( self, ev );
-}
-
-void RendererManager::renderer_event_item_removed( vlc_renderer_discovery_t *p_rd,
- vlc_renderer_item_t *p_item )
-{
- RendererManager *self = reinterpret_cast<RendererManager*>( p_rd->owner.sys );
- QEvent *ev = new RendererManagerEvent( RendererManagerEvent::RemovedEvent,
- p_item );
- QApplication::postEvent( self, ev );
+ d->setStatus( RendererManager::RendererStatus::IDLE );
}
=====================================
modules/gui/qt/util/renderer_manager.hpp
=====================================
@@ -1,38 +1,30 @@
+/*****************************************************************************
+ * Copyright (C) 2024 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 RENDERER_MANAGER_HPP
#define RENDERER_MANAGER_HPP
#include "qt.hpp"
-#include "util/singleton.hpp"
+#include <QAbstractListModel>
-#include <QObject>
-#include <QEvent>
-#include <QTimer>
-#include <QVector>
-#include <QHash>
-
-extern "C" {
- typedef struct vlc_renderer_item_t vlc_renderer_item_t;
-}
-
-class RendererManagerEvent : public QEvent
-{
-public:
- static const QEvent::Type AddedEvent;
- static const QEvent::Type RemovedEvent;
-
- RendererManagerEvent( QEvent::Type type, vlc_renderer_item_t *p_item_ );
- virtual ~RendererManagerEvent();
-
- vlc_renderer_item_t * getItem() const { return p_item; }
-
-private:
- vlc_renderer_item_t *p_item;
-};
-
-class RendererManager : public QObject, public Singleton<RendererManager>
+class RendererManagerPrivate;
+class RendererManager : public QAbstractListModel
{
Q_OBJECT
- friend class Singleton<RendererManager>;
public:
enum RendererStatus
@@ -41,36 +33,53 @@ public:
IDLE = -1,
RUNNING,
};
- RendererManager( qt_intf_t * );
+ Q_ENUM(RendererStatus)
+
+ enum Roles {
+ NAME = Qt::UserRole,
+ TYPE,
+ DEMUX_FILTER,
+ SOUT,
+ ICON_URI,
+ FLAGS,
+ SELECTED
+ };
+
+ ///remaining time before scan timeout
+ Q_PROPERTY(int scanRemain READ getScanRemain NOTIFY scanRemainChanged FINAL)
+ Q_PROPERTY(RendererStatus status READ getStatus NOTIFY statusChanged FINAL)
+ Q_PROPERTY(bool useRenderer READ useRenderer NOTIFY useRendererChanged FINAL)
+
+public:
+ RendererManager( qt_intf_t* intf, vlc_player_t* player );
virtual ~RendererManager();
- void customEvent( QEvent * );
+
+ void disableRenderer();
public slots:
- void SelectRenderer( vlc_renderer_item_t * );
void StartScan();
void StopScan();
+ //QAbstractListModel override
+public:
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+ QHash<int,QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ //properties accessor
+public:
+ int getScanRemain() const;
+ RendererStatus getStatus() const;
+ bool useRenderer() const;
signals:
- void rendererItemAdded( vlc_renderer_item_t * ); /* For non queued only */
- void rendererItemRemoved( vlc_renderer_item_t * ); /* For non queued only */
- void statusUpdated( int );
+ void statusChanged();
+ void scanRemainChanged();
+ void useRendererChanged();
private:
- static void renderer_event_item_added( vlc_renderer_discovery_t *,
- vlc_renderer_item_t * );
- static void renderer_event_item_removed( vlc_renderer_discovery_t *,
- vlc_renderer_item_t * );
-
- typedef std::pair<bool, vlc_renderer_item_t *> ItemEntry;
- qt_intf_t* const p_intf;
- const vlc_renderer_item_t *p_selected_item;
- QVector<vlc_renderer_discovery_t*> m_rds;
- QHash<QString, ItemEntry> m_items;
- QTimer m_stop_scan_timer;
- unsigned m_scan_remain;
-
-private slots:
- void RendererMenuCountdown();
+ Q_DECLARE_PRIVATE(RendererManager);
+ QScopedPointer<RendererManagerPrivate> d_ptr;
};
#endif // RENDERER_MANAGER_HPP
=====================================
modules/gui/qt/util/vlcaccess_image_provider.cpp
=====================================
@@ -127,6 +127,12 @@ public:
return errorStr;
}
+ void cancel() override
+ {
+ reader.reset();
+ emit finished();
+ }
+
private:
void handleImageRead()
{
@@ -171,6 +177,11 @@ QString VLCAccessImageProvider::wrapUri(QString path)
return QStringLiteral("image://vlcaccess/?") + query.toString(QUrl::FullyEncoded);
}
+QQuickImageResponse* VLCAccessImageProvider::requestImageResponseUnWrapped(const QUrl url, const QSize &requestedSize, VLCAccessImageProvider::ImagePostProcessCb cb)
+{
+ return new VLCAccessImageResponse(url, requestedSize, cb);
+}
+
//// VLCImageAccess
VLCAccessImage::VLCAccessImage(QObject* parent)
=====================================
modules/gui/qt/util/vlcaccess_image_provider.hpp
=====================================
@@ -73,6 +73,8 @@ public:
static QString wrapUri(QString path);
+ static QQuickImageResponse* requestImageResponseUnWrapped(const QUrl, const QSize &requestedSize, ImagePostProcessCb cb = nullptr);
+
private:
ImagePostProcessCb postProcessCb;
};
=====================================
test/meson.build
=====================================
@@ -15,6 +15,7 @@ foreach vlc_test: vlc_tests
'name',
'sources',
'moc_headers',
+ 'moc_sources',
'suite',
'link_with',
'module_depends',
@@ -57,10 +58,12 @@ foreach vlc_test: vlc_tests
endif
moc_sources = []
- if vlc_test.has_key('moc_headers') and qt6_dep.found()
- moc_sources += qt6.preprocess(moc_headers: vlc_test['moc_headers'],
- include_directories: qt_include_dir,
- dependencies: qt6_dep)
+ if (vlc_test.has_key('moc_headers') or vlc_test.has_key('moc_sources')) and qt6_dep.found()
+ moc_sources += qt6.preprocess(
+ moc_headers: vlc_test.get('moc_headers', []),
+ moc_sources: vlc_test.get('moc_sources', []),
+ include_directories: qt_include_dir,
+ dependencies: qt6_dep)
endif
test(vlc_test['name'],
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7b1acdc1bfb741b3bb7c2fef2b485af61e737953...e32174b38709288e07b6e1e6e71994a96e186731
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7b1acdc1bfb741b3bb7c2fef2b485af61e737953...e32174b38709288e07b6e1e6e71994a96e186731
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