1 /***************************************************************************
2 * copyright : (C) 2007 Ian Monroe <ian@monroe.nu>
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 **************************************************************************/
23 #include "PlaylistModel.h"
24 #include "PlaylistGraphicsItem.h"
25 #include "PlaylistGraphicsView.h"
26 #include "PlaylistGraphicsScene.h"
27 #include "PlaylistDropVis.h"
28 #include "TheInstances.h"
33 #include <QGraphicsItemAnimation>
34 #include <QModelIndex>
39 Playlist::GraphicsView
*Playlist::GraphicsView::s_instance
= 0;
41 Playlist::GraphicsView::GraphicsView( QWidget
*parent
)
42 : QGraphicsView( parent
)
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
);
57 Playlist::GraphicsView::setModel( Playlist::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() ) );
75 Playlist::GraphicsView::contextMenuEvent( QContextMenuEvent
*event
)
77 QPointF sceneClickPos
= mapToScene( event
->pos() );
78 QGraphicsItem
*topItem
= scene()->itemAt( sceneClickPos
);
82 Playlist::GraphicsItem
*item
= dynamic_cast<Playlist::GraphicsItem
*>( topItem
);
84 item
= dynamic_cast<Playlist::GraphicsItem
*>( topItem
->parentItem() );
85 if( !item
) // we've clicked on empty space
88 item
->setSelected( true );
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() );
127 Playlist::GraphicsView::showItemImage()
129 if( !m_contextMenuItem
)
131 m_contextMenuItem
->showImage();
132 m_contextMenuItem
= 0;
136 Playlist::GraphicsView::fetchItemImage()
138 if( !m_contextMenuItem
)
140 m_contextMenuItem
->fetchImage();
141 m_contextMenuItem
= 0;
145 Playlist::GraphicsView::unsetItemImage()
147 if( !m_contextMenuItem
)
149 m_contextMenuItem
->unsetImage();
150 m_contextMenuItem
= 0;
154 Playlist::GraphicsView::dragEnterEvent( QDragEnterEvent
*event
)
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();
168 QGraphicsView::dragEnterEvent( event
);
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();
183 QGraphicsView::dragMoveEvent( event
);
186 Playlist::GraphicsView::dragLeaveEvent( QDragLeaveEvent
*event
)
188 Playlist::DropVis::instance()->hide();
189 QGraphicsView::dragLeaveEvent( event
);
193 Playlist::GraphicsView::dropEvent( QDropEvent
*event
)
196 The::playlistModel()->dropMimeData( event
->mimeData(), Qt::CopyAction
, -1, 0, QModelIndex() );
197 Playlist::DropVis::instance()->hide();
201 Playlist::GraphicsView::keyPressEvent( QKeyEvent
* event
)
203 if( event
->matches( QKeySequence::Delete
) )
205 if( !scene()->selectedItems().isEmpty() )
212 QGraphicsView::keyPressEvent( event
);
216 Playlist::GraphicsView::playTrack()
218 QAction
*playAction
= dynamic_cast<QAction
*>( sender() );
222 QPointF sceneClickPos
= playAction
->data().toPointF();
223 Playlist::GraphicsItem
*item
= dynamic_cast<Playlist::GraphicsItem
*>( scene()->itemAt( sceneClickPos
)->parentItem() );
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
)
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
;
247 while( in
.data( GroupRole
).toInt() != End
)
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
);
262 Playlist::GraphicsView::rowsInserted( const QModelIndex
& parent
, int start
, int end
)
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.
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();
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 );
295 Playlist::GraphicsView::rowsRemoved(const QModelIndex
& parent
, int start
, int end
)
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() );
311 Playlist::GraphicsView::moveItem( Playlist::GraphicsItem
*moveMe
, Playlist::GraphicsItem
*above
)
313 int moveMeIndex
= m_tracks
.indexOf( moveMe
);
316 aboveIndex
= m_tracks
.indexOf( above
);
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 );
333 for ( i
= firstIndex
; i
< m_tracks
.count(); i
++ )
334 m_tracks
.at( i
)->setRow( i
);
337 //shuffleTracks( moveMeIndex, aboveIndex );
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);
358 Playlist::GraphicsView::shuffleTracks( int startPosition
, int stopPosition
)
362 debug() << "number if items: " << m_tracks
.count();
363 if( startPosition
< 0 )
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 ) )
398 if( desiredY
> currentY
)
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
) );
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
);
427 Playlist::GraphicsView::modelReset()
429 foreach( Playlist::GraphicsItem
* it
, m_tracks
)
437 Playlist::GraphicsView::dataChanged(const QModelIndex
& index
)
440 if( !index
.isValid() )
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!!
457 for ( i
= 0; i
< m_tracks
.count(); i
++ )
458 m_tracks
.at( i
)->setRow( i
);
461 shuffleTracks( 0, -1 );
466 Playlist::GraphicsView
* playlistView() { return Playlist::GraphicsView::instance(); }
469 #include "PlaylistGraphicsView.moc"