001 /*
002 * $Id: MultiSpectralControl.java,v 1.66 2012/02/19 17:35:38 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 *
012 * All Rights Reserved
013 *
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.
016 *
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 *
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
025 * GNU Lesser Public License for more details.
026 *
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program. If not, see http://www.gnu.org/licenses.
029 */
030
031 package edu.wisc.ssec.mcidasv.control;
032
033 import java.awt.Color;
034 import java.awt.Component;
035 import java.awt.Container;
036 import java.awt.Dimension;
037 import java.awt.Graphics;
038 import java.awt.GridBagConstraints;
039 import java.awt.Insets;
040 import java.awt.Rectangle;
041 import java.awt.event.ActionEvent;
042 import java.awt.event.ActionListener;
043 import java.awt.event.MouseEvent;
044 import java.awt.geom.Rectangle2D;
045 import java.rmi.RemoteException;
046 import java.text.DecimalFormat;
047 import java.util.ArrayList;
048 import java.util.Collections;
049 import java.util.Hashtable;
050 import java.util.LinkedHashMap;
051 import java.util.List;
052 import java.util.Map;
053
054 import javax.swing.AbstractCellEditor;
055 import javax.swing.BorderFactory;
056 import javax.swing.JButton;
057 import javax.swing.JColorChooser;
058 import javax.swing.JComboBox;
059 import javax.swing.JComponent;
060 import javax.swing.JDialog;
061 import javax.swing.JLabel;
062 import javax.swing.JList;
063 import javax.swing.JPanel;
064 import javax.swing.JScrollPane;
065 import javax.swing.JTabbedPane;
066 import javax.swing.JTable;
067 import javax.swing.JTextField;
068 import javax.swing.ListCellRenderer;
069 import javax.swing.border.Border;
070 import javax.swing.event.ListSelectionEvent;
071 import javax.swing.event.ListSelectionListener;
072 import javax.swing.event.MouseInputListener;
073 import javax.swing.plaf.basic.BasicTableUI;
074 import javax.swing.table.AbstractTableModel;
075 import javax.swing.table.TableCellEditor;
076 import javax.swing.table.TableCellRenderer;
077
078 import visad.DataReference;
079 import visad.DataReferenceImpl;
080 import visad.FlatField;
081 import visad.RealTuple;
082 import visad.VisADException;
083 import visad.georef.MapProjection;
084
085 import ucar.unidata.data.DataChoice;
086 import ucar.unidata.data.DataSelection;
087 import ucar.unidata.idv.DisplayControl;
088 import ucar.unidata.idv.ViewManager;
089 import ucar.unidata.idv.control.ControlWidget;
090 import ucar.unidata.idv.control.WrapperWidget;
091 import ucar.unidata.util.ColorTable;
092 import ucar.unidata.util.GuiUtils;
093 import ucar.unidata.util.LogUtil;
094 import ucar.unidata.util.Range;
095 import ucar.visad.display.DisplayMaster;
096 import ucar.visad.display.DisplayableData;
097
098 import edu.wisc.ssec.mcidasv.Constants;
099 import edu.wisc.ssec.mcidasv.McIDASV;
100 import edu.wisc.ssec.mcidasv.data.hydra.HydraRGBDisplayable;
101 import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
102 import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralDataSource;
103 import edu.wisc.ssec.mcidasv.data.hydra.SpectrumAdapter;
104 import edu.wisc.ssec.mcidasv.display.hydra.MultiSpectralDisplay;
105 import edu.wisc.ssec.mcidasv.probes.ProbeEvent;
106 import edu.wisc.ssec.mcidasv.probes.ProbeListener;
107 import edu.wisc.ssec.mcidasv.probes.ReadoutProbe;
108 import edu.wisc.ssec.mcidasv.util.Contract;
109
110 public class MultiSpectralControl extends HydraControl {
111
112 private String PARAM = "BrightnessTemp";
113
114 private static final int DEFAULT_FLAGS =
115 FLAG_COLORTABLE | FLAG_ZPOSITION;
116
117 private MultiSpectralDisplay display;
118
119 private DisplayMaster displayMaster;
120
121 private final JTextField wavenumbox =
122 new JTextField(Float.toString(0f), 12);
123
124 final JTextField minBox = new JTextField(6);
125 final JTextField maxBox = new JTextField(6);
126
127 private final List<Hashtable<String, Object>> spectraProperties = new ArrayList<Hashtable<String, Object>>();
128 private final List<Spectrum> spectra = new ArrayList<Spectrum>();
129
130 private McIDASVHistogramWrapper histoWrapper;
131
132 private float rangeMin;
133 private float rangeMax;
134
135 // REALLY not thrilled with this...
136 private int probesSeen = 0;
137
138 // boring UI stuff
139 private final JTable probeTable = new JTable(new ProbeTableModel(this, spectra));
140 private final JScrollPane scrollPane = new JScrollPane(probeTable);
141 private final JButton addProbe = new JButton("Add Probe");
142 private final JButton removeProbe = new JButton("Remove Probe");
143
144 public MultiSpectralControl() {
145 super();
146 setHelpUrl("idv.controls.hydra.multispectraldisplaycontrol");
147 }
148
149 @Override public boolean init(final DataChoice choice)
150 throws VisADException, RemoteException
151 {
152 ((McIDASV)getIdv()).getMcvDataManager().setHydraControl(choice, this);
153 Hashtable props = choice.getProperties();
154 PARAM = (String) props.get(MultiSpectralDataSource.paramKey);
155
156 List<DataChoice> choices = Collections.singletonList(choice);
157 histoWrapper = new McIDASVHistogramWrapper("histo", choices, this);
158
159 Float fieldSelectorChannel =
160 (Float)getDataSelection().getProperty(Constants.PROP_CHAN);
161
162 display = new MultiSpectralDisplay(this);
163
164 if (fieldSelectorChannel != null) {
165 display.setWaveNumber(fieldSelectorChannel);
166 }
167
168 displayMaster = getViewManager().getMaster();
169
170 // map the data choice to display.
171 ((McIDASV)getIdv()).getMcvDataManager().setHydraDisplay(choice, display);
172
173 //- intialize the Displayable with data before adding to DisplayControl
174 DisplayableData imageDisplay = display.getImageDisplay();
175 FlatField image = display.getImageData();
176
177 float[] rngvals = (image.getFloats(false))[0];
178 float[] minmax = minmax(rngvals);
179 rangeMin = minmax[0];
180 rangeMax = minmax[1];
181
182 imageDisplay.setData(display.getImageData());
183 addDisplayable(imageDisplay, DEFAULT_FLAGS);
184
185 // put the multispectral display into the layer controls
186 addViewManager(display.getViewManager());
187
188 // tell the idv what options to give the user
189 setAttributeFlags(DEFAULT_FLAGS);
190
191 setProjectionInView(true);
192
193 // handle the user trying to add a new probe
194 addProbe.addActionListener(new ActionListener() {
195 public void actionPerformed(final ActionEvent e) {
196 addSpectrum(Color.YELLOW);
197 probeTable.revalidate();
198 }
199 });
200
201 // handle the user trying to remove an existing probe
202 removeProbe.addActionListener(new ActionListener() {
203 public void actionPerformed(final ActionEvent e) {
204 int index = probeTable.getSelectedRow();
205 if (index == -1)
206 return;
207
208 removeSpectrum(index);
209 }
210 });
211 removeProbe.setEnabled(false);
212
213 // set up the table. in particular, enable/disable the remove button
214 // depending on whether or not there is a selected probe to remove.
215 probeTable.setDefaultRenderer(Color.class, new ColorRenderer(true));
216 probeTable.setDefaultEditor(Color.class, new ColorEditor());
217 probeTable.setPreferredScrollableViewportSize(new Dimension(500, 200));
218 probeTable.setUI(new HackyDragDropRowUI());
219 probeTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
220 public void valueChanged(final ListSelectionEvent e) {
221 if (!probeTable.getSelectionModel().isSelectionEmpty())
222 removeProbe.setEnabled(true);
223 else
224 removeProbe.setEnabled(false);
225 }
226 });
227
228 setShowInDisplayList(false);
229
230 return true;
231 }
232
233 @Override public void initDone() {
234 try {
235 display.showChannelSelector();
236
237 // TODO: this is ugly.
238 Float fieldSelectorChannel =
239 (Float)getDataSelection().getProperty(Constants.PROP_CHAN);
240 if (fieldSelectorChannel == null)
241 fieldSelectorChannel = 0f;
242 handleChannelChange(fieldSelectorChannel, false);
243
244 displayMaster.setDisplayInactive();
245
246 // this if-else block is detecting whether or not a bundle is
247 // being loaded; if true, then we'll have a list of spectra props.
248 // otherwise just throw two default spectrums/probes on the screen.
249 if (!spectraProperties.isEmpty()) {
250 for (Hashtable<String, Object> table : spectraProperties) {
251 Color c = (Color)table.get("color");
252 Spectrum s = addSpectrum(c);
253 s.setProperties(table);
254 }
255 spectraProperties.clear();
256 } else {
257 addSpectra(Color.MAGENTA, Color.CYAN);
258 }
259 displayMaster.setDisplayActive();
260 } catch (Exception e) {
261 logException("MultiSpectralControl.initDone", e);
262 }
263 }
264
265 /**
266 * Overridden by McIDAS-V so that {@literal "hide"} probes when their display
267 * is turned off. Otherwise users can wind up with probes on the screen which
268 * aren't associated with any displayed data.
269 *
270 * @param on {@code true} if we're visible, {@code false} otherwise.
271 *
272 * @see DisplayControl#setDisplayVisibility(boolean)
273 */
274 @Override public void setDisplayVisibility(boolean on) {
275 super.setDisplayVisibility(on);
276 for (Spectrum s : spectra) {
277 if (s.isVisible())
278 s.getProbe().quietlySetVisible(on);
279 }
280 }
281
282 // this will get called before init() by the IDV's bundle magic.
283 public void setSpectraProperties(final List<Hashtable<String, Object>> props) {
284 spectraProperties.clear();
285 spectraProperties.addAll(props);
286 }
287
288 public List<Hashtable<String, Object>> getSpectraProperties() {
289 List<Hashtable<String, Object>> props = new ArrayList<Hashtable<String, Object>>();
290 for (Spectrum s : spectra) {
291 props.add(s.getProperties());
292 }
293 return props;
294 }
295
296 protected void updateList(final List<Spectrum> updatedSpectra) {
297 spectra.clear();
298
299 List<String> dataRefIds = new ArrayList<String>(updatedSpectra.size());
300 for (Spectrum spectrum : updatedSpectra) {
301 dataRefIds.add(spectrum.getSpectrumRefName());
302 spectra.add(spectrum);
303 }
304 display.reorderDataRefsById(dataRefIds);
305 }
306
307
308
309 /**
310 * Uses a variable-length array of {@link Color}s to create new readout
311 * probes using the specified colors.
312 *
313 * @param colors Variable length array of {@code Color}s. Shouldn't be
314 * {@code null}.
315 */
316 // TODO(jon): check for null.
317 protected void addSpectra(final Color... colors) {
318 Spectrum currentSpectrum = null;
319 try {
320 for (int i = colors.length-1; i >= 0; i--) {
321 probesSeen++;
322 Color color = colors[i];
323 String id = "Probe "+probesSeen;
324 currentSpectrum = new Spectrum(this, color, id);
325 spectra.add(currentSpectrum);
326 }
327 ((ProbeTableModel)probeTable.getModel()).updateWith(spectra);
328 } catch (Exception e) {
329 LogUtil.logException("MultiSpectralControl.addSpectra: error while adding spectra", e);
330 }
331 }
332
333 /**
334 * Creates a new {@link ReadoutProbe} with the specified {@link Color}.
335 *
336 * @param color {@code Color} of the new {@code ReadoutProbe}.
337 * {@code null} values are not allowed.
338 *
339 * @return {@link Spectrum} wrapper for the newly created
340 * {@code ReadoutProbe}.
341 *
342 * @throws NullPointerException if {@code color} is {@code null}.
343 */
344 public Spectrum addSpectrum(final Color color) {
345 Spectrum spectrum = null;
346 try {
347 probesSeen++;
348 String id = "Probe "+probesSeen;
349 spectrum = new Spectrum(this, color, id);
350 spectra.add(spectrum);
351 } catch (Exception e) {
352 LogUtil.logException("MultiSpectralControl.addSpectrum: error creating new spectrum", e);
353 }
354 ((ProbeTableModel)probeTable.getModel()).updateWith(spectra);
355 return spectrum;
356 }
357
358 /**
359 * Attempts to remove the {@link Spectrum} at the given {@code index}.
360 *
361 * @param index Index of the probe to be removed (within {@link #spectra}).
362 */
363 public void removeSpectrum(final int index) {
364 List<Spectrum> newSpectra = new ArrayList<Spectrum>(spectra);
365 int mappedIndex = newSpectra.size() - (index + 1);
366 Spectrum removed = newSpectra.get(mappedIndex);
367 newSpectra.remove(mappedIndex);
368 try {
369 removed.removeValueDisplay();
370 } catch (Exception e) {
371 LogUtil.logException("MultiSpectralControl.removeSpectrum: error removing spectrum", e);
372 }
373
374 updateList(newSpectra);
375
376 // need to signal that the table should update?
377 ProbeTableModel model = (ProbeTableModel)probeTable.getModel();
378 model.updateWith(newSpectra);
379 probeTable.revalidate();
380 }
381
382 /**
383 * Iterates through the list of {@link Spectrum}s that manage each
384 * {@link ReadoutProbe} associated with this display control and calls
385 * {@link Spectrum#removeValueDisplay()} in an effort to remove this
386 * control's probes.
387 *
388 * @see #spectra
389 */
390 public void removeSpectra() {
391 try {
392 for (Spectrum s : spectra)
393 s.removeValueDisplay();
394 } catch (Exception e) {
395 LogUtil.logException("MultiSpectralControl.removeSpectra: error removing spectrum", e);
396 }
397 }
398
399 /**
400 * Makes each {@link ReadoutProbe} in this display control attempt to
401 * redisplay its readout value.
402 *
403 * <p>Sometimes the probes don't initialize correctly and this method is
404 * a stop-gap solution.
405 */
406 public void pokeSpectra() {
407 for (Spectrum s : spectra)
408 s.pokeValueDisplay();
409 try {
410 //-display.refreshDisplay();
411 } catch (Exception e) {
412 LogUtil.logException("MultiSpectralControl.pokeSpectra: error refreshing display", e);
413 }
414 }
415
416 @Override public DataSelection getDataSelection() {
417 DataSelection selection = super.getDataSelection();
418 if (display != null) {
419 selection.putProperty(Constants.PROP_CHAN, display.getWaveNumber());
420 try {
421 selection.putProperty(SpectrumAdapter.channelIndex_name, display.getChannelIndex());
422 } catch (Exception e) {
423 LogUtil.logException("MultiSpectralControl.getDataSelection", e);
424 }
425 }
426 return selection;
427 }
428
429 @Override public void setDataSelection(final DataSelection newSelection) {
430 super.setDataSelection(newSelection);
431 }
432
433 @Override public MapProjection getDataProjection() {
434 MapProjection mp = null;
435 Rectangle2D rect =
436 MultiSpectralData.getLonLatBoundingBox(display.getImageData());
437
438 try {
439 mp = new LambertAEA(rect);
440 } catch (Exception e) {
441 logException("MultiSpectralControl.getDataProjection", e);
442 }
443
444 return mp;
445 }
446
447 public static float[] minmax(float[] values) {
448 float min = Float.MAX_VALUE;
449 float max = -Float.MAX_VALUE;
450 for (int k = 0; k < values.length; k++) {
451 float val = values[k];
452 if ((val == val) && (val < Float.POSITIVE_INFINITY) && (val > Float.NEGATIVE_INFINITY)) {
453 if (val < min) min = val;
454 if (val > max) max = val;
455 }
456 }
457 return new float[] {min, max};
458 }
459
460
461 @Override protected Range getInitialRange() throws VisADException,
462 RemoteException
463 {
464 return new Range(rangeMin, rangeMax);
465 }
466
467 @Override protected ColorTable getInitialColorTable() {
468 return getDisplayConventions().getParamColorTable(PARAM);
469 }
470
471 @Override public Container doMakeContents() {
472 try {
473 JTabbedPane pane = new JTabbedPane();
474 pane.add("Display", GuiUtils.inset(getDisplayTab(), 5));
475 pane.add("Settings",
476 GuiUtils.inset(GuiUtils.top(doMakeWidgetComponent()), 5));
477 pane.add("Histogram", GuiUtils.inset(GuiUtils.top(getHistogramTabComponent()), 5));
478 GuiUtils.handleHeavyWeightComponentsInTabs(pane);
479 return pane;
480 } catch (Exception e) {
481 logException("MultiSpectralControl.doMakeContents", e);
482 }
483 return null;
484 }
485
486 @Override public void doRemove() throws VisADException, RemoteException {
487 // forcibly clear the value displays when the user has elected to kill
488 // the display. the readouts will persist otherwise.
489 removeSpectra();
490 super.doRemove();
491 }
492
493 /**
494 * Runs through the list of ViewManager-s and tells each to destroy.
495 * Creates a new viewManagers list.
496 */
497 @Override protected void clearViewManagers() {
498 if (viewManagers == null)
499 return;
500
501 List<ViewManager> tmp = new ArrayList<ViewManager>(viewManagers);
502 viewManagers = null;
503 for (ViewManager vm : tmp) {
504 if (vm != null)
505 vm.destroy();
506 }
507 }
508
509 @SuppressWarnings("unchecked")
510 @Override protected JComponent doMakeWidgetComponent() {
511 List<Component> widgetComponents;
512 try {
513 List<ControlWidget> controlWidgets = new ArrayList<ControlWidget>();
514 getControlWidgets(controlWidgets);
515 controlWidgets.add(new WrapperWidget(this, GuiUtils.rLabel("Readout Probes:"), scrollPane));
516 controlWidgets.add(new WrapperWidget(this, GuiUtils.rLabel(" "), GuiUtils.hbox(addProbe, removeProbe)));
517 widgetComponents = ControlWidget.fillList(controlWidgets);
518 } catch (Exception e) {
519 LogUtil.logException("Problem building the MultiSpectralControl settings", e);
520 widgetComponents = new ArrayList<Component>();
521 widgetComponents.add(new JLabel("Error building component..."));
522 }
523
524 GuiUtils.tmpInsets = new Insets(4, 8, 4, 8);
525 GuiUtils.tmpFill = GridBagConstraints.HORIZONTAL;
526 return GuiUtils.doLayout(widgetComponents, 2, GuiUtils.WT_NY, GuiUtils.WT_N);
527 }
528
529 protected MultiSpectralDisplay getMultiSpectralDisplay() {
530 return display;
531 }
532
533 public boolean updateImage(final float newChan) {
534 if (!display.setWaveNumber(newChan))
535 return false;
536
537 DisplayableData imageDisplay = display.getImageDisplay();
538
539 // mark the color map as needing an auto scale, these calls
540 // are needed because a setRange could have been called which
541 // locks out auto scaling.
542 ((HydraRGBDisplayable)imageDisplay).getColorMap().resetAutoScale();
543 displayMaster.reScale();
544
545 try {
546 FlatField image = display.getImageData();
547 displayMaster.setDisplayInactive(); //- try to consolidate display transforms
548 imageDisplay.setData(image);
549 pokeSpectra();
550 displayMaster.setDisplayActive();
551 updateHistogramTab();
552 } catch (Exception e) {
553 LogUtil.logException("MultiSpectralControl.updateImage", e);
554 return false;
555 }
556
557 return true;
558 }
559
560 // be sure to update the displayed image even if a channel change
561 // originates from the msd itself.
562 @Override public void handleChannelChange(final float newChan) {
563 handleChannelChange(newChan, true);
564 }
565
566 public void handleChannelChange(final float newChan, boolean update) {
567 if (update) {
568 if (updateImage(newChan)) {
569 wavenumbox.setText(Float.toString(newChan));
570 }
571 } else {
572 wavenumbox.setText(Float.toString(newChan));
573 }
574 }
575
576 private JComponent getDisplayTab() {
577 List<JComponent> compList = new ArrayList<JComponent>();
578
579 if (display.getBandSelectComboBox() == null) {
580 final JLabel nameLabel = GuiUtils.rLabel("Wavenumber: ");
581
582 wavenumbox.addActionListener(new ActionListener() {
583 public void actionPerformed(ActionEvent e) {
584 String tmp = wavenumbox.getText().trim();
585 updateImage(Float.valueOf(tmp));
586 }
587 });
588 compList.add(nameLabel);
589 compList.add(wavenumbox);
590 } else {
591 final JComboBox bandBox = display.getBandSelectComboBox();
592 bandBox.addActionListener(new ActionListener() {
593 public void actionPerformed(ActionEvent e) {
594 String bandName = (String) bandBox.getSelectedItem();
595 Float channel = (Float) display.getMultiSpectralData().getBandNameMap().get(bandName);
596 updateImage(channel.floatValue());
597 }
598 });
599 JLabel nameLabel = new JLabel("Band: ");
600 compList.add(nameLabel);
601 compList.add(bandBox);
602 }
603
604 JPanel waveNo = GuiUtils.center(GuiUtils.doLayout(compList, 2, GuiUtils.WT_N, GuiUtils.WT_N));
605 return GuiUtils.centerBottom(display.getDisplayComponent(), waveNo);
606 }
607
608 private JComponent getHistogramTabComponent() {
609 updateHistogramTab();
610 JComponent histoComp = histoWrapper.doMakeContents();
611 JLabel rangeLabel = GuiUtils.rLabel("Range ");
612 JLabel minLabel = GuiUtils.rLabel("Min");
613 JLabel maxLabel = GuiUtils.rLabel(" Max");
614 List<JComponent> rangeComps = new ArrayList<JComponent>();
615 rangeComps.add(rangeLabel);
616 rangeComps.add(minLabel);
617 rangeComps.add(minBox);
618 rangeComps.add(maxLabel);
619 rangeComps.add(maxBox);
620 minBox.addActionListener(new ActionListener() {
621 public void actionPerformed(ActionEvent ae) {
622 rangeMin = Float.valueOf(minBox.getText().trim());
623 rangeMax = Float.valueOf(maxBox.getText().trim());
624 histoWrapper.modifyRange((int)rangeMin, (int)rangeMax);
625 }
626 });
627 maxBox.addActionListener(new ActionListener() {
628 public void actionPerformed(ActionEvent ae) {
629 rangeMin = Float.valueOf(minBox.getText().trim());
630 rangeMax = Float.valueOf(maxBox.getText().trim());
631 histoWrapper.modifyRange((int)rangeMin, (int)rangeMax);
632 }
633 });
634 JPanel rangePanel =
635 GuiUtils.center(GuiUtils.doLayout(rangeComps, 5, GuiUtils.WT_N, GuiUtils.WT_N));
636 JButton resetButton = new JButton("Reset");
637 resetButton.addActionListener(new ActionListener() {
638 public void actionPerformed(ActionEvent ae) {
639 resetColorTable();
640 }
641 });
642
643 JPanel resetPanel =
644 GuiUtils.center(GuiUtils.inset(GuiUtils.wrap(resetButton), 4));
645
646 return GuiUtils.topCenterBottom(histoComp, rangePanel, resetPanel);
647 }
648
649 private void updateHistogramTab() {
650 try {
651 histoWrapper.loadData(display.getImageData());
652 org.jfree.data.Range range = histoWrapper.getRange();
653 rangeMin = (float)range.getLowerBound();
654 rangeMax = (float)range.getUpperBound();
655 minBox.setText(Integer.toString((int)rangeMin));
656 maxBox.setText(Integer.toString((int)rangeMax));
657 } catch (Exception e) {
658 logException("MultiSpectralControl.getHistogramTabComponent", e);
659 }
660 }
661
662 public void resetColorTable() {
663 histoWrapper.doReset();
664 }
665
666 protected void contrastStretch(final double low, final double high) {
667 try {
668 org.jfree.data.Range range = histoWrapper.getRange();
669 rangeMin = (float)range.getLowerBound();
670 rangeMax = (float)range.getUpperBound();
671 minBox.setText(Integer.toString((int)rangeMin));
672 maxBox.setText(Integer.toString((int)rangeMax));
673 setRange(getInitialColorTable().getName(), new Range(low, high));
674 } catch (Exception e) {
675 logException("MultiSpectralControl.contrastStretch", e);
676 }
677 }
678
679 private static class Spectrum implements ProbeListener {
680
681 private final MultiSpectralControl control;
682
683 /**
684 * Display that is displaying the spectrum associated with
685 * {@code probe}'s location.
686 */
687 private final MultiSpectralDisplay display;
688
689 /** VisAD's reference to this spectrum. */
690 private final DataReference spectrumRef;
691
692 /**
693 * Probe that appears in the {@literal "image display"} associated with
694 * the current display control.
695 */
696 private ReadoutProbe probe;
697
698 /** Whether or not {@code probe} is visible. */
699 private boolean isVisible = true;
700
701 /**
702 * Human-friendly ID for this spectrum and probe. Used in
703 * {@link MultiSpectralControl#probeTable}.
704 */
705 private final String myId;
706
707 /**
708 * Initializes a new Spectrum that is {@literal "bound"} to {@code control} and
709 * whose color is {@code color}.
710 *
711 * @param control Display control that contains this spectrum and the
712 * associated {@link ReadoutProbe}. Cannot be null.
713 * @param color Color of {@code probe}. Cannot be {@code null}.
714 * @param myId Human-friendly ID used a reference for this spectrum/probe. Cannot be {@code null}.
715 *
716 * @throws NullPointerException if {@code control}, {@code color}, or
717 * {@code myId} is {@code null}.
718 * @throws VisADException if VisAD-land had some problems.
719 * @throws RemoteException if VisAD's RMI stuff had problems.
720 */
721 public Spectrum(final MultiSpectralControl control, final Color color, final String myId) throws VisADException, RemoteException {
722 this.control = control;
723 this.display = control.getMultiSpectralDisplay();
724 this.myId = myId;
725 spectrumRef = new DataReferenceImpl(hashCode() + "_spectrumRef");
726 display.addRef(spectrumRef, color);
727 probe = new ReadoutProbe(control.getNavigatedDisplay(), display.getImageData(), color, control.getDisplayVisibility());
728 this.updatePosition(probe.getEarthPosition());
729 probe.addProbeListener(this);
730 }
731
732 public void probePositionChanged(final ProbeEvent<RealTuple> e) {
733 RealTuple position = e.getNewValue();
734 updatePosition(position);
735 }
736
737 public void updatePosition(RealTuple position) {
738 try {
739 FlatField spectrum = display.getMultiSpectralData().getSpectrum(position);
740 spectrumRef.setData(spectrum);
741 } catch (Exception ex) {
742 ex.printStackTrace();
743 }
744 }
745
746 public String getValue() {
747 return probe.getValue();
748 }
749
750 public double getLatitude() {
751 return probe.getLatitude();
752 }
753
754 public double getLongitude() {
755 return probe.getLongitude();
756 }
757
758 public Color getColor() {
759 return probe.getColor();
760 }
761
762 public String getId() {
763 return myId;
764 }
765
766 public DataReference getSpectrumRef() {
767 return spectrumRef;
768 }
769
770 public String getSpectrumRefName() {
771 return hashCode() + "_spectrumRef";
772 }
773
774 public void setColor(final Color color) {
775 if (color == null)
776 throw new NullPointerException("Can't use a null color");
777
778 try {
779 display.updateRef(spectrumRef, color);
780 probe.quietlySetColor(color);
781 } catch (Exception ex) {
782 ex.printStackTrace();
783 }
784 }
785
786 /**
787 * Shows and hides this spectrum/probe. Note that an {@literal "hidden"}
788 * spectrum merely uses an alpha value of zero for the spectrum's
789 * color--nothing is actually removed!
790 *
791 * <p>Also note that if our {@link MultiSpectralControl} has its visibility
792 * toggled {@literal "off"}, the probe itself will not be shown.
793 * <b>It will otherwise behave as if it is visible!</b>
794 *
795 * @param visible {@code true} for {@literal "visible"}, {@code false} otherwise.
796 */
797 public void setVisible(final boolean visible) {
798 isVisible = visible;
799 Color c = probe.getColor();
800 int alpha = (visible) ? 255 : 0;
801 c = new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
802 try {
803 display.updateRef(spectrumRef, c);
804 // only bother actually *showing* the probe if its display is
805 // actually visible.
806 if (control.getDisplayVisibility())
807 probe.quietlySetVisible(visible);
808 } catch (Exception e) {
809 LogUtil.logException("There was a problem setting the visibility of probe \""+spectrumRef+"\" to "+visible, e);
810 }
811 }
812
813 public boolean isVisible() {
814 return isVisible;
815 }
816
817 protected ReadoutProbe getProbe() {
818 return probe;
819 }
820
821 public void probeColorChanged(final ProbeEvent<Color> e) {
822 System.err.println(e);
823 }
824
825 public void probeVisibilityChanged(final ProbeEvent<Boolean> e) {
826 System.err.println(e);
827 Boolean newVal = e.getNewValue();
828 if (newVal != null)
829 isVisible = newVal;
830 }
831
832 public Hashtable<String, Object> getProperties() {
833 Hashtable<String, Object> table = new Hashtable<String, Object>();
834 table.put("color", probe.getColor());
835 table.put("visibility", isVisible);
836 table.put("lat", probe.getLatitude());
837 table.put("lon", probe.getLongitude());
838 return table;
839 }
840
841 public void setProperties(final Hashtable<String, Object> table) {
842 if (table == null)
843 throw new NullPointerException("properties table cannot be null");
844
845 Color color = (Color)table.get("color");
846 Double lat = (Double)table.get("lat");
847 Double lon = (Double)table.get("lon");
848 Boolean visibility = (Boolean)table.get("visibility");
849 probe.setLatLon(lat, lon);
850 probe.setColor(color);
851 setVisible(visibility);
852 }
853
854 public void pokeValueDisplay() {
855 probe.setField(display.getImageData());
856 try {
857 //FlatField spectrum = display.getMultiSpectralData().getSpectrum(probe.getEarthPosition());
858 //spectrumRef.setData(spectrum);
859 } catch (Exception e) { }
860 }
861
862 public void removeValueDisplay() throws VisADException, RemoteException {
863 probe.handleProbeRemoval();
864 display.removeRef(spectrumRef);
865 }
866 }
867
868 // TODO(jon): MultiSpectralControl should become the table model.
869 private static class ProbeTableModel extends AbstractTableModel implements ProbeListener {
870 // private static final String[] COLUMNS = {
871 // "Visibility", "Probe ID", "Value", "Spectrum", "Latitude", "Longitude", "Color"
872 // };
873
874 private static final String[] COLUMNS = {
875 "Visibility", "Probe ID", "Value", "Latitude", "Longitude", "Color"
876 };
877
878 private final Map<ReadoutProbe, Integer> probeToIndex = new LinkedHashMap<ReadoutProbe, Integer>();
879 private final Map<Integer, Spectrum> indexToSpectrum = new LinkedHashMap<Integer, Spectrum>();
880 private final MultiSpectralControl control;
881
882 public ProbeTableModel(final MultiSpectralControl control, final List<Spectrum> probes) {
883 Contract.notNull(control);
884 Contract.notNull(probes);
885 this.control = control;
886 updateWith(probes);
887 }
888
889 public void probeColorChanged(final ProbeEvent<Color> e) {
890 ReadoutProbe probe = e.getProbe();
891 if (!probeToIndex.containsKey(probe))
892 return;
893
894 int index = probeToIndex.get(probe);
895 fireTableCellUpdated(index, 5);
896 }
897
898 public void probeVisibilityChanged(final ProbeEvent<Boolean> e) {
899 ReadoutProbe probe = e.getProbe();
900 if (!probeToIndex.containsKey(probe))
901 return;
902
903 int index = probeToIndex.get(probe);
904 fireTableCellUpdated(index, 0);
905 }
906
907 public void probePositionChanged(final ProbeEvent<RealTuple> e) {
908 ReadoutProbe probe = e.getProbe();
909 if (!probeToIndex.containsKey(probe))
910 return;
911
912 int index = probeToIndex.get(probe);
913 fireTableRowsUpdated(index, index);
914 }
915
916 public void updateWith(final List<Spectrum> updatedSpectra) {
917 Contract.notNull(updatedSpectra);
918
919 probeToIndex.clear();
920 indexToSpectrum.clear();
921
922 for (int i = 0, j = updatedSpectra.size()-1; i < updatedSpectra.size(); i++, j--) {
923 Spectrum spectrum = updatedSpectra.get(j);
924 ReadoutProbe probe = spectrum.getProbe();
925 if (!probe.hasListener(this))
926 probe.addProbeListener(this);
927
928 probeToIndex.put(spectrum.getProbe(), i);
929 indexToSpectrum.put(i, spectrum);
930 }
931 }
932
933 public int getColumnCount() {
934 return COLUMNS.length;
935 }
936
937 public int getRowCount() {
938 if (probeToIndex.size() != indexToSpectrum.size())
939 throw new AssertionError("");
940
941 return probeToIndex.size();
942 }
943
944 // public Object getValueAt(final int row, final int column) {
945 // Spectrum spectrum = indexToSpectrum.get(row);
946 // switch (column) {
947 // case 0: return spectrum.isVisible();
948 // case 1: return spectrum.getId();
949 // case 2: return spectrum.getValue();
950 // case 3: return "notyet";
951 // case 4: return formatPosition(spectrum.getLatitude());
952 // case 5: return formatPosition(spectrum.getLongitude());
953 // case 6: return spectrum.getColor();
954 // default: throw new AssertionError("uh oh");
955 // }
956 // }
957 public Object getValueAt(final int row, final int column) {
958 Spectrum spectrum = indexToSpectrum.get(row);
959 switch (column) {
960 case 0: return spectrum.isVisible();
961 case 1: return spectrum.getId();
962 case 2: return spectrum.getValue();
963 case 3: return formatPosition(spectrum.getLatitude());
964 case 4: return formatPosition(spectrum.getLongitude());
965 case 5: return spectrum.getColor();
966 default: throw new AssertionError("uh oh");
967 }
968 }
969
970 public boolean isCellEditable(final int row, final int column) {
971 switch (column) {
972 case 0: return true;
973 case 5: return true;
974 default: return false;
975 }
976 }
977
978 public void setValueAt(final Object value, final int row, final int column) {
979 Spectrum spectrum = indexToSpectrum.get(row);
980 boolean didUpdate = true;
981 switch (column) {
982 case 0: spectrum.setVisible((Boolean)value); break;
983 case 5: spectrum.setColor((Color)value); break;
984 default: didUpdate = false; break;
985 }
986
987 if (didUpdate)
988 fireTableCellUpdated(row, column);
989 }
990
991 public void moveRow(final int origin, final int destination) {
992 // get the dragged spectrum (and probe)
993 Spectrum dragged = indexToSpectrum.get(origin);
994 ReadoutProbe draggedProbe = dragged.getProbe();
995
996 // get the current spectrum (and probe)
997 Spectrum current = indexToSpectrum.get(destination);
998 ReadoutProbe currentProbe = current.getProbe();
999
1000 // update references in indexToSpetrum
1001 indexToSpectrum.put(destination, dragged);
1002 indexToSpectrum.put(origin, current);
1003
1004 // update references in probeToIndex
1005 probeToIndex.put(draggedProbe, destination);
1006 probeToIndex.put(currentProbe, origin);
1007
1008 // build a list of the spectra, ordered by index
1009 List<Spectrum> updated = new ArrayList<Spectrum>();
1010 for (int i = indexToSpectrum.size()-1; i >= 0; i--)
1011 updated.add(indexToSpectrum.get(i));
1012
1013 // send it to control.
1014 control.updateList(updated);
1015 }
1016
1017 public String getColumnName(final int column) {
1018 return COLUMNS[column];
1019 }
1020
1021 public Class<?> getColumnClass(final int column) {
1022 return getValueAt(0, column).getClass();
1023 }
1024
1025 private static String formatPosition(final double position) {
1026 McIDASV mcv = McIDASV.getStaticMcv();
1027 if (mcv == null)
1028 return "NaN";
1029
1030 DecimalFormat format = new DecimalFormat(mcv.getStore().get(Constants.PREF_LATLON_FORMAT, "##0.0"));
1031 return format.format(position);
1032 }
1033 }
1034
1035 public class ColorEditor extends AbstractCellEditor implements TableCellEditor, ActionListener {
1036 private Color currentColor = Color.CYAN;
1037 private final JButton button = new JButton();
1038 private final JColorChooser colorChooser = new JColorChooser();
1039 private JDialog dialog;
1040 protected static final String EDIT = "edit";
1041
1042 // private final JComboBox combobox = new JComboBox(GuiUtils.COLORS);
1043
1044 public ColorEditor() {
1045 button.setActionCommand(EDIT);
1046 button.addActionListener(this);
1047 button.setBorderPainted(false);
1048
1049 // combobox.setActionCommand(EDIT);
1050 // combobox.addActionListener(this);
1051 // combobox.setBorder(new EmptyBorder(0, 0, 0, 0));
1052 // combobox.setOpaque(true);
1053 // ColorRenderer whut = new ColorRenderer(true);
1054 // combobox.setRenderer(whut);
1055 //
1056 // dialog = JColorChooser.createDialog(combobox, "pick a color", true, colorChooser, this, null);
1057 dialog = JColorChooser.createDialog(button, "pick a color", true, colorChooser, this, null);
1058 }
1059 public void actionPerformed(ActionEvent e) {
1060 if (EDIT.equals(e.getActionCommand())) {
1061 //The user has clicked the cell, so
1062 //bring up the dialog.
1063 // button.setBackground(currentColor);
1064 colorChooser.setColor(currentColor);
1065 dialog.setVisible(true);
1066
1067 //Make the renderer reappear.
1068 fireEditingStopped();
1069
1070 } else { //User pressed dialog's "OK" button.
1071 currentColor = colorChooser.getColor();
1072 }
1073 }
1074
1075 //Implement the one CellEditor method that AbstractCellEditor doesn't.
1076 public Object getCellEditorValue() {
1077 return currentColor;
1078 }
1079
1080 //Implement the one method defined by TableCellEditor.
1081 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
1082 currentColor = (Color)value;
1083 return button;
1084 // return combobox;
1085 }
1086 }
1087
1088 public class ColorRenderer extends JLabel implements TableCellRenderer, ListCellRenderer {
1089 Border unselectedBorder = null;
1090 Border selectedBorder = null;
1091 boolean isBordered = true;
1092
1093 public ColorRenderer(boolean isBordered) {
1094 this.isBordered = isBordered;
1095 setHorizontalAlignment(CENTER);
1096 setVerticalAlignment(CENTER);
1097 setOpaque(true);
1098 }
1099
1100 public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) {
1101 Color newColor = (Color)color;
1102 setBackground(newColor);
1103 if (isBordered) {
1104 if (isSelected) {
1105 if (selectedBorder == null)
1106 selectedBorder = BorderFactory.createMatteBorder(2,5,2,5, table.getSelectionBackground());
1107 setBorder(selectedBorder);
1108 } else {
1109 if (unselectedBorder == null)
1110 unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5, table.getBackground());
1111 setBorder(unselectedBorder);
1112 }
1113 }
1114
1115 setToolTipText(String.format("RGB: red=%d, green=%d, blue=%d", newColor.getRed(), newColor.getGreen(), newColor.getBlue()));
1116 return this;
1117 }
1118
1119 public Component getListCellRendererComponent(JList list, Object color, int index, boolean isSelected, boolean cellHasFocus) {
1120 Color newColor = (Color)color;
1121 setBackground(newColor);
1122 if (isBordered) {
1123 if (isSelected) {
1124 if (selectedBorder == null)
1125 selectedBorder = BorderFactory.createMatteBorder(2,5,2,5, list.getSelectionBackground());
1126 setBorder(selectedBorder);
1127 } else {
1128 if (unselectedBorder == null)
1129 unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5, list.getBackground());
1130 setBorder(unselectedBorder);
1131 }
1132 }
1133 setToolTipText(String.format("RGB: red=%d, green=%d, blue=%d", newColor.getRed(), newColor.getGreen(), newColor.getBlue()));
1134 return this;
1135 }
1136 }
1137
1138 public class HackyDragDropRowUI extends BasicTableUI {
1139
1140 private boolean inDrag = false;
1141 private int start;
1142 private int offset;
1143
1144 protected MouseInputListener createMouseInputListener() {
1145 return new HackyMouseInputHandler();
1146 }
1147
1148 public void paint(Graphics g, JComponent c) {
1149 super.paint(g, c);
1150
1151 if (!inDrag)
1152 return;
1153
1154 int width = table.getWidth();
1155 int height = table.getRowHeight();
1156 g.setColor(table.getParent().getBackground());
1157 Rectangle rect = table.getCellRect(table.getSelectedRow(), 0, false);
1158 g.copyArea(rect.x, rect.y, width, height, rect.x, offset);
1159
1160 if (offset < 0)
1161 g.fillRect(rect.x, rect.y + (height + offset), width, (offset * -1));
1162 else
1163 g.fillRect(rect.x, rect.y, width, offset);
1164 }
1165
1166 class HackyMouseInputHandler extends MouseInputHandler {
1167
1168 public void mouseDragged(MouseEvent e) {
1169 int row = table.getSelectedRow();
1170 if (row < 0)
1171 return;
1172
1173 inDrag = true;
1174
1175 int height = table.getRowHeight();
1176 int middleOfSelectedRow = (height * row) + (height / 2);
1177
1178 int toRow = -1;
1179 int yLoc = (int)e.getPoint().getY();
1180
1181 // goin' up?
1182 if (yLoc < (middleOfSelectedRow - height))
1183 toRow = row - 1;
1184 else if (yLoc > (middleOfSelectedRow + height))
1185 toRow = row + 1;
1186
1187 ProbeTableModel model = (ProbeTableModel)table.getModel();
1188 if (toRow >= 0 && toRow < table.getRowCount()) {
1189 model.moveRow(row, toRow);
1190 table.setRowSelectionInterval(toRow, toRow);
1191 start = yLoc;
1192 }
1193
1194 offset = (start - yLoc) * -1;
1195 table.repaint();
1196 }
1197
1198 public void mousePressed(MouseEvent e) {
1199 super.mousePressed(e);
1200 start = (int)e.getPoint().getY();
1201 }
1202
1203 public void mouseReleased(MouseEvent e){
1204 super.mouseReleased(e);
1205 inDrag = false;
1206 table.repaint();
1207 }
1208 }
1209 }
1210 }