2006-03-20 Eric Seidel <eseidel@apple.com>
[WebKit-https.git] / WebCore / ksvg2 / svg / SVGAnimationElement.cpp
1 /*
2     Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
3                   2004, 2005 Rob Buis <buis@kde.org>
4
5     This file is part of the KDE project
6
7     This library is free software; you can redistribute it and/or
8     modify it under the terms of the GNU Library General Public
9     License as published by the Free Software Foundation; either
10     version 2 of the License, or (at your option) any later version.
11
12     This library is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15     Library General Public License for more details.
16
17     You should have received a copy of the GNU Library General Public License
18     along with this library; see the file COPYING.LIB.  If not, write to
19     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20     Boston, MA 02111-1307, USA.
21 */
22
23 #include "config.h"
24 #if SVG_SUPPORT
25 #include <float.h>
26
27 #include <kdom/kdom.h>
28 #include <kdom/core/Attr.h>
29 #include "Document.h"
30 #include "DOMImplementation.h"
31 #include "css_valueimpl.h"
32 #include "PlatformString.h"
33
34 #include "SVGHelper.h"
35 #include "SVGURIReference.h"
36 #include "SVGStyledElement.h"
37 #include "SVGAnimationElement.h"
38 #include "SVGSVGElement.h"
39 #include "KSVGTimeScheduler.h"
40 #include "SVGDocumentExtensions.h"
41 #include "CSSPropertyNames.h"
42 #include "ksvgcssproperties.h"
43
44 #include "XLinkNames.h"
45
46 #include <math.h>
47
48 using namespace WebCore;
49 using namespace std;
50
51 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document *doc)
52 : SVGElement(tagName, doc), SVGTests(), SVGExternalResourcesRequired()
53 {
54     m_connected = false;
55
56     m_targetElement = 0;
57     
58     m_currentTime = 0.0;
59     m_simpleDuration = 0.0;
60
61     // Initialize shared properties...
62     m_end = 0.0;
63     m_min = 0.0;
64     m_max = 0.0;
65     m_begin = 0.0;
66
67     m_repeations = 0;
68     m_repeatCount = 0;
69
70     m_fill = FILL_REMOVE;
71     m_restart = RESTART_ALWAYS;
72     m_calcMode = CALCMODE_LINEAR;
73     m_additive = ADDITIVE_REPLACE;
74     m_accumulate = ACCUMULATE_NONE;
75     m_attributeType = ATTRIBUTETYPE_AUTO;
76 }
77
78 SVGAnimationElement::~SVGAnimationElement()
79 {
80 }
81
82 SVGElement *SVGAnimationElement::targetElement() const
83 {
84     if (!m_targetElement)
85     {
86         if (!m_href.isEmpty())
87         {
88             String targetId = SVGURIReference::getTarget(m_href);
89             Element *element = ownerDocument()->getElementById(targetId.impl());
90             m_targetElement = svg_dynamic_cast(element);
91         }
92         else if (parentNode())
93         {
94             Node *target = parentNode();
95             while(target != 0)
96             {
97                 if (target->nodeType() != ELEMENT_NODE)
98                     target = target->parentNode();
99                 else
100                     break;
101             }
102             m_targetElement = svg_dynamic_cast(target);
103         }
104     }
105                         
106     return m_targetElement;
107 }
108
109 double SVGAnimationElement::getEndTime() const
110 {
111     return m_end;
112 }
113
114 double SVGAnimationElement::getStartTime() const
115 {
116     return m_begin;
117 }
118
119 double SVGAnimationElement::getCurrentTime() const
120 {
121     return m_currentTime;
122 }
123
124 double SVGAnimationElement::getSimpleDuration() const
125 {
126     return m_simpleDuration;
127 }
128
129 void SVGAnimationElement::parseMappedAttribute(MappedAttribute *attr)
130 {
131     String value(attr->value());
132     if (attr->name().matches(XLinkNames::hrefAttr))
133             m_href = value.deprecatedString();
134     else if (attr->name() == SVGNames::attributeNameAttr)
135             m_attributeName = value.deprecatedString();
136     else if (attr->name() == SVGNames::attributeTypeAttr)
137     {
138         if (value == "CSS")
139             m_attributeType = ATTRIBUTETYPE_CSS;
140         else if (value == "XML")
141             m_attributeType = ATTRIBUTETYPE_XML;
142         else if (value == "auto")
143             m_attributeType = ATTRIBUTETYPE_AUTO;
144     }
145     else if (attr->name() == SVGNames::beginAttr || attr->name() == SVGNames::endAttr)
146     {
147         // Create list
148         RefPtr<SVGStringList> temp = new SVGStringList();
149
150         // Feed data into list
151         SVGHelper::ParseSeperatedList(temp.get(), value.deprecatedString(), ';');
152
153         // Parse data
154         for (unsigned int i = 0; i < temp->numberOfItems(); i++) {
155             DeprecatedString current = String(temp->getItem(i)).deprecatedString();
156
157             if (current.startsWith("accessKey")) {
158                 // Register keyDownEventListener for the character
159                 DeprecatedString character = current.mid(current.length() - 2, 1);
160
161                 //kdDebug() << k_funcinfo << " Supposed to register accessKey Character: " << character << " UNSUPPORTED!" << endl;
162             } else if (current.startsWith("wallclock")) {
163                 int firstBrace = current.find('(');
164                 int secondBrace = current.find(')');
165
166                 DeprecatedString wallclockValue = current.mid(firstBrace + 1, secondBrace - firstBrace - 2);
167                 //kdDebug() << k_funcinfo << " Supposed to use wallClock value: " << wallclockValue << " UNSUPPORTED!" << endl;
168             }
169             else if (current.contains('.'))
170             {
171                 int dotPosition = current.find('.');
172
173                 DeprecatedString element = current.mid(0, dotPosition);
174                 DeprecatedString clockValue;
175                 if (current.contains("begin"))
176                     clockValue = current.mid(dotPosition + 6);
177                 else if (current.contains("end"))
178                     clockValue = current.mid(dotPosition + 4);
179                 else if (current.contains("repeat"))
180                     clockValue = current.mid(dotPosition + 7);
181                 else // DOM2 Event Reference
182                 {
183                     int plusMinusPosition = -1;
184
185                     if (current.contains('+'))
186                         plusMinusPosition = current.find('+');
187                     else if (current.contains('-'))
188                         plusMinusPosition = current.find('-');
189
190                     DeprecatedString event = current.mid(dotPosition + 1, plusMinusPosition - dotPosition - 1);
191                     clockValue = current.mid(dotPosition + event.length() + 1);
192
193                     //kdDebug() << k_funcinfo << " Supposed to use DOM Event: " << event << " UNSUPPORTED!" << endl;
194                 }
195             }
196             else
197             {
198                 if (attr->name() == SVGNames::beginAttr)
199                 {
200                     m_begin = parseClockValue(current);
201                     if (!isIndefinite(m_begin))
202                         m_begin *= 1000.0;
203
204                     //kdDebug() << k_funcinfo << " Setting begin time to " << m_begin << " ms!" << endl;
205                 }
206                 else
207                 {
208                     m_end = parseClockValue(current);
209                     if (!isIndefinite(m_end))
210                         m_end *= 1000.0;
211
212                     //kdDebug() << k_funcinfo << " Setting end time to " << m_end << " ms!" << endl;
213                 }
214             }
215         }
216     }
217     else if (attr->name() == SVGNames::durAttr)
218     {
219         m_simpleDuration = parseClockValue(value.deprecatedString());
220         if (!isIndefinite(m_simpleDuration))
221             m_simpleDuration *= 1000.0;
222     }
223     else if (attr->name() == SVGNames::minAttr)
224     {
225         m_min = parseClockValue(value.deprecatedString());
226         if (!isIndefinite(m_min))
227             m_min *= 1000.0;
228     }
229     else if (attr->name() == SVGNames::maxAttr)
230     {
231         m_max = parseClockValue(value.deprecatedString());
232         if (!isIndefinite(m_max))
233             m_max *= 1000.0;
234     }
235     else if (attr->name() == SVGNames::restartAttr)
236     {
237         if (value == "whenNotActive")
238             m_restart = RESTART_WHENNOTACTIVE;
239         else if (value == "never")
240             m_restart = RESTART_NEVER;
241         else if (value == "always")
242             m_restart = RESTART_ALWAYS;
243     }
244     else if (attr->name() == SVGNames::repeatCountAttr)
245     {
246         if (value == "indefinite")
247             m_repeatCount = DBL_MAX;
248         else
249             m_repeatCount = value.deprecatedString().toDouble();
250     }
251     else if (attr->name() == SVGNames::repeatDurAttr)
252         m_repeatDur = value.deprecatedString();
253     else if (attr->name() == SVGNames::fillAttr)
254     {
255         if (value == "freeze")
256             m_fill = FILL_FREEZE;
257         else if (value == "remove")
258             m_fill = FILL_REMOVE;
259     }
260     else if (attr->name() == SVGNames::calcModeAttr)
261     {
262         if (value == "discrete")
263             m_calcMode = CALCMODE_DISCRETE;
264         else if (value == "linear")
265             m_calcMode = CALCMODE_LINEAR;
266         else if (value == "spline")
267             m_calcMode = CALCMODE_SPLINE;
268         else if (value == "paced")
269             m_calcMode = CALCMODE_PACED;
270     }
271     else if (attr->name() == SVGNames::valuesAttr)
272     {
273         m_values = new SVGStringList();
274         SVGHelper::ParseSeperatedList(m_values.get(), value.deprecatedString(), ';');
275     }
276     else if (attr->name() == SVGNames::keyTimesAttr)
277     {
278         m_keyTimes = new SVGStringList();
279         SVGHelper::ParseSeperatedList(m_keyTimes.get(), value.deprecatedString(), ';');
280     }
281     else if (attr->name() == SVGNames::keySplinesAttr)
282     {
283         m_keySplines = new SVGStringList();
284         SVGHelper::ParseSeperatedList(m_keySplines.get(), value.deprecatedString(), ';');
285     }
286     else if (attr->name() == SVGNames::fromAttr)
287         m_from = value.deprecatedString();
288     else if (attr->name() == SVGNames::toAttr)
289         m_to = value.deprecatedString();
290     else if (attr->name() == SVGNames::byAttr)
291         m_by = value.deprecatedString();
292     else if (attr->name() == SVGNames::additiveAttr)
293     {
294         if (value == "sum")
295             m_additive = ADDITIVE_SUM;
296         else if (value == "replace")
297             m_additive = ADDITIVE_REPLACE;
298     }
299     else if (attr->name() == SVGNames::accumulateAttr)
300     {
301         if (value == "sum")
302             m_accumulate = ACCUMULATE_SUM;
303         else if (value == "none")
304             m_accumulate = ACCUMULATE_NONE;
305     }
306     else
307     {
308         if (SVGTests::parseMappedAttribute(attr)) return;
309         if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) return;
310         
311         SVGElement::parseMappedAttribute(attr);
312     }
313 }
314
315 double SVGAnimationElement::parseClockValue(const DeprecatedString &data) const
316 {
317     DeprecatedString parse = data.stripWhiteSpace();
318     
319     if (parse == "indefinite") // Saves some time...
320         return DBL_MAX;
321
322     double result;
323
324     int doublePointOne = parse.find(':');
325     int doublePointTwo = parse.find(':', doublePointOne + 1);
326
327     if (doublePointOne != -1 && doublePointTwo != -1) // Spec: "Full clock values"
328     {
329         unsigned int hours = parse.mid(0, 2).toUInt();
330         unsigned int minutes = parse.mid(3, 2).toUInt();
331         unsigned int seconds = parse.mid(6, 2).toUInt();
332         unsigned int milliseconds = 0;
333
334         result = (3600 * hours) + (60 * minutes) + seconds;
335
336         if (parse.find('.') != -1)
337         {
338             DeprecatedString temp = parse.mid(9, 2);
339             milliseconds = temp.toUInt();
340             result += (milliseconds * (1 / pow(10.0, int(temp.length()))));
341         }
342     }
343     else if (doublePointOne != -1 && doublePointTwo == -1) // Spec: "Partial clock values"
344     {
345         unsigned int minutes = parse.mid(0, 2).toUInt();
346         unsigned int seconds = parse.mid(3, 2).toUInt();
347         unsigned int milliseconds = 0;
348
349         result = (60 * minutes) + seconds;
350
351         if (parse.find('.') != -1)
352         {
353             DeprecatedString temp = parse.mid(6, 2);
354             milliseconds = temp.toUInt();
355             result += (milliseconds * (1 / pow(10.0, int(temp.length()))));
356         }
357     }
358     else // Spec: "Timecount values"
359     {
360         int dotPosition = parse.find('.');
361
362         if (parse.endsWith("h"))
363         {
364             if (dotPosition == -1)
365                 result = parse.mid(0, parse.length() - 1).toUInt() * 3600;
366             else
367             {
368                 result = parse.mid(0, dotPosition).toUInt() * 3600;
369                 DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 2);
370                 result += (3600.0 * temp.toUInt()) * (1 / pow(10.0, int(temp.length())));
371             }
372         }
373         else if (parse.endsWith("min"))
374         {
375             if (dotPosition == -1)
376                 result = parse.mid(0, parse.length() - 3).toUInt() * 60;
377             else
378             {
379                 result = parse.mid(0, dotPosition).toUInt() * 60;
380                 DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 4);
381                 result += (60.0 * temp.toUInt()) * (1 / pow(10.0, int(temp.length())));
382             }
383         }
384         else if (parse.endsWith("ms"))
385         {
386             if (dotPosition == -1)
387                 result = parse.mid(0, parse.length() - 2).toUInt() / 1000.0;
388             else
389             {
390                 result = parse.mid(0, dotPosition).toUInt() / 1000.0;
391                 DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 3);
392                 result += (temp.toUInt() / 1000.0) * (1 / pow(10.0, int(temp.length())));
393             }
394         }
395         else if (parse.endsWith("s"))
396         {
397             if (dotPosition == -1)
398                 result = parse.mid(0, parse.length() - 1).toUInt();
399             else
400             {
401                 result = parse.mid(0, dotPosition).toUInt();
402                 DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 2);
403                 result += temp.toUInt() * (1 / pow(10.0, int(temp.length())));
404             }
405         }
406         else
407             result = parse.toDouble();
408     }
409
410     return result;
411 }
412
413 void SVGAnimationElement::closeRenderer()
414 {
415     getDocument()->accessSVGExtensions()->timeScheduler()->addTimer(this, lround(getStartTime()));
416 }
417
418 String SVGAnimationElement::targetAttribute() const
419 {
420     if (!targetElement())
421         return String();
422     
423     SVGElement *target = targetElement();
424     SVGStyledElement *styled = NULL;
425     if (target && target->isStyled())
426         styled = static_cast<SVGStyledElement *>(target);
427     
428     String ret;
429
430     EAttributeType attributeType = static_cast<EAttributeType>(m_attributeType);
431     if (attributeType == ATTRIBUTETYPE_AUTO) {
432         attributeType = ATTRIBUTETYPE_XML;
433
434         // Spec: The implementation should match the attributeName to an attribute
435         // for the target element. The implementation must first search through the
436         // list of CSS properties for a matching property name, and if none is found,
437         // search the default XML namespace for the element.
438         if (styled && styled->style()) {
439             if (styled->style()->getPropertyCSSValue(m_attributeName))
440                 attributeType = ATTRIBUTETYPE_CSS;
441         }
442     }
443     
444     if (attributeType == ATTRIBUTETYPE_CSS) {
445         if (styled && styled->style())
446             ret = styled->style()->getPropertyValue(m_attributeName);
447     }
448
449     if (attributeType == ATTRIBUTETYPE_XML || ret.isEmpty())
450         ret = targetElement()->getAttribute(String(m_attributeName).impl());
451
452     return ret;
453 }
454
455 void SVGAnimationElement::setTargetAttribute(StringImpl *value)
456 {
457     SVGAnimationElement::setTargetAttribute(targetElement(), String(m_attributeName).impl(), value, static_cast<EAttributeType>(m_attributeType));
458 }
459
460 void SVGAnimationElement::setTargetAttribute(SVGElement *target, StringImpl *nameImpl, StringImpl *value, EAttributeType type)
461 {
462     if (!target || !nameImpl || !value)
463         return;
464     String name(nameImpl);
465     
466     SVGStyledElement *styled = NULL;
467     if (target && target->isStyled())
468         styled = static_cast<SVGStyledElement *>(target);
469
470     EAttributeType attributeType = type;
471     if (type == ATTRIBUTETYPE_AUTO)
472     {
473         attributeType = ATTRIBUTETYPE_XML;
474
475         // Spec: The implementation should match the attributeName to an attribute
476         // for the target element. The implementation must first search through the
477         // list of CSS properties for a matching property name, and if none is found,
478         // search the default XML namespace for the element.
479         if (styled && styled->style()) {
480             if (styled->style()->getPropertyCSSValue(name))
481                 attributeType = ATTRIBUTETYPE_CSS;
482         }
483     }
484     ExceptionCode ec;
485     if (attributeType == ATTRIBUTETYPE_CSS && styled && styled->style())
486         styled->style()->setProperty(name, value, "", ec);
487     else if (attributeType == ATTRIBUTETYPE_XML)
488         target->setAttribute(nameImpl, value, ec);
489 }
490
491 DeprecatedString SVGAnimationElement::attributeName() const
492 {
493     return m_attributeName;
494 }
495
496 bool SVGAnimationElement::connected() const
497 {
498     return m_connected;
499 }
500
501 bool SVGAnimationElement::isFrozen() const
502 {
503     return (m_fill == FILL_FREEZE);
504 }
505
506 bool SVGAnimationElement::isAdditive() const
507 {
508     return (m_additive == ADDITIVE_SUM) ||
509            (detectAnimationMode() == BY_ANIMATION);
510 }
511
512 bool SVGAnimationElement::isAccumulated() const
513 {
514     return (m_accumulate == ACCUMULATE_SUM) &&
515            (detectAnimationMode() != TO_ANIMATION);
516 }
517
518 EAnimationMode SVGAnimationElement::detectAnimationMode() const
519 {
520     if ((!m_from.isEmpty() && !m_to.isEmpty()) || (!m_to.isEmpty())) // to/from-to animation
521     {
522         if (!m_from.isEmpty()) // from-to animation
523             return FROM_TO_ANIMATION;
524         else
525             return TO_ANIMATION;
526     }
527     else if ((m_from.isEmpty() && m_to.isEmpty() && !m_by.isEmpty()) ||
528             (!m_from.isEmpty() && !m_by.isEmpty())) // by/from-by animation
529     {
530         if (!m_from.isEmpty()) // from-by animation
531             return FROM_BY_ANIMATION;
532         else
533             return BY_ANIMATION;
534     }
535     else if (m_values)
536         return VALUES_ANIMATION;
537
538     return NO_ANIMATION;
539 }
540
541 int SVGAnimationElement::calculateCurrentValueItem(double timePercentage)
542 {
543     if (!m_values)
544         return -1;
545     
546     unsigned long items = m_values->numberOfItems();
547
548     // Calculate the relative time percentages for each 'fade'.
549     double startTimes[items]; startTimes[0] = 0.0;
550     for (unsigned int i = 1; i < items; ++i)
551         startTimes[i] = (((2.0 * i)) / (items - 1)) / 2.0;
552
553     int itemByPercentage = -1;
554     for (unsigned int i = 0; i < items - 1; ++i)
555     {
556         if (timePercentage >= startTimes[i] && timePercentage <= startTimes[i + 1])
557         {
558             itemByPercentage = i;
559             break;
560         }
561     }
562
563     return itemByPercentage;
564 }
565
566 double SVGAnimationElement::calculateRelativeTimePercentage(double timePercentage, int currentItem)
567 {
568     if (currentItem == -1 || !m_values)
569         return 0.0;
570
571     unsigned long items = m_values->numberOfItems();
572
573     // Calculate the relative time percentages for each 'fade'.
574     double startTimes[items]; startTimes[0] = 0.0;
575     for (unsigned int i = 1; i < items; ++i)
576         startTimes[i] = (((2.0 * i)) / (items - 1)) / 2.0;
577
578     double beginTimePercentage = startTimes[currentItem];
579     double endTimePercentage = startTimes[currentItem + 1];
580
581     if ((endTimePercentage - beginTimePercentage) == 0.0)
582         return 0.0;
583
584     return ((timePercentage - beginTimePercentage) /
585             (endTimePercentage - beginTimePercentage));
586 }
587
588 double SVGAnimationElement::repeations() const
589 {
590     return m_repeations;
591 }
592
593 bool SVGAnimationElement::isIndefinite(double value) const
594 {
595     return (value == DBL_MAX);
596 }
597
598 // vim:ts=4:noet
599 #endif // SVG_SUPPORT
600