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>
53 #include <QListWidget>
55 #include <QStyleOptionViewItem>
57 #include <QPushButton>
60 #include <QStylePainter>
61 #include <QProgressBar>
66 #include <QToolButton>
67 #include <QStackedWidget>
69 static QPixmap *loadPixmapFromData( char *, int size );
72 PluginDialog::PluginDialog( intf_thread_t *_p_intf ) : QVLCFrame( _p_intf )
74 setWindowTitle( qtr( "Plugins and extensions" ) );
75 setWindowRole( "vlc-plugins" );
77 QVBoxLayout *layout = new QVBoxLayout( this );
78 tabs = new QTabWidget( this );
79 tabs->addTab( addonsTab = new AddonsTab( p_intf ),
80 qtr( "Addons Manager" ) );
81 tabs->addTab( extensionTab = new ExtensionTab( p_intf ),
82 qtr( "Active Extensions" ) );
83 tabs->addTab( pluginTab = new PluginTab( p_intf ),
85 layout->addWidget( tabs );
87 QDialogButtonBox *box = new QDialogButtonBox;
88 QPushButton *okButton = new QPushButton( qtr( "&Close" ), this );
89 box->addButton( okButton, QDialogButtonBox::RejectRole );
90 layout->addWidget( box );
91 BUTTONACT( okButton, close() );
92 restoreWidgetPosition( "PluginsDialog", QSize( 435, 280 ) );
95 PluginDialog::~PluginDialog()
97 saveWidgetPosition( "PluginsDialog" );
102 PluginTab::PluginTab( intf_thread_t *p_intf_ )
103 : QVLCFrame( p_intf_ )
105 QGridLayout *layout = new QGridLayout( this );
107 /* Main Tree for modules */
108 treePlugins = new QTreeWidget;
109 layout->addWidget( treePlugins, 0, 0, 1, -1 );
111 /* Users cannot move the columns around but we need to sort */
113 treePlugins->header()->setSectionsMovable( false );
115 treePlugins->header()->setMovable( false );
117 treePlugins->header()->setSortIndicatorShown( true );
118 // treePlugins->header()->setResizeMode( QHeaderView::ResizeToContents );
119 treePlugins->setAlternatingRowColors( true );
120 treePlugins->setColumnWidth( 0, 200 );
122 QStringList headerNames;
123 headerNames << qtr("Name") << qtr("Capability" ) << qtr( "Score" );
124 treePlugins->setHeaderLabels( headerNames );
128 /* Set capability column to the correct Size*/
129 treePlugins->resizeColumnToContents( 1 );
130 treePlugins->header()->restoreState(
131 getSettings()->value( "Plugins/Header-State" ).toByteArray() );
133 treePlugins->setSortingEnabled( true );
134 treePlugins->sortByColumn( 1, Qt::AscendingOrder );
136 QLabel *label = new QLabel( qtr("&Search:"), this );
137 edit = new SearchLineEdit( this );
138 label->setBuddy( edit );
140 layout->addWidget( label, 1, 0 );
141 layout->addWidget( edit, 1, 1, 1, 1 );
142 CONNECT( edit, textChanged( const QString& ),
143 this, search( const QString& ) );
145 setMinimumSize( 500, 300 );
146 restoreWidgetPosition( "Plugins", QSize( 540, 400 ) );
149 inline void PluginTab::FillTree()
152 module_t **p_list = module_list_get( &count );
154 for( unsigned int i = 0; i < count; i++ )
156 module_t *p_module = p_list[i];
159 qs_item << qfu( module_get_name( p_module, true ) )
160 << qfu( module_get_capability( p_module ) )
161 << QString::number( module_get_score( p_module ) );
163 if( qs_item.at(1).isEmpty() ) continue;
166 QTreeWidgetItem *item = new PluginTreeItem( qs_item );
167 treePlugins->addTopLevelItem( item );
169 module_list_free( p_list );
172 void PluginTab::search( const QString& qs )
174 QList<QTreeWidgetItem *> items = treePlugins->findItems( qs, Qt::MatchContains );
175 items += treePlugins->findItems( qs, Qt::MatchContains, 1 );
177 QTreeWidgetItem *item = NULL;
178 for( int i = 0; i < treePlugins->topLevelItemCount(); i++ )
180 item = treePlugins->topLevelItem( i );
181 item->setHidden( !items.contains( item ) );
185 PluginTab::~PluginTab()
187 saveWidgetPosition( "Plugins" );
188 getSettings()->setValue( "Plugins/Header-State",
189 treePlugins->header()->saveState() );
192 void PluginTab::keyPressEvent( QKeyEvent *keyEvent )
194 if( keyEvent->key() == Qt::Key_Return ||
195 keyEvent->key() == Qt::Key_Enter )
201 bool PluginTreeItem::operator< ( const QTreeWidgetItem & other ) const
203 int col = treeWidget()->sortColumn();
204 if( col == PluginTab::SCORE )
205 return text( col ).toInt() < other.text( col ).toInt();
206 else if ( col == PluginTab::CAPABILITY )
208 if ( text( PluginTab::CAPABILITY ) == other.text( PluginTab::CAPABILITY ) )
209 return text( PluginTab::NAME ) < other.text( PluginTab::NAME );
211 return text( PluginTab::CAPABILITY ) < other.text( PluginTab::CAPABILITY );
213 return text( col ) < other.text( col );
217 ExtensionTab::ExtensionTab( intf_thread_t *p_intf_ )
218 : QVLCFrame( p_intf_ )
221 QVBoxLayout *layout = new QVBoxLayout( this );
224 extList = new QListView( this );
225 CONNECT( extList, activated( const QModelIndex& ),
226 this, moreInformation() );
227 layout->addWidget( extList );
229 // List item delegate
230 ExtensionItemDelegate *itemDelegate = new ExtensionItemDelegate( extList );
231 extList->setItemDelegate( itemDelegate );
233 // Extension list look & feeling
234 extList->setAlternatingRowColors( true );
235 extList->setSelectionMode( QAbstractItemView::SingleSelection );
238 ExtensionListModel *model =
239 new ExtensionListModel( extList, ExtensionsManager::getInstance( p_intf ) );
240 extList->setModel( model );
243 QDialogButtonBox *buttonsBox = new QDialogButtonBox;
245 // More information button
246 butMoreInfo = new QPushButton( QIcon( ":/menu/info" ),
247 qtr( "More information..." ),
249 CONNECT( butMoreInfo, clicked(), this, moreInformation() );
250 buttonsBox->addButton( butMoreInfo, QDialogButtonBox::ActionRole );
253 ExtensionsManager *EM = ExtensionsManager::getInstance( p_intf );
254 QPushButton *reload = new QPushButton( QIcon( ":/update" ),
255 qtr( "Reload extensions" ),
257 CONNECT( reload, clicked(), EM, reloadExtensions() );
258 CONNECT( reload, clicked(), this, updateButtons() );
259 CONNECT( extList->selectionModel(),
260 selectionChanged( const QItemSelection &, const QItemSelection & ),
263 buttonsBox->addButton( reload, QDialogButtonBox::ResetRole );
265 layout->addWidget( buttonsBox );
269 ExtensionTab::~ExtensionTab()
273 void ExtensionTab::updateButtons()
275 butMoreInfo->setEnabled( extList->selectionModel()->hasSelection() );
278 // Do not close on ESC or ENTER
279 void ExtensionTab::keyPressEvent( QKeyEvent *keyEvent )
281 if( keyEvent->key() == Qt::Key_Return ||
282 keyEvent->key() == Qt::Key_Enter )
288 // Show more information
289 void ExtensionTab::moreInformation()
291 QModelIndex index = extList->selectionModel()->selectedIndexes().first();
293 if( !index.isValid() )
296 ExtensionInfoDialog dlg( index, p_intf, this );
300 static QPixmap hueRotate( QImage image, const QColor &source, const QColor &target )
302 int distance = target.hue() - source.hue();
303 /* must be indexed as we alter palette, not a whole pic */
304 Q_ASSERT( image.colorCount() );
305 if ( target.isValid() )
307 /* color 1 = transparency */
308 for ( int i=1; i < image.colorCount(); i++ )
310 QColor color = image.color( i );
311 int newhue = color.hue() + distance;
312 if ( newhue < 0 ) newhue += 255;
313 color.setHsv( newhue, color.saturation(), color.value(), color.alpha() );
314 image.setColor( i, color.rgba() );
317 return QPixmap::fromImage( image );
321 AddonsTab::AddonsTab( intf_thread_t *p_intf_ ) : QVLCFrame( p_intf_ )
324 QSplitter *splitter = new QSplitter( this );
325 setLayout( new QHBoxLayout() );
326 layout()->addWidget( splitter );
328 QWidget *leftPane = new QWidget();
329 splitter->addWidget( leftPane );
330 leftPane->setLayout( new QVBoxLayout() );
332 QWidget *rightPane = new QWidget();
333 splitter->addWidget( rightPane );
335 splitter->setCollapsible( 0, false );
336 splitter->setCollapsible( 1, false );
337 splitter->setSizeIncrement( 32, 1 );
340 QVBoxLayout *layout = new QVBoxLayout( rightPane );
343 leftPane->layout()->setMargin(0);
344 leftPane->layout()->setSpacing(0);
346 SearchLineEdit *searchInput = new SearchLineEdit();
347 leftPane->layout()->addWidget( searchInput );
348 leftPane->layout()->addItem( new QSpacerItem( 0, 10 ) );
350 QToolButton * button;
351 QSignalMapper *mapper = new QSignalMapper();
352 QImage icon( ":/addons/default" );
353 QColor vlcorange( 0xEC, 0x83, 0x00 );
354 #define ADD_CATEGORY( label, ltooltip, numb ) \
355 button = new QToolButton( this );\
356 button->setIcon( QIcon( hueRotate( icon, vlcorange, \
357 AddonsListModel::getColorByAddonType( numb ) ) ) );\
358 button->setText( label );\
359 button->setToolTip( ltooltip );\
360 button->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );\
361 button->setIconSize( QSize( 32, 32 ) );\
362 button->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::Maximum) ;\
363 button->setMinimumSize( 32, 32 );\
364 button->setAutoRaise( true );\
365 button->setCheckable( true );\
366 if ( numb == -1 ) button->setChecked( true );\
367 button->setAutoExclusive( true );\
368 CONNECT( button, clicked(), mapper, map() );\
369 mapper->setMapping( button, numb );\
370 leftPane->layout()->addWidget( button );
372 ADD_CATEGORY( qtr("All"), qtr("Interface Settings"),
374 ADD_CATEGORY( qtr("Skins"),
375 qtr( "Skins customize player's appearance."
376 " You can activate them through preferences." ),
378 ADD_CATEGORY( qtr("Playlist parsers"),
379 qtr( "Playlist parsers add new capabilities to read"
380 " internet streams or extract meta data." ),
381 ADDON_PLAYLIST_PARSER );
382 ADD_CATEGORY( qtr("Service Discovery"),
383 qtr( "Service discoveries adds new sources to your playlist"
384 " such as web radios, video websites, ..." ),
385 ADDON_SERVICE_DISCOVERY );
386 ADD_CATEGORY( qtr("Interfaces"),
389 ADD_CATEGORY( qtr("Art and meta fetchers"),
390 qtr( "Retrieves extra info and art for playlist items" ),
392 ADD_CATEGORY( qtr("Extensions"),
393 qtr( "Extensions brings various enhancements."
394 " Check descriptions for more details" ),
398 rightPane->layout()->setMargin(0);
399 rightPane->layout()->setSpacing(0);
401 // Splitter sizes init
403 int width = leftPane->sizeHint().width();
404 sizes << width << size().width() - width;
405 splitter->setSizes( sizes );
408 leftPane->layout()->addItem( new QSpacerItem( 0, 30 ) );
410 QStackedWidget *switchStack = new QStackedWidget();
411 switchStack->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum );
412 leftPane->layout()->addWidget( switchStack );
414 QCheckBox *installedOnlyBox = new QCheckBox( qtr("Only installed") );
415 installedOnlyBox->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
416 switchStack->insertWidget( WITHONLINEADDONS, installedOnlyBox );
417 CONNECT( installedOnlyBox, stateChanged(int), this, installChecked(int) );
419 QPushButton *reposyncButton = new QPushButton( QIcon( ":/update" ),
420 qtr("Find more addons online") );
421 reposyncButton->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
422 switchStack->insertWidget( ONLYLOCALADDONS, reposyncButton );
423 switchStack->setCurrentIndex( ONLYLOCALADDONS );
424 CONNECT( reposyncButton, clicked(), this, reposync() );
426 leftPane->layout()->addItem( new QSpacerItem( 0, 0, QSizePolicy::Maximum, QSizePolicy::Expanding ) );
429 AddonsManager *AM = AddonsManager::getInstance( p_intf );
432 addonsView = new QListView( this );
433 CONNECT( addonsView, activated( const QModelIndex& ), this, moreInformation() );
434 layout->addWidget( addonsView );
436 // List item delegate
437 AddonItemDelegate *addonsDelegate = new AddonItemDelegate( addonsView );
438 addonsView->setItemDelegate( addonsDelegate );
439 addonsDelegate->setAnimator( new DelegateAnimationHelper( addonsView ) );
440 CONNECT( addonsDelegate, showInfo(), this, moreInformation() );
442 // Extension list look & feeling
443 addonsView->setAlternatingRowColors( true );
444 addonsView->setSelectionMode( QAbstractItemView::SingleSelection );
447 addonsView->setAcceptDrops( true );
448 addonsView->setDefaultDropAction( Qt::CopyAction );
449 addonsView->setDropIndicatorShown( true );
450 addonsView->setDragDropMode( QAbstractItemView::DropOnly );
453 AddonsListModel *model = new AddonsListModel( AM, addonsView );
454 addonsModel = new AddonsSortFilterProxyModel();
455 addonsModel->setDynamicSortFilter( true );
456 addonsModel->setFilterCaseSensitivity( Qt::CaseInsensitive );
457 addonsModel->setSortRole( Qt::DisplayRole );
458 addonsModel->sort( 0, Qt::AscendingOrder );
459 addonsModel->setSourceModel( model );
460 addonsModel->setFilterRole( Qt::DisplayRole );
461 addonsView->setModel( addonsModel );
463 CONNECT( mapper, mapped(int), addonsModel, setTypeFilter(int) );
465 CONNECT( searchInput, textChanged( const QString &),
466 addonsModel, setFilterFixedString( QString ) );
468 CONNECT( addonsView->selectionModel(), currentChanged(QModelIndex,QModelIndex),
469 addonsView, edit(QModelIndex) );
471 CONNECT( AM, addonAdded( addon_entry_t * ),
472 model, addonAdded( addon_entry_t * ) );
473 CONNECT( AM, addonChanged( const addon_entry_t * ),
474 model, addonChanged( const addon_entry_t * ) );
476 QList<QString> frames;
477 frames << ":/util/wait1";
478 frames << ":/util/wait2";
479 frames << ":/util/wait3";
480 frames << ":/util/wait4";
481 spinnerAnimation = new PixmapAnimator( this, frames );
482 CONNECT( spinnerAnimation, pixmapReady( const QPixmap & ),
483 addonsView->viewport(), update() );
484 addonsView->viewport()->installEventFilter( this );
487 AddonsTab::~AddonsTab()
489 delete spinnerAnimation;
492 bool AddonsTab::eventFilter( QObject *obj, QEvent *event )
494 if ( obj != addonsView->viewport() )
497 switch( event->type() )
500 if ( spinnerAnimation->state() == PixmapAnimator::Running )
502 QWidget *viewport = qobject_cast<QWidget *>( obj );
503 if ( !viewport ) break;
504 QStylePainter painter( viewport );
505 QPixmap *spinner = spinnerAnimation->getPixmap();
506 QPoint point = viewport->geometry().center();
507 point -= QPoint( spinner->size().width() / 2, spinner->size().height() / 2 );
508 painter.drawPixmap( point, *spinner );
509 QString text = qtr("Retrieving addons...");
510 QSize textsize = fontMetrics().size( 0, text );
511 point = viewport->geometry().center();
512 point -= QPoint( textsize.width() / 2, -spinner->size().height() );
513 painter.drawText( point, text );
515 else if ( addonsModel->rowCount() == 0 )
517 QWidget *viewport = qobject_cast<QWidget *>( obj );
518 if ( !viewport ) break;
519 QStylePainter painter( viewport );
520 QString text = qtr("No addons found");
521 QSize size = fontMetrics().size( 0, text );
522 QPoint point = viewport->geometry().center();
523 point -= QPoint( size.width() / 2, size.height() / 2 );
524 painter.drawText( point, text );
528 if ( !b_localdone && addonsView->model()->rowCount() < 1 )
531 AddonsManager *AM = AddonsManager::getInstance( p_intf );
535 case QEvent::DragEnter:
537 QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent *>(event);
538 if ( !dragEvent ) break;
539 QList<QUrl> urls = dragEvent->mimeData()->urls();
540 if ( dragEvent->proposedAction() != Qt::CopyAction
542 || urls.first().scheme() != "file"
543 || ! urls.first().path().endsWith(".vlp") )
545 dragEvent->acceptProposedAction();
548 case QEvent::DragMove:
550 QDragMoveEvent *moveEvent = static_cast<QDragMoveEvent *>(event);
551 if ( !moveEvent ) break;
552 if ( moveEvent->proposedAction() != Qt::CopyAction )
554 moveEvent->acceptProposedAction();
559 QDropEvent *dropEvent = static_cast<QDropEvent *>(event);
560 if ( !dropEvent ) break;
561 if ( dropEvent->proposedAction() != Qt::CopyAction )
563 if ( dropEvent->mimeData()->urls().count() )
565 AddonsManager *AM = AddonsManager::getInstance( p_intf );
566 AM->findDesignatedAddon( dropEvent->mimeData()->urls().first().toString() );
567 dropEvent->acceptProposedAction();
577 void AddonsTab::moreInformation()
579 QModelIndex index = addonsView->selectionModel()->selectedIndexes().first();
580 if( !index.isValid() ) return;
581 AddonInfoDialog dlg( index, p_intf, this );
585 void AddonsTab::installChecked( int i )
587 if ( i == Qt::Checked )
588 addonsModel->setStatusFilter( ADDON_INSTALLED );
590 addonsModel->setStatusFilter( 0 );
593 void AddonsTab::reposync()
595 QStackedWidget *tab = qobject_cast<QStackedWidget *>(sender()->parent());
598 tab->setCurrentIndex( WITHONLINEADDONS );
599 AddonsManager *AM = AddonsManager::getInstance( p_intf );
600 CONNECT( AM, discoveryEnded(), spinnerAnimation, stop() );
601 CONNECT( AM, discoveryEnded(), addonsView->viewport(), update() );
602 spinnerAnimation->start();
607 /* Safe copy of the extension_t struct */
608 ExtensionListModel::ExtensionCopy::ExtensionCopy( extension_t *p_ext )
610 name = qfu( p_ext->psz_name );
611 description = qfu( p_ext->psz_description );
612 shortdesc = qfu( p_ext->psz_shortdescription );
613 if( description.isEmpty() )
614 description = shortdesc;
615 if( shortdesc.isEmpty() && !description.isEmpty() )
616 shortdesc = description;
617 title = qfu( p_ext->psz_title );
618 author = qfu( p_ext->psz_author );
619 version = qfu( p_ext->psz_version );
620 url = qfu( p_ext->psz_url );
621 icon = loadPixmapFromData( p_ext->p_icondata, p_ext->i_icondata_size );
624 ExtensionListModel::ExtensionCopy::~ExtensionCopy()
629 QVariant ExtensionListModel::ExtensionCopy::data( int role ) const
633 case Qt::DisplayRole:
635 case Qt::DecorationRole:
636 if ( !icon ) return QPixmap( ":/logo/vlc48.png" );
653 /* Extensions list model for the QListView */
654 ExtensionListModel::ExtensionListModel( QObject *parent )
655 : QAbstractListModel( parent ), EM( NULL )
660 ExtensionListModel::ExtensionListModel( QObject *parent, ExtensionsManager* EM_ )
661 : QAbstractListModel( parent ), EM( EM_ )
663 // Connect to ExtensionsManager::extensionsUpdated()
664 CONNECT( EM, extensionsUpdated(), this, updateList() );
666 // Load extensions now if not already loaded
667 EM->loadExtensions();
670 ExtensionListModel::~ExtensionListModel()
672 // Clear extensions list
673 while( !extensions.isEmpty() )
674 delete extensions.takeLast();
677 void ExtensionListModel::updateList()
681 // Clear extensions list
682 while( !extensions.isEmpty() )
684 ext = extensions.takeLast();
688 // Find new extensions
689 extensions_manager_t *p_mgr = EM->getManager();
693 vlc_mutex_lock( &p_mgr->lock );
695 FOREACH_ARRAY( p_ext, p_mgr->extensions )
697 ext = new ExtensionCopy( p_ext );
698 extensions.append( ext );
701 vlc_mutex_unlock( &p_mgr->lock );
702 vlc_object_release( p_mgr );
704 emit dataChanged( index( 0 ), index( rowCount() - 1 ) );
707 int ExtensionListModel::rowCount( const QModelIndex& ) const
710 extensions_manager_t *p_mgr = EM->getManager();
714 vlc_mutex_lock( &p_mgr->lock );
715 count = p_mgr->extensions.i_size;
716 vlc_mutex_unlock( &p_mgr->lock );
717 vlc_object_release( p_mgr );
722 QVariant ExtensionListModel::data( const QModelIndex& index, int role ) const
724 if( !index.isValid() )
727 ExtensionCopy * extension =
728 static_cast<ExtensionCopy *>(index.internalPointer());
732 return extension->data( role );
735 QModelIndex ExtensionListModel::index( int row, int column,
736 const QModelIndex& ) const
739 return QModelIndex();
740 if( row < 0 || row >= extensions.count() )
741 return QModelIndex();
743 return createIndex( row, 0, extensions.at( row ) );
746 AddonsListModel::Addon::Addon( addon_entry_t *p_entry_ )
749 addon_entry_Hold( p_entry );
752 AddonsListModel::Addon::~Addon()
754 addon_entry_Release( p_entry );
757 bool AddonsListModel::Addon::operator==( const Addon & other ) const
759 //return data( IDRole ) == other.data( IDRole );
760 return p_entry == other.p_entry;
763 bool AddonsListModel::Addon::operator==( const addon_entry_t * p_other ) const
765 return p_entry == p_other;
768 QVariant AddonsListModel::Addon::data( int role ) const
772 vlc_mutex_lock( &p_entry->lock );
775 case Qt::DisplayRole:
777 returnval = qfu( p_entry->psz_name );
780 case Qt::DecorationRole:
781 if ( p_entry->psz_image_data )
784 pixmap.loadFromData( QByteArray::fromBase64( QByteArray( p_entry->psz_image_data ) ),
790 else if ( p_entry->e_flags & ADDON_BROKEN )
791 returnval = QPixmap( ":/addons/broken" );
793 returnval = QPixmap( ":/addons/default" );
795 case Qt::ToolTipRole:
797 if ( !( p_entry->e_flags & ADDON_MANAGEABLE ) )
799 returnval = qtr("This addon has been installed manually. VLC can't manage it by itself.");
804 returnval = qfu( p_entry->psz_summary );
806 case DescriptionRole:
807 returnval = qfu( p_entry->psz_description );
810 returnval = QVariant( (int) p_entry->e_type );
813 returnval = QByteArray( (const char *) p_entry->uuid, (int) sizeof( addon_uuid_t ) );
816 returnval = QVariant( (int) p_entry->e_flags );
819 returnval = QVariant( (int) p_entry->e_state );
821 case DownloadsCountRole:
822 returnval = QVariant( (double) p_entry->i_downloads );
825 returnval = QVariant( (int) p_entry->i_score );
828 returnval = QVariant( p_entry->psz_version );
831 returnval = qfu( p_entry->psz_author );
834 returnval = qfu( p_entry->psz_source_uri );
839 FOREACH_ARRAY( addon_file_t *p_file, p_entry->files )
840 list << qfu( p_file->psz_filename );
842 returnval = QVariant( list );
848 vlc_mutex_unlock( &p_entry->lock );
853 AddonsListModel::AddonsListModel( AddonsManager *AM_, QObject *parent )
854 :ExtensionListModel( parent ), AM( AM_ )
859 void AddonsListModel::addonAdded( addon_entry_t *p_entry )
861 beginInsertRows( QModelIndex(), addons.count(), addons.count() );
862 addons << new Addon( p_entry );
863 insertRow( addons.count() - 1 );
867 void AddonsListModel::addonChanged( const addon_entry_t *p_entry )
870 foreach ( const Addon *addon, addons )
872 if ( *addon == p_entry )
874 emit dataChanged( index( row, 0 ), index( row, 0 ) );
881 int AddonsListModel::rowCount( const QModelIndex & ) const
883 return addons.count();
886 Qt::ItemFlags AddonsListModel::flags( const QModelIndex &index ) const
888 Qt::ItemFlags i_flags = ExtensionListModel::flags( index );
889 int i_state = data( index, StateRole ).toInt();
891 if ( i_state == ADDON_UNINSTALLING || i_state == ADDON_INSTALLING )
893 i_flags &= !Qt::ItemIsEnabled;
896 i_flags |= Qt::ItemIsEditable;
901 bool AddonsListModel::setData( const QModelIndex &index, const QVariant &value, int role )
903 /* We NEVER set values directly */
904 if ( role == StateRole )
906 int i_value = value.toInt();
907 if ( i_value == ADDON_INSTALLING )
909 AM->install( data( index, UUIDRole ).toByteArray() );
911 else if ( i_value == ADDON_UNINSTALLING )
913 AM->remove( data( index, UUIDRole ).toByteArray() );
916 else if ( role == StateRole + 1 )
918 emit dataChanged( index, index );
923 QColor AddonsListModel::getColorByAddonType( int i_type )
928 case ADDON_EXTENSION:
929 color = QColor(0xDB, 0xC5, 0x40);
931 case ADDON_PLAYLIST_PARSER:
932 color = QColor(0x36, 0xBB, 0x59);
934 case ADDON_SERVICE_DISCOVERY:
935 color = QColor(0xDB, 0x52, 0x40);
938 color = QColor(0x8B, 0xD6, 0xFC);
940 case ADDON_INTERFACE:
941 color = QColor(0x00, 0x13, 0x85);
944 color = QColor(0xCD, 0x23, 0xBF);
955 QVariant AddonsListModel::data( const QModelIndex& index, int role ) const
957 if( !index.isValid() )
960 return ((Addon *)index.internalPointer())->data( role );
963 QModelIndex AddonsListModel::index( int row, int column,
964 const QModelIndex& ) const
967 return QModelIndex();
968 if( row < 0 || row >= addons.count() )
969 return QModelIndex();
971 return createIndex( row, 0, addons.at( row ) );
975 AddonsSortFilterProxyModel::AddonsSortFilterProxyModel( QObject *parent )
976 : QSortFilterProxyModel( parent )
982 void AddonsSortFilterProxyModel::setTypeFilter( int type )
984 i_type_filter = type;
988 void AddonsSortFilterProxyModel::setStatusFilter( int flags )
990 i_status_filter = flags;
994 bool AddonsSortFilterProxyModel::filterAcceptsRow( int source_row,
995 const QModelIndex &source_parent ) const
997 if ( !QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent ) )
1000 QModelIndex item = sourceModel()->index( source_row, 0, source_parent );
1002 if ( i_type_filter > -1 &&
1003 item.data( AddonsListModel::TypeRole ).toInt() != i_type_filter )
1006 if ( i_status_filter > 0 &&
1007 ( item.data( AddonsListModel::StateRole ).toInt() & i_status_filter ) != i_status_filter )
1013 /* Extension List Widget Item */
1014 ExtensionItemDelegate::ExtensionItemDelegate( QObject *parent )
1015 : QStyledItemDelegate( parent )
1017 margins = QMargins( 4, 4, 4, 4 );
1020 ExtensionItemDelegate::~ExtensionItemDelegate()
1024 void ExtensionItemDelegate::paint( QPainter *painter,
1025 const QStyleOptionViewItem &option,
1026 const QModelIndex &index ) const
1028 QStyleOptionViewItemV4 opt = option;
1029 initStyleOption( &opt, index );
1032 if ( opt.state & QStyle::State_Selected )
1033 painter->fillRect( opt.rect, opt.palette.highlight() );
1036 QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
1037 if( !icon.isNull() )
1039 painter->drawPixmap( opt.rect.left() + margins.left(),
1040 opt.rect.top() + margins.top(),
1041 icon.scaled( opt.decorationSize,
1042 Qt::KeepAspectRatio,
1043 Qt::SmoothTransformation )
1048 painter->setRenderHint( QPainter::TextAntialiasing );
1050 if ( opt.state & QStyle::State_Selected )
1051 painter->setPen( opt.palette.highlightedText().color() );
1053 QFont font( option.font );
1054 font.setBold( true );
1055 painter->setFont( font );
1056 QRect textrect( opt.rect );
1057 textrect.adjust( 2 * margins.left() + margins.right() + opt.decorationSize.width(),
1060 - margins.bottom() - opt.fontMetrics.height() );
1062 painter->drawText( textrect, Qt::AlignLeft,
1063 index.data( Qt::DisplayRole ).toString() );
1065 font.setBold( false );
1066 painter->setFont( font );
1067 painter->drawText( textrect.translated( 0, option.fontMetrics.height() ),
1069 index.data( ExtensionListModel::SummaryRole ).toString() );
1074 QSize ExtensionItemDelegate::sizeHint( const QStyleOptionViewItem &option,
1075 const QModelIndex &index ) const
1077 if ( index.isValid() )
1079 return QSize( 200, 2 * option.fontMetrics.height()
1080 + margins.top() + margins.bottom() );
1086 void ExtensionItemDelegate::initStyleOption( QStyleOptionViewItem *option,
1087 const QModelIndex &index ) const
1089 QStyledItemDelegate::initStyleOption( option, index );
1090 option->decorationSize = QSize( option->rect.height(), option->rect.height() );
1091 option->decorationSize -= QSize( margins.left() + margins.right(),
1092 margins.top() + margins.bottom() );
1095 AddonItemDelegate::AddonItemDelegate( QObject *parent )
1096 : ExtensionItemDelegate( parent )
1102 AddonItemDelegate::~AddonItemDelegate()
1107 void AddonItemDelegate::paint( QPainter *painter,
1108 const QStyleOptionViewItem &option,
1109 const QModelIndex &index ) const
1111 QStyleOptionViewItemV4 newopt = option;
1112 int i_state = index.data( AddonsListModel::StateRole ).toInt();
1113 int i_type = index.data( AddonsListModel::TypeRole ).toInt();
1115 /* Draw Background gradient by addon type */
1116 QColor backgroundColor = AddonsListModel::getColorByAddonType( i_type );
1118 if ( backgroundColor.isValid() )
1121 int i_corner = qMin( (int)(option.rect.width() * .05), 30 );
1122 QLinearGradient gradient(
1123 QPoint( option.rect.right() - i_corner, option.rect.bottom() - i_corner ),
1124 option.rect.bottomRight() );
1125 gradient.setColorAt( 0, Qt::transparent );
1126 gradient.setColorAt( 1.0, backgroundColor );
1127 painter->fillRect( option.rect, gradient );
1131 /* Draw base info from parent */
1132 ExtensionItemDelegate::paint( painter, newopt, index );
1134 initStyleOption( &newopt, index );
1137 painter->setRenderHint( QPainter::TextAntialiasing );
1140 if ( i_state == ADDON_INSTALLED )
1143 painter->setRenderHint( QPainter::Antialiasing );
1144 QMargins statusMargins( 5, 2, 5, 2 );
1145 QFont font( newopt.font );
1146 font.setBold( true );
1147 QFontMetrics metrics( font );
1148 painter->setFont( font );
1149 QRect statusRect = metrics.boundingRect( qtr("Installed") );
1150 statusRect.translate( newopt.rect.width() - statusRect.width(),
1151 newopt.rect.top() + statusRect.height() );
1152 statusRect.adjust( - statusMargins.left() - statusMargins.right(),
1154 statusMargins.top() + statusMargins.bottom() );
1156 path.addRoundedRect( statusRect, 2.0, 2.0 );
1157 painter->fillPath( path, QColor( Qt::green ).darker( 125 ) );
1158 painter->setPen( Qt::white );
1160 statusRect.adjusted( statusMargins.left(), statusMargins.top(),
1161 -statusMargins.right(), -statusMargins.bottom() ),
1166 if ( newopt.state & QStyle::State_Selected )
1167 painter->setPen( newopt.palette.highlightedText().color() );
1169 /* Start below text */
1170 QRect textrect( newopt.rect );
1171 textrect.adjust( 2 * margins.left() + margins.right() + newopt.decorationSize.width(),
1174 - margins.bottom() - newopt.fontMetrics.height() );
1175 textrect.translate( 0, newopt.fontMetrics.height() * 2 );
1178 QString version = index.data( AddonsListModel::VersionRole ).toString();
1179 if ( !version.isEmpty() )
1180 painter->drawText( textrect, Qt::AlignLeft, qtr("Version %1").arg( version ) );
1182 textrect.translate( 0, newopt.fontMetrics.height() );
1185 int i_score = index.data( AddonsListModel::ScoreRole ).toInt();
1189 scoreicon = QPixmap( ":/addons/score" ).scaledToHeight(
1190 newopt.fontMetrics.height(), Qt::SmoothTransformation );
1191 int i_width = ( (float) i_score / ADDON_MAX_SCORE ) * scoreicon.width();
1192 /* Erase the end (value) of our pixmap with a shadow */
1193 QPainter erasepainter( &scoreicon );
1194 erasepainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
1195 erasepainter.fillRect( QRect( i_width, 0,
1196 scoreicon.width() - i_width, scoreicon.height() ),
1197 newopt.palette.color( QPalette::Dark ) );
1199 painter->drawPixmap( textrect.topLeft(), scoreicon );
1203 int i_downloads = index.data( AddonsListModel::DownloadsCountRole ).toInt();
1205 painter->drawText( textrect.translated( scoreicon.width() + margins.left(), 0 ),
1206 Qt::AlignLeft, qtr("%1 downloads").arg( i_downloads ) );
1212 if ( animator->isRunning() && animator->getIndex() == index )
1214 if ( i_state != ADDON_INSTALLING && i_state != ADDON_UNINSTALLING )
1215 animator->run( false );
1217 /* Create our installation progress overlay */
1219 if ( i_state == ADDON_INSTALLING || i_state == ADDON_UNINSTALLING )
1222 painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1223 painter->fillRect( newopt.rect, QColor( 255, 255, 255, 128 ) );
1224 if ( animator && index.isValid() )
1226 animator->setIndex( index );
1227 animator->run( true );
1228 QSize adjustment = newopt.rect.size() / 4;
1229 progressbar->setGeometry(
1230 newopt.rect.adjusted( adjustment.width(), adjustment.height(),
1231 -adjustment.width(), -adjustment.height() ) );
1232 painter->drawPixmap( newopt.rect.left() + adjustment.width(),
1233 newopt.rect.top() + adjustment.height(),
1234 QPixmap::grabWidget( progressbar ) );
1241 QSize AddonItemDelegate::sizeHint( const QStyleOptionViewItem &option,
1242 const QModelIndex &index ) const
1244 if ( index.isValid() )
1246 return QSize( 200, 4 * option.fontMetrics.height()
1247 + margins.top() + margins.bottom() );
1253 QWidget *AddonItemDelegate::createEditor( QWidget *parent,
1254 const QStyleOptionViewItem &option,
1255 const QModelIndex &index) const
1258 QWidget *editorWidget = new QWidget( parent );
1259 QPushButton *installButton;
1260 QPushButton *infoButton;
1262 editorWidget->setLayout( new QHBoxLayout() );
1263 editorWidget->layout()->setMargin( 0 );
1265 infoButton = new QPushButton( QIcon( ":/menu/info" ),
1266 qtr( "More information..." ) );
1267 connect( infoButton, SIGNAL(clicked()), this, SIGNAL(showInfo()) );
1268 editorWidget->layout()->addWidget( infoButton );
1270 if ( ADDON_MANAGEABLE &
1271 index.data( AddonsListModel::FlagsRole ).toInt() )
1273 if ( index.data( AddonsListModel::StateRole ).toInt() == ADDON_INSTALLED )
1274 installButton = new QPushButton( QIcon( ":/buttons/playlist/playlist_remove" ),
1275 qtr("&Uninstall"), parent );
1277 installButton = new QPushButton( QIcon( ":/buttons/playlist/playlist_add" ),
1278 qtr("&Install"), parent );
1279 CONNECT( installButton, clicked(), this, editButtonClicked() );
1280 editorWidget->layout()->addWidget( installButton );
1283 editorWidget->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
1285 return editorWidget;
1288 void AddonItemDelegate::updateEditorGeometry( QWidget *editor,
1289 const QStyleOptionViewItem &option,
1290 const QModelIndex &index) const
1293 QSize size = editor->sizeHint();
1294 editor->setGeometry( option.rect.right() - size.width(),
1295 option.rect.top() + ( option.rect.height() - size.height()),
1300 void AddonItemDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
1301 const QModelIndex &index ) const
1303 model->setData( index, editor->property("Addon::state"), AddonsListModel::StateRole );
1306 void AddonItemDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
1308 editor->setProperty("Addon::state", index.data( AddonsListModel::StateRole ) );
1311 void AddonItemDelegate::setAnimator( DelegateAnimationHelper *animator_ )
1315 QProgressBar *progress = new QProgressBar( );
1316 progress->setMinimum( 0 );
1317 progress->setMaximum( 0 );
1318 progress->setTextVisible( false );
1319 progressbar = progress;
1321 animator = animator_;
1324 void AddonItemDelegate::editButtonClicked()
1326 QWidget *editor = qobject_cast<QWidget *>(sender()->parent());
1327 if ( !editor ) return;
1328 int value = editor->property("Addon::state").toInt();
1329 if ( ( value == ADDON_INSTALLED ) )
1331 editor->setProperty("Addon::state", ADDON_UNINSTALLING );
1334 editor->setProperty("Addon::state", ADDON_INSTALLING );
1335 emit commitData( editor );
1336 emit closeEditor( editor );
1339 /* "More information" dialog */
1341 ExtensionInfoDialog::ExtensionInfoDialog( const QModelIndex &index,
1342 intf_thread_t *p_intf,
1344 : QVLCDialog( parent, p_intf )
1346 // Let's be a modal dialog
1347 setWindowModality( Qt::WindowModal );
1350 setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
1353 QGridLayout *layout = new QGridLayout( this );
1356 QLabel *icon = new QLabel( this );
1357 QPixmap pix = index.data(Qt::DecorationRole).value<QPixmap>();
1358 Q_ASSERT( !pix.isNull() );
1359 icon->setPixmap( pix );
1360 icon->setAlignment( Qt::AlignCenter );
1361 icon->setFixedSize( 48, 48 );
1362 layout->addWidget( icon, 1, 0, 2, 1 );
1365 QLabel *label = new QLabel( index.data(Qt::DisplayRole).toString(), this );
1366 QFont font = label->font();
1367 font.setBold( true );
1368 font.setPointSizeF( font.pointSizeF() * 1.3f );
1369 label->setFont( font );
1370 layout->addWidget( label, 0, 0, 1, -1 );
1373 label = new QLabel( "<b>" + qtr( "Version" ) + ":</b>", this );
1374 layout->addWidget( label, 1, 1, 1, 1, Qt::AlignBottom );
1375 label = new QLabel( index.data(ExtensionListModel::VersionRole).toString(), this );
1376 layout->addWidget( label, 1, 2, 1, 2, Qt::AlignBottom );
1379 label = new QLabel( "<b>" + qtr( "Author" ) + ":</b>", this );
1380 layout->addWidget( label, 2, 1, 1, 1, Qt::AlignTop );
1381 label = new QLabel( index.data(ExtensionListModel::AuthorRole).toString(), this );
1382 layout->addWidget( label, 2, 2, 1, 2, Qt::AlignTop );
1386 label = new QLabel( this );
1387 label->setText( index.data(ExtensionListModel::SummaryRole).toString() );
1388 label->setWordWrap( true );
1389 label->setOpenExternalLinks( true );
1390 layout->addWidget( label, 4, 0, 1, -1 );
1393 label = new QLabel( "<b>" + qtr( "Website" ) + ":</b>", this );
1394 layout->addWidget( label, 5, 0, 1, 2 );
1395 label = new QLabel( QString("<a href=\"%1\">%2</a>")
1396 .arg( index.data(ExtensionListModel::LinkRole).toString() )
1397 .arg( index.data(ExtensionListModel::LinkRole).toString() )
1399 label->setOpenExternalLinks( true );
1400 layout->addWidget( label, 5, 2, 1, -1 );
1403 label = new QLabel( "<b>" + qtr( "File" ) + ":</b>", this );
1404 layout->addWidget( label, 6, 0, 1, 2 );
1406 new QLineEdit( index.data(ExtensionListModel::FilenameRole).toString(), this );
1407 line->setReadOnly( true );
1408 layout->addWidget( line, 6, 2, 1, -1 );
1411 QDialogButtonBox *group = new QDialogButtonBox( this );
1412 QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
1413 group->addButton( closeButton, QDialogButtonBox::RejectRole );
1414 BUTTONACT( closeButton, close() );
1416 layout->addWidget( group, 7, 0, 1, -1 );
1419 layout->setColumnStretch( 2, 1 );
1420 layout->setRowStretch( 4, 1 );
1421 setMinimumSize( 450, 350 );
1425 AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
1426 intf_thread_t *p_intf, QWidget *parent )
1427 : QVLCDialog( parent, p_intf )
1429 // Let's be a modal dialog
1430 setWindowModality( Qt::WindowModal );
1433 setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
1436 QGridLayout *layout = new QGridLayout( this );
1440 QLabel *iconLabel = new QLabel( this );
1441 iconLabel->setFixedSize( 100, 100 );
1442 QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
1443 icon.scaled( iconLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation );
1444 iconLabel->setPixmap( icon );
1445 iconLabel->setAlignment( Qt::AlignCenter | Qt::AlignTop );
1446 layout->addWidget( iconLabel, 1, 0, 2, 1 );
1449 label = new QLabel( index.data(Qt::DisplayRole).toString(), this );
1450 QFont font = label->font();
1451 font.setBold( true );
1452 font.setPointSizeF( font.pointSizeF() * 1.3f );
1453 label->setFont( font );
1454 layout->addWidget( label, 0, 0, 1, -1 );
1456 // HTML Content on right side
1457 QTextEdit *textContent = new QTextEdit();
1458 textContent->viewport()->setAutoFillBackground( false );
1459 textContent->setAcceptRichText( true );
1460 textContent->setBackgroundRole( QPalette::Window );
1461 textContent->setFrameStyle( QFrame::NoFrame );
1462 textContent->setAutoFillBackground( false );
1463 textContent->setReadOnly( true );
1464 layout->addWidget( textContent, 1, 1, 4, -1 );
1467 QString type = AddonsManager::getAddonType( index.data(AddonsListModel::TypeRole).toInt() );
1468 textContent->append( QString("<b>%1:</b> %2<br/>")
1469 .arg( qtr("Type") ).arg( type ) );
1472 QString version = index.data(ExtensionListModel::VersionRole).toString();
1473 if ( !version.isEmpty() )
1475 textContent->append( QString("<b>%1:</b> %2<br/>")
1476 .arg( qtr("Version") ).arg( version ) );
1480 QString author = index.data(ExtensionListModel::AuthorRole).toString();
1481 if ( !author.isEmpty() )
1483 textContent->append( QString("<b>%1:</b> %2<br/>")
1484 .arg( qtr("Author") ).arg( author ) );
1488 textContent->append( QString("%1<br/>\n")
1489 .arg( index.data(AddonsListModel::SummaryRole).toString() ) );
1492 QString description = index.data(AddonsListModel::DescriptionRole).toString();
1493 if ( !description.isEmpty() )
1495 textContent->append( QString("<hr/>\n%1")
1496 .arg( description.replace("\n", "<br/>") ) );
1500 QString sourceUrl = index.data(ExtensionListModel::LinkRole).toString();
1501 if ( !sourceUrl.isEmpty() )
1503 label = new QLabel( "<b>" + qtr( "Website" ) + ":</b>", this );
1504 layout->addWidget( label, 5, 0, 1, 2 );
1505 label = new QLabel( QString("<a href=\"%1\">%2</a>")
1506 .arg( sourceUrl ).arg( sourceUrl ), this );
1507 label->setOpenExternalLinks( true );
1508 layout->addWidget( label, 5, 2, 1, -1 );
1512 QList<QVariant> list = index.data(ExtensionListModel::FilenameRole).toList();
1513 if ( ! list.empty() )
1515 label = new QLabel( "<b>" + qtr( "Files" ) + ":</b>", this );
1516 layout->addWidget( label, 6, 0, 1, 2 );
1517 QComboBox *filesCombo = new QComboBox();
1518 Q_FOREACH( const QVariant & file, list )
1519 filesCombo->addItem( file.toString() );
1520 layout->addWidget( filesCombo, 6, 2, 1, -1 );
1524 QDialogButtonBox *group = new QDialogButtonBox( this );
1525 QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
1526 group->addButton( closeButton, QDialogButtonBox::RejectRole );
1527 BUTTONACT( closeButton, close() );
1529 layout->addWidget( group, 7, 0, 1, -1 );
1532 layout->setColumnStretch( 2, 1 );
1533 layout->setRowStretch( 4, 1 );
1534 setMinimumSize( 640, 480 );
1537 static QPixmap *loadPixmapFromData( char *data, int size )
1539 if( !data || size <= 0 )
1541 QPixmap *pixmap = new QPixmap();
1542 if( !pixmap->loadFromData( (const uchar*) data, (uint) size ) )