001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.bridge;
020:
021: import java.awt.geom.AffineTransform;
022: import java.awt.geom.Rectangle2D;
023:
024: import java.util.Calendar;
025:
026: import org.apache.batik.anim.AbstractAnimation;
027: import org.apache.batik.anim.AnimationEngine;
028: import org.apache.batik.anim.timing.TimedElement;
029: import org.apache.batik.anim.values.AnimatableValue;
030: import org.apache.batik.css.engine.CSSEngineEvent;
031: import org.apache.batik.dom.AbstractNode;
032: import org.apache.batik.dom.anim.AnimatableElement;
033: import org.apache.batik.dom.anim.AnimationTarget;
034: import org.apache.batik.dom.anim.AnimationTargetListener;
035: import org.apache.batik.dom.svg.AnimatedLiveAttributeValue;
036: import org.apache.batik.dom.svg.SVGAnimationContext;
037: import org.apache.batik.dom.svg.SVGOMElement;
038: import org.apache.batik.dom.util.XLinkSupport;
039: import org.apache.batik.util.SVGTypes;
040:
041: import org.w3c.dom.DOMException;
042: import org.w3c.dom.Element;
043: import org.w3c.dom.Node;
044: import org.w3c.dom.events.EventTarget;
045: import org.w3c.dom.events.MutationEvent;
046: import org.w3c.dom.svg.SVGElement;
047:
048: /**
049: * An abstract base class for the SVG animation element bridges.
050: *
051: * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a>
052: * @version $Id: SVGAnimationElementBridge.java 492528 2007-01-04 11:45:47Z cam $
053: */
054: public abstract class SVGAnimationElementBridge extends
055: AbstractSVGBridge implements GenericBridge,
056: BridgeUpdateHandler, SVGAnimationContext, AnimatableElement {
057:
058: /**
059: * The animation element.
060: */
061: protected SVGOMElement element;
062:
063: /**
064: * The BridgeContext to be used.
065: */
066: protected BridgeContext ctx;
067:
068: /**
069: * The AnimationEngine that manages all of the animations in the document.
070: */
071: protected SVGAnimationEngine eng;
072:
073: /**
074: * The TimedElement object that provides the timing for the animation.
075: */
076: protected TimedElement timedElement;
077:
078: /**
079: * The animation object that provides the values for the animation.
080: */
081: protected AbstractAnimation animation;
082:
083: /**
084: * The namespace URI of the attribute being animated.
085: */
086: protected String attributeNamespaceURI;
087:
088: /**
089: * The local name of the attribute or the name of the property being
090: * animated.
091: */
092: protected String attributeLocalName;
093:
094: /**
095: * The animation type. Must be one of the <code>ANIM_TYPE_*</code>
096: * constants defined in {@link AnimationEngine}.
097: */
098: protected short animationType;
099:
100: /**
101: * The target element of the animation.
102: */
103: protected SVGOMElement targetElement;
104:
105: /**
106: * The AnimationTarget the provides a context to the animation engine.
107: */
108: protected AnimationTarget animationTarget;
109:
110: /**
111: * Returns the TimedElement for the animation.
112: */
113: public TimedElement getTimedElement() {
114: return timedElement;
115: }
116:
117: // AnimatableElement /////////////////////////////////////////////////////
118:
119: /**
120: * Returns the underlying value of the animated attribute. Used for
121: * composition of additive animations. This should be overridden in
122: * descendant classes that are for 'other' animations.
123: */
124: public AnimatableValue getUnderlyingValue() {
125: if (animationType == AnimationEngine.ANIM_TYPE_XML) {
126: return animationTarget.getUnderlyingValue(
127: attributeNamespaceURI, attributeLocalName);
128: } else {
129: return eng.getUnderlyingCSSValue(element, animationTarget,
130: attributeLocalName);
131: }
132: }
133:
134: // GenericBridge /////////////////////////////////////////////////////////
135:
136: /**
137: * Handles this animation element.
138: *
139: * @param ctx the bridge context to use
140: * @param e the element being handled
141: */
142: public void handleElement(BridgeContext ctx, Element e) {
143: if (ctx.isDynamic() && BridgeContext.getSVGContext(e) == null) {
144: SVGAnimationElementBridge b = (SVGAnimationElementBridge) getInstance();
145: b.element = (SVGOMElement) e;
146: b.ctx = ctx;
147: b.eng = ctx.getAnimationEngine();
148: b.element.setSVGContext(b);
149: if (b.eng.hasStarted()) {
150: b.initializeAnimation();
151: b.initializeTimedElement();
152: } else {
153: b.eng.addInitialBridge(b);
154: }
155: }
156: }
157:
158: /**
159: * Parses the animation element's target attributes and adds it to the
160: * document's AnimationEngine.
161: */
162: protected void initializeAnimation() {
163: // Determine the target element.
164: String uri = XLinkSupport.getXLinkHref(element);
165: Node t;
166: if (uri.length() == 0) {
167: t = element.getParentNode();
168: } else {
169: t = ctx.getReferencedElement(element, uri);
170: if (t.getOwnerDocument() != element.getOwnerDocument()) {
171: throw new BridgeException(ctx, element,
172: ErrorConstants.ERR_URI_BAD_TARGET,
173: new Object[] { uri });
174: }
175: }
176: animationTarget = null;
177: if (t instanceof SVGOMElement) {
178: targetElement = (SVGOMElement) t;
179: animationTarget = targetElement;
180: }
181: if (animationTarget == null) {
182: throw new BridgeException(ctx, element,
183: ErrorConstants.ERR_URI_BAD_TARGET,
184: new Object[] { uri });
185: }
186:
187: // Get the attribute/property name.
188: String an = element.getAttributeNS(null,
189: SVG_ATTRIBUTE_NAME_ATTRIBUTE);
190: int ci = an.indexOf(':');
191: if (ci == -1) {
192: if (element.hasProperty(an)) {
193: animationType = AnimationEngine.ANIM_TYPE_CSS;
194: attributeLocalName = an;
195: } else {
196: animationType = AnimationEngine.ANIM_TYPE_XML;
197: attributeLocalName = an;
198: }
199: } else {
200: animationType = AnimationEngine.ANIM_TYPE_XML;
201: String prefix = an.substring(0, ci);
202: attributeNamespaceURI = element.lookupNamespaceURI(prefix);
203: attributeLocalName = an.substring(ci + 1);
204: }
205: if (animationType == AnimationEngine.ANIM_TYPE_CSS
206: && !targetElement
207: .isPropertyAnimatable(attributeLocalName)
208: || animationType == AnimationEngine.ANIM_TYPE_XML
209: && !targetElement.isAttributeAnimatable(
210: attributeNamespaceURI, attributeLocalName)) {
211: throw new BridgeException(ctx, element,
212: "attribute.not.animatable", new Object[] {
213: targetElement.getNodeName(), an });
214: }
215:
216: // Check that the attribute/property is animatable with this
217: // animation element.
218: int type;
219: if (animationType == AnimationEngine.ANIM_TYPE_CSS) {
220: type = targetElement.getPropertyType(attributeLocalName);
221: } else {
222: type = targetElement.getAttributeType(
223: attributeNamespaceURI, attributeLocalName);
224: }
225: if (!canAnimateType(type)) {
226: throw new BridgeException(ctx, element,
227: "type.not.animatable", new Object[] {
228: targetElement.getNodeName(), an,
229: element.getNodeName() });
230: }
231:
232: // Add the animation.
233: timedElement = createTimedElement();
234: animation = createAnimation(animationTarget);
235: eng.addAnimation(animationTarget, animationType,
236: attributeNamespaceURI, attributeLocalName, animation);
237: }
238:
239: /**
240: * Returns whether the animation element being handled by this bridge can
241: * animate attributes of the specified type.
242: * @param type one of the TYPE_ constants defined in {@link SVGTypes}.
243: */
244: protected abstract boolean canAnimateType(int type);
245:
246: /**
247: * Returns whether the specified {@link AnimatableValue} is of a type allowed
248: * by this animation.
249: */
250: protected boolean checkValueType(AnimatableValue v) {
251: return true;
252: }
253:
254: /**
255: * Parses the animation element's timing attributes and initializes the
256: * {@link TimedElement} object.
257: */
258: protected void initializeTimedElement() {
259: initializeTimedElement(timedElement);
260: timedElement.initialize();
261: }
262:
263: /**
264: * Creates a TimedElement for the animation element.
265: */
266: protected TimedElement createTimedElement() {
267: return new SVGTimedElement();
268: }
269:
270: /**
271: * Creates the animation object for the animation element.
272: */
273: protected abstract AbstractAnimation createAnimation(
274: AnimationTarget t);
275:
276: /**
277: * Parses an attribute as an AnimatableValue.
278: */
279: protected AnimatableValue parseAnimatableValue(String an) {
280: if (!element.hasAttributeNS(null, an)) {
281: return null;
282: }
283: String s = element.getAttributeNS(null, an);
284: AnimatableValue val = eng.parseAnimatableValue(element,
285: animationTarget, attributeNamespaceURI,
286: attributeLocalName,
287: animationType == AnimationEngine.ANIM_TYPE_CSS, s);
288: if (!checkValueType(val)) {
289: throw new BridgeException(ctx, element,
290: ErrorConstants.ERR_ATTRIBUTE_VALUE_MALFORMED,
291: new Object[] { an, s });
292: }
293: return val;
294: }
295:
296: /**
297: * Initializes the timing attributes of the timed element.
298: */
299: protected void initializeTimedElement(TimedElement timedElement) {
300: timedElement.parseAttributes(element.getAttributeNS(null,
301: "begin"), element.getAttributeNS(null, "dur"), element
302: .getAttributeNS(null, "end"), element.getAttributeNS(
303: null, "min"), element.getAttributeNS(null, "max"),
304: element.getAttributeNS(null, "repeatCount"), element
305: .getAttributeNS(null, "repeatDur"), element
306: .getAttributeNS(null, "fill"), element
307: .getAttributeNS(null, "restart"));
308: }
309:
310: // BridgeUpdateHandler ///////////////////////////////////////////////////
311:
312: /**
313: * Invoked when an MutationEvent of type 'DOMAttrModified' is fired.
314: */
315: public void handleDOMAttrModifiedEvent(MutationEvent evt) {
316: }
317:
318: /**
319: * Invoked when an MutationEvent of type 'DOMNodeInserted' is fired.
320: */
321: public void handleDOMNodeInsertedEvent(MutationEvent evt) {
322: }
323:
324: /**
325: * Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired.
326: */
327: public void handleDOMNodeRemovedEvent(MutationEvent evt) {
328: element.setSVGContext(null);
329: dispose();
330: }
331:
332: /**
333: * Invoked when an MutationEvent of type 'DOMCharacterDataModified'
334: * is fired.
335: */
336: public void handleDOMCharacterDataModified(MutationEvent evt) {
337: }
338:
339: /**
340: * Invoked when an CSSEngineEvent is fired.
341: */
342: public void handleCSSEngineEvent(CSSEngineEvent evt) {
343: }
344:
345: /**
346: * Invoked when the animated value of an animatable attribute has changed.
347: */
348: public void handleAnimatedAttributeChanged(
349: AnimatedLiveAttributeValue alav) {
350: }
351:
352: /**
353: * Invoked when an 'other' animation value has changed.
354: */
355: public void handleOtherAnimationChanged(String type) {
356: }
357:
358: /**
359: * Disposes this BridgeUpdateHandler and releases all resources.
360: */
361: public void dispose() {
362: if (element.getSVGContext() == null) {
363: // Only remove the animation if this is not part of a rebuild.
364: eng.removeAnimation(animation);
365: element = null;
366: }
367: }
368:
369: // SVGContext ///////////////////////////////////////////////////////////
370:
371: /**
372: * Returns the size of a px CSS unit in millimeters.
373: */
374: public float getPixelUnitToMillimeter() {
375: return ctx.getUserAgent().getPixelUnitToMillimeter();
376: }
377:
378: /**
379: * Returns the size of a px CSS unit in millimeters.
380: * This will be removed after next release.
381: * @see #getPixelUnitToMillimeter()
382: */
383: public float getPixelToMM() {
384: return getPixelUnitToMillimeter();
385:
386: }
387:
388: public Rectangle2D getBBox() {
389: return null;
390: }
391:
392: public AffineTransform getScreenTransform() {
393: return ctx.getUserAgent().getTransform();
394: }
395:
396: public void setScreenTransform(AffineTransform at) {
397: ctx.getUserAgent().setTransform(at);
398: }
399:
400: public AffineTransform getCTM() {
401: return null;
402: }
403:
404: public AffineTransform getGlobalTransform() {
405: return null;
406: }
407:
408: public float getViewportWidth() {
409: return ctx.getBlockWidth(element);
410: }
411:
412: public float getViewportHeight() {
413: return ctx.getBlockHeight(element);
414: }
415:
416: public float getFontSize() {
417: return 0;
418: }
419:
420: public float svgToUserSpace(float v, int type, int pcInterp) {
421: return 0;
422: }
423:
424: /**
425: * Adds a listener for changes to the given attribute value.
426: */
427: public void addTargetListener(String pn, AnimationTargetListener l) {
428: }
429:
430: /**
431: * Removes a listener for changes to the given attribute value.
432: */
433: public void removeTargetListener(String pn,
434: AnimationTargetListener l) {
435: }
436:
437: // SVGAnimationContext ///////////////////////////////////////////////////
438:
439: /**
440: * <b>DOM</b>: Implements {@link
441: * org.w3c.dom.svg.SVGAnimationElement#getTargetElement()}.
442: */
443: public SVGElement getTargetElement() {
444: return targetElement;
445: }
446:
447: /**
448: * <b>DOM</b>: Implements {@link
449: * org.w3c.dom.svg.SVGAnimationElement#getStartTime()}.
450: */
451: public float getStartTime() {
452: return timedElement.getCurrentBeginTime();
453: }
454:
455: /**
456: * <b>DOM</b>: Implements {@link
457: * org.w3c.dom.svg.SVGAnimationElement#getCurrentTime()}.
458: */
459: public float getCurrentTime() {
460: return timedElement.getLastSampleTime();
461: }
462:
463: /**
464: * <b>DOM</b>: Implements {@link
465: * org.w3c.dom.svg.SVGAnimationElement#getSimpleDuration()}. With the
466: * difference that an indefinite simple duration is returned as
467: * {@link TimedElement#INDEFINITE}, rather than throwing an exception.
468: */
469: public float getSimpleDuration() {
470: return timedElement.getSimpleDur();
471: }
472:
473: // ElementTimeControl ////////////////////////////////////////////////////
474:
475: /**
476: * <b>DOM</b>: Implements {@link
477: * org.w3c.dom.smil.ElementTimeControl#beginElement()}.
478: */
479: public boolean beginElement() throws DOMException {
480: timedElement.beginElement();
481: return timedElement.canBegin();
482: }
483:
484: /**
485: * <b>DOM</b>: Implements {@link
486: * org.w3c.dom.smil.ElementTimeControl#beginElementAt(float)}.
487: */
488: public boolean beginElementAt(float offset) throws DOMException {
489: timedElement.beginElement(offset);
490: // XXX Not right, but who knows if it is possible to begin
491: // at some arbitrary point in the future.
492: return true;
493: }
494:
495: /**
496: * <b>DOM</b>: Implements {@link
497: * org.w3c.dom.smil.ElementTimeControl#endElement()}.
498: */
499: public boolean endElement() throws DOMException {
500: timedElement.endElement();
501: return timedElement.canEnd();
502: }
503:
504: /**
505: * <b>DOM</b>: Implements {@link
506: * org.w3c.dom.smil.ElementTimeControl#endElementAt(float)}.
507: */
508: public boolean endElementAt(float offset) throws DOMException {
509: timedElement.endElement(offset);
510: // XXX Not right, but who knows if it is possible to begin
511: // at some arbitrary point in the future.
512: return true;
513: }
514:
515: /**
516: * Returns whether this is a constant animation (i.e., a 'set' animation).
517: */
518: protected boolean isConstantAnimation() {
519: return false;
520: }
521:
522: /**
523: * A TimedElement class for SVG animation elements.
524: */
525: protected class SVGTimedElement extends TimedElement {
526:
527: /**
528: * Returns the DOM element this timed element is for.
529: */
530: public Element getElement() {
531: return element;
532: }
533:
534: /**
535: * Fires a TimeEvent of the given type on this element.
536: * @param eventType the type of TimeEvent ("beginEvent", "endEvent"
537: * or "repeatEvent").
538: * @param time the timestamp of the event object
539: */
540: protected void fireTimeEvent(String eventType, Calendar time,
541: int detail) {
542: AnimationSupport.fireTimeEvent(element, eventType, time,
543: detail);
544: }
545:
546: /**
547: * Invoked to indicate this timed element became active at the
548: * specified time.
549: * @param begin the time the element became active, in document
550: * simple time
551: */
552: protected void toActive(float begin) {
553: eng.toActive(animation, begin);
554: }
555:
556: /**
557: * Invoked to indicate that this timed element became inactive.
558: * @param stillActive if true, indicates that the element is still
559: * actually active, but between the end of the
560: * computed repeat duration and the end of the
561: * interval
562: * @param isFrozen whether the element is frozen or not
563: */
564: protected void toInactive(boolean stillActive, boolean isFrozen) {
565: eng.toInactive(animation, isFrozen);
566: }
567:
568: /**
569: * Invoked to indicate that this timed element has had its fill removed.
570: */
571: protected void removeFill() {
572: eng.removeFill(animation);
573: }
574:
575: /**
576: * Invoked to indicate that this timed element has been sampled at the
577: * given time.
578: * @param simpleTime the sample time in local simple time
579: * @param simpleDur the simple duration of the element
580: * @param repeatIteration the repeat iteration during which the element
581: * was sampled
582: */
583: protected void sampledAt(float simpleTime, float simpleDur,
584: int repeatIteration) {
585: eng.sampledAt(animation, simpleTime, simpleDur,
586: repeatIteration);
587: }
588:
589: /**
590: * Invoked to indicate that this timed element has been sampled
591: * at the end of its active time, at an integer multiple of the
592: * simple duration. This is the "last" value that will be used
593: * for filling, which cannot be sampled normally.
594: */
595: protected void sampledLastValue(int repeatIteration) {
596: eng.sampledLastValue(animation, repeatIteration);
597: }
598:
599: /**
600: * Returns the timed element with the given ID.
601: */
602: protected TimedElement getTimedElementById(String id) {
603: return AnimationSupport.getTimedElementById(id, element);
604: }
605:
606: /**
607: * Returns the event target with the given ID.
608: */
609: protected EventTarget getEventTargetById(String id) {
610: return AnimationSupport.getEventTargetById(id, element);
611: }
612:
613: /**
614: * Returns the event target that should be listened to for
615: * access key events.
616: */
617: protected EventTarget getRootEventTarget() {
618: return (EventTarget) element.getOwnerDocument();
619: }
620:
621: /**
622: * Returns the target of this animation as an {@link EventTarget}. Used
623: * for eventbase timing specifiers where the element ID is omitted.
624: */
625: protected EventTarget getAnimationEventTarget() {
626: return targetElement;
627: }
628:
629: /**
630: * Returns whether this timed element comes before the given timed
631: * element in document order.
632: */
633: public boolean isBefore(TimedElement other) {
634: Element e = ((SVGTimedElement) other).getElement();
635: int pos = ((AbstractNode) element)
636: .compareDocumentPosition(e);
637: return (pos & AbstractNode.DOCUMENT_POSITION_PRECEDING) != 0;
638: }
639:
640: /**
641: * Returns a string representation of this animation.
642: */
643: public String toString() {
644: if (element != null) {
645: String id = element.getAttributeNS(null, "id");
646: if (id.length() != 0) {
647: return id;
648: }
649: }
650: return super .toString();
651: }
652:
653: /**
654: * Returns whether this timed element is for a constant animation (i.e.,
655: * a 'set' animation.
656: */
657: protected boolean isConstantAnimation() {
658: return SVGAnimationElementBridge.this.isConstantAnimation();
659: }
660: }
661: }
|