]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/selector.cpp
Qt: selector cosmetic polish
[vlc] / modules / gui / qt4 / components / playlist / selector.cpp
1 /*****************************************************************************
2  * selector.cpp : Playlist source selector
3  ****************************************************************************
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  * 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 <assert.h>
30
31 #include "components/playlist/selector.hpp"
32 #include "playlist_item.hpp"
33 #include "qt4.hpp"
34 #include "../../dialogs_provider.hpp"
35 #include "playlist.hpp"
36 #include "util/customwidgets.hpp"
37
38 #include <QVBoxLayout>
39 #include <QHeaderView>
40 #include <QMimeData>
41 #include <QInputDialog>
42 #include <QMessageBox>
43
44 #include <vlc_playlist.h>
45 #include <vlc_services_discovery.h>
46
47 void SelectorActionButton::paintEvent( QPaintEvent *event )
48 {
49     QPainter p( this );
50     QColor color = palette().color( QPalette::HighlightedText );
51     color.setAlpha( 80 );
52     if( underMouse() )
53         p.fillRect( rect(), color );
54     p.setPen( color );
55     int frame = style()->pixelMetric( QStyle::PM_DefaultFrameWidth, 0, this );
56     p.drawLine( rect().topLeft() + QPoint( 0, frame ),
57                 rect().bottomLeft() - QPoint( 0, frame ) );
58     QVLCFramelessButton::paintEvent( event );
59 }
60
61 PLSelItem::PLSelItem ( QTreeWidgetItem *i, const QString& text )
62     : qitem(i), lblAction( NULL)
63 {
64     layout = new QHBoxLayout();
65     layout->setContentsMargins(0,0,0,0);
66     layout->addSpacing( 3 );
67
68     lbl = new QLabel( text );
69
70     layout->addWidget(lbl, 1);
71
72     setLayout( layout );
73
74     setMinimumHeight( 22 ); //Action icon height plus 6
75 }
76
77 void PLSelItem::addAction( ItemAction act, const QString& tooltip )
78 {
79     if( lblAction ) return; //might change later
80
81     QIcon icon;
82
83     switch( act )
84     {
85     case ADD_ACTION:
86         icon = QIcon( ":/buttons/playlist/playlist_add" ); break;
87     case RM_ACTION:
88         icon = QIcon( ":/buttons/playlist/playlist_remove" ); break;
89     }
90
91     lblAction = new SelectorActionButton();
92     lblAction->setIcon( icon );
93     lblAction->setMinimumWidth( lblAction->sizeHint().width() + 6 );
94
95     if( !tooltip.isEmpty() ) lblAction->setToolTip( tooltip );
96
97     layout->addWidget( lblAction, 0 );
98     lblAction->hide();
99
100     CONNECT( lblAction, clicked(), this, triggerAction() );
101 }
102
103 void PLSelItem::showAction() { if( lblAction ) lblAction->show(); }
104
105 void PLSelItem::hideAction() { if( lblAction ) lblAction->hide(); }
106
107 void PLSelItem::setText( const QString& text ) { lbl->setText( text ); }
108
109 void PLSelItem::enterEvent( QEvent *ev ){ showAction(); }
110
111 void PLSelItem::leaveEvent( QEvent *ev ){ hideAction(); }
112
113 PLSelector::PLSelector( QWidget *p, intf_thread_t *_p_intf )
114            : QTreeWidget( p ), p_intf(_p_intf)
115 {
116     setFrameStyle( QFrame::NoFrame );
117     viewport()->setAutoFillBackground( false );
118     setIconSize( QSize( 24,24 ) );
119     setIndentation( 12 );
120     header()->hide();
121     setRootIsDecorated( true );
122     setAlternatingRowColors( false );
123     podcastsParent = NULL;
124     podcastsParentId = -1;
125
126     viewport()->setAcceptDrops(true);
127     setDropIndicatorShown(true);
128     invisibleRootItem()->setFlags( invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled );
129
130     CONNECT( THEMIM, playlistItemAppended( int, int ),
131              this, plItemAdded( int, int ) );
132     CONNECT( THEMIM, playlistItemRemoved( int ),
133              this, plItemRemoved( int ) );
134     CONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
135             this, inputItemUpdate( input_item_t * ) );
136
137     createItems();
138     CONNECT( this, itemActivated( QTreeWidgetItem *, int ),
139              this, setSource( QTreeWidgetItem *) );
140     CONNECT( this, itemClicked( QTreeWidgetItem *, int ),
141              this, setSource( QTreeWidgetItem *) );
142
143     /* I believe this is unnecessary, seeing
144        QStyle::SH_ItemView_ActivateItemOnSingleClick
145         CONNECT( view, itemClicked( QTreeWidgetItem *, int ),
146              this, setSource( QTreeWidgetItem *) ); */
147
148     /* select the first item */
149 //  view->setCurrentIndex( model->index( 0, 0, QModelIndex() ) );
150 }
151
152 PLSelector::~PLSelector()
153 {
154     if( podcastsParent )
155     {
156         int c = podcastsParent->childCount();
157         for( int i = 0; i < c; i++ )
158         {
159             QTreeWidgetItem *item = podcastsParent->child(i);
160             input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
161             vlc_gc_decref( p_input );
162         }
163     }
164 }
165
166 void PLSelector::setSource( QTreeWidgetItem *item )
167 {
168     if( !item )
169         return;
170
171     bool b_ok;
172     int i_type = item->data( 0, TYPE_ROLE ).toInt( &b_ok );
173     if( !b_ok || i_type == CATEGORY_TYPE )
174         return;
175
176     bool sd_loaded;
177     if( i_type == SD_TYPE )
178     {
179         QString qs = item->data( 0, NAME_ROLE ).toString();
180         sd_loaded = playlist_IsServicesDiscoveryLoaded( THEPL, qtu( qs ) );
181         if( !sd_loaded )
182             playlist_ServicesDiscoveryAdd( THEPL, qtu( qs ) );
183     }
184
185     playlist_Lock( THEPL );
186
187     playlist_item_t *pl_item = NULL;
188
189     if( i_type == SD_TYPE )
190     {
191         pl_item = playlist_ChildSearchName( THEPL->p_root, qtu( item->data(0, LONGNAME_ROLE ).toString() ) );
192         if( item->data( 0, SPECIAL_ROLE ).toInt() == IS_PODCAST )
193         {
194             if( pl_item && !sd_loaded )
195             {
196                 podcastsParentId = pl_item->i_id;
197                 for( int i=0; i < pl_item->i_children; i++ )
198                     addPodcastItem( pl_item->pp_children[i] );
199             }
200             pl_item = NULL; //to prevent activating it
201         }
202     }
203     else
204         pl_item = item->data( 0, PL_ITEM_ROLE ).value<playlist_item_t*>();
205
206     playlist_Unlock( THEPL );
207
208     if( pl_item )
209        emit activated( pl_item );
210 }
211
212 PLSelItem * PLSelector::addItem (
213   SelectorItemType type, const QString& str, bool drop,
214   QTreeWidgetItem* parentItem )
215 {
216   QTreeWidgetItem *item = parentItem ?
217       new QTreeWidgetItem( parentItem ) : new QTreeWidgetItem( this );
218   PLSelItem *selItem = new PLSelItem( item, str );
219   setItemWidget( item, 0, selItem );
220   item->setData( 0, TYPE_ROLE, (int)type );
221   if( !drop ) item->setFlags( item->flags() & ~Qt::ItemIsDropEnabled );
222
223   return selItem;
224 }
225
226 PLSelItem * putSDData( PLSelItem* item, const char* name, const char* longname )
227 {
228     item->treeItem()->setData( 0, NAME_ROLE, qfu( name ) );
229     item->treeItem()->setData( 0, LONGNAME_ROLE, qfu( longname ) );
230     return item;
231 }
232
233 PLSelItem * putPLData( PLSelItem* item, playlist_item_t* plItem )
234 {
235     item->treeItem()->setData( 0, PL_ITEM_ROLE, QVariant::fromValue( plItem ) );
236 /*    item->setData( 0, PL_ITEM_ID_ROLE, plItem->i_id );
237     item->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( (void*) plItem->p_input ) ); );*/
238     return item;
239 }
240
241 PLSelItem *PLSelector::addPodcastItem( playlist_item_t *p_item )
242 {
243   vlc_gc_incref( p_item->p_input );
244   char *psz_name = input_item_GetName( p_item->p_input );
245   PLSelItem *item = addItem(
246       PL_ITEM_TYPE, qfu( psz_name ), false, podcastsParent );
247   item->addAction( RM_ACTION, qtr( "Remove this podcast subscription" ) );
248   item->treeItem()->setData( 0, PL_ITEM_ROLE, QVariant::fromValue( p_item ) );
249   item->treeItem()->setData( 0, PL_ITEM_ID_ROLE, QVariant(p_item->i_id) );
250   item->treeItem()->setData( 0, IN_ITEM_ROLE, QVariant::fromValue( p_item->p_input ) );
251   CONNECT( item, action( PLSelItem* ), this, podcastRemove( PLSelItem* ) );
252   free( psz_name );
253   return item;
254 }
255
256 void PLSelector::createItems()
257 {
258     PLSelItem *pl = putPLData( addItem( PL_ITEM_TYPE, qtr( "Playlist" ), true ),
259                               THEPL->p_playing );
260     pl->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_PL ) );
261
262     PLSelItem *ml = putPLData( addItem( PL_ITEM_TYPE, qtr( "Media Library" ), true ),
263                               THEPL->p_media_library );
264     ml->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_ML ) );
265
266     QTreeWidgetItem *mycomp = addItem( CATEGORY_TYPE, qtr( "My Computer" ),
267                                         false )->treeItem();
268     QTreeWidgetItem *devices = addItem( CATEGORY_TYPE, qtr( "Devices" ),
269                                         false )->treeItem();
270     QTreeWidgetItem *lan = addItem( CATEGORY_TYPE, qtr( "Local Network" ),
271                                     false )->treeItem();
272     QTreeWidgetItem *internet = addItem( CATEGORY_TYPE, qtr( "Internet" ),
273                                           false )->treeItem();;
274
275     char **ppsz_longnames;
276     int *p_categories;
277     char **ppsz_names = vlc_sd_GetNames( THEPL, &ppsz_longnames, &p_categories );
278     if( !ppsz_names )
279         return;
280
281     char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
282     int *p_category = p_categories;
283     for( ; *ppsz_name; ppsz_name++, ppsz_longname++, p_category++ )
284     {
285         //msg_Dbg( p_intf, "Adding a SD item: %s", *ppsz_longname );
286
287         if( *p_category == SD_CAT_INTERNET )
288         {
289             PLSelItem *selItem = addItem( SD_TYPE, *ppsz_longname, false, internet );
290             putSDData( selItem, *ppsz_name, *ppsz_longname );
291             if( !strncmp( *ppsz_name, "podcast", 7 ) )
292             {
293                 selItem->treeItem()->setData( 0, SPECIAL_ROLE, QVariant( IS_PODCAST ) );
294                 selItem->addAction( ADD_ACTION, qtr( "Subscribe to a podcast" ) );
295                 CONNECT( selItem, action( PLSelItem* ), this, podcastAdd( PLSelItem* ) );
296                 podcastsParent = selItem->treeItem();
297             }
298         }
299         else if( *p_category == SD_CAT_DEVICES )
300         {
301             putSDData( addItem( SD_TYPE, *ppsz_longname, false, devices ),
302                        *ppsz_name, *ppsz_longname );
303         }
304         else if( *p_category == SD_CAT_LAN )
305         {
306             putSDData( addItem( SD_TYPE, *ppsz_longname, false, lan ),
307                        *ppsz_name, *ppsz_longname );
308         }
309         else if( *p_category == SD_CAT_MYCOMPUTER )
310         {
311             putSDData( addItem( SD_TYPE, *ppsz_longname, false, mycomp ),
312                        *ppsz_name, *ppsz_longname );
313         }
314         else
315         {
316             putSDData( addItem( SD_TYPE, qtr( *ppsz_longname ), false ),
317                        *ppsz_name, *ppsz_longname );
318         }
319
320         free( *ppsz_name );
321         free( *ppsz_longname );
322     }
323     free( ppsz_names );
324     free( ppsz_longnames );
325     free( p_categories );
326
327     if( mycomp->childCount() == 0 ) delete mycomp;
328     if( devices->childCount() == 0 ) delete devices;
329     if( lan->childCount() == 0 ) delete lan;
330     if( internet->childCount() == 0 ) delete internet;
331 }
332
333 QStringList PLSelector::mimeTypes() const
334 {
335     QStringList types;
336     types << "vlc/qt-playlist-item";
337     return types;
338 }
339
340 bool PLSelector::dropMimeData ( QTreeWidgetItem * parent, int index,
341   const QMimeData * data, Qt::DropAction action )
342 {
343     if( !parent ) return false;
344
345     QVariant type = parent->data( 0, TYPE_ROLE );
346     if( type == QVariant() ) return false;
347     int i_truth = parent->data( 0, SPECIAL_ROLE ).toInt();
348
349     if( i_truth != IS_PL && i_truth != IS_ML ) return false;
350     bool to_pl = ( i_truth == IS_PL );
351
352     if( data->hasFormat( "vlc/qt-playlist-item" ) )
353     {
354         QByteArray encodedData = data->data( "vlc/qt-playlist-item" );
355         QDataStream stream( &encodedData, QIODevice::ReadOnly );
356         playlist_Lock( THEPL );
357         while( !stream.atEnd() )
358         {
359             PLItem *item;
360             stream.readRawData( (char*)&item, sizeof(PLItem*) );
361             input_item_t *pl_input =item->inputItem();
362             playlist_AddExt ( THEPL,
363                 pl_input->psz_uri, pl_input->psz_name,
364                 PLAYLIST_APPEND | PLAYLIST_SPREPARSE, PLAYLIST_END,
365                 pl_input->i_duration,
366                 pl_input->i_options, pl_input->ppsz_options, pl_input->optflagc,
367                 to_pl, true );
368         }
369         playlist_Unlock( THEPL );
370     }
371     return true;
372 }
373
374 void PLSelector::plItemAdded( int item, int parent )
375 {
376     if( parent != podcastsParentId ) return;
377
378     playlist_Lock( THEPL );
379
380     playlist_item_t *p_item = playlist_ItemGetById( THEPL, item );
381     if( !p_item ) {
382         playlist_Unlock( THEPL );
383         return;
384     }
385
386     int c = podcastsParent->childCount();
387     for( int i = 0; i < c; i++ )
388     {
389         QTreeWidgetItem *podItem = podcastsParent->child(i);
390         if( podItem->data( 0, PL_ITEM_ID_ROLE ).toInt() == item )
391         {
392           //msg_Dbg( p_intf, "Podcast already in: (%d) %s", item, p_item->p_input->psz_uri);
393           playlist_Unlock( THEPL );
394           return;
395         }
396     }
397
398     //msg_Dbg( p_intf, "Adding podcast: (%d) %s", item, p_item->p_input->psz_uri );
399     addPodcastItem( p_item );
400
401     playlist_Unlock( THEPL );
402
403     podcastsParent->setExpanded( true );
404 }
405
406 void PLSelector::plItemRemoved( int id )
407 {
408     int c = podcastsParent->childCount();
409     for( int i = 0; i < c; i++ )
410     {
411         QTreeWidgetItem *item = podcastsParent->child(i);
412         if( item->data( 0, PL_ITEM_ID_ROLE ).toInt() == id )
413         {
414             input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
415             //msg_Dbg( p_intf, "Removing podcast: (%d) %s", id, p_input->psz_uri );
416             vlc_gc_decref( p_input );
417             delete item;
418             return;
419         }
420     }
421 }
422
423 void PLSelector::inputItemUpdate( input_item_t *arg )
424 {
425     int c = podcastsParent->childCount();
426     for( int i = 0; i < c; i++ )
427     {
428         QTreeWidgetItem *item = podcastsParent->child(i);
429         input_item_t *p_input = item->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
430         if( p_input == arg )
431         {
432             PLSelItem *si = itemWidget( item );
433             char *psz_name = input_item_GetName( p_input );
434             si->setText( qfu( psz_name ) );
435             free( psz_name );
436             return;
437         }
438     }
439 }
440
441 void PLSelector::podcastAdd( PLSelItem* item )
442 {
443     bool ok;
444     QString url = QInputDialog::getText( this, qtr( "Subscribe" ),
445                                          qtr( "Enter URL of the podcast to subscribe to:" ),
446                                          QLineEdit::Normal, QString(), &ok );
447     if( !ok || url.isEmpty() ) return;
448
449     setSource( podcastsParent ); //to load the SD in case it's not loaded
450
451     vlc_object_t *p_obj = (vlc_object_t*) vlc_object_find_name(
452         p_intf->p_libvlc, "podcast", FIND_CHILD );
453     if( !p_obj ) return;
454
455     QString request("ADD:");
456     request += url;
457     var_SetString( p_obj, "podcast-request", qtu( request ) );
458     vlc_object_release( p_obj );
459 }
460
461 void PLSelector::podcastRemove( PLSelItem* item )
462 {
463     //FIXME will translators know to leave that %1 somewhere inside?
464     QString question ( qtr( "Do you really want to unsubscribe from %1?" ) );
465     question = question.arg( item->text() );
466     QMessageBox::StandardButton res =
467         QMessageBox::question( this, qtr( "Unsubscribe" ), question,
468                                QMessageBox::Ok | QMessageBox::Cancel,
469                                QMessageBox::Cancel );
470     if( res == QMessageBox::Cancel ) return;
471
472     input_item_t *input = item->treeItem()->data( 0, IN_ITEM_ROLE ).value<input_item_t*>();
473     if( !input ) return;
474
475     vlc_object_t *p_obj = (vlc_object_t*) vlc_object_find_name(
476         p_intf->p_libvlc, "podcast", FIND_CHILD );
477     if( !p_obj ) return;
478
479     QString request("RM:");
480     char *psz_uri = input_item_GetURI( input );
481     request += qfu( psz_uri );
482     var_SetString( p_obj, "podcast-request", qtu( request ) );
483     vlc_object_release( p_obj );
484     free( psz_uri );
485 }
486
487 PLSelItem * PLSelector::itemWidget( QTreeWidgetItem *item )
488 {
489     return ( static_cast<PLSelItem*>( QTreeWidget::itemWidget( item, 0 ) ) );
490 }
491
492 void PLSelector::drawBranches ( QPainter * painter, const QRect & rect, const QModelIndex & index ) const
493 {
494     if( !model()->hasChildren( index ) ) return;
495     QStyleOption option;
496     option.initFrom( this );
497     option.rect = rect.adjusted( rect.width() - indentation(), 0, 0, 0 );
498
499     /*option.state = QStyle::State_Children;
500     if( isExpanded( index ) ) option.state |=  QStyle::State_Open;*/
501     style()->drawPrimitive( isExpanded( index ) ?
502                             QStyle::PE_IndicatorArrowDown :
503                             QStyle::PE_IndicatorArrowRight, &option, painter );
504 }