]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/icon_view.cpp
Qt4: scale iconview art related to fontsize
[vlc] / modules / gui / qt4 / components / playlist / icon_view.cpp
1 /*****************************************************************************
2  * icon_view.cpp : Icon view 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/icon_view.hpp"
25 #include "components/playlist/playlist_model.hpp"
26 #include "components/playlist/sorting.h"
27 #include "input_manager.hpp"
28
29 #include <QApplication>
30 #include <QPainter>
31 #include <QRect>
32 #include <QStyleOptionViewItem>
33 #include <QFontMetrics>
34 #include <QPixmapCache>
35 #include <QDrag>
36 #include <QDragMoveEvent>
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 QString AbstractPlViewItemDelegate::getMeta( const QModelIndex & index, int meta ) const
46 {
47     return index.model()->index( index.row(),
48                                   PLModel::columnFromMeta( meta ),
49                                   index.parent() )
50                                 .data().toString();
51 }
52
53 void AbstractPlViewItemDelegate::paintBackground(
54     QPainter *painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
55 {
56     /* FIXME: This does not indicate item selection in all QStyles, so for the time being we
57        have to draw it ourselves, to ensure visible effect of selection on all platforms */
58     /* QApplication::style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option,
59                                             painter ); */
60
61     painter->save();
62     QRect r = option.rect.adjusted( 0, 0, -1, -1 );
63     if( option.state & QStyle::State_Selected )
64     {
65         painter->setBrush( option.palette.color( QPalette::Highlight ) );
66         painter->setPen( option.palette.color( QPalette::Highlight ).darker( 150 ) );
67         painter->drawRect( r );
68     }
69     else if( index.data( PLModel::IsCurrentRole ).toBool() )
70     {
71         painter->setBrush( QBrush( Qt::lightGray ) );
72         painter->setPen( QColor( Qt::darkGray ) );
73         painter->drawRect( r );
74     }
75     if( option.state & QStyle::State_MouseOver )
76     {
77         painter->setOpacity( 0.5 );
78         painter->setPen( Qt::NoPen );
79         painter->setBrush( option.palette.color( QPalette::Highlight ).lighter( 150 ) );
80         painter->drawRect( option.rect );
81     }
82     painter->restore();
83 }
84
85 QPixmap AbstractPlViewItemDelegate::getArtPixmap( const QModelIndex & index, const QSize & size ) const
86 {
87     PLItem *item = static_cast<PLItem*>( index.internalPointer() );
88     assert( item );
89
90     QString artUrl = InputManager::decodeArtURL( item->inputItem() );
91
92     if( artUrl.isEmpty() )
93     {
94         for( int i = 0; i < item->childCount(); i++ )
95         {
96             artUrl = InputManager::decodeArtURL( item->child( i )->inputItem() );
97             if( !artUrl.isEmpty() )
98                 break;
99         }
100     }
101
102     QPixmap artPix;
103
104     QString key = artUrl + QString("%1%2").arg(size.width()).arg(size.height());
105
106     if( !QPixmapCache::find( key, artPix ))
107     {
108         if( artUrl.isEmpty() || !artPix.load( artUrl ) )
109         {
110             key = QString("noart%1%2").arg(size.width()).arg(size.height());
111             if( !QPixmapCache::find( key, artPix ) )
112             {
113                 artPix = QPixmap( ":/noart" ).scaled( size,
114                                                       Qt::KeepAspectRatio,
115                                                       Qt::SmoothTransformation );
116                 QPixmapCache::insert( key, artPix );
117             }
118         }
119         else
120         {
121             artPix = artPix.scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation );
122             QPixmapCache::insert( key, artPix );
123         }
124     }
125
126     return artPix;
127 }
128
129 void PlIconViewItemDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
130 {
131     QString title = getMeta( index, COLUMN_TITLE );
132     QString artist = getMeta( index, COLUMN_ARTIST );
133
134     QFont font( index.data( Qt::FontRole ).value<QFont>() );
135     painter->setFont( font );
136     QFontMetrics fm = painter->fontMetrics();
137
138     int averagewidth = fm.averageCharWidth();
139     int art_width = averagewidth * ICON_SCALER;
140     int art_height = averagewidth * ICON_SCALER;
141
142     QPixmap artPix = getArtPixmap( index, QSize( art_width, art_height) );
143
144     paintBackground( painter, option, index );
145
146     painter->save();
147
148     QRect artRect( option.rect.x() + averagewidth*2 + ( art_width - artPix.width() ) / 2,
149                    option.rect.y() + averagewidth + ( art_height - artPix.height() ) / 2,
150                    artPix.width(), artPix.height() );
151
152     // Draw the drop shadow
153     painter->save();
154     painter->setOpacity( 0.7 );
155     painter->setBrush( QBrush( Qt::darkGray ) );
156     painter->setPen( Qt::NoPen );
157     painter->drawRoundedRect( artRect.adjusted( 0, 0, 2, 2 ), ART_RADIUS, ART_RADIUS );
158     painter->restore();
159
160     // Draw the art pixmap
161     QPainterPath artRectPath;
162     artRectPath.addRoundedRect( artRect, ART_RADIUS, ART_RADIUS );
163     painter->setClipPath( artRectPath );
164     painter->drawPixmap( artRect, artPix );
165     painter->setClipping( false );
166
167     if( option.state & QStyle::State_Selected )
168         painter->setPen( option.palette.color( QPalette::HighlightedText ) );
169
170
171     //Draw children indicator
172     if( !index.data( PLModel::IsLeafNodeRole ).toBool() )
173     {
174         QRect r( option.rect );
175         r.setSize( QSize( 25, 25 ) );
176         r.translate( 5, 5 );
177         if( index.data( PLModel::IsCurrentsParentNodeRole ).toBool() )
178         {
179             painter->setOpacity( 0.75 );
180             QPainterPath nodeRectPath;
181             nodeRectPath.addRoundedRect( r, 4, 4 );
182             painter->fillPath( nodeRectPath, option.palette.color( QPalette::Highlight ) );
183             painter->setOpacity( 1.0 );
184         }
185         QPixmap dirPix( ":/type/node" );
186         QRect r2( dirPix.rect() );
187         r2.moveCenter( r.center() );
188         painter->drawPixmap( r2, dirPix );
189     }
190
191     // Draw title
192     font.setItalic( true );
193
194     fm = painter->fontMetrics();
195     QRect textRect = option.rect.adjusted( 1, art_height + 10, 0, -1 );
196     textRect.setHeight( fm.height() );
197
198     painter->drawText( textRect,
199                       fm.elidedText( title, Qt::ElideRight, textRect.width() ),
200                       QTextOption( Qt::AlignCenter ) );
201
202     // Draw artist
203     painter->setPen( painter->pen().color().lighter( 150 ) );
204     font.setItalic( false );
205     painter->setFont( font );
206     fm = painter->fontMetrics();
207
208     textRect.moveTop( textRect.bottom() + 1 );
209
210     painter->drawText(  textRect,
211                         fm.elidedText( artist, Qt::ElideRight, textRect.width() ),
212                         QTextOption( Qt::AlignCenter ) );
213
214     painter->restore();
215 }
216
217 QSize PlIconViewItemDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
218 {
219     QFont f( index.data( Qt::FontRole ).value<QFont>() );
220     f.setBold( true );
221     QFontMetrics fm( f );
222     int textHeight = fm.height();
223     int averagewidth = fm.averageCharWidth();
224     QSize sz ( averagewidth * ICON_SCALER + 4 * SPACER,
225                averagewidth * ICON_SCALER + 4 * SPACER + 2 * textHeight + 1 );
226     return sz;
227 }
228
229
230 #define LISTVIEW_ART_SIZE 45
231
232 void PlListViewItemDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
233 {
234     QModelIndex parent = index.parent();
235     QModelIndex i;
236
237     QString title = getMeta( index, COLUMN_TITLE );
238     QString duration = getMeta( index, COLUMN_DURATION );
239     if( !duration.isEmpty() ) title += QString(" [%1]").arg( duration );
240
241     QString artist = getMeta( index, COLUMN_ARTIST );
242     QString album = getMeta( index, COLUMN_ALBUM );
243     QString trackNum = getMeta( index, COLUMN_TRACK_NUMBER );
244     QString artistAlbum = artist;
245     if( !album.isEmpty() )
246     {
247         if( !artist.isEmpty() ) artistAlbum += ": ";
248         artistAlbum += album;
249         if( !trackNum.isEmpty() ) artistAlbum += QString( " [#%1]" ).arg( trackNum );
250     }
251
252     QPixmap artPix = getArtPixmap( index, QSize( LISTVIEW_ART_SIZE, LISTVIEW_ART_SIZE ) );
253
254     //Draw selection rectangle and current playing item indication
255     paintBackground( painter, option, index );
256
257     QRect artRect( artPix.rect() );
258     artRect.moveCenter( QPoint( artRect.center().x() + 3,
259                                 option.rect.center().y() ) );
260     //Draw album art
261     painter->drawPixmap( artRect, artPix );
262
263     //Start drawing text
264     painter->save();
265
266     if( option.state & QStyle::State_Selected )
267         painter->setPen( option.palette.color( QPalette::HighlightedText ) );
268
269     QTextOption textOpt( Qt::AlignVCenter | Qt::AlignLeft );
270     textOpt.setWrapMode( QTextOption::NoWrap );
271
272     QFont f( index.data( Qt::FontRole ).value<QFont>() );
273
274     //Draw title info
275     f.setItalic( true );
276     painter->setFont( f );
277     QFontMetrics fm( painter->fontMetrics() );
278
279     QRect textRect = option.rect.adjusted( LISTVIEW_ART_SIZE + 10, 0, -10, 0 );
280     if( !artistAlbum.isEmpty() )
281     {
282         textRect.setHeight( fm.height() );
283         textRect.moveBottom( option.rect.center().y() - 2 );
284     }
285
286     //Draw children indicator
287     if( !index.data( PLModel::IsLeafNodeRole ).toBool() )
288     {
289         QPixmap dirPix = QPixmap( ":/type/node" );
290         painter->drawPixmap( QPoint( textRect.x(), textRect.center().y() - dirPix.height() / 2 ),
291                              dirPix );
292         textRect.setLeft( textRect.x() + dirPix.width() + 5 );
293     }
294
295     painter->drawText( textRect,
296                        fm.elidedText( title, Qt::ElideRight, textRect.width() ),
297                        textOpt );
298
299     // Draw artist and album info
300     if( !artistAlbum.isEmpty() )
301     {
302         f.setItalic( false );
303         painter->setFont( f );
304         fm = painter->fontMetrics();
305
306         textRect.moveTop( textRect.bottom() + 4 );
307         textRect.setLeft( textRect.x() + 20 );
308
309         painter->drawText( textRect,
310                            fm.elidedText( artistAlbum, Qt::ElideRight, textRect.width() ),
311                            textOpt );
312     }
313
314     painter->restore();
315 }
316
317 QSize PlListViewItemDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
318 {
319   QFont f;
320   f.setBold( true );
321   QFontMetrics fm( f );
322   int height = qMax( LISTVIEW_ART_SIZE, 2 * fm.height() + 4 ) + 6;
323   return QSize( 0, height );
324 }
325
326 static void plViewStartDrag( QAbstractItemView *view, const Qt::DropActions & supportedActions )
327 {
328     QDrag *drag = new QDrag( view );
329     drag->setPixmap( QPixmap( ":/noart64" ) );
330     drag->setMimeData( view->model()->mimeData(
331         view->selectionModel()->selectedIndexes() ) );
332     drag->exec( supportedActions );
333 }
334
335 static void plViewDragMoveEvent( QAbstractItemView *view, QDragMoveEvent * event )
336 {
337     if( event->keyboardModifiers() & Qt::ControlModifier &&
338         event->possibleActions() & Qt::CopyAction )
339         event->setDropAction( Qt::CopyAction );
340     else event->acceptProposedAction();
341 }
342
343 PlIconView::PlIconView( PLModel *model, QWidget *parent ) : QListView( parent )
344 {
345     PlIconViewItemDelegate *delegate = new PlIconViewItemDelegate( this );
346
347     setModel( model );
348     setViewMode( QListView::IconMode );
349     setMovement( QListView::Static );
350     setResizeMode( QListView::Adjust );
351     setWrapping( true );
352     setUniformItemSizes( true );
353     setSelectionMode( QAbstractItemView::ExtendedSelection );
354     setDragEnabled(true);
355     /* dropping in QListView::IconMode does not seem to work */
356     //setAcceptDrops( true );
357     //setDropIndicatorShown(true);
358
359     setItemDelegate( delegate );
360 }
361
362 void PlIconView::startDrag ( Qt::DropActions supportedActions )
363 {
364     plViewStartDrag( this, supportedActions );
365 }
366
367 void PlIconView::dragMoveEvent ( QDragMoveEvent * event )
368 {
369     plViewDragMoveEvent( this, event );
370     QAbstractItemView::dragMoveEvent( event );
371 }
372
373 PlListView::PlListView( PLModel *model, QWidget *parent ) : QListView( parent )
374 {
375     setModel( model );
376     setViewMode( QListView::ListMode );
377     setUniformItemSizes( true );
378     setSelectionMode( QAbstractItemView::ExtendedSelection );
379     setAlternatingRowColors( true );
380     setDragEnabled(true);
381     setAcceptDrops( true );
382     setDropIndicatorShown(true);
383
384     PlListViewItemDelegate *delegate = new PlListViewItemDelegate( this );
385     setItemDelegate( delegate );
386 }
387
388 void PlListView::startDrag ( Qt::DropActions supportedActions )
389 {
390     plViewStartDrag( this, supportedActions );
391 }
392
393 void PlListView::dragMoveEvent ( QDragMoveEvent * event )
394 {
395     plViewDragMoveEvent( this, event );
396     QAbstractItemView::dragMoveEvent( event );
397 }
398
399 void PlListView::keyPressEvent( QKeyEvent *event )
400 {
401     //If the space key is pressed, override the standard list behaviour to allow pausing
402     //to proceed.
403     if ( event->modifiers() == Qt::NoModifier && event->key() == Qt::Key_Space )
404         QWidget::keyPressEvent( event );
405     //Otherwise, just do as usual.
406     else
407         QListView::keyPressEvent( event );
408 }
409
410 void PlTreeView::startDrag ( Qt::DropActions supportedActions )
411 {
412     plViewStartDrag( this, supportedActions );
413 }
414
415 void PlTreeView::dragMoveEvent ( QDragMoveEvent * event )
416 {
417     plViewDragMoveEvent( this, event );
418     QAbstractItemView::dragMoveEvent( event );
419 }
420
421 void PlTreeView::keyPressEvent( QKeyEvent *event )
422 {
423     //If the space key is pressed, override the standard list behaviour to allow pausing
424     //to proceed.
425     if ( event->modifiers() == Qt::NoModifier && event->key() == Qt::Key_Space )
426         QWidget::keyPressEvent( event );
427     //Otherwise, just do as usual.
428     else
429         QTreeView::keyPressEvent( event );
430 }