1 /*****************************************************************************
2 * gtk_playlist.c : Interface for the playlist dialog
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: gtk_playlist.c,v 1.31 2002/05/22 14:20:41 gbazin 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 */
35 #if (!defined( WIN32 ) || defined(__MINGW32__))
36 /* Mingw has its own version of dirent */
43 #ifdef MODULE_NAME_IS_gnome
49 #include "stream_control.h"
50 #include "input_ext-intf.h"
52 #include "interface.h"
53 #include "intf_playlist.h"
55 #include "gtk_callbacks.h"
56 #include "gtk_interface.h"
57 #include "gtk_support.h"
58 #include "gtk_playlist.h"
59 #include "gtk_common.h"
61 /****************************************************************************
62 * Playlist window management
63 ****************************************************************************/
64 gboolean GtkPlaylistShow( GtkWidget *widget,
65 GdkEventButton *event,
68 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
70 if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playlist ) )
72 gtk_widget_hide( p_intf->p_sys->p_playlist );
78 p_clist = GTK_CLIST( gtk_object_get_data(
79 GTK_OBJECT( p_intf->p_sys->p_playlist ), "playlist_clist" ) );
80 GtkRebuildCList( p_clist , p_main->p_playlist );
81 gtk_widget_show( p_intf->p_sys->p_playlist );
82 gdk_window_raise( p_intf->p_sys->p_playlist->window );
89 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
91 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
95 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
97 gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
102 gboolean GtkPlaylistPrev( GtkWidget *widget,
103 GdkEventButton *event,
106 if( p_input_bank->pp_input[0] != NULL )
108 /* FIXME: temporary hack */
109 intf_PlaylistPrev( p_main->p_playlist );
110 intf_PlaylistPrev( p_main->p_playlist );
111 p_input_bank->pp_input[0]->b_eof = 1;
118 gboolean GtkPlaylistNext( GtkWidget *widget,
119 GdkEventButton *event,
122 if( p_input_bank->pp_input[0] != NULL )
124 /* FIXME: temporary hack */
125 p_input_bank->pp_input[0]->b_eof = 1;
131 /****************************************************************************
132 * Menu callbacks for playlist functions
133 ****************************************************************************/
134 void GtkPlaylistActivate( GtkMenuItem * menuitem, gpointer user_data )
136 GtkPlaylistShow( GTK_WIDGET( menuitem ), NULL, user_data );
140 void GtkNextActivate( GtkMenuItem * menuitem, gpointer user_data )
142 GtkPlaylistNext( GTK_WIDGET( menuitem ), NULL, user_data );
146 void GtkPrevActivate( GtkMenuItem * menuitem, gpointer user_data )
148 GtkPlaylistPrev( GTK_WIDGET( menuitem ), NULL, user_data );
152 /****************************************************************************
153 * Playlist core functions
154 ****************************************************************************/
155 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
161 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
167 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
169 /* user wants to delete a file in the queue */
172 playlist_t *p_playlist;
174 /* catch the thread back */
175 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), /*(char*)user_data*/"intf_playlist" );
177 p_playlist = p_main->p_playlist;
179 /* lock the struct */
180 vlc_mutex_lock( &p_intf->change_lock );
182 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
183 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
185 /* I use UNDOCUMENTED features to retrieve the selection... */
186 p_selection = p_clist->selection;
188 if( g_list_length( p_selection ) > 0 )
190 /* reverse-sort so that we can delete from the furthest
191 * to the closest item to delete...
193 p_selection = g_list_sort( p_selection, GtkCompareItems );
194 g_list_foreach( p_selection, GtkDeleteGListItem, p_intf );
195 /* rebuild the CList */
196 GtkRebuildCList( p_clist, p_playlist );
199 vlc_mutex_unlock( &p_intf->change_lock );
202 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
204 /* Ok, this is a really small thing, but, hey, it works and
205 might be useful, who knows ? */
206 GtkPlaylistInvert( menuitem, user_data );
207 GtkPlaylistDeleteSelected( menuitem, user_data );
210 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
212 playlist_t *p_playlist;
218 /* catch the thread back */
219 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(menuitem), (char*)user_data );
221 p_playlist = p_main->p_playlist;
223 /* lock the struct */
224 vlc_mutex_lock( &p_intf->change_lock );
226 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
227 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
229 /* have to copy the selection to an int *
230 I wasn't able to copy the g_list to another g_list
231 glib only does pointer copies, not real copies :( */
233 pi_selected = malloc( sizeof(int) *g_list_length( p_clist->selection ) );
234 i_sel_l = g_list_length( p_clist->selection );
236 for( i_dummy = 0 ; i_dummy < i_sel_l ; i_dummy++)
238 pi_selected[i_dummy] = (long)g_list_nth_data( p_clist->selection,
242 gtk_clist_freeze( p_clist );
243 gtk_clist_select_all( p_clist );
245 for( i_dummy = 0; i_dummy < i_sel_l; i_dummy++)
247 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
248 gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 1 );
252 gtk_clist_thaw( p_clist );
254 vlc_mutex_unlock( &p_intf->change_lock );
257 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
262 gboolean GtkPlaylistEvent( GtkWidget * widget,
266 intf_thread_t *p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
268 if( ( event->button ).type == GDK_2BUTTON_PRESS )
274 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
275 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
277 if( gtk_clist_get_selection_info( p_clist, (event->button).x,
278 (event->button).y, &i_row, &i_col ) == 1 )
280 /* clicked is in range. */
281 if( p_input_bank->pp_input[0] != NULL )
283 /* FIXME: temporary hack */
284 p_input_bank->pp_input[0]->b_eof = 1;
287 intf_PlaylistJumpto( p_main->p_playlist, i_row - 1 );
295 void GtkPlaylistDragData( GtkWidget *widget,
296 GdkDragContext *drag_context,
299 GtkSelectionData *data,
304 intf_thread_t * p_intf;
308 int i_end = p_main->p_playlist->i_size;
310 /* catch the interface back */
311 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
313 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
314 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
316 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
318 /* we are dropping somewhere into the clist items */
319 GtkDropDataReceived( p_intf, data, info, i_row );
323 /* else, put that at the end of the playlist */
324 GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
327 intf_PlaylistJumpto( p_main->p_playlist, i_end - 1 );
331 gboolean GtkPlaylistDragMotion( GtkWidget *widget,
332 GdkDragContext *drag_context,
338 intf_thread_t *p_intf;
345 p_intf = GetIntf( GTK_WIDGET(widget), (char*)user_data );
347 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
348 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
350 if( !GTK_WIDGET_TOPLEVEL(widget) )
352 gdk_window_raise( p_intf->p_sys->p_playlist->window );
357 color.green = 0xffff;
359 gtk_clist_freeze( p_clist );
361 for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
363 gtk_clist_set_background ( p_clist, i_dummy , &color);
369 gtk_clist_set_background( p_clist, p_main->p_playlist->i_index , &color );
371 if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1)
375 color.green = 0x9000;
376 gtk_clist_set_background ( p_clist, i_row - 1, &color);
377 gtk_clist_set_background ( p_clist, i_row, &color);
380 gtk_clist_thaw( p_clist );
385 void GtkDropDataReceived( intf_thread_t * p_intf,
386 GtkSelectionData * p_data, guint i_info, int i_position)
388 /* first we'll have to split against all the '\n' we have */
392 gchar * p_string = p_data->data ;
393 GList * p_files = NULL;
397 /* catch the playlist back */
398 playlist_t * p_playlist = p_main->p_playlist;
401 /* if this has been URLencoded, decode it
403 * Is it a good thing to do it in place ?
406 if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
408 intf_UrlDecode( p_string );
411 /* this cuts string into single file drops */
412 /* this code was borrowed from xmms, thx guys :) */
415 p_next = strchr( p_string, '\n' );
418 if( *( p_next - 1 ) == '\r' )
420 *( p_next - 1) = '\0';
425 /* do we have a protocol or something ? */
426 p_temp = strstr( p_string, ":" );
427 if( p_temp != NULL && p_temp[0] != '\0' )
433 p_protocol = strdup( p_string );
437 /* Allowed things are proto: or proto:// */
438 if( p_temp[0] == '/' && p_temp[1] == '/')
443 intf_WarnMsg( 4, "playlist: protocol '%s', target '%s'",
444 p_protocol, p_temp );
448 p_protocol = strdup( "" );
451 /* if it uses the file protocol we can do something, else, sorry :(
452 * I think this is a good choice for now, as we don't have any
453 * ability to read http:// or ftp:// files
454 * what about adding dvd:// to the list of authorized proto ? */
456 if( strcmp( p_protocol, "file:" ) == 0 )
458 p_files = g_list_concat( p_files, GtkReadFiles( p_string ) );
462 p_files = g_list_concat( p_files,
463 g_list_append( NULL, g_strdup( p_string ) ) );
466 /* free the malloc and go on... */
473 p_string = p_next + 1;
476 /* At this point, we have a nice big list maybe NULL */
477 if( p_files != NULL )
479 /* lock the interface */
480 vlc_mutex_lock( &p_intf->change_lock );
482 intf_WarnMsg( 4, "List has %d elements", g_list_length( p_files ) );
483 GtkAppendList( p_playlist, i_position, p_files );
485 /* get the CList and rebuild it. */
486 p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playlist,
487 "playlist_clist" ) );
488 GtkRebuildCList( p_clist , p_playlist );
490 /* unlock the interface */
491 vlc_mutex_unlock( &p_intf->change_lock );
496 void GtkDeleteGListItem( gpointer data, gpointer param )
498 int i_cur_row = (long)data;
499 intf_thread_t * p_intf = param;
501 intf_PlaylistDelete( p_main->p_playlist, i_cur_row );
503 /* are we deleting the current played stream */
504 if( p_intf->p_sys->i_playing == i_cur_row )
507 p_input_bank->pp_input[0]->b_eof = 1;
508 /* this has to set the slider to 0 */
511 p_intf->p_sys->i_playing-- ;
513 vlc_mutex_lock( &p_main->p_playlist->change_lock );
514 p_main->p_playlist->i_index-- ;
515 vlc_mutex_unlock( &p_main->p_playlist->change_lock );
520 gint GtkCompareItems( gconstpointer a, gconstpointer b )
526 /* check a file (string) against supposed valid extension */
527 int GtkHasValidExtension( gchar * psz_filename )
529 char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
533 gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
535 for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
537 if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
546 /* recursive function: descend into folders and build a list of
548 GList * GtkReadFiles( gchar * psz_fsname )
551 GList * p_current = NULL;
553 /* get the attributes of this file */
554 stat( psz_fsname, &statbuf );
556 /* is it a regular file ? */
557 if( S_ISREG( statbuf.st_mode ) )
559 if( GtkHasValidExtension( psz_fsname ) )
561 intf_WarnMsg( 2, "%s is a valid file. Stacking on the playlist",
563 return g_list_append( NULL, g_strdup( psz_fsname ) );
570 /* is it a directory (should we check for symlinks ?) */
571 else if( S_ISDIR( statbuf.st_mode ) )
573 /* have to cd into this dir */
574 DIR * p_current_dir = opendir( psz_fsname );
575 struct dirent * p_dir_content;
577 intf_WarnMsg( 2, "%s is a folder.", psz_fsname );
579 if( p_current_dir == NULL )
581 /* something went bad, get out of here ! */
584 p_dir_content = readdir( p_current_dir );
586 /* while we still have entries in the directory */
587 while( p_dir_content != NULL )
589 /* if it is "." or "..", forget it */
590 if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
591 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
593 /* else build the new directory by adding
594 fsname "/" and the current entry name
597 char * psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
598 strlen( p_dir_content->d_name ) * sizeof(char) );
599 strcpy( psz_newfs, psz_fsname );
600 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
601 p_dir_content->d_name );
602 psz_newfs[strlen( psz_fsname )] = '/';
604 p_current = g_list_concat( p_current,
605 GtkReadFiles( psz_newfs ) );
609 p_dir_content = readdir( p_current_dir );
616 /* add items in a playlist
617 * when i_pos==-1 add to the end of the list...
619 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
624 i_length = g_list_length( p_list );
626 for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
628 intf_PlaylistAdd( p_playlist,
629 /* ok; this is a really nasty trick to insert
630 the item where they are suppose to go but, hey
631 this works :P (btw, you are really nasty too) */
632 i_pos==PLAYLIST_END?PLAYLIST_END:( i_pos + i_dummy ),
633 g_list_nth_data( p_list, i_dummy ) );
638 /* statis timeouted function */
639 void GtkPlayListManage( intf_thread_t * p_intf )
641 /* this thing really sucks for now :( */
643 /* TODO speak more with interface/intf_playlist.c */
645 playlist_t * p_playlist = p_main->p_playlist ;
648 if( GTK_IS_WIDGET( p_intf->p_sys->p_playlist ) )
650 p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
651 p_intf->p_sys->p_playlist ), "playlist_clist" ) );
653 vlc_mutex_lock( &p_playlist->change_lock );
655 if( p_intf->p_sys->i_playing != p_playlist->i_index )
663 gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
665 if( p_intf->p_sys->i_playing != -1 )
669 color.green = 0xffff;
670 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
673 p_intf->p_sys->i_playing = p_playlist->i_index;
676 vlc_mutex_unlock( &p_playlist->change_lock );
680 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
683 gchar * ppsz_text[2];
689 gtk_clist_freeze( p_clist );
690 gtk_clist_clear( p_clist );
692 vlc_mutex_lock( &p_playlist->change_lock );
693 for( i_dummy = p_playlist->i_size ; i_dummy-- ; )
695 ppsz_text[0] = p_playlist->p_item[i_dummy].psz_name;
696 ppsz_text[1] = "no info";
697 gtk_clist_insert( p_clist, 0, ppsz_text );
699 vlc_mutex_unlock( &p_playlist->change_lock );
701 gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
702 gtk_clist_thaw( p_clist );