/************************************************************************************************ Image map with pop-up text displays. ... ... ************************************************************************************************/ import java.awt.*; import java.awt.image.*; import java.net.*; import java.util.*; import java.applet.Applet; /************************************************************************************************ The IMapArea class defines the area shape, position and text for the the pop-up box. ************************************************************************************************/ class IMapArea { // Constants: private final static int RECT = 1; // Shape types. private final static int ELLIPSE = 2; private final static int POLY = 3; // Fields: private int shape; // This area's shape, one of: private Rectangle rect; // rectangle private int x, y, a, b; // ellipse private Polygon poly; // polygon public URL url = null; // URL to link to when clicked. public String target = null; // Target window. public String status = null; // Status window message. private Vector text = new Vector(10, 5); // Text line array. private Vector indent = new Vector(10, 5); // Indent for each text line. private int count = 0; // Number of text lines. public int width = 0; // Width of text box. public int height = 0; // Height of text box. // Constructors: // Create an area of the appropriate shape. public IMapArea(Rectangle rect) { this.shape = RECT; this.rect = rect; } public IMapArea(int x, int y, int a, int b) { this.shape = ELLIPSE; this.x = x; this.y = y; this.a = Math.abs(a); this.b = Math.abs(b); } public IMapArea(Polygon poly) { this.shape = POLY; this.poly = poly; } // Methods: // Determine the area shape. public boolean isRect() { if (this.shape == RECT) return(true); return(false); } public boolean isEllipse() { if (this.shape == ELLIPSE) return(true); return(false); } public boolean isPoly() { if (this.shape == POLY) return(true); return(false); } // Return the area as a shape object with the appropriate coordinates, useful for drawing. public Rectangle getRect() { if (this.isRect()) return(this.rect); return(new Rectangle()); } public Rectangle getEllipse() { if (this.isEllipse()) return(new Rectangle(this.x - this.a, this.y - this.b, this.a * 2, this.b * 2)); return(new Rectangle()); } public Polygon getPoly() { if (this.isPoly()) return(this.poly); return(new Polygon()); } // Find the smallest rectangle that contains the shape. public Rectangle getBoundingBox() { Rectangle rect; rect = new Rectangle(); if (this.isRect()) rect = this.rect; if (this.isEllipse()) rect = this.getEllipse(); if (this.isPoly()) rect = this.poly.getBoundingBox(); return(rect); } // Set the URL to link to. public void setURL(URL url) { this.url = url; } // Set the URL and target frame or window to link to. public void setURL(URL url, String target) { this.url = url; this.target = target; } // Set the message to display in the status window when this link is active. public void setStatusMsg(String msg) { this.status = msg; } // Determine if a given point is inside the area. public boolean inside(int x, int y) { double v; if (this.isRect()) return(this.rect.inside(x, y)); if (this.isEllipse()) { x -= this.x; y -= this.y; if (Math.abs(x) > this.a || Math.abs(y) > b) return(false); v = (int) ((double) this.b * Math.sqrt(1.0 - (double) (x * x) / (double) (this.a * this.a))); if (Math.abs(y) <= v) return(true); } if (this.isPoly()) return(this.poly.inside(x, y)); return(false); } // Add a line of text with the specified indentation. public void addText(int n, String s) { this.indent.addElement(new Integer(n)); this.text.addElement(s); this.count++; } // Get a line of text. public String getText(int i) { if (i >= 0 && i < this.count) return((String) this.text.elementAt(i)); else return((String) null); } // Get amount of indentation for a line of text. public int getIndent(int i) { if (i >= 0 && i < this.count) return(Integer.parseInt(this.indent.elementAt(i).toString())); else return(0); } // Get number of text lines. public int getCount() { return(this.count); } } /************************************************************************************************ Main applet code. ************************************************************************************************/ public class IMap extends Applet { // Parameters and defaults. Image mapImage = null; // Map image. Color pgColor = Color.white; // Applet background color. Color fgColor = Color.black; // Text color. Color bgColor = Color.white; // Text box background color. int bdSize = 0; // Text box border size. Color bdColor; // Text box border color. String fontName = "Dialog"; // Text font. int fontStyle = Font.PLAIN; // Text font style. int fontSize = 12; // Text font size. int hrznMargin = 10; // Horizontal margin for text. int vertMargin = 10; // Verticle margin for text. boolean olFlag = false; // Area outline flag. Color olColor = Color.black; // Area outline color. URL defaultUrl = null; // Default image map link. String target = null; // Target window or frame for the default link. String status = null; // Status window message for the default link. // Global variables. Vector areas = new Vector(10, 5); // A list of all the defined areas. IMapArea area; // The currently active area. int activeArea = -1; Font font; // Font size values. int xAdvance; int yAscent; int yHeight; Dimension offDimension; // Off-screen graphic for drawing. Image offImage; Graphics offGraphics; // Applet information. public String getAppletInfo() { return("IMap version 1.5\n\nCopyright 1997 by Mike Hall"); } public void init() { String s, t, u; StringTokenizer st; URL url; int i, j; Graphics g; FontMetrics fm; int n; // Take credit. System.out.println("IMap version 1.5, Copyright 1997 by Mike Hall."); // Get the map image and optional background color and start loading the image. try { s = getParameter("mapimage"); if (s != null) { st = new StringTokenizer(s, ","); mapImage = getImage(new URL(getDocumentBase(), st.nextToken())); prepareImage(mapImage, this); if (st.hasMoreTokens()) pgColor = getColorParm(st.nextToken()); } } catch (Exception e) {} // Get the pop-up text box colors. try { s = getParameter("fgcolor"); if (s != null) fgColor = getColorParm(s); } catch (Exception e) {} bdColor = fgColor; try { s = getParameter("bgcolor"); if (s != null) bgColor = getColorParm(s); } catch (Exception e) {} // Get the pop-up text box border parameters. try { s = getParameter("border"); if (s != null) { st = new StringTokenizer(s, ","); if ((n = Integer.parseInt(st.nextToken())) > 0) bdSize = n; if (st.hasMoreTokens()) bdColor = getColorParm(st.nextToken()); } } catch (Exception e) {} // Get the text box font. try { s = getParameter("font"); // Font name. st = new StringTokenizer(s, ","); t = st.nextToken(); if (t.equalsIgnoreCase("Courier")) fontName = "Courier"; else if (t.equalsIgnoreCase("Dialog")) fontName = "Dialog"; else if (t.equalsIgnoreCase("Helvetica")) fontName = "Helvetica"; else if (t.equalsIgnoreCase("Symbol")) fontName = "Symbol"; else if (t.equalsIgnoreCase("TimesRoman")) fontName = "TimesRoman"; // Font style. t = st.nextToken(); if (t.equalsIgnoreCase("plain")) fontStyle = Font.PLAIN; else if (t.equalsIgnoreCase("bold")) fontStyle = Font.BOLD; else if (t.equalsIgnoreCase("italic")) fontStyle = Font.ITALIC; else if (t.equalsIgnoreCase("boldItalic")) fontStyle = Font.BOLD + Font.ITALIC; // Font size. t = st.nextToken(); if ((n = Integer.parseInt(t)) > 0) fontSize = n; } catch (Exception e) {} // Get the pop-up text box margins. try { s = getParameter("margins"); if (s != null) { st = new StringTokenizer(s, ","); if ((n = Integer.parseInt(st.nextToken())) > 0) hrznMargin = n; if ((n = Integer.parseInt(st.nextToken())) > 0) vertMargin = n; } } catch (Exception e) {} // Get the area outline color. try { s = getParameter("outline"); if (s != null) { olColor = getColorParm(s); olFlag = true; } } catch (Exception e) {} // Get the default URL, target and status window message for the image map. try { s = getParameter("default"); st = new StringTokenizer(s, ","); t = st.nextToken(); try { url = new URL(getDocumentBase(), t); if (st.hasMoreTokens()) { t = st.nextToken(); target = t; } defaultUrl = url; } catch (MalformedURLException e) {} } catch (Exception e) {} try { s = getParameter("status"); if (s != null) status = s; } catch (Exception e) {} // Get data for each image map area. s = null; i = 1; do { try { s = getParameter("shape-" + i); if (s != null) { getShape(s); // Get the URL, target and status window message. try { t = getParameter("url-" + i); st = new StringTokenizer(t, ","); u = st.nextToken(); try { url = new URL(getDocumentBase(), u); if (st.hasMoreTokens()) { u = st.nextToken(); area.setURL(url, u); } else area.setURL(url); } catch (MalformedURLException e) {} } catch (Exception e) {} try { t = getParameter("status-" + i); if (t != null) area.setStatusMsg(t); } catch (Exception e) {} // Get text to display in the pop-up text box for this area. t = null; j = 1; do { try { t = getParameter("text-" + i + "-" + j); if (t != null) { st = new StringTokenizer(t, "|"); if (st.countTokens() > 1) { n = Integer.parseInt(st.nextToken()); u = st.nextToken(); } else { n = 0; u = t; } area.addText(n, u); } } catch (Exception e) {} j++; } while (t != null); // Add the area to the list. areas.addElement(area); } } catch (Exception e) {} i++; } while (s != null); // Trim the areas list to reclaim unused space. areas.trimToSize(); // Set size values based on the font. g = getGraphics(); font = g.getFont(); g.setFont(font = new Font(fontName, fontStyle, fontSize)); fm = g.getFontMetrics(); xAdvance = fm.getMaxAdvance(); yAscent = fm.getMaxAscent(); yHeight = fm.getHeight(); // Calculate the size of the pop-up text box for each area. for (i = 0; i < areas.size(); i++) { area = (IMapArea) areas.elementAt(i); for (j = 0; j < area.getCount(); j++) { s = area.getText(j); n = area.getIndent(j) * xAdvance + fm.stringWidth(s); if (n > area.width) area.width = n; } area.width += 2 * hrznMargin; area.height = area.getCount() * yHeight + 2 * vertMargin; } } private Color getColorParm(String s) { int r, g, b; // Check if a pre-defined color is specified. if (s.equalsIgnoreCase("black")) return(Color.black); if (s.equalsIgnoreCase("blue")) return(Color.blue); if (s.equalsIgnoreCase("cyan")) return(Color.cyan); if (s.equalsIgnoreCase("darkGray")) return(Color.darkGray); if (s.equalsIgnoreCase("gray")) return(Color.gray); if (s.equalsIgnoreCase("green")) return(Color.green); if (s.equalsIgnoreCase("lightGray")) return(Color.lightGray); if (s.equalsIgnoreCase("magenta")) return(Color.magenta); if (s.equalsIgnoreCase("orange")) return(Color.orange); if (s.equalsIgnoreCase("pink")) return(Color.pink); if (s.equalsIgnoreCase("red")) return(Color.red); if (s.equalsIgnoreCase("white")) return(Color.white); if (s.equalsIgnoreCase("yellow")) return(Color.yellow); // If the color is specified in HTML format, build it from the red, green and blue values. if (s.length() == 7 && s.charAt(0) == '#') { r = Integer.parseInt(s.substring(1,3),16); g = Integer.parseInt(s.substring(3,5),16); b = Integer.parseInt(s.substring(5,7),16); return(new Color(r, g, b)); } // If we can't figure it out, default to black. return(Color.black); } private void getShape(String s) { StringTokenizer st; String t; int x1, y1, x2, y2; int x, y, a, b; Polygon poly; // Set the area depending on the supplied shape parameters. (Note that a circle is a special // case of an ellipse where a = b.) st = new StringTokenizer(s, ","); t = st.nextToken(); if (t.equalsIgnoreCase("rect")) { x1 = Integer.parseInt(st.nextToken()); y1 = Integer.parseInt(st.nextToken()); x2 = Integer.parseInt(st.nextToken()); y2 = Integer.parseInt(st.nextToken()); area = new IMapArea(new Rectangle(x1, y1, x2 - x1, y2 - y1)); return; } if (t.equalsIgnoreCase("circle")) { x = Integer.parseInt(st.nextToken()); y = Integer.parseInt(st.nextToken()); a = Integer.parseInt(st.nextToken()); area = new IMapArea(x, y, a, a); return; } if (t.equalsIgnoreCase("ellipse")) { x = Integer.parseInt(st.nextToken()); y = Integer.parseInt(st.nextToken()); a = Integer.parseInt(st.nextToken()); b = Integer.parseInt(st.nextToken()); area = new IMapArea(x, y, a, b); return; } if (t.equalsIgnoreCase("poly")) { poly = new Polygon(); while (st.hasMoreTokens()) { x = Integer.parseInt(st.nextToken()); y = Integer.parseInt(st.nextToken()); poly.addPoint(x, y); } area = new IMapArea(poly); } } public boolean mouseExit(Event e, int x, int y) { // Deactivate any currently active area and clear the status window. activeArea = -1; getAppletContext().showStatus(""); repaint(); return true; } public boolean mouseMove(Event e, int x, int y) { int last; int i; // Save the currently active area. last = activeArea; // If the mouse is over a area, mark it as active. (Go backwards thru the list to match the // behavior of image maps when areas overlap.) activeArea = -1; for (i = areas.size() - 1; i >= 0; i--) { area = (IMapArea) areas.elementAt(i); if (area.inside(x, y)) activeArea = i; } // Update the display only if the active area has changed (this will save some processor // cycles). if (activeArea != last) { if (activeArea >= 0) { area = (IMapArea) areas.elementAt(activeArea); if (area.status != null) getAppletContext().showStatus(area.status); else if (area.url != null) getAppletContext().showStatus(area.url.toString()); else getAppletContext().showStatus(""); } // here is our little experiment area area = (IMapArea) areas.elementAt(activeArea); if (area.url != null) if (area.target != null) getAppletContext().showDocument(area.url, area.target); else getAppletContext().showDocument(area.url); repaint(); } // When no area is active, show the default status window message. if (activeArea < 0) { if (status != null) getAppletContext().showStatus(status); else if (defaultUrl != null) getAppletContext().showStatus(defaultUrl.toString()); else getAppletContext().showStatus(""); } return true; } public boolean mouseDown(Event e, int x, int y) { // If there is currently an active area with a URL, link to it. if (activeArea >= 0) { area = (IMapArea) areas.elementAt(activeArea); if (area.url != null) if (area.target != null) getAppletContext().showDocument(area.url, area.target); else getAppletContext().showDocument(area.url); } // Otherwise, if a defaut URL was specified, link to it. else if (defaultUrl != null) { if (target != null) getAppletContext().showDocument(defaultUrl, target); else getAppletContext().showDocument(defaultUrl); } // No URL to link to, just return. return true; } public void paint(Graphics g) { update(g); } public void update(Graphics g) { Dimension d = size(); int x, y; int i, j; int n; String s; Rectangle rect; Polygon poly; // Create the offscreen graphics context, if no good one exists. if (offGraphics == null || d.width != offDimension.width || d.height != offDimension.height) { offDimension = d; offImage = createImage(d.width, d.height); offGraphics = offImage.getGraphics(); } // If the image map has finished loading, fill the canvas with the background color and draw // the map image over it. if ((checkImage(mapImage, this) & ImageObserver.ALLBITS) == ImageObserver.ALLBITS) { offGraphics.setColor(pgColor); offGraphics.fillRect(0, 0, d.width, d.height); offGraphics.drawImage(mapImage, 0, 0, this); } // Otherwise, put up a message and return. else { offGraphics.setColor(bgColor); offGraphics.fillRect(0, 0, d.width, d.height); offGraphics.setFont(font); offGraphics.setColor(fgColor); offGraphics.drawString("Loading image...", xAdvance, yHeight + yAscent); g.drawImage(offImage, 0, 0, this); return; } // If there is a currently active area, process it. if (activeArea >= 0) { area = (IMapArea) areas.elementAt(activeArea); rect = area.getBoundingBox(); // Draw the outline of the area if the outline flag is set. if (olFlag) { offGraphics.setColor(olColor); if (area.isRect()) { rect = area.getRect(); offGraphics.drawRect(rect.x, rect.y, rect.width, rect.height); } else if (area.isEllipse()) { rect = area.getEllipse(); offGraphics.drawOval(rect.x, rect.y, rect.width, rect.height); } else if (area.isPoly()) { poly = area.getPoly(); offGraphics.drawPolygon(poly); n = poly.npoints - 1; offGraphics.drawLine(poly.xpoints[n], poly.ypoints[n], poly.xpoints[0], poly.ypoints[0]); } } // Draw the pop-up text box for the active area if there is any text. if (area.getCount() > 0) { // Determine a starting position for the text box. An attempt is made to keep it from // overlaying the area if possible. x = 0; y = 0; // Get the x-coord for the text box. if (d.width - (rect.x + rect.width) > rect.x) x = rect.x + rect.width + xAdvance; else x = rect.x - area.width - xAdvance; if (x + area.width > d.width) x = d.width - area.width; x = Math.max(0, x); // Get the y-coord for the box. if ((x >= rect.x && x <= rect.x + rect.width) || (x + area.width >= rect.x && x + area.width <= rect.x + rect.width)) { if (d.height - (rect.y + rect.height) > rect.y) y = rect.y + rect.height + yHeight; else y = rect.y - area.height - yHeight; } else { if (d.height - (rect.y + rect.height) > rect.y) y = rect.y + rect.height / 2; else y = rect.y + rect.height / 2 - area.height; } if (y + area.height > d.height) y = d.height - area.height; y = Math.max(0, y); // Draw the pop-up box and border. offGraphics.setColor(bdColor); offGraphics.fillRect(x, y, area.width, area.height); offGraphics.setColor(bgColor); offGraphics.fillRect(x + bdSize, y + bdSize, area.width - 2 * bdSize, area.height - 2 * bdSize); // Add the text. offGraphics.setFont(font); offGraphics.setColor(fgColor); for (i = 0; i < area.getCount(); i++) { j = 0; s = area.getText(i); n = area.getIndent(i); offGraphics.drawString(s, x + hrznMargin + xAdvance * n, y + vertMargin + i * yHeight + yAscent); } } } // Paint the image onto the screen. g.drawImage(offImage, 0, 0, this); } }