]> git.sesse.net Git - vlc/blob - plugins/gtk/gtk_playlist.c
.Added crop and invert selection.
[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, "clist1" )) ;
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,"clist1" ));
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,"clist1") );
215     selected = malloc(sizeof(int)* g_list_length(clist->selection));
216
217     sel_l = g_list_length(clist->selection);
218      
219     for(dummy=0; dummy < sel_l; dummy++)
220     {
221         selected[dummy] = (int)g_list_nth_data(clist->selection,dummy);
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->p_sys->change_lock );
233 }    
234
235 void
236 on_crop_activate                       (GtkMenuItem     *menuitem,
237                                        gpointer         user_data)
238 {
239     on_invertselection_clicked (menuitem, user_data);
240     on_delete_clicked(menuitem, user_data);
241 }
242
243
244 void
245 on_delete_clicked                      (GtkMenuItem       *item,
246                                         gpointer         user_data)
247 {
248     /* user wants to delete a file in the queue */
249     GList * selection;
250     GtkCList    * clist;
251     playlist_t * playlist_p;
252     
253     /* catch the thread back */
254     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(item), "intf_playlist" );
255     playlist_p = p_main->p_playlist;
256     
257     /* lock the struct */
258     vlc_mutex_lock( &p_intf->p_sys->change_lock );
259     clist = GTK_CLIST( lookup_widget(p_intf->p_sys->p_playlist,"clist1") );
260     
261     /* I use UNDOCUMENTED features to retrieve the selection... */
262     selection = clist->selection; 
263     
264     if( g_list_length(selection)>0 )
265     {
266         selection = g_list_sort( selection, compareItems );
267         g_list_foreach( selection,
268                         deleteGListItem, 
269                         p_intf );
270         
271         rebuildCList( clist, playlist_p );
272     }
273     
274     vlc_mutex_unlock( &p_intf->p_sys->change_lock );
275 }
276
277 gboolean
278 on_intf_playlist_destroy_event         (GtkWidget       *widget,
279                                         GdkEvent        *event,
280                                         gpointer         user_data)
281 {
282   gtk_widget_hide(widget);
283
284   return TRUE;
285 }
286
287 void
288 on_intf_playlist_drag_data_received    (GtkWidget       *widget,
289     GdkDragContext  *drag_context,
290     gint             x,
291     gint             y,
292     GtkSelectionData *data,
293     guint            info,
294     guint            time,
295     gpointer         user_data)
296 {
297     /* catch the interface back */
298     intf_thread_t * p_intf =  GetIntf( GTK_WIDGET(widget), "intf_playlist" );
299     GtkCList *  clist;
300     gint row, col;
301
302     clist = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist,"clist1" ));
303     
304     if( gtk_clist_get_selection_info( clist, 
305                 x, 
306                 y, 
307                 &row, 
308                 &col )== 1)
309     {
310         on_generic_drop_data_received( p_intf, data, info, row);
311     } else {
312         on_generic_drop_data_received( p_intf, data, info, 0);
313     }
314 }
315     
316 void on_generic_drop_data_received( intf_thread_t * p_intf,
317         GtkSelectionData *data, guint info, int position)
318 {
319     /* first we'll have to split against all the '\n' we have */
320     gchar * protocol;
321     gchar * temp;
322     gchar * string = data->data ;
323     GList * files = NULL;
324     GtkCList * clist;
325
326     
327     /* catch the playlist back */
328     playlist_t * p_playlist = p_main->p_playlist ;
329    
330
331     /* if this has been URLencoded, decode it
332      * 
333      * Is it a good thing to do it in place ?
334      * probably not... 
335      */
336     if(info == DROP_ACCEPT_TEXT_URI_LIST)
337     {
338         urldecode_path( string );
339     }
340     
341     /* this cuts string into single file drops */
342     while(*string)
343     {
344         temp = strchr(string, '\n');
345         if(temp)
346         {
347             if (*(temp - 1) == '\r')
348                 *(temp - 1) = '\0';
349             *temp = '\0';
350         }
351        
352         
353         /* do we have a protocol or something ? */
354         protocol = strstr( string, ":/" );
355         if( protocol != NULL )
356         {
357             protocol = calloc( protocol - string + 2 , 
358                             sizeof(char));
359             protocol = strncpy( protocol, string, strstr( string, ":/") + 1 - string );
360
361             intf_WarnMsg(1,"Protocol dropped is %s",protocol);
362             string += strlen(protocol) ;
363
364             /* Allowed things are proto: or proto:// */
365             if(string[0]=='/' && string[1]=='/')
366             {
367                 /* eat one '/' */
368                 string++;
369             }
370             intf_WarnMsg(1,"Dropped %s",string);
371
372         } else {
373             protocol = strdup("");
374         }
375          
376         /* if it uses the file protocol we can do something, else, sorry :( 
377          * I think this is a good choice for now, as we don't have any
378          * ability to read http:// or ftp:// files
379          * what about adding dvd:// to the list of authorized proto ? */
380         
381         if( strcmp(protocol,"file:")==0 )
382         {
383             files = g_list_concat( files, intf_readFiles( string ) ); 
384         }
385        
386         /* free the malloc and go on... */
387         free( protocol );
388         if (!temp)
389             break;
390         string = temp + 1;
391     }
392    
393     /* At this point, we have a nice big list maybe NULL */
394     if(files != NULL)
395     {
396         /* lock the interface */
397         vlc_mutex_lock( &p_intf->p_sys->change_lock );
398         intf_WarnMsg(1, "List has %d elements",g_list_length(files)); 
399         intf_AppendList( p_playlist, position, files );
400         /* get the CList  and rebuild it. */
401         clist = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist,"clist1" )); 
402         rebuildCList( clist , p_playlist );
403         
404         /* unlock the interface */
405         vlc_mutex_unlock( &p_intf->p_sys->change_lock );
406     }
407 }
408
409 /* check a file (string) against supposed valid extension */
410 int 
411 hasValidExtension(gchar * filename)
412 {
413     char * ext[6] = {"mpg","mpeg","vob","mp2","ts","ps"};
414     int  i_ext = 6;
415     int dummy;
416     gchar * p_filename = strrchr( filename, '.') + sizeof( char );
417     for(dummy=0; dummy<i_ext;dummy++)
418     {
419         if(strcmp(p_filename,ext[dummy])==0)
420             return 1;
421     }
422     return 0;
423 }
424
425 /* recursive function: descend into folders and build a list of valid filenames */
426 GList * 
427 intf_readFiles(gchar * fsname )
428 {
429     struct stat statbuf;
430     GList  * current = NULL;
431
432     stat(fsname, &statbuf);
433     
434     /* is it a regular file ? */
435     if( S_ISREG( statbuf.st_mode ) )
436     {
437         if( hasValidExtension(fsname) )
438         {
439             intf_WarnMsg( 3, "%s is a valid file. Stacking on the playlist", fsname );
440             return g_list_append( NULL, g_strdup(fsname) );
441         } else
442             return NULL;
443     } 
444     /* is it a directory (should we check for symlinks ?) */
445     else if( S_ISDIR( statbuf.st_mode ) ) 
446     {
447         /* have to cd into this dir */
448         DIR * currentDir = opendir( fsname );
449         struct dirent * dirContent; 
450         
451         intf_WarnMsg( 3, "%s is a folder.", fsname );
452         
453         if( currentDir == NULL )
454         {
455             /* something went bad, get out of here ! */
456             return current;
457         }
458         dirContent = readdir( currentDir );
459
460         /* while we still have entries in the directory */
461         while( dirContent != NULL )
462         {
463             /* if it is "." or "..", forget it */
464             if(strcmp(dirContent->d_name,".") != 0
465                     && strcmp(dirContent->d_name,"..") != 0)
466             {
467                 /* else build the new directory by adding
468                    fsname "/" and the current entry name 
469                    (kludgy :()
470                   */
471                 char * newfs = malloc ( 2 + 
472                         strlen( fsname ) + 
473                         strlen( dirContent->d_name ) * sizeof( char ) );
474                 strcpy( newfs, fsname );
475                 strcpy( newfs + strlen( fsname )+1, dirContent->d_name);
476                 newfs[strlen(fsname)] = '/';
477                 
478                 current = g_list_concat( current, intf_readFiles( newfs ) );
479                     
480                 g_free( newfs );
481             }
482             dirContent = readdir( currentDir );
483         }
484         return current;
485     }
486     return NULL;
487 }
488
489 /* add items in a playlist */
490 int intf_AppendList( playlist_t * p_playlist, int i_pos, GList * list )
491 {
492     guint length, dummy;
493     length = g_list_length( list );
494     for(dummy=0; dummy<length; dummy++)
495     {
496         intf_WarnMsg(1,"Adding: %s@%d",g_list_nth_data(list, dummy), i_pos + dummy);
497         intf_PlstAdd( p_playlist, i_pos + dummy, g_list_nth_data(list, dummy));
498     }
499     return 0;
500 }
501 gboolean
502 on_clist1_event                        (GtkWidget       *widget,
503         GdkEvent        *event,
504         gpointer         user_data)
505 {
506     intf_thread_t * p_intf =  GetIntf( GTK_WIDGET(widget), "intf_playlist" );
507
508     if( (event->button).type == GDK_2BUTTON_PRESS )
509     {
510         GtkCList *  clist;
511         gint row, col;
512
513         clist = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist,"clist1" )); 
514         if( gtk_clist_get_selection_info( clist, 
515                     (event->button).x, 
516                     (event->button).y, 
517                     &row, 
518                     &col )== 1)
519         {
520
521             /* clicked is in range. */
522             if( p_intf->p_input != NULL )
523             {
524                 /* FIXME: temporary hack */
525                 p_intf->p_input->b_eof = 1;
526             }
527             intf_PlstJumpto( p_main->p_playlist, row-1 );
528         }
529         return TRUE;
530     }
531     return FALSE;
532 }
533
534 /* statis timeouted function */
535 void GtkPlayListManage( gpointer p_data )
536 {
537
538     /* this thing really sucks for now :( */
539
540     /* TODO speak more with interface/intf_plst.c */
541
542     intf_thread_t *p_intf = (void *)p_data;
543     playlist_t * p_playlist = p_main->p_playlist ;
544
545     vlc_mutex_lock( &p_intf->p_sys->change_lock );
546
547     if(p_intf->p_sys->i_playing != p_playlist->i_index)
548     {
549         GdkColor color;
550
551         color.red = 65535;
552         color.green = 0;
553         color.blue = 0;
554
555         gtk_clist_set_background (
556         GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist, "clist1" ) ),
557         p_playlist->i_index,
558         &color);
559         if( p_intf->p_sys->i_playing != -1 )
560         {
561             color.red = 65535;
562             color.green = 65535;
563             color.blue = 65535;
564             gtk_clist_set_background (
565             GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist, "clist1" ) ),
566             p_intf->p_sys->i_playing,
567             &color);
568         }
569         p_intf->p_sys->i_playing = p_playlist->i_index;
570     }
571     vlc_mutex_unlock( &p_intf->p_sys->change_lock );
572 }
573