]> git.sesse.net Git - vlc/commitdiff
Qt: initial pass for CoverFlow view of the playlist
authorJean-Baptiste Kempf <jb@videolan.org>
Thu, 28 Oct 2010 12:33:27 +0000 (14:33 +0200)
committerJean-Baptiste Kempf <jb@videolan.org>
Sat, 30 Oct 2010 09:40:17 +0000 (11:40 +0200)
This code is unfinished, and is commited as a Proof-of-Concept
If people agree on it, we will go on this experiment, else revert.
A lot of the code is broken, you are warned.

Signed-off-by: Jean-Baptiste Kempf <jb@videolan.org>
modules/gui/qt4/Modules.am
modules/gui/qt4/components/playlist/playlist_model.cpp
modules/gui/qt4/components/playlist/playlist_model.hpp
modules/gui/qt4/components/playlist/standardpanel.cpp
modules/gui/qt4/components/playlist/standardpanel.hpp
modules/gui/qt4/components/playlist/views.cpp
modules/gui/qt4/components/playlist/views.hpp
modules/gui/qt4/util/pictureflow.cpp [new file with mode: 0644]
modules/gui/qt4/util/pictureflow.hpp [new file with mode: 0644]

index 2e616efb85511cf286631f4e27495cc7f7ca736c..5225cf8da0ea8124fd3e9459107fa631e41523f5 100644 (file)
@@ -67,6 +67,7 @@ nodist_SOURCES_qt4 = \
                util/input_slider.moc.cpp \
                util/customwidgets.moc.cpp \
                util/qvlcapp.moc.cpp \
+               util/pictureflow.moc.cpp \
                resources.cpp \
                ui/equalizer.h \
                ui/video_effects.h \
@@ -273,7 +274,8 @@ SOURCES_qt4 =       qt4.cpp \
                components/sout/sout_widgets.cpp \
                util/input_slider.cpp \
                util/customwidgets.cpp \
-               util/registry.cpp
+               util/registry.cpp \
+               util/pictureflow.cpp
 
 noinst_HEADERS = \
        qt4.hpp \
@@ -337,6 +339,7 @@ noinst_HEADERS = \
        util/qvlcapp.hpp \
        util/qt_dirs.hpp \
        util/registry.hpp \
+       util/pictureflow.hpp
        util/singleton.hpp
 
 
index 35ee685b74eefd6e6551d3b39fc2f18d3f0cc3ab..9cb0de3d85077175145ddb52a01f2c6ffb2a1653 100644 (file)
@@ -611,6 +611,9 @@ QPixmap PLModel::getArtPixmap( const QModelIndex & index, const QSize & size )
     PLItem *item = static_cast<PLItem*>( index.internalPointer() );
     assert( item );
 
+    if( item == NULL )
+        return NULL;
+
     QString artUrl = InputManager::decodeArtURL( item->inputItem() );
 
     /* If empty, take one of the children art URL */
@@ -624,6 +627,9 @@ QPixmap PLModel::getArtPixmap( const QModelIndex & index, const QSize & size )
         }
     }
 
+    if( artUrl.isEmpty() )
+        return NULL;
+
     QPixmap artPix;
     QString key = artUrl + QString("%1%2").arg(size.width()).arg(size.height());
 
@@ -682,15 +688,17 @@ void PLModel::processItemAppend( int i_item, int i_parent )
 {
     playlist_item_t *p_item = NULL;
     PLItem *newItem = NULL;
-    input_thread_t *currentInputThread;
     int pos;
 
-    PLItem *nodeItem = findById( rootItem, i_parent );
-    if( !nodeItem ) return;
+    /* Find the Parent */
+    PLItem *nodeParentItem = findById( rootItem, i_parent );
+    if( !nodeParentItem ) return;
 
-    foreach( const PLItem *existing, nodeItem->children )
+    /* Search for an already matching children */
+    foreach( const PLItem *existing, nodeParentItem->children )
         if( existing->i_id == i_item ) return;
 
+    /* Find the child */
     PL_LOCK;
     p_item = playlist_ItemGetById( p_playlist, i_item );
     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG )
@@ -701,11 +709,12 @@ void PLModel::processItemAppend( int i_item, int i_parent )
     for( pos = 0; pos < p_item->p_parent->i_children; pos++ )
         if( p_item->p_parent->pp_children[pos] == p_item ) break;
 
-    newItem = new PLItem( p_item, nodeItem );
+    newItem = new PLItem( p_item, nodeParentItem );
     PL_UNLOCK;
 
-    beginInsertRows( index( nodeItem, 0 ), pos, pos );
-    nodeItem->insertChild( newItem, pos );
+    /* We insert the newItem (children) inside the parent */
+    beginInsertRows( index( nodeParentItem, 0 ), pos, pos );
+    nodeParentItem->insertChild( newItem, pos );
     endInsertRows();
 
     if( newItem->p_input == THEMIM->currentInputItem() )
@@ -760,6 +769,7 @@ void PLModel::insertChildren( PLItem *node, QList<PLItem*>& items, int i_pos )
     assert( node );
     int count = items.size();
     if( !count ) return;
+    printf( "Here I am\n");
     beginInsertRows( index( node, 0 ), i_pos, i_pos + count - 1 );
     for( int i = 0; i < count; i++ )
     {
index 2147f227022964b4d7c85eaa6f5c3347430f9996..4fb16b9892a6a3f3a5f5860e214e79f278fa70d8 100644 (file)
@@ -64,21 +64,21 @@ public:
     /*** QModel subclassing ***/
 
     /* Data structure */
-    QVariant data( const QModelIndex &index, const int role ) const;
-    QVariant headerData( int section, Qt::Orientation orientation,
+    virtual QVariant data( const QModelIndex &index, const int role ) const;
+    virtual QVariant headerData( int section, Qt::Orientation orientation,
                          int role = Qt::DisplayRole ) const;
-    int rowCount( const QModelIndex &parent = QModelIndex() ) const;
-    int columnCount( const QModelIndex &parent = QModelIndex() ) const;
-    Qt::ItemFlags flags( const QModelIndex &index ) const;
-    QModelIndex index( const int r, const int c, const QModelIndex &parent ) const;
-    QModelIndex parent( const QModelIndex &index ) const;
+    virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
+    virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
+    virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
+    virtual QModelIndex index( const int r, const int c, const QModelIndex &parent ) const;
+    virtual QModelIndex parent( const QModelIndex &index ) const;
 
     /* Drag and Drop */
-    Qt::DropActions supportedDropActions() const;
-    QMimeData* mimeData( const QModelIndexList &indexes ) const;
-    bool dropMimeData( const QMimeData *data, Qt::DropAction action,
+    virtual Qt::DropActions supportedDropActions() const;
+    virtual QMimeData* mimeData( const QModelIndexList &indexes ) const;
+    virtual bool dropMimeData( const QMimeData *data, Qt::DropAction action,
                       int row, int column, const QModelIndex &target );
-    QStringList mimeTypes() const;
+    virtual QStringList mimeTypes() const;
 
     /**** Custom ****/
 
index 76af88082738bf0b374a2ebfeb53a72e628d7d1a..98c8392300bbdeb37fdb1c8fa7f349630126d145 100644 (file)
@@ -61,19 +61,20 @@ StandardPLPanel::StandardPLPanel( PlaylistWidget *_parent,
     viewStack->setSpacing( 0 ); viewStack->setMargin( 0 );
     setMinimumWidth( 300 );
 
-    iconView = NULL;
-    treeView = NULL;
-    listView = NULL;
+    iconView    = NULL;
+    treeView    = NULL;
+    listView    = NULL;
+    picFlowView = NULL;
 
     currentRootIndexId  = -1;
     lastActivatedId     = -1;
 
     /* Saved Settings */
     getSettings()->beginGroup("Playlist");
-    int i_viewMode = getSettings()->value( "view-mode", TREE_VIEW ).toInt();
+    int i_savedViewMode = getSettings()->value( "view-mode", TREE_VIEW ).toInt();
     getSettings()->endGroup();
 
-    showView( i_viewMode );
+    showView( i_savedViewMode );
 
     DCONNECT( THEMIM, leafBecameParent( input_item_t *),
               this, browseInto( input_item_t * ) );
@@ -96,6 +97,8 @@ StandardPLPanel::~StandardPLPanel()
         getSettings()->setValue( "view-mode", LIST_VIEW );
     else if( currentView == iconView )
         getSettings()->setValue( "view-mode", ICON_VIEW );
+    else if( currentView == picFlowView )
+        getSettings()->setValue( "view-mode", PICTUREFLOW_VIEW );
     getSettings()->endGroup();
 }
 
@@ -161,7 +164,7 @@ void StandardPLPanel::search( const QString& searchText )
     p_selector->getCurrentSelectedItem( &type, &name );
     if( type != SD_TYPE )
     {
-        bool flat = currentView == iconView || currentView == listView;
+        bool flat = currentView == iconView || currentView == listView || currentView == picFlowView;
         model->search( searchText,
                        flat ? currentView->rootIndex() : QModelIndex(),
                        !flat );
@@ -190,7 +193,7 @@ void StandardPLPanel::setRoot( playlist_item_t *p_item )
 
 void StandardPLPanel::browseInto( const QModelIndex &index )
 {
-    if( currentView == iconView || currentView == listView )
+    if( currentView == iconView || currentView == listView || currentView == picFlowView )
     {
         currentRootIndexId = model->itemId( index );
         currentView->setRootIndex( index );
@@ -258,6 +261,18 @@ void StandardPLPanel::createListView()
     viewStack->addWidget( listView );
 }
 
+void StandardPLPanel::createCoverView()
+{
+    picFlowView = new PicFlowView( model, this );
+    picFlowView->setContextMenuPolicy( Qt::CustomContextMenu );
+    CONNECT( picFlowView, customContextMenuRequested( const QPoint & ),
+             this, popupPlView( const QPoint & ) );
+    CONNECT( picFlowView, activated( const QModelIndex & ),
+             this, activate( const QModelIndex & ) );
+    viewStack->addWidget( picFlowView );
+    picFlowView->installEventFilter( this );
+}
+
 void StandardPLPanel::createTreeView()
 {
     /* Create and configure the QTreeView */
@@ -344,6 +359,13 @@ void StandardPLPanel::showView( int i_view )
         currentView = listView;
         break;
     }
+    case PICTUREFLOW_VIEW:
+    {
+        if( picFlowView == NULL )
+            createCoverView();
+        currentView = picFlowView;
+        break;
+    }
     default: return;
     }
 
@@ -360,6 +382,8 @@ const int StandardPLPanel::getViewNumber()
         return ICON_VIEW;
     else if( currentView == listView )
         return LIST_VIEW;
+    else
+        return PICTUREFLOW_VIEW;
 }
 
 void StandardPLPanel::cycleViews()
@@ -369,6 +393,8 @@ void StandardPLPanel::cycleViews()
     else if( currentView == treeView )
         showView( LIST_VIEW );
     else if( currentView == listView )
+        showView( PICTUREFLOW_VIEW  );
+    else if( currentView == picFlowView )
         showView( ICON_VIEW );
     else
         assert( 0 );
@@ -376,6 +402,7 @@ void StandardPLPanel::cycleViews()
 
 void StandardPLPanel::activate( const QModelIndex &index )
 {
+    /* If we are not a leaf node */
     if( !index.data( PLModel::IsLeafNodeRole ).toBool() )
     {
         if( currentView != treeView )
@@ -406,7 +433,6 @@ void StandardPLPanel::browseInto( input_item_t *p_input )
     }
 
     QModelIndex index = model->index( p_item->i_id, 0 );
-
     playlist_Unlock( THEPL );
 
     if( currentView == treeView )
index 5ff490804962c83bbc045b426caad72b0501b427..214352802044d9787d305f0108bdcbf8e55ca3d0 100644 (file)
@@ -46,16 +46,12 @@ class QAbstractItemView;
 class QTreeView;
 class PlIconView;
 class PlListView;
+class PicFlowView;
 
 class LocationBar;
 class PLSelector;
 class PlaylistWidget;
 
-static const QString viewNames[3 /* VIEW_COUNT */]
-                                = { qtr( "Detailed View" ),
-                                    qtr( "Icon View" ),
-                                    qtr( "List View" ) };
-
 class StandardPLPanel: public QWidget
 {
     Q_OBJECT
@@ -68,7 +64,8 @@ public:
     enum { TREE_VIEW = 0,
            ICON_VIEW,
            LIST_VIEW,
-           VIEW_COUNT  };
+           PICTUREFLOW_VIEW,
+           VIEW_COUNT };
 
     const int getViewNumber();
 
@@ -84,6 +81,8 @@ private:
     QTreeView         *treeView;
     PlIconView        *iconView;
     PlListView        *listView;
+    PicFlowView       *picFlowView;
+
     QAbstractItemView *currentView;
 
     QStackedLayout    *viewStack;
@@ -96,6 +95,7 @@ private:
     void createTreeView();
     void createIconView();
     void createListView();
+    void createCoverView();
     bool eventFilter ( QObject * watched, QEvent * event );
 
 public slots:
@@ -126,4 +126,11 @@ signals:
     void viewChanged( const QModelIndex& );
 };
 
+
+static const QString viewNames[ StandardPLPanel::VIEW_COUNT ]
+                                = { qtr( "Detailed View" ),
+                                    qtr( "Icon View" ),
+                                    qtr( "List View" ),
+                                    qtr( "PictureFlow View ") };
+
 #endif
index bb1715018f3ca02d85733baa8c286fd7250320cd..e4b1e687b9ad8e40297705654f8f656925631494 100644 (file)
  *****************************************************************************/
 
 #include "components/playlist/views.hpp"
-#include "components/playlist/playlist_model.hpp"
-#include "components/playlist/sorting.h"
-#include "input_manager.hpp"
+#include "components/playlist/playlist_model.hpp" /* PLModel */
+#include "components/playlist/sorting.h"          /* Columns List */
+#include "input_manager.hpp"                      /* THEMIM */
 
-#include <QApplication>
 #include <QPainter>
 #include <QRect>
 #include <QStyleOptionViewItem>
@@ -178,9 +177,6 @@ QSize PlIconViewItemDelegate::sizeHint ( const QStyleOptionViewItem & option, co
 
 void PlListViewItemDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
 {
-    QModelIndex parent = index.parent();
-    QModelIndex i;
-
     QString title = PLModel::getMeta( index, COLUMN_TITLE );
     QString duration = PLModel::getMeta( index, COLUMN_DURATION );
     if( !duration.isEmpty() ) title += QString(" [%1]").arg( duration );
@@ -263,14 +259,14 @@ void PlListViewItemDelegate::paint( QPainter * painter, const QStyleOptionViewIt
 
 QSize PlListViewItemDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
 {
-  QFont f;
-  f.setBold( true );
-  QFontMetrics fm( f );
-  int height = qMax( LISTVIEW_ART_SIZE, 2 * fm.height() + 4 ) + 6;
-  return QSize( 0, height );
+    QFont f;
+    f.setBold( true );
+    QFontMetrics fm( f );
+    int height = qMax( LISTVIEW_ART_SIZE, 2 * fm.height() + 4 ) + 6;
+    return QSize( 0, height );
 }
 
-static void plViewStartDrag( QAbstractItemView *view, const Qt::DropActions & supportedActions )
+static inline void plViewStartDrag( QAbstractItemView *view, const Qt::DropActions & supportedActions )
 {
     QDrag *drag = new QDrag( view );
     drag->setPixmap( QPixmap( ":/noart64" ) );
@@ -375,3 +371,84 @@ void PlTreeView::keyPressEvent( QKeyEvent *event )
     else
         QTreeView::keyPressEvent( event );
 }
+
+#include <QHBoxLayout>
+PicFlowView::PicFlowView( PLModel *p_model, QWidget *parent ) : QAbstractItemView( parent )
+{
+    QHBoxLayout *layout = new QHBoxLayout( this );
+    layout->setMargin( 0 );
+    picFlow = new PictureFlow( this );
+    picFlow->setSlideSize(QSize(128,128));
+    layout->addWidget( picFlow );
+    setSelectionMode( QAbstractItemView::SingleSelection );
+    setModel( p_model );
+
+    CONNECT( picFlow, centerIndexChanged(int), this, playItem(int) );
+}
+
+int PicFlowView::horizontalOffset() const
+{
+    return 0;
+}
+
+int PicFlowView::verticalOffset() const
+{
+    return 0;
+}
+
+QRect PicFlowView::visualRect(const QModelIndex &index ) const
+{
+    return QRect( QPoint(0,0), picFlow->slideSize() );
+}
+
+void PicFlowView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint)
+{
+    if( index.column() >= 0 && picFlow->slideCount() > 0 )
+        picFlow->showSlide( index.column() );
+}
+
+QModelIndex PicFlowView::indexAt(const QPoint &) const
+{
+    // No idea, PictureFlow doesn't provide anything to help this
+}
+
+QModelIndex PicFlowView::moveCursor(QAbstractItemView::CursorAction action, Qt::KeyboardModifiers)
+{
+}
+
+bool PicFlowView::isIndexHidden(const QModelIndex &) const
+{
+    return false;
+}
+
+QRegion PicFlowView::visualRegionForSelection(const QItemSelection &) const
+{
+    return QRect();
+}
+
+void PicFlowView::setSelection(const QRect &, QFlags<QItemSelectionModel::SelectionFlag>)
+{
+    // No selection possible
+}
+
+void PicFlowView::rowsInserted(const QModelIndex &parent, int start, int end)
+{
+    for( int i = start; i <= end; i++ )
+    {
+        const QModelIndex index = model()->index( i, 0, parent );
+        if( !index.isValid() )
+            return;
+
+        /* FIXME, this returns no art, so far */
+        QPixmap pix = PLModel::getArtPixmap( index, QSize(128,128) );
+        picFlow->addSlide(pix);
+    }
+
+    picFlow->render();
+}
+
+void PicFlowView::playItem( int i_item )
+{
+    emit activated( model()->index(i_item, 0) );
+}
+
index f648e18e11f781743dd884da2e0c39609cf025d2..b9313aa1ecc4306268ba1060ecf4b628332f3961 100644 (file)
@@ -27,6 +27,8 @@
 #include <QStyledItemDelegate>
 #include <QListView>
 #include <QTreeView>
+#include <QAbstractItemView>
+#include "util/pictureflow.hpp"
 
 class QPainter;
 class PLModel;
@@ -93,5 +95,31 @@ protected:
     virtual void keyPressEvent( QKeyEvent *event );
 };
 
-#endif
+class PicFlowView : public QAbstractItemView
+{
+    Q_OBJECT
+public:
+    PicFlowView( PLModel *model, QWidget *parent = 0 );
+
+    virtual QRect visualRect(const QModelIndex&) const;
+    virtual void scrollTo(const QModelIndex&, QAbstractItemView::ScrollHint);
+    virtual QModelIndex indexAt(const QPoint&) const;
 
+protected:
+    virtual int horizontalOffset() const;
+    virtual int verticalOffset() const;
+    virtual QModelIndex moveCursor(QAbstractItemView::CursorAction, Qt::KeyboardModifiers);
+    virtual bool isIndexHidden(const QModelIndex&) const;
+    virtual QRegion visualRegionForSelection(const QItemSelection&) const;
+    virtual void setSelection(const QRect&, QFlags<QItemSelectionModel::SelectionFlag>);
+
+private:
+    PictureFlow *picFlow;
+
+protected slots:
+    void rowsInserted ( const QModelIndex & parent, int start, int end );
+private slots:
+    void playItem( int );
+};
+
+#endif
diff --git a/modules/gui/qt4/util/pictureflow.cpp b/modules/gui/qt4/util/pictureflow.cpp
new file mode 100644 (file)
index 0000000..0bc83b8
--- /dev/null
@@ -0,0 +1,1047 @@
+/*
+  PictureFlow - animated image show widget
+  http://pictureflow.googlecode.com
+
+  Copyright (C) 2009 Ariya Hidayat (ariya@kde.org)
+  Copyright (C) 2008 Ariya Hidayat (ariya@kde.org)
+  Copyright (C) 2007 Ariya Hidayat (ariya@kde.org)
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+*/
+
+#include "pictureflow.hpp"
+
+// detect Qt version
+#if QT_VERSION < 0x040300
+#error PictureFlow widgets need Qt 4.3 or later
+#endif
+
+#include <QApplication>
+#include <QCache>
+#include <QHash>
+#include <QImage>
+#include <QKeyEvent>
+#include <QPainter>
+#include <QPixmap>
+#include <QTimer>
+#include <QVector>
+#include <QWidget>
+
+// for fixed-point arithmetic, we need minimum 32-bit long
+// long long (64-bit) might be useful for multiplication and division
+typedef long PFreal;
+#define PFREAL_SHIFT 10
+#define PFREAL_ONE (1 << PFREAL_SHIFT)
+
+#define IANGLE_MAX 1024
+#define IANGLE_MASK 1023
+
+inline PFreal fmul(PFreal a, PFreal b)
+{
+    return ((long long)(a))*((long long)(b)) >> PFREAL_SHIFT;
+}
+
+inline PFreal fdiv(PFreal num, PFreal den)
+{
+    long long p = (long long)(num) << (PFREAL_SHIFT * 2);
+    long long q = p / (long long)den;
+    long long r = q >> PFREAL_SHIFT;
+
+    return r;
+}
+
+inline PFreal fsin(int iangle)
+{
+    // warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed!
+    static const PFreal tab[] = {
+        3,    103,    202,    300,    394,    485,    571,    652,
+        726,    793,    853,    904,    947,    980,   1004,   1019,
+        1023,   1018,   1003,    978,    944,    901,    849,    789,
+        721,    647,    566,    479,    388,    294,    196,     97,
+        -4,   -104,   -203,   -301,   -395,   -486,   -572,   -653,
+        -727,   -794,   -854,   -905,   -948,   -981,  -1005,  -1020,
+        -1024,  -1019,  -1004,   -979,   -945,   -902,   -850,   -790,
+        -722,   -648,   -567,   -480,   -389,   -295,   -197,    -98,
+        3
+    };
+
+    while (iangle < 0)
+        iangle += IANGLE_MAX;
+    iangle &= IANGLE_MASK;
+
+    int i = (iangle >> 4);
+    PFreal p = tab[i];
+    PFreal q = tab[(i+1)];
+    PFreal g = (q - p);
+    return p + g *(iangle - i*16) / 16;
+}
+
+inline PFreal fcos(int iangle)
+{
+    return fsin(iangle + (IANGLE_MAX >> 2));
+}
+
+/* ----------------------------------------------------------
+
+PictureFlowState stores the state of all slides, i.e. all the necessary
+information to be able to render them.
+
+PictureFlowAnimator is responsible to move the slides during the
+transition between slides, to achieve the effect similar to Cover Flow,
+by changing the state.
+
+PictureFlowSoftwareRenderer (or PictureFlowOpenGLRenderer) is
+the actual 3-d renderer. It should render all slides given the state
+(an instance of PictureFlowState).
+
+Instances of all the above three classes are stored in
+PictureFlowPrivate.
+
+------------------------------------------------------- */
+
+struct SlideInfo {
+    int slideIndex;
+    int angle;
+    PFreal cx;
+    PFreal cy;
+    int blend;
+};
+
+class PictureFlowState
+{
+public:
+    PictureFlowState();
+    ~PictureFlowState();
+
+    void reposition();
+    void reset();
+
+    QRgb backgroundColor;
+    int slideWidth;
+    int slideHeight;
+    PictureFlow::ReflectionEffect reflectionEffect;
+    QVector<QImage*> slideImages;
+
+    int angle;
+    int spacing;
+    PFreal offsetX;
+    PFreal offsetY;
+
+    SlideInfo centerSlide;
+    QVector<SlideInfo> leftSlides;
+    QVector<SlideInfo> rightSlides;
+    int centerIndex;
+};
+
+class PictureFlowAnimator
+{
+public:
+    PictureFlowAnimator();
+    PictureFlowState* state;
+
+    void start(int slide);
+    void stop(int slide);
+    void update();
+
+    int target;
+    int step;
+    int frame;
+    QTimer animateTimer;
+};
+
+class PictureFlowAbstractRenderer
+{
+public:
+    PictureFlowAbstractRenderer(): state(0), dirty(false), widget(0) {}
+    virtual ~PictureFlowAbstractRenderer() {}
+
+    PictureFlowState* state;
+    bool dirty;
+    QWidget* widget;
+
+    virtual void init() = 0;
+    virtual void paint() = 0;
+};
+
+class PictureFlowSoftwareRenderer: public PictureFlowAbstractRenderer
+{
+public:
+    PictureFlowSoftwareRenderer();
+    ~PictureFlowSoftwareRenderer();
+
+    virtual void init();
+    virtual void paint();
+
+private:
+    QSize size;
+    QRgb bgcolor;
+    int effect;
+    QImage buffer;
+    QVector<PFreal> rays;
+    QImage* blankSurface;
+    QCache<int, QImage> surfaceCache;
+    QHash<int, QImage*> imageHash;
+
+    void render();
+    void renderSlides();
+    QRect renderSlide(const SlideInfo &slide, int col1 = -1, int col2 = -1);
+    QImage* surface(int slideIndex);
+};
+
+// ------------- PictureFlowState ---------------------------------------
+
+PictureFlowState::PictureFlowState():
+        backgroundColor(0), slideWidth(150), slideHeight(200),
+        reflectionEffect(PictureFlow::BlurredReflection), centerIndex(0)
+{
+}
+
+PictureFlowState::~PictureFlowState()
+{
+    for (int i = 0; i < (int)slideImages.count(); i++)
+        delete slideImages[i];
+}
+
+// readjust the settings, call this when slide dimension is changed
+void PictureFlowState::reposition()
+{
+    angle = 70 * IANGLE_MAX / 360;  // approx. 70 degrees tilted
+
+    offsetX = slideWidth / 2 * (PFREAL_ONE - fcos(angle));
+    offsetY = slideWidth / 2 * fsin(angle);
+    offsetX += slideWidth * PFREAL_ONE;
+    offsetY += slideWidth * PFREAL_ONE / 4;
+    spacing = 40;
+}
+
+// adjust slides so that they are in "steady state" position
+void PictureFlowState::reset()
+{
+    centerSlide.angle = 0;
+    centerSlide.cx = 0;
+    centerSlide.cy = 0;
+    centerSlide.slideIndex = centerIndex;
+    centerSlide.blend = 256;
+
+    leftSlides.resize(6);
+    for (int i = 0; i < (int)leftSlides.count(); i++) {
+        SlideInfo& si = leftSlides[i];
+        si.angle = angle;
+        si.cx = -(offsetX + spacing * i * PFREAL_ONE);
+        si.cy = offsetY;
+        si.slideIndex = centerIndex - 1 - i;
+        si.blend = 256;
+        if (i == (int)leftSlides.count() - 2)
+            si.blend = 128;
+        if (i == (int)leftSlides.count() - 1)
+            si.blend = 0;
+    }
+
+    rightSlides.resize(6);
+    for (int i = 0; i < (int)rightSlides.count(); i++) {
+        SlideInfo& si = rightSlides[i];
+        si.angle = -angle;
+        si.cx = offsetX + spacing * i * PFREAL_ONE;
+        si.cy = offsetY;
+        si.slideIndex = centerIndex + 1 + i;
+        si.blend = 256;
+        if (i == (int)rightSlides.count() - 2)
+            si.blend = 128;
+        if (i == (int)rightSlides.count() - 1)
+            si.blend = 0;
+    }
+}
+
+// ------------- PictureFlowAnimator  ---------------------------------------
+
+PictureFlowAnimator::PictureFlowAnimator():
+        state(0), target(0), step(0), frame(0)
+{
+}
+
+void PictureFlowAnimator::start(int slide)
+{
+    target = slide;
+    if (!animateTimer.isActive() && state) {
+        step = (target < state->centerSlide.slideIndex) ? -1 : 1;
+        animateTimer.start(30);
+    }
+}
+
+void PictureFlowAnimator::stop(int slide)
+{
+    step = 0;
+    target = slide;
+    frame = slide << 16;
+    animateTimer.stop();
+}
+
+void PictureFlowAnimator::update()
+{
+    if (!animateTimer.isActive())
+        return;
+    if (step == 0)
+        return;
+    if (!state)
+        return;
+
+    int speed = 16384 / 4;
+
+#if 1
+    // deaccelerate when approaching the target
+    const int max = 2 * 65536;
+
+    int fi = frame;
+    fi -= (target << 16);
+    if (fi < 0)
+        fi = -fi;
+    fi = qMin(fi, max);
+
+    int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
+    speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
+#endif
+
+    frame += speed * step;
+
+    int index = frame >> 16;
+    int pos = frame & 0xffff;
+    int neg = 65536 - pos;
+    int tick = (step < 0) ? neg : pos;
+    PFreal ftick = (tick * PFREAL_ONE) >> 16;
+
+    if (step < 0)
+        index++;
+
+    if (state->centerIndex != index) {
+        state->centerIndex = index;
+        frame = index << 16;
+        state->centerSlide.slideIndex = state->centerIndex;
+        for (int i = 0; i < (int)state->leftSlides.count(); i++)
+            state->leftSlides[i].slideIndex = state->centerIndex - 1 - i;
+        for (int i = 0; i < (int)state->rightSlides.count(); i++)
+            state->rightSlides[i].slideIndex = state->centerIndex + 1 + i;
+    }
+
+    state->centerSlide.angle = (step * tick * state->angle) >> 16;
+    state->centerSlide.cx = -step * fmul(state->offsetX, ftick);
+    state->centerSlide.cy = fmul(state->offsetY, ftick);
+
+    if (state->centerIndex == target) {
+        stop(target);
+        state->reset();
+        return;
+    }
+
+    for (int i = 0; i < (int)state->leftSlides.count(); i++) {
+        SlideInfo& si = state->leftSlides[i];
+        si.angle = state->angle;
+        si.cx = -(state->offsetX + state->spacing * i * PFREAL_ONE + step * state->spacing * ftick);
+        si.cy = state->offsetY;
+    }
+
+    for (int i = 0; i < (int)state->rightSlides.count(); i++) {
+        SlideInfo& si = state->rightSlides[i];
+        si.angle = -state->angle;
+        si.cx = state->offsetX + state->spacing * i * PFREAL_ONE - step * state->spacing * ftick;
+        si.cy = state->offsetY;
+    }
+
+    if (step > 0) {
+        PFreal ftick = (neg * PFREAL_ONE) >> 16;
+        state->rightSlides[0].angle = -(neg * state->angle) >> 16;
+        state->rightSlides[0].cx = fmul(state->offsetX, ftick);
+        state->rightSlides[0].cy = fmul(state->offsetY, ftick);
+    } else {
+        PFreal ftick = (pos * PFREAL_ONE) >> 16;
+        state->leftSlides[0].angle = (pos * state->angle) >> 16;
+        state->leftSlides[0].cx = -fmul(state->offsetX, ftick);
+        state->leftSlides[0].cy = fmul(state->offsetY, ftick);
+    }
+
+    // must change direction ?
+    if (target < index) if (step > 0)
+            step = -1;
+    if (target > index) if (step < 0)
+            step = 1;
+
+    // the first and last slide must fade in/fade out
+    int nleft = state->leftSlides.count();
+    int nright = state->rightSlides.count();
+    int fade = pos / 256;
+
+    for (int index = 0; index < nleft; index++) {
+        int blend = 256;
+        if (index == nleft - 1)
+            blend = (step > 0) ? 0 : 128 - fade / 2;
+        if (index == nleft - 2)
+            blend = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
+        if (index == nleft - 3)
+            blend = (step > 0) ? 256 - fade / 2 : 256;
+        state->leftSlides[index].blend = blend;
+    }
+    for (int index = 0; index < nright; index++) {
+        int blend = (index < nright - 2) ? 256 : 128;
+        if (index == nright - 1)
+            blend = (step > 0) ? fade / 2 : 0;
+        if (index == nright - 2)
+            blend = (step > 0) ? 128 + fade / 2 : fade / 2;
+        if (index == nright - 3)
+            blend = (step > 0) ? 256 : 128 + fade / 2;
+        state->rightSlides[index].blend = blend;
+    }
+
+}
+
+// ------------- PictureFlowSoftwareRenderer ---------------------------------------
+
+PictureFlowSoftwareRenderer::PictureFlowSoftwareRenderer():
+        PictureFlowAbstractRenderer(), size(0, 0), bgcolor(0), effect(-1), blankSurface(0)
+{
+}
+
+PictureFlowSoftwareRenderer::~PictureFlowSoftwareRenderer()
+{
+    surfaceCache.clear();
+    buffer = QImage();
+    delete blankSurface;
+}
+
+void PictureFlowSoftwareRenderer::paint()
+{
+    if (!widget)
+        return;
+
+    if (widget->size() != size)
+        init();
+
+    if (state->backgroundColor != bgcolor) {
+        bgcolor = state->backgroundColor;
+        surfaceCache.clear();
+    }
+
+    if ((int)(state->reflectionEffect) != effect) {
+        effect = (int)state->reflectionEffect;
+        surfaceCache.clear();
+    }
+
+    if (dirty)
+        render();
+
+    QPainter painter(widget);
+    painter.drawImage(QPoint(0, 0), buffer);
+}
+
+void PictureFlowSoftwareRenderer::init()
+{
+    if (!widget)
+        return;
+
+    surfaceCache.clear();
+    blankSurface = 0;
+
+    size = widget->size();
+    int ww = size.width();
+    int wh = size.height();
+    int w = (ww + 1) / 2;
+    int h = (wh + 1) / 2;
+
+    buffer = QImage(ww, wh, QImage::Format_RGB32);
+    buffer.fill(bgcolor);
+
+    rays.resize(w*2);
+    for (int i = 0; i < w; i++) {
+        PFreal gg = ((PFREAL_ONE >> 1) + i * PFREAL_ONE) / (2 * h);
+        rays[w-i-1] = -gg;
+        rays[w+i] = gg;
+    }
+
+    dirty = true;
+}
+
+// TODO: optimize this with lookup tables
+static QRgb blendColor(QRgb c1, QRgb c2, int blend)
+{
+    int r = qRed(c1) * blend / 256 + qRed(c2) * (256 - blend) / 256;
+    int g = qGreen(c1) * blend / 256 + qGreen(c2) * (256 - blend) / 256;
+    int b = qBlue(c1) * blend / 256 + qBlue(c2) * (256 - blend) / 256;
+    return qRgb(r, g, b);
+}
+
+
+static QImage* prepareSurface(const QImage* slideImage, int w, int h, QRgb bgcolor,
+                              PictureFlow::ReflectionEffect reflectionEffect)
+{
+    Qt::TransformationMode mode = Qt::SmoothTransformation;
+    QImage img = slideImage->scaled(w, h, Qt::IgnoreAspectRatio, mode);
+
+    // slightly larger, to accomodate for the reflection
+    int hs = h * 2;
+    int hofs = h / 3;
+
+    // offscreen buffer: black is sweet
+    QImage* result = new QImage(hs, w, QImage::Format_RGB32);
+    result->fill(bgcolor);
+
+    // transpose the image, this is to speed-up the rendering
+    // because we process one column at a time
+    // (and much better and faster to work row-wise, i.e in one scanline)
+    for (int x = 0; x < w; x++)
+        for (int y = 0; y < h; y++)
+            result->setPixel(hofs + y, x, img.pixel(x, y));
+
+    if (reflectionEffect != PictureFlow::NoReflection) {
+        // create the reflection
+        int ht = hs - h - hofs;
+        int hte = ht;
+        for (int x = 0; x < w; x++)
+            for (int y = 0; y < ht; y++) {
+                QRgb color = img.pixel(x, img.height() - y - 1);
+                result->setPixel(h + hofs + y, x, blendColor(color, bgcolor, 128*(hte - y) / hte));
+            }
+
+        if (reflectionEffect == PictureFlow::BlurredReflection) {
+            // blur the reflection everything first
+            // Based on exponential blur algorithm by Jani Huhtanen
+            QRect rect(hs / 2, 0, hs / 2, w);
+            rect &= result->rect();
+
+            int r1 = rect.top();
+            int r2 = rect.bottom();
+            int c1 = rect.left();
+            int c2 = rect.right();
+
+            int bpl = result->bytesPerLine();
+            int rgba[4];
+            unsigned char* p;
+
+            // how many times blur is applied?
+            // for low-end system, limit this to only 1 loop
+            for (int loop = 0; loop < 2; loop++) {
+                for (int col = c1; col <= c2; col++) {
+                    p = result->scanLine(r1) + col * 4;
+                    for (int i = 0; i < 3; i++)
+                        rgba[i] = p[i] << 4;
+
+                    p += bpl;
+                    for (int j = r1; j < r2; j++, p += bpl)
+                        for (int i = 0; i < 3; i++)
+                            p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
+                }
+
+                for (int row = r1; row <= r2; row++) {
+                    p = result->scanLine(row) + c1 * 4;
+                    for (int i = 0; i < 3; i++)
+                        rgba[i] = p[i] << 4;
+
+                    p += 4;
+                    for (int j = c1; j < c2; j++, p += 4)
+                        for (int i = 0; i < 3; i++)
+                            p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
+                }
+
+                for (int col = c1; col <= c2; col++) {
+                    p = result->scanLine(r2) + col * 4;
+                    for (int i = 0; i < 3; i++)
+                        rgba[i] = p[i] << 4;
+
+                    p -= bpl;
+                    for (int j = r1; j < r2; j++, p -= bpl)
+                        for (int i = 0; i < 3; i++)
+                            p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
+                }
+
+                for (int row = r1; row <= r2; row++) {
+                    p = result->scanLine(row) + c2 * 4;
+                    for (int i = 0; i < 3; i++)
+                        rgba[i] = p[i] << 4;
+
+                    p -= 4;
+                    for (int j = c1; j < c2; j++, p -= 4)
+                        for (int i = 0; i < 3; i++)
+                            p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
+                }
+            }
+
+            // overdraw to leave only the reflection blurred (but not the actual image)
+            for (int x = 0; x < w; x++)
+                for (int y = 0; y < h; y++)
+                    result->setPixel(hofs + y, x, img.pixel(x, y));
+        }
+    }
+
+    return result;
+}
+
+QImage* PictureFlowSoftwareRenderer::surface(int slideIndex)
+{
+    if (!state)
+        return 0;
+    if (slideIndex < 0)
+        return 0;
+    if (slideIndex >= (int)state->slideImages.count())
+        return 0;
+
+    int key = slideIndex;
+
+    QImage* img = state->slideImages.at(slideIndex);
+    bool empty = img ? img->isNull() : true;
+    if (empty) {
+        surfaceCache.remove(key);
+        imageHash.remove(slideIndex);
+        if (!blankSurface) {
+            int sw = state->slideWidth;
+            int sh = state->slideHeight;
+
+            QImage img = QImage(sw, sh, QImage::Format_RGB32);
+
+            QPainter painter(&img);
+            QPoint p1(sw*4 / 10, 0);
+            QPoint p2(sw*6 / 10, sh);
+            QLinearGradient linearGrad(p1, p2);
+            linearGrad.setColorAt(0, Qt::black);
+            linearGrad.setColorAt(1, Qt::white);
+            painter.setBrush(linearGrad);
+            painter.fillRect(0, 0, sw, sh, QBrush(linearGrad));
+
+            painter.setPen(QPen(QColor(64, 64, 64), 4));
+            painter.setBrush(QBrush());
+            painter.drawRect(2, 2, sw - 3, sh - 3);
+            painter.end();
+
+            blankSurface = prepareSurface(&img, sw, sh, bgcolor, state->reflectionEffect);
+        }
+        return blankSurface;
+    }
+
+    bool exist = imageHash.contains(slideIndex);
+    if (exist)
+        if (img == imageHash.find(slideIndex).value())
+            if (surfaceCache.contains(key))
+                return surfaceCache[key];
+
+    QImage* sr = prepareSurface(img, state->slideWidth, state->slideHeight, bgcolor, state->reflectionEffect);
+    surfaceCache.insert(key, sr);
+    imageHash.insert(slideIndex, img);
+
+    return sr;
+}
+
+// Renders a slide to offscreen buffer. Returns a rect of the rendered area.
+// col1 and col2 limit the column for rendering.
+QRect PictureFlowSoftwareRenderer::renderSlide(const SlideInfo &slide, int col1, int col2)
+{
+    int blend = slide.blend;
+    if (!blend)
+        return QRect();
+
+    QImage* src = surface(slide.slideIndex);
+    if (!src)
+        return QRect();
+
+    QRect rect(0, 0, 0, 0);
+
+    int sw = src->height();
+    int sh = src->width();
+    int h = buffer.height();
+    int w = buffer.width();
+
+    if (col1 > col2) {
+        int c = col2;
+        col2 = col1;
+        col1 = c;
+    }
+
+    col1 = (col1 >= 0) ? col1 : 0;
+    col2 = (col2 >= 0) ? col2 : w - 1;
+    col1 = qMin(col1, w - 1);
+    col2 = qMin(col2, w - 1);
+
+    int zoom = 100;
+    int distance = h * 100 / zoom;
+    PFreal sdx = fcos(slide.angle);
+    PFreal sdy = fsin(slide.angle);
+    PFreal xs = slide.cx - state->slideWidth * sdx / 2;
+    PFreal ys = slide.cy - state->slideWidth * sdy / 2;
+    PFreal dist = distance * PFREAL_ONE;
+
+    int xi = qMax((PFreal)0, (w * PFREAL_ONE / 2) + fdiv(xs * h, dist + ys) >> PFREAL_SHIFT);
+    if (xi >= w)
+        return rect;
+
+    bool flag = false;
+    rect.setLeft(xi);
+    for (int x = qMax(xi, col1); x <= col2; x++) {
+        PFreal hity = 0;
+        PFreal fk = rays[x];
+        if (sdy) {
+            fk = fk - fdiv(sdx, sdy);
+            hity = -fdiv((rays[x] * distance - slide.cx + slide.cy * sdx / sdy), fk);
+        }
+
+        dist = distance * PFREAL_ONE + hity;
+        if (dist < 0)
+            continue;
+
+        PFreal hitx = fmul(dist, rays[x]);
+        PFreal hitdist = fdiv(hitx - slide.cx, sdx);
+
+        int column = sw / 2 + (hitdist >> PFREAL_SHIFT);
+        if (column >= sw)
+            break;
+        if (column < 0)
+            continue;
+
+        rect.setRight(x);
+        if (!flag)
+            rect.setLeft(x);
+        flag = true;
+
+        int y1 = h / 2;
+        int y2 = y1 + 1;
+        QRgb* pixel1 = (QRgb*)(buffer.scanLine(y1)) + x;
+        QRgb* pixel2 = (QRgb*)(buffer.scanLine(y2)) + x;
+        QRgb pixelstep = pixel2 - pixel1;
+
+        int center = (sh / 2);
+        int dy = dist / h;
+        int p1 = center * PFREAL_ONE - dy / 2;
+        int p2 = center * PFREAL_ONE + dy / 2;
+
+        const QRgb *ptr = (const QRgb*)(src->scanLine(column));
+        if (blend == 256)
+            while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
+                *pixel1 = ptr[p1 >> PFREAL_SHIFT];
+                *pixel2 = ptr[p2 >> PFREAL_SHIFT];
+                p1 -= dy;
+                p2 += dy;
+                y1--;
+                y2++;
+                pixel1 -= pixelstep;
+                pixel2 += pixelstep;
+            }
+        else
+            while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
+                QRgb c1 = ptr[p1 >> PFREAL_SHIFT];
+                QRgb c2 = ptr[p2 >> PFREAL_SHIFT];
+                *pixel1 = blendColor(c1, bgcolor, blend);
+                *pixel2 = blendColor(c2, bgcolor, blend);
+                p1 -= dy;
+                p2 += dy;
+                y1--;
+                y2++;
+                pixel1 -= pixelstep;
+                pixel2 += pixelstep;
+            }
+    }
+
+    rect.setTop(0);
+    rect.setBottom(h - 1);
+    return rect;
+}
+
+void PictureFlowSoftwareRenderer::renderSlides()
+{
+    int nleft = state->leftSlides.count();
+    int nright = state->rightSlides.count();
+
+    QRect r = renderSlide(state->centerSlide);
+    int c1 = r.left();
+    int c2 = r.right();
+
+    for (int index = 0; index < nleft; index++) {
+        QRect rs = renderSlide(state->leftSlides[index], 0, c1 - 1);
+        if (!rs.isEmpty())
+            c1 = rs.left();
+    }
+    for (int index = 0; index < nright; index++) {
+        QRect rs = renderSlide(state->rightSlides[index], c2 + 1, buffer.width());
+        if (!rs.isEmpty())
+            c2 = rs.right();
+    }
+}
+
+// Render the slides. Updates only the offscreen buffer.
+void PictureFlowSoftwareRenderer::render()
+{
+    buffer.fill(state->backgroundColor);
+    renderSlides();
+    dirty = false;
+}
+
+// -----------------------------------------
+
+class PictureFlowPrivate
+{
+public:
+    PictureFlowState* state;
+    PictureFlowAnimator* animator;
+    PictureFlowAbstractRenderer* renderer;
+    QTimer triggerTimer;
+};
+
+
+PictureFlow::PictureFlow(QWidget* parent): QWidget(parent)
+{
+    d = new PictureFlowPrivate;
+
+    d->state = new PictureFlowState;
+    d->state->reset();
+    d->state->reposition();
+
+    d->renderer = new PictureFlowSoftwareRenderer;
+    d->renderer->state = d->state;
+    d->renderer->widget = this;
+    d->renderer->init();
+
+    d->animator = new PictureFlowAnimator;
+    d->animator->state = d->state;
+    QObject::connect(&d->animator->animateTimer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
+
+    QObject::connect(&d->triggerTimer, SIGNAL(timeout()), this, SLOT(render()));
+
+    setAttribute(Qt::WA_StaticContents, true);
+    setAttribute(Qt::WA_OpaquePaintEvent, true);
+    setAttribute(Qt::WA_NoSystemBackground, true);
+}
+
+PictureFlow::~PictureFlow()
+{
+    delete d->renderer;
+    delete d->animator;
+    delete d->state;
+    delete d;
+}
+
+int PictureFlow::slideCount() const
+{
+    return d->state->slideImages.count();
+}
+
+QColor PictureFlow::backgroundColor() const
+{
+    return QColor(d->state->backgroundColor);
+}
+
+void PictureFlow::setBackgroundColor(const QColor& c)
+{
+    d->state->backgroundColor = c.rgb();
+    triggerRender();
+}
+
+QSize PictureFlow::slideSize() const
+{
+    return QSize(d->state->slideWidth, d->state->slideHeight);
+}
+
+void PictureFlow::setSlideSize(QSize size)
+{
+    d->state->slideWidth = size.width();
+    d->state->slideHeight = size.height();
+    d->state->reposition();
+    triggerRender();
+}
+
+PictureFlow::ReflectionEffect PictureFlow::reflectionEffect() const
+{
+    return d->state->reflectionEffect;
+}
+
+void PictureFlow::setReflectionEffect(ReflectionEffect effect)
+{
+    d->state->reflectionEffect = effect;
+    triggerRender();
+}
+
+QImage PictureFlow::slide(int index) const
+{
+    QImage* i = 0;
+    if ((index >= 0) && (index < slideCount()))
+        i = d->state->slideImages[index];
+    return i ? QImage(*i) : QImage();
+}
+
+void PictureFlow::addSlide(const QImage& image)
+{
+    int c = d->state->slideImages.count();
+    d->state->slideImages.resize(c + 1);
+    d->state->slideImages[c] = new QImage(image);
+    triggerRender();
+}
+
+void PictureFlow::addSlide(const QPixmap& pixmap)
+{
+    addSlide(pixmap.toImage());
+}
+
+void PictureFlow::removeSlide(int index)
+{
+    int c = d->state->slideImages.count();
+    if (index >= 0 && index < c) {
+        d->state->slideImages.remove(index);
+        triggerRender();
+    }
+}
+
+void PictureFlow::setSlide(int index, const QImage& image)
+{
+    if ((index >= 0) && (index < slideCount())) {
+        QImage* i = image.isNull() ? 0 : new QImage(image);
+        delete d->state->slideImages[index];
+        d->state->slideImages[index] = i;
+        triggerRender();
+    }
+}
+
+void PictureFlow::setSlide(int index, const QPixmap& pixmap)
+{
+    setSlide(index, pixmap.toImage());
+}
+
+int PictureFlow::centerIndex() const
+{
+    return d->state->centerIndex;
+}
+
+void PictureFlow::setCenterIndex(int index)
+{
+    index = qMin(index, slideCount() - 1);
+    index = qMax(index, 0);
+    d->state->centerIndex = index;
+    d->state->reset();
+    d->animator->stop(index);
+    triggerRender();
+}
+
+void PictureFlow::clear()
+{
+    int c = d->state->slideImages.count();
+    for (int i = 0; i < c; i++)
+        delete d->state->slideImages[i];
+    d->state->slideImages.resize(0);
+
+    d->state->reset();
+    triggerRender();
+}
+
+void PictureFlow::render()
+{
+    d->renderer->dirty = true;
+    update();
+}
+
+void PictureFlow::triggerRender()
+{
+    d->triggerTimer.setSingleShot(true);
+    d->triggerTimer.start(0);
+}
+
+void PictureFlow::showPrevious()
+{
+    int step = d->animator->step;
+    int center = d->state->centerIndex;
+
+    if (step > 0)
+        d->animator->start(center);
+
+    if (step == 0)
+        if (center > 0)
+            d->animator->start(center - 1);
+
+    if (step < 0)
+        d->animator->target = qMax(0, center - 2);
+}
+
+void PictureFlow::showNext()
+{
+    int step = d->animator->step;
+    int center = d->state->centerIndex;
+
+    if (step < 0)
+        d->animator->start(center);
+
+    if (step == 0)
+        if (center < slideCount() - 1)
+            d->animator->start(center + 1);
+
+    if (step > 0)
+        d->animator->target = qMin(center + 2, slideCount() - 1);
+}
+
+void PictureFlow::showSlide(int index)
+{
+    index = qMax(index, 0);
+    index = qMin(slideCount() - 1, index);
+    if (index == d->state->centerSlide.slideIndex)
+        return;
+
+    d->animator->start(index);
+}
+
+void PictureFlow::keyPressEvent(QKeyEvent* event)
+{
+    if (event->key() == Qt::Key_Left) {
+        if (event->modifiers() == Qt::ControlModifier)
+            showSlide(centerIndex() - 10);
+        else
+            showPrevious();
+        event->accept();
+        return;
+    }
+
+    if (event->key() == Qt::Key_Right) {
+        if (event->modifiers() == Qt::ControlModifier)
+            showSlide(centerIndex() + 10);
+        else
+            showNext();
+        event->accept();
+        return;
+    }
+
+    event->ignore();
+}
+
+void PictureFlow::mousePressEvent(QMouseEvent* event)
+{
+    if (event->x() > width() / 2)
+        showNext();
+    else
+        showPrevious();
+}
+
+void PictureFlow::paintEvent(QPaintEvent* event)
+{
+    Q_UNUSED(event);
+    d->renderer->paint();
+}
+
+void PictureFlow::resizeEvent(QResizeEvent* event)
+{
+    triggerRender();
+    QWidget::resizeEvent(event);
+}
+
+void PictureFlow::updateAnimation()
+{
+    int old_center = d->state->centerIndex;
+    d->animator->update();
+    triggerRender();
+    if (d->state->centerIndex != old_center)
+        emit centerIndexChanged(d->state->centerIndex);
+}
+
diff --git a/modules/gui/qt4/util/pictureflow.hpp b/modules/gui/qt4/util/pictureflow.hpp
new file mode 100644 (file)
index 0000000..7c6f47b
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+  PictureFlow - animated image show widget
+  http://pictureflow.googlecode.com
+
+  Copyright (C) 2009 Ariya Hidayat (ariya@kde.org)
+  Copyright (C) 2008 Ariya Hidayat (ariya@kde.org)
+  Copyright (C) 2007 Ariya Hidayat (ariya@kde.org)
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+  THE SOFTWARE.
+*/
+
+#ifndef PICTUREFLOW_H
+#define PICTUREFLOW_H
+
+#include <qwidget.h>
+
+class PictureFlowPrivate;
+
+/*!
+  Class PictureFlow implements an image show widget with animation effect
+  like Apple's CoverFlow (in iTunes and iPod). Images are arranged in form
+  of slides, one main slide is shown at the center with few slides on
+  the left and right sides of the center slide. When the next or previous
+  slide is brought to the front, the whole slides flow to the right or
+  the right with smooth animation effect; until the new slide is finally
+  placed at the center.
+
+ */
+class PictureFlow : public QWidget
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
+    Q_PROPERTY(QSize slideSize READ slideSize WRITE setSlideSize)
+    Q_PROPERTY(int slideCount READ slideCount)
+    Q_PROPERTY(int centerIndex READ centerIndex WRITE setCenterIndex)
+
+public:
+
+    enum ReflectionEffect {
+        NoReflection,
+        PlainReflection,
+        BlurredReflection
+    };
+
+    /*!
+      Creates a new PictureFlow widget.
+    */
+    PictureFlow(QWidget* parent = 0);
+
+    /*!
+      Destroys the widget.
+    */
+    ~PictureFlow();
+
+    /*!
+      Returns the background color.
+    */
+    QColor backgroundColor() const;
+
+    /*!
+      Sets the background color. By default it is black.
+    */
+    void setBackgroundColor(const QColor& c);
+
+    /*!
+      Returns the dimension of each slide (in pixels).
+    */
+    QSize slideSize() const;
+
+    /*!
+      Sets the dimension of each slide (in pixels).
+    */
+    void setSlideSize(QSize size);
+
+    /*!
+      Returns the total number of slides.
+    */
+    int slideCount() const;
+
+    /*!
+      Returns QImage of specified slide.
+    */
+    QImage slide(int index) const;
+
+    /*!
+      Returns the index of slide currently shown in the middle of the viewport.
+    */
+    int centerIndex() const;
+
+    /*!
+      Returns the effect applied to the reflection.
+    */
+    ReflectionEffect reflectionEffect() const;
+
+    /*!
+      Sets the effect applied to the reflection. The default is PlainReflection.
+    */
+    void setReflectionEffect(ReflectionEffect effect);
+
+
+public slots:
+
+    /*!
+      Adds a new slide.
+    */
+    void addSlide(const QImage& image);
+
+    /*!
+      Adds a new slide.
+    */
+    void addSlide(const QPixmap& pixmap);
+
+    /*!
+      Removes an existing slide.
+    */
+    void removeSlide(int index);
+
+    /*!
+      Sets an image for specified slide. If the slide already exists,
+      it will be replaced.
+    */
+    void setSlide(int index, const QImage& image);
+
+    /*!
+      Sets a pixmap for specified slide. If the slide already exists,
+      it will be replaced.
+    */
+    void setSlide(int index, const QPixmap& pixmap);
+
+    /*!
+      Sets slide to be shown in the middle of the viewport. No animation
+      effect will be produced, unlike using showSlide.
+    */
+    void setCenterIndex(int index);
+
+    /*!
+      Clears all slides.
+    */
+    void clear();
+
+    /*!
+      Shows previous slide using animation effect.
+    */
+    void showPrevious();
+
+    /*!
+      Shows next slide using animation effect.
+    */
+    void showNext();
+
+    /*!
+      Go to specified slide using animation effect.
+    */
+    void showSlide(int index);
+
+    /*!
+      Rerender the widget. Normally this function will be automatically invoked
+      whenever necessary, e.g. during the transition animation.
+    */
+    void render();
+
+    /*!
+      Schedules a rendering update. Unlike render(), this function does not cause
+      immediate rendering.
+    */
+    void triggerRender();
+
+signals:
+    void centerIndexChanged(int index);
+
+protected:
+    void paintEvent(QPaintEvent *event);
+    void keyPressEvent(QKeyEvent* event);
+    void mousePressEvent(QMouseEvent* event);
+    void resizeEvent(QResizeEvent* event);
+
+private slots:
+    void updateAnimation();
+
+private:
+    PictureFlowPrivate* d;
+};
+
+#endif // PICTUREFLOW_H
+