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