]> git.sesse.net Git - vlc/blob - src/interface/interaction.c
Use the same comments covention
[vlc] / src / interface / interaction.c
1 /*****************************************************************************
2  * interaction.c: User interaction functions
3  *****************************************************************************
4  * Copyright (C) 2005-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Felix Kühne <fkuehne@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 /**
26  *   \file
27  *   This file contains functions related to user interaction management
28  */
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33
34 #ifdef HAVE_CONFIG_H
35 # include "config.h"
36 #endif
37
38 #include <vlc/vlc.h>
39
40 #include <stdlib.h>                                      /* free(), strtol() */
41 #include <stdio.h>                                                   /* FILE */
42 #include <string.h>
43
44 #include <vlc_interface.h>
45 #include <vlc_playlist.h>
46 #include "interface.h"
47
48 /*****************************************************************************
49  * Local prototypes
50  *****************************************************************************/
51 static void                  InteractionInit( playlist_t *p_playlist );
52 static interaction_t *       InteractionGet( vlc_object_t *p_this );
53 static void                  InteractionSearchInterface( interaction_t *
54                                                           p_interaction );
55 static interaction_dialog_t *DialogGetById( interaction_t* , int );
56 static void                  DialogDestroy( interaction_dialog_t *p_dialog );
57 static int DialogSend( vlc_object_t *p_this, interaction_dialog_t *p_dialog );
58
59 /**
60  * Destroy the interaction system
61  *
62  * \param The interaction object to destroy
63  * \return nothing
64  */
65 void intf_InteractionDestroy( interaction_t *p_interaction )
66 {
67     int i;
68     // Remove all dialogs - Interfaces must be able to clean up their data
69     for( i = p_interaction->i_dialogs -1 ; i >= 0; i-- )
70     {
71         interaction_dialog_t * p_dialog = p_interaction->pp_dialogs[i];
72         DialogDestroy( p_dialog );
73         REMOVE_ELEM( p_interaction->pp_dialogs, p_interaction->i_dialogs, i );
74     }
75     vlc_object_release( p_interaction );
76 }
77
78 /**
79  * The main interaction processing loop
80  * This function is called from the playlist loop
81  *
82  * \param p_playlist the parent playlist
83  * \return nothing
84  */
85 void intf_InteractionManage( playlist_t *p_playlist )
86 {
87     vlc_value_t val;
88     int i_index;
89     interaction_t *p_interaction = p_playlist->p_interaction;
90
91     // Nothing to do
92     if( p_interaction->i_dialogs == 0 ) return;
93
94     vlc_mutex_lock( &p_interaction->object_lock );
95
96     InteractionSearchInterface( p_interaction );
97     if( !p_interaction->p_intf )
98     {
99         // We mark all dialogs as answered with their "default" answer
100         for( i_index = 0 ; i_index < p_interaction->i_dialogs; i_index ++ )
101         {
102             interaction_dialog_t *p_dialog = p_interaction->pp_dialogs[i_index];
103             p_dialog->i_return = DIALOG_DEFAULT; // Give default answer
104
105             // Pretend we have hidden and destroyed it
106             if( p_dialog->i_status == HIDDEN_DIALOG )
107                 p_dialog->i_status = DESTROYED_DIALOG;
108             else
109                 p_dialog->i_status = HIDING_DIALOG;
110         }
111     }
112     else
113         vlc_object_yield( p_interaction->p_intf );
114
115     for( i_index = 0 ; i_index < p_interaction->i_dialogs; i_index ++ )
116     {
117         interaction_dialog_t *p_dialog = p_interaction->pp_dialogs[i_index];
118         switch( p_dialog->i_status )
119         {
120         case ANSWERED_DIALOG:
121             // Ask interface to hide it
122             p_dialog->i_action = INTERACT_HIDE;
123             val.p_address = p_dialog;
124             if( p_interaction->p_intf )
125                 var_Set( p_interaction->p_intf, "interaction", val );
126             p_dialog->i_status = HIDING_DIALOG;
127             break;
128         case UPDATED_DIALOG:
129             p_dialog->i_action = INTERACT_UPDATE;
130             val.p_address = p_dialog;
131             if( p_interaction->p_intf )
132                 var_Set( p_interaction->p_intf, "interaction", val );
133             p_dialog->i_status = SENT_DIALOG;
134             break;
135         case HIDDEN_DIALOG:
136             if( !(p_dialog->i_flags & DIALOG_GOT_ANSWER) ) break;
137             p_dialog->i_action = INTERACT_DESTROY;
138             val.p_address = p_dialog;
139             if( p_interaction->p_intf )
140                 var_Set( p_interaction->p_intf, "interaction", val );
141             break;
142         case DESTROYED_DIALOG:
143             // Interface has now destroyed it, remove it
144             REMOVE_ELEM( p_interaction->pp_dialogs, p_interaction->i_dialogs,
145                          i_index);
146             i_index--;
147             DialogDestroy( p_dialog );
148             break;
149         case NEW_DIALOG:
150             // This is truly a new dialog, send it.
151             p_dialog->i_action = INTERACT_NEW;
152             val.p_address = p_dialog;
153             if( p_interaction->p_intf )
154                 var_Set( p_interaction->p_intf, "interaction", val );
155             p_dialog->i_status = SENT_DIALOG;
156             break;
157         }
158     }
159
160     if( p_interaction->p_intf )
161     {
162         vlc_object_release( p_interaction->p_intf );
163     }
164
165     vlc_mutex_unlock( &p_playlist->p_interaction->object_lock );
166 }
167
168 #define DIALOG_INIT( type ) \
169         DECMALLOC_ERR( p_new, interaction_dialog_t );                     \
170         memset( p_new, 0, sizeof( interaction_dialog_t ) );               \
171         p_new->b_cancelled = VLC_FALSE;                                   \
172         p_new->i_status = NEW_DIALOG;                                     \
173         p_new->i_flags = 0; \
174         p_new->i_type = INTERACT_DIALOG_##type;                           \
175         p_new->psz_returned[0] = NULL;                                    \
176         p_new->psz_returned[1] = NULL;
177
178 #define FORMAT_DESC \
179         va_start( args, psz_format ); \
180         vasprintf( &p_new->psz_description, psz_format, args ); \
181         va_end( args );
182
183 /**
184  * Send an error message, both in a blocking and non-blocking way
185  *
186  * \param p_this     Parent vlc_object
187  * \param b_blocking Is this dialog blocking or not?
188  * \param psz_title  Title for the dialog
189  * \param psz_format The message to display
190  * \return           VLC_SUCCESS or VLC_EGENERIC
191  */
192 int __intf_UserFatal( vlc_object_t *p_this, vlc_bool_t b_blocking,
193                        const char *psz_title,
194                        const char *psz_format, ... )
195 {
196     va_list args;
197     DIALOG_INIT( ONEWAY );
198
199     p_new->psz_title = strdup( psz_title );
200     FORMAT_DESC;
201
202     if( b_blocking )
203         p_new->i_flags = DIALOG_BLOCKING_ERROR;
204     else
205         p_new->i_flags = DIALOG_NONBLOCKING_ERROR;
206
207     return DialogSend( p_this, p_new );
208 }
209
210 /**
211  * Helper function to send a warning, which is always shown non-blocking
212  *
213  * \param p_this     Parent vlc_object
214  * \param psz_title  Title for the dialog
215  * \param psz_format The message to display
216  * \return           VLC_SUCCESS or VLC_EGENERIC
217  */
218 int __intf_UserWarn( vlc_object_t *p_this,
219                      const char *psz_title,
220                      const char *psz_format, ... )
221 {
222     va_list args;
223     DIALOG_INIT( ONEWAY );
224
225     p_new->psz_title = strdup( psz_title );
226     FORMAT_DESC
227
228     p_new->i_flags = DIALOG_WARNING;
229
230     return DialogSend( p_this, p_new );
231 }
232
233 /**
234  * Helper function to ask a yes-no-cancel question
235  *
236  * \param p_this           Parent vlc_object
237  * \param psz_title        Title for the dialog
238  * \param psz_description  A description
239  * \param psz_default      caption for the default button
240  * \param psz_alternate    caption for the alternate button
241  * \param psz_other        caption for the optional 3rd button (== cancel)
242  * \return                 Clicked button code
243  */
244 int __intf_UserYesNo( vlc_object_t *p_this,
245                       const char *psz_title,
246                       const char *psz_description,
247                       const char *psz_default,
248                       const char *psz_alternate,
249                       const char *psz_other )
250 {
251     DIALOG_INIT( TWOWAY );
252
253     p_new->psz_title = strdup( psz_title );
254     p_new->psz_description = strdup( psz_description );
255     p_new->i_flags = DIALOG_YES_NO_CANCEL;
256     p_new->psz_default_button = strdup( psz_default );
257     p_new->psz_alternate_button = strdup( psz_alternate );
258     if( psz_other )
259         p_new->psz_other_button = strdup( psz_other );
260
261     return DialogSend( p_this, p_new );
262 }
263
264 /**
265  * Helper function to create a dialogue showing a progress-bar with some info
266  *
267  * \param p_this           Parent vlc_object
268  * \param psz_title        Title for the dialog (NULL implies main intf )
269  * \param psz_status       Current status
270  * \param f_position       Current position (0.0->100.0)
271  * \param i_timeToGo       Time (in sec) to go until process is finished
272  * \return                 Dialog id, to give to UserProgressUpdate
273  */
274 int __intf_Progress( vlc_object_t *p_this, const char *psz_title,
275                      const char *psz_status, float f_pos, int i_time )
276 {
277     DIALOG_INIT( ONEWAY );
278     p_new->psz_description = strdup( psz_status );
279     p_new->val.f_float = f_pos;
280     p_new->i_timeToGo = i_time;
281     p_new->psz_alternate_button = strdup( _( "Cancel" ) );
282
283     if( psz_title )
284     {
285         p_new->psz_title = strdup( psz_title );
286         p_new->i_flags = DIALOG_USER_PROGRESS;
287     }
288     else
289         p_new->i_flags = DIALOG_INTF_PROGRESS;
290
291     DialogSend( p_this, p_new );
292     return p_new->i_id;
293 }
294
295 /**
296  * Update a progress bar in a dialogue
297  *
298  * \param p_this           Parent vlc_object
299  * \param i_id             Identifier of the dialog
300  * \param psz_status       New status
301  * \param f_position       New position (0.0->100.0)
302  * \param i_timeToGo       Time (in sec) to go until process is finished
303  * \return                 nothing
304  */
305 void __intf_ProgressUpdate( vlc_object_t *p_this, int i_id,
306                             const char *psz_status, float f_pos, int i_time )
307 {
308     interaction_t *p_interaction = InteractionGet( p_this );
309     interaction_dialog_t *p_dialog;
310
311     if( !p_interaction ) return;
312
313     vlc_mutex_lock( &p_interaction->object_lock );
314     p_dialog  =  DialogGetById( p_interaction, i_id );
315
316     if( !p_dialog )
317     {
318         vlc_mutex_unlock( &p_interaction->object_lock ) ;
319         return;
320     }
321
322     free( p_dialog->psz_description );
323     p_dialog->psz_description = strdup( psz_status );
324
325     p_dialog->val.f_float = f_pos;
326     p_dialog->i_timeToGo = i_time;
327
328     p_dialog->i_status = UPDATED_DIALOG;
329     vlc_mutex_unlock( &p_interaction->object_lock) ;
330
331     playlist_Signal( pl_Get( p_this ) );
332 }
333
334 /**
335  * Helper function to communicate dialogue cancellations between the
336  * interface module and the caller
337  *
338  * \param p_this           Parent vlc_object
339  * \param i_id             Identifier of the dialogue
340  * \return                 Either true or false
341  */
342 vlc_bool_t __intf_UserProgressIsCancelled( vlc_object_t *p_this, int i_id )
343 {
344     interaction_t *p_interaction = InteractionGet( p_this );
345     interaction_dialog_t *p_dialog;
346     vlc_bool_t b_cancel;
347
348     if( !p_interaction ) return VLC_TRUE;
349
350     vlc_mutex_lock( &p_interaction->object_lock );
351     p_dialog  =  DialogGetById( p_interaction, i_id );
352     if( !p_dialog )
353     {
354         vlc_mutex_unlock( &p_interaction->object_lock ) ;
355         return VLC_TRUE;
356     }
357
358     b_cancel = p_dialog->b_cancelled;
359     vlc_mutex_unlock( &p_interaction->object_lock );
360     return b_cancel;
361 }
362
363 /**
364  * Helper function to make a login/password dialogue
365  *
366  * \param p_this           Parent vlc_object
367  * \param psz_title        Title for the dialog
368  * \param psz_description  A description
369  * \param ppsz_login       Returned login
370  * \param ppsz_password    Returned password
371  * \return                 Clicked button code
372  */
373 int __intf_UserLoginPassword( vlc_object_t *p_this,
374                               const char *psz_title,
375                               const char *psz_description,
376                               char **ppsz_login,
377                               char **ppsz_password )
378 {
379     int i_ret;
380     DIALOG_INIT( TWOWAY );
381     p_new->i_type = INTERACT_DIALOG_TWOWAY;
382     p_new->psz_title = strdup( psz_title );
383     p_new->psz_description = strdup( psz_description );
384     p_new->psz_default_button = strdup( _("Ok" ) );
385     p_new->psz_alternate_button = strdup( _("Cancel" ) );
386
387     p_new->i_flags = DIALOG_LOGIN_PW_OK_CANCEL;
388
389     i_ret = DialogSend( p_this, p_new );
390
391     if( i_ret != DIALOG_CANCELLED && i_ret != VLC_EGENERIC )
392     {
393         *ppsz_login = p_new->psz_returned[0]?
394                                 strdup( p_new->psz_returned[0] ) : NULL;
395         *ppsz_password = p_new->psz_returned[1]?
396                                 strdup( p_new->psz_returned[1] ) : NULL;
397     }
398     return i_ret;
399 }
400
401 /**
402  * Helper function to make a dialogue asking the user for !password string
403  *
404  * \param p_this           Parent vlc_object
405  * \param psz_title        Title for the dialog
406  * \param psz_description  A description
407  * \param ppsz_usersString Returned login
408  * \return                 Clicked button code
409  */
410 int __intf_UserStringInput( vlc_object_t *p_this,
411                               const char *psz_title,
412                               const char *psz_description,
413                               char **ppsz_usersString )
414 {
415     int i_ret;
416     DIALOG_INIT( TWOWAY );
417     p_new->i_type = INTERACT_DIALOG_TWOWAY;
418     p_new->psz_title = strdup( psz_title );
419     p_new->psz_description = strdup( psz_description );
420
421     p_new->i_flags = DIALOG_PSZ_INPUT_OK_CANCEL;
422
423     i_ret = DialogSend( p_this, p_new );
424
425     if( i_ret != DIALOG_CANCELLED )
426     {
427         *ppsz_usersString = p_new->psz_returned[0]?
428                                     strdup( p_new->psz_returned[0] ) : NULL;
429     }
430     return i_ret;
431 }
432
433 /**
434  * Hide an interaction dialog
435  *
436  * \param p_this the parent vlc object
437  * \param i_id the id of the item to hide
438  * \return nothing
439  */
440 void __intf_UserHide( vlc_object_t *p_this, int i_id )
441 {
442     interaction_t *p_interaction = InteractionGet( p_this );
443     interaction_dialog_t *p_dialog;
444
445     if( !p_interaction ) return;
446
447     vlc_mutex_lock( &p_interaction->object_lock );
448     p_dialog = DialogGetById( p_interaction, i_id );
449
450     if( !p_dialog )
451     {
452        vlc_mutex_unlock( &p_interaction->object_lock );
453        return;
454     }
455
456     p_dialog->i_status = ANSWERED_DIALOG;
457     vlc_mutex_unlock( &p_interaction->object_lock );
458 }
459
460 /**********************************************************************
461  * The following functions are local
462  **********************************************************************/
463
464 /* Get the interaction object. Create it if needed */
465 static interaction_t * InteractionGet( vlc_object_t *p_this )
466 {
467     interaction_t *p_interaction;
468     playlist_t *p_playlist = pl_Yield( p_this );
469
470     PL_LOCK;
471     if( p_playlist->p_interaction == NULL )
472        InteractionInit( p_playlist );
473
474     p_interaction = p_playlist->p_interaction;
475     PL_UNLOCK;
476
477     pl_Release( p_this );
478     return p_interaction;
479 }
480
481 /* Create the interaction object in the given playlist object */
482 static void InteractionInit( playlist_t *p_playlist )
483 {
484     interaction_t *p_interaction = vlc_object_create( VLC_OBJECT( p_playlist ),
485                                                       sizeof( interaction_t ) );
486     if( !p_interaction )
487     {
488         msg_Err( p_playlist,"out of memory" );
489         return;
490     }
491
492     p_interaction->psz_object_name = "interaction";
493     p_interaction->i_dialogs = 0;
494     p_interaction->pp_dialogs = NULL;
495     p_interaction->p_intf = NULL;
496     p_interaction->i_last_id = 0;
497
498     vlc_mutex_init( p_interaction , &p_interaction->object_lock );
499     p_playlist->p_interaction  = p_interaction;
500 }
501
502 /* Look for an interface suitable for interaction */
503 static void InteractionSearchInterface( interaction_t *p_interaction )
504 {
505     vlc_list_t  *p_list;
506     int          i_index;
507
508     p_interaction->p_intf = NULL;
509
510     p_list = vlc_list_find( p_interaction, VLC_OBJECT_INTF, FIND_ANYWHERE );
511     if( !p_list )
512     {
513         msg_Err( p_interaction, "unable to create module list" );
514         return;
515     }
516
517     for( i_index = 0; i_index < p_list->i_count; i_index ++ )
518     {
519         intf_thread_t *p_intf = (intf_thread_t *)
520                                         p_list->p_values[i_index].p_object;
521         if( p_intf->b_interaction )
522         {
523             p_interaction->p_intf = p_intf;
524             break;
525         }
526     }
527     vlc_list_release ( p_list );
528 }
529
530 /* Find an interaction dialog by its id */
531 static interaction_dialog_t *DialogGetById( interaction_t *p_interaction,
532                                             int i_id )
533 {
534     int i;
535     for( i = 0 ; i< p_interaction->i_dialogs; i++ )
536     {
537         if( p_interaction->pp_dialogs[i]->i_id == i_id )
538             return p_interaction->pp_dialogs[i];
539     }
540     return NULL;
541 }
542
543 /* Destroy a dialog */
544 static void DialogDestroy( interaction_dialog_t *p_dialog )
545 {
546     free( p_dialog->psz_title );
547     free( p_dialog->psz_description );
548     free( p_dialog->psz_default_button );
549     free( p_dialog->psz_alternate_button );
550     free( p_dialog->psz_other_button );
551     free( p_dialog );
552 }
553
554 /* Ask for the dialog to be sent to the user. Wait for answer
555  * if required */
556 static int DialogSend( vlc_object_t *p_this, interaction_dialog_t *p_dialog )
557 {
558     interaction_t *p_interaction = InteractionGet( p_this );
559
560     /* Get an id, if we don't already have one */
561     if( p_dialog->i_id == 0 )
562         p_dialog->i_id = ++p_interaction->i_last_id;
563
564     if( p_this->i_flags & OBJECT_FLAGS_NOINTERACT ) return VLC_EGENERIC;
565
566     if( config_GetInt( p_this, "interact" ) ||
567         p_dialog->i_flags & DIALOG_BLOCKING_ERROR ||
568         p_dialog->i_flags & DIALOG_NONBLOCKING_ERROR )
569     {
570         vlc_bool_t b_found = VLC_FALSE;
571         int i;
572         p_dialog->p_interaction = p_interaction;
573         p_dialog->p_parent = p_this;
574
575         /* Check if we have already added this dialog */
576         vlc_mutex_lock( &p_interaction->object_lock );
577         for( i = 0 ; i< p_interaction->i_dialogs; i++ )
578         {
579             if( p_interaction->pp_dialogs[i]->i_id == p_dialog->i_id )
580                 b_found = VLC_TRUE;
581         }
582         /* Add it to the queue, the main loop will send the orders to the
583          * interface */
584         if( ! b_found )
585         {
586             INSERT_ELEM( p_interaction->pp_dialogs,
587                          p_interaction->i_dialogs,
588                          p_interaction->i_dialogs,
589                          p_dialog );
590         }
591         else
592             p_dialog->i_status = UPDATED_DIALOG;
593
594         if( p_dialog->i_type == INTERACT_DIALOG_TWOWAY ) // Wait for answer
595         {
596             playlist_Signal( pl_Get( p_this ) );
597             while( p_dialog->i_status != ANSWERED_DIALOG &&
598                    p_dialog->i_status != HIDING_DIALOG &&
599                    p_dialog->i_status != HIDDEN_DIALOG &&
600                    !p_dialog->p_parent->b_die )
601             {
602                 vlc_mutex_unlock( &p_interaction->object_lock );
603                 msleep( 100000 );
604                 vlc_mutex_lock( &p_interaction->object_lock );
605             }
606             if( p_dialog->p_parent->b_die )
607             {
608                 p_dialog->i_return = DIALOG_CANCELLED;
609                 p_dialog->i_status = ANSWERED_DIALOG;
610             }
611             p_dialog->i_flags |= DIALOG_GOT_ANSWER;
612             vlc_mutex_unlock( &p_interaction->object_lock );
613             playlist_Signal( pl_Get( p_this ) );
614             return p_dialog->i_return;
615         }
616         else
617         {
618             // Pretend we already retrieved the "answer"
619             p_dialog->i_flags |=  DIALOG_GOT_ANSWER;
620             vlc_mutex_unlock( &p_interaction->object_lock );
621             playlist_Signal( pl_Get( p_this ) );
622             return VLC_SUCCESS;
623         }
624     }
625     else
626         return VLC_EGENERIC;
627 }