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