1 /*****************************************************************************
2 * gtk_playlist.c : Interface for the playlist dialog
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: gtk_playlist.c,v 1.12 2001/05/15 01:01:44 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 p_intf->p_sys->p_playlist = create_intf_playlist();
78 gtk_object_set_data( GTK_OBJECT( p_intf->p_sys->p_playlist ),
82 if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playlist ) )
84 gtk_widget_hide( p_intf->p_sys->p_playlist );
90 p_clist = GTK_CLIST( gtk_object_get_data(
91 GTK_OBJECT( p_intf->p_sys->p_playlist ), "playlist_clist" ) );
92 GtkRebuildCList( p_clist , p_main->p_playlist );
93 gtk_widget_show( p_intf->p_sys->p_playlist );
94 gdk_window_raise( p_intf->p_sys->p_playlist->window );
101 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
103 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
107 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
109 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
114 gboolean GtkPlaylistPrev( GtkWidget *widget,
115 GdkEventButton *event,
118 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
120 if( p_intf->p_input != NULL )
122 /* FIXME: temporary hack */
123 intf_PlaylistPrev( p_main->p_playlist );
124 intf_PlaylistPrev( p_main->p_playlist );
125 p_intf->p_input->b_eof = 1;
132 gboolean GtkPlaylistNext( GtkWidget *widget,
133 GdkEventButton *event,
136 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
138 if( p_intf->p_input != NULL )
140 /* FIXME: temporary hack */
141 p_intf->p_input->b_eof = 1;
147 /****************************************************************************
148 * Menu callbacks for playlist functions
149 ****************************************************************************/
150 void GtkNextActivate( GtkMenuItem * menuitem, gpointer user_data )
152 GtkPlaylistNext( GTK_WIDGET( menuitem ), NULL, user_data );
156 void GtkPrevActivate( GtkMenuItem * menuitem, gpointer user_data )
158 GtkPlaylistPrev( GTK_WIDGET( menuitem ), NULL, user_data );
162 /****************************************************************************
163 * Playlist core functions
164 ****************************************************************************/
165 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
171 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
177 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
179 /* user wants to delete a file in the queue */
182 playlist_t *p_playlist;
184 /* catch the thread back */
185 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
187 p_playlist = p_main->p_playlist;
189 /* lock the struct */
190 vlc_mutex_lock( &p_intf->change_lock );
192 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
193 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
195 /* I use UNDOCUMENTED features to retrieve the selection... */
196 p_selection = p_clist->selection;
198 if( g_list_length( p_selection ) > 0 )
200 /* reverse-sort so that we can delete from the furthest
201 * to the closest item to delete...
203 p_selection = g_list_sort( p_selection, GtkCompareItems );
204 g_list_foreach( p_selection, GtkDeleteGListItem, p_intf );
205 /* rebuild the CList */
206 GtkRebuildCList( p_clist, p_playlist );
209 vlc_mutex_unlock( &p_intf->change_lock );
212 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
214 /* Ok, this is a really small thing, but, hey, it works and
215 might be useful, who knows ? */
216 GtkPlaylistInvert( menuitem, user_data );
217 GtkPlaylistDeleteSelected( menuitem, user_data );
220 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
222 playlist_t *p_playlist;
228 /* catch the thread back */
229 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
231 p_playlist = p_main->p_playlist;
233 /* lock the struct */
234 vlc_mutex_lock( &p_intf->change_lock );
236 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
237 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
239 /* have to copy the selection to an int *
240 I wasn't able to copy the g_list to another g_list
241 glib only does pointer copies, not real copies :( */
243 pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
244 i_sel_l = g_list_length( p_clist->selection );
246 for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
248 pi_selected[i_dummy] = (int)g_list_nth_data( p_clist->selection,
252 gtk_clist_freeze( p_clist );
253 gtk_clist_select_all( p_clist );
255 for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
257 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
258 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
262 gtk_clist_thaw( p_clist );
264 vlc_mutex_unlock( &p_intf->change_lock );
267 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
272 gboolean GtkPlaylistEvent( GtkWidget * widget,
276 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
278 if( ( event->button ).type == GDK_2BUTTON_PRESS )
284 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
285 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
287 if( gtk_clist_get_selection_info( p_clist, (event->button).x,
288 (event->button).y, &i_row, &i_col ) == 1 )
290 /* clicked is in range. */
291 if( p_intf->p_input != NULL )
293 /* FIXME: temporary hack */
294 p_intf->p_input->b_eof = 1;
297 // vlc_mutex_lock( &p_main->p_playlist->change_lock );
299 intf_PlaylistJumpto( p_main->p_playlist, i_row - 1 );
300 p_main->p_playlist->b_stopped = 0;
302 // vlc_mutex_unlock( &p_main->p_playlist->change_lock );
310 void GtkPlaylistDragData( GtkWidget *widget,
311 GdkDragContext *drag_context,
314 GtkSelectionData *data,
319 intf_thread_t * p_intf;
323 int i_end = p_main->p_playlist->i_size;
325 /* catch the interface back */
326 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
328 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
329 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
331 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
333 /* we are dropping somewhere into the clist items */
334 GtkDropDataReceived( p_intf, data, info, i_row );
338 /* else, put that at the end of the playlist */
339 GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
342 // vlc_mutex_lock( &p_main->p_playlist->change_lock );
344 intf_PlaylistJumpto( p_main->p_playlist, i_end - 1 );
345 p_main->p_playlist->b_stopped = 0;
347 // vlc_mutex_unlock( &p_main->p_playlist->change_lock );
351 gboolean GtkPlaylistDragMotion( GtkWidget *widget,
352 GdkDragContext *drag_context,
358 intf_thread_t *p_intf;
365 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
367 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
368 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
370 if( !GTK_WIDGET_TOPLEVEL(widget) )
372 gdk_window_raise( p_intf->p_sys->p_playlist->window );
377 color.green = 0xffff;
379 gtk_clist_freeze( p_clist );
381 for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
383 gtk_clist_set_background ( p_clist, i_dummy , &color);
389 gtk_clist_set_background( p_clist, p_main->p_playlist->i_index , &color );
391 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
395 color.green = 0x9000;
396 gtk_clist_set_background ( p_clist, i_row - 1, &color);
397 gtk_clist_set_background ( p_clist, i_row, &color);
400 gtk_clist_thaw( p_clist );
405 void GtkDropDataReceived( intf_thread_t * p_intf,
406 GtkSelectionData * p_data, guint i_info, int i_position)
408 /* first we'll have to split against all the '\n' we have */
411 gchar * p_string = p_data->data ;
412 GList * p_files = NULL;
416 /* catch the playlist back */
417 playlist_t * p_playlist = p_main->p_playlist;
420 /* if this has been URLencoded, decode it
422 * Is it a good thing to do it in place ?
425 if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
427 urldecode_path( p_string );
430 /* this cuts string into single file drops */
431 /* this code was borrowed from xmms, thx guys :) */
434 p_temp = strchr( p_string, '\n' );
437 if( *( p_temp - 1 ) == '\r' )
439 *( p_temp - 1) = '\0';
444 /* do we have a protocol or something ? */
445 p_protocol = strstr( p_string, ":/" );
446 if( p_protocol != NULL )
448 p_protocol = calloc( p_protocol - p_string + 2, sizeof(char) );
449 p_protocol = strncpy( p_protocol, p_string,
450 strstr( p_string, ":/" ) + 1 - p_string );
452 intf_WarnMsg( 4, "Protocol dropped is %s", p_protocol );
453 p_string += strlen( p_protocol );
455 /* Allowed things are proto: or proto:// */
456 if( p_string[0] == '/' && p_string[1] == '/')
461 intf_WarnMsg( 4, " Dropped %s", p_string );
465 p_protocol = strdup( "" );
468 /* if it uses the file protocol we can do something, else, sorry :(
469 * I think this is a good choice for now, as we don't have any
470 * ability to read http:// or ftp:// files
471 * what about adding dvd:// to the list of authorized proto ? */
473 if( strcmp( p_protocol, "file:" ) == 0 )
475 p_files = g_list_concat( p_files, GtkReadFiles( p_string ) );
478 /* free the malloc and go on... */
484 p_string = p_temp + 1;
487 /* At this point, we have a nice big list maybe NULL */
488 if( p_files != NULL )
490 /* lock the interface */
491 vlc_mutex_lock( &p_intf->change_lock );
493 intf_WarnMsg( 4, "List has %d elements", g_list_length( p_files ) );
494 GtkAppendList( p_playlist, i_position, p_files );
496 /* get the CList and rebuild it. */
497 p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playlist,
498 "playlist_clist" ) );
499 GtkRebuildCList( p_clist , p_playlist );
501 /* unlock the interface */
502 vlc_mutex_unlock( &p_intf->change_lock );
504 p_main->p_playlist->b_stopped = 0;
509 void GtkDeleteGListItem( gpointer data, gpointer param )
511 int i_cur_row = ( int )data;
512 intf_thread_t * p_intf = param;
514 intf_PlaylistDelete( p_main->p_playlist, i_cur_row );
516 /* are we deleting the current played stream */
517 if( p_intf->p_sys->i_playing == i_cur_row )
520 p_intf->p_input->b_eof = 1;
521 /* this has to set the slider to 0 */
524 p_intf->p_sys->i_playing-- ;
526 vlc_mutex_lock( &p_main->p_playlist->change_lock );
527 p_main->p_playlist->i_index-- ;
528 vlc_mutex_unlock( &p_main->p_playlist->change_lock );
533 gint GtkCompareItems( gconstpointer a, gconstpointer b )
539 /* check a file (string) against supposed valid extension */
540 int GtkHasValidExtension( gchar * psz_filename )
542 char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
546 gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
548 for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
550 if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
559 /* recursive function: descend into folders and build a list of
561 GList * GtkReadFiles( gchar * psz_fsname )
564 GList * p_current = NULL;
566 /* get the attributes of this file */
567 stat( psz_fsname, &statbuf );
569 /* is it a regular file ? */
570 if( S_ISREG( statbuf.st_mode ) )
572 if( GtkHasValidExtension( psz_fsname ) )
574 intf_WarnMsg( 2, "%s is a valid file. Stacking on the playlist",
576 return g_list_append( NULL, g_strdup( psz_fsname ) );
583 /* is it a directory (should we check for symlinks ?) */
584 else if( S_ISDIR( statbuf.st_mode ) )
586 /* have to cd into this dir */
587 DIR * p_current_dir = opendir( psz_fsname );
588 struct dirent * p_dir_content;
590 intf_WarnMsg( 2, "%s is a folder.", psz_fsname );
592 if( p_current_dir == NULL )
594 /* something went bad, get out of here ! */
597 p_dir_content = readdir( p_current_dir );
599 /* while we still have entries in the directory */
600 while( p_dir_content != NULL )
602 /* if it is "." or "..", forget it */
603 if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
604 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
606 /* else build the new directory by adding
607 fsname "/" and the current entry name
610 char * psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
611 strlen( p_dir_content->d_name ) * sizeof(char) );
612 strcpy( psz_newfs, psz_fsname );
613 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
614 p_dir_content->d_name );
615 psz_newfs[strlen( psz_fsname )] = '/';
617 p_current = g_list_concat( p_current,
618 GtkReadFiles( psz_newfs ) );
622 p_dir_content = readdir( p_current_dir );
629 /* add items in a playlist
630 * when i_pos==-1 add to the end of the list...
632 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
637 i_length = g_list_length( p_list );
639 for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
641 intf_PlaylistAdd( p_playlist,
642 /* ok; this is a really nasty trick to insert
643 the item where they are suppose to go but, hey
644 this works :P (btw, you are really nasty too) */
645 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + i_dummy ),
646 g_list_nth_data( p_list, i_dummy ) );
651 /* statis timeouted function */
652 void GtkPlayListManage( intf_thread_t * p_intf )
654 /* this thing really sucks for now :( */
656 /* TODO speak more with interface/intf_playlist.c */
658 playlist_t * p_playlist = p_main->p_playlist ;
661 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
662 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
664 if( p_intf->p_sys->i_playing != p_playlist->i_index )
672 gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
674 if( p_intf->p_sys->i_playing != -1 )
678 color.green = 0xffff;
679 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
682 p_intf->p_sys->i_playing = p_playlist->i_index;
686 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
689 gchar * ppsz_text[2];
695 gtk_clist_freeze( p_clist );
696 gtk_clist_clear( p_clist );
698 for( i_dummy = 0; i_dummy < p_playlist->i_size ; i_dummy++ )
700 #ifdef WIN32 /* WIN32 HACK */
701 ppsz_text[0] = g_strdup( "" );
703 ppsz_text[0] = g_strdup( rindex( (char *)(p_playlist->p_item[
704 p_playlist->i_size - 1 - i_dummy].psz_name ), '/' ) + 1 );
706 ppsz_text[1] = g_strdup( "no info");
708 gtk_clist_insert( p_clist, 0, ppsz_text );
710 free( ppsz_text[0] );
711 free( ppsz_text[1] );
713 gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
714 gtk_clist_thaw( p_clist );