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