0001: /*
0002:
0003: Licensed to the Apache Software Foundation (ASF) under one or more
0004: contributor license agreements. See the NOTICE file distributed with
0005: this work for additional information regarding copyright ownership.
0006: The ASF licenses this file to You under the Apache License, Version 2.0
0007: (the "License"); you may not use this file except in compliance with
0008: the License. You may obtain a copy of the License at
0009:
0010: http://www.apache.org/licenses/LICENSE-2.0
0011:
0012: Unless required by applicable law or agreed to in writing, software
0013: distributed under the License is distributed on an "AS IS" BASIS,
0014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0015: See the License for the specific language governing permissions and
0016: limitations under the License.
0017:
0018: */
0019: package org.apache.batik.anim.timing;
0020:
0021: import java.util.*;
0022:
0023: import org.apache.batik.anim.AnimationException;
0024: import org.apache.batik.i18n.LocalizableSupport;
0025: import org.apache.batik.parser.ClockHandler;
0026: import org.apache.batik.parser.ClockParser;
0027: import org.apache.batik.parser.ParseException;
0028: import org.apache.batik.util.SMILConstants;
0029:
0030: import org.w3c.dom.Element;
0031: import org.w3c.dom.events.Event;
0032: import org.w3c.dom.events.EventTarget;
0033:
0034: /**
0035: * An abstract base class for elements that can have timing applied to them.
0036: * The concrete versions of this class do not necessarily have to be the
0037: * same as the DOM class, and in fact, this will mostly be impossible unless
0038: * creating new DOM classes that inherit from these elements.
0039: *
0040: * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a>
0041: * @version $Id: TimedElement.java 522271 2007-03-25 14:42:45Z dvholten $
0042: */
0043: public abstract class TimedElement implements SMILConstants {
0044:
0045: // Constants for fill mode.
0046: public static final int FILL_REMOVE = 0;
0047: public static final int FILL_FREEZE = 1;
0048:
0049: // Constants for restart mode.
0050: public static final int RESTART_ALWAYS = 0;
0051: public static final int RESTART_WHEN_NOT_ACTIVE = 1;
0052: public static final int RESTART_NEVER = 2;
0053:
0054: // Constants for time values.
0055: public static final float INDEFINITE = Float.POSITIVE_INFINITY;
0056: public static final float UNRESOLVED = Float.NaN;
0057:
0058: /**
0059: * The root time container.
0060: */
0061: protected TimedDocumentRoot root;
0062:
0063: /**
0064: * The parent time container.
0065: */
0066: protected TimeContainer parent;
0067:
0068: /**
0069: * Timing specifiers for the begin times of this element.
0070: */
0071: protected TimingSpecifier[] beginTimes;
0072:
0073: /**
0074: * Timing specifiers for the end times of this element.
0075: */
0076: protected TimingSpecifier[] endTimes;
0077:
0078: /**
0079: * Duration of this element, if {@link #durMedia} <code>= false</code>.
0080: * If unspecified, it will be {@link #UNRESOLVED}.
0081: */
0082: protected float simpleDur;
0083:
0084: /**
0085: * Whether the simple duration of this element should be equal
0086: * to the implicit duration.
0087: */
0088: protected boolean durMedia;
0089:
0090: /**
0091: * The number of repeats. If unspecified, it will be
0092: * {@link #UNRESOLVED}.
0093: */
0094: protected float repeatCount;
0095:
0096: /**
0097: * The duration of repeats. If unspecified, it will be
0098: * {@link #UNRESOLVED}.
0099: */
0100: protected float repeatDur;
0101:
0102: /**
0103: * The current repeat iteration.
0104: */
0105: protected int currentRepeatIteration;
0106:
0107: /**
0108: * The local active time of the last repeat.
0109: */
0110: protected float lastRepeatTime;
0111:
0112: /**
0113: * The fill mode for this element. Uses the FILL_* constants
0114: * defined in this class.
0115: */
0116: protected int fillMode;
0117:
0118: /**
0119: * The restart mode for this element. Uses the RESTART_* constants
0120: * defined in this class.
0121: */
0122: protected int restartMode;
0123:
0124: /**
0125: * The minimum active duration of this element. If {@link #minMedia}
0126: * <code>= true</code>, it will be <code>0f</code>.
0127: */
0128: protected float min;
0129:
0130: /**
0131: * Whether the min value was specified as 'media'.
0132: */
0133: protected boolean minMedia;
0134:
0135: /**
0136: * The maximum active duration of this element. If {@link #maxMedia}
0137: * <code>= true</code>, it will be {@link #INDEFINITE}.
0138: */
0139: protected float max;
0140:
0141: /**
0142: * Whether the max value was specified as 'media'.
0143: */
0144: protected boolean maxMedia;
0145:
0146: /**
0147: * Whether the element is currently active.
0148: */
0149: protected boolean isActive;
0150:
0151: /**
0152: * Whether the element is currently frozen.
0153: */
0154: protected boolean isFrozen;
0155:
0156: /**
0157: * The current time of this element in local active time.
0158: */
0159: protected float lastSampleTime;
0160:
0161: /**
0162: * The computed repeat duration of the element.
0163: */
0164: protected float repeatDuration;
0165:
0166: /**
0167: * List of begin InstanceTimes.
0168: */
0169: protected List beginInstanceTimes = new ArrayList();
0170:
0171: /**
0172: * List of end InstanceTimes.
0173: */
0174: protected List endInstanceTimes = new ArrayList();
0175:
0176: /**
0177: * The current Interval.
0178: */
0179: protected Interval currentInterval;
0180:
0181: /**
0182: * The end time of the previous interval, initially
0183: * {@link Float#NEGATIVE_INFINITY}.
0184: */
0185: protected float lastIntervalEnd;
0186:
0187: /**
0188: * List of previous intervals.
0189: */
0190: protected LinkedList previousIntervals = new LinkedList();
0191:
0192: /**
0193: * List of TimingSpecifiers on other elements that depend on this
0194: * element's begin times.
0195: */
0196: protected LinkedList beginDependents = new LinkedList();
0197:
0198: /**
0199: * List of TimingSpecifiers on other elements that depend on this
0200: * element's end times.
0201: */
0202: protected LinkedList endDependents = new LinkedList();
0203:
0204: /**
0205: * Whether the list of instance times should be checked to update
0206: * the current interval.
0207: */
0208: protected boolean shouldUpdateCurrentInterval = true;
0209:
0210: /**
0211: * Whether this timed element has parsed its timing attributes yet.
0212: */
0213: protected boolean hasParsed;
0214:
0215: /**
0216: * Map of {@link Event} objects to {@link HashSet}s of {@link
0217: * TimingSpecifier}s that caught them.
0218: */
0219: protected Map handledEvents = new HashMap();
0220:
0221: /**
0222: * Whether this timed element is currently being sampled.
0223: */
0224: protected boolean isSampling;
0225:
0226: /**
0227: * Whether an instance time update message has already been propagated to
0228: * this timed element.
0229: */
0230: protected boolean hasPropagated;
0231:
0232: /**
0233: * Creates a new TimedElement.
0234: */
0235: public TimedElement() {
0236: beginTimes = new TimingSpecifier[0];
0237: endTimes = beginTimes;
0238: simpleDur = UNRESOLVED;
0239: repeatCount = UNRESOLVED;
0240: repeatDur = UNRESOLVED;
0241: lastRepeatTime = UNRESOLVED;
0242: max = INDEFINITE;
0243: lastSampleTime = UNRESOLVED;
0244: lastIntervalEnd = Float.NEGATIVE_INFINITY;
0245: }
0246:
0247: /**
0248: * Returns the root time container of this timed element.
0249: */
0250: public TimedDocumentRoot getRoot() {
0251: return root;
0252: }
0253:
0254: /**
0255: * Returns the current active time of this element.
0256: */
0257: public float getActiveTime() {
0258: return lastSampleTime;
0259: }
0260:
0261: /**
0262: * Returns the current simple time of this element.
0263: */
0264: public float getSimpleTime() {
0265: return lastSampleTime - lastRepeatTime;
0266: }
0267:
0268: /**
0269: * Called by a TimingSpecifier of this element when a new
0270: * InstanceTime is created. This will be in response to an event
0271: * firing, a DOM method being called or a new Instance being
0272: * created by a syncbase element.
0273: */
0274: protected float addInstanceTime(InstanceTime time, boolean isBegin) {
0275: // Trace.enter(this, "addInstanceTime", new Object[] { time, new Boolean(isBegin) } ); try {
0276: hasPropagated = true;
0277: List instanceTimes = isBegin ? beginInstanceTimes
0278: : endInstanceTimes;
0279: int index = Collections.binarySearch(instanceTimes, time);
0280: if (index < 0) {
0281: index = -(index + 1);
0282: }
0283: instanceTimes.add(index, time);
0284: shouldUpdateCurrentInterval = true;
0285: float ret;
0286: if (root.isSampling() && !isSampling) {
0287: ret = sampleAt(root.getCurrentTime(), root.isHyperlinking());
0288: } else {
0289: ret = Float.POSITIVE_INFINITY;
0290: }
0291: hasPropagated = false;
0292: return ret;
0293: // } finally { Trace.exit(); }
0294: }
0295:
0296: /**
0297: * Called by a TimingSpecifier of this element when an InstanceTime
0298: * should be removed. This will be in response to the pruning of an
0299: * Interval.
0300: */
0301: protected float removeInstanceTime(InstanceTime time,
0302: boolean isBegin) {
0303: // Trace.enter(this, "removeInstanceTime", new Object[] { time, new Boolean(isBegin) } ); try {
0304: hasPropagated = true;
0305: List instanceTimes = isBegin ? beginInstanceTimes
0306: : endInstanceTimes;
0307: int index = Collections.binarySearch(instanceTimes, time);
0308: for (int i = index; i >= 0; i--) {
0309: InstanceTime it = (InstanceTime) instanceTimes.get(i);
0310: if (it == time) {
0311: instanceTimes.remove(i);
0312: break;
0313: }
0314: if (it.compareTo(time) != 0) {
0315: break;
0316: }
0317: }
0318: int len = instanceTimes.size();
0319: for (int i = index + 1; i < len; i++) {
0320: InstanceTime it = (InstanceTime) instanceTimes.get(i);
0321: if (it == time) {
0322: instanceTimes.remove(i);
0323: break;
0324: }
0325: if (it.compareTo(time) != 0) {
0326: break;
0327: }
0328: }
0329: shouldUpdateCurrentInterval = true;
0330: float ret;
0331: if (root.isSampling() && !isSampling) {
0332: ret = sampleAt(root.getCurrentTime(), root.isHyperlinking());
0333: } else {
0334: ret = Float.POSITIVE_INFINITY;
0335: }
0336: hasPropagated = false;
0337: return ret;
0338: // } finally { Trace.exit(); }
0339: }
0340:
0341: /**
0342: * Called by a TimingSpecifier of this element when an InstanceTime
0343: * has been updated. This will be in response to a dependent
0344: * syncbase change.
0345: */
0346: protected float instanceTimeChanged(InstanceTime time,
0347: boolean isBegin) {
0348: // Trace.enter(this, "instanceTimeChanged", new Object[] { time, new Boolean(isBegin) } ); try {
0349: hasPropagated = true;
0350: shouldUpdateCurrentInterval = true;
0351: float ret;
0352: if (root.isSampling() && !isSampling) {
0353: ret = sampleAt(root.getCurrentTime(), root.isHyperlinking());
0354: } else {
0355: ret = Float.POSITIVE_INFINITY;
0356: }
0357: hasPropagated = false;
0358: return ret;
0359: // } finally { Trace.exit(); }
0360: }
0361:
0362: /**
0363: * Adds a dependent TimingSpecifier for this element.
0364: */
0365: protected void addDependent(TimingSpecifier dependent,
0366: boolean forBegin) {
0367: // Trace.enter(this, "addDependent", new Object[] { dependent, new Boolean(forBegin) } ); try {
0368: if (forBegin) {
0369: beginDependents.add(dependent);
0370: } else {
0371: endDependents.add(dependent);
0372: }
0373: // } finally { Trace.exit(); }
0374: }
0375:
0376: /**
0377: * Removes a dependent TimingSpecifier for this element.
0378: */
0379: protected void removeDependent(TimingSpecifier dependent,
0380: boolean forBegin) {
0381: // Trace.enter(this, "removeDependent", new Object[] { dependent, new Boolean(forBegin) } ); try {
0382: if (forBegin) {
0383: beginDependents.remove(dependent);
0384: } else {
0385: endDependents.remove(dependent);
0386: }
0387: // } finally { Trace.exit(); }
0388: }
0389:
0390: /**
0391: * Returns the simple duration time of this element.
0392: */
0393: public float getSimpleDur() {
0394: if (durMedia) {
0395: return getImplicitDur();
0396: } else if (isUnresolved(simpleDur)) {
0397: if (isUnresolved(repeatCount) && isUnresolved(repeatDur)
0398: && endTimes.length > 0) {
0399: return INDEFINITE;
0400: }
0401: return getImplicitDur();
0402: } else {
0403: return simpleDur;
0404: }
0405: }
0406:
0407: /**
0408: * Returns whether the given time value is equal to the
0409: * {@link #UNRESOLVED} value.
0410: */
0411: public static boolean isUnresolved(float t) {
0412: return Float.isNaN(t);
0413: }
0414:
0415: /**
0416: * Returns the active duration time of this element.
0417: */
0418: public float getActiveDur(float B, float end) {
0419: float d = getSimpleDur();
0420: float PAD;
0421: if (!isUnresolved(end) && d == INDEFINITE) {
0422: PAD = minusTime(end, B);
0423: repeatDuration = minTime(max, maxTime(min, PAD));
0424: return repeatDuration;
0425: }
0426:
0427: float IAD;
0428: if (d == 0) {
0429: IAD = 0;
0430: } else {
0431: if (isUnresolved(repeatDur) && isUnresolved(repeatCount)) {
0432: IAD = d;
0433: } else {
0434: float p1 = isUnresolved(repeatCount) ? INDEFINITE
0435: : multiplyTime(d, repeatCount);
0436: float p2 = isUnresolved(repeatDur) ? INDEFINITE
0437: : repeatDur;
0438: IAD = minTime(minTime(p1, p2), INDEFINITE);
0439: }
0440: }
0441: if (isUnresolved(end) || end == INDEFINITE) {
0442: PAD = IAD;
0443: } else {
0444: PAD = minTime(IAD, minusTime(end, B));
0445: }
0446: repeatDuration = IAD;
0447: return minTime(max, maxTime(min, PAD));
0448: }
0449:
0450: /**
0451: * Subtracts one simple time from another.
0452: */
0453: protected float minusTime(float t1, float t2) {
0454: if (isUnresolved(t1) || isUnresolved(t2)) {
0455: return UNRESOLVED;
0456: }
0457: if (t1 == INDEFINITE || t2 == INDEFINITE) {
0458: return INDEFINITE;
0459: }
0460: return t1 - t2;
0461: }
0462:
0463: /**
0464: * Multiplies one simple time by n.
0465: */
0466: protected float multiplyTime(float t, float n) {
0467: if (isUnresolved(t) || t == INDEFINITE) {
0468: return t;
0469: }
0470: return t * n;
0471: }
0472:
0473: /**
0474: * Returns the minimum of two time values.
0475: */
0476: protected float minTime(float t1, float t2) {
0477: if (t1 == 0.0f || t2 == 0.0f) {
0478: return 0.0f;
0479: }
0480: if ((t1 == INDEFINITE || isUnresolved(t1)) && t2 != INDEFINITE
0481: && !isUnresolved(t2)) {
0482: return t2;
0483: }
0484: if ((t2 == INDEFINITE || isUnresolved(t2)) && t1 != INDEFINITE
0485: && !isUnresolved(t1)) {
0486: return t1;
0487: }
0488: if (t1 == INDEFINITE && isUnresolved(t2) || isUnresolved(t1)
0489: && t2 == INDEFINITE) {
0490: return INDEFINITE;
0491: }
0492: if (t1 < t2) {
0493: return t1;
0494: }
0495: return t2;
0496: }
0497:
0498: /**
0499: * Returns the maximum of two time values.
0500: */
0501: protected float maxTime(float t1, float t2) {
0502: if ((t1 == INDEFINITE || isUnresolved(t1)) && t2 != INDEFINITE
0503: && !isUnresolved(t2)) {
0504: return t1;
0505: }
0506: if ((t2 == INDEFINITE || isUnresolved(t2)) && t1 != INDEFINITE
0507: && !isUnresolved(t1)) {
0508: return t2;
0509: }
0510: if (t1 == INDEFINITE && isUnresolved(t2) || isUnresolved(t1)
0511: && t2 == INDEFINITE) {
0512: return UNRESOLVED;
0513: }
0514: if (t1 > t2) {
0515: return t1;
0516: }
0517: return t2;
0518: }
0519:
0520: /**
0521: * Returns the implicit duration of the element. Currently, nested time
0522: * containers are not supported by SVG so this just returns
0523: * {@link #UNRESOLVED} by default. This should be overriden in derived
0524: * classes that play media, since they will have an implicit duration.
0525: */
0526: protected float getImplicitDur() {
0527: return UNRESOLVED;
0528: }
0529:
0530: /**
0531: * Notifies dependents of a new interval.
0532: */
0533: protected float notifyNewInterval(Interval interval) {
0534: // Trace.enter(this, "notifyNewInterval", new Object[] { interval } ); try {
0535: float dependentMinTime = Float.POSITIVE_INFINITY;
0536: Iterator i = beginDependents.iterator();
0537: while (i.hasNext()) {
0538: TimingSpecifier ts = (TimingSpecifier) i.next();
0539: // Trace.print(ts.owner + "'s " + (ts.isBegin ? "begin" : "end" ) + ": " + ts);
0540: float t = ts.newInterval(interval);
0541: if (t < dependentMinTime) {
0542: dependentMinTime = t;
0543: }
0544: }
0545: i = endDependents.iterator();
0546: while (i.hasNext()) {
0547: TimingSpecifier ts = (TimingSpecifier) i.next();
0548: // Trace.print(ts.owner + "'s " + (ts.isBegin ? "begin" : "end" ) + ": " + ts);
0549: float t = ts.newInterval(interval);
0550: if (t < dependentMinTime) {
0551: dependentMinTime = t;
0552: }
0553: }
0554: return dependentMinTime;
0555: // } finally { Trace.exit(); }
0556: }
0557:
0558: /**
0559: * Notifies dependents of a removed interval.
0560: */
0561: protected float notifyRemoveInterval(Interval interval) {
0562: // Trace.enter(this, "notifyRemoveInterval", new Object[] { interval } ); try {
0563: float dependentMinTime = Float.POSITIVE_INFINITY;
0564: Iterator i = beginDependents.iterator();
0565: while (i.hasNext()) {
0566: TimingSpecifier ts = (TimingSpecifier) i.next();
0567: float t = ts.removeInterval(interval);
0568: if (t < dependentMinTime) {
0569: dependentMinTime = t;
0570: }
0571: }
0572: i = endDependents.iterator();
0573: while (i.hasNext()) {
0574: TimingSpecifier ts = (TimingSpecifier) i.next();
0575: float t = ts.removeInterval(interval);
0576: if (t < dependentMinTime) {
0577: dependentMinTime = t;
0578: }
0579: }
0580: return dependentMinTime;
0581: // } finally { Trace.exit(); }
0582: }
0583:
0584: /**
0585: * Calculates the local simple time. Currently the hyperlinking parameter
0586: * is ignored, so DOM timing events are fired during hyperlinking seeks.
0587: * If we were following SMIL 2.1 rather than SMIL Animation, then these
0588: * events would have to be surpressed.
0589: *
0590: * @return the number of seconds until this element becomes active again
0591: * if it currently is not, {@link Float#POSITIVE_INFINITY} if this
0592: * element will become active at some undetermined point in the
0593: * future (because of unresolved begin times, for example) or
0594: * will never become active again, or <code>0f</code> if the
0595: * element is currently active.
0596: */
0597: protected float sampleAt(float parentSimpleTime,
0598: boolean hyperlinking) {
0599: // Trace.enter(this, "sampleAt", new Object[] { new Float(parentSimpleTime) } ); try {
0600: isSampling = true;
0601:
0602: float time = parentSimpleTime; // No time containers in SVG.
0603:
0604: // First, process any events that occurred since the last sampling,
0605: // taking into account event sensitivity.
0606: Iterator i = handledEvents.entrySet().iterator();
0607: while (i.hasNext()) {
0608: Map.Entry e = (Map.Entry) i.next();
0609: Event evt = (Event) e.getKey();
0610: Set ts = (Set) e.getValue();
0611: Iterator j = ts.iterator();
0612: boolean hasBegin = false, hasEnd = false;
0613: while (j.hasNext() && !(hasBegin && hasEnd)) {
0614: EventLikeTimingSpecifier t = (EventLikeTimingSpecifier) j
0615: .next();
0616: if (t.isBegin()) {
0617: hasBegin = true;
0618: } else {
0619: hasEnd = true;
0620: }
0621: }
0622: boolean useBegin, useEnd;
0623: if (hasBegin && hasEnd) {
0624: useBegin = !isActive || restartMode == RESTART_ALWAYS;
0625: useEnd = !useBegin;
0626: } else if (hasBegin
0627: && (!isActive || restartMode == RESTART_ALWAYS)) {
0628: useBegin = true;
0629: useEnd = false;
0630: } else if (hasEnd && isActive) {
0631: useBegin = false;
0632: useEnd = true;
0633: } else {
0634: continue;
0635: }
0636: j = ts.iterator();
0637: while (j.hasNext()) {
0638: EventLikeTimingSpecifier t = (EventLikeTimingSpecifier) j
0639: .next();
0640: boolean isBegin = t.isBegin();
0641: if (isBegin && useBegin || !isBegin && useEnd) {
0642: t.resolve(evt);
0643: shouldUpdateCurrentInterval = true;
0644: }
0645: }
0646: }
0647: handledEvents.clear();
0648:
0649: // Now process intervals.
0650: if (currentInterval != null) {
0651: float begin = currentInterval.getBegin();
0652: if (lastSampleTime < begin && time >= begin) {
0653: if (!isActive) {
0654: toActive(begin);
0655: }
0656: isActive = true;
0657: isFrozen = false;
0658: lastRepeatTime = begin;
0659: fireTimeEvent(SMIL_BEGIN_EVENT_NAME, currentInterval
0660: .getBegin(), 0);
0661: }
0662: }
0663: // For each sample, we might need to update the current interval's
0664: // begin and end times, or end the current interval and compute
0665: // a new one.
0666: boolean hasEnded = currentInterval != null
0667: && time >= currentInterval.getEnd();
0668: // Fire any repeat events that should have been fired since the
0669: // last sample.
0670: if (currentInterval != null) {
0671: float begin = currentInterval.getBegin();
0672: if (time >= begin) {
0673: float d = getSimpleDur();
0674: while (time - lastRepeatTime >= d
0675: && lastRepeatTime + d < begin + repeatDuration) {
0676: lastRepeatTime += d;
0677: currentRepeatIteration++;
0678: fireTimeEvent(root.getRepeatEventName(),
0679: lastRepeatTime, currentRepeatIteration);
0680: }
0681: }
0682: }
0683:
0684: // Trace.print("begin loop");
0685: float dependentMinTime = Float.POSITIVE_INFINITY;
0686: if (hyperlinking) {
0687: shouldUpdateCurrentInterval = true;
0688: }
0689: while (shouldUpdateCurrentInterval || hasEnded) {
0690: if (hasEnded) {
0691: previousIntervals.add(currentInterval);
0692: isActive = false;
0693: isFrozen = fillMode == FILL_FREEZE;
0694: toInactive(false, isFrozen);
0695: fireTimeEvent(SMIL_END_EVENT_NAME, currentInterval
0696: .getEnd(), 0);
0697: }
0698: boolean first = currentInterval == null
0699: && previousIntervals.isEmpty();
0700: if (currentInterval == null || hasEnded) {
0701: if (first || hyperlinking
0702: || restartMode != RESTART_NEVER) {
0703: float beginAfter;
0704: if (first || hyperlinking) {
0705: beginAfter = Float.NEGATIVE_INFINITY;
0706: } else {
0707: beginAfter = ((Interval) previousIntervals
0708: .getLast()).getEnd();
0709: }
0710: Interval interval = computeInterval(first, false,
0711: beginAfter);
0712: if (interval == null) {
0713: currentInterval = null;
0714: } else {
0715: float dmt = selectNewInterval(time, interval);
0716: if (dmt < dependentMinTime) {
0717: dependentMinTime = dmt;
0718: }
0719: }
0720: } else {
0721: currentInterval = null;
0722: }
0723: } else {
0724: float currentBegin = currentInterval.getBegin();
0725: if (currentBegin > time) {
0726: // Interval hasn't started yet.
0727: float beginAfter;
0728: if (previousIntervals.isEmpty()) {
0729: beginAfter = Float.NEGATIVE_INFINITY;
0730: } else {
0731: beginAfter = ((Interval) previousIntervals
0732: .getLast()).getEnd();
0733: }
0734: Interval interval = computeInterval(false, false,
0735: beginAfter);
0736: float dmt = notifyRemoveInterval(currentInterval);
0737: if (dmt < dependentMinTime) {
0738: dependentMinTime = dmt;
0739: }
0740: if (interval == null) {
0741: currentInterval = null;
0742: } else {
0743: dmt = selectNewInterval(time, interval);
0744: if (dmt < dependentMinTime) {
0745: dependentMinTime = dmt;
0746: }
0747: }
0748: } else {
0749: // Interval has already started.
0750: Interval interval = computeInterval(false, true,
0751: currentBegin);
0752: float newEnd = interval.getEnd();
0753: if (currentInterval.getEnd() != newEnd) {
0754: float dmt = currentInterval.setEnd(newEnd,
0755: interval.getEndInstanceTime());
0756: if (dmt < dependentMinTime) {
0757: dependentMinTime = dmt;
0758: }
0759: }
0760: }
0761: }
0762: shouldUpdateCurrentInterval = false;
0763: hyperlinking = false;
0764: hasEnded = currentInterval != null
0765: && time >= currentInterval.getEnd();
0766: }
0767: // Trace.print("end loop");
0768:
0769: float d = getSimpleDur();
0770: if (isActive && !isFrozen) {
0771: if (time - currentInterval.getBegin() >= repeatDuration) {
0772: // Trace.print("element between repeat and active duration");
0773: isFrozen = fillMode == FILL_FREEZE;
0774: toInactive(true, isFrozen);
0775: } else {
0776: // Trace.print("element active, sampling at simple time " + (time - lastRepeatTime));
0777: sampledAt(time - lastRepeatTime, d,
0778: currentRepeatIteration);
0779: }
0780: }
0781: if (isFrozen) {
0782: float t;
0783: if (isActive) {
0784: t = currentInterval.getBegin() + repeatDuration
0785: - lastRepeatTime;
0786: } else {
0787: Interval previousInterval = (Interval) previousIntervals
0788: .getLast();
0789: t = previousInterval.getEnd() - lastRepeatTime;
0790: }
0791: if (t % d == 0) {
0792: // Trace.print("element frozen" + (isActive ? " (but still active)" : "") + ", sampling last value");
0793: sampledLastValue(currentRepeatIteration);
0794: } else {
0795: // Trace.print("element frozen" + (isActive ? " (but still active)" : "") + ", sampling at simple time " + (t % d));
0796: sampledAt(t % d, d, currentRepeatIteration);
0797: }
0798: } else if (!isActive) {
0799: // Trace.print("element not sampling");
0800: }
0801:
0802: isSampling = false;
0803:
0804: lastSampleTime = time;
0805: if (currentInterval != null) {
0806: float t = currentInterval.getBegin() - time;
0807: if (t <= 0) {
0808: t = isConstantAnimation() || isFrozen ? currentInterval
0809: .getEnd()
0810: - time : 0;
0811: }
0812: if (dependentMinTime < t) {
0813: return dependentMinTime;
0814: }
0815: return t;
0816: }
0817: return dependentMinTime;
0818: // } finally { Trace.exit(); }
0819: }
0820:
0821: /**
0822: * Returns whether the end timing specifier list contains any eventbase,
0823: * accesskey or repeat timing specifiers.
0824: */
0825: protected boolean endHasEventConditions() {
0826: for (int i = 0; i < endTimes.length; i++) {
0827: if (endTimes[i].isEventCondition()) {
0828: return true;
0829: }
0830: }
0831: return false;
0832: }
0833:
0834: /**
0835: * Sets the current interval to the one specified. This will notify
0836: * dependents and fire the 'begin' and any necessary 'repeat' events.
0837: * @param time the current sampling time
0838: * @param interval the Interval object to select to be current
0839: * @return the minimum time the animation engine can safely wait, as
0840: * determined by dependents of the interval
0841: */
0842: protected float selectNewInterval(float time, Interval interval) {
0843: // Trace.enter(this, "selectNewInterval", new Object[] { interval }); try {
0844: currentInterval = interval;
0845: float dmt = notifyNewInterval(currentInterval);
0846: float beginEventTime = currentInterval.getBegin();
0847: if (time >= beginEventTime) {
0848: lastRepeatTime = beginEventTime;
0849: if (beginEventTime < 0) {
0850: beginEventTime = 0;
0851: }
0852: toActive(beginEventTime);
0853: isActive = true;
0854: isFrozen = false;
0855: fireTimeEvent(SMIL_BEGIN_EVENT_NAME, beginEventTime, 0);
0856: float d = getSimpleDur();
0857: float end = currentInterval.getEnd();
0858: while (time - lastRepeatTime >= d
0859: && lastRepeatTime + d < end) {
0860: lastRepeatTime += d;
0861: currentRepeatIteration++;
0862: fireTimeEvent(root.getRepeatEventName(),
0863: lastRepeatTime, currentRepeatIteration);
0864: }
0865: }
0866: return dmt;
0867: // } finally { Trace.exit(); }
0868: }
0869:
0870: /**
0871: * Computes an interval from the begin and end instance time lists.
0872: * @param first indicates whether this is the first interval to compute
0873: * @param fixedBegin if true, specifies that the value given for
0874: * {@code beginAfter} is taken to be the actual begin
0875: * time for the interval; only the end value is computed.
0876: * @param beginAfter the earliest possible begin time for the computed
0877: * interval.
0878: */
0879: protected Interval computeInterval(boolean first,
0880: boolean fixedBegin, float beginAfter) {
0881: // Trace.enter(this, "computeInterval", new Object[] { new Boolean(first), new Boolean(fixedBegin), new Float(beginAfter)} ); try {
0882: // Trace.print("computing interval from begins=" + beginInstanceTimes + ", ends=" + endInstanceTimes);
0883: Iterator beginIterator = beginInstanceTimes.iterator();
0884: Iterator endIterator = endInstanceTimes.iterator();
0885: float parentSimpleDur = parent.getSimpleDur();
0886: InstanceTime endInstanceTime = endIterator.hasNext() ? (InstanceTime) endIterator
0887: .next()
0888: : null;
0889: boolean firstEnd = true;
0890: InstanceTime beginInstanceTime = null;
0891: InstanceTime nextBeginInstanceTime = null;
0892: for (;;) {
0893: float tempBegin;
0894: if (fixedBegin) {
0895: tempBegin = beginAfter;
0896: while (beginIterator.hasNext()) {
0897: nextBeginInstanceTime = (InstanceTime) beginIterator
0898: .next();
0899: if (nextBeginInstanceTime.getTime() > tempBegin) {
0900: break;
0901: }
0902: }
0903: } else {
0904: for (;;) {
0905: if (!beginIterator.hasNext()) {
0906: // ran out of begin values
0907: // Trace.print("returning null interval");
0908: return null;
0909: }
0910: beginInstanceTime = (InstanceTime) beginIterator
0911: .next();
0912: tempBegin = beginInstanceTime.getTime();
0913: if (tempBegin >= beginAfter) {
0914: if (beginIterator.hasNext()) {
0915: nextBeginInstanceTime = (InstanceTime) beginIterator
0916: .next();
0917: }
0918: break;
0919: }
0920: }
0921: }
0922: if (tempBegin >= parentSimpleDur) {
0923: // the begin value is after the parent has ended
0924: // Trace.print("returning null interval");
0925: return null;
0926: }
0927: float tempEnd;
0928: if (endTimes.length == 0) {
0929: // no 'end' attribute specified
0930: tempEnd = tempBegin
0931: + getActiveDur(tempBegin, INDEFINITE);
0932: // Trace.print("no end specified, so tempEnd = " + tempEnd);
0933: } else {
0934: if (endInstanceTimes.isEmpty()) {
0935: tempEnd = UNRESOLVED;
0936: } else {
0937: tempEnd = endInstanceTime.getTime();
0938: if (first && !firstEnd && tempEnd == tempBegin
0939: || !first && currentInterval != null
0940: && tempEnd == currentInterval.getEnd()
0941: && beginAfter >= tempEnd) {
0942: for (;;) {
0943: if (!endIterator.hasNext()) {
0944: if (endHasEventConditions()) {
0945: tempEnd = UNRESOLVED;
0946: break;
0947: }
0948: // Trace.print("returning null interval");
0949: return null;
0950: }
0951: endInstanceTime = (InstanceTime) endIterator
0952: .next();
0953: tempEnd = endInstanceTime.getTime();
0954: if (tempEnd > tempBegin) {
0955: break;
0956: }
0957: }
0958: }
0959: firstEnd = false;
0960: for (;;) {
0961: if (tempEnd >= tempBegin) {
0962: break;
0963: }
0964: if (!endIterator.hasNext()) {
0965: if (endHasEventConditions()) {
0966: tempEnd = UNRESOLVED;
0967: break;
0968: }
0969: // Trace.print("returning null interval");
0970: return null;
0971: }
0972: endInstanceTime = (InstanceTime) endIterator
0973: .next();
0974: tempEnd = endInstanceTime.getTime();
0975: }
0976: }
0977: float ad = getActiveDur(tempBegin, tempEnd);
0978: tempEnd = tempBegin + ad;
0979: }
0980: if (!first || tempEnd > 0 || tempBegin == 0 && tempEnd == 0
0981: || isUnresolved(tempEnd)) {
0982: // Trace.print("considering restart semantics");
0983: if (restartMode == RESTART_ALWAYS
0984: && nextBeginInstanceTime != null) {
0985: float nextBegin = nextBeginInstanceTime.getTime();
0986: // Trace.print("nextBegin == " + nextBegin);
0987: if (nextBegin < tempEnd || isUnresolved(tempEnd)) {
0988: tempEnd = nextBegin;
0989: endInstanceTime = nextBeginInstanceTime;
0990: }
0991: }
0992: Interval i = new Interval(tempBegin, tempEnd,
0993: beginInstanceTime, endInstanceTime);
0994: // Trace.print("returning interval: " + i);
0995: return i;
0996: }
0997: if (fixedBegin) {
0998: // Trace.print("returning null interval");
0999: return null;
1000: }
1001: beginAfter = tempEnd;
1002: }
1003: // } finally { Trace.exit(); }
1004: }
1005:
1006: /**
1007: * Resets this element.
1008: */
1009: protected void reset(boolean clearCurrentBegin) {
1010: Iterator i = beginInstanceTimes.iterator();
1011: while (i.hasNext()) {
1012: InstanceTime it = (InstanceTime) i.next();
1013: if (it.getClearOnReset()
1014: && (clearCurrentBegin || currentInterval == null || currentInterval
1015: .getBeginInstanceTime() != it)) {
1016: i.remove();
1017: }
1018: }
1019: i = endInstanceTimes.iterator();
1020: while (i.hasNext()) {
1021: InstanceTime it = (InstanceTime) i.next();
1022: if (it.getClearOnReset()) {
1023: i.remove();
1024: }
1025: }
1026: if (isFrozen) {
1027: removeFill();
1028: }
1029: currentRepeatIteration = 0;
1030: lastRepeatTime = UNRESOLVED;
1031: isActive = false;
1032: isFrozen = false;
1033: lastSampleTime = UNRESOLVED;
1034: // XXX should reconvert resolved syncbase/wallclock/media-marker time
1035: // instances into the parent simple timespace
1036: }
1037:
1038: /**
1039: * Parses the animation attributes for this timed element.
1040: */
1041: public void parseAttributes(String begin, String dur, String end,
1042: String min, String max, String repeatCount,
1043: String repeatDur, String fill, String restart) {
1044: if (!hasParsed) {
1045: parseBegin(begin);
1046: parseDur(dur);
1047: parseEnd(end);
1048: parseMin(min);
1049: parseMax(max);
1050: if (this .min > this .max) {
1051: this .min = 0f;
1052: this .max = INDEFINITE;
1053: }
1054: parseRepeatCount(repeatCount);
1055: parseRepeatDur(repeatDur);
1056: parseFill(fill);
1057: parseRestart(restart);
1058: hasParsed = true;
1059: }
1060: }
1061:
1062: /**
1063: * Parses a new 'begin' attribute.
1064: */
1065: protected void parseBegin(String begin) {
1066: try {
1067: if (begin.length() == 0) {
1068: begin = SMIL_BEGIN_DEFAULT_VALUE;
1069: }
1070: beginTimes = TimingSpecifierListProducer
1071: .parseTimingSpecifierList(TimedElement.this , true,
1072: begin, root.useSVG11AccessKeys,
1073: root.useSVG12AccessKeys);
1074: } catch (ParseException ex) {
1075: throw createException("attribute.malformed", new Object[] {
1076: null, SMIL_BEGIN_ATTRIBUTE });
1077: }
1078: }
1079:
1080: /**
1081: * Parses a new 'dur' attribute.
1082: */
1083: protected void parseDur(String dur) {
1084: if (dur.equals(SMIL_MEDIA_VALUE)) {
1085: durMedia = true;
1086: simpleDur = UNRESOLVED;
1087: } else {
1088: durMedia = false;
1089: if (dur.length() == 0 || dur.equals(SMIL_INDEFINITE_VALUE)) {
1090: simpleDur = INDEFINITE;
1091: } else {
1092: try {
1093: simpleDur = parseClockValue(dur, false);
1094: } catch (ParseException e) {
1095: throw createException("attribute.malformed",
1096: new Object[] { null, SMIL_DUR_ATTRIBUTE });
1097: }
1098: if (simpleDur < 0) {
1099: simpleDur = INDEFINITE;
1100: }
1101: }
1102: }
1103: }
1104:
1105: /**
1106: * Parses a clock value or offset and returns it as a float.
1107: */
1108: protected float parseClockValue(String s, boolean parseOffset)
1109: throws ParseException {
1110: ClockParser p = new ClockParser(parseOffset);
1111: class Handler implements ClockHandler {
1112: protected float v = 0;
1113:
1114: public void clockValue(float newClockValue) {
1115: v = newClockValue;
1116: }
1117: }
1118:
1119: Handler h = new Handler();
1120: p.setClockHandler(h);
1121: p.parse(s);
1122: return h.v;
1123: }
1124:
1125: /**
1126: * Parses a new 'end' attribute.
1127: */
1128: protected void parseEnd(String end) {
1129: try {
1130: endTimes = TimingSpecifierListProducer
1131: .parseTimingSpecifierList(TimedElement.this , false,
1132: end, root.useSVG11AccessKeys,
1133: root.useSVG12AccessKeys);
1134: } catch (ParseException ex) {
1135: throw createException("attribute.malformed", new Object[] {
1136: null, SMIL_END_ATTRIBUTE });
1137: }
1138: }
1139:
1140: /**
1141: * Parses a new 'min' attribute.
1142: */
1143: protected void parseMin(String min) {
1144: if (min.equals(SMIL_MEDIA_VALUE)) {
1145: this .min = 0;
1146: minMedia = true;
1147: } else {
1148: minMedia = false;
1149: if (min.length() == 0) {
1150: this .min = 0;
1151: } else {
1152: try {
1153: this .min = parseClockValue(min, false);
1154: } catch (ParseException ex) {
1155: throw createException("attribute.malformed",
1156: new Object[] { null, SMIL_MIN_ATTRIBUTE });
1157: }
1158: if (this .min < 0) {
1159: this .min = 0;
1160: }
1161: }
1162: }
1163: }
1164:
1165: /**
1166: * Parses a new 'max' attribute.
1167: */
1168: protected void parseMax(String max) {
1169: if (max.equals(SMIL_MEDIA_VALUE)) {
1170: this .max = INDEFINITE;
1171: maxMedia = true;
1172: } else {
1173: maxMedia = false;
1174: if (max.length() == 0 || max.equals(SMIL_INDEFINITE_VALUE)) {
1175: this .max = INDEFINITE;
1176: } else {
1177: try {
1178: this .max = parseClockValue(max, false);
1179: } catch (ParseException ex) {
1180: throw createException("attribute.malformed",
1181: new Object[] { null, SMIL_MAX_ATTRIBUTE });
1182: }
1183: if (this .max < 0) {
1184: this .max = 0;
1185: }
1186: }
1187: }
1188: }
1189:
1190: /**
1191: * Parses a new 'repeatCount' attribute.
1192: */
1193: protected void parseRepeatCount(String repeatCount) {
1194: if (repeatCount.length() == 0) {
1195: this .repeatCount = UNRESOLVED;
1196: } else if (repeatCount.equals(SMIL_INDEFINITE_VALUE)) {
1197: this .repeatCount = INDEFINITE;
1198: } else {
1199: try {
1200: this .repeatCount = Float.parseFloat(repeatCount);
1201: if (this .repeatCount > 0) {
1202: return;
1203: }
1204: } catch (NumberFormatException ex) {
1205: throw createException("attribute.malformed",
1206: new Object[] { null,
1207: SMIL_REPEAT_COUNT_ATTRIBUTE });
1208: }
1209: }
1210: }
1211:
1212: /**
1213: * Parses a new 'repeatDur' attribute.
1214: */
1215: protected void parseRepeatDur(String repeatDur) {
1216: try {
1217: if (repeatDur.length() == 0) {
1218: this .repeatDur = UNRESOLVED;
1219: } else if (repeatDur.equals(SMIL_INDEFINITE_VALUE)) {
1220: this .repeatDur = INDEFINITE;
1221: } else {
1222: this .repeatDur = parseClockValue(repeatDur, false);
1223: }
1224: } catch (ParseException ex) {
1225: throw createException("attribute.malformed", new Object[] {
1226: null, SMIL_REPEAT_DUR_ATTRIBUTE });
1227: }
1228: }
1229:
1230: /**
1231: * Parses a new 'fill' attribute.
1232: */
1233: protected void parseFill(String fill) {
1234: if (fill.length() == 0 || fill.equals(SMIL_REMOVE_VALUE)) {
1235: fillMode = FILL_REMOVE;
1236: } else if (fill.equals(SMIL_FREEZE_VALUE)) {
1237: fillMode = FILL_FREEZE;
1238: } else {
1239: throw createException("attribute.malformed", new Object[] {
1240: null, SMIL_FILL_ATTRIBUTE });
1241: }
1242: }
1243:
1244: /**
1245: * Parses a new 'restart' attribute.
1246: */
1247: protected void parseRestart(String restart) {
1248: if (restart.length() == 0 || restart.equals(SMIL_ALWAYS_VALUE)) {
1249: restartMode = RESTART_ALWAYS;
1250: } else if (restart.equals(SMIL_WHEN_NOT_ACTIVE_VALUE)) {
1251: restartMode = RESTART_WHEN_NOT_ACTIVE;
1252: } else if (restart.equals(SMIL_NEVER_VALUE)) {
1253: restartMode = RESTART_NEVER;
1254: } else {
1255: throw createException("attribute.malformed", new Object[] {
1256: null, SMIL_RESTART_ATTRIBUTE });
1257: }
1258: }
1259:
1260: /**
1261: * Initializes this timed element.
1262: */
1263: public void initialize() {
1264: for (int i = 0; i < beginTimes.length; i++) {
1265: beginTimes[i].initialize();
1266: }
1267: for (int i = 0; i < endTimes.length; i++) {
1268: endTimes[i].initialize();
1269: }
1270: }
1271:
1272: /**
1273: * Deinitializes this timed element.
1274: */
1275: public void deinitialize() {
1276: for (int i = 0; i < beginTimes.length; i++) {
1277: beginTimes[i].deinitialize();
1278: }
1279: for (int i = 0; i < endTimes.length; i++) {
1280: beginTimes[i].deinitialize();
1281: }
1282: }
1283:
1284: /**
1285: * Adds a time to the begin time instance list that will cause
1286: * the element to begin immediately (if restart semantics allow it).
1287: */
1288: public void beginElement() {
1289: beginElement(0);
1290: }
1291:
1292: /**
1293: * Adds a time to the begin time instance list that will cause
1294: * the element to begin at some offset to the current time (if restart
1295: * semantics allow it).
1296: */
1297: public void beginElement(float offset) {
1298: float t = root.convertWallclockTime(Calendar.getInstance());
1299: InstanceTime it = new InstanceTime(null, t + offset, null, true);
1300: addInstanceTime(it, true);
1301: }
1302:
1303: /**
1304: * Adds a time to the end time instance list that will cause
1305: * the element to end immediately (if restart semantics allow it).
1306: */
1307: public void endElement() {
1308: endElement(0);
1309: }
1310:
1311: /**
1312: * Adds a time to the end time instance list that will cause
1313: * the element to end at some offset to the current time (if restart
1314: * semantics allow it).
1315: */
1316: public void endElement(float offset) {
1317: float t = root.convertWallclockTime(Calendar.getInstance());
1318: InstanceTime it = new InstanceTime(null, t + offset, null, true);
1319: addInstanceTime(it, false);
1320: }
1321:
1322: /**
1323: * Returns the last sample time of this element, in local active time.
1324: */
1325: public float getLastSampleTime() {
1326: return lastSampleTime;
1327: }
1328:
1329: /**
1330: * Returns the begin time of the current interval, in parent simple time,
1331: * or <code>Float.NaN</code> if the element is not active.
1332: */
1333: public float getCurrentBeginTime() {
1334: float begin;
1335: if (currentInterval == null
1336: || (begin = currentInterval.getBegin()) < lastSampleTime) {
1337: return Float.NaN;
1338: }
1339: return begin;
1340: }
1341:
1342: /**
1343: * Returns whether this element can be begun or restarted currently.
1344: */
1345: public boolean canBegin() {
1346: return currentInterval == null || isActive
1347: && restartMode != RESTART_NEVER;
1348: }
1349:
1350: /**
1351: * Returns whether this element can be ended currently.
1352: */
1353: public boolean canEnd() {
1354: return isActive;
1355: }
1356:
1357: /**
1358: * Fires a TimeEvent of the given type on this element.
1359: * @param eventType the type of TimeEvent ("beginEvent", "endEvent"
1360: * or "repeatEvent").
1361: * @param time the timestamp of the event object
1362: * @param detail the repeat iteration, if this event is a repeat event
1363: */
1364: protected void fireTimeEvent(String eventType, float time,
1365: int detail) {
1366: Calendar t = (Calendar) root.getDocumentBeginTime().clone();
1367: t.add(Calendar.MILLISECOND, (int) Math.round(time * 1e3));
1368: fireTimeEvent(eventType, t, detail);
1369: }
1370:
1371: /**
1372: * Invoked by a {@link TimingSpecifier} to indicate that an event occurred
1373: * that would create a new instance time for this timed element. These
1374: * will be processed at the beginning of the next tick.
1375: */
1376: void eventOccurred(TimingSpecifier t, Event e) {
1377: Set ts = (HashSet) handledEvents.get(e);
1378: if (ts == null) {
1379: ts = new HashSet();
1380: handledEvents.put(e, ts);
1381: }
1382: ts.add(t);
1383: root.currentIntervalWillUpdate();
1384: }
1385:
1386: /**
1387: * Fires a TimeEvent of the given type on this element.
1388: * @param eventType the type of TimeEvent ("beginEvent", "endEvent"
1389: * or "repeatEvent").
1390: * @param time the timestamp of the event object
1391: */
1392: protected abstract void fireTimeEvent(String eventType,
1393: Calendar time, int detail);
1394:
1395: /**
1396: * Invoked to indicate this timed element became active at the
1397: * specified time.
1398: * @param begin the time the element became active, in document simple time
1399: */
1400: protected abstract void toActive(float begin);
1401:
1402: /**
1403: * Invoked to indicate that this timed element became inactive.
1404: * @param stillActive if true, indicates that the element is still actually
1405: * active, but between the end of the computed repeat
1406: * duration and the end of the interval
1407: * @param isFrozen whether the element is frozen or not
1408: */
1409: protected abstract void toInactive(boolean stillActive,
1410: boolean isFrozen);
1411:
1412: /**
1413: * Invoked to indicate that this timed element has had its fill removed.
1414: */
1415: protected abstract void removeFill();
1416:
1417: /**
1418: * Invoked to indicate that this timed element has been sampled at the
1419: * given time.
1420: * @param simpleTime the sample time in local simple time
1421: * @param simpleDur the simple duration of the element
1422: * @param repeatIteration the repeat iteration during which the element
1423: * was sampled
1424: */
1425: protected abstract void sampledAt(float simpleTime,
1426: float simpleDur, int repeatIteration);
1427:
1428: /**
1429: * Invoked to indicate that this timed element has been sampled
1430: * at the end of its active time, at an integer multiple of the
1431: * simple duration. This is the "last" value that will be used
1432: * for filling, which cannot be sampled normally.
1433: */
1434: protected abstract void sampledLastValue(int repeatIteration);
1435:
1436: /**
1437: * Returns the timed element with the given ID.
1438: */
1439: protected abstract TimedElement getTimedElementById(String id);
1440:
1441: /**
1442: * Returns the event target with the given ID.
1443: */
1444: protected abstract EventTarget getEventTargetById(String id);
1445:
1446: /**
1447: * Returns the event target that should be listened to for
1448: * access key events.
1449: */
1450: protected abstract EventTarget getRootEventTarget();
1451:
1452: /**
1453: * Returns the DOM element that corresponds to this timed element, if
1454: * such a DOM element exists.
1455: */
1456: public abstract Element getElement();
1457:
1458: /**
1459: * Returns the target of this animation as an {@link EventTarget}. Used
1460: * for eventbase timing specifiers where the element ID is omitted.
1461: */
1462: protected abstract EventTarget getAnimationEventTarget();
1463:
1464: /**
1465: * Returns whether this timed element comes before the given timed element
1466: * in document order.
1467: */
1468: public abstract boolean isBefore(TimedElement other);
1469:
1470: /**
1471: * Returns whether this timed element is for a constant animation (i.e., a
1472: * 'set' animation.
1473: */
1474: protected abstract boolean isConstantAnimation();
1475:
1476: /**
1477: * Creates and returns a new {@link AnimationException}.
1478: */
1479: public AnimationException createException(String code,
1480: Object[] params) {
1481: Element e = getElement();
1482: if (e != null) {
1483: params[0] = e.getNodeName();
1484: }
1485: return new AnimationException(this , code, params);
1486: }
1487:
1488: /**
1489: * The error messages bundle class name.
1490: */
1491: protected static final String RESOURCES = "org.apache.batik.anim.resources.Messages";
1492:
1493: /**
1494: * The localizable support for the error messages.
1495: */
1496: protected static LocalizableSupport localizableSupport = new LocalizableSupport(
1497: RESOURCES, TimedElement.class.getClassLoader());
1498:
1499: /**
1500: * Implements {@link org.apache.batik.i18n.Localizable#setLocale(java.util.Locale)}.
1501: */
1502: public static void setLocale(Locale l) {
1503: localizableSupport.setLocale(l);
1504: }
1505:
1506: /**
1507: * Implements {@link org.apache.batik.i18n.Localizable#getLocale()}.
1508: */
1509: public static Locale getLocale() {
1510: return localizableSupport.getLocale();
1511: }
1512:
1513: /**
1514: * Implements {@link
1515: * org.apache.batik.i18n.Localizable#formatMessage(String,Object[])}.
1516: */
1517: public static String formatMessage(String key, Object[] args)
1518: throws MissingResourceException {
1519: return localizableSupport.formatMessage(key, args);
1520: }
1521:
1522: /**
1523: * Returns a string representation of the given time value.
1524: */
1525: public static String toString(float time) {
1526: if (Float.isNaN(time)) {
1527: return "UNRESOLVED";
1528: } else if (time == Float.POSITIVE_INFINITY) {
1529: return "INDEFINITE";
1530: } else {
1531: return Float.toString(time);
1532: }
1533: }
1534: }
|