home *** CD-ROM | disk | FTP | other *** search
/ Java 1996 August / Java - Summer 1996.iso / windows / doc / tools / packages / betaclasses / imagemap.java < prev    next >
Encoding:
Java Source  |  1996-02-26  |  33.2 KB  |  1,195 lines

  1. /*
  2.  * Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
  3.  *
  4.  * Permission to use, copy, modify, and distribute this software
  5.  * and its documentation for NON-COMMERCIAL purposes and without
  6.  * fee is hereby granted provided that this copyright notice
  7.  * appears in all copies. Please refer to the file "copyright.html"
  8.  * for further important copyright and licensing information.
  9.  *
  10.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  11.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  12.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  13.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  14.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  15.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  16.  */
  17. import java.applet.Applet;
  18. import java.awt.Image;
  19. import java.awt.Graphics;
  20. import java.awt.Rectangle;
  21. import java.util.StringTokenizer;
  22. import java.util.Vector;
  23. import java.util.Hashtable;
  24. import java.net.URL;
  25. import java.awt.image.*;
  26. import java.net.MalformedURLException;
  27.  
  28. /**
  29.  * An extensible ImageMap applet class.
  30.  * The active areas on the image are controlled by ImageArea classes
  31.  * that can be dynamically loaded over the net.
  32.  *
  33.  * @author     Jim Graham
  34.  * @version     1.1, 08 Sep 1995
  35.  */
  36. public class ImageMap extends Applet {
  37.     /**
  38.      * The unhighlighted image being mapped.
  39.      */
  40.     Image baseImage;
  41.  
  42.     /**
  43.      * The list of image area handling objects;
  44.      */
  45.     ImageMapArea areas[];
  46.  
  47.     /**
  48.      * The primary highlight mode to be used.
  49.      */
  50.     static final int BRIGHTER = 0;
  51.     static final int DARKER = 1;
  52.  
  53.     int hlmode = BRIGHTER;
  54.  
  55.     /**
  56.      * The percentage of highlight to apply for the primary highlight mode.
  57.      */
  58.     int hlpercent = 50;
  59.  
  60.     /**
  61.      * Get a rectangular region of the baseImage highlighted according to
  62.      * the primary highlight specification.
  63.      */
  64.     Image getHighlight(int x, int y, int w, int h) {
  65.     return getHighlight(x, y, w, h, hlmode, hlpercent);
  66.     }
  67.  
  68.     /**
  69.      * Get a rectangular region of the baseImage with a specific highlight.
  70.      */
  71.     Image getHighlight(int x, int y, int w, int h, int mode, int percent) {
  72.     return getHighlight(x, y, w, h, new HighlightFilter(mode == BRIGHTER,
  73.                                 percent));
  74.     }
  75.  
  76.     /**
  77.      * Get a rectangular region of the baseImage modified by an image filter.
  78.      */
  79.     Image getHighlight(int x, int y, int w, int h, ImageFilter filter) {
  80.     Image cropped = makeImage(baseImage, new CropImageFilter(x, y, w, h));
  81.     return makeImage(cropped, filter);
  82.     }
  83.  
  84.     /**
  85.      * Make the primary highlighted version of the baseImage.
  86.      */
  87.     Image makeImage(Image orig, ImageFilter filter) {
  88.     return createImage(new FilteredImageSource(orig.getSource(), filter));
  89.     }
  90.  
  91.     /**
  92.      * Parse a string representing the desired highlight to be applied.
  93.      */
  94.     void parseHighlight(String s) {
  95.     if (s == null) {
  96.         return;
  97.     }
  98.     if (s.startsWith("brighter")) {
  99.         hlmode = BRIGHTER;
  100.         if (s.length() > "brighter".length()) {
  101.         hlpercent = Integer.parseInt(s.substring("brighter".length()));
  102.         }
  103.     } else if (s.startsWith("darker")) {
  104.         hlmode = DARKER;
  105.         if (s.length() > "darker".length()) {
  106.         hlpercent = Integer.parseInt(s.substring("darker".length()));
  107.         }
  108.     }
  109.     }
  110.  
  111.     /**
  112.      * Initialize the applet. Get attributes.
  113.      *
  114.      * Initialize the ImageAreas.
  115.      * Each ImageArea is a subclass of the class ImageArea, and is
  116.      * specified with an attribute of the form:
  117.      *         areaN=ImageAreaClassName,arguments...
  118.      * The ImageAreaClassName is parsed off and a new instance of that
  119.      * class is created.  The initializer for that class is passed a
  120.      * reference to the applet and the remainder of the attribute
  121.      * string, from which the class should retrieve any information it
  122.      * needs about the area it controls and the actions it needs to
  123.      * take within that area.
  124.      */
  125.     public void init() {
  126.     String s;
  127.  
  128.     parseHighlight(getParameter("highlight"));
  129.     baseImage = getImage(getDocumentBase(), getParameter("img"));
  130.     Vector areaVec = new Vector();
  131.     int num = 1;
  132.     while (true) {
  133.         ImageMapArea newArea;
  134.         s = getParameter("area"+num);
  135.         if (s == null) {
  136.         // Try rect for backwards compatibility.
  137.         s = getParameter("rect"+num);
  138.         if (s == null) {
  139.             break;
  140.         }
  141.         String url = getParameter("href"+num);
  142.         if (url != null)
  143.             s += "," + url;
  144.         newArea = new HrefArea();
  145.         } else {
  146.         int classend = s.indexOf(",");
  147.         try {
  148.             String name = s.substring(0, classend);
  149.             newArea = (ImageMapArea) Class.forName(name).newInstance();
  150.         } catch (Exception e) {
  151.             e.printStackTrace();
  152.             break;
  153.         }
  154.         s = s.substring(classend+1);
  155.         }
  156.         newArea.init(this, s);
  157.         areaVec.addElement(newArea);
  158.         num++;
  159.     }
  160.     areas = new ImageMapArea[areaVec.size()];
  161.     areaVec.copyInto(areas);
  162.     checkSize();
  163.     }
  164.  
  165.     /**
  166.      * Check the size of this applet while the image is being loaded.
  167.      */
  168.     synchronized void checkSize() {
  169.     int w = baseImage.getWidth(this);
  170.     int h = baseImage.getHeight(this);
  171.     if (w > 0 && h > 0) {
  172.         resize(w, h);
  173.         repaintrect.x = repaintrect.y = 0;
  174.         repaintrect.width = w;
  175.         repaintrect.height = h;
  176.         fullrepaint = true;
  177.         repaint();
  178.     }
  179.     }
  180.  
  181.     private boolean fullrepaint = false;
  182.     private Rectangle repaintrect = new Rectangle();
  183.     private long lastupdate = 0;
  184.     private final static long UPDATERATE = 100;
  185.  
  186.     /**
  187.      * Handle updates from images being loaded.
  188.      */
  189.     public boolean imageUpdate(Image img, int infoflags,
  190.                    int x, int y, int width, int height) {
  191.     if ((infoflags & (WIDTH | HEIGHT)) != 0) {
  192.         checkSize();
  193.     }
  194.     if ((infoflags & (SOMEBITS | FRAMEBITS | ALLBITS)) != 0) {
  195.         repaint(((infoflags & (FRAMEBITS | ALLBITS)) != 0)
  196.             ? 0 : UPDATERATE,
  197.             x, y, width, height);
  198.     }
  199.     return (infoflags & (ALLBITS | ERROR)) == 0;
  200.     }
  201.  
  202.     /**
  203.      * Paint the image and all active highlights.
  204.      */
  205.     public void paint(Graphics g) {
  206.     synchronized(this) {
  207.         if (fullrepaint) {
  208.         g = g.create();
  209.         g.clipRect(repaintrect.x, repaintrect.y,
  210.                repaintrect.width, repaintrect.height);
  211.         fullrepaint = false;
  212.         }
  213.     }
  214.     if (baseImage == null) {
  215.         return;
  216.     }
  217.     g.drawImage(baseImage, 0, 0, this);
  218.     if (areas != null) {
  219.         for (int i = areas.length; --i >= 0; ) {
  220.         if (areas[i].active || areas[i].entered) {
  221.             areas[i].setState(g, areas[i].entered);
  222.         }
  223.         }
  224.     }
  225.     }
  226.  
  227.     /**
  228.      * Update the active highlights on the image.
  229.      */
  230.     public void update(Graphics g) {
  231.     if (fullrepaint) {
  232.         paint(g);
  233.         return;
  234.     }
  235.     if (baseImage == null) {
  236.         return;
  237.     }
  238.     g.drawImage(baseImage, 0, 0, this);
  239.     if (areas == null) {
  240.         return;
  241.     }
  242.     // First unhighlight all of the deactivated areas
  243.     for (int i = areas.length; --i >= 0; ) {
  244.         if (areas[i].active && !areas[i].entered) {
  245.         areas[i].setState(g, false);
  246.         }
  247.     }
  248.     // Then highlight all of the activated areas
  249.     for (int i = areas.length; --i >= 0; ) {
  250.         if (areas[i].entered) {
  251.         areas[i].setState(g, true);
  252.         }
  253.     }
  254.     }
  255.  
  256.     /**
  257.      * Make sure that no ImageAreas are highlighted.
  258.      */
  259.     public void mouseExit() {
  260.     boolean changed = false;
  261.  
  262.     for (int i = 0; i < areas.length; i++) {
  263.         if (areas[i].active) {
  264.         areas[i].entered = false;
  265.         changed = true;
  266.         }
  267.     }
  268.     if (changed) {
  269.         repaint();
  270.     }
  271.     }
  272.  
  273.     /**
  274.      * Find the ImageAreas that the mouse is in.
  275.      */
  276.     public boolean mouseMove(java.awt.Event evt, int x, int y) {
  277.     boolean changed = false;
  278.     boolean propagate = true;
  279.  
  280.     for (int i = 0; i < areas.length; i++) {
  281.         if (areas[i].inside(x, y)) {
  282.         areas[i].entered = propagate;
  283.         if (areas[i].terminal) {
  284.             propagate = false;
  285.         }
  286.         } else {
  287.         areas[i].entered = false;
  288.         }
  289.  
  290.         if (areas[i].active != areas[i].entered) {
  291.         changed = true;
  292.         }
  293.     }
  294.  
  295.     if (changed) {
  296.         repaint();
  297.     }
  298.  
  299.     return true;
  300.     }
  301.  
  302.     int pressX;
  303.     int pressY;
  304.  
  305.     /**
  306.      * Inform all active ImageAreas of a mouse press.
  307.      */
  308.     public boolean mouseDown(java.awt.Event evt, int x, int y) {
  309.     pressX = x;
  310.     pressY = y;
  311.  
  312.     for (int i = 0; i < areas.length; i++) {
  313.         if (areas[i].inside(x, y)) {
  314.         areas[i].press(x, y);
  315.         if (areas[i].terminal) {
  316.             break;
  317.         }
  318.         }
  319.     }
  320.  
  321.     return true;
  322.     }
  323.  
  324.     /**
  325.      * Inform all active ImageAreas of a mouse release.
  326.      * Only those areas that were inside the original mouseDown()
  327.      * are informed of the mouseUp.
  328.      */
  329.     public boolean mouseUp(java.awt.Event evt, int x, int y) {
  330.     for (int i = 0; i < areas.length; i++) {
  331.         if (areas[i].inside(pressX, pressY)) {
  332.         areas[i].lift(x, y);
  333.         if (areas[i].terminal) {
  334.             break;
  335.         }
  336.         }
  337.     }
  338.  
  339.     return true;
  340.     }
  341.  
  342.     /**
  343.      * Inform all active ImageAreas of a mouse drag.
  344.      * Only those areas that were inside the original mouseDown()
  345.      * are informed of the mouseUp.
  346.      */
  347.     public boolean mouseDrag(java.awt.Event evt, int x, int y) {
  348.     mouseMove(evt, x, y);
  349.     for (int i = 0; i < areas.length; i++) {
  350.         if (areas[i].inside(pressX, pressY)) {
  351.         areas[i].drag(x, y);
  352.         if (areas[i].terminal) {
  353.             break;
  354.         }
  355.         }
  356.     }
  357.  
  358.     return true;
  359.     }
  360. }
  361.  
  362. /**
  363.  * The base ImageArea class.
  364.  * This class performs the basic functions that most ImageArea
  365.  * classes will need and delegates specific actions to the subclasses.
  366.  *
  367.  * @author     Jim Graham
  368.  * @version     1.1, 08 Sep 1995
  369.  */
  370. class ImageMapArea implements ImageObserver {
  371.     /** The applet parent that contains this ImageArea. */
  372.     ImageMap parent;
  373.     /** The X location of the area (if rectangular). */
  374.     int X;
  375.     /** The Y location of the area (if rectangular). */
  376.     int Y;
  377.     /** The size().width of the area (if rectangular). */
  378.     int W;
  379.     /** The size().height of the area (if rectangular). */
  380.     int H;
  381.     /**
  382.      * This flag indicates whether the user was in this area during the
  383.      * last scan of mouse locations.
  384.      */
  385.     boolean entered = false;
  386.     /** This flag indicates whether the area is currently highlighted. */
  387.     boolean active = false;
  388.     /**
  389.      * This flag indicates whether the area is terminal.  Terminal areas
  390.      * prevent any areas which are under them from being activated when
  391.      * the mouse is inside them.  Some areas may wish to change this to
  392.      * false so that they can augment other areas that they are on top of.
  393.      */
  394.     boolean terminal = true;
  395.     /**
  396.      * This is the default highlight image if no special effects are
  397.      * needed to draw the highlighted image.  It is created by the
  398.      * default "makeImages()" method.
  399.      */
  400.     Image hlImage;
  401.  
  402.     /**
  403.      * Initialize this ImageArea as called from the applet.
  404.      * If the subclass does not override this initializer, then it
  405.      * will perform the basic functions of setting the parent applet
  406.      * and parsing out 4 numbers from the argument string which specify
  407.      * a rectangular region for the ImageArea to act on.
  408.      * The remainder of the argument string is passed to the handleArg()
  409.      * method for more specific handling by the subclass.
  410.      */
  411.     public void init(ImageMap parent, String args) {
  412.     this.parent = parent;
  413.     StringTokenizer st = new StringTokenizer(args, ", ");
  414.     X = Integer.parseInt(st.nextToken());
  415.     Y = Integer.parseInt(st.nextToken());
  416.     W = Integer.parseInt(st.nextToken());
  417.     H = Integer.parseInt(st.nextToken());
  418.     if (st.hasMoreTokens()) {
  419.         // hasMoreTokens() Skips the trailing comma
  420.         handleArg(st.nextToken(""));
  421.     } else {
  422.         handleArg(null);
  423.     }
  424.     makeImages();
  425.     }
  426.  
  427.     /**
  428.      * This method handles the remainder of the argument string after
  429.      * the standard initializer has parsed off the 4 rectangular
  430.      * parameters.  If the subclass does not override this method,
  431.      * the remainder will be ignored.
  432.      */
  433.     public void handleArg(String s) {
  434.     }
  435.  
  436.     /**
  437.      * This method sets the image to be used to render the ImageArea
  438.      * when it is highlighted.
  439.      */
  440.     public void setHighlight(Image img) {
  441.     hlImage = img;
  442.     }
  443.  
  444.     /**
  445.      * This method handles the construction of the various images
  446.      * used to highlight this particular ImageArea when the user
  447.      * interacts with it.
  448.      */
  449.     public void makeImages() {
  450.     setHighlight(parent.getHighlight(X, Y, W, H));
  451.     }
  452.  
  453.     /**
  454.      * This method tests to see if a point is inside this ImageArea.
  455.      * The standard method assumes a rectangular area as parsed by
  456.      * the standard initializer.  If a more complex area is required
  457.      * then this method will have to be overridden by the subclass.
  458.      */
  459.     public boolean inside(int x, int y) {
  460.     return (x >= X && x < (X + W) && y >= Y && y < (Y + H));
  461.     }
  462.  
  463.     /**
  464.      * This utility method draws a rectangular subset of a highlight
  465.      * image.
  466.      */
  467.     public void drawImage(Graphics g, Image img, int imgx, int imgy,
  468.               int x, int y, int w, int h) {
  469.     Graphics ng = g.create();
  470.     ng.clipRect(x, y, w, h);
  471.     ng.drawImage(img, imgx, imgy, this);
  472.     }
  473.  
  474.     /**
  475.      * This method handles the updates from drawing the images.
  476.      */
  477.     public boolean imageUpdate(Image img, int infoflags,
  478.                    int x, int y, int width, int height) {
  479.     if (img == hlImage) {
  480.         return parent.imageUpdate(img, infoflags, x + X, y + Y,
  481.                       width, height);
  482.     } else {
  483.         return false;
  484.     }
  485.     }
  486.  
  487.     /**
  488.      * This utility method shows a string in the status bar.
  489.      */
  490.     public void showStatus(String msg) {
  491.     parent.getAppletContext().showStatus(msg);
  492.     }
  493.  
  494.     /**
  495.      * This utility method tells the browser to visit a URL.
  496.      */
  497.     public void showDocument(URL u) {
  498.     parent.getAppletContext().showDocument(u);
  499.     }
  500.  
  501.     /**
  502.      * This method highlights the specified area when the user enters
  503.      * it with his mouse.  The standard highlight method is to replace
  504.      * the indicated rectangular area of the image with the primary
  505.      * highlighted image.
  506.      */
  507.     public void highlight(Graphics g, boolean on) {
  508.     if (on) {
  509.         g.drawImage(hlImage, X, Y, this);
  510.     } else {
  511.         drawImage(g, parent.baseImage, 0, 0, X, Y, W, H);
  512.     }
  513.     }
  514.  
  515.     /**
  516.      * This method changes the active state of the ImageArea, which
  517.      * indicates whether the user is currently "inside" this area.
  518.      * It turns around and calls the highlight method which is likely
  519.      * to have been overridden by subclasses seeking a custom highlight.
  520.      */
  521.     public void setState(Graphics g, boolean on) {
  522.     highlight(g, on);
  523.     active = on;
  524.     }
  525.  
  526.     /**
  527.      * The press method is called when the user presses the mouse
  528.      * button inside the ImageArea.  The location is supplied, but
  529.      * the standard implementation is to call the overloaded method
  530.      * with no arguments.
  531.      */
  532.     public void press(int x, int y) {
  533.     press();
  534.     }
  535.  
  536.     /**
  537.      * The overloaded press method is called when the user presses the
  538.      * mouse button inside the ImageArea.  This method can be overridden
  539.      * if the ImageArea does not need to know the location of the press.
  540.      */
  541.     public void press() {
  542.     }
  543.  
  544.     /**
  545.      * The lift method is called when the user releases the mouse button.
  546.      * The location is supplied, but the standard implementation is to
  547.      * call the overloaded method with no arguments.  Only those ImageAreas
  548.      * that were informed of a press will be informed of the corresponding
  549.      * release.
  550.      */
  551.     public void lift(int x, int y) {
  552.     lift();
  553.     }
  554.  
  555.     /**
  556.      * The overloaded lift method is called when the user releases the
  557.      * mouse button.  This method can be overridden if the ImageArea
  558.      * does not need to know the location of the release.
  559.      */
  560.     public void lift() {
  561.     }
  562.  
  563.     /**
  564.      * The drag method is called when the user moves the mouse while
  565.      * the button is pressed.  Only those ImageAreas that were informed
  566.      * of a press will be informed of the corresponding mouse movements.
  567.      */
  568.     public void drag(int x, int y) {
  569.     }
  570. }
  571.  
  572. /**
  573.  * The classic "Fetch a URL" ImageArea class.
  574.  * This class extends the basic ImageArea Class to fetch a URL when
  575.  * the user clicks in the area.
  576.  *
  577.  * @author     Jim Graham
  578.  * @version     1.1, 08 Sep 1995
  579.  */
  580. class HrefArea extends ImageMapArea {
  581.     /** The URL to be fetched when the user clicks on this area. */
  582.     URL anchor;
  583.  
  584.     /**
  585.      * The argument string is the URL to be fetched.
  586.      */
  587.     public void handleArg(String arg) {
  588.     try {
  589.         anchor = new URL(parent.getDocumentBase(), arg);
  590.     } catch (MalformedURLException e) {
  591.         anchor = null;
  592.     }
  593.     }
  594.  
  595.     /**
  596.      * The status message area is updated to show the destination URL.
  597.      * The default graphics highlight feedback is used.
  598.      */
  599.     public void highlight(Graphics g, boolean on) {
  600.     super.highlight(g, on);
  601.     showStatus((on && anchor != null)
  602.            ? "Go To " + anchor.toExternalForm()
  603.            : null);
  604.     }
  605.  
  606.     /**
  607.      * The new URL is fetched when the user releases the mouse button
  608.      * only if they are still in the area.
  609.      */
  610.     public void lift(int x, int y) {
  611.     if (inside(x, y) && anchor != null) {
  612.         showDocument(anchor);
  613.     }
  614.     // Note that we should not be active, so no repaint is necessary.
  615.     }
  616. }
  617.  
  618. /**
  619.  * An audio feedback ImageArea class.
  620.  * This class extends the basic ImageArea Class to play a sound each
  621.  * time the user enters the area.
  622.  *
  623.  * @author     Jim Graham
  624.  * @version     1.1, 08 Sep 1995
  625.  */
  626. class SoundArea extends ImageMapArea {
  627.     /** The URL of the sound to be played. */
  628.     String sound;
  629.  
  630.     /**
  631.      * The argument is the URL of the sound to be played.
  632.      * This method also sets this type of area to be non-terminal.
  633.      */
  634.     public void handleArg(String arg) {
  635.     sound = arg;
  636.     terminal = false;
  637.     }
  638.  
  639.     /**
  640.      * The highlight method plays the sound in addition to the usual
  641.      * graphical highlight feedback.
  642.      */
  643.     public void highlight(Graphics g, boolean on) {
  644.     if (on && !active) {
  645.         parent.play(parent.getDocumentBase(), sound);
  646.     }
  647.     super.highlight(g, on);
  648.     }
  649. }
  650.  
  651. /**
  652.  * An click feedback ImageArea class.
  653.  * This class extends the basic ImageArea Class to show the locations
  654.  * of clicks in the image in the status message area.  This utility
  655.  * ImageArea class is useful when setting up ImageMaps.
  656.  *
  657.  * @author     Jim Graham
  658.  * @version     1.1, 08 Sep 1995
  659.  */
  660. class ClickArea extends ImageMapArea {
  661.     /** The X location of the last mouse press. */
  662.     int startx;
  663.     /** The Y location of the last mouse press. */
  664.     int starty;
  665.  
  666.     /**
  667.      * The argument is ignored, but we use this method to set this type
  668.      * of area to be non-terminal.
  669.      */
  670.     public void handleArg(String arg) {
  671.     terminal = false;
  672.     }
  673.  
  674.     /** This class overrides the highlight method to prevent highlighting. */
  675.     public void highlight(Graphics g, boolean on) {
  676.     }
  677.  
  678.     String ptstr(int x, int y) {
  679.     return "("+x+", "+y+")";
  680.     }
  681.  
  682.     /**
  683.      * When the user presses the mouse button, start showing coordinate
  684.      * feedback in the status message line.
  685.      */
  686.     public void press(int x, int y) {
  687.     showStatus("Clicked at "+ptstr(x, y));
  688.     startx = x;
  689.     starty = y;
  690.     }
  691.  
  692.     /**
  693.      * Update the coordinate feedback every time the user moves the mouse
  694.      * while he has the button pressed.
  695.      */
  696.     public void drag(int x, int y) {
  697.     showStatus("Rectangle from "+ptstr(startx, starty)
  698.            +" to "+ptstr(x, y)
  699.            +" is "+(x-startx)+"x"+(y-starty));
  700.     }
  701.  
  702.     /**
  703.      * Update the coordinate feedback one last time when the user releases
  704.      * the mouse button.
  705.      */
  706.     public void lift(int x, int y) {
  707.     drag(x, y);
  708.     }
  709. }
  710.  
  711. /**
  712.  * A message feedback ImageArea class.
  713.  * This class extends the basic ImageArea Class to show the a given
  714.  * message in the status message area when the user enters this area.
  715.  *
  716.  * @author     Jim Graham
  717.  * @version     1.1, 08 Sep 1995
  718.  */
  719. class NameArea extends ImageMapArea {
  720.     /** The string to be shown in the status message area. */
  721.     String name;
  722.  
  723.     /**
  724.      * The argument is the string to be displayed in the status message
  725.      * area.  This method also sets this type of area to be non-terminal.
  726.      */
  727.     public void handleArg(String arg) {
  728.     name = arg;
  729.     terminal = false;
  730.     }
  731.  
  732.     /**
  733.      * The highlight method displays the message in addition to the usual
  734.      * graphical highlight feedback.
  735.      */
  736.     public void highlight(Graphics g, boolean on) {
  737.     super.highlight(g, on);
  738.     showStatus(on ? name : null);
  739.     }
  740.  
  741. }
  742.  
  743. /**
  744.  * An improved "Fetch a URL" ImageArea class.
  745.  * This class extends the basic ImageArea Class to fetch a URL when
  746.  * the user clicks in the area.  In addition, special custom highlights
  747.  * are used to make the area look and feel like a 3-D button.
  748.  *
  749.  * @author     Jim Graham
  750.  * @version     1.1, 08 Sep 1995
  751.  */
  752. class HrefButtonArea extends ImageMapArea {
  753.     /** The URL to be fetched when the user clicks on this area. */
  754.     URL anchor;
  755.     /** The highlight image for when the button is "UP". */
  756.     Image upImage;
  757.     /** The highlight image for when the button is "DOWN". */
  758.     Image downImage;
  759.     /** This flag indicates if the "button" is currently pressed. */
  760.     boolean pressed = false;
  761.     /** The border size for the 3-D effect. */
  762.     int border = 5;
  763.  
  764.     /**
  765.      * The argument string is the URL to be fetched.
  766.      * This method also constructs the various highlight images needed
  767.      * to achieve the 3-D effect.
  768.      */
  769.     public void handleArg(String arg) {
  770.     try {
  771.         anchor = new URL(parent.getDocumentBase(), arg);
  772.     } catch (MalformedURLException e) {
  773.         anchor = null;
  774.     }
  775.     if (border * 2 > W || border * 2 > H) {
  776.         border = Math.min(W, H) / 2;
  777.     }
  778.     }
  779.  
  780.     public void makeImages() {
  781.     upImage = parent.getHighlight(X, Y, W, H,
  782.                       new ButtonFilter(false,
  783.                                parent.hlpercent,
  784.                                border, W, H));
  785.     downImage = parent.getHighlight(X, Y, W, H,
  786.                     new ButtonFilter(true,
  787.                              parent.hlpercent,
  788.                              border, W, H));
  789.     }
  790.  
  791.     public boolean imageUpdate(Image img, int infoflags,
  792.                    int x, int y, int width, int height) {
  793.     if (img == (pressed ? downImage : upImage)) {
  794.         return parent.imageUpdate(img, infoflags, x + X, y + Y,
  795.                       width, height);
  796.     } else {
  797.         return (img == downImage || img == upImage);
  798.     }
  799.     }
  800.  
  801.     /**
  802.      * The status message area is updated to show the destination URL.
  803.      * The graphical highlight is achieved using the ButtonFilter.
  804.      */
  805.     public void highlight(Graphics g, boolean on) {
  806.     if (on) {
  807.         setHighlight(pressed ? downImage : upImage);
  808.     }
  809.     super.highlight(g, on);
  810.     showStatus((on && anchor != null)
  811.            ? "Go To " + anchor.toExternalForm()
  812.            : null);
  813.     }
  814.  
  815.     /**
  816.      * Since the highlight changes when the button is pressed, we need
  817.      * to record the "pressed" state and induce a repaint.
  818.      */
  819.     public void press() {
  820.     parent.repaint();
  821.     pressed = true;
  822.     }
  823.  
  824.     /**
  825.      * The new URL is fetched when the user releases the mouse button
  826.      * only if they are still in the area.
  827.      */
  828.     public void lift(int x, int y) {
  829.     pressed = false;
  830.     parent.repaint();
  831.     if (inside(x, y) && anchor != null) {
  832.         showDocument(anchor);
  833.     }
  834.     }
  835. }
  836.  
  837. /**
  838.  * An improved, round, "Fetch a URL" ImageArea class.
  839.  * This class extends the HrefButtonArea Class to make the 3D button
  840.  * a rounded ellipse.  All of the same feedback and operational
  841.  * charactistics as the HrefButtonArea apply.
  842.  *
  843.  * @author     Jim Graham
  844.  * @version     1.1, 08 Sep 1995
  845.  */
  846. class RoundHrefButtonArea extends HrefButtonArea {
  847.     public void makeImages() {
  848.     upImage = parent.getHighlight(X, Y, W, H,
  849.                       new RoundButtonFilter(false,
  850.                                 parent.hlpercent,
  851.                                 border, W, H));
  852.     downImage = parent.getHighlight(X, Y, W, H,
  853.                     new RoundButtonFilter(true,
  854.                                   parent.hlpercent,
  855.                                   border, W, H));
  856.     }
  857. }
  858.  
  859. class HighlightFilter extends RGBImageFilter {
  860.     boolean brighter;
  861.     int percent;
  862.  
  863.     public HighlightFilter(boolean b, int p) {
  864.     brighter = b;
  865.     percent = p;
  866.     canFilterIndexColorModel = true;
  867.     }
  868.  
  869.     public int filterRGB(int x, int y, int rgb) {
  870.     int r = (rgb >> 16) & 0xff;
  871.     int g = (rgb >> 8) & 0xff;
  872.     int b = (rgb >> 0) & 0xff;
  873.     if (brighter) {
  874.         r = (255 - ((255 - r) * (100 - percent) / 100));
  875.         g = (255 - ((255 - g) * (100 - percent) / 100));
  876.         b = (255 - ((255 - b) * (100 - percent) / 100));
  877.     } else {
  878.         r = (r * (100 - percent) / 100);
  879.         g = (g * (100 - percent) / 100);
  880.         b = (b * (100 - percent) / 100);
  881.     }
  882.     if (r < 0) r = 0;
  883.     if (r > 255) r = 255;
  884.     if (g < 0) g = 0;
  885.     if (g > 255) g = 255;
  886.     if (b < 0) b = 0;
  887.     if (b > 255) b = 255;
  888.     return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0);
  889.     }
  890. }
  891.  
  892. class ButtonFilter extends RGBImageFilter {
  893.     boolean pressed;
  894.     int defpercent;
  895.     int border;
  896.     int width;
  897.     int height;
  898.  
  899.     ColorModel models[] = new ColorModel[7];
  900.     ColorModel origbuttonmodel;
  901.  
  902.     public ButtonFilter(boolean press, int p, int b, int w, int h) {
  903.     pressed = press;
  904.     defpercent = p;
  905.     border = b;
  906.     width = w;
  907.     height = h;
  908.     }
  909.  
  910.     public void setHints(int hints) {
  911.     super.setHints(hints & (~ImageConsumer.COMPLETESCANLINES));
  912.     }
  913.  
  914.     public void setColorModel(ColorModel model) {
  915.     if (model instanceof IndexColorModel && true) {
  916.         IndexColorModel icm = (IndexColorModel) model;
  917.         models[0] = filterIndexColorModel(icm, false, false, 0);
  918.         models[1] = filterIndexColorModel(icm, true, !pressed, defpercent);
  919.         models[2] = null;
  920.         if (pressed) {
  921.         models[3] = filterIndexColorModel(icm, true, false,
  922.                           defpercent/2);
  923.         } else {
  924.         models[3] = models[0];
  925.         }
  926.         models[4] = null;
  927.         models[5] = filterIndexColorModel(icm, true, pressed, defpercent);
  928.         models[6] = models[0];
  929.         origbuttonmodel = model;
  930.         consumer.setColorModel(models[3]);
  931.     } else {
  932.         super.setColorModel(model);
  933.     }
  934.     }
  935.  
  936.     public IndexColorModel filterIndexColorModel(IndexColorModel icm,
  937.                          boolean opaque,
  938.                          boolean brighter,
  939.                          int percent) {
  940.     byte r[] = new byte[256];
  941.     byte g[] = new byte[256];
  942.     byte b[] = new byte[256];
  943.     byte a[] = new byte[256];
  944.     int mapsize = icm.getMapSize();
  945.     icm.getReds(r);
  946.     icm.getGreens(g);
  947.     icm.getBlues(b);
  948.     if (opaque) {
  949.         icm.getAlphas(a);
  950.         for (int i = 0; i < mapsize; i++) {
  951.         int rgb = filterRGB(icm.getRGB(i), brighter, percent);
  952.         a[i] = (byte) (rgb >> 24);
  953.         r[i] = (byte) (rgb >> 16);
  954.         g[i] = (byte) (rgb >> 8);
  955.         b[i] = (byte) (rgb >> 0);
  956.         }
  957.     }
  958.     return new IndexColorModel(icm.getPixelSize(), mapsize, r, g, b, a);
  959.     }
  960.  
  961.     /**
  962.      * Define the ranges of varying highlight for the button.
  963.      * ranges is an array of 8 values which split up a scanline into
  964.      * 7 different regions of highlighting effect:
  965.      *
  966.      * ranges[0-1] = area outside of left edge of button
  967.      * ranges[1-2] = area inside UpperLeft highlight region left of center
  968.      * ranges[2-3] = area requiring custom highlighting left of center
  969.      * ranges[3-4] = area inside center of button
  970.      * ranges[4-5] = area requiring custom highlighting right of center
  971.      * ranges[5-6] = area inside LowerRight highlight region right of center
  972.      * ranges[6-7] = area outside of right edge of button
  973.      *
  974.      * Note that ranges[0-1] and ranges[6-7] are empty where the edges of
  975.      * the button touch the left and right edges of the image (everywhere
  976.      * on a square button) and ranges[2-3] and ranges[4-5] are only nonempty
  977.      * in those regions where the UpperLeft highlighting has leaked over
  978.      * the "top" of the button onto parts of its right edge or where the
  979.      * LowerRight highlighting has leaked under the "bottom" of the button
  980.      * onto parts of its left edge (can't happen on square buttons, happens
  981.      * occasionally on round buttons).
  982.      */
  983.     public void buttonRanges(int y, int ranges[]) {
  984.     ranges[0] = ranges[1] = 0;
  985.     if (y < border) {
  986.         ranges[2] = ranges[3] = ranges[4] = ranges[5] = width - y;
  987.     } else if (y > height - border) {
  988.         ranges[2] = ranges[3] = ranges[4] = ranges[5] = height - y;
  989.     } else {
  990.         ranges[2] = ranges[3] = border;
  991.         ranges[4] = ranges[5] = width - border;
  992.     }
  993.     ranges[6] = ranges[7] = width;
  994.     }
  995.  
  996.     public void setPixels(int x, int y, int w, int h,
  997.               ColorModel model, byte pixels[], int off,
  998.               int scansize) {
  999.     if (model == origbuttonmodel) {
  1000.         int ranges[] = new int[8];
  1001.         int x2 = x + w;
  1002.         for (int cy = y; cy < y + h; cy++) {
  1003.         buttonRanges(cy, ranges);
  1004.         for (int i = 0; i < 7; i++) {
  1005.             if (x2 > ranges[i] && x < ranges[i+1]) {
  1006.             int cx1 = Math.max(x, ranges[i]);
  1007.             int cx2 = Math.min(x2, ranges[i+1]);
  1008.             if (models[i] == null) {
  1009.                 super.setPixels(cx1, cy, cx2 - cx1, 1,
  1010.                         model, pixels,
  1011.                         off + (cx1 - x), scansize);
  1012.             } else {
  1013.                 if (cx1 < cx2) {
  1014.                 consumer.setPixels(cx1, cy, cx2 - cx1, 1,
  1015.                            models[i], pixels,
  1016.                            off + (cx1 - x), scansize);
  1017.                 }
  1018.             }
  1019.             }
  1020.         }
  1021.         off += scansize;
  1022.         }
  1023.     } else {
  1024.         super.setPixels(x, y, w, h, model, pixels, off, scansize);
  1025.     }
  1026.     }
  1027.  
  1028.     public int filterRGB(int x, int y, int rgb) {
  1029.     boolean brighter;
  1030.     int percent;
  1031.     if ((x < border && y < height - x) || (y < border && x < width - y)) {
  1032.         brighter = !pressed;
  1033.         percent = defpercent;
  1034.     } else if (x >= width - border || y >= height - border) {
  1035.         brighter = pressed;
  1036.         percent = defpercent;
  1037.     } else if (pressed) {
  1038.         brighter = false;
  1039.         percent = defpercent / 2;
  1040.     } else {
  1041.         return rgb & 0x00ffffff;
  1042.     }
  1043.     return filterRGB(rgb, brighter, percent);
  1044.     }
  1045.  
  1046.     public int filterRGB(int rgb, boolean brighter, int percent) {
  1047.     int r = (rgb >> 16) & 0xff;
  1048.     int g = (rgb >> 8) & 0xff;
  1049.     int b = (rgb >> 0) & 0xff;
  1050.     if (brighter) {
  1051.         r = (255 - ((255 - r) * (100 - percent) / 100));
  1052.         g = (255 - ((255 - g) * (100 - percent) / 100));
  1053.         b = (255 - ((255 - b) * (100 - percent) / 100));
  1054.     } else {
  1055.         r = (r * (100 - percent) / 100);
  1056.         g = (g * (100 - percent) / 100);
  1057.         b = (b * (100 - percent) / 100);
  1058.     }
  1059.     if (r < 0) r = 0;
  1060.     if (g < 0) g = 0;
  1061.     if (b < 0) b = 0;
  1062.     if (r > 255) r = 255;
  1063.     if (g > 255) g = 255;
  1064.     if (b > 255) b = 255;
  1065.     return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0);
  1066.     }
  1067. }
  1068.  
  1069. class RoundButtonFilter extends ButtonFilter {
  1070.     int Xcenter;
  1071.     int Ycenter;
  1072.     int Yradsq;
  1073.     int innerW;
  1074.     int innerH;
  1075.     int Yrad2sq;
  1076.  
  1077.     public RoundButtonFilter(boolean press, int p, int b, int w, int h) {
  1078.     super(press, p, b, w, h);
  1079.     Xcenter = w/2;
  1080.     Ycenter = h/2;
  1081.     Yradsq = h * h / 4;
  1082.     innerW = w - border * 2;
  1083.     innerH = h - border * 2;
  1084.     Yrad2sq = innerH * innerH / 4;
  1085.     }
  1086.  
  1087.     public void buttonRanges(int y, int ranges[]) {
  1088.     int yrel = Math.abs(Ycenter - y);
  1089.     int xrel = (int) (Math.sqrt(Yradsq - yrel * yrel) * width / height);
  1090.     int xslash = width - (y * width / height);
  1091.     ranges[0] = 0;
  1092.     ranges[1] = Xcenter - xrel;
  1093.     ranges[6] = Xcenter + xrel;
  1094.     ranges[7] = width;
  1095.     if (y < border) {
  1096.         ranges[2] = ranges[3] = ranges[4] = Xcenter;
  1097.         ranges[5] = ranges[6];
  1098.     } else if (y + border >= height) {
  1099.         ranges[2] = ranges[1];
  1100.         ranges[3] = ranges[4] = ranges[5] = Xcenter;
  1101.     } else {
  1102.         int xrel2 = (int) (Math.sqrt(Yrad2sq - yrel * yrel)
  1103.                    * innerW / innerH);
  1104.         ranges[3] = Xcenter - xrel2;
  1105.         ranges[4] = Xcenter + xrel2;
  1106.         if (y < Ycenter) {
  1107.         ranges[2] = ranges[3];
  1108.         ranges[5] = ranges[6];
  1109.         } else {
  1110.         ranges[2] = ranges[1];
  1111.         ranges[5] = ranges[4];
  1112.         }
  1113.     }
  1114.     }
  1115.  
  1116.     private int savedranges[];
  1117.     private int savedy;
  1118.  
  1119.     private synchronized int[] getRanges(int y) {
  1120.     if (savedranges == null || savedy != y) {
  1121.         if (savedranges == null) {
  1122.         savedranges = new int[8];
  1123.         }
  1124.         buttonRanges(y, savedranges);
  1125.         savedy = y;
  1126.     }
  1127.     return savedranges;
  1128.     }
  1129.  
  1130.     public int filterRGB(int x, int y, int rgb) {
  1131.     boolean brighter;
  1132.     int percent;
  1133.     int i;
  1134.     int ranges[] = getRanges(y);
  1135.     for (i = 0; i < 7; i++) {
  1136.         if (x >= ranges[i] && x < ranges[i+1]) {
  1137.         break;
  1138.         }
  1139.     }
  1140.     double angle;
  1141.     switch (i) {
  1142.     default:
  1143.     case 0:
  1144.     case 6:
  1145.         return rgb & 0x00ffffff;
  1146.     case 1:
  1147.         brighter = !pressed;
  1148.         percent = defpercent;
  1149.         break;
  1150.     case 5:
  1151.         brighter = pressed;
  1152.         percent = defpercent;
  1153.         break;
  1154.     case 2:
  1155.         angle = Math.atan2(y - Ycenter, Xcenter - x);
  1156.         percent = defpercent - ((int) (Math.cos(angle) * 2 * defpercent));
  1157.         if (!pressed) {
  1158.         percent = -percent;
  1159.         }
  1160.         if (percent == 0) {
  1161.         return rgb;
  1162.         } else if (percent < 0) {
  1163.         percent = -percent;
  1164.         brighter = false;
  1165.         } else {
  1166.         brighter = true;
  1167.         }
  1168.         break;
  1169.     case 4:
  1170.         angle = Math.atan2(Ycenter - y, x - Xcenter);
  1171.         percent = defpercent - ((int) (Math.cos(angle) * 2 * defpercent));
  1172.         if (pressed) {
  1173.         percent = -percent;
  1174.         }
  1175.         if (percent == 0) {
  1176.         return rgb;
  1177.         } else if (percent < 0) {
  1178.         percent = -percent;
  1179.         brighter = false;
  1180.         } else {
  1181.         brighter = true;
  1182.         }
  1183.         break;
  1184.     case 3:
  1185.         if (!pressed) {
  1186.         return rgb & 0x00ffffff;
  1187.         }
  1188.         brighter = false;
  1189.         percent = defpercent;
  1190.         break;
  1191.     }
  1192.     return filterRGB(rgb, brighter, percent);
  1193.     }
  1194. }
  1195.