playlist.c
上传用户:riyaled888
上传日期:2009-03-27
资源大小:7338k
文件大小:21k
源码类别:

多媒体

开发平台:

MultiPlatform

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