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