]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/standardpanel.cpp
Qt: make playlist widget even a bit more pretty
[vlc] / modules / gui / qt4 / components / playlist / standardpanel.cpp
1 /*****************************************************************************
2  * standardpanel.cpp : The "standard" playlist panel : just a treeview
3  ****************************************************************************
4  * Copyright (C) 2000-2009 VideoLAN
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *          JB 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 "dialogs_provider.hpp"
30
31 #include "components/playlist/playlist_model.hpp"
32 #include "components/playlist/standardpanel.hpp"
33 #include "components/playlist/icon_view.hpp"
34 #include "util/customwidgets.hpp"
35
36 #include <vlc_intf_strings.h>
37
38 #include <QPushButton>
39 #include <QHeaderView>
40 #include <QKeyEvent>
41 #include <QModelIndexList>
42 #include <QLabel>
43 #include <QMenu>
44 #include <QSignalMapper>
45 #include <QWheelEvent>
46 #include <QToolButton>
47 #include <QFontMetrics>
48
49 #include <assert.h>
50
51 #include "sorting.h"
52
53 StandardPLPanel::StandardPLPanel( PlaylistWidget *_parent,
54                                   intf_thread_t *_p_intf,
55                                   playlist_t *p_playlist,
56                                   playlist_item_t *p_root ):
57                                   QWidget( _parent ), p_intf( _p_intf )
58 {
59     layout = new QGridLayout( this );
60     layout->setSpacing( 0 ); layout->setMargin( 0 );
61     setMinimumWidth( 300 );
62
63     iconView = NULL;
64     treeView = NULL;
65
66     model = new PLModel( p_playlist, p_intf, p_root, this );
67     currentRootId = -1;
68     last_activated_id = -1;
69
70     /* Title label */
71     /*title = new QLabel;
72     QFont titleFont;
73     titleFont.setPointSize( titleFont.pointSize() + 6 );
74     titleFont.setFamily( "Verdana" );
75     title->setFont( titleFont );
76     layout->addWidget( title, 0, 0 );*/
77
78     locationBar = new LocationBar( model );
79     layout->addWidget( locationBar, 0, 0 );
80
81     /* A Spacer and the search possibilities */
82     layout->setColumnStretch( 1, 10 );
83
84     SearchLineEdit *search = new SearchLineEdit( this );
85     search->setMaximumWidth( 300 );
86     layout->addWidget( search, 0, 4 );
87     CONNECT( search, textChanged( const QString& ),
88              this, search( const QString& ) );
89     layout->setColumnStretch( 4, 2 );
90
91     /* Add item to the playlist button */
92     addButton = new QToolButton;
93     addButton->setIcon( QIcon( ":/buttons/playlist/playlist_add" ) );
94     addButton->setMaximumWidth( 30 );
95     BUTTONACT( addButton, popupAdd() );
96     layout->addWidget( addButton, 0, 3 );
97
98     /* Button to switch views */
99     QToolButton *viewButton = new QToolButton( this );
100     viewButton->setIcon( style()->standardIcon( QStyle::SP_FileDialogDetailedView ) );
101     layout->addWidget( viewButton, 0, 2 );
102
103     /* View selection menu */
104     viewSelectionMapper = new QSignalMapper( this );
105     CONNECT( viewSelectionMapper, mapped( int ), this, showView( int ) );
106
107     QActionGroup *actionGroup = new QActionGroup( this );
108
109     treeViewAction = actionGroup->addAction( "Detailed view" );
110     treeViewAction->setCheckable( true );
111     viewSelectionMapper->setMapping( treeViewAction, TREE_VIEW );
112     CONNECT( treeViewAction, triggered(), viewSelectionMapper, map() );
113
114     iconViewAction = actionGroup->addAction( "Icon view" );
115     iconViewAction->setCheckable( true );
116     viewSelectionMapper->setMapping( iconViewAction, ICON_VIEW );
117     CONNECT( iconViewAction, triggered(), viewSelectionMapper, map() );
118
119     BUTTONACT( viewButton, cycleViews() );
120     QMenu *viewMenu = new QMenu( this );
121     viewMenu->addActions( actionGroup->actions() );
122
123     viewButton->setMenu( viewMenu );
124
125     /* Saved Settings */
126     getSettings()->beginGroup("Playlist");
127
128     int i_viewMode = getSettings()->value( "view-mode", TREE_VIEW ).toInt();
129     showView( i_viewMode );
130
131     getSettings()->endGroup();
132
133     CONNECT( THEMIM, leafBecameParent( input_item_t *),
134              this, browseInto( input_item_t * ) );
135
136     CONNECT( model, currentChanged( const QModelIndex& ),
137              this, handleExpansion( const QModelIndex& ) );
138 }
139
140 StandardPLPanel::~StandardPLPanel()
141 {
142     getSettings()->beginGroup("Playlist");
143     if( treeView )
144         getSettings()->setValue( "headerStateV2", treeView->header()->saveState() );
145     getSettings()->setValue( "view-mode", ( currentView == iconView ) ? ICON_VIEW : TREE_VIEW );
146     getSettings()->endGroup();
147 }
148
149 /* Unused anymore, but might be useful, like in right-click menu */
150 void StandardPLPanel::gotoPlayingItem()
151 {
152     currentView->scrollTo( model->currentIndex() );
153 }
154
155 void StandardPLPanel::handleExpansion( const QModelIndex& index )
156 {
157     assert( currentView );
158     currentView->scrollTo( index );
159 }
160
161 /* PopupAdd Menu for the Add Menu */
162 void StandardPLPanel::popupAdd()
163 {
164     QMenu popup;
165     if( currentRootId == THEPL->p_playing->i_id )
166     {
167         popup.addAction( qtr(I_PL_ADDF), THEDP, SLOT( simplePLAppendDialog()) );
168         popup.addAction( qtr(I_PL_ADDDIR), THEDP, SLOT( PLAppendDir()) );
169         popup.addAction( qtr(I_OP_ADVOP), THEDP, SLOT( PLAppendDialog()) );
170     }
171     else if( THEPL->p_media_library &&
172                 currentRootId == THEPL->p_media_library->i_id )
173     {
174         popup.addAction( qtr(I_PL_ADDF), THEDP, SLOT( simpleMLAppendDialog()) );
175         popup.addAction( qtr(I_PL_ADDDIR), THEDP, SLOT( MLAppendDir() ) );
176         popup.addAction( qtr(I_OP_ADVOP), THEDP, SLOT( MLAppendDialog() ) );
177     }
178
179     popup.exec( QCursor::pos() - addButton->mapFromGlobal( QCursor::pos() )
180                         + QPoint( 0, addButton->height() ) );
181 }
182
183 void StandardPLPanel::popupPlView( const QPoint &point )
184 {
185     QModelIndex index = currentView->indexAt( point );
186     QPoint globalPoint = currentView->viewport()->mapToGlobal( point );
187     QItemSelectionModel *selection = currentView->selectionModel();
188     QModelIndexList list = selection->selectedIndexes();
189     model->popup( index, globalPoint, list );
190 }
191
192 void StandardPLPanel::popupSelectColumn( QPoint pos )
193 {
194     QMenu menu;
195     assert( treeView );
196
197     /* We do not offer the option to hide index 0 column, or
198     * QTreeView will behave weird */
199     int i, j;
200     for( i = 1 << 1, j = 1; i < COLUMN_END; i <<= 1, j++ )
201     {
202         QAction* option = menu.addAction(
203             qfu( psz_column_title( i ) ) );
204         option->setCheckable( true );
205         option->setChecked( !treeView->isColumnHidden( j ) );
206         selectColumnsSigMapper->setMapping( option, j );
207         CONNECT( option, triggered(), selectColumnsSigMapper, map() );
208     }
209     menu.exec( QCursor::pos() );
210 }
211
212 void StandardPLPanel::toggleColumnShown( int i )
213 {
214     treeView->setColumnHidden( i, !treeView->isColumnHidden( i ) );
215 }
216
217 /* Search in the playlist */
218 void StandardPLPanel::search( const QString& searchText )
219 {
220     model->search( searchText );
221 }
222
223 /* Set the root of the new Playlist */
224 /* This activated by the selector selection */
225 void StandardPLPanel::setRoot( playlist_item_t *p_item )
226 {
227     QPL_LOCK;
228     assert( p_item );
229
230     /* needed for popupAdd() */
231     currentRootId = p_item->i_id;
232
233     /* cosmetics, ..still need playlist locking.. */
234     /*char *psz_title = input_item_GetName( p_item->p_input );
235     title->setText( qfu(psz_title) );
236     free( psz_title );*/
237
238     QPL_UNLOCK;
239
240     /* do THE job */
241     model->rebuild( p_item );
242
243     locationBar->setIndex( QModelIndex() );
244
245     /* enable/disable adding */
246     if( p_item == THEPL->p_playing )
247     {
248         addButton->setEnabled( true );
249         addButton->setToolTip( qtr(I_PL_ADDPL) );
250     }
251     else if( THEPL->p_media_library && p_item == THEPL->p_media_library )
252     {
253         addButton->setEnabled( true );
254         addButton->setToolTip( qtr(I_PL_ADDML) );
255     }
256     else
257         addButton->setEnabled( false );
258 }
259
260 void StandardPLPanel::removeItem( int i_id )
261 {
262     model->removeItem( i_id );
263 }
264
265 /* Delete and Suppr key remove the selection
266    FilterKey function and code function */
267 void StandardPLPanel::keyPressEvent( QKeyEvent *e )
268 {
269     switch( e->key() )
270     {
271     case Qt::Key_Back:
272     case Qt::Key_Delete:
273         deleteSelection();
274         break;
275     }
276 }
277
278 void StandardPLPanel::deleteSelection()
279 {
280     QItemSelectionModel *selection = currentView->selectionModel();
281     QModelIndexList list = selection->selectedIndexes();
282     model->doDelete( list );
283 }
284
285 void StandardPLPanel::createIconView()
286 {
287     iconView = new PlIconView( model, this );
288     iconView->setContextMenuPolicy( Qt::CustomContextMenu );
289     CONNECT( iconView, customContextMenuRequested( const QPoint & ),
290              this, popupPlView( const QPoint & ) );
291     CONNECT( iconView, activated( const QModelIndex & ),
292              this, activate( const QModelIndex & ) );
293     CONNECT( locationBar, invoked( const QModelIndex & ),
294              iconView, setRootIndex( const QModelIndex & ) );
295
296     layout->addWidget( iconView, 1, 0, 1, -1 );
297 }
298
299 void StandardPLPanel::createTreeView()
300 {
301     /* Create and configure the QTreeView */
302     treeView = new QTreeView;
303
304     treeView->setIconSize( QSize( 20, 20 ) );
305     treeView->setAlternatingRowColors( true );
306     treeView->setAnimated( true );
307     treeView->setUniformRowHeights( true );
308     treeView->setSortingEnabled( true );
309     treeView->header()->setSortIndicator( -1 , Qt::AscendingOrder );
310     treeView->header()->setSortIndicatorShown( true );
311     treeView->header()->setClickable( true );
312     treeView->header()->setContextMenuPolicy( Qt::CustomContextMenu );
313
314     treeView->setSelectionBehavior( QAbstractItemView::SelectRows );
315     treeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
316     treeView->setDragEnabled( true );
317     treeView->setAcceptDrops( true );
318     treeView->setDropIndicatorShown( true );
319     treeView->setContextMenuPolicy( Qt::CustomContextMenu );
320
321     /* setModel after setSortingEnabled(true), or the model will sort immediately! */
322     treeView->setModel( model );
323
324     if( getSettings()->contains( "headerStateV2" ) )
325     {
326         treeView->header()->restoreState(
327                 getSettings()->value( "headerStateV2" ).toByteArray() );
328     }
329     else
330     {
331         for( int m = 1, c = 0; m != COLUMN_END; m <<= 1, c++ )
332         {
333             treeView->setColumnHidden( c, !( m & COLUMN_DEFAULT ) );
334             if( m == COLUMN_TITLE ) treeView->header()->resizeSection( c, 200 );
335             else if( m == COLUMN_DURATION ) treeView->header()->resizeSection( c, 80 );
336         }
337     }
338
339     /* Connections for the TreeView */
340     CONNECT( treeView, activated( const QModelIndex& ),
341              this, activate( const QModelIndex& ) );
342     CONNECT( treeView->header(), customContextMenuRequested( const QPoint & ),
343              this, popupSelectColumn( QPoint ) );
344     CONNECT( treeView, customContextMenuRequested( const QPoint & ),
345              this, popupPlView( const QPoint & ) );
346
347     /* SignalMapper for columns */
348     selectColumnsSigMapper = new QSignalMapper( this );
349     CONNECT( selectColumnsSigMapper, mapped( int ),
350              this, toggleColumnShown( int ) );
351
352     /* Finish the layout */
353     layout->addWidget( treeView, 1, 0, 1, -1 );
354 }
355
356 void StandardPLPanel::showView( int i_view )
357 {
358     switch( i_view )
359     {
360     case TREE_VIEW:
361     {
362         if( treeView == NULL )
363             createTreeView();
364         locationBar->setIndex( treeView->rootIndex() );
365         if( iconView ) iconView->hide();
366         treeView->show();
367         currentView = treeView;
368         treeViewAction->setChecked( true );
369         break;
370     }
371     case ICON_VIEW:
372     {
373         if( iconView == NULL )
374             createIconView();
375
376         locationBar->setIndex( iconView->rootIndex() );
377         if( treeView ) treeView->hide();
378         iconView->show();
379         currentView = iconView;
380         iconViewAction->setChecked( true );
381         break;
382     }
383     default:;
384     }
385 }
386
387 void StandardPLPanel::cycleViews()
388 {
389     if( currentView == iconView )
390         showView( TREE_VIEW );
391     else if( currentView == treeView )
392         showView( ICON_VIEW );
393     else
394         assert( 0 );
395 }
396
397 void StandardPLPanel::wheelEvent( QWheelEvent *e )
398 {
399     // Accept this event in order to prevent unwanted volume up/down changes
400     e->accept();
401 }
402
403 void StandardPLPanel::activate( const QModelIndex &index )
404 {
405     if( model->hasChildren( index ) )
406     {
407         if( currentView == iconView ) {
408             iconView->setRootIndex( index );
409             //title->setText( index.data().toString() );
410             locationBar->setIndex( index );
411         }
412     }
413     else
414     {
415         playlist_Lock( THEPL );
416         playlist_item_t *p_item = playlist_ItemGetById( THEPL, model->itemId( index ) );
417         p_item->i_flags |= PLAYLIST_SUBITEM_STOP_FLAG;
418         last_activated_id = p_item->p_input->i_id;//model->getItem( index )->inputItem()->i_id;
419         playlist_Unlock( THEPL );
420         model->activateItem( index );
421     }
422 }
423
424 void StandardPLPanel::browseInto( input_item_t *p_input )
425 {
426
427     if( p_input->i_id != last_activated_id ) return;
428
429     playlist_Lock( THEPL );
430
431     playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
432     if( !p_item )
433     {
434         playlist_Unlock( THEPL );
435         return;
436     }
437
438     QModelIndex index = model->index( p_item->i_id, 0 );
439
440     playlist_Unlock( THEPL );
441
442     if( currentView == iconView ) {
443         iconView->setRootIndex( index );
444         locationBar->setIndex( index );
445     }
446     else
447         treeView->setExpanded( index, true );
448
449     last_activated_id = -1;
450
451
452 }
453
454 LocationBar::LocationBar( PLModel *m )
455 {
456     model = m;
457     mapper = new QSignalMapper( this );
458     CONNECT( mapper, mapped( int ), this, invoke( int ) );
459 }
460
461 void LocationBar::setIndex( const QModelIndex &index )
462 {
463     clear();
464     QAction *prev = NULL;
465     QModelIndex i = index;
466     QFont font;
467     QFontMetrics metrics( font );
468     font.setBold( true );
469     while( true )
470     {
471         PLItem *item = model->getItem( i );
472
473         QToolButton *btn = new QToolButton;
474         char *fb_name = input_item_GetTitleFbName( item->inputItem() );
475         QString text = qfu(fb_name);
476         free(fb_name);
477         text = QString("> ") + metrics.elidedText( text, Qt::ElideRight, 150 );
478         btn->setText( text );
479         btn->setFont( font );
480         prev = insertWidget( prev, btn );
481
482         mapper->setMapping( btn, item->id() );
483         CONNECT( btn, clicked( ), mapper, map( ) );
484
485         font = QFont();
486
487         if( i.isValid() ) i = i.parent();
488         else break;
489     }
490 }
491
492 void LocationBar::invoke( int i_id )
493 {
494     QModelIndex index = model->index( i_id, 0 );
495     setIndex( index );
496     emit invoked ( index );
497 }