1 /*****************************************************************************
2 * gtk_playlist.c : Interface for the playlist dialog
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: playlist.c,v 1.1 2002/08/04 17:23:43 sam Exp $
7 * Authors: Pierre Baillet <oct@zoy.org>
8 * Stéphane Borel <stef@via.ecp.fr>
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.
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.
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 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
34 #include <sys/types.h> /* for readdir and stat stuff */
36 #if (!defined( WIN32 ) || defined(__MINGW32__))
37 /* Mingw has its own version of dirent */
44 #ifdef MODULE_NAME_IS_gnome
50 #include "gtk_callbacks.h"
51 #include "gtk_interface.h"
52 #include "gtk_support.h"
57 /****************************************************************************
59 ****************************************************************************/
60 static void UrlDecode ( char * );
61 static GList * GtkReadFiles ( intf_thread_t *, gchar * );
63 /****************************************************************************
64 * Playlist window management
65 ****************************************************************************/
66 gboolean GtkPlaylistShow( GtkWidget *widget,
69 intf_thread_t * p_intf = GtkGetIntf( widget );
70 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
72 if( p_playlist == NULL )
77 if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playwin ) )
79 gtk_widget_hide( p_intf->p_sys->p_playwin );
85 p_clist = GTK_CLIST( gtk_object_get_data(
86 GTK_OBJECT( p_intf->p_sys->p_playwin ), "playlist_clist" ) );
87 GtkRebuildCList( p_clist , p_playlist );
88 gtk_widget_show( p_intf->p_sys->p_playwin );
89 gdk_window_raise( p_intf->p_sys->p_playwin->window );
92 vlc_object_release( p_playlist );
98 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
100 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
104 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
106 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
111 gboolean GtkPlaylistPrev( GtkWidget *widget,
114 intf_thread_t * p_intf = GtkGetIntf( widget );
115 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
117 if( p_playlist == NULL )
122 playlist_Prev( p_playlist );
123 vlc_object_release( p_playlist );
129 gboolean GtkPlaylistNext( GtkWidget *widget,
132 intf_thread_t * p_intf = GtkGetIntf( widget );
133 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
135 if( p_playlist == NULL )
140 playlist_Next( p_playlist );
141 vlc_object_release( p_playlist );
146 /****************************************************************************
147 * Playlist core functions
148 ****************************************************************************/
149 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
155 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
161 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
163 /* user wants to delete a file in the queue */
167 intf_thread_t * p_intf = GtkGetIntf( menuitem);
168 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
170 if( p_playlist == NULL )
175 /* lock the struct */
176 vlc_mutex_lock( &p_intf->change_lock );
178 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
179 p_intf->p_sys->p_playwin ), "playlist_clist" ) );
181 p_selection = p_clist->selection;
183 if( g_list_length( p_selection ) )
185 /* reverse-sort so that we can delete from the furthest
186 * to the closest item to delete...
188 p_selection = g_list_sort( p_selection, GtkCompareItems );
189 g_list_foreach( p_selection, GtkDeleteGListItem, p_playlist );
190 /* rebuild the CList */
191 GtkRebuildCList( p_clist, p_playlist );
194 vlc_mutex_unlock( &p_intf->change_lock );
196 vlc_object_release( p_playlist );
199 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
201 /* Ok, this is a really small thing, but, hey, it works and
202 might be useful, who knows ? */
203 GtkPlaylistInvert( menuitem, user_data );
204 GtkPlaylistDeleteSelected( menuitem, user_data );
207 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
214 /* catch the thread back */
215 intf_thread_t *p_intf = GtkGetIntf( menuitem );
217 /* lock the struct */
218 vlc_mutex_lock( &p_intf->change_lock );
220 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
221 p_intf->p_sys->p_playwin ), "playlist_clist" ) );
223 gtk_clist_freeze( p_clist );
225 /* have to copy the selection to an int *
226 I wasn't able to copy the g_list to another g_list
227 glib only does pointer copies, not real copies :( */
229 i_length = g_list_length( p_clist->selection );
230 pi_selected = malloc( sizeof(int) * i_length );
232 for( i_dummy = 0 ; i_dummy < i_length ; i_dummy++ )
234 pi_selected[i_dummy] =
235 GPOINTER_TO_UINT( g_list_nth_data( p_clist->selection, i_dummy ) );
238 gtk_clist_select_all( p_clist );
240 for( i_dummy = 0; i_dummy < i_length; i_dummy++ )
242 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
245 gtk_clist_thaw( p_clist );
247 vlc_mutex_unlock( &p_intf->change_lock );
252 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
257 gboolean GtkPlaylistEvent( GtkWidget * widget,
261 intf_thread_t * p_intf = GtkGetIntf( widget );
262 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
264 if( p_playlist == NULL )
269 if( ( event->button ).type == GDK_2BUTTON_PRESS )
275 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
276 p_intf->p_sys->p_playwin ), "playlist_clist" ) );
278 if( gtk_clist_get_selection_info( p_clist, (event->button).x,
279 (event->button).y, &i_row, &i_col ) == 1 )
281 playlist_Goto( p_playlist, i_row );
284 vlc_object_release( p_playlist );
288 vlc_object_release( p_playlist );
292 void GtkPlaylistDragData( GtkWidget *widget,
293 GdkDragContext *drag_context,
296 GtkSelectionData *data,
301 intf_thread_t * p_intf = GtkGetIntf( widget );
306 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
307 p_intf->p_sys->p_playwin ), "playlist_clist" ) );
309 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
311 /* we are dropping somewhere into the clist items */
312 GtkDropDataReceived( p_intf, data, info, i_row - 1 );
316 /* otherwise, put that at the end of the playlist */
317 GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
322 gboolean GtkPlaylistDragMotion( GtkWidget *widget,
323 GdkDragContext *drag_context,
335 intf_thread_t * p_intf = GtkGetIntf( widget );
336 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
338 if( p_playlist == NULL )
343 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
344 p_intf->p_sys->p_playwin ), "playlist_clist" ) );
346 if( !GTK_WIDGET_TOPLEVEL(widget) )
348 gdk_window_raise( p_intf->p_sys->p_playwin->window );
353 color.green = 0xffff;
355 gtk_clist_freeze( p_clist );
357 for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
359 gtk_clist_set_background( p_clist, i_dummy , &color );
364 color.green = 0x9000;
365 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
367 gtk_clist_set_background ( p_clist, i_row - 1, &color );
368 gtk_clist_set_background ( p_clist, i_row, &color );
372 gtk_clist_set_background ( p_clist, p_clist->rows - 1, &color );
378 vlc_mutex_lock( &p_playlist->object_lock );
379 gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
380 vlc_mutex_unlock( &p_playlist->object_lock );
381 vlc_object_release( p_playlist );
383 gtk_clist_thaw( p_clist );
388 void GtkDropDataReceived( intf_thread_t * p_intf,
389 GtkSelectionData * p_data, guint i_info, int i_position)
391 /* first we'll have to split against all the '\n' we have */
395 gchar * p_string = p_data->data ;
396 GList * p_files = NULL;
399 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
401 if( p_playlist == NULL )
406 /* if this has been URLencoded, decode it
408 * Is it a good thing to do it in place ?
411 if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
413 UrlDecode( p_string );
416 /* this cuts string into single file drops */
417 /* this code was borrowed from xmms, thx guys :) */
420 p_next = strchr( p_string, '\n' );
423 if( *( p_next - 1 ) == '\r' )
425 *( p_next - 1) = '\0';
430 /* do we have a protocol or something ? */
431 p_temp = strstr( p_string, ":" );
432 if( p_temp != NULL && p_temp[0] != '\0' )
438 p_protocol = strdup( p_string );
442 /* Allowed things are proto: or proto:// */
443 if( p_temp[0] == '/' && p_temp[1] == '/')
448 msg_Dbg( p_intf, "playlist protocol '%s', target '%s'",
449 p_protocol, p_temp );
453 p_protocol = strdup( "" );
456 /* if it uses the file protocol we can do something, else, sorry :(
457 * I think this is a good choice for now, as we don't have any
458 * ability to read http:// or ftp:// files
459 * what about adding dvd:// to the list of authorized proto ? */
461 if( strcmp( p_protocol, "file:" ) == 0 )
463 p_files = g_list_concat( p_files,
464 GtkReadFiles( p_intf, p_string ) );
468 p_files = g_list_concat( p_files,
469 g_list_append( NULL, g_strdup( p_string ) ) );
472 /* free the malloc and go on... */
479 p_string = p_next + 1;
482 /* At this point, we have a nice big list maybe NULL */
483 if( p_files != NULL )
485 /* lock the interface */
486 vlc_mutex_lock( &p_intf->change_lock );
488 msg_Dbg( p_intf, "adding %d elements", g_list_length( p_files ) );
489 GtkAppendList( p_playlist, i_position, p_files );
491 /* get the CList and rebuild it. */
492 p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playwin,
493 "playlist_clist" ) );
494 GtkRebuildCList( p_clist , p_playlist );
496 /* unlock the interface */
497 vlc_mutex_unlock( &p_intf->change_lock );
500 vlc_object_release( p_playlist );
504 void GtkDeleteGListItem( gpointer data, gpointer param )
506 int i_cur_row = (long)data;
507 playlist_t * p_playlist = param;
509 playlist_Delete( p_playlist, i_cur_row );
513 gint GtkCompareItems( gconstpointer a, gconstpointer b )
519 /* check a file (string) against supposed valid extension */
520 int GtkHasValidExtension( gchar * psz_filename )
522 char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
526 gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
528 for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
530 if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
539 /* recursive function: descend into folders and build a list of
541 static GList * GtkReadFiles( intf_thread_t * p_intf, gchar * psz_fsname )
544 GList * p_current = NULL;
546 /* get the attributes of this file */
547 stat( psz_fsname, &statbuf );
549 /* is it a regular file ? */
550 if( S_ISREG( statbuf.st_mode ) )
552 if( GtkHasValidExtension( psz_fsname ) )
554 msg_Dbg( p_intf, "%s is a valid file, stacking on the playlist",
556 return g_list_append( NULL, g_strdup( psz_fsname ) );
563 /* is it a directory (should we check for symlinks ?) */
564 else if( S_ISDIR( statbuf.st_mode ) )
566 /* have to cd into this dir */
567 DIR * p_current_dir = opendir( psz_fsname );
568 struct dirent * p_dir_content;
570 msg_Dbg( p_intf, "%s is a folder", psz_fsname );
572 if( p_current_dir == NULL )
574 /* something went bad, get out of here ! */
577 p_dir_content = readdir( p_current_dir );
579 /* while we still have entries in the directory */
580 while( p_dir_content != NULL )
582 /* if it is "." or "..", forget it */
583 if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
584 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
586 /* else build the new directory by adding
587 fsname "/" and the current entry name
590 char * psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
591 strlen( p_dir_content->d_name ) * sizeof(char) );
592 strcpy( psz_newfs, psz_fsname );
593 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
594 p_dir_content->d_name );
595 psz_newfs[strlen( psz_fsname )] = '/';
597 p_current = g_list_concat( p_current,
598 GtkReadFiles( p_intf, psz_newfs ) );
602 p_dir_content = readdir( p_current_dir );
609 /* add items in a playlist
610 * when i_pos==-1 add to the end of the list...
612 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
617 i_length = g_list_length( p_list );
619 for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
621 playlist_Add( p_playlist,
622 /* ok; this is a really nasty trick to insert
623 the item where they are suppose to go but, hey
624 this works :P (btw, you are really nasty too) */
625 g_list_nth_data( p_list, i_dummy ),
626 i_dummy == 0 ? PLAYLIST_INSERT | PLAYLIST_GO : PLAYLIST_INSERT,
627 i_pos == PLAYLIST_END ? PLAYLIST_END : ( i_pos + i_dummy ) );
633 /* statis timeouted function */
634 void GtkPlayListManage( intf_thread_t * p_intf )
636 playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
640 if( p_playlist == NULL )
645 /* this thing really sucks for now :( */
647 /* TODO speak more with src/playlist/playlist.c */
648 if( GTK_IS_WIDGET( p_intf->p_sys->p_playwin ) )
650 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
651 p_intf->p_sys->p_playwin ), "playlist_clist" ) );
653 vlc_mutex_lock( &p_playlist->object_lock );
655 if( p_intf->p_sys->i_playing != p_playlist->i_index )
663 gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
665 if( p_intf->p_sys->i_playing != -1 )
669 color.green = 0xffff;
670 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
673 p_intf->p_sys->i_playing = p_playlist->i_index;
676 vlc_mutex_unlock( &p_playlist->object_lock );
679 vlc_object_release( p_playlist );
682 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
685 gchar * ppsz_text[2];
691 gtk_clist_freeze( p_clist );
692 gtk_clist_clear( p_clist );
694 vlc_mutex_lock( &p_playlist->object_lock );
695 for( i_dummy = p_playlist->i_size ; i_dummy-- ; )
697 ppsz_text[0] = p_playlist->pp_items[i_dummy]->psz_name;
698 ppsz_text[1] = "no info";
699 gtk_clist_insert( p_clist, 0, ppsz_text );
701 vlc_mutex_unlock( &p_playlist->object_lock );
703 gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
704 gtk_clist_thaw( p_clist );
707 /* URL-decode a file: URL path, return NULL if it's not what we expect */
708 static void UrlDecode( char *encoded_path )
710 char *tmp = NULL, *cur = NULL, *ext = NULL;
713 if( !encoded_path || *encoded_path == '\0' )
720 tmp = calloc(strlen(encoded_path) + 1, sizeof(char) );
722 while ( ( ext = strchr(cur, '%') ) != NULL)
724 strncat(tmp, cur, (ext - cur) / sizeof(char));
727 if (!sscanf(ext, "%2x", &realchar))
733 tmp[strlen(tmp)] = (char)realchar;
739 strcpy(encoded_path,tmp);