playlist_model.cpp 34.5 KB
Newer Older
zorglub's avatar
zorglub committed
1
/*****************************************************************************
zorglub's avatar
zorglub committed
2
 * playlist_model.cpp : Manage playlist model
zorglub's avatar
zorglub committed
3
 ****************************************************************************
4
 * Copyright (C) 2006-2007 the VideoLAN team
5
 * $Id$
zorglub's avatar
zorglub committed
6
7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Ilkka Ollakkka <ileoo (at) videolan dot org>
zorglub's avatar
zorglub committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
24

25
26
27
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
zorglub's avatar
zorglub committed
28

zorglub's avatar
zorglub committed
29
#include "qt4.hpp"
30
#include "dialogs_provider.hpp"
31
#include "components/playlist/playlist_model.hpp"
zorglub's avatar
zorglub committed
32
#include "dialogs/mediainfo.hpp"
33
#include "dialogs/playlist.hpp"
34
#include <vlc_intf_strings.h>
zorglub's avatar
zorglub committed
35

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
36
#include "pixmaps/types/type_unknown.xpm"
zorglub's avatar
zorglub committed
37

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
38
39
40
41
42
43
44
#include <assert.h>
#include <QIcon>
#include <QFont>
#include <QMenu>
#include <QApplication>
#include <QSettings>

Rafaël Carré's avatar
Rafaël Carré committed
45
46
#include "sorting.h"

zorglub's avatar
zorglub committed
47
48
QIcon PLModel::icons[ITEM_TYPE_NUMBER];

zorglub's avatar
zorglub committed
49
50
51
52
53
54
55
56
57
58
59
60
61
static int PlaylistChanged( vlc_object_t *, const char *,
                            vlc_value_t, vlc_value_t, void * );
static int PlaylistNext( vlc_object_t *, const char *,
                         vlc_value_t, vlc_value_t, void * );
static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
                         vlc_value_t oval, vlc_value_t nval, void *param );
static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
                        vlc_value_t oval, vlc_value_t nval, void *param );

/*************************************************************************
 * Playlist model implementation
 *************************************************************************/

62
63
64
65
66
67
68
69
70
71
72
/*
  This model is called two times, for the selector and the standard panel
*/
PLModel::PLModel( playlist_t *_p_playlist,  /* THEPL */
                  intf_thread_t *_p_intf,   /* main Qt p_intf */
                  playlist_item_t * p_root,
                  /*playlist_GetPreferredNode( THEPL, THEPL->p_local_category );
                    and THEPL->p_root_category for SelectPL */
                  int _i_depth,             /* -1 for StandPL, 1 for SelectPL */
                  QObject *parent )         /* Basic Qt parent */
                  : QAbstractItemModel( parent )
zorglub's avatar
zorglub committed
73
{
zorglub's avatar
zorglub committed
74
    i_depth = _i_depth;
75
    assert( i_depth == DEPTH_SEL || i_depth == DEPTH_PL );
76
77
    p_intf            = _p_intf;
    p_playlist        = _p_playlist;
zorglub's avatar
zorglub committed
78
79
    i_cached_id       = -1;
    i_cached_input_id = -1;
80
    i_popup_item      = i_popup_parent = -1;
81
    currentItem       = NULL;
82
83

    rootItem          = NULL; /* PLItem rootItem, will be set in rebuild( ) */
zorglub's avatar
zorglub committed
84

85
86
87
88
89
90
91
92
93
94
95
    if( i_depth == DEPTH_SEL )
        i_showflags = 0;
    else
    {
        i_showflags = getSettings()->value( "qt-pl-showflags", COLUMN_DEFAULT ).toInt();
        if( i_showflags < 1)
            i_showflags = COLUMN_DEFAULT; /* reasonable default to show something */
        else if ( i_showflags >= COLUMN_END )
            i_showflags = COLUMN_END - 1; /* show everything */
    }

96
    /* Icons initialization */
97
#define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( QPixmap( x ) )
98
    ADD_ICON( UNKNOWN , type_unknown_xpm );
99
100
101
102
103
104
105
106
    ADD_ICON( FILE, ":/type/file" );
    ADD_ICON( DIRECTORY, ":/type/directory" );
    ADD_ICON( DISC, ":/type/disc" );
    ADD_ICON( CDDA, ":/type/cdda" );
    ADD_ICON( CARD, ":/type/capture-card" );
    ADD_ICON( NET, ":/type/net" );
    ADD_ICON( PLAYLIST, ":/type/playlist" );
    ADD_ICON( NODE, ":/type/node" );
107
#undef ADD_ICON
zorglub's avatar
zorglub committed
108

zorglub's avatar
zorglub committed
109
    rebuild( p_root );
110
111
    CONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
            this, ProcessInputItemUpdate( input_item_t *) );
112
113
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
            this, ProcessInputItemUpdate( input_thread_t* ) );
114
115
116
117
118
119
120
121
122
123
124
125
    PL_LOCK;
    playlist_item_t *p_item;
    /* Check if there's allready some item playing when playlist
     * model is created, if so, tell model that it's currentone
     */
    if( (p_item = playlist_CurrentPlayingItem(p_playlist)) )
    {
        currentItem = FindByInput( rootItem,
                                           p_item->p_input->i_id );
        emit currentChanged( index( currentItem, 0 ) );
    }
    PL_UNLOCK;
zorglub's avatar
zorglub committed
126
127
128
129
}

PLModel::~PLModel()
{
130
    if(i_depth == -1)
131
        getSettings()->setValue( "qt-pl-showflags", i_showflags );
zorglub's avatar
zorglub committed
132
133
134
135
    delCallbacks();
    delete rootItem;
}

zorglub's avatar
zorglub committed
136
137
Qt::DropActions PLModel::supportedDropActions() const
{
138
    return Qt::CopyAction; /* Why not Qt::MoveAction */
zorglub's avatar
zorglub committed
139
140
}

141
Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
zorglub's avatar
zorglub committed
142
{
143
144
145
146
147
148
    Qt::ItemFlags flags = QAbstractItemModel::flags( index );

    PLItem *item = index.isValid() ?
        static_cast<PLItem*>( index.internalPointer() ) :
        rootItem;

149
150
    input_item_t *pl_input = p_playlist->p_local_category->p_input;
    input_item_t *ml_input = p_playlist->p_ml_category->p_input;
151
152
153

    if( rootItem->i_id == p_playlist->p_root_onelevel->i_id
          || rootItem->i_id == p_playlist->p_root_category->i_id )
154
    {
155
156
        if( item->p_input == pl_input
            || item->p_input == ml_input)
157
                flags |= Qt::ItemIsDropEnabled;
158
    }
159
160
    else if( rootItem->p_input == pl_input ||
            rootItem->p_input == ml_input )
161
    {
162
163
164
        PL_LOCK;
        playlist_item_t *plItem =
            playlist_ItemGetById( p_playlist, item->i_id );
165
166
167
168

        if ( plItem && ( plItem->i_children > -1 ) )
            flags |= Qt::ItemIsDropEnabled;

169
        PL_UNLOCK;
170

171
172
173
174
        flags |= Qt::ItemIsDragEnabled;
    }

    return flags;
zorglub's avatar
zorglub committed
175
176
}

177
/* A list of model indexes are a playlist */
zorglub's avatar
zorglub committed
178
179
180
181
182
183
184
QStringList PLModel::mimeTypes() const
{
    QStringList types;
    types << "vlc/playlist-item-id";
    return types;
}

185
QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
zorglub's avatar
zorglub committed
186
187
188
{
    QMimeData *mimeData = new QMimeData();
    QByteArray encodedData;
189
    QDataStream stream( &encodedData, QIODevice::WriteOnly );
190
    QModelIndexList list;
zorglub's avatar
zorglub committed
191

192
    foreach( const QModelIndex &index, indexes ) {
193
        if( index.isValid() && index.column() == 0 )
194
195
196
197
198
199
200
            list.append(index);
    }

    qSort(list);

    foreach( const QModelIndex &index, list ) {
        stream << itemId( index );
zorglub's avatar
zorglub committed
201
    }
202
    mimeData->setData( "vlc/playlist-item-id", encodedData );
zorglub's avatar
zorglub committed
203
204
205
    return mimeData;
}

206
/* Drop operation */
207
bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
208
                           int row, int column, const QModelIndex &parent )
zorglub's avatar
zorglub committed
209
{
210
    if( data->hasFormat( "vlc/playlist-item-id" ) )
zorglub's avatar
zorglub committed
211
    {
212
        if( action == Qt::IgnoreAction )
zorglub's avatar
zorglub committed
213
214
            return true;

215
        PL_LOCK;
216

217
218
219
220
221
222
223
224
225
226
227
228
229
230
        playlist_item_t *p_parent;

        if( !parent.isValid())
        {
            if( row > -1)
                p_parent = playlist_ItemGetById( p_playlist, rootItem->i_id );
            else
            {
                PL_UNLOCK;
                return true;
            }
        }
        else
            p_parent = playlist_ItemGetById( p_playlist, itemId ( parent ) );
231

232
233
234
235
236
        if( !p_parent || p_parent->i_children == -1 )
        {
            PL_UNLOCK;
            return false;
        }
zorglub's avatar
zorglub committed
237

238
239
240
241
242
243
        bool copy = false;
        if( row == -1 &&
            ( p_parent->p_input == p_playlist->p_local_category->p_input
            || p_parent->p_input == p_playlist->p_ml_category->p_input ) )
                copy = true;

244
245
        QByteArray encodedData = data->data( "vlc/playlist-item-id" );
        QDataStream stream( &encodedData, QIODevice::ReadOnly );
zorglub's avatar
zorglub committed
246

247
        if( copy )
zorglub's avatar
zorglub committed
248
        {
249
            while( !stream.atEnd() )
zorglub's avatar
zorglub committed
250
            {
251
252
253
254
255
256
257
258
259
                int i_id;
                stream >> i_id;
                playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
                if( !p_item )
                {
                    PL_UNLOCK;
                    return false;
                }
                input_item_t *p_input = p_item->p_input;
260
                playlist_AddExt ( p_playlist,
261
                    p_input->psz_uri, p_input->psz_name,
262
                    PLAYLIST_APPEND | PLAYLIST_SPREPARSE, PLAYLIST_END,
263
264
                    p_input->i_duration,
                    p_input->i_options, p_input->ppsz_options, p_input->optflagc,
265
266
                    p_parent == p_playlist->p_local_category, true );
            }
267
268
269
270
271
        }
        else
        {
            QList<int> ids;
            while( !stream.atEnd() )
zorglub's avatar
zorglub committed
272
            {
273
274
275
                int id;
                stream >> id;
                ids.append(id);
zorglub's avatar
zorglub committed
276
            }
277
278
279
            int count = ids.size();
            playlist_item_t *items[count];
            for( int i = 0; i < count; i++ )
zorglub's avatar
zorglub committed
280
            {
281
282
283
284
285
286
287
                playlist_item_t *item = playlist_ItemGetById( p_playlist, ids[i] );
                if( !item )
                {
                    PL_UNLOCK;
                    return false;
                }
                items[i] = item;
zorglub's avatar
zorglub committed
288
            }
289
290
            playlist_TreeMoveMany( p_playlist, count, items, p_parent,
                (row == -1 ? p_parent->i_children : row) );
zorglub's avatar
zorglub committed
291
        }
292

293
        PL_UNLOCK;
ivoire's avatar
ivoire committed
294
295
        /*TODO: That's not a good idea to rebuild the playlist */
        rebuild();
zorglub's avatar
zorglub committed
296
297
    }
    return true;
zorglub's avatar
zorglub committed
298
}
zorglub's avatar
zorglub committed
299

300
/* remove item with its id */
zorglub's avatar
zorglub committed
301
302
void PLModel::removeItem( int i_id )
{
303
    PLItem *item = FindById( rootItem, i_id );
304
    if( currentItem && item && currentItem->p_input == item->p_input ) currentItem = NULL;
305
    if( item ) item->remove( item, i_depth );
zorglub's avatar
zorglub committed
306
}
zorglub's avatar
zorglub committed
307

308
/* callbacks and slots */
zorglub's avatar
zorglub committed
309
310
void PLModel::addCallbacks()
{
zorglub's avatar
zorglub committed
311
312
    /* Some global changes happened -> Rebuild all */
    var_AddCallback( p_playlist, "intf-change", PlaylistChanged, this );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
313
    /* We went to the next item
314
    var_AddCallback( p_playlist, "item-current", PlaylistNext, this );
315
    */
zorglub's avatar
zorglub committed
316
    /* One item has been updated */
317
318
    var_AddCallback( p_playlist, "playlist-item-append", ItemAppended, this );
    var_AddCallback( p_playlist, "playlist-item-deleted", ItemDeleted, this );
zorglub's avatar
zorglub committed
319
320
}

zorglub's avatar
zorglub committed
321
void PLModel::delCallbacks()
zorglub's avatar
zorglub committed
322
{
323
    /*
324
    var_DelCallback( p_playlist, "item-current", PlaylistNext, this );
325
    */
zorglub's avatar
zorglub committed
326
    var_DelCallback( p_playlist, "intf-change", PlaylistChanged, this );
327
328
    var_DelCallback( p_playlist, "playlist-item-append", ItemAppended, this );
    var_DelCallback( p_playlist, "playlist-item-deleted", ItemDeleted, this );
zorglub's avatar
zorglub committed
329
330
}

zorglub's avatar
zorglub committed
331
332
333
334
335
336
void PLModel::activateItem( const QModelIndex &index )
{
    assert( index.isValid() );
    PLItem *item = static_cast<PLItem*>(index.internalPointer());
    assert( item );
    PL_LOCK;
337
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
zorglub's avatar
zorglub committed
338
339
340
    activateItem( p_item );
    PL_UNLOCK;
}
341

zorglub's avatar
zorglub committed
342
343
344
345
/* Must be entered with lock */
void PLModel::activateItem( playlist_item_t *p_item )
{
    if( !p_item ) return;
zorglub's avatar
zorglub committed
346
347
348
349
350
351
352
    playlist_item_t *p_parent = p_item;
    while( p_parent )
    {
        if( p_parent->i_id == rootItem->i_id ) break;
        p_parent = p_parent->p_parent;
    }
    if( p_parent )
ivoire's avatar
ivoire committed
353
        playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked,
354
                          p_parent, p_item );
zorglub's avatar
zorglub committed
355
}
zorglub's avatar
zorglub committed
356

zorglub's avatar
zorglub committed
357
/****************** Base model mandatory implementations *****************/
358
QVariant PLModel::data( const QModelIndex &index, int role ) const
zorglub's avatar
zorglub committed
359
{
360
    if( !index.isValid() ) return QVariant();
zorglub's avatar
zorglub committed
361
    PLItem *item = static_cast<PLItem*>(index.internalPointer());
zorglub's avatar
zorglub committed
362
363
    if( role == Qt::DisplayRole )
    {
364
365
366
367
        int running_index = -1;
        int columncount = 0;
        int metadata = 1;

368
        if( i_depth == DEPTH_SEL )
369
370
371
372
373
374
        {
            vlc_mutex_lock( &item->p_input->lock );
            QString returninfo = QString( qfu( item->p_input->psz_name ) );
            vlc_mutex_unlock( &item->p_input->lock );
            return QVariant(returninfo);
        }
375
376
377

        while( metadata < COLUMN_END )
        {
378
            if( i_showflags & metadata )
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
                running_index++;
            if( running_index == index.column() )
                break;
            metadata <<= 1;
        }

        if( running_index != index.column() ) return QVariant();

        QString returninfo;
        if( metadata == COLUMN_NUMBER )
            returninfo = QString::number( index.row() + 1 );
        else
        {
            char *psz = psz_column_meta( item->p_input, metadata );
            returninfo = QString( qfu( psz ) );
            free( psz );
        }
        return QVariant( returninfo );
zorglub's avatar
zorglub committed
397
398
399
    }
    else if( role == Qt::DecorationRole && index.column() == 0  )
    {
400
        /* Use to segfault here because i_type wasn't always initialized */
401
402
        if( item->p_input->i_type >= 0 )
            return QVariant( PLModel::icons[item->p_input->i_type] );
zorglub's avatar
zorglub committed
403
    }
zorglub's avatar
Qt4:    
zorglub committed
404
405
    else if( role == Qt::FontRole )
    {
406
        if( isCurrent( index ) )
zorglub's avatar
Qt4:    
zorglub committed
407
408
409
410
        {
            QFont f; f.setBold( true ); return QVariant( f );
        }
    }
zorglub's avatar
zorglub committed
411
    return QVariant();
zorglub's avatar
zorglub committed
412
413
}

414
bool PLModel::isCurrent( const QModelIndex &index ) const
zorglub's avatar
Qt4:    
zorglub committed
415
416
{
    assert( index.isValid() );
417
418
    if( !currentItem ) return false;
    return static_cast<PLItem*>(index.internalPointer())->p_input == currentItem->p_input;
zorglub's avatar
Qt4:    
zorglub committed
419
420
}

zorglub's avatar
zorglub committed
421
int PLModel::itemId( const QModelIndex &index ) const
zorglub's avatar
zorglub committed
422
{
zorglub's avatar
zorglub committed
423
424
425
    assert( index.isValid() );
    return static_cast<PLItem*>(index.internalPointer())->i_id;
}
zorglub's avatar
zorglub committed
426
427

QVariant PLModel::headerData( int section, Qt::Orientation orientation,
428
                              int role ) const
zorglub's avatar
zorglub committed
429
{
430
431
432
433
434
435
436
437
438
    int metadata=1;
    int running_index=-1;
    if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
        return QVariant();

    if( i_depth == DEPTH_SEL ) return QVariant( QString("") );

    while( metadata < COLUMN_END )
    {
439
        if( metadata & i_showflags )
440
441
442
443
444
445
446
447
448
            running_index++;
        if( running_index == section )
            break;
        metadata <<= 1;
    }

    if( running_index != section ) return QVariant();

    return QVariant( qfu( psz_column_title( metadata ) ) );
zorglub's avatar
zorglub committed
449
450
}

451
QModelIndex PLModel::index( int row, int column, const QModelIndex &parent )
zorglub's avatar
zorglub committed
452
453
454
                  const
{
    PLItem *parentItem;
455
    if( !parent.isValid() )
zorglub's avatar
zorglub committed
456
457
458
459
        parentItem = rootItem;
    else
        parentItem = static_cast<PLItem*>(parent.internalPointer());

460
461
462
    PLItem *childItem = parentItem->child( row );
    if( childItem )
        return createIndex( row, column, childItem );
zorglub's avatar
zorglub committed
463
464
465
466
467
    else
        return QModelIndex();
}

/* Return the index of a given item */
zorglub's avatar
zorglub committed
468
QModelIndex PLModel::index( PLItem *item, int column ) const
zorglub's avatar
zorglub committed
469
470
471
472
{
    if( !item ) return QModelIndex();
    const PLItem *parent = item->parent();
    if( parent )
473
474
        return createIndex( parent->children.lastIndexOf( item ),
                            column, item );
zorglub's avatar
zorglub committed
475
476
477
    return QModelIndex();
}

478
QModelIndex PLModel::parent( const QModelIndex &index ) const
zorglub's avatar
zorglub committed
479
{
zorglub's avatar
Qt4:    
zorglub committed
480
    if( !index.isValid() ) return QModelIndex();
zorglub's avatar
zorglub committed
481
482

    PLItem *childItem = static_cast<PLItem*>(index.internalPointer());
483
484
485
486
487
488
    if( !childItem )
    {
        msg_Err( p_playlist, "NULL CHILD" );
        return QModelIndex();
    }

zorglub's avatar
zorglub committed
489
    PLItem *parentItem = childItem->parent();
zorglub's avatar
zorglub committed
490
    if( !parentItem || parentItem == rootItem ) return QModelIndex();
491
    if( !parentItem->parentItem )
492
    {
zorglub's avatar
zorglub committed
493
494
        msg_Err( p_playlist, "No parent parent, trying row 0 " );
        msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
495
496
        return createIndex( 0, 0, parentItem );
    }
zorglub's avatar
zorglub committed
497
498
    QModelIndex ind = createIndex(parentItem->row(), 0, parentItem);
    return ind;
zorglub's avatar
zorglub committed
499
500
}

zorglub's avatar
zorglub committed
501
int PLModel::columnCount( const QModelIndex &i) const
zorglub's avatar
zorglub committed
502
{
503
504
505
506
507
508
    int columnCount=0;
    int metadata=1;
    if( i_depth == DEPTH_SEL ) return 1;

    while( metadata < COLUMN_END )
    {
509
        if( metadata & i_showflags )
510
511
512
513
            columnCount++;
        metadata <<= 1;
    }
    return columnCount;
zorglub's avatar
zorglub committed
514
515
}

516
int PLModel::childrenCount( const QModelIndex &parent ) const
zorglub's avatar
zorglub committed
517
518
519
520
{
    return rowCount( parent );
}

521
int PLModel::rowCount( const QModelIndex &parent ) const
zorglub's avatar
zorglub committed
522
523
524
{
    PLItem *parentItem;

525
    if( !parent.isValid() )
zorglub's avatar
zorglub committed
526
527
528
529
530
531
532
        parentItem = rootItem;
    else
        parentItem = static_cast<PLItem*>(parent.internalPointer());

    return parentItem->childCount();
}

533
534
535
536
537
538
539
QStringList PLModel::selectedURIs()
{
    QStringList lst;
    for( int i = 0; i < current_selection.size(); i++ )
    {
        PLItem *item = static_cast<PLItem*>
                    (current_selection[i].internalPointer());
540
        if( item )
541
        {
542
            PL_LOCK;
543
544
545
546
547
548
            playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
            if( p_item )
            {
                char *psz = input_item_GetURI( p_item->p_input );
                if( psz )
                {
549
                    lst.append( psz );
550
551
552
                    free( psz );
                }
            }
553
            PL_UNLOCK;
554
555
556
557
558
        }
    }
    return lst;
}

zorglub's avatar
zorglub committed
559
560
561
562
/************************* General playlist status ***********************/

bool PLModel::hasRandom()
{
ivoire's avatar
ivoire committed
563
    return var_GetBool( p_playlist, "random" );
zorglub's avatar
zorglub committed
564
565
566
}
bool PLModel::hasRepeat()
{
ivoire's avatar
ivoire committed
567
    return var_GetBool( p_playlist, "repeat" );
zorglub's avatar
zorglub committed
568
569
570
}
bool PLModel::hasLoop()
{
ivoire's avatar
ivoire committed
571
    return var_GetBool( p_playlist, "loop" );
zorglub's avatar
zorglub committed
572
573
574
}
void PLModel::setLoop( bool on )
{
575
    var_SetBool( p_playlist, "loop", on ? true:false );
zorglub's avatar
zorglub committed
576
    config_PutInt( p_playlist, "loop", on ? 1: 0 );
zorglub's avatar
zorglub committed
577
578
579
}
void PLModel::setRepeat( bool on )
{
580
    var_SetBool( p_playlist, "repeat", on ? true:false );
zorglub's avatar
zorglub committed
581
    config_PutInt( p_playlist, "repeat", on ? 1: 0 );
zorglub's avatar
zorglub committed
582
583
584
}
void PLModel::setRandom( bool on )
{
585
    var_SetBool( p_playlist, "random", on ? true:false );
zorglub's avatar
zorglub committed
586
    config_PutInt( p_playlist, "random", on ? 1: 0 );
zorglub's avatar
zorglub committed
587
588
}

zorglub's avatar
zorglub committed
589
590
591
592
593
594
595
596
597
/************************* Lookups *****************************/

PLItem *PLModel::FindById( PLItem *root, int i_id )
{
    return FindInner( root, i_id, false );
}

PLItem *PLModel::FindByInput( PLItem *root, int i_id )
{
598
599
    PLItem *result = FindInner( root, i_id, true );
    return result;
zorglub's avatar
zorglub committed
600
601
}

zorglub's avatar
zorglub committed
602
603
#define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
#define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
zorglub's avatar
zorglub committed
604
605
606

PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
{
zorglub's avatar
zorglub committed
607
    if( ( !b_input && i_cached_id == i_id) ||
zorglub's avatar
zorglub committed
608
        ( b_input && i_cached_input_id ==i_id ) )
609
    {
zorglub's avatar
zorglub committed
610
        return b_input ? p_cached_item_bi : p_cached_item;
611
    }
zorglub's avatar
zorglub committed
612
613
614
615
616
617

    if( !b_input && root->i_id == i_id )
    {
        CACHE( i_id, root );
        return root;
    }
618
    else if( b_input && root->p_input->i_id == i_id )
zorglub's avatar
zorglub committed
619
620
621
622
623
624
625
626
627
628
629
630
631
    {
        ICACHE( i_id, root );
        return root;
    }

    QList<PLItem *>::iterator it = root->children.begin();
    while ( it != root->children.end() )
    {
        if( !b_input && (*it)->i_id == i_id )
        {
            CACHE( i_id, (*it) );
            return p_cached_item;
        }
632
        else if( b_input && (*it)->p_input->i_id == i_id )
zorglub's avatar
zorglub committed
633
634
        {
            ICACHE( i_id, (*it) );
635
            return p_cached_item_bi;
zorglub's avatar
zorglub committed
636
637
638
639
        }
        if( (*it)->children.size() )
        {
            PLItem *childFound = FindInner( (*it), i_id, b_input );
zorglub's avatar
zorglub committed
640
            if( childFound )
zorglub's avatar
zorglub committed
641
642
            {
                if( b_input )
zorglub's avatar
zorglub committed
643
                    ICACHE( i_id, childFound )
zorglub's avatar
zorglub committed
644
                else
zorglub's avatar
zorglub committed
645
                    CACHE( i_id, childFound )
zorglub's avatar
zorglub committed
646
                return childFound;
zorglub's avatar
zorglub committed
647
            }
zorglub's avatar
zorglub committed
648
        }
zorglub's avatar
zorglub committed
649
        it++;
zorglub's avatar
zorglub committed
650
651
652
653
654
655
656
657
658
659
    }
    return NULL;
}
#undef CACHE
#undef ICACHE


/************************* Updates handling *****************************/
void PLModel::customEvent( QEvent *event )
{
zorglub's avatar
zorglub committed
660
    int type = event->type();
661
    if( type != ItemAppend_Type &&
zorglub's avatar
zorglub committed
662
        type != ItemDelete_Type && type != PLUpdate_Type )
zorglub's avatar
zorglub committed
663
664
        return;

zorglub's avatar
zorglub committed
665
    PLEvent *ple = static_cast<PLEvent *>(event);
zorglub's avatar
zorglub committed
666

667
    if( type == ItemAppend_Type )
668
        ProcessItemAppend( &ple->add );
zorglub's avatar
zorglub committed
669
    else if( type == ItemDelete_Type )
zorglub's avatar
zorglub committed
670
        ProcessItemRemoval( ple->i_id );
zorglub's avatar
zorglub committed
671
672
    else
        rebuild();
zorglub's avatar
zorglub committed
673
674
}

zorglub's avatar
zorglub committed
675
/**** Events processing ****/
676
677
678
void PLModel::ProcessInputItemUpdate( input_thread_t *p_input )
{
    if( !p_input ) return;
679
    ProcessInputItemUpdate( input_GetItem( p_input ) );
680
681
682
    if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
    {
        PLItem *item = FindByInput( rootItem, input_GetItem( p_input )->i_id );
683
        currentItem = item;
684
685
        emit currentChanged( index( item, 0 ) );
    }
686
687
688
689
    else
    {
        currentItem = NULL;
    }
690
}
691
void PLModel::ProcessInputItemUpdate( input_item_t *p_item )
zorglub's avatar
zorglub committed
692
{
693
694
    if( !p_item ||  p_item->i_id <= 0 ) return;
    PLItem *item = FindByInput( rootItem, p_item->i_id );
zorglub's avatar
zorglub committed
695
    if( item )
696
        UpdateTreeItem( item, true, true);
zorglub's avatar
zorglub committed
697
698
699
700
}

void PLModel::ProcessItemRemoval( int i_id )
{
zorglub's avatar
zorglub committed
701
    if( i_id <= 0 ) return;
zorglub's avatar
zorglub committed
702
703
    if( i_id == i_cached_id ) i_cached_id = -1;
    i_cached_input_id = -1;
704
705

    removeItem( i_id );
zorglub's avatar
zorglub committed
706
707
}

708
void PLModel::ProcessItemAppend( const playlist_add_t *p_add )
zorglub's avatar
zorglub committed
709
710
{
    playlist_item_t *p_item = NULL;
zorglub's avatar
zorglub committed
711
    PLItem *newItem = NULL;
zorglub's avatar
zorglub committed
712
713

    PLItem *nodeItem = FindById( rootItem, p_add->i_node );
714
    if( !nodeItem ) return;
zorglub's avatar
zorglub committed
715

716
    PL_LOCK;
717
    p_item = playlist_ItemGetById( p_playlist, p_add->i_item );
zorglub's avatar
zorglub committed
718
    if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
719
    if( i_depth == DEPTH_SEL && p_item->p_parent &&
zorglub's avatar
zorglub committed
720
721
722
                        p_item->p_parent->i_id != rootItem->i_id )
        goto end;

723
    newItem = new PLItem( p_item, nodeItem );
724
725
    PL_UNLOCK;

726
727
    emit layoutAboutToBeChanged();
    emit beginInsertRows( index( newItem, 0 ), nodeItem->childCount(), nodeItem->childCount()+1 );
zorglub's avatar
zorglub committed
728
    nodeItem->appendChild( newItem );
729
730
    emit endInsertRows();
    emit layoutChanged();
731
    UpdateTreeItem( newItem, true );
732
    return;
zorglub's avatar
zorglub committed
733
end:
zorglub's avatar
Qt4:    
zorglub committed
734
    PL_UNLOCK;
zorglub's avatar
zorglub committed
735
736
    return;
}
zorglub's avatar
zorglub committed
737

zorglub's avatar
zorglub committed
738
739
740
741
742
743
744

void PLModel::rebuild()
{
    rebuild( NULL );
}

void PLModel::rebuild( playlist_item_t *p_root )
zorglub's avatar
zorglub committed
745
{
Christophe Mutricy's avatar
Christophe Mutricy committed
746
    playlist_item_t* p_item;
zorglub's avatar
zorglub committed
747
748
749
750
751
    /* Remove callbacks before locking to avoid deadlocks */
    delCallbacks();
    /* Invalidate cache */
    i_cached_id = i_cached_input_id = -1;

752
753
    emit layoutAboutToBeChanged();

zorglub's avatar
zorglub committed
754
    /* Clear the tree */
zorglub's avatar
zorglub committed
755
756
    if( rootItem )
    {
757
758
        if( rootItem->children.size() )
        {
759
            emit beginRemoveRows( index( rootItem, 0 ), 0,
760
761
762
                    rootItem->children.size() -1 );
            qDeleteAll( rootItem->children );
            rootItem->children.clear();
763
            emit endRemoveRows();
764
        }
zorglub's avatar
zorglub committed
765
    }
766
    PL_LOCK;
zorglub's avatar
zorglub committed
767
768
    if( p_root )
    {
Rafaël Carré's avatar
Rafaël Carré committed
769
        delete rootItem;
770
        rootItem = new PLItem( p_root );
zorglub's avatar
zorglub committed
771
772
    }
    assert( rootItem );
zorglub's avatar
zorglub committed
773
774
    /* Recreate from root */
    UpdateNodeChildren( rootItem );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
775
    if( (p_item = playlist_CurrentPlayingItem(p_playlist)) )
zorglub's avatar
zorglub committed
776
    {
777
        currentItem = FindByInput( rootItem,
Christophe Mutricy's avatar
Christophe Mutricy committed
778
                                           p_item->p_input->i_id );
zorglub's avatar
zorglub committed
779
780
        if( currentItem )
        {
781
            UpdateTreeItem( currentItem, true, false );
zorglub's avatar
zorglub committed
782
783
        }
    }
784
785
786
787
    else
    {
        currentItem = NULL;
    }
zorglub's avatar
Qt4:    
zorglub committed
788
    PL_UNLOCK;
zorglub's avatar
zorglub committed
789
790
791
792
793
794

    /* And signal the view */
    emit layoutChanged();
    addCallbacks();
}

zorglub's avatar
Qt4:    
zorglub committed
795
/* This function must be entered WITH the playlist lock */
zorglub's avatar
zorglub committed
796
797
void PLModel::UpdateNodeChildren( PLItem *root )
{
798
    emit layoutAboutToBeChanged();
799
    playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
zorglub's avatar
zorglub committed
800
    UpdateNodeChildren( p_node, root );
801
    emit layoutChanged();
zorglub's avatar
zorglub committed
802
803
}

zorglub's avatar
Qt4:    
zorglub committed
804
/* This function must be entered WITH the playlist lock */
zorglub's avatar
zorglub committed
805
806
807
808
void PLModel::UpdateNodeChildren( playlist_item_t *p_node, PLItem *root )
{
    for( int i = 0; i < p_node->i_children ; i++ )
    {
zorglub's avatar
zorglub committed
809
        if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
810
811
812
813
814
        PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
        emit beginInsertRows( index( newItem, 0 ), root->childCount(), root->childCount()+1 );
        root->appendChild( newItem );
        emit endInsertRows();
        UpdateTreeItem( newItem, true, true );
815
        if( i_depth == DEPTH_PL && p_node->pp_children[i]->i_children != -1 )
zorglub's avatar
zorglub committed
816
817
818
819
            UpdateNodeChildren( p_node->pp_children[i], newItem );
    }
}

820
/* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
zorglub's avatar
zorglub committed
821
void PLModel::UpdateTreeItem( PLItem *item, bool signal, bool force )
zorglub's avatar
zorglub committed
822
{
823
    if ( !item || !item->p_input )
824
        return;
825
826
    if( !force && i_depth == DEPTH_SEL && item->parentItem &&
                                 item->parentItem->p_input != rootItem->p_input )
zorglub's avatar
zorglub committed
827
        return;
zorglub's avatar
zorglub committed
828
    if( signal )
829
        emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
zorglub's avatar
zorglub committed
830
}
zorglub's avatar
zorglub committed
831

zorglub's avatar
zorglub committed
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
/************************* Actions ******************************/

/**
 * Deletion, here we have to do a ugly slow hack as we retrieve the full
 * list of indexes to delete at once: when we delete a node and all of
 * its children, we need to update the list.
 * Todo: investigate whethere we can use ranges to be sure to delete all items?
 */
void PLModel::doDelete( QModelIndexList selected )
{
    for( int i = selected.size() -1 ; i >= 0; i-- )
    {
        QModelIndex index = selected[i];
        if( index.column() != 0 ) continue;
        PLItem *item = static_cast<PLItem*>(index.internalPointer());
        if( item )
        {
            if( item->children.size() )
                recurseDelete( item->children, &selected );
            doDeleteItem( item, &selected );
        }
853
        if( i > selected.size() ) i = selected.size();
zorglub's avatar
zorglub committed
854
855
856
    }
}

857
void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
zorglub's avatar
zorglub committed
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
{
    for( int i = children.size() - 1; i >= 0 ; i-- )
    {
        PLItem *item = children[i];
        if( item->children.size() )
            recurseDelete( item->children, fullList );
        doDeleteItem( item, fullList );
    }
}

void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
{
    QModelIndex deleteIndex = index( item, 0 );
    fullList->removeAll( deleteIndex );

    PL_LOCK;
874
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
zorglub's avatar
zorglub committed
875
876
    if( !p_item )
    {
877
878
        PL_UNLOCK;
        return;
zorglub's avatar
zorglub committed
879
880
    }
    if( p_item->i_children == -1 )
881
        playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
zorglub's avatar
zorglub committed
882
    else
883
        playlist_NodeDelete( p_playlist, p_item, true, false );
884
    PL_UNLOCK;
zorglub's avatar
zorglub committed
885
    /* And finally, remove it from the tree */
886
887
888
889
    emit beginRemoveRows( index( item->parentItem, 0), item->parentItem->children.indexOf( item ),
            item->parentItem->children.indexOf( item )+1 );
    item->remove( item, i_depth );
    emit endRemoveRows();
zorglub's avatar
zorglub committed
890
891
}

zorglub's avatar
zorglub committed
892
893
894
/******* Volume III: Sorting and searching ********/
void PLModel::sort( int column, Qt::SortOrder order )
{
895
896
897
    int i_index = -1;
    int i_flag = 0;

898
899
900
901
902
903
904
905
906
907
908
909
    int i_column = 1;
    for( i_column = 1; i_column != COLUMN_END; i_column<<=1 )
    {
        if( ( shownFlags() & i_column ) )
            i_index++;
        if( column == i_index )
        {
            i_flag = i_column;
            goto next;
        }
    }

910
911

next:
zorglub's avatar
zorglub committed
912
913
    PL_LOCK;
    {
914
        playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
915
                                                        rootItem->i_id );
916
        if( p_root && i_flag )
917
        {
Rafaël Carré's avatar
Rafaël Carré committed
918
919
            playlist_RecursiveNodeSort( p_playlist, p_root,
                                        i_column_sorting( i_flag ),
920
921
                                        order == Qt::AscendingOrder ?
                                            ORDER_NORMAL : ORDER_REVERSE );
922
        }
zorglub's avatar
zorglub committed
923
    }
924
    PL_UNLOCK;
zorglub's avatar
zorglub committed
925
926
927
    rebuild();
}

928
void PLModel::search( const QString& search_text )
zorglub's avatar
zorglub committed
929
930
931
{
    /** \todo Fire the search with a small delay ? */
    PL_LOCK;
932
933
    {
        playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
934
                                                        rootItem->i_id );
935
        assert( p_root );
936
        const char *psz_name = search_text.toUtf8().data();
937
938
        playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
    }
zorglub's avatar
zorglub committed
939
940
941
942
    PL_UNLOCK;
    rebuild();
}

zorglub's avatar
zorglub committed
943
944
945
946
/*********** Popup *********/
void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
{
    PL_LOCK;
947
948
949
950
    int i_id;
    if( index.isValid() ) i_id = itemId( index );
    else i_id = rootItem->i_id;
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
zorglub's avatar
zorglub committed
951
952
953
954
    if( p_item )
    {
        i_popup_item = p_item->i_id;
        i_popup_parent = p_item->p_parent ? p_item->p_parent->i_id : -1;
jpd's avatar
jpd committed
955
956
957
958
959
960
961
962
963
964
965
966
        bool node = p_item->i_children > -1;
        bool tree = false;
        if( node )
        {
            /* check whether we are in tree view */
            playlist_item_t *p_up = p_item;
            while( p_up )
            {
                if ( p_up == p_playlist->p_root_category ) tree = true;
                p_up = p_up->p_parent;
            }
        }
zorglub's avatar
zorglub committed
967
        PL_UNLOCK;
jpd's avatar
jpd committed
968