]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/standardpanel.cpp
504b041e4da8c0fd84dd30312024a36412ffa98c
[vlc] / modules / gui / qt4 / components / playlist / standardpanel.cpp
1 /*****************************************************************************
2  * standardpanel.cpp : The "standard" playlist panel : just a treeview
3  ****************************************************************************
4  * Copyright (C) 2000-2009 VideoLAN
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *          JB Kempf <jb@videolan.org>
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 "dialogs_provider.hpp"
30
31 #include "components/playlist/playlist_model.hpp"
32 #include "components/playlist/standardpanel.hpp"
33 #include "components/playlist/icon_view.hpp"
34 #include "util/customwidgets.hpp"
35 #include "menus.hpp"
36
37 #include <vlc_intf_strings.h>
38
39 #include <QPushButton>
40 #include <QHeaderView>
41 #include <QKeyEvent>
42 #include <QModelIndexList>
43 #include <QLabel>
44 #include <QMenu>
45 #include <QSignalMapper>
46 #include <QWheelEvent>
47 #include <QToolButton>
48 #include <QFontMetrics>
49 #include <QPainter>
50
51 #include <assert.h>
52
53 #include "sorting.h"
54
55 StandardPLPanel::StandardPLPanel( PlaylistWidget *_parent,
56                                   intf_thread_t *_p_intf,
57                                   playlist_t *p_playlist,
58                                   playlist_item_t *p_root ):
59                                   QWidget( _parent ), p_intf( _p_intf )
60 {
61     layout = new QGridLayout( this );
62     layout->setSpacing( 0 ); layout->setMargin( 0 );
63     setMinimumWidth( 300 );
64
65     iconView = NULL;
66     treeView = NULL;
67
68     model = new PLModel( p_playlist, p_intf, p_root, this );
69     currentRootId = -1;
70     last_activated_id = -1;
71
72     locationBar = new LocationBar( model );
73     layout->addWidget( locationBar, 0, 0 );
74     CONNECT( model, rootChanged(), locationBar, setRootIndex() );
75
76     /* A Spacer and the search possibilities */
77     layout->setColumnStretch( 1, 10 );
78
79     SearchLineEdit *search = new SearchLineEdit( this );
80     search->setMaximumWidth( 300 );
81     layout->addWidget( search, 0, 4 );
82     CONNECT( search, textChanged( const QString& ),
83              this, search( const QString& ) );
84     layout->setColumnStretch( 4, 10 );
85
86     /* Add item to the playlist button */
87     addButton = new QToolButton;
88     addButton->setIcon( QIcon( ":/buttons/playlist/playlist_add" ) );
89     addButton->setMaximumWidth( 30 );
90     BUTTONACT( addButton, popupAdd() );
91     layout->addWidget( addButton, 0, 3 );
92
93     /* Button to switch views */
94     QToolButton *viewButton = new QToolButton( this );
95     viewButton->setIcon( style()->standardIcon( QStyle::SP_FileDialogDetailedView ) );
96     layout->addWidget( viewButton, 0, 2 );
97
98     /* View selection menu */
99     viewSelectionMapper = new QSignalMapper( this );
100     CONNECT( viewSelectionMapper, mapped( int ), this, showView( int ) );
101
102     QActionGroup *actionGroup = new QActionGroup( this );
103
104     treeViewAction = actionGroup->addAction( "Detailed view" );
105     treeViewAction->setCheckable( true );
106     viewSelectionMapper->setMapping( treeViewAction, TREE_VIEW );
107     CONNECT( treeViewAction, triggered(), viewSelectionMapper, map() );
108
109     iconViewAction = actionGroup->addAction( "Icon view" );
110     iconViewAction->setCheckable( true );
111     viewSelectionMapper->setMapping( iconViewAction, ICON_VIEW );
112     CONNECT( iconViewAction, triggered(), viewSelectionMapper, map() );
113
114     BUTTONACT( viewButton, cycleViews() );
115     QMenu *viewMenu = new QMenu( this );
116     viewMenu->addActions( actionGroup->actions() );
117
118     viewButton->setMenu( viewMenu );
119
120     /* Saved Settings */
121     getSettings()->beginGroup("Playlist");
122
123     int i_viewMode = getSettings()->value( "view-mode", TREE_VIEW ).toInt();
124     showView( i_viewMode );
125
126     getSettings()->endGroup();
127
128     CONNECT( THEMIM, leafBecameParent( input_item_t *),
129              this, browseInto( input_item_t * ) );
130
131     CONNECT( model, currentChanged( const QModelIndex& ),
132              this, handleExpansion( const QModelIndex& ) );
133     CONNECT( model, rootChanged(), this, handleRootChange() );
134 }
135
136 StandardPLPanel::~StandardPLPanel()
137 {
138     getSettings()->beginGroup("Playlist");
139     if( treeView )
140         getSettings()->setValue( "headerStateV2", treeView->header()->saveState() );
141     getSettings()->setValue( "view-mode", ( currentView == iconView ) ? ICON_VIEW : TREE_VIEW );
142     getSettings()->endGroup();
143 }
144
145 /* Unused anymore, but might be useful, like in right-click menu */
146 void StandardPLPanel::gotoPlayingItem()
147 {
148     currentView->scrollTo( model->currentIndex() );
149 }
150
151 void StandardPLPanel::handleExpansion( const QModelIndex& index )
152 {
153     assert( currentView );
154     currentView->scrollTo( index );
155 }
156
157 void StandardPLPanel::handleRootChange()
158 {
159     /* needed for popupAdd() */
160     PLItem *root = model->getItem( QModelIndex() );
161     currentRootId = root->id();
162
163     locationBar->setIndex( QModelIndex() );
164
165     /* enable/disable adding */
166     if( currentRootId == THEPL->p_playing->i_id )
167     {
168         addButton->setEnabled( true );
169         addButton->setToolTip( qtr(I_PL_ADDPL) );
170     }
171     else if( THEPL->p_media_library &&
172              currentRootId == THEPL->p_media_library->i_id )
173     {
174         addButton->setEnabled( true );
175         addButton->setToolTip( qtr(I_PL_ADDML) );
176     }
177     else
178         addButton->setEnabled( false );
179 }
180
181 /* PopupAdd Menu for the Add Menu */
182 void StandardPLPanel::popupAdd()
183 {
184     QMenu popup;
185     if( currentRootId == THEPL->p_playing->i_id )
186     {
187         popup.addAction( qtr(I_PL_ADDF), THEDP, SLOT( simplePLAppendDialog()) );
188         popup.addAction( qtr(I_PL_ADDDIR), THEDP, SLOT( PLAppendDir()) );
189         popup.addAction( qtr(I_OP_ADVOP), THEDP, SLOT( PLAppendDialog()) );
190     }
191     else if( THEPL->p_media_library &&
192                 currentRootId == THEPL->p_media_library->i_id )
193     {
194         popup.addAction( qtr(I_PL_ADDF), THEDP, SLOT( simpleMLAppendDialog()) );
195         popup.addAction( qtr(I_PL_ADDDIR), THEDP, SLOT( MLAppendDir() ) );
196         popup.addAction( qtr(I_OP_ADVOP), THEDP, SLOT( MLAppendDialog() ) );
197     }
198
199     popup.exec( QCursor::pos() - addButton->mapFromGlobal( QCursor::pos() )
200                         + QPoint( 0, addButton->height() ) );
201 }
202
203 void StandardPLPanel::popupPlView( const QPoint &point )
204 {
205     QModelIndex index = currentView->indexAt( point );
206     QPoint globalPoint = currentView->viewport()->mapToGlobal( point );
207     if( !index.isValid() ){
208         QVLCMenu::PopupMenu( p_intf, true );
209     }
210     else
211     {
212         QItemSelectionModel *selection = currentView->selectionModel();
213         QModelIndexList list = selection->selectedIndexes();
214         model->popup( index, globalPoint, list );
215     }
216 }
217
218 void StandardPLPanel::popupSelectColumn( QPoint pos )
219 {
220     QMenu menu;
221     assert( treeView );
222
223     /* We do not offer the option to hide index 0 column, or
224     * QTreeView will behave weird */
225     int i, j;
226     for( i = 1 << 1, j = 1; i < COLUMN_END; i <<= 1, j++ )
227     {
228         QAction* option = menu.addAction(
229             qfu( psz_column_title( i ) ) );
230         option->setCheckable( true );
231         option->setChecked( !treeView->isColumnHidden( j ) );
232         selectColumnsSigMapper->setMapping( option, j );
233         CONNECT( option, triggered(), selectColumnsSigMapper, map() );
234     }
235     menu.exec( QCursor::pos() );
236 }
237
238 void StandardPLPanel::toggleColumnShown( int i )
239 {
240     treeView->setColumnHidden( i, !treeView->isColumnHidden( i ) );
241 }
242
243 /* Search in the playlist */
244 void StandardPLPanel::search( const QString& searchText )
245 {
246     model->search( searchText );
247 }
248
249 /* Set the root of the new Playlist */
250 /* This activated by the selector selection */
251 void StandardPLPanel::setRoot( playlist_item_t *p_item )
252 {
253     model->rebuild( p_item );
254 }
255
256 /* Delete and Suppr key remove the selection
257    FilterKey function and code function */
258 void StandardPLPanel::keyPressEvent( QKeyEvent *e )
259 {
260     switch( e->key() )
261     {
262     case Qt::Key_Back:
263     case Qt::Key_Delete:
264         deleteSelection();
265         break;
266     }
267 }
268
269 void StandardPLPanel::deleteSelection()
270 {
271     QItemSelectionModel *selection = currentView->selectionModel();
272     QModelIndexList list = selection->selectedIndexes();
273     model->doDelete( list );
274 }
275
276 void StandardPLPanel::createIconView()
277 {
278     iconView = new PlIconView( model, this );
279     iconView->setContextMenuPolicy( Qt::CustomContextMenu );
280     CONNECT( iconView, customContextMenuRequested( const QPoint & ),
281              this, popupPlView( const QPoint & ) );
282     CONNECT( iconView, activated( const QModelIndex & ),
283              this, activate( const QModelIndex & ) );
284     CONNECT( locationBar, invoked( const QModelIndex & ),
285              iconView, setRootIndex( const QModelIndex & ) );
286
287     layout->addWidget( iconView, 1, 0, 1, -1 );
288 }
289
290 void StandardPLPanel::createTreeView()
291 {
292     /* Create and configure the QTreeView */
293     treeView = new QTreeView;
294
295     treeView->setIconSize( QSize( 20, 20 ) );
296     treeView->setAlternatingRowColors( true );
297     treeView->setAnimated( true );
298     treeView->setUniformRowHeights( true );
299     treeView->setSortingEnabled( true );
300     treeView->header()->setSortIndicator( -1 , Qt::AscendingOrder );
301     treeView->header()->setSortIndicatorShown( true );
302     treeView->header()->setClickable( true );
303     treeView->header()->setContextMenuPolicy( Qt::CustomContextMenu );
304
305     treeView->setSelectionBehavior( QAbstractItemView::SelectRows );
306     treeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
307     treeView->setDragEnabled( true );
308     treeView->setAcceptDrops( true );
309     treeView->setDropIndicatorShown( true );
310     treeView->setContextMenuPolicy( Qt::CustomContextMenu );
311
312     /* setModel after setSortingEnabled(true), or the model will sort immediately! */
313     treeView->setModel( model );
314
315     if( getSettings()->contains( "headerStateV2" ) )
316     {
317         treeView->header()->restoreState(
318                 getSettings()->value( "headerStateV2" ).toByteArray() );
319     }
320     else
321     {
322         for( int m = 1, c = 0; m != COLUMN_END; m <<= 1, c++ )
323         {
324             treeView->setColumnHidden( c, !( m & COLUMN_DEFAULT ) );
325             if( m == COLUMN_TITLE ) treeView->header()->resizeSection( c, 200 );
326             else if( m == COLUMN_DURATION ) treeView->header()->resizeSection( c, 80 );
327         }
328     }
329
330     /* Connections for the TreeView */
331     CONNECT( treeView, activated( const QModelIndex& ),
332              this, activate( const QModelIndex& ) );
333     CONNECT( treeView->header(), customContextMenuRequested( const QPoint & ),
334              this, popupSelectColumn( QPoint ) );
335     CONNECT( treeView, customContextMenuRequested( const QPoint & ),
336              this, popupPlView( const QPoint & ) );
337
338     /* SignalMapper for columns */
339     selectColumnsSigMapper = new QSignalMapper( this );
340     CONNECT( selectColumnsSigMapper, mapped( int ),
341              this, toggleColumnShown( int ) );
342
343     /* Finish the layout */
344     layout->addWidget( treeView, 1, 0, 1, -1 );
345 }
346
347 void StandardPLPanel::showView( int i_view )
348 {
349     switch( i_view )
350     {
351     case TREE_VIEW:
352     {
353         if( treeView == NULL )
354             createTreeView();
355         locationBar->setIndex( treeView->rootIndex() );
356         if( iconView ) iconView->hide();
357         treeView->show();
358         currentView = treeView;
359         treeViewAction->setChecked( true );
360         break;
361     }
362     case ICON_VIEW:
363     {
364         if( iconView == NULL )
365             createIconView();
366
367         locationBar->setIndex( iconView->rootIndex() );
368         if( treeView ) treeView->hide();
369         iconView->show();
370         currentView = iconView;
371         iconViewAction->setChecked( true );
372         break;
373     }
374     default:;
375     }
376 }
377
378 void StandardPLPanel::cycleViews()
379 {
380     if( currentView == iconView )
381         showView( TREE_VIEW );
382     else if( currentView == treeView )
383         showView( ICON_VIEW );
384     else
385         assert( 0 );
386 }
387
388 void StandardPLPanel::wheelEvent( QWheelEvent *e )
389 {
390     // Accept this event in order to prevent unwanted volume up/down changes
391     e->accept();
392 }
393
394 void StandardPLPanel::activate( const QModelIndex &index )
395 {
396     if( model->hasChildren( index ) )
397     {
398         if( currentView == iconView ) {
399             iconView->setRootIndex( index );
400             locationBar->setIndex( index );
401         }
402     }
403     else
404     {
405         playlist_Lock( THEPL );
406         playlist_item_t *p_item = playlist_ItemGetById( THEPL, model->itemId( index ) );
407         p_item->i_flags |= PLAYLIST_SUBITEM_STOP_FLAG;
408         last_activated_id = p_item->p_input->i_id;
409         playlist_Unlock( THEPL );
410         model->activateItem( index );
411     }
412 }
413
414 void StandardPLPanel::browseInto( input_item_t *p_input )
415 {
416
417     if( p_input->i_id != last_activated_id ) return;
418
419     playlist_Lock( THEPL );
420
421     playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
422     if( !p_item )
423     {
424         playlist_Unlock( THEPL );
425         return;
426     }
427
428     QModelIndex index = model->index( p_item->i_id, 0 );
429
430     playlist_Unlock( THEPL );
431
432     if( currentView == iconView ) {
433         iconView->setRootIndex( index );
434         locationBar->setIndex( index );
435     }
436     else
437         treeView->setExpanded( index, true );
438
439     last_activated_id = -1;
440
441
442 }
443
444 LocationBar::LocationBar( PLModel *m )
445 {
446     model = m;
447     mapper = new QSignalMapper( this );
448     CONNECT( mapper, mapped( int ), this, invoke( int ) );
449
450     box = new QHBoxLayout;
451     box->setSpacing( 0 );
452     box->setContentsMargins( 0, 0, 0, 0 );
453     setLayout( box );
454 }
455
456 void LocationBar::setIndex( const QModelIndex &index )
457 {
458     qDeleteAll( buttons );
459     buttons.clear();
460     QModelIndex i = index;
461     bool bold = true;
462     while( true )
463     {
464         PLItem *item = model->getItem( i );
465
466         char *fb_name = input_item_GetTitleFbName( item->inputItem() );
467         QString text = qfu(fb_name);
468         free(fb_name);
469         QAbstractButton *btn = new LocationButton( text, bold, i.isValid() );
470         if( bold ) btn->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
471         box->insertWidget( 0, btn, bold ? 1 : 0 );
472         buttons.append( btn );
473
474         mapper->setMapping( btn, item->id() );
475         CONNECT( btn, clicked( ), mapper, map( ) );
476
477         bold = false;
478
479         if( i.isValid() ) i = i.parent();
480         else break;
481     }
482 }
483
484 void LocationBar::setRootIndex()
485 {
486     setIndex( QModelIndex() );
487 }
488
489 void LocationBar::invoke( int i_id )
490 {
491     QModelIndex index = model->index( i_id, 0 );
492     setIndex( index );
493     emit invoked ( index );
494 }
495
496 LocationButton::LocationButton( const QString &text, bool bold, bool arrow )
497   : b_arrow( arrow )
498 {
499     QFont font;
500     font.setBold( bold );
501     setFont( font );
502     setText( text );
503 }
504
505 #define PADDING 4
506
507 void LocationButton::paintEvent ( QPaintEvent * event )
508 {
509     QStyleOptionButton option;
510     option.initFrom( this );
511     option.state |= QStyle::State_Enabled;
512     QPainter p( this );
513
514     if( underMouse() )
515         style()->drawControl( QStyle::CE_PushButtonBevel, &option, &p );
516
517     int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth,0,this) + PADDING;
518
519     QRect rect = option.rect.adjusted( b_arrow ? 15 + margin : margin, 0, margin * -1, 0 );
520     p.drawText( rect, Qt::AlignVCenter,
521                 fontMetrics().elidedText( text(), Qt::ElideRight, rect.width() ) );
522
523     if( b_arrow )
524     {
525         option.rect.setX( margin );
526         option.rect.setWidth( 8 );
527         style()->drawPrimitive( QStyle::PE_IndicatorArrowRight, &option, &p );
528     }
529 }
530
531 QSize LocationButton::sizeHint() const
532 {
533     int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth,0,this);
534     QSize s( fontMetrics().boundingRect( text() ).size() );
535     s.setWidth( s.width() + ( 2 * frameWidth ) + ( 2 * PADDING ) + ( b_arrow ? 15 : 0 ) );
536     s.setHeight( QPushButton::sizeHint().height() );
537     return s;
538 }
539
540 #undef PADDING