1 /*****************************************************************************
2 * gtk_playlist.c : Interface for the playlist dialog
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: gtk_playlist.c,v 1.18 2001/07/25 03:12:33 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 *****************************************************************************/
33 #include <sys/types.h> /* for readdir and stat stuff */
44 #if ( MODULE_NAME == gtk )
46 #elif ( MODULE_NAME == gnome )
57 #include "stream_control.h"
58 #include "input_ext-intf.h"
60 #include "interface.h"
61 #include "intf_playlist.h"
64 #include "gtk_callbacks.h"
65 #include "gtk_interface.h"
66 #include "gtk_support.h"
67 #include "gtk_playlist.h"
72 #include "modules_export.h"
74 /****************************************************************************
75 * Playlist window management
76 ****************************************************************************/
77 gboolean GtkPlaylistShow( GtkWidget *widget,
78 GdkEventButton *event,
81 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
83 if( !GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
85 /* The data types we are allowed to receive */
86 static GtkTargetEntry target_table[] =
88 { "text/uri-list", 0, DROP_ACCEPT_TEXT_URI_LIST },
89 { "text/plain", 0, DROP_ACCEPT_TEXT_PLAIN }
92 p_intf->p_sys->p_playlist = create_intf_playlist();
93 gtk_object_set_data( GTK_OBJECT( p_intf->p_sys->p_playlist ),
96 /* Accept file drops on the playlist window */
97 gtk_drag_dest_set( GTK_WIDGET( lookup_widget( p_intf->p_sys->p_playlist,
99 GTK_DEST_DEFAULT_ALL, target_table,
100 1, GDK_ACTION_COPY );
103 if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playlist ) )
105 gtk_widget_hide( p_intf->p_sys->p_playlist );
111 p_clist = GTK_CLIST( gtk_object_get_data(
112 GTK_OBJECT( p_intf->p_sys->p_playlist ), "playlist_clist" ) );
113 GtkRebuildCList( p_clist , p_main->p_playlist );
114 gtk_widget_show( p_intf->p_sys->p_playlist );
115 gdk_window_raise( p_intf->p_sys->p_playlist->window );
122 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
124 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
128 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
130 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
135 gboolean GtkPlaylistPrev( GtkWidget *widget,
136 GdkEventButton *event,
139 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
141 if( p_intf->p_input != NULL )
143 /* FIXME: temporary hack */
144 intf_PlaylistPrev( p_main->p_playlist );
145 intf_PlaylistPrev( p_main->p_playlist );
146 p_intf->p_input->b_eof = 1;
153 gboolean GtkPlaylistNext( GtkWidget *widget,
154 GdkEventButton *event,
157 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
159 if( p_intf->p_input != NULL )
161 /* FIXME: temporary hack */
162 p_intf->p_input->b_eof = 1;
168 /****************************************************************************
169 * Menu callbacks for playlist functions
170 ****************************************************************************/
171 void GtkPlaylistActivate( GtkMenuItem * menuitem, gpointer user_data )
173 GtkPlaylistShow( GTK_WIDGET( menuitem ), NULL, user_data );
177 void GtkNextActivate( GtkMenuItem * menuitem, gpointer user_data )
179 GtkPlaylistNext( GTK_WIDGET( menuitem ), NULL, user_data );
183 void GtkPrevActivate( GtkMenuItem * menuitem, gpointer user_data )
185 GtkPlaylistPrev( GTK_WIDGET( menuitem ), NULL, user_data );
189 /****************************************************************************
190 * Playlist core functions
191 ****************************************************************************/
192 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
198 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
204 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
206 /* user wants to delete a file in the queue */
209 playlist_t *p_playlist;
211 /* catch the thread back */
212 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), /*(char*)user_data*/"intf_playlist" );
214 p_playlist = p_main->p_playlist;
216 /* lock the struct */
217 vlc_mutex_lock( &p_intf->change_lock );
219 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
220 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
222 /* I use UNDOCUMENTED features to retrieve the selection... */
223 p_selection = p_clist->selection;
225 if( g_list_length( p_selection ) > 0 )
227 /* reverse-sort so that we can delete from the furthest
228 * to the closest item to delete...
230 p_selection = g_list_sort( p_selection, GtkCompareItems );
231 g_list_foreach( p_selection, GtkDeleteGListItem, p_intf );
232 /* rebuild the CList */
233 GtkRebuildCList( p_clist, p_playlist );
236 vlc_mutex_unlock( &p_intf->change_lock );
239 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
241 /* Ok, this is a really small thing, but, hey, it works and
242 might be useful, who knows ? */
243 GtkPlaylistInvert( menuitem, user_data );
244 GtkPlaylistDeleteSelected( menuitem, user_data );
247 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
249 playlist_t *p_playlist;
255 /* catch the thread back */
256 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
258 p_playlist = p_main->p_playlist;
260 /* lock the struct */
261 vlc_mutex_lock( &p_intf->change_lock );
263 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
264 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
266 /* have to copy the selection to an int *
267 I wasn't able to copy the g_list to another g_list
268 glib only does pointer copies, not real copies :( */
270 pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
271 i_sel_l = g_list_length( p_clist->selection );
273 for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
275 pi_selected[i_dummy] = (int)g_list_nth_data( p_clist->selection,
279 gtk_clist_freeze( p_clist );
280 gtk_clist_select_all( p_clist );
282 for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
284 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
285 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
289 gtk_clist_thaw( p_clist );
291 vlc_mutex_unlock( &p_intf->change_lock );
294 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
299 gboolean GtkPlaylistEvent( GtkWidget * widget,
303 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
305 if( ( event->button ).type == GDK_2BUTTON_PRESS )
311 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
312 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
314 if( gtk_clist_get_selection_info( p_clist, (event->button).x,
315 (event->button).y, &i_row, &i_col ) == 1 )
317 /* clicked is in range. */
318 if( p_intf->p_input != NULL )
320 /* FIXME: temporary hack */
321 p_intf->p_input->b_eof = 1;
324 intf_PlaylistJumpto( p_main->p_playlist, i_row - 1 );
332 void GtkPlaylistDragData( GtkWidget *widget,
333 GdkDragContext *drag_context,
336 GtkSelectionData *data,
341 intf_thread_t * p_intf;
345 int i_end = p_main->p_playlist->i_size;
347 /* catch the interface back */
348 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
350 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
351 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
353 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
355 /* we are dropping somewhere into the clist items */
356 GtkDropDataReceived( p_intf, data, info, i_row );
360 /* else, put that at the end of the playlist */
361 GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
364 intf_PlaylistJumpto( p_main->p_playlist, i_end - 1 );
368 gboolean GtkPlaylistDragMotion( GtkWidget *widget,
369 GdkDragContext *drag_context,
375 intf_thread_t *p_intf;
382 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
384 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
385 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
387 if( !GTK_WIDGET_TOPLEVEL(widget) )
389 gdk_window_raise( p_intf->p_sys->p_playlist->window );
394 color.green = 0xffff;
396 gtk_clist_freeze( p_clist );
398 for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
400 gtk_clist_set_background ( p_clist, i_dummy , &color);
406 gtk_clist_set_background( p_clist, p_main->p_playlist->i_index , &color );
408 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
412 color.green = 0x9000;
413 gtk_clist_set_background ( p_clist, i_row - 1, &color);
414 gtk_clist_set_background ( p_clist, i_row, &color);
417 gtk_clist_thaw( p_clist );
422 void GtkDropDataReceived( intf_thread_t * p_intf,
423 GtkSelectionData * p_data, guint i_info, int i_position)
425 /* first we'll have to split against all the '\n' we have */
428 gchar * p_string = p_data->data ;
429 GList * p_files = NULL;
433 /* catch the playlist back */
434 playlist_t * p_playlist = p_main->p_playlist;
437 /* if this has been URLencoded, decode it
439 * Is it a good thing to do it in place ?
442 if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
444 intf_UrlDecode( p_string );
447 /* this cuts string into single file drops */
448 /* this code was borrowed from xmms, thx guys :) */
451 p_temp = strchr( p_string, '\n' );
454 if( *( p_temp - 1 ) == '\r' )
456 *( p_temp - 1) = '\0';
461 /* do we have a protocol or something ? */
462 p_protocol = strstr( p_string, ":/" );
463 if( p_protocol != NULL )
465 p_protocol = calloc( p_protocol - p_string + 2, sizeof(char) );
466 p_protocol = strncpy( p_protocol, p_string,
467 strstr( p_string, ":/" ) + 1 - p_string );
469 intf_WarnMsg( 4, "Protocol dropped is %s", p_protocol );
470 p_string += strlen( p_protocol );
472 /* Allowed things are proto: or proto:// */
473 if( p_string[0] == '/' && p_string[1] == '/')
478 intf_WarnMsg( 4, " Dropped %s", p_string );
482 p_protocol = strdup( "" );
485 /* if it uses the file protocol we can do something, else, sorry :(
486 * I think this is a good choice for now, as we don't have any
487 * ability to read http:// or ftp:// files
488 * what about adding dvd:// to the list of authorized proto ? */
490 if( strcmp( p_protocol, "file:" ) == 0 )
492 p_files = g_list_concat( p_files, GtkReadFiles( p_string ) );
495 /* free the malloc and go on... */
501 p_string = p_temp + 1;
504 /* At this point, we have a nice big list maybe NULL */
505 if( p_files != NULL )
507 /* lock the interface */
508 vlc_mutex_lock( &p_intf->change_lock );
510 intf_WarnMsg( 4, "List has %d elements", g_list_length( p_files ) );
511 GtkAppendList( p_playlist, i_position, p_files );
513 /* get the CList and rebuild it. */
514 p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playlist,
515 "playlist_clist" ) );
516 GtkRebuildCList( p_clist , p_playlist );
518 /* unlock the interface */
519 vlc_mutex_unlock( &p_intf->change_lock );
524 void GtkDeleteGListItem( gpointer data, gpointer param )
526 int i_cur_row = ( int )data;
527 intf_thread_t * p_intf = param;
529 intf_PlaylistDelete( p_main->p_playlist, i_cur_row );
531 /* are we deleting the current played stream */
532 if( p_intf->p_sys->i_playing == i_cur_row )
535 p_intf->p_input->b_eof = 1;
536 /* this has to set the slider to 0 */
539 p_intf->p_sys->i_playing-- ;
541 vlc_mutex_lock( &p_main->p_playlist->change_lock );
542 p_main->p_playlist->i_index-- ;
543 vlc_mutex_unlock( &p_main->p_playlist->change_lock );
548 gint GtkCompareItems( gconstpointer a, gconstpointer b )
554 /* check a file (string) against supposed valid extension */
555 int GtkHasValidExtension( gchar * psz_filename )
557 char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
561 gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
563 for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
565 if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
574 /* recursive function: descend into folders and build a list of
576 GList * GtkReadFiles( gchar * psz_fsname )
579 GList * p_current = NULL;
581 /* get the attributes of this file */
582 stat( psz_fsname, &statbuf );
584 /* is it a regular file ? */
585 if( S_ISREG( statbuf.st_mode ) )
587 if( GtkHasValidExtension( psz_fsname ) )
589 intf_WarnMsg( 2, "%s is a valid file. Stacking on the playlist",
591 return g_list_append( NULL, g_strdup( psz_fsname ) );
598 /* is it a directory (should we check for symlinks ?) */
599 else if( S_ISDIR( statbuf.st_mode ) )
601 /* have to cd into this dir */
602 DIR * p_current_dir = opendir( psz_fsname );
603 struct dirent * p_dir_content;
605 intf_WarnMsg( 2, "%s is a folder.", psz_fsname );
607 if( p_current_dir == NULL )
609 /* something went bad, get out of here ! */
612 p_dir_content = readdir( p_current_dir );
614 /* while we still have entries in the directory */
615 while( p_dir_content != NULL )
617 /* if it is "." or "..", forget it */
618 if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
619 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
621 /* else build the new directory by adding
622 fsname "/" and the current entry name
625 char * psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
626 strlen( p_dir_content->d_name ) * sizeof(char) );
627 strcpy( psz_newfs, psz_fsname );
628 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
629 p_dir_content->d_name );
630 psz_newfs[strlen( psz_fsname )] = '/';
632 p_current = g_list_concat( p_current,
633 GtkReadFiles( psz_newfs ) );
637 p_dir_content = readdir( p_current_dir );
644 /* add items in a playlist
645 * when i_pos==-1 add to the end of the list...
647 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
652 i_length = g_list_length( p_list );
654 for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
656 intf_PlaylistAdd( p_playlist,
657 /* ok; this is a really nasty trick to insert
658 the item where they are suppose to go but, hey
659 this works :P (btw, you are really nasty too) */
660 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + i_dummy ),
661 g_list_nth_data( p_list, i_dummy ) );
666 /* statis timeouted function */
667 void GtkPlayListManage( intf_thread_t * p_intf )
669 /* this thing really sucks for now :( */
671 /* TODO speak more with interface/intf_playlist.c */
673 playlist_t * p_playlist = p_main->p_playlist ;
676 if( GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
678 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
679 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
681 vlc_mutex_lock( &p_playlist->change_lock );
683 if( p_intf->p_sys->i_playing != p_playlist->i_index )
691 gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
693 if( p_intf->p_sys->i_playing != -1 )
697 color.green = 0xffff;
698 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
701 p_intf->p_sys->i_playing = p_playlist->i_index;
704 vlc_mutex_unlock( &p_playlist->change_lock );
708 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
711 gchar * ppsz_text[2];
717 gtk_clist_freeze( p_clist );
718 gtk_clist_clear( p_clist );
720 for( i_dummy = 0; i_dummy < p_playlist->i_size ; i_dummy++ )
722 #ifdef WIN32 /* WIN32 HACK */
723 ppsz_text[0] = g_strdup( "" );
725 ppsz_text[0] = g_strdup( rindex( (char *)(p_playlist->p_item[
726 p_playlist->i_size - 1 - i_dummy].psz_name ), '/' ) + 1 );
728 ppsz_text[1] = g_strdup( "no info");
730 gtk_clist_insert( p_clist, 0, ppsz_text );
732 free( ppsz_text[0] );
733 free( ppsz_text[1] );
735 gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
736 gtk_clist_thaw( p_clist );