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