• Skip to content
  • Skip to link menu
KDE 4.7 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KIO

delegateanimationhandler.cpp
Go to the documentation of this file.
00001 /*
00002    This file is part of the KDE project
00003 
00004    Copyright © 2007 Fredrik Höglund <fredrik@kde.org>
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License as published by the Free Software Foundation; either
00009    version 2 of the License, or (at your option) any later version.
00010 
00011    This library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this library; see the file COPYING.LIB.  If not, write to
00018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019    Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include "delegateanimationhandler_p.h"
00023 
00024 #include <QListView>
00025 #include <QAbstractItemView>
00026 #include <QPersistentModelIndex>
00027 #include <QTime>
00028 #include <QDebug>
00029 
00030 #include <cmath>
00031 #include "kdirmodel.h"
00032 #include <kdebug.h>
00033 #include <qabstractproxymodel.h>
00034 
00035 #include "delegateanimationhandler_p.moc"
00036 
00037 namespace KIO
00038 {
00039 
00040 // Needed because state() is a protected method
00041 class ProtectedAccessor : public QAbstractItemView
00042 {
00043 public:
00044     bool draggingState() const { return state() == DraggingState; }
00045 };
00046 
00047 // Debug output is disabled by default, use kdebugdialog to enable it
00048 static int animationDebugArea() { static int s_area = KDebug::registerArea("kio (delegateanimationhandler)", false);
00049                                   return s_area; }
00050 
00051 // ---------------------------------------------------------------------------
00052 
00053 
00054 
00055 CachedRendering::CachedRendering(QStyle::State state, const QSize &size, QModelIndex index)
00056     : state(state), regular(QPixmap(size)), hover(QPixmap(size)), valid(true), validityIndex(index)
00057 {
00058     regular.fill(Qt::transparent);
00059     hover.fill(Qt::transparent);
00060 
00061     if (index.model())
00062     {
00063       connect(index.model(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
00064               SLOT(dataChanged(const QModelIndex &, const QModelIndex &)));
00065       connect(index.model(), SIGNAL(modelReset()), SLOT(modelReset()));
00066     }
00067 }
00068 
00069 void CachedRendering::dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight)
00070 {
00071     if (validityIndex.row() >= topLeft.row() && validityIndex.column() >= topLeft.column() &&
00072         validityIndex.row() <= bottomRight.row() && validityIndex.column() <= bottomRight.column())
00073         valid = false;
00074 }
00075 
00076 void CachedRendering::modelReset()
00077 {
00078     valid = false;
00079 }
00080 
00081 // ---------------------------------------------------------------------------
00082 
00083 
00084 
00085 AnimationState::AnimationState(const QModelIndex &index)
00086         : index(index), direction(QTimeLine::Forward),
00087           animating(false), jobAnimation(false), progress(0.0), m_fadeProgress(1.0),
00088           m_jobAnimationAngle(0.0), renderCache(NULL), fadeFromRenderCache(NULL)
00089 {
00090     creationTime.start();
00091 }
00092 
00093 
00094 AnimationState::~AnimationState()
00095 {
00096     delete renderCache;
00097     delete fadeFromRenderCache;
00098 }
00099 
00100 
00101 bool AnimationState::update()
00102 {
00103     const qreal runtime = (direction == QTimeLine::Forward ? 150 : 250); // milliseconds
00104     const qreal increment = 1000. / runtime / 1000.;
00105     const qreal delta = increment * time.restart();
00106 
00107     if (direction == QTimeLine::Forward)
00108     {
00109         progress = qMin(qreal(1.0), progress + delta);
00110         animating = (progress < 1.0);
00111     }
00112     else
00113     {
00114         progress = qMax(qreal(0.0), progress - delta);
00115         animating = (progress > 0.0);
00116     }
00117 
00118 
00119     if (fadeFromRenderCache)
00120     {
00121         //Icon fading goes always forwards
00122         m_fadeProgress = qMin(qreal(1.0), m_fadeProgress + delta);
00123         animating |= (m_fadeProgress < 1.0);
00124         if (m_fadeProgress == 1)
00125             setCachedRenderingFadeFrom(0);
00126     }
00127 
00128     if (jobAnimation)
00129     {
00130         m_jobAnimationAngle += 1.0;
00131         if (m_jobAnimationAngle == 360)
00132             m_jobAnimationAngle = 0;
00133 
00134         if (index.model()->data(index, KDirModel::HasJobRole).toBool())
00135         {
00136             animating = true;
00137             //there is a job here still...
00138             return false;
00139         }
00140         else
00141         {
00142             animating = false;
00143             //there's no job here anymore, return true so we stop painting this.
00144             return true;
00145         }
00146     }
00147     else
00148     {
00149         return !animating;
00150     }
00151 }
00152 
00153 qreal AnimationState::hoverProgress() const
00154 {
00155 #ifndef M_PI_2
00156 #define M_PI_2 1.57079632679489661923
00157 #endif
00158     return qRound(255.0 * std::sin(progress * M_PI_2)) / 255.0;
00159 }
00160 
00161 qreal AnimationState::fadeProgress() const
00162 {
00163     return qRound(255.0 * std::sin(m_fadeProgress * M_PI_2)) / 255.0;
00164 }
00165 
00166 qreal AnimationState::jobAnimationAngle() const
00167 {
00168     return m_jobAnimationAngle;
00169 }
00170 
00171 bool AnimationState::hasJobAnimation() const
00172 {
00173     return jobAnimation;
00174 }
00175 
00176 void AnimationState::setJobAnimation(bool value)
00177 {
00178     jobAnimation = value;
00179 }
00180 
00181 // ---------------------------------------------------------------------------
00182 
00183 static const int switchIconInterval = 1000; 
00184 
00185 DelegateAnimationHandler::DelegateAnimationHandler(QObject *parent)
00186     : QObject(parent)
00187 {
00188     iconSequenceTimer.setSingleShot(true);
00189     iconSequenceTimer.setInterval(switchIconInterval);
00190     connect(&iconSequenceTimer, SIGNAL(timeout()), SLOT(sequenceTimerTimeout()));;
00191 }
00192 
00193 DelegateAnimationHandler::~DelegateAnimationHandler()
00194 {
00195     timer.stop();
00196 
00197     QMapIterator<const QAbstractItemView*, AnimationList*> i(animationLists);
00198     while (i.hasNext())
00199     {
00200         i.next();
00201         qDeleteAll(*i.value());
00202         delete i.value();
00203     }
00204     animationLists.clear();
00205 }
00206 
00207 void DelegateAnimationHandler::sequenceTimerTimeout()
00208 {
00209     QAbstractItemModel* model = const_cast<QAbstractItemModel*>(sequenceModelIndex.model());
00210     QAbstractProxyModel* proxy = qobject_cast<QAbstractProxyModel*>(model);
00211     QModelIndex index = sequenceModelIndex;
00212 
00213     if (proxy)
00214     {
00215         index = proxy->mapToSource(index);
00216         model = proxy->sourceModel();
00217     }
00218 
00219     KDirModel* dirModel = dynamic_cast<KDirModel*>(model);
00220     if (dirModel)
00221     {
00222         kDebug(animationDebugArea()) << "requesting" << currentSequenceIndex;
00223         dirModel->requestSequenceIcon(index, currentSequenceIndex);
00224         iconSequenceTimer.start(); // Some upper-bound interval is needed, in case items are not generated
00225     }
00226 }
00227 
00228 void DelegateAnimationHandler::gotNewIcon(const QModelIndex& index)
00229 {
00230     Q_UNUSED(index);
00231 
00232     kDebug(animationDebugArea()) << currentSequenceIndex;
00233     if (sequenceModelIndex.isValid() && currentSequenceIndex)
00234         iconSequenceTimer.start();
00235 //   if(index ==sequenceModelIndex) //Leads to problems
00236     ++currentSequenceIndex;
00237 }
00238 
00239 void DelegateAnimationHandler::setSequenceIndex(int sequenceIndex)
00240 {
00241     kDebug(animationDebugArea()) << sequenceIndex;
00242 
00243     if (sequenceIndex > 0)
00244     {
00245         currentSequenceIndex = sequenceIndex;
00246         iconSequenceTimer.start();
00247     }
00248     else
00249     {
00250         currentSequenceIndex = 0;
00251         sequenceTimerTimeout(); //Set the icon back to the standard one
00252         currentSequenceIndex = 0; //currentSequenceIndex was incremented, set it back to 0
00253         iconSequenceTimer.stop();
00254     }
00255 }
00256 
00257 void DelegateAnimationHandler::eventuallyStartIteration(QModelIndex index)
00258 {
00259 //      if (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) {
00261 
00262     if (sequenceModelIndex.isValid())
00263         setSequenceIndex(0); // Stop old iteration, and reset the icon for the old iteration
00264 
00265     // Start sequence iteration
00266     sequenceModelIndex = index;
00267     setSequenceIndex(1);
00268 //      }
00269 }
00270 
00271 AnimationState *DelegateAnimationHandler::animationState(const QStyleOption &option,
00272                                                          const QModelIndex &index,
00273                                                          const QAbstractItemView *view)
00274 {
00275     // We can't do animations reliably when an item is being dragged, since that
00276     // item will be drawn in two locations at the same time and hovered in one and
00277     // not the other. We can't tell them apart because they both have the same index.
00278     if (!view || static_cast<const ProtectedAccessor*>(view)->draggingState())
00279         return NULL;
00280 
00281     AnimationState *state = findAnimationState(view, index);
00282     bool hover = option.state & QStyle::State_MouseOver;
00283 
00284     // If the cursor has entered an item
00285     if (!state && hover)
00286     {
00287         state = new AnimationState(index);
00288         addAnimationState(state, view);
00289 
00290         if (!fadeInAddTime.isValid() ||
00291             (fadeInAddTime.isValid() && fadeInAddTime.elapsed() > 300))
00292         {
00293             startAnimation(state);
00294         }
00295         else
00296         {
00297             state->animating = false;
00298             state->progress  = 1.0;
00299             state->direction = QTimeLine::Forward;
00300         }
00301 
00302         fadeInAddTime.restart();
00303 
00304         eventuallyStartIteration(index);
00305     }
00306     else if (state)
00307     {
00308         // If the cursor has exited an item
00309         if (!hover && (!state->animating || state->direction == QTimeLine::Forward))
00310         {
00311             state->direction = QTimeLine::Backward;
00312 
00313             if (state->creationTime.elapsed() < 200)
00314                 state->progress = 0.0;
00315 
00316             startAnimation(state);
00317 
00318             // Stop sequence iteration
00319             if (index == sequenceModelIndex)
00320             {
00321                 setSequenceIndex(0);
00322                 sequenceModelIndex = QPersistentModelIndex();
00323             }
00324         }
00325         else if (hover && state->direction == QTimeLine::Backward)
00326         {
00327             // This is needed to handle the case where an item is dragged within
00328             // the view, and dropped in a different location. State_MouseOver will
00329             // initially not be set causing a "hover out" animation to start.
00330             // This reverses the direction as soon as we see the bit being set.
00331             state->direction = QTimeLine::Forward;
00332 
00333             if (!state->animating)
00334                 startAnimation(state);
00335 
00336             eventuallyStartIteration(index);
00337         }
00338     }
00339     else if (!state && index.model()->data(index, KDirModel::HasJobRole).toBool())
00340     {
00341         state = new AnimationState(index);
00342         addAnimationState(state, view);
00343         startAnimation(state);
00344         state->setJobAnimation(true);
00345     }
00346 
00347     return state;
00348 }
00349 
00350 
00351 AnimationState *DelegateAnimationHandler::findAnimationState(const QAbstractItemView *view,
00352                                                              const QModelIndex &index) const
00353 {
00354     // Try to find a list of animation states for the view
00355     AnimationList *list = animationLists.value(view);
00356 
00357     if (list)
00358     {
00359         foreach (AnimationState *state, *list)
00360             if (state->index == index)
00361                 return state;
00362     }
00363 
00364     return NULL;
00365 }
00366 
00367 
00368 void DelegateAnimationHandler::addAnimationState(AnimationState *state, const QAbstractItemView *view)
00369 {
00370     AnimationList *list = animationLists.value(view);
00371 
00372     // If this is the first time we've seen this view
00373     if (!list)
00374     {
00375         connect(view, SIGNAL(destroyed(QObject*)), SLOT(viewDeleted(QObject*)));
00376 
00377         list = new AnimationList;
00378         animationLists.insert(view, list);
00379     }
00380 
00381     list->append(state);
00382 }
00383 
00384 void DelegateAnimationHandler::restartAnimation(AnimationState *state)
00385 {
00386     startAnimation(state);
00387 }
00388 
00389 void DelegateAnimationHandler::startAnimation(AnimationState *state)
00390 {
00391     state->time.start();
00392     state->animating = true;
00393 
00394     if (!timer.isActive())
00395         timer.start(1000 / 30, this); // 30 fps
00396 }
00397 
00398 int DelegateAnimationHandler::runAnimations(AnimationList *list, const QAbstractItemView *view)
00399 {
00400     int activeAnimations = 0;
00401     QRegion region;
00402 
00403     QMutableLinkedListIterator<AnimationState*> i(*list);
00404     while (i.hasNext())
00405     {
00406         AnimationState *state = i.next();
00407 
00408         if (!state->animating)
00409             continue;
00410 
00411         // We need to make sure the index is still valid, since it could be removed
00412         // while the animation is running.
00413         if (state->index.isValid())
00414         {
00415             bool finished = state->update();
00416             region += view->visualRect(state->index);
00417 
00418             if (!finished)
00419             {
00420                 activeAnimations++;
00421                 continue;
00422             }
00423         }
00424 
00425         // If the direction is Forward, the state object needs to stick around
00426         // after the animation has finished, so we know that we've already done
00427         // a "hover in" for the index.
00428         if (state->direction == QTimeLine::Backward || !state->index.isValid())
00429         {
00430             delete state;
00431             i.remove();
00432         }
00433     }
00434 
00435     // Trigger a repaint of the animated indexes
00436     if (!region.isEmpty())
00437         const_cast<QAbstractItemView*>(view)->viewport()->update(region);
00438 
00439     return activeAnimations;
00440 }
00441 
00442 
00443 void DelegateAnimationHandler::viewDeleted(QObject *view)
00444 {
00445     AnimationList *list = animationLists.take(static_cast<QAbstractItemView*>(view));
00446     qDeleteAll(*list);
00447     delete list;
00448 }
00449 
00450 
00451 void DelegateAnimationHandler::timerEvent(QTimerEvent *)
00452 {
00453     int activeAnimations = 0;
00454 
00455     AnimationListsIterator i(animationLists);
00456     while (i.hasNext())
00457     {
00458         i.next();
00459         AnimationList *list = i.value();
00460         const QAbstractItemView *view = i.key();
00461 
00462         activeAnimations += runAnimations(list, view);
00463     }
00464 
00465     if (activeAnimations == 0 && timer.isActive())
00466         timer.stop();
00467 }
00468 
00469 }
00470 

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal