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
) );
199 addItem( SQL_ML_TYPE
, "SQL Media Library" )->treeItem();
203 QTreeWidgetItem
*mycomp
= addItem( CATEGORY_TYPE
, N_("My Computer") )->treeItem();
204 QTreeWidgetItem
*devices
= addItem( CATEGORY_TYPE
, N_("Devices") )->treeItem();
205 QTreeWidgetItem
*lan
= addItem( CATEGORY_TYPE
, N_("Local Network") )->treeItem();
206 QTreeWidgetItem
*internet
= addItem( CATEGORY_TYPE
, N_("Internet") )->treeItem();
209 char **ppsz_longnames
;
211 char **ppsz_names
= vlc_sd_GetNames( THEPL
, &ppsz_longnames
, &p_categories
);
215 char **ppsz_name
= ppsz_names
, **ppsz_longname
= ppsz_longnames
;
216 int *p_category
= p_categories
;
217 for( ; *ppsz_name
; ppsz_name
++, ppsz_longname
++, p_category
++ )
219 //msg_Dbg( p_intf, "Adding a SD item: %s", *ppsz_longname );
222 switch( *p_category
)
224 case SD_CAT_INTERNET
:
226 selItem
= addItem( SD_TYPE
, *ppsz_longname
, false, internet
);
227 if( !strncmp( *ppsz_name
, "podcast", 7 ) )
229 selItem
->treeItem()->setData( 0, SPECIAL_ROLE
, QVariant( IS_PODCAST
) );
230 selItem
->addAction( ADD_ACTION
, qtr( "Subscribe to a podcast" ) );
231 CONNECT( selItem
, action( PLSelItem
* ), this, podcastAdd( PLSelItem
* ) );
232 podcastsParent
= selItem
->treeItem();
237 selItem
= addItem( SD_TYPE
, *ppsz_longname
, false, devices
);
240 selItem
= addItem( SD_TYPE
, *ppsz_longname
, false, lan
);
242 case SD_CAT_MYCOMPUTER
:
243 selItem
= addItem( SD_TYPE
, *ppsz_longname
, false, mycomp
);
246 selItem
= addItem( SD_TYPE
, *ppsz_longname
);
249 putSDData( selItem
, *ppsz_name
, *ppsz_longname
);
251 free( *ppsz_longname
);
254 free( ppsz_longnames
);
255 free( p_categories
);
257 if( mycomp
->childCount() == 0 ) delete mycomp
;
258 if( devices
->childCount() == 0 ) delete devices
;
259 if( lan
->childCount() == 0 ) delete lan
;
260 if( internet
->childCount() == 0 ) delete internet
;
263 void PLSelector::setSource( QTreeWidgetItem
*item
)
265 if( !item
|| item
== curItem
)
271 int i_type
= item
->data( 0, TYPE_ROLE
).toInt( &b_ok
);
272 if( !b_ok
|| i_type
== CATEGORY_TYPE
)
276 if( i_type
== SD_TYPE
)
278 QString qs
= item
->data( 0, NAME_ROLE
).toString();
279 sd_loaded
= playlist_IsServicesDiscoveryLoaded( THEPL
, qtu( qs
) );
281 playlist_ServicesDiscoveryAdd( THEPL
, qtu( qs
) );
284 else if( i_type
== SQL_ML_TYPE
)
286 emit
categoryActivated( NULL
, true );
292 playlist_Lock( THEPL
);
293 playlist_item_t
*pl_item
= NULL
;
295 /* Special case for podcast */
297 if( i_type
== SD_TYPE
)
299 /* Find the right item for the SD */
300 pl_item
= playlist_ChildSearchName( THEPL
->p_root
,
301 qtu( item
->data(0, LONGNAME_ROLE
).toString() ) );
304 if( item
->data( 0, SPECIAL_ROLE
).toInt() == IS_PODCAST
)
306 if( pl_item
&& !sd_loaded
)
308 podcastsParentId
= pl_item
->i_id
;
309 for( int i
=0; i
< pl_item
->i_children
; i
++ )
310 addPodcastItem( pl_item
->pp_children
[i
] );
312 pl_item
= NULL
; //to prevent activating it
316 pl_item
= item
->data( 0, PL_ITEM_ROLE
).value
<playlist_item_t
*>();
318 playlist_Unlock( THEPL
);
322 emit
categoryActivated( pl_item
, false );
325 PLSelItem
* PLSelector::addItem (
326 SelectorItemType type
, const char* str
, bool drop
,
327 QTreeWidgetItem
* parentItem
)
329 QTreeWidgetItem
*item
= parentItem
?
330 new QTreeWidgetItem( parentItem
) : new QTreeWidgetItem( this );
332 PLSelItem
*selItem
= new PLSelItem( item
, qtr( str
) );
333 setItemWidget( item
, 0, selItem
);
334 item
->setData( 0, TYPE_ROLE
, (int)type
);
335 if( !drop
) item
->setFlags( item
->flags() & ~Qt::ItemIsDropEnabled
);
340 PLSelItem
*PLSelector::addPodcastItem( playlist_item_t
*p_item
)
342 vlc_gc_incref( p_item
->p_input
);
344 char *psz_name
= input_item_GetName( p_item
->p_input
);
345 PLSelItem
*item
= addItem( PL_ITEM_TYPE
, psz_name
, false, podcastsParent
);
348 item
->addAction( RM_ACTION
, qtr( "Remove this podcast subscription" ) );
349 item
->treeItem()->setData( 0, PL_ITEM_ROLE
, QVariant::fromValue( p_item
) );
350 item
->treeItem()->setData( 0, PL_ITEM_ID_ROLE
, QVariant(p_item
->i_id
) );
351 item
->treeItem()->setData( 0, IN_ITEM_ROLE
, QVariant::fromValue( p_item
->p_input
) );
352 CONNECT( item
, action( PLSelItem
* ), this, podcastRemove( PLSelItem
* ) );
356 QStringList
PLSelector::mimeTypes() const
359 types
<< "vlc/qt-input-items";
363 bool PLSelector::dropMimeData ( QTreeWidgetItem
* parent
, int,
364 const QMimeData
* data
, Qt::DropAction
)
366 if( !parent
) return false;
368 QVariant type
= parent
->data( 0, TYPE_ROLE
);
369 if( type
== QVariant() ) return false;
371 int i_truth
= parent
->data( 0, SPECIAL_ROLE
).toInt();
372 if( i_truth
!= IS_PL
&& i_truth
!= IS_ML
) return false;
374 bool to_pl
= ( i_truth
== IS_PL
);
376 const PlMimeData
*plMimeData
= qobject_cast
<const PlMimeData
*>( data
);
377 if( !plMimeData
) return false;
379 QList
<input_item_t
*> inputItems
= plMimeData
->inputItems();
381 playlist_Lock( THEPL
);
383 foreach( input_item_t
*p_input
, inputItems
)
385 playlist_item_t
*p_item
= playlist_ItemGetByInput( THEPL
, p_input
);
386 if( !p_item
) continue;
388 playlist_NodeAddCopy( THEPL
, p_item
,
389 to_pl
? THEPL
->p_playing
: THEPL
->p_media_library
,
393 playlist_Unlock( THEPL
);
398 void PLSelector::dragMoveEvent ( QDragMoveEvent
* event
)
400 event
->setDropAction( Qt::CopyAction
);
401 QAbstractItemView::dragMoveEvent( event
);
404 void PLSelector::plItemAdded( int item
, int parent
)
406 if( parent
!= podcastsParentId
) return;
408 playlist_Lock( THEPL
);
410 playlist_item_t
*p_item
= playlist_ItemGetById( THEPL
, item
);
412 playlist_Unlock( THEPL
);
416 int c
= podcastsParent
->childCount();
417 for( int i
= 0; i
< c
; i
++ )
419 QTreeWidgetItem
*podItem
= podcastsParent
->child(i
);
420 if( podItem
->data( 0, PL_ITEM_ID_ROLE
).toInt() == item
)
422 //msg_Dbg( p_intf, "Podcast already in: (%d) %s", item, p_item->p_input->psz_uri);
423 playlist_Unlock( THEPL
);
428 //msg_Dbg( p_intf, "Adding podcast: (%d) %s", item, p_item->p_input->psz_uri );
429 addPodcastItem( p_item
);
431 playlist_Unlock( THEPL
);
433 podcastsParent
->setExpanded( true );
436 void PLSelector::plItemRemoved( int id
)
438 int c
= podcastsParent
->childCount();
439 for( int i
= 0; i
< c
; i
++ )
441 QTreeWidgetItem
*item
= podcastsParent
->child(i
);
442 if( item
->data( 0, PL_ITEM_ID_ROLE
).toInt() == id
)
444 input_item_t
*p_input
= item
->data( 0, IN_ITEM_ROLE
).value
<input_item_t
*>();
445 //msg_Dbg( p_intf, "Removing podcast: (%d) %s", id, p_input->psz_uri );
446 vlc_gc_decref( p_input
);
453 void PLSelector::inputItemUpdate( input_item_t
*arg
)
455 int c
= podcastsParent
->childCount();
456 for( int i
= 0; i
< c
; i
++ )
458 QTreeWidgetItem
*item
= podcastsParent
->child(i
);
459 input_item_t
*p_input
= item
->data( 0, IN_ITEM_ROLE
).value
<input_item_t
*>();
462 PLSelItem
*si
= itemWidget( item
);
463 char *psz_name
= input_item_GetName( p_input
);
464 si
->setText( qfu( psz_name
) );
471 void PLSelector::podcastAdd( PLSelItem
* )
474 QString url
= QInputDialog::getText( this, qtr( "Subscribe" ),
475 qtr( "Enter URL of the podcast to subscribe to:" ),
476 QLineEdit::Normal
, QString(), &ok
);
477 if( !ok
|| url
.isEmpty() ) return;
479 setSource( podcastsParent
); //to load the SD in case it's not loaded
481 vlc_object_t
*p_obj
= (vlc_object_t
*) vlc_object_find_name( p_intf
->p_libvlc
, "podcast" );
484 QString
request("ADD:");
485 request
+= url
.trimmed();
486 var_SetString( p_obj
, "podcast-request", qtu( request
) );
487 vlc_object_release( p_obj
);
490 void PLSelector::podcastRemove( PLSelItem
* item
)
492 QString
question ( qtr( "Do you really want to unsubscribe from %1?" ) );
493 question
= question
.arg( item
->text() );
494 QMessageBox::StandardButton res
=
495 QMessageBox::question( this, qtr( "Unsubscribe" ), question
,
496 QMessageBox::Ok
| QMessageBox::Cancel
,
497 QMessageBox::Cancel
);
498 if( res
== QMessageBox::Cancel
) return;
500 input_item_t
*input
= item
->treeItem()->data( 0, IN_ITEM_ROLE
).value
<input_item_t
*>();
503 vlc_object_t
*p_obj
= (vlc_object_t
*) vlc_object_find_name(
504 p_intf
->p_libvlc
, "podcast" );
507 QString
request("RM:");
508 char *psz_uri
= input_item_GetURI( input
);
509 request
+= qfu( psz_uri
);
510 var_SetString( p_obj
, "podcast-request", qtu( request
) );
511 vlc_object_release( p_obj
);
515 PLSelItem
* PLSelector::itemWidget( QTreeWidgetItem
*item
)
517 return ( static_cast<PLSelItem
*>( QTreeWidget::itemWidget( item
, 0 ) ) );
520 void PLSelector::drawBranches ( QPainter
* painter
, const QRect
& rect
, const QModelIndex
& index
) const
522 if( !model()->hasChildren( index
) ) return;
524 option
.initFrom( this );
525 option
.rect
= rect
.adjusted( rect
.width() - indentation(), 0, 0, 0 );
526 style()->drawPrimitive( isExpanded( index
) ?
527 QStyle::PE_IndicatorArrowDown
:
528 QStyle::PE_IndicatorArrowRight
, &option
, painter
);
531 void PLSelector::getCurrentSelectedItem( int* type
, QString
*string
)
533 *type
= currentItem()->data( 0, TYPE_ROLE
).toInt();
534 *string
= currentItem()->data( 0, NAME_ROLE
).toString();
537 int PLSelector::getCurrentItemCategory()
539 return currentItem()->data( 0, SPECIAL_ROLE
).toInt();
542 void PLSelector::wheelEvent( QWheelEvent
*e
)
544 // Accept this event in order to prevent unwanted volume up/down changes