playlist_model.cpp 28.7 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>
9
 *          Jakob Leben <jleben@videolan.org>
zorglub's avatar
zorglub committed
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 *
 * 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.
 *****************************************************************************/
25

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

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

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

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

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

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

zorglub's avatar
zorglub committed
50
51
52
53
/*************************************************************************
 * Playlist model implementation
 *************************************************************************/

54
55
56
57
58
59
60
61
62
63
/*
  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 */
                  QObject *parent )         /* Basic Qt parent */
                  : QAbstractItemModel( parent )
zorglub's avatar
zorglub committed
64
{
65
66
    p_intf            = _p_intf;
    p_playlist        = _p_playlist;
zorglub's avatar
zorglub committed
67
68
    i_cached_id       = -1;
    i_cached_input_id = -1;
69
    i_popup_item      = i_popup_parent = -1;
70
    currentItem       = NULL;
71
72

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

74
    /* Icons initialization */
75
#define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( x )
76
    ADD_ICON( UNKNOWN , type_unknown_xpm );
77
78
79
80
81
82
83
84
    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" );
85
#undef ADD_ICON
zorglub's avatar
zorglub committed
86

87
    rebuild( p_root, true );
88
    CONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
jpd's avatar
jpd committed
89
            this, processInputItemUpdate( input_item_t *) );
90
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
jpd's avatar
jpd committed
91
            this, processInputItemUpdate( input_thread_t* ) );
92
93
94
95
    CONNECT( THEMIM, playlistItemAppended( int, int ),
             this, processItemAppend( int, int ) );
    CONNECT( THEMIM, playlistItemRemoved( int ),
             this, processItemRemoval( int ) );
zorglub's avatar
zorglub committed
96
97
98
99
100
101
102
}

PLModel::~PLModel()
{
    delete rootItem;
}

zorglub's avatar
zorglub committed
103
104
Qt::DropActions PLModel::supportedDropActions() const
{
105
    return Qt::CopyAction; /* Why not Qt::MoveAction */
zorglub's avatar
zorglub committed
106
107
}

108
Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
zorglub's avatar
zorglub committed
109
{
110
111
    Qt::ItemFlags flags = QAbstractItemModel::flags( index );

112
    PLItem *item = index.isValid() ? getItem( index ) : rootItem;
113

114
    if( canEdit() )
115
    {
116
117
118
        PL_LOCK;
        playlist_item_t *plItem =
            playlist_ItemGetById( p_playlist, item->i_id );
119
120
121
122

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

123
        PL_UNLOCK;
124

125
    }
126
    flags |= Qt::ItemIsDragEnabled;
127
128

    return flags;
zorglub's avatar
zorglub committed
129
130
131
132
133
}

QStringList PLModel::mimeTypes() const
{
    QStringList types;
134
    types << "vlc/qt-playlist-item";
zorglub's avatar
zorglub committed
135
136
137
    return types;
}

138
QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
zorglub's avatar
zorglub committed
139
140
141
{
    QMimeData *mimeData = new QMimeData();
    QByteArray encodedData;
142
    QDataStream stream( &encodedData, QIODevice::WriteOnly );
143
    QModelIndexList list;
zorglub's avatar
zorglub committed
144

145
    foreach( const QModelIndex &index, indexes ) {
146
        if( index.isValid() && index.column() == 0 )
147
148
149
150
151
152
            list.append(index);
    }

    qSort(list);

    foreach( const QModelIndex &index, list ) {
153
154
        PLItem *item = getItem( index );
        stream.writeRawData( (char*) &item, sizeof( PLItem* ) );
zorglub's avatar
zorglub committed
155
    }
156
    mimeData->setData( "vlc/qt-playlist-item", encodedData );
zorglub's avatar
zorglub committed
157
158
159
    return mimeData;
}

160
/* Drop operation */
161
bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
162
                           int row, int column, const QModelIndex &parent )
zorglub's avatar
zorglub committed
163
{
164
    if( data->hasFormat( "vlc/qt-playlist-item" ) )
zorglub's avatar
zorglub committed
165
    {
166
        if( action == Qt::IgnoreAction )
zorglub's avatar
zorglub committed
167
168
            return true;

169
        PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
170

171
172
173
        PL_LOCK;
        playlist_item_t *p_parent =
            playlist_ItemGetById( p_playlist, parentItem->i_id );
174
175
176
177
178
        if( !p_parent || p_parent->i_children == -1 )
        {
            PL_UNLOCK;
            return false;
        }
zorglub's avatar
zorglub committed
179

180
        bool copy = false;
181
182
183
184
185
186
187
188
189
190
        playlist_item_t *p_pl = p_playlist->p_local_category;
        playlist_item_t *p_ml = p_playlist->p_ml_category;
        if
        (
            row == -1 && (
            ( p_pl && p_parent->p_input == p_pl->p_input ) ||
            ( p_ml && p_parent->p_input == p_ml->p_input ) )
        )
            copy = true;
        PL_UNLOCK;
zorglub's avatar
zorglub committed
191

192
        QByteArray encodedData = data->data( "vlc/qt-playlist-item" );
193
        if( copy )
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
            dropAppendCopy( encodedData, parentItem );
        else
            dropMove( encodedData, parentItem, row );
    }
    return true;
}

void PLModel::dropAppendCopy( QByteArray& data, PLItem *target )
{
    QDataStream stream( &data, QIODevice::ReadOnly );

    PL_LOCK;
    playlist_item_t *p_parent =
            playlist_ItemGetById( p_playlist, target->i_id );
    while( !stream.atEnd() )
    {
        PLItem *item;
        stream.readRawData( (char*)&item, sizeof(PLItem*) );
        playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
        if( !p_item ) continue;
        input_item_t *p_input = p_item->p_input;
        playlist_AddExt ( p_playlist,
            p_input->psz_uri, p_input->psz_name,
            PLAYLIST_APPEND | PLAYLIST_SPREPARSE, PLAYLIST_END,
            p_input->i_duration,
            p_input->i_options, p_input->ppsz_options, p_input->optflagc,
220
221
222
            ( p_parent == p_playlist->p_local_category ||
            p_parent == p_playlist->p_local_onelevel ),
            true );
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
    }
    PL_UNLOCK;
}

void PLModel::dropMove( QByteArray& data, PLItem *target, int row )
{
    QDataStream stream( &data, QIODevice::ReadOnly );
    QList<PLItem*> model_items;
    QList<int> ids;
    int new_pos = row == -1 ? target->children.size() : row;
    int model_pos = new_pos;
    while( !stream.atEnd() )
    {
        PLItem *item;
        stream.readRawData( (char*)&item, sizeof(PLItem*) );

        /* better not try to move a node into itself: */
        PLItem *climber = target;
        while( climber )
zorglub's avatar
zorglub committed
242
        {
243
244
            if( climber == item ) break;
            climber = climber->parentItem;
245
        }
246
247
248
249
250
251
252
253
254
        if( climber ) continue;

        if( item->parentItem == target &&
            target->children.indexOf( item ) < model_pos )
                model_pos--;

        ids.append( item->i_id );
        model_items.append( item );

jpd's avatar
jpd committed
255
        takeItem( item );
256
257
258
259
260
261
262
263
    }
    int count = ids.size();
    if( count )
    {
        playlist_item_t *pp_items[count];

        PL_LOCK;
        for( int i = 0; i < count; i++ )
264
        {
265
266
            playlist_item_t *p_item = playlist_ItemGetById( p_playlist, ids[i] );
            if( !p_item )
jpd's avatar
jpd committed
267
            {
268
269
                PL_UNLOCK;
                return;
zorglub's avatar
zorglub committed
270
            }
271
            pp_items[i] = p_item;
zorglub's avatar
zorglub committed
272
        }
273
274
275
276
        playlist_item_t *p_parent =
            playlist_ItemGetById( p_playlist, target->i_id );
        playlist_TreeMoveMany( p_playlist, count, pp_items, p_parent,
            new_pos );
277
        PL_UNLOCK;
278

jpd's avatar
jpd committed
279
        insertChildren( target, model_items, model_pos );
zorglub's avatar
zorglub committed
280
    }
zorglub's avatar
zorglub committed
281
}
zorglub's avatar
zorglub committed
282

283
/* remove item with its id */
zorglub's avatar
zorglub committed
284
285
void PLModel::removeItem( int i_id )
{
jpd's avatar
jpd committed
286
287
    PLItem *item = findById( rootItem, i_id );
    removeItem( item );
zorglub's avatar
zorglub committed
288
}
zorglub's avatar
zorglub committed
289

zorglub's avatar
zorglub committed
290
291
292
void PLModel::activateItem( const QModelIndex &index )
{
    assert( index.isValid() );
293
    PLItem *item = getItem( index );
zorglub's avatar
zorglub committed
294
295
    assert( item );
    PL_LOCK;
296
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
zorglub's avatar
zorglub committed
297
298
299
    activateItem( p_item );
    PL_UNLOCK;
}
300

zorglub's avatar
zorglub committed
301
302
303
304
/* Must be entered with lock */
void PLModel::activateItem( playlist_item_t *p_item )
{
    if( !p_item ) return;
zorglub's avatar
zorglub committed
305
306
307
308
309
310
311
    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
312
        playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked,
313
                          p_parent, p_item );
zorglub's avatar
zorglub committed
314
}
zorglub's avatar
zorglub committed
315

zorglub's avatar
zorglub committed
316
/****************** Base model mandatory implementations *****************/
317
QVariant PLModel::data( const QModelIndex &index, int role ) const
zorglub's avatar
zorglub committed
318
{
319
    if( !index.isValid() ) return QVariant();
320
    PLItem *item = getItem( index );
zorglub's avatar
zorglub committed
321
322
    if( role == Qt::DisplayRole )
    {
323
        int metadata = columnToMeta( index.column() );
324
        if( metadata == COLUMN_END ) return QVariant();
325
326
327
328
329
330
331

        QString returninfo;
        if( metadata == COLUMN_NUMBER )
            returninfo = QString::number( index.row() + 1 );
        else
        {
            char *psz = psz_column_meta( item->p_input, metadata );
332
            returninfo = qfu( psz );
333
334
335
            free( psz );
        }
        return QVariant( returninfo );
zorglub's avatar
zorglub committed
336
337
338
    }
    else if( role == Qt::DecorationRole && index.column() == 0  )
    {
339
340
        /* Used to segfault here because i_type wasn't always initialized */
        return QVariant( PLModel::icons[item->p_input->i_type] );
zorglub's avatar
zorglub committed
341
    }
zorglub's avatar
Qt4:    
zorglub committed
342
343
    else if( role == Qt::FontRole )
    {
344
        if( isCurrent( index ) )
zorglub's avatar
Qt4:    
zorglub committed
345
346
347
348
        {
            QFont f; f.setBold( true ); return QVariant( f );
        }
    }
zorglub's avatar
zorglub committed
349
    return QVariant();
zorglub's avatar
zorglub committed
350
351
}

352
bool PLModel::isCurrent( const QModelIndex &index ) const
zorglub's avatar
Qt4:    
zorglub committed
353
{
354
    if( !currentItem ) return false;
355
    return getItem( index )->p_input == currentItem->p_input;
zorglub's avatar
Qt4:    
zorglub committed
356
357
}

zorglub's avatar
zorglub committed
358
int PLModel::itemId( const QModelIndex &index ) const
zorglub's avatar
zorglub committed
359
{
360
    return getItem( index )->i_id;
zorglub's avatar
zorglub committed
361
}
zorglub's avatar
zorglub committed
362
363

QVariant PLModel::headerData( int section, Qt::Orientation orientation,
364
                              int role ) const
zorglub's avatar
zorglub committed
365
{
366
367
368
    if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
        return QVariant();

369
    int meta_col = columnToMeta( section );
370

371
    if( meta_col == COLUMN_END ) return QVariant();
372

373
    return QVariant( qfu( psz_column_title( meta_col ) ) );
zorglub's avatar
zorglub committed
374
375
}

376
QModelIndex PLModel::index( int row, int column, const QModelIndex &parent )
zorglub's avatar
zorglub committed
377
378
                  const
{
379
    PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
zorglub's avatar
zorglub committed
380

381
382
383
    PLItem *childItem = parentItem->child( row );
    if( childItem )
        return createIndex( row, column, childItem );
zorglub's avatar
zorglub committed
384
385
386
387
388
    else
        return QModelIndex();
}

/* Return the index of a given item */
zorglub's avatar
zorglub committed
389
QModelIndex PLModel::index( PLItem *item, int column ) const
zorglub's avatar
zorglub committed
390
391
392
393
{
    if( !item ) return QModelIndex();
    const PLItem *parent = item->parent();
    if( parent )
394
395
        return createIndex( parent->children.lastIndexOf( item ),
                            column, item );
zorglub's avatar
zorglub committed
396
397
398
    return QModelIndex();
}

399
QModelIndex PLModel::parent( const QModelIndex &index ) const
zorglub's avatar
zorglub committed
400
{
zorglub's avatar
Qt4:    
zorglub committed
401
    if( !index.isValid() ) return QModelIndex();
zorglub's avatar
zorglub committed
402

403
    PLItem *childItem = getItem( index );
404
405
406
407
408
409
    if( !childItem )
    {
        msg_Err( p_playlist, "NULL CHILD" );
        return QModelIndex();
    }

zorglub's avatar
zorglub committed
410
    PLItem *parentItem = childItem->parent();
zorglub's avatar
zorglub committed
411
    if( !parentItem || parentItem == rootItem ) return QModelIndex();
412
    if( !parentItem->parentItem )
413
    {
zorglub's avatar
zorglub committed
414
415
        msg_Err( p_playlist, "No parent parent, trying row 0 " );
        msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
416
417
        return createIndex( 0, 0, parentItem );
    }
zorglub's avatar
zorglub committed
418
419
    QModelIndex ind = createIndex(parentItem->row(), 0, parentItem);
    return ind;
zorglub's avatar
zorglub committed
420
421
}

zorglub's avatar
zorglub committed
422
int PLModel::columnCount( const QModelIndex &i) const
zorglub's avatar
zorglub committed
423
{
424
    return columnFromMeta( COLUMN_END );
zorglub's avatar
zorglub committed
425
426
}

427
int PLModel::rowCount( const QModelIndex &parent ) const
zorglub's avatar
zorglub committed
428
{
429
    PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
zorglub's avatar
zorglub committed
430
431
432
    return parentItem->childCount();
}

433
434
435
436
437
QStringList PLModel::selectedURIs()
{
    QStringList lst;
    for( int i = 0; i < current_selection.size(); i++ )
    {
438
        PLItem *item = getItem( current_selection[i] );
439
        if( item )
440
        {
441
            PL_LOCK;
442
443
444
445
446
447
            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 )
                {
448
                    lst.append( qfu(psz) );
449
450
451
                    free( psz );
                }
            }
452
            PL_UNLOCK;
453
454
455
456
457
        }
    }
    return lst;
}

zorglub's avatar
zorglub committed
458

zorglub's avatar
zorglub committed
459
460
/************************* Lookups *****************************/

jpd's avatar
jpd committed
461
PLItem *PLModel::findById( PLItem *root, int i_id )
zorglub's avatar
zorglub committed
462
{
jpd's avatar
jpd committed
463
    return findInner( root, i_id, false );
zorglub's avatar
zorglub committed
464
465
}

jpd's avatar
jpd committed
466
PLItem *PLModel::findByInput( PLItem *root, int i_id )
zorglub's avatar
zorglub committed
467
{
jpd's avatar
jpd committed
468
    PLItem *result = findInner( root, i_id, true );
469
    return result;
zorglub's avatar
zorglub committed
470
471
}

zorglub's avatar
zorglub committed
472
473
#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
474

jpd's avatar
jpd committed
475
PLItem * PLModel::findInner( PLItem *root, int i_id, bool b_input )
zorglub's avatar
zorglub committed
476
{
477
    if( !root ) return NULL;
zorglub's avatar
zorglub committed
478
    if( ( !b_input && i_cached_id == i_id) ||
zorglub's avatar
zorglub committed
479
        ( b_input && i_cached_input_id ==i_id ) )
480
    {
zorglub's avatar
zorglub committed
481
        return b_input ? p_cached_item_bi : p_cached_item;
482
    }
zorglub's avatar
zorglub committed
483
484
485
486
487
488

    if( !b_input && root->i_id == i_id )
    {
        CACHE( i_id, root );
        return root;
    }
489
    else if( b_input && root->p_input->i_id == i_id )
zorglub's avatar
zorglub committed
490
491
492
493
494
495
496
497
498
499
500
501
502
    {
        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;
        }
503
        else if( b_input && (*it)->p_input->i_id == i_id )
zorglub's avatar
zorglub committed
504
505
        {
            ICACHE( i_id, (*it) );
506
            return p_cached_item_bi;
zorglub's avatar
zorglub committed
507
508
509
        }
        if( (*it)->children.size() )
        {
jpd's avatar
jpd committed
510
            PLItem *childFound = findInner( (*it), i_id, b_input );
zorglub's avatar
zorglub committed
511
            if( childFound )
zorglub's avatar
zorglub committed
512
513
            {
                if( b_input )
zorglub's avatar
zorglub committed
514
                    ICACHE( i_id, childFound )
zorglub's avatar
zorglub committed
515
                else
zorglub's avatar
zorglub committed
516
                    CACHE( i_id, childFound )
zorglub's avatar
zorglub committed
517
                return childFound;
zorglub's avatar
zorglub committed
518
            }
zorglub's avatar
zorglub committed
519
        }
zorglub's avatar
zorglub committed
520
        it++;
zorglub's avatar
zorglub committed
521
522
523
524
525
526
    }
    return NULL;
}
#undef CACHE
#undef ICACHE

jpd's avatar
jpd committed
527
PLItem *PLModel::getItem( QModelIndex index )
528
529
530
531
532
{
    assert( index.isValid() );
    return static_cast<PLItem*>( index.internalPointer() );
}

533
int PLModel::columnToMeta( int _column ) const
534
{
jpd's avatar
jpd committed
535
    int meta = 1;
536
    int column = 0;
537

538
    while( column != _column && meta != COLUMN_END )
539
    {
jpd's avatar
jpd committed
540
        meta <<= 1;
541
        column++;
jpd's avatar
jpd committed
542
543
544
545
546
    }

    return meta;
}

547
int PLModel::columnFromMeta( int meta_col ) const
jpd's avatar
jpd committed
548
549
{
    int meta = 1;
550
    int column = 0;
jpd's avatar
jpd committed
551

552
    while( meta != meta_col && meta != COLUMN_END )
jpd's avatar
jpd committed
553
554
    {
        meta <<= 1;
555
        column++;
556
557
    }

558
    return column;
559
}
zorglub's avatar
zorglub committed
560

561
562
563
564
565
566
567
568
569
570
571
572
573
bool PLModel::canEdit() const
{
  return (
    rootItem != NULL &&
    (
      rootItem->p_input == p_playlist->p_local_category->p_input ||
      (
        p_playlist->p_ml_category &&
        rootItem->p_input == p_playlist->p_ml_category->p_input
      )
    )
  );
}
zorglub's avatar
zorglub committed
574
575
/************************* Updates handling *****************************/

zorglub's avatar
zorglub committed
576
/**** Events processing ****/
jpd's avatar
jpd committed
577
void PLModel::processInputItemUpdate( input_thread_t *p_input )
578
579
{
    if( !p_input ) return;
jpd's avatar
jpd committed
580
    processInputItemUpdate( input_GetItem( p_input ) );
581
582
    if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
    {
jpd's avatar
jpd committed
583
        PLItem *item = findByInput( rootItem, input_GetItem( p_input )->i_id );
584
        currentItem = item;
585
586
        emit currentChanged( index( item, 0 ) );
    }
587
588
589
590
    else
    {
        currentItem = NULL;
    }
591
}
jpd's avatar
jpd committed
592
void PLModel::processInputItemUpdate( input_item_t *p_item )
zorglub's avatar
zorglub committed
593
{
594
    if( !p_item ||  p_item->i_id <= 0 ) return;
jpd's avatar
jpd committed
595
    PLItem *item = findByInput( rootItem, p_item->i_id );
zorglub's avatar
zorglub committed
596
    if( item )
597
        updateTreeItem( item );
zorglub's avatar
zorglub committed
598
599
}

jpd's avatar
jpd committed
600
void PLModel::processItemRemoval( int i_id )
zorglub's avatar
zorglub committed
601
{
zorglub's avatar
zorglub committed
602
    if( i_id <= 0 ) return;
603
    removeItem( i_id );
zorglub's avatar
zorglub committed
604
605
}

606
void PLModel::processItemAppend( int i_item, int i_parent )
zorglub's avatar
zorglub committed
607
608
{
    playlist_item_t *p_item = NULL;
zorglub's avatar
zorglub committed
609
    PLItem *newItem = NULL;
zorglub's avatar
zorglub committed
610

611
    PLItem *nodeItem = findById( rootItem, i_parent );
612
    if( !nodeItem ) return;
zorglub's avatar
zorglub committed
613

jpd's avatar
jpd committed
614
    foreach( PLItem *existing, nodeItem->children )
615
      if( existing->i_id == i_item ) return;
616

617
    PL_LOCK;
618
    p_item = playlist_ItemGetById( p_playlist, i_item );
zorglub's avatar
zorglub committed
619
    if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
zorglub's avatar
zorglub committed
620

621
    newItem = new PLItem( p_item, nodeItem );
622
623
    PL_UNLOCK;

624
    beginInsertRows( index( nodeItem, 0 ), nodeItem->childCount(), nodeItem->childCount() );
zorglub's avatar
zorglub committed
625
    nodeItem->appendChild( newItem );
626
    endInsertRows();
627
    updateTreeItem( newItem );
628
    return;
zorglub's avatar
zorglub committed
629
end:
zorglub's avatar
Qt4:    
zorglub committed
630
    PL_UNLOCK;
zorglub's avatar
zorglub committed
631
632
    return;
}
zorglub's avatar
zorglub committed
633

zorglub's avatar
zorglub committed
634
635
636

void PLModel::rebuild()
{
637
    rebuild( NULL, false );
zorglub's avatar
zorglub committed
638
639
}

640
void PLModel::rebuild( playlist_item_t *p_root, bool b_first )
zorglub's avatar
zorglub committed
641
{
Christophe Mutricy's avatar
Christophe Mutricy committed
642
    playlist_item_t* p_item;
643

zorglub's avatar
zorglub committed
644
645
646
    /* Invalidate cache */
    i_cached_id = i_cached_input_id = -1;

647
    if( rootItem ) rootItem->removeChildren();
648

649
    PL_LOCK;
zorglub's avatar
zorglub committed
650
651
    if( p_root )
    {
Rafaël Carré's avatar
Rafaël Carré committed
652
        delete rootItem;
653
        rootItem = new PLItem( p_root );
zorglub's avatar
zorglub committed
654
655
    }
    assert( rootItem );
zorglub's avatar
zorglub committed
656
    /* Recreate from root */
jpd's avatar
jpd committed
657
    updateChildren( rootItem );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
658
    if( (p_item = playlist_CurrentPlayingItem(p_playlist)) )
jpd's avatar
jpd committed
659
        currentItem = findByInput( rootItem, p_item->p_input->i_id );
660
661
    else
        currentItem = NULL;
zorglub's avatar
Qt4:    
zorglub committed
662
    PL_UNLOCK;
zorglub's avatar
zorglub committed
663
664

    /* And signal the view */
665
    reset();
jpd's avatar
jpd committed
666

667
    emit currentChanged( index( currentItem, 0 ) );
zorglub's avatar
zorglub committed
668
669
}

jpd's avatar
jpd committed
670
void PLModel::takeItem( PLItem *item )
671
672
673
674
675
676
677
678
679
680
681
{
    assert( item );
    PLItem *parent = item->parentItem;
    assert( parent );
    int i_index = parent->children.indexOf( item );

    beginRemoveRows( index( parent, 0 ), i_index, i_index );
    parent->takeChildAt( i_index );
    endRemoveRows();
}

jpd's avatar
jpd committed
682
void PLModel::insertChildren( PLItem *node, QList<PLItem*>& items, int i_pos )
683
684
685
686
687
688
689
690
691
692
693
694
695
{
    assert( node );
    int count = items.size();
    if( !count ) return;
    beginInsertRows( index( node, 0 ), i_pos, i_pos + count - 1 );
    for( int i = 0; i < count; i++ )
    {
        node->children.insert( i_pos + i, items[i] );
        items[i]->parentItem = node;
    }
    endInsertRows();
}

jpd's avatar
jpd committed
696
void PLModel::removeItem( PLItem *item )
697
698
{
    if( !item ) return;
699

700
701
702
    if( item->i_id == i_cached_id ) i_cached_id = -1;
    i_cached_input_id = -1;

703
    if( currentItem == item || rootItem == item)
704
705
706
707
    {
        currentItem = NULL;
        emit currentChanged( QModelIndex() );
    }
708

709
710
711
    if(item == rootItem)
        rootItem = NULL;

712
713
714
715
716
717
718
    if( item->parentItem ) {
        int i = item->parentItem->children.indexOf( item );
        beginRemoveRows( index( item->parentItem, 0), i, i );
        item->parentItem->children.removeAt(i);
        delete item;
        endRemoveRows();
    }
719
720
    else delete item;

721
722
}

zorglub's avatar
Qt4:    
zorglub committed
723
/* This function must be entered WITH the playlist lock */
jpd's avatar
jpd committed
724
void PLModel::updateChildren( PLItem *root )
zorglub's avatar
zorglub committed
725
{
726
    playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
jpd's avatar
jpd committed
727
    updateChildren( p_node, root );
zorglub's avatar
zorglub committed
728
729
}

zorglub's avatar
Qt4:    
zorglub committed
730
/* This function must be entered WITH the playlist lock */
jpd's avatar
jpd committed
731
void PLModel::updateChildren( playlist_item_t *p_node, PLItem *root )
zorglub's avatar
zorglub committed
732
{
733
    playlist_item_t *p_item = playlist_CurrentPlayingItem(p_playlist);
zorglub's avatar
zorglub committed
734
735
    for( int i = 0; i < p_node->i_children ; i++ )
    {
zorglub's avatar
zorglub committed
736
        if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
737
738
        PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
        root->appendChild( newItem );
739
740
741
742
743
        if( p_item && newItem->p_input == p_item->p_input )
        {
            currentItem = newItem;
            emit currentChanged( index( currentItem, 0 ) );
        }
744
        if( p_node->pp_children[i]->i_children != -1 )
jpd's avatar
jpd committed
745
            updateChildren( p_node->pp_children[i], newItem );
zorglub's avatar
zorglub committed
746
747
748
    }
}

749
/* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
750
void PLModel::updateTreeItem( PLItem *item )
zorglub's avatar
zorglub committed
751
{
752
    if( !item ) return;
753
    emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
zorglub's avatar
zorglub committed
754
}
zorglub's avatar
zorglub committed
755

zorglub's avatar
zorglub committed
756
757
758
759
760
761
762
763
764
765
/************************* 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 )
{
766
767
    if( !canEdit() ) return;

zorglub's avatar
zorglub committed
768
769
770
771
    for( int i = selected.size() -1 ; i >= 0; i-- )
    {
        QModelIndex index = selected[i];
        if( index.column() != 0 ) continue;
772
        PLItem *item = getItem( index );
zorglub's avatar
zorglub committed
773
774
775
776
777
778
        if( item )
        {
            if( item->children.size() )
                recurseDelete( item->children, &selected );
            doDeleteItem( item, &selected );
        }
779
        if( i > selected.size() ) i = selected.size();
zorglub's avatar
zorglub committed
780
781
782
    }
}

783
void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
zorglub's avatar
zorglub committed
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
{
    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;
800
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
zorglub's avatar
zorglub committed
801
802
    if( !p_item )
    {
803
804
        PL_UNLOCK;
        return;
zorglub's avatar
zorglub committed
805
806
    }
    if( p_item->i_children == -1 )
807
        playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
zorglub's avatar
zorglub committed
808
    else
809
        playlist_NodeDelete( p_playlist, p_item, true, false );
810
    PL_UNLOCK;
811

zorglub's avatar
zorglub committed
812
    /* And finally, remove it from the tree */
jpd's avatar
jpd committed
813
    removeItem( item );
zorglub's avatar
zorglub committed
814
815
}

zorglub's avatar
zorglub committed
816
817
/******* Volume III: Sorting and searching ********/
void PLModel::sort( int column, Qt::SortOrder order )
818
819
820
821
822
{
    sort( rootItem->i_id, column, order );
}

void PLModel::sort( int i_root_id, int column, Qt::SortOrder order )
zorglub's avatar
zorglub committed
823
{
824
825
    int meta = columnToMeta( column );
    if( meta == COLUMN_END ) return;
826

jpd's avatar
jpd committed
827
    PLItem *item = findById( rootItem, i_root_id );
828
829
830
831
832
833
    if( !item ) return;
    QModelIndex qIndex = index( item, 0 );
    int count = item->children.size();
    if( count )
    {
        beginRemoveRows( qIndex, 0, count - 1 );
834
        item->removeChildren();
835
836
        endRemoveRows( );
    }
837

zorglub's avatar
zorglub committed
838
839
    PL_LOCK;
    {
840
        playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
841
                                                        i_root_id );
842
        if( p_root )
843
        {
Rafaël Carré's avatar
Rafaël Carré committed
844
            playlist_RecursiveNodeSort( p_playlist, p_root,
845
                                        i_column_sorting( meta ),
846
847
                                        order == Qt::AscendingOrder ?
                                            ORDER_NORMAL : ORDER_REVERSE );
848
        }
zorglub's avatar
zorglub committed
849
    }
850
851
852
    if( count )
    {
        beginInsertRows( qIndex, 0, count - 1 );
jpd's avatar
jpd committed
853
        updateChildren( item );
854
855
        endInsertRows( );
    }
856
    PL_UNLOCK;
zorglub's avatar
zorglub committed
857
858
}

859
void PLModel::search( const QString& search_text )
zorglub's avatar
zorglub committed
860
861
862
{
    /** \todo Fire the search with a small delay ? */
    PL_LOCK;
863
864
    {
        playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
865
                                                        rootItem->i_id );
866
        assert( p_root );
867
        const char *psz_name = search_text.toUtf8().data();
868
869
        playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
    }
zorglub's avatar
zorglub committed
870
871
872
873
    PL_UNLOCK;
    rebuild();
}

zorglub's avatar
zorglub committed
874
875
876
/*********** Popup *********/
void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
{
877
878
    int i_id = index.isValid() ? itemId( index ) : rootItem->i_id;

879
    PL_LOCK;
880
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
881
    if( !p_item )
zorglub's avatar
zorglub committed
882
    {
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
        PL_UNLOCK; return;
    }
    i_popup_item = index.isValid() ? p_item->i_id : -1;
    i_popup_parent = index.isValid() ?
        ( p_item->p_parent ? p_item->p_parent->i_id : -1 ) :
        ( p_item->i_id );
    i_popup_column = index.column();
    /* check whether we are in tree view */
    bool tree = false;
    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;
    }
    PL_UNLOCK;
jpd's avatar
jpd committed
899

900
    current_selection = list;
jpd's avatar
jpd committed
901
902

    QMenu menu;
903
904
    if( i_popup_item > -1 )
    {
jpd's avatar
jpd committed
905
906
907
908
909
910
911
912
913
        menu.addAction( qtr(I_POP_PLAY), this, SLOT( popupPlay() ) );
        menu.addAction( qtr(I_POP_DEL), this, SLOT( popupDel() ) );
        menu.addSeparator();
        menu.addAction( qtr(I_POP_STREAM), this, SLOT( popupStream() ) );
        menu.addAction( qtr(I_POP_SAVE), this, SLOT( popupSave() ) );
        menu.addSeparator();
        menu.addAction( qtr(I_POP_INFO), this, SLOT( popupInfo() ) );
        menu.addSeparator();
        QMenu *sort_menu = menu.addMenu( qtr( "Sort by ") +
914
            qfu( psz_column_title( columnToMeta( index.column() ) ) ) );
915
916
917
918
        sort_menu->addAction( qtr( "Ascending" ),
            this, SLOT( popupSortAsc() ) );
        sort_menu->addAction( qtr( "Descending" ),
            this, SLOT( popupSortDesc() ) );
zorglub's avatar
zorglub committed
919
    }
jpd's avatar
jpd committed
920
921
    if( tree && canEdit() )
        menu.addAction( qtr(I_POP_ADD), this, SLOT( popupAddNode() ) );
922
923
    if( i_popup_item > -1 )
    {
jpd's avatar
jpd committed
924
925
        menu.addSeparator();
        menu.addAction( qtr( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
926
    }
jpd's avatar
jpd committed
927
    if( !menu.isEmpty() ) menu.exec( point );
zorglub's avatar
zorglub committed
928
929
930
931
932
933
}

void PLModel::popupDel()
{
    doDelete( current_selection );
}
934

zorglub's avatar
zorglub committed
935
936
937
v