1 /***************************************************************************
2 * copyright : (C) 2007 Ian Monroe <ian@monroe.nu> *
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 ***************************************************************************/
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"
23 #include <QFontMetricsF>
24 #include <QGraphicsScene>
25 #include <QGraphicsTextItem>
26 #include <QGraphicsPixmapItem>
27 #include <QGraphicsRectItem>
28 #include <QGraphicsSceneMouseEvent>
29 #include <QGraphicsView>
32 #include <QPixmapCache>
33 #include <QRadialGradient>
35 #include <QStyleOptionGraphicsItem>
39 struct Playlist::GraphicsItem::ActiveItems
44 , bottomRightText( 0 )
51 delete bottomLeftText
;
52 delete bottomRightText
;
59 QGraphicsRectItem
* foreground
;
60 Playlist::TextItem
* bottomLeftText
;
61 Playlist::TextItem
* bottomRightText
;
62 Playlist::TextItem
* topLeftText
;
63 Playlist::TextItem
* topRightText
;
67 QRectF preDragLocation
;
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()
82 , m_groupModeChanged ( false )
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()
109 Playlist::GraphicsItem::paint( QPainter
* painter
, const QStyleOptionGraphicsItem
* option
, QWidget
* widget
)
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
)
126 const Meta::TrackPtr track
= index
.data( ItemRole
).value
< Playlist::Item
* >()->track();
127 m_items
= new Playlist::GraphicsItem::ActiveItems();
128 m_items
->track
= track
;
131 m_groupModeChanged
= false;
132 resize( m_items
->track
, option
->rect
.width() );
135 // paint item background:
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*/ );
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
);
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:
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();
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();
270 Playlist::GraphicsItem::init( Meta::TrackPtr track
)
274 font
.setPointSize( font
.pointSize() - 1 );
275 #define NewText( X ) \
276 X = new Playlist::TextItem( this ); \
277 X->setTextInteractionFlags( Qt::TextEditorInteraction ); \
279 NewText( m_items
->topLeftText
)
280 NewText( m_items
->bottomLeftText
)
281 NewText( m_items
->topRightText
)
282 NewText( m_items
->bottomRightText
)
287 Playlist::GraphicsItem::resize( Meta::TrackPtr track
, int totalWidth
)
289 if( totalWidth
== -1 /*|| totalWidth == m_items->lastWidth */) //no change needed
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() );
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
);
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
);
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();
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
);
368 m_items
->bottomLeftText
->setPos( MARGIN
* 3, 0 );
369 m_items
->bottomRightText
->setPos( bottomRightAlignX
, 0 );
372 m_items
->lastWidth
= totalWidth
;
376 Playlist::GraphicsItem::findArtistForCurrentAlbum() const
378 if( m_groupMode
!= Head
)
381 const QModelIndex index
= The::playlistModel()->index( m_currentRow
, 0 );
382 if( index
.data( GroupRole
).toInt() != Head
)
389 Meta::TrackPtr currentTrack
= index
.data( TrackRole
).value
< Meta::TrackPtr
>();
390 if( currentTrack
->artist() )
391 artist
= currentTrack
->artist()->name();
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
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() )
412 while( idx
.data( GroupRole
).toInt() == Body
);
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
);
426 Playlist::GraphicsItem::play()
428 The::playlistModel()->play( m_currentRow
);
432 Playlist::GraphicsItem::mouseDoubleClickEvent( QGraphicsSceneMouseEvent
*event
)
440 QGraphicsItem::mouseDoubleClickEvent( event
);
444 Playlist::GraphicsItem::mousePressEvent( QGraphicsSceneMouseEvent
*event
)
446 if( event
->buttons() & Qt::RightButton
|| !m_items
)
451 m_items
->preDragLocation
= mapToScene( boundingRect() ).boundingRect();
452 QGraphicsItem::mousePressEvent( event
);
455 // With help from QGraphicsView::mouseMoveEvent()
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 )
466 bool dragOverOriginalPosition
= m_items
->preDragLocation
.contains( scenePosition
);
468 //make sure item is drawn on top of other items
471 // Determine the list of selected items
472 QList
<QGraphicsItem
*> selectedItems
= scene()->selectedItems();
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;
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
);
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() );
509 Playlist::DropVis::instance()->show( above
);
515 QGraphicsItem::mouseMoveEvent( event
);
520 Playlist::GraphicsItem::dragEnterEvent( QGraphicsSceneDragDropEvent
*event
)
522 foreach( QString mime
, The::playlistModel()->mimeTypes() )
524 if( event
->mimeData()->hasFormat( mime
) )
527 Playlist::DropVis::instance()->show( this );
534 Playlist::GraphicsItem::dropEvent( QGraphicsSceneDragDropEvent
* event
)
538 The::playlistModel()->dropMimeData( event
->mimeData(), Qt::CopyAction
, m_currentRow
, 0, QModelIndex() );
539 Playlist::DropVis::instance()->hide();
543 Playlist::GraphicsItem::refresh()
546 if( !m_items
|| !m_items
->track
)
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();
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
);
579 // if we've dropped ourself ontop of another item, then we need to shuffle the tracks below down
582 setPos( above
->pos() );
583 The::playlistView()->moveItem( this, above
);
586 //make sure item resets its z value
588 Playlist::DropVis::instance()->hide();
591 void Playlist::GraphicsItem::setRow(int 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
) {
612 m_height
= qMax( ALBUM_WIDTH
, s_fm
->height() * 2 ) + 2 * MARGIN
+ 2;
615 m_height
= qMax( ALBUM_WIDTH
, s_fm
->height() * 2 ) + MARGIN
+ s_fm
->height();
618 m_height
= s_fm
->height()/*+ 2 * MARGIN*/;
621 m_height
= s_fm
->height() + 6 /*+ 2 * MARGIN*/;