package com.idega.presentation.awt; import java.awt.*; import java.net.*; // This appears in Core Web Programming from // Prentice Hall Publishers, and may be freely used // or adapted. 1997 Marty Hall, hall@apl.jhu.edu. //====================================================== /** * A class for displaying images. It places the Image * into a canvas so that it can moved around by layout * managers, will get repainted automatically, etc. * No mouseXXX or action events are defined, so it is * most similar to the Label Component. * <P> * By default, with FlowLayout the ImageLabel takes * its minimum size (just enclosing the image). The * default with BorderLayout is to expand to fill * the region in width (North/South), height * (East/West) or both (Center). This is the same * behavior as with the builtin Label class. If you * give an explicit resize or * reshape call <B>before</B> adding the * ImageLabel to the Container, this size will * override the defaults. * <P> * Here is an example of its use: * <P> * <PRE> * public class ShowImages extends Applet { * private ImageLabel image1, image2; * * public void init() { * image1 = new ImageLabel(getCodeBase(), * "some-image.gif"); * image2 = new ImageLabel(getCodeBase(), * "other-image.jpg"); * add(image1); * add(image2); * } * } * </PRE> * * @author Marty Hall (hall@apl.jhu.edu) * @see Icon * @see ImageButton * @version 1.0 (1997) */ public class ImageLabel extends Canvas { //---------------------------------------------------- // Instance variables. // The actual Image drawn on the canvas. private Image image; // A String corresponding to the URL of the image // you will get if you call the constructor with // no arguments. private static String defaultImageString = "http://java.sun.com/lib/images/" + "logo.java.color-transp.55x60.gif"; // The URL of the image. But sometimes we will use // an existing image object (e.g. made by // createImage) for which this info will not be // available, so a default string is used here. private String imageString = "<Existing Image>"; // Turn this on to get verbose debugging messages. private boolean debug = false; /** Amount of extra space around the image. */ private int border = 0; /** If there is a non-zero border, what color should * it be? Default is to use the background color * of the Container. */ private Color borderColor = null; // Width and height of the Canvas. This is the // width/height of the image plus twice the border. private int width, height; /** Determines if it will be sized automatically. * If the user issues a resize() or reshape() * call before adding the label to the Container, * or if the LayoutManager resizes before * drawing (as with BorderLayout), then those sizes * override the default, which is to make the label * the same size as the image it holds (after * reserving space for the border, if any). * This flag notes this, so subclasses that * override ImageLabel need to check this flag, and * if it is true, and they draw modified image, * then they need to draw them based on the width * height variables, not just blindly drawing them * full size. */ private boolean explicitSize = false; private int explicitWidth=0, explicitHeight=0; // The MediaTracker that can tell if image has been // loaded before trying to paint it or resize // based on its size. private MediaTracker tracker; // Used by MediaTracker to be sure image is loaded // before paint & resize, since you can't find out // the size until it is done loading. private static int lastTrackerID=0; private int currentTrackerID; private boolean doneLoading = false; private Container parentContainer; //---------------------------------------------------- /** Create an ImageLabel with the default image. * * @see #getDefaultImageString * @see #setDefaultImageString */ // Remember that the funny "this()" syntax calls // constructor of same class public ImageLabel() { this(defaultImageString); } /** Create an ImageLabel using the image at URL * specified by the string. * * @param imageURLString A String specifying the * URL of the image. */ public ImageLabel(String imageURLString) { this(makeURL(imageURLString)); } /** Create an ImageLabel using the image at URL * specified. * * @param imageURL The URL of the image. */ public ImageLabel(URL imageURL) { this(loadImage(imageURL)); this.imageString = imageURL.toExternalForm(); } /** Create an ImageLabel using the image in the file * in the specified directory. * * @param imageDirectory Directory containing image * @param file Filename of image */ public ImageLabel(URL imageDirectory, String file) { this(makeURL(imageDirectory, file)); this.imageString = file; } /** Create an ImageLabel using the image specified. * The other constructors eventually call this one, * but you may want to call it directly if you * already have an image (e.g. created via * createImage). * * @param image The image */ public ImageLabel(Image image) { this.image = image; this.tracker = new MediaTracker(this); this.currentTrackerID = lastTrackerID++; this.tracker.addImage(image, this.currentTrackerID); } //---------------------------------------------------- /** Makes sure that the Image associated with the * Canvas is done loading before returning, since * loadImage spins off a separate thread to do the * loading. Once you get around to drawing the * image, this will make sure it is loaded, * waiting if not. The user does not need to call * this at all, but if several ImageLabels are used * in the same Container, this can cause * several repeated layouts, so users might want to * explicitly call this themselves before adding * the ImageLabel to the Container. Another * alternative is to start asynchronous loading by * calling prepareImage on the ImageLabel's * image (see getImage). * * @param doLayout Determines if the Container * should be re-laid out after you are finished * waiting. <B>This should be true when called * from user functions</B>, but is set to false * when called from preferredSize to avoid an * infinite loop. This is needed when * using BorderLayout, which calls preferredSize * <B>before</B> calling paint. */ public void waitForImage(boolean doLayout) { if (!this.doneLoading) { debug("[waitForImage] - Resizing and waiting for " + this.imageString); try { this.tracker.waitForID(this.currentTrackerID); } catch (InterruptedException ie) {} catch (Exception e) { System.out.println("Error loading " + this.imageString + ": " + e.getMessage()); e.printStackTrace(); } if (this.tracker.isErrorID(0)) { new Throwable("Error loading image " + this.imageString).printStackTrace(); } this.doneLoading = true; if (this.explicitWidth != 0) { this.width = this.explicitWidth; } else { this.width = this.image.getWidth(this) + 2*this.border; } if (this.explicitHeight != 0) { this.height = this.explicitHeight; } else { this.height = this.image.getHeight(this) + 2*this.border; } resize(this.width, this.height); debug("[waitForImage] - " + this.imageString + " is " + this.width + "x" + this.height + "."); // If no parent, you are OK, since it will have // been resized before being added. But if // parent exists, you have already been added, // and the change in size requires re-layout. if (((this.parentContainer = getParent()) != null) && doLayout) { setBackground(this.parentContainer.getBackground()); // parentContainer.layout(); this.parentContainer.doLayout(); } } } //---------------------------------------------------- /** Moves the image so that it is <I>centered</I> at * the specified location, as opposed to the move * method of Component which places the top left * corner at the specified location. * <P> * <B>Note:</B> The effects of this could be undone * by the LayoutManager of the parent Container, if * it is using one. So this is normally only used * in conjunction with a null LayoutManager. * * @param x The X coord of center of the image * (in parent's coordinate system) * @param y The Y coord of center of the image * (in parent's coordinate system) * @see java.awt.Component#move */ public void centerAt(int x, int y) { debug("Placing center of " + this.imageString + " at (" + x + "," + y + ")"); // move(x - width/2, y - height/2); setLocation(x - this.width/2, y - this.height/2); } //---------------------------------------------------- /** Determines if the x and y <B>(in the ImageLabel's * own coordinate system)</B> is inside the * ImageLabel. Put here because Netscape 2.02 has * a bug in which it doesn't process inside() and * locate() tests correctly. */ public synchronized boolean inside(int x, int y) { return((x >= 0) && (x <= this.width) && (y >= 0) && (y <= this.height)); } //---------------------------------------------------- /** Draws the image. If you override this in a * subclass, be sure to call super.paint. */ public void paint(Graphics g) { if (!this.doneLoading) { waitForImage(true); } else { if (this.explicitSize) { g.drawImage(this.image, this.border, this.border, this.width-2*this.border, this.height-2*this.border, this); } else { g.drawImage(this.image, this.border, this.border, this); } drawRect(g, 0, 0, this.width-1, this.height-1, this.border, this.borderColor); } } //---------------------------------------------------- /** Used by layout managers to calculate the usual * size allocated for the Component. Since some * layout managers (e.g. BorderLayout) may * call this before paint is called, you need to * make sure that the image is done loading, which * will force a resize, which determines the values * returned. */ public Dimension preferredSize() { if (!this.doneLoading) { waitForImage(false); } return(super.preferredSize()); //return(super.getPreferredSize()); // return new Dimension(width,height); } public Dimension getPreferredSize() { return preferredSize(); } public Dimension getMinimumSize() { return minimumSize(); } //---------------------------------------------------- /** Used by layout managers to calculate the smallest * size allocated for the Component. Since some * layout managers (e.g. BorderLayout) may * call this before paint is called, you need to * make sure that the image is done loading, which * will force a resize, which determines the values * returned. */ public Dimension minimumSize() { if (!this.doneLoading) { waitForImage(false); } return(super.minimumSize()); // return new Dimension(width,height); } //---------------------------------------------------- // LayoutManagers (such as BorderLayout) might call // resize or reshape with only 1 dimension of // width/height non-zero. In such a case, you still // want the other dimension to come from the image // itself. /** Resizes the ImageLabel. If you don't resize the * label explicitly, then what happens depends on * the layout manager. With FlowLayout, as with * FlowLayout for Labels, the ImageLabel takes its * minimum size, just enclosing the image. With * BorderLayout, as with BorderLayout for Labels, * the ImageLabel is expanded to fill the * section. Stretching GIF/JPG files does not always * result in clear looking images. <B>So just as * with builtin Labels and Buttons, don't * use FlowLayout if you don't want the Buttons to * get resized.</B> If you don't use any * LayoutManager, then the ImageLabel will also * just fit the image. * <P> * Note that if you resize explicitly, you must do * it <B>before</B> the ImageLabel is added to the * Container. In such a case, the explicit size * overrides the image dimensions. * * @see #reshape */ /* public void resize(int width, int height) { if (!doneLoading) { explicitSize=true; if (width > 0) explicitWidth=width; if (height > 0) explicitHeight=height; } // super.resize(width, height); super.setSize(width, height); }*/ /** Resizes the ImageLabel. If you don't resize the * label explicitly, then what happens depends on * the layout manager. With FlowLayout, as with * FlowLayout for Labels, the ImageLabel takes its * minimum size, just enclosing the image. With * BorderLayout, as with BorderLayout for Labels, * the ImageLabel is expanded to fill the * section. Stretching GIF/JPG files does not always * result in clear looking images. <B>So just as * with builtin Labels and Buttons, don't * use FlowLayout if you don't want the Buttons to * get resized.</B> If you don't use any * LayoutManager, then the ImageLabel will also * just fit the image. * <P> * Note that if you resize explicitly, you must do * it <B>before</B> the ImageLabel is added to the * Container. In such a case, the explicit size * overrides the image dimensions. * * @see #resize */ public void reshape(int x, int y, int width, int height) { if (!this.doneLoading) { this.explicitSize=true; if (width > 0) { this.explicitWidth=width; } if (height > 0) { this.explicitHeight=height; } } super.reshape(x, y, width, height); // super.setBounds(x, y, width, height); } //---------------------------------------------------- // You can't just set the background color to // the borderColor and skip drawing the border, // since it messes up transparent gifs. You // need the background color to be the same as // the container. /** Draws a rectangle with the specified OUTSIDE * left, top, width, and height. * Used to draw the border. */ protected void drawRect(Graphics g, int left, int top, int width, int height, int lineThickness, Color rectangleColor) { g.setColor(rectangleColor); for(int i=0; i<lineThickness; i++) { g.drawRect(left, top, width, height); if (i < lineThickness-1) { // Skip last iteration left = left + 1; top = top + 1; width = width - 2; height = height - 2; } } } //---------------------------------------------------- /** Calls System.out.println if the debug variable * is true; does nothing otherwise. * * @param message The String to be printed. */ protected void debug(String message) { if (this.debug) { System.out.println(message); } } //---------------------------------------------------- // Creates the URL with some error checking. private static URL makeURL(String s) { URL u = null; try { u = new URL(s); } catch (MalformedURLException mue) { System.out.println("Bad URL " + s + ": " + mue); mue.printStackTrace(); } return(u); } private static URL makeURL(URL directory, String file) { URL u = null; try { u = new URL(directory, file); } catch (MalformedURLException mue) { System.out.println("Bad URL " + directory.toExternalForm() + ", " + file + ": " + mue); mue.printStackTrace(); } return(u); } //---------------------------------------------------- // Loads the image. Needs to be static since it is // called by the constructor. private static Image loadImage(URL url) { return(Toolkit.getDefaultToolkit().getImage(url)); } //---------------------------------------------------- /** The Image associated with the ImageLabel. */ public Image getImage() { return(this.image); } //---------------------------------------------------- /** Gets the border width. */ public int getTheBorder() { return(this.border); } /** Sets the border thickness. */ public void setBorder(int border) { this.border = border; } //---------------------------------------------------- /** Gets the border color. */ public Color getBorderColor() { return(this.borderColor); } /** Sets the border color. */ public void setBorderColor(Color borderColor) { this.borderColor = borderColor; } //---------------------------------------------------- // You could just call size().width and size().height, // but since we've overridden resize to record // this, we might as well use it. /** Gets the width (image width plus twice border). */ public int getWidth() { return(this.width); } /** Gets the height (image height plus 2x border). */ public int getHeight() { return(this.height); } //---------------------------------------------------- /** Has the ImageLabel been given an explicit size? * This is used to decide if the image should be * stretched or not. This will be true if you * call resize or reshape on the ImageLabel before * adding it to a Container. It will be false * otherwise. */ protected boolean hasExplicitSize() { return(this.explicitSize); } //---------------------------------------------------- /** Returns the string representing the URL that * will be used if none is supplied in the * constructor. */ public static String getDefaultImageString() { return(defaultImageString); } /** Sets the string representing the URL that * will be used if none is supplied in the * constructor. Note that this is static, * so is shared by all ImageLabels. Using this * might be convenient in testing, but "real" * applications should avoid it. */ public static void setDefaultImageString(String file) { defaultImageString = file; } //---------------------------------------------------- /** Returns the string representing the URL * of image. */ protected String getImageString() { return(this.imageString); } //---------------------------------------------------- /** Is the debugging flag set? */ public boolean isDebugging() { return(this.debug); } /** Set the debugging flag. Verbose messages * will be printed to System.out if this is true. */ public void setIsDebugging(boolean debug) { this.debug = debug; } //---------------------------------------------------- }