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

/** PieControl extends PieDial,
 * and listens for mouse events,
 * so that it can be reshaped in response to mouse inputs.
 *
 * Drags on a radial separating segments shifts the radial,
 * thus changing the relative sizes of those segments.
 *
 * By dragging some segment down to zero size,
 * further drag motion will rotate the entire pie.
 * Backing away in the same drag then restores the closed segment.
 *
 * 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
 * Oct 1 1998 */

public class PieControl extends PieDial
{

    protected int target = -1;

/** Construct a PieControl.
 * @param measure -- how angles are measured,
 * ZERO_EAST_DEG_CCW or ZERO_NORTH_DEG_CW.
 * @param colors -- colors to show for each wedge,
 * @param starts -- starting angle of each wedge,
 * order consistent with the chosen measuring system.
 * First starting angle also closes last wedge. */

    public PieControl (int measure, Color [] colors, int [] starts) {
        super (measure, colors, starts);
    }

    public PieControl (Color [] colors, int [] starts) {
        this (ZERO_EAST_DEG_CCW, colors, starts);
    }

    public PieControl (int measure, Color [] colors, float [] starts) {
        super (measure, colors, starts);
    }

    public PieControl (Color [] colors, float [] starts) {
        this (ZERO_EAST_DEG_CCW, colors, starts);
    }

/** Default constructor */

    public PieControl () { }

/** Find and make note of one of the radials if close enough,
 * and move it to the mouse position,
 * and request a repaint to show it. */

    public synchronized boolean mouseDown (Event me, int x, int y) {

        int degs = xy_to_degrees (x, y);

        target = find_radial_target (degs);

	if (move_radial (target, degs))
	    repaint (100);

	return true;
    }

/** Using the radial chosen at mouseDown if any,
 * otherwise trying again if we got close enough,
 * either way,
 * if we have chosen one by now,
 * try to move it to the mouse position,
 * unless that would cross a neighbor,
 * and request a repaint to show it. */

    public synchronized boolean mouseDrag (Event me, int x, int y) {

        int degs = xy_to_degrees (x, y);

	if (-1 == target)
            target = find_radial_target (degs);

	if (move_radial (target, degs))
	    repaint (100);

	return true;
    }

/* If no (-1) target radial just return false.
 * Otherwise find starts before and after target modulo number of parts.
 * Stay inside them -- which depends on user measure direction!!!
 * If the proposed position is inside limits,
 * update the target radial and return true,
 * otherwise leave it alone and return false. */

    boolean move_radial (int target, int degs) {
	if (-1 == target)
	    return false;

	int by = degs - starts [target];

	if (0 == by/2)
	    return false;

        int nn = starts.length;
        int nnbefore = (target +nn -1) % nn;
        int nnafter = (target +1) % nn;

        if ((ZERO_NORTH_DEG_CW == measure)
	 && inside (degs, starts [nnafter], starts [nnbefore])) {
            starts [target] = degs;
	    return true;
        }

        else if ((ZERO_EAST_DEG_CCW == measure)
	 && inside (degs, starts [nnbefore], starts [nnafter])) {
            starts [target] = degs;
	    return true;
        }

/* If we bump the next section push everything around the circle.
 * Handy way to rotate all of them. */

        else if (near (degs, starts [target], 10)) {

	    for (int ii = 0;
	        ii < starts.length;
		ii++) {
		    starts [ii] += by;
		    if (starts [ii] < 0)
			starts [ii] += 360;

		    if (starts [ii] > 360)
			starts [ii] -= 360;
		}

	    return true;
	}

	return false;
    }

/** No response */

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

/** Forget the current target and report to Listeners */

    public synchronized boolean mouseUp (Event me, int x, int y) {
	target = -1;
        processAction (me);
   
       return true;
   }

/** Update segment label if they are defined,
 * and schedule repaint if label has changed,
 * otherwise no response. */

    public synchronized boolean mouseMove (Event me, int x, int y) {
	String xys = XYToString (x, y);
	if ((null != xys)
	 && (null != xyString)
	 && xys.equals (xyString))
	    return true;

        xyString = xys;
	stringX = x;
	stringY = y;

	repaint (100);
	return true;
    }

/** Show active margin */

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

/** Show inactive margin */

    public synchronized 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 META */

    public void processAction (Event e) {

        Event actionEvent = new Event
            (this,
             Event.ACTION_EVENT,
             "Pie Control 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);
    }

}
