1 /*****************************************************************************
2 * gtk_playlist.c : Interface for the playlist dialog
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: gtk_playlist.c,v 1.15 2001/05/30 17:03:12 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 *****************************************************************************/
36 #include <sys/types.h> /* for readdir and stat stuff */
46 #include "stream_control.h"
47 #include "input_ext-intf.h"
49 #include "interface.h"
50 #include "intf_playlist.h"
53 #include "gtk_callbacks.h"
54 #include "gtk_interface.h"
55 #include "gtk_support.h"
56 #include "gtk_playlist.h"
61 #include "modules_export.h"
63 /****************************************************************************
64 * Playlist window management
65 ****************************************************************************/
66 gboolean GtkPlaylistShow( GtkWidget *widget,
67 GdkEventButton *event,
70 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
72 if( !GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
74 /* The data types we are allowed to receive */
75 static GtkTargetEntry target_table[] =
77 { "text/uri-list", 0, DROP_ACCEPT_TEXT_URI_LIST },
78 { "text/plain", 0, DROP_ACCEPT_TEXT_PLAIN }
81 p_intf->p_sys->p_playlist = create_intf_playlist();
82 gtk_object_set_data( GTK_OBJECT( p_intf->p_sys->p_playlist ),
85 /* Accept file drops on the playlist window */
86 gtk_drag_dest_set( GTK_WIDGET( lookup_widget( p_intf->p_sys->p_playlist,
88 GTK_DEST_DEFAULT_ALL, target_table,
92 if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playlist ) )
94 gtk_widget_hide( p_intf->p_sys->p_playlist );
100 p_clist = GTK_CLIST( gtk_object_get_data(
101 GTK_OBJECT( p_intf->p_sys->p_playlist ), "playlist_clist" ) );
102 GtkRebuildCList( p_clist , p_main->p_playlist );
103 gtk_widget_show( p_intf->p_sys->p_playlist );
104 gdk_window_raise( p_intf->p_sys->p_playlist->window );
111 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
113 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
117 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
119 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
124 gboolean GtkPlaylistPrev( GtkWidget *widget,
125 GdkEventButton *event,
128 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
130 if( p_intf->p_input != NULL )
132 /* FIXME: temporary hack */
133 intf_PlaylistPrev( p_main->p_playlist );
134 intf_PlaylistPrev( p_main->p_playlist );
135 p_intf->p_input->b_eof = 1;
142 gboolean GtkPlaylistNext( GtkWidget *widget,
143 GdkEventButton *event,
146 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
148 if( p_intf->p_input != NULL )
150 /* FIXME: temporary hack */
151 p_intf->p_input->b_eof = 1;
157 /****************************************************************************
158 * Menu callbacks for playlist functions
159 ****************************************************************************/
160 void GtkPlaylistActivate( GtkMenuItem * menuitem, gpointer user_data )
162 GtkPlaylistShow( GTK_WIDGET( menuitem ), NULL, user_data );
166 void GtkNextActivate( GtkMenuItem * menuitem, gpointer user_data )
168 GtkPlaylistNext( GTK_WIDGET( menuitem ), NULL, user_data );
172 void GtkPrevActivate( GtkMenuItem * menuitem, gpointer user_data )
174 GtkPlaylistPrev( GTK_WIDGET( menuitem ), NULL, user_data );
178 /****************************************************************************
179 * Playlist core functions
180 ****************************************************************************/
181 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
187 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
193 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
195 /* user wants to delete a file in the queue */
198 playlist_t *p_playlist;
200 /* catch the thread back */
201 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
203 p_playlist = p_main->p_playlist;
205 /* lock the struct */
206 vlc_mutex_lock( &p_intf->change_lock );
208 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
209 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
211 /* I use UNDOCUMENTED features to retrieve the selection... */
212 p_selection = p_clist->selection;
214 if( g_list_length( p_selection ) > 0 )
216 /* reverse-sort so that we can delete from the furthest
217 * to the closest item to delete...
219 p_selection = g_list_sort( p_selection, GtkCompareItems );
220 g_list_foreach( p_selection, GtkDeleteGListItem, p_intf );
221 /* rebuild the CList */
222 GtkRebuildCList( p_clist, p_playlist );
225 vlc_mutex_unlock( &p_intf->change_lock );
228 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
230 /* Ok, this is a really small thing, but, hey, it works and
231 might be useful, who knows ? */
232 GtkPlaylistInvert( menuitem, user_data );
233 GtkPlaylistDeleteSelected( menuitem, user_data );
236 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
238 playlist_t *p_playlist;
244 /* catch the thread back */
245 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
247 p_playlist = p_main->p_playlist;
249 /* lock the struct */
250 vlc_mutex_lock( &p_intf->change_lock );
252 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
253 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
255 /* have to copy the selection to an int *
256 I wasn't able to copy the g_list to another g_list
257 glib only does pointer copies, not real copies :( */
259 pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
260 i_sel_l = g_list_length( p_clist->selection );
262 for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
264 pi_selected[i_dummy] = (int)g_list_nth_data( p_clist->selection,
268 gtk_clist_freeze( p_clist );
269 gtk_clist_select_all( p_clist );
271 for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
273 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
274 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
278 gtk_clist_thaw( p_clist );
280 vlc_mutex_unlock( &p_intf->change_lock );
283 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
288 gboolean GtkPlaylistEvent( GtkWidget * widget,
292 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
294 if( ( event->button ).type == GDK_2BUTTON_PRESS )
300 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
301 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
303 if( gtk_clist_get_selection_info( p_clist, (event->button).x,
304 (event->button).y, &i_row, &i_col ) == 1 )
306 /* clicked is in range. */
307 if( p_intf->p_input != NULL )
309 /* FIXME: temporary hack */
310 p_intf->p_input->b_eof = 1;
313 intf_PlaylistJumpto( p_main->p_playlist, i_row - 1 );
321 void GtkPlaylistDragData( GtkWidget *widget,
322 GdkDragContext *drag_context,
325 GtkSelectionData *data,
330 intf_thread_t * p_intf;
334 int i_end = p_main->p_playlist->i_size;
336 /* catch the interface back */
337 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
339 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
340 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
342 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
344 /* we are dropping somewhere into the clist items */
345 GtkDropDataReceived( p_intf, data, info, i_row );
349 /* else, put that at the end of the playlist */
350 GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
353 intf_PlaylistJumpto( p_main->p_playlist, i_end - 1 );
357 gboolean GtkPlaylistDragMotion( GtkWidget *widget,
358 GdkDragContext *drag_context,
364 intf_thread_t *p_intf;
371 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
373 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
374 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
376 if( !GTK_WIDGET_TOPLEVEL(widget) )
378 gdk_window_raise( p_intf->p_sys->p_playlist->window );
383 color.green = 0xffff;
385 gtk_clist_freeze( p_clist );
387 for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
389 gtk_clist_set_background ( p_clist, i_dummy , &color);
395 gtk_clist_set_background( p_clist, p_main->p_playlist->i_index , &color );
397 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
401 color.green = 0x9000;
402 gtk_clist_set_background ( p_clist, i_row - 1, &color);
403 gtk_clist_set_background ( p_clist, i_row, &color);
406 gtk_clist_thaw( p_clist );
411 void GtkDropDataReceived( intf_thread_t * p_intf,
412 GtkSelectionData * p_data, guint i_info, int i_position)
414 /* first we'll have to split against all the '\n' we have */
417 gchar * p_string = p_data->data ;
418 GList * p_files = NULL;
422 /* catch the playlist back */
423 playlist_t * p_playlist = p_main->p_playlist;
426 /* if this has been URLencoded, decode it
428 * Is it a good thing to do it in place ?
431 if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
433 intf_UrlDecode( p_string );
436 /* this cuts string into single file drops */
437 /* this code was borrowed from xmms, thx guys :) */
440 p_temp = strchr( p_string, '\n' );
443 if( *( p_temp - 1 ) == '\r' )
445 *( p_temp - 1) = '\0';
450 /* do we have a protocol or something ? */
451 p_protocol = strstr( p_string, ":/" );
452 if( p_protocol != NULL )
454 p_protocol = calloc( p_protocol - p_string + 2, sizeof(char) );
455 p_protocol = strncpy( p_protocol, p_string,
456 strstr( p_string, ":/" ) + 1 - p_string );
458 intf_WarnMsg( 4, "Protocol dropped is %s", p_protocol );
459 p_string += strlen( p_protocol );
461 /* Allowed things are proto: or proto:// */
462 if( p_string[0] == '/' && p_string[1] == '/')
467 intf_WarnMsg( 4, " Dropped %s", p_string );
471 p_protocol = strdup( "" );
474 /* if it uses the file protocol we can do something, else, sorry :(
475 * I think this is a good choice for now, as we don't have any
476 * ability to read http:// or ftp:// files
477 * what about adding dvd:// to the list of authorized proto ? */
479 if( strcmp( p_protocol, "file:" ) == 0 )
481 p_files = g_list_concat( p_files, GtkReadFiles( p_string ) );
484 /* free the malloc and go on... */
490 p_string = p_temp + 1;
493 /* At this point, we have a nice big list maybe NULL */
494 if( p_files != NULL )
496 /* lock the interface */
497 vlc_mutex_lock( &p_intf->change_lock );
499 intf_WarnMsg( 4, "List has %d elements", g_list_length( p_files ) );
500 GtkAppendList( p_playlist, i_position, p_files );
502 /* get the CList and rebuild it. */
503 p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playlist,
504 "playlist_clist" ) );
505 GtkRebuildCList( p_clist , p_playlist );
507 /* unlock the interface */
508 vlc_mutex_unlock( &p_intf->change_lock );
513 void GtkDeleteGListItem( gpointer data, gpointer param )
515 int i_cur_row = ( int )data;
516 intf_thread_t * p_intf = param;
518 intf_PlaylistDelete( p_main->p_playlist, i_cur_row );
520 /* are we deleting the current played stream */
521 if( p_intf->p_sys->i_playing == i_cur_row )
524 p_intf->p_input->b_eof = 1;
525 /* this has to set the slider to 0 */
528 p_intf->p_sys->i_playing-- ;
530 vlc_mutex_lock( &p_main->p_playlist->change_lock );
531 p_main->p_playlist->i_index-- ;
532 vlc_mutex_unlock( &p_main->p_playlist->change_lock );
537 gint GtkCompareItems( gconstpointer a, gconstpointer b )
543 /* check a file (string) against supposed valid extension */
544 int GtkHasValidExtension( gchar * psz_filename )
546 char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
550 gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
552 for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
554 if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
563 /* recursive function: descend into folders and build a list of
565 GList * GtkReadFiles( gchar * psz_fsname )
568 GList * p_current = NULL;
570 /* get the attributes of this file */
571 stat( psz_fsname, &statbuf );
573 /* is it a regular file ? */
574 if( S_ISREG( statbuf.st_mode ) )
576 if( GtkHasValidExtension( psz_fsname ) )
578 intf_WarnMsg( 2, "%s is a valid file. Stacking on the playlist",
580 return g_list_append( NULL, g_strdup( psz_fsname ) );
587 /* is it a directory (should we check for symlinks ?) */
588 else if( S_ISDIR( statbuf.st_mode ) )
590 /* have to cd into this dir */
591 DIR * p_current_dir = opendir( psz_fsname );
592 struct dirent * p_dir_content;
594 intf_WarnMsg( 2, "%s is a folder.", psz_fsname );
596 if( p_current_dir == NULL )
598 /* something went bad, get out of here ! */
601 p_dir_content = readdir( p_current_dir );
603 /* while we still have entries in the directory */
604 while( p_dir_content != NULL )
606 /* if it is "." or "..", forget it */
607 if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
608 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
610 /* else build the new directory by adding
611 fsname "/" and the current entry name
614 char * psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
615 strlen( p_dir_content->d_name ) * sizeof(char) );
616 strcpy( psz_newfs, psz_fsname );
617 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
618 p_dir_content->d_name );
619 psz_newfs[strlen( psz_fsname )] = '/';
621 p_current = g_list_concat( p_current,
622 GtkReadFiles( psz_newfs ) );
626 p_dir_content = readdir( p_current_dir );
633 /* add items in a playlist
634 * when i_pos==-1 add to the end of the list...
636 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
641 i_length = g_list_length( p_list );
643 for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
645 intf_PlaylistAdd( p_playlist,
646 /* ok; this is a really nasty trick to insert
647 the item where they are suppose to go but, hey
648 this works :P (btw, you are really nasty too) */
649 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + i_dummy ),
650 g_list_nth_data( p_list, i_dummy ) );
655 /* statis timeouted function */
656 void GtkPlayListManage( intf_thread_t * p_intf )
658 /* this thing really sucks for now :( */
660 /* TODO speak more with interface/intf_playlist.c */
662 playlist_t * p_playlist = p_main->p_playlist ;
665 if( GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
667 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
668 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
670 vlc_mutex_lock( &p_playlist->change_lock );
672 if( p_intf->p_sys->i_playing != p_playlist->i_index )
680 gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
682 if( p_intf->p_sys->i_playing != -1 )
686 color.green = 0xffff;
687 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
690 p_intf->p_sys->i_playing = p_playlist->i_index;
693 vlc_mutex_unlock( &p_playlist->change_lock );
697 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
700 gchar * ppsz_text[2];
706 gtk_clist_freeze( p_clist );
707 gtk_clist_clear( p_clist );
709 for( i_dummy = 0; i_dummy < p_playlist->i_size ; i_dummy++ )
711 #ifdef WIN32 /* WIN32 HACK */
712 ppsz_text[0] = g_strdup( "" );
714 ppsz_text[0] = g_strdup( rindex( (char *)(p_playlist->p_item[
715 p_playlist->i_size - 1 - i_dummy].psz_name ), '/' ) + 1 );
717 ppsz_text[1] = g_strdup( "no info");
719 gtk_clist_insert( p_clist, 0, ppsz_text );
721 free( ppsz_text[0] );
722 free( ppsz_text[1] );
724 gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
725 gtk_clist_thaw( p_clist );