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 msg_Dbg( p_intf, "ExtensionsDialogProvider is quitting..." );
64 var_DelCallback( p_intf, "dialog-extension", DialogCallback, NULL );
68 * Note: Lock on p_dialog->lock must be held. */
69 ExtensionDialog* ExtensionsDialogProvider::CreateExtDialog(
70 extension_dialog_t *p_dialog )
72 ExtensionDialog *dialog = new ExtensionDialog( p_intf,
75 p_dialog->p_sys_intf = (void*) dialog;
76 CONNECT( dialog, destroyDialog( extension_dialog_t* ),
77 this, DestroyExtDialog( extension_dialog_t* ) );
82 * Note: Lock on p_dialog->lock must be held. */
83 int ExtensionsDialogProvider::DestroyExtDialog( extension_dialog_t *p_dialog )
86 ExtensionDialog *dialog = ( ExtensionDialog* ) p_dialog->p_sys_intf;
90 p_dialog->p_sys_intf = NULL;
91 vlc_cond_signal( &p_dialog->cond );
96 * Update/Create/Destroy a dialog
98 ExtensionDialog* ExtensionsDialogProvider::UpdateExtDialog(
99 extension_dialog_t *p_dialog )
103 ExtensionDialog *dialog = ( ExtensionDialog* ) p_dialog->p_sys_intf;
104 if( p_dialog->b_kill && !dialog )
106 /* This extension could not be activated properly but tried
107 to create a dialog. We must ignore it. */
111 vlc_mutex_lock( &p_dialog->lock );
112 if( !p_dialog->b_kill && !dialog )
114 dialog = CreateExtDialog( p_dialog );
115 dialog->setVisible( true );
117 else if( !p_dialog->b_kill && dialog )
119 dialog->has_lock = true;
120 dialog->UpdateWidgets();
121 dialog->has_lock = false;
123 else if( p_dialog->b_kill )
125 DestroyExtDialog( p_dialog );
127 vlc_cond_signal( &p_dialog->cond );
128 vlc_mutex_unlock( &p_dialog->lock );
133 * Ask the dialog manager to create/update/kill the dialog. Thread-safe.
135 void ExtensionsDialogProvider::ManageDialog( extension_dialog_t *p_dialog )
138 ExtensionsManager *extMgr = ExtensionsManager::getInstance( p_intf );
139 assert( extMgr != NULL );
140 if( !extMgr->isUnloading() )
141 emit SignalDialog( p_dialog ); // Safe because we signal Qt thread
143 UpdateExtDialog( p_dialog ); // This is safe, we're already in Qt thread
147 * Ask the dialogs provider to create a new dialog
149 static int DialogCallback( vlc_object_t *p_this, const char *psz_variable,
150 vlc_value_t old_val, vlc_value_t new_val,
158 ExtensionsDialogProvider *p_edp = ExtensionsDialogProvider::getInstance();
161 if( !new_val.p_address )
164 extension_dialog_t *p_dialog = ( extension_dialog_t* ) new_val.p_address;
165 p_edp->ManageDialog( p_dialog );
170 ExtensionDialog::ExtensionDialog( intf_thread_t *_p_intf,
171 extensions_manager_t *p_mgr,
172 extension_dialog_t *_p_dialog )
173 : QDialog( NULL ), p_intf( _p_intf ), p_extensions_manager( p_mgr )
174 , p_dialog( _p_dialog ), has_lock(false)
177 CONNECT( ExtensionsDialogProvider::getInstance(), destroyed(),
178 this, deleteLater() );
180 msg_Dbg( p_intf, "Creating a new dialog: '%s'", p_dialog->psz_title );
182 this->setWindowFlags( Qt::WindowMinMaxButtonsHint
183 | Qt::WindowCloseButtonHint );
185 this->setWindowFlags( Qt::WindowMinMaxButtonsHint );
188 this->setWindowTitle( qfu( p_dialog->psz_title ) );
190 layout = new QGridLayout( this );
191 clickMapper = new QSignalMapper( this );
192 CONNECT( clickMapper, mapped( QObject* ), this, TriggerClick( QObject* ) );
193 inputMapper = new QSignalMapper( this );
194 CONNECT( inputMapper, mapped( QObject* ), this, SyncInput( QObject* ) );
195 selectMapper = new QSignalMapper( this );
196 CONNECT( selectMapper, mapped( QObject* ), this, SyncSelection(QObject*) );
201 ExtensionDialog::~ExtensionDialog()
203 msg_Dbg( p_intf, "Deleting extension dialog '%s'", qtu(windowTitle()) );
204 /* Delete all widgets */
205 extension_widget_t *p_widget;
206 p_dialog->b_kill = true;
207 p_dialog->p_sys_intf = NULL;
208 vlc_cond_signal( &p_dialog->cond );
211 QWidget* ExtensionDialog::CreateWidget( extension_widget_t *p_widget )
213 QLabel *label = NULL;
214 QPushButton *button = NULL;
215 QTextBrowser *textArea = NULL;
216 QLineEdit *textInput = NULL;
217 QCheckBox *checkBox = NULL;
218 QComboBox *comboBox = NULL;
219 QListWidget *list = NULL;
220 struct extension_widget_t::extension_widget_value_t *p_value = NULL;
222 assert( p_widget->p_sys_intf == NULL );
224 switch( p_widget->type )
226 case EXTENSION_WIDGET_LABEL:
227 label = new QLabel( qfu( p_widget->psz_text ), this );
228 p_widget->p_sys_intf = label;
229 label->setTextFormat( Qt::RichText );
230 //label->setFixedHeight( label->sizeHint().height );
233 case EXTENSION_WIDGET_BUTTON:
234 button = new QPushButton( qfu( p_widget->psz_text ), this );
235 clickMapper->setMapping( button, new WidgetMapper( p_widget ) );
236 CONNECT( button, clicked(), clickMapper, map() );
237 p_widget->p_sys_intf = button;
240 case EXTENSION_WIDGET_IMAGE:
241 label = new QLabel( this );
242 label->setPixmap( QPixmap( qfu( p_widget->psz_text ) ) );
243 if( p_widget->i_width > 0 )
244 label->setMaximumWidth( p_widget->i_width );
245 if( p_widget->i_height > 0 )
246 label->setMaximumHeight( p_widget->i_height );
247 label->setScaledContents( true );
248 p_widget->p_sys_intf = label;
251 case EXTENSION_WIDGET_HTML:
252 textArea = new QTextBrowser( this );
253 textArea->setOpenExternalLinks( true );
254 textArea->setHtml( qfu( p_widget->psz_text ) );
255 p_widget->p_sys_intf = textArea;
258 case EXTENSION_WIDGET_TEXT_FIELD:
259 textInput = new QLineEdit( this );
260 textInput->setText( qfu( p_widget->psz_text ) );
261 textInput->setReadOnly( false );
262 textInput->setEchoMode( QLineEdit::Normal );
263 inputMapper->setMapping( textInput, new WidgetMapper( p_widget ) );
264 /// @note: maybe it would be wiser to use textEdited here?
265 CONNECT( textInput, textChanged(const QString &),
266 inputMapper, map() );
267 p_widget->p_sys_intf = textInput;
270 case EXTENSION_WIDGET_PASSWORD:
271 textInput = new QLineEdit( this );
272 textInput->setText( qfu( p_widget->psz_text ) );
273 textInput->setReadOnly( false );
274 textInput->setEchoMode( QLineEdit::Password );
275 inputMapper->setMapping( textInput, new WidgetMapper( p_widget ) );
276 /// @note: maybe it would be wiser to use textEdited here?
277 CONNECT( textInput, textChanged(const QString &),
278 inputMapper, map() );
279 p_widget->p_sys_intf = textInput;
282 case EXTENSION_WIDGET_CHECK_BOX:
283 checkBox = new QCheckBox( this );
284 checkBox->setText( qfu( p_widget->psz_text ) );
285 checkBox->setChecked( p_widget->b_checked );
286 clickMapper->setMapping( checkBox, new WidgetMapper( p_widget ) );
287 CONNECT( checkBox, stateChanged( int ), clickMapper, map() );
288 p_widget->p_sys_intf = checkBox;
291 case EXTENSION_WIDGET_DROPDOWN:
292 comboBox = new QComboBox( this );
293 comboBox->setEditable( false );
294 for( p_value = p_widget->p_values;
296 p_value = p_value->p_next )
298 comboBox->addItem( qfu( p_value->psz_text ), p_value->i_id );
300 /* Set current item */
301 if( p_widget->psz_text )
303 int idx = comboBox->findText( qfu( p_widget->psz_text ) );
305 comboBox->setCurrentIndex( idx );
307 selectMapper->setMapping( comboBox, new WidgetMapper( p_widget ) );
308 CONNECT( comboBox, currentIndexChanged( const QString& ),
309 selectMapper, map() );
312 case EXTENSION_WIDGET_LIST:
313 list = new QListWidget( this );
314 list->setSelectionMode( QAbstractItemView::ExtendedSelection );
315 for( p_value = p_widget->p_values;
317 p_value = p_value->p_next )
319 QListWidgetItem *item =
320 new QListWidgetItem( qfu( p_value->psz_text ) );
321 item->setData( Qt::UserRole, p_value->i_id );
322 list->addItem( item );
324 selectMapper->setMapping( list, new WidgetMapper( p_widget ) );
325 CONNECT( list, itemSelectionChanged(),
326 selectMapper, map() );
330 msg_Err( p_intf, "Widget type %d unknown", p_widget->type );
336 * Forward click event to the extension
337 * @param object A WidgetMapper, whose data() is the p_widget
339 int ExtensionDialog::TriggerClick( QObject *object )
341 assert( object != NULL );
342 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
343 extension_widget_t *p_widget = mapping->getWidget();
345 QCheckBox *checkBox = NULL;
346 int i_ret = VLC_EGENERIC;
348 bool lockedHere = false;
351 vlc_mutex_lock( &p_dialog->lock );
356 switch( p_widget->type )
358 case EXTENSION_WIDGET_BUTTON:
359 i_ret = extension_WidgetClicked( p_dialog, p_widget );
362 case EXTENSION_WIDGET_CHECK_BOX:
363 checkBox = static_cast< QCheckBox* >( p_widget->p_sys_intf );
364 p_widget->b_checked = checkBox->isChecked();
369 msg_Dbg( p_intf, "A click event was triggered by a wrong widget" );
375 vlc_mutex_unlock( &p_dialog->lock );
383 * Synchronize psz_text with the widget's text() value on update
384 * @param object A WidgetMapper
386 void ExtensionDialog::SyncInput( QObject *object )
388 assert( object != NULL );
390 bool lockedHere = false;
393 vlc_mutex_lock( &p_dialog->lock );
398 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
399 extension_widget_t *p_widget = mapping->getWidget();
400 assert( p_widget->type == EXTENSION_WIDGET_TEXT_FIELD
401 || p_widget->type == EXTENSION_WIDGET_PASSWORD );
402 /* Synchronize psz_text with the new value */
403 QLineEdit *widget = static_cast< QLineEdit* >( p_widget->p_sys_intf );
404 char *psz_text = widget->text().isNull() ? NULL : strdup( qtu( widget->text() ) );
405 free( p_widget->psz_text );
406 p_widget->psz_text = psz_text;
410 vlc_mutex_unlock( &p_dialog->lock );
416 * Synchronize parameter b_selected in the values list
417 * @param object A WidgetMapper
419 void ExtensionDialog::SyncSelection( QObject *object )
421 assert( object != NULL );
422 struct extension_widget_t::extension_widget_value_t *p_value;
424 bool lockedHere = false;
427 vlc_mutex_lock( &p_dialog->lock );
432 WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
433 extension_widget_t *p_widget = mapping->getWidget();
434 assert( p_widget->type == EXTENSION_WIDGET_DROPDOWN
435 || p_widget->type == EXTENSION_WIDGET_LIST );
437 if( p_widget->type == EXTENSION_WIDGET_DROPDOWN )
439 QComboBox *combo = static_cast< QComboBox* >( p_widget->p_sys_intf );
440 for( p_value = p_widget->p_values;
442 p_value = p_value->p_next )
444 // if( !qstrcmp( p_value->psz_text, qtu( combo->currentText() ) ) )
445 if( combo->itemData( combo->currentIndex(), Qt::UserRole ).toInt()
448 p_value->b_selected = true;
452 p_value->b_selected = false;
455 free( p_widget->psz_text );
456 p_widget->psz_text = strdup( qtu( combo->currentText() ) );
458 else if( p_widget->type == EXTENSION_WIDGET_LIST )
460 QListWidget *list = static_cast<QListWidget*>( p_widget->p_sys_intf );
461 QList<QListWidgetItem *> selection = list->selectedItems();
462 for( p_value = p_widget->p_values;
464 p_value = p_value->p_next )
466 bool b_selected = false;
467 foreach( const QListWidgetItem *item, selection )
469 // if( !qstrcmp( qtu( item->text() ), p_value->psz_text ) )
470 if( item->data( Qt::UserRole ).toInt() == p_value->i_id )
476 p_value->b_selected = b_selected;
482 vlc_mutex_unlock( &p_dialog->lock );
487 void ExtensionDialog::UpdateWidgets()
490 extension_widget_t *p_widget;
491 FOREACH_ARRAY( p_widget, p_dialog->widgets )
493 if( !p_widget ) continue; /* Some widgets may be NULL at this point */
495 int row = p_widget->i_row - 1;
496 int col = p_widget->i_column - 1;
499 row = layout->rowCount();
503 col = layout->columnCount();
504 int hsp = __MAX( 1, p_widget->i_horiz_span );
505 int vsp = __MAX( 1, p_widget->i_vert_span );
506 if( !p_widget->p_sys_intf && !p_widget->b_kill )
508 widget = CreateWidget( p_widget );
511 msg_Warn( p_intf, "Could not create a widget for dialog %s",
512 p_dialog->psz_title );
515 widget->setVisible( !p_widget->b_hide );
516 layout->addWidget( widget, row, col, vsp, hsp );
517 if( ( p_widget->i_width > 0 ) && ( p_widget->i_height > 0 ) )
518 widget->resize( p_widget->i_width, p_widget->i_height );
519 p_widget->p_sys_intf = widget;
520 this->resize( sizeHint() );
522 else if( p_widget->p_sys_intf && !p_widget->b_kill
523 && p_widget->b_update )
525 widget = UpdateWidget( p_widget );
528 msg_Warn( p_intf, "Could not update a widget for dialog %s",
529 p_dialog->psz_title );
532 widget->setVisible( !p_widget->b_hide );
533 layout->addWidget( widget, row, col, vsp, hsp );
534 if( ( p_widget->i_width > 0 ) && ( p_widget->i_height > 0 ) )
535 widget->resize( p_widget->i_width, p_widget->i_height );
536 p_widget->p_sys_intf = widget;
537 this->resize( sizeHint() );
539 /* Do not update again */
540 p_widget->b_update = false;
542 else if( p_widget->p_sys_intf && p_widget->b_kill )
544 DestroyWidget( p_widget );
545 p_widget->p_sys_intf = NULL;
546 this->resize( sizeHint() );
552 QWidget* ExtensionDialog::UpdateWidget( extension_widget_t *p_widget )
554 QLabel *label = NULL;
555 QPushButton *button = NULL;
556 QTextBrowser *textArea = NULL;
557 QLineEdit *textInput = NULL;
558 QCheckBox *checkBox = NULL;
559 QComboBox *comboBox = NULL;
560 QListWidget *list = NULL;
561 struct extension_widget_t::extension_widget_value_t *p_value = NULL;
563 assert( p_widget->p_sys_intf != NULL );
565 switch( p_widget->type )
567 case EXTENSION_WIDGET_LABEL:
568 label = static_cast< QLabel* >( p_widget->p_sys_intf );
569 label->setText( qfu( p_widget->psz_text ) );
572 case EXTENSION_WIDGET_BUTTON:
573 // FIXME: looks like removeMappings does not work
574 button = static_cast< QPushButton* >( p_widget->p_sys_intf );
575 button->setText( qfu( p_widget->psz_text ) );
576 clickMapper->removeMappings( button );
577 clickMapper->setMapping( button, new WidgetMapper( p_widget ) );
578 CONNECT( button, clicked(), clickMapper, map() );
581 case EXTENSION_WIDGET_IMAGE:
582 label = static_cast< QLabel* >( p_widget->p_sys_intf );
583 label->setPixmap( QPixmap( qfu( p_widget->psz_text ) ) );
586 case EXTENSION_WIDGET_HTML:
587 textArea = static_cast< QTextBrowser* >( p_widget->p_sys_intf );
588 textArea->setHtml( qfu( p_widget->psz_text ) );
591 case EXTENSION_WIDGET_TEXT_FIELD:
592 textInput = static_cast< QLineEdit* >( p_widget->p_sys_intf );
593 textInput->setText( qfu( p_widget->psz_text ) );
596 case EXTENSION_WIDGET_PASSWORD:
597 textInput = static_cast< QLineEdit* >( p_widget->p_sys_intf );
598 textInput->setText( qfu( p_widget->psz_text ) );
601 case EXTENSION_WIDGET_CHECK_BOX:
602 checkBox = static_cast< QCheckBox* >( p_widget->p_sys_intf );
603 checkBox->setText( qfu( p_widget->psz_text ) );
604 checkBox->setChecked( p_widget->b_checked );
607 case EXTENSION_WIDGET_DROPDOWN:
608 comboBox = static_cast< QComboBox* >( p_widget->p_sys_intf );
610 for( p_value = p_widget->p_values;
612 p_value = p_value->p_next )
614 comboBox->addItem( qfu( p_value->psz_text ), p_value->i_id );
616 /* Set current item */
617 if( p_widget->psz_text )
619 int idx = comboBox->findText( qfu( p_widget->psz_text ) );
621 comboBox->setCurrentIndex( idx );
625 case EXTENSION_WIDGET_LIST:
626 list = static_cast< QListWidget* >( p_widget->p_sys_intf );
628 for( p_value = p_widget->p_values;
630 p_value = p_value->p_next )
632 QListWidgetItem *item =
633 new QListWidgetItem( qfu( p_value->psz_text ) );
634 item->setData( Qt::UserRole, p_value->i_id );
635 list->addItem( item );
640 msg_Err( p_intf, "Widget type %d unknown", p_widget->type );
645 void ExtensionDialog::DestroyWidget( extension_widget_t *p_widget,
648 assert( p_widget && p_widget->b_kill );
649 QWidget *widget = static_cast< QWidget* >( p_widget->p_sys_intf );
651 p_widget->p_sys_intf = NULL;
653 vlc_cond_signal( &p_dialog->cond );
656 /** Implement closeEvent() in order to intercept the event */
657 void ExtensionDialog::closeEvent( QCloseEvent *event )
659 assert( p_dialog != NULL );
660 msg_Dbg( p_intf, "Dialog '%s' received a closeEvent",
661 p_dialog->psz_title );
662 extension_DialogClosed( p_dialog );
663 p_dialog->p_sys_intf = NULL;