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