// // ContourWidget.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.util; /* AWT packages */ import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; /* JFC packages */ import javax.swing.*; import javax.swing.event.*; /* RMI classes */ import java.rmi.RemoteException; /* VisAD packages */ import visad.*; /** A widget that allows users to control iso-contours.<P> */ public class ContourWidget extends JPanel implements ActionListener, ChangeListener, ItemListener, ControlListener, ScalarMapListener { /** This ContourRangeSlider's associated Control. */ private ContourControl control; private float cInterval; private float cBase; private float cSurface; private float cLo; private float cHi; private String name; private JTextField Interval; private JTextField Base; private JLabel SurfaceLabel; private JSlider Surface; private JCheckBox Labels; private JCheckBox Contours; private JCheckBox Dashed; private ContourRangeSlider ContourRange; private JCheckBox Fill; /** construct a ContourWidget linked to the Control in the map (which must be to Display.IsoContour), with default interval, base, min, max, and surface value, and auto-scaling min and max. */ public ContourWidget(ScalarMap smap) throws VisADException, RemoteException { this(smap, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, true); } /** construct a ContourWidget linked to the Control in the map (which must be to Display.IsoContour), with specified surface value, and default interval, min, max, and base, and auto-scaling min and max. */ public ContourWidget(ScalarMap smap, float surf) throws VisADException, RemoteException { this(smap, Float.NaN, Float.NaN, Float.NaN, Float.NaN, surf, true); } /** construct a ContourWidget linked to the Control in the map (which must be to Display.IsoContour), with specified interval and base, default surface value, min, and max, and auto-scaling min and max. */ public ContourWidget(ScalarMap smap, float interv, float min, float max, float ba) throws VisADException, RemoteException { this(smap, interv, min, max, ba, Float.NaN, true); } /** construct a ContourWidget linked to the Control in the map (which must be to Display.IsoContour), with specified interval, minimum, maximum, base, surface value, and auto-scale behavior. */ public ContourWidget(ScalarMap smap, float interv, float min, float max, float ba, float surf, boolean update) throws VisADException, RemoteException { // verify scalar map if (!Display.IsoContour.equals(smap.getDisplayScalar())) { throw new DisplayException("ContourWidget: ScalarMap must " + "be to Display.IsoContour"); } name = smap.getScalarName(); // get control settings control = (ContourControl) smap.getControl(); boolean[] flags = new boolean[2]; float[] values = new float[5]; control.getMainContours(flags, values); // initialize flags from control settings boolean contourFlag, labelFlag, dashFlag, fillFlag; contourFlag = flags[0]; labelFlag = flags[1]; dashFlag = (values[1] < 0.0f); fillFlag = control.contourFilled(); // use either parameter value or (if param val is NaN) control value boolean setSurface = false; boolean setInterval = false; if (surf == surf) { cSurface = surf; setSurface = true; } else { cSurface = values[0]; } if (interv == interv) { cInterval = interv; setInterval = true; } else { cInterval = values[1]; } if (min == min) { cLo = min; setInterval = true; } else { cLo = values[2]; } if (max == max) { cHi = max; setInterval = true; } else { cHi = values[3]; } if (ba == ba) { cBase = ba; setInterval = true; } else { cBase = values[4]; } // create JPanels JPanel top = new JPanel(); JPanel mid = new JPanel(); JPanel low = new JPanel(); // set up layouts setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); top.setLayout(new BoxLayout(top, BoxLayout.X_AXIS)); mid.setLayout(new BoxLayout(mid, BoxLayout.X_AXIS)); low.setLayout(new BoxLayout(low, BoxLayout.X_AXIS)); // create JComponents Contours = new JCheckBox("contours", contourFlag); Labels = new JCheckBox("labels", labelFlag); Dashed = new JCheckBox("dashed lines below base", dashFlag); JLabel intLabel = new JLabel("interval:"); Interval = new JTextField("---"); Fill = new JCheckBox("fill", fillFlag); // WLH 2 Dec 98 Dimension msize = Interval.getMaximumSize(); Dimension psize = Interval.getPreferredSize(); msize.height = psize.height; Interval.setMaximumSize(msize); JLabel baseLabel = new JLabel("base:"); Base = new JTextField("---"); // WLH 2 Dec 98 msize = Base.getMaximumSize(); psize = Base.getPreferredSize(); msize.height = psize.height; Base.setMaximumSize(msize); SurfaceLabel = new JLabel(name + " = ---"); Surface = new JSlider(); ContourRange = new ContourRangeSlider(smap, cLo, cHi, this, update); if (!update) { if (setSurface) { control.setSurfaceValue(cSurface); } if (setInterval) { control.setContourInterval(cInterval, cLo, cHi, cBase); } updateWidgetSurface(); updateWidgetRange(); } // set label foregrounds intLabel.setForeground(Color.black); baseLabel.setForeground(Color.black); SurfaceLabel.setForeground(Color.black); // align JComponents Dashed.setAlignmentX(JCheckBox.CENTER_ALIGNMENT); SurfaceLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT); // add listeners Interval.addActionListener(this); Interval.setActionCommand("interval"); Base.addActionListener(this); Base.setActionCommand("base"); Dimension d = new Dimension(Integer.MAX_VALUE, SurfaceLabel.getMaximumSize().height); SurfaceLabel.setMaximumSize(d); SurfaceLabel.setPreferredSize(d); Surface.addChangeListener(this); Labels.addItemListener(this); Contours.addItemListener(this); Fill.addItemListener(this); Dashed.addItemListener(this); control.addControlListener(this); smap.addScalarMapListener(this); // set up JComponents' tool tips Contours.setToolTipText("Toggle contours"); Labels.setToolTipText("Toggle iso-contour labels (2-D only)"); Fill.setToolTipText("Solid filled contours (2-D only)"); Dashed.setToolTipText("Toggle dashed lines below base value (2-D only)"); String s = "Specify the iso-contouring interval (2-D only)"; intLabel.setToolTipText(s); Interval.setToolTipText(s); String t = "Specify the iso-contouring base value (2-D only)"; baseLabel.setToolTipText(t); Base.setToolTipText(t); String u = "Specify the iso-level value (3-D only)"; SurfaceLabel.setToolTipText(u); Surface.setToolTipText(u); // lay out JComponents top.add(Contours); top.add(Labels); top.add(Fill); mid.add(intLabel); mid.add(Interval); mid.add(Box.createRigidArea(new Dimension(10, 0))); mid.add(baseLabel); mid.add(Base); low.add(Box.createRigidArea(new Dimension(10, 0))); low.add(SurfaceLabel); add(top); add(Dashed); add(mid); add(low); add(Surface); add(ContourRange); } private double sliderScale; void setSliderBounds(float min, float max) { sliderScale = 1000/(max-min); Surface.setMinimum((int) (sliderScale*min)); Surface.setMaximum((int) (sliderScale*max)); } void setMinMax(float min, float max) throws VisADException, RemoteException { if (!Util.isApproximatelyEqual(cLo, min) || !Util.isApproximatelyEqual(cHi, max)) { cLo = min; cHi = max; control.setContourLimits(cLo, cHi); } } private void detectValues(double[] range) throws VisADException, RemoteException { boolean[] bval = new boolean[2]; float[] fval = new float[5]; control.getMainContours(bval, fval); boolean setSurface = false; if (fval[0] == fval[0] && !Util.isApproximatelyEqual(cSurface, fval[0])) { cSurface = fval[0]; setSurface = true; } else if (!Util.isApproximatelyEqual(cSurface, (float )range[0])) { cSurface = (float )range[0]; setSurface = true; } if (setSurface) { control.setSurfaceValue(cSurface); } if (!Util.isApproximatelyEqual(cInterval, fval[1]) || !Util.isApproximatelyEqual(cLo, fval[2]) || !Util.isApproximatelyEqual(cHi, fval[3]) || !Util.isApproximatelyEqual(cBase, fval[4])) { cInterval = fval[1]; cLo = fval[2]; cHi = fval[3]; cBase = fval[4]; control.setContourInterval(cInterval, cLo, cHi, cBase); } updateWidgetSurface(); updateWidgetRange(); } synchronized private void updateWidgetSurface() throws VisADException, RemoteException { if (cSurface == cSurface) { Surface.setEnabled(true); int val; double tmp = sliderScale * cSurface; if (tmp < 0) { val = (int )(tmp - 0.5); } else { val = (int )(tmp + 0.5); } Surface.setValue(val); SurfaceLabel.setText(name + " = " + PlotText.shortString(cSurface)); } else { Surface.setEnabled(false); SurfaceLabel.setText(name + " = ---"); } } synchronized private void updateWidgetRange() throws VisADException, RemoteException { if (cInterval == cInterval && cBase == cBase) { Interval.setEnabled(true); Interval.setText(PlotText.shortString(Math.abs(cInterval))); Base.setEnabled(true); Base.setText("" + PlotText.shortString(cBase)); } else { Interval.setEnabled(false); Interval.setText("---"); Base.setEnabled(false); Base.setText("---"); } } /** ActionListener method for JTextFields. */ public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if (cmd.equals("interval")) { float interv = Float.NaN; try { interv = Float.valueOf(Interval.getText()).floatValue(); } catch (NumberFormatException exc) { Interval.setText(PlotText.shortString(Math.abs(cInterval))); } if (interv == interv && interv >= 0.0f) { if (cInterval < 0.0f) interv = -interv; try { control.setContourInterval(interv, cLo, cHi, cBase); cInterval = interv; } catch (VisADException exc) { Interval.setText(PlotText.shortString(Math.abs(cInterval))); } catch (RemoteException exc) { Interval.setText(PlotText.shortString(Math.abs(cInterval))); } } else Interval.setText(PlotText.shortString(Math.abs(cInterval))); } if (cmd.equals("base")) { float ba = Float.NaN; try { ba = Float.valueOf(Base.getText()).floatValue(); } catch (NumberFormatException exc) { Base.setText(PlotText.shortString(cBase)); } if (ba == ba) { try { control.setContourInterval(cInterval, cLo, cHi, ba); cBase = ba; } catch (VisADException exc) { Base.setText(PlotText.shortString(cBase)); } catch (RemoteException exc) { Base.setText(PlotText.shortString(cBase)); } } } } /** ChangeListener method for JSlider. */ public void stateChanged(ChangeEvent e) { float newVal = (float) (Surface.getValue()/sliderScale); if (!Util.isApproximatelyEqual(cSurface, newVal)) { cSurface = newVal; SurfaceLabel.setText(name + " = " + PlotText.shortString(cSurface)); try { control.setSurfaceValue(cSurface); } catch (VisADException exc) { } catch (RemoteException exc) { } } } /** ItemListener method for JCheckBoxes. */ public void itemStateChanged(ItemEvent e) { Object o = e.getItemSelectable(); boolean on = (e.getStateChange() == ItemEvent.SELECTED); if (o == Labels) { try { control.enableLabels(on); } catch (VisADException exc) { } catch (RemoteException exc) { } } if (o == Contours) { try { control.enableContours(on); } catch (VisADException exc) { } catch (RemoteException exc) { } } if (o == Dashed) { cInterval = -cInterval; try { control.setContourInterval(cInterval, cLo, cHi, cBase); } catch (VisADException exc) { } catch (RemoteException exc) { } } if (o == Fill) { try { control.setContourFill(on); } catch (VisADException exc) { } catch (RemoteException exc) { } } } /** ControlListener method for ContourControl. */ public void controlChanged(ControlEvent e) throws VisADException, RemoteException { boolean[] bvals = new boolean[2]; float[] fvals = new float[5]; control.getMainContours(bvals, fvals); if (Contours.isSelected() != bvals[0]) { Contours.setSelected(bvals[0]); } if (Labels.isSelected() != bvals[1]) { Labels.setSelected(bvals[1]); } float interval = fvals[1]; boolean dashedState = (interval < 0.0f); if (Dashed.isSelected() != dashedState) { Dashed.setSelected(dashedState); } float cInt; if (dashedState != (cInterval < 0.0f)) { cInt = -cInterval; } else { cInt = cInterval; } if (!Util.isApproximatelyEqual(interval, cInt)) { if (interval < 0.0f) interval = -interval; Interval.setText(PlotText.shortString(interval)); cInterval = fvals[1]; } if (!Util.isApproximatelyEqual(fvals[4], cBase)) { Base.setText(PlotText.shortString(fvals[4])); cBase = fvals[4]; } if (!Util.isApproximatelyEqual(fvals[0], cSurface)) { cSurface = fvals[0]; updateWidgetSurface(); } if (!Util.isApproximatelyEqual(fvals[2], cLo) || !Util.isApproximatelyEqual(fvals[3], cHi)) { cLo = fvals[2]; cHi = fvals[3]; updateWidgetRange(); ContourRange.setValues(cLo, cHi); } } private Dimension prefSize = null; /** Make ContourWidget appear decent-sized */ public Dimension getPreferredSize() { if (prefSize == null) { prefSize = new Dimension(300, super.getPreferredSize().height); } return prefSize; } /** Set ContourWidget size */ public void setPreferredSize(Dimension dim) { prefSize = dim; } /** Do-nothing method; <CODE>ContourRangeSlider</CODE> handles map data */ public void mapChanged(ScalarMapEvent e) { } /** Deal with changes to the <CODE>ScalarMap</CODE> control */ public void controlChanged(ScalarMapControlEvent evt) throws RemoteException, VisADException { int id = evt.getId(); if (control != null && (id == ScalarMapEvent.CONTROL_REMOVED || id == ScalarMapEvent.CONTROL_REPLACED)) { evt.getControl().removeControlListener(this); } if (id == ScalarMapEvent.CONTROL_REPLACED || id == ScalarMapEvent.CONTROL_ADDED) { control = (ContourControl )(evt.getScalarMap().getControl()); controlChanged(new ControlEvent(control)); control.addControlListener(this); } } /** Subclass of RangeSlider for selecting min and max values.<P> */ class ContourRangeSlider extends RangeSlider implements ScalarMapListener { ContourWidget pappy; ContourRangeSlider(ScalarMap smap, float min, float max, ContourWidget dad, boolean update) throws VisADException, RemoteException { super(RangeSlider.nameOf(smap), min, max); pappy = dad; // set auto-scaling enabled (listen for new min and max) if (update) smap.addScalarMapListener(this); } /** ScalarMapListener method used with delayed auto-scaling. */ public void mapChanged(ScalarMapEvent e) { ScalarMap s = e.getScalarMap(); ContourControl cc = (ContourControl )s.getControl(); double[] range = s.getRange(); try { minLimit = (float) range[0]; maxLimit = (float) range[1]; if (!Util.isApproximatelyEqual(cLo, minLimit) || !Util.isApproximatelyEqual(cHi, maxLimit)) { cLo = minLimit; cHi = maxLimit; } pappy.setSliderBounds(minLimit, maxLimit); pappy.detectValues(range); float[] lhb = new float[3]; boolean[] dashes = new boolean[1]; float[] lvls = cc.getLevels(lhb, dashes); setValues(lhb[0], lhb[1]); } catch (VisADException exc) { } catch (RemoteException exc) { } } /** Do-nothing method; <CODE>ContourWidget</CODE> handles map control */ public void controlChanged(ScalarMapControlEvent evt) { } /** tell parent when the value changes */ public void valuesUpdated() { try { pappy.setMinMax(minValue, maxValue); } catch (VisADException exc) { } catch (RemoteException exc) { } } } }