import      java.lang.Math;
import      java.util.*;

import      java.awt.*;
import      java.awt.event.*;

/** StarField extends Component,
 * implements MouseListener and MouseMotionListener,
 * routes the various MouseEvents to StarMarkers.

 * MOMENTARY, ACTION, SINGLE, MULTIPLE select modes.
 * Also ENTER, EXIT of individual StarMarkers.

 * ActionEvent for all of these,
 * reported by the StarField to any listeners,
 * with the target being the individual StarMarker,
 * then listener must figure out what changed.

 * IPD Inc Newport RI
 * Jan 9 1999 */

public class StarField extends Component
implements Runnable, MouseListener, MouseMotionListener {

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

    private float minX, maxX, minY, maxY;

    private float minZoomX, maxZoomX, minZoomY, maxZoomY;

    private Vector MV;

    private int   mode;

    public static final int
            MOMENTARY = 0,
	    ACTION = 1,
	    SINGLE = 2,
	    MULTIPLE = 3;

    protected RadioGroup RG = null;

    protected boolean
	enterExpand = false,
	enterHighlight = true;

/** Show when we are visited */

    protected boolean active = false;

/** Constructors */

    StarField () {
        this (0, 1, 0, 1);
    }

    StarField (int mode) {
        this (mode, 0, 1, 0, 1);
    }

    StarField (float minX, float maxX, float minY, float maxY) {
        this (SINGLE, minX, maxX, minY, maxY);
    }

    StarField (int mode, float minX, float maxX, float minY, float maxY) {

        if ((MOMENTARY != mode)
          && (ACTION != mode)
          && (SINGLE != mode)
          && (MULTIPLE != mode))
            throw new IllegalArgumentException ("Invalid select mode");

        this.mode = mode;

        if (SINGLE == mode)
            RG = new RadioGroup ();

/* It will grow if needed -- this seems a good start */

        MV = new Vector (100);

        setXAxis (minX, maxX);
        setYAxis (minY, maxY);

        addMouseListener (this);
        addMouseMotionListener (this);
    }

/** Remove all Markers from this StarField */

    public void clear () { MV.removeAllElements (); repaint (100); }

/** Set both Axes to include all current Markers */

    public void scaleAxes () {
        if ((MV.isEmpty()))
            return;

        StarMarker[] markers = getStarMarkers ();

        float
	  minX = markers[0].getX(),
	  maxX = markers[0].getX(),
	  minY = markers[0].getY(),
	  maxY = markers[0].getY();

        for (int ii = 1;
             ii < markers.length;
             ii++) {

             if (minX > markers[ii].getX())
		 minX = markers[ii].getX();

             if (maxX < markers[ii].getX())
		 maxX = markers[ii].getX();

             if (minY > markers[ii].getY())
		 minY = markers[ii].getY();

             if (maxY < markers[ii].getY())
		 maxY = markers[ii].getY();
         }

         setXAxis (minX, maxX);
         setYAxis (minY, maxY);
    }

/** Set X Axis,
 * and rescale any current contents */

    public void setXAxis (float minX, float maxX) {
        this.minX = minX;
        this.minZoomX = minX;
        this.maxX = maxX;
        this.maxZoomX = maxX;

        if (!(MV.isEmpty()))
            scaleCoords ();
    }

/** Set Y Axis,
 * and rescale any current contents */

    public void setYAxis (float minY, float maxY) {
        this.minY = minY;
        this.minZoomY = minY;
        this.maxY = maxY;
        this.maxZoomY = maxY;

        if (!(MV.isEmpty()))
            scaleCoords ();
    }

/** Zoom In,
 * to show two-thirds current span,
 * centered at given position,
 * in axis units. */

    public void zoomIn (float zoomX, float zoomY) {

        float Xhalf = (maxZoomX - minZoomX) / 3;
	minZoomX = zoomX - Xhalf;
	maxZoomX = zoomX + Xhalf;

        float Yhalf = (maxZoomY - minZoomY) / 3;
	minZoomY = zoomY - Yhalf;
	maxZoomY = zoomY + Yhalf;

        if (!(MV.isEmpty()))
            scaleCoords ();
    }

/** Zoom Out,
 * to show three-halves current span,
 * centered at given position,
 * in axis units. */

    public void zoomOut (float zoomX, float zoomY) {

        float Xhalf = (3 * (maxZoomX - minZoomX)) / 4;
	minZoomX = zoomX - Xhalf;
	maxZoomX = zoomX + Xhalf;

        float Yhalf = (3 * (maxZoomY - minZoomY)) / 4;
	minZoomY = zoomY - Yhalf;
	maxZoomY = zoomY + Yhalf;

        if (!(MV.isEmpty()))
            scaleCoords ();
    }

/** Reset Zoom to full given axes */

    public void zoomHome () {
        minZoomY = minY;
        maxZoomY = maxY;

        if (!(MV.isEmpty()))
            scaleCoords ();
    }

/** Set policy to Expand a Marker on Enter */

    public void setEnterExpand (boolean bv) { enterExpand = bv; }

/** Set policy to Highlight a Marker on Enter */

    public void setEnterHighlight (boolean bv) { enterHighlight = bv; }

/** Add a StarMarker to this StarField */

    public StarMarker add (StarMarker marker) {

        MV.addElement (marker);

        marker.setParent (this);

/* assign it a Listener according to the StarField mode,
 * but we will have to route the events to them,
 * because this is not a Container,
 * and markers are not Children. */

        if (MOMENTARY == mode)
            marker.setListener (new MomentaryButtonLikeListener(marker));

        if (ACTION == mode)
            marker.setListener (new SpringyButtonLikeListener(marker));

        if (SINGLE == mode) {
            marker.setListener (new StickyButtonLikeListener(marker));
	    marker.setRadioGroup (RG);
        }

        if (MULTIPLE == mode)
            marker.setListener (new StickyButtonLikeListener(marker));

        return marker;
    }

/** Get array of current StarMarkers */

    public StarMarker[] getStarMarkers () {
        if (MV.isEmpty())
            return null;

        int ii = 0;
        StarMarker[] markers = new StarMarker[MV.size ()];
	MV.copyInto (markers);
	/*--------------
        Enumeration elements = MV.elements ();

        while (elements.hasMoreElements())
               markers[ii++] = (StarMarker) elements.nextElement();
        ---------------*/
        return markers;
    }

/** Pass mousePressed to containing StarMarker if any */

    public synchronized void mousePressed (MouseEvent me) {
        if (null != (has_mouse = handleMouse (me)))
            has_mouse.getListener ().mousePressed (me);

        old_mouse = has_mouse;
    }

/** Pass mouseDragged to Marker of record from mousePressed */

    public synchronized void mouseDragged (MouseEvent me) {
        handleMouse  (me);
        if (null != has_mouse)
            has_mouse.getListener ().mouseDragged (me);
    }

/** Pass mouseClicked to containing StarMarker if any */

    public synchronized void mouseClicked (MouseEvent me) {
        if (null != (has_mouse = handleMouse (me)))
            has_mouse.getListener ().mouseClicked (me);

        old_mouse = has_mouse;
    }

/** Pass mouseReleased to StarMarker of record */

    public synchronized void mouseReleased (MouseEvent me) {
        handleMouse  (me);
        if (null != has_mouse)
            has_mouse.getListener ().mouseReleased (me);

        old_mouse = has_mouse;
    }

/** Possibly change mouseMoved to mouseEntered / mouseExited */

    public synchronized void mouseMoved (MouseEvent me) {
        if (null != (has_mouse = handleMouse (me))) {
            if (old_mouse == has_mouse)
                has_mouse.getListener ().mouseMoved (me);

            else {
                if (null != old_mouse) {
                    old_mouse.getListener ().mouseExited (me);
		    processExit (old_mouse);
		}
                has_mouse.getListener ().mouseEntered (me);
		processEnter (has_mouse);
            }
        }

	else if (null != old_mouse) {
            old_mouse.getListener ().mouseExited (me);
	    processExit (old_mouse);
        }

        old_mouse = has_mouse;
    }

    public synchronized void mouseEntered (MouseEvent me) {
        setActive (true);
    }

    public synchronized void mouseExited (MouseEvent me) {
        setActive (false);
    }

    private StarMarker has_mouse = null, old_mouse = null;

/** Find containing Marker if any for routing the Event */

    protected StarMarker handleMouse (MouseEvent me) {
        Point mep = me.getPoint ();

        StarMarker[] markers = getStarMarkers ();

        if (null == markers)
            return null;

        for (int ii = 0; ii < markers.length; ii++) {
            if (markers[ii].isVisible ()
             && markers[ii].isEnabled ()
             && markers[ii].contains(mep)) {
                Point pp = markers[ii];
                me.translatePoint (pp.x, pp.y);
                return markers[ii];
            }
        }
        return null;
    }

    private ActionListener actionListener = null;

/** Caller may add an ActionListener, */

    public synchronized void addActionListener (ActionListener al) {
        actionListener = AWTEventMulticaster.add (actionListener, al);
    }

/** Caller may remove an ActionListener, */

    public synchronized void removeActionListener (ActionListener al) {
        actionListener = AWTEventMulticaster.remove (actionListener, al);
    }

    private StarMarker actionMarker = null;
    private String actionString = null;

/** Report "Action" on a StarMarker to any Listeners */

    public void processAction (StarMarker sm) {
	actionMarker = sm;
	actionString = "Action";
        if (null != actionListener)
            new Thread (this).start();
    }

/** Report "Enter" on a StarMarker to any Listeners */

    public void processEnter (StarMarker sm) {

	if (enterExpand)
	    sm.setExpanded (true);

	if (enterHighlight)
	    sm.setHighlighted (true);

	actionMarker = sm;
	actionString = "Enter";
        if (null != actionListener)
            new Thread (this).start();
    }

/** Report "Exit" on a StarMarker to any Listeners */

    public void processExit (StarMarker sm) {

	if (enterExpand)
	    sm.setExpanded (false);

	if (enterHighlight)
	    sm.setHighlighted (false);

	actionMarker = sm;
	actionString = "Exit";
        if (null != actionListener)
            new Thread (this).start();
    }

/** Report "Select" or "Deselect" on a StarMarker to any Listeners */

    public void processStateChange (StarMarker sm, boolean state) {
        if (null != RG) {
            if (state)
                RG.setSelected (sm);
            else if (sm == RG.getSelected()) {
                sm.setState (true);
                return;
            }
        }
        actionMarker = sm;
	if (state)
	    actionString = "Select";
	else
	    actionString = "Deselect";
        if (null != actionListener)
            new Thread (this).start();
    }

/** Separate thread delivers the Action to listeners. */

    public void run () {
        try {
            actionListener.actionPerformed
                (new ActionEvent
                 (actionMarker,
                  ActionEvent.ACTION_PERFORMED,
                  actionString ));
        } catch (Exception exc) {
            System.out.println ("failed performAction:" + exc);
            exc.printStackTrace ();
        }
    }

/** MinimumSize is 15 times the current Font point size.
 * Still may have to make do with less of course... */

    public Dimension getMinimumSize () {

        int points = 12;
        Font oFont = getFont ();

        if (null != oFont)
            points = oFont.getSize ();

        return new Dimension (15 * points, 15 * points);
    }

/** PreferredSize is 30 times the current Font point size. */

    public Dimension getPreferredSize () {

        int points = 12;
        Font oFont = getFont ();

        if (null != oFont)
            points = oFont.getSize ();

        return new Dimension (30 * points, 30 * points);
    }

    public void setBounds (int x, int y, int w, int h) {
        super.setBounds (x, y, w, h);
        scaleCoords ();
    }

    public void setBounds (Rectangle r) {
        super.setBounds (r);
        scaleCoords ();
    }

    public void setSize (int w, int h) {
        super.setSize (w, h);
        scaleCoords ();
    }

    public void setSize (Dimension d) {
        super.setSize (d);
        scaleCoords ();
    }

/** Map all StarMarkers onto current size of StarField */

    protected void scaleCoords () {
        if ((MV.isEmpty()))
            return;

        Dimension d = getSize ();

        if ((null == d)
         || (0 == d.width)
         || (0 == d.height))
             return;

        float Xspan = maxZoomX - minZoomX;
        float Yspan = maxZoomY - minZoomY;

        StarMarker[] markers = getStarMarkers ();

        for (int ii = 0;
             ii < markers.length;
             ii++) {

            markers[ii].setLocation
            (10+(int) ((d.width-20) * (markers[ii].getX() - minZoomX) / Xspan),
             10+(int) ((d.height-20) * (markers[ii].getY() - minZoomY) / Yspan));
        }
    }

    public void setActive (boolean bv) {
        active = bv;
        repaint (100);
    }

    public void paint (Graphics g) {

        Dimension d = getSize ();

/* Paint all visible markers */

        StarMarker[] markers = getStarMarkers ();

        if (null != markers)
            for (int ii = 0;
                 ii < markers.length;
                 ii++) {
                markers[ii].paint (g);
            }

/* Show when we have focus */

        if (active) {
            g.setColor (Color.black);
            g.drawRect (0, 0, d.width - 1, d.height - 1);
        }
    }

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