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 }