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

/** CursorPanel extends Panel,
 * to provide a Panel we can overwrite with special PanelCursors,
 * which implement (and extend) the Adjustable interface.
 *
 * They are not "added" to the Panel in the usual way,
 * so they have no interaction with the Layout Manager,
 * and may occupy the same space as other Components.
 *
 * The idea is to let super.paint do everything as usual,
 * painting all "normally added" Components,
 * then paint the PanelCursors as overwrites.
 *
 * Doing all that every time a PanelCursor moves is too slow,
 * so we use super.paint only for genuine changes,
 * as indicated by external repaint () requests,
 * and maintain an offscreen Image from them.
 * We can quickly copy this OSI onto the screen.
 * and then over-draw PanelCursors on screen.
 * <IMG SRC="/cgi-bin/counter">

 * @author Morris Hirsch,
 * IPD Newport RI */

public class CursorPanel extends Panel
implements MouseListener, MouseMotionListener
{

  public String getVersion () { return "$Id: CursorPanel.java,v 1.8 1998/09/15 20:58:20 mhirsch Exp $"; }

    private PanelCursor [] PanelCursors = null;

    private PanelCursor active_cursor = null;
    private int lastx = 0;

    private Image OSI = null;
    private boolean partial_paint = false;
    private boolean printing = false;

/** Override Panel constructor,
 * to Listen for any events that fall in spaces between children. */

  public CursorPanel () {
        add_listeners (this);
    }

  public CursorPanel (LayoutManager LM) {
	setLayout (LM);
        add_listeners (this);
    }

/** Override all Panel.add methods to attach listeners.
 * Most events fall on the children,
 * so we must add listeners to all children.
 * Must override all methods to be sure of all children.
 *
 * There is a single "funnel" method we can override instead --
 * public void AddImpl (Component, Object, int)
 * super.AddImpl (Component, Object, int)
 * All the super.add methods come through here.
 * Have not tried it yet... obviously. */

    public Component add (Component comp) {
        add_listeners (comp);
        return super.add (comp);
    }

    public Component add (String name, Component comp) {
        add_listeners (comp);
        return super.add (name, comp);
    }

    public Component add (Component comp, int index) {
        add_listeners (comp);
        return super.add (comp, index);
    }

    public void add (Component comp, Object constraints) {
        add_listeners (comp);
        super.add (comp, constraints);
    }

    public void add (Component comp, Object constraints, int index) {
        add_listeners (comp);
        super.add (comp, constraints, index);
    }

/** Drag events are delivered to Component the drag began in,
 * even if the mouse moves outside of that Component.
 * We must simulate that behavior for a Draggable Cursor.

 * If we get a DOWN that falls in (or near) a Draggable Cursor,
 * and there is no currently active cursor,
 * make this the active cursor.

 * If we get a DRAG and there is a currently active cursor,
 * even if the mouse moves outside of that cursor,
 * update the desired position and schedule a repaint.

 * If we get an UP just reset any active cursor.  */

    void add_listeners (Component comp) {
        comp.addMouseListener (this);
        comp.addMouseMotionListener (this);
    }

/** Methods required by the MouseListener interface */

    public synchronized void mouseEntered (MouseEvent evt) { }

    public synchronized void mouseExited (MouseEvent evt) { }

    public synchronized void mousePressed (MouseEvent evt) {
        lastx = evt.getX ();
        if (null == active_cursor)
            active_cursor = matched (evt.getX ());

        if (null != active_cursor) repaint (100);
    }

    public synchronized void mouseClicked (MouseEvent evt) { }

    public synchronized void mouseReleased (MouseEvent evt) {
        active_cursor = null;
    }

/** Methods required by the MouseMotionListener interface */

    public synchronized void mouseDragged (MouseEvent evt) {
       lastx = evt.getX ();

       if (null != active_cursor) {

           int oldx = active_cursor.getValue ();

           if (oldx != lastx) {
               active_cursor.setValue (lastx);
               int newx = active_cursor.getValue ();

               if (oldx != newx) {

/** Fire an Adjustment if wanted...
 * Source is the Dragged Cursor -- not this Panel.
 * Caller also can have listener on a given Cursor.  */

                   if (null != adjl) {
                       adjl.adjustmentValueChanged
                         (new AdjustmentEvent
                             (active_cursor,
                              AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED,
                              AdjustmentEvent.TRACK,
                              active_cursor.getValue ()));
                   }

/** For best screen response we draw cursors now,
 * but we could schedule a repaint instead. */

                   if (null != OSI) {
                       Graphics g = getGraphics ();
                       g.drawImage (OSI, 0, 0, null);
                       paint_cursors (g);
                       g.dispose ();
                   }

                }
            }
        }
    }

    public synchronized void mouseMoved (MouseEvent evt) { }

    private AdjustmentListener adjl = null;

/** Caller may add / remove an AdjustmentListener on this Panel,
 * Source is the Dragged Cursor. */

    public void addAdjustmentListener (AdjustmentListener al) {
	this.adjl = AWTEventMulticaster.add (this.adjl, al);
    }

    public void removeAdjustmentListener (AdjustmentListener al) {
	this.adjl = AWTEventMulticaster.remove (this.adjl, al);
    }

/* Is the event on (or near) a Visible and Draggable Cursor?
 * First try very nearby then further away. */

    PanelCursor matched (int mx) {
        if (null != PanelCursors) {
            Dimension d = getSize ();
	    for (int margin = 0; margin < 33; margin += 10) {
                for (int ii = 0; ii < PanelCursors.length; ii++) {
		    if ((null != PanelCursors[ii])
                      && PanelCursors[ii].getDraggable ()
                      && PanelCursors[ii].getVisible ()) {
		       int cx = PanelCursors[ii].getValue ();
		       int cxx = cx + PanelCursors[ii].getVisibleAmount ();
                        if ((cx - margin < mx)
                          && (mx < cxx + margin))
                            return PanelCursors[ii];
                    }
                }
            }
        }
        return null;
    }

/** Add a PanelCursor and return a handle,
 * schedule a repaint to make sure it is shown.
 *
 * @param base -- The child of this CursorPanel
 * that Cursor travel must cover,
 * null if travel must cover the entire CursorPanel.
 *
 * @param fraction -- starting location,
 *               as a fraction (zero to one) of base width,
 *
 * @param width -- pixels cursor width.
 *
 * @param color -- cursor paint color.

 * @param style -- paint style,
 * zero for solid (full height) cursor,
 * small positive integers for skip-ratio for broken (dotted) cursor.
 *
 * @param draggable -- true if cursor is draggable by user.  */

    public PanelCursor addCursor (Component base, float fraction, int width, Color color, int style, boolean draggable) {

        if (null == PanelCursors) {
            PanelCursors = new PanelCursor[10];
        }
        for (int ii = 0; ii < PanelCursors.length; ii++) {
            if (null == PanelCursors[ii]) {
		repaint (100);
                return PanelCursors[ii]
		 = new PanelCursor (this, base, fraction, width, color, style, draggable);
            }
        }

        return null;
    }

/** Add a PanelCursor and return a handle,
 * short format defaults other parameters.
 *
 * @param color -- paint color.
 *
 * @param draggable -- true if cursor is draggable by user.  */

    public PanelCursor addCursor (Color color, boolean draggable) {
	return addCursor (null, 0, 3, color, 0, draggable);
    }

/** Remove all PanelCursors */

    public void removeAllCursors () {
        PanelCursors = null;
	active_cursor = null;
        repaint (100);
    }

/** Remove a PanelCursor by handle */

    public void removeCursor (PanelCursor aCursor) {
        if (null != PanelCursors)
            for (int ii = 0; ii < PanelCursors.length; ii++)
                if (aCursor == PanelCursors[ii]) {
                    PanelCursors[ii] = null;
		    if (active_cursor == aCursor)
			active_cursor = null;
		    repaint (100);
		    return;
                }
    }

/** Override all Panel.repaint methods. 
 * Outside requests for complete repaint mean all new OSI,
 * clipped repaint means only part of existing OSI,
 * so have super save the boundaries. */

    public void repaint () {
        OSI = null;
        super.repaint ();
    }

    public void repaint (int x, int y, int w, int h) {
        partial_paint = true;
        super.repaint (x, y, w, h);
    }

    public void repaint (long mSec) {
        OSI = null;
        super.repaint (mSec);
    }

    public void repaint (long mSec, int x, int y, int w, int h) {
        partial_paint = true;
        super.repaint (mSec, x, y, w, h);
    }

/** Override Panel.invalidate method.
 * Possible size changes mean all new background image is needed.
 * Also must recompute ranges of cursors. */

    public void invalidate () {
        super.invalidate ();
        OSI = null;

        if (null != PanelCursors)
            for (int ii = 0; ii < PanelCursors.length; ii++)
		if (null != PanelCursors[ii])
		    PanelCursors[ii].invalidate ();
    }

/** Print only the children not the PanelCursors,
 * seems like a good idea... could print them though,
 * at least a PanelCursor w/out any CursorLabels.
 *
 * But expect possible trouble printing CursorLabels,
 * the (g) may be missing Font or FontMetrics or BackgroundColor. */

    public void printAll (Graphics g) { 
	printing = true;
	super.printAll (g);
	printing = false;
    }

/** The usual pre-clear fillRect (not done here)
 * would be lost when we copy OSI,
 * and could cause unwanted flicker,
 * but we will do it when updating the OSI. */

   public void update (Graphics g) {
       paint (g);
   }

/* Overlay any needed PanelCursors to screen only */

    void paint_cursors (Graphics g) {
        Dimension d = getSize ();

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

    public void paint (Graphics g) {

/* If we are printing via some specialized Graphics context,
 * we would rather have all the children paint into it,
 * not just print the OSI representation.
 * But we cannot call super.paint (g) for PSGr as we would like to,
 * because Container.paint creates and then disposes,
 * a separate (translated and clipped) g
 * for each child.update (g),
 * and that does not yet work right in PSGr.
 * So we do it here with one g for all of them,
 * translate but no clip as yet.
 * They only get smaller,
 * we must gsave and grestore to get old one back. */

	if (printing || (g instanceof PSGr)) {
	  // super.paint (g);

	    Component [] children = getComponents ();
	    for (int kk = 0; kk < children.length; kk++) {
		if (children [kk].isShowing ()) {
	            Point p = children [kk].location ();
	            Dimension tb = children [kk].getSize ();
	            g.translate (p.x, p.y);
	            children [kk].update (g);
	            g.translate (-p.x, -p.y);
		}
	    }

	    return;
	}

/* Need all new? */

        if (null == OSI) {
            Dimension d = getSize ();

/* Maybe too early -- leave */

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

            OSI = createImage (d.width, d.height);
            Graphics osg = OSI.getGraphics ();
            osg.setClip (0, 0, d.width, d.height);

/* Clear the OSI */

	    osg.setColor (getBackground ());
	    osg.fillRect (0, 0, d.width, d.height);

            super.paint (osg);
            osg.dispose ();
        }

/* Need partial? */

        else if (partial_paint) {
            Dimension d = getSize ();

	    partial_paint = false;
            Graphics osg = OSI.getGraphics ();
            osg.setClip (g.getClip ());

/* Clear the OSI */

	    osg.setColor (getBackground ());
	    osg.fillRect (0, 0, d.width, d.height);

            super.paint (osg);
            osg.dispose ();
        }

/* Blast it all (if anything) to screen */

	if (null != OSI)
            g.drawImage (OSI, 0, 0, null);

/* Overlay any needed PanelCursors to screen only */

        paint_cursors (g);
    }
}
/* <IMG SRC="/cgi-bin/counter">*/
