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