001: package com.xoetrope.swing.animation;
002:
003: import java.util.Vector;
004:
005: import java.awt.Graphics;
006: import java.awt.Image;
007: import java.awt.MediaTracker;
008: import java.awt.Rectangle;
009: import java.awt.image.ImageObserver;
010: import javax.swing.JComponent;
011: import net.xoetrope.xui.XProject;
012: import net.xoetrope.xui.XProjectManager;
013:
014: /**
015: * "Mix-in" Animation Support.
016: * This mix-in will draw on the owner's surface at specified intervals. The owner
017: * must specify where the image is to be drawn and then this class will load and
018: * render the image. Where multiple images are available an animation will result.
019: *
020: * <p> Copyright (c) Xoetrope Ltd., 2001-2006, This software is licensed under
021: * the GNU Public License (GPL), please see license.txt for more details. If
022: * you make commercial use of this software you must purchase a commercial
023: * license from Xoetrope.</p>
024: * <p> $Revision: 1.5 $</p>
025: */
026: public class XAnimation implements Runnable, ImageObserver {
027: /**
028: * The owner project and the context in which this object operates.
029: */
030: protected XProject currentProject = XProjectManager
031: .getCurrentProject();
032:
033: /**
034: * Create a new animation
035: */
036: public XAnimation() {
037: }
038:
039: private void ctor(JComponent c) {
040: nameVector = new Vector(1, 1);
041: imageVector = new Vector(1, 1);
042:
043: nameVector.setSize(numImages);
044: imageVector.setSize(numImages);
045: for (int i = 0; i < numImages; i++) {
046: // Build path to next image
047: //--------------------------------------------------------------
048: String tempStr = new String("bitmaps/img00"
049: + (((i + 1) < 10) ? "0" : "") + (i + 1) + ".gif");
050: nameVector.setElementAt(tempStr, i);
051: }
052: }
053:
054: /**
055: * Sets the amount of time to wait before refreshing the image.
056: * A minimum of 100 milliseconds is enforced.
057: * @param st the time to sleep.
058: */
059: public synchronized void setSleepTime(int st) {
060: sleepTime = Math.max(st, 100);
061: explicitSleepTime = true;
062: bStopped = false;
063: notify();
064: }
065:
066: /**
067: * Draws any images on the specified graphics context.
068: * @param g the graphics context.
069: */
070: public void paintComponent(Graphics g) {
071: // ANIMATION SUPPORT:
072: // The following code displays a status message until all the
073: // images are loaded. Then it calls displayImage to display the current
074: // image.
075: //----------------------------------------------------------------------
076: bAllLoaded = tracker.checkAll();
077: if (bAllLoaded && (numImages != 0)) {
078: Rectangle r = g.getClipBounds();
079:
080: g.clearRect(r.x, r.y, r.width, r.height);
081: displayImage(g);
082: } else
083: g.drawString("Loading...", 0, 20);
084: }
085:
086: // ANIMATION SUPPORT:
087: // Draws the next image, if all images are currently loaded
088: //--------------------------------------------------------------------------
089: /**
090: * Draws the image at an offset of 0, 0.
091: * @param g the graphics context.
092: */
093: public synchronized void displayImage(Graphics g) {
094: displayImage(g, 0, 0);
095: }
096:
097: /**
098: * Draw the current image at the specified offsets.
099: * @param g the graphics context.
100: * @param xOff the x offset,
101: * @param yOff the y offset.
102: */
103: public synchronized void displayImage(Graphics g, int xOff, int yOff) {
104: bAllLoaded = tracker.checkAll();
105: if (!bAllLoaded || (numImages == 0)
106: || (currentImageIdx >= numImages) || !comp.isShowing()) {
107: g.fillRect(0, 0, 1000, 1000);
108: return;
109: }
110:
111: // Draw Image in center of applet
112: //----------------------------------------------------------------------
113: Image img = (Image) imageVector.elementAt(currentImageIdx);
114: if (img == null) {
115: g.fillRect(0, 0, 1000, 1000);
116: return;
117: }
118:
119: try {
120: Rectangle r = g.getClipBounds();
121: if ((r.width < w) || (r.height < h))
122: g.setClip(r.x, r.y, w, h);
123: if ((w != 0) && (h != 0))
124: g.drawImage(img, x + xOff, y + yOff, w, h, this );
125: else
126: g.drawImage(img, x + xOff, y + yOff, this );
127: } catch (Exception e) {
128: g.fillRect(0, 0, 1000, 1000);
129: }
130: }
131:
132: /**
133: * This method is called when information about an image which was
134: * previously requested using an asynchronous interface becomes
135: * available. Asynchronous interfaces are method calls such as
136: * getWidth(ImageObserver) and drawImage(img, x, y, ImageObserver)
137: * which take an ImageObserver object as an argument. Those methods
138: * register the caller as interested either in information about
139: * the overall image itself (in the case of getWidth(ImageObserver))
140: * or about an output version of an image (in the case of the
141: * drawImage(img, x, y, [w, h,] ImageObserver) call).
142: *
143: * <p>This method
144: * should return true if further updates are needed or false if the
145: * required information has been acquired. The image which was being
146: * tracked is passed in using the img argument. Various constants
147: * are combined to form the infoflags argument which indicates what
148: * information about the image is now available. The interpretation
149: * of the x, y, width, and height arguments depends on the contents
150: * of the infoflags argument.
151: * <p>
152: * The <code>infoflags</code> argument should be the bitwise inclusive
153: * <b>OR</b> of the following flags: <code>WIDTH</code>,
154: * <code>HEIGHT</code>, <code>PROPERTIES</code>, <code>SOMEBITS</code>,
155: * <code>FRAMEBITS</code>, <code>ALLBITS</code>, <code>ERROR</code>,
156: * <code>ABORT</code>.
157: *
158: * @param img the image being observed.
159: * @param infoflags the bitwise inclusive OR of the following
160: * flags: <code>WIDTH</code>, <code>HEIGHT</code>,
161: * <code>PROPERTIES</code>, <code>SOMEBITS</code>,
162: * <code>FRAMEBITS</code>, <code>ALLBITS</code>,
163: * <code>ERROR</code>, <code>ABORT</code>.
164: * @param x the <i>x</i> coordinate.
165: * @param y the <i>y</i> coordinate.
166: * @param width the width.
167: * @param height the height.
168: * @return <code>false</code> if the infoflags indicate that the
169: * image is completely loaded; <code>true</code> otherwise.
170: *
171: * @see #WIDTH
172: * @see #HEIGHT
173: * @see #PROPERTIES
174: * @see #SOMEBITS
175: * @see #FRAMEBITS
176: * @see #ALLBITS
177: * @see #ERROR
178: * @see #ABORT
179: * @see Image#getWidth
180: * @see Image#getHeight
181: * @see java.awt.Graphics#drawImage
182: */
183: public boolean imageUpdate(Image img, int infoflags, int x, int y,
184: int width, int height) {
185: if ((infoflags & ImageObserver.FRAMEBITS) == ImageObserver.FRAMEBITS)
186: comp.repaint();
187:
188: if ((infoflags & ImageObserver.ALLBITS) != 0) {
189: run();
190: return true;
191: }
192:
193: return false;
194: }
195:
196: /**
197: * Functions required for multi-threading. Loads the image(s) and animates if more than
198: * one image is loaded.
199: */
200: public void run() {
201: currentImageIdx = 0;
202:
203: // while (true) {
204: if (loadImages() > 0)
205: ;//bStopped = true;
206:
207: try {
208: // Draw next image in animation
209: //--------------------------------------------------------------
210: if (bAllLoaded) {
211: // if ( !( comp instanceof JForm ))
212: // comp.repaint();
213:
214: if (!bStopped)
215: currentImageIdx++;
216: if (currentImageIdx == numImages)
217: currentImageIdx = 0;
218: }
219:
220: // Thread.sleep( sleepTime );
221: synchronized (this ) {
222: while (bStopped)
223: wait();
224: }
225:
226: bAllLoaded = tracker.checkAll();
227:
228: // I don't really know what happens, but it seems like the
229: // images need some time to decode/decompress. Therefore the
230: // first paint tricks is used. I'm not sure of the best
231: // combination of firstpaint and sleeptime. This is what works
232: // locally !
233: // if (( bStopped || ( bAllLoaded && ( numImages < 2 ))))//( tracker.statusAll( false ) == MediaTracker.COMPLETE ))
234: // sleepTime = 1048576;
235: // else if ( !explicitSleepTime ) {
236: // if ( bAllLoaded )
237: // sleepTime = 2000;
238: // else
239: // yield();
240: // }
241: } catch (InterruptedException e) {
242: // TODO: Place exception-handling code here in case an
243: // InterruptedException is thrown by Thread.sleep(),
244: // meaning that another thread has interrupted this one
245: ;//suspend();
246: }
247: // }
248: }
249:
250: /**
251: * Fetch the images named in the argument. Is restartable.
252: *
253: * @param images a Vector of URLs
254: * @return true if all went well, false otherwise.
255: */
256: boolean fetchImages(Vector images) {
257: if (images == null) {
258: return true;
259: }
260:
261: int size = images.size();
262: int trackerId = 0;//app.getNextTrackerID();
263: for (int i = 0; i < size; i++) {
264: Object o = images.elementAt(i);
265: if (o instanceof String) {
266: String url = (String) o;
267: Image im = currentProject.getImage(url.toString());
268: images.setElementAt(im, i);
269: } else
270: tracker.addImage((Image) o, 0);//trackerId );
271: }
272:
273: try {
274: tracker.waitForAll();//waitForID( trackerId );
275: } catch (InterruptedException e) {
276: }
277: if (tracker.isErrorID(0/*trackerId*/)) {
278: return false;
279: }
280:
281: //if ( comp.getParent() != null )
282: // comp.getParent().repaint();
283: comp.repaint();
284: return true;
285: }
286:
287: /*
288: * Loads the images.
289: */
290: int loadImages() {
291: // If re-entering the page, then the images have already been loaded.
292: // bAllLoaded == TRUE.
293: //----------------------------------------------------------------------
294: if (!bAllLoaded && (numImages != 0)) {
295:
296: // Load in all the images
297: //------------------------------------------------------------------
298: tracker = new MediaTracker(comp);
299:
300: // For each image in the animation, this method first constructs a
301: // string containing the path to the image file; then it begins
302: // loading the image into the imageVector array. Note that the call to
303: // getImage will return before the image is completely loaded.
304: //------------------------------------------------------------------
305: imageVector.removeAllElements();
306: imageVector.setSize(numImages);
307:
308: for (int i = 0; i < numImages; i++) {
309: // Build path to next image
310: //--------------------------------------------------------------
311: String imageName = (String) nameVector.elementAt(i);
312: if (imageName.toLowerCase().indexOf(".jmf") < 0) {
313: Image tempImage = currentProject
314: .getImage(imageName);
315: imageVector.setElementAt(tempImage, i);
316: }
317: }
318:
319: bAllLoaded = fetchImages(imageVector);
320:
321: if (!bAllLoaded)
322: return 1;
323: }
324: return 0;
325: }
326:
327: /**
328: * Adds a picture to the current animation. Fetches the image immediately
329: * @param index the position at which the image is to be inserted
330: * @param fileName the name of the file to display.
331: */
332: public synchronized void add(int index, String fileName) {
333: add(index, fileName, true);
334: }
335:
336: /**
337: * Adds a picture to the current animation.
338: * @param index the position at which the image is to be inserted
339: * @param fileName the name of the file to display.
340: * @param fetchNow get teh image now or wait till more images are added
341: */
342: public synchronized void add(int index, String fileName,
343: boolean fetchNow) {
344: explicitSleepTime = false;
345:
346: String file = new String("bitmaps/");
347: String fn = new String(fileName.toLowerCase());
348: int extIdx = fn.indexOf(".fif");
349: if (extIdx > -1) {
350: fn = fn.substring(0, extIdx);
351: fn += ".jpg";
352: }
353: extIdx = fn.indexOf(".bmp");
354: if (extIdx > -1) {
355: fn = fn.substring(0, extIdx);
356: fn += ".gif";
357: }
358: extIdx = fn.indexOf(".wmf");
359: if (extIdx > -1) {
360: fn = fn.substring(0, extIdx);
361: fn += ".jmf";
362: }
363: file += fn;
364: nameVector.insertElementAt(file, index);
365:
366: numImages++;
367: bAllLoaded = false;
368:
369: if (fetchNow)
370: run();
371: }
372:
373: /**
374: * Sets the image index to the first image.
375: */
376: public synchronized void first() {
377: currentImageIdx = 0;
378: update();
379: }
380:
381: /**
382: * Sets the image index to the prev image.
383: */
384: public synchronized void prev() {
385: currentImageIdx--;
386: currentImageIdx = Math.max(currentImageIdx, 0);
387: update();
388: }
389:
390: /**
391: * Sets the image index to the next image.
392: */
393: public synchronized void next() {
394: currentImageIdx++;
395: currentImageIdx = Math.min(currentImageIdx, numImages - 1);
396: update();
397: }
398:
399: /**
400: * Sets the image index to the last image.
401: */
402: public synchronized void last() {
403: currentImageIdx = numImages - 1;
404: update();
405: }
406:
407: /**
408: * Removes the specified image from the list of images.
409: * The index is forced to be in the range of valid images and
410: * if no images exist then no action is taken.
411: * @param index the image index
412: */
413: public synchronized void remove(int index) {
414: if (numImages < 1)
415: return;
416:
417: nameVector.removeElementAt(index);
418: imageVector.removeElementAt(index);
419:
420: numImages--;
421: currentImageIdx = Math.min(currentImageIdx, numImages);
422:
423: update();
424: }
425:
426: /**
427: * Sets the interval between images in the animation.
428: * @param inc the time in milliseconds between images.
429: */
430: public synchronized void setIncrement(int inc) {
431: setSleepTime(inc);
432: }
433:
434: /**
435: * Gets the interval between images in the animation.
436: * @return the time in milliseconds between images.
437: */
438: public synchronized int getIncrement() {
439: return sleepTime;
440: }
441:
442: /**
443: * Sets the index of the image to display.
444: * The index is forced to be in the range of valid images and
445: * if no images exist then no action is taken.
446: * @param newPos the index of the image.
447: */
448: public synchronized void setIndex(int newPos) {
449: currentImageIdx = Math.max(newPos, 0);
450: currentImageIdx = Math.min(currentImageIdx, numImages);
451:
452: update();
453: }
454:
455: /**
456: * Sets the image at the specified index.
457: * The index is forced to be in the range of valid images and
458: * if no images exist then no action is taken.
459: * @param index the number of the image to be changed
460: * @param fileName the name of the new image.
461: */
462: public synchronized void setValue(int index, String fileName) {
463: explicitSleepTime = false;
464: if (numImages == 0) {
465: add(index, fileName);
466: return;
467: }
468:
469: if ((index >= 0) && (index < numImages)) {
470: bAllLoaded = false;
471: String file = new String("bitmaps/");
472: String fn = new String(fileName.toLowerCase());
473: int extIdx = fn.indexOf(".fif");
474: if (extIdx > -1) {
475: fn = fn.substring(0, extIdx);
476: fn += ".jpg";
477: }
478: extIdx = fn.indexOf(".bmp");
479: if (extIdx > -1) {
480: fn = fn.substring(0, extIdx);
481: fn += ".png";
482: }
483: extIdx = fn.indexOf(".wmf");
484: if (extIdx > -1) {
485: fn = fn.substring(0, extIdx);
486: fn += ".jmf";
487: }
488: file += fn;
489:
490: nameVector.setElementAt(file, index);
491: currentImageIdx = index;
492: }
493:
494: loadImages();
495: }
496:
497: /**
498: * Get the number of images in this animation
499: * @return the number of images
500: */
501: public synchronized int getNumImages() {
502: return numImages;
503: }
504:
505: /**
506: * Get the current image index
507: * @return the current image index
508: */
509: public synchronized int getIndex() {
510: return currentImageIdx;
511: }
512:
513: /**
514: * Starts the animation
515: */
516: public synchronized void startAnimation() {
517: loadImages();
518: bStopped = false;
519: notify();
520: }
521:
522: /**
523: * Stops the animation.
524: */
525: public synchronized void stopAnimation() {
526: bStopped = true;
527: setSleepTime(0);
528: }
529:
530: /**
531: * Causes the animation to repaint itself.
532: */
533: public void update() {
534: comp.repaint();
535: }
536:
537: /**
538: * Update the image/component
539: */
540: public void imageUpdate() {
541: notify();
542: }
543:
544: /**
545: * No need to clear anything; just paint.
546: * @param g the graphics context
547: */
548: public void update(Graphics g) {
549: paintComponent(g);
550: }
551:
552: /**
553: * Removes all images from the current animation.
554: */
555: public synchronized void purge() {
556: imageVector.removeAllElements();
557: nameVector.removeAllElements();
558: numImages = 0;
559: }
560:
561: private Vector imageVector;
562: private Vector nameVector;
563:
564: private int currentImageIdx;
565: private boolean bAllLoaded = false;
566: private boolean bStopped = false;
567: private int numImages = 0;
568:
569: private int x, y;
570: private int w, h;
571: private int sleepTime = 100;
572:
573: private JComponent comp;
574: private MediaTracker tracker;
575: private boolean explicitSleepTime = false;
576: }
|