diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index 7148a8c2d..1008794df 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -26,8 +26,10 @@ DynamicModel::DynamicModel( QObject* parent ) , m_startOnResolved( false ) , m_onDemandRunning( false ) , m_currentAttempts( 0 ) + , m_lastResolvedRow( 0 ) { - + + connect( AudioEngine::instance(), SIGNAL( loading( Tomahawk::result_ptr ) ), this, SLOT( newTrackLoading() ) ); } DynamicModel::~DynamicModel() @@ -38,6 +40,9 @@ DynamicModel::~DynamicModel() void DynamicModel::loadPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) { + if( !m_playlist.isNull() ) { + disconnect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( newTrackGenerated( Tomahawk::query_ptr ) ) ); + } m_playlist = playlist; @@ -50,7 +55,6 @@ DynamicModel::startOnDemand() { m_playlist->generator()->startOnDemand(); - connect( AudioEngine::instance(), SIGNAL( loading( Tomahawk::result_ptr ) ), this, SLOT( newTrackLoading() ) ); m_onDemandRunning = true; m_startOnResolved = true; } @@ -79,21 +83,31 @@ DynamicModel::stopOnDemand() void DynamicModel::trackResolved() { - m_currentAttempts = 0; - + Query* q = qobject_cast(sender()); + qDebug() << "Got successful resolved track:" << q->track() << q->artist() << m_lastResolvedRow << m_currentAttempts; if( m_startOnResolved ) { // on first start m_startOnResolved = false; AudioEngine::instance()->play(); } + + if( m_currentAttempts > 0 ) { + qDebug() << "EMITTING AN ASK FOR COLLAPSE:" << m_lastResolvedRow << m_currentAttempts; + emit collapseFromTo( m_lastResolvedRow, m_currentAttempts ); + } + m_currentAttempts = 0; } void DynamicModel::trackResolveFinished( bool success ) { if( !success ) { // if it was successful, we've already gotten a trackResolved() signal + Query* q = qobject_cast(sender()); + qDebug() << "Got not resolved track:" << q->track() << q->artist() << m_lastResolvedRow << m_currentAttempts; m_currentAttempts++; - if( m_currentAttempts < 100 ) { + if( m_currentAttempts < 30 ) { m_playlist->generator()->fetchNext(); + } else { + // TODO handle failure } } } @@ -103,6 +117,21 @@ void DynamicModel::newTrackLoading() { if( m_onDemandRunning && m_currentAttempts == 0 ) { // if we're in dynamic mode and we're also currently idle + m_lastResolvedRow = rowCount( QModelIndex() ); m_playlist->generator()->fetchNext(); } } + +void +DynamicModel::removeIndex(const QModelIndex& index, bool moreToCome) +{ + if ( isReadOnly() ) + return; + + if( m_playlist->mode() == OnDemand ) + TrackModel::removeIndex( index ); + // don't call onPlaylistChanged. + + if( !moreToCome ) + m_lastResolvedRow = rowCount( QModelIndex() ); +} diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.h b/src/libtomahawk/playlist/dynamic/DynamicModel.h index 7168bfa43..3259131ef 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.h +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.h @@ -40,7 +40,11 @@ public: void stopOnDemand(); void loadPlaylist( const dynplaylist_ptr& playlist ); - + + virtual void removeIndex( const QModelIndex& index, bool moreToCome = false ); +signals: + void collapseFromTo( int startRow, int num ); + private slots: void newTrackGenerated( const Tomahawk::query_ptr& query ); @@ -53,6 +57,7 @@ private: bool m_startOnResolved; bool m_onDemandRunning; int m_currentAttempts; + int m_lastResolvedRow; }; }; diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.cpp b/src/libtomahawk/playlist/dynamic/DynamicView.cpp index 1e1496542..63e1e2d17 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicView.cpp @@ -18,16 +18,35 @@ #include "widgets/overlaywidget.h" #include "playlistmodel.h" +#include "trackproxymodel.h" #include +#include +#include using namespace Tomahawk; +#define FADE_LENGTH 800 +#define SLIDE_LENGTH 300 +#define SLIDE_OFFSET 500 +#define LONG_MULT 0.4 // to avoid superfast slides when the length is long, make it longer incrementally DynamicView::DynamicView( QWidget* parent ) : PlaylistView( parent ) , m_onDemand( false ) { + m_fadeOutAnim.setDuration( FADE_LENGTH ); + m_fadeOutAnim.setCurveShape( QTimeLine::LinearCurve ); + m_fadeOutAnim.setFrameRange( 100, 0 ); + m_fadeOutAnim.setUpdateInterval( 10 ); + QEasingCurve curve( QEasingCurve::OutBounce ); + curve.setAmplitude( .2 ); + m_slideAnim.setEasingCurve( curve ); + m_slideAnim.setDirection( QTimeLine::Forward ); + m_fadeOutAnim.setUpdateInterval( 10 ); + + + connect( &m_fadeOutAnim, SIGNAL( frameChanged( int ) ), viewport(), SLOT( update() ) ); } DynamicView::~DynamicView() @@ -74,3 +93,77 @@ DynamicView::onTrackCountChanged( unsigned int tracks ) else overlay()->hide(); } + +void +DynamicView::collapseEntries( int startRow, int num ) +{ + if( m_fadeOutAnim.state() == QTimeLine::Running ) + qDebug() << "COLLAPSING TWICE!"; + // we capture the image of the rows we're going to collapse + // then we capture the image of the target row we're going to animate downwards + // then we fade the first image out while sliding the second image up. + QModelIndex topLeft = proxyModel()->index( startRow, 0, QModelIndex() ); + QModelIndex bottomRight = proxyModel()->index( startRow + num - 1, proxyModel()->columnCount( QModelIndex() ) - 1, QModelIndex() ); + QItemSelection sel( topLeft, bottomRight ); + QRect fadingRect = visualRegionForSelection( sel ).boundingRect(); + + m_fadingIndexes = QPixmap::grabWidget( viewport(), fadingRect ); + m_fadingPointAnchor = fadingRect.topLeft(); + + qDebug() << "Grabbed fading indexes from rect:" << fadingRect << m_fadingIndexes.size(); + + topLeft = proxyModel()->index( startRow + num, 0, QModelIndex() ); + bottomRight = proxyModel()->index( startRow + num, proxyModel()->columnCount( QModelIndex() ) - 1, QModelIndex() ); + QRect slidingRect = visualRegionForSelection( QItemSelection( topLeft, bottomRight ) ).boundingRect(); + + m_slidingIndex = QPixmap::grabWidget( viewport(), slidingRect ); + m_bottomAnchor = slidingRect.topLeft(); + qDebug() << "Grabbed sliding index from rect:" << slidingRect << m_slidingIndex.size(); + + // slide from the current position to the new one + int frameRange = fadingRect.topLeft().y() - slidingRect.topLeft().y(); + m_slideAnim.setDuration( SLIDE_LENGTH + frameRange * LONG_MULT ); + m_slideAnim.setFrameRange( slidingRect.topLeft().y(), fadingRect.topLeft().y() ); + + m_fadeOutAnim.start(); + QTimer::singleShot( SLIDE_OFFSET, &m_slideAnim, SLOT( start() ) ); + + QModelIndexList todel; + for( int i = 0; i < num; i++ ) { + for( int k = 0; k < proxyModel()->columnCount( QModelIndex() ); k++ ) { + todel << proxyModel()->index( startRow + i, k ); + } + } + proxyModel()->removeIndexes( todel ); +} + +void +DynamicView::paintEvent( QPaintEvent* event ) +{ + TrackView::paintEvent(event); + + QPainter p( viewport() ); + if( m_fadeOutAnim.state() == QTimeLine::Running ) { // both run together + p.save(); + QRect bg = m_fadingIndexes.rect(); + bg.moveTo( m_fadingPointAnchor ); // cover up the background + p.fillRect( bg, Qt::white ); + +// qDebug() << "FAST SETOPACITY:" << p.paintEngine()->hasFeature(QPaintEngine::ConstantOpacity); + p.setOpacity( m_fadeOutAnim.currentFrame() ); + p.drawPixmap( m_fadingPointAnchor, m_fadingIndexes ); + + p.restore(); + + if( m_slideAnim.state() == QTimeLine::Running ) { + // draw the collapsing entry + QRect bg = m_slidingIndex.rect(); + bg.moveTo( m_bottomAnchor ); + p.fillRect( bg, Qt::white ); + p.drawPixmap( 0, m_slideAnim.currentFrame(), m_slidingIndex ); + } else if( m_fadeOutAnim.state() == QTimeLine::Running ) { + p.drawPixmap( m_bottomAnchor, m_slidingIndex ); + } + } +} + diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.h b/src/libtomahawk/playlist/dynamic/DynamicView.h index 94d4d0ba1..421f86a5a 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.h +++ b/src/libtomahawk/playlist/dynamic/DynamicView.h @@ -20,6 +20,7 @@ #include "playlist/playlistview.h" #include #include +#include class PlaylistModel; class TrackModel; @@ -36,20 +37,33 @@ public: virtual void setModel( PlaylistModel* model ); void setOnDemand( bool onDemand ); + + virtual void paintEvent(QPaintEvent* event); + public slots: void showMessageTimeout( const QString& title, const QString& body ); + // collapse and animate the transition + // there MUST be a row *after* startRow + num. that is, you can't collapse + // entries unless there is at least one entry after the last collapsed row + void collapseEntries( int startRow, int num ); + private slots: void onTrackCountChanged( unsigned int ); private: - QTimer m_showTimer; - QPropertyAnimation* m_fadeOut; - QString m_title; QString m_body; bool m_onDemand; + + // for collapsing animation + QPoint m_fadingPointAnchor; + QPoint m_bottomAnchor; + QPixmap m_fadingIndexes; + QPixmap m_slidingIndex; + QTimeLine m_fadeOutAnim; + QTimeLine m_slideAnim; }; }; diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index 9b5afa5dc..1bdf999a6 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -90,7 +90,7 @@ DynamicWidget::DynamicWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget m_view->setModel( m_model ); m_view->setContentsMargins( 0, 0, 0, 0 ); m_layout->addWidget( m_view, 1 ); - + connect( m_model, SIGNAL( collapseFromTo( int, int ) ), m_view, SLOT( collapseEntries( int, int ) ), Qt::QueuedConnection ); loadDynamicPlaylist( playlist );