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 $
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 #define MODULE_NAME gtk
26 #include "modules_inner.h"
28 /*****************************************************************************
30 *****************************************************************************/
39 #include <sys/types.h> /* for readdir and stat stuff */
49 #include "stream_control.h"
50 #include "input_ext-intf.h"
52 #include "interface.h"
53 #include "intf_playlist.h"
55 #include "intf_urldecode.h"
57 #include "gtk_callbacks.h"
58 #include "gtk_interface.h"
59 #include "gtk_support.h"
60 #include "gtk_playlist.h"
66 /****************************************************************************
67 * Playlist window management
68 ****************************************************************************/
69 gboolean GtkPlaylistShow( GtkWidget *widget,
70 GdkEventButton *event,
73 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
75 if( !GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
77 /* The data types we are allowed to receive */
78 static GtkTargetEntry target_table[] =
80 { "text/uri-list", 0, DROP_ACCEPT_TEXT_URI_LIST },
81 { "text/plain", 0, DROP_ACCEPT_TEXT_PLAIN }
84 p_intf->p_sys->p_playlist = create_intf_playlist();
85 gtk_object_set_data( GTK_OBJECT( p_intf->p_sys->p_playlist ),
88 /* Accept file drops on the playlist window */
89 gtk_drag_dest_set( GTK_WIDGET( lookup_widget( p_intf->p_sys->p_playlist,
91 GTK_DEST_DEFAULT_ALL, target_table,
95 if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playlist ) )
97 gtk_widget_hide( p_intf->p_sys->p_playlist );
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 );
114 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
116 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
120 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
122 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
127 gboolean GtkPlaylistPrev( GtkWidget *widget,
128 GdkEventButton *event,
131 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
133 if( p_intf->p_input != NULL )
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;
145 gboolean GtkPlaylistNext( GtkWidget *widget,
146 GdkEventButton *event,
149 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
151 if( p_intf->p_input != NULL )
153 /* FIXME: temporary hack */
154 p_intf->p_input->b_eof = 1;
160 /****************************************************************************
161 * Menu callbacks for playlist functions
162 ****************************************************************************/
163 void GtkPlaylistActivate( GtkMenuItem * menuitem, gpointer user_data )
165 GtkPlaylistShow( GTK_WIDGET( menuitem ), NULL, user_data );
169 void GtkNextActivate( GtkMenuItem * menuitem, gpointer user_data )
171 GtkPlaylistNext( GTK_WIDGET( menuitem ), NULL, user_data );
175 void GtkPrevActivate( GtkMenuItem * menuitem, gpointer user_data )
177 GtkPlaylistPrev( GTK_WIDGET( menuitem ), NULL, user_data );
181 /****************************************************************************
182 * Playlist core functions
183 ****************************************************************************/
184 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
190 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
196 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
198 /* user wants to delete a file in the queue */
201 playlist_t *p_playlist;
203 /* catch the thread back */
204 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
206 p_playlist = p_main->p_playlist;
208 /* lock the struct */
209 vlc_mutex_lock( &p_intf->change_lock );
211 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
212 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
214 /* I use UNDOCUMENTED features to retrieve the selection... */
215 p_selection = p_clist->selection;
217 if( g_list_length( p_selection ) > 0 )
219 /* reverse-sort so that we can delete from the furthest
220 * to the closest item to delete...
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 );
228 vlc_mutex_unlock( &p_intf->change_lock );
231 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
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 );
239 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
241 playlist_t *p_playlist;
247 /* catch the thread back */
248 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
250 p_playlist = p_main->p_playlist;
252 /* lock the struct */
253 vlc_mutex_lock( &p_intf->change_lock );
255 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
256 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
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 :( */
262 pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
263 i_sel_l = g_list_length( p_clist->selection );
265 for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
267 pi_selected[i_dummy] = (int)g_list_nth_data( p_clist->selection,
271 gtk_clist_freeze( p_clist );
272 gtk_clist_select_all( p_clist );
274 for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
276 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
277 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
281 gtk_clist_thaw( p_clist );
283 vlc_mutex_unlock( &p_intf->change_lock );
286 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
291 gboolean GtkPlaylistEvent( GtkWidget * widget,
295 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
297 if( ( event->button ).type == GDK_2BUTTON_PRESS )
303 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
304 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
306 if( gtk_clist_get_selection_info( p_clist, (event->button).x,
307 (event->button).y, &i_row, &i_col ) == 1 )
309 /* clicked is in range. */
310 if( p_intf->p_input != NULL )
312 /* FIXME: temporary hack */
313 p_intf->p_input->b_eof = 1;
316 intf_PlaylistJumpto( p_main->p_playlist, i_row - 1 );
324 void GtkPlaylistDragData( GtkWidget *widget,
325 GdkDragContext *drag_context,
328 GtkSelectionData *data,
333 intf_thread_t * p_intf;
337 int i_end = p_main->p_playlist->i_size;
339 /* catch the interface back */
340 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
342 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
343 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
345 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
347 /* we are dropping somewhere into the clist items */
348 GtkDropDataReceived( p_intf, data, info, i_row );
352 /* else, put that at the end of the playlist */
353 GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
356 intf_PlaylistJumpto( p_main->p_playlist, i_end - 1 );
360 gboolean GtkPlaylistDragMotion( GtkWidget *widget,
361 GdkDragContext *drag_context,
367 intf_thread_t *p_intf;
374 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
376 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
377 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
379 if( !GTK_WIDGET_TOPLEVEL(widget) )
381 gdk_window_raise( p_intf->p_sys->p_playlist->window );
386 color.green = 0xffff;
388 gtk_clist_freeze( p_clist );
390 for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
392 gtk_clist_set_background ( p_clist, i_dummy , &color);
398 gtk_clist_set_background( p_clist, p_main->p_playlist->i_index , &color );
400 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
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);
409 gtk_clist_thaw( p_clist );
414 void GtkDropDataReceived( intf_thread_t * p_intf,
415 GtkSelectionData * p_data, guint i_info, int i_position)
417 /* first we'll have to split against all the '\n' we have */
420 gchar * p_string = p_data->data ;
421 GList * p_files = NULL;
425 /* catch the playlist back */
426 playlist_t * p_playlist = p_main->p_playlist;
429 /* if this has been URLencoded, decode it
431 * Is it a good thing to do it in place ?
434 if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
436 urldecode_path( p_string );
439 /* this cuts string into single file drops */
440 /* this code was borrowed from xmms, thx guys :) */
443 p_temp = strchr( p_string, '\n' );
446 if( *( p_temp - 1 ) == '\r' )
448 *( p_temp - 1) = '\0';
453 /* do we have a protocol or something ? */
454 p_protocol = strstr( p_string, ":/" );
455 if( p_protocol != NULL )
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 );
461 intf_WarnMsg( 4, "Protocol dropped is %s", p_protocol );
462 p_string += strlen( p_protocol );
464 /* Allowed things are proto: or proto:// */
465 if( p_string[0] == '/' && p_string[1] == '/')
470 intf_WarnMsg( 4, " Dropped %s", p_string );
474 p_protocol = strdup( "" );
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 ? */
482 if( strcmp( p_protocol, "file:" ) == 0 )
484 p_files = g_list_concat( p_files, GtkReadFiles( p_string ) );
487 /* free the malloc and go on... */
493 p_string = p_temp + 1;
496 /* At this point, we have a nice big list maybe NULL */
497 if( p_files != NULL )
499 /* lock the interface */
500 vlc_mutex_lock( &p_intf->change_lock );
502 intf_WarnMsg( 4, "List has %d elements", g_list_length( p_files ) );
503 GtkAppendList( p_playlist, i_position, p_files );
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 );
510 /* unlock the interface */
511 vlc_mutex_unlock( &p_intf->change_lock );
516 void GtkDeleteGListItem( gpointer data, gpointer param )
518 int i_cur_row = ( int )data;
519 intf_thread_t * p_intf = param;
521 intf_PlaylistDelete( p_main->p_playlist, i_cur_row );
523 /* are we deleting the current played stream */
524 if( p_intf->p_sys->i_playing == i_cur_row )
527 p_intf->p_input->b_eof = 1;
528 /* this has to set the slider to 0 */
531 p_intf->p_sys->i_playing-- ;
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 );
540 gint GtkCompareItems( gconstpointer a, gconstpointer b )
546 /* check a file (string) against supposed valid extension */
547 int GtkHasValidExtension( gchar * psz_filename )
549 char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
553 gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
555 for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
557 if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
566 /* recursive function: descend into folders and build a list of
568 GList * GtkReadFiles( gchar * psz_fsname )
571 GList * p_current = NULL;
573 /* get the attributes of this file */
574 stat( psz_fsname, &statbuf );
576 /* is it a regular file ? */
577 if( S_ISREG( statbuf.st_mode ) )
579 if( GtkHasValidExtension( psz_fsname ) )
581 intf_WarnMsg( 2, "%s is a valid file. Stacking on the playlist",
583 return g_list_append( NULL, g_strdup( psz_fsname ) );
590 /* is it a directory (should we check for symlinks ?) */
591 else if( S_ISDIR( statbuf.st_mode ) )
593 /* have to cd into this dir */
594 DIR * p_current_dir = opendir( psz_fsname );
595 struct dirent * p_dir_content;
597 intf_WarnMsg( 2, "%s is a folder.", psz_fsname );
599 if( p_current_dir == NULL )
601 /* something went bad, get out of here ! */
604 p_dir_content = readdir( p_current_dir );
606 /* while we still have entries in the directory */
607 while( p_dir_content != NULL )
609 /* if it is "." or "..", forget it */
610 if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
611 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
613 /* else build the new directory by adding
614 fsname "/" and the current entry name
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 )] = '/';
624 p_current = g_list_concat( p_current,
625 GtkReadFiles( psz_newfs ) );
629 p_dir_content = readdir( p_current_dir );
636 /* add items in a playlist
637 * when i_pos==-1 add to the end of the list...
639 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
644 i_length = g_list_length( p_list );
646 for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
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 ) );
658 /* statis timeouted function */
659 void GtkPlayListManage( intf_thread_t * p_intf )
661 /* this thing really sucks for now :( */
663 /* TODO speak more with interface/intf_playlist.c */
665 playlist_t * p_playlist = p_main->p_playlist ;
668 if( GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
670 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
671 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
673 vlc_mutex_lock( &p_playlist->change_lock );
675 if( p_intf->p_sys->i_playing != p_playlist->i_index )
683 gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
685 if( p_intf->p_sys->i_playing != -1 )
689 color.green = 0xffff;
690 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
693 p_intf->p_sys->i_playing = p_playlist->i_index;
696 vlc_mutex_unlock( &p_playlist->change_lock );
700 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
703 gchar * ppsz_text[2];
709 gtk_clist_freeze( p_clist );
710 gtk_clist_clear( p_clist );
712 for( i_dummy = 0; i_dummy < p_playlist->i_size ; i_dummy++ )
714 #ifdef WIN32 /* WIN32 HACK */
715 ppsz_text[0] = g_strdup( "" );
717 ppsz_text[0] = g_strdup( rindex( (char *)(p_playlist->p_item[
718 p_playlist->i_size - 1 - i_dummy].psz_name ), '/' ) + 1 );
720 ppsz_text[1] = g_strdup( "no info");
722 gtk_clist_insert( p_clist, 0, ppsz_text );
724 free( ppsz_text[0] );
725 free( ppsz_text[1] );
727 gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
728 gtk_clist_thaw( p_clist );