2 Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
3 2004, 2005 Rob Buis <buis@kde.org>
4 Copyright (C) 2006 Apple Computer, Inc.
6 This file is part of the KDE project
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 Boston, MA 02111-1307, USA.
26 #include "KSVGTimeScheduler.h"
28 #include "DocumentImpl.h"
29 #include "SVGAnimateColorElementImpl.h"
30 #include "SVGAnimateTransformElementImpl.h"
31 #include "SVGAnimatedTransformListImpl.h"
32 #include "SVGDOMImplementationImpl.h"
33 #include "SVGMatrixImpl.h"
35 #include "SVGStyledElementImpl.h"
36 #include "SVGStyledTransformableElementImpl.h"
37 #include "SystemTime.h"
39 #include <kcanvas/KCanvas.h>
43 const double staticTimerInterval = 0.050; // 50 ms
45 typedef HashSet<SVGAnimationElementImpl*> SVGNotifySet;
47 class SVGTimer : private Timer<TimeScheduler>
50 SVGTimer(TimeScheduler*, double interval, bool singleShot);
53 using Timer<TimeScheduler>::stop;
54 using Timer<TimeScheduler>::isActive;
57 void addNotify(SVGAnimationElementImpl*, bool enabled = false);
58 void removeNotify(SVGAnimationElementImpl*);
60 static SVGTimer* downcast(Timer<TimeScheduler>* t) { return static_cast<SVGTimer*>(t); }
63 double calculateTimePercentage(double elapsed, double start, double end, double duration, double repetitions);
65 TimeScheduler* m_scheduler;
69 SVGNotifySet m_notifySet;
70 SVGNotifySet m_enabledNotifySet;
73 SVGTimer::SVGTimer(TimeScheduler* scheduler, double interval, bool singleShot)
74 : Timer<TimeScheduler>(scheduler, &TimeScheduler::timerFired)
75 , m_scheduler(scheduler), m_interval(interval), m_singleShot(singleShot)
79 void SVGTimer::start()
82 startOneShot(m_interval);
84 startRepeating(m_interval);
87 double SVGTimer::calculateTimePercentage(double elapsed, double start, double end, double duration, double repetitions)
89 double percentage = 0.0;
91 double useElapsed = elapsed - (duration * repetitions);
93 if (duration > 0.0 && end == 0.0)
94 percentage = 1.0 - (((start + duration) - useElapsed) / duration);
95 else if (duration > 0.0 && end != 0.0) {
97 percentage = 1.0 - (((start + end) - useElapsed) / end);
99 percentage = 1.0 - (((start + duration) - useElapsed) / duration);
100 } else if(duration == 0.0 && end != 0.0)
101 percentage = 1.0 - (((start + end) - useElapsed) / end);
106 void SVGTimer::notifyAll()
108 if (m_enabledNotifySet.isEmpty())
111 double elapsed = m_scheduler->elapsed() * 1000.0; // Take time now.
113 // First build a list of animation elements per target element
114 // This is important to decide about the order & priority of
115 // the animations -> 'additive' support is handled this way.
116 typedef HashMap<SVGElementImpl*, Vector<SVGAnimationElementImpl*> > TargetAnimationMap;
117 TargetAnimationMap targetMap;
119 SVGNotifySet::const_iterator end = m_notifySet.end();
120 for (SVGNotifySet::const_iterator it = m_notifySet.begin(); it != end; ++it) {
121 SVGAnimationElementImpl* animation = *it;
123 // If we're dealing with a disabled element with fill="freeze",
124 // we have to take it into account for further calculations.
125 if (!m_enabledNotifySet.contains(animation)) {
126 if (!animation->isFrozen())
128 if (elapsed <= (animation->getStartTime() + animation->getSimpleDuration()))
132 SVGElementImpl* target = const_cast<SVGElementImpl *>(animation->targetElement());
133 TargetAnimationMap::iterator i = targetMap.find(target);
134 if (i != targetMap.end())
135 i->second.append(animation);
137 Vector<SVGAnimationElementImpl*> list;
138 list.append(animation);
139 targetMap.set(target, list);
143 TargetAnimationMap::iterator targetIterator = targetMap.begin();
144 TargetAnimationMap::iterator tend = targetMap.end();
145 for (; targetIterator != tend; ++targetIterator) {
146 HashMap<DOMString, Color> targetColor; // special <animateColor> case
147 RefPtr<SVGTransformListImpl> targetTransforms; // special <animateTransform> case
149 unsigned count = targetIterator->second.size();
150 for (unsigned i = 0; i < count; ++i) {
151 SVGAnimationElementImpl* animation = targetIterator->second[i];
153 double end = animation->getEndTime();
154 double start = animation->getStartTime();
155 double duration = animation->getSimpleDuration();
156 double repetitions = animation->repeations();
158 // Validate animation timing settings:
159 // #1 (duration > 0) -> fine
160 // #2 (duration <= 0.0 && end > 0) -> fine
162 if((duration <= 0.0 && end <= 0.0) ||
163 (animation->isIndefinite(duration) && end <= 0.0)) // Ignore dur="0" or dur="-neg"
166 float percentage = calculateTimePercentage(elapsed, start, end, duration, repetitions);
168 if(percentage <= 1.0 || animation->connected())
169 animation->handleTimerEvent(percentage);
171 // FIXME: Disable animateTransform until SVGList can be fixed.
173 // Special cases for animate* objects depending on 'additive' attribute
174 if(animation->hasTagName(SVGNames::animateTransformTag))
176 SVGAnimateTransformElementImpl *animTransform = static_cast<SVGAnimateTransformElementImpl *>(animation);
180 RefPtr<SVGMatrixImpl> transformMatrix = animTransform->transformMatrix();
184 RefPtr<SVGMatrixImpl> initialMatrix = animTransform->initialMatrix();
185 RefPtr<SVGTransformImpl> data = new SVGTransformImpl();
187 if(!targetTransforms) // lazy creation, only if needed.
189 targetTransforms = new SVGTransformListImpl();
191 if(animation->isAdditive() && initialMatrix)
193 RefPtr<SVGMatrixImpl> matrix = new SVGMatrixImpl(initialMatrix->qmatrix());
195 data->setMatrix(matrix.get());
196 targetTransforms->appendItem(data.get());
198 data = new SVGTransformImpl();
202 if(targetTransforms->numberOfItems() <= 1)
203 data->setMatrix(transformMatrix.get());
206 if(!animation->isAdditive())
207 targetTransforms->clear();
209 data->setMatrix(transformMatrix.get());
212 targetTransforms->appendItem(data.get());
216 if(animation->hasTagName(SVGNames::animateColorTag))
218 SVGAnimateColorElementImpl *animColor = static_cast<SVGAnimateColorElementImpl *>(animation);
222 QString name = animColor->attributeName();
223 Color color = animColor->color();
225 if(!targetColor.contains(name))
227 if(animation->isAdditive())
229 int r = animColor->initialColor().red() + color.red();
230 int g = animColor->initialColor().green() + color.green();
231 int b = animColor->initialColor().blue() + color.blue();
233 targetColor.set(name, animColor->clampColor(r, g, b));
236 targetColor.set(name, color);
240 if(!animation->isAdditive())
241 targetColor.set(name, color);
244 Color baseColor = targetColor.get(name);
245 int r = baseColor.red() + color.red();
246 int g = baseColor.green() + color.green();
247 int b = baseColor.blue() + color.blue();
249 targetColor.set(name, animColor->clampColor(r, g, b));
255 // Handle <animateTransform>.
256 if (targetTransforms) {
257 SVGElementImpl* key = targetIterator->first;
258 if (key && key->isStyled() && key->isStyledTransformable()) {
259 SVGStyledTransformableElementImpl *transform = static_cast<SVGStyledTransformableElementImpl *>(key);
260 transform->transform()->setAnimVal(targetTransforms.get());
261 transform->updateLocalTransform(transform->transform()->animVal());
265 // Handle <animateColor>.
266 HashMap<DOMString, Color>::iterator cend = targetColor.end();
267 for(HashMap<DOMString, Color>::iterator cit = targetColor.begin(); cit != cend; ++cit)
269 if(cit->second.isValid())
271 SVGAnimationElementImpl::setTargetAttribute(targetIterator->first,
273 DOMString(cit->second.name()).impl());
278 // Make a second pass through the map to avoid multiple setChanged calls on the same element.
279 for (targetIterator = targetMap.begin(); targetIterator != tend; ++targetIterator) {
280 SVGElementImpl *key = targetIterator->first;
281 if (key && key->isStyled())
282 static_cast<SVGStyledElementImpl *>(key)->setChanged(true);
286 void SVGTimer::addNotify(SVGAnimationElementImpl* element, bool enabled)
288 m_notifySet.add(element);
290 m_enabledNotifySet.add(element);
292 m_enabledNotifySet.remove(element);
295 void SVGTimer::removeNotify(SVGAnimationElementImpl *element)
297 // FIXME: Why do we keep a pointer to the element forever (marked disabled)?
298 // That can't be right!
300 m_enabledNotifySet.remove(element);
301 if (m_enabledNotifySet.isEmpty())
305 TimeScheduler::TimeScheduler(DocumentImpl *document)
306 : m_creationTime(currentTime()), m_savedTime(0), m_document(document)
308 // Don't start this timer yet.
309 m_intervalTimer = new SVGTimer(this, staticTimerInterval, false);
312 TimeScheduler::~TimeScheduler()
314 deleteAllValues(m_timerSet);
315 delete m_intervalTimer;
318 void TimeScheduler::addTimer(SVGAnimationElementImpl* element, unsigned ms)
320 SVGTimer* svgTimer = new SVGTimer(this, ms * 0.001, true);
321 svgTimer->addNotify(element, true);
322 m_timerSet.add(svgTimer);
323 m_intervalTimer->addNotify(element, false);
326 void TimeScheduler::connectIntervalTimer(SVGAnimationElementImpl* element)
328 m_intervalTimer->addNotify(element, true);
331 void TimeScheduler::disconnectIntervalTimer(SVGAnimationElementImpl* element)
333 m_intervalTimer->removeNotify(element);
336 void TimeScheduler::startAnimations()
338 m_creationTime = currentTime();
340 SVGTimerSet::iterator end = m_timerSet.end();
341 for (SVGTimerSet::iterator it = m_timerSet.begin(); it != end; ++it) {
342 SVGTimer* svgTimer = *it;
343 if (svgTimer && !svgTimer->isActive())
348 void TimeScheduler::toggleAnimations()
350 if (m_intervalTimer->isActive()) {
351 m_intervalTimer->stop();
352 m_savedTime = currentTime();
354 if (m_savedTime != 0) {
355 m_creationTime += currentTime() - m_savedTime;
358 m_intervalTimer->start();
362 bool TimeScheduler::animationsPaused() const
364 return !m_intervalTimer->isActive();
367 void TimeScheduler::timerFired(Timer<TimeScheduler>* baseTimer)
369 // Get the pointer now, because notifyAll could make the document,
370 // including this TimeScheduler, go away.
371 RefPtr<DocumentImpl> doc = m_document;
373 SVGTimer* timer = SVGTimer::downcast(baseTimer);
377 // FIXME: Is it really safe to look at m_intervalTimer now?
378 // Isn't it possible the TimeScheduler was deleted already?
379 // If so, timer, m_timerSet, and m_intervalTimer have all
380 // been deleted. May need to reference count the TimeScheduler
381 // to work around this, and ref/deref it in this function.
382 if (timer != m_intervalTimer) {
383 ASSERT(!timer->isActive());
384 ASSERT(m_timerSet.contains(timer));
385 m_timerSet.remove(timer);
388 // The singleShot timers of ie. <animate> with begin="3s" are notified
389 // by the previous call, and now all connections to the interval timer
390 // are created and now we just need to fire that timer (Niko)
391 if (!m_intervalTimer->isActive())
392 m_intervalTimer->start();
395 // Update any 'dirty' shapes.
396 doc->updateRendering();
399 double TimeScheduler::elapsed() const
401 return currentTime() - m_creationTime;
407 #endif // SVG_SUPPORT