]> git.sesse.net Git - vlc/blob - plugins/gtk/gtk_playlist.c
36c2d34ead41c231b12204f6d3006fc3f236458a
[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.24 2001/12/30 07:09:55 sam Exp $
6  *
7  * Authors: Pierre Baillet <oct@zoy.org>
8  *          Stéphane Borel <stef@via.ecp.fr>
9  *      
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  * 
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <videolan/vlc.h>
32
33 #include <sys/types.h>          /* for readdir  and stat stuff */
34
35 #ifndef WIN32
36 #   include <dirent.h>
37 #endif
38
39 #include <sys/stat.h>
40 #include <unistd.h>
41
42 #ifdef MODULE_NAME_IS_gnome
43 #   include <gnome.h>
44 #else
45 #   include <gtk/gtk.h>
46 #endif
47
48 #include "stream_control.h"
49 #include "input_ext-intf.h"
50
51 #include "interface.h"
52 #include "intf_playlist.h"
53
54 #include "gtk_callbacks.h"
55 #include "gtk_interface.h"
56 #include "gtk_support.h"
57 #include "gtk_playlist.h"
58 #include "gtk_common.h"
59
60 /****************************************************************************
61  * Playlist window management
62  ****************************************************************************/
63 gboolean GtkPlaylistShow( GtkWidget       *widget,
64                           GdkEventButton  *event,
65                           gpointer         user_data )
66 {
67     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
68
69     if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playlist ) )
70     {
71         gtk_widget_hide( p_intf->p_sys->p_playlist );
72     } 
73     else 
74     {        
75         GtkCList * p_clist;
76
77         p_clist = GTK_CLIST( gtk_object_get_data(
78             GTK_OBJECT( p_intf->p_sys->p_playlist ), "playlist_clist" ) );
79         GtkRebuildCList( p_clist , p_main->p_playlist );
80         gtk_widget_show( p_intf->p_sys->p_playlist );
81         gdk_window_raise( p_intf->p_sys->p_playlist->window );
82     }
83
84     return TRUE;
85 }
86
87
88 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
89 {
90      gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
91 }
92
93
94 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
95 {
96      gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
97 }
98
99
100
101 gboolean GtkPlaylistPrev( GtkWidget       *widget,
102                           GdkEventButton  *event,
103                           gpointer         user_data )
104 {
105     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
106
107     if( p_intf->p_input != NULL )
108     {
109         /* FIXME: temporary hack */
110         intf_PlaylistPrev( p_main->p_playlist );
111         intf_PlaylistPrev( p_main->p_playlist );
112         p_intf->p_input->b_eof = 1;
113     }
114
115     return TRUE;
116 }
117
118
119 gboolean GtkPlaylistNext( GtkWidget       *widget,
120                           GdkEventButton  *event,
121                           gpointer         user_data)
122 {
123     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
124
125     if( p_intf->p_input != NULL )
126     {
127         /* FIXME: temporary hack */
128         p_intf->p_input->b_eof = 1;
129     }
130
131     return TRUE;
132 }
133
134 /****************************************************************************
135  * Menu callbacks for playlist functions
136  ****************************************************************************/
137 void GtkPlaylistActivate( GtkMenuItem * menuitem, gpointer user_data )
138 {
139     GtkPlaylistShow( GTK_WIDGET( menuitem ), NULL, user_data );
140 }
141
142
143 void GtkNextActivate( GtkMenuItem * menuitem, gpointer user_data )
144 {
145     GtkPlaylistNext( GTK_WIDGET( menuitem ), NULL, user_data );
146 }
147
148
149 void GtkPrevActivate( GtkMenuItem * menuitem, gpointer user_data )
150 {
151     GtkPlaylistPrev( GTK_WIDGET( menuitem ), NULL, user_data );
152 }
153
154
155 /****************************************************************************
156  * Playlist core functions
157  ****************************************************************************/
158 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
159 {
160
161 }
162
163
164 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
165 {
166
167 }
168
169
170 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
171 {
172     /* user wants to delete a file in the queue */
173     GList *     p_selection;
174     GtkCList *  p_clist;
175     playlist_t *p_playlist;
176     
177     /* catch the thread back */
178     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), /*(char*)user_data*/"intf_playlist" );
179
180     p_playlist = p_main->p_playlist;
181     
182     /* lock the struct */
183     vlc_mutex_lock( &p_intf->change_lock );
184
185     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
186         p_intf->p_sys->p_playlist ), "playlist_clist" ) );
187     
188     /* I use UNDOCUMENTED features to retrieve the selection... */
189     p_selection = p_clist->selection;
190     
191     if( g_list_length( p_selection ) > 0 )
192     {
193         /* reverse-sort so that we can delete from the furthest
194          * to the closest item to delete...
195          */
196         p_selection = g_list_sort( p_selection, GtkCompareItems );
197         g_list_foreach( p_selection, GtkDeleteGListItem, p_intf );
198         /* rebuild the CList */
199         GtkRebuildCList( p_clist, p_playlist );
200     }
201     
202     vlc_mutex_unlock( &p_intf->change_lock );
203 }
204
205 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
206 {
207     /* Ok, this is a really small thing, but, hey, it works and
208        might be useful, who knows ? */
209     GtkPlaylistInvert( menuitem, user_data );
210     GtkPlaylistDeleteSelected( menuitem, user_data );
211 }
212
213 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
214 {
215     playlist_t *p_playlist;
216     GtkCList *  p_clist;
217     int *       pi_selected;
218     int         i_sel_l;
219     int         i_dummy;
220     
221     /* catch the thread back */
222     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
223
224     p_playlist = p_main->p_playlist;
225     
226     /* lock the struct */
227     vlc_mutex_lock( &p_intf->change_lock );
228
229     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
230         p_intf->p_sys->p_playlist ), "playlist_clist" ) );
231     
232     /* have to copy the selection to an int *
233        I wasn't able to copy the g_list to another g_list
234        glib only does pointer copies, not real copies :( */
235     
236     pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
237     i_sel_l = g_list_length( p_clist->selection );
238
239     for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
240     {
241         pi_selected[i_dummy] = (long)g_list_nth_data( p_clist->selection,
242                                                       i_dummy );
243     }
244     
245     gtk_clist_freeze( p_clist );
246     gtk_clist_select_all( p_clist );
247
248     for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
249     {
250         gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
251         gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
252     }
253
254     free( pi_selected );
255     gtk_clist_thaw( p_clist );
256
257     vlc_mutex_unlock( &p_intf->change_lock );
258 }
259
260 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
261 {
262
263 }
264
265 gboolean GtkPlaylistEvent( GtkWidget * widget,
266                            GdkEvent  * event,
267                            gpointer    user_data)
268 {
269     intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
270
271     if( ( event->button ).type == GDK_2BUTTON_PRESS )
272     {
273         GtkCList *  p_clist;
274         gint        i_row;
275         gint        i_col;
276
277         p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
278             p_intf->p_sys->p_playlist ), "playlist_clist" ) );
279         
280         if( gtk_clist_get_selection_info( p_clist, (event->button).x, 
281                     (event->button).y, &i_row, &i_col ) == 1 )
282         {
283             /* clicked is in range. */
284             if( p_intf->p_input != NULL )
285             {
286                 /* FIXME: temporary hack */
287                 p_intf->p_input->b_eof = 1;
288             }
289
290             intf_PlaylistJumpto( p_main->p_playlist, i_row - 1 );
291         }
292         return TRUE;
293     }
294
295     return FALSE;
296 }
297
298 void GtkPlaylistDragData( GtkWidget       *widget,
299                           GdkDragContext  *drag_context,
300                           gint             x,
301                           gint             y,
302                           GtkSelectionData *data,
303                           guint            info,
304                           guint            time,
305                           gpointer         user_data )
306 {
307     intf_thread_t * p_intf;
308     GtkCList *      p_clist;
309     gint            i_row;
310     gint            i_col;
311     int             i_end = p_main->p_playlist->i_size;
312
313     /* catch the interface back */
314     p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
315
316     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
317         p_intf->p_sys->p_playlist ), "playlist_clist" ) );
318    
319     if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
320     {
321         /* we are dropping somewhere into the clist items */
322         GtkDropDataReceived( p_intf, data, info, i_row );
323     } 
324     else 
325     {
326         /* else, put that at the end of the playlist */
327         GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
328     }
329
330     intf_PlaylistJumpto( p_main->p_playlist, i_end - 1 );
331 }
332
333
334 gboolean GtkPlaylistDragMotion( GtkWidget       *widget,
335                                 GdkDragContext  *drag_context,
336                                 gint             x,
337                                 gint             y,
338                                 guint            time,
339                                 gpointer         user_data )
340 {
341     intf_thread_t *p_intf;
342     GtkCList *  p_clist;
343     gint        i_row;
344     gint        i_col;
345     int         i_dummy;
346     GdkColor    color;
347
348     p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
349
350     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
351         p_intf->p_sys->p_playlist ), "playlist_clist" ) );
352
353     if( !GTK_WIDGET_TOPLEVEL(widget) )
354     {
355         gdk_window_raise( p_intf->p_sys->p_playlist->window );
356     }
357
358     color.red =   0xffff;
359     color.blue =  0xffff;
360     color.green = 0xffff;
361
362     gtk_clist_freeze( p_clist );
363     
364     for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
365     {
366        gtk_clist_set_background ( p_clist, i_dummy , &color);
367     }
368
369     color.red = 0xffff;
370     color.blue = 0;
371     color.green = 0;
372     gtk_clist_set_background( p_clist, p_main->p_playlist->i_index , &color );
373         
374     if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
375     {
376         color.red = 0;
377         color.blue = 0xf000;
378         color.green = 0x9000;
379         gtk_clist_set_background ( p_clist, i_row - 1, &color);
380         gtk_clist_set_background ( p_clist, i_row, &color);
381     }
382
383     gtk_clist_thaw( p_clist );
384     
385     return TRUE;
386 }
387
388 void GtkDropDataReceived( intf_thread_t * p_intf,
389         GtkSelectionData * p_data, guint i_info, int i_position)
390 {
391     /* first we'll have to split against all the '\n' we have */
392     gchar *     p_protocol;
393     gchar *     p_temp;
394     gchar *     p_next;
395     gchar *     p_string = p_data->data ;
396     GList *     p_files = NULL;
397     GtkCList *  p_clist;
398
399     
400     /* catch the playlist back */
401     playlist_t * p_playlist = p_main->p_playlist;
402    
403
404     /* if this has been URLencoded, decode it
405      * 
406      * Is it a good thing to do it in place ?
407      * probably not... 
408      */
409     if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
410     {
411         intf_UrlDecode( p_string );
412     }
413     
414     /* this cuts string into single file drops */
415     /* this code was borrowed from xmms, thx guys :) */
416     while( *p_string)
417     {
418         p_next = strchr( p_string, '\n' );
419         if( p_next )
420         {
421             if( *( p_next - 1 ) == '\r' )
422             {
423                 *( p_next - 1) = '\0';
424             }
425             *p_next = '\0';
426         }
427
428         /* do we have a protocol or something ? */
429         p_temp = strstr( p_string, ":" );
430         if( p_temp != NULL && p_temp[0] != '\0' )
431         {
432             char i_save;
433
434             i_save = p_temp[0];
435             p_temp[0] = '\0';
436             p_protocol = strdup( p_string );
437             p_temp[0] = i_save;
438             p_temp++;
439             
440             /* Allowed things are proto: or proto:// */
441             if( p_temp[0] == '/' && p_temp[1] == '/')
442             {
443                 /* eat two '/'s */
444                 p_temp += 2;
445             }
446             intf_WarnMsg( 4, "playlist: protocol '%s', target '%s'",
447                           p_protocol, p_temp );
448         } 
449         else 
450         {
451             p_protocol = strdup( "" );
452         }
453          
454         /* if it uses the file protocol we can do something, else, sorry :( 
455          * I think this is a good choice for now, as we don't have any
456          * ability to read http:// or ftp:// files
457          * what about adding dvd:// to the list of authorized proto ? */
458         
459         if( strcmp( p_protocol, "file:" ) == 0 )
460         {
461             p_files = g_list_concat( p_files, GtkReadFiles( p_string ) ); 
462         }
463         else
464         {
465             p_files = g_list_concat( p_files,
466                       g_list_append( NULL, g_strdup( p_string ) ) );
467         }
468        
469         /* free the malloc and go on... */
470         free( p_protocol );
471
472         if( p_next == NULL )
473         {
474             break;
475         }
476         p_string = p_next + 1;
477     }
478    
479     /* At this point, we have a nice big list maybe NULL */
480     if( p_files != NULL )
481     {
482         /* lock the interface */
483         vlc_mutex_lock( &p_intf->change_lock );
484
485         intf_WarnMsg( 4, "List has %d elements", g_list_length( p_files ) ); 
486         GtkAppendList( p_playlist, i_position, p_files );
487
488         /* get the CList  and rebuild it. */
489         p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playlist,
490                                             "playlist_clist" ) ); 
491         GtkRebuildCList( p_clist , p_playlist );
492         
493         /* unlock the interface */
494         vlc_mutex_unlock( &p_intf->change_lock );
495     }
496 }
497
498
499 void GtkDeleteGListItem( gpointer data, gpointer param )
500 {
501     int i_cur_row = (long)data;
502     intf_thread_t * p_intf = param;    
503     
504     intf_PlaylistDelete( p_main->p_playlist, i_cur_row );
505
506     /* are we deleting the current played stream */
507     if( p_intf->p_sys->i_playing == i_cur_row )
508     {
509         /* next ! */
510         p_intf->p_input->b_eof = 1;
511         /* this has to set the slider to 0 */
512         
513         /* step minus one */
514         p_intf->p_sys->i_playing-- ;
515
516         vlc_mutex_lock( &p_main->p_playlist->change_lock );
517         p_main->p_playlist->i_index-- ;
518         vlc_mutex_unlock( &p_main->p_playlist->change_lock );
519     }
520 }
521
522
523 gint GtkCompareItems( gconstpointer a, gconstpointer b )
524 {
525     return b - a;
526 }
527
528
529 /* check a file (string) against supposed valid extension */
530 int GtkHasValidExtension( gchar * psz_filename )
531 {
532     char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
533     int  i_ext = 6;
534     int  i_dummy;
535
536     gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
537
538     for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
539     {
540         if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
541         {
542             return 1;
543         }
544     }
545
546     return 0;
547 }
548
549 /* recursive function: descend into folders and build a list of
550  * valid filenames */
551 GList * GtkReadFiles( gchar * psz_fsname )
552 {
553     struct stat statbuf;
554     GList  *    p_current = NULL;
555
556     /* get the attributes of this file */
557     stat( psz_fsname, &statbuf );
558     
559     /* is it a regular file ? */
560     if( S_ISREG( statbuf.st_mode ) )
561     {
562         if( GtkHasValidExtension( psz_fsname ) )
563         {
564             intf_WarnMsg( 2, "%s is a valid file. Stacking on the playlist",
565                           psz_fsname );
566             return g_list_append( NULL, g_strdup( psz_fsname ) );
567         } 
568         else
569         {
570             return NULL;
571         }
572     } 
573     /* is it a directory (should we check for symlinks ?) */
574     else if( S_ISDIR( statbuf.st_mode ) ) 
575     {
576         /* have to cd into this dir */
577         DIR *           p_current_dir = opendir( psz_fsname );
578         struct dirent * p_dir_content; 
579         
580         intf_WarnMsg( 2, "%s is a folder.", psz_fsname );
581         
582         if( p_current_dir == NULL )
583         {
584             /* something went bad, get out of here ! */
585             return p_current;
586         }
587         p_dir_content = readdir( p_current_dir );
588
589         /* while we still have entries in the directory */
590         while( p_dir_content != NULL )
591         {
592             /* if it is "." or "..", forget it */
593             if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
594                 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
595             {
596                 /* else build the new directory by adding
597                    fsname "/" and the current entry name 
598                    (kludgy :()
599                   */
600                 char *  psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
601                             strlen( p_dir_content->d_name ) * sizeof(char) );
602                 strcpy( psz_newfs, psz_fsname );
603                 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
604                         p_dir_content->d_name );
605                 psz_newfs[strlen( psz_fsname )] = '/';
606                 
607                 p_current = g_list_concat( p_current,
608                                            GtkReadFiles( psz_newfs ) );
609                     
610                 g_free( psz_newfs );
611             }
612             p_dir_content = readdir( p_current_dir );
613         }
614         return p_current;
615     }
616     return NULL;
617 }
618
619 /* add items in a playlist 
620  * when i_pos==-1 add to the end of the list... 
621  */
622 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
623 {
624     guint i_dummy;
625     guint i_length;
626
627     i_length = g_list_length( p_list );
628
629     for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
630     {
631         intf_PlaylistAdd( p_playlist, 
632                 /* ok; this is a really nasty trick to insert
633                    the item where they are suppose to go but, hey
634                    this works :P (btw, you are really nasty too) */
635                 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + i_dummy ), 
636                 g_list_nth_data( p_list, i_dummy ) );
637     }
638     return 0;
639 }
640
641 /* statis timeouted function */
642 void GtkPlayListManage( intf_thread_t * p_intf )
643 {
644     /* this thing really sucks for now :( */
645
646     /* TODO speak more with interface/intf_playlist.c */
647
648     playlist_t *    p_playlist = p_main->p_playlist ;
649     GtkCList *      p_clist;
650
651     if( GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
652     {
653         p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
654                        p_intf->p_sys->p_playlist ), "playlist_clist" ) );
655     
656         vlc_mutex_lock( &p_playlist->change_lock );
657     
658         if( p_intf->p_sys->i_playing != p_playlist->i_index )
659         {
660             GdkColor color;
661     
662             color.red = 0xffff;
663             color.blue = 0;
664             color.green = 0;
665     
666             gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
667     
668             if( p_intf->p_sys->i_playing != -1 )
669             {
670                 color.red = 0xffff;
671                 color.blue = 0xffff;
672                 color.green = 0xffff;
673                 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
674                                           &color);
675             }
676             p_intf->p_sys->i_playing = p_playlist->i_index;
677         }
678     
679         vlc_mutex_unlock( &p_playlist->change_lock );
680     }
681 }
682
683 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
684 {
685     int         i_dummy;
686     gchar *     ppsz_text[2];
687     GdkColor    red;
688     red.red     = 65535;
689     red.blue    = 0;
690     red.green   = 0;
691     
692     gtk_clist_freeze( p_clist );
693     gtk_clist_clear( p_clist );
694    
695     for( i_dummy = 0; i_dummy < p_playlist->i_size ; i_dummy++ )
696     {
697 #ifdef WIN32 /* WIN32 HACK */
698         ppsz_text[0] = "";
699 #else
700         ppsz_text[0] = rindex( p_playlist->p_item[
701                 p_playlist->i_size - 1 - i_dummy].psz_name, '/' );
702         if ( ppsz_text[0] == NULL )
703         {
704             ppsz_text[0] =
705               p_playlist->p_item[ p_playlist->i_size - 1 - i_dummy ].psz_name;
706         }
707         else
708         {
709             /* Skip leading '/' */
710             ppsz_text[0]++;
711         }
712 #endif
713         ppsz_text[1] = "no info";
714         
715         gtk_clist_insert( p_clist, 0, ppsz_text );
716     }
717     gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
718     gtk_clist_thaw( p_clist );
719 }
720