import java.lang.Math;
import java.awt.*;

/** BarControl extends BarDial,
 * and listens for mouse events,
 * so that it can be reshaped in response to mouse inputs.
 *
 * This version uses the 1.0.2 event model.
 *
 * Drags on a border separating segments shifts the border,
 * thus changing the relative sizes of those segments.
 * drags between borders move the segment (both borders together).
 * Either change repaints the display and is reported.
 * Note that this directly updates the callers array of values.
 *
 * We first find a reasonable Unit Increment for the range,
 * and detent all adjustable starts to multiples of it.
 * Called only when user adjusts the BarControl,
 * otherwise all values are left alone.
 * Note that starts [0] and starts [last] are end-points,
 * which are not adjustable,
 * and never changed.
 *
 * Other responses could be programmed,
 * for example Click to divide a segment in two,
 * when enabled by some other control.
 * We would need a new color for it.

 * @author Morris Hirsch
 * IPD Inc Newport RI
 * Nov 10 1998 */

public class BarControl extends BarDial
{

    protected int itarget = -1, isegment = -1;

    protected boolean active = false;

/** Construct a BarControl.
 * @param orient -- direction of the bar,
 * @param colors -- colors to show for each segment,
 * @param starts -- starting value of each segment,
 * plus one value to close the last segment. */

    public BarControl (int orient, Color [] colors, float [] starts) {
        super (orient, colors, starts);
    }

/** Construct a BarControl.
 * @param colors -- colors to show for each segment,
 * @param starts -- starting value of each segment,
 * plus one value to close the last segment. */

    public BarControl (Color [] colors, float [] starts) {
        this (HORIZONTAL, colors, starts);
    }

/** Construct a BarControl.
 * @param orient -- direction of the bar, */

    public BarControl (int orient) {
        this (orient, null, null);
    }

/** Default constructor */

    public BarControl () {
        this (null, null);
    }

/** Get the starting values as currently shown,
 * but note that we have access to the callers array,
 * so if anything is changed by BarControl,
 * it has already changed for the caller. */

    public float [] getStarts () { return starts; }

/** Nothing */

    public boolean mouseDown (Event me, int x, int y) {
        return true;
    }

/** Using the border chosen,
 * otherwise,
 * we fall between two borders.
 * If both of them are adjustable,
 * drag the enclosed segment.
 * For any of these changes,
 * request a repaint to show it.
 * Also convert from pixels to user values,
 * and clean them up. */

    public boolean mouseDrag (Event me, int x, int y) {
         int ival = xy_to_ivalue (x, y);

         if (move_border (itarget, ival)
          || move_pair (isegment, ival)) {
             scaleStarts ();
             detentStarts ();
             repaint (100);

             processAction (me);
         }

        return true;
    }

/* If no (-1) target border just return false.
 * Otherwise find starts before and after target.
 * If the proposed position is inside limits,
 * update the target border,
 * push neighbors along if needed,
 * and return true,
 * otherwise leave it alone and return false. */

    protected boolean move_border (int itarget, int ival) {

        if (-1 == itarget)
            return false;

	if ((null == coords) || (2 > coords.length))
	    return false;

        if ((0 == itarget) || (coords.length-1 == itarget))
            return false;

        if ((ival < coords [0])
         || (ival > coords [coords.length-1]))
            return false;

        coords [itarget] = ival;
        push_down (itarget-1, ival);
        push_up (itarget+1, ival);
        return true;
    }

/* move both of them to follow ival,
 * if both can be moved legally,
 * push neighbors along if needed,
 * and return true,
 * otherwise leave it alone and return false. */

    protected boolean move_pair (int isegment, int ival) {
        if (-1 == isegment)
            return false;

        int delta = ival - (coords [isegment] + coords [isegment+1])/2;

        if ((coords [isegment] + delta < coords [0])
         || (coords [isegment+1] + delta > coords [coords.length-1]))
              return false;

        coords [isegment] += delta;
        push_down (isegment-1, ival);

        coords [isegment+1] += delta;
        push_up (isegment+2, ival);

        return true;
    }

/* Find the pair of coords that ival falls between,
 * considering only borders that can both be moved, */

    protected int find_segment (int ival) {

	if ((null == coords) || (2 > coords.length))
	    return -1;

        for (int nn = 1; nn+1 < coords.length-1; nn++)
            if (inside (ival, coords [nn], coords [nn+1]))
		return nn;

        return -1;
    }

/* Make all coords [nn down] lt ival and each other */

    protected void push_down (int nn, int ival) {
        for (int ii = nn; ii > 0; ii--)
            if (coords [ii] >= coords [ii+1])
                coords [ii] = coords [ii+1]-1;
            else
                break;
    }

/* Make all coords [nn up] gt ival and each other */

    protected void push_up (int nn, int ival) {
        for (int ii = nn; ii < coords.length-1; ii++)
            if (coords [ii] <= coords [ii-1])
                coords [ii] = coords [ii-1]+1;
            else
                break;
    }

/* First try for very close,
 * then accept further misses,
 * but finally give up.
 * Not the end points,
 * only inner borders that could be moved.
 * Ignore any null colors or invalid coordinates,
 * their space is merged with nearest valid color,
 * and their border is not visible,
 * so should not be chosen. */

    protected int find_border_target (int ival) {

	if ((null == coords) || (2 > coords.length))
	    return -1;

        if ((ival < coords [0])
         || (ival > coords [coords.length-1]))
             return -1;

        for (int delta = 2;
             delta < 9;
             delta *= 2)
            for (int ii = 1;
                 ii < coords.length-1;
                 ii++)
                if ((null != colors [ii])
                  && near (ival, coords [ii], delta))
                    return ii;

        return -1;
    }

/** Are two values within some range of each other?
 * Public as a convenience for users.
 * @param a -- one value,
 * @param b -- other value,
 * @param range -- range for nearness. */

    public static boolean near (int a, int b, int range) {

        if (a == b)
            return true;

        float larger, smaller;

        if (a > b) {
            larger = a;
            smaller = b;
        }
        else {
            larger = b;
            smaller = a;
        }

        return (smaller + range > larger);
    }

/** Is a value between two others?
 * Public as a convenience for users. */

    public static boolean inside (int val, int starting, int ending) {
        if (starting < ending)
            return ((starting < val) && (val < ending));
        else
            return ((ending < val) && (val < starting));
    }

    protected int xy_to_ivalue (int x, int y) {
        if (HORIZONTAL == orient)
            return x;
        else
            return y;
    }

/** Draw an edge to show we have focus. */

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

/** Not used */

    public boolean mouseClick (Event me, int x, int y) {
        return true;
    }

/** Forget the current target */

    public boolean mouseUp (Event me, int x, int y) {
        itarget = -1;
        isegment = -1;

        return true;
   }

/* Choose target */

    public boolean mouseMove (Event me, int x, int y) {
         int ival = xy_to_ivalue (x, y);

         itarget = find_border_target (ival);
	 isegment = find_segment (ival);

        return true;
    }

    public boolean mouseEnter (Event me, int x, int y)
    { setActive (true);
        return true;
    }

    public boolean mouseExit (Event me, int x, int y)
    { setActive (false);
        return true;
    }

/* About mouse modifiers --

   mouseEnter, Exit, Down, Up, Move, Drag all have modifers,

   Modifier masks Event.X_MASK are SHIFT, CTRL, ALT, META

   For some mice ALT = middle button, META = right button

   But keyboard and mouse are not fully equivalent,
   some platforms intercept certain codes before we see them.

   In general, SHIFT and CTRL are OK but avoid using ALT or META */

    public void processAction (Event e) {

        Event actionEvent = new Event
            (this,
             Event.ACTION_EVENT,
             "Hello!");

        if (e.shiftDown ())
            actionEvent.modifiers |= Event.SHIFT_MASK;

        if (e.controlDown ())
            actionEvent.modifiers |= Event.CTRL_MASK;

        if (e.metaDown ())
            actionEvent.modifiers |= Event.META_MASK;

        postEvent (actionEvent);
    }

/** First have super class paint segments.
 *
 * Overpaint drag targets.
 * Ignore any null colors or invalid coordinates,
 * their space is merged with nearest valid color,
 * and their border is not visible,
 * so should not be chosen.
 *
 * Draw the current one special.
 *
 * Overpaint edge to show when active. */

    public void paint (Graphics g) {

        Dimension d = getSize ();

        int ii;

        super.paint (g);

        if ((null != coords) && (2 < coords.length)) {
            if (HORIZONTAL == orient) {
                for (ii = 1;
                     ii < coords.length-1;
                     ii++) {
                    if (null != colors [ii]) {
                        g.setColor (Color.black);
                        g.fillArc (coords [ii]-5, 1, 10, 10, 0, 360);
                        g.setColor (Color.white);
                        g.fillArc (coords [ii]-3, 3, 6, 6, 0, 360);
                    }
                 }
             }

            else {
                for (ii = 1;
                     ii < coords.length-1;
                     ii++) {
                    if (null != colors [ii]) {
                        g.setColor (Color.black);
                        g.fillArc (1,coords [ii]-5, 10, 10, 0, 360);
                        g.setColor (Color.white);
                        g.fillArc (3,coords [ii]-3, 6, 6, 0, 360);
                    }
                 }
             }
         }

/* Draw an edge to show if we have focus */

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

}
