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