]> git.sesse.net Git - vlc/blob - plugins/gtk/gtk_playlist.c
* Coding style fixes here and there.
[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.10 2001/04/28 03:36:25 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 "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 #ifdef WIN32 /* WIN32 HACK */
174         text[0] = g_strdup( "" );
175 #else
176         text[0] = g_strdup( rindex( (char *)(playlist_p->p_item[playlist_p->i_size -1 - dummy].psz_name ), '/' ) + 1 );
177 #endif
178         text[1] = g_strdup( "no info");
179         
180         gtk_clist_insert( clist, 0, text );
181         
182         free(text[0]);
183         free(text[1]);
184     }
185     gtk_clist_set_background (
186       clist, 
187       playlist_p->i_index, 
188       &red);
189     gtk_clist_thaw( clist );
190 }
191
192 void
193 on_invertselection_clicked (GtkMenuItem *item, gpointer user_data)
194 {
195     int * selected, sel_l;
196     GtkCList    * clist;
197     playlist_t * playlist_p;
198     int dummy;
199     
200     /* catch the thread back */
201     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(item), "intf_playlist" );
202     playlist_p = p_main->p_playlist;
203     
204     /* lock the struct */
205     vlc_mutex_lock( &p_intf->change_lock );
206     clist = GTK_CLIST( lookup_widget(p_intf->p_sys->p_playlist,"playlist_clist") );
207     
208     /* have to copy the selection to an int *
209        I wasn't able to copy the g_list to another g_list
210        glib only does pointer copies, not real copies :( */
211     
212     selected = malloc(sizeof(int)* g_list_length(clist->selection));
213     sel_l = g_list_length(clist->selection);
214     for(dummy=0; dummy < sel_l; dummy++)
215     {
216         selected[dummy] = (int)g_list_nth_data(clist->selection,dummy);
217     }
218     
219     gtk_clist_freeze( clist );
220     gtk_clist_select_all( clist );
221     for(dummy=0; dummy < sel_l; dummy++)
222     {
223         gtk_clist_unselect_row( clist, selected[dummy],0);
224         gtk_clist_unselect_row( clist, selected[dummy],1);
225     }
226     free( selected );
227     gtk_clist_thaw( clist );
228     vlc_mutex_unlock( &p_intf->change_lock );
229 }    
230
231 void
232 on_crop_activate                       (GtkMenuItem     *menuitem,
233                                        gpointer         user_data)
234 {
235     /* Ok, this is a really small thing, but, hey, it works and
236        might be useful, who knows ? */
237     
238     on_invertselection_clicked (menuitem, user_data);
239     on_delete_clicked(menuitem, user_data);
240 }
241
242
243 void
244 on_delete_clicked                      (GtkMenuItem       *item,
245                                         gpointer         user_data)
246 {
247     /* user wants to delete a file in the queue */
248     GList * selection;
249     GtkCList    * clist;
250     playlist_t * playlist_p;
251     
252     /* catch the thread back */
253     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(item), "intf_playlist" );
254     playlist_p = p_main->p_playlist;
255     
256     /* lock the struct */
257     vlc_mutex_lock( &p_intf->change_lock );
258     clist = GTK_CLIST( lookup_widget(p_intf->p_sys->p_playlist,"playlist_clist") );
259     
260     /* I use UNDOCUMENTED features to retrieve the selection... */
261     selection = clist->selection; 
262     
263     if( g_list_length(selection)>0 )
264     {
265         /* reverse-sort so that we can delete from the furthest to the 
266            closest item to delete...
267           */
268         selection = g_list_sort( selection, compareItems );
269         g_list_foreach( selection,
270                         deleteGListItem, 
271                         p_intf );
272         /* rebuild the CList */
273         rebuildCList( clist, playlist_p );
274     }
275     
276     vlc_mutex_unlock( &p_intf->change_lock );
277 }
278
279 gboolean
280 on_intf_playlist_destroy_event         (GtkWidget       *widget,
281                                         GdkEvent        *event,
282                                         gpointer         user_data)
283 {
284     /* hide ! */
285     gtk_widget_hide(widget);
286     return TRUE;
287 }
288
289 void
290 on_intf_playlist_drag_data_received    (GtkWidget       *widget,
291     GdkDragContext  *drag_context,
292     gint             x,
293     gint             y,
294     GtkSelectionData *data,
295     guint            info,
296     guint            time,
297     gpointer         user_data)
298 {
299     /* catch the interface back */
300     intf_thread_t * p_intf =  GetIntf( GTK_WIDGET(widget), "intf_playlist" );
301     GtkCList *  clist;
302     gint row, col;
303
304     clist = GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist,"playlist_clist" ));
305    
306     /* are we dropping somewhere into the clist items ? */
307     if( gtk_clist_get_selection_info( clist, 
308                 x, 
309                 y, 
310                 &row, 
311                 &col )== 1)
312     {
313         on_generic_drop_data_received( p_intf, data, info, row );
314     } 
315     /* else, put that at the end of the playlist */
316     else 
317     {
318         on_generic_drop_data_received( p_intf, data, info, PLAYLIST_END);
319     }
320 }
321     
322 void on_generic_drop_data_received( intf_thread_t * p_intf,
323         GtkSelectionData *data, guint info, int position)
324 {
325     /* first we'll have to split against all the '\n' we have */
326     gchar * protocol;
327     gchar * temp;
328     gchar * string = data->data ;
329     GList * files = NULL;
330     GtkCList * clist;
331
332     
333     /* catch the playlist back */
334     playlist_t * p_playlist = p_main->p_playlist ;
335    
336
337     /* if this has been URLencoded, decode it
338      * 
339      * Is it a good thing to do it in place ?
340      * probably not... 
341      */
342     if(info == DROP_ACCEPT_TEXT_URI_LIST)
343     {
344         urldecode_path( string );
345     }
346     
347     /* this cuts string into single file drops */
348     /* this code was borrowed from xmms, thx guys :) */
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         } 
380         else 
381         {
382             protocol = strdup("");
383         }
384          
385         /* if it uses the file protocol we can do something, else, sorry :( 
386          * I think this is a good choice for now, as we don't have any
387          * ability to read http:// or ftp:// files
388          * what about adding dvd:// to the list of authorized proto ? */
389         
390         if( strcmp(protocol,"file:")==0 )
391         {
392             files = g_list_concat( files, intf_readFiles( string ) ); 
393         }
394        
395         /* free the malloc and go on... */
396         free( protocol );
397         if (!temp)
398             break;
399         string = temp + 1;
400     }
401    
402     /* At this point, we have a nice big list maybe NULL */
403     if(files != NULL)
404     {
405         /* lock the interface */
406         vlc_mutex_lock( &p_intf->change_lock );
407         intf_WarnMsg( 1, "List has %d elements",g_list_length( files ) ); 
408         intf_AppendList( p_playlist, position, files );
409
410         /* get the CList  and rebuild it. */
411         clist = GTK_CLIST(
412                 lookup_widget( p_intf->p_sys->p_playlist,
413                                "playlist_clist" ) ); 
414         rebuildCList( clist , p_playlist );
415         
416         /* unlock the interface */
417         vlc_mutex_unlock( &p_intf->change_lock );
418     }
419 }
420
421 /* check a file (string) against supposed valid extension */
422 int 
423 hasValidExtension( gchar * filename )
424 {
425     char * ext[6] = {"mpg","mpeg","vob","mp2","ts","ps"};
426     int  i_ext = 6;
427     int dummy;
428     gchar * p_filename = strrchr( filename, '.' ) + sizeof( char );
429     for( dummy=0; dummy<i_ext;dummy++ )
430     {
431         if( strcmp( p_filename,ext[dummy] )==0 )
432             return 1;
433     }
434     return 0;
435 }
436
437 /* recursive function: descend into folders and build a list of valid filenames */
438 GList * 
439 intf_readFiles( gchar * fsname )
440 {
441     struct stat statbuf;
442     GList  * current = NULL;
443
444     /* get the attributes of this file */
445     stat(fsname, &statbuf);
446     
447     /* is it a regular file ? */
448     if( S_ISREG( statbuf.st_mode ) )
449     {
450         if( hasValidExtension(fsname) )
451         {
452             intf_WarnMsg( 3, "%s is a valid file. Stacking on the playlist", fsname );
453             return g_list_append( NULL, g_strdup(fsname) );
454         } 
455         else
456         {
457             return NULL;
458         }
459     } 
460     /* is it a directory (should we check for symlinks ?) */
461     else if( S_ISDIR( statbuf.st_mode ) ) 
462     {
463         /* have to cd into this dir */
464         DIR * currentDir = opendir( fsname );
465         struct dirent * dirContent; 
466         
467         intf_WarnMsg( 3, "%s is a folder.", fsname );
468         
469         if( currentDir == NULL )
470         {
471             /* something went bad, get out of here ! */
472             return current;
473         }
474         dirContent = readdir( currentDir );
475
476         /* while we still have entries in the directory */
477         while( dirContent != NULL )
478         {
479             /* if it is "." or "..", forget it */
480             if(strcmp(dirContent->d_name,".") != 0
481                     && strcmp(dirContent->d_name,"..") != 0)
482             {
483                 /* else build the new directory by adding
484                    fsname "/" and the current entry name 
485                    (kludgy :()
486                   */
487                 char * newfs = malloc ( 2 + 
488                         strlen( fsname ) + 
489                         strlen( dirContent->d_name ) * sizeof( char ) );
490                 strcpy( newfs, fsname );
491                 strcpy( newfs + strlen( fsname )+1, dirContent->d_name);
492                 newfs[strlen( fsname )] = '/';
493                 
494                 current = g_list_concat( current, intf_readFiles( newfs ) );
495                     
496                 g_free( newfs );
497             }
498             dirContent = readdir( currentDir );
499         }
500         return current;
501     }
502     return NULL;
503 }
504
505 /* add items in a playlist 
506   when i_pos==-1 add to the end of the list... 
507  */
508 int intf_AppendList( playlist_t * p_playlist, int i_pos, GList * list )
509 {
510     guint length, dummy;
511     length = g_list_length( list );
512     for( dummy=0; dummy<length; dummy++ )
513     {
514         intf_PlaylistAdd( p_playlist, 
515                 /* ok; this is a really nasty trick to insert
516                    the item where they are suppose to go but, hey
517                    this works :P (btw, you are really nasty too) */
518                 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + dummy ), 
519                 g_list_nth_data(list, dummy));
520     }
521     return 0;
522 }
523 gboolean
524 on_playlist_clist_event (GtkWidget       *widget,
525                             GdkEvent        *event,
526                             gpointer         user_data)
527 {
528     intf_thread_t * p_intf =  GetIntf( GTK_WIDGET( widget ), "intf_playlist" );
529
530     if( ( event->button ).type == GDK_2BUTTON_PRESS )
531     {
532         GtkCList *  clist;
533         gint row, col;
534
535         clist = GTK_CLIST( 
536                     lookup_widget( 
537                         p_intf->p_sys->p_playlist,
538                         "playlist_clist" ) );
539         
540         if( gtk_clist_get_selection_info( clist, 
541                     (event->button).x, 
542                     (event->button).y, 
543                     &row, 
544                     &col )== 1 )
545         {
546
547             /* clicked is in range. */
548             if( p_intf->p_input != NULL )
549             {
550                 /* FIXME: temporary hack */
551                 p_intf->p_input->b_eof = 1;
552             }
553             intf_PlaylistJumpto( p_main->p_playlist, row-1 );
554         }
555         return TRUE;
556     }
557     return FALSE;
558 }
559
560 /* statis timeouted function */
561 void GtkPlayListManage( gpointer p_data )
562 {
563     /* this thing really sucks for now :( */
564
565     /* TODO speak more with interface/intf_playlist.c */
566
567     intf_thread_t *p_intf = (void *)p_data;
568     playlist_t * p_playlist = p_main->p_playlist ;
569
570     vlc_mutex_lock( &p_intf->change_lock );
571
572     if( p_intf->p_sys->i_playing != p_playlist->i_index )
573     {
574         GdkColor color;
575
576         color.red = 0xffff;
577         color.green = 0;
578         color.blue = 0;
579
580         gtk_clist_set_background ( GTK_CLIST(
581                     lookup_widget( p_intf->p_sys->p_playlist, 
582                                    "playlist_clist" ) ),
583                     p_playlist->i_index,
584                     &color );
585
586         if( p_intf->p_sys->i_playing != -1 )
587         {
588             color.red = 0xffff;
589             color.green = 0xffff;
590             color.blue = 0xffff;
591             gtk_clist_set_background (
592             GTK_CLIST(lookup_widget( p_intf->p_sys->p_playlist, "playlist_clist" ) ),
593             p_intf->p_sys->i_playing,
594             &color);
595         }
596         p_intf->p_sys->i_playing = p_playlist->i_index;
597     }
598     vlc_mutex_unlock( &p_intf->change_lock );
599 }
600