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