//
// ContourControl.java
//

/*
VisAD system for interactive analysis and visualization of numerical
data.  Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/

package visad;

import java.rmi.RemoteException;
import java.util.StringTokenizer;

import visad.browser.Convert;
import visad.util.Util;

/**
 * ContourControl is the VisAD class for controlling IsoContour display scalars.
 */

public class ContourControl extends Control {

  private static final long serialVersionUID = 1L;
  
  // number below is from GUI control, it is approximately
  // how many times a contour line will (attempt to) be labeled
  public static final int LABEL_FREQ_LO = 1;
  public static final int LABEL_FREQ_MED = 5;
  public static final int LABEL_FREQ_HI = 9;
  
  // default and (somewhat arbitrary) values for labeling every Nth line
  public static final int EVERY_NTH_MIN = 0;
  public static final int EVERY_NTH_DEFAULT = 2;
  public static final int EVERY_NTH_MAX = 100;
  
  private boolean mainContours;
  // for 3-D mainContours
  private float surfaceValue;
  // for 2-D mainContours
  // these are the 'old' descriptors for 2-D contour lines
  private float contourInterval;
  private float lowLimit;
  private float hiLimit;
  private float base;
  private boolean labels;
  private int labelFreq = LABEL_FREQ_LO;
  private int everyNth = EVERY_NTH_DEFAULT;

  private boolean public_set = false; // application called setLevels()

  //
  // these are the 'new' descriptors for 2-D contour lines
  // includes lowLimit, hiLimit and base from the 'old' descriptors
  // true if contourInterval is valid
  private boolean arithmeticProgression = true;
  // contour line levels
  private float[] levels = null;
  private int lineStyle = GraphicsModeControl.SOLID_STYLE;
  private boolean dash = false;

  private boolean horizontalContourSlice;
  private boolean verticalContourSlice;

  private float horizontalSliceLow;
  private float horizontalSliceHi;
  private float horizontalSliceStep;
  private float verticalSliceLow;
  private float verticalSliceHi;
  private float verticalSliceStep;

  boolean contourFill;

  private static double init_scale = Double.NaN;
  private static double default_init_scale = 0.40;
  private boolean autoSizeLabels = true; // BMF 2009-03-05 change default to true
  private boolean alignLabels = true;
  private double labelSizeFactor = 1;
  private transient ZoomDoneListener zoom;
  private ProjectionControl pcntrl;
  private ControlListener projListener;
  private double ratio = 1.20;
  
  // label color defaults to white
  private byte[] labelColor = null;
  private boolean colorSet = false;

  private Object labelFont = null;
  //private Object labelFont = new java.awt.Font("Dialog", java.awt.Font.PLAIN, 14);

  
  /**
   * Construct a new ContourControl for the display
   * @param d    Display to associate with this
   */
  public ContourControl(DisplayImpl d) {
    super(d);
    mainContours = true;
    labels = false;
    surfaceValue = Float.NaN;
    contourInterval = Float.NaN;
    lowLimit = Float.NaN;
    hiLimit = Float.NaN;
    base = Float.NaN;

    horizontalContourSlice = false;
    verticalContourSlice = false;

    horizontalSliceLow = Float.NaN;
    horizontalSliceHi = Float.NaN;
    horizontalSliceStep = Float.NaN;
    verticalSliceLow = Float.NaN;
    verticalSliceHi = Float.NaN;
    verticalSliceStep = Float.NaN;

    contourFill = false;

    pcntrl = d.getProjectionControl();
    double[] matrix          = pcntrl.getMatrix();
    double[] rot             = new double[3];
    double[] trans           = new double[3];
    double[] scale           = new double[1];
    MouseBehavior mouse      = d.getMouseBehavior();

    double projScale;
    if (mouse != null) {
      mouse.instance_unmake_matrix(rot, scale, trans, matrix);
      if (init_scale != init_scale) init_scale = scale[0];
      projScale = scale[0];
    }
    else {
      init_scale = default_init_scale;
      projScale = init_scale;
    }

    zoom = new ZoomDoneListener(this, pcntrl, mouse, projScale);
    d.addDisplayListener(zoom);
  }

  /**
   * set parameters for IsoContour depictions, if not already set
   * @param  bvalues   must be dimensioned boolean[2], where
   *         bvalues[0]  enable contours
   *         bvalues[1]  enable labels (if applicable)
   * @param  fvalues   must be dimensioned float[5], where
   *         fvalues[0]  level for iso-surface
   *         fvalues[1]  interval for iso-lines
   *         fvalues[2]  low limit for iso-lines
   *         fvalues[3]  high limit for iso-lines
   *         fvalues[4]  base for iso-lines
   * @throws VisADException  a VisAD error occurred
   * @throws RemoteException  an RMI error occurred
   */
  void setMainContours(boolean[] bvalues, float[] fvalues)
         throws VisADException, RemoteException {
    setMainContours(bvalues, fvalues, false, false);
  }

  /**
   * set parameters for IsoContour depictions, if not already set
   * @param  bvalues   must be dimensioned boolean[2], where
   *         bvalues[0]  enable contours
   *         bvalues[1]  enable labels (if applicable)
   * @param  fvalues   must be dimensioned float[5], where
   *         fvalues[0]  level for iso-surface
   *         fvalues[1]  interval for iso-lines
   *         fvalues[2]  low limit for iso-lines
   *         fvalues[3]  high limit for iso-lines
   *         fvalues[4]  base for iso-lines
   * @param  noChange  true to not trigger re-transform (false for
   *                   auto-scale)
   * @throws VisADException  a VisAD error occurred
   * @throws RemoteException  an RMI error occurred
   */
  void setMainContours(boolean[] bvalues, float[] fvalues, boolean noChange)
         throws VisADException, RemoteException {
    setMainContours(bvalues, fvalues, noChange, false);
  }

  /**
   * set parameters for IsoContour depictions
   * @param  bvalues   must be dimensioned boolean[2], where
   *         bvalues[0]  enable contours
   *         bvalues[1]  enable labels (if applicable)
   * @param  fvalues   must be dimensioned float[5], where
   *         fvalues[0]  level for iso-surface
   *         fvalues[1]  interval for iso-lines
   *         fvalues[2]  low limit for iso-lines
   *         fvalues[3]  high limit for iso-lines
   *         fvalues[4]  base for iso-lines
   * @param  noChange  true to not trigger re-transform (false for
   *                   auto-scale)
   * @param  override  true to set float values even if already set
   *                   (i.e., not NaNs)
   * @throws VisADException  a VisAD error occurred
   * @throws RemoteException  an RMI error occurred
   */
  void setMainContours(boolean[] bvalues, float[] fvalues, boolean noChange,
                       boolean override)
         throws VisADException, RemoteException {
    if (fvalues == null || fvalues.length != 5 ||
        bvalues == null || bvalues.length != 2) {
      throw new DisplayException("ContourControl.setMainContours: " +
                                 "bad array length");
    }
    boolean setLevels = false;
    float[] levs = null;
    boolean[] dashes = null;
    float myBase = 0;
    synchronized(this) {
      mainContours = bvalues[0];
      labels = bvalues[1];

      // WLH 13 Sept 2000
      if (override) {
        surfaceValue = fvalues[0];
        contourInterval = fvalues[1];
        lowLimit = fvalues[2];
        hiLimit = fvalues[3];
        base = fvalues[4];
      }
      else {
        if (surfaceValue != surfaceValue) surfaceValue = fvalues[0];
        if (contourInterval != contourInterval) contourInterval = fvalues[1];
        if (lowLimit != lowLimit) lowLimit = fvalues[2];
        if (hiLimit != hiLimit) hiLimit = fvalues[3];
        if (base != base) base = fvalues[4];
      }
      // adapt to 'new' descriptors
      if (arithmeticProgression) {
        if (contourInterval == contourInterval && base == base &&
            lowLimit == lowLimit && hiLimit == hiLimit) {
          dashes = new boolean[] {false};
          levs =
            Contour2D.intervalToLevels(contourInterval, lowLimit, hiLimit, base, dashes);
          myBase = base;
          setLevels = true;
        }
        else {
          dash = false;
          levels = null;
        }
      }
    }

    /**
     * The following methods are "alien" because they are outside the control of
     * this class.  If they were invoked in a synchronized block, then deadlock
     * could occur if one of the methods waits on a thread that it creates that
     * calls back into this class and tries to obtain a lock (it's happened
     * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's
     * "Effective Java" for more information.
     */
    if (setLevels)
      setLevels(levs, myBase, dashes[0], false);

    changeControl(!noChange);
  }

  /** 
   * Set level for iso-surfaces 
   * @param value   value of the iso-surface to display
   * @throws VisADException     VisAD error
   * @throws RemoteException    Java RMI failure.
   */
  public void setSurfaceValue(float value)
         throws VisADException, RemoteException {
    setSurfaceValue(value, false);
  }

  /** 
   * Set level for iso-surfaces 
   * @param value   value of the iso-surface to display
   * @param setLevels  true if this should be used for contour levels
   * @throws VisADException     VisAD error
   * @throws RemoteException    Java RMI failure.
   */
  public void setSurfaceValue(float value, boolean setLevels)
         throws VisADException, RemoteException {
    boolean change;
    synchronized(this) {
      change = !Util.isApproximatelyEqual(surfaceValue, value);
      surfaceValue = value;
      if (setLevels) levels = new float[] {value};
    }
    if (change) {
      /**
       * The following method is "alien" because it is outside the control of
       * this class.  If it were invoked in a synchronized block, then deadlock
       * could occur if the method waits on a thread that it creates that
       * calls back into this class and tries to obtain a lock (it's happened
       * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua
       * Bloch's "Effective Java" for more information.
       */
      changeControl(true);
    }
  }

  /**
   * Sets the parameters for contour iso-lines.  This method invokes the
   * {@link ControlListener#controlChanged(ControlEvent)} method of all
   * registered listeners;
   *
   * @param interval            The contour interval.  Must be non-zero.  If
   *                            negative, then contour lines below the base will
   *                            be dashed.  Must not be NaN.
   * @param low                 The minimum contour value.  No contour line less
   *                            than this value will be drawn.  Must not be NaN.
   * @param hi                  The maximum contour value.  No contour line
   *                            greater than this value will be drawn.  Must not
   *                            be NaN.
   * @param ba                  The base contour value.  The contour lines will
   *                            be integer multiples of the interval away from
   *                            this value.  Must not be NaN.
   * @throws VisADException     The interval is zero or too small.
   * @throws RemoteException    Java RMI failure.
   */
  public void setContourInterval(float interval, float low,
                                 float hi, float ba)
         throws VisADException, RemoteException {
    public_set = true;
    float[] levs;
    float myBase;
    boolean[] dashes = {false};
    boolean change;
    synchronized(this) {
      change = (contourInterval != interval) || (base != ba) ||
               !Util.isApproximatelyEqual(lowLimit, low) ||
               !Util.isApproximatelyEqual(hiLimit, hi);
      contourInterval = interval;
      lowLimit = low;
      hiLimit = hi;
      myBase = base = ba;

      // adapt to 'new' descriptors
      levs =
        Contour2D.intervalToLevels(contourInterval, lowLimit, hiLimit, base, dashes);
      arithmeticProgression = true;
    }
    /**
     * The following method is "alien" because it is outside the control of this
     * class.  If it were invoked in a synchronized block, then deadlock could
     * occur if the method waits on a thread that it creates that calls back
     * into this class and tries to obtain a lock (it's happened before).  See
     * Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's "Effective
     * Java" for more information.
     */
    setLevels(levs, myBase, dashes[0], change);
  }

  /** 
   * Set low and high iso-line levels 
   * @param low                 The minimum contour value.  No contour line less
   *                            than this value will be drawn.  Must not be NaN.
   * @param hi                  The maximum contour value.  No contour line
   *                            greater than this value will be drawn.  Must not
   *                            be NaN.
   * @throws VisADException     VisAD error
   * @throws RemoteException    Java RMI failure.
   */
  public void setContourLimits(float low, float hi)
         throws VisADException, RemoteException {
    public_set = true;
    boolean change;
    boolean setLevels;
    float[] levs = null;
    float myBase = 0;
    boolean[] dashes = null;
    synchronized(this) {
      change = !Util.isApproximatelyEqual(lowLimit, low) ||
               !Util.isApproximatelyEqual(hiLimit, hi);
      lowLimit = low;
      hiLimit = hi;
      // adapt to 'new' descriptors
      if (arithmeticProgression) {
        setLevels = true;
        dashes = new boolean[] {false};
        levs =
          Contour2D.intervalToLevels(contourInterval, lowLimit, hiLimit, base, dashes);
        myBase = base;
      }
      else {
        setLevels = false;
        int n = 0;
        for (int i=0; i max) max = levs[i];
      newLevels[i] = levs[i];
    }
    synchronized(this) {
      levels = newLevels;
      dash = da;
      base = ba;
      if (by_user) {
        lowLimit = min - Math.abs(.01f * min);  // DRM 25-APR-2001
        hiLimit  = max + Math.abs(.01f * max);  // DRM 25-APR-2001
      }
    }
    if (by_user) {
      /**
       * The following method is "alien" because it is outside the control of
       * this class.  If it were invoked in a synchronized block, then deadlock
       * could occur if the method waits on a thread that it creates that
       * calls back into this class and tries to obtain a lock (it's happened
       * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua
       * Bloch's "Effective Java" for more information.
       */
      changeControl(true);
    }
  }

  /** get 'new' descriptors for 2-D contour lines
   * @param lowhibase   must be dimensioned float[3], where
   *        lowhibase[0]  used to return low limit
   *        lowhibase[1]  used to return high limit
   *        lowhibase[2]  used to return base
   * @param dashes      must be dimensioned boolean[1], where
   *        dashed[0]     used to return dash enable
   * @return float[] array of levels for contour curves
   */
  public synchronized float[] getLevels(float[] lowhibase, boolean[] dashes) {
    float[] levs = null;
    if (levels != null) {
      levs = new float[levels.length];
      System.arraycopy(levels, 0, levs, 0, levels.length);
    }
    lowhibase[0] = lowLimit;
    lowhibase[1] = hiLimit;
    lowhibase[2] = base;
    dashes[0] = dash;
    return levs;
  }

  /**
   * set label enable
   * @param on  new value for label enable
   * @throws VisADException  a VisAD error occurred
   * @throws RemoteException  an RMI error occurred
   */
  public void enableLabels(boolean on)
         throws VisADException, RemoteException {
    boolean change;
    synchronized(this) {
      change = (labels != on);
      labels = on;
    }
    /**
     * The following method is "alien" because it is outside the control of this
     * class.  If it were invoked in a synchronized block, then deadlock could
     * occur if the method waits on a thread that it creates that calls back
     * into this class and tries to obtain a lock (it's happened before).  See
     * Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's "Effective
     * Java" for more information.
     */
    if (change) changeControl(true);
  }

  /** 
   * set contour enable 
   * @param on  new value for contour enable
   * @throws VisADException  a VisAD error occurred
   * @throws RemoteException  an RMI error occurred
   */
  public void enableContours(boolean on)
         throws VisADException, RemoteException {
    boolean change;
    synchronized(this) {
      change = (mainContours != on);
      mainContours = on;
    }
    /**
     * The following method is "alien" because it is outside the control of this
     * class.  If it were invoked in a synchronized block, then deadlock could
     * occur if the method waits on a thread that it creates that calls back
     * into this class and tries to obtain a lock (it's happened before).  See
     * Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's "Effective
     * Java" for more information.
     */
    if (change) changeControl(true);
  }

  /**
   * get parameters for IsoContour depictions
   * @param  bvalues   must be dimensioned boolean[2], where
   *         bvalues[0]  used to return contour enable
   *         bvalues[1]  used to return label enable
   * @param  fvalues   must be dimensioned float[5], where
   *         fvalues[0]  used to return level for iso-surface
   *         fvalues[1]  used to return interval for iso-lines
   *         fvalues[2]  used to return low limit for iso-lines
   *         fvalues[3]  used to return high limit for iso-lines
   *         fvalues[4]  used to return base for iso-lines
   * @throws VisADException  a VisAD error occurred
   */
  public void getMainContours(boolean[] bvalues, float[] fvalues)
         throws VisADException {
    if (fvalues == null || fvalues.length != 5 ||
        bvalues == null || bvalues.length != 2) {
      throw new DisplayException("ContourControl.getMainContours: " +
                                 "bad array length");
    }
    synchronized(this) {
      bvalues[0] = mainContours;
      bvalues[1] = labels;
      fvalues[0] = surfaceValue;
      fvalues[1] = contourInterval;
      fvalues[2] = lowLimit;
      fvalues[3] = hiLimit;
      fvalues[4] = base;
    }
  }

  public void setContourFill(boolean flag)
         throws VisADException, RemoteException {
    
    if (flag) {
      if (!colorSet)
        labelColor = new byte[]{(byte)255, (byte)255, (byte)255, (byte)255};
    
    } else {
      if (!colorSet)
        labelColor = null;
    }
        
    synchronized(this) {
      contourFill = flag;
    }
    /**
     * The following method is "alien" because it is outside the control of this
     * class.  If it were invoked in a synchronized block, then deadlock could
     * occur if the method waits on a thread that it creates that calls back
     * into this class and tries to obtain a lock (it's happened before).  See
     * Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's "Effective
     * Java" for more information.
     */
    changeControl(true);
  }

  /**
   * @return contourFill enable
   */
  public synchronized boolean contourFilled() {
    return contourFill;
  }

  /**
   * @return initial scale for label auto-size
   */
  public static double getInitScale() {
    return init_scale;
  }

  /**
   * set enable for label auto-size
   * @param flag  new value for label auto-size enable
   */
  public void setAutoScaleLabels(boolean flag) {
    synchronized(this) {
      autoSizeLabels = flag;
    }
  }

  /**
   * @return label auto-size enable
   */
  public boolean getAutoSizeLabels() {
    return autoSizeLabels;
  }

  /**
   * Set the contour label alignment policy
   * @param flag true: labels follow the contour line, false: labels are horizontal, default: true
   * @throws VisADException
   * @throws RemoteException
   */
  public void setAlignLabels(boolean flag) throws RemoteException, VisADException {
    synchronized(this) {
      alignLabels = flag;
    }
    changeControl(true);
  }

  public boolean getAlignLabels() {
    return alignLabels;
  }
  
  //BMF 2006-10-04
  /** 
   * Sets the color for label.
   * @param color RGB color array, if null label takes contour color
   * @throws VisADException 
   * @throws RemoteException 
   */
  public void setLabelColor(byte[] color) throws RemoteException, VisADException {
    setLabelColor(color, true);
  }

  //BMF 2006-10-04
  /**
   * Sets the label color. 
   * @param color RGB color array, if null label takes contour color
   * @param change If false, no {@link visad.ControlEvent} is fired.
   * @throws RemoteException
   * @throws VisADException
   */
  public void setLabelColor(byte[] color, boolean change) throws RemoteException, VisADException {
    labelColor = color;
    colorSet = true;
    changeControl(change);
  }
  
  //BMF 2006-10-04
  /**
   * Gets the label color.
   * @return label color as RGB array, null if not set
   */
  public byte[] getLabelColor() {
    return labelColor;
  }

  /**
   * Set the contour label Font
   * @param font can be java.awt.Font or visad.util.HersheyFont.  The former are rendered as
   *             filled polygons which can exhibit co-planar artifacts, the latter as lines
   *             which will always be visible, front or back, on co-planar surfaces.  Can be
   *             null, the default,  which results in the Times Roman HersheyFont. 
   * @throws VisADException
   * @throws RemoteException
   */
  
  public void setLabelFont(Object font) throws RemoteException, VisADException {
    synchronized(this) {
      labelFont = font;
    }
    changeControl(true);
  }

  /**
   * Get contour label font
   * @return label font
   */
  
  public Object getLabelFont() {
    return labelFont;
  }

  /**
   * set label frequency 
   * @param freq  how many labels to attempt per line
   * @throws VisADException  a VisAD error occurred
   * @throws RemoteException  an RMI error occurred
   */
  
  public void setLabelFreq(int freq)
  throws VisADException, RemoteException {
	  synchronized (this) {
		  // bounds check the setter value, if out of range set to default
		  if ((labelFreq < LABEL_FREQ_LO) || (labelFreq > LABEL_FREQ_HI)) {
			  labelFreq = LABEL_FREQ_LO;
		  } else {
			  labelFreq = freq;
		  }
	  }
	  changeControl(true);
  }

  /**
   * @return label frequency (number per line)
   */
  
  public int getLabelFreq() {
    return labelFreq;
  }
  
  /**
   * @return the everyNth
   */
  
  public int getEveryNth() {
	  return everyNth;
  }

  /**
   * Set how often we label lines 
   * @param lineCount  how many lines to skip before next labeled line
   * @throws VisADException  a VisAD error occurred
   * @throws RemoteException  an RMI error occurred
   */
  
  public void setEveryNth(int lineCount) throws VisADException, RemoteException {
	  synchronized (this) {
		  // bounds check the setter value, if out of range set to bounds
		  if (lineCount < 1) {
			  everyNth = EVERY_NTH_DEFAULT;
		  } else if (lineCount > EVERY_NTH_MAX) {
			  everyNth = EVERY_NTH_MAX;
		  } else {
			  everyNth = lineCount;
		  }
	  }
	  changeControl(true);
  }

/**
   * set size for label auto-size
   * @param factor  new size for label auto-size
   * @throws VisADException  a VisAD error occurred
   * @throws RemoteException  an RMI error occurred
   */
  
  public void setLabelSize(double factor)
  throws VisADException, RemoteException {
	  synchronized (this) {
		  labelSizeFactor = factor;
	  }
	  changeControl(true);
  }

  /**
   * @return size for label auto-size
   */
  public double getLabelSize() {
    return labelSizeFactor;
  }
  
  /**
   * Get the line style for lines that are styled.
   * @return One of the line style constants from GraphicsModeControl.
   */
  public int getDashedStyle() {
	  return lineStyle;
  }
  
  /**
   * Set the line style to apply to dashed lines.
   * @param style One of the line style constants from GraphicsModeControl.
   * @throws RemoteException
   * @throws VisADException
   */
  public void setDashedStyle(int style) 
  	throws RemoteException, VisADException {
  	lineStyle = style;
  	changeControl(true);
  }

  /**
   * @return String representation of this ContourControl
   */
  public synchronized String getSaveString() {
    return mainContours + " " + labels + " " + surfaceValue + " " +
      contourInterval + " " + lowLimit + " " + hiLimit + " " + base;
  }

  /**
   * reconstruct this ContourControl using the specified save string
   * @param save - String representation for reconstruction
   * @throws VisADException if a VisAD error occurs
   * @throws RemoteException if an RMI error occurs
   */
  public void setSaveString(String save)
    throws VisADException, RemoteException
  {
    if (save == null) throw new VisADException("Invalid save string");
    StringTokenizer st = new StringTokenizer(save);
    if (st.countTokens() < 7) throw new VisADException("Invalid save string");
    boolean[] b = new boolean[2];
    float[] f = new float[5];
    for (int i=0; i<2; i++) b[i] = Convert.getBoolean(st.nextToken());
    for (int i=0; i<5; i++) f[i] = Convert.getFloat(st.nextToken());
    setMainContours(b, f, false, true);
  }

  /**
   * remove previous projListener from pcntrl, and save cl as
   * new projListener
   * @param cl  new ControlListener for projListener
   * @param pcntrl ProjectionControl
   */
  public void addProjectionControlListener(ControlListener cl,
                                           ProjectionControl pcntrl)
  {
    pcntrl.removeControlListener(projListener);
    projListener = cl;
  }

  /**
   * copy the state of a remote control to this control
   * @param rmt remote Control whose state is copied
   * @throws VisADException if a VisAD error occurs
   */
  public void syncControl(Control rmt)
        throws VisADException
  {
    if (rmt == null) {
      throw new VisADException("Cannot synchronize " + getClass().getName() +
                               " with null Control object");
    }

    if (!(rmt instanceof ContourControl)) {
      throw new VisADException("Cannot synchronize " + getClass().getName() +
                               " with " + rmt.getClass().getName());
    }

    ContourControl cc = (ContourControl )rmt;

    boolean changed = false;

    synchronized(this) {
      synchronized(cc) {
        if (mainContours != cc.mainContours) {
          changed = true;
          mainContours = cc.mainContours;
        }
        if (!Util.isApproximatelyEqual(surfaceValue, cc.surfaceValue)) {
          changed = true;
          surfaceValue = cc.surfaceValue;
        }

        if (!Util.isApproximatelyEqual(contourInterval, cc.contourInterval)) {
          changed = true;
          contourInterval = cc.contourInterval;
        }
        if (!Util.isApproximatelyEqual(lowLimit, cc.lowLimit)) {
          changed = true;
          lowLimit = cc.lowLimit;
        }
        if (!Util.isApproximatelyEqual(hiLimit, cc.hiLimit)) {
          changed = true;
          hiLimit = cc.hiLimit;
        }
        if (!Util.isApproximatelyEqual(base, cc.base)) {
          changed = true;
          base = cc.base;
        }

        if (labels != cc.labels) {
          changed = true;
          labels = cc.labels;
        }
        if (arithmeticProgression != cc.arithmeticProgression) {
          changed = true;
          arithmeticProgression = cc.arithmeticProgression;
        }

        if (cc.levels == null) {
          if (levels != null) {
            changed = true;
            levels = null;
          }
        } else {
          // make sure array lengths match
          if (levels == null || levels.length != cc.levels.length) {
            changed = true;
            levels = new float[cc.levels.length];
            for (int i = 0; i < levels.length; i++) {
              levels[i] = 0;
            }
          }
          // copy remote values
          for (int i = 0; i < levels.length; i++) {
            if (!Util.isApproximatelyEqual(levels[i], cc.levels[i])) {
              changed = true;
              levels[i] = cc.levels[i];
            }
          }
        }

        if (dash != cc.dash) {
          changed = true;
          dash = cc.dash;
        }

        if (horizontalContourSlice != cc.horizontalContourSlice) {
          changed = true;
          horizontalContourSlice = cc.horizontalContourSlice;
        }
        if (verticalContourSlice != cc.verticalContourSlice) {
          changed = true;
          verticalContourSlice = cc.verticalContourSlice;
        }

        if (!Util.isApproximatelyEqual(horizontalSliceLow,
                                       cc.horizontalSliceLow))
        {
          changed = true;
          horizontalSliceLow = cc.horizontalSliceLow;
        }
        if (!Util.isApproximatelyEqual(horizontalSliceHi, cc.horizontalSliceHi)) {
          changed = true;
          horizontalSliceHi = cc.horizontalSliceHi;
        }
        if (!Util.isApproximatelyEqual(horizontalSliceStep,
                                       cc.horizontalSliceStep))
        {
          changed = true;
          horizontalSliceStep = cc.horizontalSliceStep;
        }
        if (!Util.isApproximatelyEqual(verticalSliceLow, cc.verticalSliceLow)) {
          changed = true;
          verticalSliceLow = cc.verticalSliceLow;
        }
        if (!Util.isApproximatelyEqual(verticalSliceHi, cc.verticalSliceHi)) {
          changed = true;
          verticalSliceHi = cc.verticalSliceHi;
        }
        if (!Util.isApproximatelyEqual(verticalSliceStep, cc.verticalSliceStep)) {
          changed = true;
          verticalSliceStep = cc.verticalSliceStep;
        }

        if (contourFill != cc.contourFill) {
          changed = true;
          contourFill = cc.contourFill;
        }

        if (autoSizeLabels != cc.autoSizeLabels) {
          changed = true;
          autoSizeLabels = cc.autoSizeLabels;
        }

        if (labelSizeFactor != cc.labelSizeFactor) {
          changed = true;
          labelSizeFactor = cc.labelSizeFactor;
        }

        if (alignLabels != cc.alignLabels) {
          changed = true;
          alignLabels = cc.alignLabels;
        }

        if (labelFont != cc.labelFont) {
          changed = true;
          labelFont = cc.labelFont;
        }

      }
    }

    if (changed) {
      try {
      /**
       * The following method is "alien" because it is outside the control of
       * this class.  If it were invoked in a synchronized block, then deadlock
       * could occur if the method waits on a thread that it creates that
       * calls back into this class and tries to obtain a lock (it's happened
       * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua
       * Bloch's "Effective Java" for more information.
       */
        changeControl(true);
      } catch (RemoteException re) {
        throw new VisADException("Could not indicate that control" +
                                 " changed: " + re.getMessage());
      }
    }
  }

  /**
   * Indicates whether or not this instance equals an Object
   * @param o  an Object
   * @return true if and only if this instance is equal to o
   */
  public boolean equals(Object o)
  {
    if (!super.equals(o)) {
      return false;
    }

    ContourControl cc = (ContourControl )o;

    synchronized(this) {
      synchronized(cc) {
        if (mainContours != cc.mainContours) {
          return false;
        }
        if (!Util.isApproximatelyEqual(surfaceValue, cc.surfaceValue)) {
          return false;
        }

        if (!Util.isApproximatelyEqual(contourInterval, cc.contourInterval)) {
          return false;
        }
        if (!Util.isApproximatelyEqual(lowLimit, cc.lowLimit)) {
          return false;
        }
        if (!Util.isApproximatelyEqual(hiLimit, cc.hiLimit)) {
          return false;
        }
        if (!Util.isApproximatelyEqual(base, cc.base)) {
          return false;
        }

        if (labels != cc.labels) {
          return false;
        }
        if (arithmeticProgression != cc.arithmeticProgression) {
          return false;
        }

        if (levels == null) {
          if (cc.levels != null) {
            return false;
          }
        } else {
          // make sure array lengths match
          if (cc.levels == null || levels.length != cc.levels.length) {
            return false;
          }
          // copy remote values
          for (int i = 0; i < levels.length; i++) {
            if (!Util.isApproximatelyEqual(levels[i], cc.levels[i])) {
              return false;
            }
          }
        }

        if (dash != cc.dash) {
          return false;
        }

        if (horizontalContourSlice != cc.horizontalContourSlice) {
          return false;
        }
        if (verticalContourSlice != cc.verticalContourSlice) {
          return false;
        }

        if (!Util.isApproximatelyEqual(horizontalSliceLow,
                                       cc.horizontalSliceLow))
        {
          return false;
        }
        if (!Util.isApproximatelyEqual(horizontalSliceHi, cc.horizontalSliceHi)) {
          return false;
        }
        if (!Util.isApproximatelyEqual(horizontalSliceStep,
                                       cc.horizontalSliceStep))
        {
          return false;
        }
        if (!Util.isApproximatelyEqual(verticalSliceLow, cc.verticalSliceLow)) {
          return false;
        }
        if (!Util.isApproximatelyEqual(verticalSliceHi, cc.verticalSliceHi)) {
          return false;
        }
        if (!Util.isApproximatelyEqual(verticalSliceStep, cc.verticalSliceStep)) {
          return false;
        }

        if (contourFill != cc.contourFill) {
          return false;
        }

        if (autoSizeLabels != cc.autoSizeLabels) {
          return false;
        }

        if (alignLabels != cc.alignLabels) {
          return false;
        }

        if (labelFont != cc.labelFont) {
          return false;
        }

        if (!Util.isApproximatelyEqual(labelSizeFactor, cc.labelSizeFactor)) {
          return false;
        }
      }
    }

    return true;
  }

  /**
   * @return a copy of this ContourControl
   */
  public synchronized Object clone()
  {
    ContourControl cc = (ContourControl )super.clone();
    if (levels != null) {
      cc.levels = (float[] )levels.clone();
    }
    cc.lineStyle = lineStyle;

    return cc;
  }

  //BMF 2006-10-04 added contourFill condition
  /**
   * If zoom scale has changed sufficiently, re-transform in
   * order to recompute labels. 
   * 
   * No action is taken if {@link #setContourFill(boolean)} has been set.
   *  
   * @throws VisADException if a VisAD error occurs
   * @throws RemoteException if an RMI error occurs
   */
  public void reLabel() throws VisADException, RemoteException {   
    if (zoom == null || contourFill) return;
    zoom.reLabel(ratio);
  }

  class ZoomDoneListener implements DisplayListener
  {
    ContourControl    c_cntrl;
    ProjectionControl p_cntrl;
    MouseBehavior     mouse;
    double            last_scale;

    ZoomDoneListener(ContourControl c_cntrl, ProjectionControl p_cntrl,
                      MouseBehavior mouse, double scale) {
      this.c_cntrl = c_cntrl;
      this.p_cntrl = p_cntrl;
      this.mouse   = mouse;
      last_scale   = scale;
    }

    public void displayChanged(DisplayEvent de)
           throws VisADException, RemoteException
    {
      if (de.getId() == DisplayEvent.MOUSE_RELEASED_LEFT ||
          de.getId() == DisplayEvent.MOUSE_RELEASED_RIGHT) {
        reLabel(ratio);
      }
    }
     
    public void reLabel(double ratio) 
           throws VisADException, RemoteException {
      if (!c_cntrl.contourFilled() && autoSizeLabels) {
        double[] matrix        = p_cntrl.getMatrix();
        double[] rot           = new double[3];
        double[] trans         = new double[3];
        double[] scale         = new double[1];
        mouse.instance_unmake_matrix(rot, scale, trans, matrix);
        if (scale[0]/last_scale > ratio ||
            scale[0]/last_scale < 1/ratio) { //- re-label
          if (labels) c_cntrl.changeControl(true);
          last_scale = scale[0];
        }
      }
    }
  }


  /**
   * End this control (called by ScalarMap.nullDisplay()). Override
   * to remove zoom control listener.
   */
  public void nullControl() {
    if (projListener != null) {
      pcntrl.removeControlListener(projListener);
    }
    getDisplay().removeDisplayListener(zoom);
    zoom = null;
    super.nullControl();
  }

}

 

Ads help maintain this website.