diff --git a/src/gui/qxtflowview.cpp b/src/gui/qxtflowview.cpp index 1debf918..6ff58cfd 100644 --- a/src/gui/qxtflowview.cpp +++ b/src/gui/qxtflowview.cpp @@ -1,766 +1,735 @@ /**************************************************************************** ** ** Copyright (C) Qxt Foundation. Some rights reserved. ** ** This file is part of the QxtGui module of the Qxt library. ** ** This library is free software; you can redistribute it and/or modify it ** under the terms of the Common Public License, version 1.0, as published ** by IBM, and/or under the terms of the GNU Lesser General Public License, ** version 2.1, as published by the Free Software Foundation. ** ** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY ** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY ** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR ** FITNESS FOR A PARTICULAR PURPOSE. ** ** You should have received a copy of the CPL and the LGPL along with this ** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files ** included with the source distribution for more information. ** If you did not receive a copy of the licenses, contact the Qxt Foundation. ** ** ** ** ** This is a derived work of PictureFlow (http://pictureflow.googlecode.com) ** The original code was distributed under the following terms: ** ** 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 "qxtflowview_p.h" #include /*! \class QxtFlowView \inmodule QxtGui \brief The QxtFlowView widget is an item view for images with impressive flow effects A widget for showin images with animation effects, like Apple's Cover Flow (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 left or to the right with smooth animation effect; until the new slide is finally placed at the center. \image qxtflowview.png "QxtFlowView in action." This is a derived work of \l{http://pictureflow.googlecode.com}{PictureFlow} */ /*! \enum QxtFlowView::ReflectionEffect \brief This enum describes available reflection effects. \value NoReflection No reflection \value PlainReflection Plain reflection \value BlurredReflection Blurred reflection */ /*! \fn QxtFlowView::currentIndexChanged(QModelIndex index) This signal is emitted whenever the current \a index has changed. */ /*! Constructs a new QxtFlowView with \a parent. */ QxtFlowView::QxtFlowView(QWidget* parent): QWidget(parent) { d = new QxtFlowViewPrivate; d->model = 0; d->picrole = Qt::DecorationRole; d->textrole = Qt::DisplayRole; d->piccolumn = 0; d->textcolumn = 0; d->state = new QxtFlowViewState; d->state->reset(); d->state->reposition(); d->renderer = new QxtFlowViewSoftwareRenderer; d->renderer->state = d->state; d->renderer->widget = this; d->renderer->init(); d->animator = new QxtFlowViewAnimator; 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); } /*! Destructs the flow view. */ QxtFlowView::~QxtFlowView() { delete d->renderer; delete d->animator; delete d->state; delete d; } /*! Sets the \a model. \bold {Note:} The view does not take ownership of the model unless it is the model's parent object because it may be shared between many different views. */ void QxtFlowView::setModel(QAbstractItemModel * model) { d->setModel(model); } /*! Returns the model. */ QAbstractItemModel * QxtFlowView::model() { return d->model; } /*! \property QxtFlowView::backgroundColor \brief the background color The default value is black. */ QColor QxtFlowView::backgroundColor() const { return QColor(d->state->backgroundColor); } void QxtFlowView::setBackgroundColor(const QColor& c) { d->state->backgroundColor = c.rgb(); triggerRender(); } /*! \property QxtFlowView::slideSize \brief the slide size The slide dimensions are in pixels. The default value is 150x200. */ QSize QxtFlowView::slideSize() const { return QSize(d->state->slideWidth, d->state->slideHeight); } void QxtFlowView::setSlideSize(QSize size) { d->state->slideWidth = size.width(); d->state->slideHeight = size.height(); d->state->reposition(); triggerRender(); } /*! \property QxtFlowView::reflectionEffect \brief the reflection effect The default value is PlainReflection. */ QxtFlowView::ReflectionEffect QxtFlowView::reflectionEffect() const { return d->state->reflectionEffect; } void QxtFlowView::setReflectionEffect(ReflectionEffect effect) { d->state->reflectionEffect = effect; d->reset(); } /*! \property QxtFlowView::pictureRole \brief the picture role The default value is Qt::DecorationRole. */ int QxtFlowView::pictureRole() { return d->picrole; } void QxtFlowView::setPictureRole(int a) { d->picrole = a; d->reset(); } /*! \property QxtFlowView::pictureColumn \brief the picture column The default value is \c 0. */ int QxtFlowView::pictureColumn() { return d->piccolumn; } void QxtFlowView::setPictureColumn(int a) { d->piccolumn = a; d->reset(); } #if 0 int QxtFlowView::textRole() { return d->textrole; } void QxtFlowView::setTextRole(int a) { d->textrole = a; d->reset(); } int QxtFlowView::textColumn() { return d->textcolumn; } void QxtFlowView::setTextColumn(int a) { d->textcolumn = a; d->reset(); } #endif /*! \property QxtFlowView::rootIndex \brief the root index The root index is the parent index to the view's toplevel items. The root can be invalid. */ QModelIndex QxtFlowView::rootIndex() const { return d->rootindex; } void QxtFlowView::setRootIndex(QModelIndex index) { d->rootindex = index; } /*! \property QxtFlowView::currentIndex \brief the current index The slide of the current index is shown in the middle of the viewport. \bold {Note:} No animation effect will be produced. \sa showSlide() */ QModelIndex QxtFlowView::currentIndex() const { if (!d->model) return QModelIndex(); return d->currentcenter; } void QxtFlowView::setCurrentIndex(QModelIndex index) { d->setCurrentIndex(index); } /*! Rerender the widget. Normally this function will be automatically invoked whenever necessary, e.g. during the transition animation. */ void QxtFlowView::render() { d->renderer->dirty = true; update(); } /*! Schedules a rendering update. Unlike render(), this function does not cause immediate rendering.*/ void QxtFlowView::triggerRender() { d->triggerRender(); } /*! Shows previous slide using animation effect. */ void QxtFlowView::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); } /*! Shows next slide using animation effect. */ void QxtFlowView::showNext() { int step = d->animator->step; int center = d->state->centerIndex; if (step < 0) d->animator->start(center); if (step == 0) if (center < d->state->slideImages.count() - 1) d->animator->start(center + 1); if (step > 0) d->animator->target = qMin(center + 2, d->state->slideImages.count() - 1); } /*! Go to specified slide at \a index using animation effect. */ void QxtFlowView::showSlide(QModelIndex index) { int r = d->modelmap.indexOf(index); if (r < 0) return; d->showSlide(r); } /*! \reimp */ void QxtFlowView::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Left) { if (event->modifiers() == Qt::ControlModifier) d->showSlide(currentIndex().row() - 10); else showPrevious(); event->accept(); return; } if (event->key() == Qt::Key_Right) { if (event->modifiers() == Qt::ControlModifier) d->showSlide(currentIndex().row() + 10); else showNext(); event->accept(); return; } event->ignore(); } /*! \reimp */ void QxtFlowView::mousePressEvent(QMouseEvent* event) { d->lastgrabpos = event->pos(); } /*! \reimp */ void QxtFlowView::mouseMoveEvent(QMouseEvent * event) { int i = (event->pos() - d->lastgrabpos).x() / (d->state->slideWidth / 4); if (i > 0) { showPrevious(); d->lastgrabpos = event->pos(); } if (i < 0) { showNext(); d->lastgrabpos = event->pos(); } - } /*! \reimp */ void QxtFlowView::mouseReleaseEvent(QMouseEvent* event) { Q_UNUSED(event); - } /*! \reimp */ void QxtFlowView::paintEvent(QPaintEvent* event) { Q_UNUSED(event); d->renderer->paint(); } /*! \reimp */ void QxtFlowView::resizeEvent(QResizeEvent* event) { triggerRender(); QWidget::resizeEvent(event); } /*! \reimp */ void QxtFlowView::wheelEvent(QWheelEvent * event) { - if (event->orientation() == Qt::Horizontal) { event->ignore(); } else { int numSteps = -((event->delta() / 8) / 15); if (numSteps > 0) { for (int i = 0;i < numSteps;i++) { showNext(); } } else { for (int i = numSteps;i < 0;i++) { showPrevious(); } } event->accept(); } - - } /*! \internal */ void QxtFlowView::updateAnimation() { int old_center = d->state->centerIndex; d->animator->update(); triggerRender(); if (d->state->centerIndex != old_center) { d->currentcenter = d->modelmap.at(d->state->centerIndex); emit currentIndexChanged(d->currentcenter); } } - - - - - - - - void QxtFlowViewPrivate::columnsAboutToBeInserted(const QModelIndex & parent, int start, int end) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); } void QxtFlowViewPrivate::columnsAboutToBeRemoved(const QModelIndex & parent, int start, int end) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); } void QxtFlowViewPrivate::columnsInserted(const QModelIndex & parent, int start, int end) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); } void QxtFlowViewPrivate::columnsRemoved(const QModelIndex & parent, int start, int end) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); } void QxtFlowViewPrivate::dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight) { Q_UNUSED(topLeft); Q_UNUSED(bottomRight); if (topLeft.parent() != rootindex) return; if (bottomRight.parent() != rootindex) return; int start = topLeft.row(); int end = bottomRight.row(); for (int i = start;i <= end;i++) replaceSlide(i, qvariant_cast(model->data(model->index(i, piccolumn, rootindex), picrole))); } void QxtFlowViewPrivate::headerDataChanged(Qt::Orientation orientation, int first, int last) { Q_UNUSED(orientation); Q_UNUSED(first); Q_UNUSED(last); - - - - - } void QxtFlowViewPrivate::layoutAboutToBeChanged() { - } void QxtFlowViewPrivate::layoutChanged() { reset(); setCurrentIndex(currentcenter); } void QxtFlowViewPrivate::modelAboutToBeReset() { } void QxtFlowViewPrivate::modelReset() { reset(); } void QxtFlowViewPrivate::rowsAboutToBeInserted(const QModelIndex & parent, int start, int end) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); } void QxtFlowViewPrivate::rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) { Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); } void QxtFlowViewPrivate::rowsInserted(const QModelIndex & parent, int start, int end) { if (rootindex != parent) return; for (int i = start;i <= end;i++) { QModelIndex idx = model->index(i, piccolumn, rootindex); insertSlide(i, qvariant_cast(model->data(idx, picrole))); modelmap.insert(i, idx); } } void QxtFlowViewPrivate::rowsRemoved(const QModelIndex & parent, int start, int end) { if (rootindex != parent) return; for (int i = start;i <= end;i++) { removeSlide(i); modelmap.removeAt(i); } - } void QxtFlowViewPrivate::setModel(QAbstractItemModel * m) { - - if (model) { disconnect(this->model, SIGNAL(columnsAboutToBeInserted(const QModelIndex & , int , int)), this, SLOT(columnsAboutToBeInserted(const QModelIndex & , int , int))); disconnect(this->model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex & , int , int)), this, SLOT(columnsAboutToBeRemoved(const QModelIndex & , int , int))); disconnect(this->model, SIGNAL(columnsInserted(const QModelIndex & , int , int)), this, SLOT(columnsInserted(const QModelIndex & , int , int))); disconnect(this->model, SIGNAL(columnsRemoved(const QModelIndex & , int , int)), this, SLOT(columnsRemoved(const QModelIndex & , int , int))); disconnect(this->model, SIGNAL(dataChanged(const QModelIndex & , const QModelIndex &)), this, SLOT(dataChanged(const QModelIndex & , const QModelIndex &))); disconnect(this->model, SIGNAL(headerDataChanged(Qt::Orientation , int , int)), this, SLOT(headerDataChanged(Qt::Orientation , int , int))); disconnect(this->model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); disconnect(this->model, SIGNAL(layoutChanged()), this, SLOT(layoutChanged())); disconnect(this->model, SIGNAL(modelAboutToBeReset()), this, SLOT(modelAboutToBeReset())); disconnect(this->model, SIGNAL(modelReset()), this, SLOT(modelReset())); disconnect(this->model, SIGNAL(rowsAboutToBeInserted(const QModelIndex & , int , int)), this, SLOT(rowsAboutToBeInserted(const QModelIndex & , int , int))); disconnect(this->model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex & , int , int)), this, SLOT(rowsAboutToBeRemoved(const QModelIndex & , int , int))); disconnect(this->model, SIGNAL(rowsInserted(const QModelIndex & , int , int)), this, SLOT(rowsInserted(const QModelIndex & , int , int))); disconnect(this->model, SIGNAL(rowsRemoved(const QModelIndex & , int , int)), this, SLOT(rowsRemoved(const QModelIndex & , int , int))); } model = m; if (model) { rootindex = model->parent(QModelIndex()); connect(this->model, SIGNAL(columnsAboutToBeInserted(const QModelIndex & , int , int)), this, SLOT(columnsAboutToBeInserted(const QModelIndex & , int , int))); connect(this->model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex & , int , int)), this, SLOT(columnsAboutToBeRemoved(const QModelIndex & , int , int))); connect(this->model, SIGNAL(columnsInserted(const QModelIndex & , int , int)), this, SLOT(columnsInserted(const QModelIndex & , int , int))); connect(this->model, SIGNAL(columnsRemoved(const QModelIndex & , int , int)), this, SLOT(columnsRemoved(const QModelIndex & , int , int))); connect(this->model, SIGNAL(dataChanged(const QModelIndex & , const QModelIndex &)), this, SLOT(dataChanged(const QModelIndex & , const QModelIndex &))); connect(this->model, SIGNAL(headerDataChanged(Qt::Orientation , int , int)), this, SLOT(headerDataChanged(Qt::Orientation , int , int))); connect(this->model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); connect(this->model, SIGNAL(layoutChanged()), this, SLOT(layoutChanged())); connect(this->model, SIGNAL(modelAboutToBeReset()), this, SLOT(modelAboutToBeReset())); connect(this->model, SIGNAL(modelReset()), this, SLOT(modelReset())); connect(this->model, SIGNAL(rowsAboutToBeInserted(const QModelIndex & , int , int)), this, SLOT(rowsAboutToBeInserted(const QModelIndex & , int , int))); connect(this->model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex & , int , int)), this, SLOT(rowsAboutToBeRemoved(const QModelIndex & , int , int))); connect(this->model, SIGNAL(rowsInserted(const QModelIndex & , int , int)), this, SLOT(rowsInserted(const QModelIndex & , int , int))); connect(this->model, SIGNAL(rowsRemoved(const QModelIndex & , int , int)), this, SLOT(rowsRemoved(const QModelIndex & , int , int))); } reset(); } - void QxtFlowViewPrivate::clear() { int c = state->slideImages.count(); for (int i = 0; i < c; i++) delete state->slideImages[i]; state->slideImages.resize(0); state->reset(); modelmap.clear(); triggerRender(); } - void QxtFlowViewPrivate::triggerRender() { triggerTimer.setSingleShot(true); triggerTimer.start(0); } - - void QxtFlowViewPrivate::insertSlide(int index, const QImage& image) { state->slideImages.insert(index, new QImage(image)); triggerRender(); } void QxtFlowViewPrivate::replaceSlide(int index, const QImage& image) { Q_ASSERT((index >= 0) && (index < state->slideImages.count())); QImage* i = image.isNull() ? 0 : new QImage(image); delete state->slideImages[index]; state->slideImages[index] = i; triggerRender(); } void QxtFlowViewPrivate::removeSlide(int index) { delete state->slideImages[index]; state->slideImages.remove(index); triggerRender(); } - void QxtFlowViewPrivate::showSlide(int index) { if (index == state->centerSlide.slideIndex) return; animator->start(index); } - - void QxtFlowViewPrivate::reset() { clear(); if (model) { for (int i = 0;i < model->rowCount(rootindex);i++) { QModelIndex idx = model->index(i, piccolumn, rootindex); insertSlide(i, qvariant_cast(model->data(idx, picrole))); modelmap.insert(i, idx); } if(modelmap.count()) currentcenter=modelmap.at(0); else currentcenter=QModelIndex(); } triggerRender(); } - - void QxtFlowViewPrivate::setCurrentIndex(QModelIndex index) { if (model->parent(index) != rootindex) return; int r = modelmap.indexOf(index); if (r < 0) return; state->centerIndex = r; state->reset(); animator->stop(r); triggerRender(); } diff --git a/src/gui/qxtflowview_p.cpp b/src/gui/qxtflowview_p.cpp index ee68a664..a1cbc006 100644 --- a/src/gui/qxtflowview_p.cpp +++ b/src/gui/qxtflowview_p.cpp @@ -1,714 +1,706 @@ /**************************************************************************** ** ** Copyright (C) Qxt Foundation. Some rights reserved. ** ** This file is part of the QxtGui module of the Qxt library. ** ** This library is free software; you can redistribute it and/or modify it ** under the terms of the Common Public License, version 1.0, as published ** by IBM, and/or under the terms of the GNU Lesser General Public License, ** version 2.1, as published by the Free Software Foundation. ** ** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY ** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY ** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR ** FITNESS FOR A PARTICULAR PURPOSE. ** ** You should have received a copy of the CPL and the LGPL along with this ** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files ** included with the source distribution for more information. ** If you did not receive a copy of the licenses, contact the Qxt Foundation. ** ** ** ** ** This is a derived work of QxtFlowView (http://pictureflow.googlecode.com) ** The original code was distributed under the following terms: ** ** 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 "qxtflowview_p.h" // ------------- QxtFlowViewState --------------------------------------- QxtFlowViewState::QxtFlowViewState(): backgroundColor(0), slideWidth(150), slideHeight(200), reflectionEffect(QxtFlowView::BlurredReflection), centerIndex(0) { } QxtFlowViewState::~QxtFlowViewState() { for (int i = 0; i < (int)slideImages.count(); i++) delete slideImages[i]; } // readjust the settings, call this when slide dimension is changed void QxtFlowViewState::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 QxtFlowViewState::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; } } // ------------- QxtFlowViewAnimator --------------------------------------- QxtFlowViewAnimator::QxtFlowViewAnimator(): state(0), target(0), step(0), frame(0) { } void QxtFlowViewAnimator::start(int slide) { target = slide; if (!animateTimer.isActive() && state) { step = (target < state->centerSlide.slideIndex) ? -1 : 1; animateTimer.start(30); } } void QxtFlowViewAnimator::stop(int slide) { step = 0; target = slide; frame = slide << 16; animateTimer.stop(); } void QxtFlowViewAnimator::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; } } // ------------- QxtFlowViewSoftwareRenderer --------------------------------------- QxtFlowViewSoftwareRenderer::QxtFlowViewSoftwareRenderer(): QxtFlowViewAbstractRenderer(), size(0, 0), bgcolor(0), effect(-1), blankSurface(0) { } QxtFlowViewSoftwareRenderer::~QxtFlowViewSoftwareRenderer() { surfaceCache.clear(); buffer = QImage(); delete blankSurface; } void QxtFlowViewSoftwareRenderer::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 QxtFlowViewSoftwareRenderer::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; #ifdef PICTUREFLOW_QT4 buffer = QImage(ww, wh, QImage::Format_RGB32); #endif #if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) buffer.create(ww, wh, 32); #endif 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, QxtFlowView::ReflectionEffect reflectionEffect) { #ifdef PICTUREFLOW_QT4 Qt::TransformationMode mode = Qt::SmoothTransformation; QImage img = slideImage->scaled(w, h, Qt::IgnoreAspectRatio, mode); #endif #if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) QImage img = slideImage->smoothScale(w, h); #endif // slightly larger, to accommodate for the reflection int hs = h * 2; int hofs = h / 3; // offscreen buffer: black is sweet #ifdef PICTUREFLOW_QT4 QImage* result = new QImage(hs, w, QImage::Format_RGB32); #endif #if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) QImage* result = new QImage; result->create(hs, w, 32); #endif 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 != QxtFlowView::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 == QxtFlowView::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* QxtFlowViewSoftwareRenderer::surface(int slideIndex) { if (!state) return 0; if (slideIndex < 0) return 0; if (slideIndex >= (int)state->slideImages.count()) return 0; #ifdef PICTUREFLOW_QT4 int key = slideIndex; #endif #if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) QString key = QString::number(slideIndex); #endif 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; #ifdef PICTUREFLOW_QT4 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(); #endif #if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) QPixmap pixmap(sw, sh, 32); QPainter painter(&pixmap); painter.fillRect(pixmap.rect(), QColor(192, 192, 192)); painter.fillRect(5, 5, sw - 10, sh - 10, QColor(64, 64, 64)); painter.end(); QImage img = pixmap.convertToImage(); #endif blankSurface = prepareSurface(&img, sw, sh, bgcolor, state->reflectionEffect); } return blankSurface; } #ifdef PICTUREFLOW_QT4 bool exist = imageHash.contains(slideIndex); if (exist) if (img == imageHash.find(slideIndex).value()) #endif #ifdef PICTUREFLOW_QT3 bool exist = imageHash.find(slideIndex) != imageHash.end(); if (exist) if (img == imageHash.find(slideIndex).data()) #endif #ifdef PICTUREFLOW_QT2 if (img == imageHash[slideIndex]) #endif 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 QxtFlowViewSoftwareRenderer::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 QxtFlowViewSoftwareRenderer::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 QxtFlowViewSoftwareRenderer::render() { buffer.fill(state->backgroundColor); renderSlides(); dirty = false; } - - - - - - - -