]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/views.cpp
Qt: show context menu on pictureflow (fix #11876)
[vlc] / modules / gui / qt4 / components / playlist / views.cpp
1 /*****************************************************************************
2  * views.cpp : Views for the Playlist
3  ****************************************************************************
4  * Copyright © 2010 the VideoLAN team
5  * $Id$
6  *
7  * Authors:         Jean-Baptiste Kempf <jb@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #include "components/playlist/views.hpp"
25 #include "components/playlist/vlc_model.hpp"      /* VLCModel */
26 #include "components/playlist/sorting.h"          /* Columns List */
27 #include "input_manager.hpp"                      /* THEMIM */
28
29 #include <QPainter>
30 #include <QRect>
31 #include <QStyleOptionViewItem>
32 #include <QFontMetrics>
33 #include <QDrag>
34 #include <QDragMoveEvent>
35 #include <QMetaType>
36 #include <QHeaderView>
37
38 #include <assert.h>
39
40 /* ICON_SCALER comes currently from harrison-stetson method, so good value */
41 #define ICON_SCALER         16
42 #define ART_RADIUS          5
43 #define SPACER              5
44
45 void AbstractPlViewItemDelegate::paintBackground(
46     QPainter *painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
47 {
48     /* FIXME: This does not indicate item selection in all QStyles, so for the time being we
49        have to draw it ourselves, to ensure visible effect of selection on all platforms */
50     /* QApplication::style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option,
51                                             painter ); */
52
53     painter->save();
54     QRect r = option.rect.adjusted( 0, 0, -1, -1 );
55     if( option.state & QStyle::State_Selected )
56     {
57         painter->setBrush( option.palette.color( QPalette::Highlight ) );
58         painter->setPen( option.palette.color( QPalette::Highlight ).darker( 150 ) );
59         painter->drawRect( r );
60     }
61     else if( index.data( VLCModel::IsCurrentRole ).toBool() )
62     {
63         painter->setBrush( QBrush( Qt::lightGray ) );
64         painter->setPen( QColor( Qt::darkGray ) );
65         painter->drawRect( r );
66     }
67     if( option.state & QStyle::State_MouseOver )
68     { /* requires WA_hover on viewport */
69         painter->setOpacity( 0.5 );
70         painter->setPen( Qt::NoPen );
71         painter->setBrush( option.palette.color( QPalette::Highlight ).lighter( 150 ) );
72         painter->drawRect( option.rect );
73     }
74     painter->restore();
75 }
76
77 void PlIconViewItemDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
78 {
79     QString title = VLCModel::getMeta( index, COLUMN_TITLE );
80     QString artist = VLCModel::getMeta( index, COLUMN_ARTIST );
81
82     QFont font( index.data( Qt::FontRole ).value<QFont>() );
83     font.setPointSize( __MAX( font.pointSize() + i_zoom, 4 ) );
84     font.setBold( index.data( VLCModel::IsCurrentRole ).toBool() );
85     painter->setFont( font );
86     QFontMetrics fm = painter->fontMetrics();
87
88     int averagewidth = fm.averageCharWidth();
89     QSize rectSize = option.rect.size();
90     int art_width = averagewidth * ICON_SCALER;
91     int art_height = averagewidth * ICON_SCALER;
92
93     QPixmap artPix = VLCModel::getArtPixmap( index, QSize( art_width, art_height) );
94
95     paintBackground( painter, option, index );
96
97     painter->save();
98
99     QRect artRect( option.rect.x() + ( rectSize.width() - artPix.width() ) / 2,
100                    option.rect.y() - averagewidth*3 + ( rectSize.height() - artPix.height() ) / 2,
101                    artPix.width(), artPix.height() );
102
103     // Draw the drop shadow
104     painter->save();
105     painter->setOpacity( 0.7 );
106     painter->setBrush( QBrush( Qt::darkGray ) );
107     painter->setPen( Qt::NoPen );
108     painter->drawRoundedRect( artRect.adjusted( 0, 0, 2, 2 ), ART_RADIUS, ART_RADIUS );
109     painter->restore();
110
111     // Draw the art pixmap
112     QPainterPath artRectPath;
113     artRectPath.addRoundedRect( artRect, ART_RADIUS, ART_RADIUS );
114     painter->setClipPath( artRectPath );
115     painter->drawPixmap( artRect, artPix );
116     painter->setClipping( false );
117
118     if( option.state & QStyle::State_Selected )
119         painter->setPen( option.palette.color( QPalette::HighlightedText ) );
120
121
122     //Draw children indicator
123     if( !index.data( VLCModel::IsLeafNodeRole ).toBool() )
124     {
125         QRect r( option.rect );
126         r.setSize( QSize( 25, 25 ) );
127         r.translate( 5, 5 );
128         if( index.data( VLCModel::IsCurrentsParentNodeRole ).toBool() )
129         {
130             painter->setOpacity( 0.75 );
131             QPainterPath nodeRectPath;
132             nodeRectPath.addRoundedRect( r, 4, 4 );
133             painter->fillPath( nodeRectPath, option.palette.color( QPalette::Highlight ) );
134             painter->setOpacity( 1.0 );
135         }
136         QPixmap dirPix( ":/type/node" );
137         QRect r2( dirPix.rect() );
138         r2.moveCenter( r.center() );
139         painter->drawPixmap( r2, dirPix );
140     }
141
142     // Draw title
143     font.setItalic( true );
144
145     QRect textRect;
146     textRect.setRect( option.rect.x() , artRect.bottom() + fm.height()/2, option.rect.width(), fm.height() );
147
148     painter->drawText( textRect,
149                       fm.elidedText( title, Qt::ElideRight, textRect.width() ),
150                       QTextOption( Qt::AlignCenter ) );
151
152     // Draw artist
153     painter->setPen( painter->pen().color().lighter( 150 ) );
154     font.setItalic( false );
155     painter->setFont( font );
156     fm = painter->fontMetrics();
157
158     textRect.moveTop( textRect.bottom() + 1 );
159
160     painter->drawText(  textRect,
161                         fm.elidedText( artist, Qt::ElideRight, textRect.width() ),
162                         QTextOption( Qt::AlignCenter ) );
163
164     painter->restore();
165 }
166
167 QSize PlIconViewItemDelegate::sizeHint ( const QStyleOptionViewItem &, const QModelIndex & index ) const
168 {
169     QFont f( index.data( Qt::FontRole ).value<QFont>() );
170     f.setPointSize( __MAX( f.pointSize() + i_zoom, 4 ) );
171     f.setBold( true );
172     QFontMetrics fm( f );
173     int textHeight = fm.height();
174     int averagewidth = fm.averageCharWidth();
175     QSize sz ( averagewidth * ICON_SCALER + 4 * SPACER,
176                averagewidth * ICON_SCALER + 4 * SPACER + 2 * textHeight + 1 );
177     return sz;
178 }
179
180
181 #define LISTVIEW_ART_SIZE 45
182
183 void PlListViewItemDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
184 {
185     QString title = VLCModel::getMeta( index, COLUMN_TITLE );
186     QString duration = VLCModel::getMeta( index, COLUMN_DURATION );
187     if( !duration.isEmpty() ) title += QString(" [%1]").arg( duration );
188
189     QString artist = VLCModel::getMeta( index, COLUMN_ARTIST );
190     QString album = VLCModel::getMeta( index, COLUMN_ALBUM );
191     QString trackNum = VLCModel::getMeta( index, COLUMN_TRACK_NUMBER );
192     QString artistAlbum = artist;
193     if( !album.isEmpty() )
194     {
195         if( !artist.isEmpty() ) artistAlbum += ": ";
196         artistAlbum += album;
197         if( !trackNum.isEmpty() ) artistAlbum += QString( " [#%1]" ).arg( trackNum );
198     }
199
200     QPixmap artPix = VLCModel::getArtPixmap( index, QSize( LISTVIEW_ART_SIZE, LISTVIEW_ART_SIZE ) );
201
202     //Draw selection rectangle and current playing item indication
203     paintBackground( painter, option, index );
204
205     QRect artRect( artPix.rect() );
206     artRect.moveCenter( QPoint( artRect.center().x() + 3,
207                                 option.rect.center().y() ) );
208     //Draw album art
209     painter->drawPixmap( artRect, artPix );
210
211     //Start drawing text
212     painter->save();
213
214     if( option.state & QStyle::State_Selected )
215         painter->setPen( option.palette.color( QPalette::HighlightedText ) );
216
217     QTextOption textOpt( Qt::AlignVCenter | Qt::AlignLeft );
218     textOpt.setWrapMode( QTextOption::NoWrap );
219
220     QFont f( index.data( Qt::FontRole ).value<QFont>() );
221
222     //Draw title info
223     f.setItalic( true );
224     f.setPointSize( __MAX( f.pointSize() + i_zoom, 4 ) );
225     f.setBold( index.data( VLCModel::IsCurrentRole ).toBool() );
226     painter->setFont( f );
227     QFontMetrics fm( painter->fontMetrics() );
228
229     QRect textRect = option.rect.adjusted( LISTVIEW_ART_SIZE + 10, 0, -10, 0 );
230     if( !artistAlbum.isEmpty() )
231     {
232         textRect.setHeight( fm.height() );
233         textRect.moveBottom( option.rect.center().y() - 2 );
234     }
235
236     //Draw children indicator
237     if( !index.data( VLCModel::IsLeafNodeRole ).toBool() )
238     {
239         QPixmap dirPix = QPixmap( ":/type/node" );
240         painter->drawPixmap( QPoint( textRect.x(), textRect.center().y() - dirPix.height() / 2 ),
241                              dirPix );
242         textRect.setLeft( textRect.x() + dirPix.width() + 5 );
243     }
244
245     painter->drawText( textRect,
246                        fm.elidedText( title, Qt::ElideRight, textRect.width() ),
247                        textOpt );
248
249     // Draw artist and album info
250     if( !artistAlbum.isEmpty() )
251     {
252         f.setItalic( false );
253         painter->setFont( f );
254         fm = painter->fontMetrics();
255
256         textRect.moveTop( textRect.bottom() + 4 );
257         textRect.setLeft( textRect.x() + 20 );
258
259         painter->drawText( textRect,
260                            fm.elidedText( artistAlbum, Qt::ElideRight, textRect.width() ),
261                            textOpt );
262     }
263
264     painter->restore();
265 }
266
267 QSize PlListViewItemDelegate::sizeHint ( const QStyleOptionViewItem &, const QModelIndex & ) const
268 {
269     QFont f;
270     f.setBold( true );
271     QFontMetrics fm( f );
272     int height = qMax( LISTVIEW_ART_SIZE, 2 * fm.height() + 4 ) + 6;
273     return QSize( 0, height );
274 }
275
276
277 void PlTreeViewItemDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
278 {
279     if ( index.data( VLCModel::IsCurrentRole ).toBool() )
280     {
281         QStyleOptionViewItem myoptions = option;
282         myoptions.font.setBold( true );
283         AbstractPlViewItemDelegate::paint( painter, myoptions, index );
284     }
285     else
286     {
287         AbstractPlViewItemDelegate::paint( painter, option, index );
288     }
289 }
290
291 void CellPixmapDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
292 {
293     QPixmap pixmap = index.data( Qt::DecorationRole ).value<QPixmap>();
294     painter->drawPixmap( option.rect.topLeft(),
295                          pixmap.scaled( option.rect.size(), Qt::KeepAspectRatio ) );
296 }
297
298 static inline void plViewStartDrag( QAbstractItemView *view, const Qt::DropActions & supportedActions )
299 {
300     QDrag *drag = new QDrag( view );
301     drag->setPixmap( QPixmap( ":/noart64" ) );
302     drag->setMimeData( view->model()->mimeData(
303         view->selectionModel()->selectedIndexes() ) );
304     drag->exec( supportedActions );
305 }
306
307 static void plViewDragMoveEvent( QAbstractItemView *, QDragMoveEvent * event )
308 {
309     if( event->keyboardModifiers() & Qt::ControlModifier &&
310         event->possibleActions() & Qt::CopyAction )
311         event->setDropAction( Qt::CopyAction );
312     else event->acceptProposedAction();
313 }
314
315 PlIconView::PlIconView( QAbstractItemModel *, QWidget *parent ) : QListView( parent )
316 {
317     PlIconViewItemDelegate *delegate = new PlIconViewItemDelegate( this );
318
319     setViewMode( QListView::IconMode );
320     setMovement( QListView::Static );
321     setResizeMode( QListView::Adjust );
322     setWrapping( true );
323     setUniformItemSizes( true );
324     setSelectionMode( QAbstractItemView::ExtendedSelection );
325     setSelectionBehavior( QAbstractItemView::SelectRows );
326     setDragEnabled(true);
327     setAttribute( Qt::WA_MacShowFocusRect, false );
328     viewport()->setAttribute( Qt::WA_Hover );
329     /* dropping in QListView::IconMode does not seem to work */
330     //setAcceptDrops( true );
331     //setDropIndicatorShown(true);
332
333     setItemDelegate( delegate );
334 }
335
336 void PlIconView::startDrag ( Qt::DropActions supportedActions )
337 {
338     plViewStartDrag( this, supportedActions );
339 }
340
341 void PlIconView::dragMoveEvent ( QDragMoveEvent * event )
342 {
343     plViewDragMoveEvent( this, event );
344     QAbstractItemView::dragMoveEvent( event );
345 }
346
347 bool PlIconView::viewportEvent ( QEvent * event )
348 {
349     if ( event->type() == QEvent::ToolTip )
350     {
351         event->ignore();
352         return true;
353     }
354     else if ( event->type() == QEvent::Wheel )
355     {
356         QWheelEvent *wEvent = static_cast<QWheelEvent *>(event);
357         if( wEvent->modifiers() & Qt::ControlModifier ) {
358             event->ignore();
359             return true;
360         }
361     }
362     return QAbstractItemView::viewportEvent( event );
363 }
364
365 PlListView::PlListView( QAbstractItemModel *, QWidget *parent ) : QListView( parent )
366 {
367     setViewMode( QListView::ListMode );
368     setUniformItemSizes( true );
369     setSelectionMode( QAbstractItemView::ExtendedSelection );
370     setSelectionBehavior( QAbstractItemView::SelectRows );
371     setAlternatingRowColors( true );
372     setDragEnabled(true);
373     setAcceptDrops( true );
374     setDropIndicatorShown(true);
375
376     PlListViewItemDelegate *delegate = new PlListViewItemDelegate( this );
377     setItemDelegate( delegate );
378     setAttribute( Qt::WA_MacShowFocusRect, false );
379     viewport()->setAttribute( Qt::WA_Hover );
380 }
381
382 void PlListView::startDrag ( Qt::DropActions supportedActions )
383 {
384     plViewStartDrag( this, supportedActions );
385 }
386
387 void PlListView::dragMoveEvent ( QDragMoveEvent * event )
388 {
389     plViewDragMoveEvent( this, event );
390     QAbstractItemView::dragMoveEvent( event );
391 }
392
393 void PlListView::keyPressEvent( QKeyEvent *event )
394 {
395     //If the space key is pressed, override the standard list behaviour to allow pausing
396     //to proceed.
397     if ( event->modifiers() == Qt::NoModifier && event->key() == Qt::Key_Space )
398         QWidget::keyPressEvent( event );
399     //Otherwise, just do as usual.
400     else
401         QListView::keyPressEvent( event );
402 }
403
404 bool PlListView::viewportEvent ( QEvent * event )
405 {
406     if ( event->type() == QEvent::ToolTip )
407     {
408         event->ignore();
409         return true;
410     }
411     else if ( event->type() == QEvent::Wheel )
412     {
413         QWheelEvent *wEvent = static_cast<QWheelEvent *>(event);
414         if( wEvent->modifiers() & Qt::ControlModifier ) {
415             event->ignore();
416             return true;
417         }
418     }
419     return QAbstractItemView::viewportEvent( event );
420 }
421
422 PlTreeView::PlTreeView( QAbstractItemModel *, QWidget *parent ) : QTreeView( parent )
423 {
424     setItemDelegate( new PlTreeViewItemDelegate( this ) );
425     setItemDelegateForColumn( VLCModel::metaToColumn(COLUMN_COVER),
426                               new CellPixmapDelegate( this ) );
427     setIconSize( QSize( 20, 20 ) );
428     setAlternatingRowColors( true );
429     setAnimated( true );
430     setUniformRowHeights( true );
431     setSortingEnabled( true );
432     setAttribute( Qt::WA_MacShowFocusRect, false );
433     viewport()->setAttribute( Qt::WA_Hover );
434     header()->setSortIndicator( -1 , Qt::AscendingOrder );
435     header()->setSortIndicatorShown( true );
436 #if HAS_QT5
437     header()->setSectionsClickable( true );
438 #else
439     header()->setClickable( true );
440 #endif
441     header()->setContextMenuPolicy( Qt::CustomContextMenu );
442
443     setSelectionBehavior( QAbstractItemView::SelectRows );
444     setSelectionMode( QAbstractItemView::ExtendedSelection );
445     setDragEnabled( true );
446     setAcceptDrops( true );
447     setDropIndicatorShown( true );
448     setContextMenuPolicy( Qt::CustomContextMenu );
449 }
450
451 void PlTreeView::setModel( QAbstractItemModel * model )
452 {
453     QTreeView::setModel( model );
454     VLCModel *m = static_cast<VLCModel*>(model);
455     CONNECT( this, expanded( const QModelIndex & ),
456              m, ensureArtRequested( const QModelIndex & ) );
457 }
458
459 void PlTreeView::startDrag ( Qt::DropActions supportedActions )
460 {
461     plViewStartDrag( this, supportedActions );
462 }
463
464 void PlTreeView::dragMoveEvent ( QDragMoveEvent * event )
465 {
466     plViewDragMoveEvent( this, event );
467     QAbstractItemView::dragMoveEvent( event );
468 }
469
470 void PlTreeView::keyPressEvent( QKeyEvent *event )
471 {
472     //If the space key is pressed, override the standard list behaviour to allow pausing
473     //to proceed.
474     if ( event->modifiers() == Qt::NoModifier && event->key() == Qt::Key_Space )
475         QWidget::keyPressEvent( event );
476     //Otherwise, just do as usual.
477     else
478         QTreeView::keyPressEvent( event );
479 }
480
481 #include <QHBoxLayout>
482 PicFlowView::PicFlowView( QAbstractItemModel *p_model, QWidget *parent ) : QAbstractItemView( parent )
483 {
484     QHBoxLayout *layout = new QHBoxLayout( this );
485     layout->setMargin( 0 );
486     picFlow = new PictureFlow( this, p_model );
487     picFlow->setContextMenuPolicy( Qt::CustomContextMenu );
488     connect( picFlow, SIGNAL(customContextMenuRequested( const QPoint & )),
489              this,    SIGNAL(customContextMenuRequested( const QPoint & )) );
490     layout->addWidget( picFlow );
491     picFlow->setSlideSize(QSize( 4*LISTVIEW_ART_SIZE, 3*LISTVIEW_ART_SIZE) );
492     setSelectionMode( QAbstractItemView::SingleSelection );
493 }
494
495 void PicFlowView::setModel( QAbstractItemModel *model )
496 {
497     QAbstractItemView::setModel( model );
498     picFlow->setModel( model );
499 }
500
501 int PicFlowView::horizontalOffset() const
502 {
503     return 0;
504 }
505
506 int PicFlowView::verticalOffset() const
507 {
508     return 0;
509 }
510
511 QRect PicFlowView::visualRect(const QModelIndex & ) const
512 {
513     return QRect( QPoint(0,0), picFlow->slideSize() );
514 }
515
516 void PicFlowView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint)
517 {
518      int currentIndex = picFlow->centerIndex();
519      if( qAbs( currentIndex - index.row()) > 20 )
520      {
521         /* offset is offset from target index toward currentIndex */
522         int offset = -19;
523         if( index.row() > currentIndex )
524             offset = 19;
525         picFlow->setCenterIndex( index.row() + offset );
526         picFlow->showSlide( index.row() );
527      }
528      else
529         picFlow->showSlide( index.row() );
530 }
531
532 QModelIndex PicFlowView::indexAt(const QPoint &) const
533 {
534     return QModelIndex();
535     // No idea, PictureFlow doesn't provide anything to help this
536 }
537
538 QModelIndex PicFlowView::moveCursor(QAbstractItemView::CursorAction, Qt::KeyboardModifiers)
539 {
540     return QModelIndex();
541 }
542
543 bool PicFlowView::isIndexHidden(const QModelIndex &index) const
544 {
545     int currentIndex = picFlow->centerIndex();
546     if( index.row()-5 <= currentIndex &&
547         index.row()+5 >= currentIndex )
548         return false;
549     else
550         return true;
551 }
552
553 QRegion PicFlowView::visualRegionForSelection(const QItemSelection &) const
554 {
555     return QRect();
556 }
557
558 void PicFlowView::setSelection(const QRect &, QFlags<QItemSelectionModel::SelectionFlag>)
559 {
560     // No selection possible
561 }
562
563 void PicFlowView::dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
564 {
565     int currentIndex = picFlow->centerIndex();
566     for(int i = topLeft.row(); i<=bottomRight.row(); i++ )
567     {
568         if( i-5 <= currentIndex &&
569             i+5 >= currentIndex )
570         {
571             picFlow->render();
572             return;
573         }
574     }
575 }
576
577 void PicFlowView::playItem( int i_item )
578 {
579     emit activated( model()->index(i_item, 0) );
580 }
581
582 bool PicFlowView::viewportEvent ( QEvent * event )
583 {
584     if ( event->type() == QEvent::ToolTip )
585     {
586         event->ignore();
587         return true;
588     }
589     else if ( event->type() == QEvent::Wheel )
590     {
591         QWheelEvent *wEvent = static_cast<QWheelEvent *>(event);
592         if( wEvent->modifiers() & Qt::ControlModifier ) {
593             event->ignore();
594             return true;
595         }
596     }
597     return QAbstractItemView::viewportEvent( event );
598 }