3ac9df9e641b00dfcb3c82a5fe8a1276b9d4ae75
[vlc.git] / modules / gui / familiar / familiar.c
1 /*****************************************************************************
2  * familiar.c : familiar plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2002 VideoLAN
5  * $Id: familiar.c,v 1.33 2003/03/17 18:02:10 sam Exp $
6  *
7  * Authors: Jean-Paul Saman <jpsaman@wxs.nl>
8  *          Marc Ariberti <marcari@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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>                                      /* malloc(), free() */
29 #include <errno.h>                                                 /* ENOMEM */
30 #include <string.h>                                            /* strerror() */
31 #include <stdio.h>
32
33 #include <vlc/vlc.h>
34 #include <vlc/intf.h>
35
36 #include <gtk/gtk.h>
37
38 #ifdef HAVE_GPE_INIT_H
39 #include <gpe/init.h>
40 #endif
41
42 #include "callbacks.h"
43 #include "interface.h"
44 #include "support.h"
45 #include "familiar.h"
46
47 /*****************************************************************************
48  * Local prototypes.
49  *****************************************************************************/
50 static int  Open         ( vlc_object_t * );
51 static void Close        ( vlc_object_t * );
52 static void Run          ( intf_thread_t * );
53
54 void GtkAutoPlayFile     ( vlc_object_t * );
55 static int Manage        ( intf_thread_t *p_intf );
56 void E_(GtkDisplayDate)  ( GtkAdjustment *p_adj );
57 gint E_(GtkModeManage)   ( intf_thread_t * p_intf );
58
59 /*****************************************************************************
60  * Module descriptor
61  *****************************************************************************/
62 #define AUTOPLAYFILE_TEXT  N_("autoplay selected file")
63 #define AUTOPLAYFILE_LONGTEXT N_("automatically play a file when selected in the "\
64         "file selection list")
65
66 /*****************************************************************************
67  * Module descriptor
68  *****************************************************************************/
69 vlc_module_begin();
70     add_category_hint( N_("Miscellaneous"), NULL, VLC_TRUE );
71     add_bool( "familiar-autoplayfile", 1, GtkAutoPlayFile, AUTOPLAYFILE_TEXT, AUTOPLAYFILE_LONGTEXT, VLC_TRUE );
72     set_description( _("Familiar Linux Gtk+ interface module") );
73     set_capability( "interface", 70 );
74     set_callbacks( Open, Close );
75 vlc_module_end();
76
77 /*****************************************************************************
78  * Open: initialize and create window
79  *****************************************************************************/
80 static int Open( vlc_object_t *p_this )
81 {
82     intf_thread_t *p_intf = (intf_thread_t *)p_this;
83
84     /* Allocate instance and initialize some members */
85     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
86     if( p_intf->p_sys == NULL )
87     {
88         msg_Err( p_intf, "out of memory" );
89         return VLC_ENOMEM;
90     }
91
92 #ifdef NEED_GTK_MAIN
93     msg_Dbg( p_intf, "Using gui-helper" );
94     p_intf->p_sys->p_gtk_main = module_Need( p_this, "gui-helper", "gtk" );
95     if( p_intf->p_sys->p_gtk_main == NULL )
96     {
97         free( p_intf->p_sys );
98         return VLC_ENOMOD;
99     }
100 #endif
101
102     /* Initialize Gtk+ thread */
103     p_intf->p_sys->p_input = NULL;
104
105     p_intf->p_sys->b_autoplayfile = 1;
106     p_intf->p_sys->b_playing = 0;
107     p_intf->p_sys->b_slider_free = 1;
108
109     p_intf->pf_run = Run;
110
111     return VLC_SUCCESS;
112 }
113
114 /*****************************************************************************
115  * Close: destroy interface window
116  *****************************************************************************/
117 static void Close( vlc_object_t *p_this )
118 {
119     intf_thread_t *p_intf = (intf_thread_t *)p_this;
120
121     if( p_intf->p_sys->p_input )
122     {
123         vlc_object_release( p_intf->p_sys->p_input );
124     }
125
126 #ifdef NEED_GTK_MAIN
127     msg_Dbg( p_intf, "Releasing gui-helper" );
128     module_Unneed( p_intf, p_intf->p_sys->p_gtk_main );
129 #endif
130
131     /* Destroy structure */
132     free( p_intf->p_sys );
133 }
134
135 /*****************************************************************************
136  * Run: Gtk+ thread
137  *****************************************************************************
138  * this part of the interface is in a separate thread so that we can call
139  * gtk_main() from within it without annoying the rest of the program.
140  *****************************************************************************/
141 static void Run( intf_thread_t *p_intf )
142 {
143 #ifndef NEED_GTK_MAIN
144     /* gtk_init needs to know the command line. We don't care, so we
145      * give it an empty one */
146     char  *p_args[] = { "", NULL };
147     char **pp_args  = p_args;
148     int    i_args   = 1;
149     int    i_dummy;
150 #endif
151
152 #ifdef HAVE_GPE_INIT_H
153     /* Initialize GPE interface */
154     msg_Dbg( p_intf, "Starting familiar GPE interface" );
155     if (gpe_application_init(&i_args, &pp_args) == FALSE)
156         exit (1);
157 #else
158     gtk_set_locale ();
159 #   ifndef NEED_GTK_MAIN
160     msg_Dbg( p_intf, "Starting familiar GTK+ interface" );
161     gtk_init( &i_args, &pp_args );
162 #   else
163     /* Initialize Gtk+ */
164     msg_Dbg( p_intf, "Starting familiar GTK+ interface thread" );
165     gdk_threads_enter();
166 #   endif
167 #endif
168
169     /* Create some useful widgets that will certainly be used */
170 // FIXME: magic path
171     add_pixmap_directory("share");
172     add_pixmap_directory("/usr/share/vlc");
173
174     /* Path for pixmaps under linupy 1.4 */
175     add_pixmap_directory("/usr/local/share/pixmaps/vlc");
176
177     /* Path for pixmaps under linupy 2.0 */
178     add_pixmap_directory("/usr/share/pixmaps/vlc");
179
180     p_intf->p_sys->p_window = create_familiar();
181     if (p_intf->p_sys->p_window == NULL)
182     {
183         msg_Err( p_intf, "unable to create familiar interface" );
184     }
185     gtk_widget_set_usize(p_intf->p_sys->p_window, 
186                         gdk_screen_width() , gdk_screen_height() - 30 );
187
188     /* Set the title of the main window */
189     gtk_window_set_title( GTK_WINDOW(p_intf->p_sys->p_window),
190                           VOUT_TITLE " (Familiar Linux interface)");
191
192     p_intf->p_sys->p_notebook = GTK_NOTEBOOK( gtk_object_get_data(
193         GTK_OBJECT( p_intf->p_sys->p_window ), "notebook" ) );
194     p_intf->p_sys->p_mediabook = GTK_NOTEBOOK( gtk_object_get_data(
195         GTK_OBJECT( p_intf->p_sys->p_window ), "mediabook" ) );
196
197     /* Get the slider object */
198     p_intf->p_sys->p_slider = GTK_HSCALE( gtk_object_get_data(
199         GTK_OBJECT( p_intf->p_sys->p_window ), "slider" ) );
200     p_intf->p_sys->p_slider_label = GTK_LABEL( gtk_object_get_data(
201         GTK_OBJECT( p_intf->p_sys->p_window ), "slider_label" ) );
202
203     /* Connect the date display to the slider */
204 #define P_SLIDER GTK_RANGE( gtk_object_get_data( \
205                          GTK_OBJECT( p_intf->p_sys->p_window ), "slider" ) )
206     p_intf->p_sys->p_adj = gtk_range_get_adjustment( P_SLIDER );
207
208     gtk_signal_connect ( GTK_OBJECT( p_intf->p_sys->p_adj ), "value_changed",
209                          GTK_SIGNAL_FUNC( E_(GtkDisplayDate) ), NULL );
210     p_intf->p_sys->f_adj_oldvalue = 0;
211     p_intf->p_sys->i_adj_oldvalue = 0;
212 #undef P_SLIDER
213
214     p_intf->p_sys->p_clist = GTK_CLIST( gtk_object_get_data(
215         GTK_OBJECT( p_intf->p_sys->p_window ), "clistmedia" ) );
216     gtk_clist_set_column_visibility (GTK_CLIST (p_intf->p_sys->p_clist), 2, FALSE);
217     gtk_clist_set_column_visibility (GTK_CLIST (p_intf->p_sys->p_clist), 3, FALSE);
218     gtk_clist_set_column_visibility (GTK_CLIST (p_intf->p_sys->p_clist), 4, FALSE);
219     gtk_clist_column_titles_show (GTK_CLIST (p_intf->p_sys->p_clist));
220
221     /* the playlist object */
222     p_intf->p_sys->p_clistplaylist = GTK_CLIST( gtk_object_get_data(
223         GTK_OBJECT( p_intf->p_sys->p_window ), "clistplaylist" ) );
224     
225     p_intf->p_sys->p_mrlentry = GTK_ENTRY( gtk_object_get_data(
226         GTK_OBJECT( p_intf->p_sys->p_window ), "mrl_entry" ) );
227
228     /* Store p_intf to keep an eye on it */
229     gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_window),
230                          "p_intf", p_intf );
231     gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_adj),
232                          "p_intf", p_intf );
233     
234     /* Show the control window */
235     gtk_widget_show( p_intf->p_sys->p_window );
236     ReadDirectory(p_intf->p_sys->p_clist, ".");
237
238     /* update the playlist */
239     FamiliarRebuildCList( p_intf->p_sys->p_clistplaylist, 
240         vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE ));
241     
242 #ifdef NEED_GTK_MAIN
243     msg_Dbg( p_intf, "Manage GTK keyboard events using threads" );
244     while( !p_intf->b_die )
245     {
246         Manage( p_intf );
247
248         /* Sleep to avoid using all CPU - since some interfaces need to
249          * access keyboard events, a 100ms delay is a good compromise */
250         gdk_threads_leave();
251         if (p_intf->p_libvlc->i_cpu & CPU_CAPABILITY_FPU)
252             msleep( INTF_IDLE_SLEEP );
253         else
254             msleep( 1000 );
255         gdk_threads_enter();
256     }
257 #else
258     msg_Dbg( p_intf, "Manage GTK keyboard events using timeouts" );
259     /* Sleep to avoid using all CPU - since some interfaces needs to access
260      * keyboard events, a 1000ms delay is a good compromise */
261     if (p_intf->p_libvlc->i_cpu & CPU_CAPABILITY_FPU)
262         i_dummy = gtk_timeout_add( INTF_IDLE_SLEEP / 1000, (GtkFunction)Manage, p_intf );
263     else
264         i_dummy = gtk_timeout_add( 1000, (GtkFunction)Manage, p_intf );
265
266     /* Enter Gtk mode */
267     gtk_main();
268     /* Remove the timeout */
269     gtk_timeout_remove( i_dummy );
270 #endif
271
272     gtk_object_destroy( GTK_OBJECT(p_intf->p_sys->p_window) );
273 #ifdef NEED_GTK_MAIN
274     gdk_threads_leave();
275 #endif
276 }
277
278 /*****************************************************************************
279  * GtkAutoplayFile: Autoplay file depending on configuration settings
280  *****************************************************************************/
281 void GtkAutoPlayFile( vlc_object_t *p_this )
282 {
283     GtkWidget *cbautoplay;
284     intf_thread_t *p_intf;
285     int i_index;
286     vlc_list_t *p_list = vlc_list_find( p_this, VLC_OBJECT_INTF,
287                                         FIND_ANYWHERE );
288
289     for( i_index = 0; i_index < p_list->i_count; i_index++ )
290     {
291         p_intf = (intf_thread_t *)p_list->p_values[i_index].p_object ;
292
293         if( strcmp( MODULE_STRING, p_intf->p_module->psz_object_name ) )
294         {
295             continue;
296         }
297         cbautoplay = GTK_WIDGET( gtk_object_get_data(
298                             GTK_OBJECT( p_intf->p_sys->p_window ),
299                             "cbautoplay" ) );
300
301         if( !config_GetInt( p_this, "familiar-autoplayfile" ) )
302         {
303             p_intf->p_sys->b_autoplayfile = VLC_FALSE;
304         }
305         else
306         {
307             p_intf->p_sys->b_autoplayfile = VLC_TRUE;
308         }
309         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( cbautoplay ),
310                                       p_intf->p_sys->b_autoplayfile );
311     }
312     vlc_list_release( p_list );
313 }
314
315 /* following functions are local */
316
317 /*****************************************************************************
318  * Manage: manage main thread messages
319  *****************************************************************************
320  * In this function, called approx. 10 times a second, we check what the
321  * main program wanted to tell us.
322  *****************************************************************************/
323 static int Manage( intf_thread_t *p_intf )
324 {
325     vlc_mutex_lock( &p_intf->change_lock );
326
327     /* Update the input */
328     if( p_intf->p_sys->p_input == NULL )
329     {
330         p_intf->p_sys->p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT,
331                                                           FIND_ANYWHERE );
332     }
333     else if( p_intf->p_sys->p_input->b_dead )
334     {
335         vlc_object_release( p_intf->p_sys->p_input );
336         p_intf->p_sys->p_input = NULL;
337     }
338
339     if( p_intf->p_sys->p_input )
340     {
341         input_thread_t *p_input = p_intf->p_sys->p_input;
342
343         vlc_mutex_lock( &p_input->stream.stream_lock );
344         if( !p_input->b_die )
345         {
346             /* New input or stream map change */
347             if( p_input->stream.b_changed )
348             {
349                 playlist_t *p_playlist;
350
351                 E_(GtkModeManage)( p_intf );
352                 p_intf->p_sys->b_playing = 1;
353
354                 /* update playlist interface */
355                 p_playlist = (playlist_t *) vlc_object_find( 
356                         p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
357                 if (p_playlist != NULL)
358                 {
359                     FamiliarRebuildCList( p_intf->p_sys->p_clistplaylist, 
360                                           p_playlist );
361                 }
362             }
363
364             /* Manage the slider */
365             if (p_intf->p_libvlc->i_cpu & CPU_CAPABILITY_FPU)
366             {
367                 /* Manage the slider for CPU_CAPABILITY_FPU hardware */ 
368                 if( p_input->stream.b_seekable && p_intf->p_sys->b_playing )
369                 {
370                     float newvalue = p_intf->p_sys->p_adj->value;
371
372 #define p_area p_input->stream.p_selected_area
373                     /* If the user hasn't touched the slider since the last time,
374                      * then the input can safely change it */
375                     if( newvalue == p_intf->p_sys->f_adj_oldvalue )
376                     {
377                         /* Update the value */
378                         p_intf->p_sys->p_adj->value =
379                         p_intf->p_sys->f_adj_oldvalue =
380                             ( 100. * p_area->i_tell ) / p_area->i_size;
381                         gtk_signal_emit_by_name( GTK_OBJECT( p_intf->p_sys->p_adj ),
382                                                  "value_changed" );
383                     }
384                     /* Otherwise, send message to the input if the user has
385                      * finished dragging the slider */
386                     else if( p_intf->p_sys->b_slider_free )
387                     {
388                         off_t i_seek = ( newvalue * p_area->i_size ) / 100;
389
390                         /* release the lock to be able to seek */
391                         vlc_mutex_unlock( &p_input->stream.stream_lock );
392                         input_Seek( p_input, i_seek, INPUT_SEEK_SET );
393                         vlc_mutex_lock( &p_input->stream.stream_lock );
394
395                         /* Update the old value */
396                         p_intf->p_sys->f_adj_oldvalue = newvalue;
397                     }
398 #undef p_area
399                 }
400             }
401             else
402             {
403                 /* Manage the slider without CPU_CAPABILITY_FPU hardware */
404                 if( p_input->stream.b_seekable && p_intf->p_sys->b_playing )
405                 {
406                     off_t newvalue = p_intf->p_sys->p_adj->value;
407
408 #define p_area p_input->stream.p_selected_area
409                     /* If the user hasn't touched the slider since the last time,
410                      * then the input can safely change it */
411                     if( newvalue == p_intf->p_sys->i_adj_oldvalue )
412                     {
413                         /* Update the value */
414                         p_intf->p_sys->p_adj->value =
415                         p_intf->p_sys->i_adj_oldvalue =
416                             ( 100 * p_area->i_tell ) / p_area->i_size;
417                         gtk_signal_emit_by_name( GTK_OBJECT( p_intf->p_sys->p_adj ),
418                                                  "value_changed" );
419                     }
420                     /* Otherwise, send message to the input if the user has
421                      * finished dragging the slider */
422                     else if( p_intf->p_sys->b_slider_free )
423                     {
424                         off_t i_seek = ( newvalue * p_area->i_size ) / 100;
425
426                         /* release the lock to be able to seek */
427                         vlc_mutex_unlock( &p_input->stream.stream_lock );
428                         input_Seek( p_input, i_seek, INPUT_SEEK_SET );
429                         vlc_mutex_lock( &p_input->stream.stream_lock );
430
431                         /* Update the old value */
432                         p_intf->p_sys->i_adj_oldvalue = newvalue;
433                     }
434 #undef p_area
435                 }
436             }
437         }
438         vlc_mutex_unlock( &p_input->stream.stream_lock );
439     }
440     else if( p_intf->p_sys->b_playing && !p_intf->b_die )
441     {
442         E_(GtkModeManage)( p_intf );
443         p_intf->p_sys->b_playing = 0;
444     }
445
446 #ifndef NEED_GTK_MAIN
447     if( p_intf->b_die )
448     {
449         vlc_mutex_unlock( &p_intf->change_lock );
450
451         /* Prepare to die, young Skywalker */
452         gtk_main_quit();
453
454         return FALSE;
455     }
456 #endif
457
458     vlc_mutex_unlock( &p_intf->change_lock );
459
460     return TRUE;
461 }
462
463 /*****************************************************************************
464  * GtkDisplayDate: display stream date
465  *****************************************************************************
466  * This function displays the current date related to the position in
467  * the stream. It is called whenever the slider changes its value.
468  * The lock has to be taken before you call the function.
469  *****************************************************************************/
470 void E_(GtkDisplayDate)( GtkAdjustment *p_adj )
471 {
472     intf_thread_t *p_intf;
473
474     p_intf = gtk_object_get_data( GTK_OBJECT( p_adj ), "p_intf" );
475
476     if( p_intf->p_sys->p_input )
477     {
478 #define p_area p_intf->p_sys->p_input->stream.p_selected_area
479         char psz_time[ OFFSETTOTIME_MAX_SIZE ];
480
481         gtk_label_set_text( GTK_LABEL( p_intf->p_sys->p_slider_label ),
482                         input_OffsetToTime( p_intf->p_sys->p_input, psz_time,
483                                    ( p_area->i_size * p_adj->value ) / 100 ) );
484 #undef p_area
485      }
486 }
487
488 /*****************************************************************************
489  * GtkModeManage: actualize the aspect of the interface whenever the input
490  *                changes.
491  *****************************************************************************
492  * The lock has to be taken before you call the function.
493  *****************************************************************************/
494 gint E_(GtkModeManage)( intf_thread_t * p_intf )
495 {
496     GtkWidget *     p_slider;
497     vlc_bool_t      b_control;
498
499 #define GETWIDGET( ptr, name ) GTK_WIDGET( gtk_object_get_data( GTK_OBJECT( \
500                            p_intf->p_sys->ptr ) , ( name ) ) )
501     /* hide slider */
502     p_slider = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
503                            p_intf->p_sys->p_window ), "slider" ) );
504     gtk_widget_hide( GTK_WIDGET( p_slider ) );
505
506     /* controls unavailable */
507     b_control = 0;
508
509     /* show the box related to current input mode */
510     if( p_intf->p_sys->p_input )
511     {
512         /* initialize and show slider for seekable streams */
513         if( p_intf->p_sys->p_input->stream.b_seekable )
514         {
515             if (p_intf->p_libvlc->i_cpu & CPU_CAPABILITY_FPU)
516                 p_intf->p_sys->p_adj->value = p_intf->p_sys->f_adj_oldvalue = 0;
517             else
518                 p_intf->p_sys->p_adj->value = p_intf->p_sys->i_adj_oldvalue = 0;
519             gtk_signal_emit_by_name( GTK_OBJECT( p_intf->p_sys->p_adj ),
520                                      "value_changed" );
521             gtk_widget_show( GTK_WIDGET( p_slider ) );
522         }
523
524         /* control buttons for free pace streams */
525         b_control = p_intf->p_sys->p_input->stream.b_pace_control;
526
527         p_intf->p_sys->p_input->stream.b_changed = 0;
528         msg_Dbg( p_intf, "stream has changed, refreshing interface" );
529     }
530
531     /* set control items */
532     gtk_widget_set_sensitive( GETWIDGET(p_window, "toolbar_rewind"), b_control );
533     gtk_widget_set_sensitive( GETWIDGET(p_window, "toolbar_pause"), b_control );
534     gtk_widget_set_sensitive( GETWIDGET(p_window, "toolbar_forward"), b_control );
535
536 #undef GETWIDGET
537     return TRUE;
538 }
539