You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
569 lines
17 KiB
569 lines
17 KiB
/*****************************************************************************
|
|
* custom_menus.cpp : Qt custom menus classes
|
|
*****************************************************************************
|
|
* Copyright © 2006-2018 VideoLAN authors
|
|
* 2018 VideoLabs
|
|
*
|
|
* 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 <vlc_common.h>
|
|
#include <vlc_renderer_discovery.h>
|
|
|
|
#include "custom_menus.hpp"
|
|
#include "util/renderer_manager.hpp"
|
|
|
|
// MediaLibrary includes
|
|
#include "medialibrary/mlbookmarkmodel.hpp"
|
|
|
|
// Dialogs includes
|
|
#include "dialogs/dialogs_provider.hpp"
|
|
|
|
// Menus includes
|
|
#include "menus/menus.hpp"
|
|
|
|
// Qt includes
|
|
#include <QMenu>
|
|
#include <QAction>
|
|
#include <QActionGroup>
|
|
#include <QWidgetAction>
|
|
#include <QVBoxLayout>
|
|
#include <QLabel>
|
|
#include <QProgressBar>
|
|
#include <QMetaObject>
|
|
#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;
|
|
}
|
|
|
|
RendererMenu::RendererMenu( QMenu *parent, qt_intf_t *p_intf_ )
|
|
: QMenu( parent ), p_intf( p_intf_ )
|
|
{
|
|
setTitle( qtr("&Renderer") );
|
|
|
|
group = new QActionGroup( this );
|
|
|
|
QAction *action = new QAction( qtr("<Local>"), this );
|
|
action->setCheckable(true);
|
|
addAction( action );
|
|
group->addAction(action);
|
|
|
|
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 );
|
|
|
|
addSeparator();
|
|
|
|
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 );
|
|
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 );
|
|
}
|
|
|
|
RendererMenu::~RendererMenu()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void RendererMenu::updateStatus( int val )
|
|
{
|
|
QProgressBar *pb = findChild<QProgressBar *>("statusprogressbar");
|
|
QLabel *label = findChild<QLabel *>("statuslabel");
|
|
if( val >= RendererManager::RendererStatus::RUNNING )
|
|
{
|
|
label->setText( qtr("Scanning...").
|
|
append( QString(" (%1s)").arg( val ) ) );
|
|
pb->setVisible( true );
|
|
status->setVisible( true );
|
|
}
|
|
else if( val == RendererManager::RendererStatus::FAILED )
|
|
{
|
|
label->setText( "Failed (no discovery module available)" );
|
|
pb->setVisible( false );
|
|
status->setVisible( true );
|
|
}
|
|
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())
|
|
{
|
|
RendererAction *ra = qobject_cast<RendererAction *>( action );
|
|
if( !ra || ra->getItem() != p_item )
|
|
continue;
|
|
removeRendererAction( ra );
|
|
delete ra;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
: 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
|
|
|
|
ListMenuHelper::ListMenuHelper(QMenu * menu, QAbstractListModel * model, QAction * before,
|
|
QObject * parent)
|
|
: QObject(parent), m_menu(menu), m_model(model), m_before(before)
|
|
{
|
|
m_group = new QActionGroup(this);
|
|
|
|
onModelReset();
|
|
|
|
connect(m_model, &QAbstractListModel::rowsInserted, this, &ListMenuHelper::onRowsInserted);
|
|
connect(m_model, &QAbstractListModel::rowsRemoved, this, &ListMenuHelper::onRowsRemoved);
|
|
|
|
connect(m_model, &QAbstractListModel::dataChanged, this, &ListMenuHelper::onDataChanged);
|
|
|
|
connect(m_model, &QAbstractListModel::modelReset, this, &ListMenuHelper::onModelReset);
|
|
}
|
|
|
|
// Interface
|
|
|
|
int ListMenuHelper::count() const
|
|
{
|
|
return m_actions.count();
|
|
}
|
|
|
|
// Private slots
|
|
|
|
void ListMenuHelper::onRowsInserted(const QModelIndex &, int first, int last)
|
|
{
|
|
QAction * before;
|
|
|
|
if (first < m_actions.count())
|
|
before = m_actions.at(first);
|
|
else
|
|
before = m_before;
|
|
|
|
for (int i = first; i <= last; i++)
|
|
{
|
|
QModelIndex index = m_model->index(i, 0);
|
|
|
|
QString name = m_model->data(index, Qt::DisplayRole).toString();
|
|
|
|
QAction * action = new QAction(name, this);
|
|
|
|
action->setCheckable(true);
|
|
|
|
bool checked = m_model->data(index, Qt::CheckStateRole).toBool();
|
|
|
|
action->setChecked(checked);
|
|
|
|
// NOTE: We are adding sequentially *before* the next action in the list.
|
|
m_menu->insertAction(before, action);
|
|
|
|
m_group->addAction(action);
|
|
|
|
m_actions.insert(i, action);
|
|
|
|
connect(action, &QAction::triggered, this, &ListMenuHelper::onTriggered);
|
|
}
|
|
|
|
emit countChanged(m_actions.count());
|
|
}
|
|
|
|
void ListMenuHelper::onRowsRemoved(const QModelIndex &, int first, int last)
|
|
{
|
|
for (int i = first; i <= last; i++)
|
|
{
|
|
QAction * action = m_actions.at(i);
|
|
|
|
m_group->removeAction(action);
|
|
|
|
delete action;
|
|
}
|
|
|
|
QList<QAction *>::iterator begin = m_actions.begin();
|
|
|
|
m_actions.erase(begin + first, begin + last + 1);
|
|
|
|
emit countChanged(m_actions.count());
|
|
}
|
|
|
|
void ListMenuHelper::onDataChanged(const QModelIndex & topLeft,
|
|
const QModelIndex & bottomRight, const QVector<int> &)
|
|
{
|
|
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);
|
|
|
|
bool checked = m_model->data(index, Qt::CheckStateRole).toBool();
|
|
|
|
action->setChecked(checked);
|
|
}
|
|
}
|
|
|
|
void ListMenuHelper::onModelReset()
|
|
{
|
|
for (QAction * action : m_actions)
|
|
{
|
|
m_group->removeAction(action);
|
|
|
|
delete action;
|
|
}
|
|
|
|
m_actions.clear();
|
|
|
|
int count = m_model->rowCount();
|
|
|
|
if (count)
|
|
onRowsInserted(QModelIndex(), 0, count - 1);
|
|
}
|
|
|
|
void ListMenuHelper::onTriggered(bool)
|
|
{
|
|
QAction * action = static_cast<QAction *> (sender());
|
|
|
|
emit select(m_actions.indexOf(action));
|
|
}
|
|
|
|
/* 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.typeId() == QMetaType::Bool);
|
|
const QMetaObject* selfMeta = this->metaObject();
|
|
|
|
assert(property.hasNotifySignal());
|
|
QMetaMethod checkedSlot = selfMeta->method(selfMeta->indexOfSlot( "setChecked(bool)" ));
|
|
connect( model, property.notifySignal(), this, checkedSlot );
|
|
connect( this, &BooleanPropertyAction::triggered, this, &BooleanPropertyAction::setModelChecked );
|
|
|
|
setCheckable(true);
|
|
setChecked(property.read(model).toBool());
|
|
}
|
|
|
|
void BooleanPropertyAction::setModelChecked(bool checked)
|
|
{
|
|
m_model->setProperty(qtu(m_propertyName), QVariant::fromValue<bool>(checked) );
|
|
}
|
|
|
|
|
|
RecentMenu::RecentMenu(MLRecentsModel* model, MediaLib* ml, QWidget* parent)
|
|
: QMenu(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;
|
|
|
|
for (int i = first; i <= last; i++)
|
|
{
|
|
QModelIndex index = m_model->index(i);
|
|
QString url = m_model->data(index, MLRecentsModel::RECENT_MEDIA_URL).toString();
|
|
|
|
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();
|
|
|
|
int nb_rows = m_model->rowCount();
|
|
if (nb_rows == 0 || nb_rows == -1)
|
|
setEnabled(false);
|
|
else
|
|
onRowInserted({}, 0, nb_rows - 1);
|
|
}
|
|
|
|
// BookmarkMenu
|
|
|
|
BookmarkMenu::BookmarkMenu(MediaLib * mediaLib, vlc_player_t * player, QWidget * parent)
|
|
: QMenu(parent)
|
|
{
|
|
// FIXME: Do we really need a translation call for the string shortcut ?
|
|
addAction(qtr("&Manage"), THEDP, &DialogsProvider::bookmarksDialog, qtr("Ctrl+B"));
|
|
|
|
addSeparator();
|
|
|
|
MLBookmarkModel * model = new MLBookmarkModel(this);
|
|
model->setPlayer(player);
|
|
model->setMl(mediaLib);
|
|
|
|
ListMenuHelper * helper = new ListMenuHelper(this, model, nullptr, this);
|
|
|
|
connect(helper, &ListMenuHelper::select, [model](int index)
|
|
{
|
|
model->select(model->index(index, 0));
|
|
});
|
|
|
|
setTearOffEnabled(true);
|
|
}
|
|
|