home *** CD-ROM | disk | FTP | other *** search
/ Java 1996 August / Java - Summer 1996.iso / windows / doc / tools / packages / classes / imagemap.java < prev    next >
Encoding:
Java Source  |  1996-02-26  |  21.7 KB  |  756 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 browser.Applet;
  18. import awt.DIBitmap;
  19. import awt.Image;
  20. import awt.Graphics;
  21. import java.util.StringTokenizer;
  22. import java.util.Vector;
  23. import java.util.Hashtable;
  24. import net.www.html.URL;
  25.  
  26. /**
  27.  * An extensible ImageMap applet class.
  28.  * The active areas on the image are controlled by ImageArea classes
  29.  * that can be dynamically extended over the web.
  30.  *
  31.  * @author     Jim Graham
  32.  * @version     1.1, 23 Mar 1995
  33.  */
  34. class ImageMap extends Applet {
  35.     /**
  36.      * The unhighlighted image being mapped.
  37.      */
  38.     Image baseImage;
  39.  
  40.     /**
  41.      * The primary highlighted image for providing user feedback.
  42.      */
  43.     Image hlImage;
  44.  
  45.     /**
  46.      * The list of image area handling objects;
  47.      */
  48.     ImageMapArea areas[];
  49.  
  50.     /**
  51.      * The primary highlight mode to be used.
  52.      */
  53.     static final int BRIGHTER = 0;
  54.     static final int DARKER = 1;
  55.  
  56.     int hlmode = BRIGHTER;
  57.  
  58.     /**
  59.      * The percentage of highlight to apply for the primary highlight mode.
  60.      */
  61.     int hlpercent = 50;
  62.  
  63.     /**
  64.      * A Hashtable of various highlighted images for ImageAreas
  65.      * that want a custom highlight.
  66.      */
  67.     Hashtable hlImages = new Hashtable();
  68.  
  69.     /**
  70.      * Get the primary highlighted version of the baseImage.
  71.      */
  72.     Image getHighlight() {
  73.     return (hlImage == null) ? makeHighlight() : hlImage;
  74.     }
  75.  
  76.     /**
  77.      * Get a version of the baseImage with a specific highlight.
  78.      * Create a new highlight image if necessary.
  79.      */
  80.     synchronized Image getHighlight(int mode, int percent) {
  81.     Image img = (Image) hlImages.get("HL"+mode+","+percent);
  82.     if (img == null) {
  83.         img = makeHighlight(mode, percent);
  84.     }
  85.     return img;
  86.     }
  87.  
  88.     /**
  89.      * Make the primary highlighted version of the baseImage.
  90.      */
  91.     Image makeHighlight() {
  92.     return makeHighlight(hlmode, hlpercent);
  93.     }
  94.  
  95.     /**
  96.      * Create a highlighted image from a highlight mode and a percentage.
  97.      */
  98.     synchronized Image makeHighlight(int mode, int percent) {
  99.     DIBitmap sourceBitmap = baseImage.getDIBitmap();
  100.  
  101.     byte newred[] = new byte[sourceBitmap.num_colors];
  102.     byte newgreen[] = new byte[sourceBitmap.num_colors];
  103.     byte newblue[] = new byte[sourceBitmap.num_colors];
  104.     for (int i = 0; i < sourceBitmap.num_colors; i++) {
  105.         if (i == sourceBitmap.trans_index) {
  106.         newred[i] = sourceBitmap.red[i];
  107.         newgreen[i] = sourceBitmap.green[i];
  108.         newblue[i] = sourceBitmap.blue[i];
  109.         } else {
  110.         int oldred = (sourceBitmap.red[i] & 0xff);
  111.         int oldgreen = (sourceBitmap.green[i] & 0xff);
  112.         int oldblue = (sourceBitmap.blue[i] & 0xff);
  113.         switch (mode) {
  114.         case DARKER:
  115.             newred[i] = (byte) (oldred * (100 - percent) / 100);
  116.             newgreen[i] = (byte) (oldgreen * (100 - percent) / 100);
  117.             newblue[i] = (byte) (oldblue * (100 - percent) / 100);
  118.             break;
  119.         case BRIGHTER:
  120.             newred[i] = (byte) (255 - ((255 - oldred)
  121.                            * (100 - percent) / 100));
  122.             newgreen[i] = (byte) (255 - ((255 - oldgreen)
  123.                          * (100 - percent) / 100));
  124.             newblue[i] = (byte) (255 - ((255 - oldblue)
  125.                         * (100 - percent) / 100));
  126.             break;
  127.         }
  128.         }
  129.     }
  130.     DIBitmap newBitmap = new DIBitmap(sourceBitmap.width,
  131.                       sourceBitmap.height,
  132.                       sourceBitmap.num_colors,
  133.                       newred, newgreen, newblue,
  134.                       sourceBitmap.raster);
  135.     newBitmap.trans_index = sourceBitmap.trans_index;
  136.     Image newImage = item.parent.createImage(newBitmap);
  137.     if (mode == hlmode && percent == hlpercent) {
  138.         hlImage = newImage;
  139.     }
  140.     hlImages.put("HL"+mode+","+percent, newImage);
  141.     return newImage;
  142.     }
  143.  
  144.     /**
  145.      * Parse a string representing the desired highlight to be applied.
  146.      */
  147.     void parseHighlight(String s) {
  148.     if (s == null) {
  149.         return;
  150.     }
  151.     if (s.startsWith("brighter")) {
  152.         hlmode = BRIGHTER;
  153.         if (s.length() > "brighter".length()) {
  154.         hlpercent = Integer.parseInt(s.substring("brighter".length()));
  155.         }
  156.     } else if (s.startsWith("darker")) {
  157.         hlmode = DARKER;
  158.         if (s.length() > "darker".length()) {
  159.         hlpercent = Integer.parseInt(s.substring("darker".length()));
  160.         }
  161.     }
  162.     }
  163.  
  164.     /**
  165.      * Initialize the applet. Get attributes.
  166.      *
  167.      * Initialize the ImageAreas.
  168.      * Each ImageArea is a subclass of the class ImageArea, and is
  169.      * specified with an attribute of the form:
  170.      *         areaN=ImageAreaClassName,arguments...
  171.      * The ImageAreaClassName is parsed off and a new instance of that
  172.      * class is created.  The initializer for that class is passed a
  173.      * reference to the applet and the remainder of the attribute
  174.      * string, from which the class should retrieve any information it
  175.      * needs about the area it controls and the actions it needs to
  176.      * take within that area.
  177.      */
  178.     public void init() {
  179.     String s;
  180.  
  181.     parseHighlight(getAttribute("highlight"));
  182.     baseImage = getImage(getAttribute("img"));
  183.     makeHighlight();
  184.     Vector areaVec = new Vector();
  185.     int num = 1;
  186.     while (true) {
  187.         ImageMapArea newArea;
  188.         s = getAttribute("area"+num);
  189.         if (s == null) {
  190.         // Try rect for backwards compatibility.
  191.         s = getAttribute("rect"+num);
  192.         if (s == null) {
  193.             break;
  194.         }
  195.         String url = getAttribute("href"+num);
  196.         if (url != null)
  197.             s += "," + url;
  198.         newArea = new HrefArea();
  199.         } else {
  200.         int classend = s.indexOf(",");
  201.         newArea = (ImageMapArea) new (s.substring(0, classend));
  202.         s = s.substring(classend+1);
  203.         }
  204.         newArea.init(this, s);
  205.         areaVec.addElement(newArea);
  206.         num++;
  207.     }
  208.     areas = new ImageMapArea[areaVec.size()];
  209.     areaVec.copyInto(areas);
  210.     resize(baseImage.width, baseImage.height);
  211.     }
  212.  
  213.     /**
  214.      * Paint the image and all active highlights.
  215.      */
  216.     public void paint(Graphics g) {
  217.     g.drawImage(baseImage, 0, 0);
  218.     for (int i = areas.length; --i >= 0; ) {
  219.         if (areas[i].active) {
  220.         areas[i].setState(g, true);
  221.         }
  222.     }
  223.     }
  224.  
  225.     /**
  226.      * Update the active highlights on the image.
  227.      */
  228.     public void update(Graphics g) {
  229.     // First unhighlight all of the deactivated areas
  230.     for (int i = areas.length; --i >= 0; ) {
  231.         if (areas[i].active && !areas[i].entered) {
  232.         areas[i].setState(g, false);
  233.         }
  234.     }
  235.     // Then highlight all of the activated areas
  236.     for (int i = areas.length; --i >= 0; ) {
  237.         if (areas[i].entered) {
  238.         areas[i].setState(g, true);
  239.         }
  240.     }
  241.     }
  242.  
  243.     /**
  244.      * Make sure that no ImageAreas are highlighted.
  245.      */
  246.     public void mouseExit() {
  247.     boolean changed = false;
  248.  
  249.     for (int i = 0; i < areas.length; i++) {
  250.         if (areas[i].active) {
  251.         areas[i].entered = false;
  252.         changed = true;
  253.         }
  254.     }
  255.     if (changed) {
  256.         repaint();
  257.     }
  258.     }
  259.  
  260.     /**
  261.      * Find the ImageAreas that the mouse is in.
  262.      */
  263.     public void mouseMove(int x, int y) {
  264.     boolean changed = false;
  265.     boolean propagate = true;
  266.  
  267.     for (int i = 0; i < areas.length; i++) {
  268.         if (areas[i].inside(x, y)) {
  269.         areas[i].entered = propagate;
  270.         if (areas[i].terminal) {
  271.             propagate = false;
  272.         }
  273.         } else {
  274.         areas[i].entered = false;
  275.         }
  276.  
  277.         if (areas[i].active != areas[i].entered) {
  278.         changed = true;
  279.         }
  280.     }
  281.  
  282.     if (changed) {
  283.         repaint();
  284.     }
  285.     }
  286.  
  287.     int pressX;
  288.     int pressY;
  289.  
  290.     /**
  291.      * Inform all active ImageAreas of a mouse press.
  292.      */
  293.     public void mouseDown(int x, int y) {
  294.     pressX = x;
  295.     pressY = y;
  296.  
  297.     for (int i = 0; i < areas.length; i++) {
  298.         if (areas[i].inside(x, y)) {
  299.         areas[i].press(x, y);
  300.         if (areas[i].terminal) {
  301.             break;
  302.         }
  303.         }
  304.     }
  305.     }
  306.  
  307.     /**
  308.      * Inform all active ImageAreas of a mouse release.
  309.      * Only those areas that were inside the original mouseDown()
  310.      * are informed of the mouseUp.
  311.      */
  312.     public void mouseUp(int x, int y) {
  313.     for (int i = 0; i < areas.length; i++) {
  314.         if (areas[i].inside(pressX, pressY)) {
  315.         areas[i].lift(x, y);
  316.         if (areas[i].terminal) {
  317.             break;
  318.         }
  319.         }
  320.     }
  321.     }
  322.  
  323.     /**
  324.      * Inform all active ImageAreas of a mouse drag.
  325.      * Only those areas that were inside the original mouseDown()
  326.      * are informed of the mouseDrag.
  327.      */
  328.     public void mouseDrag(int x, int y) {
  329.     mouseMove(x, y);
  330.     for (int i = 0; i < areas.length; i++) {
  331.         if (areas[i].inside(pressX, pressY)) {
  332.         areas[i].drag(x, y);
  333.         if (areas[i].terminal) {
  334.             break;
  335.         }
  336.         }
  337.     }
  338.     }
  339. }
  340.  
  341. /**
  342.  * The base ImageArea class.
  343.  * This class performs the basic functions that most ImageArea
  344.  * classes will need and delegates specific actions to the subclasses.
  345.  *
  346.  * @author     Jim Graham
  347.  * @version     1.1, 23 Mar 1995
  348.  */
  349. class ImageMapArea {
  350.     /** The Applet parent that contains this ImageArea. */
  351.     ImageMap parent;
  352.  
  353.     /** The X location of the area (if rectangular). */
  354.     int X;
  355.     /** The Y location of the area (if rectangular). */
  356.     int Y;
  357.     /** The width of the area (if rectangular). */
  358.     int W;
  359.     /** The height of the area (if rectangular). */
  360.     int H;
  361.  
  362.     /**
  363.      * This flag indicates whether the user was in this area during the
  364.      * last scan of mouse locations.
  365.      */
  366.     boolean entered = false;
  367.     /** This flag indicates whether the area is currently highlighted. */
  368.     boolean active = false;
  369.  
  370.     /**
  371.      * This flag indicates whether the area is terminal.  Terminal areas
  372.      * prevent any areas which are under them from being activated when
  373.      * the mouse is inside them.  Some areas may wish to change this to
  374.      * false so that they can augment other areas that they are on top of.
  375.      */
  376.     boolean terminal = true;
  377.  
  378.     /**
  379.      * Initialize this ImageArea as called from the Applet.
  380.      * If the subclass does not override this initializer, then it
  381.      * will perform the basic functions of setting the parent applet
  382.      * and parsing out 4 numbers from the argument string which specify
  383.      * a rectangular region for the ImageArea to act on.
  384.      * The remainder of the argument string is passed to the handleArg()
  385.      * method for more specific handling by the subclass.
  386.      */
  387.     public void init(ImageMap parent, String args) {
  388.     this.parent = parent;
  389.     StringTokenizer st = new StringTokenizer(args, ", ");
  390.     X = Integer.parseInt(st.nextToken());
  391.     Y = Integer.parseInt(st.nextToken());
  392.     W = Integer.parseInt(st.nextToken());
  393.     H = Integer.parseInt(st.nextToken());
  394.     if (st.hasMoreTokens()) {
  395.         // hasMoreTokens() Skips the trailing comma
  396.         handleArg(st.nextToken(""));
  397.     } else {
  398.         handleArg(null);
  399.     }
  400.     }
  401.  
  402.     /**
  403.      * This method handles the remainder of the argument string after
  404.      * the standard initializer has parsed off the 4 rectangular
  405.      * parameters.  If the subclass does not override this method,
  406.      * the remainder will be ignored.
  407.      */
  408.     public void handleArg(String s) {
  409.     }
  410.  
  411.     /**
  412.      * This method tests to see if a point is inside this ImageArea.
  413.      * The standard method assumes a rectangular area as parsed by
  414.      * the standard initializer.  If a more complex area is required
  415.      * then this method will have to be overridden by the subclass.
  416.      */
  417.     public boolean inside(int x, int y) {
  418.     return (x >= X && x < (X + W) && y >= Y && y < (Y + H));
  419.     }
  420.  
  421.     /**
  422.      * This utility method draws a rectangular subset of a highlight
  423.      * image.
  424.      */
  425.     public void drawImage(Graphics g, Image img, int x, int y, int w, int h) {
  426.     g.clipRect(x, y, w, h);
  427.     g.drawImage(img, 0, 0);
  428.     g.clearClip();
  429.     }
  430.  
  431.     /**
  432.      * This method highlights the specified area when the user enters
  433.      * it with his mouse.  The standard highlight method is to replace
  434.      * the indicated rectangular area of the image with the primary
  435.      * highlighted image.
  436.      */
  437.     public void highlight(Graphics g, boolean on) {
  438.     drawImage(g, on ? parent.hlImage : parent.baseImage, X, Y, W, H);
  439.     }
  440.  
  441.     /**
  442.      * This method changes the active state of the ImageArea, which
  443.      * indicates whether the user is currently "inside" this area.
  444.      * It turns around and calls the highlight method which is likely
  445.      * to have been overridden by subclasses seeking a custom highlight.
  446.      */
  447.     public void setState(Graphics g, boolean on) {
  448.     highlight(g, on);
  449.     active = on;
  450.     }
  451.  
  452.     /**
  453.      * The press method is called when the user presses the mouse
  454.      * button inside the ImageArea.  The location is supplied, but
  455.      * the standard implementation is to call the overloaded method
  456.      * with no arguments.
  457.      */
  458.     public void press(int x, int y) {
  459.     press();
  460.     }
  461.  
  462.     /**
  463.      * The overloaded press method is called when the user presses the
  464.      * mouse button inside the ImageArea.  This method can be overridden
  465.      * if the ImageArea does not need to know the location of the press.
  466.      */
  467.     public void press() {
  468.     }
  469.  
  470.     /**
  471.      * The lift method is called when the user releases the mouse button.
  472.      * The location is supplied, but the standard implementation is to
  473.      * call the overloaded method with no arguments.  Only those ImageAreas
  474.      * that were informed of a press will be informed of the corresponding
  475.      * release.
  476.      */
  477.     public void lift(int x, int y) {
  478.     lift();
  479.     }
  480.  
  481.     /**
  482.      * The overloaded lift method is called when the user releases the
  483.      * mouse button.  This method can be overridden if the ImageArea
  484.      * does not need to know the location of the release.
  485.      */
  486.     public void lift() {
  487.     }
  488.  
  489.     /**
  490.      * The drag method is called when the user moves the mouse while
  491.      * the button is pressed.  Only those ImageAreas that were informed
  492.      * of a press will be informed of the corresponding mouse movements.
  493.      */
  494.     public void drag(int x, int y) {
  495.     }
  496. }
  497.  
  498. /**
  499.  * The classic "Fetch a URL" ImageArea class.
  500.  * This class extends the basic ImageArea Class to fetch a URL when
  501.  * the user clicks in the area.
  502.  *
  503.  * @author     Jim Graham
  504.  * @version     1.1, 23 Mar 1995
  505.  */
  506. class HrefArea extends ImageMapArea {
  507.     /** The URL to be fetched when the user clicks on this area. */
  508.     URL anchor;
  509.  
  510.     /**
  511.      * The argument string is the URL to be fetched.
  512.      */
  513.     public void handleArg(String arg) {
  514.     anchor = new URL(parent.documentURL, arg);
  515.     }
  516.  
  517.     /**
  518.      * The status message area is updated to show the destination URL.
  519.      * The default graphics highlight feedback is used.
  520.      */
  521.     public void highlight(Graphics g, boolean on) {
  522.     super.highlight(g, on);
  523.     parent.showStatus(on ? "Go To " + anchor.toExternalForm() : null);
  524.     }
  525.  
  526.     /**
  527.      * The new URL is fetched when the user releases the mouse button
  528.      * only if they are still in the area.
  529.      */
  530.     public void lift(int x, int y) {
  531.     if (inside(x, y)) {
  532.         parent.showDocument(anchor);
  533.     }
  534.     // Note that we should not be active, so no repaint is necessary.
  535.     }
  536. }
  537.  
  538. /**
  539.  * An audio feedback ImageArea class.
  540.  * This class extends the basic ImageArea Class to play a sound each
  541.  * time the user enters the area.
  542.  *
  543.  * @author     Jim Graham
  544.  * @version     1.1, 23 Mar 1995
  545.  */
  546. class SoundArea extends ImageMapArea {
  547.     /** The URL of the sound to be played. */
  548.     String sound;
  549.  
  550.     /**
  551.      * The argument is the URL of the sound to be played.
  552.      * This method also sets this type of area to be non-terminal.
  553.      */
  554.     public void handleArg(String arg) {
  555.     sound = arg;
  556.     terminal = false;
  557.     }
  558.  
  559.     /**
  560.      * The highlight method plays the sound in addition to the usual
  561.      * graphical highlight feedback.
  562.      */
  563.     public void highlight(Graphics g, boolean on) {
  564.     super.highlight(g, on);
  565.     if (on) {
  566.         parent.play(sound);
  567.     }
  568.     }
  569. }
  570.  
  571. /**
  572.  * A click feedback ImageArea class.
  573.  * This class extends the basic ImageArea Class to show the locations
  574.  * of clicks in the image in the status message area.  This ImageArea
  575.  * utility class is useful when setting up ImageMaps.
  576.  *
  577.  * @author     Jim Graham
  578.  * @version     1.1, 23 Mar 1995
  579.  */
  580. class ClickArea extends ImageMapArea {
  581.     /** The X location of the last mouse press. */
  582.     int startx;
  583.     /** The Y location of the last mouse press. */
  584.     int starty;
  585.  
  586.     /**
  587.      * The argument is ignored, but we use this method to set this type
  588.      * of area to be non-terminal.
  589.      */
  590.     public void handleArg(String arg) {
  591.     terminal = false;
  592.     }
  593.  
  594.     /** This class overrides the highlight method to prevent highlighting. */
  595.     public void highlight(Graphics g, boolean on) {
  596.     }
  597.  
  598.     String ptstr(int x, int y) {
  599.     return "("+x+", "+y+")";
  600.     }
  601.  
  602.     /**
  603.      * When the user presses the mouse button, start showing coordinate
  604.      * feedback in the status message line.
  605.      */
  606.     public void press(int x, int y) {
  607.     parent.showStatus("Clicked at "+ptstr(x, y));
  608.     startx = x;
  609.     starty = y;
  610.     }
  611.  
  612.     /**
  613.      * Update the coordinate feedback every time the user moves the mouse
  614.      * while he has the button pressed.
  615.      */
  616.     public void drag(int x, int y) {
  617.     parent.showStatus("Rectangle from "+ptstr(startx, starty)
  618.               +" to "+ptstr(x, y)
  619.               +" is "+(x-startx)+"x"+(y-starty));
  620.     }
  621.  
  622.     /**
  623.      * Update the coordinate feedback one last time when the user releases
  624.      * the mouse button.
  625.      */
  626.     public void lift(int x, int y) {
  627.     drag(x, y);
  628.     }
  629. }
  630.  
  631. /**
  632.  * A message feedback ImageArea class.
  633.  * This class extends the basic ImageArea Class to show the a given
  634.  * message in the status message area when the user enters this area.
  635.  *
  636.  * @author     Jim Graham
  637.  * @version     1.1, 23 Mar 1995
  638.  */
  639. class NameArea extends ImageMapArea {
  640.     /** The string to be shown in the status message area. */
  641.     String name;
  642.  
  643.     /**
  644.      * The argument is the string to be displayed in the status message
  645.      * area.  This method also sets this type of area to be non-terminal.
  646.      */
  647.     public void handleArg(String arg) {
  648.     name = arg;
  649.     terminal = false;
  650.     }
  651.  
  652.     /**
  653.      * The highlight method displays the message in addition to the usual
  654.      * graphical highlight feedback.
  655.      */
  656.     public void highlight(Graphics g, boolean on) {
  657.     super.highlight(g, on);
  658.     parent.showStatus(on ? name : null);
  659.     }
  660.  
  661. }
  662.  
  663. /**
  664.  * An improved "Fetch a URL" ImageArea class.
  665.  * This class extends the basic ImageArea Class to fetch a URL when
  666.  * the user clicks in the area.  In addition, special custom highlights
  667.  * are used to make the area look and feel like a 3-D button.
  668.  *
  669.  * @author     Jim Graham
  670.  * @version     1.1, 23 Mar 1995
  671.  */
  672. class HrefButtonArea extends ImageMapArea {
  673.     /** The URL to be fetched when the user clicks on this area. */
  674.     URL anchor;
  675.  
  676.     /** The highlight image for the bright parts of the 3-D effect. */
  677.     Image brightImage;
  678.     /** The highlight image for the dark parts of the 3-D effect. */
  679.     Image darkImage;
  680.     /** The highlight image for the depressed part of the 3-D "button". */
  681.     Image pressImage;
  682.  
  683.     /** This flag indicates if the "button" is currently pressed. */
  684.     boolean pressed = false;
  685.  
  686.     /** The border size for the 3-D effect. */
  687.     int border = 5;
  688.  
  689.     /**
  690.      * The argument string is the URL to be fetched.
  691.      * This method also constructs the various highlight images needed
  692.      * to achieve the 3-D effect.
  693.      */
  694.     public void handleArg(String arg) {
  695.     anchor = new URL(parent.documentURL, arg);
  696.     brightImage = parent.getHighlight(parent.BRIGHTER, parent.hlpercent);
  697.     darkImage = parent.getHighlight(parent.DARKER, parent.hlpercent);
  698.     pressImage = parent.getHighlight(parent.DARKER, parent.hlpercent/2);
  699.     if (border * 2 > W || border * 2 > H) {
  700.         border = Math.min(W, H) / 2;
  701.     }
  702.     }
  703.  
  704.     /**
  705.      * A utility function to draw a 3-D button wrapped with an image.
  706.      */
  707.     public void drawButton(Graphics g) {
  708.     drawImage(g, pressed ? darkImage : brightImage, X, Y, W, border);
  709.     drawImage(g, pressed ? darkImage : brightImage, X, Y, border, H);
  710.     int R = X + W - 1;
  711.     int B = Y + H - 1;
  712.     if (pressed) {
  713.         drawImage(g, pressImage,
  714.               X+border, Y+border, W-border*2, H-border*2);
  715.     }
  716.     for (int i = 0; i < border; i++) {
  717.         drawImage(g, pressed ? brightImage : darkImage, X+i, B-i, W-i, 1);
  718.         drawImage(g, pressed ? brightImage : darkImage, R-i, Y+i, 1, H-i);
  719.     }
  720.     }
  721.  
  722.     /**
  723.      * The status message area is updated to show the destination URL.
  724.      * The graphical highlight is achieved using the drawButton method.
  725.      */
  726.     public void highlight(Graphics g, boolean on) {
  727.     if (on) {
  728.         drawButton(g);
  729.     } else {
  730.         super.highlight(g, false);
  731.     }
  732.     parent.showStatus(on ? "Go To " + anchor.toExternalForm() : null);
  733.     }
  734.  
  735.     /**
  736.      * Since the highlight changes when the button is pressed, we need
  737.      * to record the "pressed" state and induce a repaint.
  738.      */
  739.     public void press() {
  740.     parent.repaint();
  741.     pressed = true;
  742.     }
  743.  
  744.     /**
  745.      * The new URL is fetched when the user releases the mouse button
  746.      * only if they are still in the area.
  747.      */
  748.     public void lift(int x, int y) {
  749.     pressed = false;
  750.     if (inside(x, y)) {
  751.         parent.showDocument(anchor);
  752.     }
  753.     // Note that we should not be active, so no repaint is necessary.
  754.     }
  755. }
  756.