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 */ 028 029package edu.wisc.ssec.mcidasv.chooser; 030 031import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.*; 032import static javax.swing.BorderFactory.*; 033import static javax.swing.GroupLayout.DEFAULT_SIZE; 034import static javax.swing.GroupLayout.Alignment.BASELINE; 035import static javax.swing.GroupLayout.Alignment.LEADING; 036import static javax.swing.GroupLayout.Alignment.TRAILING; 037import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 038import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED; 039import static ucar.unidata.util.IOUtil.hasSuffix; 040 041import java.awt.Component; 042import java.awt.Image; 043import java.awt.MediaTracker; 044import java.awt.Toolkit; 045import java.awt.event.FocusEvent; 046import java.awt.event.FocusListener; 047import java.io.File; 048import java.io.FileNotFoundException; 049import java.io.FileReader; 050import java.io.IOException; 051import java.io.InputStream; 052import java.io.Reader; 053import java.util.ArrayList; 054import java.util.Collections; 055import java.util.Hashtable; 056import java.util.List; 057import java.util.prefs.Preferences; 058 059import javax.swing.AbstractButton; 060import javax.swing.GroupLayout; 061import javax.swing.JButton; 062import javax.swing.JCheckBox; 063import javax.swing.JComboBox; 064import javax.swing.JComponent; 065import javax.swing.JFileChooser; 066import javax.swing.JLabel; 067import javax.swing.JPanel; 068import javax.swing.JRadioButton; 069import javax.swing.JTextField; 070import javax.swing.JToggleButton; 071import javax.swing.text.JTextComponent; 072 073import edu.wisc.ssec.mcidasv.util.CollectionHelpers; 074import org.slf4j.Logger; 075import org.slf4j.LoggerFactory; 076import org.w3c.dom.Element; 077 078import ucar.unidata.idv.IntegratedDataViewer; 079import ucar.unidata.idv.chooser.IdvChooser; 080import ucar.unidata.idv.chooser.IdvChooserManager; 081import ucar.unidata.util.GuiUtils; 082import ucar.unidata.util.IOUtil; 083import ucar.unidata.util.LayoutUtil; 084import ucar.unidata.util.TwoFacedObject; 085import ucar.unidata.xml.XmlUtil; 086import visad.util.ImageHelper; 087import edu.wisc.ssec.mcidasv.Constants; 088import edu.wisc.ssec.mcidasv.data.AxformInfo; 089import edu.wisc.ssec.mcidasv.data.EnviInfo; 090import edu.wisc.ssec.mcidasv.data.HeaderInfo; 091import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position; 092import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Prefer; 093import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor; 094import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width; 095import edu.wisc.ssec.mcidasv.util.McVGuiUtils.IconPanel; 096 097public class FlatFileChooser extends IdvChooser implements Constants { 098 099 private static final long serialVersionUID = 1L; 100 private static final Logger logger = LoggerFactory.getLogger(FlatFileChooser.class); 101 102 /** Set default stride to keep dimensions within this */ 103 private final int maxDefDim = 1000; 104 105 // Properties associated with the button selector 106 private File dataFile; 107 private final JTextField dataFileText = new JTextField(); 108 private JButton dataFileButton = new JButton(); 109 private final JLabel dataFileDescription = new JLabel(); 110 private final JLabel textDescription = new JLabel(); 111 112 // Dimensions 113 // elements, lines, bands 114 private final JTextComponent textElements = new JTextField(); 115 private final JTextComponent textLines = new JTextField(); 116 private final JTextComponent textBands = new JTextField(); 117 private final JTextComponent textUnit = new JTextField(); 118 private final JTextComponent textStride = new JTextField(); 119 private final AbstractButton checkTranspose = new JCheckBox("Transpose elements/lines"); 120 private final List<String> bandNames = new ArrayList<>(20); 121 private final List<String> bandFiles = new ArrayList<>(20); 122 123 // Navigation 124 // lat/lon files or bounds 125 private final JToggleButton radioLatLonFiles = new JRadioButton("Files", true); 126 private final JToggleButton radioLatLonBounds = new JRadioButton("Bounds", false); 127 private File latFile; 128 private File lonFile; 129 private final JLabel textLatFile = new JLabel(); 130 private JButton buttonLatFile = new JButton(); 131 private final JLabel textLonFile = new JLabel(); 132 private JButton buttonLonFile = new JButton(); 133 private JPanel panelLatLonFiles = new JPanel(); 134 private final JTextComponent textLatUL = new JTextField(); 135 private final JTextComponent textLonUL = new JTextField(); 136 private final JTextComponent textLatLR = new JTextField(); 137 private final JTextComponent textLonLR = new JTextField(); 138 private JPanel panelLatLonBounds = new JPanel(); 139 private final JTextComponent textLatLonScale = new JTextField(); 140 private final AbstractButton checkEastPositive = new JCheckBox("East positive"); 141 142 143 // Properties associated with the data file 144 // bytes/pixel, ASCII delimiter, endianness, interleave, offset, missing 145 private final JToggleButton radioBinary = new JRadioButton("Binary", true); 146 private final JToggleButton radioASCII = new JRadioButton("ASCII", false); 147 private final JToggleButton radioImage = new JRadioButton("Image", false); 148 private final JToggleButton radioEndianLittle = new JRadioButton("Little", true); 149 private final JToggleButton radioEndianBig = new JRadioButton("Big", false); 150 private final JComboBox<TwoFacedObject> comboByteFormat = new JComboBox<>(); 151 private final JComboBox<TwoFacedObject> comboInterleave = new JComboBox<>(); 152 private final JTextComponent textOffset = new JTextField("0"); 153 private JPanel panelBinary = new JPanel(); 154 private final JTextComponent textDelimiter = new JTextField(); 155 private JPanel panelASCII = new JPanel(); 156 private JPanel panelImage = new JPanel(); 157 private final JTextComponent textMissing = new JTextField(); 158 159 private static File lastDir = null; 160 161 private final List<TwoFacedObject> listByteFormat = CollectionHelpers.list( 162 new TwoFacedObject("1-byte unsigned integer", HeaderInfo.kFormat1ByteUInt), 163 new TwoFacedObject("2-byte signed integer", HeaderInfo.kFormat2ByteSInt), 164 new TwoFacedObject("4-byte signed integer", HeaderInfo.kFormat4ByteSInt), 165 new TwoFacedObject("4-byte float", HeaderInfo.kFormat4ByteFloat), 166 new TwoFacedObject("8-byte double", HeaderInfo.kFormat8ByteDouble), 167 new TwoFacedObject("2x8-byte complex number", HeaderInfo.kFormat2x8Byte), 168 new TwoFacedObject("2-byte unsigned integer", HeaderInfo.kFormat2ByteUInt) 169 ); 170 171 private final List<TwoFacedObject> listInterleave = CollectionHelpers.list( 172 new TwoFacedObject("Sequential", HeaderInfo.kInterleaveSequential), 173 new TwoFacedObject("By line", HeaderInfo.kInterleaveByLine), 174 new TwoFacedObject("By pixel", HeaderInfo.kInterleaveByPixel) 175 ); 176 177 private final JLabel statusLabel = new JLabel("Status"); 178 179 /** Handle to the IDV. */ 180 protected IntegratedDataViewer idv = getIdv(); 181 182 /** 183 * Super setStatus() takes a second string to enable "simple" mode 184 * which highlights the required component. We don't really care 185 * about that feature, and we don't want getStatusLabel() to 186 * change the label background color. 187 */ 188 @Override 189 public void setStatus(String statusString, String foo) { 190 if (statusString == null) { 191 statusString = ""; 192 } 193 statusLabel.setText(statusString); 194 } 195 196 /** 197 * Create the FileChooser, passing in the manager and the xml element 198 * from choosers.xml 199 * 200 * @param mgr The manager 201 * @param root The xml root 202 */ 203 public FlatFileChooser(IdvChooserManager mgr, Element root) { 204 super(mgr, root); 205 206 loadButton = makeImageTextButton(ICON_ACCEPT_SMALL, getLoadCommandName()); 207 loadButton.setActionCommand(getLoadCommandName()); 208 loadButton.addActionListener(this); 209 210 dataFileButton = makeImageButton(ICON_OPEN, "Open file"); 211 dataFileButton.addActionListener(e -> { 212 dataFile = getDataFile(dataFile); 213 if (dataFile != null) { 214 dataFileText.setText(dataFile.getAbsolutePath()); 215 inspectDataFile(dataFile); 216 } 217 }); 218 dataFileText.addActionListener(e -> { 219 dataFile = new File(dataFileText.getText()); 220 inspectDataFile(dataFile); 221 }); 222 dataFileText.addFocusListener(new FocusListener() { 223 @Override public void focusGained(FocusEvent e) {} 224 @Override public void focusLost(FocusEvent e) { 225 dataFile = new File(dataFileText.getText()); 226 inspectDataFile(dataFile); 227 } 228 }); 229 230 radioLatLonFiles.addActionListener(e -> checkSetLatLon()); 231 radioLatLonBounds.addActionListener(e -> checkSetLatLon()); 232 233 buttonLatFile = makeImageButton(ICON_OPEN, "Select latitude file"); 234 buttonLatFile.addActionListener(e -> { 235 latFile = getDataFile(latFile); 236 if (latFile != null) { 237 textLatFile.setText(latFile.getName()); 238 } 239 }); 240 buttonLonFile = makeImageButton(ICON_OPEN, "Select longitude file"); 241 buttonLonFile.addActionListener(e -> { 242 lonFile = getDataFile(lonFile); 243 if (lonFile != null) { 244 textLonFile.setText(lonFile.getName()); 245 } 246 }); 247 GuiUtils.buttonGroup(radioLatLonFiles, radioLatLonBounds); 248 249 radioBinary.addActionListener(e -> checkSetBinaryASCIIImage()); 250 radioASCII.addActionListener(e -> checkSetBinaryASCIIImage()); 251 radioImage.addActionListener(e -> checkSetBinaryASCIIImage()); 252 253 GuiUtils.buttonGroup(radioBinary, radioASCII, radioImage); 254 GuiUtils.buttonGroup(radioEndianLittle, radioEndianBig); 255 256 GuiUtils.setListData(comboByteFormat, listByteFormat); 257 GuiUtils.setListData(comboInterleave, listInterleave); 258 259 setHaveData(false); 260 } 261 262 /** 263 * enable/disable widgets for navigation 264 */ 265 private void checkSetLatLon() { 266 boolean isFile = radioLatLonFiles.isSelected(); 267 GuiUtils.enableTree(panelLatLonFiles, isFile); 268 GuiUtils.enableTree(panelLatLonBounds, !isFile); 269 } 270 271 /** 272 * enable/disable widgets for binary/ASCII 273 */ 274 private void checkSetBinaryASCIIImage() { 275 GuiUtils.enableTree(panelBinary, radioBinary.isSelected()); 276 GuiUtils.enableTree(panelASCII, radioASCII.isSelected()); 277 GuiUtils.enableTree(panelImage, radioImage.isSelected()); 278 } 279 280 /** 281 * Set whether the user has made a selection that contains data. 282 * 283 * @param have true to set the haveData property. Enables the 284 * loading button 285 */ 286 @Override public void setHaveData(boolean have) { 287 super.setHaveData(have); 288 updateStatus(); 289 } 290 291 /** 292 * Set the status message appropriately 293 */ 294 @Override protected void updateStatus() { 295 super.updateStatus(); 296 checkSetLatLon(); 297 checkSetBinaryASCIIImage(); 298 if (!getHaveData()) { 299 setStatus("Select a file"); 300 } 301 } 302 303 private static boolean isImage(File f) { 304 String s = f.getName(); 305 return hasSuffix(s, ".gif") || hasSuffix(s, ".jpg") || hasSuffix(s, ".png"); 306 } 307 308 private static boolean isXml(File f) { 309 String s = f.getName(); 310 return hasSuffix(s, ".xml") || hasSuffix(s, ".ximg"); 311 } 312 313 private void inspectDataFile(File thisFile) { 314 if ((thisFile == null) || thisFile.getName().isEmpty()) { 315 dataFileDescription.setText(""); 316 setHaveData(false); 317 } else if (!thisFile.exists()) { 318 dataFileDescription.setText("File does not exist"); 319 setHaveData(false); 320 } else { 321 try (Reader fr = new FileReader(thisFile)) { 322 char[] first80c = new char[80]; 323 fr.read(first80c, 0, 80); 324 String first80 = new String(first80c); 325 326 clearValues(); 327 328 boolean doStride = false; 329 330 if (isImage(thisFile)) { 331 dataFileDescription.setText("Image file"); 332 processImageFile(thisFile); 333 } else if (isXml(thisFile)) { 334 dataFileDescription.setText("XML image header file"); 335 processXmlHeaderFile(thisFile); 336 } else if (first80.contains(" Space Science & Engineering Center")) { 337 dataFileDescription.setText("McIDAS-X AXFORM header file"); 338 processAxformHeaderFile(thisFile); 339 doStride = true; 340 } else if (isENVI()) { 341 dataFileDescription.setText("ENVI Data File"); 342 logger.trace("Found ENVI file, about to process header..."); 343 processEnviHeaderFile(new File(dataFile.getAbsolutePath().replace(".img", ".hdr"))); 344 doStride = true; 345 } else { 346 dataFileDescription.setText("Binary, ASCII or Image data"); 347 processGenericFile(thisFile); 348 doStride = true; 349 } 350 351 // Default the stride 352 int newStride = 1; 353 if (doStride) { 354 String textLinesText = textLines.getText(); 355 String textElementsText = textElements.getText(); 356 if (!(textLinesText.isEmpty() || textElementsText.isEmpty())) { 357 int myLines = Integer.parseInt(textLinesText); 358 int myElements = Integer.parseInt(textElementsText); 359 if ((myLines > maxDefDim) || (myElements > maxDefDim)) { 360 newStride = Math.max((int) Math.ceil((float) myLines / (float) maxDefDim), (int) Math.ceil((float) myElements / (float) maxDefDim)); 361 } 362 } 363 } 364 textStride.setText(Integer.toString(newStride)); 365 setHaveData(true); 366 } catch (Exception e) { 367 logger.error("error inspecting file '" + thisFile + '\'', e); 368 } 369 } 370 } 371 372 private boolean isENVI() { 373 // look for a corresponding header file 374 // filename.replace(".hdr", ".img"); 375 boolean result = false; 376 File f = new File(dataFile.getAbsolutePath().replace(".img", ".hdr")); 377// if (!f.exists()) { 378// return false; 379// } 380 if (f.exists()) { 381 try (Reader fr = new FileReader(f)) { 382 char[] first80c = new char[80]; 383 fr.read(first80c, 0, 80); 384 fr.close(); 385 String first80 = new String(first80c); 386 if (first80.contains("ENVI")) { 387 result = true; 388 } 389 } catch (FileNotFoundException fnfe) { 390 logger.error("could not locate file", fnfe); 391 } catch (IOException ioe) { 392 logger.error("could not read file", ioe); 393 } 394 } 395 return result; 396 } 397 398 /** 399 * Special processing for a known data type. 400 * 401 * <p>This deals specifically with AXFORM header files.</p> 402 * 403 * @param thisFile AXFORM header file. 404 */ 405 private void processAxformHeaderFile(File thisFile) { 406 bandNames.clear(); 407 bandFiles.clear(); 408 409 try { 410 AxformInfo axformInfo = new AxformInfo(thisFile); 411 412 // Set the properties in the GUI 413 textDescription.setText(axformInfo.getParameter(HeaderInfo.DESCRIPTION, "")); 414 textElements.setText(axformInfo.getParameter(HeaderInfo.ELEMENTS, 0).toString()); 415 textLines.setText(axformInfo.getParameter(HeaderInfo.LINES, 0).toString()); 416 textUnit.setText(axformInfo.getParameter(HeaderInfo.UNIT, "")); 417 bandNames.addAll(axformInfo.getParameter(HeaderInfo.BANDNAMES, Collections.emptyList())); 418 bandFiles.addAll(axformInfo.getParameter(HeaderInfo.BANDFILES, Collections.emptyList())); 419 textBands.setText(Integer.toString(bandNames.size())); 420 textOffset.setText(axformInfo.getParameter(HeaderInfo.OFFSET, 0).toString()); 421 textMissing.setText(axformInfo.getParameter(HeaderInfo.MISSINGVALUE, (float)0).toString()); 422 423 Integer dataType = axformInfo.getParameter(HeaderInfo.DATATYPE, HeaderInfo.kFormatUnknown); 424 Boolean bigEndian = axformInfo.getParameter(HeaderInfo.BIGENDIAN, Boolean.FALSE); 425 if (dataType == HeaderInfo.kFormatASCII) { 426 radioASCII.setSelected(true); 427 } else if (dataType == HeaderInfo.kFormatImage) { 428 radioImage.setSelected(true); 429 } else { 430 radioBinary.setSelected(true); 431 TwoFacedObject tfo = TwoFacedObject.findId(dataType.intValue(), listByteFormat); 432 if (tfo != null) { 433 comboByteFormat.setSelectedItem(tfo); 434 } 435 tfo = TwoFacedObject.findId(HeaderInfo.kInterleaveSequential, listInterleave); 436 if (tfo != null) { 437 comboInterleave.setSelectedItem(tfo); 438 } 439 } 440 441 radioEndianLittle.setSelected(!bigEndian); 442 radioEndianBig.setSelected(bigEndian); 443 444 List<String> latlonFiles = axformInfo.getParameter(HeaderInfo.NAVFILES, new ArrayList<String>(4)); 445 if (latlonFiles.size() == 2) { 446 latFile = new File(latlonFiles.get(0)); 447 lonFile = new File(latlonFiles.get(1)); 448 } 449 450 if ((latFile == null) || (lonFile == null)) { 451 radioLatLonBounds.setSelected(true); 452 } else { 453 textLatFile.setText(latFile.getName()); 454 textLonFile.setText(lonFile.getName()); 455 radioLatLonFiles.setSelected(true); 456 } 457 458 textLatLonScale.setText("100"); 459 checkEastPositive.setSelected(true); 460 } catch (Exception e) { 461 logger.error("error processing AXFORM header file", e); 462 } 463 } 464 465 /** 466 * Special processing for a known data type. 467 * 468 * <p>This deals specifically with ENVI header files.</p> 469 * 470 * @param thisFile ENVI header file. 471 */ 472 private void processEnviHeaderFile(File thisFile) { 473 try { 474 EnviInfo enviInfo = new EnviInfo(thisFile); 475 476 // Set the properties in the GUI 477 textDescription.setText(enviInfo.getParameter(HeaderInfo.DESCRIPTION, "")); 478 textElements.setText(enviInfo.getParameter(HeaderInfo.ELEMENTS, 0).toString()); 479 textLines.setText(enviInfo.getParameter(HeaderInfo.LINES, 0).toString()); 480 textUnit.setText(enviInfo.getParameter(HeaderInfo.UNIT, "")); 481 bandNames.addAll(enviInfo.getParameter(HeaderInfo.BANDNAMES, Collections.emptyList())); 482 bandFiles.addAll(enviInfo.getParameter(HeaderInfo.BANDFILES, Collections.emptyList())); 483 textBands.setText(Integer.toString(bandNames.size())); 484 textOffset.setText(enviInfo.getParameter(HeaderInfo.OFFSET, 0).toString()); 485 textMissing.setText(enviInfo.getParameter(HeaderInfo.MISSINGVALUE, (float)0).toString()); 486 487 Integer dataType = enviInfo.getParameter(HeaderInfo.DATATYPE, HeaderInfo.kFormatUnknown); 488 String interleaveType = enviInfo.getParameter(HeaderInfo.INTERLEAVE, HeaderInfo.kInterleaveSequential); 489 Boolean bigEndian = enviInfo.getParameter(HeaderInfo.BIGENDIAN, Boolean.FALSE); 490 radioBinary.setSelected(true); 491 TwoFacedObject tfo = TwoFacedObject.findId(dataType.intValue(), listByteFormat); 492 if (tfo != null) { 493 comboByteFormat.setSelectedItem(tfo); 494 } 495 tfo = TwoFacedObject.findId(interleaveType, listInterleave); 496 if (tfo != null) { 497 comboInterleave.setSelectedItem(tfo); 498 } 499 500 radioEndianLittle.setSelected(!bigEndian); 501 radioEndianBig.setSelected(bigEndian); 502 503 // Look for a geo.hdr file that contains Latitude and Longitude bands 504 String parent = thisFile.getParent(); 505 if (parent == null) { 506 parent = "."; 507 } 508 String navFile = thisFile.getName().replace(".hdr", ""); 509 int lastDot = navFile.lastIndexOf('.'); 510 if (lastDot >= 0) { 511 navFile = navFile.substring(0, lastDot) + ".geo.hdr"; 512 } 513 navFile = parent + '/' + navFile; 514 EnviInfo navInfo = new EnviInfo(navFile); 515 if (navInfo.isNavHeader()) { 516 latFile = new File(navFile); 517 lonFile = new File(navFile); 518 } 519 520 if ((latFile == null) || (lonFile == null)) { 521 radioLatLonBounds.setSelected(true); 522 } 523 else { 524 textLatFile.setText(latFile.getName()); 525 textLonFile.setText(lonFile.getName()); 526 radioLatLonFiles.setSelected(true); 527 } 528 529 // fill in Lat/Lon bounds if we can 530 if (enviInfo.isHasBounds()) { 531 textLatUL.setText(enviInfo.getParameter("BOUNDS.ULLAT", "")); 532 textLonUL.setText(enviInfo.getParameter("BOUNDS.ULLON", "")); 533 textLatLR.setText(enviInfo.getParameter("BOUNDS.LRLAT", "")); 534 textLonLR.setText(enviInfo.getParameter("BOUNDS.LRLON", "")); 535 } 536 537 textLatLonScale.setText("1"); 538 checkEastPositive.setSelected(false); 539 } catch (Exception e) { 540 logger.error("error processing ENVI header file", e); 541 } 542 } 543 544 /** 545 * Special processing for a known data type. 546 * 547 * <p>This deals specifically with XML header files.</p> 548 * 549 * @param thisFile XML header file. 550 */ 551 private void processXmlHeaderFile(File thisFile) { 552 try { 553 554 bandFiles.clear(); 555 bandNames.clear(); 556 557 Element root = XmlUtil.getRoot(thisFile.getAbsolutePath(), getClass()); 558 if (!"image".equals(root.getTagName())) { 559 processGenericFile(thisFile); 560 return; 561 } 562 563 String description = XmlUtil.getAttribute(root, "name", (String)null); 564 String url = ""; 565 if (XmlUtil.hasAttribute(root, "url")) { 566 url = XmlUtil.getAttribute(root, "url"); 567 if (description.isEmpty()) { 568 description = url; 569 } 570 String parent = thisFile.getParent(); 571 if (parent == null) { 572 parent = "."; 573 } 574 url = parent + '/' + url; 575 } 576 else { 577 processGenericFile(thisFile); 578 return; 579 } 580 if (XmlUtil.hasAttribute(root, "ullat")) { 581 radioLatLonBounds.setSelected(true); 582 textLatUL.setText(XmlUtil.getAttribute(root, "ullat")); 583 } 584 if (XmlUtil.hasAttribute(root, "ullon")) { 585 radioLatLonBounds.setSelected(true); 586 textLonUL.setText(XmlUtil.getAttribute(root, "ullon")); 587 } 588 if (XmlUtil.hasAttribute(root, "lrlat")) { 589 radioLatLonBounds.setSelected(true); 590 textLatLR.setText(XmlUtil.getAttribute(root, "lrlat")); 591 } 592 if (XmlUtil.hasAttribute(root, "lrlon")) { 593 radioLatLonBounds.setSelected(true); 594 textLonLR.setText(XmlUtil.getAttribute(root, "lrlon")); 595 } 596 597 // Try to read the referenced image to get lines and elements 598 setStatus("Loading image"); 599 InputStream is = null; 600 is = IOUtil.getInputStream(url, getClass()); 601 byte[] imageContent = IOUtil.readBytes(is); 602 Image image = Toolkit.getDefaultToolkit().createImage(imageContent); 603 MediaTracker tracker = new MediaTracker(this); 604 tracker.addImage(image, 0); 605 try { 606 tracker.waitForAll(); 607 } catch(InterruptedException e) {} 608 ImageHelper ih = new ImageHelper(); 609 image.getWidth(ih); 610 if (ih.badImage) { 611 throw new IllegalStateException("Bad image: " + url); 612 } 613 int elements = image.getWidth(ih); 614 int lines = image.getHeight(ih); 615 616 // Bands 617 bandFiles.add(url); 618 bandNames.add("XML image file"); 619 620 // Set the properties in the GUI 621 textDescription.setText(description); 622 textElements.setText(Integer.toString(elements)); 623 textLines.setText(Integer.toString(lines)); 624 textBands.setText(Integer.toString(bandNames.size())); 625 626 radioImage.setSelected(true); 627 textMissing.setText(Float.toString(-1.0F)); 628 629 textLatLonScale.setText("1"); 630 checkEastPositive.setSelected(false); 631 } catch (Exception e) { 632 logger.error("error processing XML header file", e); 633 } 634 } 635 636 /** 637 * Special processing for a known data type. 638 * 639 * <p>This deals specifically with {@literal "image"} files.</p> 640 * 641 * @param thisFile Image file. 642 */ 643 private void processImageFile(File thisFile) { 644 try { 645 646 bandFiles.clear(); 647 bandNames.clear(); 648 649 String description = thisFile.getName(); 650 String url = thisFile.getAbsolutePath(); 651 652 // Try to read the referenced image to get lines and elements 653 setStatus("Loading image"); 654 InputStream is = null; 655 is = IOUtil.getInputStream(url, getClass()); 656 byte[] imageContent = IOUtil.readBytes(is); 657 Image image = Toolkit.getDefaultToolkit().createImage(imageContent); 658 MediaTracker tracker = new MediaTracker(this); 659 tracker.addImage(image, 0); 660 try { 661 tracker.waitForAll(); 662 } catch (InterruptedException e) {} 663 ImageHelper ih = new ImageHelper(); 664 image.getWidth(ih); 665 if (ih.badImage) { 666 throw new IllegalStateException("Bad image: " + url); 667 } 668 669 // Bands 670 bandFiles.add(url); 671 bandNames.add("Image file"); 672 673 // Set the properties in the GUI 674 textDescription.setText(description); 675 textElements.setText(Integer.toString(image.getWidth(ih))); 676 textLines.setText(Integer.toString(image.getHeight(ih))); 677 textBands.setText(Integer.toString(bandNames.size())); 678 679 radioImage.setSelected(true); 680 textMissing.setText(Float.toString(-1.0F)); 681 682 textLatLonScale.setText("1"); 683 checkEastPositive.setSelected(false); 684 } 685 catch (Exception e) { 686 logger.error("error processing image file", e); 687 } 688 } 689 690 /** 691 * Special processing for an unknown data type. 692 * 693 * <p>Can we glean anything about the file by inspecting it more?</p> 694 * 695 * @param thisFile Unknown file type. 696 */ 697 private void processGenericFile(File thisFile) { 698 699 clearValues(); 700 701 // Set appropriate defaults 702 // Bands 703 bandFiles.clear(); 704 bandFiles.add(thisFile.getAbsolutePath()); 705 bandNames.clear(); 706 bandNames.add("Flat data"); 707 708 // Set the properties in the GUI 709 textDescription.setText(thisFile.getName()); 710// textElements.setText(Integer.toString(elements)); 711// textLines.setText(Integer.toString(lines)); 712 textBands.setText("1"); 713 714 radioBinary.setSelected(true); 715 716 textLatLonScale.setText("1"); 717 checkEastPositive.setSelected(false); 718 719 } 720 721 /** 722 * Clear out any data values presented to the user 723 */ 724 private void clearValues() { 725 textDescription.setText(""); 726 textElements.setText(""); 727 textLines.setText(""); 728 textBands.setText(""); 729 textUnit.setText(""); 730 textStride.setText(""); 731 checkTranspose.setSelected(false); 732 733 textLatFile.setText(""); 734 textLonFile.setText(""); 735 textLatUL.setText(""); 736 textLonUL.setText(""); 737 textLatLR.setText(""); 738 textLonLR.setText(""); 739 740 textLatLonScale.setText(""); 741 checkEastPositive.setSelected(false); 742 743 textOffset.setText("0"); 744 textMissing.setText(""); 745 } 746 747 /** 748 * Ask the user for a data file. 749 * 750 * @param thisFile File or directory to use as initial location. 751 * 752 * @return Selected data file. 753 */ 754 private static File getDataFile(File thisFile) { 755 Preferences prefs = Preferences.userNodeForPackage(FlatFileChooser.class); 756 757 // If no file provided, try in-memory lastDir first 758 if (thisFile == null && lastDir != null) { 759 thisFile = lastDir; 760 } 761 762 // If still nothing, fall back to stored preference 763 if (thisFile == null) { 764 String storedDir = prefs.get("flatfile.lastDir", null); 765 if (storedDir != null) { 766 thisFile = new File(storedDir); 767 } 768 } 769 770 JFileChooser fileChooser = new JFileChooser(thisFile); 771 fileChooser.setMultiSelectionEnabled(false); 772 int status = fileChooser.showOpenDialog(null); 773 if (status == JFileChooser.APPROVE_OPTION) { 774 thisFile = fileChooser.getSelectedFile(); 775 // Save directory in memory (for same session) 776 lastDir = thisFile.getParentFile(); 777 // Save directory in preferences (for next session) 778 prefs.put("flatfile.lastDir", lastDir.getAbsolutePath()); 779 } 780 return thisFile; 781 } 782 783 /** 784 * Get the name of the dataset. 785 * 786 * @return descriptive name of the dataset. 787 */ 788 public static String getDatasetName() { 789 return "Data Set Name"; 790 } 791 792 /** 793 * Get the properties from the datasource 794 * 795 * @param ht a Hashtable of properties 796 */ 797 @Override protected void getDataSourceProperties(Hashtable ht) { 798 super.getDataSourceProperties(ht); 799 ht.put("FLAT.NAME", textDescription.getText()); 800 ht.put("FLAT.ELEMENTS", textElements.getText()); 801 ht.put("FLAT.LINES", textLines.getText()); 802 ht.put("FLAT.BANDNAMES", bandNames); 803 ht.put("FLAT.BANDFILES", bandFiles); 804 ht.put("FLAT.UNIT", textUnit.getText()); 805 ht.put("FLAT.STRIDE", textStride.getText()); 806 ht.put("FLAT.TRANSPOSE", checkTranspose.isSelected()); 807 ht.put("FLAT.MISSING", textMissing.getText()); 808 809 // Navigation 810 if (radioLatLonFiles.isSelected()) { 811 ht.put("NAV.TYPE", "FILES"); 812 ht.put("FILE.LAT", latFile.getAbsolutePath()); 813 ht.put("FILE.LON", lonFile.getAbsolutePath()); 814 } else if (radioLatLonBounds.isSelected()) { 815 ht.put("NAV.TYPE", "BOUNDS"); 816 ht.put("BOUNDS.ULLAT", textLatUL.getText()); 817 ht.put("BOUNDS.ULLON", textLonUL.getText()); 818 ht.put("BOUNDS.LRLAT", textLatLR.getText()); 819 ht.put("BOUNDS.LRLON", textLonLR.getText()); 820 } else { 821 ht.put("NAV.TYPE", "UNKNOWN"); 822 } 823 ht.put("NAV.SCALE", textLatLonScale.getText()); 824 ht.put("NAV.EASTPOS", checkEastPositive.isSelected()); 825 826 // Data type 827 if (radioBinary.isSelected()) { 828 TwoFacedObject format = (TwoFacedObject) comboByteFormat.getSelectedItem(); 829 TwoFacedObject interleave = (TwoFacedObject) comboInterleave.getSelectedItem(); 830 ht.put("FORMAT.TYPE", "BINARY"); 831 ht.put("BINARY.FORMAT", format.getId()); 832 ht.put("BINARY.INTERLEAVE", interleave.getId()); 833 ht.put("BINARY.BIGENDIAN", radioEndianBig.isSelected()); 834 ht.put("BINARY.OFFSET", textOffset.getText()); 835 } else if (radioASCII.isSelected()) { 836 ht.put("FORMAT.TYPE", "ASCII"); 837 ht.put("ASCII.DELIMITER", textDelimiter.getText()); 838 } else if (radioImage.isSelected()) { 839 ht.put("FORMAT.TYPE", "IMAGE"); 840 } else { 841 ht.put("FORMAT.TYPE", "UNKNOWN"); 842 } 843 844 } 845 846 /** 847 * User said go, so we go. 848 * 849 * <p>Simply get the list of images from the imageChooser and create the 850 * {@code FILE.FLAT} {@code DataSource}.</p> 851 */ 852 public void doLoadInThread() { 853 String definingObject = dataFileText.getText(); 854 String dataType = "FILE.FLAT"; 855 856 Hashtable properties = new Hashtable(); 857 getDataSourceProperties(properties); 858 859 makeDataSource(definingObject, dataType, properties); 860 } 861 862 /** 863 * Creates the dimensions inner panel. 864 * 865 * @return The {@literal "dimensions"} panel. 866 */ 867 protected JPanel makeDimensionsPanel() { 868 JPanel myPanel = new JPanel(); 869 myPanel.setBorder(createTitledBorder("Dimensions")); 870 871 JLabel elementsLabel = makeLabelRight("Elements:"); 872 setComponentWidth(textElements); 873 874 JLabel linesLabel = makeLabelRight("Lines:"); 875 setComponentWidth(textLines); 876 877 JLabel bandsLabel = makeLabelRight("Bands:"); 878 setComponentWidth(textBands); 879 880 JLabel unitLabel = makeLabelRight("Units:"); 881 setComponentWidth(textUnit); 882 883 JLabel strideLabel = makeLabelRight("Sampling:"); 884 setComponentWidth(textStride); 885 886// JLabel transposeLabel = McVGuiUtils.makeLabelRight(""); 887 888 GroupLayout layout = new GroupLayout(myPanel); 889 myPanel.setLayout(layout); 890 layout.setHorizontalGroup( 891 layout.createParallelGroup(LEADING) 892 .addGroup(layout.createSequentialGroup() 893 .addContainerGap() 894 .addGroup(layout.createParallelGroup(LEADING) 895 .addGroup(layout.createSequentialGroup() 896 .addComponent(elementsLabel) 897 .addGap(GAP_RELATED) 898 .addComponent(textElements)) 899 .addGroup(layout.createSequentialGroup() 900 .addComponent(linesLabel) 901 .addGap(GAP_RELATED) 902 .addComponent(textLines)) 903 .addGroup(layout.createSequentialGroup() 904 .addComponent(bandsLabel) 905 .addGap(GAP_RELATED) 906 .addComponent(textBands)) 907 .addGroup(layout.createSequentialGroup() 908 .addComponent(unitLabel) 909 .addGap(GAP_RELATED) 910 .addComponent(textUnit)) 911 .addGroup(layout.createSequentialGroup() 912 .addComponent(strideLabel) 913 .addGap(GAP_RELATED) 914 .addComponent(textStride))) 915 .addContainerGap()) 916 ); 917 layout.setVerticalGroup( 918 layout.createParallelGroup(LEADING) 919 .addGroup(TRAILING, layout.createSequentialGroup() 920 .addContainerGap() 921 .addGroup(layout.createParallelGroup(BASELINE) 922 .addComponent(textElements) 923 .addComponent(elementsLabel)) 924 .addPreferredGap(RELATED) 925 .addGroup(layout.createParallelGroup(BASELINE) 926 .addComponent(textLines) 927 .addComponent(linesLabel)) 928 .addPreferredGap(RELATED) 929 .addGroup(layout.createParallelGroup(BASELINE) 930 .addComponent(textBands) 931 .addComponent(bandsLabel)) 932 .addPreferredGap(RELATED) 933 .addGroup(layout.createParallelGroup(BASELINE) 934 .addComponent(textUnit) 935 .addComponent(unitLabel)) 936 .addPreferredGap(RELATED) 937 .addGroup(layout.createParallelGroup(BASELINE) 938 .addComponent(textStride) 939 .addComponent(strideLabel)) 940 .addContainerGap()) 941 ); 942 943 return myPanel; 944 } 945 946 /** 947 * Creates the navigation inner panel. 948 * 949 * @return The {@literal "navigation"} panel. 950 */ 951 protected JPanel makeNavigationPanel() { 952 JPanel myPanel = new JPanel(); 953 myPanel.setBorder(createTitledBorder("Navigation")); 954 955 setComponentWidth(textLatFile, Width.DOUBLE); 956 setComponentWidth(textLonFile, Width.DOUBLE); 957 panelLatLonFiles = topBottom( 958 LayoutUtil.leftRight(makeLabeledComponent("Latitude:", textLatFile), buttonLatFile), 959 LayoutUtil.leftRight(makeLabeledComponent("Longitude:", textLonFile), buttonLonFile), 960 Prefer.NEITHER); 961 962 // Images to make the bounds more clear 963 Component urPanel = new IconPanel("/edu/wisc/ssec/mcidasv/images/upper_right.gif"); 964 Component llPanel = new IconPanel("/edu/wisc/ssec/mcidasv/images/lower_left.gif"); 965 966 setComponentWidth(textLatUL); 967 setComponentWidth(textLonUL); 968 setComponentWidth(textLatLR); 969 setComponentWidth(textLonLR); 970 panelLatLonBounds = topBottom( 971 makeLabeledComponent("UL Lat/Lon:", LayoutUtil.leftRight(GuiUtils.hbox(textLatUL, textLonUL), urPanel)), 972 makeLabeledComponent("LR Lat/Lon:", LayoutUtil.leftRight(llPanel, GuiUtils.hbox(textLatLR, textLonLR))), 973 Prefer.NEITHER); 974 975 setComponentWidth(radioLatLonFiles); 976 setComponentWidth(radioLatLonBounds); 977 978 JLabel labelScale = makeLabelRight("Scale:"); 979 setComponentWidth(textLatLonScale); 980 981 JPanel panelScaleEastPositive = LayoutUtil.hbox(textLatLonScale, checkEastPositive); 982 983 GroupLayout layout = new GroupLayout(myPanel); 984 myPanel.setLayout(layout); 985 layout.setHorizontalGroup( 986 layout.createParallelGroup(LEADING) 987 .addGroup(layout.createSequentialGroup() 988 .addContainerGap() 989 .addGroup(layout.createParallelGroup(LEADING) 990 .addGroup(layout.createSequentialGroup() 991 .addComponent(radioLatLonFiles) 992 .addGap(GAP_RELATED) 993 .addComponent(panelLatLonFiles)) 994 .addGroup(layout.createSequentialGroup() 995 .addComponent(radioLatLonBounds) 996 .addGap(GAP_RELATED) 997 .addComponent(panelLatLonBounds)) 998 .addGroup(layout.createSequentialGroup() 999 .addComponent(labelScale) 1000 .addGap(GAP_RELATED) 1001 .addComponent(panelScaleEastPositive))) 1002 .addContainerGap()) 1003 ); 1004 layout.setVerticalGroup( 1005 layout.createParallelGroup(LEADING) 1006 .addGroup(TRAILING, layout.createSequentialGroup() 1007 .addContainerGap() 1008 .addGroup(layout.createParallelGroup(BASELINE) 1009 .addComponent(radioLatLonFiles) 1010 .addComponent(panelLatLonFiles)) 1011 .addPreferredGap(RELATED) 1012 .addGroup(layout.createParallelGroup(BASELINE) 1013 .addComponent(radioLatLonBounds) 1014 .addComponent(panelLatLonBounds)) 1015 .addPreferredGap(RELATED) 1016 .addGroup(layout.createParallelGroup(BASELINE) 1017 .addComponent(labelScale) 1018 .addComponent(panelScaleEastPositive)) 1019 .addContainerGap()) 1020 ); 1021 return myPanel; 1022 } 1023 1024 /** 1025 * Creates the format inner panel. 1026 * 1027 * @return The {@literal "format"} panel. 1028 */ 1029 protected JPanel makeFormatPanel() { 1030 JPanel myPanel = new JPanel(); 1031 myPanel.setBorder(createTitledBorder("Format")); 1032 1033 setComponentWidth(radioBinary); 1034 setComponentWidth(radioASCII); 1035 setComponentWidth(radioImage); 1036 setComponentWidth(radioEndianLittle); 1037 setComponentWidth(radioEndianBig); 1038 1039 setComponentWidth(comboByteFormat, Width.TRIPLE); 1040 setComponentWidth(comboInterleave, Width.DOUBLE); 1041 setComponentWidth(textOffset, Width.HALF); 1042 1043 panelBinary = topBottom( 1044 topBottom( 1045 makeLabeledComponent("Byte format:", comboByteFormat), 1046 makeLabeledComponent("Interleave:", comboInterleave), 1047 Prefer.NEITHER), 1048 topBottom( 1049 makeLabeledComponent("Endian:", GuiUtils.hbox(radioEndianLittle, radioEndianBig, GAP_RELATED)), 1050 makeLabeledComponent("Offset:", makeComponentLabeled(textOffset, "bytes")), 1051 Prefer.NEITHER), 1052 Prefer.NEITHER); 1053 1054 setComponentWidth(textDelimiter, Width.HALF); 1055 panelASCII = makeLabeledComponent("Delimiter:", textDelimiter); 1056 panelImage = new JPanel(); 1057 1058 JLabel missingLabel = makeLabelRight("Missing value:"); 1059 setComponentWidth(textMissing); 1060 JPanel missingPanel = makeComponentLabeled(textMissing, ""); 1061 1062 GroupLayout layout = new GroupLayout(myPanel); 1063 myPanel.setLayout(layout); 1064 layout.setHorizontalGroup( 1065 layout.createParallelGroup(LEADING) 1066 .addGroup(layout.createSequentialGroup() 1067 .addContainerGap() 1068 .addGroup(layout.createParallelGroup(LEADING) 1069 .addGroup(layout.createSequentialGroup() 1070 .addComponent(radioBinary) 1071 .addGap(GAP_RELATED) 1072 .addComponent(panelBinary)) 1073 .addGroup(layout.createSequentialGroup() 1074 .addComponent(radioASCII) 1075 .addGap(GAP_RELATED) 1076 .addComponent(panelASCII)) 1077 .addGroup(layout.createSequentialGroup() 1078 .addComponent(radioImage) 1079 .addGap(GAP_RELATED) 1080 .addComponent(panelImage)) 1081 .addGroup(layout.createSequentialGroup() 1082 .addComponent(missingLabel) 1083 .addGap(GAP_RELATED) 1084 .addComponent(missingPanel))) 1085 .addContainerGap()) 1086 ); 1087 layout.setVerticalGroup( 1088 layout.createParallelGroup(LEADING) 1089 .addGroup(TRAILING, layout.createSequentialGroup() 1090 .addContainerGap() 1091 .addGroup(layout.createParallelGroup(BASELINE) 1092 .addComponent(radioBinary) 1093 .addComponent(panelBinary)) 1094 .addPreferredGap(RELATED) 1095 .addGroup(layout.createParallelGroup(BASELINE) 1096 .addComponent(radioASCII) 1097 .addComponent(panelASCII)) 1098 .addPreferredGap(RELATED) 1099 .addGroup(layout.createParallelGroup(BASELINE) 1100 .addComponent(radioImage) 1101 .addComponent(panelImage)) 1102 .addPreferredGap(RELATED) 1103 .addGroup(layout.createParallelGroup(BASELINE) 1104 .addComponent(missingLabel) 1105 .addComponent(missingPanel)) 1106 .addContainerGap()) 1107 ); 1108 return myPanel; 1109 } 1110 1111 /** 1112 * Creates the main panel properties panel. 1113 * 1114 * @return The {@literal "properties"} panel. 1115 */ 1116 protected JPanel makePropertiesPanel() { 1117 JPanel topPanel = sideBySide(makeDimensionsPanel(), makeNavigationPanel()); 1118 JPanel bottomPanel = makeFormatPanel(); 1119 return topBottom(topPanel, bottomPanel, Prefer.NEITHER); 1120 } 1121 1122 /** 1123 * Builds the GUI of the flat file chooser. 1124 * 1125 * @return GUI of this chooser. 1126 */ 1127 protected JComponent doMakeContents() { 1128 1129 Element chooserNode = getXmlNode(); 1130 String path = (String)idv.getPreference(PREF_DEFAULTDIR + getId()); 1131 if (path == null) { 1132 path = XmlUtil.getAttribute(chooserNode, "path", (String)null); 1133 } 1134 1135 JPanel myPanel = new JPanel(); 1136 1137 // File 1138 JLabel fileLabel = makeLabelRight("File:"); 1139 setComponentWidth(dataFileText, Width.DOUBLEDOUBLE); 1140 1141 JLabel typeLabel = makeLabelRight("Type:"); 1142 setLabelBold(dataFileDescription, true); 1143 1144 JLabel descriptionLabel = makeLabelRight("Description:"); 1145 setLabelBold(textDescription, true); 1146 1147 JLabel propertiesLabel = makeLabelRight("Properties:"); 1148 JPanel propertiesPanel = makePropertiesPanel(); 1149 1150 JLabel statusLabelLabel = makeLabelRight(""); 1151 setLabelPosition(statusLabel, Position.RIGHT); 1152 setComponentColor(statusLabel, TextColor.STATUS); 1153 1154 JButton helpButton = makeImageButton(ICON_HELP, "Show help"); 1155 helpButton.setActionCommand(GuiUtils.CMD_HELP); 1156 helpButton.addActionListener(this); 1157 1158 setComponentWidth(loadButton, Width.DOUBLE); 1159 1160 GroupLayout layout = new GroupLayout(myPanel); 1161 myPanel.setLayout(layout); 1162 layout.setHorizontalGroup( 1163 layout.createParallelGroup(LEADING) 1164 .addGroup(layout.createSequentialGroup() 1165 .addContainerGap() 1166 .addGroup(layout.createParallelGroup(LEADING) 1167 .addGroup(layout.createSequentialGroup() 1168 .addComponent(fileLabel) 1169 .addGap(GAP_RELATED) 1170 .addComponent(dataFileText) 1171 .addGap(GAP_RELATED) 1172 .addComponent(dataFileButton)) 1173 .addGroup(layout.createSequentialGroup() 1174 .addComponent(typeLabel) 1175 .addGap(GAP_RELATED) 1176 .addComponent(dataFileDescription)) 1177 .addGroup(layout.createSequentialGroup() 1178 .addComponent(descriptionLabel) 1179 .addGap(GAP_RELATED) 1180 .addComponent(textDescription)) 1181 .addGroup(layout.createSequentialGroup() 1182 .addComponent(propertiesLabel) 1183 .addGap(GAP_RELATED) 1184 .addComponent(propertiesPanel)) 1185 .addGroup(TRAILING, layout.createSequentialGroup() 1186 .addComponent(helpButton) 1187 .addPreferredGap(RELATED) 1188 .addComponent(loadButton)) 1189 .addGroup(layout.createSequentialGroup() 1190 .addComponent(statusLabelLabel) 1191 .addGap(GAP_RELATED) 1192 .addComponent(statusLabel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))) 1193 .addContainerGap()) 1194 ); 1195 layout.setVerticalGroup( 1196 layout.createParallelGroup(LEADING) 1197 .addGroup(TRAILING, layout.createSequentialGroup() 1198 .addContainerGap() 1199 .addGroup(layout.createParallelGroup(BASELINE) 1200 .addComponent(dataFileButton) 1201 .addComponent(dataFileText) 1202 .addComponent(fileLabel)) 1203 .addPreferredGap(RELATED) 1204 .addGroup(layout.createParallelGroup(BASELINE) 1205 .addComponent(dataFileDescription) 1206 .addComponent(typeLabel)) 1207 .addPreferredGap(RELATED) 1208 .addGroup(layout.createParallelGroup(BASELINE) 1209 .addComponent(textDescription) 1210 .addComponent(descriptionLabel)) 1211 .addPreferredGap(RELATED) 1212 .addGroup(layout.createParallelGroup(BASELINE) 1213 .addComponent(propertiesPanel) 1214 .addComponent(propertiesLabel)) 1215 .addPreferredGap(RELATED, DEFAULT_SIZE, Short.MAX_VALUE) 1216 .addGroup(layout.createParallelGroup(BASELINE) 1217 .addComponent(statusLabelLabel) 1218 .addComponent(statusLabel)) 1219 .addPreferredGap(UNRELATED) 1220 .addGroup(layout.createParallelGroup(BASELINE) 1221 .addComponent(loadButton) 1222 .addComponent(helpButton)) 1223 .addContainerGap()) 1224 ); 1225 return myPanel; 1226 } 1227} 1228