]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/standardpanel.cpp
Qt: further playlistPanel simplifications
[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
68     currentRootIndexId  = -1;
69     lastActivatedId     = -1;
70
71     /* Saved Settings */
72     getSettings()->beginGroup("Playlist");
73     int i_viewMode = getSettings()->value( "view-mode", TREE_VIEW ).toInt();
74     getSettings()->endGroup();
75
76     showView( i_viewMode );
77
78     DCONNECT( THEMIM, leafBecameParent( input_item_t *),
79               this, browseInto( input_item_t * ) );
80
81     CONNECT( model, currentChanged( const QModelIndex& ),
82              this, handleExpansion( const QModelIndex& ) );
83     CONNECT( model, rootChanged(), this, handleRootChange() );
84
85     setRoot( p_root );
86 }
87
88 StandardPLPanel::~StandardPLPanel()
89 {
90     getSettings()->beginGroup("Playlist");
91     if( treeView )
92         getSettings()->setValue( "headerStateV2", treeView->header()->saveState() );
93     if( currentView == treeView )
94         getSettings()->setValue( "view-mode", TREE_VIEW );
95     else if( currentView == listView )
96         getSettings()->setValue( "view-mode", LIST_VIEW );
97     else if( currentView == iconView )
98         getSettings()->setValue( "view-mode", ICON_VIEW );
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::handleRootChange()
117 {
118     browseInto();
119 }
120
121 void StandardPLPanel::popupPlView( const QPoint &point )
122 {
123     QModelIndex index = currentView->indexAt( point );
124     QPoint globalPoint = currentView->viewport()->mapToGlobal( point );
125     QItemSelectionModel *selection = currentView->selectionModel();
126     QModelIndexList list = selection->selectedIndexes();
127
128     if( !model->popup( index, globalPoint, list ) )
129         QVLCMenu::PopupMenu( p_intf, true );
130 }
131
132 void StandardPLPanel::popupSelectColumn( QPoint pos )
133 {
134     QMenu menu;
135     assert( treeView );
136
137     /* We do not offer the option to hide index 0 column, or
138     * QTreeView will behave weird */
139     int i, j;
140     for( i = 1 << 1, j = 1; i < COLUMN_END; i <<= 1, j++ )
141     {
142         QAction* option = menu.addAction( qfu( psz_column_title( i ) ) );
143         option->setCheckable( true );
144         option->setChecked( !treeView->isColumnHidden( j ) );
145         selectColumnsSigMapper->setMapping( option, j );
146         CONNECT( option, triggered(), selectColumnsSigMapper, map() );
147     }
148     menu.exec( QCursor::pos() );
149 }
150
151 void StandardPLPanel::toggleColumnShown( int i )
152 {
153     treeView->setColumnHidden( i, !treeView->isColumnHidden( i ) );
154 }
155
156 /* Search in the playlist */
157 void StandardPLPanel::search( const QString& searchText )
158 {
159     int type;
160     QString name;
161     p_selector->getCurrentSelectedItem( &type, &name );
162     if( type != SD_TYPE )
163     {
164         bool flat = currentView == iconView || currentView == listView;
165         model->search( searchText,
166                        flat ? currentView->rootIndex() : QModelIndex(),
167                        !flat );
168     }
169 }
170
171 void StandardPLPanel::searchDelayed( const QString& searchText )
172 {
173     int type;
174     QString name;
175     p_selector->getCurrentSelectedItem( &type, &name );
176
177     if( type == SD_TYPE )
178     {
179         if( !name.isEmpty() && !searchText.isEmpty() )
180             playlist_ServicesDiscoveryControl( THEPL, qtu( name ), SD_CMD_SEARCH, qtu( searchText ) );
181     }
182 }
183
184 /* Set the root of the new Playlist */
185 /* This activated by the selector selection */
186 void StandardPLPanel::setRoot( playlist_item_t *p_item )
187 {
188     model->rebuild( p_item );
189 }
190
191 void StandardPLPanel::browseInto( const QModelIndex &index )
192 {
193     if( currentView == iconView || currentView == listView )
194     {
195         currentRootIndexId = model->itemId( index );
196         currentView->setRootIndex( index );
197     }
198
199     emit viewChanged( index );
200 }
201
202 void StandardPLPanel::browseInto( )
203 {
204     browseInto( currentRootIndexId != -1 && currentView != treeView ?
205                 model->index( currentRootIndexId, 0 ) :
206                 QModelIndex() );
207 }
208
209 void StandardPLPanel::wheelEvent( QWheelEvent *e )
210 {
211     // Accept this event in order to prevent unwanted volume up/down changes
212     e->accept();
213 }
214
215 bool StandardPLPanel::eventFilter ( QObject * watched, QEvent * event )
216 {
217     if (event->type() == QEvent::KeyPress)
218     {
219         QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
220         if( keyEvent->key() == Qt::Key_Delete ||
221             keyEvent->key() == Qt::Key_Backspace )
222         {
223             deleteSelection();
224             return true;
225         }
226     }
227     return false;
228 }
229
230 void StandardPLPanel::deleteSelection()
231 {
232     QItemSelectionModel *selection = currentView->selectionModel();
233     QModelIndexList list = selection->selectedIndexes();
234     model->doDelete( list );
235 }
236
237 void StandardPLPanel::createIconView()
238 {
239     iconView = new PlIconView( model, this );
240     iconView->setContextMenuPolicy( Qt::CustomContextMenu );
241     CONNECT( iconView, customContextMenuRequested( const QPoint & ),
242              this, popupPlView( const QPoint & ) );
243     CONNECT( iconView, activated( const QModelIndex & ),
244              this, activate( const QModelIndex & ) );
245     iconView->installEventFilter( this );
246     viewStack->addWidget( iconView );
247 }
248
249 void StandardPLPanel::createListView()
250 {
251     listView = new PlListView( model, this );
252     listView->setContextMenuPolicy( Qt::CustomContextMenu );
253     CONNECT( listView, customContextMenuRequested( const QPoint & ),
254              this, popupPlView( const QPoint & ) );
255     CONNECT( listView, activated( const QModelIndex & ),
256              this, activate( const QModelIndex & ) );
257     listView->installEventFilter( this );
258     viewStack->addWidget( listView );
259 }
260
261 void StandardPLPanel::createTreeView()
262 {
263     /* Create and configure the QTreeView */
264     treeView = new PlTreeView;
265
266     treeView->setIconSize( QSize( 20, 20 ) );
267     treeView->setAlternatingRowColors( true );
268     treeView->setAnimated( true );
269     treeView->setUniformRowHeights( true );
270     treeView->setSortingEnabled( true );
271     treeView->header()->setSortIndicator( -1 , Qt::AscendingOrder );
272     treeView->header()->setSortIndicatorShown( true );
273     treeView->header()->setClickable( true );
274     treeView->header()->setContextMenuPolicy( Qt::CustomContextMenu );
275
276     treeView->setSelectionBehavior( QAbstractItemView::SelectRows );
277     treeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
278     treeView->setDragEnabled( true );
279     treeView->setAcceptDrops( true );
280     treeView->setDropIndicatorShown( true );
281     treeView->setContextMenuPolicy( Qt::CustomContextMenu );
282
283     /* setModel after setSortingEnabled(true), or the model will sort immediately! */
284     treeView->setModel( model );
285
286     getSettings()->beginGroup("Playlist");
287
288     if( getSettings()->contains( "headerStateV2" ) )
289     {
290         treeView->header()->restoreState(
291                 getSettings()->value( "headerStateV2" ).toByteArray() );
292     }
293     else
294     {
295         for( int m = 1, c = 0; m != COLUMN_END; m <<= 1, c++ )
296         {
297             treeView->setColumnHidden( c, !( m & COLUMN_DEFAULT ) );
298             if( m == COLUMN_TITLE ) treeView->header()->resizeSection( c, 200 );
299             else if( m == COLUMN_DURATION ) treeView->header()->resizeSection( c, 80 );
300         }
301     }
302
303     getSettings()->endGroup();
304
305     /* Connections for the TreeView */
306     CONNECT( treeView, activated( const QModelIndex& ),
307              this, activate( const QModelIndex& ) );
308     CONNECT( treeView->header(), customContextMenuRequested( const QPoint & ),
309              this, popupSelectColumn( QPoint ) );
310     CONNECT( treeView, customContextMenuRequested( const QPoint & ),
311              this, popupPlView( const QPoint & ) );
312     treeView->installEventFilter( this );
313
314     /* SignalMapper for columns */
315     selectColumnsSigMapper = new QSignalMapper( this );
316     CONNECT( selectColumnsSigMapper, mapped( int ),
317              this, toggleColumnShown( int ) );
318
319     viewStack->addWidget( treeView );
320 }
321
322 void StandardPLPanel::showView( int i_view )
323 {
324     switch( i_view )
325     {
326     case TREE_VIEW:
327     {
328         if( treeView == NULL )
329             createTreeView();
330         currentView = treeView;
331         break;
332     }
333     case ICON_VIEW:
334     {
335         if( iconView == NULL )
336             createIconView();
337         currentView = iconView;
338         break;
339     }
340     case LIST_VIEW:
341     {
342         if( listView == NULL )
343             createListView();
344         currentView = listView;
345         break;
346     }
347     default: return;
348     }
349
350     viewStack->setCurrentWidget( currentView );
351     //viewActions[i_view]->setChecked( true );
352     browseInto();
353     gotoPlayingItem();
354 }
355
356 void StandardPLPanel::cycleViews()
357 {
358     if( currentView == iconView )
359         showView( TREE_VIEW );
360     else if( currentView == treeView )
361         showView( LIST_VIEW );
362     else if( currentView == listView )
363         showView( ICON_VIEW );
364     else
365         assert( 0 );
366 }
367
368 void StandardPLPanel::activate( const QModelIndex &index )
369 {
370     if( !index.data( PLModel::IsLeafNodeRole ).toBool() )
371     {
372         if( currentView != treeView )
373             browseInto( index );
374     }
375     else
376     {
377         playlist_Lock( THEPL );
378         playlist_item_t *p_item = playlist_ItemGetById( THEPL, model->itemId( index ) );
379         p_item->i_flags |= PLAYLIST_SUBITEM_STOP_FLAG;
380         lastActivatedId = p_item->p_input->i_id;
381         playlist_Unlock( THEPL );
382         model->activateItem( index );
383     }
384 }
385
386 void StandardPLPanel::browseInto( input_item_t *p_input )
387 {
388     if( p_input->i_id != lastActivatedId ) return;
389
390     playlist_Lock( THEPL );
391
392     playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
393     if( !p_item )
394     {
395         playlist_Unlock( THEPL );
396         return;
397     }
398
399     QModelIndex index = model->index( p_item->i_id, 0 );
400
401     playlist_Unlock( THEPL );
402
403     if( currentView == treeView )
404         treeView->setExpanded( index, true );
405     else
406         browseInto( index );
407
408     lastActivatedId = -1;
409 }