2007-01-02 Eric Seidel <eric@webkit.org>
[WebKit-https.git] / WebCore / ksvg2 / misc / SVGTimer.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     Copyright (C) 2007 Eric Seidel <eric@webkit.org>
6
7     This file is part of the KDE project
8
9     This library is free software; you can redistribute it and/or
10     modify it under the terms of the GNU Library General Public
11     License as published by the Free Software Foundation; either
12     version 2 of the License, or (at your option) any later version.
13
14     This library is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17     Library General Public License for more details.
18
19     You should have received a copy of the GNU Library General Public License
20     along with this library; see the file COPYING.LIB.  If not, write to
21     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22     Boston, MA 02111-1307, USA.
23 */
24
25 #include "config.h"
26 #ifdef SVG_SUPPORT
27 #include "SVGTimer.h"
28
29 #include <wtf/HashMap.h>
30 #include "SVGAnimateTransformElement.h"
31 #include "SVGAnimateMotionElement.h"
32 #include "SVGTransformList.h"
33 #include "SVGAnimateColorElement.h"
34 #include "SVGStyledTransformableElement.h"
35
36 namespace WebCore {
37
38 SVGTimer::SVGTimer(TimeScheduler* scheduler, double interval, bool singleShot)
39     : Timer<TimeScheduler>(scheduler, &TimeScheduler::timerFired)
40     , m_scheduler(scheduler)
41     , m_interval(interval)
42     , m_singleShot(singleShot)
43 {
44 }
45
46 void SVGTimer::start()
47 {
48     if (m_singleShot)
49         startOneShot(m_interval);
50     else
51         startRepeating(m_interval);
52 }
53
54 SVGTimer::TargetAnimationMap SVGTimer::animationsByElement(double elapsedSeconds)
55 {
56     // Build a list of all animations which apply to each element
57     // FIXME: This list should be sorted by animation priority
58     ExceptionCode ec = 0;
59     TargetAnimationMap targetMap;
60     SVGNotifySet::const_iterator end = m_notifySet.end();
61     for (SVGNotifySet::const_iterator it = m_notifySet.begin(); it != end; ++it) {
62         SVGAnimationElement* animation = *it;
63         
64         // If we're dealing with a disabled element with fill="freeze",
65         // we have to take it into account for further calculations.
66         if (!m_enabledNotifySet.contains(animation)) {
67             if (!animation->isFrozen())
68                 continue;
69             if (elapsedSeconds <= (animation->getStartTime() + animation->getSimpleDuration(ec)))
70                 continue;
71         }
72         
73         SVGElement* target = const_cast<SVGElement*>(animation->targetElement());
74         TargetAnimationMap::iterator i = targetMap.find(target);
75         if (i != targetMap.end())
76             i->second.append(animation);
77         else {
78             Vector<SVGAnimationElement*> list;
79             list.append(animation);
80             targetMap.set(target, list);
81         }
82     }
83     return targetMap;
84 }
85
86 // FIXME: Eventually this logic will move into an AnimationCompositor class
87 // FIXME: I also think this also violates the spec.  We should probably just copy the baseValue and not consolidate
88 static PassRefPtr<SVGTransformList> baseValueTransformList(SVGElement* targetElement)
89 {
90     ASSERT(targetElement->isStyledTransformable());
91     SVGStyledTransformableElement* transform = static_cast<SVGStyledTransformableElement*>(targetElement);
92     RefPtr<SVGTransformList> targetTransforms = new SVGTransformList();
93     SVGTransformList* transformList = transform->transformBaseValue();
94     if (transformList) {
95         RefPtr<SVGTransform> initialTransform = transformList->concatenate();
96         if (initialTransform) {
97             ExceptionCode ec;
98             targetTransforms->appendItem(initialTransform.get(), ec);
99             return targetTransforms.release();
100         }
101     }
102     return targetTransforms;
103 }
104
105 // FIXME: This funtion will eventually become part of the AnimationCompositor
106 void SVGTimer::applyAnimations(double elapsedSeconds, const SVGTimer::TargetAnimationMap& targetMap)
107 {    
108     TargetAnimationMap::const_iterator targetIterator = targetMap.begin();
109     TargetAnimationMap::const_iterator tend = targetMap.end();
110     for (; targetIterator != tend; ++targetIterator) {
111         HashMap<String, Color> attributeToColorMap; // special <animateColor> case
112         RefPtr<SVGTransformList> targetTransforms; // special <animateTransform> case
113         
114         // FIXME: This is still not 100% correct.  Correct would be:
115         // 1. Walk backwards through the priority list until a replace (!isAdditive()) is found
116         // 2. Set the initial value (or last replace) as the new animVal
117         // 3. Call each enabled animation in turn, to have it apply its changes
118         // 4. After building a new animVal, set it on the element.
119         
120         unsigned count = targetIterator->second.size();
121         for (unsigned i = 0; i < count; ++i) {
122             SVGAnimationElement* animation = targetIterator->second[i];
123             
124             if (!animation->hasValidTarget())
125                 continue;
126             
127             if (!animation->updateForElapsedSeconds(elapsedSeconds))
128                 continue;
129             
130             if (!targetTransforms && (animation->hasTagName(SVGNames::animateTransformTag) || animation->hasTagName(SVGNames::animateMotionTag)))
131                 targetTransforms = baseValueTransformList(animation->targetElement());
132             
133             if (animation->hasTagName(SVGNames::animateTransformTag))
134                 static_cast<SVGAnimateTransformElement*>(animation)->applyAnimationToValue(targetTransforms.get());
135             else if (animation->hasTagName(SVGNames::animateMotionTag))
136                 static_cast<SVGAnimateMotionElement*>(animation)->applyAnimationToValue(targetTransforms.get());
137             else if (animation->hasTagName(SVGNames::animateColorTag)) {
138                 SVGAnimateColorElement* animColor = static_cast<SVGAnimateColorElement*>(animation);
139                 String name = animColor->attributeName();
140                 Color currentColor = attributeToColorMap.contains(name) ? attributeToColorMap.get(name) : animColor->initialColor();
141                 animColor->applyAnimationToValue(currentColor);
142                 attributeToColorMap.set(name, currentColor);
143             }
144         }
145         
146         // Apply any transform changes (animateTransform & animateMotion)
147         if (targetTransforms) {
148             SVGElement* key = targetIterator->first;
149             if (key && key->isStyledTransformable()) {
150                 SVGStyledTransformableElement* transform = static_cast<SVGStyledTransformableElement*>(key);
151                 transform->setTransform(targetTransforms.get());
152                 transform->updateLocalTransform(transform->transform());
153             }
154         }
155         
156         // Apply any color changes (animateColor)
157         HashMap<String, Color>::iterator colorIteratorEnd = attributeToColorMap.end();
158         for (HashMap<String, Color>::iterator colorIterator = attributeToColorMap.begin(); colorIterator != colorIteratorEnd; ++colorIterator) {
159             if (colorIterator->second.isValid())
160                 SVGAnimationElement::setTargetAttribute(targetIterator->first, colorIterator->first, colorIterator->second.name());
161         }
162     }
163     
164     // Make a second pass through the map to avoid multiple setChanged calls on the same element.
165     for (targetIterator = targetMap.begin(); targetIterator != tend; ++targetIterator) {
166         SVGElement* key = targetIterator->first;
167         if (key && key->isStyled())
168             static_cast<SVGStyledElement*>(key)->setChanged(true);
169     }
170 }
171
172 void SVGTimer::notifyAll()
173 {
174     if (m_enabledNotifySet.isEmpty())
175         return;
176
177     // First build a list of animation elements per target element
178     double elapsedSeconds = m_scheduler->elapsed() * 1000.0; // Take time now.
179     TargetAnimationMap targetMap = animationsByElement(elapsedSeconds);
180     
181     // Then composite those animations down to final values and apply
182     applyAnimations(elapsedSeconds, targetMap);
183 }
184
185 void SVGTimer::addNotify(SVGAnimationElement* element, bool enabled)
186 {
187     m_notifySet.add(element);
188     if (enabled)
189         m_enabledNotifySet.add(element);
190     else
191         m_enabledNotifySet.remove(element);
192 }
193
194 void SVGTimer::removeNotify(SVGAnimationElement *element)
195 {
196     // FIXME: Why do we keep a pointer to the element forever (marked disabled)?
197     // That can't be right!
198
199     m_enabledNotifySet.remove(element);
200     if (m_enabledNotifySet.isEmpty())
201         stop();
202 }
203
204 } // namespace
205
206 // vim:ts=4:noet
207 #endif // SVG_SUPPORT