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>
40 #include <QCoreApplication>
42 ExtensionsDialogProvider *ExtensionsDialogProvider::instance = NULL;
44 static int DialogCallback( vlc_object_t *p_this, const char *psz_variable,
45 vlc_value_t old_val, vlc_value_t new_val,
49 ExtensionsDialogProvider::ExtensionsDialogProvider( intf_thread_t *_p_intf,
50 extensions_manager_t *p_mgr )
51 : QObject( NULL ), p_intf( _p_intf ), p_extensions_manager( p_mgr )
53 // At this point, we consider that the Qt interface already called
54 // dialog_Register() in order to be the extension dialog provider
55 var_Create( p_intf, "dialog-extension", VLC_VAR_ADDRESS );
56 var_AddCallback( p_intf, "dialog-extension", DialogCallback, NULL );
58 CONNECT( this, SignalDialog( extension_dialog_t* ),
59 this, UpdateExtDialog( extension_dialog_t* ) );
62 ExtensionsDialogProvider::~ExtensionsDialogProvider()
64 msg_Dbg( p_intf, "ExtensionsDialogProvider is quitting..." );
65 var_DelCallback( p_intf, "dialog-extension", DialogCallback, NULL );
69 * Note: Lock on p_dialog->lock must be held. */
70 ExtensionDialog* ExtensionsDialogProvider::CreateExtDialog(
71 extension_dialog_t *p_dialog )
73 ExtensionDialog *dialog = new ExtensionDialog( p_intf,
76 p_dialog->p_sys_intf = (void*) dialog;
77 CONNECT( dialog, destroyDialog( extension_dialog_t* ),
78 this, DestroyExtDialog( extension_dialog_t* ) );
83 * Note: Lock on p_dialog->lock must be held. */
84 int ExtensionsDialogProvider::DestroyExtDialog( extension_dialog_t *p_dialog )
87 ExtensionDialog *dialog = ( ExtensionDialog* ) p_dialog->p_sys_intf;
91 p_dialog->p_sys_intf = NULL;
92 vlc_cond_signal( &p_dialog->cond );
97 * Update/Create/Destroy a dialog
99 ExtensionDialog* ExtensionsDialogProvider::UpdateExtDialog(
100 extension_dialog_t *p_dialog )
104 ExtensionDialog *dialog = ( ExtensionDialog* ) p_dialog->p_sys_intf;
105 if( p_dialog->b_kill && !dialog )
107 /* This extension could not be activated properly but tried
108 to create a dialog. We must ignore it. */
112 vlc_mutex_lock( &p_dialog->lock );
113 if( !p_dialog->b_kill && !dialog )
115 dialog = CreateExtDialog( p_dialog );
116 dialog->setVisible( !p_dialog->b_hide );
117 dialog->has_lock = false;
119 else if( !p_dialog->b_kill && dialog )
121 dialog->has_lock = true;
122 dialog->UpdateWidgets();
123 if( strcmp( qtu( dialog->windowTitle() ),
124 p_dialog->psz_title ) != 0 )
125 dialog->setWindowTitle( qfu( p_dialog->psz_title ) );
126 dialog->has_lock = false;
127 dialog->setVisible( !p_dialog->b_hide );
129 else if( p_dialog->b_kill )
131 DestroyExtDialog( p_dialog );
133 vlc_cond_signal( &p_dialog->cond );
134 vlc_mutex_unlock( &p_dialog->lock );
139 * Ask the dialog manager to create/update/kill the dialog. Thread-safe.
141 void ExtensionsDialogProvider::ManageDialog( extension_dialog_t *p_dialog )
144 ExtensionsManager *extMgr = ExtensionsManager::getInstance( p_intf );
145 assert( extMgr != NULL );
146 if( !extMgr->isUnloading() )
147 emit SignalDialog( p_dialog ); // Safe because we signal Qt thread
149 UpdateExtDialog( p_dialog ); // This is safe, we're already in Qt thread
153 * Ask the dialogs provider to create a new dialog
155 static int DialogCallback( vlc_object_t *p_this, const char *psz_variable,
156 vlc_value_t old_val, vlc_value_t new_val,
164 ExtensionsDialogProvider *p_edp = ExtensionsDialogProvider::getInstance();
167 if( !new_val.p_address )
170 extension_dialog_t *p_dialog = ( extension_dialog_t* ) new_val.p_address;
171 p_edp->ManageDialog( p_dialog );
176 ExtensionDialog::ExtensionDialog( intf_thread_t *_p_intf,
177 extensions_manager_t *p_mgr,
178 extension_dialog_t *_p_dialog )
179 : QDialog( NULL ), p_intf( _p_intf ), p_extensions_manager( p_mgr )
180 , p_dialog( _p_dialog ), has_lock(true)
183 CONNECT( ExtensionsDialogProvider::getInstance(), destroyed(),
184 this, parentDestroyed() );
186 msg_Dbg( p_intf, "Creating a new dialog: '%s'", p_dialog->psz_title );
188 this->setWindowFlags( Qt::WindowMinMaxButtonsHint
189 | Qt::WindowCloseButtonHint );
191 this->setWindowFlags( Qt::WindowMinMaxButtonsHint );
194 this->setWindowTitle( qfu( p_dialog->psz_title ) );
196 layout = new QGridLayout( this );
197 clickMapper = new QSignalMapper( this );
198 CONNECT( clickMapper, mapped( QObject* ), this, TriggerClick( QObject* ) );
199 inputMapper = new QSignalMapper( this );
200 CONNECT( inputMapper, mapped( QObject* ), this, SyncInput( QObject* ) );
201 selectMapper = new QSignalMapper( this );
202 CONNECT( selectMapper, mapped( QObject* ), this, SyncSelection(QObject*) );
207 ExtensionDialog::~ExtensionDialog()
209 msg_Dbg( p_intf, "Deleting extension dialog '%s'", qtu(windowTitle()) );
212 QWidget* ExtensionDialog::CreateWidget( extension_widget_t *p_widget )
214 QLabel *label = NULL;
215 QPushButton *button = NULL;
216 QTextBrowser *textArea = NULL;
217 QLineEdit *textInput = NULL;
218 QCheckBox *checkBox = NULL;
219 QComboBox *comboBox = NULL;
220 QListWidget *list = NULL;
221 struct extension_widget_t::extension_widget_value_t *p_value = NULL;
223 assert( p_widget->p_sys_intf == NULL );
225 switch( p_widget->type )
227 case EXTENSION_WIDGET_LABEL:
228 label = new QLabel( qfu( p_widget->psz_text ), this );
229 p_widget->p_sys_intf = label;
230 label->setTextFormat( Qt::RichText );
231 label->setOpenExternalLinks( true );
234 case EXTENSION_WIDGET_BUTTON:
235 button = new QPushButton( qfu( p_widget->psz_text ), this );
236 clickMapper->setMapping( button, new WidgetMapper( p_widget ) );
237 CONNECT( button, clicked(), clickMapper, map() );
238 p_widget->p_sys_intf = button;
241 case EXTENSION_WIDGET_IMAGE:
242 label = new QLabel( this );
243 label->setPixmap( QPixmap( qfu( p_widget->psz_text ) ) );
244 if( p_widget->i_width > 0 )
245 label->setMaximumWidth( p_widget->i_width );
246 if( p_widget->i_height > 0 )
247 label->setMaximumHeight( p_widget->i_height );
248 label->setScaledContents( true );
249 p_widget->p_sys_intf = label;
252 case EXTENSION_WIDGET_HTML:
253 textArea = new QTextBrowser( this );
254 textArea->setOpenExternalLinks( true );
255 textArea->setHtml( qfu( p_widget->psz_text ) );
256 p_widget->p_sys_intf = textArea;
259 case EXTENSION_WIDGET_TEXT_FIELD:
260 textInput = new QLineEdit( this );
261 textInput->setText( qfu( p_widget->psz_text ) );
262 textInput->setReadOnly( false );
263 textInput->setEchoMode( QLineEdit::Normal );
264 inputMapper->setMapping( textInput, new WidgetMapper( p_widget ) );
265 /// @note: maybe it would be wiser to use textEdited here?
266 CONNECT( textInput, textChanged(const QString &),
267 inputMapper, map() );
268 p_widget->p_sys_intf = textInput;
271 case EXTENSION_WIDGET_PASSWORD:
272 textInput = new QLineEdit( this );
273 textInput->setText( qfu( p_widget->psz_text ) );
274 textInput->setReadOnly( false );
275 textInput->setEchoMode( QLineEdit::Password );
276 inputMapper->setMapping( textInput, new WidgetMapper( p_widget ) );
277 /// @note: maybe it would be wiser to use textEdited here?
278 CONNECT( textInput, textChanged(const QString &),
279 inputMapper, map() );
280 p_widget->p_sys_intf = textInput;
283 case EXTENSION_WIDGET_CHECK_BOX:
284 checkBox = new QCheckBox( this );
285 checkBox->setText( qfu( p_widget->psz_text ) );
286 checkBox->setChecked( p_widget->b_checked );
287 clickMapper->setMapping( checkBox, new WidgetMapper( p_widget ) );
288 CONNECT( checkBox, stateChanged( int ), clickMapper, map() );
289 p_widget->p_sys_intf = checkBox;
292 case EXTENSION_WIDGET_DROPDOWN:
293 comboBox = new QComboBox( this );
294 comboBox->setEditable( false );
295 for( p_value = p_widget->p_values;
297 p_value = p_value->p_next )
299 comboBox->addItem( qfu( p_value->psz_text ), p_value->i_id );
301 /* Set current item */
302 if( p_widget->psz_text )
304 int idx = comboBox->findText( qfu( p_widget->psz_text ) );
306 comboBox->setCurrentIndex( idx );
308 selectMapper->setMapping( comboBox, new WidgetMapper( p_widget ) );
309 CONNECT( comboBox, currentIndexChanged( const QString& ),
310 selectMapper, map() );
313 case EXTENSION_WIDGET_LIST:
314 list = new QListWidget( this );
315 list->setSelectionMode( QAbstractItemView::ExtendedSelection );
316 for( p_value = p_widget->p_values;
318 p_value = p_value->p_next )
320 QListWidgetItem *item =
321 new QListWidgetItem( qfu( p_value->psz_text ) );
322 item->setData( Qt::UserRole, p_value->i_id );
323 list->addItem( item );
325 selectMapper->setMapping( list, new WidgetMapper( p_widget ) );
326 CONNECT( list, itemSelectionChanged(),
327 selectMapper, map() );
331 msg_Err( p_intf, "Widget type %d unknown", p_widget->type );
337 * Forward click event to the extension
338 * @param object A WidgetMapper, whose data() is the p_widget
340 int ExtensionDialog::TriggerClick( QObject *object )
342 assert( object != NULL );
343 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
344 extension_widget_t *p_widget = mapping->getWidget();
346 QCheckBox *checkBox = NULL;
347 int i_ret = VLC_EGENERIC;
349 bool lockedHere = false;
352 vlc_mutex_lock( &p_dialog->lock );
357 switch( p_widget->type )
359 case EXTENSION_WIDGET_BUTTON:
360 i_ret = extension_WidgetClicked( p_dialog, p_widget );
363 case EXTENSION_WIDGET_CHECK_BOX:
364 checkBox = static_cast< QCheckBox* >( p_widget->p_sys_intf );
365 p_widget->b_checked = checkBox->isChecked();
370 msg_Dbg( p_intf, "A click event was triggered by a wrong widget" );
376 vlc_mutex_unlock( &p_dialog->lock );
384 * Synchronize psz_text with the widget's text() value on update
385 * @param object A WidgetMapper
387 void ExtensionDialog::SyncInput( QObject *object )
389 assert( object != NULL );
391 bool lockedHere = false;
394 vlc_mutex_lock( &p_dialog->lock );
399 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
400 extension_widget_t *p_widget = mapping->getWidget();
401 assert( p_widget->type == EXTENSION_WIDGET_TEXT_FIELD
402 || p_widget->type == EXTENSION_WIDGET_PASSWORD );
403 /* Synchronize psz_text with the new value */
404 QLineEdit *widget = static_cast< QLineEdit* >( p_widget->p_sys_intf );
405 char *psz_text = widget->text().isNull() ? NULL : strdup( qtu( widget->text() ) );
406 free( p_widget->psz_text );
407 p_widget->psz_text = psz_text;
411 vlc_mutex_unlock( &p_dialog->lock );
417 * Synchronize parameter b_selected in the values list
418 * @param object A WidgetMapper
420 void ExtensionDialog::SyncSelection( QObject *object )
422 assert( object != NULL );
423 struct extension_widget_t::extension_widget_value_t *p_value;
425 bool lockedHere = false;
428 vlc_mutex_lock( &p_dialog->lock );
433 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
434 extension_widget_t *p_widget = mapping->getWidget();
435 assert( p_widget->type == EXTENSION_WIDGET_DROPDOWN
436 || p_widget->type == EXTENSION_WIDGET_LIST );
438 if( p_widget->type == EXTENSION_WIDGET_DROPDOWN )
440 QComboBox *combo = static_cast< QComboBox* >( p_widget->p_sys_intf );
441 for( p_value = p_widget->p_values;
443 p_value = p_value->p_next )
445 // if( !qstrcmp( p_value->psz_text, qtu( combo->currentText() ) ) )
446 if( combo->itemData( combo->currentIndex(), Qt::UserRole ).toInt()
449 p_value->b_selected = true;
453 p_value->b_selected = false;
456 free( p_widget->psz_text );
457 p_widget->psz_text = strdup( qtu( combo->currentText() ) );
459 else if( p_widget->type == EXTENSION_WIDGET_LIST )
461 QListWidget *list = static_cast<QListWidget*>( p_widget->p_sys_intf );
462 QList<QListWidgetItem *> selection = list->selectedItems();
463 for( p_value = p_widget->p_values;
465 p_value = p_value->p_next )
467 bool b_selected = false;
468 foreach( const QListWidgetItem *item, selection )
470 // if( !qstrcmp( qtu( item->text() ), p_value->psz_text ) )
471 if( item->data( Qt::UserRole ).toInt() == p_value->i_id )
477 p_value->b_selected = b_selected;
483 vlc_mutex_unlock( &p_dialog->lock );
488 void ExtensionDialog::UpdateWidgets()
491 extension_widget_t *p_widget;
492 FOREACH_ARRAY( p_widget, p_dialog->widgets )
494 if( !p_widget ) continue; /* Some widgets may be NULL at this point */
496 int row = p_widget->i_row - 1;
497 int col = p_widget->i_column - 1;
500 row = layout->rowCount();
504 col = layout->columnCount();
505 int hsp = __MAX( 1, p_widget->i_horiz_span );
506 int vsp = __MAX( 1, p_widget->i_vert_span );
507 if( !p_widget->p_sys_intf && !p_widget->b_kill )
509 widget = CreateWidget( p_widget );
512 msg_Warn( p_intf, "Could not create a widget for dialog %s",
513 p_dialog->psz_title );
516 widget->setVisible( !p_widget->b_hide );
517 layout->addWidget( widget, row, col, vsp, hsp );
518 if( ( p_widget->i_width > 0 ) && ( p_widget->i_height > 0 ) )
519 widget->resize( p_widget->i_width, p_widget->i_height );
520 p_widget->p_sys_intf = widget;
521 this->resize( sizeHint() );
523 else if( p_widget->p_sys_intf && !p_widget->b_kill
524 && p_widget->b_update )
526 widget = UpdateWidget( p_widget );
529 msg_Warn( p_intf, "Could not update a widget for dialog %s",
530 p_dialog->psz_title );
533 widget->setVisible( !p_widget->b_hide );
534 layout->addWidget( widget, row, col, vsp, hsp );
535 if( ( p_widget->i_width > 0 ) && ( p_widget->i_height > 0 ) )
536 widget->resize( p_widget->i_width, p_widget->i_height );
537 p_widget->p_sys_intf = widget;
538 this->resize( sizeHint() );
540 /* Do not update again */
541 p_widget->b_update = false;
543 else if( p_widget->p_sys_intf && p_widget->b_kill )
545 DestroyWidget( p_widget );
546 p_widget->p_sys_intf = NULL;
547 this->resize( sizeHint() );
553 QWidget* ExtensionDialog::UpdateWidget( extension_widget_t *p_widget )
555 QLabel *label = NULL;
556 QPushButton *button = NULL;
557 QTextBrowser *textArea = NULL;
558 QLineEdit *textInput = NULL;
559 QCheckBox *checkBox = NULL;
560 QComboBox *comboBox = NULL;
561 QListWidget *list = NULL;
562 struct extension_widget_t::extension_widget_value_t *p_value = NULL;
564 assert( p_widget->p_sys_intf != NULL );
566 switch( p_widget->type )
568 case EXTENSION_WIDGET_LABEL:
569 label = static_cast< QLabel* >( p_widget->p_sys_intf );
570 label->setText( qfu( p_widget->psz_text ) );
573 case EXTENSION_WIDGET_BUTTON:
574 // FIXME: looks like removeMappings does not work
575 button = static_cast< QPushButton* >( p_widget->p_sys_intf );
576 button->setText( qfu( p_widget->psz_text ) );
577 clickMapper->removeMappings( button );
578 clickMapper->setMapping( button, new WidgetMapper( p_widget ) );
579 CONNECT( button, clicked(), clickMapper, map() );
582 case EXTENSION_WIDGET_IMAGE:
583 label = static_cast< QLabel* >( p_widget->p_sys_intf );
584 label->setPixmap( QPixmap( qfu( p_widget->psz_text ) ) );
587 case EXTENSION_WIDGET_HTML:
588 textArea = static_cast< QTextBrowser* >( p_widget->p_sys_intf );
589 textArea->setHtml( qfu( p_widget->psz_text ) );
592 case EXTENSION_WIDGET_TEXT_FIELD:
593 textInput = static_cast< QLineEdit* >( p_widget->p_sys_intf );
594 textInput->setText( qfu( p_widget->psz_text ) );
597 case EXTENSION_WIDGET_PASSWORD:
598 textInput = static_cast< QLineEdit* >( p_widget->p_sys_intf );
599 textInput->setText( qfu( p_widget->psz_text ) );
602 case EXTENSION_WIDGET_CHECK_BOX:
603 checkBox = static_cast< QCheckBox* >( p_widget->p_sys_intf );
604 checkBox->setText( qfu( p_widget->psz_text ) );
605 checkBox->setChecked( p_widget->b_checked );
608 case EXTENSION_WIDGET_DROPDOWN:
609 comboBox = static_cast< QComboBox* >( p_widget->p_sys_intf );
611 for( p_value = p_widget->p_values;
613 p_value = p_value->p_next )
615 comboBox->addItem( qfu( p_value->psz_text ), p_value->i_id );
617 /* Set current item */
618 if( p_widget->psz_text )
620 int idx = comboBox->findText( qfu( p_widget->psz_text ) );
622 comboBox->setCurrentIndex( idx );
626 case EXTENSION_WIDGET_LIST:
627 list = static_cast< QListWidget* >( p_widget->p_sys_intf );
629 for( p_value = p_widget->p_values;
631 p_value = p_value->p_next )
633 QListWidgetItem *item =
634 new QListWidgetItem( qfu( p_value->psz_text ) );
635 item->setData( Qt::UserRole, p_value->i_id );
636 list->addItem( item );
641 msg_Err( p_intf, "Widget type %d unknown", p_widget->type );
646 void ExtensionDialog::DestroyWidget( extension_widget_t *p_widget,
649 assert( p_widget && p_widget->b_kill );
650 QWidget *widget = static_cast< QWidget* >( p_widget->p_sys_intf );
652 p_widget->p_sys_intf = NULL;
654 vlc_cond_signal( &p_dialog->cond );
657 /** Implement closeEvent() in order to intercept the event */
658 void ExtensionDialog::closeEvent( QCloseEvent *event )
660 assert( p_dialog != NULL );
661 msg_Dbg( p_intf, "Dialog '%s' received a closeEvent",
662 p_dialog->psz_title );
663 extension_DialogClosed( p_dialog );
666 void ExtensionDialog::parentDestroyed()
668 msg_Dbg( p_intf, "About to destroy dialog '%s'", p_dialog->psz_title );
669 deleteLater(); // May not work at this point (event loop can be ended)
670 p_dialog->p_sys_intf = NULL;
671 vlc_cond_signal( &p_dialog->cond );