]> git.sesse.net Git - vlc/blob - plugins/gnome/intf_gnome.c
-Fall back to one-packet-once reading in dvd input since multi-block
[vlc] / plugins / gnome / intf_gnome.c
1 /*****************************************************************************
2  * intf_gnome.c: Gnome interface
3  *****************************************************************************
4  * Copyright (C) 1999, 2000 VideoLAN
5  * $Id: intf_gnome.c,v 1.25 2001/04/03 03:39:41 stef Exp $
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *          Stéphane Borel <stef@via.ecp.fr>
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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 #define MODULE_NAME gnome
26 #include "modules_inner.h"
27
28 /*****************************************************************************
29  * Preamble
30  *****************************************************************************/
31 #include "defs.h"
32
33 #include <errno.h>                                                 /* ENOMEM */
34 #include <stdlib.h>                                                /* free() */
35 #include <string.h>                                            /* strerror() */
36 #include <stdio.h>
37
38 #include <gnome.h>
39
40 #include "config.h"
41 #include "common.h"
42 #include "threads.h"
43 #include "mtime.h"
44 #include "tests.h"
45 #include "modules.h"
46
47 #include "stream_control.h"
48 #include "input_ext-intf.h"
49
50 #include "intf_msg.h"
51 #include "interface.h"
52 #include "intf_playlist.h"
53
54 #include "gnome_callbacks.h"
55 #include "gnome_interface.h"
56 #include "gnome_support.h"
57 #include "intf_gnome.h"
58
59 #include "main.h"
60
61 /*****************************************************************************
62  * Local prototypes.
63  *****************************************************************************/
64 static int  intf_Probe     ( probedata_t *p_data );
65 static int  intf_Open      ( intf_thread_t *p_intf );
66 static void intf_Close     ( intf_thread_t *p_intf );
67 static void intf_Run       ( intf_thread_t *p_intf );
68
69 static gint GnomeManage    ( gpointer p_data );
70 static gint GnomeLanguageMenus( gpointer, GtkWidget *, es_descriptor_t *, gint,
71                               void (*pf_toggle)(GtkCheckMenuItem *, gpointer) );
72 static gint GnomeChapterMenu  ( gpointer, GtkWidget *,
73                               void (*pf_toggle)(GtkCheckMenuItem *, gpointer) );
74 static gint GnomeTitleMenu    ( gpointer, GtkWidget *, 
75                               void (*pf_toggle)(GtkCheckMenuItem *, gpointer) );
76 static gint GnomeSetupMenu    ( intf_thread_t * p_intf );
77 static void GnomeDisplayDate  ( GtkAdjustment *p_adj );
78 static gint GnomeDVDModeManage( intf_thread_t * p_intf );
79
80 /*****************************************************************************
81  * g_atexit: kludge to avoid the Gnome thread to segfault at exit
82  *****************************************************************************
83  * gtk_init() makes several calls to g_atexit() which calls atexit() to
84  * register tidying callbacks to be called at program exit. Since the Gnome
85  * plugin is likely to be unloaded at program exit, we have to export this
86  * symbol to intercept the g_atexit() calls. Talk about crude hack.
87  *****************************************************************************/
88 void g_atexit( GVoidFunc func )
89 {
90     intf_thread_t *p_intf = p_main->p_intf;
91
92     if( p_intf->p_sys->pf_gdk_callback == NULL )
93     {
94         p_intf->p_sys->pf_gdk_callback = func;
95     }
96     else if( p_intf->p_sys->pf_gtk_callback == NULL )
97     {
98         p_intf->p_sys->pf_gtk_callback = func;
99     }
100     /* else nothing, but we could do something here */
101     return;
102 }
103
104 /*****************************************************************************
105  * Functions exported as capabilities. They are declared as static so that
106  * we don't pollute the namespace too much.
107  *****************************************************************************/
108 void _M( intf_getfunctions )( function_list_t * p_function_list )
109 {
110     p_function_list->pf_probe = intf_Probe;
111     p_function_list->functions.intf.pf_open  = intf_Open;
112     p_function_list->functions.intf.pf_close = intf_Close;
113     p_function_list->functions.intf.pf_run   = intf_Run;
114 }
115
116 /*****************************************************************************
117  * intf_Probe: probe the interface and return a score
118  *****************************************************************************
119  * This function tries to initialize Gnome and returns a score to the
120  * plugin manager so that it can select the best plugin.
121  *****************************************************************************/
122 static int intf_Probe( probedata_t *p_data )
123 {
124     if( TestMethod( INTF_METHOD_VAR, "gnome" ) )
125     {
126         return( 999 );
127     }
128
129     return( 100 );
130 }
131
132 /*****************************************************************************
133  * intf_Open: initialize and create window
134  *****************************************************************************/
135 static int intf_Open( intf_thread_t *p_intf )
136 {
137     /* Allocate instance and initialize some members */
138     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
139     if( p_intf->p_sys == NULL )
140     {
141         intf_ErrMsg("error: %s", strerror(ENOMEM));
142         return( 1 );
143     }
144
145     /* Initialize Gnome thread */
146     p_intf->p_sys->b_popup_changed = 0;
147     p_intf->p_sys->b_window_changed = 0;
148     p_intf->p_sys->b_playlist_changed = 0;
149     p_intf->p_sys->b_menus_update = 1;
150     p_intf->p_sys->b_menus_ready = 0;
151
152     p_intf->p_sys->b_slider_free = 1;
153
154     p_intf->p_sys->b_mode_changed = 0;
155     p_intf->p_sys->i_intf_mode = FILE_MODE;
156
157     p_intf->p_sys->pf_gtk_callback = NULL;
158     p_intf->p_sys->pf_gdk_callback = NULL;
159
160     return( 0 );
161 }
162
163 /*****************************************************************************
164  * intf_Close: destroy interface window
165  *****************************************************************************/
166 static void intf_Close( intf_thread_t *p_intf )
167 {
168     /* Destroy structure */
169     free( p_intf->p_sys );
170 }
171
172 /*****************************************************************************
173  * intf_Run: Gnome thread
174  *****************************************************************************
175  * this part of the interface is in a separate thread so that we can call
176  * gtk_main() from within it without annoying the rest of the program.
177  * XXX: the approach may look kludgy, and probably is, but I could not find
178  * a better way to dynamically load a Gnome interface at runtime.
179  *****************************************************************************/
180 static void intf_Run( intf_thread_t *p_intf )
181 {
182     /* gnome_init needs to know the command line. We don't care, so we
183      * give it an empty one */
184     char *p_args[] = { "" };
185     int   i_args   = 1;
186
187     /* The data types we are allowed to receive */
188     static GtkTargetEntry target_table[] =
189     {
190         { "text/uri-list", 0, DROP_ACCEPT_TEXT_URI_LIST },
191         { "text/plain",    0, DROP_ACCEPT_TEXT_PLAIN }
192     };
193
194     /* intf_Manage callback timeout */
195     int i_timeout;
196
197     /* Initialize Gnome */
198     gnome_init( p_main->psz_arg0, VERSION, i_args, p_args );
199
200     /* Create some useful widgets that will certainly be used */
201     p_intf->p_sys->p_window = create_intf_window( );
202     p_intf->p_sys->p_popup = create_intf_popup( );
203     p_intf->p_sys->p_disc = create_intf_disc( );
204     p_intf->p_sys->p_network = create_intf_network( );
205
206     /* Sets the interface mode according to playlist item */
207     if( p_main->p_playlist->p_item != NULL )
208     {
209         if( !strncmp( p_main->p_playlist->p_item->psz_name, "dvd:", 4 ) )
210         {
211             p_intf->p_sys->i_intf_mode = DVD_MODE;
212             p_intf->p_sys->b_mode_changed = 1;
213         }
214     }
215
216     /* Set the title of the main window */
217     gtk_window_set_title( GTK_WINDOW(p_intf->p_sys->p_window),
218                           VOUT_TITLE " (Gnome interface)");
219
220     /* Accept file drops on the main window */
221     gtk_drag_dest_set( GTK_WIDGET( p_intf->p_sys->p_window ),
222                        GTK_DEST_DEFAULT_ALL, target_table,
223                        1, GDK_ACTION_COPY );
224
225     /* Get the interface labels */
226     #define P_LABEL( name ) GTK_LABEL( gtk_object_get_data( \
227                          GTK_OBJECT( p_intf->p_sys->p_window ), name ) )
228     p_intf->p_sys->p_label_date = P_LABEL( "label_date" );
229     p_intf->p_sys->p_label_status = P_LABEL( "label_status" );
230     p_intf->p_sys->p_label_title = P_LABEL( "label_title" );
231     p_intf->p_sys->p_label_chapter = P_LABEL( "label_chapter" );
232     #undef P_LABEL
233
234     /* Connect the date display to the slider */
235     #define P_SLIDER GTK_RANGE( gtk_object_get_data( \
236                          GTK_OBJECT( p_intf->p_sys->p_window ), "slider" ) )
237     p_intf->p_sys->p_adj = gtk_range_get_adjustment( P_SLIDER );
238
239     gtk_signal_connect ( GTK_OBJECT( p_intf->p_sys->p_adj ), "value_changed",
240                          GTK_SIGNAL_FUNC( GnomeDisplayDate ), NULL );
241     p_intf->p_sys->f_adj_oldvalue = 0;
242     #undef P_SLIDER
243
244     /* We don't create these ones yet because we perhaps won't need them */
245     p_intf->p_sys->p_about = NULL;
246     p_intf->p_sys->p_playlist = NULL;
247     p_intf->p_sys->p_modules = NULL;
248     p_intf->p_sys->p_fileopen = NULL;
249
250     /* Store p_intf to keep an eye on it */
251     gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_window),
252                          "p_intf", p_intf );
253
254     gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_popup),
255                          "p_intf", p_intf );
256
257     gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_disc),
258                          "p_intf", p_intf );
259
260     gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_network),
261                          "p_intf", p_intf );
262
263     gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_adj),
264                          "p_intf", p_intf );
265
266     /* Show the control window */
267     gtk_widget_show( p_intf->p_sys->p_window );
268
269     /* Sleep to avoid using all CPU - since some interfaces needs to access
270      * keyboard events, a 100ms delay is a good compromise */
271     i_timeout = gtk_timeout_add( INTF_IDLE_SLEEP / 1000, GnomeManage, p_intf );
272
273     /* Enter gnome mode */
274     gtk_main();
275
276     /* Remove the timeout */
277     gtk_timeout_remove( i_timeout );
278
279     /* Get rid of stored callbacks so we can unload the plugin */
280     if( p_intf->p_sys->pf_gtk_callback != NULL )
281     {
282         p_intf->p_sys->pf_gtk_callback( );
283         p_intf->p_sys->pf_gtk_callback = NULL;
284
285     }
286
287     if( p_intf->p_sys->pf_gdk_callback != NULL )
288     {
289         p_intf->p_sys->pf_gdk_callback( );
290         p_intf->p_sys->pf_gdk_callback = NULL;
291     }
292 }
293
294 /* following functions are local */
295
296 /*****************************************************************************
297  * GnomeManage: manage main thread messages
298  *****************************************************************************
299  * In this function, called approx. 10 times a second, we check what the
300  * main program wanted to tell us.
301  *****************************************************************************/
302 static gint GnomeManage( gpointer p_data )
303 {
304 #define p_intf ((intf_thread_t *)p_data)
305
306     vlc_mutex_lock( &p_intf->change_lock );
307
308     /* If the "display popup" flag has changed */
309     if( p_intf->b_menu_change )
310     {
311         gnome_popup_menu_do_popup( p_intf->p_sys->p_popup,
312                                    NULL, NULL, NULL, NULL );
313         p_intf->b_menu_change = 0;
314     }
315
316     if( p_intf->p_input != NULL && p_intf->p_sys->p_window != NULL &&
317         p_intf->p_sys->b_mode_changed )
318     {
319         switch( p_intf->p_sys->i_intf_mode )
320         {
321             case DVD_MODE:
322                 GnomeDVDModeManage( p_intf );
323                 break;
324         }
325
326         p_intf->p_sys->b_mode_changed = 0;
327     }
328
329
330     /* Update language/chapter menus after user request */
331     if( p_intf->p_input != NULL && p_intf->p_sys->p_window != NULL &&
332         p_intf->p_sys->b_menus_update )
333     {
334         p_intf->p_sys->b_menus_ready = 0;
335         GnomeSetupMenu( p_intf );
336         p_intf->p_sys->b_menus_ready = 1;
337     }
338
339     /* Manage the slider */
340     if( p_intf->p_input != NULL )
341     {
342         float newvalue = p_intf->p_sys->p_adj->value;
343
344 #define p_area p_intf->p_input->stream.p_selected_area
345         /* If the user hasn't touched the slider since the last time,
346          * then the input can safely change it */
347         if( newvalue == p_intf->p_sys->f_adj_oldvalue )
348         {
349             /* Update the value */
350             p_intf->p_sys->p_adj->value = p_intf->p_sys->f_adj_oldvalue =
351                 ( 100. * p_area->i_tell ) / p_area->i_size;
352
353             gtk_signal_emit_by_name( GTK_OBJECT( p_intf->p_sys->p_adj ),
354                                      "value_changed" );
355         }
356         /* Otherwise, send message to the input if the user has
357          * finished dragging the slider */
358         else if( p_intf->p_sys->b_slider_free )
359         {
360             off_t i_seek = ( newvalue * p_area->i_size ) / 100;
361
362             input_Seek( p_intf->p_input, i_seek );
363
364             /* Update the old value */
365             p_intf->p_sys->f_adj_oldvalue = newvalue;
366         }
367 #undef p_area
368     }
369
370     /* Manage core vlc functions through the callback */
371     p_intf->pf_manage( p_intf );
372
373     if( p_intf->b_die )
374     {
375         vlc_mutex_unlock( &p_intf->change_lock );
376
377         /* Prepare to die, young Skywalker */
378         gtk_main_quit();
379
380         /* Just in case */
381         return( FALSE );
382     }
383
384     vlc_mutex_unlock( &p_intf->change_lock );
385
386     return( TRUE );
387
388 #undef p_intf
389 }
390
391 /*****************************************************************************
392  * GnomeLanguageMenus: update interactive menus of the interface
393  *****************************************************************************
394  * Sets up menus with information from input:
395  *  -languages
396  *  -sub-pictures
397  * Warning: since this function is designed to be called by management
398  * function, the interface lock has to be taken
399  *****************************************************************************/
400 static gint GnomeLanguageMenus( gpointer          p_data,
401                                 GtkWidget *       p_root,
402                                 es_descriptor_t * p_es,
403                                 gint              i_type,
404                           void(*pf_toggle )( GtkCheckMenuItem *, gpointer ) )
405 {
406     intf_thread_t *     p_intf;
407     GtkWidget *         p_menu;
408     GtkWidget *         p_separator;
409     GtkWidget *         p_item;
410     GtkWidget *         p_item_active;
411     GSList *            p_group;
412     char *              psz_name;
413     gint                b_audio;
414     gint                b_spu;
415     gint                i_item;
416     gint                i;
417
418     
419
420     /* cast */
421     p_intf = (intf_thread_t *)p_data;
422
423     /* removes previous menu */
424     gtk_menu_item_remove_submenu( GTK_MENU_ITEM( p_root ) );
425     gtk_widget_set_sensitive( p_root, FALSE );
426
427     p_group = NULL;
428
429     /* menu container */
430     p_menu = gtk_menu_new();
431
432     /* special case for "off" item */
433     psz_name = "Off";
434
435     p_item = gtk_radio_menu_item_new_with_label( p_group, psz_name );
436     p_group = gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( p_item ) );
437
438     gtk_widget_show( p_item );
439
440     /* signal hanling for off */
441     gtk_signal_connect( GTK_OBJECT( p_item ), "toggled",
442                         GTK_SIGNAL_FUNC ( pf_toggle ), NULL );
443
444     gtk_menu_append( GTK_MENU( p_menu ), p_item );
445
446     p_separator = gtk_menu_item_new();
447     gtk_widget_set_sensitive( p_separator, FALSE );
448     gtk_widget_show( p_separator );
449     gtk_menu_append( GTK_MENU( p_menu ), p_separator );
450
451     vlc_mutex_lock( &p_intf->p_input->stream.stream_lock );
452     p_item_active = NULL;
453     i_item = 0;
454
455     /* create a set of language buttons and append them to the container */
456     for( i = 0 ; i < p_intf->p_input->stream.i_es_number ; i++ )
457     {
458
459         b_audio = ( i_type == 1 ) && p_intf->p_input->stream.pp_es[i]->b_audio;
460         b_spu   = ( i_type == 2 ) && p_intf->p_input->stream.pp_es[i]->b_spu;
461
462         if( b_audio || b_spu )
463         {
464             i_item++;
465             psz_name = p_intf->p_input->stream.pp_es[i]->psz_desc;
466             if( psz_name[0] == '\0' )
467             {
468                 sprintf( psz_name, "Language %d", i_item );
469             }
470
471             p_item = gtk_radio_menu_item_new_with_label( p_group, psz_name );
472             p_group =
473                 gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( p_item ) );
474
475             if( p_es == p_intf->p_input->stream.pp_es[i] )
476             {
477                 /* don't lose p_item when we append into menu */
478                 p_item_active = p_item;
479             }
480
481             gtk_widget_show( p_item );
482
483             /* setup signal hanling */
484             gtk_signal_connect( GTK_OBJECT( p_item ), "toggled",
485                             GTK_SIGNAL_FUNC( pf_toggle ),
486                             (gpointer)( p_intf->p_input->stream.pp_es[i] ) );
487
488             gtk_menu_append( GTK_MENU( p_menu ), p_item );
489         }
490     }
491
492     vlc_mutex_unlock( &p_intf->p_input->stream.stream_lock );
493
494     /* link the new menu to the menubar item */
495     gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_root ), p_menu );
496
497     /* acitvation will call signals so we can only do it
498      * when submenu is attached to menu - to get intf_window */
499     if( p_item_active != NULL )
500     {
501         gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( p_item_active ),
502                                         TRUE );
503     }
504
505     /* be sure that menu is sensitive if non empty */
506     if( i_item > 0 )
507     {
508         gtk_widget_set_sensitive( p_root, TRUE );
509     }
510
511     return TRUE;
512 }
513
514 /*****************************************************************************
515  * GnomeChapterMenu: generate chapter menu for current title
516  *****************************************************************************/
517 static gint GnomeChapterMenu( gpointer p_data, GtkWidget * p_chapter,
518                         void(*pf_toggle )( GtkCheckMenuItem *, gpointer ) )
519 {
520     intf_thread_t *     p_intf;
521     char                psz_name[10];
522     GtkWidget *         p_chapter_menu;
523     GtkWidget *         p_chapter_submenu;
524     GtkWidget *         p_menu_item;
525     GtkWidget *         p_item;
526     GtkWidget *         p_item_selected;
527     GSList *            p_chapter_group;
528     gint                i_title;
529     gint                i_chapter;
530     gint                i_nb;
531
532     /* cast */
533     p_intf = (intf_thread_t*)p_data;
534
535     /* removes previous menu */
536     gtk_menu_item_remove_submenu( GTK_MENU_ITEM( p_chapter ) );
537     gtk_widget_set_sensitive( p_chapter, FALSE );
538
539     p_chapter_submenu = NULL;
540     p_chapter_group = NULL;
541     p_item_selected = NULL;
542     p_menu_item = NULL;
543
544     i_title = p_intf->p_input->stream.p_selected_area->i_id;
545     p_chapter_menu = gtk_menu_new();
546     i_nb = p_intf->p_input->stream.pp_areas[i_title]->i_part_nb;
547
548     for( i_chapter = 0 ; i_chapter < i_nb ; i_chapter++ )
549     {
550         /* we group chapters in packets of ten for small screens */
551         if( ( i_chapter % 10 == 0 ) && ( i_nb > 20 ) )
552         {
553             if( i_chapter != 0 )
554             {
555                 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_menu_item ),
556                                            p_chapter_submenu );
557                 gtk_menu_append( GTK_MENU( p_chapter_menu ), p_menu_item );
558             }
559
560             sprintf( psz_name, "%d - %d", i_chapter + 1, i_chapter + 10);
561             p_menu_item = gtk_menu_item_new_with_label( psz_name );
562             gtk_widget_show( p_menu_item );
563             p_chapter_submenu = gtk_menu_new();
564         }
565
566         sprintf( psz_name, "Chapter %d", i_chapter + 1 );
567
568         p_item = gtk_radio_menu_item_new_with_label( p_chapter_group,
569                                                      psz_name );
570         p_chapter_group =
571             gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( p_item ) );
572
573         if( p_intf->p_input->stream.pp_areas[i_title]->i_part
574                      == i_chapter + 1 )
575         {
576             p_item_selected = p_item;
577         }
578         
579         gtk_widget_show( p_item );
580
581         /* setup signal hanling */
582         gtk_signal_connect( GTK_OBJECT( p_item ),
583                         "toggled",
584                         GTK_SIGNAL_FUNC( pf_toggle ),
585                         (gpointer)(i_chapter + 1) );
586
587         if( i_nb > 20 )
588         {
589             gtk_menu_append( GTK_MENU( p_chapter_submenu ), p_item );
590         }
591         else
592         {
593             gtk_menu_append( GTK_MENU( p_chapter_menu ), p_item );
594         }
595     }
596
597     if( i_nb > 20 )
598     {
599         gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_menu_item ),
600                                    p_chapter_submenu );
601         gtk_menu_append( GTK_MENU( p_chapter_menu ), p_menu_item );
602     }
603
604     /* link the new menu to the title menu item */
605     gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_chapter ),
606                                p_chapter_menu );
607
608     /* toggle currently selected chapter */
609     if( p_item_selected != NULL )
610     {
611         gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( p_item_selected ),
612                                         TRUE );
613     }
614
615     /* be sure that chapter menu is sensitive, if there are several items */
616     if( p_intf->p_input->stream.pp_areas[i_title]->i_part_nb > 1 )
617     {
618         gtk_widget_set_sensitive( p_chapter, TRUE );
619     }
620
621     return TRUE;
622 }
623
624 /*****************************************************************************
625  * GnomeTitleMenu: sets menus for titles and chapters selection
626  *****************************************************************************
627  * Generates two types of menus:
628  *  -simple list of titles
629  *  -cascaded lists of chapters for each title
630  *****************************************************************************/
631 static gint GnomeTitleMenu( gpointer       p_data,
632                             GtkWidget *    p_navigation, 
633                             void(*pf_toggle )( GtkCheckMenuItem *, gpointer ) )
634 {
635     intf_thread_t *     p_intf;
636     char                psz_name[10];
637     GtkWidget *         p_title_menu;
638     GtkWidget *         p_title_submenu;
639     GtkWidget *         p_title_item;
640     GtkWidget *         p_item_active;
641     GtkWidget *         p_chapter_menu;
642     GtkWidget *         p_chapter_submenu;
643     GtkWidget *         p_title_menu_item;
644     GtkWidget *         p_chapter_menu_item;
645     GtkWidget *         p_item;
646     GSList *            p_title_group;
647     GSList *            p_chapter_group;
648     gint                i_title;
649     gint                i_chapter;
650     gint                i_title_nb;
651     gint                i_chapter_nb;
652
653     /* cast */
654     p_intf = (intf_thread_t*)p_data;
655
656     p_title_menu = gtk_menu_new();
657     p_title_group = NULL;
658     p_title_submenu = NULL;
659     p_title_menu_item = NULL;
660     p_chapter_group = NULL;
661     p_chapter_submenu = NULL;
662     p_chapter_menu_item = NULL;
663     p_item_active = NULL;
664     i_title_nb = p_intf->p_input->stream.i_area_nb;
665
666     /* loop on titles */
667     for( i_title = 1 ; i_title < i_title_nb ; i_title++ )
668     {
669         /* we group titles in packets of ten for small screens */
670         if( ( i_title % 10 == 1 ) && ( i_title_nb > 20 ) )
671         {
672             if( i_title != 1 )
673             {
674                 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_title_menu_item ),
675                                            p_title_submenu );
676                 gtk_menu_append( GTK_MENU( p_title_menu ), p_title_menu_item );
677             }
678
679             sprintf( psz_name, "%d - %d", i_title, i_title + 9 );
680             p_title_menu_item = gtk_menu_item_new_with_label( psz_name );
681             gtk_widget_show( p_title_menu_item );
682             p_title_submenu = gtk_menu_new();
683         }
684
685         sprintf( psz_name, "Title %d", i_title );
686
687         if( pf_toggle == on_menubar_title_toggle )
688         {
689             p_title_item = gtk_radio_menu_item_new_with_label( p_title_group,
690                                                            psz_name );
691             p_title_group =
692               gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( p_title_item ) );
693
694             if( p_intf->p_input->stream.pp_areas[i_title] ==
695                          p_intf->p_input->stream.p_selected_area )
696             {
697                 p_item_active = p_title_item;
698             }
699
700             /* setup signal hanling */
701             gtk_signal_connect( GTK_OBJECT( p_title_item ),
702                      "toggled",
703                      GTK_SIGNAL_FUNC( pf_toggle ),
704                      (gpointer)(p_intf->p_input->stream.pp_areas[i_title]) );
705
706             if( p_intf->p_input->stream.i_area_nb > 1 )
707             {
708                 /* be sure that menu is sensitive */
709                 gtk_widget_set_sensitive( p_navigation, TRUE );
710             }
711         }
712         else
713         {
714     
715             p_title_item = gtk_menu_item_new_with_label( psz_name );
716             p_chapter_menu = gtk_menu_new();
717             i_chapter_nb =
718                     p_intf->p_input->stream.pp_areas[i_title]->i_part_nb;
719     
720             for( i_chapter = 0 ; i_chapter < i_chapter_nb ; i_chapter++ )
721             {
722                 /* we group chapters in packets of ten for small screens */
723                 if( ( i_chapter % 10 == 0 ) && ( i_chapter_nb > 20 ) )
724                 {
725                     if( i_chapter != 0 )
726                     {
727                         gtk_menu_item_set_submenu(
728                                     GTK_MENU_ITEM( p_chapter_menu_item ),
729                                     p_chapter_submenu );
730                         gtk_menu_append( GTK_MENU( p_chapter_menu ),
731                                          p_chapter_menu_item );
732                     }
733
734                     sprintf( psz_name, "%d - %d", i_chapter + 1,
735                                                   i_chapter + 10);
736                     p_chapter_menu_item =
737                             gtk_menu_item_new_with_label( psz_name );
738                     gtk_widget_show( p_chapter_menu_item );
739                     p_chapter_submenu = gtk_menu_new();
740                 }
741
742                 sprintf( psz_name, "Chapter %d", i_chapter + 1 );
743     
744                 p_item = gtk_radio_menu_item_new_with_label(
745                                                 p_chapter_group, psz_name );
746                 p_chapter_group = gtk_radio_menu_item_group(
747                                                 GTK_RADIO_MENU_ITEM( p_item ) );
748                 gtk_widget_show( p_item );
749
750                 if( p_intf->p_input->stream.pp_areas[i_title]->i_part
751                              == i_chapter + 1 )
752                 {
753                     p_item_active = p_item;
754                 }
755
756                 /* setup signal hanling */
757                 gtk_signal_connect( GTK_OBJECT( p_item ),
758                            "toggled",
759                            GTK_SIGNAL_FUNC( pf_toggle ),
760                            (gpointer)( ( i_title * 100 ) + ( i_chapter + 1) ) );
761
762                 if( i_chapter_nb > 20 )
763                 {
764                     gtk_menu_append( GTK_MENU( p_chapter_submenu ), p_item );
765                 }
766                 else
767                 {
768                     gtk_menu_append( GTK_MENU( p_chapter_menu ), p_item );
769                 }
770             }
771
772             if( i_chapter_nb > 20 )
773             {
774                 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_chapter_menu_item ),
775                                            p_chapter_submenu );
776                 gtk_menu_append( GTK_MENU( p_chapter_menu ),
777                                  p_chapter_menu_item );
778             }
779
780             /* link the new menu to the title menu item */
781             gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_title_item ),
782                                        p_chapter_menu );
783
784             if( p_intf->p_input->stream.pp_areas[i_title]->i_part_nb > 1 )
785             {
786                 /* be sure that menu is sensitive */
787                 gtk_widget_set_sensitive( p_navigation, TRUE );
788             }
789         }
790         gtk_widget_show( p_title_item );
791
792         if( i_title_nb > 20 )
793         {
794             gtk_menu_append( GTK_MENU( p_title_submenu ), p_title_item );
795         }
796         else
797         {
798             gtk_menu_append( GTK_MENU( p_title_menu ), p_title_item );
799         }
800     }
801
802     if( i_title_nb > 20 )
803     {
804         gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_title_menu_item ),
805                                    p_title_submenu );
806         gtk_menu_append( GTK_MENU( p_title_menu ), p_title_menu_item );
807     }
808
809     /* be sure that menu is sensitive */
810     gtk_widget_set_sensitive( p_title_menu, TRUE );
811
812     /* link the new menu to the menubar item */
813     gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_navigation ), p_title_menu );
814
815     if( p_item_active != NULL )
816     {
817         gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( p_item_active ),
818                                         TRUE );
819     }
820
821
822     return TRUE;
823 }
824
825 /*****************************************************************************
826  * GnomeSetupMenu: function that generates title/chapter/audio/subpic
827  * menus with help from preceding functions
828  *****************************************************************************/
829 static gint GnomeSetupMenu( intf_thread_t * p_intf )
830 {
831     es_descriptor_t *   p_audio_es;
832     es_descriptor_t *   p_spu_es;
833     GtkWidget *         p_menubar_menu;
834     GtkWidget *         p_popup_menu;
835     gint                i;
836
837     if( p_intf->p_input->stream.i_area_nb > 1 )
838     {
839         p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT( 
840                             p_intf->p_sys->p_window ), "menubar_title" ) );
841         GnomeTitleMenu( p_intf, p_menubar_menu, on_menubar_title_toggle );
842
843         p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT( 
844                              p_intf->p_sys->p_popup ), "popup_navigation" ) );
845         GnomeTitleMenu( p_intf, p_popup_menu, on_popup_navigation_toggle );
846     
847         p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT( 
848                              p_intf->p_sys->p_window ), "menubar_chapter" ) );
849         GnomeChapterMenu( p_intf, p_menubar_menu, on_menubar_chapter_toggle );
850     }
851
852     /* look for selected ES */
853     p_audio_es = NULL;
854     p_spu_es = NULL;
855
856     for( i = 0 ; i < p_intf->p_input->stream.i_selected_es_number ; i++ )
857     {
858         if( p_intf->p_input->stream.pp_selected_es[i]->b_audio )
859         {
860             p_audio_es = p_intf->p_input->stream.pp_selected_es[i];
861         }
862
863         if( p_intf->p_input->stream.pp_selected_es[i]->b_spu )
864         {
865             p_spu_es = p_intf->p_input->stream.pp_selected_es[i];
866         }
867     }
868
869     /* audio menus */
870
871     /* find audio root menu */
872     p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
873                          p_intf->p_sys->p_window ), "menubar_audio" ) );
874
875     p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT( 
876                  p_intf->p_sys->p_popup ), "popup_audio" ) );
877
878     GnomeLanguageMenus( p_intf, p_menubar_menu, p_audio_es, 1,
879                         on_menubar_audio_toggle );
880     GnomeLanguageMenus( p_intf, p_popup_menu, p_audio_es, 1,
881                         on_popup_audio_toggle );
882
883     /* sub picture menus */
884
885     /* find spu root menu */
886     p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
887                       p_intf->p_sys->p_window ), "menubar_subtitle" ) );
888
889     p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT( 
890                  p_intf->p_sys->p_popup ), "popup_subtitle" ) );
891
892     GnomeLanguageMenus( p_intf, p_menubar_menu, p_spu_es, 2,
893                         on_menubar_subtitle_toggle  );
894     GnomeLanguageMenus( p_intf, p_popup_menu, p_spu_es, 2,
895                         on_popup_subtitle_toggle );
896
897     /* everything is ready */
898     p_intf->p_sys->b_menus_update = 0;
899
900     return TRUE;
901 }
902
903 /*****************************************************************************
904  * GnomeDisplayDate: display stream date
905  *****************************************************************************
906  * This function displays the current date related to the position in
907  * the stream. It is called whenever the slider changes its value.
908  *****************************************************************************/
909 void GnomeDisplayDate( GtkAdjustment *p_adj )
910 {
911     intf_thread_t *p_intf;
912    
913     p_intf = gtk_object_get_data( GTK_OBJECT( p_adj ), "p_intf" );
914
915     if( p_intf->p_input != NULL )
916     {
917 #define p_area p_intf->p_input->stream.p_selected_area
918         char psz_time[ OFFSETTOTIME_MAX_SIZE ];
919
920         vlc_mutex_lock( &p_intf->p_input->stream.stream_lock );
921
922         gtk_label_set_text( p_intf->p_sys->p_label_date,
923                             input_OffsetToTime( p_intf->p_input, psz_time,
924                                    ( p_area->i_size * p_adj->value ) / 100 ) );
925
926         vlc_mutex_unlock( &p_intf->p_input->stream.stream_lock );
927 #undef p_area
928      }
929 }
930
931
932 /*****************************************************************************
933  * GnomeDVDModeManage
934  *****************************************************************************/
935 static gint GnomeDVDModeManage( intf_thread_t * p_intf )
936 {
937     GtkWidget *     p_dvd_box;
938     GtkWidget *     p_toolbar_next;
939     GtkWidget *     p_toolbar_prev;
940
941     p_dvd_box = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
942                  p_intf->p_sys->p_window ), "dvd_box" ) );
943     gtk_widget_show( GTK_WIDGET( p_dvd_box ) );
944
945     p_toolbar_next = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
946                  p_intf->p_sys->p_window ), "toolbar_next" ) );
947     gtk_signal_disconnect_by_data( GTK_OBJECT( p_toolbar_next ), NULL );
948     gtk_signal_connect( GTK_OBJECT( p_toolbar_next ), "clicked",
949                         GTK_SIGNAL_FUNC( on_toolbar_next_title_clicked ),
950                         NULL);
951
952     p_toolbar_prev = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
953                  p_intf->p_sys->p_window ), "toolbar_prev" ) );
954     gtk_signal_disconnect_by_data( GTK_OBJECT( p_toolbar_prev ), NULL );
955     gtk_signal_connect( GTK_OBJECT( p_toolbar_prev ), "clicked",
956                         GTK_SIGNAL_FUNC( on_toolbar_prev_title_clicked ),
957                         NULL);
958
959     return TRUE;
960 }
961