]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/standardpanel.cpp
Qt, selector: activate the SQL ML
[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, false );
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, bool b )
193 {
194     if( b ) //SQLML
195         return;
196
197     model->rebuild( p_item );
198 }
199
200 void StandardPLPanel::browseInto( const QModelIndex &index )
201 {
202     if( currentView == iconView || currentView == listView || currentView == picFlowView )
203     {
204         currentRootIndexId = model->itemId( index );
205         currentView->setRootIndex( index );
206     }
207
208     emit viewChanged( index );
209 }
210
211 void StandardPLPanel::browseInto( )
212 {
213     browseInto( currentRootIndexId != -1 && currentView != treeView ?
214                 model->index( currentRootIndexId, 0 ) :
215                 QModelIndex() );
216 }
217
218 void StandardPLPanel::wheelEvent( QWheelEvent *e )
219 {
220     // Accept this event in order to prevent unwanted volume up/down changes
221     e->accept();
222 }
223
224 bool StandardPLPanel::eventFilter ( QObject * watched, QEvent * event )
225 {
226     if (event->type() == QEvent::KeyPress)
227     {
228         QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
229         if( keyEvent->key() == Qt::Key_Delete ||
230             keyEvent->key() == Qt::Key_Backspace )
231         {
232             deleteSelection();
233             return true;
234         }
235     }
236     return false;
237 }
238
239 void StandardPLPanel::deleteSelection()
240 {
241     QItemSelectionModel *selection = currentView->selectionModel();
242     QModelIndexList list = selection->selectedIndexes();
243     model->doDelete( list );
244 }
245
246 void StandardPLPanel::createIconView()
247 {
248     iconView = new PlIconView( model, this );
249     iconView->setContextMenuPolicy( Qt::CustomContextMenu );
250     CONNECT( iconView, customContextMenuRequested( const QPoint & ),
251              this, popupPlView( const QPoint & ) );
252     CONNECT( iconView, activated( const QModelIndex & ),
253              this, activate( const QModelIndex & ) );
254     iconView->installEventFilter( this );
255     viewStack->addWidget( iconView );
256 }
257
258 void StandardPLPanel::createListView()
259 {
260     listView = new PlListView( model, this );
261     listView->setContextMenuPolicy( Qt::CustomContextMenu );
262     CONNECT( listView, customContextMenuRequested( const QPoint & ),
263              this, popupPlView( const QPoint & ) );
264     CONNECT( listView, activated( const QModelIndex & ),
265              this, activate( const QModelIndex & ) );
266     listView->installEventFilter( this );
267     viewStack->addWidget( listView );
268 }
269
270 void StandardPLPanel::createCoverView()
271 {
272     picFlowView = new PicFlowView( model, this );
273     picFlowView->setContextMenuPolicy( Qt::CustomContextMenu );
274     CONNECT( picFlowView, customContextMenuRequested( const QPoint & ),
275              this, popupPlView( const QPoint & ) );
276     CONNECT( picFlowView, activated( const QModelIndex & ),
277              this, activate( const QModelIndex & ) );
278     viewStack->addWidget( picFlowView );
279     picFlowView->installEventFilter( this );
280 }
281
282 void StandardPLPanel::createTreeView()
283 {
284     /* Create and configure the QTreeView */
285     treeView = new PlTreeView;
286
287     treeView->setIconSize( QSize( 20, 20 ) );
288     treeView->setAlternatingRowColors( true );
289     treeView->setAnimated( true );
290     treeView->setUniformRowHeights( true );
291     treeView->setSortingEnabled( true );
292     treeView->setAttribute( Qt::WA_MacShowFocusRect, false );
293     treeView->header()->setSortIndicator( -1 , Qt::AscendingOrder );
294     treeView->header()->setSortIndicatorShown( true );
295     treeView->header()->setClickable( true );
296     treeView->header()->setContextMenuPolicy( Qt::CustomContextMenu );
297
298     treeView->setSelectionBehavior( QAbstractItemView::SelectRows );
299     treeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
300     treeView->setDragEnabled( true );
301     treeView->setAcceptDrops( true );
302     treeView->setDropIndicatorShown( true );
303     treeView->setContextMenuPolicy( Qt::CustomContextMenu );
304
305     /* setModel after setSortingEnabled(true), or the model will sort immediately! */
306     treeView->setModel( model );
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::showView( int i_view )
345 {
346     switch( i_view )
347     {
348     case TREE_VIEW:
349     {
350         if( treeView == NULL )
351             createTreeView();
352         currentView = treeView;
353         break;
354     }
355     case ICON_VIEW:
356     {
357         if( iconView == NULL )
358             createIconView();
359         currentView = iconView;
360         break;
361     }
362     case LIST_VIEW:
363     {
364         if( listView == NULL )
365             createListView();
366         currentView = listView;
367         break;
368     }
369     case PICTUREFLOW_VIEW:
370     {
371         if( picFlowView == NULL )
372             createCoverView();
373         currentView = picFlowView;
374         break;
375     }
376     default: return;
377     }
378
379     viewStack->setCurrentWidget( currentView );
380     browseInto();
381     gotoPlayingItem();
382 }
383
384 const int StandardPLPanel::currentViewIndex()
385 {
386     if( currentView == treeView )
387         return TREE_VIEW;
388     else if( currentView == iconView )
389         return ICON_VIEW;
390     else if( currentView == listView )
391         return LIST_VIEW;
392     else
393         return PICTUREFLOW_VIEW;
394 }
395
396 void StandardPLPanel::cycleViews()
397 {
398     if( currentView == iconView )
399         showView( TREE_VIEW );
400     else if( currentView == treeView )
401         showView( LIST_VIEW );
402     else if( currentView == listView )
403         showView( PICTUREFLOW_VIEW  );
404     else if( currentView == picFlowView )
405         showView( ICON_VIEW );
406     else
407         assert( 0 );
408 }
409
410 void StandardPLPanel::activate( const QModelIndex &index )
411 {
412     /* If we are not a leaf node */
413     if( !index.data( PLModel::IsLeafNodeRole ).toBool() )
414     {
415         if( currentView != treeView )
416             browseInto( index );
417     }
418     else
419     {
420         playlist_Lock( THEPL );
421         playlist_item_t *p_item = playlist_ItemGetById( THEPL, model->itemId( index ) );
422         p_item->i_flags |= PLAYLIST_SUBITEM_STOP_FLAG;
423         lastActivatedId = p_item->p_input->i_id;
424         playlist_Unlock( THEPL );
425         model->activateItem( index );
426     }
427 }
428
429 void StandardPLPanel::browseInto( input_item_t *p_input )
430 {
431     if( p_input->i_id != lastActivatedId ) return;
432
433     playlist_Lock( THEPL );
434
435     playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
436     if( !p_item )
437     {
438         playlist_Unlock( THEPL );
439         return;
440     }
441
442     QModelIndex index = model->index( p_item->i_id, 0 );
443     playlist_Unlock( THEPL );
444
445     if( currentView == treeView )
446         treeView->setExpanded( index, true );
447     else
448         browseInto( index );
449
450     lastActivatedId = -1;
451 }