qt/macos: add missing addons intf and meta categories
[vlc.git] / modules / gui / qt4 / dialogs / plugins.cpp
1 /*****************************************************************************
2  * plugins.cpp : Plug-ins and extensions listing
3  ****************************************************************************
4  * Copyright (C) 2008-2010 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jean-Baptiste Kempf <jb (at) videolan.org>
8  *          Jean-Philippe AndrĂ© <jpeg (at) videolan.org>
9  *
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.
14  *
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.
19  *
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  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include "plugins.hpp"
30
31 #include "util/searchlineedit.hpp"
32 #include "extensions_manager.hpp"
33 #include "managers/addons_manager.hpp"
34 #include "util/animators.hpp"
35
36 #include <assert.h>
37
38 #include <vlc_modules.h>
39
40 #include <QTreeWidget>
41 #include <QStringList>
42 #include <QTabWidget>
43 #include <QHeaderView>
44 #include <QDialogButtonBox>
45 #include <QLineEdit>
46 #include <QLabel>
47 #include <QVBoxLayout>
48 #include <QComboBox>
49 #include <QHBoxLayout>
50 #include <QVBoxLayout>
51 #include <QSpacerItem>
52 #include <QListView>
53 #include <QListWidget>
54 #include <QPainter>
55 #include <QStyleOptionViewItem>
56 #include <QKeyEvent>
57 #include <QPushButton>
58 #include <QCheckBox>
59 #include <QPixmap>
60 #include <QStylePainter>
61 #include <QProgressBar>
62 #include <QTextEdit>
63 #include <QUrl>
64 #include <QMimeData>
65 #include <QSplitter>
66 #include <QToolButton>
67 #include <QStackedWidget>
68
69 static QPixmap *loadPixmapFromData( char *, int size );
70
71
72 PluginDialog::PluginDialog( intf_thread_t *_p_intf ) : QVLCFrame( _p_intf )
73 {
74     setWindowTitle( qtr( "Plugins and extensions" ) );
75     setWindowRole( "vlc-plugins" );
76
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 ),
84                   qtr( "Plugins" ) );
85     layout->addWidget( tabs );
86
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 ) );
93 }
94
95 PluginDialog::~PluginDialog()
96 {
97     saveWidgetPosition( "PluginsDialog" );
98 }
99
100 /* Plugins tab */
101
102 PluginTab::PluginTab( intf_thread_t *p_intf_ )
103         : QVLCFrame( p_intf_ )
104 {
105     QGridLayout *layout = new QGridLayout( this );
106
107     /* Main Tree for modules */
108     treePlugins = new QTreeWidget;
109     layout->addWidget( treePlugins, 0, 0, 1, -1 );
110
111     /* Users cannot move the columns around but we need to sort */
112 #if HAS_QT5
113     treePlugins->header()->setSectionsMovable( false );
114 #else
115     treePlugins->header()->setMovable( false );
116 #endif
117     treePlugins->header()->setSortIndicatorShown( true );
118     //    treePlugins->header()->setResizeMode( QHeaderView::ResizeToContents );
119     treePlugins->setAlternatingRowColors( true );
120     treePlugins->setColumnWidth( 0, 200 );
121
122     QStringList headerNames;
123     headerNames << qtr("Name") << qtr("Capability" ) << qtr( "Score" );
124     treePlugins->setHeaderLabels( headerNames );
125
126     FillTree();
127
128     /* Set capability column to the correct Size*/
129     treePlugins->resizeColumnToContents( 1 );
130     treePlugins->header()->restoreState(
131             getSettings()->value( "Plugins/Header-State" ).toByteArray() );
132
133     treePlugins->setSortingEnabled( true );
134     treePlugins->sortByColumn( 1, Qt::AscendingOrder );
135
136     QLabel *label = new QLabel( qtr("&Search:"), this );
137     edit = new SearchLineEdit( this );
138     label->setBuddy( edit );
139
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& ) );
144
145     setMinimumSize( 500, 300 );
146     restoreWidgetPosition( "Plugins", QSize( 540, 400 ) );
147 }
148
149 inline void PluginTab::FillTree()
150 {
151     size_t count;
152     module_t **p_list = module_list_get( &count );
153
154     for( unsigned int i = 0; i < count; i++ )
155     {
156         module_t *p_module = p_list[i];
157
158         QStringList qs_item;
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 ) );
162 #ifndef DEBUG
163         if( qs_item.at(1).isEmpty() ) continue;
164 #endif
165
166         QTreeWidgetItem *item = new PluginTreeItem( qs_item );
167         treePlugins->addTopLevelItem( item );
168     }
169     module_list_free( p_list );
170 }
171
172 void PluginTab::search( const QString& qs )
173 {
174     QList<QTreeWidgetItem *> items = treePlugins->findItems( qs, Qt::MatchContains );
175     items += treePlugins->findItems( qs, Qt::MatchContains, 1 );
176
177     QTreeWidgetItem *item = NULL;
178     for( int i = 0; i < treePlugins->topLevelItemCount(); i++ )
179     {
180         item = treePlugins->topLevelItem( i );
181         item->setHidden( !items.contains( item ) );
182     }
183 }
184
185 PluginTab::~PluginTab()
186 {
187     saveWidgetPosition( "Plugins" );
188     getSettings()->setValue( "Plugins/Header-State",
189                              treePlugins->header()->saveState() );
190 }
191
192 void PluginTab::keyPressEvent( QKeyEvent *keyEvent )
193 {
194     if( keyEvent->key() == Qt::Key_Return ||
195         keyEvent->key() == Qt::Key_Enter )
196         keyEvent->accept();
197     else
198         keyEvent->ignore();
199 }
200
201 bool PluginTreeItem::operator< ( const QTreeWidgetItem & other ) const
202 {
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 )
207     {
208         if ( text( PluginTab::CAPABILITY ) == other.text( PluginTab::CAPABILITY ) )
209             return text( PluginTab::NAME ) < other.text( PluginTab::NAME );
210         else
211             return text( PluginTab::CAPABILITY ) < other.text( PluginTab::CAPABILITY );
212     }
213     return text( col ) < other.text( col );
214 }
215
216 /* Extensions tab */
217 ExtensionTab::ExtensionTab( intf_thread_t *p_intf_ )
218         : QVLCFrame( p_intf_ )
219 {
220     // Layout
221     QVBoxLayout *layout = new QVBoxLayout( this );
222
223     // ListView
224     extList = new QListView( this );
225     CONNECT( extList, activated( const QModelIndex& ),
226              this, moreInformation() );
227     layout->addWidget( extList );
228
229     // List item delegate
230     ExtensionItemDelegate *itemDelegate = new ExtensionItemDelegate( extList );
231     extList->setItemDelegate( itemDelegate );
232
233     // Extension list look & feeling
234     extList->setAlternatingRowColors( true );
235     extList->setSelectionMode( QAbstractItemView::SingleSelection );
236
237     // Model
238     ExtensionListModel *model =
239       new ExtensionListModel( extList, ExtensionsManager::getInstance( p_intf ) );
240     extList->setModel( model );
241
242     // Buttons' layout
243     QDialogButtonBox *buttonsBox = new QDialogButtonBox;
244
245     // More information button
246     butMoreInfo = new QPushButton( QIcon( ":/menu/info" ),
247                                    qtr( "More information..." ),
248                                    this );
249     CONNECT( butMoreInfo, clicked(), this, moreInformation() );
250     buttonsBox->addButton( butMoreInfo, QDialogButtonBox::ActionRole );
251
252     // Reload button
253     ExtensionsManager *EM = ExtensionsManager::getInstance( p_intf );
254     QPushButton *reload = new QPushButton( QIcon( ":/update" ),
255                                            qtr( "Reload extensions" ),
256                                            this );
257     CONNECT( reload, clicked(), EM, reloadExtensions() );
258     CONNECT( reload, clicked(), this, updateButtons() );
259     CONNECT( extList->selectionModel(),
260              selectionChanged( const QItemSelection &, const QItemSelection & ),
261              this,
262              updateButtons() );
263     buttonsBox->addButton( reload, QDialogButtonBox::ResetRole );
264
265     layout->addWidget( buttonsBox );
266     updateButtons();
267 }
268
269 ExtensionTab::~ExtensionTab()
270 {
271 }
272
273 void ExtensionTab::updateButtons()
274 {
275     butMoreInfo->setEnabled( extList->selectionModel()->hasSelection() );
276 }
277
278 // Do not close on ESC or ENTER
279 void ExtensionTab::keyPressEvent( QKeyEvent *keyEvent )
280 {
281     if( keyEvent->key() == Qt::Key_Return ||
282         keyEvent->key() == Qt::Key_Enter )
283         keyEvent->accept();
284     else
285         keyEvent->ignore();
286 }
287
288 // Show more information
289 void ExtensionTab::moreInformation()
290 {
291     QModelIndex index = extList->selectionModel()->selectedIndexes().first();
292
293     if( !index.isValid() )
294         return;
295
296     ExtensionInfoDialog dlg( index, p_intf, this );
297     dlg.exec();
298 }
299
300 static QPixmap hueRotate( QImage image, const QColor &source, const QColor &target )
301 {
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() )
306     {
307         /* color 1 = transparency */
308         for ( int i=1; i < image.colorCount(); i++ )
309         {
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() );
315         }
316     }
317     return QPixmap::fromImage( image );
318 }
319
320 /* Add-ons tab */
321 AddonsTab::AddonsTab( intf_thread_t *p_intf_ ) : QVLCFrame( p_intf_ )
322 {
323     b_localdone = false;
324     QSplitter *splitter = new QSplitter( this );
325     setLayout( new QHBoxLayout() );
326     layout()->addWidget( splitter );
327
328     QWidget *leftPane = new QWidget();
329     splitter->addWidget( leftPane );
330     leftPane->setLayout( new QVBoxLayout() );
331
332     QWidget *rightPane = new QWidget();
333     splitter->addWidget( rightPane );
334
335     splitter->setCollapsible( 0, false );
336     splitter->setCollapsible( 1, false );
337     splitter->setSizeIncrement( 32, 1 );
338
339     // Layout
340     QVBoxLayout *layout = new QVBoxLayout( rightPane );
341
342     // Left Pane
343     leftPane->layout()->setMargin(0);
344     leftPane->layout()->setSpacing(0);
345
346     SearchLineEdit *searchInput = new SearchLineEdit();
347     leftPane->layout()->addWidget( searchInput );
348     leftPane->layout()->addItem( new QSpacerItem( 0, 10 ) );
349
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 );
371
372     ADD_CATEGORY( qtr("All"), qtr("Interface Settings"),
373                   -1 );
374     ADD_CATEGORY( qtr("Skins"),
375                   qtr( "Skins customize player's appearance."
376                        " You can activate them through preferences." ),
377                   ADDON_SKIN2 );
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"),
387                   "",
388                   ADDON_INTERFACE );
389     ADD_CATEGORY( qtr("Art and meta fetchers"),
390                   qtr( "Retrieves extra info and art for playlist items" ),
391                   ADDON_META );
392     ADD_CATEGORY( qtr("Extensions"),
393                   qtr( "Extensions brings various enhancements."
394                        " Check descriptions for more details" ),
395                   ADDON_EXTENSION );
396
397     // Right Pane
398     rightPane->layout()->setMargin(0);
399     rightPane->layout()->setSpacing(0);
400
401     // Splitter sizes init
402     QList<int> sizes;
403     int width = leftPane->sizeHint().width();
404     sizes << width << size().width() - width;
405     splitter->setSizes( sizes );
406
407     // Filters
408     leftPane->layout()->addItem( new QSpacerItem( 0, 30 ) );
409
410     QStackedWidget *switchStack = new QStackedWidget();
411     switchStack->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum );
412     leftPane->layout()->addWidget( switchStack );
413
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) );
418
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() );
425
426     leftPane->layout()->addItem( new QSpacerItem( 0, 0, QSizePolicy::Maximum, QSizePolicy::Expanding ) );
427
428     // Main View
429     AddonsManager *AM = AddonsManager::getInstance( p_intf );
430
431     // ListView
432     addonsView = new QListView( this );
433     CONNECT( addonsView, activated( const QModelIndex& ), this, moreInformation() );
434     layout->addWidget( addonsView );
435
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() );
441
442     // Extension list look & feeling
443     addonsView->setAlternatingRowColors( true );
444     addonsView->setSelectionMode( QAbstractItemView::SingleSelection );
445
446     // Drop packages
447     addonsView->setAcceptDrops( true );
448     addonsView->setDefaultDropAction( Qt::CopyAction );
449     addonsView->setDropIndicatorShown( true );
450     addonsView->setDragDropMode( QAbstractItemView::DropOnly );
451
452     // Model
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 );
462
463     CONNECT( mapper, mapped(int), addonsModel, setTypeFilter(int) );
464
465     CONNECT( searchInput, textChanged( const QString &),
466              addonsModel, setFilterFixedString( QString ) );
467
468     CONNECT( addonsView->selectionModel(), currentChanged(QModelIndex,QModelIndex),
469              addonsView, edit(QModelIndex) );
470
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 * ) );
475
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 );
485 }
486
487 AddonsTab::~AddonsTab()
488 {
489     delete spinnerAnimation;
490 }
491
492 bool AddonsTab::eventFilter( QObject *obj, QEvent *event )
493 {
494     if ( obj != addonsView->viewport() )
495         return false;
496
497     switch( event->type() )
498     {
499     case QEvent::Paint:
500         if ( spinnerAnimation->state() == PixmapAnimator::Running )
501         {
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 );
514         }
515         else if ( addonsModel->rowCount() == 0 )
516         {
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 );
525         }
526         break;
527     case QEvent::Show:
528         if ( !b_localdone && addonsView->model()->rowCount() < 1 )
529         {
530             b_localdone = true;
531             AddonsManager *AM = AddonsManager::getInstance( p_intf );
532             AM->findInstalled();
533         }
534         break;
535     case QEvent::DragEnter:
536     {
537         QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent *>(event);
538         if ( !dragEvent ) break;
539         QList<QUrl> urls = dragEvent->mimeData()->urls();
540         if ( dragEvent->proposedAction() != Qt::CopyAction
541           || urls.count() != 1
542           || urls.first().scheme() != "file"
543           || ! urls.first().path().endsWith(".vlp") )
544             return false;
545         dragEvent->acceptProposedAction();
546         return true;
547     }
548     case QEvent::DragMove:
549     {
550         QDragMoveEvent *moveEvent = static_cast<QDragMoveEvent *>(event);
551         if ( !moveEvent ) break;
552         if ( moveEvent->proposedAction() != Qt::CopyAction )
553             return false;
554         moveEvent->acceptProposedAction();
555         return true;
556     }
557     case QEvent::Drop:
558     {
559         QDropEvent *dropEvent = static_cast<QDropEvent *>(event);
560         if ( !dropEvent ) break;
561         if ( dropEvent->proposedAction() != Qt::CopyAction )
562             return false;
563         if ( dropEvent->mimeData()->urls().count() )
564         {
565             AddonsManager *AM = AddonsManager::getInstance( p_intf );
566             AM->findDesignatedAddon( dropEvent->mimeData()->urls().first().toString() );
567             dropEvent->acceptProposedAction();
568         }
569         return true;
570     }
571     default:
572         break;
573     }
574     return false;
575 }
576
577 void AddonsTab::moreInformation()
578 {
579     QModelIndex index = addonsView->selectionModel()->selectedIndexes().first();
580     if( !index.isValid() ) return;
581     AddonInfoDialog dlg( index, p_intf, this );
582     dlg.exec();
583 }
584
585 void AddonsTab::installChecked( int i )
586 {
587     if ( i == Qt::Checked )
588         addonsModel->setStatusFilter( ADDON_INSTALLED );
589     else
590         addonsModel->setStatusFilter( 0 );
591 }
592
593 void AddonsTab::reposync()
594 {
595     QStackedWidget *tab = qobject_cast<QStackedWidget *>(sender()->parent());
596     if ( tab )
597     {
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();
603         AM->findNewAddons();
604     }
605 }
606
607 /* Safe copy of the extension_t struct */
608 ExtensionListModel::ExtensionCopy::ExtensionCopy( extension_t *p_ext )
609 {
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 );
622 }
623
624 ExtensionListModel::ExtensionCopy::~ExtensionCopy()
625 {
626     delete icon;
627 }
628
629 QVariant ExtensionListModel::ExtensionCopy::data( int role ) const
630 {
631     switch( role )
632     {
633     case Qt::DisplayRole:
634         return title;
635     case Qt::DecorationRole:
636         if ( !icon ) return QPixmap( ":/logo/vlc48.png" );
637         return *icon;
638     case SummaryRole:
639         return shortdesc;
640     case VersionRole:
641         return version;
642     case AuthorRole:
643         return author;
644     case LinkRole:
645         return url;
646     case FilenameRole:
647         return name;
648     default:
649         return QVariant();
650     }
651 }
652
653 /* Extensions list model for the QListView */
654 ExtensionListModel::ExtensionListModel( QObject *parent )
655     : QAbstractListModel( parent ), EM( NULL )
656 {
657
658 }
659
660 ExtensionListModel::ExtensionListModel( QObject *parent, ExtensionsManager* EM_ )
661         : QAbstractListModel( parent ), EM( EM_ )
662 {
663     // Connect to ExtensionsManager::extensionsUpdated()
664     CONNECT( EM, extensionsUpdated(), this, updateList() );
665
666     // Load extensions now if not already loaded
667     EM->loadExtensions();
668 }
669
670 ExtensionListModel::~ExtensionListModel()
671 {
672     // Clear extensions list
673     while( !extensions.isEmpty() )
674         delete extensions.takeLast();
675 }
676
677 void ExtensionListModel::updateList()
678 {
679     ExtensionCopy *ext;
680
681     // Clear extensions list
682     while( !extensions.isEmpty() )
683     {
684         ext = extensions.takeLast();
685         delete ext;
686     }
687
688     // Find new extensions
689     extensions_manager_t *p_mgr = EM->getManager();
690     if( !p_mgr )
691         return;
692
693     vlc_mutex_lock( &p_mgr->lock );
694     extension_t *p_ext;
695     FOREACH_ARRAY( p_ext, p_mgr->extensions )
696     {
697         ext = new ExtensionCopy( p_ext );
698         extensions.append( ext );
699     }
700     FOREACH_END()
701     vlc_mutex_unlock( &p_mgr->lock );
702     vlc_object_release( p_mgr );
703
704     emit dataChanged( index( 0 ), index( rowCount() - 1 ) );
705 }
706
707 int ExtensionListModel::rowCount( const QModelIndex& ) const
708 {
709     int count = 0;
710     extensions_manager_t *p_mgr = EM->getManager();
711     if( !p_mgr )
712         return 0;
713
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 );
718
719     return count;
720 }
721
722 QVariant ExtensionListModel::data( const QModelIndex& index, int role ) const
723 {
724     if( !index.isValid() )
725         return QVariant();
726
727     ExtensionCopy * extension =
728             static_cast<ExtensionCopy *>(index.internalPointer());
729     if ( !extension )
730         return QVariant();
731     else
732         return extension->data( role );
733 }
734
735 QModelIndex ExtensionListModel::index( int row, int column,
736                                        const QModelIndex& ) const
737 {
738     if( column != 0 )
739         return QModelIndex();
740     if( row < 0 || row >= extensions.count() )
741         return QModelIndex();
742
743     return createIndex( row, 0, extensions.at( row ) );
744 }
745
746 AddonsListModel::Addon::Addon( addon_entry_t *p_entry_ )
747 {
748     p_entry = p_entry_;
749     addon_entry_Hold( p_entry );
750 }
751
752 AddonsListModel::Addon::~Addon()
753 {
754     addon_entry_Release( p_entry );
755 }
756
757 bool AddonsListModel::Addon::operator==( const Addon & other ) const
758 {
759     //return data( IDRole ) == other.data( IDRole );
760     return p_entry == other.p_entry;
761 }
762
763 bool AddonsListModel::Addon::operator==( const addon_entry_t * p_other ) const
764 {
765     return p_entry == p_other;
766 }
767
768 QVariant AddonsListModel::Addon::data( int role ) const
769 {
770     QVariant returnval;
771
772     vlc_mutex_lock( &p_entry->lock );
773     switch( role )
774     {
775     case Qt::DisplayRole:
776     {
777         returnval = qfu( p_entry->psz_name );
778         break;
779     }
780     case Qt::DecorationRole:
781         if ( p_entry->psz_image_data )
782         {
783             QPixmap pixmap;
784             pixmap.loadFromData( QByteArray::fromBase64( QByteArray( p_entry->psz_image_data ) ),
785                 0,
786                 Qt::AutoColor
787             );
788             returnval = pixmap;
789         }
790         else if ( p_entry->e_flags & ADDON_BROKEN )
791             returnval = QPixmap( ":/addons/broken" );
792         else
793             returnval = QPixmap( ":/addons/default" );
794         break;
795     case Qt::ToolTipRole:
796     {
797         if ( !( p_entry->e_flags & ADDON_MANAGEABLE ) )
798         {
799             returnval = qtr("This addon has been installed manually. VLC can't manage it by itself.");
800         }
801         break;
802     }
803     case SummaryRole:
804         returnval = qfu( p_entry->psz_summary );
805         break;
806     case DescriptionRole:
807         returnval = qfu( p_entry->psz_description );
808         break;
809     case TypeRole:
810         returnval = QVariant( (int) p_entry->e_type );
811         break;
812     case UUIDRole:
813         returnval = QByteArray( (const char *) p_entry->uuid, (int) sizeof( addon_uuid_t ) );
814         break;
815     case FlagsRole:
816         returnval = QVariant( (int) p_entry->e_flags );
817         break;
818     case StateRole:
819         returnval = QVariant( (int) p_entry->e_state );
820         break;
821     case DownloadsCountRole:
822         returnval = QVariant( (double) p_entry->i_downloads );
823         break;
824     case ScoreRole:
825         returnval = QVariant( (int) p_entry->i_score );
826         break;
827     case VersionRole:
828         returnval = QVariant( p_entry->psz_version );
829         break;
830     case AuthorRole:
831         returnval = qfu( p_entry->psz_author );
832         break;
833     case LinkRole:
834         returnval = qfu( p_entry->psz_source_uri );
835         break;
836     case FilenameRole:
837     {
838         QList<QString> list;
839         FOREACH_ARRAY( addon_file_t *p_file, p_entry->files )
840         list << qfu( p_file->psz_filename );
841         FOREACH_END();
842         returnval = QVariant( list );
843         break;
844     }
845     default:
846         break;
847     }
848     vlc_mutex_unlock( &p_entry->lock );
849
850     return returnval;
851 }
852
853 AddonsListModel::AddonsListModel( AddonsManager *AM_, QObject *parent )
854     :ExtensionListModel( parent ), AM( AM_ )
855 {
856
857 }
858
859 void AddonsListModel::addonAdded(  addon_entry_t *p_entry )
860 {
861     beginInsertRows( QModelIndex(), addons.count(), addons.count() );
862     addons << new Addon( p_entry );
863     insertRow( addons.count() - 1 );
864     endInsertRows();
865 }
866
867 void AddonsListModel::addonChanged( const addon_entry_t *p_entry )
868 {
869     int row = 0;
870     foreach ( const Addon *addon, addons )
871     {
872         if ( *addon == p_entry )
873         {
874             emit dataChanged( index( row, 0 ), index( row, 0 ) );
875             break;
876         }
877         row++;
878     }
879 }
880
881 int AddonsListModel::rowCount( const QModelIndex & ) const
882 {
883     return addons.count();
884 }
885
886 Qt::ItemFlags AddonsListModel::flags( const QModelIndex &index ) const
887 {
888     Qt::ItemFlags i_flags = ExtensionListModel::flags( index );
889     int i_state = data( index, StateRole ).toInt();
890
891     if ( i_state == ADDON_UNINSTALLING || i_state == ADDON_INSTALLING )
892     {
893         i_flags &= !Qt::ItemIsEnabled;
894     }
895
896     i_flags |= Qt::ItemIsEditable;
897
898     return i_flags;
899 }
900
901 bool AddonsListModel::setData( const QModelIndex &index, const QVariant &value, int role )
902 {
903     /* We NEVER set values directly */
904     if ( role == StateRole )
905     {
906         int i_value = value.toInt();
907         if ( i_value == ADDON_INSTALLING )
908         {
909             AM->install( data( index, UUIDRole ).toByteArray() );
910         }
911         else if ( i_value == ADDON_UNINSTALLING )
912         {
913             AM->remove( data( index, UUIDRole ).toByteArray() );
914         }
915     }
916     else if ( role == StateRole + 1 )
917     {
918         emit dataChanged( index, index );
919     }
920     return true;
921 }
922
923 QColor AddonsListModel::getColorByAddonType( int i_type )
924 {
925     QColor color;
926     switch( i_type )
927     {
928     case ADDON_EXTENSION:
929         color = QColor(0xDB, 0xC5, 0x40);
930         break;
931     case ADDON_PLAYLIST_PARSER:
932         color = QColor(0x36, 0xBB, 0x59);
933         break;
934     case ADDON_SERVICE_DISCOVERY:
935         color = QColor(0xDB, 0x52, 0x40);
936         break;
937     case ADDON_SKIN2:
938         color = QColor(0x8B, 0xD6, 0xFC);
939         break;
940     case ADDON_INTERFACE:
941         color = QColor(0x00, 0x13, 0x85);
942         break;
943     case ADDON_META:
944         color = QColor(0xCD, 0x23, 0xBF);
945         break;
946     case ADDON_PLUGIN:
947     case ADDON_UNKNOWN:
948     case ADDON_OTHER:
949     default:
950         break;
951     }
952     return color;
953 }
954
955 QVariant AddonsListModel::data( const QModelIndex& index, int role ) const
956 {
957     if( !index.isValid() )
958         return QVariant();
959
960     return ((Addon *)index.internalPointer())->data( role );
961 }
962
963 QModelIndex AddonsListModel::index( int row, int column,
964                                        const QModelIndex& ) const
965 {
966     if( column != 0 )
967         return QModelIndex();
968     if( row < 0 || row >= addons.count() )
969         return QModelIndex();
970
971     return createIndex( row, 0, addons.at( row ) );
972 }
973
974 /* Sort Filter */
975 AddonsSortFilterProxyModel::AddonsSortFilterProxyModel( QObject *parent )
976     : QSortFilterProxyModel( parent )
977 {
978     i_type_filter = -1;
979     i_status_filter = 0;
980 }
981
982 void AddonsSortFilterProxyModel::setTypeFilter( int type )
983 {
984     i_type_filter = type;
985     invalidateFilter();
986 }
987
988 void AddonsSortFilterProxyModel::setStatusFilter( int flags )
989 {
990     i_status_filter = flags;
991     invalidateFilter();
992 }
993
994 bool AddonsSortFilterProxyModel::filterAcceptsRow( int source_row,
995                                        const QModelIndex &source_parent ) const
996 {
997     if ( !QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent ) )
998         return false;
999
1000     QModelIndex item = sourceModel()->index( source_row, 0, source_parent );
1001
1002     if ( i_type_filter > -1 &&
1003          item.data( AddonsListModel::TypeRole ).toInt() != i_type_filter )
1004         return false;
1005
1006     if ( i_status_filter > 0 &&
1007         ( item.data( AddonsListModel::StateRole ).toInt() & i_status_filter ) != i_status_filter )
1008         return false;
1009
1010     return true;
1011 }
1012
1013 /* Extension List Widget Item */
1014 ExtensionItemDelegate::ExtensionItemDelegate( QObject *parent )
1015         : QStyledItemDelegate( parent )
1016 {
1017     margins = QMargins( 4, 4, 4, 4 );
1018 }
1019
1020 ExtensionItemDelegate::~ExtensionItemDelegate()
1021 {
1022 }
1023
1024 void ExtensionItemDelegate::paint( QPainter *painter,
1025                                    const QStyleOptionViewItem &option,
1026                                    const QModelIndex &index ) const
1027 {
1028     QStyleOptionViewItemV4 opt = option;
1029     initStyleOption( &opt, index );
1030
1031     // Draw background
1032     if ( opt.state & QStyle::State_Selected )
1033         painter->fillRect( opt.rect, opt.palette.highlight() );
1034
1035     // Icon
1036     QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
1037     if( !icon.isNull() )
1038     {
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 )
1044         );
1045     }
1046
1047     painter->save();
1048     painter->setRenderHint( QPainter::TextAntialiasing );
1049
1050     if ( opt.state & QStyle::State_Selected )
1051         painter->setPen( opt.palette.highlightedText().color() );
1052
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(),
1058                      margins.top(),
1059                      - margins.right(),
1060                      - margins.bottom() - opt.fontMetrics.height() );
1061
1062     painter->drawText( textrect, Qt::AlignLeft,
1063                        index.data( Qt::DisplayRole ).toString() );
1064
1065     font.setBold( false );
1066     painter->setFont( font );
1067     painter->drawText( textrect.translated( 0, option.fontMetrics.height() ),
1068                        Qt::AlignLeft,
1069                        index.data( ExtensionListModel::SummaryRole ).toString() );
1070
1071     painter->restore();
1072 }
1073
1074 QSize ExtensionItemDelegate::sizeHint( const QStyleOptionViewItem &option,
1075                                        const QModelIndex &index ) const
1076 {
1077     if ( index.isValid() )
1078     {
1079         return QSize( 200, 2 * option.fontMetrics.height()
1080                       + margins.top() + margins.bottom() );
1081     }
1082     else
1083         return QSize();
1084 }
1085
1086 void ExtensionItemDelegate::initStyleOption( QStyleOptionViewItem *option,
1087                                              const QModelIndex &index ) const
1088 {
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() );
1093 }
1094
1095 AddonItemDelegate::AddonItemDelegate( QObject *parent )
1096     : ExtensionItemDelegate( parent )
1097 {
1098     animator = NULL;
1099     progressbar = NULL;
1100 }
1101
1102 AddonItemDelegate::~AddonItemDelegate()
1103 {
1104     delete progressbar;
1105 }
1106
1107 void AddonItemDelegate::paint( QPainter *painter,
1108                                const QStyleOptionViewItem &option,
1109                                const QModelIndex &index ) const
1110 {
1111     QStyleOptionViewItemV4 newopt = option;
1112     int i_state = index.data( AddonsListModel::StateRole ).toInt();
1113     int i_type = index.data( AddonsListModel::TypeRole ).toInt();
1114
1115     /* Draw Background gradient by addon type */
1116     QColor backgroundColor = AddonsListModel::getColorByAddonType( i_type );
1117
1118     if ( backgroundColor.isValid() )
1119     {
1120         painter->save();
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 );
1128         painter->restore();
1129     }
1130
1131     /* Draw base info from parent */
1132     ExtensionItemDelegate::paint( painter, newopt, index );
1133
1134     initStyleOption( &newopt, index );
1135
1136     painter->save();
1137     painter->setRenderHint( QPainter::TextAntialiasing );
1138
1139     /* Addon status */
1140     if ( i_state == ADDON_INSTALLED )
1141     {
1142         painter->save();
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(),
1153                            0, 0,
1154                            statusMargins.top() + statusMargins.bottom() );
1155         QPainterPath path;
1156         path.addRoundedRect( statusRect, 2.0, 2.0 );
1157         painter->fillPath( path, QColor( Qt::green ).darker( 125 ) );
1158         painter->setPen( Qt::white );
1159         painter->drawText(
1160             statusRect.adjusted( statusMargins.left(), statusMargins.top(),
1161                                  -statusMargins.right(), -statusMargins.bottom() ),
1162             qtr("Installed") );
1163         painter->restore();
1164     }
1165
1166     if ( newopt.state & QStyle::State_Selected )
1167         painter->setPen( newopt.palette.highlightedText().color() );
1168
1169     /* Start below text */
1170     QRect textrect( newopt.rect );
1171     textrect.adjust( 2 * margins.left() + margins.right() + newopt.decorationSize.width(),
1172                      margins.top(),
1173                      - margins.right(),
1174                      - margins.bottom() - newopt.fontMetrics.height() );
1175     textrect.translate( 0, newopt.fontMetrics.height() * 2 );
1176
1177     /* Version */
1178     QString version = index.data( AddonsListModel::VersionRole ).toString();
1179     if ( !version.isEmpty() )
1180         painter->drawText( textrect, Qt::AlignLeft, qtr("Version %1").arg( version ) );
1181
1182     textrect.translate( 0, newopt.fontMetrics.height() );
1183
1184     /* Score */
1185     int i_score = index.data( AddonsListModel::ScoreRole ).toInt();
1186     QPixmap scoreicon;
1187     if ( i_score )
1188     {
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 ) );
1198         erasepainter.end();
1199         painter->drawPixmap( textrect.topLeft(), scoreicon );
1200     }
1201
1202     /* Downloads # */
1203     int i_downloads = index.data( AddonsListModel::DownloadsCountRole ).toInt();
1204     if ( i_downloads )
1205         painter->drawText( textrect.translated( scoreicon.width() + margins.left(), 0 ),
1206                            Qt::AlignLeft, qtr("%1 downloads").arg( i_downloads ) );
1207
1208     painter->restore();
1209
1210     if ( animator )
1211     {
1212         if ( animator->isRunning() && animator->getIndex() == index )
1213         {
1214             if ( i_state != ADDON_INSTALLING && i_state != ADDON_UNINSTALLING )
1215                 animator->run( false );
1216         }
1217         /* Create our installation progress overlay */
1218
1219         if ( i_state == ADDON_INSTALLING || i_state == ADDON_UNINSTALLING )
1220         {
1221             painter->save();
1222             painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1223             painter->fillRect( newopt.rect, QColor( 255, 255, 255, 128 ) );
1224             if ( animator && index.isValid() )
1225             {
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 ) );
1235             }
1236             painter->restore();
1237         }
1238     }
1239 }
1240
1241 QSize AddonItemDelegate::sizeHint( const QStyleOptionViewItem &option,
1242                                    const QModelIndex &index ) const
1243 {
1244     if ( index.isValid() )
1245     {
1246         return QSize( 200, 4 * option.fontMetrics.height()
1247                       + margins.top() + margins.bottom() );
1248     }
1249     else
1250         return QSize();
1251 }
1252
1253 QWidget *AddonItemDelegate::createEditor( QWidget *parent,
1254                                           const QStyleOptionViewItem &option,
1255                                           const QModelIndex &index) const
1256 {
1257     Q_UNUSED( option );
1258     QWidget *editorWidget = new QWidget( parent );
1259     QPushButton *installButton;
1260     QPushButton *infoButton;
1261
1262     editorWidget->setLayout( new QHBoxLayout() );
1263     editorWidget->layout()->setMargin( 0 );
1264
1265     infoButton = new QPushButton( QIcon( ":/menu/info" ),
1266                                   qtr( "More information..." ) );
1267     connect( infoButton, SIGNAL(clicked()), this, SIGNAL(showInfo()) );
1268     editorWidget->layout()->addWidget( infoButton );
1269
1270     if ( ADDON_MANAGEABLE &
1271          index.data( AddonsListModel::FlagsRole ).toInt() )
1272     {
1273         if ( index.data( AddonsListModel::StateRole ).toInt() == ADDON_INSTALLED )
1274             installButton = new QPushButton( QIcon( ":/buttons/playlist/playlist_remove" ),
1275                                              qtr("&Uninstall"), parent );
1276         else
1277             installButton = new QPushButton( QIcon( ":/buttons/playlist/playlist_add" ),
1278                                              qtr("&Install"), parent );
1279         CONNECT( installButton, clicked(), this, editButtonClicked() );
1280         editorWidget->layout()->addWidget( installButton );
1281     }
1282
1283     editorWidget->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
1284
1285     return editorWidget;
1286 }
1287
1288 void AddonItemDelegate::updateEditorGeometry( QWidget *editor,
1289                                               const QStyleOptionViewItem &option,
1290                                               const QModelIndex &index) const
1291 {
1292     Q_UNUSED( index );
1293     QSize size = editor->sizeHint();
1294     editor->setGeometry( option.rect.right() - size.width(),
1295                          option.rect.top() + ( option.rect.height() - size.height()),
1296                          size.width(),
1297                          size.height() );
1298 }
1299
1300 void AddonItemDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
1301                                       const QModelIndex &index ) const
1302 {
1303     model->setData( index, editor->property("Addon::state"), AddonsListModel::StateRole );
1304 }
1305
1306 void AddonItemDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
1307 {
1308     editor->setProperty("Addon::state", index.data( AddonsListModel::StateRole ) );
1309 }
1310
1311 void AddonItemDelegate::setAnimator( DelegateAnimationHelper *animator_ )
1312 {
1313     if ( !progressbar )
1314     {
1315         QProgressBar *progress = new QProgressBar(  );
1316         progress->setMinimum( 0 );
1317         progress->setMaximum( 0 );
1318         progress->setTextVisible( false );
1319         progressbar = progress;
1320     }
1321     animator = animator_;
1322 }
1323
1324 void AddonItemDelegate::editButtonClicked()
1325 {
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 ) )
1330         /* uninstall */
1331         editor->setProperty("Addon::state", ADDON_UNINSTALLING );
1332     else
1333         /* install */
1334         editor->setProperty("Addon::state", ADDON_INSTALLING );
1335     emit commitData( editor );
1336     emit closeEditor( editor );
1337 }
1338
1339 /* "More information" dialog */
1340
1341 ExtensionInfoDialog::ExtensionInfoDialog( const QModelIndex &index,
1342                                           intf_thread_t *p_intf,
1343                                           QWidget *parent )
1344        : QVLCDialog( parent, p_intf )
1345 {
1346     // Let's be a modal dialog
1347     setWindowModality( Qt::WindowModal );
1348
1349     // Window title
1350     setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
1351
1352     // Layout
1353     QGridLayout *layout = new QGridLayout( this );
1354
1355     // Icon
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 );
1363
1364     // Title
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 );
1371
1372     // Version
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 );
1377
1378     // Author
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 );
1383
1384
1385     // Description
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 );
1391
1392     // URL
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() )
1398                         , this );
1399     label->setOpenExternalLinks( true );
1400     layout->addWidget( label, 5, 2, 1, -1 );
1401
1402     // Script file
1403     label = new QLabel( "<b>" + qtr( "File" ) + ":</b>", this );
1404     layout->addWidget( label, 6, 0, 1, 2 );
1405     QLineEdit *line =
1406             new QLineEdit( index.data(ExtensionListModel::FilenameRole).toString(), this );
1407     line->setReadOnly( true );
1408     layout->addWidget( line, 6, 2, 1, -1 );
1409
1410     // Close button
1411     QDialogButtonBox *group = new QDialogButtonBox( this );
1412     QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
1413     group->addButton( closeButton, QDialogButtonBox::RejectRole );
1414     BUTTONACT( closeButton, close() );
1415
1416     layout->addWidget( group, 7, 0, 1, -1 );
1417
1418     // Fix layout
1419     layout->setColumnStretch( 2, 1 );
1420     layout->setRowStretch( 4, 1 );
1421     setMinimumSize( 450, 350 );
1422 }
1423
1424
1425 AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
1426                                   intf_thread_t *p_intf, QWidget *parent )
1427        : QVLCDialog( parent, p_intf )
1428 {
1429     // Let's be a modal dialog
1430     setWindowModality( Qt::WindowModal );
1431
1432     // Window title
1433     setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
1434
1435     // Layout
1436     QGridLayout *layout = new QGridLayout( this );
1437     QLabel *label;
1438
1439     // Icon
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 );
1447
1448     // Title
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 );
1455
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 );
1465
1466     // Type
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 ) );
1470
1471     // Version
1472     QString version = index.data(ExtensionListModel::VersionRole).toString();
1473     if ( !version.isEmpty() )
1474     {
1475         textContent->append( QString("<b>%1:</b> %2<br/>")
1476                              .arg( qtr("Version") ).arg( version ) );
1477     }
1478
1479     // Author
1480     QString author = index.data(ExtensionListModel::AuthorRole).toString();
1481     if ( !author.isEmpty() )
1482     {
1483         textContent->append( QString("<b>%1:</b> %2<br/>")
1484                              .arg( qtr("Author") ).arg( author ) );
1485     }
1486
1487     // Summary
1488     textContent->append( QString("%1<br/>\n")
1489                 .arg( index.data(AddonsListModel::SummaryRole).toString() ) );
1490
1491     // Description
1492     QString description = index.data(AddonsListModel::DescriptionRole).toString();
1493     if ( !description.isEmpty() )
1494     {
1495         textContent->append( QString("<hr/>\n%1")
1496                              .arg( description.replace("\n", "<br/>") ) );
1497     }
1498
1499     // URL
1500     QString sourceUrl = index.data(ExtensionListModel::LinkRole).toString();
1501     if ( !sourceUrl.isEmpty() )
1502     {
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 );
1509     }
1510
1511     // Script files
1512     QList<QVariant> list = index.data(ExtensionListModel::FilenameRole).toList();
1513     if ( ! list.empty() )
1514     {
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 );
1521     }
1522
1523     // Close button
1524     QDialogButtonBox *group = new QDialogButtonBox( this );
1525     QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
1526     group->addButton( closeButton, QDialogButtonBox::RejectRole );
1527     BUTTONACT( closeButton, close() );
1528
1529     layout->addWidget( group, 7, 0, 1, -1 );
1530
1531     // Fix layout
1532     layout->setColumnStretch( 2, 1 );
1533     layout->setRowStretch( 4, 1 );
1534     setMinimumSize( 640, 480 );
1535 }
1536
1537 static QPixmap *loadPixmapFromData( char *data, int size )
1538 {
1539     if( !data || size <= 0 )
1540         return NULL;
1541     QPixmap *pixmap = new QPixmap();
1542     if( !pixmap->loadFromData( (const uchar*) data, (uint) size ) )
1543     {
1544         delete pixmap;
1545         return NULL;
1546     }
1547     return pixmap;
1548 }