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 */
028
029package edu.wisc.ssec.mcidasv.control;
030
031import java.awt.BorderLayout;
032import java.awt.Color;
033import java.awt.Container;
034import java.awt.FlowLayout;
035import java.awt.event.ActionListener;
036import java.awt.event.KeyEvent;
037import java.awt.event.KeyListener;
038import java.rmi.RemoteException;
039import java.util.*;
040
041import edu.wisc.ssec.mcidasv.servermanager.LocalEntryEditor;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045import javax.swing.*;
046import javax.swing.border.LineBorder;
047
048import org.python.core.PyObject;
049
050import ucar.unidata.ui.LatLonLabelPanel;
051import visad.ConstantMap;
052import visad.Data;
053import visad.VisADException;
054import visad.georef.MapProjection;
055
056import ucar.unidata.data.DataChoice;
057import ucar.unidata.data.DataSource;
058import ucar.unidata.data.DirectDataChoice;
059import ucar.unidata.idv.MapViewManager;
060import ucar.unidata.util.GuiUtils;
061import ucar.unidata.util.LogUtil;
062import ucar.unidata.view.geoloc.MapProjectionDisplay;
063
064import edu.wisc.ssec.mcidasv.Constants;
065import edu.wisc.ssec.mcidasv.McIDASV;
066import edu.wisc.ssec.mcidasv.control.LinearCombo.Combination;
067import edu.wisc.ssec.mcidasv.control.LinearCombo.Selector;
068import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset;
069import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
070import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralDataSource;
071import edu.wisc.ssec.mcidasv.display.hydra.MultiSpectralDisplay;
072import edu.wisc.ssec.mcidasv.jython.Console;
073import edu.wisc.ssec.mcidasv.jython.ConsoleCallback;
074
075public class HydraCombo extends HydraControl {
076    
077    private MultiSpectralDisplay display;
078    
079    private DataChoice dataChoice = null;
080    
081    private CombinationPanel comboPanel;
082    
083    private final Hashtable<String, Object> persistable = new Hashtable<>();
084    
085    private MultiSpectralDataSource source;
086    
087    float init_wavenumber;
088    
089    private Map<String, Selector> selectorMap = new HashMap<>();
090    
091    private static final String defaultButtonLabel = "Compute New Field";
092    private static final String NEW_FIELD = "New channel combination has been added to Field Selector";
093    private static final String EMPTY_MESSAGE = " ";
094    private JButton computeButton = new JButton(defaultButtonLabel);
095    protected JLabel notificationLabel = new JLabel();
096    
097    public HydraCombo() {
098        setHelpUrl("idv.controls.hydra.channelcombinationcontrol");
099    }
100    
101    @Override public boolean init(final DataChoice choice)
102        throws VisADException, RemoteException {
103        ((McIDASV)getIdv()).getMcvDataManager().setHydraControl(choice, this);
104        dataChoice = choice;
105        List<DataSource> sources = new ArrayList<DataSource>();
106        choice.getDataSources(sources);
107        source = ((MultiSpectralDataSource)sources.get(0));
108        
109        Float fieldSelectorChannel = (Float)getDataSelection().getProperty(Constants.PROP_CHAN);
110        if (fieldSelectorChannel == null) {
111            fieldSelectorChannel = 0f;
112        }
113        init_wavenumber = fieldSelectorChannel;
114        
115        display = new MultiSpectralDisplay((DirectDataChoice)choice);
116        display.setWaveNumber(fieldSelectorChannel);
117        display.setDisplayControl(this);
118        
119        ((McIDASV)getIdv()).getMcvDataManager().setHydraDisplay(choice, display);
120        
121        comboPanel = new CombinationPanel(this);
122        if (!persistable.isEmpty()) {
123            comboPanel.unpersistData(persistable);
124            persistable.clear();
125        }
126        return true;
127    }
128    
129    @Override public void initDone() {
130        MapViewManager viewManager = (MapViewManager)getViewManager();
131        MapProjectionDisplay dispMaster =
132            (MapProjectionDisplay) viewManager.getMaster();
133        try {
134            dispMaster.setMapProjection(getDataProjection());
135        } catch (Exception e) {
136            logException("problem setting MapProjection", e);
137        }
138        getIdv().getIdvUIManager().showDashboard();
139    }
140    
141    public Hashtable<String, Object> getPersistable() {
142        return comboPanel.persistData();
143    }
144    
145    public void setPersistable(final Hashtable<String, Object> table) {
146        persistable.clear();
147        persistable.putAll(table);
148    }
149    
150    @Override public MapProjection getDataProjection() {
151        MapProjection mp = null;
152        Map<String, double[]> subset = null;
153        Hashtable table = dataChoice.getProperties();
154        MultiDimensionSubset dataSel =
155            (MultiDimensionSubset) table.get(MultiDimensionSubset.key);
156        if (dataSel != null) {
157            subset = dataSel.getSubset();
158        }
159        mp = source.getDataProjection(subset);
160        return mp;
161    }
162    
163    @Override public Container doMakeContents() {
164        JTabbedPane pane = new JTabbedPane();
165        pane.add("Channel Combination Tool", GuiUtils.inset(getComboTab(), 5));
166        GuiUtils.handleHeavyWeightComponentsInTabs(pane);
167        return pane;
168    }
169    
170    public void updateComboPanel(final String id, final float value) {
171        if (comboPanel != null) {
172            comboPanel.updateSelector(id, value);
173        }
174    }
175    
176    protected void enableSelectorForWrapper(final SelectorWrapper wrapper) {
177        if (comboPanel != null) {
178            comboPanel.enableSelector(wrapper, false);
179        }
180    }
181    
182    protected void disableSelectorForWrapper(final SelectorWrapper wrapper) {
183        if (comboPanel != null) {
184            comboPanel.disableSelector(wrapper, false);
185        }
186    }
187    
188    protected MultiSpectralDisplay getMultiSpectralDisplay() {
189        return display;
190    }
191    
192    protected JButton getComputeButton() {
193        return computeButton;
194    }
195    
196    private JComponent getComboTab() {
197        computeButton.addActionListener(e -> {
198            // proceed with computation only if operators
199            // are defined correctly
200            if (!comboPanel.queueCombination()) return;
201            computeButton.setEnabled(false);
202            showWaitCursor();
203        });
204        // wrap compute button and notification label in JPanels to retain preferred sizes
205        JPanel buttonPanel = new JPanel(new BorderLayout());
206        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
207        JPanel botPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
208        topPanel.add(notificationLabel);
209        botPanel.add(computeButton);
210        buttonPanel.add(topPanel, BorderLayout.NORTH);
211        buttonPanel.add(botPanel, BorderLayout.SOUTH);
212        return GuiUtils.topCenterBottom(display.getDisplayComponent(), comboPanel.getPanel(), buttonPanel);
213    }
214    
215    public void addCombination(final String name, final Data combination) {
216        if (combination != null) {
217            source.addChoice(name, combination);
218        }
219        notificationLabel.setText(NEW_FIELD);
220    }
221    
222    public void addCombination(final Combination combination) {
223        if (combination != null) {
224            source.addChoice(combination.getName(), combination.getData());
225        }
226        notificationLabel.setText(NEW_FIELD);
227    }
228    
229    protected void addSelector(final Selector selector) throws Exception {
230        ConstantMap[] mapping = selector.getColor();
231        float r = Double.valueOf(mapping[0].getConstant()).floatValue();
232        float g = Double.valueOf(mapping[1].getConstant()).floatValue();
233        float b = Double.valueOf(mapping[2].getConstant()).floatValue();
234        Color javaColor = new Color(r, g, b);
235        display.createSelector(selector.getId(), javaColor);
236        display.setSelectorValue(selector.getId(), selector.getWaveNumber());
237        selectorMap.put(selector.getId(), selector);
238    }
239    
240    public void moveSelector(final String id, final float wavenum) {
241        if (selectorMap.containsKey(id)) {
242            display.updateControlSelector(id, wavenum);
243        }
244    }
245    
246    public void updateSelector(final String id, final float wavenum) {
247        if (selectorMap.containsKey(id)) {
248            selectorMap.get(id).setWaveNumber(wavenum);
249        }
250    }
251    
252    public enum DataType { HYPERSPECTRAL, MULTISPECTRAL }
253    
254    public enum WrapperState { ENABLED, DISABLED }
255    
256    public static class CombinationPanel implements ConsoleCallback {
257        private final SelectorWrapper a;
258        private final SelectorWrapper b;
259        private final SelectorWrapper c;
260        private final SelectorWrapper d;
261        
262        private final OperationXY ab;
263        private final OperationXY cd;
264        
265        private final CombineOperations abcd;
266        
267        private final MultiSpectralDisplay display;
268
269        private static final Logger logger = LoggerFactory.getLogger(HydraCombo.class);
270        
271        private final HydraCombo control;
272
273        private final Console console;
274        
275        private final Map<String, Selector> selectorMap = new HashMap<>();
276        private final Map<String, SelectorWrapper> wrapperMap = new HashMap<>();
277        
278        private final DataType dataType;
279        
280        public CombinationPanel(final HydraCombo control) {
281            this.control = control;
282            display = control.getMultiSpectralDisplay();
283            if (display == null) {
284                throw new NullPointerException("Display hasn't been initialized");
285            }
286            MultiSpectralData data = display.getMultiSpectralData();
287            if (data.hasBandNames()) {
288                dataType = DataType.MULTISPECTRAL;
289            } else {
290                dataType = DataType.HYPERSPECTRAL;
291            }
292            
293            this.console = new Console();
294            console.setCallbackHandler(this);
295            
296            a = makeWrapper("a", Color.RED);
297            b = makeWrapper("b", Color.GREEN);
298            c = makeWrapper("c", Color.BLUE);
299            d = makeWrapper("d", Color.MAGENTA);
300            
301            enableSelector(a, true);
302            
303            ab = new OperationXY(this, a, b);
304            cd = new OperationXY(this, c, d);
305            
306            abcd = new CombineOperations(this, ab, cd);
307        }
308        
309        public void ranBlock(final String line) {
310            PyObject jythonObj = console.getJythonObject("combo");
311//            if (jythonObj instanceof PyJavaInstance) {
312                Object combination = jythonObj.__tojava__(Object.class);
313                if (combination instanceof Combination) {
314                    control.addCombination((Combination)combination);
315                    control.getComputeButton().setEnabled(true);
316                    control.showNormalCursor();
317                }
318//            }
319        }
320        
321        public void updateSelector(final String id, final float channel) {
322            if (selectorMap.containsKey(id)) {
323                Selector selector = selectorMap.get(id);
324                selector.setWaveNumber(channel);
325                wrapperMap.get(id).update();
326            }
327        }
328        
329        protected void addSelector(final Selector selector, final boolean enabled) throws Exception {
330            String id = selector.getId();
331            if (enabled) {
332                display.createSelector(id, selector.getColor());
333                display.setSelectorValue(id, selector.getWaveNumber());
334            }
335            selectorMap.put(id, selector);
336        }
337        
338        protected void disableSelector(final SelectorWrapper wrapper, final boolean disableWrapper) {
339            if (disableWrapper) {
340                wrapper.disable();
341            }
342            try {
343                display.removeSelector(wrapper.getSelector().getId());
344            } catch (Exception e) {
345                LogUtil.logException("HydraCombo.disableSelector", e);
346            }
347        }
348        
349        protected void enableSelector(final SelectorWrapper wrapper, final boolean enableWrapper) {
350            if (enableWrapper) {
351                wrapper.enable();
352            }
353            try {
354                Selector selector = wrapper.getSelector();
355                String id = selector.getId();
356                display.createSelector(id, selector.getColor());
357                display.setSelectorValue(id, selector.getWaveNumber());
358            } catch (Exception e) {
359                LogUtil.logException("HydraCombo.disableSelector", e);
360            }
361        }
362        
363        private SelectorWrapper makeWrapper(final String var, final Color color) {
364            try {
365                ConstantMap[] mappedColor = MultiSpectralDisplay.makeColorMap(color);
366                
367                SelectorWrapper tmp;
368                if (dataType == DataType.HYPERSPECTRAL) {
369                    tmp = new HyperspectralSelectorWrapper(var, mappedColor, control, console);
370                } else {
371                    tmp = new MultispectralSelectorWrapper(var, mappedColor, control, console);
372                }
373                addSelector(tmp.getSelector(), false);
374                wrapperMap.put(tmp.getSelector().getId(), tmp);
375//                console.injectObject(var, new PyJavaInstance(tmp.getSelector()));
376                console.injectObject(var, tmp.getSelector());
377                return tmp;
378            } catch (Exception e) { 
379                LogUtil.logException("HydraCombo.makeWrapper", e);
380            }
381            return null;
382        }
383        
384        public JPanel getPanel() {
385            JPanel panel = new JPanel(new FlowLayout());
386            panel.add(new JLabel("("));
387            panel.add(a.getPanel());
388            panel.add(ab.getBox());
389            panel.add(b.getPanel());
390            panel.add(new JLabel(")"));
391            panel.add(abcd.getBox());
392            panel.add(new JLabel("("));
393            panel.add(c.getPanel());
394            panel.add(cd.getBox());
395            panel.add(d.getPanel());
396            panel.add(new JLabel(")"));
397            return panel;
398        }
399
400        private void showUndefinedOperatorsBox(String messageModifier) {
401            JOptionPane.showMessageDialog(
402                    new JFrame(),
403                    messageModifier,
404                    "Missing Operator",
405                    JOptionPane.WARNING_MESSAGE
406            );
407        }
408        
409        public boolean queueCombination() {
410            String notDefined = "-----";
411            // McIDAS Inquiry #3078-3141
412
413            if (!a.getValue().equals(notDefined) && !b.getValue().equals(notDefined)) {
414                if (ab.getBox().getSelectedIndex() == 4) {
415                    showUndefinedOperatorsBox("Expected an operator between arguments #1 and #2");
416                    return false;
417                }
418            }
419
420            if (!c.getValue().equals(notDefined)) {
421                if (abcd.getBox().getSelectedIndex() == 4) {
422                    showUndefinedOperatorsBox("Expected an operator between arguments #2 and #3");
423                    return false;
424                }
425            }
426
427            if (!c.getValue().equals(notDefined) && !d.getValue().equals(notDefined)) {
428                if (cd.getBox().getSelectedIndex() == 4) {
429                    showUndefinedOperatorsBox("Expected an operator between arguments #3 and #4");
430                    return false;
431                }
432            }
433
434            String jy = "combo="+abcd.getJython();
435            System.err.println("jython=" + jy);
436            console.queueLine(jy);
437            return true;
438        }
439        
440        public Hashtable<String, Object> persistData() {
441            Hashtable<String, Object> table = new Hashtable<String, Object>();
442            
443            table.put("a", a.persistSelectorWrapper());
444            table.put("b", b.persistSelectorWrapper());
445            table.put("c", c.persistSelectorWrapper());
446            table.put("d", d.persistSelectorWrapper());
447            table.put("ab", ab.getOperation());
448            table.put("cd", cd.getOperation());
449            table.put("abcd", abcd.getOperation());
450            return table;
451        }
452        
453        public void unpersistData(final Hashtable<String, Object> table) {
454            Hashtable<String, String> tableA = (Hashtable<String, String>)table.get("a");
455            Hashtable<String, String> tableB = (Hashtable<String, String>)table.get("b");
456            Hashtable<String, String> tableC = (Hashtable<String, String>)table.get("c");
457            Hashtable<String, String> tableD = (Hashtable<String, String>)table.get("d");
458            
459            a.unpersistSelectorWrapper(tableA);
460            b.unpersistSelectorWrapper(tableB);
461            c.unpersistSelectorWrapper(tableC);
462            d.unpersistSelectorWrapper(tableD);
463            
464            String opAb = (String)table.get("ab");
465            String opCd = (String)table.get("cd");
466            String opAbcd = (String)table.get("abcd");
467            
468            ab.setOperation(opAb);
469            cd.setOperation(opCd);
470            abcd.setOperation(opAbcd);
471        }
472    }
473    
474    private static class OperationXY {
475        private static final String[] OPERATIONS = { "+", "-", "/", "*", " " };
476        private static final String INVALID_OP = " ";
477        private final JComboBox<String> combo = new JComboBox<>(OPERATIONS);
478        
479        private CombinationPanel comboPanel;
480        private SelectorWrapper x;
481        private SelectorWrapper y;
482        
483        public OperationXY(final CombinationPanel comboPanel, final SelectorWrapper x, final SelectorWrapper y) {
484            this.x = x;
485            this.y = y;
486            this.comboPanel = comboPanel;
487            combo.setSelectedItem(" ");
488            combo.addActionListener(e -> {
489                // Blank out any previous notifications
490                comboPanel.control.notificationLabel.setText(EMPTY_MESSAGE);
491                if (Objects.equals(getOperation(), " ")) {
492                    comboPanel.disableSelector(y, true);
493                } else {
494                    comboPanel.enableSelector(y, true);
495                }
496            });
497        }
498        
499        public void disable() {
500            comboPanel.disableSelector(x, true);
501            combo.setSelectedItem(INVALID_OP);
502            comboPanel.disableSelector(y, true);
503        }
504        
505        public void enable() {
506            comboPanel.enableSelector(x, true);
507        }
508        
509        public String getOperation() {
510            return (String)combo.getSelectedItem();
511        }
512        
513        public void setOperation(final String operation) {
514            combo.setSelectedItem(operation);
515        }
516        
517        public JComboBox<String> getBox() {
518            return combo;
519        }
520        
521        public String getJython() {
522            String operation = getOperation();
523            if (Objects.equals(operation, INVALID_OP)) {
524                operation = "";
525            }
526            String jython = x.getJython() + operation + y.getJython();
527            if (x.isValid() && y.isValid()) {
528                return '(' + jython + ')';
529            }
530            return jython;
531        }
532    }
533    
534    private static class CombineOperations {
535        private static final String[] OPERATIONS = { "+", "-", "/", "*", " "};
536        private static final String INVALID_OP = " ";
537        private final JComboBox<String> combo = new JComboBox<>(OPERATIONS);
538        
539        private OperationXY x;
540        private OperationXY y;
541        
542        public CombineOperations(final CombinationPanel comboPanel, final OperationXY x, final OperationXY y) {
543            this.x = x;
544            this.y = y;
545            combo.setSelectedItem(INVALID_OP);
546            combo.addActionListener(e -> {
547                // Blank out any previous notifications
548                comboPanel.control.notificationLabel.setText(EMPTY_MESSAGE);
549                if (Objects.equals(getOperation(), " ")) {
550                    y.disable();
551                } else {
552                    y.enable();
553                }
554            });
555        }
556        
557        public String getOperation() {
558            return (String)combo.getSelectedItem();
559        }
560        
561        public void setOperation(final String operation) {
562            combo.setSelectedItem(operation);
563        }
564        
565        public JComboBox<String> getBox() {
566            return combo;
567        }
568        
569        public String getJython() {
570            String operation = getOperation();
571            if (Objects.equals(operation, INVALID_OP)) {
572                operation = "";
573            }
574            return x.getJython() + operation + y.getJython();
575        }
576    }
577    
578    private static abstract class SelectorWrapper {
579        protected static final String BLANK = "-----";
580        private String variable;
581        private final ConstantMap[] color;
582        protected final Selector selector;
583        protected final MultiSpectralDisplay display;
584        protected final MultiSpectralData data;
585        protected final JTextField scale = new JTextField(String.format("%3.1f", 1.0));
586        protected WrapperState currentState = WrapperState.DISABLED;
587        
588        public SelectorWrapper(final String variable, final ConstantMap[] color, final HydraCombo control, final Console console) {
589            this.display = control.getMultiSpectralDisplay();
590            this.data = control.getMultiSpectralDisplay().getMultiSpectralData();
591            this.variable = variable;
592            this.color = color;
593            this.selector = new Selector(control.init_wavenumber, color, control, console);
594            this.selector.addName(variable);
595        }
596        
597        public Selector getSelector() {
598            return selector;
599        }
600        
601        public JPanel getPanel() {
602            JPanel panel = new JPanel(new FlowLayout());
603            JComponent comp = getWavenumberComponent();
604            float r = Double.valueOf(color[0].getConstant()).floatValue();
605            float g = Double.valueOf(color[1].getConstant()).floatValue();
606            float b = Double.valueOf(color[2].getConstant()).floatValue();
607            comp.setBorder(new LineBorder(new Color(r, g, b), 2));
608            panel.add(scale);
609            panel.add(comp);
610            return panel;
611        }
612        
613        public String getJython() {
614            String result = "";
615            if (isValid()) {
616                result = '(' + scale.getText() + '*' + variable + ')';
617            }
618            return result;
619        }
620        
621        public boolean isValid() {
622            return !Objects.equals(getValue(), BLANK);
623        }
624        
625        public void enable() {
626            setValue(Float.toString(selector.getWaveNumber()));
627            currentState = WrapperState.ENABLED;
628        }
629        
630        public void disable() {
631            setValue(BLANK);
632            currentState = WrapperState.DISABLED;
633        }
634        
635        public void update() {
636            setValue(Float.toString(selector.getWaveNumber()));
637        }
638        
639        public Hashtable<String, String> persistSelectorWrapper() {
640            Hashtable<String, String> table = new Hashtable<>(3);
641            String scaleText = scale.getText();
642            String waveText = getValue();
643            
644            if (scaleText == null || scaleText.isEmpty()) {
645                scaleText = "1.0";
646            }
647            if (waveText == null || waveText.isEmpty() || !isValid()) {
648                waveText = BLANK;
649            }
650            
651            table.put("variable", variable);
652            table.put("scale", scale.getText());
653            table.put("wave", getValue());
654            return table;
655        }
656        
657        public void unpersistSelectorWrapper(final Hashtable<String, String> table) {
658            variable = table.get("variable");
659            selector.addName(variable);
660            scale.setText(String.format("%3.1f", Float.valueOf(table.get("scale"))));
661            
662            String waveText = table.get("wave");
663            if (Objects.equals(waveText, BLANK)) {
664                disable();
665            } else {
666                float wave = Float.valueOf(table.get("wave"));
667                selector.setWaveNumber(wave);
668                if (isValid()) {
669                    update();
670                }
671            }
672        }
673        
674        public abstract JComponent getWavenumberComponent();
675        
676        public abstract void setValue(final String value);
677        
678        public abstract String getValue();
679    }
680    
681    private static class HyperspectralSelectorWrapper extends SelectorWrapper {
682        private final JTextField wavenumber;
683        public HyperspectralSelectorWrapper(final String variable, final ConstantMap[] color, final HydraCombo control, final Console console) {
684            super(variable, color, control, console);
685            wavenumber = new JTextField(BLANK, 7);
686            wavenumber.addActionListener(e -> {
687                // Blank out any previous notifications
688                control.notificationLabel.setText(EMPTY_MESSAGE);
689                String textVal = wavenumber.getText();
690                if (!Objects.equals(textVal, BLANK)) {
691                    float wave = Float.parseFloat(textVal.trim());
692                    control.updateComboPanel(getSelector().getId(), wave);
693                }
694            });
695            // TJJ Jan 2017 - clear any previous notifications on any UI changes
696            // This requires a key listener on the scale fields
697            scale.addKeyListener(new KeyListener() {
698                @Override
699                public void keyTyped(KeyEvent ke) {
700                    // Blank out any previous notifications
701                    control.notificationLabel.setText(EMPTY_MESSAGE);
702                }
703                @Override
704                public void keyPressed(KeyEvent ke) {
705                    // Nothing needed here
706                }
707                @Override
708                public void keyReleased(KeyEvent ke) {
709                    // Nothing needed here
710                }
711            });
712        }
713        
714        @Override public JComponent getWavenumberComponent() {
715            return wavenumber;
716        }
717        
718        @Override public void setValue(final String value) {
719            if (value == null) {
720                throw new NullPointerException("");
721            }
722            if (!Objects.equals(value, BLANK)) {
723                float wave = Float.parseFloat(value);
724                String fmt = String.format("%7.2f", wave);
725                wavenumber.setText(fmt);
726            }
727        }
728        
729        public String getValue() {
730            return wavenumber.getText();
731        }
732    }
733    
734    private static class MultispectralSelectorWrapper extends SelectorWrapper {
735        
736        private final JComboBox<String> bands;
737        
738        public MultispectralSelectorWrapper(final String variable, final ConstantMap[] color, final HydraCombo control, final Console console) {
739            super(variable, color, control, console);
740            removeMSDListeners();
741            bands = bigBadBox(control);
742        }
743        
744        private void removeMSDListeners() {
745            JComboBox box = display.getBandSelectComboBox();
746            for (ActionListener l : box.getActionListeners()) {
747                box.removeActionListener(l);
748            }
749        }
750        
751        private JComboBox<String> bigBadBox(final HydraCombo control) {
752            final JComboBox<String> box = new JComboBox<>();
753            box.addItem(BLANK);
754            
755            for (String name : data.getBandNames()) {
756                box.addItem(name);
757            }
758            final SelectorWrapper wrapper = this;
759            box.addActionListener(e -> {
760                // Blank out any previous notifications
761                control.notificationLabel.setText(EMPTY_MESSAGE);
762                String selected = (String)box.getSelectedItem();
763                float wave = Float.NaN;
764                if (!Objects.equals(selected, BLANK)) {
765                    Map<String, Float> map = data.getBandNameMap();
766                    if (map.containsKey(selected)) {
767                        wave = map.get(selected);
768                    }
769                    if (currentState == WrapperState.DISABLED) {
770                        control.enableSelectorForWrapper(wrapper);
771                    }
772                    control.updateComboPanel(getSelector().getId(), wave);
773                } else {
774                    control.disableSelectorForWrapper(wrapper);
775                }
776            });
777            
778            return box;
779        }
780        
781        @Override public JComponent getWavenumberComponent() {
782            return bands;
783        }
784        
785        @Override public void setValue(final String value) {
786            if (value == null) {
787                throw new NullPointerException();
788            }
789            if (!Objects.equals(value, BLANK)) {
790                String name = data.getBandNameFromWaveNumber(Float.parseFloat(value));
791                bands.setSelectedItem(name);
792            } else {     
793                bands.setSelectedItem(BLANK);
794            }
795        }
796        
797        @Override public String getValue() {
798            return (String)bands.getSelectedItem();
799        }
800    }
801}