From db07be002d01a69841229dea74504e70cb023cc0 Mon Sep 17 00:00:00 2001
From: Leo Franchi <lfranchi@kde.org>
Date: Fri, 20 Apr 2012 18:21:20 -0400
Subject: [PATCH] Initial work on spinners for resolver download and new
 chasewidget

---
 resources.qrc                                 |   1 +
 src/AccountDelegate.cpp                       |  42 +++-
 src/AccountDelegate.h                         |   6 +-
 src/libtomahawk/CMakeLists.txt                |   1 +
 .../accounts/AccountModelFilterProxy.cpp      |  18 +-
 .../accounts/AccountModelFilterProxy.h        |   3 +
 src/libtomahawk/playlist/trackview.cpp        |   5 +-
 src/libtomahawk/playlist/trackview.h          |   7 +-
 src/libtomahawk/utils/chasewidget.cpp         | 203 ++++++++++++++++++
 src/libtomahawk/utils/chasewidget.h           |  98 +++++++++
 src/libtomahawk/widgets/searchwidget.cpp      |   3 +-
 11 files changed, 377 insertions(+), 10 deletions(-)
 create mode 100644 src/libtomahawk/utils/chasewidget.cpp
 create mode 100644 src/libtomahawk/utils/chasewidget.h

diff --git a/resources.qrc b/resources.qrc
index 602774165..575b389c9 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -143,5 +143,6 @@
         <file>data/images/process-stop.png</file>
         <file>data/icons/tomahawk-icon-128x128-grayscale.png</file>
         <file>data/images/collection.png</file>
+	<file>data/images/loading-animation-dark.gif</file>
     </qresource>
 </RCC>
diff --git a/src/AccountDelegate.cpp b/src/AccountDelegate.cpp
index 5e9e94ad0..4362d2519 100644
--- a/src/AccountDelegate.cpp
+++ b/src/AccountDelegate.cpp
@@ -27,6 +27,8 @@
 
 #include "utils/tomahawkutils.h"
 #include "utils/logger.h"
+#include "utils/chasewidget.h"
+#include "utils/closure.h"
 
 #define CHILD_ACCOUNT_HEIGHT 24
 
@@ -158,7 +160,23 @@ AccountDelegate::paint ( QPainter* painter, const QStyleOptionViewItem& option,
     QRect checkRect = QRect( leftEdge, checkboxYPos, WRENCH_SIZE, WRENCH_SIZE );
     QStyleOptionViewItemV4 opt2 = opt;
     opt2.rect = checkRect;
-    drawCheckBox( opt2, painter, opt.widget );
+
+    if ( !m_loadingSpinners.contains( index ) )
+    {
+        drawCheckBox( opt2, painter, opt.widget );
+    }
+    else
+    {
+        Q_ASSERT( m_loadingSpinners[ index ] );
+        if ( m_loadingSpinners[ index ] )
+        {
+            painter->setOpacity( 1.0 );
+            const QPixmap pm = QPixmap::grabWidget( m_loadingSpinners[ index ] );
+            painter->drawPixmap( checkRect.adjusted( -2, -2, 2, 2 ), pm );
+        }
+    }
+
+
     leftEdge += WRENCH_SIZE + PADDING / 2;
 
     // Pixmap
@@ -183,6 +201,7 @@ AccountDelegate::paint ( QPainter* painter, const QStyleOptionViewItem& option,
         topt.pos = confRect.topLeft();
 
         drawConfigWrench( painter, opt, topt );
+
         m_cachedConfigRects[ index ] = confRect;
         rightEdge = confRect.left();
 
@@ -662,6 +681,13 @@ void
 AccountDelegate::startInstalling( const QPersistentModelIndex& idx )
 {
     qDebug() << "START INSTALLING:" << idx.data( Qt::DisplayRole ).toString();
+    ChaseWidget* anim = new ChaseWidget( QApplication::topLevelWidgets().first() );
+    _detail::Closure* closure = NewClosure( anim, SIGNAL( requestUpdate() ), this, SLOT( doUpdateIndex( const QPersistentModelIndex& ) ), idx );
+    closure->setAutoDelete( false );
+
+    m_loadingSpinners[ idx ] = anim;
+
+    update( idx );
 }
 
 
@@ -669,5 +695,19 @@ void
 AccountDelegate::doneInstalling ( const QPersistentModelIndex& idx )
 {
     qDebug() << "STOP INSTALLING:" << idx.data( Qt::DisplayRole ).toString();
+    Q_ASSERT( m_loadingSpinners.contains( idx ) );
+    if ( !m_loadingSpinners.contains( idx ) )
+        return;
 
+    delete m_loadingSpinners.take( idx );
+
+    update( idx );
 }
+
+
+void
+AccountDelegate::doUpdateIndex( const QPersistentModelIndex& idx )
+{
+    emit update( idx );
+}
+
diff --git a/src/AccountDelegate.h b/src/AccountDelegate.h
index cb2e37cc9..f6a4a873e 100644
--- a/src/AccountDelegate.h
+++ b/src/AccountDelegate.h
@@ -22,6 +22,8 @@
 #include <QStyledItemDelegate>
 #include "accounts/AccountModel.h"
 
+class ChaseWidget;
+
 namespace Tomahawk
 {
 namespace Accounts
@@ -42,6 +44,8 @@ public slots:
     void startInstalling( const QPersistentModelIndex& idx );
     void doneInstalling ( const QPersistentModelIndex& idx );
 
+    void doUpdateIndex( const QPersistentModelIndex& idx );
+
 protected:
     virtual bool editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index );
 
@@ -68,7 +72,7 @@ private:
     mutable QHash< QPersistentModelIndex, QRect > m_cachedStarRects;
     mutable QHash< QPersistentModelIndex, QRect > m_cachedConfigRects;
     mutable QHash< QPersistentModelIndex, QSize > m_sizeHints;
-    mutable QHash< QPersistentModelIndex, QMovie* > m_loadingSpinners;
+    mutable QHash< QPersistentModelIndex, ChaseWidget* > m_loadingSpinners;
     mutable int m_accountRowHeight;
 };
 
diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt
index 230d4a792..0ad71153f 100644
--- a/src/libtomahawk/CMakeLists.txt
+++ b/src/libtomahawk/CMakeLists.txt
@@ -122,6 +122,7 @@ set( libGuiSources
     utils/closure.cpp
     utils/PixmapDelegateFader.cpp
     utils/SmartPointerList.h
+    utils/chasewidget.cpp
 
     widgets/animatedcounterlabel.cpp
     widgets/checkdirtree.cpp
diff --git a/src/libtomahawk/accounts/AccountModelFilterProxy.cpp b/src/libtomahawk/accounts/AccountModelFilterProxy.cpp
index 5aa005e6c..1a559faed 100644
--- a/src/libtomahawk/accounts/AccountModelFilterProxy.cpp
+++ b/src/libtomahawk/accounts/AccountModelFilterProxy.cpp
@@ -36,8 +36,8 @@ void
 AccountModelFilterProxy::setSourceModel( QAbstractItemModel* sourceModel )
 {
     connect( sourceModel, SIGNAL( scrollTo( QModelIndex ) ), this, SLOT( onScrollTo( QModelIndex ) ) );
-    connect( sourceModel, SIGNAL( startInstalling( QPersistentModelIndex ) ), this, SIGNAL( startInstalling( QPersistentModelIndex ) ) );
-    connect( sourceModel, SIGNAL( doneInstalling( QPersistentModelIndex ) ), this, SIGNAL( doneInstalling( QPersistentModelIndex ) ) );
+    connect( sourceModel, SIGNAL( startInstalling( QPersistentModelIndex ) ), this, SLOT( onStartInstalling( QPersistentModelIndex ) ) );
+    connect( sourceModel, SIGNAL( doneInstalling( QPersistentModelIndex ) ), this, SLOT( onDoneInstalling( QPersistentModelIndex ) ) );
     QSortFilterProxyModel::setSourceModel( sourceModel );
 }
 
@@ -72,3 +72,17 @@ AccountModelFilterProxy::onScrollTo( const QModelIndex& idx )
 {
     emit scrollTo( mapFromSource( idx ) );
 }
+
+
+void
+AccountModelFilterProxy::onDoneInstalling( const QPersistentModelIndex& idx )
+{
+    emit doneInstalling( mapFromSource( idx ) );
+}
+
+
+void
+AccountModelFilterProxy::onStartInstalling( const QPersistentModelIndex& idx )
+{
+    emit startInstalling( mapFromSource( idx ) );
+}
diff --git a/src/libtomahawk/accounts/AccountModelFilterProxy.h b/src/libtomahawk/accounts/AccountModelFilterProxy.h
index 527a8b067..73c1a410c 100644
--- a/src/libtomahawk/accounts/AccountModelFilterProxy.h
+++ b/src/libtomahawk/accounts/AccountModelFilterProxy.h
@@ -49,6 +49,9 @@ protected:
 private slots:
     void onScrollTo( const QModelIndex& idx );
 
+    void onStartInstalling( const QPersistentModelIndex& idx );
+    void onDoneInstalling( const QPersistentModelIndex& idx );
+
 private:
     Tomahawk::Accounts::AccountType m_filterType;
 };
diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp
index 7939596b1..20410cad3 100644
--- a/src/libtomahawk/playlist/trackview.cpp
+++ b/src/libtomahawk/playlist/trackview.cpp
@@ -31,6 +31,7 @@
 #include "context/ContextWidget.h"
 #include "widgets/overlaywidget.h"
 #include "dynamic/widgets/LoadingSpinner.h"
+#include "utils/chasewidget.h"
 #include "utils/tomahawkutils.h"
 #include "utils/logger.h"
 #include "utils/closure.h"
@@ -50,7 +51,7 @@ TrackView::TrackView( QWidget* parent )
     , m_delegate( 0 )
     , m_header( new TrackHeader( this ) )
     , m_overlay( new OverlayWidget( this ) )
-    , m_loadingSpinner( new LoadingSpinner( this ) )
+    , m_loadingSpinner( new ChaseWidget( this ) )
     , m_resizing( false )
     , m_dragging( false )
     , m_updateContextView( true )
@@ -157,7 +158,7 @@ TrackView::setTrackModel( TrackModel* model )
             setHeaderHidden( true );
             setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
         break;
-            
+
         default:
             setHeaderHidden( false );
             setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
diff --git a/src/libtomahawk/playlist/trackview.h b/src/libtomahawk/playlist/trackview.h
index 4b1b4e527..f2ac5d389 100644
--- a/src/libtomahawk/playlist/trackview.h
+++ b/src/libtomahawk/playlist/trackview.h
@@ -31,6 +31,7 @@
 
 class QAction;
 class LoadingSpinner;
+class ChaseWidget;
 class TrackHeader;
 class TrackModel;
 class TrackProxyModel;
@@ -57,7 +58,7 @@ public:
     TrackHeader* header() const { return m_header; }
     OverlayWidget* overlay() const { return m_overlay; }
     Tomahawk::ContextMenu* contextMenu() const { return m_contextMenu; }
-    LoadingSpinner* loadingSpinner() const { return m_loadingSpinner; }
+    ChaseWidget* loadingSpinner() const { return m_loadingSpinner; }
 
     QModelIndex hoveredIndex() const { return m_hoveredIndex; }
     QModelIndex contextMenuIndex() const { return m_contextMenuIndex; }
@@ -119,7 +120,7 @@ private:
     PlaylistItemDelegate* m_delegate;
     TrackHeader* m_header;
     OverlayWidget* m_overlay;
-    LoadingSpinner* m_loadingSpinner;
+    ChaseWidget* m_loadingSpinner;
 
     bool m_resizing;
     bool m_dragging;
@@ -133,7 +134,7 @@ private:
     Tomahawk::query_ptr m_autoPlaying;
 
     Tomahawk::ContextMenu* m_contextMenu;
-    
+
     QTimer m_timer;
 };
 
diff --git a/src/libtomahawk/utils/chasewidget.cpp b/src/libtomahawk/utils/chasewidget.cpp
new file mode 100644
index 000000000..8acb66736
--- /dev/null
+++ b/src/libtomahawk/utils/chasewidget.cpp
@@ -0,0 +1,203 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "chasewidget.h"
+
+#include <QtCore/QPoint>
+#include <QTimeLine>
+
+#include <QtGui/QApplication>
+#include <QtGui/QHideEvent>
+#include <QtGui/QPainter>
+#include <QtGui/QPaintEvent>
+#include <QtGui/QShowEvent>
+
+ChaseWidget::ChaseWidget(QWidget *parent, QPixmap pixmap, bool pixmapEnabled)
+    : QWidget(parent)
+    , m_segment(0)
+    , m_delay(100)
+    , m_step(40)
+    , m_timerId(-1)
+    , m_animated(false)
+    , m_pixmap(pixmap)
+    , m_pixmapEnabled(pixmapEnabled)
+    , m_showHide( new QTimeLine )
+{
+    m_showHide->setDuration( 300 );
+    m_showHide->setStartFrame( 0 );
+    m_showHide->setEndFrame( 100 );
+    m_showHide->setUpdateInterval( 20 );
+    connect( m_showHide, SIGNAL( frameChanged( int ) ), this, SLOT( update() ) );
+    connect( m_showHide, SIGNAL( finished() ), this, SLOT( hideFinished() ) );
+
+    hide();
+
+}
+
+void ChaseWidget::setAnimated(bool value)
+{
+    if (m_animated == value)
+        return;
+    m_animated = value;
+    if (m_timerId != -1) {
+        killTimer(m_timerId);
+        m_timerId = -1;
+    }
+    if (m_animated) {
+        m_segment = 0;
+        m_timerId = startTimer(m_delay);
+    }
+    update();
+}
+
+void ChaseWidget::paintEvent(QPaintEvent *event)
+{
+    Q_UNUSED(event);
+
+    if ( parentWidget() )
+    {
+        QPoint center( ( parentWidget()->width() / 2 ) - ( width() / 2 ), ( parentWidget()->height() / 2 ) - ( height() / 2 ) );
+        if ( center != pos() )
+        {
+            move( center );
+            return;
+        }
+    }
+
+    QPainter p(this);
+
+    if ( m_showHide->state() == QTimeLine::Running )
+    {
+        // showing or hiding
+        p.setOpacity( (qreal)m_showHide->currentValue() );
+    }
+
+    if (m_pixmapEnabled && !m_pixmap.isNull()) {
+        p.drawPixmap(0, 0, m_pixmap);
+        return;
+    }
+
+    const int extent = qMin(width() - 8, height() - 8);
+    const int displ = extent / 4;
+    const int ext = extent / 4 - 1;
+
+    p.setRenderHint(QPainter::Antialiasing, true);
+
+    if(m_animated)
+        p.setPen(Qt::gray);
+    else
+        p.setPen(QPen(palette().dark().color()));
+
+    p.translate(width() / 2, height() / 2); // center
+
+    for (int segment = 0; segment < segmentCount(); ++segment) {
+        p.rotate(QApplication::isRightToLeft() ? m_step : -m_step);
+        if(m_animated)
+            p.setBrush(colorForSegment(segment));
+        else
+            p.setBrush(palette().background());
+        p.drawEllipse(QRect(displ, -ext / 2, ext, ext));
+    }
+}
+
+void ChaseWidget::fadeIn()
+{
+    if ( isVisible() )
+        return;
+
+    show();
+
+    setAnimated( true );
+    m_showHide->setDirection( QTimeLine::Forward );
+
+    if ( m_showHide->state() != QTimeLine::Running )
+        m_showHide->start();
+}
+
+void ChaseWidget::fadeOut()
+{
+    m_showHide->setDirection( QTimeLine::Backward );
+
+    if ( m_showHide->state() != QTimeLine::Running )
+        m_showHide->start();
+}
+
+void ChaseWidget::hideFinished()
+{
+    if ( m_showHide->direction() == QTimeLine::Backward )
+    {
+        hide();
+        setAnimated(false);
+    }
+}
+
+QSize ChaseWidget::sizeHint() const
+{
+    return QSize(64, 64);
+}
+
+void ChaseWidget::timerEvent(QTimerEvent *event)
+{
+    if (event->timerId() == m_timerId) {
+        ++m_segment;
+        update();
+        emit requestUpdate();
+    }
+    QWidget::timerEvent(event);
+}
+
+QColor ChaseWidget::colorForSegment(int seg) const
+{
+    int index = ((seg + m_segment) % segmentCount());
+    int comp = qMax(0, 255 - (index * (255 / segmentCount())));
+    return QColor(comp, comp, comp, 255);
+}
+
+int ChaseWidget::segmentCount() const
+{
+    return 360 / m_step;
+}
+
+void ChaseWidget::setPixmapEnabled(bool enable)
+{
+    m_pixmapEnabled = enable;
+}
+
diff --git a/src/libtomahawk/utils/chasewidget.h b/src/libtomahawk/utils/chasewidget.h
new file mode 100644
index 000000000..0c3450ac7
--- /dev/null
+++ b/src/libtomahawk/utils/chasewidget.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef CHASEWIDGET_H
+#define CHASEWIDGET_H
+
+#include "dllmacro.h"
+
+#include <QtGui/QWidget>
+
+#include <QtCore/QSize>
+#include <QtGui/QColor>
+#include <QtGui/QPixmap>
+
+class QTimeLine;
+class QHideEvent;
+class QShowEvent;
+class QPaintEvent;
+class QTimerEvent;
+
+class DLLEXPORT ChaseWidget : public QWidget
+{
+    Q_OBJECT
+public:
+    ChaseWidget(QWidget *parent = 0, QPixmap pixmap = QPixmap(), bool pixmapEnabled = false);
+
+    void setPixmapEnabled(bool enable);
+    QSize sizeHint() const;
+
+public slots:
+    void fadeIn();
+    void fadeOut();
+
+signals:
+    void requestUpdate();
+
+protected:
+    void paintEvent(QPaintEvent *event);
+    void timerEvent(QTimerEvent *event);
+
+private slots:
+    void hideFinished();
+
+private:
+    void setAnimated(bool value);
+    int segmentCount() const;
+    QColor colorForSegment(int segment) const;
+
+    QTimeLine* m_showHide;
+
+    int m_segment;
+    int m_delay;
+    int m_step;
+    int m_timerId;
+    bool m_animated;
+    QPixmap m_pixmap;
+    bool m_pixmapEnabled;
+};
+
+#endif
diff --git a/src/libtomahawk/widgets/searchwidget.cpp b/src/libtomahawk/widgets/searchwidget.cpp
index 0ccbda4ab..8add731b0 100644
--- a/src/libtomahawk/widgets/searchwidget.cpp
+++ b/src/libtomahawk/widgets/searchwidget.cpp
@@ -26,6 +26,7 @@
 #include "sourcelist.h"
 #include "viewmanager.h"
 #include "dynamic/widgets/LoadingSpinner.h"
+#include "utils/chasewidget.h"
 #include "playlist/albummodel.h"
 #include "playlist/playlistmodel.h"
 #include "widgets/overlaywidget.h"
@@ -146,7 +147,7 @@ SearchWidget::onResultsFound( const QList<Tomahawk::result_ptr>& results )
         q->addResults( rl );
 
         m_resultsModel->append( q );
-        
+
         artists << result->artist();
         albums << result->album();
     }