Add nice svg graphics to the playlist, based on the work of fapasv ( http://kde-look...
[amarok.git] / src / playlist / PlaylistGraphicsItem.cpp
blob1499c9991692c2904edbb2e0c8d097d8ae3dba49
1 /***************************************************************************
2 * copyright : (C) 2007 Ian Monroe <ian@monroe.nu> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License version 2 *
6 * as published by the Free Software Foundation. *
7 ***************************************************************************/
9 #include "debug.h"
10 #include "meta/MetaUtility.h"
11 #include "AmarokMimeData.h"
12 #include "PlaylistGraphicsItem.h"
13 #include "PlaylistGraphicsView.h"
14 #include "PlaylistDropVis.h"
15 #include "PlaylistModel.h"
16 #include "PlaylistTextItem.h"
17 #include "TheInstances.h"
19 #include "KStandardDirs"
21 #include <QBrush>
22 #include <QDrag>
23 #include <QFontMetricsF>
24 #include <QGraphicsScene>
25 #include <QGraphicsTextItem>
26 #include <QGraphicsPixmapItem>
27 #include <QGraphicsRectItem>
28 #include <QGraphicsSceneMouseEvent>
29 #include <QGraphicsView>
30 #include <QMimeData>
31 #include <QPen>
32 #include <QPixmapCache>
33 #include <QRadialGradient>
34 #include <QScrollBar>
35 #include <QStyleOptionGraphicsItem>
37 #include <KLocale>
39 struct Playlist::GraphicsItem::ActiveItems
41 ActiveItems()
42 : foreground( 0 )
43 , bottomLeftText( 0 )
44 , bottomRightText( 0 )
45 , topLeftText( 0 )
46 , topRightText( 0 )
47 , lastWidth( -5 )
48 { }
49 ~ActiveItems()
51 delete bottomLeftText;
52 delete bottomRightText;
53 delete foreground;
54 delete topLeftText;
55 delete topRightText;
59 QGraphicsRectItem* foreground;
60 Playlist::TextItem* bottomLeftText;
61 Playlist::TextItem* bottomRightText;
62 Playlist::TextItem* topLeftText;
63 Playlist::TextItem* topRightText;
65 int lastWidth;
67 QRectF preDragLocation;
68 Meta::TrackPtr track;
72 const qreal Playlist::GraphicsItem::ALBUM_WIDTH = 50.0;
73 const qreal Playlist::GraphicsItem::MARGIN = 4.0;
74 QFontMetricsF* Playlist::GraphicsItem::s_fm = 0;
75 QSvgRenderer * Playlist::GraphicsItem::s_svgRenderer = 0;
77 Playlist::GraphicsItem::GraphicsItem()
78 : QGraphicsItem()
79 , m_items( 0 )
80 , m_height( -1 )
81 , m_groupMode( -1 )
82 , m_groupModeChanged ( false )
84 setZValue( 1.0 );
85 if( !s_fm )
87 s_fm = new QFontMetricsF( QFont() );
88 m_height = qMax( ALBUM_WIDTH, s_fm->height() * 2 ) + 2 * MARGIN;
91 if ( !s_svgRenderer ) {
92 s_svgRenderer = new QSvgRenderer( KStandardDirs::locate( "data","amarok/images/playlist_items.svg" ));
93 if ( ! s_svgRenderer->isValid() )
94 debug() << "svg is kaputski";
97 setFlag( QGraphicsItem::ItemIsSelectable );
98 setFlag( QGraphicsItem::ItemIsMovable );
99 setAcceptDrops( true );
100 // setHandlesChildEvents( true ); // don't let drops etc hit the text items, doing stupid things
103 Playlist::GraphicsItem::~GraphicsItem()
105 delete m_items;
108 void
109 Playlist::GraphicsItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget )
111 // ::paint RULES:
112 // 1) You do not talk about ::paint method
113 // 2) You DO NOT talk about ::paint method
114 // 3) Do not show or hide item that are already shown or hidden, respectively
115 // 4) Do not setBrush without making sure its hasn't already been set to that brush().
116 // 5) If this is your first night at ::paint method, you HAVE to paint.
117 Q_UNUSED( painter ); Q_UNUSED( widget );
118 const QModelIndex index = The::playlistModel()->index( m_currentRow, 0 );
121 if( !m_items || ( option->rect.width() != m_items->lastWidth ) || m_groupModeChanged )
124 if( !m_items )
126 const Meta::TrackPtr track = index.data( ItemRole ).value< Playlist::Item* >()->track();
127 m_items = new Playlist::GraphicsItem::ActiveItems();
128 m_items->track = track;
129 init( track );
131 m_groupModeChanged = false;
132 resize( m_items->track, option->rect.width() );
135 // paint item background:
136 QRectF trackRect;
137 if ( m_groupMode == Head ) {
139 //make the album group header stand out
140 //painter->fillRect( option->rect, QBrush( Qt::darkCyan ) );
141 trackRect = QRectF( option->rect.x(), ALBUM_WIDTH + 2 * MARGIN, option->rect.width(), s_fm->height() /*+ MARGIN*/ );
143 } else {
144 trackRect = option->rect;
146 if ( m_groupMode != Body )
147 trackRect.setHeight( trackRect.height() - 2 ); // add a little space between items
151 if ( m_groupMode == None ) {
153 QString key = QString("single_track:%1x%2").arg(trackRect.width()).arg(trackRect.height());
154 QPixmap background(trackRect.width(), trackRect.height() );
155 background.fill( Qt::transparent );
158 if (!QPixmapCache::find(key, background)) {
159 QPainter pt( &background );
160 s_svgRenderer->render( &pt, "single_track", trackRect );
161 QPixmapCache::insert(key, background);
163 painter->drawPixmap( 0, 0, background );
164 } else if ( m_groupMode == Head ) {
166 QString key = QString("head:%1x%2").arg(trackRect.width()).arg(trackRect.height());
167 QPixmap background(option->rect.width(), option->rect.height() );
168 background.fill( Qt::transparent );
170 if (!QPixmapCache::find(key, background)) {
171 QPainter pt( &background );
172 s_svgRenderer->render( &pt, "head", option->rect );
173 QPixmapCache::insert(key, background);
175 painter->drawPixmap( 0, 0, background );
176 } else if ( m_groupMode == Body ) {
178 QString key = QString("body:%1x%2").arg(trackRect.width()).arg(trackRect.height());
179 QPixmap background(trackRect.width(), trackRect.height() );
180 background.fill( Qt::transparent );
182 if (!QPixmapCache::find(key, background)) {
183 QPainter pt( &background );
184 s_svgRenderer->render( &pt, "body", trackRect );
185 QPixmapCache::insert(key, background);
187 painter->drawPixmap( 0, 0, background );
188 } else if ( m_groupMode == End ) {
190 QString key = QString( "tail:%1x%2" ).arg( trackRect.width() ).arg(trackRect.height()) ;
191 QPixmap background(trackRect.width(), trackRect.height() );
192 background.fill( Qt::transparent );
194 if (!QPixmapCache::find(key, background)) {
195 QPainter pt( &background );
196 s_svgRenderer->render( &pt, "tail", trackRect );
197 QPixmapCache::insert(key, background);
199 painter->drawPixmap( 0, 0, background );
205 if ( ( m_groupMode == Head ) || ( m_groupMode == Body ) || ( m_groupMode == End ) ) {
206 if( m_currentRow % 2 ) {
208 QString key = QString( "alternate:%1x%2" ).arg( trackRect.width() - 16 ).arg(trackRect.height() );
209 QPixmap background(trackRect.width() - 16, trackRect.height() );
210 background.fill( Qt::transparent );
212 QRectF tempRect = trackRect;
213 tempRect.setWidth( tempRect.width() - 16 );
214 if ( m_groupMode == End )
215 tempRect.setHeight( tempRect.height() - 4 );
217 if (!QPixmapCache::find( key, background ) ) {
218 QPainter pt( &background );
219 s_svgRenderer->render( &pt, "body_background", tempRect );
220 QPixmapCache::insert( key, background );
223 if ( m_groupMode == Head )
224 painter->drawPixmap( 8, MARGIN + ALBUM_WIDTH + 2, background );
225 else
226 painter->drawPixmap( 8, 0, background );
233 if ( m_groupMode < Body ) {
234 //if we are not grouped, or are the head of a group, paint cover:
235 QPixmap albumPixmap;
236 if( m_items->track->album() )
237 albumPixmap = m_items->track->album()->image( int( ALBUM_WIDTH ) );
238 painter->drawPixmap( MARGIN, MARGIN, albumPixmap );
243 //set overlay if item is active:
244 if( index.data( ActiveTrackRole ).toBool() )
246 if( !m_items->foreground )
248 m_items->foreground = new QGraphicsRectItem( trackRect, this );
249 m_items->foreground->setPos( 0.0, trackRect.top() );
250 m_items->foreground->setZValue( 5.0 );
251 QRadialGradient gradient(trackRect.width() / 2.0, trackRect.height() / 2.0, trackRect.width() / 2.0, 20 + trackRect.width() / 2.0, trackRect.height() / 2.0 );
252 QColor start = option->palette.highlight().color().light();
253 start.setAlpha( 51 );
254 QColor end = option->palette.highlight().color().dark();
255 end.setAlpha( 51 );
256 gradient.setColorAt( 0.0, start );
257 gradient.setColorAt( 1.0, end );
258 QBrush brush( gradient );
259 m_items->foreground->setBrush( brush );
260 m_items->foreground->setPen( QPen( Qt::NoPen ) );
262 if( !m_items->foreground->isVisible() )
263 m_items->foreground->show();
265 else if( m_items->foreground && m_items->foreground->isVisible() )
266 m_items->foreground->hide();
269 void
270 Playlist::GraphicsItem::init( Meta::TrackPtr track )
273 QFont font;
274 font.setPointSize( font.pointSize() - 1 );
275 #define NewText( X ) \
276 X = new Playlist::TextItem( this ); \
277 X->setTextInteractionFlags( Qt::TextEditorInteraction ); \
278 X->setFont( font );
279 NewText( m_items->topLeftText )
280 NewText( m_items->bottomLeftText )
281 NewText( m_items->topRightText )
282 NewText( m_items->bottomRightText )
283 #undef NewText
286 void
287 Playlist::GraphicsItem::resize( Meta::TrackPtr track, int totalWidth )
289 if( totalWidth == -1 /*|| totalWidth == m_items->lastWidth */) //no change needed
290 return;
291 if( m_items->lastWidth != -5 ) //this isn't the first "resize"
292 prepareGeometryChange();
293 m_items->lastWidth = totalWidth;
294 QString prettyLength = Meta::secToPrettyTime( track->length() );
295 QString album;
296 if( track->album() )
297 album = track->album()->name();
299 const qreal lineTwoY = m_height / 2 + MARGIN;
300 const qreal textWidth = ( ( qreal( totalWidth ) - ALBUM_WIDTH ) / 2.0 );
301 const qreal leftAlignX = ALBUM_WIDTH + MARGIN;
302 qreal topRightAlignX;
303 qreal bottomRightAlignX;
306 qreal middle = textWidth + ALBUM_WIDTH + ( MARGIN * 2.0 );
307 qreal rightWidth = totalWidth - qMax( s_fm->width( album )
308 , s_fm->width( prettyLength ) );
309 topRightAlignX = qMax( middle, rightWidth );
312 //lets use all the horizontal space we can get for now..
313 int lengthStringWidth = s_fm->width( prettyLength );
314 bottomRightAlignX = ( totalWidth - 4 * MARGIN ) - lengthStringWidth ;
319 qreal spaceForTopLeft = totalWidth - ( totalWidth - topRightAlignX ) - leftAlignX;
320 qreal spaceForBottomLeft = totalWidth - ( totalWidth - bottomRightAlignX ) - leftAlignX;
321 m_items->bottomLeftText->setEditableText( QString("%1 - %2").arg( QString::number( track->trackNumber() ), track->name() ) , spaceForBottomLeft );
322 m_items->bottomRightText->setEditableText( prettyLength, totalWidth - bottomRightAlignX );
324 if ( m_groupMode == None ) {
326 m_items->topRightText->setPos( topRightAlignX, MARGIN );
327 m_items->topRightText->setEditableText( album, totalWidth - topRightAlignX );
330 QString artist;
331 if( track->artist() )
332 artist = track->artist()->name();
333 m_items->topLeftText->setEditableText( artist, spaceForTopLeft );
334 m_items->topLeftText->setPos( leftAlignX, MARGIN );
337 m_items->bottomLeftText->setPos( leftAlignX, lineTwoY );
338 m_items->bottomRightText->setPos( bottomRightAlignX, lineTwoY );
339 } else if ( m_groupMode == Head ) {
341 int headingCenter = MARGIN + ( ALBUM_WIDTH - s_fm->height() ) / 2;
343 m_items->topRightText->setPos( topRightAlignX, headingCenter );
344 m_items->topRightText->setEditableText( album, totalWidth - topRightAlignX );
347 QString artist;
348 //various artist handling:
349 //if the album has no albumartist, use Various Artists, otherwise use the albumartist's name
350 if( track->album()->albumArtist() )
351 artist = track->album()->albumArtist()->name();
352 else
354 artist = findArtistForCurrentAlbum();
355 if( artist.isEmpty() )
356 artist = i18n( "Various Artists" );
358 m_items->topLeftText->setEditableText( artist, spaceForTopLeft );
359 m_items->topLeftText->setPos( leftAlignX, headingCenter );
362 int underImageY = MARGIN + ALBUM_WIDTH + 2;
364 m_items->bottomLeftText->setPos( MARGIN * 3, underImageY );
365 m_items->bottomRightText->setPos( bottomRightAlignX, underImageY );
367 } else {
368 m_items->bottomLeftText->setPos( MARGIN * 3, 0 );
369 m_items->bottomRightText->setPos( bottomRightAlignX, 0 );
372 m_items->lastWidth = totalWidth;
375 QString
376 Playlist::GraphicsItem::findArtistForCurrentAlbum() const
378 if( m_groupMode != Head )
379 return QString();
381 const QModelIndex index = The::playlistModel()->index( m_currentRow, 0 );
382 if( index.data( GroupRole ).toInt() != Head )
384 return QString();
386 else
388 QString artist;
389 Meta::TrackPtr currentTrack = index.data( TrackRole ).value< Meta::TrackPtr >();
390 if( currentTrack->artist() )
391 artist = currentTrack->artist()->name();
392 else
393 return QString();
394 //it's an album group, and the current row is the head, so the next row is either Body or End
395 //that means we have to execute the loop at least once
396 QModelIndex idx;
397 int row = m_currentRow + 1;
400 idx = The::playlistModel()->index( row++, 0 );
401 Meta::TrackPtr track = idx.data( TrackRole ).value< Meta::TrackPtr >();
402 if( track->artist() )
404 if( artist != track->artist()->name() )
405 return QString();
407 else
409 return QString();
412 while( idx.data( GroupRole ).toInt() == Body );
414 return artist;
418 QRectF
419 Playlist::GraphicsItem::boundingRect() const
421 // the viewport()->size() takes scrollbars into account
422 return QRectF( 0.0, 0.0, The::playlistView()->viewport()->size().width(), m_height );
425 void
426 Playlist::GraphicsItem::play()
428 The::playlistModel()->play( m_currentRow );
431 void
432 Playlist::GraphicsItem::mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event )
434 if( m_items )
436 event->accept();
437 play();
438 return;
440 QGraphicsItem::mouseDoubleClickEvent( event );
443 void
444 Playlist::GraphicsItem::mousePressEvent( QGraphicsSceneMouseEvent *event )
446 if( event->buttons() & Qt::RightButton || !m_items )
448 event->ignore();
449 return;
451 m_items->preDragLocation = mapToScene( boundingRect() ).boundingRect();
452 QGraphicsItem::mousePressEvent( event );
455 // With help from QGraphicsView::mouseMoveEvent()
456 void
457 Playlist::GraphicsItem::mouseMoveEvent( QGraphicsSceneMouseEvent *event )
459 if( (event->buttons() & Qt::LeftButton) && ( flags() & QGraphicsItem::ItemIsMovable) && m_items )
461 QPointF scenePosition = event->scenePos();
463 if( scenePosition.y() < 0 )
464 return;
466 bool dragOverOriginalPosition = m_items->preDragLocation.contains( scenePosition );
468 //make sure item is drawn on top of other items
469 setZValue( 2.0 );
471 // Determine the list of selected items
472 QList<QGraphicsItem *> selectedItems = scene()->selectedItems();
473 if( !isSelected() )
474 selectedItems << this;
475 // Move all selected items
476 foreach( QGraphicsItem *item, selectedItems )
478 if( (item->flags() & QGraphicsItem::ItemIsMovable) && (!item->parentItem() || !item->parentItem()->isSelected()) )
480 Playlist::GraphicsItem *above = 0;
481 QPointF diff;
482 if( item == this && !dragOverOriginalPosition )
484 diff = event->scenePos() - event->lastScenePos();
485 QList<QGraphicsItem*> collisions = scene()->items( event->scenePos() );
486 foreach( QGraphicsItem *i, collisions )
488 Playlist::GraphicsItem *c = dynamic_cast<Playlist::GraphicsItem *>( i );
489 if( c && c != this )
491 above = c;
492 break;
496 else
498 diff = item->mapToParent( item->mapFromScene(event->scenePos()))
499 - item->mapToParent(item->mapFromScene(event->lastScenePos()));
502 item->moveBy( 0, diff.y() );
503 if( item->flags() & ItemIsSelectable )
504 item->setSelected( true );
506 if( dragOverOriginalPosition )
507 Playlist::DropVis::instance()->show( m_items->preDragLocation.y() );
508 else
509 Playlist::DropVis::instance()->show( above );
513 else
515 QGraphicsItem::mouseMoveEvent( event );
519 void
520 Playlist::GraphicsItem::dragEnterEvent( QGraphicsSceneDragDropEvent *event )
522 foreach( QString mime, The::playlistModel()->mimeTypes() )
524 if( event->mimeData()->hasFormat( mime ) )
526 event->accept();
527 Playlist::DropVis::instance()->show( this );
528 break;
533 void
534 Playlist::GraphicsItem::dropEvent( QGraphicsSceneDragDropEvent * event )
536 event->accept();
537 setZValue( 1.0 );
538 The::playlistModel()->dropMimeData( event->mimeData(), Qt::CopyAction, m_currentRow, 0, QModelIndex() );
539 Playlist::DropVis::instance()->hide();
542 void
543 Playlist::GraphicsItem::refresh()
545 QPixmap albumPixmap;
546 if( !m_items || !m_items->track )
547 return;
549 if( m_items->track->album() )
550 albumPixmap = m_items->track->album()->image( int( ALBUM_WIDTH ) );
552 //m_items->albumArt->hide();
553 //delete ( m_items->albumArt );
554 //m_items->albumArt = new QGraphicsPixmapItem( albumPixmap, this );
555 //m_items->albumArt->setPos( 0.0, MARGIN );
558 void Playlist::GraphicsItem::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
560 bool dragOverOriginalPosition = m_items->preDragLocation.contains( event->scenePos() );
561 if( dragOverOriginalPosition )
563 setPos( m_items->preDragLocation.topLeft() );
564 Playlist::DropVis::instance()->hide();
565 return;
568 Playlist::GraphicsItem *above = 0;
569 QList<QGraphicsItem*> collisions = scene()->items( event->scenePos() );
570 foreach( QGraphicsItem *i, collisions )
572 Playlist::GraphicsItem *c = dynamic_cast<Playlist::GraphicsItem *>( i );
573 if( c && c != this )
575 above = c;
576 break;
579 // if we've dropped ourself ontop of another item, then we need to shuffle the tracks below down
580 if( above )
582 setPos( above->pos() );
583 The::playlistView()->moveItem( this, above );
586 //make sure item resets its z value
587 setZValue( 1.0 );
588 Playlist::DropVis::instance()->hide();
591 void Playlist::GraphicsItem::setRow(int row)
593 m_currentRow = row;
595 const QModelIndex index = The::playlistModel()->index( m_currentRow, 0 );
597 //figure out our group state and set height accordingly
598 int currentGroupState = index.data( GroupRole ).toInt();
599 if ( currentGroupState != m_groupMode ) {
601 debug() << "Group changed for row " << row;
603 prepareGeometryChange();
606 m_groupMode = currentGroupState;
607 m_groupModeChanged = true;
609 switch ( m_groupMode ) {
611 case None:
612 m_height = qMax( ALBUM_WIDTH, s_fm->height() * 2 ) + 2 * MARGIN + 2;
613 break;
614 case Head:
615 m_height = qMax( ALBUM_WIDTH, s_fm->height() * 2 ) + MARGIN + s_fm->height();
616 break;
617 case Body:
618 m_height = s_fm->height()/*+ 2 * MARGIN*/;
619 break;
620 case End:
621 m_height = s_fm->height() + 6 /*+ 2 * MARGIN*/;
622 break;