1 /*****************************************************************************
2 * gtk_playlist.c : Interface for the playlist dialog
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: gtk_playlist.c,v 1.24 2001/12/30 07:09:55 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 *****************************************************************************/
31 #include <videolan/vlc.h>
33 #include <sys/types.h> /* for readdir and stat stuff */
42 #ifdef MODULE_NAME_IS_gnome
48 #include "stream_control.h"
49 #include "input_ext-intf.h"
51 #include "interface.h"
52 #include "intf_playlist.h"
54 #include "gtk_callbacks.h"
55 #include "gtk_interface.h"
56 #include "gtk_support.h"
57 #include "gtk_playlist.h"
58 #include "gtk_common.h"
60 /****************************************************************************
61 * Playlist window management
62 ****************************************************************************/
63 gboolean GtkPlaylistShow( GtkWidget *widget,
64 GdkEventButton *event,
67 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
69 if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playlist ) )
71 gtk_widget_hide( p_intf->p_sys->p_playlist );
77 p_clist = GTK_CLIST( gtk_object_get_data(
78 GTK_OBJECT( p_intf->p_sys->p_playlist ), "playlist_clist" ) );
79 GtkRebuildCList( p_clist , p_main->p_playlist );
80 gtk_widget_show( p_intf->p_sys->p_playlist );
81 gdk_window_raise( p_intf->p_sys->p_playlist->window );
88 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
90 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
94 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
96 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
101 gboolean GtkPlaylistPrev( GtkWidget *widget,
102 GdkEventButton *event,
105 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
107 if( p_intf->p_input != NULL )
109 /* FIXME: temporary hack */
110 intf_PlaylistPrev( p_main->p_playlist );
111 intf_PlaylistPrev( p_main->p_playlist );
112 p_intf->p_input->b_eof = 1;
119 gboolean GtkPlaylistNext( GtkWidget *widget,
120 GdkEventButton *event,
123 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
125 if( p_intf->p_input != NULL )
127 /* FIXME: temporary hack */
128 p_intf->p_input->b_eof = 1;
134 /****************************************************************************
135 * Menu callbacks for playlist functions
136 ****************************************************************************/
137 void GtkPlaylistActivate( GtkMenuItem * menuitem, gpointer user_data )
139 GtkPlaylistShow( GTK_WIDGET( menuitem ), NULL, user_data );
143 void GtkNextActivate( GtkMenuItem * menuitem, gpointer user_data )
145 GtkPlaylistNext( GTK_WIDGET( menuitem ), NULL, user_data );
149 void GtkPrevActivate( GtkMenuItem * menuitem, gpointer user_data )
151 GtkPlaylistPrev( GTK_WIDGET( menuitem ), NULL, user_data );
155 /****************************************************************************
156 * Playlist core functions
157 ****************************************************************************/
158 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
164 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
170 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
172 /* user wants to delete a file in the queue */
175 playlist_t *p_playlist;
177 /* catch the thread back */
178 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), /*(char*)user_data*/"intf_playlist" );
180 p_playlist = p_main->p_playlist;
182 /* lock the struct */
183 vlc_mutex_lock( &p_intf->change_lock );
185 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
186 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
188 /* I use UNDOCUMENTED features to retrieve the selection... */
189 p_selection = p_clist->selection;
191 if( g_list_length( p_selection ) > 0 )
193 /* reverse-sort so that we can delete from the furthest
194 * to the closest item to delete...
196 p_selection = g_list_sort( p_selection, GtkCompareItems );
197 g_list_foreach( p_selection, GtkDeleteGListItem, p_intf );
198 /* rebuild the CList */
199 GtkRebuildCList( p_clist, p_playlist );
202 vlc_mutex_unlock( &p_intf->change_lock );
205 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
207 /* Ok, this is a really small thing, but, hey, it works and
208 might be useful, who knows ? */
209 GtkPlaylistInvert( menuitem, user_data );
210 GtkPlaylistDeleteSelected( menuitem, user_data );
213 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
215 playlist_t *p_playlist;
221 /* catch the thread back */
222 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
224 p_playlist = p_main->p_playlist;
226 /* lock the struct */
227 vlc_mutex_lock( &p_intf->change_lock );
229 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
230 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
232 /* have to copy the selection to an int *
233 I wasn't able to copy the g_list to another g_list
234 glib only does pointer copies, not real copies :( */
236 pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
237 i_sel_l = g_list_length( p_clist->selection );
239 for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
241 pi_selected[i_dummy] = (long)g_list_nth_data( p_clist->selection,
245 gtk_clist_freeze( p_clist );
246 gtk_clist_select_all( p_clist );
248 for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
250 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
251 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
255 gtk_clist_thaw( p_clist );
257 vlc_mutex_unlock( &p_intf->change_lock );
260 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
265 gboolean GtkPlaylistEvent( GtkWidget * widget,
269 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
271 if( ( event->button ).type == GDK_2BUTTON_PRESS )
277 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
278 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
280 if( gtk_clist_get_selection_info( p_clist, (event->button).x,
281 (event->button).y, &i_row, &i_col ) == 1 )
283 /* clicked is in range. */
284 if( p_intf->p_input != NULL )
286 /* FIXME: temporary hack */
287 p_intf->p_input->b_eof = 1;
290 intf_PlaylistJumpto( p_main->p_playlist, i_row - 1 );
298 void GtkPlaylistDragData( GtkWidget *widget,
299 GdkDragContext *drag_context,
302 GtkSelectionData *data,
307 intf_thread_t * p_intf;
311 int i_end = p_main->p_playlist->i_size;
313 /* catch the interface back */
314 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
316 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
317 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
319 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
321 /* we are dropping somewhere into the clist items */
322 GtkDropDataReceived( p_intf, data, info, i_row );
326 /* else, put that at the end of the playlist */
327 GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
330 intf_PlaylistJumpto( p_main->p_playlist, i_end - 1 );
334 gboolean GtkPlaylistDragMotion( GtkWidget *widget,
335 GdkDragContext *drag_context,
341 intf_thread_t *p_intf;
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_WIDGET_TOPLEVEL(widget) )
355 gdk_window_raise( p_intf->p_sys->p_playlist->window );
360 color.green = 0xffff;
362 gtk_clist_freeze( p_clist );
364 for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
366 gtk_clist_set_background ( p_clist, i_dummy , &color);
372 gtk_clist_set_background( p_clist, p_main->p_playlist->i_index , &color );
374 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
378 color.green = 0x9000;
379 gtk_clist_set_background ( p_clist, i_row - 1, &color);
380 gtk_clist_set_background ( p_clist, i_row, &color);
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;
400 /* catch the playlist back */
401 playlist_t * p_playlist = p_main->p_playlist;
404 /* if this has been URLencoded, decode it
406 * Is it a good thing to do it in place ?
409 if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
411 intf_UrlDecode( p_string );
414 /* this cuts string into single file drops */
415 /* this code was borrowed from xmms, thx guys :) */
418 p_next = strchr( p_string, '\n' );
421 if( *( p_next - 1 ) == '\r' )
423 *( p_next - 1) = '\0';
428 /* do we have a protocol or something ? */
429 p_temp = strstr( p_string, ":" );
430 if( p_temp != NULL && p_temp[0] != '\0' )
436 p_protocol = strdup( p_string );
440 /* Allowed things are proto: or proto:// */
441 if( p_temp[0] == '/' && p_temp[1] == '/')
446 intf_WarnMsg( 4, "playlist: protocol '%s', target '%s'",
447 p_protocol, p_temp );
451 p_protocol = strdup( "" );
454 /* if it uses the file protocol we can do something, else, sorry :(
455 * I think this is a good choice for now, as we don't have any
456 * ability to read http:// or ftp:// files
457 * what about adding dvd:// to the list of authorized proto ? */
459 if( strcmp( p_protocol, "file:" ) == 0 )
461 p_files = g_list_concat( p_files, GtkReadFiles( p_string ) );
465 p_files = g_list_concat( p_files,
466 g_list_append( NULL, g_strdup( p_string ) ) );
469 /* free the malloc and go on... */
476 p_string = p_next + 1;
479 /* At this point, we have a nice big list maybe NULL */
480 if( p_files != NULL )
482 /* lock the interface */
483 vlc_mutex_lock( &p_intf->change_lock );
485 intf_WarnMsg( 4, "List has %d elements", g_list_length( p_files ) );
486 GtkAppendList( p_playlist, i_position, p_files );
488 /* get the CList and rebuild it. */
489 p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playlist,
490 "playlist_clist" ) );
491 GtkRebuildCList( p_clist , p_playlist );
493 /* unlock the interface */
494 vlc_mutex_unlock( &p_intf->change_lock );
499 void GtkDeleteGListItem( gpointer data, gpointer param )
501 int i_cur_row = (long)data;
502 intf_thread_t * p_intf = param;
504 intf_PlaylistDelete( p_main->p_playlist, i_cur_row );
506 /* are we deleting the current played stream */
507 if( p_intf->p_sys->i_playing == i_cur_row )
510 p_intf->p_input->b_eof = 1;
511 /* this has to set the slider to 0 */
514 p_intf->p_sys->i_playing-- ;
516 vlc_mutex_lock( &p_main->p_playlist->change_lock );
517 p_main->p_playlist->i_index-- ;
518 vlc_mutex_unlock( &p_main->p_playlist->change_lock );
523 gint GtkCompareItems( gconstpointer a, gconstpointer b )
529 /* check a file (string) against supposed valid extension */
530 int GtkHasValidExtension( gchar * psz_filename )
532 char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
536 gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
538 for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
540 if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
549 /* recursive function: descend into folders and build a list of
551 GList * GtkReadFiles( gchar * psz_fsname )
554 GList * p_current = NULL;
556 /* get the attributes of this file */
557 stat( psz_fsname, &statbuf );
559 /* is it a regular file ? */
560 if( S_ISREG( statbuf.st_mode ) )
562 if( GtkHasValidExtension( psz_fsname ) )
564 intf_WarnMsg( 2, "%s is a valid file. Stacking on the playlist",
566 return g_list_append( NULL, g_strdup( psz_fsname ) );
573 /* is it a directory (should we check for symlinks ?) */
574 else if( S_ISDIR( statbuf.st_mode ) )
576 /* have to cd into this dir */
577 DIR * p_current_dir = opendir( psz_fsname );
578 struct dirent * p_dir_content;
580 intf_WarnMsg( 2, "%s is a folder.", psz_fsname );
582 if( p_current_dir == NULL )
584 /* something went bad, get out of here ! */
587 p_dir_content = readdir( p_current_dir );
589 /* while we still have entries in the directory */
590 while( p_dir_content != NULL )
592 /* if it is "." or "..", forget it */
593 if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
594 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
596 /* else build the new directory by adding
597 fsname "/" and the current entry name
600 char * psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
601 strlen( p_dir_content->d_name ) * sizeof(char) );
602 strcpy( psz_newfs, psz_fsname );
603 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
604 p_dir_content->d_name );
605 psz_newfs[strlen( psz_fsname )] = '/';
607 p_current = g_list_concat( p_current,
608 GtkReadFiles( psz_newfs ) );
612 p_dir_content = readdir( p_current_dir );
619 /* add items in a playlist
620 * when i_pos==-1 add to the end of the list...
622 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
627 i_length = g_list_length( p_list );
629 for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
631 intf_PlaylistAdd( p_playlist,
632 /* ok; this is a really nasty trick to insert
633 the item where they are suppose to go but, hey
634 this works :P (btw, you are really nasty too) */
635 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + i_dummy ),
636 g_list_nth_data( p_list, i_dummy ) );
641 /* statis timeouted function */
642 void GtkPlayListManage( intf_thread_t * p_intf )
644 /* this thing really sucks for now :( */
646 /* TODO speak more with interface/intf_playlist.c */
648 playlist_t * p_playlist = p_main->p_playlist ;
651 if( GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
653 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
654 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
656 vlc_mutex_lock( &p_playlist->change_lock );
658 if( p_intf->p_sys->i_playing != p_playlist->i_index )
666 gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
668 if( p_intf->p_sys->i_playing != -1 )
672 color.green = 0xffff;
673 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
676 p_intf->p_sys->i_playing = p_playlist->i_index;
679 vlc_mutex_unlock( &p_playlist->change_lock );
683 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
686 gchar * ppsz_text[2];
692 gtk_clist_freeze( p_clist );
693 gtk_clist_clear( p_clist );
695 for( i_dummy = 0; i_dummy < p_playlist->i_size ; i_dummy++ )
697 #ifdef WIN32 /* WIN32 HACK */
700 ppsz_text[0] = rindex( p_playlist->p_item[
701 p_playlist->i_size - 1 - i_dummy].psz_name, '/' );
702 if ( ppsz_text[0] == NULL )
705 p_playlist->p_item[ p_playlist->i_size - 1 - i_dummy ].psz_name;
709 /* Skip leading '/' */
713 ppsz_text[1] = "no info";
715 gtk_clist_insert( p_clist, 0, ppsz_text );
717 gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
718 gtk_clist_thaw( p_clist );