001: /*************************************************************************
002: * *
003: * 1) This source code file, in unmodified form, and compiled classes *
004: * derived from it can be used and distributed without restriction, *
005: * including for commercial use. (Attribution is not required *
006: * but is appreciated.) *
007: * *
008: * 2) Modified versions of this file can be made and distributed *
009: * provided: the modified versions are put into a Java package *
010: * different from the original package, edu.hws; modified *
011: * versions are distributed under the same terms as the original; *
012: * and the modifications are documented in comments. (Modification *
013: * here does not include simply making subclasses that belong to *
014: * a package other than edu.hws, which can be done without any *
015: * restriction.) *
016: * *
017: * David J. Eck *
018: * Department of Mathematics and Computer Science *
019: * Hobart and William Smith Colleges *
020: * Geneva, New York 14456, USA *
021: * Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
022: * *
023: *************************************************************************/package edu.hws.jcm.awt;
024:
025: import edu.hws.jcm.data.*;
026: import java.awt.*;
027: import java.awt.event.*;
028:
029: /**
030: * An Animator can change a value continuously, without user intervention, by running
031: * a separate Thread. By default, an animator appears as a "Start" button. When the
032: * button is pressed, the value of the animator starts counting 0, 1, 2, ... The button
033: * changes to a "Stop" button. When this is pressed, the value stops changing. A Controller
034: * can be set, by calling the setOnChange() method, to be notified whenever the value is
035: * changed. If this is done, then the value of the Animator will only change when its
036: * checkInput() method is called, so it should be added to a Controller which will call
037: * this method.
038: *
039: * <p>The getValueAsVariable() method can be called to get a Variable whose value is
040: * the value of the Animator. This variable can then be added to a Parser, so it can
041: * be used in expressions. An Animator is "Tieable", so it can share its value
042: * with another InputObject, such as a VariableSlider or a VariableIput.
043: *
044: * <p>There are many options: If maximum and minimum values are both specified, then the value
045: * of the Animator ranges between these values. By default, this interval is divided into
046: * 100 sub-intervals, so that there are 101 frames. However, the number of intervals can
047: * also be set. If no min or max is specified but a number of intervals is specified,
048: * then the value is an integer which ranges from 0 up to the specified number of intervals.
049: * If the number of frames is finite, then there are three possibities when the last
050: * frame is reached: The animation can stop; it can loop back to the the starting
051: * frame, or it can reverse direction and cycle back and forth. The behavior is controlled
052: * with the setLoopStyle() method.
053: *
054: * <p>An Animator is actually a Panel which can contain other controls in addition to or
055: * instead of the Start/Stop button. For example, it can contain a "Next" button or
056: * a pop-up menu to control the speed.
057: *
058: */
059: public class Animator extends Panel implements Value, Tieable,
060: InputObject, ActionListener, ItemListener, Runnable {
061:
062: /**
063: * Used to add a component to the Animator Panel; can be used in a constructor
064: * or in the addControl() method. Can also be used in the getControl() method
065: * to specify which component is to be retrieved.
066: */
067: public static final int START_STOP_BUTTON = 1, START_BUTTON = 2,
068: PAUSE_BUTTON = 4, STOP_BUTTON = 8, NEXT_BUTTON = 16,
069: PREV_BUTTON = 32, SPEED_CHOICE = 64, LOOP_CHOICE = 128;
070:
071: /**
072: * Indicates that the components in the Animator panel are to be stacked vertically.
073: * (Can be used in a constructor and in the setOrientation method.)
074: */
075: public static final int VERTICAL = 1;
076:
077: /**
078: * Indicates that the components in the Animator panel are to be in a horizontal row.
079: * (Can be used in a constructor and in the setOrientation method.)
080: */
081: public static final int HORIZONTAL = 0;
082:
083: /**
084: * Represents a loop style in which the animation is played once. When the final frame
085: * is reached, the animation ends. Use in the setLoopStyle() method.
086: */
087: public static final int ONCE = 0;
088:
089: /**
090: * Represents a loop style in which the animation is played repeatedly. When the final frame
091: * is reached, the animation returns to the first frame. Use in the setLoopStyle() method.
092: */
093: public static final int LOOP = 1;
094:
095: /**
096: * Represents a loop style in which the animation is cycled back and forth. When the final frame
097: * is reached, the animation reverses direction. Use in the setLoopStyle() method.
098: */
099: public static final int BACK_AND_FORTH = 2;
100:
101: private Button startStopButton, startButton, stopButton,
102: pauseButton, nextButton, prevButton;
103: private Choice speedChoice, loopChoice;
104: private int orientation;
105: private String startButtonName = "Start";
106: private String stopButtonName = "Stop";
107:
108: private volatile int loopStyle;
109: private boolean runningBackwards;
110: private volatile int millisPerFrame = 100;
111: private volatile int frame;
112: private int maxFrame;
113: private double value;
114:
115: private volatile long serialNumber = 1;
116:
117: private Computable onChange;
118:
119: private Value min, max; // If both are non-null, give the max and min values of the animator.
120: private Value intervals; // If non-null, gives the number of sub-intervals. Number of frames is this value plus one.
121: private boolean needsValueCheck = true;
122: private double min_val, max_val; // Values of min and max.
123: private int intervals_val; // Value of intervals.
124:
125: private static int START = 0, PAUSE = 1, NEXT = 2, PREV = 3,
126: STOP = 4, RUN = 5; // possible values of thread status
127: private Thread runner;
128: private volatile int status = STOP;
129:
130: private boolean undefinedWhenNotRunning;
131:
132: /**
133: * Create a default Animator. If no changes are made by calling other methods, it will appear as
134: * a Start/Stop button. When Start is pressed, the value will count 0, 1, 2, 3, ..., until the Stop
135: * button is pressed. Restarting the animation starts the value again at zero.
136: */
137: public Animator() {
138: this (START_STOP_BUTTON, HORIZONTAL);
139: }
140:
141: /**
142: * Create an Animator containing the specified control. The parameter can consist of one or
143: * more of the following constants, or'ed together: START_STOP_BUTTON, START_BUTTON, STOP_BUTTON,
144: * PAUSE_BUTTON, NEXT_BUTTON, PREV_BUTTON, SPEED_CHOICE, LOOP_CHOICE. If no changes are made
145: * by calling other methods, the value of the Animator will be 0, 1, 2, 3, .... The components
146: * are arranged into one horizontal row, using a GridLayout.
147: */
148: public Animator(int controls) {
149: this (controls, HORIZONTAL, null, null, null);
150: }
151:
152: /**
153: * Create an Animator containing specified controls. (See the one-parameter constructor.)
154: * The second parameter should be one of the constants HORIZONTAL or VERTICAL, to specify
155: * how the components are arranged in the Animator panel.
156: */
157: public Animator(int controls, int orientation) {
158: this (controls, orientation, null, null, null);
159: }
160:
161: /** Create an Animator with specified controls, orienation, range limits and number of intervals
162: *
163: * @param controls Specify the controls to add to the Animator. Can consist of one or
164: * more of the following constants, or'ed together: START_STOP_BUTTON, START_BUTTON, STOP_BUTTON,
165: * PAUSE_BUTTON, NEXT_BUTTON, PREV_BUTTON, SPEED_CHOICE, LOOP_CHOICE.
166: * @param orientation How the controls are arranged in the panel. One of the constants VERTICAL or HORIZONTAL.
167: * @param min If BOTH min and max are non-null, they specify the range of values of the Animator.
168: * @param max If BOTH min and max are non-null, they specify the range of values of the Animator.
169: * @param intervals If non-null, specifies the number of intervals into which the range of values
170: * is divided. Note that the value will be rounded to the nearest integer and clamped to the
171: * range 0 to 100000. The number of frames is the number of intervals, plus one. If min and max are
172: * non-null and intervals is null, then a default value of 100 is used. If either min or max is
173: * null and intervals is non-null, then the Animator takes on the values 0, 1, 2, ..., intervals.
174: */
175: public Animator(int controls, int orientation, Value min,
176: Value max, Value intervals) {
177: this .min = min;
178: this .max = max;
179: this .intervals = intervals;
180: this .orientation = orientation;
181: if (orientation == VERTICAL)
182: setLayout(new GridLayout(0, 1));
183: else
184: setLayout(new GridLayout(1, 0));
185: for (int i = 1; i <= LOOP_CHOICE; i <<= 1)
186: if ((i & controls) != 0)
187: addControl(i);
188: }
189:
190: //-------------- Accessor methods for public properties ------------------
191:
192: /**
193: * Get one of controls associated with the Animator. Usually, these are displayed
194: * in the Animator panel, but you could get a control and add it to another panel if you
195: * want. Even if you do this, the control will still be managed by the Animator (which
196: * will respond to it and enable/disable it, for example). You might also want to get
197: * one of the Animator's buttons so that you can change its label. The value
198: * of the parameter should be one of the constants START_STOP_BUTTON, START_BUTTON, STOP_BUTTON,
199: * PAUSE_BUTTON, NEXT_BUTTON, PREV_BUTTON, SPEED_CHOICE, LOOP_CHOICE. If the parameter is
200: * not one of these values, then null is returned.
201: */
202: public Component getControl(int controlCode) {
203: switch (controlCode) {
204: case START_STOP_BUTTON:
205: if (startStopButton == null) {
206: startStopButton = new Button(startButtonName);
207: startStopButton.setBackground(Color.lightGray);
208: startStopButton.addActionListener(this );
209: }
210: return startStopButton;
211: case START_BUTTON:
212: if (startButton == null) {
213: startButton = new Button(startButtonName);
214: startButton.setBackground(Color.lightGray);
215: startButton.addActionListener(this );
216: }
217: return startButton;
218: case STOP_BUTTON:
219: if (stopButton == null) {
220: stopButton = new Button(stopButtonName);
221: stopButton.setBackground(Color.lightGray);
222: stopButton.addActionListener(this );
223: stopButton.setEnabled(false);
224: }
225: return stopButton;
226: case PAUSE_BUTTON:
227: if (pauseButton == null) {
228: pauseButton = new Button("Pause");
229: pauseButton.setBackground(Color.lightGray);
230: pauseButton.addActionListener(this );
231: pauseButton.setEnabled(false);
232: }
233: return pauseButton;
234: case NEXT_BUTTON:
235: if (nextButton == null) {
236: nextButton = new Button("Next");
237: nextButton.setBackground(Color.lightGray);
238: nextButton.addActionListener(this );
239: }
240: return nextButton;
241: case PREV_BUTTON:
242: if (prevButton == null) {
243: prevButton = new Button("Prev");
244: prevButton.setBackground(Color.lightGray);
245: prevButton.addActionListener(this );
246: }
247: return prevButton;
248: case SPEED_CHOICE:
249: if (speedChoice == null) {
250: speedChoice = new Choice();
251: speedChoice.add("Fastest");
252: speedChoice.add("Fast");
253: speedChoice.add("Moderate");
254: speedChoice.add("Slow");
255: speedChoice.add("Slower");
256: speedChoice.select(2);
257: speedChoice.addItemListener(this );
258: }
259: return speedChoice;
260: case LOOP_CHOICE:
261: if (loopChoice == null) {
262: loopChoice = new Choice();
263: loopChoice.add("Play Once");
264: loopChoice.add("Loop");
265: loopChoice.add("Back and Forth");
266: loopChoice.addItemListener(this );
267: }
268: return loopChoice;
269: default:
270: return null;
271: }
272: }
273:
274: /**
275: * Add one of the possible control buttons or pop-up menus to the Animator. The possible values
276: * of the parameter and their meanings are as follows:
277: * <p>START_STOP_BUTTON: When clicked, animation starts and name of button changes; when clicked again, animation stops.
278: * <p>START_BUTTON: When clicked, animation starts.
279: * <p>STOP_BUTTON: When clicked, animaton stops.
280: * <p>PAUSE_BUTTON: When clicked, animation is paused; this is different from stopping the animation since
281: * a paused animation can be resumed from the same point while a stopped animation can only be restarted
282: * from the beginning.
283: * <p>NEXT_BUTTON: When clicked, the animation advances one frame; this is disabled when the animation is running.
284: * <p>PREV_BUTTON: When clicked, the animation is moved back one frame; this is disabled when the animation is running.
285: * <p>SPEED_CHOICE: A pop-up menu whose value controls the speed at which the animation plays.
286: * <P>LOOP_CHOICE: A pop-up menu that controls the style of animation, that is, what happens when the
287: * animation reaches its final frame; values are Play Once, Loop, and Back and Forth.
288: * <p>If the parameter is not one of these constants, then nothing is done. Ordinarily, this
289: * will be called during initialization. (If you call it at some other time, you will have
290: * to validate the panel yourself.) The return value is the component that is added, or null
291: * if the parameter value is not legal.
292: */
293: public Component addControl(int controlCode) {
294: Component c = getControl(controlCode);
295: if (c == null)
296: return null;
297: else {
298: add(c);
299: return c;
300: }
301: }
302:
303: /**
304: * The name of the Start/Stop button is managed by the Animator, so changing it directly makes
305: * no sense. This method can be used to specify the label displayed by the Start/Stop button
306: * when the animation is NOT running. This name is also used for the Start button. This method
307: * should ordinarily be called during initialization. In any case, it should not be called while
308: * an animation is running, since it changes the name of the Start/Stop button to the specified value.
309: */
310: public void setStartButtonName(String name) {
311: if (name != null) {
312: startButtonName = name;
313: if (startStopButton != null)
314: startStopButton.setLabel(name);
315: if (startButton != null)
316: startButton.setLabel(name);
317: }
318: }
319:
320: /**
321: * The name of the Start/Stop button is managed by the Animator, so changing it directly makes
322: * no sense. This method can be used to specify the label displayed by the Start/Stop button
323: * when the animation IS running. This name is also used for the Stop button. This method
324: * should ordinarily be called during initialization. In any case, it should not be called while
325: * an animation is running, since it does not change the name of the Start/Stop button.
326: */
327: public void setStopButtonName(String name) {
328: if (name != null) {
329: stopButtonName = name;
330: if (stopButton != null)
331: stopButton.setLabel(name);
332: }
333: }
334:
335: /**
336: * Get the constant, VERTICAL or HORIZONTAL, that was used to specify whether the components
337: * in the animator are arranged veritcally or horizontally.
338: */
339: public int getOrientation() {
340: return orientation;
341: }
342:
343: /**
344: * Set the orientation of the components in the Animator panel. The parameter should be one
345: * of the constants HORIZONTAL or VERTICAL. This just sets the layout for the panel to
346: * be a GridLayout with one row or one column and validates the panel. You could also
347: * set the layout to be something else, such as a FlowLayout, using the setLayout() method.
348: */
349: public void setOrientation(int orientation) {
350: if (orientation != this .orientation
351: && (orientation == HORIZONTAL || orientation == VERTICAL)) {
352: this .orientation = orientation;
353: if (orientation == VERTICAL)
354: setLayout(new GridLayout(0, 1));
355: else
356: setLayout(new GridLayout(1, 0));
357: validate();
358: }
359: }
360:
361: /**
362: * Get the Value object that specifies the final value of the Animator. This object can be null.
363: */
364: public Value getMax() {
365: return max;
366: }
367:
368: /**
369: * Set the Value object that gives the final value of the Animator. If both min and max are
370: * non-null, the value of the Animator ranges from min to max as the animation procedes. (It is not required
371: * that max be greater than min. They should probably be calles startVal and endVal.)
372: */
373: public void setMax(Value max) {
374: this .max = max;
375: needsValueCheck = true;
376: }
377:
378: /**
379: * A convenience method that simply calls setMax(new Constant(d)).
380: */
381: public void setMax(double d) {
382: setMax(new Constant(d));
383: }
384:
385: /**
386: * Get the Value object that specifies the starting value of the Animator. This object can be null.
387: */
388: public Value getMin() {
389: return min;
390: }
391:
392: /**
393: * Set the Value object that gives the starting value of the Animator. If both min and max are
394: * non-null, the value ranges from min to max as the animation procedes. (It is not required
395: * that max be greater than min.)
396: */
397: public void setMin(Value min) {
398: this .min = min;
399: needsValueCheck = true;
400: }
401:
402: /**
403: * A convenience method that simply calls setMin(new Constant(d)).
404: */
405: public void setMin(double d) {
406: setMin(new Constant(d));
407: }
408:
409: /**
410: * Get the Value object that specifies the number of frames in the animation. This can be null.
411: */
412: public Value getIntervals() {
413: return intervals;
414: }
415:
416: /**
417: * Set the Value object that specifies the number of frames in the animation. If non-null, then
418: * the value is rounded to the nearest integer and clamped to the range 1 to 100000. If it is
419: * null and min and max are non-null, then a default value of 100 is used. If it is null and
420: * min or max is null, then the number of frames is unlimited and the values taken on by the
421: * animator are 0, 1, 2, 3, ..., that is, the value of the animator is the frame number.
422: * If min or max is null and intervals is non-null, then the values taken on by the
423: * animator are 0, 1, 2, ..., intervals. Note that the number of frames is (intervals+1).
424: */
425: public void setIntervals(Value intervals) {
426: this .intervals = intervals;
427: needsValueCheck = true;
428: }
429:
430: /**
431: * A convenience method that simply calls setIntervals(new Constant(d)).
432: */
433: public void setIntervals(int intervals) {
434: setIntervals(new Constant(intervals));
435: }
436:
437: /**
438: * Get the nominal number of milliseconds per frame. The actual time between frames
439: * can be longer because of the work that is done processing the frame or on other tasks.
440: */
441: public int getMillisPerFrame() {
442: return millisPerFrame;
443: }
444:
445: /**
446: * Set the nominal number of milliseconds per frame. The actual time between frames
447: * can be longer because of the work that is done processing the frame or on other tasks.
448: * Values less than 5 are effectively equivalent to 5. Realistic values are 25 or more,
449: * but it depends on what system the program is running on and how complicated each frame is.
450: */
451: public void setMillisPerFrame(int millis) {
452: millisPerFrame = millis;
453: }
454:
455: /**
456: * Get the loop style, which determines what happens when the final frame of the animation is reached.
457: */
458: public int getLoopStyle() {
459: return loopStyle;
460: }
461:
462: /**
463: * Set the loop style, which determines what happens when the final frame of the animation is reached.
464: * The parameter can be one of the constants: ONCE (animation stops when final frame is reached),
465: * LOOP (animation cycles back to the first frame and continues from there); or BACK_AND_FORTH (animation reverses direction
466: * and cycles back and forth).
467: */
468: public void setLoopStyle(int style) {
469: if (style >= 0 && style <= 2 && style != loopStyle) {
470: loopStyle = style;
471: if (loopChoice != null)
472: loopChoice.select(style);
473: runningBackwards = false;
474: }
475: }
476:
477: /**
478: * Set the value of the animation. Note that the value does not have to be one of
479: * the values that would ordinarily occur in the animation. Of course, if the animation
480: * is running, then the new value won't be around for long since it will change as
481: * soon as the next frame comes up.
482: */
483: synchronized public void setVal(double val) {
484: if (needsValueCheck)
485: checkValue(); // make sure min,max,intervals are evaluated if necessary
486: value = val;
487: serialNumber++;
488: needsValueCheck = false;
489: // Try to make the frame number match the value as closely as possible
490: if (!Double.isNaN(val)) {
491: if (min == null || max == null)
492: frame = (int) Math.round(val);
493: else if (!Double.isNaN(min_val) && !Double.isNaN(max_val)
494: && min_val != max_val)
495: frame = (int) Math.round((val - min_val)
496: / (max_val - min_val) * maxFrame);
497: if (frame < 0)
498: frame = 0;
499: else if (maxFrame > 0 && frame > maxFrame)
500: frame = maxFrame;
501: }
502: }
503:
504: /**
505: * Get the current value of the Animator.
506: */
507: public double getVal() {
508: if (needsValueCheck)
509: checkValue();
510: return value;
511: }
512:
513: /**
514: * Get a variable whose value is always equal to the value of the animator.
515: * The name of the variable will be k.
516: */
517: public Variable getValueAsVariable() {
518: return getValueAsVariable("k");
519: }
520:
521: /**
522: * Get a variable whose value is always equal to the value of the animator.
523: * The name of the variable is specified by the parameter.
524: */
525: public Variable getValueAsVariable(String name) {
526: return new Variable(name) {
527: public void setVal(double val) {
528: Animator.this .setVal(val);
529: }
530:
531: public double getVal() {
532: return Animator.this .getVal();
533: }
534: };
535: }
536:
537: /**
538: * Get the Controller that is notified (by calling its compute() method) whenever
539: * the frame changes. This can be null.
540: */
541: public Computable getOnChange() {
542: return onChange;
543: }
544:
545: /**
546: * Set the Controller that is notified (by calling its compute() method) whenever
547: * the frame changes. If null, no Controller is notified. NOTE: Animators are
548: * different from InputObjects in that when onChange is null, the value of animator
549: * and its associated Variable will change without checkInput() being called.
550: * However, if the onChange is not null, then checkInput() must be called for
551: * the value to change. (So the Animation to be added to the Controller by
552: * calling the Controller's add() method. Then, the Controller will call
553: * the checkInput() method.)
554: */
555: public void setOnChange(Computable onChange) {
556: this .onChange = onChange;
557: }
558:
559: /**
560: * Method required by the InputObject interface. It just calls setOnChange(c).
561: * This is meant to be called by the gatherInputs() method in JCMPanel.
562: */
563: public void notifyControllerOnChange(Controller c) {
564: setOnChange(c);
565: }
566:
567: /**
568: * Get the value of the undefinedWhenNotRunning property.
569: */
570: public boolean getUndefinedWhenNotRunning() {
571: return undefinedWhenNotRunning;
572: }
573:
574: /**
575: * Set the value of the undefinedWhenNotRunning property. If this is true,
576: * then the value of the Animator is Double.NaN except when the animation is
577: * running (or paused), (or if it has been set by a call to the setVal() method).
578: * The default value is false.
579: */
580: public void setUndefinedWhenNotRunning(
581: boolean undefinedWhenNotRunning) {
582: this .undefinedWhenNotRunning = undefinedWhenNotRunning;
583: }
584:
585: //--------------- play control --------------------------------------
586:
587: /**
588: * Start the animation from the first frame, or continue it if it was paused.
589: * This is called when the Start button or Start/Stop button is pressed, but
590: * it could also be called directly.
591: */
592: synchronized public void start() {
593: if (runner != null && runner.isAlive() && status == STOP) {
594: // A previous run is stopping. Give it a chance to stop.
595: try {
596: wait(1000);
597: } catch (InterruptedException e) {
598: }
599: if (runner != null && runner.isAlive()) {
600: runner.stop(); // bad form, but what choice do I have?
601: runner = null;
602: }
603: }
604: if (runner == null || !runner.isAlive()) {
605: runner = new Thread(this );
606: status = START;
607: runner.start();
608: } else if (status != RUN) {
609: status = START;
610: notify();
611: }
612: }
613:
614: /**
615: * Pause the animation, if it is running.
616: * This is called when the Pause button is pressed, but
617: * it could also be called directly.
618: */
619: synchronized public void pause() {
620: if (status == RUN) {
621: status = PAUSE;
622: notify();
623: }
624: }
625:
626: /**
627: * Advance the animation by one frame. This will start the animation from the
628: * first frame if it is stopped. This has no effect unless the animation is
629: * stopped or paused. This is called when the Next button pressed, but
630: * it could also be called directly.
631: */
632: synchronized public void next() {
633: if (runner == null || !runner.isAlive()) {
634: runner = new Thread(this );
635: status = NEXT;
636: runner.start();
637: } else if (status == PAUSE) {
638: status = NEXT;
639: notify();
640: }
641: }
642:
643: /**
644: * Advance the animation BACK one frame. This will start the animation from the
645: * first frame if it is stopped. This has no effect unless the animation is
646: * stopped or paused. This is called when the Prev button pressed, but
647: * it could also be called directly.
648: */
649: synchronized public void prev() {
650: if (runner == null || !runner.isAlive()) {
651: runner = new Thread(this );
652: status = PREV;
653: runner.start();
654: } else if (status == PAUSE) {
655: status = PREV;
656: notify();
657: }
658: }
659:
660: /**
661: * Stop the animation, if it is running or paused. This is called when the Stop button
662: * or the StartStop button is pressed, but it could also be called directly.
663: * NOTE: If the Animator is in an applet, then it is a good idea to call the stop()
664: * method of the Animator from the applet's destroy() method.
665: */
666: synchronized public void stop() {
667: status = STOP;
668: if (runner == null || !runner.isAlive())
669: return;
670: notify();
671: }
672:
673: //-----------------Implementation details --------------------------
674:
675: /**
676: * Respond to button clicks. This is not meant to be called directly.
677: */
678: synchronized public void actionPerformed(ActionEvent evt) {
679: Object src = evt.getSource();
680: if (src == null)
681: return;
682: if (src == startStopButton) {
683: if (status == RUN)
684: stop();
685: else
686: start();
687: } else if (src == startButton) {
688: start();
689: } else if (src == stopButton) {
690: stop();
691: } else if (src == nextButton) {
692: next();
693: } else if (src == prevButton) {
694: prev();
695: } else if (src == pauseButton) {
696: pause();
697: }
698: }
699:
700: /**
701: * Respond to clicks on pop-up menus. This is not meant to be called directly.
702: */
703: synchronized public void itemStateChanged(ItemEvent evt) {
704: if (evt.getSource() == loopChoice && loopChoice != null) {
705: setLoopStyle(loopChoice.getSelectedIndex());
706: } else if (evt.getSource() == speedChoice
707: && speedChoice != null) {
708: switch (speedChoice.getSelectedIndex()) {
709: case 0:
710: millisPerFrame = 0;
711: break;
712: case 1:
713: millisPerFrame = 30;
714: break;
715: case 2:
716: millisPerFrame = 100;
717: break;
718: case 3:
719: millisPerFrame = 500;
720: break;
721: case 4:
722: millisPerFrame = 2000;
723: break;
724: }
725: }
726: }
727:
728: /**
729: * Part of the IputObject interface. This is meant to be called by a Controller.
730: */
731: public void checkInput() {
732: needsValueCheck = true;
733: }
734:
735: /**
736: * Part of the Tieable interface. This is meant to be called by other Tieable objects
737: * as part of object synchronization.
738: */
739: public long getSerialNumber() {
740: if (needsValueCheck)
741: checkValue();
742: return serialNumber;
743: }
744:
745: /**
746: * Part of the Tieable interface. This is meant to be called by Tie objects
747: * as part of object synchronization.
748: */
749: public void sync(Tie tie, Tieable newest) {
750: if (newest != this ) {
751: if (!(newest instanceof Value))
752: throw new IllegalArgumentException(
753: "Internal Error: An Animator can only sync with Value objects.");
754: setVal(((Value) newest).getVal());
755: serialNumber = newest.getSerialNumber();
756: }
757: }
758:
759: synchronized private void checkValue() { // Recompute the value, which might have changed.
760: double newVal;
761: if (min != null)
762: min_val = min.getVal();
763: if (max != null)
764: max_val = max.getVal();
765: if (intervals == null)
766: intervals_val = 0;
767: else {
768: double d = intervals.getVal();
769: if (Double.isNaN(d) || d <= 0.5)
770: intervals_val = 0;
771: else if (d > 100000)
772: intervals_val = 100000;
773: else
774: intervals_val = (int) Math.round(d);
775: }
776: maxFrame = intervals_val;
777: if (min == null || max == null) { // value is frame number
778: newVal = frame;
779: } else if (Double.isNaN(min_val) || Double.isNaN(max_val)
780: || Double.isInfinite(min_val)
781: || Double.isInfinite(max_val)) {
782: newVal = Double.NaN;
783: } else if (intervals_val > 0) {
784: newVal = min_val + (frame * (max_val - min_val))
785: / intervals_val;
786: } else { // Assume 100 intervals if
787: maxFrame = 100;
788: newVal = min_val + (frame * (max_val - min_val)) / 100;
789: }
790: if (undefinedWhenNotRunning && status == STOP)
791: newVal = Double.NaN;
792: value = newVal;
793: needsValueCheck = false;
794: }
795:
796: private void doControlStatus(int status) {
797: // Enable/disable buttons according to Thread status.
798: // status should be START or STOP or PAUSE
799: if (startStopButton != null)
800: startStopButton.setLabel((status == START) ? stopButtonName
801: : startButtonName);
802: if (startButton != null)
803: startButton.setEnabled(status != START);
804: if (stopButton != null)
805: stopButton.setEnabled(status != STOP);
806: if (nextButton != null)
807: nextButton.setEnabled(status != START);
808: if (prevButton != null)
809: prevButton.setEnabled(status != START);
810: if (pauseButton != null)
811: pauseButton.setEnabled(status == START);
812: }
813:
814: private void doAdvanceFrame(int amt) {
815: // Move on to next or previous frame.
816: // amt is +1 or -1
817: serialNumber++;
818: if (loopStyle == BACK_AND_FORTH && runningBackwards)
819: frame -= amt;
820: else
821: frame += amt;
822: if (frame < 0) {
823: if (loopStyle == LOOP)
824: frame = maxFrame; // might be 0, which is OK I guess
825: else if (loopStyle == BACK_AND_FORTH) {
826: frame = 1;
827: if (amt == 1)
828: runningBackwards = false;
829: else
830: runningBackwards = true;
831: } else
832: frame = 0;
833: } else if (maxFrame > 0 && frame > maxFrame) {
834: if (loopStyle == LOOP)
835: frame = 1; // Don't use 0, because usually frames 0 and maxFrame will be the same
836: else if (loopStyle == ONCE) {
837: frame = maxFrame;
838: status = STOP;
839: return; // exit at once
840: } else { // loopStyle == BACK_AND_FORTH
841: frame = maxFrame - 1;
842: if (amt == 1)
843: runningBackwards = true;
844: else
845: runningBackwards = false;
846: }
847: }
848: if (onChange != null)
849: onChange.compute();
850: else
851: needsValueCheck = true;
852: }
853:
854: /**
855: * The method that is run by the animation thread. This is not meant to be called directly.
856: */
857: public void run() {
858: int localstatus = status;
859: long lastFrameTime = 0;
860: runningBackwards = false;
861: if (frame != 0 || undefinedWhenNotRunning) {
862: frame = 0;
863: serialNumber++;
864: lastFrameTime = System.currentTimeMillis();
865: if (onChange != null)
866: onChange.compute();
867: else
868: needsValueCheck = true;
869: if (status == PREV || status == NEXT)
870: status = PAUSE;
871: }
872: try {
873: while (true) {
874: synchronized (this ) {
875: while (status == PAUSE) {
876: if (localstatus != PAUSE) {
877: doControlStatus(PAUSE);
878: localstatus = PAUSE;
879: }
880: try {
881: wait();
882: } catch (InterruptedException e) {
883: }
884: }
885: if (status == STOP)
886: break;
887: localstatus = status;
888: if (needsValueCheck)
889: checkValue(); // make sure maxFrame is correct
890: }
891: if (localstatus == START) {
892: doControlStatus(START);
893: localstatus = status = RUN;
894: }
895: if (localstatus == RUN) {
896: long sinceLastFrame = System.currentTimeMillis()
897: - lastFrameTime;
898: long waitTime = millisPerFrame - sinceLastFrame;
899: if (waitTime <= 5)
900: waitTime = 5;
901: try {
902: synchronized (this ) {
903: wait(waitTime);
904: }
905: } catch (InterruptedException e) {
906: }
907: lastFrameTime = System.currentTimeMillis();
908: if (status == RUN)
909: doAdvanceFrame(1);
910: } else if (localstatus == NEXT) {
911: doAdvanceFrame(1);
912: if (status != STOP) // status can become stop in doAdvanceFrame
913: status = localstatus = PAUSE;
914: } else if (localstatus == PREV) {
915: doAdvanceFrame(-1);
916: if (status != STOP) // status can become stop in doAdvanceFrame (but shouldn't happen here)
917: status = localstatus = PAUSE;
918: }
919: }
920: } finally {
921: synchronized (this ) {
922: status = STOP;
923: doControlStatus(STOP);
924: frame = 0;
925: serialNumber++;
926: if (onChange != null)
927: onChange.compute();
928: else
929: needsValueCheck = true;
930: runner = null;
931: notify(); // in case start() method is waiting for thread to end
932: }
933: }
934: }
935:
936: } // class Animator
|