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