selector.cpp 22 KB
Newer Older
1 2 3
/*****************************************************************************
 * selector.cpp : Playlist source selector
 ****************************************************************************
4
 * Copyright (C) 2006-2009 the VideoLAN team
5
 * $Id$
6 7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Jean-Baptiste Kempf
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
28

29
#include "qt4.hpp"
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
30 31 32
#include "components/playlist/selector.hpp"
#include "playlist_model.hpp"                /* plMimeData */
#include "input_manager.hpp"                 /* MainInputManager, for podcast */
33

34
#include <QApplication>
35 36
#include <QInputDialog>
#include <QMessageBox>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
37 38 39 40 41
#include <QMimeData>
#include <QDragMoveEvent>
#include <QTreeWidgetItem>
#include <QHBoxLayout>
#include <QPainter>
42
#include <QPalette>
43
#include <QScrollBar>
44
#include <QResource>
45
#include <assert.h>
46 47

#include <vlc_playlist.h>
48
#include <vlc_services_discovery.h>
49

Jakob Leben's avatar
Jakob Leben committed
50 51 52 53 54 55 56 57 58 59 60
void SelectorActionButton::paintEvent( QPaintEvent *event )
{
    QPainter p( this );
    QColor color = palette().color( QPalette::HighlightedText );
    color.setAlpha( 80 );
    if( underMouse() )
        p.fillRect( rect(), color );
    p.setPen( color );
    int frame = style()->pixelMetric( QStyle::PM_DefaultFrameWidth, 0, this );
    p.drawLine( rect().topLeft() + QPoint( 0, frame ),
                rect().bottomLeft() - QPoint( 0, frame ) );
61
    QFramelessButton::paintEvent( event );
Jakob Leben's avatar
Jakob Leben committed
62 63
}

64
PLSelItem::PLSelItem ( QTreeWidgetItem *i, const QString& text )
65
    : qitem(i), lblAction( NULL)
66
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
67
    layout = new QHBoxLayout( this );
68
    layout->setContentsMargins(0,0,0,0);
69
    layout->addSpacing( 3 );
70

71
    lbl = new QElidingLabel( text );
72
    layout->addWidget(lbl, 1);
73

Jakob Leben's avatar
Jakob Leben committed
74 75
    int height = qMax( 22, fontMetrics().height() + 8 );
    setMinimumHeight( height );
76 77 78 79
}

void PLSelItem::addAction( ItemAction act, const QString& tooltip )
{
80 81 82
    if( lblAction ) return; //might change later

    QIcon icon;
83 84 85 86

    switch( act )
    {
    case ADD_ACTION:
87
        icon = QIcon( ":/buttons/playlist/playlist_add" ); break;
88
    case RM_ACTION:
89
        icon = QIcon( ":/buttons/playlist/playlist_remove" ); break;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
90 91
    default:
        return;
92 93
    }

Jakob Leben's avatar
Jakob Leben committed
94
    lblAction = new SelectorActionButton();
95
    lblAction->setIcon( icon );
Jakob Leben's avatar
Jakob Leben committed
96
    lblAction->setMinimumWidth( lblAction->sizeHint().width() + 6 );
97 98 99 100 101

    if( !tooltip.isEmpty() ) lblAction->setToolTip( tooltip );

    layout->addWidget( lblAction, 0 );
    lblAction->hide();
102

103
    CONNECT( lblAction, clicked(), this, triggerAction() );
104 105 106
}


107
PLSelector::PLSelector( QWidget *p, intf_thread_t *_p_intf )
108
           : QTreeWidget( p ), p_intf(_p_intf)
109
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
110
    /* Properties */
111
    setFrameStyle( QFrame::NoFrame );
112
    setAttribute( Qt::WA_MacShowFocusRect, false );
113
    viewport()->setAutoFillBackground( false );
114
    setIconSize( QSize( 24,24 ) );
Jakob Leben's avatar
Jakob Leben committed
115
    setIndentation( 12 );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
116
    setHeaderHidden( true );
117
    setRootIsDecorated( true );
118
    setAlternatingRowColors( false );
119

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
120
    /* drops */
121 122 123
    viewport()->setAcceptDrops(true);
    setDropIndicatorShown(true);
    invisibleRootItem()->setFlags( invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled );
124

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
125 126 127 128 129 130 131 132
#ifdef Q_WS_MAC
    setAutoFillBackground( true );
    QPalette palette;
    palette.setColor( QPalette::Window, QColor(209,215,226) );
    setPalette( palette );
#endif
    setMinimumHeight( 120 );

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
133 134 135 136 137
    /* Podcasts */
    podcastsParent = NULL;
    podcastsParentId = -1;

    /* Podcast connects */
138
    CONNECT( THEMIM, playlistItemAppended( int, int ),
139
             this, plItemAdded( int, int ) );
140
    CONNECT( THEMIM, playlistItemRemoved( int ),
141
             this, plItemRemoved( int ) );
142 143
    DCONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
              this, inputItemUpdate( input_item_t * ) );
144

145
    createItems();
146

147 148
    setRootIsDecorated( false );
    setIndentation( 5 );
149 150 151 152
    /* Expand at least to show level 2 */
    for ( int i = 0; i < topLevelItemCount(); i++ )
        expandItem( topLevelItem( i ) );

153 154 155 156 157 158
    /***
     * We need to react to both clicks and activation (enter-key) here.
     * We use curItem to avoid rebuilding twice.
     * See QStyle::SH_ItemView_ActivateItemOnSingleClick
     ***/
    curItem = NULL;
159
    CONNECT( this, itemActivated( QTreeWidgetItem *, int ),
160
             this, setSource( QTreeWidgetItem *) );
161 162
    CONNECT( this, itemClicked( QTreeWidgetItem *, int ),
             this, setSource( QTreeWidgetItem *) );
163 164
}

165 166 167 168 169 170 171 172 173 174 175 176 177 178
PLSelector::~PLSelector()
{
    if( podcastsParent )
    {
        int c = podcastsParent->childCount();
        for( int i = 0; i < c; i++ )
        {
            QTreeWidgetItem *item = podcastsParent->child(i);
            input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
            vlc_gc_decref( p_input );
        }
    }
}

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
PLSelItem * putSDData( PLSelItem* item, const char* name, const char* longname )
{
    item->treeItem()->setData( 0, NAME_ROLE, qfu( name ) );
    item->treeItem()->setData( 0, LONGNAME_ROLE, qfu( longname ) );
    return item;
}

PLSelItem * putPLData( PLSelItem* item, playlist_item_t* plItem )
{
    item->treeItem()->setData( 0, PL_ITEM_ROLE, QVariant::fromValue( plItem ) );
/*    item->setData( 0, PL_ITEM_ID_ROLE, plItem->i_id );
    item->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( (void*) plItem->p_input ) ); );*/
    return item;
}

194 195 196 197 198 199 200 201 202 203 204
/*
 * Reads and updates the playlist's duration as [xx:xx] after the label in the tree
 * item - the treeview item to get the duration for
 * prefix - the string to use before the time (should be the category name)
 */
void PLSelector::updateTotalDuration( PLSelItem* item, const char* prefix )
{
    /* Getting  the playlist */
    QVariant playlistVariant = item->treeItem()->data( 0, PL_ITEM_ROLE );
    playlist_item_t* node = playlistVariant.value<playlist_item_t*>();

205 206 207 208 209
    /* Get the duration of the playlist item */
    playlist_Lock( THEPL );
    mtime_t mt_duration = playlist_GetNodeDuration( node );
    playlist_Unlock( THEPL );

210 211
    /* Formatting time */
    QString qs_timeLabel( prefix );
212

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
    int i_seconds = mt_duration / 1000000;
    int i_minutes = i_seconds / 60;
    i_seconds = i_seconds % 60;
    if( i_minutes >= 60 )
    {
        int i_hours = i_minutes / 60;
        i_minutes = i_minutes % 60;
        qs_timeLabel += QString(" [%1:%2:%3]").arg( i_hours ).arg( i_minutes, 2, 10, QChar('0') ).arg( i_seconds, 2, 10, QChar('0') );
    }
    else
        qs_timeLabel += QString( " [%1:%2]").arg( i_minutes, 2, 10, QChar('0') ).arg( i_seconds, 2, 10, QChar('0') );

    item->setText( qs_timeLabel );
}

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
228 229 230
void PLSelector::createItems()
{
    /* PL */
231
    playlistItem = putPLData( addItem( PL_ITEM_TYPE, N_("Playlist"), true ),
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
232
                              THEPL->p_playing );
233
    playlistItem->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_PL ) );
234
    playlistItem->treeItem()->setData( 0, Qt::DecorationRole, QIcon( ":/sidebar/playlist" ) );
235
    setCurrentItem( playlistItem->treeItem() );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
236 237

    /* ML */
238
    PLSelItem *ml = putPLData( addItem( PL_ITEM_TYPE, N_("Media Library"), true ),
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
239 240
                              THEPL->p_media_library );
    ml->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_ML ) );
241
    ml->treeItem()->setData( 0, Qt::DecorationRole, QIcon( ":/sidebar/library" ) );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
242

243
#ifdef MEDIA_LIBRARY
244
    /* SQL ML */
245
    ml = addItem( SQL_ML_TYPE, "SQL Media Library" )->treeItem();
246
    ml->treeItem()->setData( 0, Qt::DecorationRole, QIcon( ":/sidebar/library" ) );
247
#endif
248

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
249
    /* SD nodes */
250 251 252 253
    QTreeWidgetItem *mycomp = addItem( CATEGORY_TYPE, N_("My Computer"), false, true )->treeItem();
    QTreeWidgetItem *devices = addItem( CATEGORY_TYPE, N_("Devices"), false, true )->treeItem();
    QTreeWidgetItem *lan = addItem( CATEGORY_TYPE, N_("Local Network"), false, true )->treeItem();
    QTreeWidgetItem *internet = addItem( CATEGORY_TYPE, N_("Internet"), false, true )->treeItem();
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
254

255 256 257 258 259 260 261
#define NOT_SELECTABLE(w) w->setFlags( w->flags() ^ Qt::ItemIsSelectable );
    NOT_SELECTABLE( mycomp );
    NOT_SELECTABLE( devices );
    NOT_SELECTABLE( lan );
    NOT_SELECTABLE( internet );
#undef NOT_SELECTABLE

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
262 263 264 265 266 267 268 269 270 271 272 273 274 275
    /* SD subnodes */
    char **ppsz_longnames;
    int *p_categories;
    char **ppsz_names = vlc_sd_GetNames( THEPL, &ppsz_longnames, &p_categories );
    if( !ppsz_names )
        return;

    char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
    int *p_category = p_categories;
    for( ; *ppsz_name; ppsz_name++, ppsz_longname++, p_category++ )
    {
        //msg_Dbg( p_intf, "Adding a SD item: %s", *ppsz_longname );

        PLSelItem *selItem;
276 277
        QIcon icon;
        QString name( *ppsz_name );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
278 279 280 281
        switch( *p_category )
        {
        case SD_CAT_INTERNET:
            {
282 283
            selItem = addItem( SD_TYPE, *ppsz_longname, false, false, internet );
            if( name.startsWith( "podcast" ) )
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
284 285 286 287 288
            {
                selItem->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_PODCAST ) );
                selItem->addAction( ADD_ACTION, qtr( "Subscribe to a podcast" ) );
                CONNECT( selItem, action( PLSelItem* ), this, podcastAdd( PLSelItem* ) );
                podcastsParent = selItem->treeItem();
289 290 291 292 293 294
                icon = QIcon( ":/sidebar/podcast" );
            }
            else if ( name.startsWith( "lua{" ) )
            {
                int i_head = name.indexOf( "sd='" ) + 4;
                int i_tail = name.indexOf( '\'', i_head );
295
                QString iconname = QString( ":/sidebar/sd/%1" ).arg( name.mid( i_head, i_tail - i_head ) );
296 297
                QResource resource( iconname );
                if ( !resource.isValid() )
298
                    icon = QIcon( ":/sidebar/network" );
299 300
                else
                    icon = QIcon( iconname );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
301 302 303 304
            }
            }
            break;
        case SD_CAT_DEVICES:
305 306
            name = name.mid( 0, name.indexOf( '{' ) );
            selItem = addItem( SD_TYPE, *ppsz_longname, false, false, devices );
307 308 309 310
            if ( name == "xcb_apps" )
                icon = QIcon( ":/sidebar/screen" );
            else if ( name == "mtp" )
                icon = QIcon( ":/sidebar/mtp" );
311
            else if ( name == "disc" )
312
                icon = QIcon( ":/sidebar/disc" );
313
            else
314
                icon = QIcon( ":/sidebar/capture" );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
315 316
            break;
        case SD_CAT_LAN:
317
            selItem = addItem( SD_TYPE, *ppsz_longname, false, false, lan );
318
            icon = QIcon( ":/sidebar/lan" );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
319 320
            break;
        case SD_CAT_MYCOMPUTER:
321 322 323 324 325 326 327 328 329
            name = name.mid( 0, name.indexOf( '{' ) );
            selItem = addItem( SD_TYPE, *ppsz_longname, false, false, mycomp );
            if ( name == "video_dir" )
                icon = QIcon( ":/sidebar/movie" );
            else if ( name == "audio_dir" )
                icon = QIcon( ":/sidebar/music" );
            else if ( name == "picture_dir" )
                icon = QIcon( ":/sidebar/pictures" );
            else
330
                icon = QIcon( ":/sidebar/movie" );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
331 332 333 334 335
            break;
        default:
            selItem = addItem( SD_TYPE, *ppsz_longname );
        }

336
        selItem->treeItem()->setData( 0, SD_CATEGORY_ROLE, *p_category );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
337
        putSDData( selItem, *ppsz_name, *ppsz_longname );
338 339 340
        if ( ! icon.isNull() )
            selItem->treeItem()->setData( 0, Qt::DecorationRole, icon );

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
341 342 343 344 345 346 347 348 349 350 351 352 353
        free( *ppsz_name );
        free( *ppsz_longname );
    }
    free( ppsz_names );
    free( ppsz_longnames );
    free( p_categories );

    if( mycomp->childCount() == 0 ) delete mycomp;
    if( devices->childCount() == 0 ) delete devices;
    if( lan->childCount() == 0 ) delete lan;
    if( internet->childCount() == 0 ) delete internet;
}

354
void PLSelector::setSource( QTreeWidgetItem *item )
355
{
356
    if( !item || item == curItem )
357 358
        return;

359 360
    bool b_ok;
    int i_type = item->data( 0, TYPE_ROLE ).toInt( &b_ok );
361
    if( !b_ok || i_type == CATEGORY_TYPE )
362
        return;
363

364
    bool sd_loaded;
365 366
    if( i_type == SD_TYPE )
    {
367
        QString qs = item->data( 0, NAME_ROLE ).toString();
368 369
        sd_loaded = playlist_IsServicesDiscoveryLoaded( THEPL, qtu( qs ) );
        if( !sd_loaded )
370
        {
371 372
            if ( playlist_ServicesDiscoveryAdd( THEPL, qtu( qs ) ) != VLC_SUCCESS )
                return ;
373 374

            services_discovery_descriptor_t *p_test = new services_discovery_descriptor_t;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
375 376
            int i_ret = playlist_ServicesDiscoveryControl( THEPL, qtu( qs ), SD_CMD_DESCRIPTOR, p_test );
            if( i_ret == VLC_SUCCESS && p_test->i_capabilities & SD_CAP_SEARCH )
377 378
                item->setData( 0, CAP_SEARCH_ROLE, true );
        }
379
    }
380
#ifdef MEDIA_LIBRARY
381
    else if( i_type == SQL_ML_TYPE )
382
    {
383
        emit categoryActivated( NULL, true );
384
        curItem = item;
385 386
        return;
    }
387
#endif
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
388

389 390
    curItem = item;

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
391
    /* */
392 393
    playlist_Lock( THEPL );
    playlist_item_t *pl_item = NULL;
394

395 396
    /* Special case for podcast */
    // FIXME: simplify
397
    if( i_type == SD_TYPE )
398
    {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
399 400 401 402 403
        /* Find the right item for the SD */
        pl_item = playlist_ChildSearchName( THEPL->p_root,
                      qtu( item->data(0, LONGNAME_ROLE ).toString() ) );

        /* Podcasts */
404 405 406 407 408 409 410 411 412 413 414 415 416 417
        if( item->data( 0, SPECIAL_ROLE ).toInt() == IS_PODCAST )
        {
            if( pl_item && !sd_loaded )
            {
                podcastsParentId = pl_item->i_id;
                for( int i=0; i < pl_item->i_children; i++ )
                    addPodcastItem( pl_item->pp_children[i] );
            }
            pl_item = NULL; //to prevent activating it
        }
    }
    else
        pl_item = item->data( 0, PL_ITEM_ROLE ).value<playlist_item_t*>();

418 419
    playlist_Unlock( THEPL );

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
420
    /* */
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
421
    if( pl_item )
422
    {
423
        emit categoryActivated( pl_item, false );
424 425 426 427
        int i_cat = item->data( 0, SD_CATEGORY_ROLE ).toInt();
        emit SDCategorySelected( i_cat == SD_CAT_INTERNET
                                 || i_cat == SD_CAT_LAN );
    }
428 429
}

430
PLSelItem * PLSelector::addItem (
431
    SelectorItemType type, const char* str, bool drop, bool bold,
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
432
    QTreeWidgetItem* parentItem )
433 434 435
{
  QTreeWidgetItem *item = parentItem ?
      new QTreeWidgetItem( parentItem ) : new QTreeWidgetItem( this );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
436

437
  PLSelItem *selItem = new PLSelItem( item, qtr( str ) );
438
  if ( bold ) selItem->setStyleSheet( "font-weight: bold;" );
439 440 441 442 443 444 445 446
  setItemWidget( item, 0, selItem );
  item->setData( 0, TYPE_ROLE, (int)type );
  if( !drop ) item->setFlags( item->flags() & ~Qt::ItemIsDropEnabled );

  return selItem;
}

PLSelItem *PLSelector::addPodcastItem( playlist_item_t *p_item )
447
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
448
    vlc_gc_incref( p_item->p_input );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
449

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
450
    char *psz_name = input_item_GetName( p_item->p_input );
451
    PLSelItem *item = addItem( PL_ITEM_TYPE,  psz_name, false, false, podcastsParent );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
452 453
    free( psz_name );

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
454 455 456 457 458 459
    item->addAction( RM_ACTION, qtr( "Remove this podcast subscription" ) );
    item->treeItem()->setData( 0, PL_ITEM_ROLE, QVariant::fromValue( p_item ) );
    item->treeItem()->setData( 0, PL_ITEM_ID_ROLE, QVariant(p_item->i_id) );
    item->treeItem()->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( p_item->p_input ) );
    CONNECT( item, action( PLSelItem* ), this, podcastRemove( PLSelItem* ) );
    return item;
460 461
}

462 463 464
QStringList PLSelector::mimeTypes() const
{
    QStringList types;
465
    types << "vlc/qt-input-items";
466 467 468
    return types;
}

469 470
bool PLSelector::dropMimeData ( QTreeWidgetItem * parent, int,
    const QMimeData * data, Qt::DropAction )
471 472 473 474 475
{
    if( !parent ) return false;

    QVariant type = parent->data( 0, TYPE_ROLE );
    if( type == QVariant() ) return false;
476

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
477
    int i_truth = parent->data( 0, SPECIAL_ROLE ).toInt();
478
    if( i_truth != IS_PL && i_truth != IS_ML ) return false;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
479

480
    bool to_pl = ( i_truth == IS_PL );
481

482 483 484 485
    const PlMimeData *plMimeData = qobject_cast<const PlMimeData*>( data );
    if( !plMimeData ) return false;

    QList<input_item_t*> inputItems = plMimeData->inputItems();
486

487
    playlist_Lock( THEPL );
488

489 490 491 492 493
    foreach( input_item_t *p_input, inputItems )
    {
        playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
        if( !p_item ) continue;

494 495 496
        playlist_NodeAddCopy( THEPL, p_item,
                              to_pl ? THEPL->p_playing : THEPL->p_media_library,
                              PLAYLIST_END );
497
    }
498 499 500

    playlist_Unlock( THEPL );

501 502 503
    return true;
}

504 505 506 507 508 509
void PLSelector::dragMoveEvent ( QDragMoveEvent * event )
{
    event->setDropAction( Qt::CopyAction );
    QAbstractItemView::dragMoveEvent( event );
}

510
void PLSelector::plItemAdded( int item, int parent )
511
{
512
    updateTotalDuration(playlistItem, "Playlist");
513
    if( parent != podcastsParentId || podcastsParent == NULL ) return;
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544

    playlist_Lock( THEPL );

    playlist_item_t *p_item = playlist_ItemGetById( THEPL, item );
    if( !p_item ) {
        playlist_Unlock( THEPL );
        return;
    }

    int c = podcastsParent->childCount();
    for( int i = 0; i < c; i++ )
    {
        QTreeWidgetItem *podItem = podcastsParent->child(i);
        if( podItem->data( 0, PL_ITEM_ID_ROLE ).toInt() == item )
        {
          //msg_Dbg( p_intf, "Podcast already in: (%d) %s", item, p_item->p_input->psz_uri);
          playlist_Unlock( THEPL );
          return;
        }
    }

    //msg_Dbg( p_intf, "Adding podcast: (%d) %s", item, p_item->p_input->psz_uri );
    addPodcastItem( p_item );

    playlist_Unlock( THEPL );

    podcastsParent->setExpanded( true );
}

void PLSelector::plItemRemoved( int id )
{
545
    updateTotalDuration(playlistItem, "Playlist");
546 547
    if( !podcastsParent ) return;

548 549 550 551 552 553 554 555 556 557 558 559 560
    int c = podcastsParent->childCount();
    for( int i = 0; i < c; i++ )
    {
        QTreeWidgetItem *item = podcastsParent->child(i);
        if( item->data( 0, PL_ITEM_ID_ROLE ).toInt() == id )
        {
            input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
            //msg_Dbg( p_intf, "Removing podcast: (%d) %s", id, p_input->psz_uri );
            vlc_gc_decref( p_input );
            delete item;
            return;
        }
    }
561
}
562 563 564

void PLSelector::inputItemUpdate( input_item_t *arg )
{
565 566
    updateTotalDuration(playlistItem, "Playlist");

567 568 569
    if( podcastsParent == NULL )
        return;

570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
    int c = podcastsParent->childCount();
    for( int i = 0; i < c; i++ )
    {
        QTreeWidgetItem *item = podcastsParent->child(i);
        input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
        if( p_input == arg )
        {
            PLSelItem *si = itemWidget( item );
            char *psz_name = input_item_GetName( p_input );
            si->setText( qfu( psz_name ) );
            free( psz_name );
            return;
        }
    }
}

586
void PLSelector::podcastAdd( PLSelItem * )
587
{
588 589
    assert( podcastsParent );

590 591 592 593 594 595 596 597
    bool ok;
    QString url = QInputDialog::getText( this, qtr( "Subscribe" ),
                                         qtr( "Enter URL of the podcast to subscribe to:" ),
                                         QLineEdit::Normal, QString(), &ok );
    if( !ok || url.isEmpty() ) return;

    setSource( podcastsParent ); //to load the SD in case it's not loaded

598
    vlc_object_t *p_obj = (vlc_object_t*) vlc_object_find_name( p_intf->p_libvlc, "podcast" );
599 600 601
    if( !p_obj ) return;

    QString request("ADD:");
602
    request += url.trimmed();
603 604 605 606 607 608 609 610 611 612
    var_SetString( p_obj, "podcast-request", qtu( request ) );
    vlc_object_release( p_obj );
}

void PLSelector::podcastRemove( PLSelItem* item )
{
    QString question ( qtr( "Do you really want to unsubscribe from %1?" ) );
    question = question.arg( item->text() );
    QMessageBox::StandardButton res =
        QMessageBox::question( this, qtr( "Unsubscribe" ), question,
613 614 615
                               QMessageBox::Yes | QMessageBox::No,
                               QMessageBox::No );
    if( res == QMessageBox::No ) return;
616 617 618 619 620

    input_item_t *input = item->treeItem()->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
    if( !input ) return;

    vlc_object_t *p_obj = (vlc_object_t*) vlc_object_find_name(
621
        p_intf->p_libvlc, "podcast" );
622 623 624 625 626 627 628 629 630 631 632 633 634
    if( !p_obj ) return;

    QString request("RM:");
    char *psz_uri = input_item_GetURI( input );
    request += qfu( psz_uri );
    var_SetString( p_obj, "podcast-request", qtu( request ) );
    vlc_object_release( p_obj );
    free( psz_uri );
}

PLSelItem * PLSelector::itemWidget( QTreeWidgetItem *item )
{
    return ( static_cast<PLSelItem*>( QTreeWidget::itemWidget( item, 0 ) ) );
635 636
}

637 638 639 640 641
void PLSelector::drawBranches ( QPainter * painter, const QRect & rect, const QModelIndex & index ) const
{
    if( !model()->hasChildren( index ) ) return;
    QStyleOption option;
    option.initFrom( this );
642
    option.rect = rect.adjusted( rect.width() - indentation(), 0, 0, 0 );
643 644 645 646
    style()->drawPrimitive( isExpanded( index ) ?
                            QStyle::PE_IndicatorArrowDown :
                            QStyle::PE_IndicatorArrowRight, &option, painter );
}
647

648
void PLSelector::getCurrentItemInfos( int* type, bool* can_delay_search, QString *string)
649 650 651
{
    *type = currentItem()->data( 0, TYPE_ROLE ).toInt();
    *string = currentItem()->data( 0, NAME_ROLE ).toString();
652
    *can_delay_search = currentItem()->data( 0, CAP_SEARCH_ROLE ).toBool();
653
}
654

655 656 657 658 659
int PLSelector::getCurrentItemCategory()
{
    return currentItem()->data( 0, SPECIAL_ROLE ).toInt();
}

660 661
void PLSelector::wheelEvent( QWheelEvent *e )
{
662 663 664 665 666 667
    if( verticalScrollBar()->isVisible() && (
        (verticalScrollBar()->value() != verticalScrollBar()->minimum() && e->delta() >= 0 ) ||
        (verticalScrollBar()->value() != verticalScrollBar()->maximum() && e->delta() < 0 )
        ) )
        QApplication::sendEvent(verticalScrollBar(), e);

668 669 670
    // Accept this event in order to prevent unwanted volume up/down changes
    e->accept();
}