001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas/ 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see https://www.gnu.org/licenses/. 027 */ 028package edu.wisc.ssec.mcidasv.data; 029 030import static javax.swing.BorderFactory.createTitledBorder; 031import static javax.swing.GroupLayout.Alignment.BASELINE; 032import static javax.swing.GroupLayout.Alignment.LEADING; 033import static javax.swing.GroupLayout.Alignment.TRAILING; 034import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 035 036import java.awt.Container; 037import java.awt.Dimension; 038import java.awt.EventQueue; 039import java.awt.Font; 040import java.awt.event.WindowEvent; 041import java.awt.event.WindowAdapter; 042import java.io.ByteArrayInputStream; 043import java.io.ByteArrayOutputStream; 044import java.io.File; 045import java.io.IOException; 046import java.nio.file.Paths; 047import java.util.ArrayList; 048import java.util.List; 049import java.util.stream.Collectors; 050 051import javax.swing.BoxLayout; 052import javax.swing.GroupLayout; 053import javax.swing.JButton; 054import javax.swing.JComboBox; 055import javax.swing.JDialog; 056import javax.swing.JEditorPane; 057import javax.swing.JFrame; 058import javax.swing.JLabel; 059import javax.swing.JOptionPane; 060import javax.swing.JPanel; 061import javax.swing.JRadioButton; 062import javax.swing.JScrollPane; 063import javax.swing.JSeparator; 064import javax.swing.JTextField; 065import javax.swing.JTextPane; 066import javax.swing.JToolBar; 067import javax.swing.ScrollPaneConstants; 068import javax.swing.UIManager; 069import javax.swing.WindowConstants; 070import javax.swing.border.EmptyBorder; 071import javax.swing.event.HyperlinkEvent; 072import javax.swing.text.PlainDocument; 073 074import org.slf4j.Logger; 075import org.slf4j.LoggerFactory; 076 077import net.miginfocom.swing.MigLayout; 078import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 079import edu.wisc.ssec.mcidasv.util.McVGuiUtils.IconPanel; 080import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Prefer; 081import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width; 082import edu.wisc.ssec.mcidasv.util.WebBrowser; 083import edu.wisc.ssec.mcidasv.Constants; 084import ucar.ma2.Array; 085import ucar.nc2.Variable; 086import ucar.nc2.dataset.NetcdfDataset; 087import ucar.nc2.ncml.NcMLReader; 088import ucar.nc2.dt.grid.GridDataset; 089import ucar.unidata.util.FileManager; 090import ucar.unidata.util.GuiUtils; 091import ucar.unidata.util.IOUtil; 092import ucar.unidata.util.LayoutUtil; 093import ucar.unidata.data.DataSourceDescriptor; 094import ucar.unidata.data.grid.GeoGridDataSource; 095import ucar.unidata.idv.IntegratedDataViewer; 096import visad.ConstantMap; 097import visad.Display; 098import visad.FlatField; 099import visad.python.JPythonMethods; 100import visad.ss.BasicSSCell; 101import visad.ss.FancySSCell; 102 103/** 104 * GUI widget that allows users to attempt to specify an {@literal "NCML"} 105 * if a given NetCDF is not CF-compliant. 106 */ 107public class BadNetCDFWidget implements Constants { 108 109 private static final Logger logger = 110 LoggerFactory.getLogger(BadNetCDFWidget.class); 111 112 private IntegratedDataViewer idv; 113 114 private NetcdfDataset ncFile; 115 private List<Variable> varList; 116 private List<String> varNames; 117 118 // For NcML Editor 119 private JEditorPane NcMLeditor; 120 121 // For variable display 122 BasicSSCell display; 123 ConstantMap[] cmaps; 124 125 // For nav specification 126 private JRadioButton radioLatLonVars = new JRadioButton("Variables", true); 127 private JRadioButton radioLatLonBounds = new JRadioButton("Bounds", false); 128 129 private JComboBox refComboBox = new JComboBox(); 130 private JComboBox latComboBox = new JComboBox(); 131 private JComboBox lonComboBox = new JComboBox(); 132 133 private JPanel panelLatLonVars = new JPanel(); 134 private JPanel panelLatLonBounds = new JPanel(); 135 136 private JTextField textLatUL = new JTextField(); 137 private JTextField textLonUL = new JTextField(); 138 private JTextField textLatLR = new JTextField(); 139 private JTextField textLonLR = new JTextField(); 140 141 /** 142 * Handles problems from {@code openDataset}. 143 * 144 * @param ncFile NetCDF that caused a problem. Cannot be {@code null}. 145 * @param idv Reference to the IDV. Cannot be {@code null}. 146 */ 147 public BadNetCDFWidget(NetcdfDataset ncFile, IntegratedDataViewer idv) { 148 this.idv = idv; 149 this.ncFile = ncFile; 150 varList = ncFile.getVariables(); 151 varNames = new ArrayList<>(varList.size()); 152 varNames.addAll(varList.stream().map(Variable::getFullName).collect(Collectors.toList())); 153 } 154 155 /** 156 * Passes through any exception from openDataset - this function 157 * doesn't provide an IDV and should only be used for testing. 158 * 159 * (Some functionality using the rest of the IDV won't work.) 160 * 161 * @param filepath Path to a NetCDF file. 162 * 163 * @throws IOException if there was a problem. 164 */ 165 public BadNetCDFWidget(String filepath) throws IOException { 166 this(NetcdfDataset.openDataset(filepath), null); 167 } 168 169 /** 170 * Displays our "main menu" of choices to fix the given file. Everything 171 * else needed can get called from here. 172 */ 173 public void showChoices() { 174 EventQueue.invokeLater(() -> { 175 try { 176 BadNetCDFDialog dialog = new BadNetCDFDialog(); 177 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); 178 dialog.setVisible(true); 179 dialog.toFront(); 180 } catch (Exception e) { 181 logger.error("Could not show choices", e); 182 } 183 }); 184 } 185 186 /** 187 * Creates an editor for NcML and displays it in a window. This includes 188 * buttons for saving just the NcML and the full NetCDF file with the 189 * changes made. 190 */ 191 private void showNcMLEditor() { 192 NcMLeditor = new JEditorPane(); 193 194 // We use this to store the actual ncml - 10000 is just the number 195 // toolsUI used 196 ByteArrayOutputStream bos = new ByteArrayOutputStream(10000); 197 try { 198 ncFile.writeNcml(bos, null); 199 NcMLeditor.setText(bos.toString()); 200 NcMLeditor.setCaretPosition(0); 201 } catch (IOException ioe) { 202 logger.error("Could not write ncml", ioe); 203 // DataSourceImpl - doesn't work if we're not a data source 204 // setInError(true, false, ""); 205 return; 206 } 207 208 NcMLeditor.setEditable(true); 209 210 // Set the font style. 211 NcMLeditor.setFont(new Font("Courier", Font.PLAIN, 12)); 212 213 // Set the tab size 214 NcMLeditor.getDocument().putProperty(PlainDocument.tabSizeAttribute, 2); 215 216 // Button to save NcML as text, 217 // popup allows them to specify where. 218 JButton saveNcMLBtn = new JButton("Save NcML as text"); 219 saveNcMLBtn.addActionListener(e -> { 220 // Begin with getting the filename we want to write to. 221 String ncLocation = ncFile.getLocation(); 222 223 if (ncLocation == null) { 224 ncLocation = "test"; 225 } 226 int pos = ncLocation.lastIndexOf("."); 227 if (pos > 0) { 228 ncLocation = ncLocation.substring(0, pos); 229 } 230 String filename = FileManager.getWriteFile(ncLocation + ".ncml"); 231 if (filename == null) { 232 return; 233 } 234 235 // Once we have that, we can actually write to the file! 236 try { 237 IOUtil.writeFile(new File(filename), NcMLeditor.getText()); 238 } catch (Exception exc) { 239 logger.error("Could not write to '"+filename+'\'', exc); 240 } 241 }); 242 243 // Button to merge the NcML with NetCDF 244 // a'la ToolsUI and write it back out as NetCDF3. 245 JButton saveNetCDFBtn = new JButton("Merge and save NetCDF"); 246 saveNetCDFBtn.addActionListener(e -> { 247 // Begin with getting the filename we want to write to. 248 String ncLocation = ncFile.getLocation(); 249 250 if (ncLocation == null) { 251 ncLocation = "test"; 252 } 253 int pos = ncLocation.lastIndexOf("."); 254 if (pos > 0) { 255 ncLocation = ncLocation.substring(0, pos); 256 } 257 String filename = FileManager.getWriteFile(ncLocation + ".nc"); 258 if (filename == null) { 259 return; 260 } 261 262 // Once we have that, we can actually write to the file! 263 try { 264 ByteArrayInputStream bis = 265 new ByteArrayInputStream(NcMLeditor.getText().getBytes()); 266 NcMLReader.writeNcMLToFile(bis, filename); 267 } catch (Exception exc) { 268 logger.error("Could not write to '"+filename+'\'', exc); 269 } 270 }); 271 272 // Button to load this data into McV from NcML 273 JButton sendToMcVBtn = new JButton("Attempt to load with this NcML"); 274 sendToMcVBtn.addActionListener(ae -> { 275 // TODO: save the current NcML into the NetcdfDataSource 276 createIDVdisplay(); 277 }); 278 279 JToolBar toolbar = new JToolBar("NcML Editor Controls"); 280 281 toolbar.add(saveNcMLBtn); 282 toolbar.add(saveNetCDFBtn); 283 toolbar.add(sendToMcVBtn); 284 285 JScrollPane scrollPane = 286 new JScrollPane(NcMLeditor, 287 ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, 288 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); 289 290 // TODO: PREFERRED SIZE? 291 scrollPane.setPreferredSize(new Dimension(600, 600)); 292 293 JPanel panel = LayoutUtil.topCenter(toolbar, scrollPane); 294 JFrame editorWindow = 295 GuiUtils.makeWindow("NcML Editor", LayoutUtil.inset(panel, 10), 0, 0); 296 editorWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 297 editorWindow.setVisible(true); 298 editorWindow.toFront(); 299 } 300 301 /** 302 * Takes our ncFile and puts it back into IDV (McV). 303 */ 304 private void createIDVdisplay() { 305 306 // Make a NetcdfDataset from our NcML 307 ByteArrayInputStream bis = 308 new ByteArrayInputStream(NcMLeditor.getText().getBytes()); 309 310 try { 311 ncFile = NcMLReader.readNcML(bis, null); 312 } catch (IOException e1) { 313 logger.error("Could not read ncml", e1); 314 return; 315 } 316 317 // Now try to turn that NetcdfDataset into a legitimate DataSource! 318 GridDataset gd; 319 320 try { 321 gd = new GridDataset(ncFile); 322 } catch (IOException e) { 323 logger.error("could not create grid dataset from netcdf file", e); 324 return; 325 } 326 327 ncFile.getLocation(); 328 DataSourceDescriptor dsd = new DataSourceDescriptor(); 329 dsd.setLabel("NcML DS Label"); 330 GeoGridDataSource ggds = 331 new GeoGridDataSource(dsd, gd, "NcML Data Source", ncFile.getLocation()); 332 ggds.initAfterCreation(); 333 idv.getDataManager().addDataSource(ggds); 334 } 335 336 /** 337 * Shows a window that gives a choice of variables. 338 */ 339 private void showVarPicker() { 340 final JComboBox<String> varDD = new JComboBox<>(); 341 GuiUtils.setListData(varDD, varNames); 342 343 varDD.addActionListener(ae -> { 344 JComboBox<String> cb = (JComboBox<String>) ae.getSource(); 345 Variable plotVar = varList.get(cb.getSelectedIndex()); 346 String varName = (String) cb.getSelectedItem(); 347 348 float [] varVals; 349 try { 350 // TODO: Is there a better way to convert this? 351 // Is there another function like reshape? 352 Array varArray = plotVar.read(); 353 varVals = (float[])varArray.get1DJavaArray(float.class); 354 } catch (IOException IOexe) { 355 logger.error("error while reading from variable '" + plotVar + '\'', IOexe); 356 return; 357 } 358 359 int size = plotVar.getDimensions().size(); 360 if( size != 2) { 361 JOptionPane.showMessageDialog(null, 362 ("<html>Variables must have 2 dimensions to be displayed here.<br><br>\"" + varName + "\" has " + size + ".</html>"), 363 "Invalid Dimensions", 364 JOptionPane.ERROR_MESSAGE); 365 return; 366 } 367 368 int xdim = plotVar.getDimensions().get(0).getLength(); 369 int ydim = plotVar.getDimensions().get(1).getLength(); 370 371 float[][] var2D = reshape(varVals, ydim, xdim); 372 373 //JPythonMethods.plot(varVals); 374 //JPythonMethods.plot(var2D); 375 376 try { 377 FlatField varField = JPythonMethods.field(var2D); 378 379 ConstantMap[] cmaps1 = { 380 new ConstantMap(1.0, Display.Red), 381 new ConstantMap(1.0, Display.Green), 382 new ConstantMap(1.0, Display.Blue) 383 }; 384 385 // Clear out the display or we get some weird stuff going on. 386 display.clearCell(); 387 display.clearMaps(); 388 display.clearDisplay(); 389 390 display.addData(varField, cmaps1); 391 } catch (Exception exe) { 392 logger.error("problem encountered", exe); 393 } 394 }); 395 396 //BasicSSCell display = new FancySSCell("Variable!"); 397 //display.setDimension(BasicSSCell.JAVA3D_3D); 398 399 try { 400 // Heavily borrowed from VISAD's JPythonMethods 401 display = new FancySSCell("Variable Viewer"); 402 display.setDimension(BasicSSCell.JAVA3D_3D); 403 display.setPreferredSize(new Dimension(256, 256)); 404 JFrame frame = new JFrame("Variable Viewer"); 405 JPanel pane = new JPanel(); 406 pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS)); 407 frame.setContentPane(pane); 408 pane.add(varDD); 409 pane.add(display); 410 JButton controls = new JButton("Controls"); 411 JPanel buttons = new JPanel(); 412 buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); 413 buttons.add(controls); 414 pane.add(buttons); 415 final FancySSCell fdisp = (FancySSCell) display; 416 fdisp.setAutoShowControls(false); 417 418 controls.addActionListener(e -> fdisp.showWidgetFrame()); 419 420 frame.pack(); 421 frame.setVisible(true); 422 frame.toFront(); 423 } catch (Exception exe) { 424 logger.error("problem displaying", exe); 425 } 426 } 427 428 /** 429 * Reshape a 1D float array into a 2D float array. 430 * 431 * @param arr Array to reshape. 432 * @param m First dimension. 433 * @param n Second dimension. 434 * 435 * @return {@code arr} reshaped into a new 2D array. 436 */ 437 private static float[][] reshape(float[] arr, int m, int n) { 438 float[][] newArr = new float[m][n]; 439 int index = 0; 440 for (int i = 0; i < n; i++) { 441 for (int j = 0; j< m; j++) { 442 newArr[j][i] = arr[index++]; 443 } 444 } 445 return newArr; 446 } 447 448 /** 449 * Shows a window that gives the opportunity to either define coordinate 450 * variables or specify corner points. 451 * 452 * Borrowed heavily from FlatFileChooser's makeNavigationPanel for style. 453 */ 454 private void showNavChooser() { 455 JPanel midPanel = new JPanel(); 456 midPanel.setBorder(createTitledBorder("Navigation")); 457 458 GuiUtils.setListData(refComboBox, varNames); 459 GuiUtils.setListData(latComboBox, varNames); 460 GuiUtils.setListData(lonComboBox, varNames); 461 462 McVGuiUtils.setComponentWidth(latComboBox, Width.QUADRUPLE); 463 McVGuiUtils.setComponentWidth(lonComboBox, Width.QUADRUPLE); 464 McVGuiUtils.setComponentWidth(refComboBox, Width.QUADRUPLE); 465 466 panelLatLonVars = 467 McVGuiUtils.topBottom( 468 McVGuiUtils.makeLabeledComponent("Latitude:",latComboBox), 469 McVGuiUtils.makeLabeledComponent("Longitude:",lonComboBox), 470 Prefer.NEITHER); 471 472 GuiUtils.buttonGroup(radioLatLonVars, radioLatLonBounds); 473 474 // Images to make the bounds more clear 475 IconPanel urPanel = 476 new IconPanel("/edu/wisc/ssec/mcidasv/images/upper_right.gif"); 477 IconPanel llPanel = 478 new IconPanel("/edu/wisc/ssec/mcidasv/images/lower_left.gif"); 479 480 McVGuiUtils.setComponentWidth(textLatUL); 481 McVGuiUtils.setComponentWidth(textLonUL); 482 McVGuiUtils.setComponentWidth(textLatLR); 483 McVGuiUtils.setComponentWidth(textLonLR); 484 panelLatLonBounds = McVGuiUtils.topBottom( 485 McVGuiUtils.makeLabeledComponent("UL Lat/Lon:", LayoutUtil.leftRight(LayoutUtil.hbox(textLatUL, textLonUL), urPanel)), 486 McVGuiUtils.makeLabeledComponent("LR Lat/Lon:", LayoutUtil.leftRight(llPanel, LayoutUtil.hbox(textLatLR, textLonLR))), 487 Prefer.NEITHER); 488 489 panelLatLonBounds = 490 McVGuiUtils.topBottom( 491 panelLatLonBounds, 492 McVGuiUtils.makeLabeledComponent("Reference:", refComboBox), 493 Prefer.NEITHER); 494 495 McVGuiUtils.setComponentWidth(radioLatLonVars); 496 McVGuiUtils.setComponentWidth(radioLatLonBounds); 497 498 // Add a bit of a buffer to both 499 panelLatLonVars = LayoutUtil.inset(panelLatLonVars, 5); 500 panelLatLonBounds = LayoutUtil.inset(panelLatLonBounds, 5); 501 502 GroupLayout layout = new GroupLayout(midPanel); 503 midPanel.setLayout(layout); 504 layout.setHorizontalGroup( 505 layout.createParallelGroup(LEADING) 506 .addGroup(layout.createSequentialGroup() 507 .addContainerGap() 508 .addGroup(layout.createParallelGroup(LEADING) 509 .addGroup(layout.createSequentialGroup() 510 .addComponent(radioLatLonVars) 511 .addGap(GAP_RELATED) 512 .addComponent(panelLatLonVars)) 513 .addGroup(layout.createSequentialGroup() 514 .addComponent(radioLatLonBounds) 515 .addGap(GAP_RELATED) 516 .addComponent(panelLatLonBounds))) 517 .addContainerGap()) 518 ); 519 layout.setVerticalGroup( 520 layout.createParallelGroup(LEADING) 521 .addGroup(TRAILING, layout.createSequentialGroup() 522 .addContainerGap() 523 .addGroup(layout.createParallelGroup(BASELINE) 524 .addComponent(radioLatLonVars) 525 .addComponent(panelLatLonVars)) 526 .addPreferredGap(RELATED) 527 .addGroup(layout.createParallelGroup(BASELINE) 528 .addComponent(radioLatLonBounds) 529 .addComponent(panelLatLonBounds)) 530 .addPreferredGap(RELATED) 531 .addContainerGap()) 532 ); 533 534 535 536 radioLatLonVars.addActionListener(e -> checkSetLatLon()); 537 538 radioLatLonBounds.addActionListener(e -> checkSetLatLon()); 539 540 JButton goBtn = new JButton("Go!"); 541 goBtn.addActionListener(ae -> { 542 boolean isVar = radioLatLonVars.isSelected(); 543 if (isVar) { 544 navVarAction(); 545 } else { 546 navCornersAction(); 547 } 548 }); 549 550 JPanel wholePanel = 551 McVGuiUtils.topBottom(midPanel, goBtn, Prefer.NEITHER); 552 553 JFrame myWindow = 554 GuiUtils.makeWindow( 555 "Pick Your Navigation!", 556 LayoutUtil.inset(wholePanel, 10), 557 0, 0); 558 559 checkSetLatLon(); 560 myWindow.setVisible(true); 561 myWindow.toFront(); 562 } 563 564 565 /** 566 * Enable or disable widgets for navigation. 567 */ 568 private void checkSetLatLon() { 569 boolean isVar = radioLatLonVars.isSelected(); 570 GuiUtils.enableTree(panelLatLonVars, isVar); 571 GuiUtils.enableTree(panelLatLonBounds, !isVar); 572 } 573 574 /** 575 * One of the two workhorses of our nav chooser, it alters the chosen 576 * (existing) variables so they can be used as lat/lon pairs. 577 */ 578 private void navVarAction() { 579 } 580 581 /** 582 * One of the two workhorses of our nav chooser, it creates new 583 * variables for lat/lon based on the specified cornerpoints and 584 * reference variable (for dimensions). 585 */ 586 private void navCornersAction() { 587 } 588 589 public class BadNetCDFDialog extends JDialog { 590 591 private static final long serialVersionUID = 1L; 592 593 /** 594 * Create the dialog. 595 */ 596 597 public BadNetCDFDialog() { 598 setTitle("Non-Compliant NetCDF Tool"); 599 setLocation(100,100); 600 Container contentPane = getContentPane(); 601 602 JLabel headerLabel = 603 new JLabel("McIDAS-V is unable to read your file."); 604 605 // TJJ Aug 2019 - TROPOMI L1B files sneak through to here, kinda hacky but 606 // might as well mention as of this date we handle L2 but not L1B 607 608 String filename = Paths.get(ncFile.getLocation()).getFileName().toString(); 609 if (TropomiIOSP.TROPOMI_MATCHER.matcher(filename).matches()) { 610 if (filename.contains("_L1B_") || filename.contains("_L2__NP")) { 611 headerLabel.setText("<html>" + headerLabel.getText() + "<br>" + 612 "Only TROPOMI Level 2 Products are supported at this time.</html>"); 613 } 614 } 615 616 headerLabel.setFont(UIManager.getFont("OptionPane.font")); 617 headerLabel.setBorder(new EmptyBorder(0, 0, 4, 0)); 618 619 JTextPane messageTextPane = new JTextPane(); 620 Font textPaneFont = UIManager.getFont("TextPane.font"); 621 String fontCss = 622 String.format("style=\"font-family: '%s'; font-size: %d;\"", textPaneFont.getFamily(), textPaneFont.getSize()); 623 messageTextPane.setBackground(UIManager.getColor("Label.background")); 624 messageTextPane.setContentType("text/html"); 625 messageTextPane.setDragEnabled(false); 626 messageTextPane.setText("<html>\n<body "+ 627 fontCss + 628 ">To verify if your file is CF-compliant, you can run your file through an online compliance checker " + 629 "(<a href=\"https://compliance.ioos.us/index.html\">example CF-compliance utility</a>). " + 630 "<br/><br/> \n\nIf the checker indicates that your file is not compliant you can attempt to fix it using " + 631 "the NcML Editor provided in this window.<br/><br/>\n\nIn a future release of McIDAS-V, this interface will " + 632 "present you with choices for the variables necessary for McIDAS-V to display your data.<br/></font></body></html>"); 633 messageTextPane.setEditable(false); 634 messageTextPane.addHyperlinkListener(e -> { 635 HyperlinkEvent.EventType type = e.getEventType(); 636 if (HyperlinkEvent.EventType.ACTIVATED.equals(type)) { 637 String url = (e.getURL() == null) 638 ? e.getDescription() 639 : e.getURL().toString(); 640 WebBrowser.browse(url); 641 } 642 }); 643 644 JSeparator separator = new JSeparator(); 645 // seems pretty dumb to have to do this sizing business in order 646 // to get the separator to appear, right? 647 // check out the following: 648 // http://docs.oracle.com/javase/tutorial/uiswing/components/separator.html 649 // 650 // "Separators have almost no API and are extremely easy to use as 651 // long as you keep one thing in mind: In most implementations, 652 // a vertical separator has a preferred height of 0, and a 653 // horizontal separator has a preferred width of 0. This means a 654 // separator is not visible unless you either set its preferred 655 // size or put it in under the control of a layout manager such as 656 // BorderLayout or BoxLayout that stretches it to fill its 657 // available display area." 658 // WHO ON EARTH DECIDED THAT WAS SENSIBLE DEFAULT BEHAVIOR FOR A 659 // SEPARATOR WIDGET!? 660 separator.setMinimumSize(new Dimension(1, 12)); 661 separator.setPreferredSize(new Dimension(1, 12)); 662 663 JLabel editorLabel = 664 new JLabel("Open the file in the NcML editor:"); 665 666 JButton editorButton = new JButton("NcML Editor"); 667 editorButton.addActionListener(e -> showNcMLEditor()); 668 669 JLabel viewLabel = 670 new JLabel("I just want to view one of the variables:"); 671 672 JButton viewButton = new JButton("View Variable"); 673 viewButton.addActionListener(e -> showVarPicker()); 674 675 JButton closeButton = new JButton("Close"); 676 closeButton.addActionListener(e -> dispose()); 677 678 JLabel noncompliantLabel = 679 new JLabel("I have navigation variables, they just aren't CF-compliant: (FEATURE INCOMPLETE)"); 680 681 JButton noncompliantButton = new JButton("Choose Nav"); 682 noncompliantButton.addActionListener(e -> showNavChooser()); 683 this.addWindowListener(new WindowAdapter() { 684 @Override public void windowClosing(WindowEvent e) { 685 BadNetCDFDialog.this.dispose(); 686 } 687 }); 688 689 contentPane.setLayout(new MigLayout( 690 "", 691 "[grow][]", 692 "[][grow][][][][][][]")); 693 contentPane.add(headerLabel, "spanx 2, alignx left, aligny top, wrap"); 694 contentPane.add(messageTextPane, "spanx 2, grow, wrap"); 695 contentPane.add(separator, "spanx 2, growx, aligny top, wrap"); 696 contentPane.add(editorLabel, "alignx left, aligny baseline"); 697 contentPane.add(editorButton, "growx, aligny baseline, wrap"); 698 contentPane.add(viewLabel, "alignx left, aligny baseline"); 699 contentPane.add(viewButton, "growx, aligny baseline, wrap"); 700 contentPane.add(noncompliantLabel, "alignx left, aligny baseline"); 701 contentPane.add(noncompliantButton, "growx, aligny baseline, wrap"); 702 contentPane.add(closeButton, "alignx left, aligny baseline"); 703 pack(); 704 } 705 } 706 707 /** 708 * Tester function to pick a file and send it through the paces. 709 * 710 * @param args Incoming arguments. 711 */ 712 public static void main(String... args) { 713 String testfile = FileManager.getReadFile(); 714 BadNetCDFWidget bfReader; 715 try { 716 bfReader = new BadNetCDFWidget(testfile); 717 bfReader.showChoices(); 718 //bfReader.showNavChooser(); 719 } catch (Exception exe) { 720 logger.error("Could not read '"+testfile+'\'', exe); 721 } 722 } 723}