| /* |
| Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org> |
| 2004, 2005 Rob Buis <buis@kde.org> |
| Copyright (C) 2006 Apple Computer, Inc. |
| |
| This file is part of the KDE project |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Library General Public |
| License as published by the Free Software Foundation; either |
| version 2 of the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Library General Public License for more details. |
| |
| You should have received a copy of the GNU Library General Public License |
| along with this library; see the file COPYING.LIB. If not, write to |
| the Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. |
| */ |
| |
| #include "config.h" |
| #ifdef SVG_SUPPORT |
| #include "KSVGTimeScheduler.h" |
| |
| #include "Document.h" |
| #include "SVGAnimateColorElement.h" |
| #include "SVGAnimateTransformElement.h" |
| #include "SVGTransformList.h" |
| #include "SVGDOMImplementation.h" |
| #include "SVGMatrix.h" |
| #include "SVGNames.h" |
| #include "SVGStyledElement.h" |
| #include "SVGStyledTransformableElement.h" |
| #include "SystemTime.h" |
| #include "Timer.h" |
| |
| namespace WebCore { |
| |
| const double staticTimerInterval = 0.050; // 50 ms |
| |
| typedef HashSet<SVGAnimationElement*> SVGNotifySet; |
| |
| class SVGTimer : private Timer<TimeScheduler> |
| { |
| public: |
| SVGTimer(TimeScheduler*, double interval, bool singleShot); |
| |
| void start(); |
| using Timer<TimeScheduler>::stop; |
| using Timer<TimeScheduler>::isActive; |
| |
| void notifyAll(); |
| void addNotify(SVGAnimationElement*, bool enabled = false); |
| void removeNotify(SVGAnimationElement*); |
| |
| static SVGTimer* downcast(Timer<TimeScheduler>* t) { return static_cast<SVGTimer*>(t); } |
| |
| private: |
| double calculateTimePercentage(double elapsed, double start, double end, double duration, double repetitions); |
| |
| TimeScheduler* m_scheduler; |
| double m_interval; |
| bool m_singleShot; |
| |
| SVGNotifySet m_notifySet; |
| SVGNotifySet m_enabledNotifySet; |
| }; |
| |
| SVGTimer::SVGTimer(TimeScheduler* scheduler, double interval, bool singleShot) |
| : Timer<TimeScheduler>(scheduler, &TimeScheduler::timerFired) |
| , m_scheduler(scheduler), m_interval(interval), m_singleShot(singleShot) |
| { |
| } |
| |
| void SVGTimer::start() |
| { |
| if (m_singleShot) |
| startOneShot(m_interval); |
| else |
| startRepeating(m_interval); |
| } |
| |
| double SVGTimer::calculateTimePercentage(double elapsed, double start, double end, double duration, double repetitions) |
| { |
| double percentage = 0.0; |
| |
| double useElapsed = elapsed - (duration * repetitions); |
| |
| if (duration > 0.0 && end == 0.0) |
| percentage = 1.0 - (((start + duration) - useElapsed) / duration); |
| else if (duration > 0.0 && end != 0.0) { |
| if (duration > end) |
| percentage = 1.0 - (((start + end) - useElapsed) / end); |
| else |
| percentage = 1.0 - (((start + duration) - useElapsed) / duration); |
| } else if(duration == 0.0 && end != 0.0) |
| percentage = 1.0 - (((start + end) - useElapsed) / end); |
| |
| return percentage; |
| } |
| |
| void SVGTimer::notifyAll() |
| { |
| if (m_enabledNotifySet.isEmpty()) |
| return; |
| |
| double elapsed = m_scheduler->elapsed() * 1000.0; // Take time now. |
| |
| // First build a list of animation elements per target element |
| // This is important to decide about the order & priority of |
| // the animations -> 'additive' support is handled this way. |
| typedef HashMap<SVGElement*, Vector<SVGAnimationElement*> > TargetAnimationMap; |
| TargetAnimationMap targetMap; |
| |
| SVGNotifySet::const_iterator end = m_notifySet.end(); |
| for (SVGNotifySet::const_iterator it = m_notifySet.begin(); it != end; ++it) { |
| SVGAnimationElement* animation = *it; |
| |
| // If we're dealing with a disabled element with fill="freeze", |
| // we have to take it into account for further calculations. |
| if (!m_enabledNotifySet.contains(animation)) { |
| if (!animation->isFrozen()) |
| continue; |
| if (elapsed <= (animation->getStartTime() + animation->getSimpleDuration())) |
| continue; |
| } |
| |
| SVGElement* target = const_cast<SVGElement *>(animation->targetElement()); |
| TargetAnimationMap::iterator i = targetMap.find(target); |
| if (i != targetMap.end()) |
| i->second.append(animation); |
| else { |
| Vector<SVGAnimationElement*> list; |
| list.append(animation); |
| targetMap.set(target, list); |
| } |
| } |
| |
| TargetAnimationMap::iterator targetIterator = targetMap.begin(); |
| TargetAnimationMap::iterator tend = targetMap.end(); |
| for (; targetIterator != tend; ++targetIterator) { |
| HashMap<String, Color> targetColor; // special <animateColor> case |
| RefPtr<SVGTransformList> targetTransforms; // special <animateTransform> case |
| |
| unsigned count = targetIterator->second.size(); |
| for (unsigned i = 0; i < count; ++i) { |
| SVGAnimationElement* animation = targetIterator->second[i]; |
| |
| double end = animation->getEndTime(); |
| double start = animation->getStartTime(); |
| double duration = animation->getSimpleDuration(); |
| double repetitions = animation->repeations(); |
| |
| // Validate animation timing settings: |
| // #1 (duration > 0) -> fine |
| // #2 (duration <= 0.0 && end > 0) -> fine |
| |
| if((duration <= 0.0 && end <= 0.0) || |
| (animation->isIndefinite(duration) && end <= 0.0)) // Ignore dur="0" or dur="-neg" |
| continue; |
| |
| float percentage = calculateTimePercentage(elapsed, start, end, duration, repetitions); |
| |
| if(percentage <= 1.0 || animation->connected()) |
| animation->handleTimerEvent(percentage); |
| |
| // FIXME: Disable animateTransform until SVGList can be fixed. |
| #if 0 |
| // Special cases for animate* objects depending on 'additive' attribute |
| if(animation->hasTagName(SVGNames::animateTransformTag)) |
| { |
| SVGAnimateTransformElement *animTransform = static_cast<SVGAnimateTransformElement *>(animation); |
| if(!animTransform) |
| continue; |
| |
| RefPtr<SVGMatrix> transformMatrix = animTransform->transformMatrix(); |
| if(!transformMatrix) |
| continue; |
| |
| RefPtr<SVGMatrix> initialMatrix = animTransform->initialMatrix(); |
| RefPtr<SVGTransform> data = new SVGTransform(); |
| |
| if(!targetTransforms) // lazy creation, only if needed. |
| { |
| targetTransforms = new SVGTransformList(); |
| |
| if(animation->isAdditive() && initialMatrix) |
| { |
| RefPtr<SVGMatrix> matrix = new SVGMatrix(initialMatrix->matrix()); |
| |
| data->setMatrix(matrix.get()); |
| targetTransforms->appendItem(data.get()); |
| |
| data = new SVGTransform(); |
| } |
| } |
| |
| if(targetTransforms->numberOfItems() <= 1) |
| data->setMatrix(transformMatrix.get()); |
| else |
| { |
| if(!animation->isAdditive()) |
| targetTransforms->clear(); |
| |
| data->setMatrix(transformMatrix.get()); |
| } |
| |
| targetTransforms->appendItem(data.get()); |
| } |
| else |
| #endif |
| if(animation->hasTagName(SVGNames::animateColorTag)) |
| { |
| SVGAnimateColorElement *animColor = static_cast<SVGAnimateColorElement *>(animation); |
| if(!animColor) |
| continue; |
| |
| String name = animColor->attributeName(); |
| Color color = animColor->color(); |
| |
| if(!targetColor.contains(name)) |
| { |
| if(animation->isAdditive()) |
| { |
| int r = animColor->initialColor().red() + color.red(); |
| int g = animColor->initialColor().green() + color.green(); |
| int b = animColor->initialColor().blue() + color.blue(); |
| |
| targetColor.set(name, animColor->clampColor(r, g, b)); |
| } |
| else |
| targetColor.set(name, color); |
| } |
| else |
| { |
| if(!animation->isAdditive()) |
| targetColor.set(name, color); |
| else |
| { |
| Color baseColor = targetColor.get(name); |
| int r = baseColor.red() + color.red(); |
| int g = baseColor.green() + color.green(); |
| int b = baseColor.blue() + color.blue(); |
| |
| targetColor.set(name, animColor->clampColor(r, g, b)); |
| } |
| } |
| } |
| } |
| |
| // Handle <animateTransform>. |
| if (targetTransforms) { |
| SVGElement* key = targetIterator->first; |
| if (key && key->isStyled() && key->isStyledTransformable()) { |
| SVGStyledTransformableElement *transform = static_cast<SVGStyledTransformableElement *>(key); |
| transform->setTransform(targetTransforms.get()); |
| transform->updateLocalTransform(transform->transform()); |
| } |
| } |
| |
| // Handle <animateColor>. |
| HashMap<String, Color>::iterator cend = targetColor.end(); |
| for(HashMap<String, Color>::iterator cit = targetColor.begin(); cit != cend; ++cit) |
| { |
| if(cit->second.isValid()) |
| { |
| SVGAnimationElement::setTargetAttribute(targetIterator->first, |
| cit->first.impl(), |
| String(cit->second.name()).impl()); |
| } |
| } |
| } |
| |
| // Make a second pass through the map to avoid multiple setChanged calls on the same element. |
| for (targetIterator = targetMap.begin(); targetIterator != tend; ++targetIterator) { |
| SVGElement *key = targetIterator->first; |
| if (key && key->isStyled()) |
| static_cast<SVGStyledElement *>(key)->setChanged(true); |
| } |
| } |
| |
| void SVGTimer::addNotify(SVGAnimationElement* element, bool enabled) |
| { |
| m_notifySet.add(element); |
| if (enabled) |
| m_enabledNotifySet.add(element); |
| else |
| m_enabledNotifySet.remove(element); |
| } |
| |
| void SVGTimer::removeNotify(SVGAnimationElement *element) |
| { |
| // FIXME: Why do we keep a pointer to the element forever (marked disabled)? |
| // That can't be right! |
| |
| m_enabledNotifySet.remove(element); |
| if (m_enabledNotifySet.isEmpty()) |
| stop(); |
| } |
| |
| TimeScheduler::TimeScheduler(Document *document) |
| : m_creationTime(currentTime()), m_savedTime(0), m_document(document) |
| { |
| // Don't start this timer yet. |
| m_intervalTimer = new SVGTimer(this, staticTimerInterval, false); |
| } |
| |
| TimeScheduler::~TimeScheduler() |
| { |
| deleteAllValues(m_timerSet); |
| delete m_intervalTimer; |
| } |
| |
| void TimeScheduler::addTimer(SVGAnimationElement* element, unsigned ms) |
| { |
| SVGTimer* svgTimer = new SVGTimer(this, ms * 0.001, true); |
| svgTimer->addNotify(element, true); |
| m_timerSet.add(svgTimer); |
| m_intervalTimer->addNotify(element, false); |
| } |
| |
| void TimeScheduler::connectIntervalTimer(SVGAnimationElement* element) |
| { |
| m_intervalTimer->addNotify(element, true); |
| } |
| |
| void TimeScheduler::disconnectIntervalTimer(SVGAnimationElement* element) |
| { |
| m_intervalTimer->removeNotify(element); |
| } |
| |
| void TimeScheduler::startAnimations() |
| { |
| m_creationTime = currentTime(); |
| |
| SVGTimerSet::iterator end = m_timerSet.end(); |
| for (SVGTimerSet::iterator it = m_timerSet.begin(); it != end; ++it) { |
| SVGTimer* svgTimer = *it; |
| if (svgTimer && !svgTimer->isActive()) |
| svgTimer->start(); |
| } |
| } |
| |
| void TimeScheduler::toggleAnimations() |
| { |
| if (m_intervalTimer->isActive()) { |
| m_intervalTimer->stop(); |
| m_savedTime = currentTime(); |
| } else { |
| if (m_savedTime != 0) { |
| m_creationTime += currentTime() - m_savedTime; |
| m_savedTime = 0; |
| } |
| m_intervalTimer->start(); |
| } |
| } |
| |
| bool TimeScheduler::animationsPaused() const |
| { |
| return !m_intervalTimer->isActive(); |
| } |
| |
| void TimeScheduler::timerFired(Timer<TimeScheduler>* baseTimer) |
| { |
| // Get the pointer now, because notifyAll could make the document, |
| // including this TimeScheduler, go away. |
| RefPtr<Document> doc = m_document; |
| |
| SVGTimer* timer = SVGTimer::downcast(baseTimer); |
| |
| timer->notifyAll(); |
| |
| // FIXME: Is it really safe to look at m_intervalTimer now? |
| // Isn't it possible the TimeScheduler was deleted already? |
| // If so, timer, m_timerSet, and m_intervalTimer have all |
| // been deleted. May need to reference count the TimeScheduler |
| // to work around this, and ref/deref it in this function. |
| if (timer != m_intervalTimer) { |
| ASSERT(!timer->isActive()); |
| ASSERT(m_timerSet.contains(timer)); |
| m_timerSet.remove(timer); |
| delete timer; |
| |
| // The singleShot timers of ie. <animate> with begin="3s" are notified |
| // by the previous call, and now all connections to the interval timer |
| // are created and now we just need to fire that timer (Niko) |
| if (!m_intervalTimer->isActive()) |
| m_intervalTimer->start(); |
| } |
| |
| // Update any 'dirty' shapes. |
| doc->updateRendering(); |
| } |
| |
| double TimeScheduler::elapsed() const |
| { |
| return currentTime() - m_creationTime; |
| } |
| |
| } // namespace |
| |
| // vim:ts=4:noet |
| #endif // SVG_SUPPORT |