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
KDE 4.7 API Reference