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