1 /*****************************************************************************
2 * selector.cpp : Playlist source selector
3 ****************************************************************************
4 * Copyright (C) 2006-2009 the VideoLAN team
7 * Authors: Clément Stenac <zorglub@videolan.org>
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.
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.
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 *****************************************************************************/
30 #include "components/playlist/selector.hpp"
31 #include "playlist_model.hpp" /* plMimeData */
32 #include "input_manager.hpp" /* MainInputManager, for podcast */
34 #include <QInputDialog>
35 #include <QMessageBox>
37 #include <QDragMoveEvent>
38 #include <QTreeWidgetItem>
39 #include <QHBoxLayout>
43 #include <vlc_playlist.h>
44 #include <vlc_services_discovery.h>
46 void SelectorActionButton::paintEvent( QPaintEvent *event )
49 QColor color = palette().color( QPalette::HighlightedText );
52 p.fillRect( rect(), color );
54 int frame = style()->pixelMetric( QStyle::PM_DefaultFrameWidth, 0, this );
55 p.drawLine( rect().topLeft() + QPoint( 0, frame ),
56 rect().bottomLeft() - QPoint( 0, frame ) );
57 QFramelessButton::paintEvent( event );
60 PLSelItem::PLSelItem ( QTreeWidgetItem *i, const QString& text )
61 : qitem(i), lblAction( NULL)
63 layout = new QHBoxLayout( this );
64 layout->setContentsMargins(0,0,0,0);
65 layout->addSpacing( 3 );
67 lbl = new QElidingLabel( text );
68 layout->addWidget(lbl, 1);
70 int height = qMax( 22, fontMetrics().height() + 8 );
71 setMinimumHeight( height );
74 void PLSelItem::addAction( ItemAction act, const QString& tooltip )
76 if( lblAction ) return; //might change later
83 icon = QIcon( ":/buttons/playlist/playlist_add" ); break;
85 icon = QIcon( ":/buttons/playlist/playlist_remove" ); break;
88 lblAction = new SelectorActionButton();
89 lblAction->setIcon( icon );
90 lblAction->setMinimumWidth( lblAction->sizeHint().width() + 6 );
92 if( !tooltip.isEmpty() ) lblAction->setToolTip( tooltip );
94 layout->addWidget( lblAction, 0 );
97 CONNECT( lblAction, clicked(), this, triggerAction() );
101 PLSelector::PLSelector( QWidget *p, intf_thread_t *_p_intf )
102 : QTreeWidget( p ), p_intf(_p_intf)
105 setFrameStyle( QFrame::NoFrame );
106 setAttribute( Qt::WA_MacShowFocusRect, false );
107 viewport()->setAutoFillBackground( false );
108 setIconSize( QSize( 24,24 ) );
109 setIndentation( 12 );
110 setHeaderHidden( true );
111 setRootIsDecorated( true );
112 setAlternatingRowColors( false );
115 viewport()->setAcceptDrops(true);
116 setDropIndicatorShown(true);
117 invisibleRootItem()->setFlags( invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled );
120 podcastsParent = NULL;
121 podcastsParentId = -1;
123 /* Podcast connects */
124 CONNECT( THEMIM, playlistItemAppended( int, int ),
125 this, plItemAdded( int, int ) );
126 CONNECT( THEMIM, playlistItemRemoved( int ),
127 this, plItemRemoved( int ) );
128 DCONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
129 this, inputItemUpdate( input_item_t * ) );
134 * We need to react to both clicks and activation (enter-key) here.
135 * We use curItem to avoid rebuilding twice.
136 * See QStyle::SH_ItemView_ActivateItemOnSingleClick
139 CONNECT( this, itemActivated( QTreeWidgetItem *, int ),
140 this, setSource( QTreeWidgetItem *) );
141 CONNECT( this, itemClicked( QTreeWidgetItem *, int ),
142 this, setSource( QTreeWidgetItem *) );
144 /* select the first item */
145 // view->setCurrentIndex( model->index( 0, 0, QModelIndex() ) );
148 setAutoFillBackground( true );
150 palette.setColor( QPalette::Window, QColor(209,215,226) );
151 setPalette( palette );
153 setMinimumHeight( 120 );
156 PLSelector::~PLSelector()
160 int c = podcastsParent->childCount();
161 for( int i = 0; i < c; i++ )
163 QTreeWidgetItem *item = podcastsParent->child(i);
164 input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
165 vlc_gc_decref( p_input );
170 PLSelItem * putSDData( PLSelItem* item, const char* name, const char* longname )
172 item->treeItem()->setData( 0, NAME_ROLE, qfu( name ) );
173 item->treeItem()->setData( 0, LONGNAME_ROLE, qfu( longname ) );
177 PLSelItem * putPLData( PLSelItem* item, playlist_item_t* plItem )
179 item->treeItem()->setData( 0, PL_ITEM_ROLE, QVariant::fromValue( plItem ) );
180 /* item->setData( 0, PL_ITEM_ID_ROLE, plItem->i_id );
181 item->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( (void*) plItem->p_input ) ); );*/
185 void PLSelector::createItems()
188 PLSelItem *pl = putPLData( addItem( PL_ITEM_TYPE, N_("Playlist"), true ),
190 pl->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_PL ) );
193 PLSelItem *ml = putPLData( addItem( PL_ITEM_TYPE, N_("Media Library"), true ),
194 THEPL->p_media_library );
195 ml->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_ML ) );
198 addItem( SQL_ML_TYPE, "SQL Media Library" )->treeItem();
201 QTreeWidgetItem *mycomp = addItem( CATEGORY_TYPE, N_("My Computer") )->treeItem();
202 QTreeWidgetItem *devices = addItem( CATEGORY_TYPE, N_("Devices") )->treeItem();
203 QTreeWidgetItem *lan = addItem( CATEGORY_TYPE, N_("Local Network") )->treeItem();
204 QTreeWidgetItem *internet = addItem( CATEGORY_TYPE, N_("Internet") )->treeItem();
207 char **ppsz_longnames;
209 char **ppsz_names = vlc_sd_GetNames( THEPL, &ppsz_longnames, &p_categories );
213 char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
214 int *p_category = p_categories;
215 for( ; *ppsz_name; ppsz_name++, ppsz_longname++, p_category++ )
217 //msg_Dbg( p_intf, "Adding a SD item: %s", *ppsz_longname );
220 switch( *p_category )
222 case SD_CAT_INTERNET:
224 selItem = addItem( SD_TYPE, *ppsz_longname, false, internet );
225 if( !strncmp( *ppsz_name, "podcast", 7 ) )
227 selItem->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_PODCAST ) );
228 selItem->addAction( ADD_ACTION, qtr( "Subscribe to a podcast" ) );
229 CONNECT( selItem, action( PLSelItem* ), this, podcastAdd( PLSelItem* ) );
230 podcastsParent = selItem->treeItem();
235 selItem = addItem( SD_TYPE, *ppsz_longname, false, devices );
238 selItem = addItem( SD_TYPE, *ppsz_longname, false, lan );
240 case SD_CAT_MYCOMPUTER:
241 selItem = addItem( SD_TYPE, *ppsz_longname, false, mycomp );
244 selItem = addItem( SD_TYPE, *ppsz_longname );
247 putSDData( selItem, *ppsz_name, *ppsz_longname );
249 free( *ppsz_longname );
252 free( ppsz_longnames );
253 free( p_categories );
255 if( mycomp->childCount() == 0 ) delete mycomp;
256 if( devices->childCount() == 0 ) delete devices;
257 if( lan->childCount() == 0 ) delete lan;
258 if( internet->childCount() == 0 ) delete internet;
261 void PLSelector::setSource( QTreeWidgetItem *item )
263 if( !item || item == curItem )
269 int i_type = item->data( 0, TYPE_ROLE ).toInt( &b_ok );
270 if( !b_ok || i_type == CATEGORY_TYPE )
274 if( i_type == SD_TYPE )
276 QString qs = item->data( 0, NAME_ROLE ).toString();
277 sd_loaded = playlist_IsServicesDiscoveryLoaded( THEPL, qtu( qs ) );
279 playlist_ServicesDiscoveryAdd( THEPL, qtu( qs ) );
281 else if( i_type == SQL_ML_TYPE )
283 emit categoryActivated( NULL, true );
288 playlist_Lock( THEPL );
289 playlist_item_t *pl_item = NULL;
291 /* Special case for podcast */
293 if( i_type == SD_TYPE )
295 /* Find the right item for the SD */
296 pl_item = playlist_ChildSearchName( THEPL->p_root,
297 qtu( item->data(0, LONGNAME_ROLE ).toString() ) );
300 if( item->data( 0, SPECIAL_ROLE ).toInt() == IS_PODCAST )
302 if( pl_item && !sd_loaded )
304 podcastsParentId = pl_item->i_id;
305 for( int i=0; i < pl_item->i_children; i++ )
306 addPodcastItem( pl_item->pp_children[i] );
308 pl_item = NULL; //to prevent activating it
312 pl_item = item->data( 0, PL_ITEM_ROLE ).value<playlist_item_t*>();
314 playlist_Unlock( THEPL );
318 emit categoryActivated( pl_item, false );
321 PLSelItem * PLSelector::addItem (
322 SelectorItemType type, const char* str, bool drop,
323 QTreeWidgetItem* parentItem )
325 QTreeWidgetItem *item = parentItem ?
326 new QTreeWidgetItem( parentItem ) : new QTreeWidgetItem( this );
328 PLSelItem *selItem = new PLSelItem( item, qtr( str ) );
329 setItemWidget( item, 0, selItem );
330 item->setData( 0, TYPE_ROLE, (int)type );
331 if( !drop ) item->setFlags( item->flags() & ~Qt::ItemIsDropEnabled );
336 PLSelItem *PLSelector::addPodcastItem( playlist_item_t *p_item )
338 vlc_gc_incref( p_item->p_input );
340 char *psz_name = input_item_GetName( p_item->p_input );
341 PLSelItem *item = addItem( PL_ITEM_TYPE, psz_name, false, podcastsParent );
344 item->addAction( RM_ACTION, qtr( "Remove this podcast subscription" ) );
345 item->treeItem()->setData( 0, PL_ITEM_ROLE, QVariant::fromValue( p_item ) );
346 item->treeItem()->setData( 0, PL_ITEM_ID_ROLE, QVariant(p_item->i_id) );
347 item->treeItem()->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( p_item->p_input ) );
348 CONNECT( item, action( PLSelItem* ), this, podcastRemove( PLSelItem* ) );
352 QStringList PLSelector::mimeTypes() const
355 types << "vlc/qt-input-items";
359 bool PLSelector::dropMimeData ( QTreeWidgetItem * parent, int,
360 const QMimeData * data, Qt::DropAction )
362 if( !parent ) return false;
364 QVariant type = parent->data( 0, TYPE_ROLE );
365 if( type == QVariant() ) return false;
367 int i_truth = parent->data( 0, SPECIAL_ROLE ).toInt();
368 if( i_truth != IS_PL && i_truth != IS_ML ) return false;
370 bool to_pl = ( i_truth == IS_PL );
372 const PlMimeData *plMimeData = qobject_cast<const PlMimeData*>( data );
373 if( !plMimeData ) return false;
375 QList<input_item_t*> inputItems = plMimeData->inputItems();
377 playlist_Lock( THEPL );
379 foreach( input_item_t *p_input, inputItems )
381 playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
382 if( !p_item ) continue;
384 playlist_NodeAddCopy( THEPL, p_item,
385 to_pl ? THEPL->p_playing : THEPL->p_media_library,
389 playlist_Unlock( THEPL );
394 void PLSelector::dragMoveEvent ( QDragMoveEvent * event )
396 event->setDropAction( Qt::CopyAction );
397 QAbstractItemView::dragMoveEvent( event );
400 void PLSelector::plItemAdded( int item, int parent )
402 if( parent != podcastsParentId ) return;
404 playlist_Lock( THEPL );
406 playlist_item_t *p_item = playlist_ItemGetById( THEPL, item );
408 playlist_Unlock( THEPL );
412 int c = podcastsParent->childCount();
413 for( int i = 0; i < c; i++ )
415 QTreeWidgetItem *podItem = podcastsParent->child(i);
416 if( podItem->data( 0, PL_ITEM_ID_ROLE ).toInt() == item )
418 //msg_Dbg( p_intf, "Podcast already in: (%d) %s", item, p_item->p_input->psz_uri);
419 playlist_Unlock( THEPL );
424 //msg_Dbg( p_intf, "Adding podcast: (%d) %s", item, p_item->p_input->psz_uri );
425 addPodcastItem( p_item );
427 playlist_Unlock( THEPL );
429 podcastsParent->setExpanded( true );
432 void PLSelector::plItemRemoved( int id )
434 int c = podcastsParent->childCount();
435 for( int i = 0; i < c; i++ )
437 QTreeWidgetItem *item = podcastsParent->child(i);
438 if( item->data( 0, PL_ITEM_ID_ROLE ).toInt() == id )
440 input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
441 //msg_Dbg( p_intf, "Removing podcast: (%d) %s", id, p_input->psz_uri );
442 vlc_gc_decref( p_input );
449 void PLSelector::inputItemUpdate( input_item_t *arg )
451 int c = podcastsParent->childCount();
452 for( int i = 0; i < c; i++ )
454 QTreeWidgetItem *item = podcastsParent->child(i);
455 input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
458 PLSelItem *si = itemWidget( item );
459 char *psz_name = input_item_GetName( p_input );
460 si->setText( qfu( psz_name ) );
467 void PLSelector::podcastAdd( PLSelItem * )
470 QString url = QInputDialog::getText( this, qtr( "Subscribe" ),
471 qtr( "Enter URL of the podcast to subscribe to:" ),
472 QLineEdit::Normal, QString(), &ok );
473 if( !ok || url.isEmpty() ) return;
475 setSource( podcastsParent ); //to load the SD in case it's not loaded
477 vlc_object_t *p_obj = (vlc_object_t*) vlc_object_find_name( p_intf->p_libvlc, "podcast" );
480 QString request("ADD:");
481 request += url.trimmed();
482 var_SetString( p_obj, "podcast-request", qtu( request ) );
483 vlc_object_release( p_obj );
486 void PLSelector::podcastRemove( PLSelItem* item )
488 QString question ( qtr( "Do you really want to unsubscribe from %1?" ) );
489 question = question.arg( item->text() );
490 QMessageBox::StandardButton res =
491 QMessageBox::question( this, qtr( "Unsubscribe" ), question,
492 QMessageBox::Ok | QMessageBox::Cancel,
493 QMessageBox::Cancel );
494 if( res == QMessageBox::Cancel ) return;
496 input_item_t *input = item->treeItem()->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
499 vlc_object_t *p_obj = (vlc_object_t*) vlc_object_find_name(
500 p_intf->p_libvlc, "podcast" );
503 QString request("RM:");
504 char *psz_uri = input_item_GetURI( input );
505 request += qfu( psz_uri );
506 var_SetString( p_obj, "podcast-request", qtu( request ) );
507 vlc_object_release( p_obj );
511 PLSelItem * PLSelector::itemWidget( QTreeWidgetItem *item )
513 return ( static_cast<PLSelItem*>( QTreeWidget::itemWidget( item, 0 ) ) );
516 void PLSelector::drawBranches ( QPainter * painter, const QRect & rect, const QModelIndex & index ) const
518 if( !model()->hasChildren( index ) ) return;
520 option.initFrom( this );
521 option.rect = rect.adjusted( rect.width() - indentation(), 0, 0, 0 );
522 style()->drawPrimitive( isExpanded( index ) ?
523 QStyle::PE_IndicatorArrowDown :
524 QStyle::PE_IndicatorArrowRight, &option, painter );
527 void PLSelector::getCurrentSelectedItem( int* type, QString *string)
529 *type = currentItem()->data( 0, TYPE_ROLE ).toInt();
530 *string = currentItem()->data( 0, NAME_ROLE ).toString();
533 void PLSelector::wheelEvent( QWheelEvent *e )
535 // Accept this event in order to prevent unwanted volume up/down changes