1 /*****************************************************************************
2 * intf_gtk.c: Gtk+ interface
3 *****************************************************************************
4 * Copyright (C) 1999, 2000 VideoLAN
5 * $Id: intf_gtk.c,v 1.11 2001/04/08 07:24:47 stef Exp $
7 * Authors: Samuel Hocevar <sam@zoy.org>
8 * Stéphane Borel <stef@via.ecp.fr>
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.
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.
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 *****************************************************************************/
25 #define MODULE_NAME gtk
26 #include "modules_inner.h"
28 /*****************************************************************************
30 *****************************************************************************/
33 #include <errno.h> /* ENOMEM */
34 #include <stdlib.h> /* free() */
35 #include <string.h> /* strerror() */
47 #include "stream_control.h"
48 #include "input_ext-intf.h"
51 #include "interface.h"
53 #include "gtk_callbacks.h"
54 #include "gtk_interface.h"
55 #include "gtk_support.h"
60 /*****************************************************************************
62 *****************************************************************************/
63 static int intf_Probe ( probedata_t *p_data );
64 static int intf_Open ( intf_thread_t *p_intf );
65 static void intf_Close ( intf_thread_t *p_intf );
66 static void intf_Run ( intf_thread_t *p_intf );
68 static gint GtkManage ( gpointer p_data );
69 static gint GtkLanguageMenus( gpointer, GtkWidget *, es_descriptor_t *, gint,
70 void (*pf_activate)(GtkMenuItem *, gpointer) );
71 static gint GtkChapterMenu ( gpointer, GtkWidget *,
72 void (*pf_activate)(GtkMenuItem *, gpointer) );
73 static gint GtkTitleMenu ( gpointer, GtkWidget *,
74 void (*pf_activate)(GtkMenuItem *, gpointer) );
75 static void GtkDisplayDate ( GtkAdjustment *p_adj );
77 void GtkPlayListManage( gpointer p_data );
79 /*****************************************************************************
80 * g_atexit: kludge to avoid the Gtk+ thread to segfault at exit
81 *****************************************************************************
82 * gtk_init() makes several calls to g_atexit() which calls atexit() to
83 * register tidying callbacks to be called at program exit. Since the Gtk+
84 * plugin is likely to be unloaded at program exit, we have to export this
85 * symbol to intercept the g_atexit() calls. Talk about crude hack.
86 *****************************************************************************/
87 void g_atexit( GVoidFunc func )
89 intf_thread_t *p_intf = p_main->p_intf;
91 if( p_intf->p_sys->pf_gdk_callback == NULL )
93 p_intf->p_sys->pf_gdk_callback = func;
95 else if( p_intf->p_sys->pf_gtk_callback == NULL )
97 p_intf->p_sys->pf_gtk_callback = func;
99 /* else nothing, but we could do something here */
103 /*****************************************************************************
104 * Functions exported as capabilities. They are declared as static so that
105 * we don't pollute the namespace too much.
106 *****************************************************************************/
107 void _M( intf_getfunctions )( function_list_t * p_function_list )
109 p_function_list->pf_probe = intf_Probe;
110 p_function_list->functions.intf.pf_open = intf_Open;
111 p_function_list->functions.intf.pf_close = intf_Close;
112 p_function_list->functions.intf.pf_run = intf_Run;
115 /*****************************************************************************
116 * intf_Probe: probe the interface and return a score
117 *****************************************************************************
118 * This function tries to initialize Gtk+ and returns a score to the
119 * plugin manager so that it can select the best plugin.
120 *****************************************************************************/
121 static int intf_Probe( probedata_t *p_data )
123 if( TestMethod( INTF_METHOD_VAR, "gtk" ) )
131 /*****************************************************************************
132 * intf_Open: initialize and create window
133 *****************************************************************************/
134 static int intf_Open( intf_thread_t *p_intf )
136 /* Allocate instance and initialize some members */
137 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
138 if( p_intf->p_sys == NULL )
140 intf_ErrMsg("error: %s", strerror(ENOMEM));
144 /* Initialize Gtk+ thread */
145 p_intf->p_sys->b_popup_changed = 0;
146 p_intf->p_sys->b_window_changed = 0;
147 p_intf->p_sys->b_playlist_changed = 0;
149 p_intf->p_sys->b_menus_update = 1;
150 p_intf->p_sys->b_slider_free = 1;
153 p_intf->p_sys->i_playing = -1;
155 p_intf->p_sys->pf_gtk_callback = NULL;
156 p_intf->p_sys->pf_gdk_callback = NULL;
161 /*****************************************************************************
162 * intf_Close: destroy interface window
163 *****************************************************************************/
164 static void intf_Close( intf_thread_t *p_intf )
166 /* Destroy structure */
167 free( p_intf->p_sys );
170 /*****************************************************************************
171 * intf_Run: Gtk+ thread
172 *****************************************************************************
173 * this part of the interface is in a separate thread so that we can call
174 * gtk_main() from within it without annoying the rest of the program.
175 * XXX: the approach may look kludgy, and probably is, but I could not find
176 * a better way to dynamically load a Gtk+ interface at runtime.
177 *****************************************************************************/
178 static void intf_Run( intf_thread_t *p_intf )
180 /* gtk_init needs to know the command line. We don't care, so we
181 * give it an empty one */
182 char *p_args[] = { "" };
183 char **pp_args = p_args;
186 /* The data types we are allowed to receive */
187 static GtkTargetEntry target_table[] =
189 { "text/uri-list", 0, DROP_ACCEPT_TEXT_URI_LIST },
190 { "text/plain", 0, DROP_ACCEPT_TEXT_PLAIN }
193 /* intf_Manage callback timeout */
196 /* Initialize Gtk+ */
197 gtk_init( &i_args, &pp_args );
199 /* Create some useful widgets that will certainly be used */
200 p_intf->p_sys->p_window = create_intf_window( );
201 p_intf->p_sys->p_popup = create_intf_popup( );
202 p_intf->p_sys->p_disc = create_intf_disc( );
203 p_intf->p_sys->p_network = create_intf_network( );
204 p_intf->p_sys->p_playlist = create_intf_playlist( );
207 /* Set the title of the main window */
208 gtk_window_set_title( GTK_WINDOW(p_intf->p_sys->p_window),
209 VOUT_TITLE " (Gtk+ interface)");
211 /* Accept file drops on the main window */
212 gtk_drag_dest_set( GTK_WIDGET( p_intf->p_sys->p_window ),
213 GTK_DEST_DEFAULT_ALL, target_table,
214 1, GDK_ACTION_COPY );
216 /* Accept file drops on the playlist window */
217 gtk_drag_dest_set( GTK_WIDGET( lookup_widget( p_intf->p_sys->p_playlist,
219 GTK_DEST_DEFAULT_ALL, target_table,
220 1, GDK_ACTION_COPY );
222 /* Get the interface labels */
223 #define P_LABEL( name ) GTK_LABEL( gtk_object_get_data( \
224 GTK_OBJECT( p_intf->p_sys->p_window ), name ) )
225 p_intf->p_sys->p_label_date = P_LABEL( "label_date" );
226 p_intf->p_sys->p_label_status = P_LABEL( "label_status" );
229 /* Connect the date display to the slider */
230 #define P_SLIDER GTK_RANGE( gtk_object_get_data( \
231 GTK_OBJECT( p_intf->p_sys->p_window ), "slider" ) )
232 p_intf->p_sys->p_adj = gtk_range_get_adjustment( P_SLIDER );
234 gtk_signal_connect ( GTK_OBJECT( p_intf->p_sys->p_adj ), "value_changed",
235 GTK_SIGNAL_FUNC( GtkDisplayDate ), NULL );
236 p_intf->p_sys->f_adj_oldvalue = 0;
239 /* We don't create these ones yet because we perhaps won't need them */
240 p_intf->p_sys->p_about = NULL;
241 p_intf->p_sys->p_modules = NULL;
242 p_intf->p_sys->p_fileopen = NULL;
244 /* Store p_intf to keep an eye on it */
245 gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_window),
248 gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_popup),
251 gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_playlist),
254 gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_disc),
257 gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_network),
260 gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_adj),
263 /* Show the control window */
264 gtk_widget_show( p_intf->p_sys->p_window );
266 /* Sleep to avoid using all CPU - since some interfaces needs to access
267 * keyboard events, a 100ms delay is a good compromise */
268 i_timeout = gtk_timeout_add( INTF_IDLE_SLEEP / 1000, GtkManage, p_intf );
273 /* Remove the timeout */
274 gtk_timeout_remove( i_timeout );
276 /* Launch stored callbacks */
277 if( p_intf->p_sys->pf_gtk_callback != NULL )
279 p_intf->p_sys->pf_gtk_callback();
281 if( p_intf->p_sys->pf_gdk_callback != NULL )
283 p_intf->p_sys->pf_gdk_callback();
288 /* following functions are local */
290 /*****************************************************************************
291 * GtkManage: manage main thread messages
292 *****************************************************************************
293 * In this function, called approx. 10 times a second, we check what the
294 * main program wanted to tell us.
295 *****************************************************************************/
297 static gint GtkManage( gpointer p_data )
299 #define p_intf ((intf_thread_t *)p_data)
301 GtkPlayListManage( p_data );
303 vlc_mutex_lock( &p_intf->change_lock );
305 /* If the "display popup" flag has changed */
306 if( p_intf->b_menu_change )
308 if( !GTK_IS_WIDGET( p_intf->p_sys->p_popup ) )
310 p_intf->p_sys->p_popup = create_intf_popup();
311 gtk_object_set_data( GTK_OBJECT( p_intf->p_sys->p_popup ),
314 gtk_menu_popup( GTK_MENU( p_intf->p_sys->p_popup ),
315 NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME );
316 p_intf->b_menu_change = 0;
319 /* Update language/chapter menus after user request */
320 if( p_intf->p_input != NULL && p_intf->p_sys->p_window != NULL &&
321 p_intf->p_sys->b_menus_update )
323 es_descriptor_t * p_audio_es;
324 es_descriptor_t * p_spu_es;
325 GtkWidget * p_menubar_menu;
326 GtkWidget * p_popup_menu;
329 p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
330 p_intf->p_sys->p_window ), "menubar_title" ) );
332 GtkTitleMenu( p_intf, p_menubar_menu, on_menubar_title_activate );
334 p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
335 p_intf->p_sys->p_window ), "menubar_chapter" ) );
337 GtkChapterMenu( p_intf, p_menubar_menu, on_menubar_chapter_activate );
339 p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
340 p_intf->p_sys->p_popup ), "popup_navigation" ) );
342 GtkTitleMenu( p_intf, p_popup_menu, on_popup_navigation_activate );
344 /* look for selected ES */
348 for( i = 0 ; i < p_intf->p_input->stream.i_selected_es_number ; i++ )
350 if( p_intf->p_input->stream.pp_es[i]->i_cat == AUDIO_ES )
352 p_audio_es = p_intf->p_input->stream.pp_es[i];
355 if( p_intf->p_input->stream.pp_es[i]->i_cat == SPU_ES )
357 p_spu_es = p_intf->p_input->stream.pp_es[i];
363 /* find audio root menu */
364 p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
365 p_intf->p_sys->p_window ), "menubar_audio" ) );
367 p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
368 p_intf->p_sys->p_popup ), "popup_audio" ) );
370 GtkLanguageMenus( p_intf, p_menubar_menu, p_audio_es, AUDIO_ES,
371 on_menubar_audio_activate );
372 GtkLanguageMenus( p_intf, p_popup_menu, p_audio_es, AUDIO_ES,
373 on_popup_audio_activate );
375 /* sub picture menus */
377 /* find spu root menu */
378 p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
379 p_intf->p_sys->p_window ), "menubar_subpictures" ) );
381 p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
382 p_intf->p_sys->p_popup ), "popup_subpictures" ) );
384 GtkLanguageMenus( p_intf, p_menubar_menu, p_spu_es, SPU_ES,
385 on_menubar_subpictures_activate );
386 GtkLanguageMenus( p_intf, p_popup_menu, p_spu_es, SPU_ES,
387 on_popup_subpictures_activate );
389 /* everything is ready */
390 p_intf->p_sys->b_menus_update = 0;
393 /* Manage the slider */
394 if( p_intf->p_input != NULL )
396 float newvalue = p_intf->p_sys->p_adj->value;
398 #define p_area p_intf->p_input->stream.p_selected_area
399 /* If the user hasn't touched the slider since the last time,
400 * then the input can safely change it */
401 if( newvalue == p_intf->p_sys->f_adj_oldvalue )
403 /* Update the value */
404 p_intf->p_sys->p_adj->value = p_intf->p_sys->f_adj_oldvalue =
405 ( 100. * p_area->i_tell ) / p_area->i_size;
407 gtk_signal_emit_by_name( GTK_OBJECT( p_intf->p_sys->p_adj ),
410 /* Otherwise, send message to the input if the user has
411 * finished dragging the slider */
412 else if( p_intf->p_sys->b_slider_free )
414 off_t i_seek = ( newvalue * p_area->i_size ) / 100;
416 input_Seek( p_intf->p_input, i_seek );
418 /* Update the old value */
419 p_intf->p_sys->f_adj_oldvalue = newvalue;
424 /* Manage core vlc functions through the callback */
425 p_intf->pf_manage( p_intf );
429 vlc_mutex_unlock( &p_intf->change_lock );
431 /* Prepare to die, young Skywalker */
438 vlc_mutex_unlock( &p_intf->change_lock );
445 /*****************************************************************************
446 * GtkMenuRadioItem: give a menu item adapted to language/title selection,
447 * ie the menu item is a radio button.
448 *****************************************************************************/
449 static GtkWidget * GtkMenuRadioItem( GtkWidget * p_menu,
450 GSList ** p_button_group,
457 GtkWidget * p_button;
461 gtk_radio_button_new_with_label( *p_button_group, psz_name );
463 /* add button to group */
465 gtk_radio_button_group( GTK_RADIO_BUTTON( p_button ) );
467 /* prepare button for display */
468 gtk_widget_show( p_button );
470 /* create menu item to store button */
471 p_item = gtk_menu_item_new();
473 /* put button inside item */
474 gtk_container_add( GTK_CONTAINER( p_item ), p_button );
476 /* add item to menu */
477 gtk_menu_append( GTK_MENU( p_menu ), p_item );
479 gtk_signal_connect( GTK_OBJECT( p_item ), "activate",
480 GTK_SIGNAL_FUNC( on_audio_toggle ),
484 /* prepare item for display */
485 gtk_widget_show( p_item );
487 /* is it the selected item ? */
490 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( p_button ), TRUE );
493 p_item = gtk_menu_item_new_with_label( psz_name );
494 gtk_menu_append( GTK_MENU( p_menu ), p_item );
495 gtk_widget_show( p_item );
501 /*****************************************************************************
502 * GtkLanguageMenus: update interactive menus of the interface
503 *****************************************************************************
504 * Sets up menus with information from input:
507 * Warning: since this function is designed to be called by management
508 * function, the interface lock has to be taken
509 *****************************************************************************/
510 static gint GtkLanguageMenus( gpointer p_data,
512 es_descriptor_t * p_es,
514 void(*pf_activate )( GtkMenuItem *, gpointer ) )
516 intf_thread_t * p_intf;
518 GtkWidget * p_separator;
520 GSList * p_button_group;
528 p_intf = (intf_thread_t *)p_data;
530 vlc_mutex_lock( &p_intf->p_input->stream.stream_lock );
532 p_button_group = NULL;
534 /* menu container for audio */
535 p_menu = gtk_menu_new();
537 /* create a set of language buttons and append them to the container */
538 b_active = ( p_es == NULL );
541 p_item = GtkMenuRadioItem( p_menu, &p_button_group, b_active, psz_name );
543 /* setup signal hanling */
544 gtk_signal_connect( GTK_OBJECT( p_item ), "activate",
545 GTK_SIGNAL_FUNC ( pf_activate ), NULL );
547 p_separator = gtk_menu_item_new();
548 gtk_widget_show( p_separator );
549 gtk_menu_append( GTK_MENU( p_menu ), p_separator );
550 gtk_widget_set_sensitive( p_separator, FALSE );
552 for( i = 0 ; i < p_intf->p_input->stream.i_es_number ; i++ )
554 if( p_intf->p_input->stream.pp_es[i]->i_cat == i_cat )
556 b_active = ( p_es == p_intf->p_input->stream.pp_es[i] ) ? 1 : 0;
557 psz_name = p_intf->p_input->stream.pp_es[i]->psz_desc;
559 p_item = GtkMenuRadioItem( p_menu, &p_button_group,
560 b_active, psz_name );
562 /* setup signal hanling */
563 gtk_signal_connect( GTK_OBJECT( p_item ), "activate",
564 GTK_SIGNAL_FUNC( pf_activate ),
565 (gpointer)( p_intf->p_input->stream.pp_es[i] ) );
570 /* link the new menu to the menubar item */
571 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_root ), p_menu );
573 /* be sure that menu is sensitive */
574 gtk_widget_set_sensitive( p_root, TRUE );
576 vlc_mutex_unlock( &p_intf->p_input->stream.stream_lock );
581 /*****************************************************************************
582 * GtkChapterMenu: generate chapter menu for current title
583 *****************************************************************************/
584 static gint GtkChapterMenu( gpointer p_data, GtkWidget * p_chapter,
585 void(*pf_activate )( GtkMenuItem *, gpointer ) )
587 intf_thread_t * p_intf;
589 GtkWidget * p_chapter_menu;
591 GSList * p_chapter_button_group;
597 p_intf = (intf_thread_t*)p_data;
599 i_title = p_intf->p_input->stream.p_selected_area->i_id;
600 p_chapter_menu = gtk_menu_new();
603 i_chapter < p_intf->p_input->stream.pp_areas[i_title]->i_part_nb ;
606 b_active = ( p_intf->p_input->stream.pp_areas[i_title]->i_part
607 == i_chapter + 1 ) ? 1 : 0;
609 sprintf( psz_name, "Chapter %d", i_chapter + 1 );
611 p_item = GtkMenuRadioItem( p_chapter_menu, &p_chapter_button_group,
612 b_active, psz_name );
613 /* setup signal hanling */
614 gtk_signal_connect( GTK_OBJECT( p_item ),
616 GTK_SIGNAL_FUNC( pf_activate ),
617 (gpointer)(i_chapter + 1) );
620 /* link the new menu to the title menu item */
621 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_chapter ),
624 /* be sure that chapter menu is sensitive */
625 gtk_widget_set_sensitive( p_chapter, TRUE );
630 /*****************************************************************************
631 * GtkTitleMenu: sets menus for titles and chapters selection
632 *****************************************************************************
633 * Generates two type of menus:
634 * -simple list of titles
635 * -cascaded lists of chapters for each title
636 *****************************************************************************/
637 static gint GtkTitleMenu( gpointer p_data,
638 GtkWidget * p_navigation,
639 void(*pf_activate )( GtkMenuItem *, gpointer ) )
641 intf_thread_t * p_intf;
643 GtkWidget * p_title_menu;
644 GtkWidget * p_title_item;
645 GtkWidget * p_chapter_menu;
647 GSList * p_title_button_group;
648 GSList * p_chapter_button_group;
654 p_intf = (intf_thread_t*)p_data;
656 p_title_menu = gtk_menu_new();
657 p_title_button_group = NULL;
658 p_chapter_button_group = NULL;
662 i_title < p_intf->p_input->stream.i_area_nb ;
665 b_active = ( p_intf->p_input->stream.pp_areas[i_title] ==
666 p_intf->p_input->stream.p_selected_area ) ? 1 : 0;
667 sprintf( psz_name, "Title %d", i_title );
669 p_title_item = GtkMenuRadioItem( p_title_menu, &p_title_button_group,
670 b_active, psz_name );
672 if( pf_activate == on_menubar_title_activate )
674 /* setup signal hanling */
675 gtk_signal_connect( GTK_OBJECT( p_title_item ),
677 GTK_SIGNAL_FUNC( pf_activate ),
678 (gpointer)(p_intf->p_input->stream.pp_areas[i_title]) );
682 p_chapter_menu = gtk_menu_new();
686 p_intf->p_input->stream.pp_areas[i_title]->i_part_nb ;
689 b_active = ( p_intf->p_input->stream.pp_areas[i_title]->i_part
690 == i_chapter + 1 ) ? 1 : 0;
692 sprintf( psz_name, "Chapter %d", i_chapter + 1 );
694 p_item = GtkMenuRadioItem( p_chapter_menu,
695 &p_chapter_button_group,
696 b_active, psz_name );
698 /* setup signal hanling */
699 gtk_signal_connect( GTK_OBJECT( p_item ),
701 GTK_SIGNAL_FUNC( pf_activate ),
702 (gpointer)( ( i_title * 100 ) + ( i_chapter + 1) ) );
705 /* link the new menu to the title menu item */
706 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_title_item ),
710 /* be sure that chapter menu is sensitive */
711 gtk_widget_set_sensitive( p_title_menu, TRUE );
715 /* link the new menu to the menubar audio item */
716 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_navigation ), p_title_menu );
718 /* be sure that audio menu is sensitive */
719 gtk_widget_set_sensitive( p_navigation, TRUE );
725 void GtkDisplayDate( GtkAdjustment *p_adj )
727 intf_thread_t *p_intf;
729 p_intf = gtk_object_get_data( GTK_OBJECT( p_adj ), "p_intf" );
731 if( p_intf->p_input != NULL )
733 #define p_area p_intf->p_input->stream.p_selected_area
734 char psz_time[ OFFSETTOTIME_MAX_SIZE ];
736 vlc_mutex_lock( &p_intf->p_input->stream.stream_lock );
738 gtk_label_set_text( p_intf->p_sys->p_label_date,
739 input_OffsetToTime( p_intf->p_input, psz_time,
740 ( p_area->i_size * p_adj->value ) / 100 ) );
742 vlc_mutex_unlock( &p_intf->p_input->stream.stream_lock );