]> git.sesse.net Git - vlc/blob - modules/gui/wxwidgets/playlist_manager.cpp
Wxwidgets playlist fix
[vlc] / modules / gui / wxwidgets / playlist_manager.cpp
1 /*****************************************************************************
2  * playlist_manager.cpp : wxWindows plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2000-2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Olivier Teulière <ipkiss@via.ecp.fr>
8  *          Clément Stenac <zorglub@videolan.org>
9  *          Gildas Bazin <gbazin@videolan.org>
10  *
11  * This program is free software; you can redistribute it and/OR MODIFy
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #include "playlist_manager.hpp"
30 #include "interface.hpp"
31
32 #include "bitmaps/type_unknown.xpm"
33 #include "bitmaps/type_afile.xpm"
34 #include "bitmaps/type_vfile.xpm"
35 #include "bitmaps/type_net.xpm"
36 #include "bitmaps/type_card.xpm"
37 #include "bitmaps/type_disc.xpm"
38 #include "bitmaps/type_cdda.xpm"
39 #include "bitmaps/type_directory.xpm"
40 #include "bitmaps/type_playlist.xpm"
41 #include "bitmaps/type_node.xpm"
42
43 #include <wx/dynarray.h>
44 #include <wx/imaglist.h>
45 #include "vlc_meta.h"
46
47 namespace wxvlc {
48 /* Callback prototype */
49 static int PlaylistChanged( vlc_object_t *, const char *,
50                             vlc_value_t, vlc_value_t, void * );
51 static int PlaylistNext( vlc_object_t *, const char *,
52                          vlc_value_t, vlc_value_t, void * );
53 static int ItemChanged( vlc_object_t *, const char *,
54                         vlc_value_t, vlc_value_t, void * );
55 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
56                       vlc_value_t oval, vlc_value_t nval, void *param );
57 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
58                       vlc_value_t oval, vlc_value_t nval, void *param );
59
60 /*****************************************************************************
61  * Event Table.
62  *****************************************************************************/
63
64 /* IDs for the controls and the menu commands */
65 enum
66 {
67     TreeCtrl_Event,
68
69     UpdateItem_Event,
70     AppendItem_Event,
71     RemoveItem_Event,
72 };
73
74 DEFINE_LOCAL_EVENT_TYPE( wxEVT_PLAYLIST );
75
76 BEGIN_EVENT_TABLE(PlaylistManager, wxPanel)
77     /* Tree control events */
78     EVT_TREE_ITEM_ACTIVATED( TreeCtrl_Event, PlaylistManager::OnActivateItem )
79
80     /* Custom events */
81     EVT_COMMAND(-1, wxEVT_PLAYLIST, PlaylistManager::OnPlaylistEvent)
82 END_EVENT_TABLE()
83
84 /*****************************************************************************
85  * PlaylistItem class
86  ****************************************************************************/
87 class PlaylistItem : public wxTreeItemData
88 {
89 public:
90     PlaylistItem( playlist_item_t *p_item ) : i_id(p_item->p_input->i_id) {}
91     int i_id;
92 };
93
94 /*****************************************************************************
95  * Constructor.
96  *****************************************************************************/
97 PlaylistManager::PlaylistManager( intf_thread_t *_p_intf, wxWindow *p_parent ):
98     wxPanel( p_parent, -1, wxDefaultPosition, wxSize(0,0) )
99 {
100     /* Initializations */
101     p_intf = _p_intf;
102     b_need_update = VLC_FALSE;
103     i_items_to_append = 0;
104     i_cached_item_id = -1;
105     i_update_counter = 0;
106
107     p_playlist = (playlist_t *)
108         vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
109     if( p_playlist == NULL ) return;
110
111     var_Create( p_intf, "random", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
112     var_Create( p_intf, "loop", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
113     var_Create( p_intf, "repeat", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );;
114
115     /* Create the tree */
116     treectrl = new wxTreeCtrl( this, TreeCtrl_Event,
117                                wxDefaultPosition, wxDefaultSize,
118                                wxTR_HIDE_ROOT | wxTR_LINES_AT_ROOT|
119                                wxTR_NO_LINES |
120                                wxTR_HAS_BUTTONS | wxTR_TWIST_BUTTONS |
121                                wxTR_MULTIPLE | wxTR_EXTENDED );
122
123     /* Add everything to the panel */
124     sizer = new wxBoxSizer( wxHORIZONTAL );
125     SetSizer( sizer );
126     sizer->Add( treectrl, 1, wxEXPAND );
127     sizer->Layout();
128     sizer->Fit( this );
129
130     /* Create image list */
131     wxImageList *p_images = new wxImageList( 16 , 16, TRUE );
132
133     /* FIXME: absolutely needs to be in the right order FIXME */
134     p_images->Add( wxIcon( type_unknown_xpm ) );
135     p_images->Add( wxIcon( type_afile_xpm ) );
136     p_images->Add( wxIcon( type_vfile_xpm ) );
137     p_images->Add( wxIcon( type_directory_xpm ) );
138     p_images->Add( wxIcon( type_disc_xpm ) );
139     p_images->Add( wxIcon( type_cdda_xpm ) );
140     p_images->Add( wxIcon( type_card_xpm ) );
141     p_images->Add( wxIcon( type_net_xpm ) );
142     p_images->Add( wxIcon( type_playlist_xpm ) );
143     p_images->Add( wxIcon( type_node_xpm ) );
144     treectrl->AssignImageList( p_images );
145
146     /* Reduce font size */
147     wxFont font = treectrl->GetFont(); font.SetPointSize(9);
148     treectrl->SetFont( font );
149
150 #if wxUSE_DRAG_AND_DROP
151     /* Associate drop targets with the playlist */
152     SetDropTarget( new DragAndDrop( p_intf, VLC_TRUE ) );
153 #endif
154
155     /* Update the playlist */
156     Rebuild( VLC_TRUE );
157
158     /*
159      * We want to be notified of playlist changes
160      */
161
162     /* Some global changes happened -> Rebuild all */
163     var_AddCallback( p_playlist, "intf-change", PlaylistChanged, this );
164
165     /* We went to the next item */
166     var_AddCallback( p_playlist, "playlist-current", PlaylistNext, this );
167
168     /* One item has been updated */
169     var_AddCallback( p_playlist, "item-change", ItemChanged, this );
170
171     var_AddCallback( p_playlist, "item-append", ItemAppended, this );
172     var_AddCallback( p_playlist, "item-deleted", ItemDeleted, this );
173 }
174
175 PlaylistManager::~PlaylistManager()
176 {
177     if( p_playlist == NULL ) return;
178
179     var_DelCallback( p_playlist, "item-change", ItemChanged, this );
180     var_DelCallback( p_playlist, "playlist-current", PlaylistNext, this );
181     var_DelCallback( p_playlist, "intf-change", PlaylistChanged, this );
182     var_DelCallback( p_playlist, "item-append", ItemAppended, this );
183     var_DelCallback( p_playlist, "item-deleted", ItemDeleted, this );
184     vlc_object_release( p_playlist );
185 }
186
187 /*****************************************************************************
188  * PlaylistChanged: callback triggered by the intf-change playlist variable
189  *  We don't rebuild the playlist directly here because we don't want the
190  *  caller to block for a too long time.
191  *****************************************************************************/
192 static int PlaylistChanged( vlc_object_t *p_this, const char *psz_variable,
193                             vlc_value_t oval, vlc_value_t nval, void *param )
194 {
195     PlaylistManager *p_playlist = (PlaylistManager *)param;
196     p_playlist->b_need_update = VLC_TRUE;
197     return VLC_SUCCESS;
198 }
199
200 /*****************************************************************************
201  * Next: callback triggered by the playlist-current playlist variable
202  *****************************************************************************/
203 static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
204                          vlc_value_t oval, vlc_value_t nval, void *param )
205 {
206     PlaylistManager *p_playlist = (PlaylistManager *)param;
207
208     wxCommandEvent event( wxEVT_PLAYLIST, UpdateItem_Event );
209     event.SetInt( oval.i_int );
210     p_playlist->AddPendingEvent( event );
211     event.SetInt( nval.i_int );
212     p_playlist->AddPendingEvent( event );
213
214     return VLC_SUCCESS;
215 }
216
217 /*****************************************************************************
218  * Update functions
219  *****************************************************************************/
220 void PlaylistManager::CreateNode( playlist_item_t *p_node, wxTreeItemId parent)
221 {
222     wxTreeItemId node =
223         treectrl->AppendItem( parent, wxL2U( p_node->p_input->psz_name ), -1, -1,
224                               new PlaylistItem( p_node ) );
225     treectrl->SetItemImage( node, p_node->p_input->i_type );
226
227     UpdateNodeChildren( p_node, node );
228 }
229
230 void PlaylistManager::UpdateNode( playlist_item_t *p_node, wxTreeItemId node )
231 {
232     wxTreeItemIdValue cookie;
233     wxTreeItemId child;
234
235     for( int i = 0; i < p_node->i_children ; i++ )
236     {
237         if( !i ) child = treectrl->GetFirstChild( node, cookie);
238         else child = treectrl->GetNextChild( node, cookie );
239
240         if( !child.IsOk() )
241         {
242             /* Not enough children */
243             CreateNode( p_node->pp_children[i], node );
244             /* Keep the tree pointer up to date */
245             child = treectrl->GetNextChild( node, cookie );
246         }
247     }
248
249     treectrl->SetItemImage( node, p_node->p_input->i_type );
250
251 }
252
253 void PlaylistManager::UpdateNodeChildren( playlist_item_t *p_node,
254                                           wxTreeItemId node )
255 {
256     for( int i = 0; i< p_node->i_children ; i++ )
257     {
258         /* Append the item */
259         if( p_node->pp_children[i]->i_children == -1 )
260         {
261             wxTreeItemId item =
262                 treectrl->AppendItem( node,
263                     wxL2U( p_node->pp_children[i]->p_input->psz_name ), -1,-1,
264                            new PlaylistItem( p_node->pp_children[i]) );
265
266             UpdateTreeItem( item );
267         }
268         else
269         {
270             CreateNode( p_node->pp_children[i], node );
271         }
272     }
273 }
274
275 void PlaylistManager::UpdateTreeItem( wxTreeItemId item )
276 {
277     if( ! item.IsOk() ) return;
278
279     wxTreeItemData *p_data = treectrl->GetItemData( item );
280     if( !p_data ) return;
281
282     LockPlaylist( p_intf->p_sys, p_playlist );
283     playlist_item_t *p_item =
284         playlist_ItemGetById( p_playlist, ((PlaylistItem *)p_data)->i_id,
285                 VLC_TRUE );
286     if( !p_item )
287     {
288         UnlockPlaylist( p_intf->p_sys, p_playlist );
289         return;
290     }
291
292     wxString msg;
293     wxString duration = wxU( "" );
294     char *psz_artist = p_item->p_input->p_meta->psz_artist ?
295                         strdup( p_item->p_input->p_meta->psz_artist ) :
296                         strdup( "" );
297     if( !psz_artist )
298     {
299         UnlockPlaylist( p_intf->p_sys, p_playlist );
300         return;
301     }
302
303     char psz_duration[MSTRTIME_MAX_SIZE];
304     mtime_t dur = p_item->p_input->i_duration;
305
306     if( dur != -1 )
307     {
308         secstotimestr( psz_duration, dur/1000000 );
309         duration.Append( wxU( " ( " ) +  wxString( wxU( psz_duration ) ) +
310                          wxU( " )" ) );
311     }
312
313     if( !strcmp( psz_artist, "" ) || p_item->p_input->b_fixed_name == VLC_TRUE )
314     {
315         msg = wxString( wxU( p_item->p_input->psz_name ) ) + duration;
316     }
317     else
318     {
319         msg = wxString(wxU( psz_artist )) + wxT(" - ") +
320                     wxString(wxU(p_item->p_input->psz_name)) + duration;
321     }
322     free( psz_artist );
323     treectrl->SetItemText( item , msg );
324     treectrl->SetItemImage( item, p_item->p_input->i_type );
325
326     if( p_playlist->status.p_item == p_item )
327     {
328         treectrl->SetItemBold( item, true );
329         while( treectrl->GetItemParent( item ).IsOk() )
330         {
331             item = treectrl->GetItemParent( item );
332             treectrl->Expand( item );
333         }
334     }
335     else
336     {
337         treectrl->SetItemBold( item, false );
338     }
339     UnlockPlaylist( p_intf->p_sys, p_playlist );
340 }
341
342 void PlaylistManager::AppendItem( wxCommandEvent& event )
343 {
344     playlist_add_t *p_add = (playlist_add_t *)event.GetClientData();
345     playlist_item_t *p_item = NULL;
346     wxTreeItemId item, node;
347
348     i_items_to_append--;
349
350     /* No need to do anything if the playlist is going to be rebuilt */
351     if( b_need_update ) return;
352
353     //if( p_add->i_view != i_current_view ) goto update;
354
355     node = FindItem( treectrl->GetRootItem(), p_add->i_node );
356     if( !node.IsOk() ) goto update;
357
358     p_item = playlist_ItemGetById( p_playlist, p_add->i_item, VLC_FALSE );
359     if( !p_item ) goto update;
360
361     item = FindItem( treectrl->GetRootItem(), p_add->i_item );
362     if( item.IsOk() ) goto update;
363
364     item = treectrl->AppendItem( node, wxL2U( p_item->p_input->psz_name ), -1,-1,
365                                  new PlaylistItem( p_item ) );
366     treectrl->SetItemImage( item, p_item->p_input->i_type );
367
368     if( item.IsOk() && p_item->i_children == -1 ) UpdateTreeItem( item );
369
370 update:
371     return;
372 }
373
374 void PlaylistManager::UpdateItem( int i )
375 {
376     if( i < 0 ) return; /* Sanity check */
377
378     wxTreeItemId item = FindItem( treectrl->GetRootItem(), i );
379     if( item.IsOk() ) UpdateTreeItem( item );
380 }
381
382 void PlaylistManager::RemoveItem( int i )
383 {
384     if( i <= 0 ) return; /* Sanity check */
385
386     wxTreeItemId item = FindItem( treectrl->GetRootItem(), i );
387     if( item.IsOk() )
388     {
389         treectrl->Delete( item );
390
391         /* Invalidate cache */
392         i_cached_item_id = -1;
393     }
394 }
395
396 /* This function is called on a regular basis */
397 void PlaylistManager::Update()
398 {
399     i_update_counter++;
400
401     /* If the playlist isn't show there's no need to update it */
402     if( !IsShown() ) return;
403
404     if( this->b_need_update )
405     {
406         this->b_need_update = VLC_FALSE;
407         Rebuild( VLC_TRUE );
408     }
409
410     /* Updating the playing status every 0.5s is enough */
411     if( i_update_counter % 5 ) return;
412 }
413
414 /**********************************************************************
415  * Rebuild the playlist
416  **********************************************************************/
417 void PlaylistManager::Rebuild( vlc_bool_t b_root )
418 {
419     i_items_to_append = 0;
420     i_cached_item_id = -1;
421
422     treectrl->DeleteAllItems();
423     treectrl->AddRoot( wxU(_("root" )), -1, -1,
424                        new PlaylistItem( p_playlist->p_root_category ) );
425
426     wxTreeItemId root = treectrl->GetRootItem();
427     UpdateNode( p_playlist->p_root_category, root );
428 }
429
430 /**********************************************************************
431  * Search functions (internal)
432  **********************************************************************/
433
434 /* Find a wxItem from a playlist id */
435 wxTreeItemId PlaylistManager::FindItem( wxTreeItemId root, int i_id )
436 {
437     wxTreeItemIdValue cookie;
438     PlaylistItem *p_wxcurrent;
439     wxTreeItemId dummy, search, item, child;
440
441     if( i_id < 0 ) return dummy;
442     if( i_cached_item_id == i_id ) return cached_item;
443
444     p_wxcurrent = (PlaylistItem *)treectrl->GetItemData( root );
445     if( !p_wxcurrent ) return dummy;
446
447     if( p_wxcurrent->i_id == i_id )
448     {
449         i_cached_item_id = i_id;
450         cached_item = root;
451         return root;
452     }
453
454     item = treectrl->GetFirstChild( root, cookie );
455     while( item.IsOk() )
456     {
457         p_wxcurrent = (PlaylistItem *)treectrl->GetItemData( item );
458         if( !p_wxcurrent )
459         {
460             item = treectrl->GetNextChild( root, cookie );
461             continue;
462         }
463
464         if( p_wxcurrent->i_id == i_id )
465         {
466             i_cached_item_id = i_id;
467             cached_item = item;
468             return item;
469         }
470
471         if( treectrl->ItemHasChildren( item ) )
472         {
473             wxTreeItemId search = FindItem( item, i_id );
474             if( search.IsOk() ) return search;
475         }
476
477         item = treectrl->GetNextChild( root, cookie );
478     }
479
480     return dummy;
481 }
482
483 /********************************************************************
484  * Events
485  ********************************************************************/
486 void PlaylistManager::OnActivateItem( wxTreeEvent& event )
487 {
488     playlist_item_t *p_item, *p_node;
489     wxTreeItemId parent = treectrl->GetItemParent( event.GetItem() );
490     PlaylistItem *p_wxitem = (PlaylistItem *)
491         treectrl->GetItemData( event.GetItem() );
492
493     if( !p_wxitem || !parent.IsOk() ) return;
494
495     PlaylistItem *p_wxparent = (PlaylistItem *)treectrl->GetItemData( parent );
496     if( !p_wxparent ) return;
497
498     LockPlaylist( p_intf->p_sys, p_playlist );
499     p_item = playlist_ItemGetById( p_playlist, p_wxitem->i_id, VLC_TRUE );
500     p_node = playlist_ItemGetById( p_playlist, p_wxparent->i_id, VLC_TRUE );
501     if( !p_item || p_item->i_children >= 0 )
502     {
503         p_node = p_item;
504         p_item = NULL;
505     }
506     playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE,  p_node, p_item );
507     UnlockPlaylist( p_intf->p_sys, p_playlist );
508 }
509
510 void PlaylistManager::OnPlaylistEvent( wxCommandEvent& event )
511 {
512     switch( event.GetId() )
513     {
514     case UpdateItem_Event:
515         UpdateItem( event.GetInt() );
516         break;
517     case AppendItem_Event:
518         AppendItem( event );
519         break;
520     case RemoveItem_Event:
521         RemoveItem( event.GetInt() );
522         break;
523     }
524 }
525
526 /*****************************************************************************
527  * ItemChanged: callback triggered by the item-change playlist variable
528  *****************************************************************************/
529 static int ItemChanged( vlc_object_t *p_this, const char *psz_variable,
530                         vlc_value_t old_val, vlc_value_t new_val, void *param )
531 {
532     PlaylistManager *p_playlist = (PlaylistManager *)param;
533
534     wxCommandEvent event( wxEVT_PLAYLIST, UpdateItem_Event );
535     event.SetInt( new_val.i_int );
536     p_playlist->AddPendingEvent( event );
537
538     return VLC_SUCCESS;
539 }
540 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
541                         vlc_value_t old_val, vlc_value_t new_val, void *param )
542 {
543     PlaylistManager *p_playlist = (PlaylistManager *)param;
544
545     wxCommandEvent event( wxEVT_PLAYLIST, RemoveItem_Event );
546     event.SetInt( new_val.i_int );
547     p_playlist->AddPendingEvent( event );
548
549     return VLC_SUCCESS;
550 }
551
552 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
553                          vlc_value_t oval, vlc_value_t nval, void *param )
554 {
555     PlaylistManager *p_playlist = (PlaylistManager *)param;
556
557     playlist_add_t *p_add = (playlist_add_t *)malloc(sizeof( playlist_add_t));
558     memcpy( p_add, nval.p_address, sizeof( playlist_add_t ) );
559
560     if( ++p_playlist->i_items_to_append >= 50 )
561     {
562         /* Too many items waiting to be added, it will be quicker to rebuild
563          * the whole playlist */
564         p_playlist->b_need_update = VLC_TRUE;
565         return VLC_SUCCESS;
566     }
567
568     wxCommandEvent event( wxEVT_PLAYLIST, AppendItem_Event );
569     event.SetClientData( (void *)p_add );
570     p_playlist->AddPendingEvent( event );
571
572     return VLC_SUCCESS;
573 }
574 }