Commit d6a48806 authored by Jean-Baptiste Kempf's avatar Jean-Baptiste Kempf

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: default avatarJean-Baptiste Kempf <jb@videolan.org>
parent f80154be
......@@ -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
......
......@@ -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++ )
{
......
......@@ -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 ****/
......
......@@ -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 )
......
......@@ -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
......@@ -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 <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) );
}
......@@ -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
/*
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);
}
/*
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment