]> git.sesse.net Git - vlc/blob - plugins/gtk/gtk_playlist.c
. cosmetic variable name changes
[vlc] / plugins / gtk / gtk_playlist.c
1 /*****************************************************************************
2  * playlist_interface.c : Interface for the playlist dialog
3  *****************************************************************************
4  * Copyright (C) 2000, 2001 VideoLAN
5  *
6  * Authors: .Pierre Baillet <oct@zoy.org>
7  *      
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  * 
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
21  *****************************************************************************/
22
23 #define MODULE_NAME gtk
24 #include "modules_inner.h"
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #include "defs.h"
30
31 #include <stdlib.h>
32
33 #include <gtk/gtk.h>
34
35 #include <string.h>
36
37 #include <sys/types.h>          /* for readdir  and stat stuff */
38 #include <dirent.h>
39 #include <sys/stat.h>
40 #include <unistd.h>
41
42 #include "config.h"
43 #include "common.h"
44 #include "threads.h"
45 #include "mtime.h"
46
47 #include "stream_control.h"
48 #include "input_ext-intf.h"
49
50 #include "interface.h"
51 #include "intf_plst.h"
52 #include "intf_msg.h"
53 #include "intf_urldecode.h"
54
55 #include "gtk_sys.h"
56 #include "gtk_callbacks.h"
57 #include "gtk_interface.h"
58 #include "gtk_support.h"
59
60 #include "main.h"
61
62 /* Playlist specific functions */
63 void rebuildCList(GtkCList * clist, playlist_t * playlist_p);
64 gint compareItems(gconstpointer a, gconstpointer b);
65 int hasValidExtension(gchar * filename);
66 GList * intf_readFiles(gchar * fsname );
67 int intf_AppendList( playlist_t * p_playlist, int i_pos, GList * list );
68 void GtkPlayListManage( gpointer p_data );
69 void on_generic_drop_data_received( intf_thread_t * p_intf,
70                 GtkSelectionData *data, guint info, int position);
71
72
73 static __inline__ intf_thread_t * GetIntf( GtkWidget *item, char * psz_parent )
74 {
75     return( gtk_object_get_data( GTK_OBJECT( lookup_widget(item, psz_parent) ),
76                                  "p_intf" ) );
77 }
78
79
80
81 void
82 on_menubar_playlist_activate           (GtkMenuItem     *menuitem,
83                                         gpointer         user_data)
84 {
85     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), "intf_window" );
86     playlist_t * p_playlist ;
87     GtkCList * list;
88     
89     if( !GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
90     {
91         p_intf->p_sys->p_playlist = create_intf_playlist();
92         gtk_object_set_data( GTK_OBJECT( p_intf->p_sys->p_playlist ),
93                              "p_intf", p_intf );
94     }
95     
96     
97     vlc_mutex_lock( &p_main->p_playlist->change_lock );
98     if(p_main->p_playlist->i_size > 0 )
99     {
100         p_playlist = p_main->p_playlist;
101         
102         list = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist, "playlist_clist" )) ;
103         rebuildCList( list, p_playlist );
104         
105        
106     }
107     vlc_mutex_unlock( &p_main->p_playlist->change_lock );
108     
109     gtk_widget_show( p_intf->p_sys->p_playlist );
110     gdk_window_raise( p_intf->p_sys->p_playlist->window );
111 }
112
113
114 void
115 on_toolbar_playlist_clicked            (GtkButton       *button,
116                                         gpointer         user_data)
117 {
118     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(button), "intf_window" );
119
120     if( !GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
121     {
122         p_intf->p_sys->p_playlist = create_intf_playlist();
123         gtk_object_set_data( GTK_OBJECT( p_intf->p_sys->p_playlist ),
124                              "p_intf", p_intf );
125     }
126     if( GTK_WIDGET_VISIBLE(p_intf->p_sys->p_playlist) ) {
127         gtk_widget_hide( p_intf->p_sys->p_playlist);
128     } else {        
129         GtkCList * clist;
130         gtk_widget_show( p_intf->p_sys->p_playlist );
131         clist = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist,"playlist_clist" ));
132         gdk_window_raise( p_intf->p_sys->p_playlist->window );
133         rebuildCList( clist , p_main->p_playlist );
134     }
135 }
136
137 void
138 on_playlist_ok_clicked                 (GtkButton       *button,
139                                         gpointer         user_data)
140 {
141     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(button), "intf_playlist" );
142     gtk_widget_hide( p_intf->p_sys->p_playlist );
143 }
144
145 void  deleteGListItem(gpointer data, gpointer param)
146 {
147     int curRow = ( int )data;
148     intf_thread_t * p_intf = param;    
149     
150     intf_PlstDelete( p_main->p_playlist, curRow );
151
152     /* are we deleting the current played stream */
153     if( p_intf->p_sys->i_playing == curRow )
154     {
155         /* next ! */
156         p_intf->p_input->b_eof = 1;
157         /* this has to set the slider to 0 */
158         
159         /* step minus one */
160         p_intf->p_sys->i_playing-- ;
161         p_main->p_playlist->i_index-- ;
162     }
163 }
164 gint compareItems(gconstpointer a, gconstpointer b)
165 {
166     return b - a;
167 }
168
169 void 
170 rebuildCList(GtkCList * clist, playlist_t * playlist_p)
171 {
172     int dummy;
173     gchar * text[2];
174     GdkColor red;
175     red.red = 65535;
176     red.green = 0;
177     red.blue = 0;
178
179     
180     gtk_clist_freeze( clist );
181     gtk_clist_clear( clist );
182    
183     for( dummy=0; dummy < playlist_p->i_size; dummy++ )
184     {
185         text[0] = g_strdup( rindex( (char *)(playlist_p->p_item[playlist_p->i_size -1 - dummy].psz_name ), '/' ) + 1 );
186         text[1] = g_strdup( "no info");
187         
188         gtk_clist_insert( clist, 0, text );
189         
190         free(text[0]);
191         free(text[1]);
192     }
193     gtk_clist_set_background (
194       clist, 
195       playlist_p->i_index, 
196       &red);
197     gtk_clist_thaw( clist );
198 }
199
200 void
201 on_invertselection_clicked (GtkMenuItem *item, gpointer user_data)
202 {
203     int * selected, sel_l;
204     GtkCList    * clist;
205     playlist_t * playlist_p;
206     int dummy;
207     
208     /* catch the thread back */
209     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(item), "intf_playlist" );
210     playlist_p = p_main->p_playlist;
211     
212     /* lock the struct */
213     vlc_mutex_lock( &p_intf->p_sys->change_lock );
214     clist = GTK_CLIST( lookup_widget(p_intf->p_sys->p_playlist,"playlist_clist") );
215     
216     /* have to copy the selection to an int *
217        I wasn't able to copy the g_list to another g_list
218        glib only does pointer copies, not real copies :( */
219     
220     selected = malloc(sizeof(int)* g_list_length(clist->selection));
221     sel_l = g_list_length(clist->selection);
222     for(dummy=0; dummy < sel_l; dummy++)
223     {
224         selected[dummy] = (int)g_list_nth_data(clist->selection,dummy);
225     }
226     
227     gtk_clist_freeze( clist );
228     gtk_clist_select_all( clist );
229     for(dummy=0; dummy < sel_l; dummy++)
230     {
231         gtk_clist_unselect_row( clist, selected[dummy],0);
232         gtk_clist_unselect_row( clist, selected[dummy],1);
233     }
234     free( selected );
235     gtk_clist_thaw( clist );
236     vlc_mutex_unlock( &p_intf->p_sys->change_lock );
237 }    
238
239 void
240 on_crop_activate                       (GtkMenuItem     *menuitem,
241                                        gpointer         user_data)
242 {
243     /* Ok, this is a really small thing, but, hey, it works and
244        might be useful, who knows ? */
245     
246     on_invertselection_clicked (menuitem, user_data);
247     on_delete_clicked(menuitem, user_data);
248 }
249
250
251 void
252 on_delete_clicked                      (GtkMenuItem       *item,
253                                         gpointer         user_data)
254 {
255     /* user wants to delete a file in the queue */
256     GList * selection;
257     GtkCList    * clist;
258     playlist_t * playlist_p;
259     
260     /* catch the thread back */
261     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(item), "intf_playlist" );
262     playlist_p = p_main->p_playlist;
263     
264     /* lock the struct */
265     vlc_mutex_lock( &p_intf->p_sys->change_lock );
266     clist = GTK_CLIST( lookup_widget(p_intf->p_sys->p_playlist,"playlist_clist") );
267     
268     /* I use UNDOCUMENTED features to retrieve the selection... */
269     selection = clist->selection; 
270     
271     if( g_list_length(selection)>0 )
272     {
273         selection = g_list_sort( selection, compareItems );
274         g_list_foreach( selection,
275                         deleteGListItem, 
276                         p_intf );
277         
278         rebuildCList( clist, playlist_p );
279     }
280     
281     vlc_mutex_unlock( &p_intf->p_sys->change_lock );
282 }
283
284 gboolean
285 on_intf_playlist_destroy_event         (GtkWidget       *widget,
286                                         GdkEvent        *event,
287                                         gpointer         user_data)
288 {
289   gtk_widget_hide(widget);
290
291   return TRUE;
292 }
293
294 void
295 on_intf_playlist_drag_data_received    (GtkWidget       *widget,
296     GdkDragContext  *drag_context,
297     gint             x,
298     gint             y,
299     GtkSelectionData *data,
300     guint            info,
301     guint            time,
302     gpointer         user_data)
303 {
304     /* catch the interface back */
305     intf_thread_t * p_intf =  GetIntf( GTK_WIDGET(widget), "intf_playlist" );
306     GtkCList *  clist;
307     gint row, col;
308
309     clist = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist,"playlist_clist" ));
310     
311     if( gtk_clist_get_selection_info( clist, 
312                 x, 
313                 y, 
314                 &row, 
315                 &col )== 1)
316     {
317         on_generic_drop_data_received( p_intf, data, info, row);
318     } else {
319         on_generic_drop_data_received( p_intf, data, info, PLAYLIST_END);
320     }
321 }
322     
323 void on_generic_drop_data_received( intf_thread_t * p_intf,
324         GtkSelectionData *data, guint info, int position)
325 {
326     /* first we'll have to split against all the '\n' we have */
327     gchar * protocol;
328     gchar * temp;
329     gchar * string = data->data ;
330     GList * files = NULL;
331     GtkCList * clist;
332
333     
334     /* catch the playlist back */
335     playlist_t * p_playlist = p_main->p_playlist ;
336    
337
338     /* if this has been URLencoded, decode it
339      * 
340      * Is it a good thing to do it in place ?
341      * probably not... 
342      */
343     if(info == DROP_ACCEPT_TEXT_URI_LIST)
344     {
345         urldecode_path( string );
346     }
347     
348     /* this cuts string into single file drops */
349     while(*string)
350     {
351         temp = strchr(string, '\n');
352         if(temp)
353         {
354             if (*(temp - 1) == '\r')
355                 *(temp - 1) = '\0';
356             *temp = '\0';
357         }
358        
359         
360         /* do we have a protocol or something ? */
361         protocol = strstr( string, ":/" );
362         if( protocol != NULL )
363         {
364             protocol = calloc( protocol - string + 2 , 
365                             sizeof(char));
366             protocol = strncpy( protocol, string, strstr( string, ":/") + 1 - string );
367
368             intf_WarnMsg(1,"Protocol dropped is %s",protocol);
369             string += strlen(protocol) ;
370
371             /* Allowed things are proto: or proto:// */
372             if(string[0]=='/' && string[1]=='/')
373             {
374                 /* eat one '/' */
375                 string++;
376             }
377             intf_WarnMsg(1,"Dropped %s",string);
378
379         } else {
380             protocol = strdup("");
381         }
382          
383         /* if it uses the file protocol we can do something, else, sorry :( 
384          * I think this is a good choice for now, as we don't have any
385          * ability to read http:// or ftp:// files
386          * what about adding dvd:// to the list of authorized proto ? */
387         
388         if( strcmp(protocol,"file:")==0 )
389         {
390             files = g_list_concat( files, intf_readFiles( string ) ); 
391         }
392        
393         /* free the malloc and go on... */
394         free( protocol );
395         if (!temp)
396             break;
397         string = temp + 1;
398     }
399    
400     /* At this point, we have a nice big list maybe NULL */
401     if(files != NULL)
402     {
403         /* lock the interface */
404         vlc_mutex_lock( &p_intf->p_sys->change_lock );
405         intf_WarnMsg(1, "List has %d elements",g_list_length(files)); 
406         intf_AppendList( p_playlist, position, files );
407         /* get the CList  and rebuild it. */
408         clist = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist,"playlist_clist" )); 
409         rebuildCList( clist , p_playlist );
410         
411         /* unlock the interface */
412         vlc_mutex_unlock( &p_intf->p_sys->change_lock );
413     }
414 }
415
416 /* check a file (string) against supposed valid extension */
417 int 
418 hasValidExtension(gchar * filename)
419 {
420     char * ext[6] = {"mpg","mpeg","vob","mp2","ts","ps"};
421     int  i_ext = 6;
422     int dummy;
423     gchar * p_filename = strrchr( filename, '.') + sizeof( char );
424     for(dummy=0; dummy<i_ext;dummy++)
425     {
426         if(strcmp(p_filename,ext[dummy])==0)
427             return 1;
428     }
429     return 0;
430 }
431
432 /* recursive function: descend into folders and build a list of valid filenames */
433 GList * 
434 intf_readFiles(gchar * fsname )
435 {
436     struct stat statbuf;
437     GList  * current = NULL;
438
439     stat(fsname, &statbuf);
440     
441     /* is it a regular file ? */
442     if( S_ISREG( statbuf.st_mode ) )
443     {
444         if( hasValidExtension(fsname) )
445         {
446             intf_WarnMsg( 3, "%s is a valid file. Stacking on the playlist", fsname );
447             return g_list_append( NULL, g_strdup(fsname) );
448         } else
449             return NULL;
450     } 
451     /* is it a directory (should we check for symlinks ?) */
452     else if( S_ISDIR( statbuf.st_mode ) ) 
453     {
454         /* have to cd into this dir */
455         DIR * currentDir = opendir( fsname );
456         struct dirent * dirContent; 
457         
458         intf_WarnMsg( 3, "%s is a folder.", fsname );
459         
460         if( currentDir == NULL )
461         {
462             /* something went bad, get out of here ! */
463             return current;
464         }
465         dirContent = readdir( currentDir );
466
467         /* while we still have entries in the directory */
468         while( dirContent != NULL )
469         {
470             /* if it is "." or "..", forget it */
471             if(strcmp(dirContent->d_name,".") != 0
472                     && strcmp(dirContent->d_name,"..") != 0)
473             {
474                 /* else build the new directory by adding
475                    fsname "/" and the current entry name 
476                    (kludgy :()
477                   */
478                 char * newfs = malloc ( 2 + 
479                         strlen( fsname ) + 
480                         strlen( dirContent->d_name ) * sizeof( char ) );
481                 strcpy( newfs, fsname );
482                 strcpy( newfs + strlen( fsname )+1, dirContent->d_name);
483                 newfs[strlen(fsname)] = '/';
484                 
485                 current = g_list_concat( current, intf_readFiles( newfs ) );
486                     
487                 g_free( newfs );
488             }
489             dirContent = readdir( currentDir );
490         }
491         return current;
492     }
493     return NULL;
494 }
495
496 /* add items in a playlist 
497   when i_pos==-1 add to the end of the list... 
498  */
499 int intf_AppendList( playlist_t * p_playlist, int i_pos, GList * list )
500 {
501     guint length, dummy;
502     length = g_list_length( list );
503     for(dummy=0; dummy<length; dummy++)
504     {
505         intf_PlstAdd( p_playlist, 
506                 i_pos==PLAYLIST_END?PLAYLIST_END:(i_pos + dummy), 
507                 g_list_nth_data(list, dummy));
508     }
509     return 0;
510 }
511 gboolean
512 on_playlist_clist_event                        (GtkWidget       *widget,
513         GdkEvent        *event,
514         gpointer         user_data)
515 {
516     intf_thread_t * p_intf =  GetIntf( GTK_WIDGET(widget), "intf_playlist" );
517
518     if( (event->button).type == GDK_2BUTTON_PRESS )
519     {
520         GtkCList *  clist;
521         gint row, col;
522
523         clist = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist,"playlist_clist" )); 
524         if( gtk_clist_get_selection_info( clist, 
525                     (event->button).x, 
526                     (event->button).y, 
527                     &row, 
528                     &col )== 1)
529         {
530
531             /* clicked is in range. */
532             if( p_intf->p_input != NULL )
533             {
534                 /* FIXME: temporary hack */
535                 p_intf->p_input->b_eof = 1;
536             }
537             intf_PlstJumpto( p_main->p_playlist, row-1 );
538         }
539         return TRUE;
540     }
541     return FALSE;
542 }
543
544 /* statis timeouted function */
545 void GtkPlayListManage( gpointer p_data )
546 {
547
548     /* this thing really sucks for now :( */
549
550     /* TODO speak more with interface/intf_plst.c */
551
552     intf_thread_t *p_intf = (void *)p_data;
553     playlist_t * p_playlist = p_main->p_playlist ;
554
555     vlc_mutex_lock( &p_intf->p_sys->change_lock );
556
557     if(p_intf->p_sys->i_playing != p_playlist->i_index)
558     {
559         GdkColor color;
560
561         color.red = 65535;
562         color.green = 0;
563         color.blue = 0;
564
565         gtk_clist_set_background (
566         GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist, "playlist_clist" ) ),
567         p_playlist->i_index,
568         &color);
569         if( p_intf->p_sys->i_playing != -1 )
570         {
571             color.red = 65535;
572             color.green = 65535;
573             color.blue = 65535;
574             gtk_clist_set_background (
575             GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist, "playlist_clist" ) ),
576             p_intf->p_sys->i_playing,
577             &color);
578         }
579         p_intf->p_sys->i_playing = p_playlist->i_index;
580     }
581     vlc_mutex_unlock( &p_intf->p_sys->change_lock );
582 }
583