]> git.sesse.net Git - vlc/blob - modules/gui/qt4/playlist_model.cpp
Very very preliminary Qt implementation of album art
[vlc] / modules / gui / qt4 / playlist_model.cpp
1 /*****************************************************************************
2  * playlist_model.cpp : Manage playlist model
3  ****************************************************************************
4  * Copyright (C) 2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #include <assert.h>
25 #include <QIcon>
26 #include <QFont>
27 #include <QMenu>
28 #include <QApplication>
29
30 #include "qt4.hpp"
31 #include "playlist_model.hpp"
32 #include <vlc_intf_strings.h>
33
34 #include "pixmaps/type_unknown.xpm"
35 #include "pixmaps/type_afile.xpm"
36 #include "pixmaps/type_vfile.xpm"
37 #include "pixmaps/type_net.xpm"
38 #include "pixmaps/type_card.xpm"
39 #include "pixmaps/type_disc.xpm"
40 #include "pixmaps/type_cdda.xpm"
41 #include "pixmaps/type_directory.xpm"
42 #include "pixmaps/type_playlist.xpm"
43 #include "pixmaps/type_node.xpm"
44
45 QIcon PLModel::icons[ITEM_TYPE_NUMBER];
46
47 static int PlaylistChanged( vlc_object_t *, const char *,
48                             vlc_value_t, vlc_value_t, void * );
49 static int PlaylistNext( vlc_object_t *, const char *,
50                          vlc_value_t, vlc_value_t, void * );
51 static int ItemChanged( vlc_object_t *, const char *,
52                         vlc_value_t, vlc_value_t, void * );
53 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
54                          vlc_value_t oval, vlc_value_t nval, void *param );
55 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
56                         vlc_value_t oval, vlc_value_t nval, void *param );
57
58 /*************************************************************************
59  * Playlist item implementation
60  *************************************************************************/
61
62 /**
63  * Column strings
64  *      Title
65  *      Artist
66  *      Duration
67  */
68
69 void PLItem::init( int _i_id, int _i_input_id, PLItem *parent, PLModel *m)
70 {
71     parentItem = parent;
72     i_id = _i_id; i_input_id = _i_input_id;
73     model = m;
74     strings.append( "" );
75     strings.append( "" );
76     strings.append( "" );
77     strings.append( "" );
78 }
79
80 PLItem::PLItem( int _i_id, int _i_input_id, PLItem *parent, PLModel *m)
81 {
82     init( _i_id, _i_input_id, parent, m );
83 }
84
85 PLItem::PLItem( playlist_item_t * p_item, PLItem *parent, PLModel *m )
86 {
87     init( p_item->i_id, p_item->p_input->i_id, parent, m );
88 }
89
90 PLItem::~PLItem()
91 {
92     qDeleteAll(children);
93     children.clear();
94 }
95
96 void PLItem::insertChild( PLItem *item, int i_pos, bool signal )
97 {
98     assert( model );
99     if( signal )
100         model->beginInsertRows( model->index( this , 0 ), i_pos, i_pos );
101     children.append( item );
102     if( signal )
103         model->endInsertRows();
104 }
105
106 void PLItem::remove( PLItem *removed )
107 {
108     assert( model && parentItem );
109     int i_index = parentItem->children.indexOf( removed );
110     model->beginRemoveRows( model->index( parentItem, 0 ), i_index, i_index );
111     parentItem->children.removeAt( i_index );
112     model->endRemoveRows();
113 }
114
115 int PLItem::row() const
116 {
117     if( parentItem )
118         return parentItem->children.indexOf(const_cast<PLItem*>(this));
119     return 0;
120 }
121
122 void PLItem::update( playlist_item_t *p_item, bool iscurrent )
123 {
124     char psz_duration[MSTRTIME_MAX_SIZE];
125     assert( p_item->p_input->i_id == i_input_id );
126     strings[0] = QString::fromUtf8( p_item->p_input->psz_name );
127     if( p_item->p_input->p_meta )
128     {
129         strings[1] = QString::fromUtf8( p_item->p_input->p_meta->psz_artist );
130     }
131     secstotimestr( psz_duration, p_item->p_input->i_duration / 1000000 );
132     strings[2] = QString( psz_duration );
133     type = p_item->p_input->i_type;
134     current = iscurrent;
135     fprintf( stderr, "Updating current %i\n" );
136     fprintf( stderr, "Meta %p art %s\n", p_item->p_input->p_meta,p_item->p_input->p_meta ?p_item->p_input->p_meta->psz_arturl : "non" );
137     if( current && p_item->p_input->p_meta &&
138         p_item->p_input->p_meta->psz_arturl &&
139         !strncmp( p_item->p_input->p_meta->psz_arturl, "file://", 7 ) )
140     {
141         fprintf( stderr, "Have art %s\n", p_item->p_input->p_meta->psz_arturl );
142         model->sendArt( qfu( p_item->p_input->p_meta->psz_arturl ) );
143     }
144 }
145
146 /*************************************************************************
147  * Playlist model implementation
148  *************************************************************************/
149
150 PLModel::PLModel( playlist_t *_p_playlist,
151                   playlist_item_t * p_root, int _i_depth, QObject *parent)
152                                     : QAbstractItemModel(parent)
153 {
154     i_depth = _i_depth;
155     assert( i_depth == 1 || i_depth == -1 );
156     p_playlist= _p_playlist;
157     i_items_to_append = 0;
158     b_need_update     = false;
159     i_cached_id       = -1;
160     i_cached_input_id = -1;
161     i_popup_item = i_popup_parent = -1;
162
163 #define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( QPixmap( type_##x##_xpm ) );
164     ADD_ICON( UNKNOWN , unknown );
165     ADD_ICON( AFILE,afile );
166     ADD_ICON( VFILE, vfile );
167     ADD_ICON( DIRECTORY, directory );
168     ADD_ICON( DISC, disc );
169     ADD_ICON( CDDA, cdda );
170     ADD_ICON( CARD, card );
171     ADD_ICON( NET, net );
172     ADD_ICON( PLAYLIST, playlist );
173     ADD_ICON( NODE, node );
174
175     rootItem = NULL;
176     addCallbacks();
177     rebuild( p_root );
178 }
179
180
181 PLModel::~PLModel()
182 {
183     delCallbacks();
184     delete rootItem;
185 }
186
187 void PLModel::addCallbacks()
188 {
189     /* Some global changes happened -> Rebuild all */
190     var_AddCallback( p_playlist, "intf-change", PlaylistChanged, this );
191     /* We went to the next item */
192     var_AddCallback( p_playlist, "playlist-current", PlaylistNext, this );
193     /* One item has been updated */
194     var_AddCallback( p_playlist, "item-change", ItemChanged, this );
195     var_AddCallback( p_playlist, "item-append", ItemAppended, this );
196     var_AddCallback( p_playlist, "item-deleted", ItemDeleted, this );
197 }
198
199 void PLModel::delCallbacks()
200 {
201     var_DelCallback( p_playlist, "item-change", ItemChanged, this );
202     var_DelCallback( p_playlist, "playlist-current", PlaylistNext, this );
203     var_DelCallback( p_playlist, "intf-change", PlaylistChanged, this );
204     var_DelCallback( p_playlist, "item-append", ItemAppended, this );
205     var_DelCallback( p_playlist, "item-deleted", ItemDeleted, this );
206 }
207
208 void PLModel::activateItem( const QModelIndex &index )
209 {
210     assert( index.isValid() );
211     PLItem *item = static_cast<PLItem*>(index.internalPointer());
212     assert( item );
213     PL_LOCK;
214     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
215     activateItem( p_item );
216     PL_UNLOCK;
217 }
218 /* Must be entered with lock */
219 void PLModel::activateItem( playlist_item_t *p_item )
220 {
221     if( !p_item ) return;
222     playlist_item_t *p_parent = p_item;
223     while( p_parent )
224     {
225         if( p_parent->i_id == rootItem->i_id ) break;
226         p_parent = p_parent->p_parent;
227     }
228     if( p_parent )
229         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, p_parent, p_item );
230 }
231
232 /****************** Base model mandatory implementations *****************/
233 QVariant PLModel::data(const QModelIndex &index, int role) const
234 {
235     assert( index.isValid() );
236     PLItem *item = static_cast<PLItem*>(index.internalPointer());
237     if( role == Qt::DisplayRole )
238     {
239         return QVariant( item->columnString( index.column() ) );
240     }
241     else if( role == Qt::DecorationRole && index.column() == 0  )
242     {
243         if( item->type >= 0 )
244             return QVariant( PLModel::icons[item->type] );
245     }
246     else if( role == Qt::FontRole )
247     {
248         if( item->current == true )
249         {
250             QFont f; f.setBold( true ); return QVariant( f );
251         }
252     }
253     return QVariant();
254 }
255
256 bool PLModel::isCurrent( const QModelIndex &index )
257 {
258     assert( index.isValid() );
259     return static_cast<PLItem*>(index.internalPointer())->current;
260 }
261
262 int PLModel::itemId( const QModelIndex &index ) const
263 {
264     assert( index.isValid() );
265     return static_cast<PLItem*>(index.internalPointer())->i_id;
266 }
267
268 Qt::ItemFlags PLModel::flags(const QModelIndex &index) const
269 {
270     if( !index.isValid() ) return Qt::ItemIsEnabled;
271     return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
272 }
273
274 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
275                               int role) const
276 {
277     if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
278             return QVariant( rootItem->columnString( section ) );
279     return QVariant();
280 }
281
282 QModelIndex PLModel::index(int row, int column, const QModelIndex &parent)
283                   const
284 {
285     PLItem *parentItem;
286     if (!parent.isValid())
287         parentItem = rootItem;
288     else
289         parentItem = static_cast<PLItem*>(parent.internalPointer());
290
291     PLItem *childItem = parentItem->child(row);
292     if (childItem)
293         return createIndex(row, column, childItem);
294     else
295         return QModelIndex();
296 }
297
298 /* Return the index of a given item */
299 QModelIndex PLModel::index( PLItem *item, int column ) const
300 {
301     if( !item ) return QModelIndex();
302     const PLItem *parent = item->parent();
303     if( parent )
304         return createIndex( parent->children.lastIndexOf( item ),
305                             column, item );
306     return QModelIndex();
307 }
308
309 QModelIndex PLModel::parent(const QModelIndex &index) const
310 {
311     if( !index.isValid() ) return QModelIndex();
312
313     PLItem *childItem = static_cast<PLItem*>(index.internalPointer());
314     if( !childItem ) { msg_Err( p_playlist, "NULL CHILD \n" ); return QModelIndex(); }
315     PLItem *parentItem = childItem->parent();
316     if( !parentItem || parentItem == rootItem ) return QModelIndex();
317     if( ! parentItem->parentItem )
318     {
319         msg_Err( p_playlist, "No parent parent, trying row 0 " );
320         msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
321         return createIndex( 0, 0, parentItem );
322     }
323     QModelIndex ind = createIndex(parentItem->row(), 0, parentItem);
324     return ind;
325 }
326
327 int PLModel::columnCount( const QModelIndex &i) const
328 {
329     if( i_depth == 1 ) return 1;
330     return 3;
331 }
332
333 int PLModel::childrenCount(const QModelIndex &parent) const
334 {
335     return rowCount( parent );
336 }
337
338 int PLModel::rowCount(const QModelIndex &parent) const
339 {
340     PLItem *parentItem;
341
342     if (!parent.isValid())
343         parentItem = rootItem;
344     else
345         parentItem = static_cast<PLItem*>(parent.internalPointer());
346
347     return parentItem->childCount();
348 }
349
350 /************************* General playlist status ***********************/
351
352 bool PLModel::hasRandom()
353 {
354     if( var_GetBool( p_playlist, "random" ) ) return true;
355     return false;
356 }
357 bool PLModel::hasRepeat()
358 {
359     if( var_GetBool( p_playlist, "repeat" ) ) return true;
360     return false;
361 }
362 bool PLModel::hasLoop()
363 {
364     if( var_GetBool( p_playlist, "loop" ) ) return true;
365     return false;
366 }
367 void PLModel::setLoop( bool on )
368 {
369     var_SetBool( p_playlist, "loop", on ? VLC_TRUE:VLC_FALSE );
370     config_PutInt( p_playlist, "loop", on ? 1: 0 );
371 }
372 void PLModel::setRepeat( bool on )
373 {
374     var_SetBool( p_playlist, "repeat", on ? VLC_TRUE:VLC_FALSE );
375     config_PutInt( p_playlist, "repeat", on ? 1: 0 );
376 }
377 void PLModel::setRandom( bool on )
378 {
379     var_SetBool( p_playlist, "random", on ? VLC_TRUE:VLC_FALSE );
380     config_PutInt( p_playlist, "random", on ? 1: 0 );
381 }
382
383 /************************* Lookups *****************************/
384
385 PLItem *PLModel::FindById( PLItem *root, int i_id )
386 {
387     return FindInner( root, i_id, false );
388 }
389
390 PLItem *PLModel::FindByInput( PLItem *root, int i_id )
391 {
392     return FindInner( root, i_id, true );
393 }
394
395 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
396 #define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
397
398 PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
399 {
400     if( ( !b_input && i_cached_id == i_id) ||
401         ( b_input && i_cached_input_id ==i_id ) )
402     {
403         return b_input ? p_cached_item_bi : p_cached_item;
404     }
405
406     if( !b_input && root->i_id == i_id )
407     {
408         CACHE( i_id, root );
409         return root;
410     }
411     else if( b_input && root->i_input_id == i_id )
412     {
413         ICACHE( i_id, root );
414         return root;
415     }
416
417     QList<PLItem *>::iterator it = root->children.begin();
418     while ( it != root->children.end() )
419     {
420         if( !b_input && (*it)->i_id == i_id )
421         {
422             CACHE( i_id, (*it) );
423             return p_cached_item;
424         }
425         else if( b_input && (*it)->i_input_id == i_id )
426         {
427             ICACHE( i_id, (*it) );
428             return p_cached_item_bi;
429         }
430         if( (*it)->children.size() )
431         {
432             PLItem *childFound = FindInner( (*it), i_id, b_input );
433             if( childFound )
434             {
435                 if( b_input )
436                     ICACHE( i_id, childFound )
437                 else
438                     CACHE( i_id, childFound )
439                 return childFound;
440             }
441         }
442         it++;
443     }
444     return NULL;
445 }
446 #undef CACHE
447 #undef ICACHE
448
449
450 /************************* Updates handling *****************************/
451 void PLModel::customEvent( QEvent *event )
452 {
453     int type = event->type();
454     if( type != ItemUpdate_Type && type != ItemAppend_Type &&
455         type != ItemDelete_Type )
456         return;
457
458     PLEvent *ple = static_cast<PLEvent *>(event);
459
460     if( type == ItemUpdate_Type )
461         ProcessInputItemUpdate( ple->i_id );
462     else if( type == ItemAppend_Type )
463         ProcessItemAppend( ple->p_add );
464     else
465         ProcessItemRemoval( ple->i_id );
466 }
467
468 /**** Events processing ****/
469 void PLModel::ProcessInputItemUpdate( int i_input_id )
470 {
471     if( i_input_id <= 0 ) return;
472     PLItem *item = FindByInput( rootItem, i_input_id );
473     if( item )
474         UpdateTreeItem( item, true );
475 }
476
477 void PLModel::ProcessItemRemoval( int i_id )
478 {
479     if( i_id <= 0 ) return;
480     if( i_id == i_cached_id ) i_cached_id = -1;
481     i_cached_input_id = -1;
482     PLItem *item = FindById( rootItem, i_id );
483     if( item )
484         item->remove( item );
485 }
486
487 void PLModel::ProcessItemAppend( playlist_add_t *p_add )
488 {
489     playlist_item_t *p_item = NULL;
490     PLItem *newItem = NULL;
491     i_items_to_append--;
492     if( b_need_update ) return;
493
494     PLItem *nodeItem = FindById( rootItem, p_add->i_node );
495     PL_LOCK;
496     if( !nodeItem ) goto end;
497
498     p_item = playlist_ItemGetById( p_playlist, p_add->i_item );
499     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
500     if( i_depth == 1 && p_item->p_parent &&
501                         p_item->p_parent->i_id != rootItem->i_id )
502         goto end;
503
504     newItem = new PLItem( p_item, nodeItem, this );
505     nodeItem->appendChild( newItem );
506     UpdateTreeItem( p_item, newItem, true );
507 end:
508     PL_UNLOCK;
509     return;
510 }
511
512
513 void PLModel::rebuild()
514 {
515     rebuild( NULL );
516 }
517
518 void PLModel::rebuild( playlist_item_t *p_root )
519 {
520     /* Remove callbacks before locking to avoid deadlocks */
521     delCallbacks();
522     /* Invalidate cache */
523     i_cached_id = i_cached_input_id = -1;
524
525     PL_LOCK;
526     /* Clear the tree */
527     if( rootItem )
528     {
529         beginRemoveRows( index( rootItem, 0 ), 0,
530                          rootItem->children.size() -1 );
531         qDeleteAll( rootItem->children );
532         rootItem->children.clear();
533         endRemoveRows();
534     }
535     if( p_root )
536     {
537         //if( rootItem ) delete rootItem;
538         rootItem = new PLItem( p_root, NULL, this );
539         rootItem->strings[0] = qtr("Name");
540         rootItem->strings[1] = qtr("Artist");
541         rootItem->strings[2] = qtr("Duration");
542     }
543     assert( rootItem );
544     /* Recreate from root */
545     UpdateNodeChildren( rootItem );
546     if( p_playlist->status.p_item )
547     {
548         PLItem *currentItem = FindByInput( rootItem,
549                                      p_playlist->status.p_item->p_input->i_id );
550         if( currentItem )
551         {
552             UpdateTreeItem( p_playlist->status.p_item, currentItem,
553                             true, false );
554         }
555     }
556     PL_UNLOCK;
557
558     /* And signal the view */
559     emit layoutChanged();
560     addCallbacks();
561 }
562
563 /* This function must be entered WITH the playlist lock */
564 void PLModel::UpdateNodeChildren( PLItem *root )
565 {
566     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
567     UpdateNodeChildren( p_node, root );
568 }
569
570 /* This function must be entered WITH the playlist lock */
571 void PLModel::UpdateNodeChildren( playlist_item_t *p_node, PLItem *root )
572 {
573     for( int i = 0; i < p_node->i_children ; i++ )
574     {
575         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
576         PLItem *newItem =  new PLItem( p_node->pp_children[i], root, this );
577         root->appendChild( newItem, false );
578         UpdateTreeItem( newItem, false, true );
579         if( i_depth != 1 && p_node->pp_children[i]->i_children != -1 )
580             UpdateNodeChildren( p_node->pp_children[i], newItem );
581     }
582 }
583
584 /* This function must be entered WITH the playlist lock */
585 void PLModel::UpdateTreeItem( PLItem *item, bool signal, bool force )
586 {
587     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
588     UpdateTreeItem( p_item, item, signal, force );
589 }
590
591 /* This function must be entered WITH the playlist lock */
592 void PLModel::UpdateTreeItem( playlist_item_t *p_item, PLItem *item,
593                               bool signal, bool force )
594 {
595     if( !force && i_depth == 1 && p_item->p_parent &&
596                                  p_item->p_parent->i_id != rootItem->i_id )
597         return;
598     item->update( p_item, p_item == p_playlist->status.p_item );
599     if( signal )
600         emit dataChanged( index( item, 0 ) , index( item, 1 ) );
601 }
602
603 /************************* Actions ******************************/
604
605 void PLModel::sendArt( QString url )
606 {
607     QString arturl = url.replace( "file://",QString("" ) );
608     fprintf( stderr, "send %s\n", qta( arturl ) );
609     emit artSet( arturl );
610 }
611
612 /**
613  * Deletion, here we have to do a ugly slow hack as we retrieve the full
614  * list of indexes to delete at once: when we delete a node and all of
615  * its children, we need to update the list.
616  * Todo: investigate whethere we can use ranges to be sure to delete all items?
617  */
618 void PLModel::doDelete( QModelIndexList selected )
619 {
620     for( int i = selected.size() -1 ; i >= 0; i-- )
621     {
622         QModelIndex index = selected[i];
623         if( index.column() != 0 ) continue;
624         PLItem *item = static_cast<PLItem*>(index.internalPointer());
625         if( item )
626         {
627             if( item->children.size() )
628                 recurseDelete( item->children, &selected );
629             doDeleteItem( item, &selected );
630         }
631     }
632 }
633
634 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList)
635 {
636     for( int i = children.size() - 1; i >= 0 ; i-- )
637     {
638         PLItem *item = children[i];
639         if( item->children.size() )
640             recurseDelete( item->children, fullList );
641         doDeleteItem( item, fullList );
642     }
643 }
644
645 void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
646 {
647     QModelIndex deleteIndex = index( item, 0 );
648     fullList->removeAll( deleteIndex );
649
650     PL_LOCK;
651     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
652     if( !p_item )
653     {
654         PL_UNLOCK; return;
655     }
656     if( p_item->i_children == -1 )
657         playlist_DeleteAllFromInput( p_playlist, item->i_input_id );
658     else
659         playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
660     /* And finally, remove it from the tree */
661     item->remove( item );
662     PL_UNLOCK;
663 }
664
665 /******* Volume III: Sorting and searching ********/
666 void PLModel::sort( int column, Qt::SortOrder order )
667 {
668     PL_LOCK;
669     playlist_item_t *p_root = playlist_ItemGetById( p_playlist, rootItem->i_id );
670     int i_mode;
671     switch( column )
672     {
673     case 0: i_mode = SORT_TITLE_NODES_FIRST;break;
674     case 1: i_mode = SORT_ARTIST;break;
675     case 2: i_mode = SORT_DURATION; break;
676     }
677     if( p_root )
678         playlist_RecursiveNodeSort( p_playlist, p_root, i_mode,
679                                     order == Qt::AscendingOrder ? ORDER_NORMAL :
680                                                             ORDER_REVERSE );
681     PL_UNLOCK
682     rebuild();
683 }
684
685 void PLModel::search( QString search_text )
686 {
687     /** \todo Fire the search with a small delay ? */
688     PL_LOCK;
689     playlist_item_t *p_root = playlist_ItemGetById( p_playlist,rootItem->i_id );
690     assert( p_root );
691     char *psz_name = search_text.toUtf8().data();
692     playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
693     PL_UNLOCK;
694     rebuild();
695 }
696
697 /*********** Popup *********/
698 void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
699 {
700     assert( index.isValid() );
701     PL_LOCK;
702     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
703                                                     itemId( index ) );
704     if( p_item )
705     {
706         i_popup_item = p_item->i_id;
707         i_popup_parent = p_item->p_parent ? p_item->p_parent->i_id : -1;
708         PL_UNLOCK;
709         current_selection = list;
710         QMenu *menu = new QMenu;
711         menu->addAction( qfu(I_POP_PLAY), this, SLOT( popupPlay() ) );
712         menu->addAction( qfu(I_POP_DEL), this, SLOT( popupDel() ) );
713         menu->addSeparator();
714         menu->addAction( qfu(I_POP_STREAM), this, SLOT( popupStream() ) );
715         menu->addAction( qfu(I_POP_SAVE), this, SLOT( popupSave() ) );
716         menu->addSeparator();
717         menu->addAction( qfu(I_POP_INFO), this, SLOT( popupInfo() ) );
718         if( p_item->i_children > -1 )
719         {
720             menu->addSeparator();
721             menu->addAction( qfu(I_POP_SORT), this, SLOT( popupSort() ) );
722             menu->addAction( qfu(I_POP_ADD), this, SLOT( popupAdd() ) );
723         }
724         menu->popup( point );
725     }
726     else
727         PL_UNLOCK;
728 }
729
730 void PLModel::popupDel()
731 {
732     doDelete( current_selection );
733 }
734 void PLModel::popupPlay()
735 {
736     PL_LOCK;
737     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_popup_item );
738     activateItem( p_item );
739     PL_UNLOCK;
740 }
741
742 void PLModel::popupInfo()
743 {
744     fprintf( stderr, "Popup Info is NOT implemented\n" );
745 }
746
747 void PLModel::popupStream()
748 {
749     fprintf( stderr, "Stream not implemented\n" );
750 }
751 void PLModel::popupSave()
752 {
753     fprintf( stderr, "Save not implemented\n" );
754 }
755
756 /**********************************************************************
757  * Playlist callbacks
758  **********************************************************************/
759 static int PlaylistChanged( vlc_object_t *p_this, const char *psz_variable,
760                             vlc_value_t oval, vlc_value_t nval, void *param )
761 {
762     PLModel *p_model = (PLModel *) param;
763 //    p_model->b_need_update = VLC_TRUE;
764     return VLC_SUCCESS;
765 }
766
767 static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
768                          vlc_value_t oval, vlc_value_t nval, void *param )
769 {
770     PLModel *p_model = (PLModel *) param;
771     PLEvent *event = new PLEvent( ItemUpdate_Type, oval.i_int );
772     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
773     event = new PLEvent( ItemUpdate_Type, nval.i_int );
774     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
775     return VLC_SUCCESS;
776 }
777
778 static int ItemChanged( vlc_object_t *p_this, const char *psz_variable,
779                         vlc_value_t oval, vlc_value_t nval, void *param )
780 {
781     PLModel *p_model = (PLModel *) param;
782     PLEvent *event = new PLEvent( ItemUpdate_Type, nval.i_int );
783     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
784     return VLC_SUCCESS;
785 }
786
787 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
788                         vlc_value_t oval, vlc_value_t nval, void *param )
789 {
790     PLModel *p_model = (PLModel *) param;
791     PLEvent *event = new PLEvent( ItemDelete_Type, nval.i_int );
792     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
793     return VLC_SUCCESS;
794 }
795
796 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
797                          vlc_value_t oval, vlc_value_t nval, void *param )
798 {
799     PLModel *p_model = (PLModel *) param;
800     playlist_add_t *p_add = (playlist_add_t *)malloc( sizeof( playlist_add_t));
801     memcpy( p_add, nval.p_address, sizeof( playlist_add_t ) );
802
803     if( ++p_model->i_items_to_append >= 50 )
804     {
805 //        p_model->b_need_update = VLC_TRUE;
806 //        return VLC_SUCCESS;
807     }
808     PLEvent *event = new PLEvent(  p_add );
809     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
810     return VLC_SUCCESS;
811 }