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