playlist_model.cpp 31.4 KB
Newer Older
Clément Stenac's avatar
Clément Stenac committed
1
/*****************************************************************************
2
 * playlist_model.cpp : Manage playlist model
Clément Stenac's avatar
Clément Stenac committed
3
 ****************************************************************************
4
 * Copyright (C) 2006-2007 the VideoLAN team
5
 * $Id$
Clément Stenac's avatar
Clément Stenac committed
6 7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Ilkka Ollakkka <ileoo (at) videolan dot org>
9
 *          Jakob Leben <jleben@videolan.org>
Clément Stenac's avatar
Clément Stenac 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
Clément Stenac's avatar
Clément Stenac committed
29

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

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
37
#include "pixmaps/types/type_unknown.xpm"
Clément Stenac's avatar
Clément Stenac 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"

Clément Stenac's avatar
Clément Stenac committed
48 49
QIcon PLModel::icons[ITEM_TYPE_NUMBER];

Clément Stenac's avatar
Clément Stenac committed
50 51 52 53 54 55 56 57 58
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
 *************************************************************************/

59 60 61 62 63 64 65 66 67 68
/*
  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 )
Clément Stenac's avatar
Clément Stenac committed
69
{
70 71
    p_intf            = _p_intf;
    p_playlist        = _p_playlist;
Clément Stenac's avatar
Clément Stenac committed
72 73
    i_cached_id       = -1;
    i_cached_input_id = -1;
74
    i_popup_item      = i_popup_parent = -1;
75
    currentItem       = NULL;
76 77

    rootItem          = NULL; /* PLItem rootItem, will be set in rebuild( ) */
Clément Stenac's avatar
Clément Stenac committed
78

79
    /* Icons initialization */
80
#define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( QPixmap( x ) )
81
    ADD_ICON( UNKNOWN , type_unknown_xpm );
82 83 84 85 86 87 88 89
    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" );
90
#undef ADD_ICON
Clément Stenac's avatar
Clément Stenac committed
91

92
    rebuild( p_root, true );
93
    CONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
Jakob Leben's avatar
Jakob Leben committed
94
            this, processInputItemUpdate( input_item_t *) );
95
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
Jakob Leben's avatar
Jakob Leben committed
96
            this, processInputItemUpdate( input_thread_t* ) );
Clément Stenac's avatar
Clément Stenac committed
97 98 99 100 101 102 103 104
}

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

105 106
Qt::DropActions PLModel::supportedDropActions() const
{
107
    return Qt::CopyAction; /* Why not Qt::MoveAction */
108 109
}

110
Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
111
{
112 113
    Qt::ItemFlags flags = QAbstractItemModel::flags( index );

114
    PLItem *item = index.isValid() ? getItem( index ) : rootItem;
115

116 117 118 119 120 121
    input_item_t *pl_input =
        p_playlist->p_local_category ?
        p_playlist->p_local_category->p_input : NULL;
    input_item_t *ml_input =
        p_playlist->p_ml_category ?
        p_playlist->p_ml_category->p_input : NULL;
122

123
    if( ( pl_input && rootItem->p_input == pl_input ) ||
124
              ( ml_input && rootItem->p_input == ml_input ) )
125
    {
126 127 128
        PL_LOCK;
        playlist_item_t *plItem =
            playlist_ItemGetById( p_playlist, item->i_id );
129 130 131 132

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

133
        PL_UNLOCK;
134

135
    }
136
    flags |= Qt::ItemIsDragEnabled;
137 138

    return flags;
139 140 141 142 143
}

QStringList PLModel::mimeTypes() const
{
    QStringList types;
144
    types << "vlc/qt-playlist-item";
145 146 147
    return types;
}

148
QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
149 150 151
{
    QMimeData *mimeData = new QMimeData();
    QByteArray encodedData;
152
    QDataStream stream( &encodedData, QIODevice::WriteOnly );
153
    QModelIndexList list;
154

155
    foreach( const QModelIndex &index, indexes ) {
156
        if( index.isValid() && index.column() == 0 )
157 158 159 160 161 162
            list.append(index);
    }

    qSort(list);

    foreach( const QModelIndex &index, list ) {
163 164
        PLItem *item = getItem( index );
        stream.writeRawData( (char*) &item, sizeof( PLItem* ) );
165
    }
166
    mimeData->setData( "vlc/qt-playlist-item", encodedData );
167 168 169
    return mimeData;
}

170
/* Drop operation */
171
bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
172
                           int row, int column, const QModelIndex &parent )
173
{
174
    if( data->hasFormat( "vlc/qt-playlist-item" ) )
175
    {
176
        if( action == Qt::IgnoreAction )
177 178
            return true;

179
        PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
180

181 182 183
        PL_LOCK;
        playlist_item_t *p_parent =
            playlist_ItemGetById( p_playlist, parentItem->i_id );
184 185 186 187 188
        if( !p_parent || p_parent->i_children == -1 )
        {
            PL_UNLOCK;
            return false;
        }
189

190
        bool copy = false;
191 192 193 194 195 196 197 198 199 200
        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;
201

202
        QByteArray encodedData = data->data( "vlc/qt-playlist-item" );
203
        if( copy )
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
            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,
            p_parent == p_playlist->p_local_category, true );
    }
    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 )
250
        {
251 252
            if( climber == item ) break;
            climber = climber->parentItem;
253
        }
254 255 256 257 258 259 260 261 262
        if( climber ) continue;

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

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

Jakob Leben's avatar
Jakob Leben committed
263
        takeItem( item );
264 265 266 267 268 269 270 271
    }
    int count = ids.size();
    if( count )
    {
        playlist_item_t *pp_items[count];

        PL_LOCK;
        for( int i = 0; i < count; i++ )
272
        {
273 274
            playlist_item_t *p_item = playlist_ItemGetById( p_playlist, ids[i] );
            if( !p_item )
275
            {
276 277
                PL_UNLOCK;
                return;
278
            }
279
            pp_items[i] = p_item;
280
        }
281 282 283 284
        playlist_item_t *p_parent =
            playlist_ItemGetById( p_playlist, target->i_id );
        playlist_TreeMoveMany( p_playlist, count, pp_items, p_parent,
            new_pos );
285
        PL_UNLOCK;
286

Jakob Leben's avatar
Jakob Leben committed
287
        insertChildren( target, model_items, model_pos );
288
    }
Clément Stenac's avatar
Clément Stenac committed
289
}
290

291
/* remove item with its id */
Clément Stenac's avatar
Clément Stenac committed
292 293
void PLModel::removeItem( int i_id )
{
Jakob Leben's avatar
Jakob Leben committed
294 295
    PLItem *item = findById( rootItem, i_id );
    removeItem( item );
Clément Stenac's avatar
Clément Stenac committed
296
}
297

298
/* callbacks and slots */
Clément Stenac's avatar
Clément Stenac committed
299 300
void PLModel::addCallbacks()
{
Clément Stenac's avatar
Clément Stenac committed
301
    /* One item has been updated */
302 303
    var_AddCallback( p_playlist, "playlist-item-append", ItemAppended, this );
    var_AddCallback( p_playlist, "playlist-item-deleted", ItemDeleted, this );
Clément Stenac's avatar
Clément Stenac committed
304 305
}

Clément Stenac's avatar
Clément Stenac committed
306
void PLModel::delCallbacks()
Clément Stenac's avatar
Clément Stenac committed
307
{
308 309
    var_DelCallback( p_playlist, "playlist-item-append", ItemAppended, this );
    var_DelCallback( p_playlist, "playlist-item-deleted", ItemDeleted, this );
Clément Stenac's avatar
Clément Stenac committed
310 311
}

312 313 314
void PLModel::activateItem( const QModelIndex &index )
{
    assert( index.isValid() );
315
    PLItem *item = getItem( index );
316 317
    assert( item );
    PL_LOCK;
318
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
Clément Stenac's avatar
Clément Stenac committed
319 320 321
    activateItem( p_item );
    PL_UNLOCK;
}
322

Clément Stenac's avatar
Clément Stenac committed
323 324 325 326
/* Must be entered with lock */
void PLModel::activateItem( playlist_item_t *p_item )
{
    if( !p_item ) return;
327 328 329 330 331 332 333
    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 )
334
        playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked,
335
                          p_parent, p_item );
336
}
Clément Stenac's avatar
Clément Stenac committed
337

338
/****************** Base model mandatory implementations *****************/
339
QVariant PLModel::data( const QModelIndex &index, int role ) const
Clément Stenac's avatar
Clément Stenac committed
340
{
341
    if( !index.isValid() ) return QVariant();
342
    PLItem *item = getItem( index );
Clément Stenac's avatar
Clément Stenac committed
343 344
    if( role == Qt::DisplayRole )
    {
345
        int metadata = columnToMeta( index.column() );
346
        if( metadata == COLUMN_END ) return QVariant();
347 348 349 350 351 352 353

        QString returninfo;
        if( metadata == COLUMN_NUMBER )
            returninfo = QString::number( index.row() + 1 );
        else
        {
            char *psz = psz_column_meta( item->p_input, metadata );
354
            returninfo = qfu( psz );
355 356 357
            free( psz );
        }
        return QVariant( returninfo );
Clément Stenac's avatar
Clément Stenac committed
358 359 360
    }
    else if( role == Qt::DecorationRole && index.column() == 0  )
    {
361
        /* Use to segfault here because i_type wasn't always initialized */
362 363
        if( item->p_input->i_type >= 0 )
            return QVariant( PLModel::icons[item->p_input->i_type] );
Clément Stenac's avatar
Clément Stenac committed
364
    }
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
365 366
    else if( role == Qt::FontRole )
    {
367
        if( isCurrent( index ) )
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
368 369 370 371
        {
            QFont f; f.setBold( true ); return QVariant( f );
        }
    }
Clément Stenac's avatar
Clément Stenac committed
372
    return QVariant();
Clément Stenac's avatar
Clément Stenac committed
373 374
}

375
bool PLModel::isCurrent( const QModelIndex &index ) const
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
376
{
377
    if( !currentItem ) return false;
378
    return getItem( index )->p_input == currentItem->p_input;
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
379 380
}

381
int PLModel::itemId( const QModelIndex &index ) const
Clément Stenac's avatar
Clément Stenac committed
382
{
383
    return getItem( index )->i_id;
384
}
Clément Stenac's avatar
Clément Stenac committed
385 386

QVariant PLModel::headerData( int section, Qt::Orientation orientation,
387
                              int role ) const
Clément Stenac's avatar
Clément Stenac committed
388
{
389 390 391
    if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
        return QVariant();

392
    int meta_col = columnToMeta( section );
393

394
    if( meta_col == COLUMN_END ) return QVariant();
395

396
    return QVariant( qfu( psz_column_title( meta_col ) ) );
Clément Stenac's avatar
Clément Stenac committed
397 398
}

399
QModelIndex PLModel::index( int row, int column, const QModelIndex &parent )
Clément Stenac's avatar
Clément Stenac committed
400 401
                  const
{
402
    PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
Clément Stenac's avatar
Clément Stenac committed
403

404 405 406
    PLItem *childItem = parentItem->child( row );
    if( childItem )
        return createIndex( row, column, childItem );
Clément Stenac's avatar
Clément Stenac committed
407 408 409 410 411
    else
        return QModelIndex();
}

/* Return the index of a given item */
412
QModelIndex PLModel::index( PLItem *item, int column ) const
Clément Stenac's avatar
Clément Stenac committed
413 414 415 416
{
    if( !item ) return QModelIndex();
    const PLItem *parent = item->parent();
    if( parent )
417 418
        return createIndex( parent->children.lastIndexOf( item ),
                            column, item );
Clément Stenac's avatar
Clément Stenac committed
419 420 421
    return QModelIndex();
}

422
QModelIndex PLModel::parent( const QModelIndex &index ) const
Clément Stenac's avatar
Clément Stenac committed
423
{
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
424
    if( !index.isValid() ) return QModelIndex();
Clément Stenac's avatar
Clément Stenac committed
425

426
    PLItem *childItem = getItem( index );
427 428 429 430 431 432
    if( !childItem )
    {
        msg_Err( p_playlist, "NULL CHILD" );
        return QModelIndex();
    }

Clément Stenac's avatar
Clément Stenac committed
433
    PLItem *parentItem = childItem->parent();
Clément Stenac's avatar
Clément Stenac committed
434
    if( !parentItem || parentItem == rootItem ) return QModelIndex();
435
    if( !parentItem->parentItem )
436
    {
Clément Stenac's avatar
Clément Stenac committed
437 438
        msg_Err( p_playlist, "No parent parent, trying row 0 " );
        msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
439 440
        return createIndex( 0, 0, parentItem );
    }
Clément Stenac's avatar
Clément Stenac committed
441 442
    QModelIndex ind = createIndex(parentItem->row(), 0, parentItem);
    return ind;
Clément Stenac's avatar
Clément Stenac committed
443 444
}

445
int PLModel::columnCount( const QModelIndex &i) const
Clément Stenac's avatar
Clément Stenac committed
446
{
447
    return columnFromMeta( COLUMN_END );
Clément Stenac's avatar
Clément Stenac committed
448 449
}

450
int PLModel::rowCount( const QModelIndex &parent ) const
Clément Stenac's avatar
Clément Stenac committed
451
{
452
    PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
Clément Stenac's avatar
Clément Stenac committed
453 454 455
    return parentItem->childCount();
}

456 457 458 459 460
QStringList PLModel::selectedURIs()
{
    QStringList lst;
    for( int i = 0; i < current_selection.size(); i++ )
    {
461
        PLItem *item = getItem( current_selection[i] );
462
        if( item )
463
        {
464
            PL_LOCK;
465 466 467 468 469 470
            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 )
                {
471
                    lst.append( qfu(psz) );
472 473 474
                    free( psz );
                }
            }
475
            PL_UNLOCK;
476 477 478 479 480
        }
    }
    return lst;
}

Clément Stenac's avatar
Clément Stenac committed
481 482 483 484
/************************* General playlist status ***********************/

bool PLModel::hasRandom()
{
Rémi Duraffort's avatar
Rémi Duraffort committed
485
    return var_GetBool( p_playlist, "random" );
Clément Stenac's avatar
Clément Stenac committed
486 487 488
}
bool PLModel::hasRepeat()
{
Rémi Duraffort's avatar
Rémi Duraffort committed
489
    return var_GetBool( p_playlist, "repeat" );
Clément Stenac's avatar
Clément Stenac committed
490 491 492
}
bool PLModel::hasLoop()
{
Rémi Duraffort's avatar
Rémi Duraffort committed
493
    return var_GetBool( p_playlist, "loop" );
Clément Stenac's avatar
Clément Stenac committed
494 495 496
}
void PLModel::setLoop( bool on )
{
497
    var_SetBool( p_playlist, "loop", on ? true:false );
498
    config_PutInt( p_playlist, "loop", on ? 1: 0 );
Clément Stenac's avatar
Clément Stenac committed
499 500 501
}
void PLModel::setRepeat( bool on )
{
502
    var_SetBool( p_playlist, "repeat", on ? true:false );
503
    config_PutInt( p_playlist, "repeat", on ? 1: 0 );
Clément Stenac's avatar
Clément Stenac committed
504 505 506
}
void PLModel::setRandom( bool on )
{
507
    var_SetBool( p_playlist, "random", on ? true:false );
508
    config_PutInt( p_playlist, "random", on ? 1: 0 );
Clément Stenac's avatar
Clément Stenac committed
509 510
}

Clément Stenac's avatar
Clément Stenac committed
511 512
/************************* Lookups *****************************/

Jakob Leben's avatar
Jakob Leben committed
513
PLItem *PLModel::findById( PLItem *root, int i_id )
Clément Stenac's avatar
Clément Stenac committed
514
{
Jakob Leben's avatar
Jakob Leben committed
515
    return findInner( root, i_id, false );
Clément Stenac's avatar
Clément Stenac committed
516 517
}

Jakob Leben's avatar
Jakob Leben committed
518
PLItem *PLModel::findByInput( PLItem *root, int i_id )
Clément Stenac's avatar
Clément Stenac committed
519
{
Jakob Leben's avatar
Jakob Leben committed
520
    PLItem *result = findInner( root, i_id, true );
521
    return result;
Clément Stenac's avatar
Clément Stenac committed
522 523
}

524 525
#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; }
Clément Stenac's avatar
Clément Stenac committed
526

Jakob Leben's avatar
Jakob Leben committed
527
PLItem * PLModel::findInner( PLItem *root, int i_id, bool b_input )
Clément Stenac's avatar
Clément Stenac committed
528
{
529
    if( !root ) return NULL;
530
    if( ( !b_input && i_cached_id == i_id) ||
Clément Stenac's avatar
Clément Stenac committed
531
        ( b_input && i_cached_input_id ==i_id ) )
532
    {
Clément Stenac's avatar
Clément Stenac committed
533
        return b_input ? p_cached_item_bi : p_cached_item;
534
    }
Clément Stenac's avatar
Clément Stenac committed
535 536 537 538 539 540

    if( !b_input && root->i_id == i_id )
    {
        CACHE( i_id, root );
        return root;
    }
541
    else if( b_input && root->p_input->i_id == i_id )
Clément Stenac's avatar
Clément Stenac committed
542 543 544 545 546 547 548 549 550 551 552 553 554
    {
        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;
        }
555
        else if( b_input && (*it)->p_input->i_id == i_id )
Clément Stenac's avatar
Clément Stenac committed
556 557
        {
            ICACHE( i_id, (*it) );
558
            return p_cached_item_bi;
Clément Stenac's avatar
Clément Stenac committed
559 560 561
        }
        if( (*it)->children.size() )
        {
Jakob Leben's avatar
Jakob Leben committed
562
            PLItem *childFound = findInner( (*it), i_id, b_input );
563
            if( childFound )
Clément Stenac's avatar
Clément Stenac committed
564 565
            {
                if( b_input )
566
                    ICACHE( i_id, childFound )
Clément Stenac's avatar
Clément Stenac committed
567
                else
568
                    CACHE( i_id, childFound )
Clément Stenac's avatar
Clément Stenac committed
569
                return childFound;
570
            }
Clément Stenac's avatar
Clément Stenac committed
571
        }
Clément Stenac's avatar
Clément Stenac committed
572
        it++;
Clément Stenac's avatar
Clément Stenac committed
573 574 575 576 577 578
    }
    return NULL;
}
#undef CACHE
#undef ICACHE

579
PLItem *PLModel::getItem( QModelIndex index )
580 581 582 583 584
{
    assert( index.isValid() );
    return static_cast<PLItem*>( index.internalPointer() );
}

585
int PLModel::columnToMeta( int _column ) const
586
{
587
    int meta = 1;
588
    int column = 0;
589

590
    while( column != _column && meta != COLUMN_END )
591
    {
592
        meta <<= 1;
593
        column++;
594 595 596 597 598
    }

    return meta;
}

599
int PLModel::columnFromMeta( int meta_col ) const
600 601
{
    int meta = 1;
602
    int column = 0;
603

604
    while( meta != meta_col && meta != COLUMN_END )
605 606
    {
        meta <<= 1;
607
        column++;
608 609
    }

610
    return column;
611
}
Clément Stenac's avatar
Clément Stenac committed
612 613 614 615

/************************* Updates handling *****************************/
void PLModel::customEvent( QEvent *event )
{
Clément Stenac's avatar
Clément Stenac committed
616
    int type = event->type();
617
    if( type != ItemAppend_Type &&
618
        type != ItemDelete_Type )
Clément Stenac's avatar
Clément Stenac committed
619 620
        return;

621
    PLEvent *ple = static_cast<PLEvent *>(event);
Clément Stenac's avatar
Clément Stenac committed
622

623
    if( type == ItemAppend_Type )
Jakob Leben's avatar
Jakob Leben committed
624
        processItemAppend( &ple->add );
625
    else if( type == ItemDelete_Type )
Jakob Leben's avatar
Jakob Leben committed
626
        processItemRemoval( ple->i_id );
Clément Stenac's avatar
Clément Stenac committed
627 628
}

Clément Stenac's avatar
Clément Stenac committed
629
/**** Events processing ****/
Jakob Leben's avatar
Jakob Leben committed
630
void PLModel::processInputItemUpdate( input_thread_t *p_input )
631 632
{
    if( !p_input ) return;
Jakob Leben's avatar
Jakob Leben committed
633
    processInputItemUpdate( input_GetItem( p_input ) );
634 635
    if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
    {
Jakob Leben's avatar
Jakob Leben committed
636
        PLItem *item = findByInput( rootItem, input_GetItem( p_input )->i_id );
637
        currentItem = item;
638 639
        emit currentChanged( index( item, 0 ) );
    }
640 641 642 643
    else
    {
        currentItem = NULL;
    }
644
}
Jakob Leben's avatar
Jakob Leben committed
645
void PLModel::processInputItemUpdate( input_item_t *p_item )
Clément Stenac's avatar
Clément Stenac committed
646
{
647
    if( !p_item ||  p_item->i_id <= 0 ) return;
Jakob Leben's avatar
Jakob Leben committed
648
    PLItem *item = findByInput( rootItem, p_item->i_id );
649
    if( item )
650
        updateTreeItem( item );
Clément Stenac's avatar
Clément Stenac committed
651 652
}

Jakob Leben's avatar
Jakob Leben committed
653
void PLModel::processItemRemoval( int i_id )
Clément Stenac's avatar
Clément Stenac committed
654
{
Clément Stenac's avatar
Clément Stenac committed
655
    if( i_id <= 0 ) return;
Clément Stenac's avatar
Clément Stenac committed
656 657
    if( i_id == i_cached_id ) i_cached_id = -1;
    i_cached_input_id = -1;
658 659

    removeItem( i_id );
Clément Stenac's avatar
Clément Stenac committed
660 661
}

Jakob Leben's avatar
Jakob Leben committed
662
void PLModel::processItemAppend( const playlist_add_t *p_add )
Clément Stenac's avatar
Clément Stenac committed
663 664
{
    playlist_item_t *p_item = NULL;
Clément Stenac's avatar
Clément Stenac committed
665
    PLItem *newItem = NULL;
Clément Stenac's avatar
Clément Stenac committed
666

Jakob Leben's avatar
Jakob Leben committed
667
    PLItem *nodeItem = findById( rootItem, p_add->i_node );
668
    if( !nodeItem ) return;
Clément Stenac's avatar
Clément Stenac committed
669

670 671 672
		foreach( PLItem *existing, nodeItem->children )
			if( existing->i_id == p_add->i_item ) { return; }

673
    PL_LOCK;
674
    p_item = playlist_ItemGetById( p_playlist, p_add->i_item );
Clément Stenac's avatar
Clément Stenac committed
675
    if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
676

677
    newItem = new PLItem( p_item, nodeItem );
678 679
    PL_UNLOCK;

680
    beginInsertRows( index( nodeItem, 0 ), nodeItem->childCount(), nodeItem->childCount() );
Clément Stenac's avatar
Clément Stenac committed
681
    nodeItem->appendChild( newItem );
682
    endInsertRows();
683
    updateTreeItem( newItem );
684
    return;
Clément Stenac's avatar
Clément Stenac committed
685
end:
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
686
    PL_UNLOCK;
Clément Stenac's avatar
Clément Stenac committed
687 688
    return;
}
Clément Stenac's avatar
Clément Stenac committed
689

Clément Stenac's avatar
Clément Stenac committed
690 691 692

void PLModel::rebuild()
{
693
    rebuild( NULL, false );
Clément Stenac's avatar
Clément Stenac committed
694 695
}

696
void PLModel::rebuild( playlist_item_t *p_root, bool b_first )
Clément Stenac's avatar
Clément Stenac committed
697
{
Christophe Mutricy's avatar
Christophe Mutricy committed
698
    playlist_item_t* p_item;
699 700 701 702 703 704
    /* Remove callbacks before locking to avoid deadlocks
       The first time the callbacks are not present so
       don't try to delete them */
    if( !b_first )
        delCallbacks();

Clément Stenac's avatar
Clément Stenac committed
705 706 707
    /* Invalidate cache */
    i_cached_id = i_cached_input_id = -1;

708
    if( rootItem ) rootItem->removeChildren();
709

710
    PL_LOCK;
Clément Stenac's avatar
Clément Stenac committed
711 712
    if( p_root )
    {
Rafaël Carré's avatar
Rafaël Carré committed
713
        delete rootItem;
714
        rootItem = new PLItem( p_root );
Clément Stenac's avatar
Clément Stenac committed
715 716
    }
    assert( rootItem );
Clément Stenac's avatar
Clément Stenac committed
717
    /* Recreate from root */
Jakob Leben's avatar
Jakob Leben committed
718
    updateChildren( rootItem );
719
    if( (p_item = playlist_CurrentPlayingItem(p_playlist)) )
Jakob Leben's avatar
Jakob Leben committed
720
        currentItem = findByInput( rootItem, p_item->p_input->i_id );
721 722
    else
        currentItem = NULL;
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
723
    PL_UNLOCK;
Clément Stenac's avatar
Clément Stenac committed
724 725

    /* And signal the view */
726
    reset();
727

728
    emit currentChanged( index( currentItem, 0 ) );
729

Clément Stenac's avatar
Clément Stenac committed
730 731 732
    addCallbacks();
}

Jakob Leben's avatar
Jakob Leben committed
733
void PLModel::takeItem( PLItem *item )
734 735 736 737 738 739 740 741 742 743 744
{
    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();
}

Jakob Leben's avatar
Jakob Leben committed
745
void PLModel::insertChildren( PLItem *node, QList<PLItem*>& items, int i_pos )
746 747 748 749 750 751 752 753 754 755 756 757 758
{
    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();
}

Jakob Leben's avatar
Jakob Leben committed
759
void PLModel::removeItem( PLItem *item )
760 761
{
    if( !item ) return;
762

763
    if( currentItem == item )
764 765 766 767
    {
        currentItem = NULL;
        emit currentChanged( QModelIndex() );
    }
768 769 770 771 772 773 774 775 776

    if( item->parentItem ) item->parentItem->removeChild( item );
    else delete item;

    if(item == rootItem)
    {
        rootItem = NULL;
        reset();
    }
777 778
}

Clément Stenac's avatar
Qt4:  
Clément Stenac committed
779
/* This function must be entered WITH the playlist lock */
Jakob Leben's avatar
Jakob Leben committed
780
void PLModel::updateChildren( PLItem *root )
Clément Stenac's avatar
Clément Stenac committed
781
{
782
    playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
Jakob Leben's avatar
Jakob Leben committed
783
    updateChildren( p_node, root );
Clément Stenac's avatar
Clément Stenac committed
784 785
}

Clément Stenac's avatar
Qt4:  
Clément Stenac committed
786
/* This function must be entered WITH the playlist lock */
Jakob Leben's avatar
Jakob Leben committed
787
void PLModel::updateChildren( playlist_item_t *p_node, PLItem *root )
Clément Stenac's avatar
Clément Stenac committed
788
{
789
    playlist_item_t *p_item = playlist_CurrentPlayingItem(p_playlist);
Clément Stenac's avatar
Clément Stenac committed
790 791
    for( int i = 0; i < p_node->i_children ; i++ )
    {
Clément Stenac's avatar
Clément Stenac committed
792
        if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
793 794
        PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
        root->appendChild( newItem );
795 796 797 798 799
        if( p_item && newItem->p_input == p_item->p_input )
        {
            currentItem = newItem;
            emit currentChanged( index( currentItem, 0 ) );
        }
800
        if( p_node->pp_children[i]->i_children != -1 )
Jakob Leben's avatar
Jakob Leben committed
801
            updateChildren( p_node->pp_children[i], newItem );
Clément Stenac's avatar
Clément Stenac committed
802 803 804
    }
}

805
/* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
806
void PLModel::updateTreeItem( PLItem *item )
Clément Stenac's avatar
Clément Stenac committed
807
{
808
    if( !item ) return;
809
    emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
Clément Stenac's avatar
Clément Stenac committed
810
}
811

812 813 814 815 816 817 818 819 820 821 822 823 824 825
/************************* 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;
826
        PLItem *item = getItem( index );
827 828 829 830 831 832
        if( item )
        {
            if( item->children.size() )
                recurseDelete( item->children, &selected );
            doDeleteItem( item, &selected );
        }
833
        if( i > selected.size() ) i = selected.size();
834 835 836
    }
}

837
void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
{
    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;
854
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
855 856
    if( !p_item )
    {
857 858
        PL_UNLOCK;
        return;
859 860
    }
    if( p_item->i_children == -1 )
861
        playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
862
    else
863
        playlist_NodeDelete( p_playlist, p_item, true, false );
864
    PL_UNLOCK;
865
    /* And finally, remove it from the tree */
866 867
    int itemIndex = item->parentItem->children.indexOf( item );
    beginRemoveRows( index( item->parentItem, 0), itemIndex, itemIndex );
Jakob Leben's avatar
Jakob Leben committed
868
    removeItem( item );
869
    endRemoveRows();
870 871
}

Clément Stenac's avatar
Clément Stenac committed
872 873
/******* Volume III: Sorting and searching ********/
void PLModel::sort( int column, Qt::SortOrder order )
874 875 876 877 878
{
    sort( rootItem->i_id, column, order );
}

void PLModel::sort( int i_root_id, int column, Qt::SortOrder order )
Clément Stenac's avatar
Clément Stenac committed
879
{
880 881
    int meta = columnToMeta( column );
    if( meta == COLUMN_END ) return;
882

Jakob Leben's avatar
Jakob Leben committed
883
    PLItem *item = findById( rootItem, i_root_id );
884 885 886 887 888 889
    if( !item ) return;
    QModelIndex qIndex = index( item, 0 );
    int count = item->children.size();
    if( count )
    {
        beginRemoveRows( qIndex, 0, count - 1 );
890
        item->removeChildren();
891 892
        endRemoveRows( );
    }
893

Clément Stenac's avatar
Clément Stenac committed
894 895
    PL_LOCK;
    {
896
        playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
897
                                                        i_root_id );
898
        if( p_root )
899
        {
Rafaël Carré's avatar
Rafaël Carré committed
900
            playlist_RecursiveNodeSort( p_playlist, p_root,
901
                                        i_column_sorting( meta ),
902 903
                                        order == Qt::AscendingOrder ?
                                            ORDER_NORMAL : ORDER_REVERSE );
904
        }
Clément Stenac's avatar
Clément Stenac committed
905
    }
906 907 908
    if( count )
    {
        beginInsertRows( qIndex, 0, count - 1 );
Jakob Leben's avatar
Jakob Leben committed
909
        updateChildren( item );
910 911
        endInsertRows( );
    }
912
    PL_UNLOCK;
Clément Stenac's avatar
Clément Stenac committed
913 914
}

915
void PLModel::search( const QString& search_text )
Clément Stenac's avatar
Clément Stenac committed
916 917 918
{
    /** \todo Fire the search with a small delay ? */
    PL_LOCK;
919 920
    {
        playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
921
                                                        rootItem->i_id );
922
        assert( p_root );
923
        const char *psz_name = search_text.toUtf8().data();
924 925
        playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
    }
Clément Stenac's avatar
Clément Stenac committed
926 927 928 929
    PL_UNLOCK;
    rebuild();
}

Clément Stenac's avatar
Clément Stenac committed
930 931 932
/*********** Popup *********/
void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
{
933 934
    int i_id = index.isValid() ? itemId( index ) : rootItem->i_id;

935
    PL_LOCK;
936
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
937
    if( !p_item )
Clément Stenac's avatar
Clément Stenac committed
938
    {
939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
        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;
955

956 957 958 959 960 961 962 963 964 965 966 967 968
    current_selection = list;
    QMenu *menu = new QMenu;
    if( i_popup_item > -1 )
    {
        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 ") +
969
            qfu( psz_column_title( columnToMeta( index.column() ) ) ) );
970 971 972 973
        sort_menu->addAction( qtr( "Ascending" ),
            this, SLOT( popupSortAsc() ) );
        sort_menu->addAction( qtr( "Descending" ),
            this, SLOT( popupSortDesc() ) );
Clément Stenac's avatar
Clément Stenac committed
974
    }
975 976 977 978 979 980 981 982
    if( tree )
        menu->addAction( qtr(I_POP_ADD), this, SLOT( popupAddNode() ) );
    if( i_popup_item > -1 )
    {
        menu->addSeparator();
        menu->addAction( qtr( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
    }
    menu->popup( point );
Clément Stenac's avatar
Clément Stenac committed
983 984 985 986 987 988
}

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

Clément Stenac's avatar
Clément Stenac committed
990 991 992
void PLModel::popupPlay()
{
    PL_LOCK;
993 994
    {
        playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
995
                                                        i_popup_item );
996 997
        activateItem( p_item );
    }
Clément Stenac's avatar
Clément Stenac committed
998 999 1000
    PL_UNLOCK;
}

1001 1002
void PLModel::popupInfo()
{
1003
    PL_LOCK;
1004
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1005
                                                    i_popup_item );
1006 1007
    if( p_item )
    {
1008 1009 1010 1011 1012
        input_item_t* p_input = p_item->p_input;
        vlc_gc_incref( p_input );
        PL_UNLOCK;
        MediaInfoDialog *mid = new MediaInfoDialog( p_intf, p_input );
        vlc_gc_decref( p_input );
1013 1014
        mid->setParent( PlaylistDialog::getInstance( p_intf ),
                        Qt::Dialog );
1015
        mid->show();
1016 1017
    } else
        PL_UNLOCK;
1018 1019 1020 1021
}

void PLModel::popupStream()
{
1022 1023 1024 1025
    QStringList mrls = selectedURIs();
    if( !mrls.isEmpty() )
        THEDP->streamingDialog( NULL, mrls[0], false );

1026
}
1027

1028 1029
void PLModel::popupSave()
{
1030 1031
    QStringList mrls = selectedURIs();
    if( !mrls.isEmpty() )
1032
        THEDP->streamingDialog( NULL, mrls[0] );
1033 1034
}

1035 1036 1037
#include <QUrl>
#include <QFileInfo>
#include <QDesktopServices>
1038 1039
void PLModel::popupExplore()
{
1040
    PL_LOCK;
1041
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1042
                                                    i_popup_item );
1043 1044 1045 1046
    if( p_item )
    {
       input_item_t *p_input = p_item->p_input;
       char *psz_meta = input_item_GetURI( p_input );
1047
       PL_UNLOCK;
1048 1049
       if( psz_meta )
       {
1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
           const char *psz_access;
           const char *psz_demux;
           char  *psz_path;
           input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_meta );

           if( EMPTY_STR( psz_access ) ||
               !strncasecmp( psz_access, "file", 4 ) ||
               !strncasecmp( psz_access, "dire", 4 ) )
           {
               QFileInfo info( qfu( psz_meta ) );
               QDesktopServices::openUrl(
                               QUrl::fromLocalFile( info.absolutePath() ) );
           }
1063 1064 1065
           free( psz_meta );
       }
    }
1066 1067
    else
        PL_UNLOCK;
1068
}
1069

1070 1071 1072 1073 1074
#include <QInputDialog>
void PLModel::popupAddNode()
{
    bool ok;
    QString name = QInputDialog::getText( PlaylistDialog::getInstance( p_intf ),
1075
        qtr( I_POP_ADD ), qtr( "Enter name for new node:" ),
1076 1077 1078 1079
        QLineEdit::Normal, QString(), &ok);
    if( !ok || name.isEmpty() ) return;
    PL_LOCK;
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1080
                                                    i_popup_parent );
1081 1082 1083 1084 1085 1086
    if( p_item )
    {
        playlist_NodeCreate( p_playlist, qtu( name ), p_item, 0, NULL );
    }
    PL_UNLOCK;
}
1087 1088 1089

void PLModel::popupSortAsc()
{
1090
    sort( i_popup_parent, i_popup_column, Qt::AscendingOrder );
1091 1092 1093 1094
}

void PLModel::popupSortDesc()
{
1095
    sort( i_popup_parent, i_popup_column, Qt::DescendingOrder );
1096
}
Clément Stenac's avatar
Clément Stenac committed
1097
/**********************************************************************
1098
 * Playlist callbacks
Clément Stenac's avatar
Clément Stenac committed
1099 1100 1101 1102 1103 1104 1105
 **********************************************************************/

static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
                        vlc_value_t oval, vlc_value_t nval, void *param )
{
    PLModel *p_model = (PLModel *) param;
    PLEvent *event = new PLEvent( ItemDelete_Type, nval.i_int );
1106
    QApplication::postEvent( p_model, event );
Clément Stenac's avatar
Clément Stenac committed
1107 1108 1109 1110 1111 1112 1113
    return VLC_SUCCESS;
}

static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
                         vlc_value_t oval, vlc_value_t nval, void *param )
{
    PLModel *p_model = (PLModel *) param;
1114
    const playlist_add_t *p_add = (playlist_add_t *)nval.p_address;
Rémi Duraffort's avatar
Rémi Duraffort committed
1115
    PLEvent *event = new PLEvent( p_add );
1116
    QApplication::postEvent( p_model, event );
Clément Stenac's avatar
Clément Stenac committed
1117 1118
    return VLC_SUCCESS;
}
1119