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