package graph;

import java.awt.*;
import java.applet.*;
import java.net.URL;
import java.util.*;

/*
**************************************************************************
**
**    Class  G2Dint
**
**************************************************************************
**    Copyright (C) 1995, 1996 Leigh Brookshaw
**
**    This program is free software; you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation; either version 2 of the License, or
**    (at your option) any later version.
**
**    This program is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**    GNU General Public License for more details.
**
**    You should have received a copy of the GNU General Public License
**    along with this program; if not, write to the Free Software
**    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**************************************************************************
**
**    This class is an extension of Graph2D class.
**    It adds interactive selection of the plotting range
**    and can display the mouse position in user coordinates.
**
*************************************************************************/

/**
 *    This class is an extension of Graph2D class.
 *    It adds interactive selection of the plotting range
 *    and can display the mouse position in user coordinates.
 *
 *    <h4>Mouse Events</h4>
 *    <dl>
 *     <dt>MouseDown
 *     <dd>Starts the range selection
 *     <dt>MouseDrag
 *     <dd>Drag out a rectangular range selection
 *     <dt>MouseUp
 *     <dd>Replot with modified plotting range.
 *     <dt>
 *    </dl>
 *    <h4>KeyDown Events</h4>
 *    <dl>
 *     <dt>R
 *     <dd>Redraw plot with default limits
 *     <dt>r
 *     <dd>Redraw plot using current limits
 *     <dt>m
 *     <dd>Pop window to enter manually plot range
 *     <dt>c
 *     <dd>Toggle pop-up window that displays the mouse position
 *         in user coordinates
 *     <dt>d
 *     <dd>Show coordinates of the closest data point to the cursor
 *     <dt>D
 *     <dd>Hide data coordinates pop-window
 *     <dt>h
 *     <dd>This key pressed in Any pop-window at any-time will hide it.
 *    </dl>
 *    <P>
 *    <B>Note:</B> To hide Any pop-window press the key <B>h</B> in the
 *    window. This will hide the window at any time. Depending on your
 *    windowing system the mouse button might have to be pressed in the
 *    popup window to ensure it has the keyboard focus.
 *
 * @version $Revision: 1.9 $, $Date: 1996/07/02 06:01:12 $.
 * @author Leigh Brookshaw
 */

public class G2Dint extends Graph2D {

/**
 *    Set to true when a rectangle is being dragged out by the mouse
 */
      protected boolean drag = false;
/**
 *    User limits. The user has set the limits using the mouse drag option
 */
      protected boolean userlimits = false;

/**
 *    Ths popup window for the cursor position command
 */
      private Gin cpgin = null;
/**
 *    Ths popup window for the data point command
 */
      private Gin dpgin = null;
/**
 *    The popup window to manually set the range
 */
      private Range range = null;
/**
 *    Button Down position
 */
      private int x0,y0;
/**
 *    Button Drag position
 */
      private int  x1,y1;
/*
**    Previous Button Drag position
*/
      private int x1old, y1old;

/**
 *    Attached X Axis which must be registered with this class.
 *    This is one of the axes used to find the drag range.
 *    If no X axis is registered no mouse drag.
 */
      protected Axis xaxis;
/**
 *    Attached Y Axis which must be registered with this class.
 *    This is one of the axes used to find the drag range.
 *    If no Y axis is registered no mouse drag.
 */
      protected Axis yaxis;


/**
 *    Create Xaxis to be used for the drag scaling
 */
      public Axis createXAxis() {
         xaxis = super.createAxis(Axis.BOTTOM);
         return xaxis;
      }
/**
 *    Create Yaxis to be used for the drag scaling
 */
      public Axis createYAxis() {
         yaxis = super.createAxis(Axis.LEFT);
         return yaxis;
      }
/**
 *    Attach axis to be used for the drag scaling. X axes are assumed to
 *    have Axis position Axis.BOTTOM or Axis.TOP. Y axes are assumed
 *    to have position Axis.LEFT or Axis.RIGHT.
 * @param a Axis to attach
 * @see Axis
 */
      public void attachAxis(Axis a) {
         if(a==null) return;

         super.attachAxis(a);         

         if(a.getAxisPos() == Axis.BOTTOM || a.getAxisPos() == Axis.TOP) {
              xaxis = a;
         } else {
              yaxis = a;
         }
      }
/**
 *  New update method incorporating mouse dragging.
 */
    public void update(Graphics g) {
          Rectangle r = bounds();
          Color c = g.getColor();

	  /* The r.x and r.y returned from bounds is relative to the
	  ** parents space so set them equal to zero
          */
          r.x = 0;
          r.y = 0;



          if(drag) {
	    /**
	     * Set the dragColor. Do it everytime just incase someone
             * is playing silly buggers with the background color. 
	     */
  	     g.setColor(DataBackground);

             float hsb[] = Color.RGBtoHSB(
                           DataBackground.getRed(),
                           DataBackground.getGreen(),
                           DataBackground.getBlue(),
                            null);

              if(hsb[2] < 0.5) g.setXORMode(Color.white);
              else             g.setXORMode(Color.black);

/*
**         Drag out the new box.
**         Use drawLine instead of drawRect to avoid problems
**         when width and heights become negative. Seems drawRect
**         can't handle it!
*/

	   /*
	   ** Draw over old box to erase it. This works because XORMode
	   ** has been set. If from one call to the next the background
           ** color changes going to get some odd results.
	   */
           g.drawLine(x0, y0, x1old, y0);
           g.drawLine(x1old, y0, x1old, y1old);
           g.drawLine(x1old, y1old, x0, y1old);
           g.drawLine(x0, y1old, x0, y0);
	   /*
	   ** draw out new box
	   */
           g.drawLine(x0, y0, x1, y0);
           g.drawLine(x1, y0, x1, y1);
           g.drawLine(x1, y1, x0, y1);
           g.drawLine(x0, y1, x0, y0);
	   /*
	   ** Set color back to default color
	   */
           g.setColor(c);

           x1old = x1;
           y1old = y1;

           return;
           }

          if( clearAll ) {
             g.setColor(getBackground());
             g.fillRect(r.x,r.y,r.width,r.height);
             g.setColor(c);
          }
          if( paintAll ) paint(g);
    }

/**
 * Handle the Key Down events.
 */
   public boolean keyDown(Event e, int key) {

               if(xaxis==null || yaxis==null) return false;

               switch ( key ) {

               case 'R':
                          xaxis.resetRange();
                          yaxis.resetRange();

                          userlimits = false;

                          repaint();
                          return true;
               case 'r':
                              repaint();
                              return true;
 	       case 'c':
                             if( cpgin == null) cpgin = new Gin("Position");
                             if( cpgin.isVisible() ) {
                                     cpgin.hide();
  		             } else {
                                    cpgin.show();
		             }
                             return true;
 	       case 'D':
                            if(dpgin != null) dpgin.hide();
                            return true; 
 	       case 'd':
                            if(dpgin == null) dpgin = new Gin("Data Point");
                            dpgin.show();
                            double d[] = getClosestPoint(e.x, e.y);
                            dpgin.setXlabel( d[0] );
                            dpgin.setYlabel( d[1] );
                            int ix = xaxis.getInteger(d[0]);
                            int iy = yaxis.getInteger(d[1]);
                            if( ix >= datarect.x &&
                                ix <= datarect.x +datarect.width && 
                                iy >= datarect.y &&
                                iy <= datarect.y +datarect.height ) {
                                Graphics g = getGraphics();
                                g.fillOval(ix-4, iy-4, 8, 8);
 			    }
                            return true;
		case 'm':
                            if(range == null) range = new Range(this);
                       
                            range.show();
                            range.requestFocus();
                            userlimits = true;
                            return true;
	       default:
//                             System.out.println("KeyPress "+e.key);
                             return false;
               }

	     }

/**
 * Handle the Mouse Down events
 */
    public boolean mouseDown(Event e, int x, int y) {

                if(xaxis==null || yaxis==null) return false;
		/*
		** Soon as the mouse button is pressed request the Focus
		** otherwise we will miss key events
		*/
                requestFocus();

                x0 = x;
                y0 = y;

                drag = true; 
                x1old = x0;
                y1old = y0;


                if(x0 < datarect.x) x0 = datarect.x;
                else
                if(x0 > datarect.x + datarect.width ) 
                    x0 = datarect.x + datarect.width;

                if(y0 < datarect.y) y0 = datarect.y;
                else
                if(y0 > datarect.y + datarect.height ) 
                    y0 = datarect.y + datarect.height;


                return true;
     }
/**
 * Handle the Mouse Up events
 */
    public boolean mouseUp(Event e, int x, int y) {

                if(xaxis==null || yaxis==null) return false;

                x1   = x;
                y1   = y;

                if(drag) userlimits = true;

                drag = false;


                if(x1 < datarect.x) x1 = datarect.x;
                else
                if(x1 > datarect.x + datarect.width ) 
                    x1 = datarect.x + datarect.width;

                if(y1 < datarect.y) y1 = datarect.y;
                else
                if(y1 > datarect.y + datarect.height ) 
                    y1 = datarect.y + datarect.height;


                if( Math.abs(x0-x1) > 5 &&  Math.abs(y0-y1) > 5 ) {
                   if(x0 < x1 ) {                
                      xaxis.minimum = xaxis.getDouble(x0);
                      xaxis.maximum = xaxis.getDouble(x1);
                   } else {
                      xaxis.maximum = xaxis.getDouble(x0);
                      xaxis.minimum = xaxis.getDouble(x1);
                   }

                   if(y0 >y1 ) {                
                      yaxis.minimum = yaxis.getDouble(y0);
                      yaxis.maximum = yaxis.getDouble(y1);
                   } else {
                      yaxis.maximum = yaxis.getDouble(y0);
                      yaxis.minimum = yaxis.getDouble(y1);
                   }

                   repaint();
                 }
                return true;
	      }
/**
 * Handle the Mouse Drag events
 */
    public boolean mouseDrag(Event e, int x, int y) {

                if(xaxis==null || yaxis==null) return false;

                x1   = x;
                y1   = y;

                if(drag) {

                  if(x1 < datarect.x) x1 = datarect.x;
                  else
                  if(x1 > datarect.x + datarect.width ) 
                     x1 = datarect.x + datarect.width;

                  if(y1 < datarect.y) y1 = datarect.y;
                  else
                  if(y1 > datarect.y + datarect.height ) 
                     y1 = datarect.y + datarect.height;

                }
                
                if(cpgin != null && cpgin.isVisible()) {
                            cpgin.setXlabel(  xaxis.getDouble(x1) );
                            cpgin.setYlabel(  yaxis.getDouble(y1) );
                }

                repaint();

                return true;


    }
/** 
 * Handle the Mouse Mouve events
 */
    public boolean mouseMove(Event e, int x, int y) {

                if(xaxis==null || yaxis==null) return false;

                x1   = e.x;
                y1   = e.y;

                
                if(cpgin != null && cpgin.isVisible()) {
                            cpgin.setXlabel(  xaxis.getDouble(x1) );
                            cpgin.setYlabel(  yaxis.getDouble(y1) );
                }

                return true;

    }
/**
 * Handle the Action Events.
 * This handler allows external classes (pop-up windows etc.) to
 * communicate to this class asyncronously.
 */ 
    public boolean action(Event e, Object a) {


          if(xaxis==null || yaxis==null) return false;

          if(e.target instanceof Range && range != null) {
            Double d;
            double txmin = xaxis.minimum;
            double txmax = xaxis.maximum;
            double tymin = yaxis.minimum;
            double tymax = yaxis.maximum;



            d = range.getXmin();
            if(d != null) txmin = d.doubleValue();
            d = range.getXmax();
            if(d != null) txmax = d.doubleValue();
            d = range.getYmin();
            if(d != null) tymin = d.doubleValue();
            d = range.getYmax();
            if(d != null) tymax = d.doubleValue();


            if( txmax > txmin && tymax > tymin ) {
                 xaxis.minimum = txmin;
                 xaxis.maximum = txmax;
                 yaxis.minimum = tymin;
                 yaxis.maximum = tymax;
	    }




            repaint();


            return true;
          }
           return false;
    }


/**
 *   Find the closest data point to the cursor
 */
     protected double[] getClosestPoint(int ix, int iy) {
        DataSet ds;
        int   i;
        double a[] = new double[3];
        double distsq = -1.0;
        double data[] = {0.0, 0.0};
        double x = xaxis.getDouble(ix);
        double y = yaxis.getDouble(iy);

        //System.out.println("getClosestPoint: x="+x+", y="+y);

        for (i=0; i<dataset.size(); i++) {
               ds = (DataSet)(dataset.elementAt(i));

               a = ds.getClosestPoint(x,y);

               if( distsq < 0.0 || distsq > a[2] ) {
                                    data[0] = a[0];
                                    data[1] = a[1];
                                    distsq  = a[2];
	        }
	}
        return data;

      }

}

/**
 *      Popup a window to output data after a Graphics Input command
 *      the window contains the following
 *           X  value
 *           Y  value
 */

class Gin extends Frame {

     private Label xlabel = new Label();
     private Label ylabel = new Label();

  /**
   * Instantiate the class
   */
     public Gin() {

         setLayout(new GridLayout(2,1) ) ;

         xlabel.setAlignment(Label.LEFT);
         ylabel.setAlignment(Label.LEFT);

         this.setFont(new Font("Helvetica", Font.PLAIN, 20));

         add("x",xlabel);
         add("y",ylabel);

         resize(150,100);
         super.setTitle("Graphics Input");
     }
  /**
   * Instantiate the class. 
   * @param title the title to use on the pop-window.
   */

     public Gin(String title) {
         this();
         if(title != null) super.setTitle(title);
       }

  /**
   *  Set the X value
   * @param d The value to set it
   */

     public void setXlabel(double d) {
         xlabel.setText( String.valueOf(d) );
     }

  /**
   *  Set the Y value
   * @param d The value to set it
   */

     public void setYlabel(double d) {
         ylabel.setText( String.valueOf(d) );
     }

  /**
   *  Set the both values
   * @param dx The X value to set
   * @param dy The Y value to set
   */

     public void setLabels(double dx, double dy) {
         xlabel.setText( String.valueOf(dx) );
         ylabel.setText( String.valueOf(dy) );
     }

  /**
   * Set the display font
   */

     public void setFont( Font f ) {

        if ( f == null ) return;
        xlabel.setFont( f );
        ylabel.setFont( f );

     }
  /**
   * Set the size of the window
   * @param x width in pixels
   * @param y height in pixels
   */
     public void resize( int x, int y) {
        super.resize(x,y);
     }

  /**
   * Catch the Key Down event 'h'. If the key is pressed then
   * hide this window.
   *
   * @param e The event
   * @param key the key pressed
   */

     public boolean keyDown(Event e, int key) {

             if( key == 'h' ) {
                                  this.hide();
                                  return true;
				}

             return false;

     }

}

/**
 *    A  popup window for altering the range of the plot
 */

class Range extends Frame {

     Graph2D g2d = null;


     private Label xminLabel = new Label("Xmin");
     private Label yminLabel = new Label("Ymin");
     private Label xmaxLabel = new Label("Xmax");
     private Label ymaxLabel = new Label("Ymax");

     private TextField xminText = new TextField(20);
     private TextField yminText = new TextField(20);
     private TextField xmaxText = new TextField(20);
     private TextField ymaxText = new TextField(20);

     private Button cancel = new Button("Cancel");
     private Button done   = new Button("Done");


     public Range(Graph2D g) {

         g2d = g;

         setLayout(new GridLayout(5,2,5,10) ) ;

         xminLabel.setAlignment(Label.LEFT);
         xmaxLabel.setAlignment(Label.LEFT);
         yminLabel.setAlignment(Label.LEFT);
         ymaxLabel.setAlignment(Label.LEFT);


         add("xminLabel",xminLabel);
         add("xminText",xminText);

         add("xmaxLabel",xmaxLabel);
         add("xmaxText",xmaxText);

         add("yminLabel",yminLabel);
         add("yminText",yminText);

         add("ymaxLabel",ymaxLabel);
         add("ymaxText",ymaxText);

         add("cancel", cancel);
         cancel.setBackground(Color.red);

         add("done",done);
         done.setBackground(Color.green);

         resize(250,250);
         super.setTitle("Set Plot Range");
     }

     public Double getXmin() {
         try {
               return Double.valueOf(xminText.getText());
             }
         catch (Exception ex) {
               return null;
	     }
     }
     
     public Double getXmax() {
         try {
               return Double.valueOf(xmaxText.getText());
             }
         catch (Exception ex) {
               return null;
	     }
     }

     public Double getYmin() {
         try {
               return Double.valueOf(yminText.getText());
             }
         catch (Exception ex) {
               return null;
	     }
     }
     
     public Double getYmax() {
         try {
               return Double.valueOf(ymaxText.getText());
             }
         catch (Exception ex) {
               return null;
	     }
     }


     public void resize( int x, int y) {
        super.resize(x,y);
     }
 
     public void requestFocus() {
       xminText.requestFocus();
     }
/*
** Handle the events
*/
   public boolean keyDown(Event e, int key) {

         if(e.target instanceof TextField) {

            if( ( key == 10 || e.key == 13 ) ) {

                        if(xminText.equals(e.target)) {
                          xmaxText.requestFocus();
                          return true;
                        } else
                        if(xmaxText.equals(e.target)) {
                          yminText.requestFocus();
                          return true;
                        } else
                        if(yminText.equals(e.target)) {
                          ymaxText.requestFocus();
                          return true;
                        } else
                        if(ymaxText.equals(e.target)) {
                          xminText.requestFocus();
                          return true;
		        }


                        return true;
            }

	 }

         return false;

   }


    public boolean action(Event e, Object a) {

         if(e.target instanceof Button) {
             if( done.equals(e.target) && g2d != null) {
                  g2d.deliverEvent( new Event(this,Event.ACTION_EVENT,this) );
                  this.hide();
                  return true;
             } else 
             if( cancel.equals(e.target) ) {
                  this.hide();
                  return true;
	     }
         }


         return false;
       }



   }
