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