1 /*****************************************************************************
2 * plugins.cpp : Plug-ins and extensions listing
3 ****************************************************************************
4 * Copyright (C) 2008-2010 the VideoLAN team
7 * Authors: Jean-Baptiste Kempf <jb (at) videolan.org>
8 * Jean-Philippe André <jpeg (at) videolan.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
29 #include "plugins.hpp"
31 #include "util/searchlineedit.hpp"
32 #include "extensions_manager.hpp"
33 #include "managers/addons_manager.hpp"
34 #include "util/animators.hpp"
38 #include <vlc_modules.h>
40 #include <QTreeWidget>
41 #include <QStringList>
43 #include <QHeaderView>
44 #include <QDialogButtonBox>
47 #include <QVBoxLayout>
49 #include <QHBoxLayout>
50 #include <QVBoxLayout>
51 #include <QSpacerItem>
54 #include <QStyleOptionViewItem>
56 #include <QPushButton>
59 #include <QStylePainter>
60 #include <QGraphicsColorizeEffect>
61 #include <QProgressBar>
66 static QPixmap *loadPixmapFromData( char *, int size );
69 PluginDialog::PluginDialog( intf_thread_t *_p_intf ) : QVLCFrame( _p_intf )
71 setWindowTitle( qtr( "Plugins and extensions" ) );
72 setWindowRole( "vlc-plugins" );
74 QVBoxLayout *layout = new QVBoxLayout( this );
75 tabs = new QTabWidget( this );
76 tabs->addTab( extensionTab = new ExtensionTab( p_intf ),
77 qtr( "Active Extensions" ) );
78 tabs->addTab( pluginTab = new PluginTab( p_intf ),
80 tabs->addTab( addonsTab = new AddonsTab( p_intf ),
81 qtr( "Addons Manager" ) );
82 layout->addWidget( tabs );
84 QDialogButtonBox *box = new QDialogButtonBox;
85 QPushButton *okButton = new QPushButton( qtr( "&Close" ), this );
86 box->addButton( okButton, QDialogButtonBox::RejectRole );
87 layout->addWidget( box );
88 BUTTONACT( okButton, close() );
89 restoreWidgetPosition( "PluginsDialog", QSize( 435, 280 ) );
92 PluginDialog::~PluginDialog()
94 saveWidgetPosition( "PluginsDialog" );
99 PluginTab::PluginTab( intf_thread_t *p_intf_ )
100 : QVLCFrame( p_intf_ )
102 QGridLayout *layout = new QGridLayout( this );
104 /* Main Tree for modules */
105 treePlugins = new QTreeWidget;
106 layout->addWidget( treePlugins, 0, 0, 1, -1 );
108 /* Users cannot move the columns around but we need to sort */
109 #if QT_VERSION >= 0x050000
110 treePlugins->header()->setSectionsMovable( false );
112 treePlugins->header()->setMovable( false );
114 treePlugins->header()->setSortIndicatorShown( true );
115 // treePlugins->header()->setResizeMode( QHeaderView::ResizeToContents );
116 treePlugins->setAlternatingRowColors( true );
117 treePlugins->setColumnWidth( 0, 200 );
119 QStringList headerNames;
120 headerNames << qtr("Name") << qtr("Capability" ) << qtr( "Score" );
121 treePlugins->setHeaderLabels( headerNames );
125 /* Set capability column to the correct Size*/
126 treePlugins->resizeColumnToContents( 1 );
127 treePlugins->header()->restoreState(
128 getSettings()->value( "Plugins/Header-State" ).toByteArray() );
130 treePlugins->setSortingEnabled( true );
131 treePlugins->sortByColumn( 1, Qt::AscendingOrder );
133 QLabel *label = new QLabel( qtr("&Search:"), this );
134 edit = new SearchLineEdit( this );
135 label->setBuddy( edit );
137 layout->addWidget( label, 1, 0 );
138 layout->addWidget( edit, 1, 1, 1, 1 );
139 CONNECT( edit, textChanged( const QString& ),
140 this, search( const QString& ) );
142 setMinimumSize( 500, 300 );
143 restoreWidgetPosition( "Plugins", QSize( 540, 400 ) );
146 inline void PluginTab::FillTree()
149 module_t **p_list = module_list_get( &count );
151 for( unsigned int i = 0; i < count; i++ )
153 module_t *p_module = p_list[i];
156 qs_item << qfu( module_get_name( p_module, true ) )
157 << qfu( module_get_capability( p_module ) )
158 << QString::number( module_get_score( p_module ) );
160 if( qs_item.at(1).isEmpty() ) continue;
163 QTreeWidgetItem *item = new PluginTreeItem( qs_item );
164 treePlugins->addTopLevelItem( item );
166 module_list_free( p_list );
169 void PluginTab::search( const QString& qs )
171 QList<QTreeWidgetItem *> items = treePlugins->findItems( qs, Qt::MatchContains );
172 items += treePlugins->findItems( qs, Qt::MatchContains, 1 );
174 QTreeWidgetItem *item = NULL;
175 for( int i = 0; i < treePlugins->topLevelItemCount(); i++ )
177 item = treePlugins->topLevelItem( i );
178 item->setHidden( !items.contains( item ) );
182 PluginTab::~PluginTab()
184 saveWidgetPosition( "Plugins" );
185 getSettings()->setValue( "Plugins/Header-State",
186 treePlugins->header()->saveState() );
189 void PluginTab::keyPressEvent( QKeyEvent *keyEvent )
191 if( keyEvent->key() == Qt::Key_Return ||
192 keyEvent->key() == Qt::Key_Enter )
198 bool PluginTreeItem::operator< ( const QTreeWidgetItem & other ) const
200 int col = treeWidget()->sortColumn();
201 if( col == PluginTab::SCORE )
202 return text( col ).toInt() < other.text( col ).toInt();
203 else if ( col == PluginTab::CAPABILITY )
205 if ( text( PluginTab::CAPABILITY ) == other.text( PluginTab::CAPABILITY ) )
206 return text( PluginTab::NAME ) < other.text( PluginTab::NAME );
208 return text( PluginTab::CAPABILITY ) < other.text( PluginTab::CAPABILITY );
210 return text( col ) < other.text( col );
214 ExtensionTab::ExtensionTab( intf_thread_t *p_intf_ )
215 : QVLCFrame( p_intf_ )
218 QVBoxLayout *layout = new QVBoxLayout( this );
220 QLabel *notice = new QLabel( qtr("Get more extensions from")
221 + QString( " <a href=\"http://addons.videolan.org/\">"
222 "addons.videolan.org</a>." ) );
223 notice->setOpenExternalLinks( true );
224 layout->addWidget( notice );
227 extList = new QListView( this );
228 CONNECT( extList, activated( const QModelIndex& ),
229 this, moreInformation() );
230 layout->addWidget( extList );
232 // List item delegate
233 ExtensionItemDelegate *itemDelegate = new ExtensionItemDelegate( extList );
234 extList->setItemDelegate( itemDelegate );
236 // Extension list look & feeling
237 extList->setAlternatingRowColors( true );
238 extList->setSelectionMode( QAbstractItemView::SingleSelection );
241 ExtensionListModel *model =
242 new ExtensionListModel( extList, ExtensionsManager::getInstance( p_intf ) );
243 extList->setModel( model );
246 QDialogButtonBox *buttonsBox = new QDialogButtonBox;
248 // More information button
249 butMoreInfo = new QPushButton( QIcon( ":/menu/info" ),
250 qtr( "More information..." ),
252 CONNECT( butMoreInfo, clicked(), this, moreInformation() );
253 buttonsBox->addButton( butMoreInfo, QDialogButtonBox::ActionRole );
256 ExtensionsManager *EM = ExtensionsManager::getInstance( p_intf );
257 QPushButton *reload = new QPushButton( QIcon( ":/update" ),
258 qtr( "Reload extensions" ),
260 CONNECT( reload, clicked(), EM, reloadExtensions() );
261 CONNECT( reload, clicked(), this, updateButtons() );
262 CONNECT( extList->selectionModel(),
263 selectionChanged( const QItemSelection &, const QItemSelection & ),
266 buttonsBox->addButton( reload, QDialogButtonBox::ResetRole );
268 layout->addWidget( buttonsBox );
272 ExtensionTab::~ExtensionTab()
276 void ExtensionTab::updateButtons()
278 butMoreInfo->setEnabled( extList->selectionModel()->hasSelection() );
281 // Do not close on ESC or ENTER
282 void ExtensionTab::keyPressEvent( QKeyEvent *keyEvent )
284 if( keyEvent->key() == Qt::Key_Return ||
285 keyEvent->key() == Qt::Key_Enter )
291 // Show more information
292 void ExtensionTab::moreInformation()
294 QModelIndex index = extList->selectionModel()->selectedIndexes().first();
296 if( !index.isValid() )
299 ExtensionInfoDialog dlg( index, p_intf, this );
304 AddonsTab::AddonsTab( intf_thread_t *p_intf_ ) : QVLCFrame( p_intf_ )
307 QVBoxLayout *layout = new QVBoxLayout( this );
310 QHBoxLayout *filtersLayout = new QHBoxLayout();
312 QLabel *addonsLabel = new QLabel( qtr("Addon type:") );
313 addonsLabel->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
314 filtersLayout->addWidget( addonsLabel );
315 QComboBox *typeCombo = new QComboBox();
316 typeCombo->addItem( qtr("All"), -1 );
317 typeCombo->addItem( qtr("Skins"), ADDON_SKIN2 );
318 typeCombo->addItem( qtr("Playlist parsers"), ADDON_PLAYLIST_PARSER );
319 typeCombo->addItem( qtr("Service Discovery"), ADDON_SERVICE_DISCOVERY );
320 typeCombo->addItem( qtr("Extensions"), ADDON_EXTENSION );
321 CONNECT( typeCombo, currentIndexChanged(int), this, typeChanged( int ) );
322 filtersLayout->addWidget( typeCombo );
324 QCheckBox *installedOnlyBox = new QCheckBox( qtr("Show Installed Only") );
325 filtersLayout->addWidget( installedOnlyBox );
326 CONNECT( installedOnlyBox, stateChanged(int), this, installChecked(int) );
328 layout->addLayout( filtersLayout );
331 helpLabel = new QLabel();
332 layout->addWidget( helpLabel );
335 AddonsManager *AM = AddonsManager::getInstance( p_intf );
338 addonsView = new QListView( this );
339 CONNECT( addonsView, activated( const QModelIndex& ), this, moreInformation() );
340 layout->addWidget( addonsView );
342 // List item delegate
343 AddonItemDelegate *addonsDelegate = new AddonItemDelegate( addonsView );
344 addonsView->setItemDelegate( addonsDelegate );
345 addonsDelegate->setAnimator( new DelegateAnimationHelper( addonsView ) );
346 CONNECT( addonsDelegate, showInfo(), this, moreInformation() );
348 // Extension list look & feeling
349 addonsView->setAlternatingRowColors( true );
350 addonsView->setSelectionMode( QAbstractItemView::SingleSelection );
353 addonsView->setAcceptDrops( true );
354 addonsView->setDefaultDropAction( Qt::CopyAction );
355 addonsView->setDropIndicatorShown( true );
356 addonsView->setDragDropMode( QAbstractItemView::DropOnly );
359 AddonsListModel *model = new AddonsListModel( AM, addonsView );
360 addonsModel = new AddonsSortFilterProxyModel();
361 addonsModel->setDynamicSortFilter( true );
362 addonsModel->setSortRole( Qt::DisplayRole );
363 addonsModel->sort( 0, Qt::AscendingOrder );
364 addonsModel->setSourceModel( model );
365 addonsModel->setFilterRole( Qt::DisplayRole );
366 addonsView->setModel( addonsModel );
368 CONNECT( addonsView->selectionModel(), currentChanged(QModelIndex,QModelIndex),
369 addonsView, edit(QModelIndex) );
371 CONNECT( AM, addonAdded( addon_entry_t * ),
372 model, addonAdded( addon_entry_t * ) );
373 CONNECT( AM, addonChanged( const addon_entry_t * ),
374 model, addonChanged( const addon_entry_t * ) );
376 QList<QString> frames;
377 frames << ":/util/wait1";
378 frames << ":/util/wait2";
379 frames << ":/util/wait3";
380 frames << ":/util/wait4";
381 spinnerAnimation = new PixmapAnimator( this, frames );
382 CONNECT( spinnerAnimation, pixmapReady( const QPixmap & ),
383 addonsView->viewport(), update() );
384 addonsView->viewport()->installEventFilter( this );
387 AddonsTab::~AddonsTab()
389 delete spinnerAnimation;
392 bool AddonsTab::eventFilter( QObject *obj, QEvent *event )
394 if ( obj != addonsView->viewport() )
397 switch( event->type() )
400 if ( spinnerAnimation->state() == PixmapAnimator::Running )
402 QWidget *viewport = qobject_cast<QWidget *>( obj );
403 if ( !viewport ) break;
404 QStylePainter painter( viewport );
405 QPixmap *spinner = spinnerAnimation->getPixmap();
406 QPoint point = viewport->geometry().center();
407 point -= QPoint( spinner->size().width() / 2, spinner->size().height() / 2 );
408 painter.drawPixmap( point, *spinner );
409 QString text = qtr("Retrieving addons...");
410 QSize textsize = fontMetrics().size( 0, text );
411 point = viewport->geometry().center();
412 point -= QPoint( textsize.width() / 2, -spinner->size().height() );
413 painter.drawText( point, text );
415 else if ( addonsModel->rowCount() == 0 )
417 QWidget *viewport = qobject_cast<QWidget *>( obj );
418 if ( !viewport ) break;
419 QStylePainter painter( viewport );
420 QString text = qtr("No addons found");
421 QSize size = fontMetrics().size( 0, text );
422 QPoint point = viewport->geometry().center();
423 point -= QPoint( size.width() / 2, size.height() / 2 );
424 painter.drawText( point, text );
428 if ( addonsView->model()->rowCount() < 1 )
430 AddonsManager *AM = AddonsManager::getInstance( p_intf );
431 CONNECT( AM, discoveryEnded(), spinnerAnimation, stop() );
432 CONNECT( AM, discoveryEnded(), addonsView->viewport(), update() );
433 spinnerAnimation->start();
438 case QEvent::DragEnter:
440 QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent *>(event);
441 if ( !dragEvent ) break;
442 QList<QUrl> urls = dragEvent->mimeData()->urls();
443 if ( dragEvent->proposedAction() != Qt::CopyAction
445 || urls.first().scheme() != "file"
446 || ! urls.first().path().endsWith(".vlp") )
448 dragEvent->acceptProposedAction();
451 case QEvent::DragMove:
453 QDragMoveEvent *moveEvent = static_cast<QDragMoveEvent *>(event);
454 if ( !moveEvent ) break;
455 if ( moveEvent->proposedAction() != Qt::CopyAction )
457 moveEvent->acceptProposedAction();
462 QDropEvent *dropEvent = static_cast<QDropEvent *>(event);
463 if ( !dropEvent ) break;
464 if ( dropEvent->proposedAction() != Qt::CopyAction )
466 if ( dropEvent->mimeData()->urls().count() )
468 AddonsManager *AM = AddonsManager::getInstance( p_intf );
469 AM->findDesignatedAddon( dropEvent->mimeData()->urls().first().toString() );
470 dropEvent->acceptProposedAction();
480 void AddonsTab::moreInformation()
482 QModelIndex index = addonsView->selectionModel()->selectedIndexes().first();
483 if( !index.isValid() ) return;
484 AddonInfoDialog dlg( index, p_intf, this );
488 void AddonsTab::typeChanged( int i )
490 QComboBox *combo = qobject_cast<QComboBox *>( sender() );
491 if ( !combo ) return;
492 int i_type = combo->itemData( i, Qt::UserRole ).toInt();
493 addonsModel->setTypeFilter( i_type );
498 help = qtr( "Skins customize player's appearance."
499 " You can activate them through preferences." );
501 case ADDON_PLAYLIST_PARSER:
502 help = qtr( "Playlist parsers add new capabilities to read"
503 " internet streams or extract meta data." );
505 case ADDON_SERVICE_DISCOVERY:
506 help = qtr( "Service discoveries adds new sources to your playlist"
507 " such as web radios, video websites, ..." );
509 case ADDON_EXTENSION:
510 help = qtr( "Extensions brings various enhancements."
511 " Check descriptions for more details" );
514 helpLabel->setText("");
517 helpLabel->setTextFormat( Qt::RichText );
518 helpLabel->setText( QString( "<img src=\":/menu/info\"/> %1" ).arg( help ) );
521 void AddonsTab::installChecked( int i )
523 if ( i == Qt::Checked )
524 addonsModel->setStatusFilter( ADDON_INSTALLED );
526 addonsModel->setStatusFilter( 0 );
529 /* Safe copy of the extension_t struct */
530 ExtensionListModel::ExtensionCopy::ExtensionCopy( extension_t *p_ext )
532 name = qfu( p_ext->psz_name );
533 description = qfu( p_ext->psz_description );
534 shortdesc = qfu( p_ext->psz_shortdescription );
535 if( description.isEmpty() )
536 description = shortdesc;
537 if( shortdesc.isEmpty() && !description.isEmpty() )
538 shortdesc = description;
539 title = qfu( p_ext->psz_title );
540 author = qfu( p_ext->psz_author );
541 version = qfu( p_ext->psz_version );
542 url = qfu( p_ext->psz_url );
543 icon = loadPixmapFromData( p_ext->p_icondata, p_ext->i_icondata_size );
546 ExtensionListModel::ExtensionCopy::~ExtensionCopy()
551 QVariant ExtensionListModel::ExtensionCopy::data( int role ) const
555 case Qt::DisplayRole:
557 case Qt::DecorationRole:
558 if ( !icon ) return QPixmap( ":/logo/vlc48.png" );
575 /* Extensions list model for the QListView */
576 ExtensionListModel::ExtensionListModel( QObject *parent )
577 : QAbstractListModel( parent ), EM( NULL )
582 ExtensionListModel::ExtensionListModel( QObject *parent, ExtensionsManager* EM_ )
583 : QAbstractListModel( parent ), EM( EM_ )
585 // Connect to ExtensionsManager::extensionsUpdated()
586 CONNECT( EM, extensionsUpdated(), this, updateList() );
588 // Load extensions now if not already loaded
589 EM->loadExtensions();
592 ExtensionListModel::~ExtensionListModel()
594 // Clear extensions list
595 while( !extensions.isEmpty() )
596 delete extensions.takeLast();
599 void ExtensionListModel::updateList()
603 // Clear extensions list
604 while( !extensions.isEmpty() )
606 ext = extensions.takeLast();
610 // Find new extensions
611 extensions_manager_t *p_mgr = EM->getManager();
615 vlc_mutex_lock( &p_mgr->lock );
617 FOREACH_ARRAY( p_ext, p_mgr->extensions )
619 ext = new ExtensionCopy( p_ext );
620 extensions.append( ext );
623 vlc_mutex_unlock( &p_mgr->lock );
624 vlc_object_release( p_mgr );
626 emit dataChanged( index( 0 ), index( rowCount() - 1 ) );
629 int ExtensionListModel::rowCount( const QModelIndex& ) const
632 extensions_manager_t *p_mgr = EM->getManager();
636 vlc_mutex_lock( &p_mgr->lock );
637 count = p_mgr->extensions.i_size;
638 vlc_mutex_unlock( &p_mgr->lock );
639 vlc_object_release( p_mgr );
644 QVariant ExtensionListModel::data( const QModelIndex& index, int role ) const
646 if( !index.isValid() )
649 ExtensionCopy * extension =
650 static_cast<ExtensionCopy *>(index.internalPointer());
654 return extension->data( role );
657 QModelIndex ExtensionListModel::index( int row, int column,
658 const QModelIndex& ) const
661 return QModelIndex();
662 if( row < 0 || row >= extensions.count() )
663 return QModelIndex();
665 return createIndex( row, 0, extensions.at( row ) );
668 AddonsListModel::Addon::Addon( addon_entry_t *p_entry_ )
671 addon_entry_Hold( p_entry );
674 AddonsListModel::Addon::~Addon()
676 addon_entry_Release( p_entry );
679 bool AddonsListModel::Addon::operator==( const Addon & other ) const
681 //return data( IDRole ) == other.data( IDRole );
682 return p_entry == other.p_entry;
685 bool AddonsListModel::Addon::operator==( const addon_entry_t * p_other ) const
687 return p_entry == p_other;
690 QVariant AddonsListModel::Addon::data( int role ) const
694 vlc_mutex_lock( &p_entry->lock );
697 case Qt::DisplayRole:
699 QString name = qfu( p_entry->psz_name );
700 if ( p_entry->e_state == ADDON_INSTALLED )
701 name.append( QString(" (%1)").arg( qtr("installed") ) );
706 case Qt::DecorationRole:
707 if ( p_entry->psz_image_data )
710 pixmap.loadFromData( QByteArray::fromBase64( QByteArray( p_entry->psz_image_data ) ),
716 else if ( p_entry->e_flags & ADDON_BROKEN )
717 returnval = QPixmap( ":/addons/broken" );
719 returnval = QPixmap( ":/addons/default" );
721 case Qt::ToolTipRole:
723 if ( !( p_entry->e_flags & ADDON_MANAGEABLE ) )
725 returnval = qtr("This addon has been installed manually. VLC can't manage it by itself.");
730 returnval = qfu( p_entry->psz_summary );
732 case DescriptionRole:
733 returnval = qfu( p_entry->psz_description );
736 returnval = QVariant( (int) p_entry->e_type );
739 returnval = QByteArray( (const char *) p_entry->uuid, (int) sizeof( addon_uuid_t ) );
742 returnval = QVariant( (int) p_entry->e_flags );
745 returnval = QVariant( (int) p_entry->e_state );
747 case DownloadsCountRole:
748 returnval = QVariant( (double) p_entry->i_downloads );
751 returnval = QVariant( (double) p_entry->i_score );
754 returnval = QVariant( p_entry->psz_version );
757 returnval = qfu( p_entry->psz_author );
760 returnval = qfu( p_entry->psz_source_uri );
765 FOREACH_ARRAY( addon_file_t *p_file, p_entry->files )
766 list << qfu( p_file->psz_filename );
768 returnval = QVariant( list );
774 vlc_mutex_unlock( &p_entry->lock );
779 AddonsListModel::AddonsListModel( AddonsManager *AM_, QObject *parent )
780 :ExtensionListModel( parent ), AM( AM_ )
785 void AddonsListModel::addonAdded( addon_entry_t *p_entry )
787 beginInsertRows( QModelIndex(), addons.count(), addons.count() );
788 addons << new Addon( p_entry );
789 insertRow( addons.count() - 1 );
793 void AddonsListModel::addonChanged( const addon_entry_t *p_entry )
796 foreach ( const Addon *addon, addons )
798 if ( *addon == p_entry )
800 emit dataChanged( index( row, 0 ), index( row, 0 ) );
807 int AddonsListModel::rowCount( const QModelIndex & ) const
809 return addons.count();
812 Qt::ItemFlags AddonsListModel::flags( const QModelIndex &index ) const
814 Qt::ItemFlags i_flags = ExtensionListModel::flags( index );
815 int i_state = data( index, StateRole ).toInt();
817 if ( i_state == ADDON_UNINSTALLING || i_state == ADDON_INSTALLING )
819 i_flags &= !Qt::ItemIsEnabled;
822 i_flags |= Qt::ItemIsEditable;
827 bool AddonsListModel::setData( const QModelIndex &index, const QVariant &value, int role )
829 /* We NEVER set values directly */
830 if ( role == StateRole )
832 int i_value = value.toInt();
833 if ( i_value == ADDON_INSTALLING )
835 AM->install( data( index, UUIDRole ).toByteArray() );
837 else if ( i_value == ADDON_UNINSTALLING )
839 AM->remove( data( index, UUIDRole ).toByteArray() );
842 else if ( role == StateRole + 1 )
844 emit dataChanged( index, index );
849 QVariant AddonsListModel::data( const QModelIndex& index, int role ) const
851 if( !index.isValid() )
854 return ((Addon *)index.internalPointer())->data( role );
857 QModelIndex AddonsListModel::index( int row, int column,
858 const QModelIndex& ) const
861 return QModelIndex();
862 if( row < 0 || row >= addons.count() )
863 return QModelIndex();
865 return createIndex( row, 0, addons.at( row ) );
869 AddonsSortFilterProxyModel::AddonsSortFilterProxyModel( QObject *parent )
870 : QSortFilterProxyModel( parent )
876 void AddonsSortFilterProxyModel::setTypeFilter( int type )
878 i_type_filter = type;
882 void AddonsSortFilterProxyModel::setStatusFilter( int flags )
884 i_status_filter = flags;
888 bool AddonsSortFilterProxyModel::filterAcceptsRow( int source_row,
889 const QModelIndex &source_parent ) const
891 if ( !QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent ) )
894 QModelIndex item = sourceModel()->index( source_row, 0, source_parent );
896 if ( i_type_filter > -1 &&
897 item.data( AddonsListModel::TypeRole ).toInt() != i_type_filter )
900 if ( i_status_filter > 0 &&
901 ( item.data( AddonsListModel::StateRole ).toInt() & i_status_filter ) != i_status_filter )
907 /* Extension List Widget Item */
908 ExtensionItemDelegate::ExtensionItemDelegate( QObject *parent )
909 : QStyledItemDelegate( parent )
911 margins = QMargins( 4, 4, 4, 4 );
914 ExtensionItemDelegate::~ExtensionItemDelegate()
918 void ExtensionItemDelegate::paint( QPainter *painter,
919 const QStyleOptionViewItem &option,
920 const QModelIndex &index ) const
922 QStyleOptionViewItemV4 opt = option;
923 initStyleOption( &opt, index );
926 if ( opt.state & QStyle::State_Selected )
927 painter->fillRect( opt.rect, opt.palette.highlight() );
930 QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
933 painter->drawPixmap( opt.rect.left() + margins.left(),
934 opt.rect.top() + margins.top(),
935 icon.scaled( opt.decorationSize,
937 Qt::SmoothTransformation )
942 painter->setRenderHint( QPainter::TextAntialiasing );
944 if ( opt.state & QStyle::State_Selected )
945 painter->setPen( opt.palette.highlightedText().color() );
947 QFont font( option.font );
948 font.setBold( true );
949 painter->setFont( font );
950 QRect textrect( opt.rect );
951 textrect.adjust( 2 * margins.left() + margins.right() + opt.decorationSize.width(),
954 - margins.bottom() - opt.fontMetrics.height() );
956 painter->drawText( textrect, Qt::AlignLeft,
957 index.data( Qt::DisplayRole ).toString() );
959 font.setBold( false );
960 painter->setFont( font );
961 painter->drawText( textrect.translated( 0, option.fontMetrics.height() ),
963 index.data( ExtensionListModel::SummaryRole ).toString() );
968 QSize ExtensionItemDelegate::sizeHint( const QStyleOptionViewItem &option,
969 const QModelIndex &index ) const
971 if ( index.isValid() )
973 return QSize( 200, 2 * option.fontMetrics.height()
974 + margins.top() + margins.bottom() );
980 void ExtensionItemDelegate::initStyleOption( QStyleOptionViewItem *option,
981 const QModelIndex &index ) const
983 QStyledItemDelegate::initStyleOption( option, index );
984 option->decorationSize = QSize( option->rect.height(), option->rect.height() );
985 option->decorationSize -= QSize( margins.left() + margins.right(),
986 margins.top() + margins.bottom() );
989 AddonItemDelegate::AddonItemDelegate( QObject *parent )
990 : ExtensionItemDelegate( parent )
996 AddonItemDelegate::~AddonItemDelegate()
1001 void AddonItemDelegate::paint( QPainter *painter,
1002 const QStyleOptionViewItem &option,
1003 const QModelIndex &index ) const
1005 QStyleOptionViewItemV4 newopt = option;
1006 int i_state = index.data( AddonsListModel::StateRole ).toInt();
1008 ExtensionItemDelegate::paint( painter, newopt, index );
1010 initStyleOption( &newopt, index );
1013 painter->setRenderHint( QPainter::TextAntialiasing );
1015 if ( newopt.state & QStyle::State_Selected )
1016 painter->setPen( newopt.palette.highlightedText().color() );
1018 /* Start below text */
1019 QRect textrect( newopt.rect );
1020 textrect.adjust( 2 * margins.left() + margins.right() + newopt.decorationSize.width(),
1023 - margins.bottom() - newopt.fontMetrics.height() );
1024 textrect.translate( 0, newopt.fontMetrics.height() * 2 );
1027 QString version = index.data( AddonsListModel::VersionRole ).toString();
1028 if ( !version.isEmpty() )
1029 painter->drawText( textrect, Qt::AlignLeft, qtr("Version %1").arg( version ) );
1031 textrect.translate( 0, newopt.fontMetrics.height() );
1034 double i_score = index.data( AddonsListModel::ScoreRole ).toDouble();
1038 scoreicon = QPixmap( ":/addons/score" ).scaledToHeight(
1039 newopt.fontMetrics.height(), Qt::SmoothTransformation );
1040 int i_width = ( i_score / 5.0 ) * scoreicon.width();
1041 /* Erase the end (value) of our pixmap with a shadow */
1042 QPainter erasepainter( &scoreicon );
1043 erasepainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
1044 erasepainter.fillRect( QRect( i_width, 0,
1045 scoreicon.width() - i_width, scoreicon.height() ),
1046 newopt.palette.color( QPalette::Dark ) );
1048 painter->drawPixmap( textrect.topLeft(), scoreicon );
1052 int i_downloads = index.data( AddonsListModel::DownloadsCountRole ).toInt();
1054 painter->drawText( textrect.translated( scoreicon.width() + margins.left(), 0 ),
1055 Qt::AlignLeft, qtr("%1 downloads").arg( i_downloads ) );
1061 if ( animator->isRunning() && animator->getIndex() == index )
1063 if ( i_state != ADDON_INSTALLING && i_state != ADDON_UNINSTALLING )
1064 animator->run( false );
1066 /* Create our installation progress overlay */
1068 if ( i_state == ADDON_INSTALLING || i_state == ADDON_UNINSTALLING )
1071 painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1072 painter->fillRect( newopt.rect, QColor( 255, 255, 255, 128 ) );
1073 if ( animator && index.isValid() )
1075 animator->setIndex( index );
1076 animator->run( true );
1077 QSize adjustment = newopt.rect.size() / 4;
1078 progressbar->setGeometry(
1079 newopt.rect.adjusted( adjustment.width(), adjustment.height(),
1080 -adjustment.width(), -adjustment.height() ) );
1081 painter->drawPixmap( newopt.rect.left() + adjustment.width(),
1082 newopt.rect.top() + adjustment.height(),
1083 QPixmap::grabWidget( progressbar ) );
1090 QSize AddonItemDelegate::sizeHint( const QStyleOptionViewItem &option,
1091 const QModelIndex &index ) const
1093 if ( index.isValid() )
1095 return QSize( 200, 4 * option.fontMetrics.height()
1096 + margins.top() + margins.bottom() );
1102 QWidget *AddonItemDelegate::createEditor( QWidget *parent,
1103 const QStyleOptionViewItem &option,
1104 const QModelIndex &index) const
1107 QWidget *editorWidget = new QWidget( parent );
1108 QPushButton *installButton;
1109 QPushButton *infoButton;
1111 editorWidget->setLayout( new QHBoxLayout() );
1112 editorWidget->layout()->setMargin( 0 );
1114 infoButton = new QPushButton( QIcon( ":/menu/info" ),
1115 qtr( "More information..." ) );
1116 connect( infoButton, SIGNAL(clicked()), this, SIGNAL(showInfo()) );
1117 editorWidget->layout()->addWidget( infoButton );
1119 if ( ADDON_MANAGEABLE &
1120 index.data( AddonsListModel::FlagsRole ).toInt() )
1122 if ( index.data( AddonsListModel::StateRole ).toInt() == ADDON_INSTALLED )
1123 installButton = new QPushButton( QIcon( ":/buttons/playlist/playlist_remove" ),
1124 qtr("&Uninstall"), parent );
1126 installButton = new QPushButton( QIcon( ":/buttons/playlist/playlist_add" ),
1127 qtr("&Install"), parent );
1128 CONNECT( installButton, clicked(), this, editButtonClicked() );
1129 editorWidget->layout()->addWidget( installButton );
1132 editorWidget->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
1134 return editorWidget;
1137 void AddonItemDelegate::updateEditorGeometry( QWidget *editor,
1138 const QStyleOptionViewItem &option,
1139 const QModelIndex &index) const
1142 QSize size = editor->sizeHint();
1143 editor->setGeometry( option.rect.right() - size.width(),
1144 option.rect.top() + ( option.rect.height() - size.height()),
1149 void AddonItemDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
1150 const QModelIndex &index ) const
1152 model->setData( index, editor->property("Addon::state"), AddonsListModel::StateRole );
1155 void AddonItemDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
1157 editor->setProperty("Addon::state", index.data( AddonsListModel::StateRole ) );
1160 void AddonItemDelegate::setAnimator( DelegateAnimationHelper *animator_ )
1164 QProgressBar *progress = new QProgressBar( );
1165 progress->setMinimum( 0 );
1166 progress->setMaximum( 0 );
1167 progress->setTextVisible( false );
1168 progressbar = progress;
1170 animator = animator_;
1173 void AddonItemDelegate::editButtonClicked()
1175 QWidget *editor = qobject_cast<QWidget *>(sender()->parent());
1176 if ( !editor ) return;
1177 int value = editor->property("Addon::state").toInt();
1178 if ( ( value == ADDON_INSTALLED ) )
1180 editor->setProperty("Addon::state", ADDON_UNINSTALLING );
1183 editor->setProperty("Addon::state", ADDON_INSTALLING );
1184 emit commitData( editor );
1185 emit closeEditor( editor );
1188 /* "More information" dialog */
1190 ExtensionInfoDialog::ExtensionInfoDialog( const QModelIndex &index,
1191 intf_thread_t *p_intf,
1193 : QVLCDialog( parent, p_intf )
1195 // Let's be a modal dialog
1196 setWindowModality( Qt::WindowModal );
1199 setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
1202 QGridLayout *layout = new QGridLayout( this );
1205 QLabel *icon = new QLabel( this );
1206 QPixmap pix = index.data(Qt::DecorationRole).value<QPixmap>();
1207 Q_ASSERT( !pix.isNull() );
1208 icon->setPixmap( pix );
1209 icon->setAlignment( Qt::AlignCenter );
1210 icon->setFixedSize( 48, 48 );
1211 layout->addWidget( icon, 1, 0, 2, 1 );
1214 QLabel *label = new QLabel( index.data(Qt::DisplayRole).toString(), this );
1215 QFont font = label->font();
1216 font.setBold( true );
1217 font.setPointSizeF( font.pointSizeF() * 1.3f );
1218 label->setFont( font );
1219 layout->addWidget( label, 0, 0, 1, -1 );
1222 label = new QLabel( "<b>" + qtr( "Version" ) + ":</b>", this );
1223 layout->addWidget( label, 1, 1, 1, 1, Qt::AlignBottom );
1224 label = new QLabel( index.data(ExtensionListModel::VersionRole).toString(), this );
1225 layout->addWidget( label, 1, 2, 1, 2, Qt::AlignBottom );
1228 label = new QLabel( "<b>" + qtr( "Author" ) + ":</b>", this );
1229 layout->addWidget( label, 2, 1, 1, 1, Qt::AlignTop );
1230 label = new QLabel( index.data(ExtensionListModel::AuthorRole).toString(), this );
1231 layout->addWidget( label, 2, 2, 1, 2, Qt::AlignTop );
1235 label = new QLabel( this );
1236 label->setText( index.data(ExtensionListModel::SummaryRole).toString() );
1237 label->setWordWrap( true );
1238 label->setOpenExternalLinks( true );
1239 layout->addWidget( label, 4, 0, 1, -1 );
1242 label = new QLabel( "<b>" + qtr( "Website" ) + ":</b>", this );
1243 layout->addWidget( label, 5, 0, 1, 2 );
1244 label = new QLabel( QString("<a href=\"%1\">%2</a>")
1245 .arg( index.data(ExtensionListModel::LinkRole).toString() )
1246 .arg( index.data(ExtensionListModel::LinkRole).toString() )
1248 label->setOpenExternalLinks( true );
1249 layout->addWidget( label, 5, 2, 1, -1 );
1252 label = new QLabel( "<b>" + qtr( "File" ) + ":</b>", this );
1253 layout->addWidget( label, 6, 0, 1, 2 );
1255 new QLineEdit( index.data(ExtensionListModel::FilenameRole).toString(), this );
1256 line->setReadOnly( true );
1257 layout->addWidget( line, 6, 2, 1, -1 );
1260 QDialogButtonBox *group = new QDialogButtonBox( this );
1261 QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
1262 group->addButton( closeButton, QDialogButtonBox::RejectRole );
1263 BUTTONACT( closeButton, close() );
1265 layout->addWidget( group, 7, 0, 1, -1 );
1268 layout->setColumnStretch( 2, 1 );
1269 layout->setRowStretch( 4, 1 );
1270 setMinimumSize( 450, 350 );
1274 AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
1275 intf_thread_t *p_intf, QWidget *parent )
1276 : QVLCDialog( parent, p_intf )
1278 // Let's be a modal dialog
1279 setWindowModality( Qt::WindowModal );
1282 setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
1285 QGridLayout *layout = new QGridLayout( this );
1289 QLabel *iconLabel = new QLabel( this );
1290 iconLabel->setFixedSize( 100, 100 );
1291 QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
1292 icon.scaled( iconLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation );
1293 iconLabel->setPixmap( icon );
1294 iconLabel->setAlignment( Qt::AlignCenter | Qt::AlignTop );
1295 layout->addWidget( iconLabel, 1, 0, 2, 1 );
1298 label = new QLabel( index.data(Qt::DisplayRole).toString(), this );
1299 QFont font = label->font();
1300 font.setBold( true );
1301 font.setPointSizeF( font.pointSizeF() * 1.3f );
1302 label->setFont( font );
1303 layout->addWidget( label, 0, 0, 1, -1 );
1305 // HTML Content on right side
1306 QTextEdit *textContent = new QTextEdit();
1307 textContent->viewport()->setAutoFillBackground( false );
1308 textContent->setAcceptRichText( true );
1309 textContent->setBackgroundRole( QPalette::Window );
1310 textContent->setFrameStyle( QFrame::NoFrame );
1311 textContent->setAutoFillBackground( false );
1312 textContent->setReadOnly( true );
1313 layout->addWidget( textContent, 1, 1, 4, -1 );
1316 QString type = AddonsManager::getAddonType( index.data(AddonsListModel::TypeRole).toInt() );
1317 textContent->append( QString("<b>%1:</b> %2<br/>")
1318 .arg( qtr("Type") ).arg( type ) );
1321 QString version = index.data(ExtensionListModel::VersionRole).toString();
1322 if ( !version.isEmpty() )
1324 textContent->append( QString("<b>%1:</b> %2<br/>")
1325 .arg( qtr("Version") ).arg( version ) );
1329 QString author = index.data(ExtensionListModel::AuthorRole).toString();
1330 if ( !author.isEmpty() )
1332 textContent->append( QString("<b>%1:</b> %2<br/>")
1333 .arg( qtr("Author") ).arg( author ) );
1337 textContent->append( QString("%1<br/>\n")
1338 .arg( index.data(AddonsListModel::SummaryRole).toString() ) );
1341 QString description = index.data(AddonsListModel::DescriptionRole).toString();
1342 if ( !description.isEmpty() )
1344 textContent->append( QString("<hr/>\n%1")
1345 .arg( description.replace("\n", "<br/>") ) );
1349 QString sourceUrl = index.data(ExtensionListModel::LinkRole).toString();
1350 if ( !sourceUrl.isEmpty() )
1352 label = new QLabel( "<b>" + qtr( "Website" ) + ":</b>", this );
1353 layout->addWidget( label, 5, 0, 1, 2 );
1354 label = new QLabel( QString("<a href=\"%1\">%2</a>")
1355 .arg( sourceUrl ).arg( sourceUrl ), this );
1356 label->setOpenExternalLinks( true );
1357 layout->addWidget( label, 5, 2, 1, -1 );
1361 QList<QVariant> list = index.data(ExtensionListModel::FilenameRole).toList();
1362 if ( ! list.empty() )
1364 label = new QLabel( "<b>" + qtr( "Files" ) + ":</b>", this );
1365 layout->addWidget( label, 6, 0, 1, 2 );
1366 QComboBox *filesCombo = new QComboBox();
1367 Q_FOREACH( const QVariant & file, list )
1368 filesCombo->addItem( file.toString() );
1369 layout->addWidget( filesCombo, 6, 2, 1, -1 );
1373 QDialogButtonBox *group = new QDialogButtonBox( this );
1374 QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
1375 group->addButton( closeButton, QDialogButtonBox::RejectRole );
1376 BUTTONACT( closeButton, close() );
1378 layout->addWidget( group, 7, 0, 1, -1 );
1381 layout->setColumnStretch( 2, 1 );
1382 layout->setRowStretch( 4, 1 );
1383 setMinimumSize( 640, 480 );
1386 static QPixmap *loadPixmapFromData( char *data, int size )
1388 if( !data || size <= 0 )
1390 QPixmap *pixmap = new QPixmap();
1391 if( !pixmap->loadFromData( (const uchar*) data, (uint) size ) )