import java.lang.Math;

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

/** PieControl extends PieDial,
 * and implements MouseListener and MouseMotionListener,
 * 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
implements Runnable, MouseListener, MouseMotionListener
{

    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);
        addMouseListener (this);
        addMouseMotionListener (this);
    }

    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);
        addMouseListener (this);
        addMouseMotionListener (this);
    }

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

/** Default constructor */

    public PieControl () {
        this ((Color [])null, (float [])null);
    }

/** 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 void mousePressed (MouseEvent me) {
        int x = me.getX ();
        int y = me.getY ();

        int degs = xy_to_degrees (x, y);

        target = find_radial_target (degs);

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

/** Using the radial chosen at mousePressed 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 void mouseDragged (MouseEvent me) {
        int x = me.getX ();
        int y = me.getY ();

        int degs = xy_to_degrees (x, y);

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

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

/* 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 void mouseClicked (MouseEvent me) { }

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

    public synchronized void mouseReleased (MouseEvent me) {
	target = -1;
        processAction ();
    }

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

    public synchronized void mouseMoved (MouseEvent me) {
        int x = me.getX ();
        int y = me.getY ();

	String xys = XYToString (x, y);
	if ((null != xys)
	 && (null != xyString)
	 && xys.equals (xyString))
	    return;

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

	repaint (100);
	return;
    }

/** Show active margin */

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

/** Show inactive margin */

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

    private ActionListener actionListener = null;

/** Caller may add or remove an ActionListener, */

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

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

/** Notify any listeners,
 * We use a separate Thread to notify listeners,
 * so we (and our calling event handler) can immediately return.
 * Otherwise,
 * if the event listener happens to launch a modal dialog,
 * we don't wrap up for a long time,
 * and the event will be gone by the time we return,
 * causing the event distributor to throw a NullPointerException! */

    public void processAction () {
        if (null != actionListener)
            new Thread (this).start ();
    }

    public void run () {

        try { actionListener.actionPerformed
              (new ActionEvent
              (this,
              ActionEvent.ACTION_PERFORMED,
              toString ())); 
        } catch (Exception exc) { 
            System.out.println ("failed processAction:"+exc); 
            exc.printStackTrace (); 
        }
    }

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