001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2024 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 javax.swing.GroupLayout.DEFAULT_SIZE; 032import static javax.swing.GroupLayout.PREFERRED_SIZE; 033import static javax.swing.GroupLayout.Alignment.BASELINE; 034import static javax.swing.GroupLayout.Alignment.LEADING; 035import static javax.swing.GroupLayout.Alignment.TRAILING; 036import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 037import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED; 038 039import java.awt.Component; 040import java.awt.Dimension; 041import java.awt.event.ActionListener; 042import java.awt.event.ItemEvent; 043import java.awt.event.ItemListener; 044import java.io.IOException; 045import java.net.URI; 046import java.util.ArrayList; 047import java.util.Date; 048import java.util.Hashtable; 049import java.util.List; 050import java.util.Vector; 051import java.util.regex.Pattern; 052 053import javax.swing.GroupLayout; 054import javax.swing.JButton; 055import javax.swing.JComboBox; 056import javax.swing.JComponent; 057import javax.swing.JLabel; 058import javax.swing.JPanel; 059import javax.swing.JScrollPane; 060import javax.swing.JTabbedPane; 061import javax.swing.JTextField; 062import javax.swing.event.DocumentEvent; 063import javax.swing.event.DocumentListener; 064import javax.swing.text.BadLocationException; 065 066import edu.wisc.ssec.mcidasv.McIDASV; 067import edu.wisc.ssec.mcidasv.chooser.adde.AddeChooser; 068import edu.wisc.ssec.mcidasv.util.McVTextField; 069import org.jdom2.Document; 070import org.jdom2.JDOMException; 071import org.jdom2.Namespace; 072import org.jdom2.input.SAXBuilder; 073import org.slf4j.Logger; 074import org.slf4j.LoggerFactory; 075import org.w3c.dom.Element; 076 077import thredds.catalog.XMLEntityResolver; 078import ucar.unidata.data.DataSelection; 079import ucar.unidata.data.radar.TDSRadarDatasetCollection; 080import ucar.nc2.units.DateUnit; 081import ucar.unidata.data.radar.RadarQuery; 082import ucar.unidata.geoloc.StationImpl; 083import ucar.unidata.idv.chooser.IdvChooserManager; 084import ucar.unidata.idv.chooser.TimesChooser; 085import ucar.unidata.metdata.NamedStation; 086import ucar.unidata.metdata.NamedStationImpl; 087import ucar.unidata.util.DateSelection; 088import ucar.unidata.util.DateUtil; 089import ucar.unidata.util.DatedThing; 090import ucar.unidata.util.GuiUtils; 091import ucar.unidata.util.LogUtil; 092import ucar.unidata.util.Misc; 093import ucar.unidata.util.PreferenceList; 094import ucar.unidata.util.Product; 095import ucar.unidata.util.TwoFacedObject; 096 097import visad.CommonUnit; 098import visad.DateTime; 099 100import edu.wisc.ssec.mcidasv.Constants; 101import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 102import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Position; 103import edu.wisc.ssec.mcidasv.util.McVGuiUtils.TextColor; 104import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width; 105 106/** 107 * This class is responsible for the {@literal "Remote Level II Radar"} 108 * chooser in McIDAS-V. 109 */ 110public class TDSRadarChooser extends TimesChooser implements Constants { 111 112 /** Loggging object. */ 113 private static final Logger logger = 114 LoggerFactory.getLogger(TDSRadarChooser.class); 115 116 /** The collection */ 117 private TDSRadarDatasetCollection collection; 118 119 /** The currently selected station */ 120 private NamedStation selectedStation; 121 122 /** The currently selected level3 product */ 123 private String selectedProduct; 124 125 /** Those urls we connect to */ 126 //"http://motherlode.ucar.edu:8080/thredds/radarServer/catalog.xml"; 127 private String serverUrl; 128 129 ///** Each dataset collection URL */ 130 //"http://motherlode.ucar.edu:8080/thredds/radarServer/level2/idd/dataset.xml"; 131 //private String collectionUrl; 132 133 /** Component to hold collections */ 134 private JComboBox collectionSelector; 135 136 /** Component to hold product list */ 137 private JComboBox productComboBox; 138 139 /** Level 3 panel that can be hidden */ 140 private JPanel productPanel; 141 142 /** components that need a server for activation */ 143 private List compsThatNeedServer = new ArrayList(); 144 145 /** components that need a server for activation */ 146 private List level3CompsThatNeedServer = new ArrayList(); 147 148 /** persistent holder for catalog URLS */ 149 private PreferenceList urlListHandler; 150 151 /** catalog URL holder */ 152 private JComboBox urlBox; 153 154 /** ok flag */ 155 private boolean okToDoUrlListEvents = true; 156 157 /** dataset list */ 158 private List datasetList; 159 160 /** Command for connecting */ 161 protected static final String CMD_CONNECT = "cmd.connect"; 162 163 /** _more_ */ 164 private boolean isLevel3; 165 166 /** _more_ */ 167 public static final String[] level3_ExName = { "NVW", "DPA" }; 168 169 /** Number of relative time steps to load */ 170 private int relativeTimes = 5; 171 172 /** 173 * Create the RadarChooser. 174 * 175 * @param mgr {@code IdvChooserManager} 176 * @param root XML root that defines this chooser. 177 * 178 */ 179 public TDSRadarChooser(IdvChooserManager mgr, Element root) { 180 super(mgr, root); 181 182 loadButton = McVGuiUtils.makeImageTextButton(ICON_ACCEPT_SMALL, getLoadCommandName()); 183 loadButton.setActionCommand(getLoadCommandName()); 184 loadButton.addActionListener(this); 185 186 cancelButton = McVGuiUtils.makeImageButton(ICON_CANCEL, "Cancel"); 187 cancelButton.setActionCommand(GuiUtils.CMD_CANCEL); 188 cancelButton.addActionListener(this); 189 cancelButton.setEnabled(false); 190 191 } 192 193 194 195 /** 196 * Handle the update event. Just pass it through to the imageChooser 197 */ 198 public void doUpdate() { 199 if ((serverUrl == null) || (datasetList == null) 200 || (datasetList.size() == 0) || (selectedProduct == null)) { 201 if (urlBox != null) { 202 setServer((String) urlBox.getSelectedItem()); 203 } 204 return; 205 } 206 Misc.run(this, "stationOrProductChanged"); 207 } 208 209 210 211 /** 212 * Update the status of the gui 213 */ 214 protected void updateStatus() { 215 super.updateStatus(); 216 if (serverUrl == null) { 217 setHaveData(false); 218 setStatus("Please connect to the server"); 219 } 220 else if (selectedStation == null) { 221 setHaveData(false); 222 setStatus("Please select a station", "stations"); 223 } 224 else if (isLevel3 && (selectedProduct == null)) { 225 setHaveData(false); 226 setStatus("Please select a level 3 product", "products"); 227 } 228 else { 229 boolean haveTimesSelected; 230 if (getDoAbsoluteTimes()) { 231 haveTimesSelected = getSelectedAbsoluteTimes().size() > 0; 232 } else { 233 haveTimesSelected = true; 234 } 235 setHaveData(haveTimesSelected); 236 if (haveTimesSelected) { 237 setStatus("Press \"" + CMD_LOAD + "\" to load the selected radar data", "buttons"); 238 } else { 239 setStatus("Please select times", "timepanel"); 240 } 241 } 242 GuiUtils.enableTree(loadButton, getHaveData()); 243 if (drivercbx != null) { 244 drivercbx.setEnabled(anyTimeDrivers() && getHaveData()); 245 } 246 } 247 248 249 250 /** 251 * Handle when there are newly selected stations 252 * 253 * @param stations list of newly selected stations 254 */ 255 protected void newSelectedStations(List stations) { 256 super.newSelectedStations(stations); 257 if ((stations == null) || (stations.size() == 0)) { 258 selectedStation = null; 259 } else { 260 NamedStation newStation = (NamedStation) stations.get(0); 261 if (Misc.equals(newStation, selectedStation)) { 262 return; 263 } 264 selectedStation = newStation; 265 } 266 Misc.run(TDSRadarChooser.this, "stationOrProductChanged"); 267 } 268 269 270 /** A widget for the list of dataset descriptors */ 271 272 273 /** Flag to keep from infinite looping */ 274 private boolean ignoreProductChange = false; 275 276 /** Selection label text */ 277 protected static final String LABEL_SELECT = " -- Select -- "; 278 279 /** 280 * _more_ 281 */ 282 protected void productChanged() { 283 stationOrProductChanged(); 284 // updateStatus(); 285 } 286 287 /** 288 * Reset the descriptor stuff 289 */ 290 private void resetProductBox() { 291 ignoreProductChange = true; 292 productComboBox.setSelectedItem(LABEL_SELECT); 293 ignoreProductChange = false; 294 } 295 296 /** 297 * Should we update on first display 298 * 299 * @return true 300 */ 301 protected boolean shouldDoUpdateOnFirstDisplay() { 302 return false; 303 } 304 305 /** 306 * Set the server 307 * 308 * @param s the server URL 309 */ 310 private void setServer(String s) { 311 datasetList = new ArrayList(); 312 serverUrl = s; 313 try { 314 datasetList = getRadarCollections(serverUrl); 315 GuiUtils.setListData(collectionSelector, datasetList); 316 for (int i = 0; i < datasetList.size(); i++) { 317 TwoFacedObject obj = (TwoFacedObject) datasetList.get(i); 318 String tmpStr = (String) (obj.getLabel()); 319 if ((tmpStr).contains("NEXRAD Level II Radar from IDD")) { 320 collectionSelector.setSelectedIndex(i); 321 } 322 } 323 } catch (Exception e) { 324 GuiUtils.setListData(collectionSelector, new ArrayList()); 325 } 326 } 327 328 /** 329 * Set the active collection 330 * 331 * @param s collection URL 332 */ 333 private void setCollection(String s) { 334 isLevel3 = false; 335 GuiUtils.enableComponents(level3CompsThatNeedServer, false); 336 productPanel.setVisible(false); 337 GuiUtils.enableComponents(compsThatNeedServer, true); 338 if (drivercbx != null) { 339 drivercbx.setEnabled(anyTimeDrivers() && getHaveData()); 340 } 341 setAbsoluteTimes(new ArrayList()); 342 selectedProduct = null; 343 selectedStation = null; 344 Misc.run(this, "initializeCollection", s); 345 } 346 347 /** 348 * _more_ 349 * 350 * @param s _more_ 351 */ 352 private void setLevel3Collection(String s) { 353 isLevel3 = true; 354 GuiUtils.enableComponents(level3CompsThatNeedServer, true); 355 productPanel.setVisible(true); 356 GuiUtils.enableComponents(compsThatNeedServer, true); 357 if (drivercbx != null) { 358 drivercbx.setEnabled(anyTimeDrivers() && getHaveData()); 359 } 360 setAbsoluteTimes(new ArrayList()); 361 selectedProduct = null; 362 selectedStation = null; 363 Misc.run(this, "initializeLevel3Collection", s); 364 } 365 366 /** 367 * Add a component that needs to have a valid server 368 * 369 * @param comp the component 370 * 371 * @return the component 372 */ 373 protected JComponent addServerComp(JComponent comp) { 374 compsThatNeedServer.add(comp); 375 return comp; 376 } 377 378 /** 379 * Add a component that needs to have a valid server 380 * 381 * @param comp the component 382 * 383 * @return the component 384 */ 385 protected JComponent addLevel3ServerComp(JComponent comp) { 386 level3CompsThatNeedServer.add(comp); 387 return comp; 388 } 389 390 /** 391 * Get the radar collections for the given server URL 392 * 393 * @param radarServerURL server URL 394 * 395 * @return a map of the collection names to URL 396 */ 397 private List getRadarCollections(String radarServerURL) { 398 SAXBuilder builder; 399 Document doc = null; 400 XMLEntityResolver jaxp = new XMLEntityResolver(true); 401 builder = jaxp.getSAXBuilder(); 402 List<TwoFacedObject> collections = new ArrayList<>(); 403 404 try { 405 doc = builder.build(radarServerURL); 406 } catch (JDOMException e) { 407 if (canShowErrorWindows()) { 408 userMessage("Invalid catalog"); 409 } 410 logger.warn("Invalid catalog: "+radarServerURL, e); 411 } catch (IOException e) { 412 if (canShowErrorWindows()) { 413 userMessage("Unable to open catalog"); 414 } 415 logger.warn("Unable to open catalog: "+radarServerURL, e); 416 } 417 418 org.jdom2.Element rootElem = doc.getRootElement(); 419 org.jdom2.Element serviceElem = readElements(rootElem, "service"); 420 String uriBase = serviceElem.getAttributeValue("base"); 421 org.jdom2.Element dsElem = readElements(rootElem, "dataset"); 422 String naming = "catalogRef"; 423 Namespace nss = rootElem.getNamespace("xlink"); 424 List children = dsElem.getChildren(); 425 for (int j = 0; j < children.size(); j++) { 426 org.jdom2.Element child = (org.jdom2.Element) children.get(j); 427 String childName = child.getName(); 428 if (childName.equals(naming)) { 429 //String id = child.getAttributeValue("ID"); 430 String desc = child.getAttributeValue("title", nss); 431 String urlpath = child.getAttributeValue("href", nss); 432 String[] c = radarServerURL.split(uriBase); //.replaceFirst("catalog.xml", ""); 433 String ul = c[0] + uriBase + urlpath; 434 TwoFacedObject twoObj = new TwoFacedObject(desc, ul); 435 collections.add(twoObj); 436 //collections.put(desc, ul); 437 } 438 439 } 440 441 return collections; 442 } 443 444 /** 445 * Read the elements 446 * 447 * @param elem element 448 * @param eleName element name 449 * 450 * @return an element 451 */ 452 public org.jdom2.Element readElements(org.jdom2.Element elem, 453 String eleName) { 454 List children = elem.getChildren(); 455 for (int j = 0; j < children.size(); j++) { 456 org.jdom2.Element child = (org.jdom2.Element) children.get(j); 457 String childName = child.getName(); 458 if (childName.equals(eleName)) { 459 return child; 460 } 461 } 462 return null; 463 } 464 465 /** 466 * Make the collection. If there is an error, pop up a user message. 467 * 468 * @param url URL for the collection 469 */ 470 public synchronized void initializeCollection(String url) { 471 472 List<NamedStationImpl> stations = new ArrayList<NamedStationImpl>(); 473 try { 474 StringBuffer errlog = new StringBuffer(); 475 try { 476 logger.debug("Initializing collection from URL: " + url); 477 collection = TDSRadarDatasetCollection.factory("test", url, errlog); 478 } catch (Exception exc) { 479 if (canShowErrorWindows()) { 480 userMessage("Invalid catalog"); 481 } 482 logger.warn("Invalid catalog: "+url, exc); 483 return; 484 } 485 logger.debug("Iterating over collection..."); 486 List tdsStations = collection.getRadarStations(); 487 for (int i = 0; i < tdsStations.size(); i++) { 488 StationImpl stn = (StationImpl) tdsStations.get(i); 489 // thredds.catalog.query.Location loc = stn.getLocation(); 490 //TODO: need better station need to switch lat lon 491 NamedStationImpl station = 492 new NamedStationImpl(stn.getName(), stn.getName(), 493 stn.getLatitude(), 494 stn.getLongitude(), 495 stn.getAltitude(), CommonUnit.meter); 496 stations.add(station); 497 498 } 499 500 getStationMap().setStations(stations); 501 } catch (Exception exc) { 502 if (canShowErrorWindows()) { 503 userMessage("Unable to load stations"); 504 } 505 logger.warn("Unable to load stations from URL: "+url, exc); 506 return; 507 } 508 urlListHandler.saveState(urlBox); 509 } 510 511 /** 512 * Utility method to check to see if McIDAS-V is ready to show error dialogs. 513 * 514 * @return {@code true} if McIDAS-V has finished starting and not in background mode. 515 * {@code false} otherwise. 516 */ 517 private static boolean canShowErrorWindows() { 518 // TODO(jon): maybe this should be integrated into the userMessage method instead? 519 McIDASV mcv = McIDASV.getStaticMcv(); 520 boolean result = false; 521 if (mcv != null) { 522 result = mcv.getHaveInitialized() && mcv.okToShowWindows(); 523 } 524 return result; 525 } 526 527 /** 528 * _more_ 529 * 530 * @param url _more_ 531 */ 532 public void initializeLevel3Collection(String url) { 533 534 List<NamedStationImpl> stations = new ArrayList<NamedStationImpl>(); 535 List<Product> products; 536 List<String> exProducts = new ArrayList<String>(); 537 538 for(String ename: level3_ExName){ 539 exProducts.add(ename); 540 } 541 542 try { 543 StringBuffer errlog = new StringBuffer(); 544 try { 545 collection = TDSRadarDatasetCollection.factory("test", url, 546 errlog); 547 } catch (Exception exc) { 548 if (canShowErrorWindows()) { 549 userMessage("Invalid catalog"); 550 } 551 logger.warn("Invalid catalog: "+url, exc); 552 return; 553 } 554 products = collection.getRadarProducts(); 555 List tdsStations = collection.getRadarStations(); 556 for (int i = 0; i < tdsStations.size(); i++) { 557 StationImpl stn = (StationImpl) tdsStations.get(i); 558 // thredds.catalog.query.Location loc = stn.getLocation(); 559 //TODO: need better station need to switch lat lon 560 NamedStationImpl station = 561 new NamedStationImpl(stn.getName(), stn.getName(), 562 stn.getLatitude(), 563 stn.getLongitude(), 564 stn.getAltitude(), CommonUnit.meter); 565 stations.add(station); 566 567 } 568 List<TwoFacedObject> productNames = new ArrayList(); 569 for (Product product : products) { 570 // if ( !product.getID().contains("DPA") 571 // && !product.getID().contains("NVW")) { 572 if ( !exProducts.contains(product.getID())) { 573 String lable = product.getName() + " (" + product.getID() 574 + ")"; 575 TwoFacedObject twoObj = new TwoFacedObject(lable, 576 product.getID()); 577 productNames.add(twoObj); 578 } 579 } 580 GuiUtils.setListData(productComboBox, productNames); 581 582 // GuiUtils.setListData(dataTypeComboBox, dataTypes); 583 getStationMap().setStations(stations); 584 } catch (Exception exc) { 585 if (canShowErrorWindows()) { 586 userMessage("Unable to load stations"); 587 } 588 logger.warn("Unable to load stations from URL: "+url, exc); 589 return; 590 } 591 urlListHandler.saveState(urlBox); 592 } 593 594 595 /** 596 * Handle when the user has selected a new station 597 */ 598 public void stationOrProductChanged() { 599 Vector times = new Vector(); 600 setHaveData(false); 601 if ((!isLevel3 && selectedStation != null) || 602 (isLevel3 && selectedStation != null && selectedProduct != null)) { 603 List timeSpan = collection.getRadarTimeSpan(); 604 Date fromDate = DateUnit.getStandardOrISO((String) timeSpan.get(0)); 605 //Date toDate = DateUnit.getStandardOrISO((String) timeSpan.get(1)); 606 Date toDate = new Date(System.currentTimeMillis() 607 + DateUtil.daysToMillis(1)); 608 //Go back 10 years (or so) 609 //Date fromDate = new Date(System.currentTimeMillis() 610 // - DateUtil.daysToMillis(365 * 10)); 611 try { 612 showWaitCursor(); 613 setAbsoluteTimes(new ArrayList()); 614 setStatus("Reading times for station: " + selectedStation, 615 ""); 616 // LogUtil.message("Reading times for station: " 617 // + selectedStation); 618 String pid = null; 619 if(isLevel3) 620 pid = TwoFacedObject.getIdString( 621 productComboBox.getSelectedItem()); 622 List allTimes = 623 collection.getRadarStationTimes(selectedStation.getID(), 624 pid, fromDate, toDate); 625 626 // if(allTimes.size() == 0) { 627 // toDate = new Date(System.currentTimeMillis() 628 // + DateUtil.daysToMillis(1)); 629 // allTimes = 630 // collection.getRadarStationTimes(selectedStation.getID(), 631 // pid, fromDate, toDate); 632 // } 633 634 for (int timeIdx = 0; timeIdx < allTimes.size(); timeIdx++) { 635 Object timeObj = allTimes.get(timeIdx); 636 Date date; 637 if (timeObj instanceof Date) { 638 date = (Date) timeObj; 639 } else { 640 date = DateUnit.getStandardOrISO(timeObj.toString()); 641 } 642 times.add(new DateTime(date)); 643 } 644 // LogUtil.message(""); 645 showNormalCursor(); 646 } catch (Exception exc) { 647 if (canShowErrorWindows()) { 648 userMessage("Error reading times for station: " 649 + selectedStation); 650 } 651 //logException("Getting times for station: " + selectedStation, 652 // exc); 653 setStatus("Select a different collection", "collections"); 654 showNormalCursor(); 655 return; 656 } 657 } 658 setAbsoluteTimes(times); 659 updateStatus(); 660 } 661 662 663 664 665 666 /** 667 * Load the data 668 */ 669 public void doLoadInThread() { 670 // to the CDMRadarDataSource 671 Hashtable ht = new Hashtable(); 672 if (selectedStation != null) { 673 ht.put(ucar.unidata.data.radar.RadarDataSource.STATION_LOCATION, 674 selectedStation.getNamedLocation()); 675 ht.put(DataSelection.PROP_CHOOSERTIMEMATCHING, getDoTimeDrivers()); 676 } else { 677 LogUtil.userMessage("No Station selected"); 678 } 679 680 if (isLevel3 && (selectedProduct == null)) { 681 682 LogUtil.userMessage("No Product selected"); 683 } 684 685 try { 686 DateSelection dateSelection = new DateSelection(); 687 String collectionUrl = TwoFacedObject.getIdString( 688 collectionSelector.getSelectedItem()); 689 String pid = null; 690 RadarQuery radarQuery; 691 if (isLevel3) { 692 pid = TwoFacedObject.getIdString( 693 productComboBox.getSelectedItem()); 694 radarQuery = new RadarQuery(collectionUrl, 695 selectedStation.getID(), pid, 696 dateSelection); 697 } else { 698 radarQuery = new RadarQuery(collectionUrl, 699 selectedStation.getID(), 700 dateSelection); 701 } 702 703 List urls = new ArrayList(); 704 705 if (getDoAbsoluteTimes()) { 706 List times = new ArrayList(); 707 List selected = makeDatedObjects(getSelectedAbsoluteTimes()); 708 for (int i = 0; i < selected.size(); i++) { 709 DatedThing datedThing = (DatedThing) selected.get(i); 710 Date date = datedThing.getDate(); 711 times.add(date); 712 URI uri = null; 713 try { 714 uri = collection.getRadarDatasetURI( 715 selectedStation.getID(), pid, date); 716 } catch (Exception excp) { 717 LogUtil.userMessage("incorrect times selected"); 718 return; 719 } 720 urls.add(uri.toString()); 721 } 722 if (urls.size() == 0) { 723 LogUtil.userMessage("No times selected"); 724 return; 725 } 726 dateSelection.setTimes(times); 727 } else { 728// int count = getRelativeTimesList().getSelectedIndex() + 1; 729 if (relativeTimes == 0) { 730 LogUtil.userMessage("No relative times selected"); 731 return; 732 } 733 Date toDate = new Date(System.currentTimeMillis() 734 + DateUtil.daysToMillis(365 * 100)); 735 //Go back 10 years (or so) 736 Date fromDate = new Date(System.currentTimeMillis() 737 - DateUtil.daysToMillis(365 * 10)); 738 739 dateSelection.setStartFixedTime(fromDate); 740 dateSelection.setEndFixedTime(toDate); 741 dateSelection.setCount(relativeTimes); 742 } 743 makeDataSource(radarQuery, "FILE.RADAR", ht); 744 } catch (Exception exc) { 745 logException("Loading radar data", exc); 746 } 747 // uncheck the check box every time click the add source button 748 drivercbx.setSelected(false); 749 enableTimeWidgets(); 750 setDoTimeDrivers(false); 751 } 752 753 protected int getNumTimesToSelect() { 754 return 5; 755 } 756 757 /** 758 * Get the default selected index for the relative times list. 759 * 760 * @return default index 761 */ 762 protected int getDefaultRelativeTimeIndex() { 763 return 4; 764 } 765 766 /** 767 * Check the times lists 768 */ 769 protected void checkTimesLists() { 770 super.checkTimesLists(); 771 if (timesCardPanelExtra == null) { 772 return; 773 } 774 if (getDoAbsoluteTimes()) { 775 timesCardPanelExtra.show("absolute"); 776 } else { 777 timesCardPanelExtra.show("relative"); 778 } 779 } 780 781 /** Card panel to hold extra relative and absolute time components */ 782 private GuiUtils.CardLayoutPanel timesCardPanelExtra; 783 784 /** 785 * Add the interval selector to the component. 786 * @return superclass component with extra stuff 787 */ 788 protected JPanel makeTimesPanel() { 789 JComponent extra = getExtraTimeComponent(); 790 GuiUtils.enableTree(extra, false); 791 JPanel timesPanel = makeTimesPanel(extra, null); 792 return timesPanel; 793 } 794 795 /** 796 * Set the relative and absolute extra components 797 */ 798 protected JPanel makeTimesPanel(JComponent relativeCard, JComponent absoluteCard) { 799 JPanel timesPanel = super.makeTimesPanel(false, true, getIdv().getUseTimeDriver()); 800 801 // Make a new timesPanel that has extra components tacked on the bottom, inside the tabs 802 Component[] comps = timesPanel.getComponents(); 803 804 if ((comps.length == 2) && (comps[0] instanceof JTabbedPane) && (comps[1] instanceof JLabel)) { 805 timesCardPanelExtra = new GuiUtils.CardLayoutPanel(); 806 if (relativeCard == null) { 807 relativeCard = new JPanel(); 808 } 809 if (absoluteCard == null) { 810 absoluteCard = new JPanel(); 811 } 812 absoluteCard = GuiUtils.hbox(comps[1], GuiUtils.right(absoluteCard)); 813 timesCardPanelExtra.add(relativeCard, "relative"); 814 timesCardPanelExtra.add(absoluteCard, "absolute"); 815 timesPanel = GuiUtils.centerBottom(comps[0], timesCardPanelExtra); 816 } 817 return timesPanel; 818 } 819 820 /** 821 * Get the time popup widget 822 * 823 * @return a widget for selecting the day 824 */ 825 protected JComponent getExtraTimeComponent() { 826 JPanel filler = new JPanel(); 827 McVGuiUtils.setComponentHeight(filler, new JComboBox()); 828 return filler; 829 } 830 831 /** 832 * Make the contents 833 * 834 * @return the contents 835 */ 836 protected JPanel doMakeInnerPanel() { 837 JPanel myPanel = new JPanel(); 838 839 JLabel collectionLabel = McVGuiUtils.makeLabelRight("Collection:"); 840 841 collectionSelector = new JComboBox(); 842 collectionSelector.addItemListener(new ItemListener() { 843 public void itemStateChanged(ItemEvent e) { 844 newSelectedStations(new ArrayList()); 845 if (collectionSelector.getSelectedItem() == null) { 846 return; 847 } 848 String collectionUrl = 849 TwoFacedObject.getIdString( 850 collectionSelector.getSelectedItem()); 851 852 if (collectionUrl.contains("level3")) { 853 setLevel3Collection(collectionUrl); 854 } else { 855 setCollection(collectionUrl); 856 } 857 } 858 859 }); 860 addServerComp(collectionLabel); 861 addServerComp(collectionSelector); 862 863 productComboBox = new JComboBox(); 864 productComboBox.addItemListener(new ItemListener() { 865 public void itemStateChanged(ItemEvent e) { 866 if (productComboBox.getSelectedItem() == null) { 867 return; 868 } 869 selectedProduct = 870 productComboBox.getSelectedItem().toString(); 871 resetProductBox(); 872 productChanged(); 873 } 874 875 }); 876 addLevel3ServerComp(productComboBox); 877 878 productPanel = McVGuiUtils.makeLabeledComponent("Product:", productComboBox); 879 880 JLabel stationLabel = McVGuiUtils.makeLabelRight("Station:"); 881 addServerComp(stationLabel); 882 883 JComponent stationPanel = getStationMap(); 884 registerStatusComp("stations", stationPanel); 885 addServerComp(stationPanel); 886 887 JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:"); 888 addServerComp(timesLabel); 889 890 JPanel timesPanel = makeTimesPanel(); 891 timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); 892 addServerComp(timesPanel); 893 894 GuiUtils.enableComponents(compsThatNeedServer, false); 895 GuiUtils.enableComponents(level3CompsThatNeedServer, false); 896 productPanel.setVisible(false); 897 898 GroupLayout layout = new GroupLayout(myPanel); 899 myPanel.setLayout(layout); 900 layout.setHorizontalGroup( 901 layout.createParallelGroup(LEADING) 902 .addGroup(layout.createSequentialGroup() 903 .addGroup(layout.createParallelGroup(LEADING) 904 .addGroup(layout.createSequentialGroup() 905 .addComponent(collectionLabel) 906 .addGap(GAP_RELATED) 907 .addComponent(collectionSelector, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 908 .addComponent(productPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)) 909 .addGroup(layout.createSequentialGroup() 910 .addComponent(stationLabel) 911 .addGap(GAP_RELATED) 912 .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)) 913 .addGroup(layout.createSequentialGroup() 914 .addComponent(timesLabel) 915 .addGap(GAP_RELATED) 916 .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))) 917 ); 918 layout.setVerticalGroup( 919 layout.createParallelGroup(LEADING) 920 .addGroup(layout.createSequentialGroup() 921 .addGroup(layout.createParallelGroup(BASELINE) 922 .addComponent(collectionLabel) 923 .addComponent(collectionSelector) 924 .addComponent(productPanel)) 925 .addPreferredGap(RELATED) 926 .addGroup(layout.createParallelGroup(LEADING) 927 .addComponent(stationLabel) 928 .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)) 929 .addPreferredGap(RELATED) 930 .addGroup(layout.createParallelGroup(LEADING) 931 .addComponent(timesLabel) 932 .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)) 933 .addPreferredGap(RELATED)) 934 ); 935 936 return myPanel; 937 } 938 939 private JPanel innerPanel = doMakeInnerPanel(); 940 941 private JLabel statusLabel = new JLabel("Status"); 942 943 @Override public void setStatus(String statusString, String foo) { 944 if (statusString == null) { 945 statusString = ""; 946 } 947 statusLabel.setText(statusString); 948 } 949 950 protected void setInnerPanel(JPanel newInnerPanel) { 951 innerPanel = newInnerPanel; 952 } 953 954 /** 955 * Create the widget responsible for handling relative time selection. 956 * 957 * @return GUI widget. 958 */ 959 @Override public JComponent getRelativeTimesChooser() { 960 McVTextField relativeTimesField = 961 McVGuiUtils.makeTextFieldAllow(String.valueOf(relativeTimes), 962 4, 963 false, 964 '0', '1', '2', '3', '4', '5', '6', 965 '7','8','9'); 966 967 // need to keep *both* the ActionListener and DocumentListener around. 968 // removing the ActionListener results in strange behavior when you've 969 // accidentally cleared out the text field. 970 relativeTimesField.setAllow(Pattern.compile("^[1-9][0-9]*$"), true); 971// relativeTimesField.setDeny(Pattern.compile("^0$"), true); 972 relativeTimesField.setColumns(4); 973 relativeTimesField.addActionListener(e -> { 974 String text = ((JTextField)e.getSource()).getText(); 975 validateRelativeTimeInput(text); 976 }); 977 relativeTimesField.getDocument().addDocumentListener(new DocumentListener() { 978 @Override public void insertUpdate(DocumentEvent e) { 979 handleRelativeTimeChange(e); 980 } 981 982 @Override public void removeUpdate(DocumentEvent e) { 983 handleRelativeTimeChange(e); 984 } 985 986 @Override public void changedUpdate(DocumentEvent e) { 987 handleRelativeTimeChange(e); 988 } 989 }); 990 relativeTimesField.setToolTipText(AddeChooser.RELATIVE_TIMES_TOOLTIP); 991 992 JPanel panel = 993 GuiUtils.topLeft(GuiUtils.label(AddeChooser.RELATIVE_TIMES_LABEL, 994 relativeTimesField)); 995 JScrollPane scrollPane = new JScrollPane(panel); 996 scrollPane.setPreferredSize(new Dimension(150, 100)); 997 return scrollPane; 998 } 999 1000 /** 1001 * Validate the contents of the relative times text field. 1002 * 1003 * <p>This method overwrites {@link #relativeTimes} if {@code text} is an 1004 * integer greater than zero.</p> 1005 * 1006 * @param text Contents of the text field. 1007 */ 1008 private void validateRelativeTimeInput(String text) { 1009 try { 1010 int value = Integer.valueOf(text); 1011 if (value > 0) { 1012 relativeTimes = value; 1013 setHaveData(true); 1014 updateStatus(); 1015 } 1016 } catch (NumberFormatException e) { 1017 setHaveData(false); 1018 setStatus("Please provide an integer value greater than zero."); 1019 } 1020 } 1021 1022 /** 1023 * Handle {@link DocumentListener} events for the {@link JTextField} 1024 * created by {@link #getRelativeTimesChooser()}. 1025 * 1026 * @param event Event to handle. Cannot be {@code null}. 1027 */ 1028 private void handleRelativeTimeChange(DocumentEvent event) { 1029 int len = event.getDocument().getLength(); 1030 try { 1031 String text = event.getDocument().getText(0, len); 1032 validateRelativeTimeInput(text); 1033 } catch (BadLocationException ex) { 1034 logger.warn("Could not get contents of text field!", ex); 1035 } 1036 } 1037 1038 /** 1039 * Get the relative time indices 1040 * 1041 * @return an array of indices 1042 */ 1043 @Override public int[] getRelativeTimeIndices() { 1044 int[] indices = new int[relativeTimes]; 1045 for (int i = 0; i < indices.length; i++) { 1046 indices[i] = i; 1047 } 1048 return indices; 1049 } 1050 1051 /** 1052 * Make the UI for this selector. 1053 * 1054 * Thank you NetBeans for helping with the layout! 1055 * 1056 * @return The GUI. 1057 */ 1058 public JComponent doMakeContents() { 1059 JPanel outerPanel = new JPanel(); 1060 1061 JLabel serverLabel = McVGuiUtils.makeLabelRight("Catalog:"); 1062 1063 // Get the list of catalogs but remove the old catalog.xml entry 1064 urlListHandler = getPreferenceList(PREF_TDSRADARSERVER); 1065 1066 ActionListener catListListener = ae -> { 1067 if (okToDoUrlListEvents) { 1068 setServer((String) urlBox.getSelectedItem()); 1069 } 1070 }; 1071 1072 urlBox = urlListHandler.createComboBox(GuiUtils.CMD_UPDATE, catListListener, true); 1073 1074 // TJJ Nov 2019 1075 // Since URLs are stored in user prefs, and THREDDS wants HTTPS now for radar catalog, 1076 // need to convert on-the-fly where they occur 1077 // See: https://mcidas.ssec.wisc.edu/inquiry-v/?inquiry=2857 1078 1079 String selectedURL = (String) urlBox.getSelectedItem(); 1080 // At first init, URL may be null. If so, set to default 1081 if (selectedURL != null) { 1082 selectedURL = selectedURL.replace("http:", "https:"); 1083 urlBox.setSelectedItem(selectedURL); 1084 } else { 1085 urlBox.setSelectedItem(getProperty(PREF_TDSRADARSERVER, null)); 1086 } 1087 1088 McVGuiUtils.setComponentWidth(urlBox, Width.DOUBLEDOUBLE); 1089 1090 // productComboBox gets created a little too tall--set to same height as urlBox 1091 if (productComboBox != null) { 1092 McVGuiUtils.setComponentHeight(productComboBox, urlBox); 1093 } 1094 JButton connectButton = McVGuiUtils.makeImageTextButton(ICON_CONNECT_SMALL, "Connect"); 1095 McVGuiUtils.setComponentWidth(connectButton, Width.DOUBLE); 1096 connectButton.setActionCommand(GuiUtils.CMD_UPDATE); 1097 connectButton.addActionListener(this); 1098 1099 JLabel statusLabelLabel = McVGuiUtils.makeLabelRight(""); 1100 1101 statusLabel.setText("Status"); 1102 McVGuiUtils.setLabelPosition(statusLabel, Position.RIGHT); 1103 McVGuiUtils.setComponentColor(statusLabel, TextColor.STATUS); 1104 1105 JButton helpButton = McVGuiUtils.makeImageButton(ICON_HELP, "Show help"); 1106 helpButton.setActionCommand(GuiUtils.CMD_HELP); 1107 helpButton.addActionListener(this); 1108 1109 JButton refreshButton = McVGuiUtils.makeImageButton(ICON_REFRESH, "Refresh"); 1110 refreshButton.setActionCommand(GuiUtils.CMD_UPDATE); 1111 refreshButton.addActionListener(this); 1112 1113 McVGuiUtils.setComponentWidth(loadButton, Width.DOUBLE); 1114 1115 GroupLayout layout = new GroupLayout(outerPanel); 1116 outerPanel.setLayout(layout); 1117 layout.setHorizontalGroup( 1118 layout.createParallelGroup(LEADING) 1119 .addGroup(TRAILING, layout.createSequentialGroup() 1120 .addGroup(layout.createParallelGroup(TRAILING) 1121 .addGroup(layout.createSequentialGroup() 1122 .addContainerGap() 1123 .addComponent(helpButton) 1124 .addGap(GAP_RELATED) 1125 .addComponent(refreshButton) 1126 .addGap(GAP_RELATED) 1127 .addComponent(cancelButton) 1128 .addPreferredGap(RELATED) 1129 .addComponent(loadButton)) 1130 .addGroup(LEADING, layout.createSequentialGroup() 1131 .addContainerGap() 1132 .addGroup(layout.createParallelGroup(LEADING) 1133 .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 1134 .addGroup(layout.createSequentialGroup() 1135 .addComponent(serverLabel) 1136 .addGap(GAP_RELATED) 1137 .addComponent(urlBox, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 1138 .addGap(GAP_UNRELATED) 1139 .addComponent(connectButton)) 1140 .addGroup(layout.createSequentialGroup() 1141 .addComponent(statusLabelLabel) 1142 .addGap(GAP_RELATED) 1143 .addComponent(statusLabel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))))) 1144 .addContainerGap()) 1145 ); 1146 layout.setVerticalGroup( 1147 layout.createParallelGroup(LEADING) 1148 .addGroup(layout.createSequentialGroup() 1149 .addContainerGap() 1150 .addGroup(layout.createParallelGroup(BASELINE) 1151 .addComponent(serverLabel) 1152 .addComponent(urlBox) 1153 .addComponent(connectButton)) 1154 .addPreferredGap(UNRELATED) 1155 .addComponent(innerPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 1156 .addPreferredGap(UNRELATED) 1157 .addGroup(layout.createParallelGroup(BASELINE) 1158 .addComponent(statusLabelLabel) 1159 .addComponent(statusLabel)) 1160 .addPreferredGap(UNRELATED) 1161 .addGroup(layout.createParallelGroup(BASELINE) 1162 .addComponent(loadButton) 1163 .addComponent(cancelButton) 1164 .addComponent(refreshButton) 1165 .addComponent(helpButton)) 1166 .addContainerGap()) 1167 ); 1168 return outerPanel; 1169 } 1170}