]> git.sesse.net Git - vlc/blob - modules/gui/qt4/dialogs/extensions.cpp
Extensions/Qt: dialogs
[vlc] / modules / gui / qt4 / dialogs / extensions.cpp
1 /*****************************************************************************
2  * extensions.cpp: Extensions manager for Qt: dialogs manager
3  ****************************************************************************
4  * Copyright (C) 2009-2010 VideoLAN and authors
5  * $Id$
6  *
7  * Authors: Jean-Philippe AndrĂ© < jpeg # videolan.org >
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23
24 #include "extensions.hpp"
25 #include "../extensions_manager.hpp" // for isUnloading()
26
27 #include <vlc_dialog.h>
28
29 #include <QGridLayout>
30 #include <QPushButton>
31 #include <QSignalMapper>
32 #include <QLabel>
33 #include <QPixmap>
34 #include <QLineEdit>
35 #include <QTextEdit>
36 #include <QCheckBox>
37 #include <QListWidget>
38 #include <QComboBox>
39 #include <QCloseEvent>
40
41 ExtensionsDialogProvider *ExtensionsDialogProvider::instance = NULL;
42
43 static int DialogCallback( vlc_object_t *p_this, const char *psz_variable,
44                            vlc_value_t old_val, vlc_value_t new_val,
45                            void *param );
46
47
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 )
51 {
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 );
56
57     CONNECT( this, SignalDialog( extension_dialog_t* ),
58              this, UpdateDialog( extension_dialog_t* ) );
59 }
60
61 ExtensionsDialogProvider::~ExtensionsDialogProvider()
62 {
63     var_DelCallback( p_intf, "dialog-extension", DialogCallback, NULL );
64 }
65
66 /** Create a dialog
67  * Note: Lock on p_dialog->lock must be held. */
68 ExtensionDialog* ExtensionsDialogProvider::CreateDialog( extension_dialog_t *p_dialog )
69 {
70     ExtensionDialog *dialog = new ExtensionDialog( p_intf,
71                                                    p_extensions_manager,
72                                                    p_dialog );
73     p_dialog->p_sys_intf = (void*) dialog;
74     CONNECT( dialog, destroyDialog( extension_dialog_t* ),
75              this, DestroyDialog( extension_dialog_t* ) );
76     return dialog;
77 }
78
79 /** Destroy a dialog
80  * Note: Lock on p_dialog->lock must be held. */
81 int ExtensionsDialogProvider::DestroyDialog( extension_dialog_t *p_dialog )
82 {
83     assert( p_dialog );
84     ExtensionDialog *dialog = ( ExtensionDialog* ) p_dialog->p_sys_intf;
85     if( !dialog )
86         return VLC_EGENERIC;
87     delete dialog;
88     p_dialog->p_sys_intf = NULL;
89     return VLC_SUCCESS;
90 }
91
92 /**
93  * Update/Create/Destroy a dialog
94  **/
95 ExtensionDialog* ExtensionsDialogProvider::UpdateDialog( extension_dialog_t *p_dialog )
96 {
97     assert( p_dialog );
98
99     ExtensionDialog *dialog = ( ExtensionDialog* ) p_dialog->p_sys_intf;
100     if( p_dialog->b_kill && !dialog )
101     {
102         /* This extension could not be activated properly but tried
103            to create a dialog. We must ignore it. */
104         return NULL;
105     }
106
107     vlc_mutex_lock( &p_dialog->lock );
108     if( !p_dialog->b_kill && !dialog )
109     {
110         dialog = CreateDialog( p_dialog );
111         dialog->setVisible( !p_dialog->b_hide );
112     }
113     else if( !p_dialog->b_kill && dialog )
114     {
115         dialog->has_lock = true;
116         dialog->UpdateWidgets();
117         dialog->has_lock = false;
118         dialog->setVisible( !p_dialog->b_hide );
119     }
120     else if( p_dialog->b_kill )
121     {
122         DestroyDialog( p_dialog );
123     }
124     vlc_cond_signal( &p_dialog->cond );
125     vlc_mutex_unlock( &p_dialog->lock );
126     return dialog;
127 }
128
129 /**
130  * Ask the dialog manager to create/update/kill the dialog. Thread-safe.
131  **/
132 void ExtensionsDialogProvider::ManageDialog( extension_dialog_t *p_dialog )
133 {
134     assert( p_dialog );
135     ExtensionsManager *extMgr = ExtensionsManager::getInstance( p_intf );
136     assert( extMgr != NULL );
137     if( !extMgr->isUnloading() )
138         emit SignalDialog( p_dialog ); // Safe because we signal Qt thread
139     else
140         UpdateDialog( p_dialog ); // This is safe, we're already in Qt thread
141 }
142
143 /**
144  * Ask the dialogs provider to create a new dialog
145  **/
146 static int DialogCallback( vlc_object_t *p_this, const char *psz_variable,
147                            vlc_value_t old_val, vlc_value_t new_val,
148                            void *param )
149 {
150     (void) p_this;
151     (void) psz_variable;
152     (void) old_val;
153     (void) param;
154
155     ExtensionsDialogProvider *p_edp = ExtensionsDialogProvider::getInstance();
156     if( !p_edp )
157         return VLC_EGENERIC;
158     if( !new_val.p_address )
159         return VLC_EGENERIC;
160
161     extension_dialog_t *p_dialog = ( extension_dialog_t* ) new_val.p_address;
162     p_edp->ManageDialog( p_dialog );
163     return VLC_SUCCESS;
164 }
165
166
167 ExtensionDialog::ExtensionDialog( intf_thread_t *_p_intf,
168                                   extensions_manager_t *p_mgr,
169                                   extension_dialog_t *_p_dialog )
170          : QDialog( NULL ), p_intf( _p_intf ), p_extensions_manager( p_mgr )
171          , p_dialog( _p_dialog ), has_lock(false)
172 {
173     assert( p_dialog );
174
175     msg_Dbg( p_intf, "Creating a new dialog: '%s'", p_dialog->psz_title );
176     this->setWindowFlags( Qt::WindowMinMaxButtonsHint
177                         | Qt::WindowCloseButtonHint );
178     this->setWindowTitle( qfu( p_dialog->psz_title ) );
179
180     layout = new QGridLayout( this );
181     clickMapper = new QSignalMapper( this );
182     CONNECT( clickMapper, mapped( QObject* ), this, TriggerClick( QObject* ) );
183     inputMapper = new QSignalMapper( this );
184     CONNECT( inputMapper, mapped( QObject* ), this, SyncInput( QObject* ) );
185     selectMapper = new QSignalMapper( this );
186     CONNECT( selectMapper, mapped( QObject* ), this, SyncSelection(QObject*) );
187
188     UpdateWidgets();
189 }
190
191 ExtensionDialog::~ExtensionDialog()
192 {
193     msg_Dbg( p_intf, "Deleting extension dialog '%s'", qtu(windowTitle()) );
194     p_dialog->p_sys_intf = NULL;
195 }
196
197 QWidget* ExtensionDialog::CreateWidget( extension_widget_t *p_widget )
198 {
199     QLabel *label = NULL;
200     QPushButton *button = NULL;
201     QTextEdit *textArea = NULL;
202     QLineEdit *textInput = NULL;
203     QCheckBox *checkBox = NULL;
204     QComboBox *comboBox = NULL;
205     QListWidget *list = NULL;
206     struct extension_widget_t::extension_widget_value_t *p_value = NULL;
207
208     assert( p_widget->p_sys_intf == NULL );
209
210     switch( p_widget->type )
211     {
212         case EXTENSION_WIDGET_LABEL:
213             label = new QLabel( qfu( p_widget->psz_text ), this );
214             p_widget->p_sys_intf = label;
215             label->setTextFormat( Qt::RichText );
216             //label->setFixedHeight( label->sizeHint().height );
217             return label;
218
219         case EXTENSION_WIDGET_BUTTON:
220             button = new QPushButton( qfu( p_widget->psz_text ), this );
221             clickMapper->setMapping( button, new WidgetMapper( p_widget ) );
222             CONNECT( button, clicked(), clickMapper, map() );
223             p_widget->p_sys_intf = button;
224             return button;
225
226         case EXTENSION_WIDGET_IMAGE:
227             label = new QLabel( this );
228             label->setPixmap( QPixmap( qfu( p_widget->psz_text ) ) );
229             if( p_widget->i_width > 0 )
230                 label->setMaximumWidth( p_widget->i_width );
231             if( p_widget->i_height > 0 )
232                 label->setMaximumHeight( p_widget->i_height );
233             label->setScaledContents( true );
234             p_widget->p_sys_intf = label;
235             return label;
236
237         case EXTENSION_WIDGET_HTML:
238             textArea = new QTextEdit( this );
239             textArea->setAcceptRichText( true );
240             textArea->setReadOnly( true );
241             textArea->setHtml( qfu( p_widget->psz_text ) );
242             p_widget->p_sys_intf = textArea;
243             return textArea;
244
245         case EXTENSION_WIDGET_TEXT_FIELD:
246             textInput = new QLineEdit( this );
247             textInput->setText( qfu( p_widget->psz_text ) );
248             textInput->setReadOnly( false );
249             textInput->setEchoMode( QLineEdit::Normal );
250             inputMapper->setMapping( textInput, new WidgetMapper( p_widget ) );
251             /// @note: maybe it would be wiser to use textEdited here?
252             CONNECT( textInput, textChanged(const QString &),
253                      inputMapper, map() );
254             p_widget->p_sys_intf = textInput;
255             return textInput;
256
257         case EXTENSION_WIDGET_PASSWORD:
258             textInput = new QLineEdit( this );
259             textInput->setText( qfu( p_widget->psz_text ) );
260             textInput->setReadOnly( false );
261             textInput->setEchoMode( QLineEdit::Password );
262             inputMapper->setMapping( textInput, new WidgetMapper( p_widget ) );
263             /// @note: maybe it would be wiser to use textEdited here?
264             CONNECT( textInput, textChanged(const QString &),
265                      inputMapper, map() );
266             p_widget->p_sys_intf = textInput;
267             return textInput;
268
269         case EXTENSION_WIDGET_CHECK_BOX:
270             checkBox = new QCheckBox( this );
271             checkBox->setText( qfu( p_widget->psz_text ) );
272             checkBox->setChecked( p_widget->b_checked );
273             clickMapper->setMapping( checkBox, new WidgetMapper( p_widget ) );
274             CONNECT( checkBox, stateChanged( int ), clickMapper, map() );
275             p_widget->p_sys_intf = checkBox;
276             return checkBox;
277
278         case EXTENSION_WIDGET_DROPDOWN:
279             comboBox = new QComboBox( this );
280             comboBox->setEditable( false );
281             for( p_value = p_widget->p_values;
282                  p_value != NULL;
283                  p_value = p_value->p_next )
284             {
285                 comboBox->addItem( qfu( p_value->psz_text ), p_value->i_id );
286             }
287             /* Set current item */
288             if( p_widget->psz_text )
289             {
290                 int idx = comboBox->findText( qfu( p_widget->psz_text ) );
291                 if( idx >= 0 )
292                     comboBox->setCurrentIndex( idx );
293             }
294             selectMapper->setMapping( comboBox, new WidgetMapper( p_widget ) );
295             CONNECT( comboBox, currentIndexChanged( const QString& ),
296                      selectMapper, map() );
297             return comboBox;
298
299         case EXTENSION_WIDGET_LIST:
300             list = new QListWidget( this );
301             list->setSelectionMode( QAbstractItemView::MultiSelection );
302             for( p_value = p_widget->p_values;
303                  p_value != NULL;
304                  p_value = p_value->p_next )
305             {
306                 QListWidgetItem *item =
307                     new QListWidgetItem( qfu( p_value->psz_text ) );
308                 item->setData( Qt::UserRole, p_value->i_id );
309                 list->addItem( item );
310             }
311             selectMapper->setMapping( list, new WidgetMapper( p_widget ) );
312             CONNECT( list, itemSelectionChanged(),
313                      selectMapper, map() );
314             return list;
315
316         default:
317             msg_Err( p_intf, "Widget type %d unknown" );
318             return NULL;
319     }
320 }
321
322 /**
323  * Forward click event to the extension
324  * @param object A WidgetMapper, whose data() is the p_widget
325  **/
326 int ExtensionDialog::TriggerClick( QObject *object )
327 {
328     assert( object != NULL );
329     WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
330     extension_widget_t *p_widget = mapping->getWidget();
331
332     QCheckBox *checkBox = NULL;
333     int i_ret = VLC_EGENERIC;
334
335     bool lockedHere = false;
336     if( !has_lock )
337     {
338         vlc_mutex_lock( &p_dialog->lock );
339         has_lock = true;
340         lockedHere = true;
341     }
342
343     switch( p_widget->type )
344     {
345         case EXTENSION_WIDGET_BUTTON:
346             i_ret = extension_WidgetClicked( p_dialog, p_widget );
347             break;
348
349         case EXTENSION_WIDGET_CHECK_BOX:
350             checkBox = static_cast< QCheckBox* >( p_widget->p_sys_intf );
351             p_widget->b_checked = checkBox->isChecked();
352             i_ret = VLC_SUCCESS;
353             break;
354
355         default:
356             msg_Dbg( p_intf, "A click event was triggered by a wrong widget" );
357             break;
358     }
359
360     if( lockedHere )
361     {
362         vlc_mutex_unlock( &p_dialog->lock );
363         has_lock = false;
364     }
365
366     return i_ret;
367 }
368
369 /**
370  * Synchronize psz_text with the widget's text() value on update
371  * @param object A WidgetMapper
372  **/
373 void ExtensionDialog::SyncInput( QObject *object )
374 {
375     assert( object != NULL );
376
377     bool lockedHere = false;
378     if( !has_lock )
379     {
380         vlc_mutex_lock( &p_dialog->lock );
381         has_lock = true;
382         lockedHere = true;
383     }
384
385     WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
386     extension_widget_t *p_widget = mapping->getWidget();
387     assert( p_widget->type == EXTENSION_WIDGET_TEXT_FIELD
388             || p_widget->type == EXTENSION_WIDGET_PASSWORD );
389     /* Synchronize psz_text with the new value */
390     QLineEdit *widget = static_cast< QLineEdit* >( p_widget->p_sys_intf );
391     char *psz_text = qstrdup( qtu( widget->text() ) );
392     free( p_widget->psz_text );
393     p_widget->psz_text = psz_text;
394
395     if( lockedHere )
396     {
397         vlc_mutex_unlock( &p_dialog->lock );
398         has_lock = false;
399     }
400 }
401
402 /**
403  * Synchronize parameter b_selected in the values list
404  * @param object A WidgetMapper
405  **/
406 void ExtensionDialog::SyncSelection( QObject *object )
407 {
408     assert( object != NULL );
409     struct extension_widget_t::extension_widget_value_t *p_value;
410
411     bool lockedHere = false;
412     if( !has_lock )
413     {
414         vlc_mutex_lock( &p_dialog->lock );
415         has_lock = true;
416         lockedHere = true;
417     }
418
419     WidgetMapper *mapping = static_cast< WidgetMapper* >( object );
420     extension_widget_t *p_widget = mapping->getWidget();
421     assert( p_widget->type == EXTENSION_WIDGET_DROPDOWN
422             || p_widget->type == EXTENSION_WIDGET_LIST );
423
424     if( p_widget->type == EXTENSION_WIDGET_DROPDOWN )
425     {
426         QComboBox *combo = static_cast< QComboBox* >( p_widget->p_sys_intf );
427         for( p_value = p_widget->p_values;
428              p_value != NULL;
429              p_value = p_value->p_next )
430         {
431 //             if( !qstrcmp( p_value->psz_text, qtu( combo->currentText() ) ) )
432             if( combo->itemData( combo->currentIndex(), Qt::UserRole ).toInt()
433                 == p_value->i_id )
434             {
435                 p_value->b_selected = true;
436             }
437             else
438             {
439                 p_value->b_selected = false;
440             }
441         }
442         free( p_widget->psz_text );
443         p_widget->psz_text = strdup( qtu( combo->currentText() ) );
444     }
445     else if( p_widget->type == EXTENSION_WIDGET_LIST )
446     {
447         QListWidget *list = static_cast<QListWidget*>( p_widget->p_sys_intf );
448         QList<QListWidgetItem *> selection = list->selectedItems();
449         for( p_value = p_widget->p_values;
450              p_value != NULL;
451              p_value = p_value->p_next )
452         {
453             bool b_selected = false;
454             foreach( const QListWidgetItem *item, selection )
455             {
456 //                 if( !qstrcmp( qtu( item->text() ), p_value->psz_text ) )
457                 if( item->data( Qt::UserRole ).toInt() == p_value->i_id )
458                 {
459                     b_selected = true;
460                     break;
461                 }
462             }
463             p_value->b_selected = b_selected;
464         }
465     }
466
467     if( lockedHere )
468     {
469         vlc_mutex_unlock( &p_dialog->lock );
470         has_lock = false;
471     }
472 }
473
474 void ExtensionDialog::UpdateWidgets()
475 {
476     assert( p_dialog );
477     extension_widget_t *p_widget;
478     FOREACH_ARRAY( p_widget, p_dialog->widgets )
479     {
480         if( !p_widget ) continue; /* Some widgets may be NULL at this point */
481         QWidget *widget;
482         int row = p_widget->i_row - 1;
483         int col = p_widget->i_column - 1;
484         if( row < 0 )
485         {
486             row = layout->rowCount();
487             col = 0;
488         }
489         else if( col < 0 )
490             col = layout->columnCount();
491         int hsp = __MAX( 1, p_widget->i_horiz_span );
492         int vsp = __MAX( 1, p_widget->i_vert_span );
493         if( !p_widget->p_sys_intf && !p_widget->b_kill )
494         {
495             widget = CreateWidget( p_widget );
496             if( !widget )
497             {
498                 msg_Warn( p_intf, "Could not create a widget for dialog %s",
499                           p_dialog->psz_title );
500                 continue;
501             }
502             widget->setVisible( !p_widget->b_hide );
503             layout->addWidget( widget, row, col, vsp, hsp );
504             if( ( p_widget->i_width > 0 ) && ( p_widget->i_height > 0 ) )
505                 widget->resize( p_widget->i_width, p_widget->i_height );
506             p_widget->p_sys_intf = widget;
507             this->resize( sizeHint() );
508         }
509         else if( p_widget->p_sys_intf && !p_widget->b_kill
510                  && p_widget->b_update )
511         {
512             widget = UpdateWidget( p_widget );
513             if( !widget )
514             {
515                 msg_Warn( p_intf, "Could not update a widget for dialog %s",
516                           p_dialog->psz_title );
517                 return;
518             }
519             widget->setVisible( !p_widget->b_hide );
520             layout->addWidget( widget, row, col, vsp, hsp );
521             if( ( p_widget->i_width > 0 ) && ( p_widget->i_height > 0 ) )
522                 widget->resize( p_widget->i_width, p_widget->i_height );
523             p_widget->p_sys_intf = widget;
524             this->resize( sizeHint() );
525
526             /* Do not update again */
527             p_widget->b_update = false;
528         }
529         else if( p_widget->p_sys_intf && p_widget->b_kill )
530         {
531             DestroyWidget( p_widget );
532             p_widget->p_sys_intf = NULL;
533             this->resize( sizeHint() );
534         }
535     }
536     FOREACH_END()
537 }
538
539 QWidget* ExtensionDialog::UpdateWidget( extension_widget_t *p_widget )
540 {
541     QLabel *label = NULL;
542     QPushButton *button = NULL;
543     QTextEdit *textArea = NULL;
544     QLineEdit *textInput = NULL;
545     QCheckBox *checkBox = NULL;
546     QComboBox *comboBox = NULL;
547     QListWidget *list = NULL;
548     struct extension_widget_t::extension_widget_value_t *p_value = NULL;
549
550     assert( p_widget->p_sys_intf != NULL );
551
552     switch( p_widget->type )
553     {
554         case EXTENSION_WIDGET_LABEL:
555             label = static_cast< QLabel* >( p_widget->p_sys_intf );
556             label->setText( qfu( p_widget->psz_text ) );
557             return label;
558
559         case EXTENSION_WIDGET_BUTTON:
560             // FIXME: looks like removeMappings does not work
561             button = static_cast< QPushButton* >( p_widget->p_sys_intf );
562             button->setText( qfu( p_widget->psz_text ) );
563             clickMapper->removeMappings( button );
564             clickMapper->setMapping( button, new WidgetMapper( p_widget ) );
565             CONNECT( button, clicked(), clickMapper, map() );
566             return button;
567
568         case EXTENSION_WIDGET_IMAGE:
569             label = static_cast< QLabel* >( p_widget->p_sys_intf );
570             label->setPixmap( QPixmap( qfu( p_widget->psz_text ) ) );
571             return label;
572
573         case EXTENSION_WIDGET_HTML:
574             textArea = static_cast< QTextEdit* >( p_widget->p_sys_intf );
575             textArea->setHtml( qfu( p_widget->psz_text ) );
576             return textArea;
577
578         case EXTENSION_WIDGET_TEXT_FIELD:
579             textInput = static_cast< QLineEdit* >( p_widget->p_sys_intf );
580             textInput->setText( qfu( p_widget->psz_text ) );
581             return textInput;
582
583         case EXTENSION_WIDGET_PASSWORD:
584             textInput = static_cast< QLineEdit* >( p_widget->p_sys_intf );
585             textInput->setText( qfu( p_widget->psz_text ) );
586             return textInput;
587
588         case EXTENSION_WIDGET_CHECK_BOX:
589             checkBox = static_cast< QCheckBox* >( p_widget->p_sys_intf );
590             checkBox->setText( qfu( p_widget->psz_text ) );
591             checkBox->setChecked( p_widget->b_checked );
592             return checkBox;
593
594         case EXTENSION_WIDGET_DROPDOWN:
595             comboBox = static_cast< QComboBox* >( p_widget->p_sys_intf );
596             comboBox->clear();
597             for( p_value = p_widget->p_values;
598                  p_value != NULL;
599                  p_value = p_value->p_next )
600             {
601                 comboBox->addItem( qfu( p_value->psz_text ), p_value->i_id );
602             }
603             /* Set current item */
604             if( p_widget->psz_text )
605             {
606                 int idx = comboBox->findText( qfu( p_widget->psz_text ) );
607                 if( idx >= 0 )
608                     comboBox->setCurrentIndex( idx );
609             }
610             return comboBox;
611
612         case EXTENSION_WIDGET_LIST:
613             list = static_cast< QListWidget* >( p_widget->p_sys_intf );
614             list->clear();
615             for( p_value = p_widget->p_values;
616                  p_value != NULL;
617                  p_value = p_value->p_next )
618             {
619                 QListWidgetItem *item =
620                         new QListWidgetItem( qfu( p_value->psz_text ) );
621                 item->setData( Qt::UserRole, p_value->i_id );
622                 list->addItem( item );
623             }
624             return list;
625
626         default:
627             msg_Err( p_intf, "Widget type %d unknown" );
628             return NULL;
629     }
630 }
631
632 void ExtensionDialog::DestroyWidget( extension_widget_t *p_widget )
633 {
634     assert( p_widget && p_widget->b_kill );
635     QWidget *widget = static_cast< QWidget* >( p_widget->p_sys_intf );
636     delete widget;
637     p_widget->p_sys_intf = NULL;
638 }
639
640 /** Implement closeEvent() in order to intercept the event */
641 void ExtensionDialog::closeEvent( QCloseEvent *event )
642 {
643     assert( p_dialog != NULL );
644     extension_DialogClosed( p_dialog );
645     event->accept();
646     emit destroyDialog( p_dialog );
647 }
648