1 /*****************************************************************************
2 * standardpanel.cpp : The "standard" playlist panel : just a treeview
3 ****************************************************************************
4 * Copyright (C) 2000-2009 VideoLAN
7 * Authors: Clément Stenac <zorglub@videolan.org>
8 * JB Kempf <jb@videolan.org>
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.
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.
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 *****************************************************************************/
29 #include "dialogs_provider.hpp"
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"
37 #include <vlc_intf_strings.h>
39 #include <QPushButton>
40 #include <QHeaderView>
42 #include <QModelIndexList>
45 #include <QSignalMapper>
46 #include <QWheelEvent>
47 #include <QToolButton>
48 #include <QFontMetrics>
55 static const QString viewNames
[] = { qtr( "Detailed View" ),
59 StandardPLPanel::StandardPLPanel( PlaylistWidget
*_parent
,
60 intf_thread_t
*_p_intf
,
61 playlist_t
*p_playlist
,
62 playlist_item_t
*p_root
):
63 QWidget( _parent
), p_intf( _p_intf
)
65 layout
= new QGridLayout( this );
66 layout
->setSpacing( 0 ); layout
->setMargin( 0 );
67 setMinimumWidth( 300 );
73 model
= new PLModel( p_playlist
, p_intf
, p_root
, this );
75 currentRootIndexId
= -1;
78 locationBar
= new LocationBar( model
);
79 locationBar
->setSizePolicy( QSizePolicy::Maximum
, QSizePolicy::Preferred
);
80 layout
->addWidget( locationBar
, 0, 1 );
81 layout
->setColumnStretch( 1, 100 );
82 CONNECT( locationBar
, invoked( const QModelIndex
& ),
83 this, browseInto( const QModelIndex
& ) );
85 layout
->setColumnStretch( 2, 1 );
87 searchEdit
= new SearchLineEdit( this );
88 searchEdit
->setMaximumWidth( 250 );
89 searchEdit
->setMinimumWidth( 80 );
90 layout
->addWidget( searchEdit
, 0, 3 );
91 CONNECT( searchEdit
, textChanged( const QString
& ),
92 this, search( const QString
& ) );
93 layout
->setColumnStretch( 3, 50 );
95 /* Add item to the playlist button */
96 addButton
= new QToolButton
;
97 addButton
->setIcon( QIcon( ":/buttons/playlist/playlist_add" ) );
98 addButton
->setMaximumWidth( 30 );
99 BUTTONACT( addButton
, popupAdd() );
100 layout
->addWidget( addButton
, 0, 0 );
102 /* Button to switch views */
103 QToolButton
*viewButton
= new QToolButton( this );
104 viewButton
->setIcon( style()->standardIcon( QStyle::SP_FileDialogDetailedView
) );
105 layout
->addWidget( viewButton
, 0, 4 );
107 /* View selection menu */
108 viewSelectionMapper
= new QSignalMapper( this );
109 CONNECT( viewSelectionMapper
, mapped( int ), this, showView( int ) );
111 QActionGroup
*actionGroup
= new QActionGroup( this );
113 for( int i
= 0; i
< VIEW_COUNT
; i
++ )
115 viewActions
[i
] = actionGroup
->addAction( viewNames
[i
] );
116 viewActions
[i
]->setCheckable( true );
117 viewSelectionMapper
->setMapping( viewActions
[i
], i
);
118 CONNECT( viewActions
[i
], triggered(), viewSelectionMapper
, map() );
121 BUTTONACT( viewButton
, cycleViews() );
122 QMenu
*viewMenu
= new QMenu( this );
123 viewMenu
->addActions( actionGroup
->actions() );
125 viewButton
->setMenu( viewMenu
);
128 getSettings()->beginGroup("Playlist");
130 int i_viewMode
= getSettings()->value( "view-mode", TREE_VIEW
).toInt();
131 showView( i_viewMode
);
133 getSettings()->endGroup();
135 CONNECT( THEMIM
, leafBecameParent( input_item_t
*),
136 this, browseInto( input_item_t
* ) );
138 CONNECT( model
, currentChanged( const QModelIndex
& ),
139 this, handleExpansion( const QModelIndex
& ) );
140 CONNECT( model
, rootChanged(), this, handleRootChange() );
143 StandardPLPanel::~StandardPLPanel()
145 getSettings()->beginGroup("Playlist");
147 getSettings()->setValue( "headerStateV2", treeView
->header()->saveState() );
148 if( currentView
== treeView
)
149 getSettings()->setValue( "view-mode", TREE_VIEW
);
150 else if( currentView
== listView
)
151 getSettings()->setValue( "view-mode", LIST_VIEW
);
152 else if( currentView
== iconView
)
153 getSettings()->setValue( "view-mode", ICON_VIEW
);
154 getSettings()->endGroup();
157 /* Unused anymore, but might be useful, like in right-click menu */
158 void StandardPLPanel::gotoPlayingItem()
160 currentView
->scrollTo( model
->currentIndex() );
163 void StandardPLPanel::handleExpansion( const QModelIndex
& index
)
165 assert( currentView
);
166 currentView
->scrollTo( index
);
169 void StandardPLPanel::handleRootChange()
171 /* needed for popupAdd() */
172 PLItem
*root
= model
->getItem( QModelIndex() );
173 currentRootId
= root
->id();
177 /* enable/disable adding */
178 if( currentRootId
== THEPL
->p_playing
->i_id
)
180 addButton
->setEnabled( true );
181 addButton
->setToolTip( qtr(I_PL_ADDPL
) );
183 else if( THEPL
->p_media_library
&&
184 currentRootId
== THEPL
->p_media_library
->i_id
)
186 addButton
->setEnabled( true );
187 addButton
->setToolTip( qtr(I_PL_ADDML
) );
190 addButton
->setEnabled( false );
193 /* PopupAdd Menu for the Add Menu */
194 void StandardPLPanel::popupAdd()
197 if( currentRootId
== THEPL
->p_playing
->i_id
)
199 popup
.addAction( qtr(I_PL_ADDF
), THEDP
, SLOT( simplePLAppendDialog()) );
200 popup
.addAction( qtr(I_PL_ADDDIR
), THEDP
, SLOT( PLAppendDir()) );
201 popup
.addAction( qtr(I_OP_ADVOP
), THEDP
, SLOT( PLAppendDialog()) );
203 else if( THEPL
->p_media_library
&&
204 currentRootId
== THEPL
->p_media_library
->i_id
)
206 popup
.addAction( qtr(I_PL_ADDF
), THEDP
, SLOT( simpleMLAppendDialog()) );
207 popup
.addAction( qtr(I_PL_ADDDIR
), THEDP
, SLOT( MLAppendDir() ) );
208 popup
.addAction( qtr(I_OP_ADVOP
), THEDP
, SLOT( MLAppendDialog() ) );
211 popup
.exec( QCursor::pos() - addButton
->mapFromGlobal( QCursor::pos() )
212 + QPoint( 0, addButton
->height() ) );
215 void StandardPLPanel::popupPlView( const QPoint
&point
)
217 QModelIndex index
= currentView
->indexAt( point
);
218 QPoint globalPoint
= currentView
->viewport()->mapToGlobal( point
);
219 if( !index
.isValid() ){
220 QVLCMenu::PopupMenu( p_intf
, true );
224 QItemSelectionModel
*selection
= currentView
->selectionModel();
225 QModelIndexList list
= selection
->selectedIndexes();
226 model
->popup( index
, globalPoint
, list
);
230 void StandardPLPanel::popupSelectColumn( QPoint pos
)
235 /* We do not offer the option to hide index 0 column, or
236 * QTreeView will behave weird */
238 for( i
= 1 << 1, j
= 1; i
< COLUMN_END
; i
<<= 1, j
++ )
240 QAction
* option
= menu
.addAction(
241 qfu( psz_column_title( i
) ) );
242 option
->setCheckable( true );
243 option
->setChecked( !treeView
->isColumnHidden( j
) );
244 selectColumnsSigMapper
->setMapping( option
, j
);
245 CONNECT( option
, triggered(), selectColumnsSigMapper
, map() );
247 menu
.exec( QCursor::pos() );
250 void StandardPLPanel::toggleColumnShown( int i
)
252 treeView
->setColumnHidden( i
, !treeView
->isColumnHidden( i
) );
255 /* Search in the playlist */
256 void StandardPLPanel::search( const QString
& searchText
)
258 bool flat
= currentView
== iconView
|| currentView
== listView
;
259 model
->search( searchText
,
260 flat
? currentView
->rootIndex() : QModelIndex(),
264 /* Set the root of the new Playlist */
265 /* This activated by the selector selection */
266 void StandardPLPanel::setRoot( playlist_item_t
*p_item
)
268 model
->rebuild( p_item
);
271 void StandardPLPanel::browseInto( const QModelIndex
&index
)
273 if( currentView
== iconView
|| currentView
== listView
)
275 currentRootIndexId
= model
->itemId( index
);;
276 currentView
->setRootIndex( index
);
279 locationBar
->setIndex( index
);
280 model
->search( QString(), index
, false );
284 void StandardPLPanel::browseInto( )
286 browseInto( currentRootIndexId
!= -1 && currentView
!= treeView
?
287 model
->index( currentRootIndexId
, 0 ) :
291 void StandardPLPanel::wheelEvent( QWheelEvent
*e
)
293 // Accept this event in order to prevent unwanted volume up/down changes
297 bool StandardPLPanel::eventFilter ( QObject
* watched
, QEvent
* event
)
299 if (event
->type() == QEvent::KeyPress
)
301 QKeyEvent
*keyEvent
= static_cast<QKeyEvent
*>(event
);
302 if( keyEvent
->key() == Qt::Key_Delete
||
303 keyEvent
->key() == Qt::Key_Back
)
312 void StandardPLPanel::deleteSelection()
314 QItemSelectionModel
*selection
= currentView
->selectionModel();
315 QModelIndexList list
= selection
->selectedIndexes();
316 model
->doDelete( list
);
319 void StandardPLPanel::createIconView()
321 iconView
= new PlIconView( model
, this );
322 iconView
->setContextMenuPolicy( Qt::CustomContextMenu
);
323 CONNECT( iconView
, customContextMenuRequested( const QPoint
& ),
324 this, popupPlView( const QPoint
& ) );
325 CONNECT( iconView
, activated( const QModelIndex
& ),
326 this, activate( const QModelIndex
& ) );
327 iconView
->installEventFilter( this );
328 layout
->addWidget( iconView
, 1, 0, 1, -1 );
331 void StandardPLPanel::createListView()
333 listView
= new PlListView( model
, this );
334 listView
->setContextMenuPolicy( Qt::CustomContextMenu
);
335 CONNECT( listView
, customContextMenuRequested( const QPoint
& ),
336 this, popupPlView( const QPoint
& ) );
337 CONNECT( listView
, activated( const QModelIndex
& ),
338 this, activate( const QModelIndex
& ) );
339 listView
->installEventFilter( this );
340 layout
->addWidget( listView
, 1, 0, 1, -1 );
344 void StandardPLPanel::createTreeView()
346 /* Create and configure the QTreeView */
347 treeView
= new QTreeView
;
349 treeView
->setIconSize( QSize( 20, 20 ) );
350 treeView
->setAlternatingRowColors( true );
351 treeView
->setAnimated( true );
352 treeView
->setUniformRowHeights( true );
353 treeView
->setSortingEnabled( true );
354 treeView
->header()->setSortIndicator( -1 , Qt::AscendingOrder
);
355 treeView
->header()->setSortIndicatorShown( true );
356 treeView
->header()->setClickable( true );
357 treeView
->header()->setContextMenuPolicy( Qt::CustomContextMenu
);
359 treeView
->setSelectionBehavior( QAbstractItemView::SelectRows
);
360 treeView
->setSelectionMode( QAbstractItemView::ExtendedSelection
);
361 treeView
->setDragEnabled( true );
362 treeView
->setAcceptDrops( true );
363 treeView
->setDropIndicatorShown( true );
364 treeView
->setContextMenuPolicy( Qt::CustomContextMenu
);
366 /* setModel after setSortingEnabled(true), or the model will sort immediately! */
367 treeView
->setModel( model
);
369 if( getSettings()->contains( "headerStateV2" ) )
371 treeView
->header()->restoreState(
372 getSettings()->value( "headerStateV2" ).toByteArray() );
376 for( int m
= 1, c
= 0; m
!= COLUMN_END
; m
<<= 1, c
++ )
378 treeView
->setColumnHidden( c
, !( m
& COLUMN_DEFAULT
) );
379 if( m
== COLUMN_TITLE
) treeView
->header()->resizeSection( c
, 200 );
380 else if( m
== COLUMN_DURATION
) treeView
->header()->resizeSection( c
, 80 );
384 /* Connections for the TreeView */
385 CONNECT( treeView
, activated( const QModelIndex
& ),
386 this, activate( const QModelIndex
& ) );
387 CONNECT( treeView
->header(), customContextMenuRequested( const QPoint
& ),
388 this, popupSelectColumn( QPoint
) );
389 CONNECT( treeView
, customContextMenuRequested( const QPoint
& ),
390 this, popupPlView( const QPoint
& ) );
391 treeView
->installEventFilter( this );
393 /* SignalMapper for columns */
394 selectColumnsSigMapper
= new QSignalMapper( this );
395 CONNECT( selectColumnsSigMapper
, mapped( int ),
396 this, toggleColumnShown( int ) );
398 /* Finish the layout */
399 layout
->addWidget( treeView
, 1, 0, 1, -1 );
402 void StandardPLPanel::showView( int i_view
)
408 if( treeView
== NULL
)
410 if( iconView
) iconView
->hide();
411 if( listView
) listView
->hide();
413 currentView
= treeView
;
414 viewActions
[i_view
]->setChecked( true );
419 if( iconView
== NULL
)
422 if( treeView
) treeView
->hide();
423 if( listView
) listView
->hide();
425 currentView
= iconView
;
426 viewActions
[i_view
]->setChecked( true );
431 if( listView
== NULL
)
434 if( treeView
) treeView
->hide();
435 if( iconView
) iconView
->hide();
437 currentView
= listView
;
438 viewActions
[i_view
]->setChecked( true );
447 void StandardPLPanel::cycleViews()
449 if( currentView
== iconView
)
450 showView( TREE_VIEW
);
451 else if( currentView
== treeView
)
452 showView( LIST_VIEW
);
453 else if( currentView
== listView
)
454 showView( ICON_VIEW
);
459 void StandardPLPanel::activate( const QModelIndex
&index
)
461 if( model
->hasChildren( index
) )
463 if( currentView
!= treeView
)
468 playlist_Lock( THEPL
);
469 playlist_item_t
*p_item
= playlist_ItemGetById( THEPL
, model
->itemId( index
) );
470 p_item
->i_flags
|= PLAYLIST_SUBITEM_STOP_FLAG
;
471 lastActivatedId
= p_item
->p_input
->i_id
;
472 playlist_Unlock( THEPL
);
473 model
->activateItem( index
);
477 void StandardPLPanel::browseInto( input_item_t
*p_input
)
480 if( p_input
->i_id
!= lastActivatedId
) return;
482 playlist_Lock( THEPL
);
484 playlist_item_t
*p_item
= playlist_ItemGetByInput( THEPL
, p_input
);
487 playlist_Unlock( THEPL
);
491 QModelIndex index
= model
->index( p_item
->i_id
, 0 );
493 playlist_Unlock( THEPL
);
495 if( currentView
== treeView
)
496 treeView
->setExpanded( index
, true );
500 lastActivatedId
= -1;
505 LocationBar::LocationBar( PLModel
*m
)
508 mapper
= new QSignalMapper( this );
509 CONNECT( mapper
, mapped( int ), this, invoke( int ) );
511 box
= new QHBoxLayout
;
512 box
->setSpacing( 0 );
513 box
->setContentsMargins( 0, 0, 0, 0 );
517 void LocationBar::setIndex( const QModelIndex
&index
)
519 qDeleteAll( buttons
);
521 QModelIndex i
= index
;
525 PLItem
*item
= model
->getItem( i
);
527 char *fb_name
= input_item_GetTitleFbName( item
->inputItem() );
528 QString text
= qfu(fb_name
);
530 QAbstractButton
*btn
= new LocationButton( text
, bold
, i
.isValid() );
531 btn
->setSizePolicy( QSizePolicy::Maximum
, QSizePolicy::Fixed
);
532 box
->insertWidget( 0, btn
, bold
? 1 : 0 );
533 buttons
.append( btn
);
535 mapper
->setMapping( btn
, item
->id() );
536 CONNECT( btn
, clicked( ), mapper
, map( ) );
540 if( i
.isValid() ) i
= i
.parent();
545 void LocationBar::setRootIndex()
547 setIndex( QModelIndex() );
550 void LocationBar::invoke( int i_id
)
552 QModelIndex index
= model
->index( i_id
, 0 );
553 emit
invoked ( index
);
556 LocationButton::LocationButton( const QString
&text
, bool bold
, bool arrow
)
560 font
.setBold( bold
);
567 void LocationButton::paintEvent ( QPaintEvent
* event
)
569 QStyleOptionButton option
;
570 option
.initFrom( this );
571 option
.state
|= QStyle::State_Enabled
;
575 style()->drawControl( QStyle::CE_PushButtonBevel
, &option
, &p
);
577 int margin
= style()->pixelMetric(QStyle::PM_DefaultFrameWidth
,0,this) + PADDING
;
579 QRect rect
= option
.rect
.adjusted( b_arrow
? 15 + margin
: margin
, 0, margin
* -1, 0 );
580 p
.drawText( rect
, Qt::AlignVCenter
,
581 fontMetrics().elidedText( text(), Qt::ElideRight
, rect
.width() ) );
585 option
.rect
.setX( margin
);
586 option
.rect
.setWidth( 8 );
587 style()->drawPrimitive( QStyle::PE_IndicatorArrowRight
, &option
, &p
);
591 QSize
LocationButton::sizeHint() const
593 int frameWidth
= style()->pixelMetric(QStyle::PM_DefaultFrameWidth
,0,this);
594 QSize
s( fontMetrics().boundingRect( text() ).size() );
595 s
.setWidth( s
.width() + ( 2 * frameWidth
) + ( 2 * PADDING
) + ( b_arrow
? 15 : 0 ) );
596 s
.setHeight( QPushButton::sizeHint().height() );