]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/standardpanel.cpp
Qt: playlist: pad zoom slider (fixes #5291)
[vlc] / modules / gui / qt4 / components / playlist / standardpanel.cpp
1 /*****************************************************************************
2  * standardpanel.cpp : The "standard" playlist panel : just a treeview
3  ****************************************************************************
4  * Copyright © 2000-2010 VideoLAN
5  * $Id$
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Jean-Baptiste 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 "components/playlist/standardpanel.hpp"
30
31 #include "components/playlist/vlc_model.hpp"      /* VLCModel */
32 #include "components/playlist/playlist_model.hpp" /* PLModel */
33 #include "components/playlist/ml_model.hpp"       /* MLModel */
34 #include "components/playlist/views.hpp"          /* 3 views */
35 #include "components/playlist/selector.hpp"       /* PLSelector */
36 #include "menus.hpp"                              /* Popup */
37 #include "input_manager.hpp"                      /* THEMIM */
38
39 #include "sorting.h"                              /* Columns order */
40
41 #include <vlc_services_discovery.h>               /* SD_CMD_SEARCH */
42
43 #include <QHeaderView>
44 #include <QModelIndexList>
45 #include <QMenu>
46 #include <QKeyEvent>
47 #include <QWheelEvent>
48 #include <QStackedLayout>
49 #include <QSignalMapper>
50 #include <QSettings>
51 #include <QScrollBar>
52
53 #include <assert.h>
54
55 StandardPLPanel::StandardPLPanel( PlaylistWidget *_parent,
56                                   intf_thread_t *_p_intf,
57                                   playlist_item_t *p_root,
58                                   PLSelector *_p_selector,
59                                   PLModel *_p_model,
60                                   MLModel *_p_plmodel)
61                 : QWidget( _parent ),
62                   model( _p_model ),
63                   mlmodel( _p_plmodel),
64                   p_intf( _p_intf ),
65                   p_selector( _p_selector )
66 {
67     viewStack = new QStackedLayout( this );
68     viewStack->setSpacing( 0 ); viewStack->setMargin( 0 );
69     setMinimumWidth( 300 );
70
71     iconView    = NULL;
72     treeView    = NULL;
73     listView    = NULL;
74     picFlowView = NULL;
75
76     currentRootIndexId  = -1;
77     lastActivatedId     = -1;
78
79     /* Saved Settings */
80     int i_savedViewMode = getSettings()->value( "Playlist/view-mode", TREE_VIEW ).toInt();
81     showView( i_savedViewMode );
82
83     DCONNECT( THEMIM, leafBecameParent( int ),
84               this, browseInto( int ) );
85
86     CONNECT( model, currentChanged( const QModelIndex& ),
87              this, handleExpansion( const QModelIndex& ) );
88     CONNECT( model, rootChanged(), this, browseInto() );
89
90     setRoot( p_root, false );
91 }
92
93 StandardPLPanel::~StandardPLPanel()
94 {
95     getSettings()->beginGroup("Playlist");
96     if( treeView )
97         getSettings()->setValue( "headerStateV2", treeView->header()->saveState() );
98     getSettings()->setValue( "view-mode", currentViewIndex() );
99     getSettings()->endGroup();
100 }
101
102 /* Unused anymore, but might be useful, like in right-click menu */
103 void StandardPLPanel::gotoPlayingItem()
104 {
105     currentView->scrollTo( model->currentIndex() );
106 }
107
108 void StandardPLPanel::handleExpansion( const QModelIndex& index )
109 {
110     assert( currentView );
111     if( currentRootIndexId != -1 && currentRootIndexId != model->itemId( index.parent() ) )
112         browseInto( index.parent() );
113     currentView->scrollTo( index );
114 }
115
116 void StandardPLPanel::popupPlView( const QPoint &point )
117 {
118     QModelIndex index = currentView->indexAt( point );
119     QPoint globalPoint = currentView->viewport()->mapToGlobal( point );
120     QItemSelectionModel *selection = currentView->selectionModel();
121     QModelIndexList list = selection->selectedIndexes();
122
123     if( !model->popup( index, globalPoint, list ) )
124         QVLCMenu::PopupMenu( p_intf, true );
125 }
126
127 void StandardPLPanel::popupSelectColumn( QPoint )
128 {
129     QMenu menu;
130     assert( treeView );
131
132     /* We do not offer the option to hide index 0 column, or
133      * QTreeView will behave weird */
134     for( int i = 1 << 1, j = 1; i < COLUMN_END; i <<= 1, j++ )
135     {
136         QAction* option = menu.addAction( qfu( psz_column_title( i ) ) );
137         option->setCheckable( true );
138         option->setChecked( !treeView->isColumnHidden( j ) );
139         selectColumnsSigMapper->setMapping( option, j );
140         CONNECT( option, triggered(), selectColumnsSigMapper, map() );
141     }
142     menu.exec( QCursor::pos() );
143 }
144
145 void StandardPLPanel::toggleColumnShown( int i )
146 {
147     treeView->setColumnHidden( i, !treeView->isColumnHidden( i ) );
148 }
149
150 /* Search in the playlist */
151 void StandardPLPanel::search( const QString& searchText )
152 {
153     int type;
154     QString name;
155     p_selector->getCurrentSelectedItem( &type, &name );
156     if( type != SD_TYPE )
157     {
158         bool flat = ( currentView == iconView ||
159                       currentView == listView ||
160                       currentView == picFlowView );
161         model->search( searchText,
162                        flat ? currentView->rootIndex() : QModelIndex(),
163                        !flat );
164     }
165 }
166
167 void StandardPLPanel::searchDelayed( const QString& searchText )
168 {
169     int type;
170     QString name;
171     p_selector->getCurrentSelectedItem( &type, &name );
172
173     if( type == SD_TYPE )
174     {
175         if( !name.isEmpty() && !searchText.isEmpty() )
176             playlist_ServicesDiscoveryControl( THEPL, qtu( name ), SD_CMD_SEARCH,
177                                               qtu( searchText ) );
178     }
179 }
180
181 /* Set the root of the new Playlist */
182 /* This activated by the selector selection */
183 void StandardPLPanel::setRoot( playlist_item_t *p_item, bool b )
184 {
185 #ifdef MEDIA_LIBRARY
186     if( b )
187     {
188         msg_Dbg( p_intf, "Setting the SQL ML" );
189         currentView->setModel( mlmodel );
190     }
191     else
192 #endif
193     {
194         msg_Dbg( p_intf, "Normal PL/ML or SD" );
195         if( currentView->model() != model )
196             currentView->setModel( model );
197         model->rebuild( p_item );
198     }
199 }
200
201 void StandardPLPanel::browseInto( const QModelIndex &index )
202 {
203     if( currentView == iconView || currentView == listView || currentView == picFlowView )
204     {
205         currentRootIndexId = model->itemId( index );
206         currentView->setRootIndex( index );
207     }
208
209     emit viewChanged( index );
210 }
211
212 void StandardPLPanel::browseInto()
213 {
214     browseInto( (currentRootIndexId != -1 && currentView != treeView) ?
215                  model->index( currentRootIndexId, 0 ) :
216                  QModelIndex() );
217 }
218
219 void StandardPLPanel::wheelEvent( QWheelEvent *e )
220 {
221     // Accept this event in order to prevent unwanted volume up/down changes
222     e->accept();
223 }
224
225 bool StandardPLPanel::eventFilter ( QObject *, QEvent * event )
226 {
227     if (event->type() == QEvent::KeyPress)
228     {
229         QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
230         if( keyEvent->key() == Qt::Key_Delete ||
231             keyEvent->key() == Qt::Key_Backspace )
232         {
233             deleteSelection();
234             return true;
235         }
236     }
237     return false;
238 }
239
240 void StandardPLPanel::deleteSelection()
241 {
242     QItemSelectionModel *selection = currentView->selectionModel();
243     QModelIndexList list = selection->selectedIndexes();
244     model->doDelete( list );
245 }
246
247 void StandardPLPanel::createIconView()
248 {
249     iconView = new PlIconView( model, this );
250     iconView->setContextMenuPolicy( Qt::CustomContextMenu );
251     CONNECT( iconView, customContextMenuRequested( const QPoint & ),
252              this, popupPlView( const QPoint & ) );
253     CONNECT( iconView, activated( const QModelIndex & ),
254              this, activate( const QModelIndex & ) );
255     iconView->installEventFilter( this );
256     viewStack->addWidget( iconView );
257 }
258
259 void StandardPLPanel::createListView()
260 {
261     listView = new PlListView( model, this );
262     listView->setContextMenuPolicy( Qt::CustomContextMenu );
263     CONNECT( listView, customContextMenuRequested( const QPoint & ),
264              this, popupPlView( const QPoint & ) );
265     CONNECT( listView, activated( const QModelIndex & ),
266              this, activate( const QModelIndex & ) );
267     listView->installEventFilter( this );
268     viewStack->addWidget( listView );
269 }
270
271 void StandardPLPanel::createCoverView()
272 {
273     picFlowView = new PicFlowView( model, this );
274     picFlowView->setContextMenuPolicy( Qt::CustomContextMenu );
275     CONNECT( picFlowView, customContextMenuRequested( const QPoint & ),
276              this, popupPlView( const QPoint & ) );
277     CONNECT( picFlowView, activated( const QModelIndex & ),
278              this, activate( const QModelIndex & ) );
279     viewStack->addWidget( picFlowView );
280     picFlowView->installEventFilter( this );
281 }
282
283 void StandardPLPanel::createTreeView()
284 {
285     /* Create and configure the QTreeView */
286     treeView = new PlTreeView;
287
288     treeView->setIconSize( QSize( 20, 20 ) );
289     treeView->setAlternatingRowColors( true );
290     treeView->setAnimated( true );
291     treeView->setUniformRowHeights( true );
292     treeView->setSortingEnabled( true );
293     treeView->setAttribute( Qt::WA_MacShowFocusRect, false );
294     treeView->header()->setSortIndicator( -1 , Qt::AscendingOrder );
295     treeView->header()->setSortIndicatorShown( true );
296     treeView->header()->setClickable( true );
297     treeView->header()->setContextMenuPolicy( Qt::CustomContextMenu );
298
299     treeView->setSelectionBehavior( QAbstractItemView::SelectRows );
300     treeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
301     treeView->setDragEnabled( true );
302     treeView->setAcceptDrops( true );
303     treeView->setDropIndicatorShown( true );
304     treeView->setContextMenuPolicy( Qt::CustomContextMenu );
305
306     /* setModel after setSortingEnabled(true), or the model will sort immediately! */
307
308     getSettings()->beginGroup("Playlist");
309
310     if( getSettings()->contains( "headerStateV2" ) )
311     {
312         treeView->header()->restoreState(
313                 getSettings()->value( "headerStateV2" ).toByteArray() );
314     }
315     else
316     {
317         for( int m = 1, c = 0; m != COLUMN_END; m <<= 1, c++ )
318         {
319             treeView->setColumnHidden( c, !( m & COLUMN_DEFAULT ) );
320             if( m == COLUMN_TITLE ) treeView->header()->resizeSection( c, 200 );
321             else if( m == COLUMN_DURATION ) treeView->header()->resizeSection( c, 80 );
322         }
323     }
324
325     getSettings()->endGroup();
326
327     /* Connections for the TreeView */
328     CONNECT( treeView, activated( const QModelIndex& ),
329              this, activate( const QModelIndex& ) );
330     CONNECT( treeView->header(), customContextMenuRequested( const QPoint & ),
331              this, popupSelectColumn( QPoint ) );
332     CONNECT( treeView, customContextMenuRequested( const QPoint & ),
333              this, popupPlView( const QPoint & ) );
334     treeView->installEventFilter( this );
335
336     /* SignalMapper for columns */
337     selectColumnsSigMapper = new QSignalMapper( this );
338     CONNECT( selectColumnsSigMapper, mapped( int ),
339              this, toggleColumnShown( int ) );
340
341     viewStack->addWidget( treeView );
342 }
343
344 void StandardPLPanel::changeModel( bool b_ml )
345 {
346 #ifdef MEDIA_LIBRARY
347     VLCModel *mod;
348     if( b_ml )
349         mod = mlmodel;
350     else
351         mod = model;
352     if( currentView->model() != mod )
353         currentView->setModel( mod );
354 #else
355     if( currentView->model() != model )
356         currentView->setModel( model );
357 #endif
358 }
359
360 void StandardPLPanel::showView( int i_view )
361 {
362
363     switch( i_view )
364     {
365     case ICON_VIEW:
366     {
367         if( iconView == NULL )
368             createIconView();
369         currentView = iconView;
370         break;
371     }
372     case LIST_VIEW:
373     {
374         if( listView == NULL )
375             createListView();
376         currentView = listView;
377         break;
378     }
379     case PICTUREFLOW_VIEW:
380     {
381         if( picFlowView == NULL )
382             createCoverView();
383         currentView = picFlowView;
384         break;
385     }
386     default:
387     case TREE_VIEW:
388     {
389         if( treeView == NULL )
390             createTreeView();
391         currentView = treeView;
392         break;
393     }
394     }
395
396     changeModel( false );
397
398     viewStack->setCurrentWidget( currentView );
399     browseInto();
400     gotoPlayingItem();
401 }
402
403 int StandardPLPanel::currentViewIndex() const
404 {
405     if( currentView == treeView )
406         return TREE_VIEW;
407     else if( currentView == iconView )
408         return ICON_VIEW;
409     else if( currentView == listView )
410         return LIST_VIEW;
411     else
412         return PICTUREFLOW_VIEW;
413 }
414
415 int StandardPLPanel::getScrollBarsSize() const
416 {
417     /* FIXME: should return a set in case of different widths */
418     return currentView->verticalScrollBar()->sizeHint().width();
419 }
420
421 void StandardPLPanel::cycleViews()
422 {
423     if( currentView == iconView )
424         showView( TREE_VIEW );
425     else if( currentView == treeView )
426         showView( LIST_VIEW );
427     else if( currentView == listView )
428         showView( PICTUREFLOW_VIEW  );
429     else if( currentView == picFlowView )
430         showView( ICON_VIEW );
431     else
432         assert( 0 );
433 }
434
435 void StandardPLPanel::activate( const QModelIndex &index )
436 {
437     if( currentView->model() == model )
438     {
439         /* If we are not a leaf node */
440         if( !index.data( PLModel::IsLeafNodeRole ).toBool() )
441         {
442             if( currentView != treeView )
443                 browseInto( index );
444         }
445         else
446         {
447             playlist_Lock( THEPL );
448             playlist_item_t *p_item = playlist_ItemGetById( THEPL, model->itemId( index ) );
449             p_item->i_flags |= PLAYLIST_SUBITEM_STOP_FLAG;
450             lastActivatedId = p_item->p_input->i_id;
451             playlist_Unlock( THEPL );
452             model->activateItem( index );
453         }
454     }
455 }
456
457 void StandardPLPanel::browseInto( int i_id )
458 {
459     if( i_id != lastActivatedId ) return;
460
461     QModelIndex index = model->index( i_id, 0 );
462     playlist_Unlock( THEPL );
463
464     if( currentView == treeView )
465         treeView->setExpanded( index, true );
466     else
467         browseInto( index );
468
469     lastActivatedId = -1;
470 }