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