2006-02-11 Eric Seidel <eseidel@apple.com>
[WebKit-https.git] / WebCore / ksvg2 / misc / KSVGTimeScheduler.cpp
1 /*
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.
5
6     This file is part of the KDE project
7
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.
12
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.
17
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.
22 */
23
24 #include "config.h"
25 #if SVG_SUPPORT
26 #include "KSVGTimeScheduler.h"
27
28 #include "DocumentImpl.h"
29 #include "SVGAnimateColorElementImpl.h"
30 #include "SVGAnimateTransformElementImpl.h"
31 #include "SVGAnimatedTransformListImpl.h"
32 #include "SVGDOMImplementationImpl.h"
33 #include "SVGMatrixImpl.h"
34 #include "SVGNames.h"
35 #include "SVGStyledElementImpl.h"
36 #include "SVGStyledTransformableElementImpl.h"
37 #include "SystemTime.h"
38 #include "Timer.h"
39 #include <kcanvas/KCanvas.h>
40
41 namespace WebCore {
42
43 const double staticTimerInterval = 0.050; // 50 ms
44
45 typedef HashSet<SVGAnimationElementImpl*> SVGNotifySet;
46
47 class SVGTimer : private Timer<TimeScheduler>
48 {
49 public:
50     SVGTimer(TimeScheduler*, double interval, bool singleShot);
51
52     void start();
53     using Timer<TimeScheduler>::stop;
54     using Timer<TimeScheduler>::isActive;
55
56     void notifyAll();
57     void addNotify(SVGAnimationElementImpl*, bool enabled = false);
58     void removeNotify(SVGAnimationElementImpl*);
59
60     static SVGTimer* downcast(Timer<TimeScheduler>* t) { return static_cast<SVGTimer*>(t); }
61
62 private:
63     double calculateTimePercentage(double elapsed, double start, double end, double duration, double repetitions);
64
65     TimeScheduler* m_scheduler;
66     double m_interval;
67     bool m_singleShot;
68
69     SVGNotifySet m_notifySet;
70     SVGNotifySet m_enabledNotifySet;
71 };
72
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)
76 {
77 }
78
79 void SVGTimer::start()
80 {
81     if (m_singleShot)
82         startOneShot(m_interval);
83     else
84         startRepeating(m_interval);
85 }
86
87 double SVGTimer::calculateTimePercentage(double elapsed, double start, double end, double duration, double repetitions)
88 {
89     double percentage = 0.0;
90
91     double useElapsed = elapsed - (duration * repetitions);
92     
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) {
96         if (duration > end)
97             percentage = 1.0 - (((start + end) - useElapsed) / end);
98         else
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);
102
103     return percentage;
104 }
105
106 void SVGTimer::notifyAll()
107 {
108     if (m_enabledNotifySet.isEmpty())
109         return;
110
111     double elapsed = m_scheduler->elapsed() * 1000.0; // Take time now.
112
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;
118     
119     SVGNotifySet::const_iterator end = m_notifySet.end();
120     for (SVGNotifySet::const_iterator it = m_notifySet.begin(); it != end; ++it) {
121         SVGAnimationElementImpl* animation = *it;
122
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())
127                 continue;
128             if (elapsed <= (animation->getStartTime() + animation->getSimpleDuration()))
129                 continue;
130         }
131
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);
136         else {
137             Vector<SVGAnimationElementImpl*> list;
138             list.append(animation);
139             targetMap.set(target, list);
140         }
141     }
142
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    
148
149         unsigned count = targetIterator->second.size();
150         for (unsigned i = 0; i < count; ++i) {
151             SVGAnimationElementImpl* animation = targetIterator->second[i];
152
153             double end = animation->getEndTime();
154             double start = animation->getStartTime();
155             double duration = animation->getSimpleDuration();
156             double repetitions = animation->repeations();
157
158             // Validate animation timing settings:
159             // #1 (duration > 0) -> fine
160             // #2 (duration <= 0.0 && end > 0) -> fine
161             
162             if((duration <= 0.0 && end <= 0.0) ||
163                (animation->isIndefinite(duration) && end <= 0.0)) // Ignore dur="0" or dur="-neg"
164                 continue;
165             
166             float percentage = calculateTimePercentage(elapsed, start, end, duration, repetitions);
167                 
168             if(percentage <= 1.0 || animation->connected())
169                 animation->handleTimerEvent(percentage);
170
171 // FIXME: Disable animateTransform until SVGList can be fixed.
172 #if 0
173             // Special cases for animate* objects depending on 'additive' attribute
174             if(animation->hasTagName(SVGNames::animateTransformTag))
175             {
176                 SVGAnimateTransformElementImpl *animTransform = static_cast<SVGAnimateTransformElementImpl *>(animation);
177                 if(!animTransform)
178                     continue;
179
180                 RefPtr<SVGMatrixImpl> transformMatrix = animTransform->transformMatrix();
181                 if(!transformMatrix)
182                     continue;
183
184                 RefPtr<SVGMatrixImpl> initialMatrix = animTransform->initialMatrix();
185                 RefPtr<SVGTransformImpl> data = new SVGTransformImpl();
186
187                 if(!targetTransforms) // lazy creation, only if needed.
188                 {
189                     targetTransforms = new SVGTransformListImpl();
190
191                     if(animation->isAdditive() && initialMatrix)
192                     {
193                         RefPtr<SVGMatrixImpl> matrix = new SVGMatrixImpl(initialMatrix->qmatrix());
194                         
195                         data->setMatrix(matrix.get());
196                         targetTransforms->appendItem(data.get());
197
198                         data = new SVGTransformImpl();
199                     }
200                 }
201
202                 if(targetTransforms->numberOfItems() <= 1)
203                     data->setMatrix(transformMatrix.get());
204                 else
205                 {
206                     if(!animation->isAdditive())
207                         targetTransforms->clear();    
208                     
209                     data->setMatrix(transformMatrix.get());
210                 }
211
212                 targetTransforms->appendItem(data.get());
213             }
214             else
215 #endif
216             if(animation->hasTagName(SVGNames::animateColorTag))
217             {
218                 SVGAnimateColorElementImpl *animColor = static_cast<SVGAnimateColorElementImpl *>(animation);
219                 if(!animColor)
220                     continue;
221
222                 QString name = animColor->attributeName();
223                 Color color = animColor->color();
224
225                 if(!targetColor.contains(name))
226                 {
227                     if(animation->isAdditive())
228                     {
229                         int r = animColor->initialColor().red() + color.red();
230                         int g = animColor->initialColor().green() + color.green();
231                         int b = animColor->initialColor().blue() + color.blue();
232                     
233                         targetColor.set(name, animColor->clampColor(r, g, b));
234                     }
235                     else
236                         targetColor.set(name, color);
237                 }
238                 else
239                 {
240                     if(!animation->isAdditive())
241                         targetColor.set(name, color);
242                     else
243                     {
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();
248
249                         targetColor.set(name, animColor->clampColor(r, g, b));
250                     }
251                 }
252             }
253         }
254
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());
262             }
263         }
264
265         // Handle <animateColor>.
266         HashMap<DOMString, Color>::iterator cend = targetColor.end();
267         for(HashMap<DOMString, Color>::iterator cit = targetColor.begin(); cit != cend; ++cit)
268         {
269             if(cit->second.isValid())
270             {
271                 SVGAnimationElementImpl::setTargetAttribute(targetIterator->first,
272                                                             cit->first.impl(),
273                                                             DOMString(cit->second.name()).impl());
274             }
275         }
276     }
277
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);
283     }
284 }
285
286 void SVGTimer::addNotify(SVGAnimationElementImpl* element, bool enabled)
287 {
288     m_notifySet.add(element);
289     if (enabled)
290         m_enabledNotifySet.add(element);
291     else
292         m_enabledNotifySet.remove(element);
293 }
294
295 void SVGTimer::removeNotify(SVGAnimationElementImpl *element)
296 {
297     // FIXME: Why do we keep a pointer to the element forever (marked disabled)?
298     // That can't be right!
299
300     m_enabledNotifySet.remove(element);
301     if (m_enabledNotifySet.isEmpty())
302         stop();
303 }
304
305 TimeScheduler::TimeScheduler(DocumentImpl *document)
306     : m_creationTime(currentTime()), m_savedTime(0), m_document(document)
307 {
308     // Don't start this timer yet.
309     m_intervalTimer = new SVGTimer(this, staticTimerInterval, false);
310 }
311
312 TimeScheduler::~TimeScheduler()
313 {
314     deleteAllValues(m_timerSet);
315     delete m_intervalTimer;
316 }
317
318 void TimeScheduler::addTimer(SVGAnimationElementImpl* element, unsigned ms)
319 {
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);
324 }
325
326 void TimeScheduler::connectIntervalTimer(SVGAnimationElementImpl* element)
327 {
328     m_intervalTimer->addNotify(element, true);
329 }
330
331 void TimeScheduler::disconnectIntervalTimer(SVGAnimationElementImpl* element)
332 {
333     m_intervalTimer->removeNotify(element);
334 }
335
336 void TimeScheduler::startAnimations()
337 {
338     m_creationTime = currentTime();
339
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())
344             svgTimer->start();
345     }
346 }
347
348 void TimeScheduler::toggleAnimations()
349 {
350     if (m_intervalTimer->isActive()) {
351         m_intervalTimer->stop();
352         m_savedTime = currentTime();
353     } else {
354         if (m_savedTime != 0) {
355             m_creationTime += currentTime() - m_savedTime;
356             m_savedTime = 0;
357         }
358         m_intervalTimer->start();
359     }
360 }
361
362 bool TimeScheduler::animationsPaused() const
363 {
364     return !m_intervalTimer->isActive();
365 }
366
367 void TimeScheduler::timerFired(Timer<TimeScheduler>* baseTimer)
368 {
369     // Get the pointer now, because notifyAll could make the document,
370     // including this TimeScheduler, go away.
371     RefPtr<DocumentImpl> doc = m_document;
372
373     SVGTimer* timer = SVGTimer::downcast(baseTimer);
374
375     timer->notifyAll();
376
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);
386         delete timer;
387
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();
393     }
394
395     // Update any 'dirty' shapes.
396     doc->updateRendering();
397 }
398
399 double TimeScheduler::elapsed() const
400 {
401     return currentTime() - m_creationTime;
402 }
403
404 } // namespace
405
406 // vim:ts=4:noet
407 #endif // SVG_SUPPORT