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