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