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