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