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