6c4a9e7727ce7f1e59851d217584538684ec19d1
[vlc.git] / modules / gui / qt4 / playlist_model.cpp
1 /*****************************************************************************
2  * input_manager.cpp : Manage an input and interact with its GUI elements
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 <QApplication>
25 #include "playlist_model.hpp"
26 #include <assert.h>
27
28
29 static int PlaylistChanged( vlc_object_t *, const char *,
30                             vlc_value_t, vlc_value_t, void * );
31 static int PlaylistNext( vlc_object_t *, const char *,
32                          vlc_value_t, vlc_value_t, void * );
33 static int ItemChanged( vlc_object_t *, const char *,
34                         vlc_value_t, vlc_value_t, void * );
35 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
36                          vlc_value_t oval, vlc_value_t nval, void *param );
37 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
38                         vlc_value_t oval, vlc_value_t nval, void *param );
39     
40
41 /*************************************************************************
42  * Playlist item implementation
43  *************************************************************************/
44
45 /**
46  * Column strings
47  *      Title
48  *      Artist
49  *      Duration
50  */
51
52 void PLItem::init( int _i_id, int _i_input_id, PLItem *parent, PLModel *m)
53 {
54     parentItem = parent;
55     i_id = _i_id; i_input_id = _i_input_id;
56     model = m;
57     strings.append( "" );    
58     strings.append( "" );    
59     strings.append( "" );    
60 }
61
62 PLItem::PLItem( int _i_id, int _i_input_id, PLItem *parent, PLModel *m)
63 {
64     init( _i_id, _i_input_id, parent, m );
65 }
66
67 PLItem::PLItem( playlist_item_t * p_item, PLItem *parent, PLModel *m )
68 {
69     init( p_item->i_id, p_item->p_input->i_id, parent, m );
70 }
71
72 PLItem::~PLItem()
73 {
74     qDeleteAll(children);
75 }
76
77 void PLItem::insertChild( PLItem *item, int i_pos, bool signal )
78 {
79     assert( model );
80     fprintf( stderr, "Inserting child \n" );
81     if( signal )
82         model->beginInsertRows( model->index( this , 0 ), i_pos, i_pos );
83     children.append( item );
84     if( signal )
85         model->endInsertRows();
86 }
87
88 int PLItem::row() const
89 {
90     if (parentItem)
91         return parentItem->children.indexOf(const_cast<PLItem*>(this));
92     return 0;
93 }
94
95 void PLItem::update( playlist_item_t *p_item )
96 {
97     assert( p_item->p_input->i_id == i_input_id );
98     strings[0] = QString::fromUtf8( p_item->p_input->psz_name );
99 }
100
101 /*************************************************************************
102  * Playlist model implementation
103  *************************************************************************/
104
105 PLModel::PLModel( playlist_t *_p_playlist,
106                   playlist_item_t * p_root, int i_depth, QObject *parent)
107                                     : QAbstractItemModel(parent)
108 {
109      rootItem = NULL;
110         rootItem = new PLItem( p_root, NULL, this );
111     fprintf( stderr, "%i -> %i, %i -> %i", p_root->i_id, rootItem->i_id, p_root->p_input->i_id, rootItem->i_input_id );
112     p_playlist= _p_playlist;
113     i_items_to_append = 0;
114     b_need_update     = false;
115     i_cached_id       = -1;
116     i_cached_input_id = -1;
117
118     addCallbacks();
119 }
120
121 PLModel::~PLModel()
122 {
123     delCallbacks();
124     delete rootItem;
125 }
126
127 void PLModel::addCallbacks()
128 {
129     /* Some global changes happened -> Rebuild all */
130     var_AddCallback( p_playlist, "intf-change", PlaylistChanged, this );
131     /* We went to the next item */
132     var_AddCallback( p_playlist, "playlist-current", PlaylistNext, this );
133     /* One item has been updated */
134     var_AddCallback( p_playlist, "item-change", ItemChanged, this );
135     var_AddCallback( p_playlist, "item-append", ItemAppended, this );
136     var_AddCallback( p_playlist, "item-deleted", ItemDeleted, this );
137 }
138
139 void PLModel::delCallbacks()
140 {
141     var_DelCallback( p_playlist, "item-change", ItemChanged, this );
142     var_DelCallback( p_playlist, "playlist-current", PlaylistNext, this );
143     var_DelCallback( p_playlist, "intf-change", PlaylistChanged, this );
144     var_DelCallback( p_playlist, "item-append", ItemAppended, this );
145     var_DelCallback( p_playlist, "item-deleted", ItemDeleted, this );
146 }
147
148 /****************** Base model mandatory implementations *****************/
149
150 QVariant PLModel::data(const QModelIndex &index, int role) const
151 {
152     if (!index.isValid())
153         return QVariant();
154     if (role != Qt::DisplayRole)
155         return QVariant();
156
157     PLItem *item = static_cast<PLItem*>(index.internalPointer());
158     return QVariant( item->columnString( index.column() ) );
159 }
160
161 Qt::ItemFlags PLModel::flags(const QModelIndex &index) const
162 {
163     if (!index.isValid())
164         return Qt::ItemIsEnabled;
165
166     return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
167 }
168
169 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
170                               int role) const
171 {
172     if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
173             return QVariant( rootItem->columnString( section ) );
174     return QVariant();
175 }
176
177 QModelIndex PLModel::index(int row, int column, const QModelIndex &parent)
178                   const
179 {
180     PLItem *parentItem;
181     if (!parent.isValid())
182         parentItem = rootItem;
183     else
184         parentItem = static_cast<PLItem*>(parent.internalPointer());
185
186     PLItem *childItem = parentItem->child(row);
187     if (childItem)
188         return createIndex(row, column, childItem);
189     else
190         return QModelIndex();
191 }
192
193 /* Return the index of a given item */
194 QModelIndex PLModel::index( PLItem *item, int column ) const 
195 {
196     if( !item ) return QModelIndex();
197     const PLItem *parent = item->parent();
198     if( parent )
199         return createIndex( parent->children.lastIndexOf( item ), column, item );
200     return QModelIndex();
201 }
202
203 QModelIndex PLModel::parent(const QModelIndex &index) const
204 {
205     if (!index.isValid())
206         return QModelIndex();
207
208     PLItem *childItem = static_cast<PLItem*>(index.internalPointer());
209     PLItem *parentItem = childItem->parent();
210
211     if (parentItem == rootItem)
212         return QModelIndex();
213
214     return createIndex(parentItem->row(), 0, parentItem);
215 }
216
217 int PLModel::columnCount( const QModelIndex &i) const 
218 {
219     return 1;
220 }
221
222 int PLModel::childrenCount(const QModelIndex &parent) const
223 {
224     return rowCount( parent );
225 }
226
227 int PLModel::rowCount(const QModelIndex &parent) const
228 {
229     PLItem *parentItem;
230
231     if (!parent.isValid())
232         parentItem = rootItem;
233     else
234         parentItem = static_cast<PLItem*>(parent.internalPointer());
235
236     return parentItem->childCount();
237 }
238
239 /************************* Lookups *****************************/
240
241 PLItem *PLModel::FindById( PLItem *root, int i_id )
242 {
243     return FindInner( root, i_id, false );
244 }
245
246 PLItem *PLModel::FindByInput( PLItem *root, int i_id )
247 {
248     return FindInner( root, i_id, true );
249 }
250
251 #define CACHE( i, p ) i_cached_id = i; p_cached_item = p;
252 #define ICACHE( i, p ) i_cached_input_id = i; p_cached_item_bi = p;
253
254 PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
255 {
256     if( ( !b_input && i_cached_id == i_id) || 
257         ( b_input && i_cached_input_id ==i_id ) )
258     {
259         return b_input ? p_cached_item_bi : p_cached_item;
260     }
261
262     if( !b_input && root->i_id == i_id )
263     {
264         CACHE( i_id, root );
265         return root;
266     }
267     else if( b_input && root->i_input_id == i_id )
268     {
269         ICACHE( i_id, root );
270         return root;
271     }
272
273     QList<PLItem *>::iterator it = root->children.begin();
274     while ( it != root->children.end() )
275     {
276         if( !b_input && (*it)->i_id == i_id )
277         {
278             CACHE( i_id, (*it) );
279             return p_cached_item;
280         }
281         else if( b_input && (*it)->i_input_id == i_id )
282         {
283             ICACHE( i_id, (*it) );
284             return p_cached_item_bi;
285         }
286         if( (*it)->children.size() )
287         {
288             PLItem *childFound = FindInner( (*it), i_id, b_input );
289             if( childFound ) 
290             {
291                 if( b_input )
292                 {
293                     ICACHE( i_id, childFound );
294                 }
295                 else
296                 {
297                     CACHE( i_id, childFound );
298                 }
299                 return childFound;
300             } 
301         }
302         it++;
303     }
304     fprintf( stderr, "Never found" );
305     return NULL;
306 }
307 #undef CACHE
308 #undef ICACHE
309
310
311 /************************* Updates handling *****************************/
312 void PLModel::customEvent( QEvent *event )
313 {
314     int type = event->type();
315     if( type != ItemUpdate_Type && type != ItemAppend_Type &&
316         type != ItemDelete_Type )
317         return;
318
319     PLEvent *ple = static_cast<PLEvent *>(event);
320
321     if( type == ItemUpdate_Type )
322         ProcessInputItemUpdate( ple->i_id );
323     else if( type == ItemAppend_Type )
324         ProcessItemAppend( ple->p_add );
325     else
326         ProcessItemRemoval( ple->i_id );
327 }
328
329 /**** Events processing ****/
330 void PLModel::ProcessInputItemUpdate( int i_input_id )
331 {
332     if( i_input_id <= 0 ) return;
333     PLItem *item = FindByInput( rootItem, i_input_id );
334     fprintf( stderr, "Updating %i -> %p \n", i_input_id, item );
335     UpdateTreeItem( item, true );
336 }
337
338 void PLModel::ProcessItemRemoval( int i_id )
339 {
340     if( i_id <= 0 ) return;
341     if( i_id == i_cached_id ) i_cached_id = -1;
342     i_cached_input_id = -1;
343
344     /// \todo
345 }
346
347 void PLModel::ProcessItemAppend( playlist_add_t *p_add )
348 {
349     playlist_item_t *p_item = NULL;
350     PLItem *newItem = NULL;
351     i_items_to_append--;
352     if( b_need_update ) return;
353
354     PLItem *nodeItem = FindById( rootItem, p_add->i_node );
355     if( !nodeItem ) goto end;
356
357     p_item = playlist_ItemGetById( p_playlist, p_add->i_item );
358     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
359
360     newItem = new PLItem( p_item, nodeItem, this );
361     nodeItem->appendChild( newItem );
362     UpdateTreeItem( p_item, newItem, true );
363
364 end:
365     return;
366 }
367
368 void PLModel::Rebuild()
369 {
370     /* Remove callbacks before locking to avoid deadlocks */
371     delCallbacks();
372     PL_LOCK;
373
374     /* Invalidate cache */
375     i_cached_id = i_cached_input_id = -1;
376
377     /* Clear the tree */
378     qDeleteAll( rootItem->children );
379
380     /* Recreate from root */
381     UpdateNodeChildren( rootItem );
382
383     /* And signal the view */
384     emit layoutChanged();
385
386     addCallbacks();
387     PL_UNLOCK;
388 }
389
390 void PLModel::UpdateNodeChildren( PLItem *root )
391 {
392     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
393     UpdateNodeChildren( p_node, root );
394 }
395
396 void PLModel::UpdateNodeChildren( playlist_item_t *p_node, PLItem *root )
397 {
398     for( int i = 0; i < p_node->i_children ; i++ )
399     {
400         PLItem *newItem =  new PLItem( p_node->pp_children[i], root, this );
401         fprintf( stderr, "New %p\n", newItem );
402         root->appendChild( newItem, false );
403         UpdateTreeItem( newItem, false );
404         if( p_node->pp_children[i]->i_children != -1 )
405             UpdateNodeChildren( p_node->pp_children[i], newItem );
406     }
407 }
408
409 void PLModel::UpdateTreeItem( PLItem *item, bool signal )
410 {
411     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
412     UpdateTreeItem( p_item, item, signal );
413 }
414
415 void PLModel::UpdateTreeItem( playlist_item_t *p_item, PLItem *item, bool signal ) 
416 {
417     /// \todo
418     fprintf( stderr, "Updating item %s\n", p_item->p_input->psz_name );
419     item->update( p_item );
420     if( signal )
421     {    // emit 
422     }
423 }
424         
425 /**********************************************************************
426  * Playlist callbacks 
427  **********************************************************************/
428 static int PlaylistChanged( vlc_object_t *p_this, const char *psz_variable,
429                             vlc_value_t oval, vlc_value_t nval, void *param )
430 {
431     PLModel *p_model = (PLModel *) param;
432     p_model->b_need_update = VLC_TRUE;
433     return VLC_SUCCESS;
434 }
435
436 static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
437                          vlc_value_t oval, vlc_value_t nval, void *param )
438 {
439     PLModel *p_model = (PLModel *) param;
440     PLEvent *event = new PLEvent( ItemUpdate_Type, oval.i_int );
441     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
442     event = new PLEvent( ItemUpdate_Type, nval.i_int );
443     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
444     return VLC_SUCCESS;
445 }
446
447 static int ItemChanged( vlc_object_t *p_this, const char *psz_variable,
448                         vlc_value_t oval, vlc_value_t nval, void *param )
449 {
450     PLModel *p_model = (PLModel *) param;
451     PLEvent *event = new PLEvent( ItemUpdate_Type, nval.i_int ); 
452     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
453     return VLC_SUCCESS;
454 }
455
456 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
457                         vlc_value_t oval, vlc_value_t nval, void *param )
458 {
459     PLModel *p_model = (PLModel *) param;
460     PLEvent *event = new PLEvent( ItemDelete_Type, nval.i_int );
461     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
462     return VLC_SUCCESS;
463 }
464
465 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
466                          vlc_value_t oval, vlc_value_t nval, void *param )
467 {
468     PLModel *p_model = (PLModel *) param;
469     playlist_add_t *p_add = (playlist_add_t *)malloc( sizeof( playlist_add_t));
470     memcpy( p_add, nval.p_address, sizeof( playlist_add_t ) );
471
472     if( ++p_model->i_items_to_append >= 50 )
473     {
474         p_model->b_need_update = VLC_TRUE;
475         return VLC_SUCCESS;
476     }
477     PLEvent *event = new PLEvent(  p_add );
478     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
479     return VLC_SUCCESS;
480 }