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