import java.awt.*;
import java.util.*;

/** StarMarker extends Point,
 * rather than Canvas / Component,
 * to be drawn directly on a parentStarField,
 * which provides Graphics g and coordinate space for the drawing.
 * Parent also provides event handling and interpretation.
 *
 * StarMarker should be as close as possible to ImageButton,
 * except for being lightweight rather than Canvas / Component,
 * but single inheritance means we must implement in both places.  */

public class StarMarker
extends Point
implements ButtonLike {

    public static final String VERSION_STRING = "StarMarker.java 1.0 9/29/99";

/* Need this for unique hashCode,
 * because all Point (0, 0) are created equal.
 * Or we could extend Object instead of Point,
 * declare x and y and a few methods. */

    protected Object myObject;

    public int width = 24, height = 20;

    protected float fX = 0, fY = 0;

    protected String text = null;

    protected Color fgColor = null, bgColor = null;

    private boolean visible = true;
    private boolean stale = true;
    protected boolean Enabled = true;
    protected boolean Pressed = false;
    protected boolean Highlighted = false;
    protected boolean Expanded = false;

    protected StarField parentStarField = null;
    protected ButtonLikeListener listener = null;
    protected RadioGroup RG = null;

/* Appearance details that belong in an extension,
 * or at least that extensions may do differently */

    protected Color fgHLColor = null, bgHLColor = null;

    protected int borderThickness = 0;

    protected int borderHLThickness = 0;

/* Iconic display options --
 * String, Image, Simple Drawn Shapes.
 * And an Expansion option -- just String I think. */

    protected String abbrev = null;
    protected Image image = null;
    protected int shape = 0;
    protected int edge = 0;

/** Constructor */

    StarMarker (String text, String abbrev) {
	myObject = new Object ();
        this.text = text;
        this.abbrev = abbrev;
    }

/** Constructor */

    StarMarker (String text) {
        this (text, null);
    }

/** Constructor */

    StarMarker () {
        this (null);
    }

    public void setXY (float fX, float fY) {
	this.fX = fX;
	this.fY = fY;
	repaint (50);
    }

    public void setX (float fX) {
	this.fX = fX;
	repaint (50);
    }

    public void setY (float fY) {
	this.fY = fY;
	repaint (50);
    }

    public float getX () { return fX; }

    public float getY () { return fY; }

    public void setParent (StarField parentStarField) {
        this.parentStarField = parentStarField;
    }

    public StarField getParent () { return parentStarField; }

    public int hashCode () { return myObject.hashCode (); }

    public void setVisible (boolean bv) { visible = bv; }

    public boolean isVisible () {
	if (null == parentStarField)
	    return false;

	if ((x < 0) || (y < 0))
	    return false;

	Dimension ps = parentStarField.getSize ();
	if ((x > ps.width) || (y > ps.height))
	    return false;

        return visible;
    }

/** Set the Listener to provide some button behavior.
 * Really all happens in the parent not here. */

    public void setListener (ButtonLikeListener ibl) {
        listener = ibl;
    }

/** Get the current Listener. */

    public ButtonLikeListener getListener () { return listener; }

/** Have parentStarField do the rest for us... */

    public void processStateChange (boolean s) {
         parentStarField.processStateChange (this, s);
    }

    public void processAction () {
	parentStarField.processAction (this);
    }

/** Set the state,
 * have RadioGroup (if any) turn off any prior selection,
 * update the "state machine" in the event handler,
 * also update the appearance of the button.
 * Should only be called when Sticky listener is attached.
 *
 * Here for internal call,
 * if this is the RG current selection,
 * and it is being turned off,
 * let it happen,
 * and null out the RG current selection. */

    public void setState (boolean state) {
        ((StickyButtonLikeListener)listener).setState (state);

	if (null != RG) {
	    if (state)
	        RG.setSelected (this);
	    else if (this == RG.getSelected ())
	        RG.setSelected (null);
	}

        setPressed (state);
    }

/** Get the current state.
 * Should only be called when Sticky listener is attached. */

    public boolean getState () { return ((StickyButtonLikeListener)listener).getState (); }

/** Set the RadioGroup.
 * Note that the RadioGroup will call getState () and setState ().
 * @param RG -- the RadioGroup to manage this control. */

    public void setRadioGroup (RadioGroup RG) {
	this.RG = RG;
    }

    public boolean contains (Point cp) { return contains (cp.x, cp.y); }

    public boolean contains (int cx, int cy) {
	return ((x - width/2 < cx)
	    && (x + width/2 > cx)
	    && (y - height/2 < cy)
	    && (y + height/2 > cy));
    }

/** Set appearance Pressed or not */

    public void setPressed (boolean Pressed) {
	if (this.Pressed == Pressed)
	    return;

        this.Pressed = Pressed;

        if (isVisible ()) repaint();
    }

    public boolean isPressed () { return Pressed; }

/** Set appearance Highlighted or not */

    public void setHighlighted (boolean Highlighted) {
	if (this.Highlighted == Highlighted)
	    return;

        this.Highlighted = Highlighted;

        if (isVisible ()) repaint();
    }

    public boolean isHighlighted () { return Highlighted; }

/** Set appearance Expanded or not */

    public void setExpanded (boolean Expanded) {
	if (this.Expanded == Expanded)
	    return;

        this.Expanded = Expanded;

	if ((null == abbrev) && (null == image) && (0 == shape))
	    return;

        if (isVisible ()) repaint();
    }

    public boolean isExpanded () { return Expanded; }

    public void setEnabled (boolean enable) {
	if (Enabled == enable)
	    return;

        Enabled = enable;

        if (isVisible ()) repaint();
    }

    public boolean isEnabled () { return Enabled; }

    public void setForeground (Color set_fgColor) {
        if (fgColor == set_fgColor) return;

        fgColor = set_fgColor;
        repaint (50);
    }

    public void setBackground (Color set_bgColor) {
        if (bgColor == set_bgColor) return;

        bgColor = set_bgColor;
        repaint (50);
    }

    public void setText (String text) {
        this.text = text;
        repaint (50);
    }

    public String getText () { return text; }

/** Appearance implementation methods subject to override */

    public void setBorderHLThickness (int borderHLThickness) {
	this.borderHLThickness =  borderHLThickness;
    }

    public int getBorderHLThickness () { return borderHLThickness; }

    public void setBorderThickness (int borderThickness) {
	this.borderThickness =  borderThickness;
    }

    public int getBorderThickness () { return borderThickness; }

    public void setHLForeground (Color set_fgHLColor) {
        if (fgHLColor == set_fgHLColor) return;

        fgHLColor = set_fgHLColor;
	if (Highlighted)
            repaint (50);
    }

    public void setHLBackground (Color set_bgHLColor) {
        if (bgHLColor == set_bgHLColor) return;

        bgHLColor = set_bgHLColor;
	if (Highlighted)
            repaint (50);
    }

    public void setAbbrev (String abbrev) {
        this.abbrev = abbrev;
        repaint (50);
    }

    public void setImage (Image image) {
        this.image = image;
        repaint (50);
    }

    public void setShape (int shape) {
        this.shape = shape;
	if (0 == edge)
	    edge = 12;
        repaint (50);
    }

    public void setShape (int shape, int edge) {
        this.shape = shape;
        this.edge = edge;
        repaint (50);
    }

    public String toString () { return super.toString () + text; }

/** To repaint this StarMarker after some change,
   schedule repaint for parentStarField,
   allowing enough delay to batch more of these requests. */

    public void repaint (int msec) {
	stale = true;
        if (isVisible ())
            parentStarField.repaint (msec);
    }

/** To repaint this StarMarker as soon as possible */

    public void repaint () {
	stale = true;
        if (isVisible ())
            parentStarField.repaint ();
    }

/* parentStarField calls this explicitly for each StarMarker. */

    public void paint (Graphics g) {

        if (!isVisible ()) return;

        if ((null == fgColor) || (null == bgColor)) {
            fgColor = parentStarField.getForeground ();
            bgColor = parentStarField.getBackground ();
        }

/* How to show not Enabled? Could it still be Expanded? */

/* Use HLColors / HLThickness when Highlighted */

/* Show the Expansion text if requested,
 * or if we have no abbrev or image or shape,
 * otherwise the abbrev,
 * or image,
 * or shape */

        if (Expanded
	 || ((null == abbrev) && (null == image) && (0 == shape)))
	    gString (g, text);

        else if (null != abbrev)
	    gString (g, abbrev);

        else if (null != image)
	    gImage (g, image);

        else if (0 < shape)
	    gShape (g, shape);

	stale = false;
    }

    protected void gShape (Graphics g, int shape) {
    }

    protected void gImage (Graphics g, Image image) {
	if (stale) {
            Util.waitForImage (parentStarField, image);
            width  = image.getWidth (parentStarField);
            height = image.getHeight (parentStarField);
	}

/* What to do for Pressed when using an Image? */

        g.drawImage (image,
	  x - width/2, 
          y - height/2,
          parentStarField);
    }

    protected void gString (Graphics g, String str) {
	if (stale) {
            FontMetrics fm = g.getFontMetrics ();
	    width = fm.stringWidth (str);
	    height = fm.getHeight ();
	}

/* Reverse fg and bg Colors when Pressed. */

        if (Pressed) {
            g.setColor (fgColor);
            g.fillRect (x-width/2, y-height/2, width, height);
            g.setColor (bgColor);
        }

        else {
            g.setColor (bgColor);
            g.fillRect (x-width/2, y-height/2, width, height);
            g.setColor (fgColor);
        }

        g.drawString (str, x-width/2, y+height/2);
    }

}
/* <IMG SRC="/cgi-bin/counter">*/
