From d6a48806e897f96dac741dd6f88d0e8ece701213 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kempf Date: Thu, 28 Oct 2010 14:33:27 +0200 Subject: [PATCH] Qt: initial pass for CoverFlow view of the playlist 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 --- modules/gui/qt4/Modules.am | 5 +- .../components/playlist/playlist_model.cpp | 24 +- .../components/playlist/playlist_model.hpp | 22 +- .../qt4/components/playlist/standardpanel.cpp | 42 +- .../qt4/components/playlist/standardpanel.hpp | 19 +- modules/gui/qt4/components/playlist/views.cpp | 103 +- modules/gui/qt4/components/playlist/views.hpp | 30 +- modules/gui/qt4/util/pictureflow.cpp | 1047 +++++++++++++++++ modules/gui/qt4/util/pictureflow.hpp | 202 ++++ 9 files changed, 1447 insertions(+), 47 deletions(-) create mode 100644 modules/gui/qt4/util/pictureflow.cpp create mode 100644 modules/gui/qt4/util/pictureflow.hpp diff --git a/modules/gui/qt4/Modules.am b/modules/gui/qt4/Modules.am index 2e616efb85..5225cf8da0 100644 --- a/modules/gui/qt4/Modules.am +++ b/modules/gui/qt4/Modules.am @@ -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 diff --git a/modules/gui/qt4/components/playlist/playlist_model.cpp b/modules/gui/qt4/components/playlist/playlist_model.cpp index 35ee685b74..9cb0de3d85 100644 --- a/modules/gui/qt4/components/playlist/playlist_model.cpp +++ b/modules/gui/qt4/components/playlist/playlist_model.cpp @@ -611,6 +611,9 @@ QPixmap PLModel::getArtPixmap( const QModelIndex & index, const QSize & size ) PLItem *item = static_cast( 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& 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++ ) { diff --git a/modules/gui/qt4/components/playlist/playlist_model.hpp b/modules/gui/qt4/components/playlist/playlist_model.hpp index 2147f22702..4fb16b9892 100644 --- a/modules/gui/qt4/components/playlist/playlist_model.hpp +++ b/modules/gui/qt4/components/playlist/playlist_model.hpp @@ -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 ****/ diff --git a/modules/gui/qt4/components/playlist/standardpanel.cpp b/modules/gui/qt4/components/playlist/standardpanel.cpp index 76af880827..98c8392300 100644 --- a/modules/gui/qt4/components/playlist/standardpanel.cpp +++ b/modules/gui/qt4/components/playlist/standardpanel.cpp @@ -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 ) diff --git a/modules/gui/qt4/components/playlist/standardpanel.hpp b/modules/gui/qt4/components/playlist/standardpanel.hpp index 5ff4908049..2143528020 100644 --- a/modules/gui/qt4/components/playlist/standardpanel.hpp +++ b/modules/gui/qt4/components/playlist/standardpanel.hpp @@ -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 diff --git a/modules/gui/qt4/components/playlist/views.cpp b/modules/gui/qt4/components/playlist/views.cpp index bb1715018f..e4b1e687b9 100644 --- a/modules/gui/qt4/components/playlist/views.cpp +++ b/modules/gui/qt4/components/playlist/views.cpp @@ -22,11 +22,10 @@ *****************************************************************************/ #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 #include #include #include @@ -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 +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) +{ + // 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) ); +} + diff --git a/modules/gui/qt4/components/playlist/views.hpp b/modules/gui/qt4/components/playlist/views.hpp index f648e18e11..b9313aa1ec 100644 --- a/modules/gui/qt4/components/playlist/views.hpp +++ b/modules/gui/qt4/components/playlist/views.hpp @@ -27,6 +27,8 @@ #include #include #include +#include +#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); + +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 index 0000000000..0bc83b8c9e --- /dev/null +++ b/modules/gui/qt4/util/pictureflow.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 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 slideImages; + + int angle; + int spacing; + PFreal offsetX; + PFreal offsetY; + + SlideInfo centerSlide; + QVector leftSlides; + QVector 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 rays; + QImage* blankSurface; + QCache surfaceCache; + QHash 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 index 0000000000..7c6f47b28f --- /dev/null +++ b/modules/gui/qt4/util/pictureflow.hpp @@ -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 + +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 + -- 2.39.5