]> git.sesse.net Git - vlc/blob - modules/gui/qt4/dialogs/plugins.cpp
Qt: addons: sort view
[vlc] / 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 <QPainter>
54 #include <QStyleOptionViewItem>
55 #include <QKeyEvent>
56 #include <QPushButton>
57 #include <QCheckBox>
58 #include <QPixmap>
59 #include <QStylePainter>
60 #include <QGraphicsColorizeEffect>
61 #include <QProgressBar>
62 #include <QTextEdit>
63 #include <QUrl>
64 #include <QMimeData>
65
66 static QPixmap *loadPixmapFromData( char *, int size );
67
68
69 PluginDialog::PluginDialog( intf_thread_t *_p_intf ) : QVLCFrame( _p_intf )
70 {
71     setWindowTitle( qtr( "Plugins and extensions" ) );
72     setWindowRole( "vlc-plugins" );
73
74     QVBoxLayout *layout = new QVBoxLayout( this );
75     tabs = new QTabWidget( this );
76     tabs->addTab( extensionTab = new ExtensionTab( p_intf ),
77                   qtr( "Active Extensions" ) );
78     tabs->addTab( pluginTab = new PluginTab( p_intf ),
79                   qtr( "Plugins" ) );
80     tabs->addTab( addonsTab = new AddonsTab( p_intf ),
81                   qtr( "Addons Manager" ) );
82     layout->addWidget( tabs );
83
84     QDialogButtonBox *box = new QDialogButtonBox;
85     QPushButton *okButton = new QPushButton( qtr( "&Close" ), this );
86     box->addButton( okButton, QDialogButtonBox::RejectRole );
87     layout->addWidget( box );
88     BUTTONACT( okButton, close() );
89     restoreWidgetPosition( "PluginsDialog", QSize( 435, 280 ) );
90 }
91
92 PluginDialog::~PluginDialog()
93 {
94     saveWidgetPosition( "PluginsDialog" );
95 }
96
97 /* Plugins tab */
98
99 PluginTab::PluginTab( intf_thread_t *p_intf_ )
100         : QVLCFrame( p_intf_ )
101 {
102     QGridLayout *layout = new QGridLayout( this );
103
104     /* Main Tree for modules */
105     treePlugins = new QTreeWidget;
106     layout->addWidget( treePlugins, 0, 0, 1, -1 );
107
108     /* Users cannot move the columns around but we need to sort */
109 #if QT_VERSION >= 0x050000
110     treePlugins->header()->setSectionsMovable( false );
111 #else
112     treePlugins->header()->setMovable( false );
113 #endif
114     treePlugins->header()->setSortIndicatorShown( true );
115     //    treePlugins->header()->setResizeMode( QHeaderView::ResizeToContents );
116     treePlugins->setAlternatingRowColors( true );
117     treePlugins->setColumnWidth( 0, 200 );
118
119     QStringList headerNames;
120     headerNames << qtr("Name") << qtr("Capability" ) << qtr( "Score" );
121     treePlugins->setHeaderLabels( headerNames );
122
123     FillTree();
124
125     /* Set capability column to the correct Size*/
126     treePlugins->resizeColumnToContents( 1 );
127     treePlugins->header()->restoreState(
128             getSettings()->value( "Plugins/Header-State" ).toByteArray() );
129
130     treePlugins->setSortingEnabled( true );
131     treePlugins->sortByColumn( 1, Qt::AscendingOrder );
132
133     QLabel *label = new QLabel( qtr("&Search:"), this );
134     edit = new SearchLineEdit( this );
135     label->setBuddy( edit );
136
137     layout->addWidget( label, 1, 0 );
138     layout->addWidget( edit, 1, 1, 1, 1 );
139     CONNECT( edit, textChanged( const QString& ),
140             this, search( const QString& ) );
141
142     setMinimumSize( 500, 300 );
143     restoreWidgetPosition( "Plugins", QSize( 540, 400 ) );
144 }
145
146 inline void PluginTab::FillTree()
147 {
148     size_t count;
149     module_t **p_list = module_list_get( &count );
150
151     for( unsigned int i = 0; i < count; i++ )
152     {
153         module_t *p_module = p_list[i];
154
155         QStringList qs_item;
156         qs_item << qfu( module_get_name( p_module, true ) )
157                 << qfu( module_get_capability( p_module ) )
158                 << QString::number( module_get_score( p_module ) );
159 #ifndef DEBUG
160         if( qs_item.at(1).isEmpty() ) continue;
161 #endif
162
163         QTreeWidgetItem *item = new PluginTreeItem( qs_item );
164         treePlugins->addTopLevelItem( item );
165     }
166     module_list_free( p_list );
167 }
168
169 void PluginTab::search( const QString& qs )
170 {
171     QList<QTreeWidgetItem *> items = treePlugins->findItems( qs, Qt::MatchContains );
172     items += treePlugins->findItems( qs, Qt::MatchContains, 1 );
173
174     QTreeWidgetItem *item = NULL;
175     for( int i = 0; i < treePlugins->topLevelItemCount(); i++ )
176     {
177         item = treePlugins->topLevelItem( i );
178         item->setHidden( !items.contains( item ) );
179     }
180 }
181
182 PluginTab::~PluginTab()
183 {
184     saveWidgetPosition( "Plugins" );
185     getSettings()->setValue( "Plugins/Header-State",
186                              treePlugins->header()->saveState() );
187 }
188
189 void PluginTab::keyPressEvent( QKeyEvent *keyEvent )
190 {
191     if( keyEvent->key() == Qt::Key_Return ||
192         keyEvent->key() == Qt::Key_Enter )
193         keyEvent->accept();
194     else
195         keyEvent->ignore();
196 }
197
198 bool PluginTreeItem::operator< ( const QTreeWidgetItem & other ) const
199 {
200     int col = treeWidget()->sortColumn();
201     if( col == PluginTab::SCORE )
202         return text( col ).toInt() < other.text( col ).toInt();
203     else if ( col == PluginTab::CAPABILITY )
204     {
205         if ( text( PluginTab::CAPABILITY ) == other.text( PluginTab::CAPABILITY ) )
206             return text( PluginTab::NAME ) < other.text( PluginTab::NAME );
207         else
208             return text( PluginTab::CAPABILITY ) < other.text( PluginTab::CAPABILITY );
209     }
210     return text( col ) < other.text( col );
211 }
212
213 /* Extensions tab */
214 ExtensionTab::ExtensionTab( intf_thread_t *p_intf_ )
215         : QVLCFrame( p_intf_ )
216 {
217     // Layout
218     QVBoxLayout *layout = new QVBoxLayout( this );
219
220     QLabel *notice = new QLabel( qtr("Get more extensions from")
221             + QString( " <a href=\"http://addons.videolan.org/\">"
222                        "addons.videolan.org</a>." ) );
223     notice->setOpenExternalLinks( true );
224     layout->addWidget( notice );
225
226     // ListView
227     extList = new QListView( this );
228     CONNECT( extList, activated( const QModelIndex& ),
229              this, moreInformation() );
230     layout->addWidget( extList );
231
232     // List item delegate
233     ExtensionItemDelegate *itemDelegate = new ExtensionItemDelegate( extList );
234     extList->setItemDelegate( itemDelegate );
235
236     // Extension list look & feeling
237     extList->setAlternatingRowColors( true );
238     extList->setSelectionMode( QAbstractItemView::SingleSelection );
239
240     // Model
241     ExtensionListModel *model =
242       new ExtensionListModel( extList, ExtensionsManager::getInstance( p_intf ) );
243     extList->setModel( model );
244
245     // Buttons' layout
246     QDialogButtonBox *buttonsBox = new QDialogButtonBox;
247
248     // More information button
249     butMoreInfo = new QPushButton( QIcon( ":/menu/info" ),
250                                    qtr( "More information..." ),
251                                    this );
252     CONNECT( butMoreInfo, clicked(), this, moreInformation() );
253     buttonsBox->addButton( butMoreInfo, QDialogButtonBox::ActionRole );
254
255     // Reload button
256     ExtensionsManager *EM = ExtensionsManager::getInstance( p_intf );
257     QPushButton *reload = new QPushButton( QIcon( ":/update" ),
258                                            qtr( "Reload extensions" ),
259                                            this );
260     CONNECT( reload, clicked(), EM, reloadExtensions() );
261     CONNECT( reload, clicked(), this, updateButtons() );
262     CONNECT( extList->selectionModel(),
263              selectionChanged( const QItemSelection &, const QItemSelection & ),
264              this,
265              updateButtons() );
266     buttonsBox->addButton( reload, QDialogButtonBox::ResetRole );
267
268     layout->addWidget( buttonsBox );
269     updateButtons();
270 }
271
272 ExtensionTab::~ExtensionTab()
273 {
274 }
275
276 void ExtensionTab::updateButtons()
277 {
278     butMoreInfo->setEnabled( extList->selectionModel()->hasSelection() );
279 }
280
281 // Do not close on ESC or ENTER
282 void ExtensionTab::keyPressEvent( QKeyEvent *keyEvent )
283 {
284     if( keyEvent->key() == Qt::Key_Return ||
285         keyEvent->key() == Qt::Key_Enter )
286         keyEvent->accept();
287     else
288         keyEvent->ignore();
289 }
290
291 // Show more information
292 void ExtensionTab::moreInformation()
293 {
294     QModelIndex index = extList->selectionModel()->selectedIndexes().first();
295
296     if( !index.isValid() )
297         return;
298
299     ExtensionInfoDialog dlg( index, p_intf, this );
300     dlg.exec();
301 }
302
303 /* Add-ons tab */
304 AddonsTab::AddonsTab( intf_thread_t *p_intf_ ) : QVLCFrame( p_intf_ )
305 {
306     // Layout
307     QVBoxLayout *layout = new QVBoxLayout( this );
308
309     // Filters
310     QHBoxLayout *filtersLayout = new QHBoxLayout();
311
312     QLabel *addonsLabel = new QLabel( qtr("Addon type:") );
313     addonsLabel->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
314     filtersLayout->addWidget( addonsLabel );
315     QComboBox *typeCombo = new QComboBox();
316     typeCombo->addItem( qtr("All"), -1 );
317     typeCombo->addItem( qtr("Skins"), ADDON_SKIN2 );
318     typeCombo->addItem( qtr("Playlist parsers"), ADDON_PLAYLIST_PARSER );
319     typeCombo->addItem( qtr("Service Discovery"), ADDON_SERVICE_DISCOVERY );
320     typeCombo->addItem( qtr("Extensions"), ADDON_EXTENSION );
321     CONNECT( typeCombo, currentIndexChanged(int), this, typeChanged( int ) );
322     filtersLayout->addWidget( typeCombo );
323
324     QCheckBox *installedOnlyBox = new QCheckBox( qtr("Show Installed Only") );
325     filtersLayout->addWidget( installedOnlyBox );
326     CONNECT( installedOnlyBox, stateChanged(int), this, installChecked(int) );
327
328     layout->addLayout( filtersLayout );
329
330     // Help Tab
331     helpLabel = new QLabel();
332     layout->addWidget( helpLabel );
333
334     // Main View
335     AddonsManager *AM = AddonsManager::getInstance( p_intf );
336
337     // ListView
338     addonsView = new QListView( this );
339     CONNECT( addonsView, activated( const QModelIndex& ), this, moreInformation() );
340     layout->addWidget( addonsView );
341
342     // List item delegate
343     AddonItemDelegate *addonsDelegate = new AddonItemDelegate( addonsView );
344     addonsView->setItemDelegate( addonsDelegate );
345     addonsDelegate->setAnimator( new DelegateAnimationHelper( addonsView ) );
346     CONNECT( addonsDelegate, showInfo(), this, moreInformation() );
347
348     // Extension list look & feeling
349     addonsView->setAlternatingRowColors( true );
350     addonsView->setSelectionMode( QAbstractItemView::SingleSelection );
351
352     // Drop packages
353     addonsView->setAcceptDrops( true );
354     addonsView->setDefaultDropAction( Qt::CopyAction );
355     addonsView->setDropIndicatorShown( true );
356     addonsView->setDragDropMode( QAbstractItemView::DropOnly );
357
358     // Model
359     AddonsListModel *model = new AddonsListModel( AM, addonsView );
360     addonsModel = new AddonsSortFilterProxyModel();
361     addonsModel->setDynamicSortFilter( true );
362     addonsModel->setSortRole( Qt::DisplayRole );
363     addonsModel->sort( 0, Qt::AscendingOrder );
364     addonsModel->setSourceModel( model );
365     addonsModel->setFilterRole( Qt::DisplayRole );
366     addonsView->setModel( addonsModel );
367
368     CONNECT( addonsView->selectionModel(), currentChanged(QModelIndex,QModelIndex),
369              addonsView, edit(QModelIndex) );
370
371     CONNECT( AM, addonAdded( addon_entry_t * ),
372              model, addonAdded( addon_entry_t * ) );
373     CONNECT( AM, addonChanged( const addon_entry_t * ),
374              model, addonChanged( const addon_entry_t * ) );
375
376     QList<QString> frames;
377     frames << ":/util/wait1";
378     frames << ":/util/wait2";
379     frames << ":/util/wait3";
380     frames << ":/util/wait4";
381     spinnerAnimation = new PixmapAnimator( this, frames );
382     CONNECT( spinnerAnimation, pixmapReady( const QPixmap & ),
383              addonsView->viewport(), update() );
384     addonsView->viewport()->installEventFilter( this );
385 }
386
387 AddonsTab::~AddonsTab()
388 {
389     delete spinnerAnimation;
390 }
391
392 bool AddonsTab::eventFilter( QObject *obj, QEvent *event )
393 {
394     if ( obj != addonsView->viewport() )
395         return false;
396
397     switch( event->type() )
398     {
399     case QEvent::Paint:
400         if ( spinnerAnimation->state() == PixmapAnimator::Running )
401         {
402             QWidget *viewport = qobject_cast<QWidget *>( obj );
403             if ( !viewport ) break;
404             QStylePainter painter( viewport );
405             QPixmap *spinner = spinnerAnimation->getPixmap();
406             QPoint point = viewport->geometry().center();
407             point -= QPoint( spinner->size().width() / 2, spinner->size().height() / 2 );
408             painter.drawPixmap( point, *spinner );
409             QString text = qtr("Retrieving addons...");
410             QSize textsize = fontMetrics().size( 0, text );
411             point = viewport->geometry().center();
412             point -= QPoint( textsize.width() / 2, -spinner->size().height() );
413             painter.drawText( point, text );
414         }
415         else if ( addonsModel->rowCount() == 0 )
416         {
417             QWidget *viewport = qobject_cast<QWidget *>( obj );
418             if ( !viewport ) break;
419             QStylePainter painter( viewport );
420             QString text = qtr("No addons found");
421             QSize size = fontMetrics().size( 0, text );
422             QPoint point = viewport->geometry().center();
423             point -= QPoint( size.width() / 2, size.height() / 2 );
424             painter.drawText( point, text );
425         }
426         break;
427     case QEvent::Show:
428         if ( addonsView->model()->rowCount() < 1 )
429         {
430             AddonsManager *AM = AddonsManager::getInstance( p_intf );
431             CONNECT( AM, discoveryEnded(), spinnerAnimation, stop() );
432             CONNECT( AM, discoveryEnded(), addonsView->viewport(), update() );
433             spinnerAnimation->start();
434             AM->findInstalled();
435             AM->findNewAddons();
436         }
437         break;
438     case QEvent::DragEnter:
439     {
440         QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent *>(event);
441         if ( !dragEvent ) break;
442         QList<QUrl> urls = dragEvent->mimeData()->urls();
443         if ( dragEvent->proposedAction() != Qt::CopyAction
444           || urls.count() != 1
445           || urls.first().scheme() != "file"
446           || ! urls.first().path().endsWith(".vlp") )
447             return false;
448         dragEvent->acceptProposedAction();
449         return true;
450     }
451     case QEvent::DragMove:
452     {
453         QDragMoveEvent *moveEvent = static_cast<QDragMoveEvent *>(event);
454         if ( !moveEvent ) break;
455         if ( moveEvent->proposedAction() != Qt::CopyAction )
456             return false;
457         moveEvent->acceptProposedAction();
458         return true;
459     }
460     case QEvent::Drop:
461     {
462         QDropEvent *dropEvent = static_cast<QDropEvent *>(event);
463         if ( !dropEvent ) break;
464         if ( dropEvent->proposedAction() != Qt::CopyAction )
465             return false;
466         if ( dropEvent->mimeData()->urls().count() )
467         {
468             AddonsManager *AM = AddonsManager::getInstance( p_intf );
469             AM->findDesignatedAddon( dropEvent->mimeData()->urls().first().toString() );
470             dropEvent->acceptProposedAction();
471         }
472         return true;
473     }
474     default:
475         break;
476     }
477     return false;
478 }
479
480 void AddonsTab::moreInformation()
481 {
482     QModelIndex index = addonsView->selectionModel()->selectedIndexes().first();
483     if( !index.isValid() ) return;
484     AddonInfoDialog dlg( index, p_intf, this );
485     dlg.exec();
486 }
487
488 void AddonsTab::typeChanged( int i )
489 {
490     QComboBox *combo = qobject_cast<QComboBox *>( sender() );
491     if ( !combo ) return;
492     int i_type = combo->itemData( i, Qt::UserRole ).toInt();
493     addonsModel->setTypeFilter( i_type );
494     QString help;
495     switch( i_type )
496     {
497     case ADDON_SKIN2:
498         help = qtr( "Skins customize player's appearance."
499                     " You can activate them through preferences." );
500         break;
501     case ADDON_PLAYLIST_PARSER:
502         help = qtr( "Playlist parsers add new capabilities to read"
503                     " internet streams or extract meta data." );
504         break;
505     case ADDON_SERVICE_DISCOVERY:
506         help = qtr( "Service discoveries adds new sources to your playlist"
507                     " such as web radios, video websites, ..." );
508         break;
509     case ADDON_EXTENSION:
510         help = qtr( "Extensions brings various enhancements."
511                     " Check descriptions for more details" );
512         break;
513     default:
514         helpLabel->setText("");
515         return;
516     }
517     helpLabel->setTextFormat( Qt::RichText );
518     helpLabel->setText( QString( "<img src=\":/menu/info\"/> %1" ).arg( help ) );
519 }
520
521 void AddonsTab::installChecked( int i )
522 {
523     if ( i == Qt::Checked )
524         addonsModel->setStatusFilter( ADDON_INSTALLED );
525     else
526         addonsModel->setStatusFilter( 0 );
527 }
528
529 /* Safe copy of the extension_t struct */
530 ExtensionListModel::ExtensionCopy::ExtensionCopy( extension_t *p_ext )
531 {
532     name = qfu( p_ext->psz_name );
533     description = qfu( p_ext->psz_description );
534     shortdesc = qfu( p_ext->psz_shortdescription );
535     if( description.isEmpty() )
536         description = shortdesc;
537     if( shortdesc.isEmpty() && !description.isEmpty() )
538         shortdesc = description;
539     title = qfu( p_ext->psz_title );
540     author = qfu( p_ext->psz_author );
541     version = qfu( p_ext->psz_version );
542     url = qfu( p_ext->psz_url );
543     icon = loadPixmapFromData( p_ext->p_icondata, p_ext->i_icondata_size );
544 }
545
546 ExtensionListModel::ExtensionCopy::~ExtensionCopy()
547 {
548     delete icon;
549 }
550
551 QVariant ExtensionListModel::ExtensionCopy::data( int role ) const
552 {
553     switch( role )
554     {
555     case Qt::DisplayRole:
556         return title;
557     case Qt::DecorationRole:
558         if ( !icon ) return QPixmap( ":/logo/vlc48.png" );
559         return *icon;
560     case SummaryRole:
561         return shortdesc;
562     case VersionRole:
563         return version;
564     case AuthorRole:
565         return author;
566     case LinkRole:
567         return url;
568     case FilenameRole:
569         return name;
570     default:
571         return QVariant();
572     }
573 }
574
575 /* Extensions list model for the QListView */
576 ExtensionListModel::ExtensionListModel( QObject *parent )
577     : QAbstractListModel( parent ), EM( NULL )
578 {
579
580 }
581
582 ExtensionListModel::ExtensionListModel( QObject *parent, ExtensionsManager* EM_ )
583         : QAbstractListModel( parent ), EM( EM_ )
584 {
585     // Connect to ExtensionsManager::extensionsUpdated()
586     CONNECT( EM, extensionsUpdated(), this, updateList() );
587
588     // Load extensions now if not already loaded
589     EM->loadExtensions();
590 }
591
592 ExtensionListModel::~ExtensionListModel()
593 {
594     // Clear extensions list
595     while( !extensions.isEmpty() )
596         delete extensions.takeLast();
597 }
598
599 void ExtensionListModel::updateList()
600 {
601     ExtensionCopy *ext;
602
603     // Clear extensions list
604     while( !extensions.isEmpty() )
605     {
606         ext = extensions.takeLast();
607         delete ext;
608     }
609
610     // Find new extensions
611     extensions_manager_t *p_mgr = EM->getManager();
612     if( !p_mgr )
613         return;
614
615     vlc_mutex_lock( &p_mgr->lock );
616     extension_t *p_ext;
617     FOREACH_ARRAY( p_ext, p_mgr->extensions )
618     {
619         ext = new ExtensionCopy( p_ext );
620         extensions.append( ext );
621     }
622     FOREACH_END()
623     vlc_mutex_unlock( &p_mgr->lock );
624     vlc_object_release( p_mgr );
625
626     emit dataChanged( index( 0 ), index( rowCount() - 1 ) );
627 }
628
629 int ExtensionListModel::rowCount( const QModelIndex& ) const
630 {
631     int count = 0;
632     extensions_manager_t *p_mgr = EM->getManager();
633     if( !p_mgr )
634         return 0;
635
636     vlc_mutex_lock( &p_mgr->lock );
637     count = p_mgr->extensions.i_size;
638     vlc_mutex_unlock( &p_mgr->lock );
639     vlc_object_release( p_mgr );
640
641     return count;
642 }
643
644 QVariant ExtensionListModel::data( const QModelIndex& index, int role ) const
645 {
646     if( !index.isValid() )
647         return QVariant();
648
649     ExtensionCopy * extension =
650             static_cast<ExtensionCopy *>(index.internalPointer());
651     if ( !extension )
652         return QVariant();
653     else
654         return extension->data( role );
655 }
656
657 QModelIndex ExtensionListModel::index( int row, int column,
658                                        const QModelIndex& ) const
659 {
660     if( column != 0 )
661         return QModelIndex();
662     if( row < 0 || row >= extensions.count() )
663         return QModelIndex();
664
665     return createIndex( row, 0, extensions.at( row ) );
666 }
667
668 AddonsListModel::Addon::Addon( addon_entry_t *p_entry_ )
669 {
670     p_entry = p_entry_;
671     addon_entry_Hold( p_entry );
672 }
673
674 AddonsListModel::Addon::~Addon()
675 {
676     addon_entry_Release( p_entry );
677 }
678
679 bool AddonsListModel::Addon::operator==( const Addon & other ) const
680 {
681     //return data( IDRole ) == other.data( IDRole );
682     return p_entry == other.p_entry;
683 }
684
685 bool AddonsListModel::Addon::operator==( const addon_entry_t * p_other ) const
686 {
687     return p_entry == p_other;
688 }
689
690 QVariant AddonsListModel::Addon::data( int role ) const
691 {
692     QVariant returnval;
693
694     vlc_mutex_lock( &p_entry->lock );
695     switch( role )
696     {
697     case Qt::DisplayRole:
698     {
699         QString name = qfu( p_entry->psz_name );
700         if ( p_entry->e_state == ADDON_INSTALLED )
701             name.append( QString(" (%1)").arg( qtr("installed") ) );
702
703         returnval = name;
704         break;
705     }
706     case Qt::DecorationRole:
707         if ( p_entry->psz_image_data )
708         {
709             QPixmap pixmap;
710             pixmap.loadFromData( QByteArray::fromBase64( QByteArray( p_entry->psz_image_data ) ),
711                 0,
712                 Qt::AutoColor
713             );
714             returnval = pixmap;
715         }
716         else if ( p_entry->e_flags & ADDON_BROKEN )
717             returnval = QPixmap( ":/addons/broken" );
718         else
719             returnval = QPixmap( ":/addons/default" );
720         break;
721     case Qt::ToolTipRole:
722     {
723         if ( !( p_entry->e_flags & ADDON_MANAGEABLE ) )
724         {
725             returnval = qtr("This addon has been installed manually. VLC can't manage it by itself.");
726         }
727         break;
728     }
729     case SummaryRole:
730         returnval = qfu( p_entry->psz_summary );
731         break;
732     case DescriptionRole:
733         returnval = qfu( p_entry->psz_description );
734         break;
735     case TypeRole:
736         returnval = QVariant( (int) p_entry->e_type );
737         break;
738     case UUIDRole:
739         returnval = QByteArray( (const char *) p_entry->uuid, (int) sizeof( addon_uuid_t ) );
740         break;
741     case FlagsRole:
742         returnval = QVariant( (int) p_entry->e_flags );
743         break;
744     case StateRole:
745         returnval = QVariant( (int) p_entry->e_state );
746         break;
747     case DownloadsCountRole:
748         returnval = QVariant( (double) p_entry->i_downloads );
749         break;
750     case ScoreRole:
751         returnval = QVariant( (double) p_entry->i_score );
752         break;
753     case VersionRole:
754         returnval = QVariant( p_entry->psz_version );
755         break;
756     case AuthorRole:
757         returnval = qfu( p_entry->psz_author );
758         break;
759     case LinkRole:
760         returnval = qfu( p_entry->psz_source_uri );
761         break;
762     case FilenameRole:
763     {
764         QList<QString> list;
765         FOREACH_ARRAY( addon_file_t *p_file, p_entry->files )
766         list << qfu( p_file->psz_filename );
767         FOREACH_END();
768         returnval = QVariant( list );
769         break;
770     }
771     default:
772         break;
773     }
774     vlc_mutex_unlock( &p_entry->lock );
775
776     return returnval;
777 }
778
779 AddonsListModel::AddonsListModel( AddonsManager *AM_, QObject *parent )
780     :ExtensionListModel( parent ), AM( AM_ )
781 {
782
783 }
784
785 void AddonsListModel::addonAdded(  addon_entry_t *p_entry )
786 {
787     beginInsertRows( QModelIndex(), addons.count(), addons.count() );
788     addons << new Addon( p_entry );
789     insertRow( addons.count() - 1 );
790     endInsertRows();
791 }
792
793 void AddonsListModel::addonChanged( const addon_entry_t *p_entry )
794 {
795     int row = 0;
796     foreach ( const Addon *addon, addons )
797     {
798         if ( *addon == p_entry )
799         {
800             emit dataChanged( index( row, 0 ), index( row, 0 ) );
801             break;
802         }
803         row++;
804     }
805 }
806
807 int AddonsListModel::rowCount( const QModelIndex & ) const
808 {
809     return addons.count();
810 }
811
812 Qt::ItemFlags AddonsListModel::flags( const QModelIndex &index ) const
813 {
814     Qt::ItemFlags i_flags = ExtensionListModel::flags( index );
815     int i_state = data( index, StateRole ).toInt();
816
817     if ( i_state == ADDON_UNINSTALLING || i_state == ADDON_INSTALLING )
818     {
819         i_flags &= !Qt::ItemIsEnabled;
820     }
821
822     i_flags |= Qt::ItemIsEditable;
823
824     return i_flags;
825 }
826
827 bool AddonsListModel::setData( const QModelIndex &index, const QVariant &value, int role )
828 {
829     /* We NEVER set values directly */
830     if ( role == StateRole )
831     {
832         int i_value = value.toInt();
833         if ( i_value == ADDON_INSTALLING )
834         {
835             AM->install( data( index, UUIDRole ).toByteArray() );
836         }
837         else if ( i_value == ADDON_UNINSTALLING )
838         {
839             AM->remove( data( index, UUIDRole ).toByteArray() );
840         }
841     }
842     else if ( role == StateRole + 1 )
843     {
844         emit dataChanged( index, index );
845     }
846     return true;
847 }
848
849 QVariant AddonsListModel::data( const QModelIndex& index, int role ) const
850 {
851     if( !index.isValid() )
852         return QVariant();
853
854     return ((Addon *)index.internalPointer())->data( role );
855 }
856
857 QModelIndex AddonsListModel::index( int row, int column,
858                                        const QModelIndex& ) const
859 {
860     if( column != 0 )
861         return QModelIndex();
862     if( row < 0 || row >= addons.count() )
863         return QModelIndex();
864
865     return createIndex( row, 0, addons.at( row ) );
866 }
867
868 /* Sort Filter */
869 AddonsSortFilterProxyModel::AddonsSortFilterProxyModel( QObject *parent )
870     : QSortFilterProxyModel( parent )
871 {
872     i_type_filter = -1;
873     i_status_filter = 0;
874 }
875
876 void AddonsSortFilterProxyModel::setTypeFilter( int type )
877 {
878     i_type_filter = type;
879     invalidateFilter();
880 }
881
882 void AddonsSortFilterProxyModel::setStatusFilter( int flags )
883 {
884     i_status_filter = flags;
885     invalidateFilter();
886 }
887
888 bool AddonsSortFilterProxyModel::filterAcceptsRow( int source_row,
889                                        const QModelIndex &source_parent ) const
890 {
891     if ( !QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent ) )
892         return false;
893
894     QModelIndex item = sourceModel()->index( source_row, 0, source_parent );
895
896     if ( i_type_filter > -1 &&
897          item.data( AddonsListModel::TypeRole ).toInt() != i_type_filter )
898         return false;
899
900     if ( i_status_filter > 0 &&
901         ( item.data( AddonsListModel::StateRole ).toInt() & i_status_filter ) != i_status_filter )
902         return false;
903
904     return true;
905 }
906
907 /* Extension List Widget Item */
908 ExtensionItemDelegate::ExtensionItemDelegate( QObject *parent )
909         : QStyledItemDelegate( parent )
910 {
911     margins = QMargins( 4, 4, 4, 4 );
912 }
913
914 ExtensionItemDelegate::~ExtensionItemDelegate()
915 {
916 }
917
918 void ExtensionItemDelegate::paint( QPainter *painter,
919                                    const QStyleOptionViewItem &option,
920                                    const QModelIndex &index ) const
921 {
922     QStyleOptionViewItemV4 opt = option;
923     initStyleOption( &opt, index );
924
925     // Draw background
926     if ( opt.state & QStyle::State_Selected )
927         painter->fillRect( opt.rect, opt.palette.highlight() );
928
929     // Icon
930     QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
931     if( !icon.isNull() )
932     {
933         painter->drawPixmap( opt.rect.left() + margins.left(),
934                              opt.rect.top() + margins.top(),
935                              icon.scaled( opt.decorationSize,
936                                           Qt::KeepAspectRatio,
937                                           Qt::SmoothTransformation )
938         );
939     }
940
941     painter->save();
942     painter->setRenderHint( QPainter::TextAntialiasing );
943
944     if ( opt.state & QStyle::State_Selected )
945         painter->setPen( opt.palette.highlightedText().color() );
946
947     QFont font( option.font );
948     font.setBold( true );
949     painter->setFont( font );
950     QRect textrect( opt.rect );
951     textrect.adjust( 2 * margins.left() + margins.right() + opt.decorationSize.width(),
952                      margins.top(),
953                      - margins.right(),
954                      - margins.bottom() - opt.fontMetrics.height() );
955
956     painter->drawText( textrect, Qt::AlignLeft,
957                        index.data( Qt::DisplayRole ).toString() );
958
959     font.setBold( false );
960     painter->setFont( font );
961     painter->drawText( textrect.translated( 0, option.fontMetrics.height() ),
962                        Qt::AlignLeft,
963                        index.data( ExtensionListModel::SummaryRole ).toString() );
964
965     painter->restore();
966 }
967
968 QSize ExtensionItemDelegate::sizeHint( const QStyleOptionViewItem &option,
969                                        const QModelIndex &index ) const
970 {
971     if ( index.isValid() )
972     {
973         return QSize( 200, 2 * option.fontMetrics.height()
974                       + margins.top() + margins.bottom() );
975     }
976     else
977         return QSize();
978 }
979
980 void ExtensionItemDelegate::initStyleOption( QStyleOptionViewItem *option,
981                                              const QModelIndex &index ) const
982 {
983     QStyledItemDelegate::initStyleOption( option, index );
984     option->decorationSize = QSize( option->rect.height(), option->rect.height() );
985     option->decorationSize -= QSize( margins.left() + margins.right(),
986                                      margins.top() + margins.bottom() );
987 }
988
989 AddonItemDelegate::AddonItemDelegate( QObject *parent )
990     : ExtensionItemDelegate( parent )
991 {
992     animator = NULL;
993     progressbar = NULL;
994 }
995
996 AddonItemDelegate::~AddonItemDelegate()
997 {
998     delete progressbar;
999 }
1000
1001 void AddonItemDelegate::paint( QPainter *painter,
1002                                const QStyleOptionViewItem &option,
1003                                const QModelIndex &index ) const
1004 {
1005     QStyleOptionViewItemV4 newopt = option;
1006     int i_state = index.data( AddonsListModel::StateRole ).toInt();
1007
1008     ExtensionItemDelegate::paint( painter, newopt, index );
1009
1010     initStyleOption( &newopt, index );
1011
1012     painter->save();
1013     painter->setRenderHint( QPainter::TextAntialiasing );
1014
1015     if ( newopt.state & QStyle::State_Selected )
1016         painter->setPen( newopt.palette.highlightedText().color() );
1017
1018     /* Start below text */
1019     QRect textrect( newopt.rect );
1020     textrect.adjust( 2 * margins.left() + margins.right() + newopt.decorationSize.width(),
1021                      margins.top(),
1022                      - margins.right(),
1023                      - margins.bottom() - newopt.fontMetrics.height() );
1024     textrect.translate( 0, newopt.fontMetrics.height() * 2 );
1025
1026     /* Version */
1027     QString version = index.data( AddonsListModel::VersionRole ).toString();
1028     if ( !version.isEmpty() )
1029         painter->drawText( textrect, Qt::AlignLeft, qtr("Version %1").arg( version ) );
1030
1031     textrect.translate( 0, newopt.fontMetrics.height() );
1032
1033     /* Score */
1034     double i_score = index.data( AddonsListModel::ScoreRole ).toDouble();
1035     QPixmap scoreicon;
1036     if ( i_score )
1037     {
1038         scoreicon = QPixmap( ":/addons/score" ).scaledToHeight(
1039                     newopt.fontMetrics.height(), Qt::SmoothTransformation );
1040         int i_width = ( i_score / 5.0 ) * scoreicon.width();
1041         /* Erase the end (value) of our pixmap with a shadow */
1042         QPainter erasepainter( &scoreicon );
1043         erasepainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
1044         erasepainter.fillRect( QRect( i_width, 0,
1045                                       scoreicon.width() - i_width, scoreicon.height() ),
1046                                newopt.palette.color( QPalette::Dark ) );
1047         erasepainter.end();
1048         painter->drawPixmap( textrect.topLeft(), scoreicon );
1049     }
1050
1051     /* Downloads # */
1052     int i_downloads = index.data( AddonsListModel::DownloadsCountRole ).toInt();
1053     if ( i_downloads )
1054         painter->drawText( textrect.translated( scoreicon.width() + margins.left(), 0 ),
1055                            Qt::AlignLeft, qtr("%1 downloads").arg( i_downloads ) );
1056
1057     painter->restore();
1058
1059     if ( animator )
1060     {
1061         if ( animator->isRunning() && animator->getIndex() == index )
1062         {
1063             if ( i_state != ADDON_INSTALLING && i_state != ADDON_UNINSTALLING )
1064                 animator->run( false );
1065         }
1066         /* Create our installation progress overlay */
1067
1068         if ( i_state == ADDON_INSTALLING || i_state == ADDON_UNINSTALLING )
1069         {
1070             painter->save();
1071             painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1072             painter->fillRect( newopt.rect, QColor( 255, 255, 255, 128 ) );
1073             if ( animator && index.isValid() )
1074             {
1075                 animator->setIndex( index );
1076                 animator->run( true );
1077                 QSize adjustment = newopt.rect.size() / 4;
1078                 progressbar->setGeometry(
1079                     newopt.rect.adjusted( adjustment.width(), adjustment.height(),
1080                                           -adjustment.width(), -adjustment.height() ) );
1081                 painter->drawPixmap( newopt.rect.left() + adjustment.width(),
1082                                      newopt.rect.top() + adjustment.height(),
1083                                      QPixmap::grabWidget( progressbar ) );
1084             }
1085             painter->restore();
1086         }
1087     }
1088 }
1089
1090 QSize AddonItemDelegate::sizeHint( const QStyleOptionViewItem &option,
1091                                    const QModelIndex &index ) const
1092 {
1093     if ( index.isValid() )
1094     {
1095         return QSize( 200, 4 * option.fontMetrics.height()
1096                       + margins.top() + margins.bottom() );
1097     }
1098     else
1099         return QSize();
1100 }
1101
1102 QWidget *AddonItemDelegate::createEditor( QWidget *parent,
1103                                           const QStyleOptionViewItem &option,
1104                                           const QModelIndex &index) const
1105 {
1106     Q_UNUSED( option );
1107     QWidget *editorWidget = new QWidget( parent );
1108     QPushButton *installButton;
1109     QPushButton *infoButton;
1110
1111     editorWidget->setLayout( new QHBoxLayout() );
1112     editorWidget->layout()->setMargin( 0 );
1113
1114     infoButton = new QPushButton( QIcon( ":/menu/info" ),
1115                                   qtr( "More information..." ) );
1116     connect( infoButton, SIGNAL(clicked()), this, SIGNAL(showInfo()) );
1117     editorWidget->layout()->addWidget( infoButton );
1118
1119     if ( ADDON_MANAGEABLE &
1120          index.data( AddonsListModel::FlagsRole ).toInt() )
1121     {
1122         if ( index.data( AddonsListModel::StateRole ).toInt() == ADDON_INSTALLED )
1123             installButton = new QPushButton( QIcon( ":/buttons/playlist/playlist_remove" ),
1124                                              qtr("&Uninstall"), parent );
1125         else
1126             installButton = new QPushButton( QIcon( ":/buttons/playlist/playlist_add" ),
1127                                              qtr("&Install"), parent );
1128         CONNECT( installButton, clicked(), this, editButtonClicked() );
1129         editorWidget->layout()->addWidget( installButton );
1130     }
1131
1132     editorWidget->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
1133
1134     return editorWidget;
1135 }
1136
1137 void AddonItemDelegate::updateEditorGeometry( QWidget *editor,
1138                                               const QStyleOptionViewItem &option,
1139                                               const QModelIndex &index) const
1140 {
1141     Q_UNUSED( index );
1142     QSize size = editor->sizeHint();
1143     editor->setGeometry( option.rect.right() - size.width(),
1144                          option.rect.top() + ( option.rect.height() - size.height()),
1145                          size.width(),
1146                          size.height() );
1147 }
1148
1149 void AddonItemDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
1150                                       const QModelIndex &index ) const
1151 {
1152     model->setData( index, editor->property("Addon::state"), AddonsListModel::StateRole );
1153 }
1154
1155 void AddonItemDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
1156 {
1157     editor->setProperty("Addon::state", index.data( AddonsListModel::StateRole ) );
1158 }
1159
1160 void AddonItemDelegate::setAnimator( DelegateAnimationHelper *animator_ )
1161 {
1162     if ( !progressbar )
1163     {
1164         QProgressBar *progress = new QProgressBar(  );
1165         progress->setMinimum( 0 );
1166         progress->setMaximum( 0 );
1167         progress->setTextVisible( false );
1168         progressbar = progress;
1169     }
1170     animator = animator_;
1171 }
1172
1173 void AddonItemDelegate::editButtonClicked()
1174 {
1175     QWidget *editor = qobject_cast<QWidget *>(sender()->parent());
1176     if ( !editor ) return;
1177     int value = editor->property("Addon::state").toInt();
1178     if ( ( value == ADDON_INSTALLED ) )
1179         /* uninstall */
1180         editor->setProperty("Addon::state", ADDON_UNINSTALLING );
1181     else
1182         /* install */
1183         editor->setProperty("Addon::state", ADDON_INSTALLING );
1184     emit commitData( editor );
1185     emit closeEditor( editor );
1186 }
1187
1188 /* "More information" dialog */
1189
1190 ExtensionInfoDialog::ExtensionInfoDialog( const QModelIndex &index,
1191                                           intf_thread_t *p_intf,
1192                                           QWidget *parent )
1193        : QVLCDialog( parent, p_intf )
1194 {
1195     // Let's be a modal dialog
1196     setWindowModality( Qt::WindowModal );
1197
1198     // Window title
1199     setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
1200
1201     // Layout
1202     QGridLayout *layout = new QGridLayout( this );
1203
1204     // Icon
1205     QLabel *icon = new QLabel( this );
1206     QPixmap pix = index.data(Qt::DecorationRole).value<QPixmap>();
1207     Q_ASSERT( !pix.isNull() );
1208     icon->setPixmap( pix );
1209     icon->setAlignment( Qt::AlignCenter );
1210     icon->setFixedSize( 48, 48 );
1211     layout->addWidget( icon, 1, 0, 2, 1 );
1212
1213     // Title
1214     QLabel *label = new QLabel( index.data(Qt::DisplayRole).toString(), this );
1215     QFont font = label->font();
1216     font.setBold( true );
1217     font.setPointSizeF( font.pointSizeF() * 1.3f );
1218     label->setFont( font );
1219     layout->addWidget( label, 0, 0, 1, -1 );
1220
1221     // Version
1222     label = new QLabel( "<b>" + qtr( "Version" ) + ":</b>", this );
1223     layout->addWidget( label, 1, 1, 1, 1, Qt::AlignBottom );
1224     label = new QLabel( index.data(ExtensionListModel::VersionRole).toString(), this );
1225     layout->addWidget( label, 1, 2, 1, 2, Qt::AlignBottom );
1226
1227     // Author
1228     label = new QLabel( "<b>" + qtr( "Author" ) + ":</b>", this );
1229     layout->addWidget( label, 2, 1, 1, 1, Qt::AlignTop );
1230     label = new QLabel( index.data(ExtensionListModel::AuthorRole).toString(), this );
1231     layout->addWidget( label, 2, 2, 1, 2, Qt::AlignTop );
1232
1233
1234     // Description
1235     label = new QLabel( this );
1236     label->setText( index.data(ExtensionListModel::SummaryRole).toString() );
1237     label->setWordWrap( true );
1238     label->setOpenExternalLinks( true );
1239     layout->addWidget( label, 4, 0, 1, -1 );
1240
1241     // URL
1242     label = new QLabel( "<b>" + qtr( "Website" ) + ":</b>", this );
1243     layout->addWidget( label, 5, 0, 1, 2 );
1244     label = new QLabel( QString("<a href=\"%1\">%2</a>")
1245                         .arg( index.data(ExtensionListModel::LinkRole).toString() )
1246                         .arg( index.data(ExtensionListModel::LinkRole).toString() )
1247                         , this );
1248     label->setOpenExternalLinks( true );
1249     layout->addWidget( label, 5, 2, 1, -1 );
1250
1251     // Script file
1252     label = new QLabel( "<b>" + qtr( "File" ) + ":</b>", this );
1253     layout->addWidget( label, 6, 0, 1, 2 );
1254     QLineEdit *line =
1255             new QLineEdit( index.data(ExtensionListModel::FilenameRole).toString(), this );
1256     line->setReadOnly( true );
1257     layout->addWidget( line, 6, 2, 1, -1 );
1258
1259     // Close button
1260     QDialogButtonBox *group = new QDialogButtonBox( this );
1261     QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
1262     group->addButton( closeButton, QDialogButtonBox::RejectRole );
1263     BUTTONACT( closeButton, close() );
1264
1265     layout->addWidget( group, 7, 0, 1, -1 );
1266
1267     // Fix layout
1268     layout->setColumnStretch( 2, 1 );
1269     layout->setRowStretch( 4, 1 );
1270     setMinimumSize( 450, 350 );
1271 }
1272
1273
1274 AddonInfoDialog::AddonInfoDialog( const QModelIndex &index,
1275                                   intf_thread_t *p_intf, QWidget *parent )
1276        : QVLCDialog( parent, p_intf )
1277 {
1278     // Let's be a modal dialog
1279     setWindowModality( Qt::WindowModal );
1280
1281     // Window title
1282     setWindowTitle( qtr( "About" ) + " " + index.data(Qt::DisplayRole).toString() );
1283
1284     // Layout
1285     QGridLayout *layout = new QGridLayout( this );
1286     QLabel *label;
1287
1288     // Icon
1289     QLabel *iconLabel = new QLabel( this );
1290     iconLabel->setFixedSize( 100, 100 );
1291     QPixmap icon = index.data( Qt::DecorationRole ).value<QPixmap>();
1292     icon.scaled( iconLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation );
1293     iconLabel->setPixmap( icon );
1294     iconLabel->setAlignment( Qt::AlignCenter | Qt::AlignTop );
1295     layout->addWidget( iconLabel, 1, 0, 2, 1 );
1296
1297     // Title
1298     label = new QLabel( index.data(Qt::DisplayRole).toString(), this );
1299     QFont font = label->font();
1300     font.setBold( true );
1301     font.setPointSizeF( font.pointSizeF() * 1.3f );
1302     label->setFont( font );
1303     layout->addWidget( label, 0, 0, 1, -1 );
1304
1305     // HTML Content on right side
1306     QTextEdit *textContent = new QTextEdit();
1307     textContent->viewport()->setAutoFillBackground( false );
1308     textContent->setAcceptRichText( true );
1309     textContent->setBackgroundRole( QPalette::Window );
1310     textContent->setFrameStyle( QFrame::NoFrame );
1311     textContent->setAutoFillBackground( false );
1312     textContent->setReadOnly( true );
1313     layout->addWidget( textContent, 1, 1, 4, -1 );
1314
1315     // Type
1316     QString type = AddonsManager::getAddonType( index.data(AddonsListModel::TypeRole).toInt() );
1317     textContent->append( QString("<b>%1:</b> %2<br/>")
1318                          .arg( qtr("Type") ).arg( type ) );
1319
1320     // Version
1321     QString version = index.data(ExtensionListModel::VersionRole).toString();
1322     if ( !version.isEmpty() )
1323     {
1324         textContent->append( QString("<b>%1:</b> %2<br/>")
1325                              .arg( qtr("Version") ).arg( version ) );
1326     }
1327
1328     // Author
1329     QString author = index.data(ExtensionListModel::AuthorRole).toString();
1330     if ( !author.isEmpty() )
1331     {
1332         textContent->append( QString("<b>%1:</b> %2<br/>")
1333                              .arg( qtr("Author") ).arg( author ) );
1334     }
1335
1336     // Summary
1337     textContent->append( QString("%1<br/>\n")
1338                 .arg( index.data(AddonsListModel::SummaryRole).toString() ) );
1339
1340     // Description
1341     QString description = index.data(AddonsListModel::DescriptionRole).toString();
1342     if ( !description.isEmpty() )
1343     {
1344         textContent->append( QString("<hr/>\n%1")
1345                              .arg( description.replace("\n", "<br/>") ) );
1346     }
1347
1348     // URL
1349     QString sourceUrl = index.data(ExtensionListModel::LinkRole).toString();
1350     if ( !sourceUrl.isEmpty() )
1351     {
1352         label = new QLabel( "<b>" + qtr( "Website" ) + ":</b>", this );
1353         layout->addWidget( label, 5, 0, 1, 2 );
1354         label = new QLabel( QString("<a href=\"%1\">%2</a>")
1355                             .arg( sourceUrl ).arg( sourceUrl ), this );
1356         label->setOpenExternalLinks( true );
1357         layout->addWidget( label, 5, 2, 1, -1 );
1358     }
1359
1360     // Script files
1361     QList<QVariant> list = index.data(ExtensionListModel::FilenameRole).toList();
1362     if ( ! list.empty() )
1363     {
1364         label = new QLabel( "<b>" + qtr( "Files" ) + ":</b>", this );
1365         layout->addWidget( label, 6, 0, 1, 2 );
1366         QComboBox *filesCombo = new QComboBox();
1367         Q_FOREACH( const QVariant & file, list )
1368             filesCombo->addItem( file.toString() );
1369         layout->addWidget( filesCombo, 6, 2, 1, -1 );
1370     }
1371
1372     // Close button
1373     QDialogButtonBox *group = new QDialogButtonBox( this );
1374     QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
1375     group->addButton( closeButton, QDialogButtonBox::RejectRole );
1376     BUTTONACT( closeButton, close() );
1377
1378     layout->addWidget( group, 7, 0, 1, -1 );
1379
1380     // Fix layout
1381     layout->setColumnStretch( 2, 1 );
1382     layout->setRowStretch( 4, 1 );
1383     setMinimumSize( 640, 480 );
1384 }
1385
1386 static QPixmap *loadPixmapFromData( char *data, int size )
1387 {
1388     if( !data || size <= 0 )
1389         return NULL;
1390     QPixmap *pixmap = new QPixmap();
1391     if( !pixmap->loadFromData( (const uchar*) data, (uint) size ) )
1392     {
1393         delete pixmap;
1394         return NULL;
1395     }
1396     return pixmap;
1397 }