You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-commits@xmlgraphics.apache.org by ca...@apache.org on 2006/12/11 05:04:54 UTC

svn commit: r485485 - in /xmlgraphics/batik/trunk/sources/org/apache/batik: anim/ anim/timing/ bridge/

Author: cam
Date: Sun Dec 10 20:04:53 2006
New Revision: 485485

URL: http://svn.apache.org/viewvc?view=rev&rev=485485
Log:
1. Fix some animation problems when the document is sampled infrequently enough
   that whole intervals get skipped over.

Modified:
    xmlgraphics/batik/trunk/sources/org/apache/batik/anim/AnimationEngine.java
    xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/InstanceTime.java
    xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/Interval.java
    xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/SyncbaseTimingSpecifier.java
    xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedDocumentRoot.java
    xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedElement.java
    xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimingSpecifier.java
    xmlgraphics/batik/trunk/sources/org/apache/batik/bridge/SVGAnimationEngine.java

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/anim/AnimationEngine.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/anim/AnimationEngine.java?view=diff&rev=485485&r1=485484&r2=485485
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/anim/AnimationEngine.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/anim/AnimationEngine.java Sun Dec 10 20:04:53 2006
@@ -415,12 +415,12 @@
     public void toInactive(AbstractAnimation anim, boolean isFrozen) {
         anim.isActive = false;
         anim.isFrozen = isFrozen;
-        if (!isFrozen) {
-            anim.value = null;
-        }
         anim.markDirty();
         if (!isFrozen) {
+            anim.value = null;
             moveToBottom(anim);
+        } else {
+            pushDown(anim);
         }
     }
 

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/InstanceTime.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/InstanceTime.java?view=diff&rev=485485&r1=485484&r2=485485
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/InstanceTime.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/InstanceTime.java Sun Dec 10 20:04:53 2006
@@ -92,15 +92,16 @@
      * has changed.
      * @param newTime the new time, in parent simple time
      */
-    void dependentUpdate(float newTime) {
+    float dependentUpdate(float newTime) {
         // Trace.enter(this, "dependentUpdate", new Object[] { new Float(newTime) } ); try {
         // XXX Convert time from the creator's syncbase's
         //     time system into this time system.  Not
         //     strictly necessary in SVG.
         time = newTime;
         if (creator != null) {
-            creator.handleTimebaseUpdate(this, time);
+            return creator.handleTimebaseUpdate(this, time);
         }
+        return Float.POSITIVE_INFINITY;
         // } finally { Trace.exit(); }
     }
 

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/Interval.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/Interval.java?view=diff&rev=485485&r1=485484&r2=485485
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/Interval.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/Interval.java Sun Dec 10 20:04:53 2006
@@ -146,28 +146,38 @@
     /**
      * Updates the begin time for this interval.
      */
-    void setBegin(float begin) {
+    float setBegin(float begin) {
         // Trace.enter(this, "setBegin", new Object[] { new Float(begin) } ); try {
+        float minTime = Float.POSITIVE_INFINITY;
         this.begin = begin;
         Iterator i = beginDependents.iterator();
         while (i.hasNext()) {
             InstanceTime it = (InstanceTime) i.next();
-            it.dependentUpdate(begin);
+            float t = it.dependentUpdate(begin);
+            if (t < minTime) {
+                minTime = t;
+            }
         }
+        return minTime;
         // } finally { Trace.exit(); }
     }
 
     /**
      * Updates the end time for this interval.
      */
-    void setEnd(float end) {
+    float setEnd(float end) {
         // Trace.enter(this, "setEnd", new Object[] { new Float(end) } ); try {
+        float minTime = Float.POSITIVE_INFINITY;
         this.end = end;
         Iterator i = endDependents.iterator();
         while (i.hasNext()) {
             InstanceTime it = (InstanceTime) i.next();
-            it.dependentUpdate(end);
+            float t = it.dependentUpdate(end);
+            if (t < minTime) {
+                minTime = t;
+            }
         }
+        return minTime;
         // } finally { Trace.exit(); }
     }
 }

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/SyncbaseTimingSpecifier.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/SyncbaseTimingSpecifier.java?view=diff&rev=485485&r1=485484&r2=485485
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/SyncbaseTimingSpecifier.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/SyncbaseTimingSpecifier.java Sun Dec 10 20:04:53 2006
@@ -92,26 +92,32 @@
     /**
      * Called by the timebase element when it creates a new Interval.
      */
-    void newInterval(Interval interval) {
+    float newInterval(Interval interval) {
         // Trace.enter(this, "newInterval", new Object[] { interval } ); try {
+        if (owner.hasPropagated) {
+            return Float.POSITIVE_INFINITY;
+        }
         InstanceTime instance =
             new InstanceTime(this, (syncBegin ? interval.getBegin()
                                               : interval.getEnd()) + offset,
                              interval, true);
         instances.put(interval, instance);
         interval.addDependent(instance, syncBegin);
-        owner.addInstanceTime(instance, isBegin);
+        return owner.addInstanceTime(instance, isBegin);
         // } finally { Trace.exit(); }
     }
 
     /**
      * Called by the timebase element when it deletes an Interval.
      */
-    void removeInterval(Interval interval) {
+    float removeInterval(Interval interval) {
         // Trace.enter(this, "removeInterval", new Object[] { interval } ); try {
+        if (owner.hasPropagated) {
+            return Float.POSITIVE_INFINITY;
+        }
         InstanceTime instance = (InstanceTime) instances.get(interval);
         interval.removeDependent(instance, syncBegin);
-        owner.removeInstanceTime(instance, isBegin);
+        return owner.removeInstanceTime(instance, isBegin);
         // } finally { Trace.exit(); }
     }
 
@@ -119,9 +125,12 @@
      * Called by an {@link InstanceTime} created by this TimingSpecifier
      * to indicate that its value has changed.
      */
-    void handleTimebaseUpdate(InstanceTime instanceTime, float newTime) {
+    float handleTimebaseUpdate(InstanceTime instanceTime, float newTime) {
         // Trace.enter(this, "handleTimebaseUpdate", new Object[] { instanceTime, new Float(newTime) } ); try {
-        owner.instanceTimeChanged(instanceTime, isBegin);
+        if (owner.hasPropagated) {
+            return Float.POSITIVE_INFINITY;
+        }
+        return owner.instanceTimeChanged(instanceTime, isBegin);
         // } finally { Trace.exit(); }
     }
 }

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedDocumentRoot.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedDocumentRoot.java?view=diff&rev=485485&r1=485484&r2=485485
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedDocumentRoot.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedDocumentRoot.java Sun Dec 10 20:04:53 2006
@@ -63,6 +63,16 @@
     protected LinkedList listeners = new LinkedList();
 
     /**
+     * Whether the document is currently being sampled.
+     */
+    protected boolean isSampling;
+
+    /**
+     * Whether the document is currently being sampled for a hyperlink.
+     */
+    protected boolean isHyperlinking;
+
+    /**
      * Creates a new TimedDocumentRoot.
      * @param useSVG11AccessKeys allows the use of accessKey() timing
      *                           specifiers with a single character
@@ -102,11 +112,27 @@
     }
 
     /**
+     * Returns whether the document is currently being sampled.
+     */
+    public boolean isSampling() {
+        return isSampling;
+    }
+
+    /**
+     * Returns whether the document is currently being sampled for a hyperlink.
+     */
+    public boolean isHyperlinking() {
+        return isHyperlinking;
+    }
+
+    /**
      * Samples the entire timegraph at the given time.
      */
     public float seekTo(float time, boolean hyperlinking) {
-        lastSampleTime = time;
         // Trace.enter(this, "seekTo", new Object[] { new Float(time) } ); try {
+        isSampling = true;
+        lastSampleTime = time;
+        isHyperlinking = hyperlinking;
         propagationFlags.clear();
         // No time containers in SVG, so we don't have to worry
         // about a partial ordering of timed elements to sample.
@@ -132,6 +158,7 @@
                 }
             }
         } while (needsUpdates);
+        isSampling = false;
         return mint;
         // } finally { Trace.exit(); }
     }

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedElement.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedElement.java?view=diff&rev=485485&r1=485484&r2=485485
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedElement.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimedElement.java Sun Dec 10 20:04:53 2006
@@ -222,6 +222,17 @@
     protected HashMap handledEvents = new HashMap();
 
     /**
+     * Whether this timed element is currently being sampled.
+     */
+    protected boolean isSampling;
+
+    /**
+     * Whether an instance time update message has already been propagated to
+     * this timed element.
+     */
+    protected boolean hasPropagated;
+
+    /**
      * Creates a new TimedElement.
      */
     public TimedElement() {
@@ -263,8 +274,9 @@
      * firing, a DOM method being called or a new Instance being
      * created by a syncbase element.
      */
-    protected void addInstanceTime(InstanceTime time, boolean isBegin) {
+    protected float addInstanceTime(InstanceTime time, boolean isBegin) {
         // Trace.enter(this, "addInstanceTime", new Object[] { time, new Boolean(isBegin) } ); try {
+        hasPropagated = true;
         Vector instanceTimes = isBegin ? beginInstanceTimes : endInstanceTimes;
         int index = Collections.binarySearch(instanceTimes, time);
         if (index < 0) {
@@ -272,6 +284,14 @@
         }
         instanceTimes.insertElementAt(time, index);
         shouldUpdateCurrentInterval = true;
+        float ret;
+        if (root.isSampling() && !isSampling) {
+            ret = sampleAt(root.getCurrentTime(), root.isHyperlinking());
+        } else {
+            ret = Float.POSITIVE_INFINITY;
+        }
+        hasPropagated = false;
+        return ret;
         // } finally { Trace.exit(); }
     }
 
@@ -280,33 +300,41 @@
      * should be removed.  This will be in response to the pruning of an
      * Interval.
      */
-    protected void removeInstanceTime(InstanceTime time, boolean isBegin) {
+    protected float removeInstanceTime(InstanceTime time, boolean isBegin) {
         // Trace.enter(this, "removeInstanceTime", new Object[] { time, new Boolean(isBegin) } ); try {
+        hasPropagated = true;
         Vector instanceTimes = isBegin ? beginInstanceTimes : endInstanceTimes;
         int index = Collections.binarySearch(instanceTimes, time);
         for (int i = index; i >= 0; i--) {
             InstanceTime it = (InstanceTime) instanceTimes.get(i);
             if (it == time) {
                 instanceTimes.remove(i);
-                return;
+                break;
             }
             if (it.compareTo(time) != 0) {
                 break;
             }
         }
         int len = instanceTimes.size();
-        for (int i = index + 1; i < len; i++) {      // todo cam, please check this
+        for (int i = index + 1; i < len; i++) {
             InstanceTime it = (InstanceTime) instanceTimes.get(i);
             if (it == time) {
                 instanceTimes.remove(i);
-                return;
+                break;
             }
             if (it.compareTo(time) != 0) {
                 break;
             }
         }
-        // The instance time wasn't found, shouldn't get here.
         shouldUpdateCurrentInterval = true;
+        float ret;
+        if (root.isSampling() && !isSampling) {
+            ret = sampleAt(root.getCurrentTime(), root.isHyperlinking());
+        } else {
+            ret = Float.POSITIVE_INFINITY;
+        }
+        hasPropagated = false;
+        return ret;
         // } finally { Trace.exit(); }
     }
 
@@ -315,9 +343,18 @@
      * has been updated.  This will be in response to a dependent
      * syncbase change.
      */
-    protected void instanceTimeChanged(InstanceTime time, boolean isBegin) {
+    protected float instanceTimeChanged(InstanceTime time, boolean isBegin) {
         // Trace.enter(this, "instanceTimeChanged", new Object[] { time, new Boolean(isBegin) } ); try {
+        hasPropagated = true;
         shouldUpdateCurrentInterval = true;
+        float ret;
+        if (root.isSampling() && !isSampling) {
+            ret = sampleAt(root.getCurrentTime(), root.isHyperlinking());
+        } else {
+            ret = Float.POSITIVE_INFINITY;
+        }
+        hasPropagated = false;
+        return ret;
         // } finally { Trace.exit(); }
     }
 
@@ -490,46 +527,62 @@
     /**
      * Notifies dependents of a new interval.
      */
-    protected void notifyNewInterval(Interval interval) {
+    protected float notifyNewInterval(Interval interval) {
         // Trace.enter(this, "notifyNewInterval", new Object[] { interval } ); try {
+        float dependentMinTime = Float.POSITIVE_INFINITY;
         Iterator i = beginDependents.iterator();
         while (i.hasNext()) {
             TimingSpecifier ts = (TimingSpecifier) i.next();
             // Trace.print(ts.owner + "'s " + (ts.isBegin ? "begin" : "end" ) + ": " + ts);
-            if (root.shouldPropagate(interval, ts, true)) {
-                ts.newInterval(interval);
-            } else {
-                // Trace.print("(but not propagating)");
-            }
+            // if (root.shouldPropagate(interval, ts, true)) {
+                float t = ts.newInterval(interval);
+                if (t < dependentMinTime) {
+                    dependentMinTime = t;
+                }
+            // } else {
+            //     // Trace.print("(but not propagating)");
+            // }
         }
         i = endDependents.iterator();
         while (i.hasNext()) {
             TimingSpecifier ts = (TimingSpecifier) i.next();
             // Trace.print(ts.owner + "'s " + (ts.isBegin ? "begin" : "end" ) + ": " + ts);
-            if (root.shouldPropagate(interval, ts, false)) {
-                ts.newInterval(interval);
-            } else {
-                // Trace.print("(but not propagating)");
-            }
+            // if (root.shouldPropagate(interval, ts, false)) {
+            // } else {
+                float t = ts.newInterval(interval);
+                if (t < dependentMinTime) {
+                    dependentMinTime = t;
+                }
+            //     // Trace.print("(but not propagating)");
+            // }
         }
+        return dependentMinTime;
         // } finally { Trace.exit(); }
     }
 
     /**
      * Notifies dependents of a removed interval.
      */
-    protected void notifyRemoveInterval(Interval interval) {
+    protected float notifyRemoveInterval(Interval interval) {
         // Trace.enter(this, "notifyRemoveInterval", new Object[] { interval } ); try {
+        float dependentMinTime = Float.POSITIVE_INFINITY;
         Iterator i = beginDependents.iterator();
         while (i.hasNext()) {
             TimingSpecifier ts = (TimingSpecifier) i.next();
-            ts.removeInterval(interval);
+            float t = ts.removeInterval(interval);
+            if (t < dependentMinTime) {
+                dependentMinTime = t;
+            }
         }
         i = endDependents.iterator();
         while (i.hasNext()) {
             TimingSpecifier ts = (TimingSpecifier) i.next();
-            ts.removeInterval(interval);
+            float t = ts.removeInterval(interval);
+            if (t < dependentMinTime) {
+                dependentMinTime = t;
+            }
         }
+        return dependentMinTime;
         // } finally { Trace.exit(); }
     }
 
@@ -548,6 +601,8 @@
      */
     protected float sampleAt(float parentSimpleTime, boolean hyperlinking) {
         // Trace.enter(this, "sampleAt", new Object[] { new Float(parentSimpleTime) } ); try {
+        isSampling = true;
+
         float time = parentSimpleTime; // No time containers in SVG.
 
         // First, process any events that occurred since the last sampling,
@@ -604,7 +659,6 @@
                     (SMIL_BEGIN_EVENT_NAME, currentInterval.getBegin(), 0);
             }
         }
-        boolean wasActive = isActive;
         // For each sample, we might need to update the current interval's
         // begin and end times, or end the current interval and compute
         // a new one.
@@ -622,7 +676,9 @@
                               currentRepeatIteration);
             }
         }
+
         // Trace.print("begin loop");
+        float dependentMinTime = Float.POSITIVE_INFINITY;
         if (hyperlinking) {
             shouldUpdateCurrentInterval = true;
         }
@@ -630,7 +686,8 @@
             if (hasEnded) {
                 previousIntervals.add(currentInterval);
                 isActive = false;
-                isFrozen = false;
+                isFrozen = fillMode == FILL_FREEZE;
+                toInactive(isFrozen);
                 fireTimeEvent(SMIL_END_EVENT_NAME, currentInterval.getEnd(), 0);
             }
             boolean first =
@@ -646,13 +703,17 @@
                     currentInterval = computeInterval(first, false, beginAfter);
                     if (currentInterval != null) {
                         // Trace.print("creating new interval " + currentInterval + ", propagating to:");
-                        notifyNewInterval(currentInterval);
+                        float dmt = notifyNewInterval(currentInterval);
+                        if (dmt < dependentMinTime) {
+                            dependentMinTime = dmt;
+                        }
                         float beginEventTime = currentInterval.getBegin();
                         if (time >= beginEventTime) {
                             lastRepeatTime = beginEventTime;
                             if (beginEventTime < 0) {
                                 beginEventTime = 0;
                             }
+                            toActive(beginEventTime);
                             isActive = true;
                             isFrozen = false;
                             fireTimeEvent(SMIL_BEGIN_EVENT_NAME, beginEventTime, 0);
@@ -680,16 +741,25 @@
                     }
                     Interval interval = computeInterval(false, false, beginAfter);
                     if (interval == null) {
-                        notifyRemoveInterval(currentInterval);
+                        float dmt = notifyRemoveInterval(currentInterval);
+                        if (dmt < dependentMinTime) {
+                            dependentMinTime = dmt;
+                        }
                         currentInterval = null;
                     } else {
                         float newBegin = interval.getBegin();
                         float newEnd = interval.getEnd();
                         if (currentBegin != newBegin) {
-                            currentInterval.setBegin(newBegin);
+                            float dmt = currentInterval.setBegin(newBegin);
+                            if (dmt < dependentMinTime) {
+                                dependentMinTime = dmt;
+                            }
                         }
                         if (currentInterval.getEnd() != newEnd) {
-                            currentInterval.setEnd(newEnd);
+                            float dmt = currentInterval.setEnd(newEnd);
+                            if (dmt < dependentMinTime) {
+                                dependentMinTime = dmt;
+                            }
                         }
                     }
                 } else {
@@ -697,7 +767,10 @@
                     Interval interval = computeInterval(false, true, currentBegin);
                     float newEnd = interval.getEnd();
                     if (currentInterval.getEnd() != newEnd) {
-                        currentInterval.setEnd(newEnd);
+                        float dmt = currentInterval.setEnd(newEnd);
+                        if (dmt < dependentMinTime) {
+                            dependentMinTime = dmt;
+                        }
                     }
                 }
             }
@@ -707,14 +780,6 @@
         }
         // Trace.print("end loop");
 
-        if (!wasActive && isActive) {
-            isFrozen = false;
-            toActive(currentInterval.getBegin());
-        } else if (wasActive && !isActive) {
-            isFrozen = fillMode == FILL_FREEZE;
-            toInactive(isFrozen);
-        }
-
         float d = getSimpleDur();
         if (isActive) {
             // Trace.print("element active, sampling at simple time " + (time - lastRepeatTime));
@@ -733,15 +798,20 @@
             // Trace.print("element not sampling");
         }
 
+        isSampling = false;
+
         lastSampleTime = time;
         if (currentInterval != null) {
             float t = currentInterval.getBegin() - time;
-            if (t > 0) {
-                return t;
+            if (t <= 0) {
+                t = isConstantAnimation() ? currentInterval.getEnd() - time : 0;
             }
-            return isConstantAnimation() ? currentInterval.getEnd() - time : 0;
+            if (dependentMinTime < t) {
+                return dependentMinTime;
+            }
+            return t;
         }
-        return Float.POSITIVE_INFINITY;
+        return dependentMinTime;
         // } finally { Trace.exit(); }
     }
 

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimingSpecifier.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimingSpecifier.java?view=diff&rev=485485&r1=485484&r2=485485
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimingSpecifier.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/anim/timing/TimingSpecifier.java Sun Dec 10 20:04:53 2006
@@ -85,7 +85,8 @@
      * This should be overridden in descendant classes that generate
      * time instances based on the interval of a timebase element.
      */
-    void newInterval(Interval interval) {
+    float newInterval(Interval interval) {
+        return Float.POSITIVE_INFINITY;
     }
 
     /**
@@ -93,7 +94,8 @@
      * This should be overridden in descendant classes that generate
      * time instances based on the interval of a timebase element.
      */
-    void removeInterval(Interval interval) {
+    float removeInterval(Interval interval) {
+        return Float.POSITIVE_INFINITY;
     }
 
     /**
@@ -102,6 +104,7 @@
      * in descendant classes that generate time instances based on the
      * interval of a timebase element.
      */
-    void handleTimebaseUpdate(InstanceTime instanceTime, float newTime) {
+    float handleTimebaseUpdate(InstanceTime instanceTime, float newTime) {
+        return Float.POSITIVE_INFINITY;
     }
 }

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/bridge/SVGAnimationEngine.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/bridge/SVGAnimationEngine.java?view=diff&rev=485485&r1=485484&r2=485485
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/bridge/SVGAnimationEngine.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/bridge/SVGAnimationEngine.java Sun Dec 10 20:04:53 2006
@@ -615,6 +615,69 @@
     }
 
     /**
+     * Idle runnable to tick the animation, that reads times from System.in.
+     */
+    protected class DebugAnimationTickRunnable extends AnimationTickRunnable {
+
+        float t = 0f;
+
+        public DebugAnimationTickRunnable(RunnableQueue q) {
+            super(q);
+            waitTime = Long.MAX_VALUE;
+            new Thread() {
+                public void run() {
+                    java.io.BufferedReader r = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
+                    System.out.println("Enter times.");
+                    for (;;) {
+                        String s;
+                        try {
+                            s = r.readLine();
+                        } catch (java.io.IOException e) {
+                            s = null;
+                        }
+                        if (s == null) {
+                            System.exit(0);
+                        }
+                        t = Float.parseFloat(s);
+                        DebugAnimationTickRunnable.this.resume();
+                    }
+                }
+            }.start();
+        }
+
+        public void resume() {
+            waitTime = 0;
+            Object lock = q.getIteratorLock();
+            synchronized (lock) {
+                lock.notify();
+            }
+        }
+
+        public long getWaitTime() {
+            long wt = waitTime;
+            waitTime = Long.MAX_VALUE;
+            return wt;
+        }
+
+        public void run() {
+            try {
+                try {
+                    tick(t, false);
+                } catch (AnimationException ex) {
+                    throw new BridgeException(ctx, ex.getElement().getElement(),
+                                              ex.getMessage());
+                }
+            } catch (BridgeException ex) {
+                if (ctx.getUserAgent() == null) {
+                    ex.printStackTrace();
+                } else {
+                    ctx.getUserAgent().displayError(ex);
+                }
+            }
+        }
+    }
+
+    /**
      * Idle runnable to tick the animation.
      */
     protected class AnimationTickRunnable
@@ -622,7 +685,6 @@
 
         protected Calendar time = Calendar.getInstance();
         double second = -1.;
-        int idx = -1;
         int frames;
         long waitTime;
         RunnableQueue q;