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