001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2025
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028package edu.wisc.ssec.mcidasv.control;
029
030import edu.wisc.ssec.mcidasv.data.hydra.ImageRGBDisplayable;
031
032import net.miginfocom.swing.MigLayout;
033
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036import ucar.unidata.data.DataChoice;
037import ucar.unidata.data.DataSelection;
038import ucar.unidata.idv.ViewManager;
039import ucar.unidata.idv.control.DisplayControlImpl;
040import ucar.unidata.util.ColorTable;
041import ucar.unidata.util.LogUtil;
042import ucar.visad.display.DisplayMaster;
043
044import visad.*;
045import visad.georef.MapProjection;
046
047import javax.swing.Box;
048import javax.swing.JButton;
049import javax.swing.JCheckBox;
050import javax.swing.JLabel;
051import javax.swing.JPanel;
052import javax.swing.JTextField;
053
054import java.awt.Container;
055import java.awt.FlowLayout;
056import java.rmi.RemoteException;
057import java.util.Hashtable;
058import java.util.Iterator;
059
060public class RGBCompositeControl extends DisplayControlImpl {
061
062    public final static String FORMULA_IN_PROGRESS_FLAG = "Formula_Active";
063    private static final Logger logger = LoggerFactory.getLogger(RGBCompositeControl.class);
064    /** Displayable for the data */
065    private ImageRGBDisplayable imageDisplay;
066
067    private DisplayMaster displayMaster;
068
069    private ScalarMap redMap = null;
070    private ScalarMap grnMap = null;
071    private ScalarMap bluMap = null;
072
073    float[][] redTable = null;
074    float[][] grnTable = null;
075    float[][] bluTable = null;
076
077    final private double[] redRange = { Double.NaN, Double.NaN };
078    final private double[] grnRange = { Double.NaN, Double.NaN };
079    final private double[] bluRange = { Double.NaN, Double.NaN };
080
081    final double[] initRedRange = { Double.NaN, Double.NaN };
082    final double[] initGrnRange = { Double.NaN, Double.NaN };
083    final double[] initBluRange = { Double.NaN, Double.NaN };
084
085    private FieldImpl imageField = null;
086    private MapProjection mapProjection = null;
087
088    private static final double DEFAULT_GAMMA = 1.0d;
089    private double gamma = DEFAULT_GAMMA;
090
091    private double redGamma = DEFAULT_GAMMA;
092    private double grnGamma = DEFAULT_GAMMA;
093    private double bluGamma = DEFAULT_GAMMA;
094
095    private JCheckBox matchFieldsCbox = null;
096
097    private final JTextField gammaTxtFld =
098        new JTextField(Double.toString(gamma), 4);
099    private final JTextField redGammaTxtFld =
100        new JTextField(Double.toString(redGamma), 4);
101    private final JTextField grnGammaTxtFld =
102        new JTextField(Double.toString(grnGamma), 4);
103    private final JTextField bluGammaTxtFld =
104        new JTextField(Double.toString(bluGamma), 4);
105
106    private final JTextField redLowTxtFld =
107        new JTextField(Float.toString(1.0f), 10);
108    private final JTextField redHighTxtFld =
109        new JTextField(Float.toString(1.0f), 10);
110    private final JTextField grnLowTxtFld =
111        new JTextField(Float.toString(1.0f), 10);
112    private final JTextField grnHighTxtFld =
113        new JTextField(Float.toString(1.0f), 10);
114    private final JTextField bluLowTxtFld =
115        new JTextField(Float.toString(1.0f), 10);
116    private final JTextField bluHighTxtFld =
117        new JTextField(Float.toString(1.0f), 10);
118
119    public boolean init(DataChoice dataChoice) throws VisADException, RemoteException {
120
121        displayMaster = getViewManager().getMaster();
122        DataSelection dataSelection = getDataSelection();
123
124        // TJJ Jul 2014
125        // by sharing a property via the active View Manager, we can signal all three
126        // preview windows they are part of an in-progress RGB Composite. If so, it
127        // appears we need to use a shared HydraContext so our geographic coverage
128        // subset applies across channels.
129
130        Hashtable ht = getIdv().getViewManager().getProperties();
131        ht.put(FORMULA_IN_PROGRESS_FLAG, true);
132
133        imageField = (FieldImpl) dataChoice.getData(dataSelection);
134
135        imageDisplay = new ImageRGBDisplayable("rgb composite", null, false, imageField);
136
137        ht.put(FORMULA_IN_PROGRESS_FLAG, false);
138
139        Iterator iter = imageDisplay.getScalarMapSet().iterator();
140        while (iter.hasNext()) {
141            ScalarMap map = (ScalarMap) iter.next();
142            if (map.getScalarName().startsWith("redimage")) {
143                redMap = map;
144            }
145            if (map.getScalarName().startsWith("greenimage")) {
146                grnMap = map;
147            }
148            if (map.getScalarName().startsWith("blueimage")) {
149                bluMap = map;
150            }
151        }
152
153        if (checkRange()) { //- from unpersistence if true, initialize gui, ScalarMaps
154            double[] redRange = getRedRange();
155            double[] grnRange = getGrnRange();
156            double[] bluRange = getBluRange();
157
158            initRedRange[0] = redRange[0];
159            initRedRange[1] = redRange[1];
160            initGrnRange[0] = grnRange[0];
161            initGrnRange[1] = grnRange[1];
162            initBluRange[0] = bluRange[0];
163            initBluRange[1] = bluRange[1];
164
165            redLowTxtFld.setText(Float.toString((float)redRange[0]));
166            redHighTxtFld.setText(Float.toString((float)redRange[1]));
167            grnLowTxtFld.setText(Float.toString((float)grnRange[0]));
168            grnHighTxtFld.setText(Float.toString((float)grnRange[1]));
169            bluLowTxtFld.setText(Float.toString((float)bluRange[0]));
170            bluHighTxtFld.setText(Float.toString((float)bluRange[1]));
171
172            gammaTxtFld.setText(Float.toString((float)gamma));
173            redGammaTxtFld.setText(Float.toString((float)redGamma));
174            grnGammaTxtFld.setText(Float.toString((float)grnGamma));
175            bluGammaTxtFld.setText(Float.toString((float)bluGamma));
176
177            redMap.setRange(redRange[0], redRange[1]);
178            grnMap.setRange(grnRange[0], grnRange[1]);
179            bluMap.setRange(bluRange[0], bluRange[1]);
180        } else {
181            redMap.resetAutoScale();
182            grnMap.resetAutoScale();
183            bluMap.resetAutoScale();
184
185            redMap.addScalarMapListener(new ColorMapListener(redMap, initRedRange, redRange, redLowTxtFld, redHighTxtFld));
186            grnMap.addScalarMapListener(new ColorMapListener(grnMap, initGrnRange, grnRange, grnLowTxtFld, grnHighTxtFld));
187            bluMap.addScalarMapListener(new ColorMapListener(bluMap, initBluRange, bluRange, bluLowTxtFld, bluHighTxtFld));
188        }
189
190        setShowInDisplayList(true);
191
192        addDisplayable(imageDisplay, FLAG_COLORTABLE | FLAG_ZPOSITION);
193
194        return true;
195    }
196
197    public void initDone() {
198        while (true) {
199            if (null != redMap.getControl()) {
200                redTable = ((BaseColorControl) redMap.getControl()).getTable();
201                break;
202            }
203        }
204        while (true) {
205            if (null != grnMap.getControl()) {
206                grnTable = ((BaseColorControl) grnMap.getControl()).getTable();
207                break;
208            }
209        }
210
211        while (true) {
212            if (null != bluMap.getControl()) {
213                bluTable = ((BaseColorControl) bluMap.getControl()).getTable();
214                break;
215            }
216        }
217
218        float[][] newRedTbl = getZeroOutArray(redTable);
219        float[][] newGrnTbl = getZeroOutArray(grnTable);
220        float[][] newBluTbl = getZeroOutArray(bluTable);
221
222        for (int k=0; k<redTable[0].length; k++) {
223            newRedTbl[0][k] = (float) Math.pow(redTable[0][k], redGamma);
224            newGrnTbl[1][k] = (float) Math.pow(grnTable[1][k], grnGamma);
225            newBluTbl[2][k] = (float) Math.pow(bluTable[2][k], bluGamma);
226        }
227
228        try {
229            displayMaster.setDisplayInactive();
230            ((BaseColorControl)redMap.getControl()).setTable(newRedTbl);
231            ((BaseColorControl)grnMap.getControl()).setTable(newGrnTbl);
232            ((BaseColorControl)bluMap.getControl()).setTable(newBluTbl);
233            imageDisplay.loadData(imageField);
234            displayMaster.setDisplayActive();
235        } catch (Exception ex) {
236            LogUtil.logException("setDisplayInactive", ex);
237        }
238    }
239
240    public MapProjection getDataProjection() {
241        CoordinateSystem cs = null;
242        try {
243            if (imageField instanceof FlatField) {
244                cs = ((FunctionType)imageField.getType()).getDomain().getCoordinateSystem();
245            }
246            else if (imageField instanceof FieldImpl) {
247                Data dat = imageField.getSample(0, false);
248                if (dat instanceof FlatField) {
249                    FlatField img = (FlatField) dat;
250                    cs = ((FunctionType)img.getType()).getDomain().getCoordinateSystem();
251                }
252            }
253        }
254        catch (Exception ex) {
255            LogUtil.logException("problem accessing data", ex);
256        }
257
258        if (cs instanceof MapProjection) {
259            mapProjection = (MapProjection)cs;
260        }
261
262        return mapProjection;
263    }
264
265    boolean checkRange() {
266        return !(Double.isNaN(redRange[0]) || Double.isNaN(grnRange[0]) || Double.isNaN(bluRange[0]));
267    }
268
269    private void updateRedRange(double lo, double hi) {
270        redRange[0] = lo;
271        redRange[1] = hi;
272        redHighTxtFld.setText(Float.toString((float)hi));
273        redLowTxtFld.setText(Float.toString((float)lo));
274        try {
275            redMap.setRange(lo, hi);
276        } catch (VisADException | RemoteException ex) {
277            LogUtil.logException("redMap.setRange", ex);
278        }
279    }
280
281    public void setRedRange(double[] range) {
282        redRange[0] = range[0];
283        redRange[1] = range[1];
284    }
285
286    public double[] getRedRange() {
287        return new double[] {redRange[0], redRange[1]};
288    }
289
290    private void updateGrnRange(double lo, double hi) {
291        grnRange[0] = lo;
292        grnRange[1] = hi;
293        grnHighTxtFld.setText(Float.toString((float)hi));
294        grnLowTxtFld.setText(Float.toString((float)lo));
295        try {
296            grnMap.setRange(lo, hi);
297        } catch (VisADException | RemoteException ex) {
298            LogUtil.logException("grnMap.setRange", ex);
299        }
300    }
301
302    public void setGrnRange(double[] range) {
303        grnRange[0] = range[0];
304        grnRange[1] = range[1];
305    }
306
307    public double[] getGrnRange() {
308        return new double[] {grnRange[0], grnRange[1]};
309    }
310
311    private void updateBluRange(double lo, double hi) {
312        bluRange[0] = lo;
313        bluRange[1] = hi;
314        bluHighTxtFld.setText(Float.toString((float)hi));
315        bluLowTxtFld.setText(Float.toString((float)lo));
316        try {
317            bluMap.setRange(lo, hi);
318        } catch (VisADException | RemoteException ex) {
319            LogUtil.logException("bluMap.setRange", ex);
320        }
321    }
322
323    public void setBluRange(double[] range) {
324        bluRange[0] = range[0];
325        bluRange[1] = range[1];
326    }
327
328    public double[] getBluRange() {
329        return new double[] {bluRange[0], bluRange[1]};
330    }
331
332    public void setRedGamma(double gamma) {
333        redGamma = gamma;
334    }
335
336    public double getRedGamma() {
337        return redGamma;
338    }
339
340    public void setGrnGamma(double gamma) {
341        grnGamma = gamma;
342    }
343
344    public double getGrnGamma() {
345        return grnGamma;
346    }
347
348    public void setBluGamma(double gamma) {
349        bluGamma = gamma;
350    }
351
352    public double getBluGamma() {
353        return bluGamma;
354    }
355
356    public void setGamma(double gamma) {
357        this.gamma = gamma;
358    }
359
360    public double getGamma() {
361        return gamma;
362    }
363
364    /**
365     * TJJ - quick hack, just do something visually jarring to test path
366     */
367
368    /**
369     * Computes a Rayleigh scattering corrected 2D grid for visible range data.
370     *
371     * @param visibleDataGrid      2D grid of remote sensing data in the visible range
372     * (assuming it represents top-of-atmosphere radiance or reflectance).
373     * @param satelliteZenithGrid  2D grid of satellite zenith angles (in degrees).
374     * @param solarZenithGrid      2D grid of solar zenith angles (in degrees).
375     * @param satelliteAzimuthGrid 2D grid of satellite azimuth angles (in degrees).
376     * @param solarAzimuthGrid     2D grid of solar azimuth angles (in degrees).
377     * @param wavelengthVisible    Wavelength of the visible band (in micrometers).
378     * @param atmosphericPressure  Atmospheric pressure at the surface (in hPa).
379     * @return A 2D grid representing the Rayleigh scattering corrected data.
380     * @throws IllegalArgumentException if input grid dimensions are inconsistent.
381     */
382
383    public static FieldImpl correctRayleighVisible(FieldImpl visibleField,
384                                               FieldImpl satelliteZenithField,
385                                               FieldImpl solarZenithField,
386                                               FieldImpl satelliteAzimuthField,
387                                               FieldImpl solarAzimuthField,
388                                               double wavelengthVisible,
389                                               double atmosphericPressure)
390            throws VisADException, RemoteException {
391
392        double[][] visibleDataGrid = visibleField.getValues();
393        double[][] satelliteZenithGrid = satelliteZenithField.getValues();
394        double[][] solarZenithGrid = solarZenithField.getValues();
395        double[][] satelliteAzimuthGrid = satelliteAzimuthField.getValues();
396        double[][] solarAzimuthGrid = solarAzimuthField.getValues();
397
398        int rows = visibleDataGrid.length;
399        int cols = visibleDataGrid[0].length;
400
401        // float[][] correctedDataGrid = new float[rows][cols];
402
403        for (int i = 0; i < rows; i++) {
404            for (int j = 0; j < cols; j++) {
405                float thetaV = (float) Math.toRadians(satelliteZenithGrid[i][j]);
406                float thetaS = (float) Math.toRadians(solarZenithGrid[i][j]);
407                float phiV = (float) Math.toRadians(satelliteAzimuthGrid[i][j]);
408                float phiS = (float) Math.toRadians(solarAzimuthGrid[i][j]);
409
410                double rhoRayleigh = calculateRayleighReflectance(wavelengthVisible, thetaS, thetaV, phiS, phiV, atmosphericPressure);
411                visibleDataGrid[i][j] = (float) (visibleDataGrid[i][j] - rhoRayleigh);
412            }
413        }
414        visibleField.setSamples(visibleDataGrid);
415
416        logger.info("3141 - made it out");
417        return visibleField;
418    }
419
420    // Calculate Rayleigh reflectance - McIDAS Inquiry #3055-3141
421    private static double calculateRayleighReflectance(
422            double wavelength, double solarZenithRad, double satelliteZenithRad,
423            double solarAzimuthRad, double satelliteAzimuthRad, double pressureHPa) {
424
425        double tau_ro = (0.008569 / Math.pow(wavelength, 4))
426                        * (1 + (0.0113 / Math.pow(wavelength, 2)) + (0.00013 / Math.pow(wavelength, 4)));
427
428        // \tau_{ro} = \frac{0.008569}{\lambda^4} \cdot (1 + \frac{0.0113}{\lambda^2} + \frac{0.00013}{\lambda^4})
429
430        double pressure_rat = pressureHPa / 1013.25;
431        // P = \frac{pHPa}{atmP}
432
433        double tau = pressure_rat * tau_ro; // this is the only thing matters next
434        // \tau = P \cdot \tau_{ro}
435
436        double scattering_angle_cos = Math.cos(solarZenithRad) * Math.cos(satelliteZenithRad)
437                                    + Math.sin(solarZenithRad) * Math.sin(satelliteZenithRad)
438                                    * Math.cos(satelliteAzimuthRad - solarAzimuthRad);
439
440        // \cos (\Theta) = \cos (\theta_{sun})\cos (\theta_{sat}) + \sin (\theta_{sun})\sin (\theta_{sat}) \cdot \cos(\phi_{sat} - \phi_{sun})
441
442        double P_big_theta = 0.75 * (1 + Math.pow(scattering_angle_cos, 2)); // another important value
443        // P(\Theta) = 0.75 * (1 + (\cos(\Theta))^2)
444
445        double mu_1 = Math.cos(solarZenithRad);
446        double mu_2 = Math.cos(solarZenithRad);
447
448        return tau * P_big_theta * (1 / (4 * mu_1 * mu_2));
449        // \tau \cdot P(\Theta) \cdot (\frac{1}{4 \cdot \mu_1 \cdot \mu_2})
450    }
451
452    private void applyRayleighCorrection() {
453        float[][] newRedTbl = getZeroOutArray(redTable);
454        float[][] newGrnTbl = getZeroOutArray(grnTable);
455        float[][] newBluTbl = getZeroOutArray(bluTable);
456
457        for (int k = 0; k < redTable[0].length; k++) {
458            newRedTbl[0][k] = (float) Math.pow(redTable[0][k], 0.2);
459            newGrnTbl[1][k] = (float) Math.pow(grnTable[1][k], 0.2);
460            newBluTbl[2][k] = (float) Math.pow(bluTable[2][k], 0.2);
461        }
462        try {
463            displayMaster.setDisplayInactive();
464            ((BaseColorControl) redMap.getControl()).setTable(newRedTbl);
465            ((BaseColorControl) grnMap.getControl()).setTable(newGrnTbl);
466            ((BaseColorControl) bluMap.getControl()).setTable(newBluTbl);
467            displayMaster.setDisplayActive();
468        } catch (Exception e) {
469            LogUtil.logException("setDisplayInactive", e);
470        }
471    }
472
473    private void updateGamma(double gamma) {
474        setGamma(gamma);
475        setRedGamma(gamma);
476        setGrnGamma(gamma);
477        setBluGamma(gamma);
478        redGammaTxtFld.setText(Float.toString((float)gamma));
479        grnGammaTxtFld.setText(Float.toString((float)gamma));
480        bluGammaTxtFld.setText(Float.toString((float)gamma));
481
482        float[][] newRedTbl = getZeroOutArray(redTable);
483        float[][] newGrnTbl = getZeroOutArray(grnTable);
484        float[][] newBluTbl = getZeroOutArray(bluTable);
485
486        for (int k=0; k<redTable[0].length; k++) {
487            newRedTbl[0][k] = (float) Math.pow(redTable[0][k], gamma);
488            newGrnTbl[1][k] = (float) Math.pow(grnTable[1][k], gamma);
489            newBluTbl[2][k] = (float) Math.pow(bluTable[2][k], gamma);
490        }
491        try {
492            displayMaster.setDisplayInactive();
493            ((BaseColorControl)redMap.getControl()).setTable(newRedTbl);
494            ((BaseColorControl)grnMap.getControl()).setTable(newGrnTbl);
495            ((BaseColorControl)bluMap.getControl()).setTable(newBluTbl);
496            displayMaster.setDisplayActive();
497        } catch (Exception ex) {
498            LogUtil.logException("setDisplayInactive", ex);
499        }
500    }
501
502    private void updateRedGamma(double gamma) {
503        setRedGamma(gamma);
504
505        float[][] newRedTbl = getZeroOutArray(redTable);
506
507        for (int k=0; k<redTable[0].length; k++) {
508            newRedTbl[0][k] = (float) Math.pow(redTable[0][k], gamma);
509        }
510
511        try {
512            displayMaster.setDisplayInactive();
513            ((BaseColorControl)redMap.getControl()).setTable(newRedTbl);
514            displayMaster.setDisplayActive();
515        } catch (Exception ex) {
516            LogUtil.logException("setDisplayInactive", ex);
517        }
518    }
519
520    private void updateGrnGamma(double gamma) {
521        setGrnGamma(gamma);
522
523        float[][] newGrnTbl = getZeroOutArray(grnTable);
524        for (int k=0; k<grnTable[0].length; k++) {
525            newGrnTbl[1][k] = (float) Math.pow(grnTable[1][k], gamma);
526        }
527
528        try {
529            displayMaster.setDisplayInactive();
530            ((BaseColorControl)grnMap.getControl()).setTable(newGrnTbl);
531            displayMaster.setDisplayActive();
532        } catch (Exception ex) {
533            LogUtil.logException("setDisplayInactive", ex);
534        }
535    }
536
537    private void updateBluGamma(double gamma) {
538        setBluGamma(gamma);
539
540        float[][] newBluTbl = getZeroOutArray(bluTable);
541        for (int k=0; k<bluTable[0].length; k++) {
542            newBluTbl[2][k] = (float) Math.pow(bluTable[2][k], gamma);
543        }
544
545        try {
546            displayMaster.setDisplayInactive();
547            ((BaseColorControl)bluMap.getControl()).setTable(newBluTbl);
548            displayMaster.setDisplayActive();
549        } catch (Exception ex) {
550            LogUtil.logException("setDisplayInactive", ex);
551        }
552    }
553
554    public float[][] getZeroOutArray(float[][] array) {
555        float[][] newArray = new float[array.length][array[0].length];
556        for (int i=0; i<newArray.length; i++) {
557            for (int j=0; j<newArray[0].length; j++) {
558                newArray[i][j] = 0.0f;
559            }
560        }
561        return newArray;
562    }
563
564    protected ColorTable getInitialColorTable() {
565        return getDisplayConventions().getParamColorTable("image");
566    }
567
568    void setAllFields(String txtl1, String txtl2) {
569        Double l1 = Double.valueOf(txtl1.trim());
570        Double l2 = Double.valueOf(txtl2.trim());
571        bluRange[0] = l1;
572        bluRange[1] = l2;
573        redRange[0] = l1;
574        redRange[1] = l2;
575        grnRange[0] = l1;
576        grnRange[1] = l2;
577        updateRedRange(redRange[0], redRange[1]);
578        updateBluRange(redRange[0], redRange[1]);
579        updateGrnRange(redRange[0], redRange[1]);
580
581        redLowTxtFld.setText(txtl1);
582        grnLowTxtFld.setText(txtl1);
583        bluLowTxtFld.setText(txtl1);
584
585        redHighTxtFld.setText(txtl2);
586        bluHighTxtFld.setText(txtl2);
587        grnHighTxtFld.setText(txtl2);
588    }
589
590    public Container doMakeContents() {
591
592        JButton allGammaButton = new JButton("Apply to All Gamma Fields");
593        allGammaButton.addActionListener(e -> {
594            String tmp = gammaTxtFld.getText().trim();
595            updateGamma(Double.valueOf(tmp));
596        });
597
598        gammaTxtFld.addActionListener(e -> {
599            String tmp = gammaTxtFld.getText().trim();
600            updateGamma(Double.valueOf(tmp));
601        });
602
603        // McIDAS Inquiry #3193-3141
604        redLowTxtFld.addActionListener(e -> {
605            if (matchFieldsCbox.isSelected())
606                setAllFields(redLowTxtFld.getText(),redHighTxtFld.getText());
607
608            Double l1 = Double.valueOf(redLowTxtFld.getText().trim());
609            Double l2 = Double.valueOf(redHighTxtFld.getText().trim());
610            redRange[0] = l1;
611            redRange[1] = l2;
612            updateRedRange(redRange[0], redRange[1]);
613
614        });
615
616        redHighTxtFld.addActionListener(e -> {
617            if (matchFieldsCbox.isSelected())
618                setAllFields(redLowTxtFld.getText(),redHighTxtFld.getText());
619
620            Double l1 = Double.valueOf(redLowTxtFld.getText().trim());
621            Double l2 = Double.valueOf(redHighTxtFld.getText().trim());
622            redRange[0] = l1;
623            redRange[1] = l2;
624            updateRedRange(redRange[0], redRange[1]);
625        });
626
627        redGammaTxtFld.addActionListener(e -> {
628            String tmp = redGammaTxtFld.getText().trim();
629            updateRedGamma(Double.valueOf(tmp));
630
631            if (matchFieldsCbox.isSelected()) {
632                grnGammaTxtFld.setText(tmp);
633                bluGammaTxtFld.setText(tmp);
634                updateBluGamma(Double.valueOf(tmp));
635                updateGrnGamma(Double.valueOf(tmp));
636            }
637        });
638
639        JButton redReset = new JButton("Reset");
640        redReset.addActionListener(e -> {
641            updateRedRange(initRedRange[0], initRedRange[1]);
642            redRange[0] = initRedRange[0];
643            redRange[1] = initRedRange[1];
644            redLowTxtFld.setText(Float.toString((float)redRange[0]));
645            redHighTxtFld.setText(Float.toString((float)redRange[1]));
646            updateRedGamma(1.0);
647            redGammaTxtFld.setText("1.0");
648        });
649
650        grnLowTxtFld.addActionListener(e -> {
651            if (matchFieldsCbox.isSelected())
652                setAllFields(grnLowTxtFld.getText(),grnHighTxtFld.getText());
653
654            Double l1 = Double.valueOf(grnLowTxtFld.getText().trim());
655            Double l2 = Double.valueOf(grnHighTxtFld.getText().trim());
656            grnRange[0] = l1;
657            grnRange[1] = l2;
658            updateGrnRange(grnRange[0], grnRange[1]);
659        });
660
661        grnHighTxtFld.addActionListener(e -> {
662            if (matchFieldsCbox.isSelected())
663                setAllFields(grnLowTxtFld.getText(),grnHighTxtFld.getText());
664
665            Double l1 = Double.valueOf(grnLowTxtFld.getText().trim());
666            Double l2 = Double.valueOf(grnHighTxtFld.getText().trim());
667            grnRange[0] = l1;
668            grnRange[1] = l2;
669            updateGrnRange(grnRange[0], grnRange[1]);
670        });
671
672        grnGammaTxtFld.addActionListener(e -> {
673            String tmp = grnGammaTxtFld.getText().trim();
674            updateGrnGamma(Double.valueOf(tmp));
675
676            if (matchFieldsCbox.isSelected()) {
677                redGammaTxtFld.setText(tmp);
678                bluGammaTxtFld.setText(tmp);
679                updateRedGamma(Double.valueOf(tmp));
680                updateGrnGamma(Double.valueOf(tmp));
681            }
682        });
683
684        JButton grnReset = new JButton("Reset");
685        grnReset.addActionListener(e -> {
686            updateGrnRange(initGrnRange[0], initGrnRange[1]);
687            grnRange[0] = initGrnRange[0];
688            grnRange[1] = initGrnRange[1];
689            grnLowTxtFld.setText(Float.toString((float)grnRange[0]));
690            grnHighTxtFld.setText(Float.toString((float)grnRange[1]));
691            updateGrnGamma(1.0);
692            grnGammaTxtFld.setText("1.0");
693        });
694
695        bluLowTxtFld.addActionListener(e -> {
696            if (matchFieldsCbox.isSelected())
697                setAllFields(bluLowTxtFld.getText(),bluHighTxtFld.getText());
698
699            Double l1 = Double.valueOf(bluLowTxtFld.getText().trim());
700            Double l2 = Double.valueOf(bluHighTxtFld.getText().trim());
701            bluRange[0] = l1;
702            bluRange[1] = l2;
703            updateBluRange(bluRange[0], bluRange[1]);
704        });
705
706        bluHighTxtFld.addActionListener(e -> {
707            if (matchFieldsCbox.isSelected())
708                setAllFields(bluLowTxtFld.getText(),bluHighTxtFld.getText());
709
710            Double l1 = Double.valueOf(bluLowTxtFld.getText().trim());
711            Double l2 = Double.valueOf(bluHighTxtFld.getText().trim());
712            bluRange[0] = l1;
713            bluRange[1] = l2;
714            updateBluRange(bluRange[0], bluRange[1]);
715        });
716
717        bluGammaTxtFld.addActionListener(e -> {
718            String tmp = bluGammaTxtFld.getText().trim();
719            updateBluGamma(Double.valueOf(tmp));
720
721            if (matchFieldsCbox.isSelected()) {
722                grnGammaTxtFld.setText(tmp);
723                redGammaTxtFld.setText(tmp);
724                updateGrnGamma(Double.valueOf(tmp));
725                updateRedGamma(Double.valueOf(tmp));
726            }
727        });
728
729        JButton bluReset = new JButton("Reset");
730        bluReset.addActionListener(e -> {
731            updateBluRange(initBluRange[0], initBluRange[1]);
732            bluRange[0] = initBluRange[0];
733            bluRange[1] = initBluRange[1];
734            bluLowTxtFld.setText(Float.toString((float)bluRange[0]));
735            bluHighTxtFld.setText(Float.toString((float)bluRange[1]));
736            updateBluGamma(1.0);
737            bluGammaTxtFld.setText("1.0");
738        });
739
740        JButton applyButton = new JButton("Apply");
741        applyButton.addActionListener(e -> {
742            String redLow = redLowTxtFld.getText().trim();
743            String redHigh = redHighTxtFld.getText().trim();
744            updateRedRange(Double.valueOf(redLow), Double.valueOf(redHigh));
745            String grnLow = grnLowTxtFld.getText().trim();
746            String grnHigh = grnHighTxtFld.getText().trim();
747            updateGrnRange(Double.valueOf(grnLow), Double.valueOf(grnHigh));
748            String bluLow = bluLowTxtFld.getText().trim();
749            String bluHigh = bluHighTxtFld.getText().trim();
750            updateBluRange(Double.valueOf(bluLow), Double.valueOf(bluHigh));
751
752            String tmp1 = redGammaTxtFld.getText().trim();
753            updateRedGamma(Double.valueOf(tmp1));
754            String tmp2 = grnGammaTxtFld.getText().trim();
755            updateGrnGamma(Double.valueOf(tmp2));
756            String tmp3 = bluGammaTxtFld.getText().trim();
757            updateBluGamma(Double.valueOf(tmp3));
758        });
759
760        // McIDAS Inquiry #3193-3141
761        matchFieldsCbox = new JCheckBox();
762        matchFieldsCbox.setToolTipText("When enabled, changing a setting for one color changes the setting for all colors.");
763        JPanel topPanel = new JPanel(new MigLayout());
764        topPanel.add(new JLabel("Match fields: "));
765        topPanel.add(matchFieldsCbox, "wrap");
766        topPanel.add(new JLabel("Red Range: "));
767        topPanel.add(redLowTxtFld);
768        topPanel.add(redHighTxtFld);
769        topPanel.add(new JLabel("Red Gamma: "));
770        topPanel.add(redGammaTxtFld);
771        topPanel.add(redReset, "wrap");
772
773        topPanel.add(new JLabel("Green Range: "));
774        topPanel.add(grnLowTxtFld);
775        topPanel.add(grnHighTxtFld);
776        topPanel.add(new JLabel("Green Gamma: "));
777        topPanel.add(grnGammaTxtFld);
778        topPanel.add(grnReset, "wrap");
779
780        topPanel.add(new JLabel("Blue Range: "));
781        topPanel.add(bluLowTxtFld);
782        topPanel.add(bluHighTxtFld);
783        topPanel.add(new JLabel("Blue Gamma: "));
784        topPanel.add(bluGammaTxtFld);
785        topPanel.add(bluReset, "wrap");
786
787        topPanel.add(Box.createHorizontalStrut(2), "span 5");
788        topPanel.add(applyButton, "wrap");
789
790        JPanel bottomPanel = new JPanel(new MigLayout());
791        bottomPanel.add(new JLabel("Common Gamma: "));
792        bottomPanel.add(gammaTxtFld);
793        bottomPanel.add(allGammaButton, "wrap");
794        bottomPanel.add(new JLabel("Vertical Position: "));
795        bottomPanel.add(doMakeZPositionSlider());
796
797        JPanel mainPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
798        mainPanel.add(topPanel);
799        mainPanel.add(bottomPanel);
800
801        return mainPanel;
802    }
803
804    private class ColorMapListener implements ScalarMapListener {
805        ScalarMap clrMap;
806
807        double[] range = null;
808        double[] initRange = null;
809
810        JTextField lowTxtFld;
811        JTextField highTxtFld;
812
813        ColorMapListener(ScalarMap clrMap, double[] initRange, double[] range, JTextField lowTxtFld, JTextField highTxtFld) {
814            this.clrMap = clrMap;
815            this.lowTxtFld = lowTxtFld;
816            this.highTxtFld = highTxtFld;
817            this.range = range;
818            this.initRange = initRange;
819        }
820
821        public void controlChanged(ScalarMapControlEvent event) throws RemoteException, VisADException {
822        }
823
824        @Override
825        public void mapChanged(ScalarMapEvent event) throws RemoteException, VisADException {
826            if (event.getId() == ScalarMapEvent.AUTO_SCALE) {
827                double[] rng = clrMap.getRange();
828                boolean shouldRemove = false;
829                //Ghansham: decide whether it is first time. The cleaner way
830                if (!Double.isNaN(rng[0]) && !Double.isNaN(rng[1]) && Double.isNaN(initRange[0]) && Double.isNaN(initRange[1])) {
831                    shouldRemove = true;
832                }
833                range[0] = rng[0];
834                range[1] = rng[1];
835                initRange[0] = rng[0];
836                initRange[1] = rng[1];
837                lowTxtFld.setText(Float.toString((float)rng[0]));
838                highTxtFld.setText(Float.toString((float)rng[1]));
839                //Ghansham:If its first time remove the scalarmaplistener and setRange manually to disable autscaling of the scalarmap
840                if (shouldRemove) {
841                    clrMap.removeScalarMapListener(this);
842                    //-Lock out auto-scaling
843                    clrMap.disableAutoScale();
844                }
845            } else if (event.getId() == ScalarMapEvent.MANUAL) {
846                double[] rng = clrMap.getRange();
847                range[0] = rng[0];
848                range[1] = rng[1];
849            }
850        }
851    }
852}