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 *****************************************************************************/
31 #include "components/playlist/selector.hpp"
32 #include "playlist_item.hpp"
34 #include "../../dialogs_provider.hpp"
35 #include "playlist.hpp"
36 #include "util/customwidgets.hpp"
38 #include <QVBoxLayout>
39 #include <QHeaderView>
41 #include <QInputDialog>
42 #include <QMessageBox>
44 #include <vlc_playlist.h>
45 #include <vlc_services_discovery.h>
47 PLSelItem::PLSelItem ( QTreeWidgetItem
*i
, const QString
& text
)
48 : qitem(i
), lblAction( NULL
)
50 layout
= new QHBoxLayout();
51 layout
->setContentsMargins(0,0,0,0);
52 layout
->addSpacing( 3 );
54 lbl
= new QLabel( text
);
56 layout
->addWidget(lbl
, 1);
60 setMinimumHeight( 22 ); //Action icon height plus 6
63 void PLSelItem::addAction( ItemAction act
, const QString
& tooltip
)
65 if( lblAction
) return; //might change later
72 icon
= QIcon( ":/buttons/playlist/playlist_add" ); break;
74 icon
= QIcon( ":/buttons/playlist/playlist_remove" ); break;
77 lblAction
= new QVLCIconLabel( icon
);
79 if( !tooltip
.isEmpty() ) lblAction
->setToolTip( tooltip
);
81 layout
->addWidget( lblAction
, 0 );
83 layout
->addSpacing( 3 );
85 CONNECT( lblAction
, clicked(), this, triggerAction() );
88 void PLSelItem::showAction() { if( lblAction
) lblAction
->show(); }
90 void PLSelItem::hideAction() { if( lblAction
) lblAction
->hide(); }
92 void PLSelItem::setText( const QString
& text
) { lbl
->setText( text
); }
94 void PLSelItem::enterEvent( QEvent
*ev
){ showAction(); }
96 void PLSelItem::leaveEvent( QEvent
*ev
){ hideAction(); }
98 PLSelector::PLSelector( QWidget
*p
, intf_thread_t
*_p_intf
)
99 : QTreeWidget( p
), p_intf(_p_intf
)
101 setFrameStyle( QFrame::NoFrame
);
102 viewport()->setAutoFillBackground( false );
103 setIconSize( QSize( 24,24 ) );
104 setIndentation( 10 );
106 setRootIsDecorated( false );
107 setAlternatingRowColors( false );
108 podcastsParent
= NULL
;
109 podcastsParentId
= -1;
111 viewport()->setAcceptDrops(true);
112 setDropIndicatorShown(true);
113 invisibleRootItem()->setFlags( invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled
);
115 CONNECT( THEMIM
, playlistItemAppended( int, int ),
116 this, plItemAdded( int, int ) );
117 CONNECT( THEMIM
, playlistItemRemoved( int ),
118 this, plItemRemoved( int ) );
119 CONNECT( THEMIM
->getIM(), metaChanged( input_item_t
*),
120 this, inputItemUpdate( input_item_t
* ) );
123 CONNECT( this, itemActivated( QTreeWidgetItem
*, int ),
124 this, setSource( QTreeWidgetItem
*) );
125 CONNECT( this, itemClicked( QTreeWidgetItem
*, int ),
126 this, setSource( QTreeWidgetItem
*) );
128 /* I believe this is unnecessary, seeing
129 QStyle::SH_ItemView_ActivateItemOnSingleClick
130 CONNECT( view, itemClicked( QTreeWidgetItem *, int ),
131 this, setSource( QTreeWidgetItem *) ); */
133 /* select the first item */
134 // view->setCurrentIndex( model->index( 0, 0, QModelIndex() ) );
137 PLSelector::~PLSelector()
141 int c
= podcastsParent
->childCount();
142 for( int i
= 0; i
< c
; i
++ )
144 QTreeWidgetItem
*item
= podcastsParent
->child(i
);
145 input_item_t
*p_input
= item
->data( 0, IN_ITEM_ROLE
).value
<input_item_t
*>();
146 vlc_gc_decref( p_input
);
151 void PLSelector::setSource( QTreeWidgetItem
*item
)
157 int i_type
= item
->data( 0, TYPE_ROLE
).toInt( &b_ok
);
158 if( !b_ok
|| i_type
== CATEGORY_TYPE
)
162 if( i_type
== SD_TYPE
)
164 QString qs
= item
->data( 0, NAME_ROLE
).toString();
165 sd_loaded
= playlist_IsServicesDiscoveryLoaded( THEPL
, qtu( qs
) );
167 playlist_ServicesDiscoveryAdd( THEPL
, qtu( qs
) );
170 playlist_Lock( THEPL
);
172 playlist_item_t
*pl_item
= NULL
;
174 if( i_type
== SD_TYPE
)
176 pl_item
= playlist_ChildSearchName( THEPL
->p_root_category
, qtu( item
->data(0, LONGNAME_ROLE
).toString() ) );
177 if( item
->data( 0, SPECIAL_ROLE
).toInt() == IS_PODCAST
)
179 if( pl_item
&& !sd_loaded
)
181 podcastsParentId
= pl_item
->i_id
;
182 for( int i
=0; i
< pl_item
->i_children
; i
++ )
183 addPodcastItem( pl_item
->pp_children
[i
] );
185 pl_item
= NULL
; //to prevent activating it
189 pl_item
= item
->data( 0, PL_ITEM_ROLE
).value
<playlist_item_t
*>();
191 playlist_Unlock( THEPL
);
194 emit
activated( pl_item
);
197 PLSelItem
* PLSelector::addItem (
198 SelectorItemType type
, const QString
& str
, bool drop
,
199 QTreeWidgetItem
* parentItem
)
201 QTreeWidgetItem
*item
= parentItem
?
202 new QTreeWidgetItem( parentItem
) : new QTreeWidgetItem( this );
203 PLSelItem
*selItem
= new PLSelItem( item
, str
);
204 setItemWidget( item
, 0, selItem
);
205 item
->setData( 0, TYPE_ROLE
, (int)type
);
206 if( !drop
) item
->setFlags( item
->flags() & ~Qt::ItemIsDropEnabled
);
211 PLSelItem
* putSDData( PLSelItem
* item
, const char* name
, const char* longname
)
213 item
->treeItem()->setData( 0, NAME_ROLE
, qfu( name
) );
214 item
->treeItem()->setData( 0, LONGNAME_ROLE
, qfu( longname
) );
218 PLSelItem
* putPLData( PLSelItem
* item
, playlist_item_t
* plItem
)
220 item
->treeItem()->setData( 0, PL_ITEM_ROLE
, QVariant::fromValue( plItem
) );
221 /* item->setData( 0, PL_ITEM_ID_ROLE, plItem->i_id );
222 item->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( (void*) plItem->p_input ) ); );*/
226 PLSelItem
*PLSelector::addPodcastItem( playlist_item_t
*p_item
)
228 vlc_gc_incref( p_item
->p_input
);
229 char *psz_name
= input_item_GetName( p_item
->p_input
);
230 PLSelItem
*item
= addItem(
231 PL_ITEM_TYPE
, qfu( psz_name
), false, podcastsParent
);
232 item
->addAction( RM_ACTION
, qtr( "Remove this podcast subscription" ) );
233 item
->treeItem()->setData( 0, PL_ITEM_ROLE
, QVariant::fromValue( p_item
) );
234 item
->treeItem()->setData( 0, PL_ITEM_ID_ROLE
, QVariant(p_item
->i_id
) );
235 item
->treeItem()->setData( 0, IN_ITEM_ROLE
, QVariant::fromValue( p_item
->p_input
) );
236 CONNECT( item
, action( PLSelItem
* ), this, podcastRemove( PLSelItem
* ) );
241 void PLSelector::createItems()
243 PLSelItem
*pl
= putPLData( addItem( PL_ITEM_TYPE
, qtr( "Playlist" ), true ),
244 THEPL
->p_local_category
);
245 pl
->treeItem()->setData( 0, SPECIAL_ROLE
, QVariant( IS_PL
) );
247 PLSelItem
*ml
= putPLData( addItem( PL_ITEM_TYPE
, qtr( "Media Library" ), true ),
248 THEPL
->p_ml_category
);
249 ml
->treeItem()->setData( 0, SPECIAL_ROLE
, QVariant( IS_ML
) );
251 QTreeWidgetItem
*msrc
= addItem( CATEGORY_TYPE
, qtr( "Media Sources" ),
254 QTreeWidgetItem
*mfldrs
= NULL
;
256 QTreeWidgetItem
*shouts
= NULL
;
258 msrc
->setExpanded( true );
260 char **ppsz_longnames
;
261 char **ppsz_names
= vlc_sd_GetNames( THEPL
, &ppsz_longnames
);
265 char **ppsz_name
= ppsz_names
, **ppsz_longname
= ppsz_longnames
;
266 for( ; *ppsz_name
; ppsz_name
++, ppsz_longname
++ )
268 //msg_Dbg( p_intf, "Adding a SD item: %s", *ppsz_longname );
269 #define SD_IS( name ) ( !strcmp( *ppsz_name, name ) )
271 if( SD_IS("shoutcast") || SD_IS("shoutcasttv") ||
272 SD_IS("frenchtv") || SD_IS("freebox") )
274 if( !shouts
) shouts
= addItem( CATEGORY_TYPE
, qtr( "Shoutcast" ),
275 false, msrc
)->treeItem();
276 putSDData( addItem( SD_TYPE
, *ppsz_longname
, false, shouts
),
277 *ppsz_name
, *ppsz_longname
);
279 else if( SD_IS("video_dir") || SD_IS("audio_dir") || SD_IS("picture_dir") )
281 if( !mfldrs
) mfldrs
= addItem( CATEGORY_TYPE
, qtr( "Media Folders" ),
282 false, msrc
)->treeItem();
283 putSDData( addItem( SD_TYPE
, *ppsz_longname
, false, mfldrs
),
284 *ppsz_name
, *ppsz_longname
);
286 else if( SD_IS("podcast") )
289 PLSelItem
*podItem
= addItem( SD_TYPE
, qtr( "Podcasts" ), false, msrc
);
290 putSDData( podItem
, *ppsz_name
, *ppsz_longname
);
291 podItem
->treeItem()->setData( 0, SPECIAL_ROLE
, QVariant( IS_PODCAST
) );
292 podItem
->addAction( ADD_ACTION
, qtr( "Subscribe to a podcast" ) );
293 CONNECT( podItem
, action( PLSelItem
* ), this, podcastAdd( PLSelItem
* ) );
295 podcastsParent
= podItem
->treeItem();
299 putSDData( addItem( SD_TYPE
, qtr( *ppsz_longname
), false, msrc
),
300 *ppsz_name
, *ppsz_longname
);
306 free( *ppsz_longname
);
309 free( ppsz_longnames
);
312 QStringList
PLSelector::mimeTypes() const
315 types
<< "vlc/qt-playlist-item";
319 bool PLSelector::dropMimeData ( QTreeWidgetItem
* parent
, int index
,
320 const QMimeData
* data
, Qt::DropAction action
)
322 if( !parent
) return false;
324 QVariant type
= parent
->data( 0, TYPE_ROLE
);
325 if( type
== QVariant() ) return false;
326 int i_truth
= parent
->data( 0, SPECIAL_ROLE
).toInt();
328 if( i_truth
!= IS_PL
&& i_truth
!= IS_ML
) return false;
329 bool to_pl
= ( i_truth
== IS_PL
);
331 if( data
->hasFormat( "vlc/qt-playlist-item" ) )
333 QByteArray encodedData
= data
->data( "vlc/qt-playlist-item" );
334 QDataStream
stream( &encodedData
, QIODevice::ReadOnly
);
335 playlist_Lock( THEPL
);
336 while( !stream
.atEnd() )
339 stream
.readRawData( (char*)&item
, sizeof(PLItem
*) );
340 input_item_t
*pl_input
=item
->inputItem();
341 playlist_AddExt ( THEPL
,
342 pl_input
->psz_uri
, pl_input
->psz_name
,
343 PLAYLIST_APPEND
| PLAYLIST_SPREPARSE
, PLAYLIST_END
,
344 pl_input
->i_duration
,
345 pl_input
->i_options
, pl_input
->ppsz_options
, pl_input
->optflagc
,
348 playlist_Unlock( THEPL
);
353 void PLSelector::plItemAdded( int item
, int parent
)
355 if( parent
!= podcastsParentId
) return;
357 playlist_Lock( THEPL
);
359 playlist_item_t
*p_item
= playlist_ItemGetById( THEPL
, item
);
361 playlist_Unlock( THEPL
);
365 int c
= podcastsParent
->childCount();
366 for( int i
= 0; i
< c
; i
++ )
368 QTreeWidgetItem
*podItem
= podcastsParent
->child(i
);
369 if( podItem
->data( 0, PL_ITEM_ID_ROLE
).toInt() == item
)
371 //msg_Dbg( p_intf, "Podcast already in: (%d) %s", item, p_item->p_input->psz_uri);
372 playlist_Unlock( THEPL
);
377 //msg_Dbg( p_intf, "Adding podcast: (%d) %s", item, p_item->p_input->psz_uri );
378 addPodcastItem( p_item
);
380 playlist_Unlock( THEPL
);
382 podcastsParent
->setExpanded( true );
385 void PLSelector::plItemRemoved( int id
)
387 int c
= podcastsParent
->childCount();
388 for( int i
= 0; i
< c
; i
++ )
390 QTreeWidgetItem
*item
= podcastsParent
->child(i
);
391 if( item
->data( 0, PL_ITEM_ID_ROLE
).toInt() == id
)
393 input_item_t
*p_input
= item
->data( 0, IN_ITEM_ROLE
).value
<input_item_t
*>();
394 //msg_Dbg( p_intf, "Removing podcast: (%d) %s", id, p_input->psz_uri );
395 vlc_gc_decref( p_input
);
402 void PLSelector::inputItemUpdate( input_item_t
*arg
)
404 int c
= podcastsParent
->childCount();
405 for( int i
= 0; i
< c
; i
++ )
407 QTreeWidgetItem
*item
= podcastsParent
->child(i
);
408 input_item_t
*p_input
= item
->data( 0, IN_ITEM_ROLE
).value
<input_item_t
*>();
411 PLSelItem
*si
= itemWidget( item
);
412 char *psz_name
= input_item_GetName( p_input
);
413 si
->setText( qfu( psz_name
) );
420 void PLSelector::podcastAdd( PLSelItem
* item
)
423 QString url
= QInputDialog::getText( this, qtr( "Subscribe" ),
424 qtr( "Enter URL of the podcast to subscribe to:" ),
425 QLineEdit::Normal
, QString(), &ok
);
426 if( !ok
|| url
.isEmpty() ) return;
428 setSource( podcastsParent
); //to load the SD in case it's not loaded
430 vlc_object_t
*p_obj
= (vlc_object_t
*) vlc_object_find_name(
431 p_intf
->p_libvlc
, "podcast", FIND_CHILD
);
434 QString
request("ADD:");
436 var_SetString( p_obj
, "podcast-request", qtu( request
) );
437 vlc_object_release( p_obj
);
440 void PLSelector::podcastRemove( PLSelItem
* item
)
442 //FIXME will translators know to leave that %1 somewhere inside?
443 QString
question ( qtr( "Do you really want to unsubscribe from %1?" ) );
444 question
= question
.arg( item
->text() );
445 QMessageBox::StandardButton res
=
446 QMessageBox::question( this, qtr( "Unsubscribe" ), question
,
447 QMessageBox::Ok
| QMessageBox::Cancel
,
448 QMessageBox::Cancel
);
449 if( res
== QMessageBox::Cancel
) return;
451 input_item_t
*input
= item
->treeItem()->data( 0, IN_ITEM_ROLE
).value
<input_item_t
*>();
454 vlc_object_t
*p_obj
= (vlc_object_t
*) vlc_object_find_name(
455 p_intf
->p_libvlc
, "podcast", FIND_CHILD
);
458 QString
request("RM:");
459 char *psz_uri
= input_item_GetURI( input
);
460 request
+= qfu( psz_uri
);
461 var_SetString( p_obj
, "podcast-request", qtu( request
) );
462 vlc_object_release( p_obj
);
466 PLSelItem
* PLSelector::itemWidget( QTreeWidgetItem
*item
)
468 return ( static_cast<PLSelItem
*>( QTreeWidget::itemWidget( item
, 0 ) ) );