1 /*****************************************************************************
2  * selector.cpp : Playlist source selector
3  ****************************************************************************
4  * Copyright (C) 2006-2009 the VideoLAN team
5  * $Id: bb654d41893810a93bea7a8766df9c191693af73 $
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Jean-Baptiste Kempf
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24 
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28 
29 #include "qt.hpp"
30 #include "components/playlist/selector.hpp"
31 #include "playlist_model.hpp"                /* plMimeData */
32 #include "input_manager.hpp"                 /* MainInputManager, for podcast */
33 
34 #include <QApplication>
35 #include <QInputDialog>
36 #include <QMessageBox>
37 #include <QMimeData>
38 #include <QDragMoveEvent>
39 #include <QTreeWidgetItem>
40 #include <QHBoxLayout>
41 #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 
paintEvent(QPaintEvent * event)50 void SelectorActionButton::paintEvent( QPaintEvent *event )
51 {
52     QPainter p( this );
53     QColor color = palette().color( QPalette::HighlightedText );
54     color.setAlpha( 80 );
55     if( underMouse() )
56         p.fillRect( rect(), color );
57     p.setPen( color );
58     int frame = style()->pixelMetric( QStyle::PM_DefaultFrameWidth, 0, this );
59     p.drawLine( rect().topLeft() + QPoint( 0, frame ),
60                 rect().bottomLeft() - QPoint( 0, frame ) );
61     QFramelessButton::paintEvent( event );
62 }
63 
PLSelItem(QTreeWidgetItem * i,const QString & text)64 PLSelItem::PLSelItem ( QTreeWidgetItem *i, const QString& text )
65     : qitem(i), lblAction( NULL)
66 {
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 
74     int height = qMax( 22, fontMetrics().height() + 8 );
75     setMinimumHeight( height );
76 }
77 
addAction(ItemAction act,const QString & tooltip)78 void PLSelItem::addAction( ItemAction act, const QString& tooltip )
79 {
80     if( lblAction ) return; //might change later
81 
82     QIcon icon;
83 
84     switch( act )
85     {
86     case ADD_ACTION:
87         icon = QIcon( ":/buttons/playlist/playlist_add.svg" ); break;
88     case RM_ACTION:
89         icon = QIcon( ":/buttons/playlist/playlist_remove.svg" ); break;
90     default:
91         return;
92     }
93 
94     lblAction = new SelectorActionButton();
95     lblAction->setIcon( icon );
96     int icon_size = fontMetrics().height();
97     lblAction->setIconSize( QSize( icon_size, icon_size ) );
98     lblAction->setMinimumWidth( lblAction->sizeHint().width() + icon_size );
99 
100     if( !tooltip.isEmpty() ) lblAction->setToolTip( tooltip );
101 
102     layout->addWidget( lblAction, 0 );
103     lblAction->hide();
104 
105     CONNECT( lblAction, clicked(), this, triggerAction() );
106 }
107 
108 
PLSelector(QWidget * p,intf_thread_t * _p_intf)109 PLSelector::PLSelector( QWidget *p, intf_thread_t *_p_intf )
110            : QTreeWidget( p ), p_intf(_p_intf)
111 {
112     /* Properties */
113     setFrameStyle( QFrame::NoFrame );
114     setAttribute( Qt::WA_MacShowFocusRect, false );
115     viewport()->setAutoFillBackground( false );
116     setIconSize( QSize( 24,24 ) );
117     setIndentation( 12 );
118     setHeaderHidden( true );
119     setRootIsDecorated( true );
120     setAlternatingRowColors( false );
121 
122     /* drops */
123     viewport()->setAcceptDrops(true);
124     setDropIndicatorShown(true);
125     invisibleRootItem()->setFlags( invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled );
126 
127     setMinimumHeight( 120 );
128 
129     /* Podcasts */
130     podcastsParent = NULL;
131     podcastsParentId = -1;
132 
133     /* Podcast connects */
134     CONNECT( THEMIM, playlistItemAppended( int, int ),
135              this, plItemAdded( int, int ) );
136     CONNECT( THEMIM, playlistItemRemoved( int ),
137              this, plItemRemoved( int ) );
138     DCONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
139               this, inputItemUpdate( input_item_t * ) );
140 
141     createItems();
142 
143     setRootIsDecorated( false );
144     setIndentation( 5 );
145     /* Expand at least to show level 2 */
146     for ( int i = 0; i < topLevelItemCount(); i++ )
147         expandItem( topLevelItem( i ) );
148 
149     /***
150      * We need to react to both clicks and activation (enter-key) here.
151      * We use curItem to avoid rebuilding twice.
152      * See QStyle::SH_ItemView_ActivateItemOnSingleClick
153      ***/
154     curItem = NULL;
155     CONNECT( this, itemActivated( QTreeWidgetItem *, int ),
156              this, setSource( QTreeWidgetItem *) );
157     CONNECT( this, itemClicked( QTreeWidgetItem *, int ),
158              this, setSource( QTreeWidgetItem *) );
159 }
160 
~PLSelector()161 PLSelector::~PLSelector()
162 {
163     if( podcastsParent )
164     {
165         int c = podcastsParent->childCount();
166         for( int i = 0; i < c; i++ )
167         {
168             QTreeWidgetItem *item = podcastsParent->child(i);
169             input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
170             input_item_Release( p_input );
171         }
172     }
173 }
174 
putSDData(PLSelItem * item,const char * name,const char * longname)175 PLSelItem * putSDData( PLSelItem* item, const char* name, const char* longname )
176 {
177     item->treeItem()->setData( 0, NAME_ROLE, qfu( name ) );
178     item->treeItem()->setData( 0, LONGNAME_ROLE, qfu( longname ) );
179     return item;
180 }
181 
putPLData(PLSelItem * item,playlist_item_t * plItem)182 PLSelItem * putPLData( PLSelItem* item, playlist_item_t* plItem )
183 {
184     item->treeItem()->setData( 0, PL_ITEM_ROLE, QVariant::fromValue( plItem ) );
185 /*    item->setData( 0, PL_ITEM_ID_ROLE, plItem->i_id );
186     item->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( (void*) plItem->p_input ) ); );*/
187     return item;
188 }
189 
190 /*
191  * Reads and updates the playlist's duration as [xx:xx] after the label in the tree
192  * item - the treeview item to get the duration for
193  * prefix - the string to use before the time (should be the category name)
194  */
updateTotalDuration(PLSelItem * item,const char * prefix)195 void PLSelector::updateTotalDuration( PLSelItem* item, const char* prefix )
196 {
197     /* Getting  the playlist */
198     QVariant playlistVariant = item->treeItem()->data( 0, PL_ITEM_ROLE );
199     playlist_item_t* node = playlistVariant.value<playlist_item_t*>();
200 
201     /* Get the duration of the playlist item */
202     playlist_Lock( THEPL );
203     mtime_t mt_duration = playlist_GetNodeDuration( node );
204     playlist_Unlock( THEPL );
205 
206     /* Formatting time */
207     QString qs_timeLabel( prefix );
208 
209     int i_seconds = mt_duration / 1000000;
210     int i_minutes = i_seconds / 60;
211     i_seconds = i_seconds % 60;
212     if( i_minutes >= 60 )
213     {
214         int i_hours = i_minutes / 60;
215         i_minutes = i_minutes % 60;
216         qs_timeLabel += QString(" [%1:%2:%3]").arg( i_hours ).arg( i_minutes, 2, 10, QChar('0') ).arg( i_seconds, 2, 10, QChar('0') );
217     }
218     else
219         qs_timeLabel += QString( " [%1:%2]").arg( i_minutes, 2, 10, QChar('0') ).arg( i_seconds, 2, 10, QChar('0') );
220 
221     item->setText( qs_timeLabel );
222 }
223 
createItems()224 void PLSelector::createItems()
225 {
226     /* PL */
227     playlistItem = putPLData( addItem( PL_ITEM_TYPE, N_("Playlist"), true ),
228                               THEPL->p_playing );
229     playlistItem->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_PL ) );
230     playlistItem->treeItem()->setData( 0, Qt::DecorationRole, QIcon( ":/sidebar/playlist.svg" ) );
231     setCurrentItem( playlistItem->treeItem() );
232 
233     /* ML */
234     if( THEPL->p_media_library )
235     {
236         PLSelItem *ml = putPLData( addItem( PL_ITEM_TYPE, N_("Media Library"), true ),
237           THEPL->p_media_library );
238         ml->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_ML ) );
239         ml->treeItem()->setData( 0, Qt::DecorationRole, QIcon( ":/sidebar/library.svg" ) );
240     }
241 
242     /* SD nodes */
243     QTreeWidgetItem *mycomp = addItem( CATEGORY_TYPE, N_("My Computer"), false, true )->treeItem();
244     QTreeWidgetItem *devices = addItem( CATEGORY_TYPE, N_("Devices"), false, true )->treeItem();
245     QTreeWidgetItem *lan = addItem( CATEGORY_TYPE, N_("Local Network"), false, true )->treeItem();
246     QTreeWidgetItem *internet = addItem( CATEGORY_TYPE, N_("Internet"), false, true )->treeItem();
247 
248 #define NOT_SELECTABLE(w) w->setFlags( w->flags() ^ Qt::ItemIsSelectable );
249     NOT_SELECTABLE( mycomp );
250     NOT_SELECTABLE( devices );
251     NOT_SELECTABLE( lan );
252     NOT_SELECTABLE( internet );
253 #undef NOT_SELECTABLE
254 
255     /* SD subnodes */
256     char **ppsz_longnames;
257     int *p_categories;
258     char **ppsz_names = vlc_sd_GetNames( THEPL, &ppsz_longnames, &p_categories );
259     if( !ppsz_names )
260         return;
261 
262     char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
263     int *p_category = p_categories;
264     for( ; *ppsz_name; ppsz_name++, ppsz_longname++, p_category++ )
265     {
266         //msg_Dbg( p_intf, "Adding a SD item: %s", *ppsz_longname );
267 
268         PLSelItem *selItem;
269         QIcon icon;
270         QString name( *ppsz_name );
271         switch( *p_category )
272         {
273         case SD_CAT_INTERNET:
274             {
275             selItem = addItem( SD_TYPE, *ppsz_longname, false, false, internet );
276             if( name.startsWith( "podcast" ) )
277             {
278                 selItem->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_PODCAST ) );
279                 selItem->addAction( ADD_ACTION, qtr( "Subscribe to a podcast" ) );
280                 CONNECT( selItem, action( PLSelItem* ), this, podcastAdd( PLSelItem* ) );
281                 podcastsParent = selItem->treeItem();
282                 icon = QIcon( ":/sidebar/podcast.svg" );
283             }
284             else if ( name.startsWith( "lua{" ) )
285             {
286                 int i_head = name.indexOf( "sd='" ) + 4;
287                 int i_tail = name.indexOf( '\'', i_head );
288                 QString iconname = QString( ":/sidebar/sd/%1.svg" ).arg( name.mid( i_head, i_tail - i_head ) );
289                 QResource resource( iconname );
290                 if ( !resource.isValid() )
291                     icon = QIcon( ":/sidebar/network.svg" );
292                 else
293                     icon = QIcon( iconname );
294             }
295             }
296             break;
297         case SD_CAT_DEVICES:
298             name = name.mid( 0, name.indexOf( '{' ) );
299             selItem = addItem( SD_TYPE, *ppsz_longname, false, false, devices );
300             if ( name == "xcb_apps" )
301                 icon = QIcon( ":/sidebar/screen.svg" );
302             else if ( name == "mtp" )
303                 icon = QIcon( ":/sidebar/mtp.svg" );
304             else if ( name == "disc" )
305                 icon = QIcon( ":/sidebar/disc.svg" );
306             else
307                 icon = QIcon( ":/sidebar/capture.svg" );
308             break;
309         case SD_CAT_LAN:
310             selItem = addItem( SD_TYPE, *ppsz_longname, false, false, lan );
311             icon = QIcon( ":/sidebar/lan.svg" );
312             break;
313         case SD_CAT_MYCOMPUTER:
314             name = name.mid( 0, name.indexOf( '{' ) );
315             selItem = addItem( SD_TYPE, *ppsz_longname, false, false, mycomp );
316             if ( name == "video_dir" )
317                 icon = QIcon( ":/sidebar/movie.svg" );
318             else if ( name == "audio_dir" )
319                 icon = QIcon( ":/sidebar/music.svg" );
320             else if ( name == "picture_dir" )
321                 icon = QIcon( ":/sidebar/pictures.svg" );
322             else
323                 icon = QIcon( ":/sidebar/movie.svg" );
324             break;
325         default:
326             selItem = addItem( SD_TYPE, *ppsz_longname );
327         }
328 
329         selItem->treeItem()->setData( 0, SD_CATEGORY_ROLE, *p_category );
330         putSDData( selItem, *ppsz_name, *ppsz_longname );
331         if ( ! icon.isNull() )
332             selItem->treeItem()->setData( 0, Qt::DecorationRole, icon );
333 
334         free( *ppsz_name );
335         free( *ppsz_longname );
336     }
337     free( ppsz_names );
338     free( ppsz_longnames );
339     free( p_categories );
340 
341     if( mycomp->childCount() == 0 ) delete mycomp;
342     if( devices->childCount() == 0 ) delete devices;
343     if( lan->childCount() == 0 ) delete lan;
344     if( internet->childCount() == 0 ) delete internet;
345 }
346 
setSource(QTreeWidgetItem * item)347 void PLSelector::setSource( QTreeWidgetItem *item )
348 {
349     if( !item || item == curItem )
350         return;
351 
352     bool b_ok;
353     int i_type = item->data( 0, TYPE_ROLE ).toInt( &b_ok );
354     if( !b_ok || i_type == CATEGORY_TYPE )
355         return;
356 
357     bool sd_loaded;
358     if( i_type == SD_TYPE )
359     {
360         QString qs = item->data( 0, NAME_ROLE ).toString();
361         sd_loaded = playlist_IsServicesDiscoveryLoaded( THEPL, qtu( qs ) );
362         if( !sd_loaded )
363         {
364             if ( playlist_ServicesDiscoveryAdd( THEPL, qtu( qs ) ) != VLC_SUCCESS )
365                 return ;
366 
367             services_discovery_descriptor_t test;
368 
369             if ( playlist_ServicesDiscoveryControl( THEPL, qtu( qs ),
370                                                     SD_CMD_DESCRIPTOR, &test ) == VLC_SUCCESS )
371             {
372                 item->setData( 0, CAP_SEARCH_ROLE, (test.i_capabilities & SD_CAP_SEARCH) );
373             }
374         }
375     }
376 
377     curItem = item;
378 
379     /* */
380     playlist_Lock( THEPL );
381     playlist_item_t *pl_item = NULL;
382 
383     /* Special case for podcast */
384     // FIXME: simplify
385     if( i_type == SD_TYPE )
386     {
387         /* Find the right item for the SD */
388         /* FIXME: searching by name - what could possibly go wrong? */
389         pl_item = playlist_ChildSearchName( &(THEPL->root),
390             vlc_gettext(qtu(item->data(0, LONGNAME_ROLE).toString())) );
391 
392         /* Podcasts */
393         if( item->data( 0, SPECIAL_ROLE ).toInt() == IS_PODCAST )
394         {
395             if( pl_item && !sd_loaded )
396             {
397                 podcastsParentId = pl_item->i_id;
398                 for( int i=0; i < pl_item->i_children; i++ )
399                     addPodcastItem( pl_item->pp_children[i] );
400             }
401             pl_item = NULL; //to prevent activating it
402         }
403     }
404     else
405         pl_item = item->data( 0, PL_ITEM_ROLE ).value<playlist_item_t*>();
406 
407     playlist_Unlock( THEPL );
408 
409     /* */
410     if( pl_item )
411     {
412         emit categoryActivated( pl_item, false );
413         int i_cat = item->data( 0, SD_CATEGORY_ROLE ).toInt();
414         emit SDCategorySelected( i_cat == SD_CAT_INTERNET
415                                  || i_cat == SD_CAT_LAN );
416     }
417 }
418 
addItem(SelectorItemType type,const char * str,bool drop,bool bold,QTreeWidgetItem * parentItem)419 PLSelItem * PLSelector::addItem (
420     SelectorItemType type, const char* str, bool drop, bool bold,
421     QTreeWidgetItem* parentItem )
422 {
423   QTreeWidgetItem *item = parentItem ?
424       new QTreeWidgetItem( parentItem ) : new QTreeWidgetItem( this );
425 
426   PLSelItem *selItem = new PLSelItem( item, qtr( str ) );
427   if ( bold ) selItem->setStyleSheet( "font-weight: bold;" );
428   setItemWidget( item, 0, selItem );
429   item->setData( 0, TYPE_ROLE, (int)type );
430   if( !drop ) item->setFlags( item->flags() & ~Qt::ItemIsDropEnabled );
431 
432   return selItem;
433 }
434 
addPodcastItem(playlist_item_t * p_item)435 PLSelItem *PLSelector::addPodcastItem( playlist_item_t *p_item )
436 {
437     input_item_Hold( p_item->p_input );
438 
439     char *psz_name = input_item_GetName( p_item->p_input );
440     PLSelItem *item = addItem( PL_ITEM_TYPE,  psz_name, false, false, podcastsParent );
441     free( psz_name );
442 
443     item->addAction( RM_ACTION, qtr( "Remove this podcast subscription" ) );
444     item->treeItem()->setData( 0, PL_ITEM_ROLE, QVariant::fromValue( p_item ) );
445     item->treeItem()->setData( 0, PL_ITEM_ID_ROLE, QVariant(p_item->i_id) );
446     item->treeItem()->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( p_item->p_input ) );
447     CONNECT( item, action( PLSelItem* ), this, podcastRemove( PLSelItem* ) );
448     return item;
449 }
450 
mimeTypes() const451 QStringList PLSelector::mimeTypes() const
452 {
453     QStringList types;
454     types << "vlc/qt-input-items";
455     return types;
456 }
457 
dropMimeData(QTreeWidgetItem * parent,int,const QMimeData * data,Qt::DropAction)458 bool PLSelector::dropMimeData ( QTreeWidgetItem * parent, int,
459     const QMimeData * data, Qt::DropAction )
460 {
461     if( !parent ) return false;
462 
463     QVariant type = parent->data( 0, TYPE_ROLE );
464     if( type == QVariant() ) return false;
465 
466     int i_truth = parent->data( 0, SPECIAL_ROLE ).toInt();
467     if( i_truth != IS_PL && i_truth != IS_ML ) return false;
468 
469     bool to_pl = ( i_truth == IS_PL );
470 
471     const PlMimeData *plMimeData = qobject_cast<const PlMimeData*>( data );
472     if( !plMimeData ) return false;
473 
474     QList<input_item_t*> inputItems = plMimeData->inputItems();
475 
476     playlist_Lock( THEPL );
477 
478     foreach( input_item_t *p_input, inputItems )
479     {
480         playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
481         if( !p_item ) continue;
482 
483         playlist_NodeAddCopy( THEPL, p_item,
484                               to_pl ? THEPL->p_playing : THEPL->p_media_library,
485                               PLAYLIST_END );
486     }
487 
488     playlist_Unlock( THEPL );
489 
490     return true;
491 }
492 
dragMoveEvent(QDragMoveEvent * event)493 void PLSelector::dragMoveEvent ( QDragMoveEvent * event )
494 {
495     event->setDropAction( Qt::CopyAction );
496     QAbstractItemView::dragMoveEvent( event );
497 }
498 
plItemAdded(int item,int parent)499 void PLSelector::plItemAdded( int item, int parent )
500 {
501     updateTotalDuration(playlistItem, "Playlist");
502     if( parent != podcastsParentId || podcastsParent == NULL ) return;
503 
504     playlist_Lock( THEPL );
505 
506     playlist_item_t *p_item = playlist_ItemGetById( THEPL, item );
507     if( !p_item ) {
508         playlist_Unlock( THEPL );
509         return;
510     }
511 
512     int c = podcastsParent->childCount();
513     for( int i = 0; i < c; i++ )
514     {
515         QTreeWidgetItem *podItem = podcastsParent->child(i);
516         if( podItem->data( 0, PL_ITEM_ID_ROLE ).toInt() == item )
517         {
518           //msg_Dbg( p_intf, "Podcast already in: (%d) %s", item, p_item->p_input->psz_uri);
519           playlist_Unlock( THEPL );
520           return;
521         }
522     }
523 
524     //msg_Dbg( p_intf, "Adding podcast: (%d) %s", item, p_item->p_input->psz_uri );
525     addPodcastItem( p_item );
526 
527     playlist_Unlock( THEPL );
528 
529     podcastsParent->setExpanded( true );
530 }
531 
plItemRemoved(int id)532 void PLSelector::plItemRemoved( int id )
533 {
534     updateTotalDuration(playlistItem, "Playlist");
535     if( !podcastsParent ) return;
536 
537     int c = podcastsParent->childCount();
538     for( int i = 0; i < c; i++ )
539     {
540         QTreeWidgetItem *item = podcastsParent->child(i);
541         if( item->data( 0, PL_ITEM_ID_ROLE ).toInt() == id )
542         {
543             input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
544             //msg_Dbg( p_intf, "Removing podcast: (%d) %s", id, p_input->psz_uri );
545             input_item_Release( p_input );
546             delete item;
547             return;
548         }
549     }
550 }
551 
inputItemUpdate(input_item_t * arg)552 void PLSelector::inputItemUpdate( input_item_t *arg )
553 {
554     updateTotalDuration(playlistItem, "Playlist");
555 
556     if( podcastsParent == NULL )
557         return;
558 
559     int c = podcastsParent->childCount();
560     for( int i = 0; i < c; i++ )
561     {
562         QTreeWidgetItem *item = podcastsParent->child(i);
563         input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
564         if( p_input == arg )
565         {
566             PLSelItem *si = itemWidget( item );
567             char *psz_name = input_item_GetName( p_input );
568             si->setText( qfu( psz_name ) );
569             free( psz_name );
570             return;
571         }
572     }
573 }
574 
podcastAdd(PLSelItem *)575 void PLSelector::podcastAdd( PLSelItem * )
576 {
577     assert( podcastsParent );
578 
579     bool ok;
580     QString url = QInputDialog::getText( this, qtr( "Subscribe" ),
581                                          qtr( "Enter URL of the podcast to subscribe to:" ),
582                                          QLineEdit::Normal, QString(), &ok );
583     if( !ok || url.isEmpty() ) return;
584 
585     setSource( podcastsParent ); //to load the SD in case it's not loaded
586 
587     QString request("ADD:");
588     request += url.trimmed();
589     var_SetString( THEPL, "podcast-request", qtu( request ) );
590 }
591 
podcastRemove(PLSelItem * item)592 void PLSelector::podcastRemove( PLSelItem* item )
593 {
594     QString question ( qtr( "Do you really want to unsubscribe from %1?" ) );
595     question = question.arg( item->text() );
596     QMessageBox::StandardButton res =
597         QMessageBox::question( this, qtr( "Unsubscribe" ), question,
598                                QMessageBox::Yes | QMessageBox::No,
599                                QMessageBox::No );
600     if( res == QMessageBox::No ) return;
601 
602     input_item_t *input = item->treeItem()->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
603     if( !input ) return;
604 
605     QString request("RM:");
606     char *psz_uri = input_item_GetURI( input );
607     request += qfu( psz_uri );
608     var_SetString( THEPL, "podcast-request", qtu( request ) );
609     free( psz_uri );
610 }
611 
itemWidget(QTreeWidgetItem * item)612 PLSelItem * PLSelector::itemWidget( QTreeWidgetItem *item )
613 {
614     return ( static_cast<PLSelItem*>( QTreeWidget::itemWidget( item, 0 ) ) );
615 }
616 
drawBranches(QPainter * painter,const QRect & rect,const QModelIndex & index) const617 void PLSelector::drawBranches ( QPainter * painter, const QRect & rect, const QModelIndex & index ) const
618 {
619     if( !model()->hasChildren( index ) ) return;
620     QStyleOption option;
621     option.initFrom( this );
622     option.rect = rect.adjusted( rect.width() - indentation(), 0, 0, 0 );
623     style()->drawPrimitive( isExpanded( index ) ?
624                             QStyle::PE_IndicatorArrowDown :
625                             QStyle::PE_IndicatorArrowRight, &option, painter );
626 }
627 
getCurrentItemInfos(int * type,bool * can_delay_search,QString * string)628 void PLSelector::getCurrentItemInfos( int* type, bool* can_delay_search, QString *string)
629 {
630     *type = currentItem()->data( 0, TYPE_ROLE ).toInt();
631     *string = currentItem()->data( 0, NAME_ROLE ).toString();
632     *can_delay_search = currentItem()->data( 0, CAP_SEARCH_ROLE ).toBool();
633 }
634 
getCurrentItemCategory()635 int PLSelector::getCurrentItemCategory()
636 {
637     return currentItem()->data( 0, SPECIAL_ROLE ).toInt();
638 }
639 
wheelEvent(QWheelEvent * e)640 void PLSelector::wheelEvent( QWheelEvent *e )
641 {
642     if( verticalScrollBar()->isVisible() && (
643         (verticalScrollBar()->value() != verticalScrollBar()->minimum() && e->delta() >= 0 ) ||
644         (verticalScrollBar()->value() != verticalScrollBar()->maximum() && e->delta() < 0 )
645         ) )
646         QApplication::sendEvent(verticalScrollBar(), e);
647 
648     // Accept this event in order to prevent unwanted volume up/down changes
649     e->accept();
650 }
651