1 /*****************************************************************************
2 * extensions.cpp: Extensions manager for Qt: dialogs manager
3 ****************************************************************************
4 * Copyright (C) 2009-2010 VideoLAN and authors
7 * Authors: Jean-Philippe André < jpeg # videolan.org >
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 #include "extensions.hpp"
25 #include "../extensions_manager.hpp" // for isUnloading()
27 #include <vlc_dialog.h>
29 #include <QGridLayout>
30 #include <QPushButton>
31 #include <QSignalMapper>
35 #include <QTextBrowser>
37 #include <QListWidget>
39 #include <QCloseEvent>
41 ExtensionsDialogProvider *ExtensionsDialogProvider::instance = NULL;
43 static int DialogCallback( vlc_object_t *p_this, const char *psz_variable,
44 vlc_value_t old_val, vlc_value_t new_val,
48 ExtensionsDialogProvider::ExtensionsDialogProvider( intf_thread_t *_p_intf,
49 extensions_manager_t *p_mgr )
50 : QObject( NULL ), p_intf( _p_intf ), p_extensions_manager( p_mgr )
52 // At this point, we consider that the Qt interface already called
53 // dialog_Register() in order to be the extension dialog provider
54 var_Create( p_intf, "dialog-extension", VLC_VAR_ADDRESS );
55 var_AddCallback( p_intf, "dialog-extension", DialogCallback, NULL );
57 CONNECT( this, SignalDialog( extension_dialog_t* ),
58 this, UpdateExtDialog( extension_dialog_t* ) );
61 ExtensionsDialogProvider::~ExtensionsDialogProvider()
63 var_DelCallback( p_intf, "dialog-extension", DialogCallback, NULL );
67 * Note: Lock on p_dialog->lock must be held. */
68 ExtensionDialog* ExtensionsDialogProvider::CreateExtDialog(
69 extension_dialog_t *p_dialog )
71 ExtensionDialog *dialog = new ExtensionDialog( p_intf,
74 p_dialog->p_sys_intf = (void*) dialog;
75 CONNECT( dialog, destroyDialog( extension_dialog_t* ),
76 this, DestroyExtDialog( extension_dialog_t* ) );
81 * Note: Lock on p_dialog->lock must be held. */
82 int ExtensionsDialogProvider::DestroyExtDialog( extension_dialog_t *p_dialog )
85 ExtensionDialog *dialog = ( ExtensionDialog* ) p_dialog->p_sys_intf;
89 p_dialog->p_sys_intf = NULL;
94 * Update/Create/Destroy a dialog
96 ExtensionDialog* ExtensionsDialogProvider::UpdateExtDialog(
97 extension_dialog_t *p_dialog )
101 ExtensionDialog *dialog = ( ExtensionDialog* ) p_dialog->p_sys_intf;
102 if( p_dialog->b_kill && !dialog )
104 /* This extension could not be activated properly but tried
105 to create a dialog. We must ignore it. */
109 vlc_mutex_lock( &p_dialog->lock );
110 if( !p_dialog->b_kill && !dialog )
112 dialog = CreateExtDialog( p_dialog );
113 dialog->setVisible( true );
115 else if( !p_dialog->b_kill && dialog )
117 dialog->has_lock = true;
118 dialog->UpdateWidgets();
119 dialog->has_lock = false;
121 else if( p_dialog->b_kill )
123 DestroyExtDialog( p_dialog );
125 vlc_cond_signal( &p_dialog->cond );
126 vlc_mutex_unlock( &p_dialog->lock );
131 * Ask the dialog manager to create/update/kill the dialog. Thread-safe.
133 void ExtensionsDialogProvider::ManageDialog( extension_dialog_t *p_dialog )
136 ExtensionsManager *extMgr = ExtensionsManager::getInstance( p_intf );
137 assert( extMgr != NULL );
138 if( !extMgr->isUnloading() )
139 emit SignalDialog( p_dialog ); // Safe because we signal Qt thread
141 UpdateExtDialog( p_dialog ); // This is safe, we're already in Qt thread
145 * Ask the dialogs provider to create a new dialog
147 static int DialogCallback( vlc_object_t *p_this, const char *psz_variable,
148 vlc_value_t old_val, vlc_value_t new_val,
156 ExtensionsDialogProvider *p_edp = ExtensionsDialogProvider::getInstance();
159 if( !new_val.p_address )
162 extension_dialog_t *p_dialog = ( extension_dialog_t* ) new_val.p_address;
163 p_edp->ManageDialog( p_dialog );
168 ExtensionDialog::ExtensionDialog( intf_thread_t *_p_intf,
169 extensions_manager_t *p_mgr,
170 extension_dialog_t *_p_dialog )
171 : QDialog( NULL ), p_intf( _p_intf ), p_extensions_manager( p_mgr )
172 , p_dialog( _p_dialog ), has_lock(false)
176 msg_Dbg( p_intf, "Creating a new dialog: '%s'", p_dialog->psz_title );
178 this->setWindowFlags( Qt::WindowMinMaxButtonsHint
179 | Qt::WindowCloseButtonHint );
181 this->setWindowFlags( Qt::WindowMinMaxButtonsHint );
184 this->setWindowTitle( qfu( p_dialog->psz_title ) );
186 layout = new QGridLayout( this );
187 clickMapper = new QSignalMapper( this );
188 CONNECT( clickMapper, mapped( QObject* ), this, TriggerClick( QObject* ) );
189 inputMapper = new QSignalMapper( this );
190 CONNECT( inputMapper, mapped( QObject* ), this, SyncInput( QObject* ) );
191 selectMapper = new QSignalMapper( this );
192 CONNECT( selectMapper, mapped( QObject* ), this, SyncSelection(QObject*) );
197 ExtensionDialog::~ExtensionDialog()
199 msg_Dbg( p_intf, "Deleting extension dialog '%s'", qtu(windowTitle()) );
200 p_dialog->p_sys_intf = NULL;
203 QWidget* ExtensionDialog::CreateWidget( extension_widget_t *p_widget )
205 QLabel *label = NULL;
206 QPushButton *button = NULL;
207 QTextBrowser *textArea = NULL;
208 QLineEdit *textInput = NULL;
209 QCheckBox *checkBox = NULL;
210 QComboBox *comboBox = NULL;
211 QListWidget *list = NULL;
212 struct extension_widget_t::extension_widget_value_t *p_value = NULL;
214 assert( p_widget->p_sys_intf == NULL );
216 switch( p_widget->type )
218 case EXTENSION_WIDGET_LABEL:
219 label = new QLabel( qfu( p_widget->psz_text ), this );
220 p_widget->p_sys_intf = label;
221 label->setTextFormat( Qt::RichText );
222 //label->setFixedHeight( label->sizeHint().height );
225 case EXTENSION_WIDGET_BUTTON:
226 button = new QPushButton( qfu( p_widget->psz_text ), this );
227 clickMapper->setMapping( button, new WidgetMapper( p_widget ) );
228 CONNECT( button, clicked(), clickMapper, map() );
229 p_widget->p_sys_intf = button;
232 case EXTENSION_WIDGET_IMAGE:
233 label = new QLabel( this );
234 label->setPixmap( QPixmap( qfu( p_widget->psz_text ) ) );
235 if( p_widget->i_width > 0 )
236 label->setMaximumWidth( p_widget->i_width );
237 if( p_widget->i_height > 0 )
238 label->setMaximumHeight( p_widget->i_height );
239 label->setScaledContents( true );
240 p_widget->p_sys_intf = label;
243 case EXTENSION_WIDGET_HTML:
244 textArea = new QTextBrowser( this );
245 textArea->setOpenExternalLinks( true );
246 textArea->setHtml( qfu( p_widget->psz_text ) );
247 p_widget->p_sys_intf = textArea;
250 case EXTENSION_WIDGET_TEXT_FIELD:
251 textInput = new QLineEdit( this );
252 textInput->setText( qfu( p_widget->psz_text ) );
253 textInput->setReadOnly( false );
254 textInput->setEchoMode( QLineEdit::Normal );
255 inputMapper->setMapping( textInput, new WidgetMapper( p_widget ) );
256 /// @note: maybe it would be wiser to use textEdited here?
257 CONNECT( textInput, textChanged(const QString &),
258 inputMapper, map() );
259 p_widget->p_sys_intf = textInput;
262 case EXTENSION_WIDGET_PASSWORD:
263 textInput = new QLineEdit( this );
264 textInput->setText( qfu( p_widget->psz_text ) );
265 textInput->setReadOnly( false );
266 textInput->setEchoMode( QLineEdit::Password );
267 inputMapper->setMapping( textInput, new WidgetMapper( p_widget ) );
268 /// @note: maybe it would be wiser to use textEdited here?
269 CONNECT( textInput, textChanged(const QString &),
270 inputMapper, map() );
271 p_widget->p_sys_intf = textInput;
274 case EXTENSION_WIDGET_CHECK_BOX:
275 checkBox = new QCheckBox( this );
276 checkBox->setText( qfu( p_widget->psz_text ) );
277 checkBox->setChecked( p_widget->b_checked );
278 clickMapper->setMapping( checkBox, new WidgetMapper( p_widget ) );
279 CONNECT( checkBox, stateChanged( int ), clickMapper, map() );
280 p_widget->p_sys_intf = checkBox;
283 case EXTENSION_WIDGET_DROPDOWN:
284 comboBox = new QComboBox( this );
285 comboBox->setEditable( false );
286 for( p_value = p_widget->p_values;
288 p_value = p_value->p_next )
290 comboBox->addItem( qfu( p_value->psz_text ), p_value->i_id );
292 /* Set current item */
293 if( p_widget->psz_text )
295 int idx = comboBox->findText( qfu( p_widget->psz_text ) );
297 comboBox->setCurrentIndex( idx );
299 selectMapper->setMapping( comboBox, new WidgetMapper( p_widget ) );
300 CONNECT( comboBox, currentIndexChanged( const QString& ),
301 selectMapper, map() );
304 case EXTENSION_WIDGET_LIST:
305 list = new QListWidget( this );
306 list->setSelectionMode( QAbstractItemView::ExtendedSelection );
307 for( p_value = p_widget->p_values;
309 p_value = p_value->p_next )
311 QListWidgetItem *item =
312 new QListWidgetItem( qfu( p_value->psz_text ) );
313 item->setData( Qt::UserRole, p_value->i_id );
314 list->addItem( item );
316 selectMapper->setMapping( list, new WidgetMapper( p_widget ) );
317 CONNECT( list, itemSelectionChanged(),
318 selectMapper, map() );
322 msg_Err( p_intf, "Widget type %d unknown", p_widget->type );
328 * Forward click event to the extension
329 * @param object A WidgetMapper, whose data() is the p_widget
331 int ExtensionDialog::TriggerClick( QObject *object )
333 assert( object != NULL );
334 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
335 extension_widget_t *p_widget = mapping->getWidget();
337 QCheckBox *checkBox = NULL;
338 int i_ret = VLC_EGENERIC;
340 bool lockedHere = false;
343 vlc_mutex_lock( &p_dialog->lock );
348 switch( p_widget->type )
350 case EXTENSION_WIDGET_BUTTON:
351 i_ret = extension_WidgetClicked( p_dialog, p_widget );
354 case EXTENSION_WIDGET_CHECK_BOX:
355 checkBox = static_cast< QCheckBox* >( p_widget->p_sys_intf );
356 p_widget->b_checked = checkBox->isChecked();
361 msg_Dbg( p_intf, "A click event was triggered by a wrong widget" );
367 vlc_mutex_unlock( &p_dialog->lock );
375 * Synchronize psz_text with the widget's text() value on update
376 * @param object A WidgetMapper
378 void ExtensionDialog::SyncInput( QObject *object )
380 assert( object != NULL );
382 bool lockedHere = false;
385 vlc_mutex_lock( &p_dialog->lock );
390 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
391 extension_widget_t *p_widget = mapping->getWidget();
392 assert( p_widget->type == EXTENSION_WIDGET_TEXT_FIELD
393 || p_widget->type == EXTENSION_WIDGET_PASSWORD );
394 /* Synchronize psz_text with the new value */
395 QLineEdit *widget = static_cast< QLineEdit* >( p_widget->p_sys_intf );
396 char *psz_text = widget->text().isNull() ? NULL : strdup( qtu( widget->text() ) );
397 free( p_widget->psz_text );
398 p_widget->psz_text = psz_text;
402 vlc_mutex_unlock( &p_dialog->lock );
408 * Synchronize parameter b_selected in the values list
409 * @param object A WidgetMapper
411 void ExtensionDialog::SyncSelection( QObject *object )
413 assert( object != NULL );
414 struct extension_widget_t::extension_widget_value_t *p_value;
416 bool lockedHere = false;
419 vlc_mutex_lock( &p_dialog->lock );
424 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
425 extension_widget_t *p_widget = mapping->getWidget();
426 assert( p_widget->type == EXTENSION_WIDGET_DROPDOWN
427 || p_widget->type == EXTENSION_WIDGET_LIST );
429 if( p_widget->type == EXTENSION_WIDGET_DROPDOWN )
431 QComboBox *combo = static_cast< QComboBox* >( p_widget->p_sys_intf );
432 for( p_value = p_widget->p_values;
434 p_value = p_value->p_next )
436 // if( !qstrcmp( p_value->psz_text, qtu( combo->currentText() ) ) )
437 if( combo->itemData( combo->currentIndex(), Qt::UserRole ).toInt()
440 p_value->b_selected = true;
444 p_value->b_selected = false;
447 free( p_widget->psz_text );
448 p_widget->psz_text = strdup( qtu( combo->currentText() ) );
450 else if( p_widget->type == EXTENSION_WIDGET_LIST )
452 QListWidget *list = static_cast<QListWidget*>( p_widget->p_sys_intf );
453 QList<QListWidgetItem *> selection = list->selectedItems();
454 for( p_value = p_widget->p_values;
456 p_value = p_value->p_next )
458 bool b_selected = false;
459 foreach( const QListWidgetItem *item, selection )
461 // if( !qstrcmp( qtu( item->text() ), p_value->psz_text ) )
462 if( item->data( Qt::UserRole ).toInt() == p_value->i_id )
468 p_value->b_selected = b_selected;
474 vlc_mutex_unlock( &p_dialog->lock );
479 void ExtensionDialog::UpdateWidgets()
482 extension_widget_t *p_widget;
483 FOREACH_ARRAY( p_widget, p_dialog->widgets )
485 if( !p_widget ) continue; /* Some widgets may be NULL at this point */
487 int row = p_widget->i_row - 1;
488 int col = p_widget->i_column - 1;
491 row = layout->rowCount();
495 col = layout->columnCount();
496 int hsp = __MAX( 1, p_widget->i_horiz_span );
497 int vsp = __MAX( 1, p_widget->i_vert_span );
498 if( !p_widget->p_sys_intf && !p_widget->b_kill )
500 widget = CreateWidget( p_widget );
503 msg_Warn( p_intf, "Could not create a widget for dialog %s",
504 p_dialog->psz_title );
507 widget->setVisible( !p_widget->b_hide );
508 layout->addWidget( widget, row, col, vsp, hsp );
509 if( ( p_widget->i_width > 0 ) && ( p_widget->i_height > 0 ) )
510 widget->resize( p_widget->i_width, p_widget->i_height );
511 p_widget->p_sys_intf = widget;
512 this->resize( sizeHint() );
514 else if( p_widget->p_sys_intf && !p_widget->b_kill
515 && p_widget->b_update )
517 widget = UpdateWidget( p_widget );
520 msg_Warn( p_intf, "Could not update a widget for dialog %s",
521 p_dialog->psz_title );
524 widget->setVisible( !p_widget->b_hide );
525 layout->addWidget( widget, row, col, vsp, hsp );
526 if( ( p_widget->i_width > 0 ) && ( p_widget->i_height > 0 ) )
527 widget->resize( p_widget->i_width, p_widget->i_height );
528 p_widget->p_sys_intf = widget;
529 this->resize( sizeHint() );
531 /* Do not update again */
532 p_widget->b_update = false;
534 else if( p_widget->p_sys_intf && p_widget->b_kill )
536 DestroyWidget( p_widget );
537 p_widget->p_sys_intf = NULL;
538 this->resize( sizeHint() );
544 QWidget* ExtensionDialog::UpdateWidget( extension_widget_t *p_widget )
546 QLabel *label = NULL;
547 QPushButton *button = NULL;
548 QTextBrowser *textArea = NULL;
549 QLineEdit *textInput = NULL;
550 QCheckBox *checkBox = NULL;
551 QComboBox *comboBox = NULL;
552 QListWidget *list = NULL;
553 struct extension_widget_t::extension_widget_value_t *p_value = NULL;
555 assert( p_widget->p_sys_intf != NULL );
557 switch( p_widget->type )
559 case EXTENSION_WIDGET_LABEL:
560 label = static_cast< QLabel* >( p_widget->p_sys_intf );
561 label->setText( qfu( p_widget->psz_text ) );
564 case EXTENSION_WIDGET_BUTTON:
565 // FIXME: looks like removeMappings does not work
566 button = static_cast< QPushButton* >( p_widget->p_sys_intf );
567 button->setText( qfu( p_widget->psz_text ) );
568 clickMapper->removeMappings( button );
569 clickMapper->setMapping( button, new WidgetMapper( p_widget ) );
570 CONNECT( button, clicked(), clickMapper, map() );
573 case EXTENSION_WIDGET_IMAGE:
574 label = static_cast< QLabel* >( p_widget->p_sys_intf );
575 label->setPixmap( QPixmap( qfu( p_widget->psz_text ) ) );
578 case EXTENSION_WIDGET_HTML:
579 textArea = static_cast< QTextBrowser* >( p_widget->p_sys_intf );
580 textArea->setHtml( qfu( p_widget->psz_text ) );
583 case EXTENSION_WIDGET_TEXT_FIELD:
584 textInput = static_cast< QLineEdit* >( p_widget->p_sys_intf );
585 textInput->setText( qfu( p_widget->psz_text ) );
588 case EXTENSION_WIDGET_PASSWORD:
589 textInput = static_cast< QLineEdit* >( p_widget->p_sys_intf );
590 textInput->setText( qfu( p_widget->psz_text ) );
593 case EXTENSION_WIDGET_CHECK_BOX:
594 checkBox = static_cast< QCheckBox* >( p_widget->p_sys_intf );
595 checkBox->setText( qfu( p_widget->psz_text ) );
596 checkBox->setChecked( p_widget->b_checked );
599 case EXTENSION_WIDGET_DROPDOWN:
600 comboBox = static_cast< QComboBox* >( p_widget->p_sys_intf );
602 for( p_value = p_widget->p_values;
604 p_value = p_value->p_next )
606 comboBox->addItem( qfu( p_value->psz_text ), p_value->i_id );
608 /* Set current item */
609 if( p_widget->psz_text )
611 int idx = comboBox->findText( qfu( p_widget->psz_text ) );
613 comboBox->setCurrentIndex( idx );
617 case EXTENSION_WIDGET_LIST:
618 list = static_cast< QListWidget* >( p_widget->p_sys_intf );
620 for( p_value = p_widget->p_values;
622 p_value = p_value->p_next )
624 QListWidgetItem *item =
625 new QListWidgetItem( qfu( p_value->psz_text ) );
626 item->setData( Qt::UserRole, p_value->i_id );
627 list->addItem( item );
632 msg_Err( p_intf, "Widget type %d unknown", p_widget->type );
637 void ExtensionDialog::DestroyWidget( extension_widget_t *p_widget )
639 assert( p_widget && p_widget->b_kill );
640 QWidget *widget = static_cast< QWidget* >( p_widget->p_sys_intf );
642 p_widget->p_sys_intf = NULL;
645 /** Implement closeEvent() in order to intercept the event */
646 void ExtensionDialog::closeEvent( QCloseEvent *event )
648 assert( p_dialog != NULL );
649 extension_DialogClosed( p_dialog );
651 emit destroyDialog( p_dialog );