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