]> git.sesse.net Git - vlc/blob - plugins/gtk/gtk_playlist.c
*initialization bugfixes in input_dvd
[vlc] / plugins / gtk / gtk_playlist.c
1 /*****************************************************************************
2  * gtk_playlist.c : Interface for the playlist dialog
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: gtk_playlist.c,v 1.12 2001/05/15 01:01:44 stef Exp $
6  *
7  * Authors: Pierre Baillet <oct@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 gtk
26 #include "modules_inner.h"
27
28 /*****************************************************************************
29  * Preamble
30  *****************************************************************************/
31 #include "defs.h"
32
33 #include <stdlib.h>
34
35 #include <gtk/gtk.h>
36
37 #include <string.h>
38
39 #include <sys/types.h>          /* for readdir  and stat stuff */
40 #include <dirent.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43
44 #include "config.h"
45 #include "common.h"
46 #include "threads.h"
47 #include "mtime.h"
48
49 #include "stream_control.h"
50 #include "input_ext-intf.h"
51
52 #include "interface.h"
53 #include "intf_playlist.h"
54 #include "intf_msg.h"
55 #include "intf_urldecode.h"
56
57 #include "gtk_callbacks.h"
58 #include "gtk_interface.h"
59 #include "gtk_support.h"
60 #include "gtk_playlist.h"
61 #include "intf_gtk.h"
62
63
64 #include "main.h"
65
66 /****************************************************************************
67  * Playlist window management
68  ****************************************************************************/
69 gboolean GtkPlaylistShow( GtkWidget       *widget,
70                           GdkEventButton  *event,
71                           gpointer         user_data )
72 {
73     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
74
75     if( !GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
76     {
77         p_intf->p_sys->p_playlist = create_intf_playlist();
78         gtk_object_set_data( GTK_OBJECT( p_intf->p_sys->p_playlist ),
79                              "p_intf", p_intf );
80     }
81
82     if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playlist ) )
83     {
84         gtk_widget_hide( p_intf->p_sys->p_playlist );
85     } 
86     else 
87     {        
88         GtkCList * p_clist;
89
90         p_clist = GTK_CLIST( gtk_object_get_data(
91             GTK_OBJECT( p_intf->p_sys->p_playlist ), "playlist_clist" ) );
92         GtkRebuildCList( p_clist , p_main->p_playlist );
93         gtk_widget_show( p_intf->p_sys->p_playlist );
94         gdk_window_raise( p_intf->p_sys->p_playlist->window );
95     }
96
97     return TRUE;
98 }
99
100
101 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
102 {
103      gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
104 }
105
106
107 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
108 {
109      gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
110 }
111
112
113
114 gboolean GtkPlaylistPrev( GtkWidget       *widget,
115                           GdkEventButton  *event,
116                           gpointer         user_data )
117 {
118     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
119
120     if( p_intf->p_input != NULL )
121     {
122         /* FIXME: temporary hack */
123         intf_PlaylistPrev( p_main->p_playlist );
124         intf_PlaylistPrev( p_main->p_playlist );
125         p_intf->p_input->b_eof = 1;
126     }
127
128     return TRUE;
129 }
130
131
132 gboolean GtkPlaylistNext( GtkWidget       *widget,
133                           GdkEventButton  *event,
134                           gpointer         user_data)
135 {
136     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
137
138     if( p_intf->p_input != NULL )
139     {
140         /* FIXME: temporary hack */
141         p_intf->p_input->b_eof = 1;
142     }
143
144     return TRUE;
145 }
146
147 /****************************************************************************
148  * Menu callbacks for playlist functions
149  ****************************************************************************/
150 void GtkNextActivate( GtkMenuItem * menuitem, gpointer user_data )
151 {
152     GtkPlaylistNext( GTK_WIDGET( menuitem ), NULL, user_data );
153 }
154
155
156 void GtkPrevActivate( GtkMenuItem * menuitem, gpointer user_data )
157 {
158     GtkPlaylistPrev( GTK_WIDGET( menuitem ), NULL, user_data );
159 }
160
161
162 /****************************************************************************
163  * Playlist core functions
164  ****************************************************************************/
165 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
166 {
167
168 }
169
170
171 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
172 {
173
174 }
175
176
177 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
178 {
179     /* user wants to delete a file in the queue */
180     GList *     p_selection;
181     GtkCList *  p_clist;
182     playlist_t *p_playlist;
183     
184     /* catch the thread back */
185     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
186
187     p_playlist = p_main->p_playlist;
188     
189     /* lock the struct */
190     vlc_mutex_lock( &p_intf->change_lock );
191
192     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
193         p_intf->p_sys->p_playlist ), "playlist_clist" ) );
194     
195     /* I use UNDOCUMENTED features to retrieve the selection... */
196     p_selection = p_clist->selection;
197     
198     if( g_list_length( p_selection ) > 0 )
199     {
200         /* reverse-sort so that we can delete from the furthest
201          * to the closest item to delete...
202          */
203         p_selection = g_list_sort( p_selection, GtkCompareItems );
204         g_list_foreach( p_selection, GtkDeleteGListItem, p_intf );
205         /* rebuild the CList */
206         GtkRebuildCList( p_clist, p_playlist );
207     }
208     
209     vlc_mutex_unlock( &p_intf->change_lock );
210 }
211
212 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
213 {
214     /* Ok, this is a really small thing, but, hey, it works and
215        might be useful, who knows ? */
216     GtkPlaylistInvert( menuitem, user_data );
217     GtkPlaylistDeleteSelected( menuitem, user_data );
218 }
219
220 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
221 {
222     playlist_t *p_playlist;
223     GtkCList *  p_clist;
224     int *       pi_selected;
225     int         i_sel_l;
226     int         i_dummy;
227     
228     /* catch the thread back */
229     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
230
231     p_playlist = p_main->p_playlist;
232     
233     /* lock the struct */
234     vlc_mutex_lock( &p_intf->change_lock );
235
236     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
237         p_intf->p_sys->p_playlist ), "playlist_clist" ) );
238     
239     /* have to copy the selection to an int *
240        I wasn't able to copy the g_list to another g_list
241        glib only does pointer copies, not real copies :( */
242     
243     pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
244     i_sel_l = g_list_length( p_clist->selection );
245
246     for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
247     {
248         pi_selected[i_dummy] = (int)g_list_nth_data( p_clist->selection,
249                                                      i_dummy );
250     }
251     
252     gtk_clist_freeze( p_clist );
253     gtk_clist_select_all( p_clist );
254
255     for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
256     {
257         gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
258         gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
259     }
260
261     free( pi_selected );
262     gtk_clist_thaw( p_clist );
263
264     vlc_mutex_unlock( &p_intf->change_lock );
265 }
266
267 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
268 {
269
270 }
271
272 gboolean GtkPlaylistEvent( GtkWidget * widget,
273                            GdkEvent  * event,
274                            gpointer    user_data)
275 {
276     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
277
278     if( ( event->button ).type == GDK_2BUTTON_PRESS )
279     {
280         GtkCList *  p_clist;
281         gint        i_row;
282         gint        i_col;
283
284         p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
285             p_intf->p_sys->p_playlist ), "playlist_clist" ) );
286         
287         if( gtk_clist_get_selection_info( p_clist, (event->button).x, 
288                     (event->button).y, &i_row, &i_col ) == 1 )
289         {
290             /* clicked is in range. */
291             if( p_intf->p_input != NULL )
292             {
293                 /* FIXME: temporary hack */
294                 p_intf->p_input->b_eof = 1;
295             }
296
297 //            vlc_mutex_lock( &p_main->p_playlist->change_lock );
298
299             intf_PlaylistJumpto( p_main->p_playlist, i_row - 1 );
300             p_main->p_playlist->b_stopped = 0;
301
302 //            vlc_mutex_unlock( &p_main->p_playlist->change_lock );
303         }
304         return TRUE;
305     }
306
307     return FALSE;
308 }
309
310 void GtkPlaylistDragData( GtkWidget       *widget,
311                           GdkDragContext  *drag_context,
312                           gint             x,
313                           gint             y,
314                           GtkSelectionData *data,
315                           guint            info,
316                           guint            time,
317                           gpointer         user_data )
318 {
319     intf_thread_t * p_intf;
320     GtkCList *      p_clist;
321     gint            i_row;
322     gint            i_col;
323     int             i_end = p_main->p_playlist->i_size;
324
325     /* catch the interface back */
326     p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
327
328     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
329         p_intf->p_sys->p_playlist ), "playlist_clist" ) );
330    
331     if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
332     {
333         /* we are dropping somewhere into the clist items */
334         GtkDropDataReceived( p_intf, data, info, i_row );
335     } 
336     else 
337     {
338         /* else, put that at the end of the playlist */
339         GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
340     }
341
342 //    vlc_mutex_lock( &p_main->p_playlist->change_lock );
343
344     intf_PlaylistJumpto( p_main->p_playlist, i_end - 1 );
345     p_main->p_playlist->b_stopped = 0;
346
347 //    vlc_mutex_unlock( &p_main->p_playlist->change_lock );
348 }
349
350
351 gboolean GtkPlaylistDragMotion( GtkWidget       *widget,
352                                 GdkDragContext  *drag_context,
353                                 gint             x,
354                                 gint             y,
355                                 guint            time,
356                                 gpointer         user_data )
357 {
358     intf_thread_t *p_intf;
359     GtkCList *  p_clist;
360     gint        i_row;
361     gint        i_col;
362     int         i_dummy;
363     GdkColor    color;
364
365     p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
366
367     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
368         p_intf->p_sys->p_playlist ), "playlist_clist" ) );
369
370     if( !GTK_WIDGET_TOPLEVEL(widget) )
371     {
372         gdk_window_raise( p_intf->p_sys->p_playlist->window );
373     }
374
375     color.red =   0xffff;
376     color.blue =  0xffff;
377     color.green = 0xffff;
378
379     gtk_clist_freeze( p_clist );
380     
381     for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
382     {
383        gtk_clist_set_background ( p_clist, i_dummy , &color);
384     }
385
386     color.red = 0xffff;
387     color.blue = 0;
388     color.green = 0;
389     gtk_clist_set_background( p_clist, p_main->p_playlist->i_index , &color );
390         
391     if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
392     {
393         color.red = 0;
394         color.blue = 0xf000;
395         color.green = 0x9000;
396         gtk_clist_set_background ( p_clist, i_row - 1, &color);
397         gtk_clist_set_background ( p_clist, i_row, &color);
398     }
399
400     gtk_clist_thaw( p_clist );
401     
402     return TRUE;
403 }
404
405 void GtkDropDataReceived( intf_thread_t * p_intf,
406         GtkSelectionData * p_data, guint i_info, int i_position)
407 {
408     /* first we'll have to split against all the '\n' we have */
409     gchar *     p_protocol;
410     gchar *     p_temp;
411     gchar *     p_string = p_data->data ;
412     GList *     p_files = NULL;
413     GtkCList *  p_clist;
414
415     
416     /* catch the playlist back */
417     playlist_t * p_playlist = p_main->p_playlist;
418    
419
420     /* if this has been URLencoded, decode it
421      * 
422      * Is it a good thing to do it in place ?
423      * probably not... 
424      */
425     if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
426     {
427         urldecode_path( p_string );
428     }
429     
430     /* this cuts string into single file drops */
431     /* this code was borrowed from xmms, thx guys :) */
432     while( *p_string)
433     {
434         p_temp = strchr( p_string, '\n' );
435         if( p_temp )
436         {
437             if( *( p_temp - 1 ) == '\r' )
438             {
439                 *( p_temp - 1) = '\0';
440             }
441             *p_temp = '\0';
442         }
443
444         /* do we have a protocol or something ? */
445         p_protocol = strstr( p_string, ":/" );
446         if( p_protocol != NULL )
447         {
448             p_protocol = calloc( p_protocol - p_string + 2, sizeof(char) );
449             p_protocol = strncpy( p_protocol, p_string,
450                                   strstr( p_string, ":/" ) + 1 - p_string );
451
452             intf_WarnMsg( 4, "Protocol dropped is %s", p_protocol );
453             p_string += strlen( p_protocol );
454
455             /* Allowed things are proto: or proto:// */
456             if( p_string[0] == '/' && p_string[1] == '/')
457             {
458                 /* eat one '/' */
459                 p_string++;
460             }
461             intf_WarnMsg( 4, " Dropped %s", p_string );
462         } 
463         else 
464         {
465             p_protocol = strdup( "" );
466         }
467          
468         /* if it uses the file protocol we can do something, else, sorry :( 
469          * I think this is a good choice for now, as we don't have any
470          * ability to read http:// or ftp:// files
471          * what about adding dvd:// to the list of authorized proto ? */
472         
473         if( strcmp( p_protocol, "file:" ) == 0 )
474         {
475             p_files = g_list_concat( p_files, GtkReadFiles( p_string ) ); 
476         }
477        
478         /* free the malloc and go on... */
479         free( p_protocol );
480         if( !p_temp )
481         {
482             break;
483         }
484         p_string = p_temp + 1;
485     }
486    
487     /* At this point, we have a nice big list maybe NULL */
488     if( p_files != NULL )
489     {
490         /* lock the interface */
491         vlc_mutex_lock( &p_intf->change_lock );
492
493         intf_WarnMsg( 4, "List has %d elements", g_list_length( p_files ) ); 
494         GtkAppendList( p_playlist, i_position, p_files );
495
496         /* get the CList  and rebuild it. */
497         p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playlist,
498                                             "playlist_clist" ) ); 
499         GtkRebuildCList( p_clist , p_playlist );
500         
501         /* unlock the interface */
502         vlc_mutex_unlock( &p_intf->change_lock );
503
504         p_main->p_playlist->b_stopped = 0;
505     }
506 }
507
508
509 void GtkDeleteGListItem( gpointer data, gpointer param )
510 {
511     int i_cur_row = ( int )data;
512     intf_thread_t * p_intf = param;    
513     
514     intf_PlaylistDelete( p_main->p_playlist, i_cur_row );
515
516     /* are we deleting the current played stream */
517     if( p_intf->p_sys->i_playing == i_cur_row )
518     {
519         /* next ! */
520         p_intf->p_input->b_eof = 1;
521         /* this has to set the slider to 0 */
522         
523         /* step minus one */
524         p_intf->p_sys->i_playing-- ;
525
526         vlc_mutex_lock( &p_main->p_playlist->change_lock );
527         p_main->p_playlist->i_index-- ;
528         vlc_mutex_unlock( &p_main->p_playlist->change_lock );
529     }
530 }
531
532
533 gint GtkCompareItems( gconstpointer a, gconstpointer b )
534 {
535     return b - a;
536 }
537
538
539 /* check a file (string) against supposed valid extension */
540 int GtkHasValidExtension( gchar * psz_filename )
541 {
542     char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
543     int  i_ext = 6;
544     int  i_dummy;
545
546     gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
547
548     for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
549     {
550         if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
551         {
552             return 1;
553         }
554     }
555
556     return 0;
557 }
558
559 /* recursive function: descend into folders and build a list of
560  * valid filenames */
561 GList * GtkReadFiles( gchar * psz_fsname )
562 {
563     struct stat statbuf;
564     GList  *    p_current = NULL;
565
566     /* get the attributes of this file */
567     stat( psz_fsname, &statbuf );
568     
569     /* is it a regular file ? */
570     if( S_ISREG( statbuf.st_mode ) )
571     {
572         if( GtkHasValidExtension( psz_fsname ) )
573         {
574             intf_WarnMsg( 2, "%s is a valid file. Stacking on the playlist",
575                           psz_fsname );
576             return g_list_append( NULL, g_strdup( psz_fsname ) );
577         } 
578         else
579         {
580             return NULL;
581         }
582     } 
583     /* is it a directory (should we check for symlinks ?) */
584     else if( S_ISDIR( statbuf.st_mode ) ) 
585     {
586         /* have to cd into this dir */
587         DIR *           p_current_dir = opendir( psz_fsname );
588         struct dirent * p_dir_content; 
589         
590         intf_WarnMsg( 2, "%s is a folder.", psz_fsname );
591         
592         if( p_current_dir == NULL )
593         {
594             /* something went bad, get out of here ! */
595             return p_current;
596         }
597         p_dir_content = readdir( p_current_dir );
598
599         /* while we still have entries in the directory */
600         while( p_dir_content != NULL )
601         {
602             /* if it is "." or "..", forget it */
603             if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
604                 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
605             {
606                 /* else build the new directory by adding
607                    fsname "/" and the current entry name 
608                    (kludgy :()
609                   */
610                 char *  psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
611                             strlen( p_dir_content->d_name ) * sizeof(char) );
612                 strcpy( psz_newfs, psz_fsname );
613                 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
614                         p_dir_content->d_name );
615                 psz_newfs[strlen( psz_fsname )] = '/';
616                 
617                 p_current = g_list_concat( p_current,
618                                            GtkReadFiles( psz_newfs ) );
619                     
620                 g_free( psz_newfs );
621             }
622             p_dir_content = readdir( p_current_dir );
623         }
624         return p_current;
625     }
626     return NULL;
627 }
628
629 /* add items in a playlist 
630  * when i_pos==-1 add to the end of the list... 
631  */
632 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
633 {
634     guint i_dummy;
635     guint i_length;
636
637     i_length = g_list_length( p_list );
638
639     for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
640     {
641         intf_PlaylistAdd( p_playlist, 
642                 /* ok; this is a really nasty trick to insert
643                    the item where they are suppose to go but, hey
644                    this works :P (btw, you are really nasty too) */
645                 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + i_dummy ), 
646                 g_list_nth_data( p_list, i_dummy ) );
647     }
648     return 0;
649 }
650
651 /* statis timeouted function */
652 void GtkPlayListManage( intf_thread_t * p_intf )
653 {
654     /* this thing really sucks for now :( */
655
656     /* TODO speak more with interface/intf_playlist.c */
657
658     playlist_t *    p_playlist = p_main->p_playlist ;
659     GtkCList *      p_clist;
660
661     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
662                    p_intf->p_sys->p_playlist ), "playlist_clist" ) );
663
664     if( p_intf->p_sys->i_playing != p_playlist->i_index )
665     {
666         GdkColor color;
667
668         color.red = 0xffff;
669         color.blue = 0;
670         color.green = 0;
671
672         gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
673
674         if( p_intf->p_sys->i_playing != -1 )
675         {
676             color.red = 0xffff;
677             color.blue = 0xffff;
678             color.green = 0xffff;
679             gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
680                                       &color);
681         }
682         p_intf->p_sys->i_playing = p_playlist->i_index;
683     }
684 }
685
686 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
687 {
688     int         i_dummy;
689     gchar *     ppsz_text[2];
690     GdkColor    red;
691     red.red     = 65535;
692     red.blue    = 0;
693     red.green   = 0;
694     
695     gtk_clist_freeze( p_clist );
696     gtk_clist_clear( p_clist );
697    
698     for( i_dummy = 0; i_dummy < p_playlist->i_size ; i_dummy++ )
699     {
700 #ifdef WIN32 /* WIN32 HACK */
701         ppsz_text[0] = g_strdup( "" );
702 #else
703         ppsz_text[0] = g_strdup( rindex( (char *)(p_playlist->p_item[
704                 p_playlist->i_size - 1 - i_dummy].psz_name ), '/' ) + 1 );
705 #endif
706         ppsz_text[1] = g_strdup( "no info");
707         
708         gtk_clist_insert( p_clist, 0, ppsz_text );
709         
710         free( ppsz_text[0] );
711         free( ppsz_text[1] );
712     }
713     gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
714     gtk_clist_thaw( p_clist );
715 }
716