Revert previous commit, was incorrect
[amarok.git] / src / playlist / PlaylistGraphicsView.cpp
blobfe0764da07bd29a329f8369634782b1b3676e6c5
1 /***************************************************************************
2 * copyright : (C) 2007 Ian Monroe <ian@monroe.nu>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of
7 * the License or (at your option) version 3 or any later version
8 * accepted by the membership of KDE e.V. (or its successor approved
9 * by the membership of KDE e.V.), which shall act as a proxy
10 * defined in Section 14 of version 3 of the license.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **************************************************************************/
21 #include "amarok.h"
22 #include "debug.h"
23 #include "PlaylistModel.h"
24 #include "PlaylistGraphicsItem.h"
25 #include "PlaylistGraphicsView.h"
26 #include "PlaylistGraphicsScene.h"
27 #include "PlaylistDropVis.h"
28 #include "TheInstances.h"
30 #include <KAction>
31 #include <KMenu>
33 #include <QGraphicsItemAnimation>
34 #include <QModelIndex>
35 #include <QKeyEvent>
36 #include <QTimeLine>
37 #include <QVariant>
39 Playlist::GraphicsView *Playlist::GraphicsView::s_instance = 0;
41 Playlist::GraphicsView::GraphicsView( QWidget *parent )
42 : QGraphicsView( parent )
43 , m_model( 0 )
44 , m_contextMenuItem( 0 )
46 setAcceptDrops( true );
47 setAlignment( Qt::AlignLeft | Qt::AlignTop );
48 setTransformationAnchor( QGraphicsView::AnchorUnderMouse );
50 setScene( new Playlist::GraphicsScene() );
51 scene()->addItem( Playlist::DropVis::instance() );
53 setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
56 void
57 Playlist::GraphicsView::setModel( Playlist::Model *model )
59 DEBUG_BLOCK
61 m_model = model;
63 rowsInserted( QModelIndex(), 0, m_model->rowCount() - 1);
65 connect( m_model, SIGNAL( modelReset() ), this, SLOT( modelReset() ) );
66 connect( m_model, SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), this, SLOT( rowsInserted( const QModelIndex &, int, int ) ) );
67 connect( m_model, SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), this, SLOT( rowsRemoved( const QModelIndex&, int, int ) ) );
68 connect( m_model, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( dataChanged( const QModelIndex& ) ) );
69 connect( m_model, SIGNAL( playlistGroupingChanged( ) ), this, SLOT( groupingChanged() ) );
71 show();
74 void
75 Playlist::GraphicsView::contextMenuEvent( QContextMenuEvent *event )
77 QPointF sceneClickPos = mapToScene( event->pos() );
78 QGraphicsItem *topItem = scene()->itemAt( sceneClickPos );
79 if( !topItem )
80 return;
82 Playlist::GraphicsItem *item = dynamic_cast<Playlist::GraphicsItem*>( topItem );
83 if( !item )
84 item = dynamic_cast<Playlist::GraphicsItem*>( topItem->parentItem() );
85 if( !item ) // we've clicked on empty space
86 return;
88 item->setSelected( true );
89 event->accept();
91 KAction *playAction = new KAction( KIcon( Amarok::icon( "play" ) ), i18n( "&Play" ), this );
92 playAction->setData( QVariant( sceneClickPos ) );
93 connect( playAction, SIGNAL( triggered() ), this, SLOT( playTrack() ) );
95 KMenu *menu = new KMenu( this );
97 menu->addAction( playAction );
98 ( menu->addAction( i18n( "Queue Track" ), this, SLOT( queueItem() ) ) )->setEnabled( false );
99 ( menu->addAction( i18n( "Stop Playing After Track" ), this, SLOT( stopAfterTrack() ) ) )->setEnabled( false );
100 menu->addSeparator();
101 ( menu->addAction( i18n( "Remove From Playlist" ), this, SLOT( removeSelection() ) ) )->setEnabled( false );
102 menu->addSeparator();
103 ( menu->addAction( i18n( "Edit Track Information" ), this, SLOT( editTrackInformation() ) ) )->setEnabled( false );
104 menu->addSeparator();
106 QPointF itemClickPos = item->mapFromScene( sceneClickPos );
107 if( item->groupMode() < Playlist::Body && item->imageLocation().contains( itemClickPos ) )
109 bool hasCover = item->hasImage();
111 QAction *showCoverAction = menu->addAction( i18n( "Show Fullsize" ), this, SLOT( showItemImage() ) );
112 QAction *fetchCoverAction = menu->addAction( i18n( "Fetch Cover" ), this, SLOT( fetchItemImage() ) );
113 QAction *unsetCoverAction = menu->addAction( i18n( "Unset Cover" ), this, SLOT( unsetItemImage() ) );
115 showCoverAction->setEnabled( hasCover );
116 fetchCoverAction->setEnabled( true );
117 unsetCoverAction->setEnabled( hasCover );
121 m_contextMenuItem = item;
123 menu->exec( event->globalPos() );
126 void
127 Playlist::GraphicsView::showItemImage()
129 if( !m_contextMenuItem )
130 return;
131 m_contextMenuItem->showImage();
132 m_contextMenuItem = 0;
135 void
136 Playlist::GraphicsView::fetchItemImage()
138 if( !m_contextMenuItem )
139 return;
140 m_contextMenuItem->fetchImage();
141 m_contextMenuItem = 0;
144 void
145 Playlist::GraphicsView::unsetItemImage()
147 if( !m_contextMenuItem )
148 return;
149 m_contextMenuItem->unsetImage();
150 m_contextMenuItem = 0;
153 void
154 Playlist::GraphicsView::dragEnterEvent( QDragEnterEvent *event )
156 DEBUG_BLOCK
157 event->accept();
158 foreach( QString mime, The::playlistModel()->mimeTypes() )
160 if( event->mimeData()->hasFormat( mime ) )
162 QGraphicsView::dragEnterEvent( event );
163 event->acceptProposedAction();
164 Playlist::DropVis::instance()->show();
165 return;
168 QGraphicsView::dragEnterEvent( event );
170 void
171 Playlist::GraphicsView::dragMoveEvent( QDragMoveEvent *event )
173 foreach( QString mime, The::playlistModel()->mimeTypes() )
175 if( event->mimeData()->hasFormat( mime ) )
177 QGraphicsView::dragMoveEvent( event );
178 Playlist::DropVis::instance()->show();
179 event->acceptProposedAction();
180 return;
183 QGraphicsView::dragMoveEvent( event );
185 void
186 Playlist::GraphicsView::dragLeaveEvent( QDragLeaveEvent *event )
188 Playlist::DropVis::instance()->hide();
189 QGraphicsView::dragLeaveEvent( event );
192 void
193 Playlist::GraphicsView::dropEvent( QDropEvent *event )
195 event->accept();
196 The::playlistModel()->dropMimeData( event->mimeData(), Qt::CopyAction, -1, 0, QModelIndex() );
197 Playlist::DropVis::instance()->hide();
200 void
201 Playlist::GraphicsView::keyPressEvent( QKeyEvent* event )
203 if( event->matches( QKeySequence::Delete ) )
205 if( !scene()->selectedItems().isEmpty() )
207 event->accept();
208 removeSelection();
209 return;
212 QGraphicsView::keyPressEvent( event );
215 void
216 Playlist::GraphicsView::playTrack()
218 QAction *playAction = dynamic_cast<QAction*>( sender() );
219 if( !playAction )
220 return;
222 QPointF sceneClickPos = playAction->data().toPointF();
223 Playlist::GraphicsItem *item = dynamic_cast<Playlist::GraphicsItem*>( scene()->itemAt( sceneClickPos )->parentItem() );
224 if( !item )
225 return;
226 item->play();
229 void
230 Playlist::GraphicsView::removeSelection()
232 QList<QGraphicsItem*> selection = scene()->selectedItems();
234 int firstIndex = m_tracks.indexOf( static_cast<Playlist::GraphicsItem*>( selection.first() ) );
235 if ( firstIndex > 0) firstIndex -= 1;
237 foreach( QGraphicsItem *i, selection )
239 int count = 1;
240 int index = m_tracks.indexOf( static_cast<Playlist::GraphicsItem*>(i) );
241 QModelIndex modelIndex = The::playlistModel()->index( index, 0 );
242 QModelIndex nextIndex = The::playlistModel()->index( index+1 , 0 );
243 if( modelIndex.data( GroupRole ).toInt() == Head && nextIndex.data( GroupRole ).toInt() != Head )
245 QModelIndex in = modelIndex;
246 int i = index;
247 while( in.data( GroupRole ).toInt() != End )
249 ++count;
250 in = The::playlistModel()->index( i++, 0 );
253 count = modelIndex.data( GroupRole ).toInt() == Head ? count - 1 : count;
254 m_model->removeRows( index, count );
257 for ( int i = firstIndex ; i < m_tracks.count(); i++ )
258 m_tracks.at( i )->setRow( i );
261 void
262 Playlist::GraphicsView::rowsInserted( const QModelIndex& parent, int start, int end )
264 Q_UNUSED( parent );
266 //call setRow on track imidiately preceding the insertion as this might have to change its
267 // look and height if it has been grouped by the model.
268 if ( start > 0 )
269 m_tracks[ start-1]->setRow( start-1 );
271 double cumulativeHeight = 0;
272 for ( int j = 0; j < start; j++ )
273 cumulativeHeight += m_tracks.at( j )->boundingRect().height();
275 debug() << "start: " << start << " ,end: " << end;
276 for( int i = start; i <= end; i++ )
279 Playlist::GraphicsItem* item = new Playlist::GraphicsItem();
280 item->setRow( i );
281 item->setPos( 0.0, cumulativeHeight );
282 cumulativeHeight += item->boundingRect().height();
283 scene()->addItem( item );
284 m_tracks.insert( i, item );
287 // make sure all following tracks has their colors updated correctly
288 for ( int i = end + 1 ; i < m_tracks.count(); i++ )
289 m_tracks.at( i )->setRow( i );
291 shuffleTracks( end + 1 );
294 void
295 Playlist::GraphicsView::rowsRemoved(const QModelIndex& parent, int start, int end )
297 DEBUG_BLOCK
298 Q_UNUSED( parent );
299 for( int i = end; i >= start; i-- )
300 delete m_tracks.takeAt( i );
302 // make sure all following tracks has their colors updated correctly
303 for ( int i = start; i < m_tracks.count(); i++ )
304 m_tracks.at( i )->setRow( i );
306 shuffleTracks( start );
307 scene()->setSceneRect( scene()->itemsBoundingRect() );
310 void
311 Playlist::GraphicsView::moveItem( Playlist::GraphicsItem *moveMe, Playlist::GraphicsItem *above )
313 int moveMeIndex = m_tracks.indexOf( moveMe );
314 int aboveIndex;
315 if ( above )
316 aboveIndex = m_tracks.indexOf( above );
317 else
318 aboveIndex = m_tracks.count();
320 //call set row on all items below the first one potentially modified to
321 //make sure that all items have correct background color and group info
323 int firstIndex = QMIN ( aboveIndex, moveMeIndex ) -1;
324 if ( firstIndex < 0 ) firstIndex = 0;
326 if( moveMeIndex < aboveIndex )
328 m_model->moveRow( moveMeIndex, aboveIndex -1 );
329 m_tracks.move( moveMeIndex, aboveIndex - 1 );
332 int i;
333 for ( i = firstIndex; i < m_tracks.count(); i++ )
334 m_tracks.at( i )->setRow( i );
337 //shuffleTracks( moveMeIndex, aboveIndex );
338 shuffleTracks( 0 );
340 else
342 m_model->moveRow( moveMeIndex, aboveIndex );
343 m_tracks.move( moveMeIndex, aboveIndex );
345 debug() << "First index: " << firstIndex;
347 for ( int i = firstIndex; i < m_tracks.count(); i++ )
348 m_tracks.at( i )->setRow( i );
350 //shuffleTracks( aboveIndex, moveMeIndex + 1);
351 shuffleTracks( 0 );
357 void
358 Playlist::GraphicsView::shuffleTracks( int startPosition, int stopPosition )
360 DEBUG_BLOCK
362 debug() << "number if items: " << m_tracks.count();
363 if( startPosition < 0 )
364 return;
366 if( stopPosition < 0 || stopPosition > m_tracks.size() )
367 stopPosition = m_tracks.size();
369 QTimeLine *timer = new QTimeLine( 300 ); // 0.3 second duration
370 timer->setCurveShape( QTimeLine::EaseInCurve );
371 timer->setUpdateInterval( 30 ); // make sure that there is no leftover time
372 //that results in items not moving all the way
374 double cumulativeHeight = 0;
376 for ( int j = 0; j < startPosition; j++ )
377 cumulativeHeight += m_tracks.at( j )->boundingRect().height();
379 for( int i = startPosition; i < stopPosition; ++i )
381 Playlist::GraphicsItem *item = m_tracks.at( i );
382 qreal currentY = item->pos().y();
384 qreal desiredY = cumulativeHeight;
386 double itemHeight = item->boundingRect().height();
387 cumulativeHeight += itemHeight;
389 double visibleTop = mapToScene( 0,0 ).y();
390 double visibleBottom = mapToScene( 0, height() ).y();
392 // Animate the repositioning of the item if it is within the viewable area
393 if ( !( ( desiredY < visibleTop ) || ( desiredY > visibleBottom ) ) &&
394 ( ( currentY >= visibleTop ) && ( currentY <= visibleBottom ) ) &&
395 ( itemHeight != 0 ) )
397 bool moveUp = false;
398 if( desiredY > currentY )
399 moveUp = true;
401 qreal distanceMoved = moveUp ? ( desiredY - currentY ) : ( currentY - desiredY );
403 QGraphicsItemAnimation *animator = new QGraphicsItemAnimation;
404 animator->setItem( item );
405 animator->setTimeLine( timer );
407 // if distanceMoved is negative, then we are moving the object towards the bottom of the screen
408 for( qreal i = 0; i < distanceMoved; ++i )
410 qreal newY = moveUp ? ( currentY + i ) : ( currentY - i );
411 animator->setPosAt( i / distanceMoved, QPointF( 0.0, newY ) );
413 animator->setPosAt( 1, QPointF( 0.0, desiredY ) );
415 else
417 //don't animate items if both currentY and desiredY are outside the visible area!
418 //We still do need to update their position though
419 m_tracks.at( i )->setPos( 0.0, desiredY );
423 timer->start();
426 void
427 Playlist::GraphicsView::modelReset()
429 foreach( Playlist::GraphicsItem* it, m_tracks )
431 delete it;
433 m_tracks.clear();
436 void
437 Playlist::GraphicsView::dataChanged(const QModelIndex & index)
439 DEBUG_BLOCK
440 if( !index.isValid() )
441 return;
443 if( m_tracks.count() > index.row() )
445 debug() << "Refreshing item...";
446 m_tracks.at( index.row() )->dataChanged();
447 m_tracks.at( index.row() )->update();
451 void Playlist::GraphicsView::groupingChanged()
453 // ouch!!! this is expensive!!
454 //DEBUG_BLOCK
456 int i;
457 for ( i = 0; i < m_tracks.count(); i++ )
458 m_tracks.at( i )->setRow( i );
461 shuffleTracks( 0, -1 );
462 // update();
465 namespace The {
466 Playlist::GraphicsView* playlistView() { return Playlist::GraphicsView::instance(); }
469 #include "PlaylistGraphicsView.moc"