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