001 /* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2013 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 package edu.wisc.ssec.mcidasv.servermanager; 029 030 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 031 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashSet; 032 import static edu.wisc.ssec.mcidasv.util.Contract.notNull; 033 import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.runOnEDT; 034 035 import java.awt.BorderLayout; 036 import java.awt.Component; 037 import java.awt.Dimension; 038 import java.awt.Font; 039 import java.awt.event.WindowAdapter; 040 import java.awt.event.WindowEvent; 041 import java.io.File; 042 import java.util.Collection; 043 import java.util.Collections; 044 import java.util.EnumSet; 045 import java.util.List; 046 import java.util.Set; 047 import java.util.concurrent.Callable; 048 import java.util.concurrent.CompletionService; 049 import java.util.concurrent.ExecutionException; 050 import java.util.concurrent.ExecutorCompletionService; 051 import java.util.concurrent.ExecutorService; 052 import java.util.concurrent.Executors; 053 import java.util.regex.Pattern; 054 055 import javax.swing.Box; 056 import javax.swing.BoxLayout; 057 import javax.swing.GroupLayout; 058 import javax.swing.Icon; 059 import javax.swing.JButton; 060 import javax.swing.JCheckBox; 061 import javax.swing.JCheckBoxMenuItem; 062 import javax.swing.JDialog; 063 import javax.swing.JFileChooser; 064 import javax.swing.JFrame; 065 import javax.swing.JLabel; 066 import javax.swing.JMenu; 067 import javax.swing.JMenuBar; 068 import javax.swing.JMenuItem; 069 import javax.swing.JPanel; 070 import javax.swing.JPopupMenu; 071 import javax.swing.JScrollPane; 072 import javax.swing.JSeparator; 073 import javax.swing.JTabbedPane; 074 import javax.swing.JTable; 075 import javax.swing.JTextField; 076 import javax.swing.LayoutStyle; 077 import javax.swing.ListSelectionModel; 078 import javax.swing.SwingUtilities; 079 import javax.swing.UIManager; 080 import javax.swing.WindowConstants; 081 import javax.swing.border.EmptyBorder; 082 import javax.swing.event.ChangeEvent; 083 import javax.swing.event.ChangeListener; 084 import javax.swing.event.ListSelectionEvent; 085 import javax.swing.event.ListSelectionListener; 086 import javax.swing.table.AbstractTableModel; 087 import javax.swing.table.DefaultTableCellRenderer; 088 089 import net.miginfocom.swing.MigLayout; 090 091 import org.bushe.swing.event.EventBus; 092 import org.bushe.swing.event.annotation.AnnotationProcessor; 093 import org.bushe.swing.event.annotation.EventSubscriber; 094 095 import org.slf4j.Logger; 096 import org.slf4j.LoggerFactory; 097 098 import ucar.unidata.idv.IdvObjectStore; 099 import ucar.unidata.util.GuiUtils; 100 import ucar.unidata.util.LogUtil; 101 102 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource; 103 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 104 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity; 105 import edu.wisc.ssec.mcidasv.servermanager.AddeThread.McservEvent; 106 import edu.wisc.ssec.mcidasv.servermanager.EntryStore.Event; 107 import edu.wisc.ssec.mcidasv.servermanager.RemoteEntryEditor.AddeStatus; 108 import edu.wisc.ssec.mcidasv.ui.BetterJTable; 109 import edu.wisc.ssec.mcidasv.util.McVTextField.Prompt; 110 import java.awt.event.ActionListener; 111 import java.awt.event.ActionEvent; 112 113 /** 114 * This class is the GUI frontend to {@link EntryStore} (the server manager). 115 * It allows users to manipulate their local and remote ADDE data. 116 */ 117 // TODO(jon): don't forget to persist tab choice and window position. maybe also the "positions" of the scrollpanes (if possible). 118 // TODO(jon): GUI could look much better. 119 // TODO(jon): finish up the javadocs. 120 @SuppressWarnings({"serial", "AssignmentToStaticFieldFromInstanceMethod", "FieldCanBeLocal"}) 121 public class TabbedAddeManager extends JFrame { 122 123 /** Pretty typical logger object. */ 124 private final static Logger logger = LoggerFactory.getLogger(TabbedAddeManager.class); 125 126 /** Path to the help resources. */ 127 private static final String HELP_TOP_DIR = "/docs/userguide"; 128 129 /** Help target for the remote servers. */ 130 private static final String REMOTE_HELP_TARGET = "idv.tools.remotedata"; 131 132 /** Help target for the local servers. */ 133 private static final String LOCAL_HELP_TARGET = "idv.tools.localdata"; 134 135 /** ID used to save/restore the last visible tab between sessions. */ 136 private static final String LAST_TAB = "mcv.adde.lasttab"; 137 138 /** ID used to save/restore the last directory that contained a MCTABLE.TXT. */ 139 private static final String LAST_IMPORTED = "mcv.adde.lastmctabledir"; 140 141 /** Size of the ADDE entry verification thread pool. */ 142 private static final int POOL = 2; 143 144 /** Static reference to an instance of this class. Bad idea! */ 145 private static TabbedAddeManager staticTabbedManager; 146 147 /** 148 * These are the various {@literal "events"} that the server manager GUI 149 * supports. These are published via the wonderful {@link EventBus#publish(Object)} method. 150 */ 151 public enum Event { 152 /** The GUI was created. */ 153 OPENED, 154 /** The GUI was hidden/minimized/etc. */ 155 HIDDEN, 156 /** GUI was unhidden or some such thing. */ 157 SHOWN, 158 /** The GUI was closed. */ 159 CLOSED 160 } 161 162 /** Reference to the actual server manager. */ 163 private final EntryStore serverManager; 164 165 /** 166 * Entries stored within the server manager GUI. This may differ from the 167 * contents of the server manager itself. 168 */ 169 // private final Set<AddeEntry> entrySet; 170 171 /** */ 172 private final List<RemoteAddeEntry> selectedRemoteEntries; 173 174 /** */ 175 private final List<LocalAddeEntry> selectedLocalEntries; 176 177 /** */ 178 private JTextField importUser; 179 180 /** */ 181 private JTextField importProject; 182 183 /** Whether or not {@link #initComponents()} has been called. */ 184 private boolean guiInitialized = false; 185 186 /** 187 * Creates a standalone server manager GUI. 188 */ 189 public TabbedAddeManager() { 190 //noinspection AssignmentToNull 191 AnnotationProcessor.process(this); 192 this.serverManager = null; 193 // this.entrySet = newLinkedHashSet(); 194 this.selectedLocalEntries = arrList(); 195 this.selectedRemoteEntries = arrList(); 196 197 SwingUtilities.invokeLater(new Runnable() { 198 @Override public void run() { 199 initComponents(); 200 } 201 }); 202 } 203 204 /** 205 * Creates a server manager GUI that's linked back to the rest of McIDAS-V. 206 * 207 * @param entryStore Server manager reference. 208 * 209 * @throws NullPointerException if {@code entryStore} is {@code null}. 210 */ 211 public TabbedAddeManager(final EntryStore entryStore) { 212 notNull(entryStore, "Cannot pass a null server manager"); 213 AnnotationProcessor.process(this); 214 this.serverManager = entryStore; 215 this.selectedLocalEntries = arrList(); 216 this.selectedRemoteEntries = arrList(); 217 SwingUtilities.invokeLater(new Runnable() { 218 @Override public void run() { 219 initComponents(); 220 } 221 }); 222 } 223 224 /** 225 * Returns an instance of this class. The instance <i>should</i> correspond 226 * to the one being used by the {@literal "rest"} of McIDAS-V. 227 * 228 * @return Either an instance of this class or {@code null}. 229 */ 230 public static TabbedAddeManager getTabbedManager() { 231 return staticTabbedManager; 232 } 233 234 // public void addEntries(final Collection<? extends AddeEntry> entries) { 235 // logger.trace("entries={}", entries); 236 // entrySet.addAll(entries); 237 // SwingUtilities.invokeLater(new Runnable() { 238 // public void run() { 239 // refreshDisplay(); 240 // } 241 // }); 242 // } 243 // 244 // public void replaceEntries(final Collection<? extends AddeEntry> currentEntries, final Collection<? extends AddeEntry> newEntries) { 245 // logger.trace("currentEntries={} newEntries={}", currentEntries, newEntries); 246 // entrySet.removeAll(currentEntries); 247 // entrySet.addAll(newEntries); 248 // SwingUtilities.invokeLater(new Runnable() { 249 // public void run() { 250 // refreshDisplay(); 251 // } 252 // }); 253 // } 254 // 255 // public void removeEntries(final Collection<? extends AddeEntry> entries) { 256 // logger.trace("entries={}", entries); 257 // entrySet.removeAll(entries); 258 // SwingUtilities.invokeLater(new Runnable() { 259 // public void run() { 260 // refreshDisplay(); 261 // } 262 // }); 263 // } 264 265 /** 266 * If the GUI isn't shown, this method will display things. If the GUI <i>is 267 * shown</i>, bring it to the front. 268 * 269 * <p>This method publishes {@link Event#SHOWN}. 270 */ 271 public void showManager() { 272 if (!isVisible()) { 273 setVisible(true); 274 } else { 275 toFront(); 276 } 277 staticTabbedManager = this; 278 EventBus.publish(Event.SHOWN); 279 } 280 281 /** 282 * Closes and disposes (if needed) the GUI. 283 */ 284 public void closeManager() { 285 //noinspection AssignmentToNull 286 staticTabbedManager = null; 287 EventBus.publish(Event.CLOSED); 288 if (isDisplayable()) { 289 dispose(); 290 } 291 } 292 293 /** 294 * Attempts to refresh the contents of both the local and remote dataset 295 * tables. 296 */ 297 public void refreshDisplay() { 298 if (guiInitialized) { 299 ((RemoteAddeTableModel)remoteTable.getModel()).refreshEntries(); 300 ((LocalAddeTableModel)localTable.getModel()).refreshEntries(); 301 } 302 } 303 304 /** 305 * Create and show the GUI the remote ADDE dataset GUI. Since no 306 * {@link RemoteAddeEntry RemoteAddeEntries} have been provided, none of 307 * the fields will be prefilled (user is creating a new dataset). 308 */ 309 // TODO(jon): differentiate between showRemoteEditor() and showRemoteEditor(entries) 310 public void showRemoteEditor() { 311 if (tabbedPane.getSelectedIndex() != 0) { 312 tabbedPane.setSelectedIndex(0); 313 } 314 RemoteEntryEditor editor = new RemoteEntryEditor(this, true, this, serverManager); 315 editor.setVisible(true); 316 } 317 318 /** 319 * Create and show the GUI the remote ADDE dataset GUI. Since some 320 * {@link RemoteAddeEntry RemoteAddeEntries} have been provided, all of the 321 * applicable fields will be filled (user is editing an existing dataset). 322 * 323 * @param entries Selection to edit. Should not be {@code null}. 324 */ 325 // TODO(jon): differentiate between showRemoteEditor() and showRemoteEditor(entries) 326 public void showRemoteEditor(final List<RemoteAddeEntry> entries) { 327 if (tabbedPane.getSelectedIndex() != 0) { 328 tabbedPane.setSelectedIndex(0); 329 } 330 RemoteEntryEditor editor = new RemoteEntryEditor(this, true, this, serverManager, entries); 331 editor.setVisible(true); 332 } 333 334 /** 335 * Removes the given remote ADDE entries from the server manager GUI. 336 * 337 * @param entries Entries to remove. {@code null} is permissible, but is a {@literal "no-op"}. 338 */ 339 public void removeRemoteEntries(final Collection<RemoteAddeEntry> entries) { 340 if (entries == null) { 341 return; 342 } 343 List<RemoteAddeEntry> removable = arrList(entries.size()); 344 for (RemoteAddeEntry entry : entries) { 345 if (entry.getEntrySource() != EntrySource.SYSTEM) { 346 removable.add(entry); 347 } 348 } 349 // if (entrySet.removeAll(removable)) { 350 if (serverManager.removeEntries(removable)) { 351 RemoteAddeTableModel tableModel = ((RemoteAddeTableModel)remoteTable.getModel()); 352 int first = Integer.MAX_VALUE; 353 int last = Integer.MIN_VALUE; 354 for (RemoteAddeEntry entry : removable) { 355 int index = tableModel.getRowForEntry(entry); 356 if (index >= 0) { 357 if (index < first) { 358 first = index; 359 } 360 if (index > last) { 361 last = index; 362 } 363 } 364 } 365 tableModel.fireTableDataChanged(); 366 refreshDisplay(); 367 remoteTable.revalidate(); 368 if (first < remoteTable.getRowCount()) { 369 remoteTable.setRowSelectionInterval(first, first); 370 } 371 } else { 372 logger.debug("could not remove entries={}", removable); 373 } 374 } 375 376 /** 377 * Shows a local ADDE entry editor <b>without</b> anything pre-populated 378 * (creating a new local ADDE dataset). 379 */ 380 public void showLocalEditor() { 381 // TODO(jon): differentiate between showLocalEditor() and showLocalEditor(entry) 382 if (tabbedPane.getSelectedIndex() != 1) { 383 tabbedPane.setSelectedIndex(1); 384 } 385 LocalEntryEditor editor = new LocalEntryEditor(this, true, this, serverManager); 386 editor.setVisible(true); 387 } 388 389 /** 390 * Shows a local ADDE entry editor <b>with</b> the appropriate fields 391 * pre-populated, using the values from {@code entry}. This is intended to 392 * handled {@literal "editing"} a local ADDE dataset. 393 * 394 * @param entry Entry to edit; should not be {@code null}. 395 */ 396 public void showLocalEditor(final LocalAddeEntry entry) { 397 // TODO(jon): differentiate between showLocalEditor() and showLocalEditor(entry) 398 if (tabbedPane.getSelectedIndex() != 1) { 399 tabbedPane.setSelectedIndex(1); 400 } 401 LocalEntryEditor editor = new LocalEntryEditor(this, true, this, serverManager, entry); 402 editor.setVisible(true); 403 } 404 405 /** 406 * Removes the given local ADDE entries from the server manager GUI. 407 * 408 * @param entries Entries to remove. {@code null} is permissible, but is a {@literal "no-op"}. 409 */ 410 public void removeLocalEntries(final Collection<LocalAddeEntry> entries) { 411 if (entries == null) { 412 return; 413 } 414 // if (entrySet.removeAll(entries)) { 415 if (serverManager.removeEntries(entries)) { 416 logger.trace("successful removal of entries={}",entries); 417 LocalAddeTableModel tableModel = ((LocalAddeTableModel)localTable.getModel()); 418 int first = Integer.MAX_VALUE; 419 int last = Integer.MIN_VALUE; 420 for (LocalAddeEntry entry : entries) { 421 int index = tableModel.getRowForEntry(entry); 422 if (index >= 0) { 423 if (index < first) { 424 first = index; 425 } 426 if (index > last) { 427 last = index; 428 } 429 } 430 } 431 tableModel.fireTableDataChanged(); 432 refreshDisplay(); 433 localTable.revalidate(); 434 if (first < localTable.getRowCount()) { 435 localTable.setRowSelectionInterval(first, first); 436 } 437 } else { 438 logger.debug("could not remove entries={}", entries); 439 } 440 } 441 442 /** 443 * Extracts datasets from a given MCTABLE.TXT and adds them to the server 444 * manager. 445 * 446 * @param path Path to the MCTABLE.TXT. Cannot be {@code null}. 447 * @param username ADDE username to use for verifying extracted datasets. Cannot be {@code null}. 448 * @param project ADDE project number to use for verifying extracted datasets. Cannot be {@code null}. 449 */ 450 public void importMctable(final String path, final String username, final String project) { 451 logger.trace("extracting path={} username={}, project={}", new Object[] { path, username, project }); 452 final Set<RemoteAddeEntry> imported = EntryTransforms.extractMctableEntries(path, username, project); 453 logger.trace("extracted entries={}", imported); 454 if (imported.equals(Collections.emptySet())) { 455 LogUtil.userErrorMessage("Selection does not appear to a valid MCTABLE.TXT file:\n"+path); 456 } else { 457 logger.trace("adding extracted entries..."); 458 // verify entries first! 459 serverManager.addEntries(imported); 460 refreshDisplay(); 461 repaint(); 462 Runnable r = new Runnable() { 463 public void run() { 464 checkDatasets(imported); 465 } 466 }; 467 Thread t = new Thread(r); 468 t.start(); 469 } 470 } 471 472 /** 473 * Attempts to start the local servers. 474 * 475 * @see EntryStore#startLocalServer() 476 */ 477 public void startLocalServers() { 478 logger.trace("starting local servers...?"); 479 serverManager.startLocalServer(); 480 } 481 482 /** 483 * Attempts to stop the local servers. 484 * 485 * @see EntryStore#stopLocalServer() 486 */ 487 public void stopLocalServers() { 488 logger.trace("stopping local servers...?"); 489 serverManager.stopLocalServer(); 490 } 491 492 /** 493 * Responds to local server events and attempts to update the GUI status 494 * message. 495 * 496 * @param event Local server event. Should not be {@code null}. 497 */ 498 @EventSubscriber(eventClass=AddeThread.McservEvent.class) 499 public void mcservUpdated(final AddeThread.McservEvent event) { 500 logger.trace("eventbus evt={}", event.toString()); 501 final String msg; 502 switch (event) { 503 case ACTIVE: case DIED: case STOPPED: 504 msg = event.getMessage(); 505 break; 506 case STARTED: 507 // msg = "Local servers are listening on port "+EntryStore.getLocalPort(); 508 msg = String.format(event.getMessage(),EntryStore.getLocalPort()); 509 break; 510 default: 511 msg = "Unknown local servers status: "+event.toString(); 512 break; 513 } 514 SwingUtilities.invokeLater(new Runnable() { 515 public void run() { 516 if (statusLabel != null) { 517 statusLabel.setText(msg); 518 } 519 } 520 }); 521 } 522 523 /** 524 * Builds the server manager GUI. 525 */ 526 @SuppressWarnings({"unchecked", "FeatureEnvy", "MagicNumber"}) 527 public void initComponents() { 528 Dimension frameSize = new Dimension(730, 460); 529 ucar.unidata.ui.Help.setTopDir(HELP_TOP_DIR); 530 system = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/padlock_closed.png"); 531 mctable = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/bug.png"); 532 user = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/hand_pro.png"); 533 invalid = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/emotion_sad.png"); 534 unverified = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/eye_inv.png"); 535 setTitle("ADDE Data Manager"); 536 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 537 setSize(frameSize); 538 setMinimumSize(frameSize); 539 addWindowListener(new WindowAdapter() { 540 public void windowClosed(WindowEvent evt) { 541 formWindowClosed(evt); 542 } 543 }); 544 545 JMenuBar menuBar = new JMenuBar(); 546 setJMenuBar(menuBar); 547 548 JMenu fileMenu = new JMenu("File"); 549 menuBar.add(fileMenu); 550 551 JMenuItem remoteNewMenuItem = new JMenuItem("New Remote Dataset"); 552 remoteNewMenuItem.addActionListener(new java.awt.event.ActionListener() { 553 public void actionPerformed(ActionEvent evt) { 554 showRemoteEditor(); 555 } 556 }); 557 fileMenu.add(remoteNewMenuItem); 558 559 JMenuItem localNewMenuItem = new JMenuItem("New Local Dataset"); 560 localNewMenuItem.addActionListener(new java.awt.event.ActionListener() { 561 public void actionPerformed(ActionEvent evt) { 562 showLocalEditor(); 563 } 564 }); 565 fileMenu.add(localNewMenuItem); 566 567 fileMenu.add(new JSeparator()); 568 569 JMenuItem importMctableMenuItem = new JMenuItem("Import MCTABLE..."); 570 importMctableMenuItem.addActionListener(new ActionListener() { 571 public void actionPerformed(ActionEvent e) { 572 importButtonActionPerformed(e); 573 } 574 }); 575 fileMenu.add(importMctableMenuItem); 576 577 JMenuItem importUrlMenuItem = new JMenuItem("Import from URL..."); 578 final TabbedAddeManager myRef = this; 579 importUrlMenuItem.addActionListener(new ActionListener() { 580 public void actionPerformed(ActionEvent e) { 581 SwingUtilities.invokeLater(new Runnable() { 582 public void run() { 583 try { 584 ImportUrl dialog = new ImportUrl(serverManager, myRef); 585 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); 586 dialog.setVisible(true); 587 } catch (Exception e) { 588 e.printStackTrace(); 589 } 590 } 591 }); 592 } 593 }); 594 fileMenu.add(importUrlMenuItem); 595 596 // JMenuItem exportMenuItem = new JMenuItem("Export..."); 597 // exportMenuItem.addActionListener(new ActionListener() { 598 // public void actionPerformed(ActionEvent e) { 599 // logger.trace("exporting datasets..."); 600 // } 601 // }); 602 // fileMenu.add(exportMenuItem); 603 // 604 fileMenu.add(new JSeparator()); 605 606 JMenuItem closeMenuItem = new JMenuItem("Close"); 607 closeMenuItem.addActionListener(new java.awt.event.ActionListener() { 608 public void actionPerformed(java.awt.event.ActionEvent evt) { 609 logger.debug("evt={}", evt.toString()); 610 closeManager(); 611 } 612 }); 613 fileMenu.add(closeMenuItem); 614 615 JMenu editMenu = new JMenu("Edit"); 616 menuBar.add(editMenu); 617 618 editMenuItem = new JMenuItem("Edit Entry..."); 619 editMenuItem.setEnabled(false); 620 editMenuItem.addActionListener(new java.awt.event.ActionListener() { 621 public void actionPerformed(java.awt.event.ActionEvent evt) { 622 if (tabbedPane.getSelectedIndex() == 0) { 623 showRemoteEditor(getSelectedRemoteEntries()); 624 } else { 625 showLocalEditor(getSingleLocalSelection()); 626 } 627 } 628 }); 629 editMenu.add(editMenuItem); 630 631 removeMenuItem = new JMenuItem("Remove Selection"); 632 removeMenuItem.setEnabled(false); 633 removeMenuItem.addActionListener(new java.awt.event.ActionListener() { 634 public void actionPerformed(java.awt.event.ActionEvent evt) { 635 if (tabbedPane.getSelectedIndex() == 0) { 636 removeRemoteEntries(getSelectedRemoteEntries()); 637 } else { 638 removeLocalEntries(getSelectedLocalEntries()); 639 } 640 } 641 }); 642 editMenu.add(removeMenuItem); 643 644 JMenu localServersMenu = new JMenu("Local Servers"); 645 menuBar.add(localServersMenu); 646 647 JMenuItem startLocalMenuItem = new JMenuItem("Start Local Servers"); 648 startLocalMenuItem.addActionListener(new ActionListener() { 649 public void actionPerformed(ActionEvent e) { 650 startLocalServers(); 651 } 652 }); 653 localServersMenu.add(startLocalMenuItem); 654 655 JMenuItem stopLocalMenuItem = new JMenuItem("Stop Local Servers"); 656 stopLocalMenuItem.addActionListener(new ActionListener() { 657 public void actionPerformed(ActionEvent e) { 658 stopLocalServers(); 659 } 660 }); 661 localServersMenu.add(stopLocalMenuItem); 662 663 JMenu helpMenu = new JMenu("Help"); 664 menuBar.add(helpMenu); 665 666 JMenuItem remoteHelpMenuItem = new JMenuItem("Show Remote Data Help"); 667 remoteHelpMenuItem.addActionListener(new java.awt.event.ActionListener() { 668 public void actionPerformed(java.awt.event.ActionEvent evt) { 669 ucar.unidata.ui.Help.getDefaultHelp().gotoTarget(REMOTE_HELP_TARGET); 670 } 671 }); 672 helpMenu.add(remoteHelpMenuItem); 673 674 JMenuItem localHelpMenuItem = new JMenuItem("Show Local Data Help"); 675 localHelpMenuItem.addActionListener(new java.awt.event.ActionListener() { 676 public void actionPerformed(java.awt.event.ActionEvent evt) { 677 ucar.unidata.ui.Help.getDefaultHelp().gotoTarget(LOCAL_HELP_TARGET); 678 } 679 }); 680 helpMenu.add(localHelpMenuItem); 681 682 contentPane = new JPanel(); 683 contentPane.setBorder(null); 684 setContentPane(contentPane); 685 contentPane.setLayout(new MigLayout("", "[grow]", "[grow][grow][grow]")); 686 687 tabbedPane = new JTabbedPane(JTabbedPane.TOP); 688 tabbedPane.addChangeListener(new ChangeListener() { 689 public void stateChanged(ChangeEvent event) { 690 handleTabStateChanged(event); 691 } 692 }); 693 contentPane.add(tabbedPane, "cell 0 0 1 3,grow"); 694 695 JPanel remoteTab = new JPanel(); 696 remoteTab.setBorder(new EmptyBorder(0, 4, 4, 4)); 697 tabbedPane.addTab("Remote Data", null, remoteTab, null); 698 remoteTab.setLayout(new BoxLayout(remoteTab, BoxLayout.Y_AXIS)); 699 700 remoteTable = new BetterJTable(); 701 JScrollPane remoteScroller = BetterJTable.createStripedJScrollPane(remoteTable); 702 703 remoteTable.setModel(new RemoteAddeTableModel(serverManager)); 704 remoteTable.setColumnSelectionAllowed(false); 705 remoteTable.setRowSelectionAllowed(true); 706 remoteTable.getTableHeader().setReorderingAllowed(false); 707 remoteTable.setFont(UIManager.getFont("Table.font").deriveFont(11.0f)); 708 remoteTable.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 709 remoteTable.setDefaultRenderer(String.class, new TextRenderer()); 710 remoteTable.getColumnModel().getColumn(0).setPreferredWidth(10); 711 remoteTable.getColumnModel().getColumn(1).setPreferredWidth(10); 712 remoteTable.getColumnModel().getColumn(3).setPreferredWidth(50); 713 remoteTable.getColumnModel().getColumn(4).setPreferredWidth(50); 714 remoteTable.getColumnModel().getColumn(0).setCellRenderer(new EntryValidityRenderer()); 715 remoteTable.getColumnModel().getColumn(1).setCellRenderer(new EntrySourceRenderer()); 716 remoteTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { 717 public void valueChanged(final ListSelectionEvent e) { 718 remoteSelectionModelChanged(e); 719 } 720 }); 721 remoteTable.addMouseListener(new java.awt.event.MouseAdapter() { 722 public void mouseClicked(final java.awt.event.MouseEvent e) { 723 if ((e.getClickCount() == 2) && (hasSingleRemoteSelection())) { 724 showRemoteEditor(getSelectedRemoteEntries()); 725 } 726 } 727 }); 728 remoteScroller.setViewportView(remoteTable); 729 remoteTab.add(remoteScroller); 730 731 JPanel remoteActionPanel = new JPanel(); 732 remoteTab.add(remoteActionPanel); 733 remoteActionPanel.setLayout(new BoxLayout(remoteActionPanel, BoxLayout.X_AXIS)); 734 735 newRemoteButton = new JButton("Add New Dataset"); 736 newRemoteButton.addActionListener(new ActionListener() { 737 public void actionPerformed(ActionEvent e) { 738 logger.trace("new remote dataset"); 739 showRemoteEditor(); 740 } 741 }); 742 newRemoteButton.setToolTipText("Create a new remote ADDE dataset."); 743 remoteActionPanel.add(newRemoteButton); 744 745 editRemoteButton = new JButton("Edit Dataset"); 746 editRemoteButton.addActionListener(new ActionListener() { 747 public void actionPerformed(ActionEvent e) { 748 logger.trace("edit remote dataset"); 749 showRemoteEditor(getSelectedRemoteEntries()); 750 } 751 }); 752 editRemoteButton.setToolTipText("Edit an existing remote ADDE dataset."); 753 remoteActionPanel.add(editRemoteButton); 754 755 removeRemoteButton = new JButton("Remove Selection"); 756 removeRemoteButton.addActionListener(new ActionListener() { 757 public void actionPerformed(ActionEvent e) { 758 logger.trace("remove remote dataset"); 759 removeRemoteEntries(getSelectedRemoteEntries()); 760 } 761 }); 762 removeRemoteButton.setToolTipText("Remove the selected remote ADDE datasets."); 763 remoteActionPanel.add(removeRemoteButton); 764 765 importRemoteButton = new JButton("Import MCTABLE..."); 766 importRemoteButton.addActionListener(new ActionListener() { 767 public void actionPerformed(ActionEvent e) { 768 logger.trace("import from mctable..."); 769 importButtonActionPerformed(e); 770 } 771 }); 772 remoteActionPanel.add(importRemoteButton); 773 774 JPanel localTab = new JPanel(); 775 localTab.setBorder(new EmptyBorder(0, 4, 4, 4)); 776 tabbedPane.addTab("Local Data", null, localTab, null); 777 localTab.setLayout(new BoxLayout(localTab, BoxLayout.Y_AXIS)); 778 779 localTable = new BetterJTable(); 780 JScrollPane localScroller = BetterJTable.createStripedJScrollPane(localTable); 781 localTable.setModel(new LocalAddeTableModel(serverManager)); 782 localTable.setColumnSelectionAllowed(false); 783 localTable.setRowSelectionAllowed(true); 784 localTable.getTableHeader().setReorderingAllowed(false); 785 localTable.setFont(UIManager.getFont("Table.font").deriveFont(11.0f)); 786 localTable.setDefaultRenderer(String.class, new TextRenderer()); 787 localTable.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 788 localTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { 789 public void valueChanged(final ListSelectionEvent e) { 790 localSelectionModelChanged(e); 791 } 792 }); 793 localTable.addMouseListener(new java.awt.event.MouseAdapter() { 794 public void mouseClicked(final java.awt.event.MouseEvent e) { 795 if ((e.getClickCount() == 2) && (hasSingleLocalSelection())) { 796 showLocalEditor(getSingleLocalSelection()); 797 } 798 } 799 }); 800 localScroller.setViewportView(localTable); 801 localTab.add(localScroller); 802 803 JPanel localActionPanel = new JPanel(); 804 localTab.add(localActionPanel); 805 localActionPanel.setLayout(new BoxLayout(localActionPanel, BoxLayout.X_AXIS)); 806 807 newLocalButton = new JButton("Add New Dataset"); 808 newLocalButton.addActionListener(new ActionListener() { 809 public void actionPerformed(ActionEvent e) { 810 logger.trace("new local dataset"); 811 showLocalEditor(); 812 } 813 }); 814 newLocalButton.setToolTipText("Create a new local ADDE dataset."); 815 localActionPanel.add(newLocalButton); 816 817 editLocalButton = new JButton("Edit Dataset"); 818 editLocalButton.setEnabled(false); 819 editLocalButton.addActionListener(new ActionListener() { 820 public void actionPerformed(ActionEvent e) { 821 logger.trace("edit local dataset"); 822 showLocalEditor(getSingleLocalSelection()); 823 } 824 }); 825 editLocalButton.setToolTipText("Edit an existing local ADDE dataset."); 826 localActionPanel.add(editLocalButton); 827 828 removeLocalButton = new JButton("Remove Selection"); 829 removeLocalButton.setEnabled(false); 830 removeLocalButton.addActionListener(new ActionListener() { 831 public void actionPerformed(ActionEvent e) { 832 logger.trace("remove local dataset"); 833 removeLocalEntries(getSelectedLocalEntries()); 834 } 835 }); 836 removeLocalButton.setToolTipText("Remove the selected local ADDE datasets."); 837 localActionPanel.add(removeLocalButton); 838 839 JPanel statusPanel = new JPanel(); 840 statusPanel.setBorder(new EmptyBorder(0, 6, 0, 6)); 841 contentPane.add(statusPanel, "cell 0 3,grow"); 842 statusPanel.setLayout(new BorderLayout(0, 0)); 843 844 Box statusMessageBox = Box.createHorizontalBox(); 845 statusPanel.add(statusMessageBox, BorderLayout.WEST); 846 847 String statusMessage = McservEvent.STOPPED.getMessage(); 848 if (serverManager.checkLocalServer()) { 849 statusMessage = McservEvent.ACTIVE.getMessage(); 850 } 851 statusLabel = new JLabel(statusMessage); 852 statusMessageBox.add(statusLabel); 853 statusLabel.setEnabled(false); 854 855 Box frameControlBox = Box.createHorizontalBox(); 856 statusPanel.add(frameControlBox, BorderLayout.EAST); 857 858 // cancelButton = new JButton("Cancel"); 859 // cancelButton.addActionListener(new ActionListener() { 860 // public void actionPerformed(ActionEvent e) { 861 // handleCancellingChanges(); 862 // } 863 // }); 864 // frameControlBox.add(cancelButton); 865 866 // applyButton = new JButton("Apply"); 867 // applyButton.addActionListener(new ActionListener() { 868 // public void actionPerformed(ActionEvent e) { 869 // logger.trace("apply"); 870 // handleSavingChanges(); 871 // } 872 // }); 873 // frameControlBox.add(applyButton); 874 875 okButton = new JButton("Ok"); 876 okButton.addActionListener(new ActionListener() { 877 public void actionPerformed(ActionEvent e) { 878 // handleSavingChanges(); 879 closeManager(); 880 } 881 }); 882 frameControlBox.add(okButton); 883 tabbedPane.setSelectedIndex(getLastTab()); 884 guiInitialized = true; 885 } 886 887 // /** 888 // * Determines whether or not the user has changed anything (where {@literal "changed"} means added, modified, or removed entries). 889 // * 890 // * @return {@code true} if the user has changed any entries; {@code false} otherwise. 891 // */ 892 // public boolean hasUserChanges() { 893 // return !entrySet.equals(serverManager.getEntrySet()); 894 // } 895 896 // /** 897 // * Respond to the user clicking the {@literal "cancel"} button. 898 // */ 899 // public void handleCancellingChanges() { 900 // logger.trace("cancel changes. anything to do={}", hasUserChanges()); 901 // closeManager(); 902 // } 903 // 904 // /** 905 // * Respond to the user clicking the {@literal "save changes"} button. 906 // */ 907 // public void handleSavingChanges() { 908 // boolean userChanges = hasUserChanges(); 909 // logger.trace("save changes. anything to do={}", userChanges); 910 // if (userChanges) { 911 // serverManager.addEntries(entrySet); 912 // } 913 // } 914 915 /** 916 * Respond to changes in {@link #tabbedPane}; primarily switching tabs. 917 * 918 * @param event Event being handled. Ignored for now. 919 */ 920 private void handleTabStateChanged(final ChangeEvent event) { 921 assert SwingUtilities.isEventDispatchThread(); 922 boolean hasSelection = false; 923 int index = 0; 924 if (guiInitialized) { 925 index = tabbedPane.getSelectedIndex(); 926 if (index == 0) { 927 hasSelection = hasRemoteSelection(); 928 editRemoteButton.setEnabled(hasSelection); 929 removeRemoteButton.setEnabled(hasSelection); 930 } else { 931 hasSelection = hasLocalSelection(); 932 editLocalButton.setEnabled(hasSelection); 933 removeLocalButton.setEnabled(hasSelection); 934 } 935 editMenuItem.setEnabled(hasSelection); 936 removeMenuItem.setEnabled(hasSelection); 937 setLastTab(index); 938 } 939 logger.trace("index={} hasRemote={} hasLocal={} guiInit={}", new Object[] {index, hasRemoteSelection(), hasLocalSelection(), guiInitialized}); 940 } 941 942 /** 943 * Respond to events. 944 * 945 * @param e {@link ListSelectionEvent} that necessitated this call. 946 */ 947 private void remoteSelectionModelChanged(final ListSelectionEvent e) { 948 if (e.getValueIsAdjusting()) { 949 return; 950 } 951 952 int selectedRowCount = 0; 953 ListSelectionModel selModel = (ListSelectionModel)e.getSource(); 954 Set<RemoteAddeEntry> selectedEntries; 955 if (selModel.isSelectionEmpty()) { 956 selectedEntries = Collections.emptySet(); 957 } else { 958 int min = selModel.getMinSelectionIndex(); 959 int max = selModel.getMaxSelectionIndex(); 960 RemoteAddeTableModel tableModel = ((RemoteAddeTableModel)remoteTable.getModel()); 961 selectedEntries = newLinkedHashSet((max - min) * AddeEntry.EntryType.values().length); 962 for (int i = min; i <= max; i++) { 963 if (selModel.isSelectedIndex(i)) { 964 List<RemoteAddeEntry> entries = tableModel.getEntriesAtRow(i); 965 selectedEntries.addAll(entries); 966 selectedRowCount++; 967 } 968 } 969 } 970 971 boolean onlyDefaultEntries = true; 972 for (RemoteAddeEntry entry : selectedEntries) { 973 if (entry.getEntrySource() != EntrySource.SYSTEM) { 974 onlyDefaultEntries = false; 975 break; 976 } 977 } 978 setSelectedRemoteEntries(selectedEntries); 979 980 // the current "edit" dialog doesn't work so well with multiple 981 // servers/datasets, so only allow the user to edit entries one at a time. 982 boolean singleSelection = selectedRowCount == 1; 983 editRemoteButton.setEnabled(singleSelection); 984 editMenuItem.setEnabled(singleSelection); 985 986 boolean hasSelection = (selectedRowCount >= 1) && (!onlyDefaultEntries); 987 removeRemoteButton.setEnabled(hasSelection); 988 removeMenuItem.setEnabled(hasSelection); 989 } 990 991 /** 992 * Respond to events from the local dataset table. 993 * 994 * @param e {@link ListSelectionEvent} that necessitated this call. 995 */ 996 private void localSelectionModelChanged(final ListSelectionEvent e) { 997 if (e.getValueIsAdjusting()) { 998 return; 999 } 1000 ListSelectionModel selModel = (ListSelectionModel)e.getSource(); 1001 Set<LocalAddeEntry> selectedEntries; 1002 if (selModel.isSelectionEmpty()) { 1003 selectedEntries = Collections.emptySet(); 1004 } else { 1005 int min = selModel.getMinSelectionIndex(); 1006 int max = selModel.getMaxSelectionIndex(); 1007 LocalAddeTableModel tableModel = ((LocalAddeTableModel)localTable.getModel()); 1008 selectedEntries = newLinkedHashSet(max - min); 1009 for (int i = min; i <= max; i++) { 1010 if (selModel.isSelectedIndex(i)) { 1011 selectedEntries.add(tableModel.getEntryAtRow(i)); 1012 } 1013 } 1014 } 1015 1016 setSelectedLocalEntries(selectedEntries); 1017 1018 // the current "edit" dialog doesn't work so well with multiple 1019 // servers/datasets, so only allow the user to edit entries one at a time. 1020 boolean singleSelection = (selectedEntries.size() == 1); 1021 this.editRemoteButton.setEnabled(singleSelection); 1022 this.editMenuItem.setEnabled(singleSelection); 1023 1024 boolean hasSelection = !selectedEntries.isEmpty(); 1025 removeRemoteButton.setEnabled(hasSelection); 1026 removeMenuItem.setEnabled(hasSelection); 1027 } 1028 1029 /** 1030 * Checks to see if {@link #selectedRemoteEntries} contains any 1031 * {@link RemoteAddeEntry}s. 1032 * 1033 * @return Whether or not any {@code RemoteAddeEntry} values are selected. 1034 */ 1035 private boolean hasRemoteSelection() { 1036 return !selectedRemoteEntries.isEmpty(); 1037 } 1038 1039 /** 1040 * Checks to see if {@link #selectedLocalEntries} contains any 1041 * {@link LocalAddeEntry}s. 1042 * 1043 * @return Whether or not any {@code LocalAddeEntry} values are selected. 1044 */ 1045 private boolean hasLocalSelection() { 1046 return !selectedLocalEntries.isEmpty(); 1047 } 1048 1049 /** 1050 * Checks to see if the user has select a <b>single</b> remote dataset. 1051 * 1052 * @return {@code true} if there is a single remote dataset selected. {@code false} otherwise. 1053 */ 1054 private boolean hasSingleRemoteSelection() { 1055 String entryText = null; 1056 for (RemoteAddeEntry entry : selectedRemoteEntries) { 1057 if (entryText == null) { 1058 entryText = entry.getEntryText(); 1059 } 1060 if (!entry.getEntryText().equals(entryText)) { 1061 return false; 1062 } 1063 } 1064 return true; 1065 } 1066 1067 /** 1068 * Checks to see if the user has select a <b>single</b> local dataset. 1069 * 1070 * @return {@code true} if there is a single local dataset selected. {@code false} otherwise. 1071 */ 1072 private boolean hasSingleLocalSelection() { 1073 return (selectedLocalEntries.size() == 1); 1074 } 1075 1076 /** 1077 * If there is a single local dataset selected, this method will return that 1078 * dataset. 1079 * 1080 * @return Either the single selected local dataset, or {@link LocalAddeEntry#INVALID_ENTRY}. 1081 */ 1082 private LocalAddeEntry getSingleLocalSelection() { 1083 LocalAddeEntry entry = LocalAddeEntry.INVALID_ENTRY; 1084 if (selectedLocalEntries.size() == 1) { 1085 entry = selectedLocalEntries.get(0); 1086 } 1087 return entry; 1088 } 1089 1090 /** 1091 * Corresponds to the selected remote ADDE entries in the GUI. 1092 * 1093 * @param entries Should not be {@code null}. 1094 */ 1095 private void setSelectedRemoteEntries(final Collection<RemoteAddeEntry> entries) { 1096 selectedRemoteEntries.clear(); 1097 selectedRemoteEntries.addAll(entries); 1098 this.editRemoteButton.setEnabled(entries.size() == 1); 1099 this.removeRemoteButton.setEnabled(!entries.isEmpty()); 1100 logger.trace("remote entries={}", entries); 1101 } 1102 1103 /** 1104 * Gets the selected remote ADDE entries. 1105 * 1106 * @return Either an empty list or the remote entries selected in the GUI. 1107 */ 1108 private List<RemoteAddeEntry> getSelectedRemoteEntries() { 1109 if (selectedRemoteEntries.isEmpty()) { 1110 return Collections.emptyList(); 1111 } else { 1112 return arrList(selectedRemoteEntries); 1113 } 1114 } 1115 1116 /** 1117 * Corresponds to the selected local ADDE entries in the GUI. 1118 * 1119 * @param entries Should not be {@code null}. 1120 */ 1121 private void setSelectedLocalEntries(final Collection<LocalAddeEntry> entries) { 1122 selectedLocalEntries.clear(); 1123 selectedLocalEntries.addAll(entries); 1124 this.editLocalButton.setEnabled(entries.size() == 1); 1125 this.removeLocalButton.setEnabled(!entries.isEmpty()); 1126 logger.trace("local entries={}", entries); 1127 } 1128 1129 /** 1130 * Gets the selected local ADDE entries. 1131 * 1132 * @return Either an empty list or the local entries selected in the GUI. 1133 */ 1134 private List<LocalAddeEntry> getSelectedLocalEntries() { 1135 if (selectedLocalEntries.isEmpty()) { 1136 return Collections.emptyList(); 1137 } else { 1138 return arrList(selectedLocalEntries); 1139 } 1140 } 1141 1142 /** 1143 * Handles the user closing the server manager GUI. 1144 * 1145 * @param evt Event that triggered this method call. 1146 * 1147 * @see #closeManager() 1148 */ 1149 private void formWindowClosed(java.awt.event.WindowEvent evt) { 1150 logger.debug("evt={}", evt.toString()); 1151 closeManager(); 1152 } 1153 1154 @SuppressWarnings({"MagicNumber"}) 1155 private JPanel makeFileChooserAccessory() { 1156 assert SwingUtilities.isEventDispatchThread(); 1157 JPanel accessory = new JPanel(); 1158 accessory.setLayout(new BoxLayout(accessory, BoxLayout.PAGE_AXIS)); 1159 importAccountBox = new JCheckBox("Use ADDE Accounting?"); 1160 importAccountBox.setSelected(false); 1161 importAccountBox.addActionListener(new java.awt.event.ActionListener() { 1162 public void actionPerformed(java.awt.event.ActionEvent evt) { 1163 boolean selected = importAccountBox.isSelected(); 1164 importUser.setEnabled(selected); 1165 importProject.setEnabled(selected); 1166 } 1167 }); 1168 String clientProp = "JComponent.sizeVariant"; 1169 String propVal = "mini"; 1170 1171 importUser = new JTextField(); 1172 importUser.putClientProperty(clientProp, propVal); 1173 Prompt userPrompt = new Prompt(importUser, "Username"); 1174 userPrompt.putClientProperty(clientProp, propVal); 1175 importUser.setEnabled(importAccountBox.isSelected()); 1176 1177 importProject = new JTextField(); 1178 Prompt projPrompt = new Prompt(importProject, "Project Number"); 1179 projPrompt.putClientProperty(clientProp, propVal); 1180 importProject.putClientProperty(clientProp, propVal); 1181 importProject.setEnabled(importAccountBox.isSelected()); 1182 1183 GroupLayout layout = new GroupLayout(accessory); 1184 accessory.setLayout(layout); 1185 layout.setHorizontalGroup( 1186 layout.createParallelGroup(GroupLayout.Alignment.LEADING) 1187 .addComponent(importAccountBox) 1188 .addGroup(layout.createSequentialGroup() 1189 .addContainerGap() 1190 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING, false) 1191 .addComponent(importProject, GroupLayout.Alignment.LEADING) 1192 .addComponent(importUser, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 131, Short.MAX_VALUE))) 1193 ); 1194 layout.setVerticalGroup( 1195 layout.createParallelGroup(GroupLayout.Alignment.LEADING) 1196 .addGroup(layout.createSequentialGroup() 1197 .addComponent(importAccountBox) 1198 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 1199 .addComponent(importUser, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) 1200 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 1201 .addComponent(importProject, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) 1202 .addContainerGap(55, (int)Short.MAX_VALUE)) 1203 ); 1204 return accessory; 1205 } 1206 1207 private void importButtonActionPerformed(java.awt.event.ActionEvent evt) { 1208 assert SwingUtilities.isEventDispatchThread(); 1209 JFileChooser fc = new JFileChooser(getLastImportPath()); 1210 fc.setAccessory(makeFileChooserAccessory()); 1211 fc.setFileSelectionMode(JFileChooser.FILES_ONLY); 1212 int ret = fc.showOpenDialog(TabbedAddeManager.this); 1213 if (ret == JFileChooser.APPROVE_OPTION) { 1214 File f = fc.getSelectedFile(); 1215 String path = f.getPath(); 1216 1217 boolean defaultUser = false; 1218 String forceUser = importUser.getText(); 1219 if (forceUser.length() == 0) { 1220 forceUser = AddeEntry.DEFAULT_ACCOUNT.getUsername(); 1221 defaultUser = true; 1222 } 1223 1224 boolean defaultProj = false; 1225 String forceProj = importProject.getText(); 1226 if (forceProj.length() == 0) { 1227 forceProj = AddeEntry.DEFAULT_ACCOUNT.getProject(); 1228 defaultProj = true; 1229 } 1230 1231 1232 if ((importAccountBox.isSelected()) && (defaultUser || defaultProj)) { 1233 logger.warn("bad acct dialog: forceUser={} forceProj={}", forceUser, forceProj); 1234 } else { 1235 logger.warn("acct appears valid: forceUser={} forceProj={}", forceUser, forceProj); 1236 importMctable(path, forceUser, forceProj); 1237 // don't worry about file validity; i'll just assume the user clicked 1238 // on the wrong entry by accident. 1239 setLastImportPath(f.getParent()); 1240 } 1241 } 1242 } 1243 1244 /** 1245 * Returns the directory that contained the most recently imported MCTABLE.TXT. 1246 * 1247 * @return Either the path to the most recently imported MCTABLE.TXT file, 1248 * or an empty {@code String}. 1249 */ 1250 private String getLastImportPath() { 1251 String lastPath = serverManager.getIdvStore().get(LAST_IMPORTED, ""); 1252 logger.trace("last path='{}'", lastPath); 1253 return lastPath; 1254 // return serverManager.getIdvStore().get(LAST_IMPORTED, ""); 1255 } 1256 1257 /** 1258 * Saves the directory that contained the most recently imported MCTABLE.TXT. 1259 * 1260 * @param path Path to the most recently imported MCTABLE.TXT file. 1261 * {@code null} values are replaced with an empty {@code String}. 1262 */ 1263 private void setLastImportPath(final String path) { 1264 String okayPath = (path == null) ? "" : path; 1265 logger.trace("saving path='{}'", okayPath); 1266 serverManager.getIdvStore().put(LAST_IMPORTED, okayPath); 1267 } 1268 1269 /** 1270 * Returns the index of the user's last server manager tab. 1271 * 1272 * @return Index of the user's most recently viewed server manager tab, or {@code 0}. 1273 */ 1274 private int getLastTab() { 1275 int index = serverManager.getIdvStore().get(LAST_TAB, 0); 1276 logger.trace("last tab={}", index); 1277 return index; 1278 // return serverManager.getIdvStore().get(LAST_TAB, 0); 1279 } 1280 1281 /** 1282 * Saves the index of the last server manager tab the user was looking at. 1283 * 1284 * @param index Index of the user's most recently viewed server manager tab. 1285 */ 1286 private void setLastTab(final int index) { 1287 int okayIndex = ((index >= 0) && (index < 2)) ? index : 0; 1288 IdvObjectStore store = serverManager.getIdvStore(); 1289 logger.trace("storing tab={}", okayIndex); 1290 store.put(LAST_TAB, okayIndex); 1291 } 1292 1293 // stupid adde.ucar.edu entries never seem to time out! great! making the gui hang is just so awesome! 1294 @SuppressWarnings({"ObjectAllocationInLoop"}) 1295 public Set<RemoteAddeEntry> checkDatasets(final Collection<RemoteAddeEntry> entries) { 1296 notNull(entries, "can't check a null collection of entries"); 1297 if (entries.isEmpty()) { 1298 return Collections.emptySet(); 1299 } 1300 1301 Set<RemoteAddeEntry> valid = newLinkedHashSet(); 1302 ExecutorService exec = Executors.newFixedThreadPool(POOL); 1303 CompletionService<List<RemoteAddeEntry>> ecs = new ExecutorCompletionService<List<RemoteAddeEntry>>(exec); 1304 final RemoteAddeTableModel tableModel = (RemoteAddeTableModel)remoteTable.getModel(); 1305 1306 // place entries 1307 for (RemoteAddeEntry entry : entries) { 1308 ecs.submit(new BetterCheckTask(entry)); 1309 logger.trace("submitting entry={}", entry); 1310 final int row = tableModel.getRowForEntry(entry); 1311 runOnEDT(new Runnable() { 1312 public void run() { 1313 tableModel.fireTableRowsUpdated(row, row); 1314 } 1315 }); 1316 } 1317 1318 // work through the entries 1319 try { 1320 for (int i = 0; i < entries.size(); i++) { 1321 final List<RemoteAddeEntry> checkedEntries = ecs.take().get(); 1322 if (!checkedEntries.isEmpty()) { 1323 final int row = tableModel.getRowForEntry(checkedEntries.get(0)); 1324 runOnEDT(new Runnable() { 1325 public void run() { 1326 List<RemoteAddeEntry> oldEntries = tableModel.getEntriesAtRow(row); 1327 serverManager.replaceEntries(oldEntries, checkedEntries); 1328 // entrySet.removeAll(oldEntries); 1329 // entrySet.addAll(checkedEntries); 1330 tableModel.fireTableRowsUpdated(row, row); 1331 } 1332 }); 1333 } 1334 valid.addAll(checkedEntries); 1335 } 1336 } catch (InterruptedException e) { 1337 LogUtil.logException("Interrupted while validating entries", e); 1338 } catch (ExecutionException e) { 1339 LogUtil.logException("ADDE validation execution error", e); 1340 } finally { 1341 exec.shutdown(); 1342 } 1343 return valid; 1344 } 1345 1346 private static class BetterCheckTask implements Callable<List<RemoteAddeEntry>> { 1347 private final RemoteAddeEntry entry; 1348 public BetterCheckTask(final RemoteAddeEntry entry) { 1349 this.entry = entry; 1350 this.entry.setEntryValidity(EntryValidity.VALIDATING); 1351 } 1352 @SuppressWarnings({"FeatureEnvy"}) 1353 public List<RemoteAddeEntry> call() { 1354 List<RemoteAddeEntry> valid = arrList(); 1355 if (RemoteAddeEntry.checkHost(entry)) { 1356 for (RemoteAddeEntry tmp : EntryTransforms.createEntriesFrom(entry)) { 1357 if (RemoteAddeEntry.checkEntry(false, tmp) == AddeStatus.OK) { 1358 tmp.setEntryValidity(EntryValidity.VERIFIED); 1359 valid.add(tmp); 1360 } 1361 } 1362 } 1363 if (!valid.isEmpty()) { 1364 entry.setEntryValidity(EntryValidity.VERIFIED); 1365 } else { 1366 entry.setEntryValidity(EntryValidity.INVALID); 1367 } 1368 return valid; 1369 } 1370 } 1371 1372 private class CheckEntryTask implements Callable<RemoteAddeEntry> { 1373 private final RemoteAddeEntry entry; 1374 public CheckEntryTask(final RemoteAddeEntry entry) { 1375 notNull(entry); 1376 this.entry = entry; 1377 this.entry.setEntryValidity(EntryValidity.VALIDATING); 1378 } 1379 @SuppressWarnings({"FeatureEnvy"}) 1380 public RemoteAddeEntry call() { 1381 AddeStatus status = RemoteAddeEntry.checkEntry(entry); 1382 switch (status) { 1383 case OK: entry.setEntryValidity(EntryValidity.VERIFIED); break; 1384 default: entry.setEntryValidity(EntryValidity.INVALID); break; 1385 } 1386 return entry; 1387 } 1388 } 1389 1390 private static class RemoteAddeTableModel extends AbstractTableModel { 1391 1392 // TODO(jon): these constants can go once things calm down 1393 private static final int VALID = 0; 1394 private static final int SOURCE = 1; 1395 private static final int DATASET = 2; 1396 private static final int ACCT = 3; 1397 private static final int TYPES = 4; 1398 private static final Pattern ENTRY_ID_SPLITTER = Pattern.compile("!"); 1399 1400 /** Labels that appear as the column headers. */ 1401 private final String[] columnNames = { 1402 "Valid", "Source", "Dataset", "Accounting", "Data Types" 1403 }; 1404 1405 private final List<String> servers; 1406 1407 /** {@link EntryStore} used to query and apply changes. */ 1408 private final EntryStore entryStore; 1409 1410 /** 1411 * Builds an {@link javax.swing.table.AbstractTableModel} with some extensions that 1412 * facilitate working with {@link RemoteAddeEntry RemoteAddeEntrys}. 1413 * 1414 * @param entryStore Server manager object. 1415 */ 1416 public RemoteAddeTableModel(final EntryStore entryStore) { 1417 notNull(entryStore, "Cannot query a null EntryStore"); 1418 this.entryStore = entryStore; 1419 this.servers = arrList(entryStore.getRemoteEntryTexts()); 1420 } 1421 1422 /** 1423 * Returns the {@link RemoteAddeEntry} at the given index. 1424 * 1425 * @param row Index of the entry. 1426 * 1427 * @return The {@code RemoteAddeEntry} at the index specified by {@code row}. 1428 */ 1429 protected List<RemoteAddeEntry> getEntriesAtRow(final int row) { 1430 String server = servers.get(row).replace('/', '!'); 1431 List<RemoteAddeEntry> matches = arrList(); 1432 for (AddeEntry entry : entryStore.searchWithPrefix(server)) { 1433 if (entry instanceof RemoteAddeEntry) { 1434 matches.add((RemoteAddeEntry)entry); 1435 } 1436 } 1437 return matches; 1438 } 1439 1440 /** 1441 * Returns the index of the given {@code entry}. 1442 * 1443 * @param entry {@link RemoteAddeEntry} whose row is desired. 1444 * 1445 * @return Index of the desired {@code entry}, or {@code -1} if the 1446 * entry wasn't found. 1447 */ 1448 protected int getRowForEntry(final RemoteAddeEntry entry) { 1449 return getRowForEntry(entry.getEntryText()); 1450 } 1451 1452 /** 1453 * Returns the index of the given entry text within the table. 1454 * 1455 * @param entryText String representation of the desired entry. 1456 * 1457 * @return Index of the desired entry, or {@code -1} if the entry was 1458 * not found. 1459 * 1460 * @see edu.wisc.ssec.mcidasv.servermanager.AddeEntry#getEntryText() 1461 */ 1462 protected int getRowForEntry(final String entryText) { 1463 return servers.indexOf(entryText); 1464 } 1465 1466 /** 1467 * Clears and re-adds all {@link RemoteAddeEntry}s within {@link #entryStore}. 1468 */ 1469 public void refreshEntries() { 1470 servers.clear(); 1471 servers.addAll(entryStore.getRemoteEntryTexts()); 1472 this.fireTableDataChanged(); 1473 } 1474 1475 /** 1476 * Returns the length of {@link #columnNames}. 1477 * 1478 * @return The number of columns. 1479 */ 1480 @Override public int getColumnCount() { 1481 return columnNames.length; 1482 } 1483 1484 /** 1485 * Returns the number of entries being managed. 1486 */ 1487 @Override public int getRowCount() { 1488 return servers.size(); 1489 } 1490 1491 /** 1492 * Finds the value at the given coordinates. 1493 * 1494 * @param row Table row. 1495 * @param column Table column. 1496 * 1497 * @return Value stored at the given {@code row} and {@code column} 1498 * coordinates 1499 * 1500 * @throws IndexOutOfBoundsException if {@code row} or {@code column} 1501 * refer to an invalid table cell. 1502 */ 1503 @Override public Object getValueAt(int row, int column) { 1504 String serverText = servers.get(row); 1505 String prefix = serverText.replace('/', '!'); 1506 switch (column) { 1507 case VALID: return formattedValidity(prefix, entryStore); 1508 case SOURCE: return formattedSource(prefix, entryStore); 1509 case DATASET: return serverText; 1510 case ACCT: return formattedAccounting(prefix, entryStore); 1511 case TYPES: return formattedTypes(prefix, entryStore); 1512 default: throw new IndexOutOfBoundsException(); 1513 } 1514 } 1515 1516 private static String formattedSource(final String serv, final EntryStore manager) { 1517 List<AddeEntry> matches = manager.searchWithPrefix(serv); 1518 EntrySource source = EntrySource.INVALID; 1519 if (!matches.isEmpty()) { 1520 for (AddeEntry entry : matches) { 1521 if (entry.getEntrySource() == EntrySource.USER) { 1522 return EntrySource.USER.toString(); 1523 } 1524 } 1525 source = matches.get(0).getEntrySource(); 1526 } 1527 return source.toString(); 1528 } 1529 1530 private static String formattedValidity(final String serv, final EntryStore manager) { 1531 List<AddeEntry> matches = manager.searchWithPrefix(serv); 1532 EntryValidity validity = EntryValidity.INVALID; 1533 if (!matches.isEmpty()) { 1534 validity = matches.get(0).getEntryValidity(); 1535 } 1536 return validity.toString(); 1537 } 1538 1539 private static String formattedAccounting(final String serv, final EntryStore manager) { 1540 List<AddeEntry> matches = manager.searchWithPrefix(serv); 1541 AddeAccount acct = AddeEntry.DEFAULT_ACCOUNT; 1542 if (!matches.isEmpty()) { 1543 acct = matches.get(0).getAccount(); 1544 } 1545 if (AddeEntry.DEFAULT_ACCOUNT.equals(acct)) { 1546 return "public dataset"; 1547 } 1548 return acct.friendlyString(); 1549 } 1550 1551 private static boolean hasType(final String serv, final EntryStore manager, final EntryType type) { 1552 String[] chunks = ENTRY_ID_SPLITTER.split(serv); 1553 Set<EntryType> types = Collections.emptySet(); 1554 if (chunks.length == 2) { 1555 types = manager.getTypes(chunks[0], chunks[1]); 1556 } 1557 return types.contains(type); 1558 } 1559 1560 private static String formattedTypes(final String serv, final EntryStore manager) { 1561 String[] chunks = ENTRY_ID_SPLITTER.split(serv); 1562 Set<EntryType> types = Collections.emptySet(); 1563 if (chunks.length == 2) { 1564 types = manager.getTypes(chunks[0], chunks[1]); 1565 } 1566 1567 @SuppressWarnings({"MagicNumber"}) 1568 StringBuilder sb = new StringBuilder(30); 1569 for (EntryType type : EnumSet.of(EntryType.IMAGE, EntryType.GRID, EntryType.NAV, EntryType.POINT, EntryType.RADAR, EntryType.TEXT)) { 1570 if (types.contains(type)) { 1571 sb.append(type.toString()).append(' '); 1572 } 1573 } 1574 return sb.toString().toLowerCase(); 1575 } 1576 1577 /** 1578 * Returns the column name associated with {@code column}. 1579 * 1580 * @return One of {@link #columnNames}. 1581 */ 1582 @Override public String getColumnName(final int column) { 1583 return columnNames[column]; 1584 } 1585 1586 @Override public Class<?> getColumnClass(final int column) { 1587 return String.class; 1588 } 1589 1590 @Override public boolean isCellEditable(final int row, final int column) { 1591 return false; 1592 } 1593 } 1594 1595 private static class LocalAddeTableModel extends AbstractTableModel { 1596 1597 /** Labels that appear as the column headers. */ 1598 private final String[] columnNames = { 1599 "Dataset (e.g. MYDATA)", "Image Type (e.g. JAN 07 GOES)", "Format", "Directory" 1600 }; 1601 1602 /** Entries that currently populate the server manager. */ 1603 private final List<LocalAddeEntry> entries; 1604 1605 /** {@link EntryStore} used to query and apply changes. */ 1606 private final EntryStore entryStore; 1607 1608 public LocalAddeTableModel(final EntryStore entryStore) { 1609 notNull(entryStore, "Cannot query a null EntryStore"); 1610 this.entryStore = entryStore; 1611 this.entries = arrList(entryStore.getLocalEntries()); 1612 } 1613 1614 /** 1615 * Returns the {@link LocalAddeEntry} at the given index. 1616 * 1617 * @param row Index of the entry. 1618 * 1619 * @return The {@code LocalAddeEntry} at the index specified by {@code row}. 1620 */ 1621 protected LocalAddeEntry getEntryAtRow(final int row) { 1622 return entries.get(row); 1623 } 1624 1625 protected int getRowForEntry(final LocalAddeEntry entry) { 1626 return entries.indexOf(entry); 1627 } 1628 1629 protected List<LocalAddeEntry> getSelectedEntries(final int[] rows) { 1630 List<LocalAddeEntry> selected = arrList(rows.length); 1631 int rowCount = entries.size(); 1632 for (int i = 0; i < rows.length; i++) { 1633 int tmpIdx = rows[i]; 1634 if ((tmpIdx >= 0) && (tmpIdx < rowCount)) { 1635 selected.add(entries.get(tmpIdx)); 1636 } else { 1637 throw new IndexOutOfBoundsException(); 1638 } 1639 } 1640 return selected; 1641 } 1642 1643 public void refreshEntries() { 1644 entries.clear(); 1645 entries.addAll(entryStore.getLocalEntries()); 1646 this.fireTableDataChanged(); 1647 } 1648 1649 /** 1650 * Returns the length of {@link #columnNames}. 1651 * 1652 * @return The number of columns. 1653 */ 1654 @Override public int getColumnCount() { 1655 return columnNames.length; 1656 } 1657 1658 /** 1659 * Returns the number of entries being managed. 1660 */ 1661 @Override public int getRowCount() { 1662 return entries.size(); 1663 } 1664 1665 /** 1666 * Finds the value at the given coordinates. 1667 * 1668 * @param row Table row. 1669 * @param column Table column. 1670 * 1671 * @return Value stored at the given {@code row} and {@code column} 1672 * coordinates 1673 * 1674 * @throws IndexOutOfBoundsException if {@code row} or {@code column} 1675 * refer to an invalid table cell. 1676 */ 1677 @Override public Object getValueAt(int row, int column) { 1678 LocalAddeEntry entry = entries.get(row); 1679 if (entry == null) { 1680 throw new IndexOutOfBoundsException(); // still questionable... 1681 } 1682 1683 switch (column) { 1684 case 0: return entry.getGroup(); 1685 case 1: return entry.getName(); 1686 case 2: return entry.getFormat(); 1687 case 3: return entry.getMask(); 1688 default: throw new IndexOutOfBoundsException(); 1689 } 1690 } 1691 1692 /** 1693 * Returns the column name associated with {@code column}. 1694 * 1695 * @return One of {@link #columnNames}. 1696 */ 1697 @Override public String getColumnName(final int column) { 1698 return columnNames[column]; 1699 } 1700 } 1701 1702 // i need the following icons: 1703 // something to convey entry validity: invalid, verified, unverified 1704 // a "system" entry icon (thinking of something with prominent "V") 1705 // a "mctable" entry icon (similar to above, but with a prominent "X") 1706 // a "user" entry icon (no idea yet!) 1707 public class EntrySourceRenderer extends DefaultTableCellRenderer { 1708 1709 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 1710 Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 1711 EntrySource source = EntrySource.valueOf((String)value); 1712 EntrySourceRenderer renderer = (EntrySourceRenderer)comp; 1713 Icon icon = null; 1714 String tooltip = null; 1715 switch (source) { 1716 case SYSTEM: 1717 icon = system; 1718 tooltip = "Default dataset and cannot be removed, only disabled."; 1719 break; 1720 case MCTABLE: 1721 icon = mctable; 1722 tooltip = "Dataset imported from a MCTABLE.TXT."; 1723 break; 1724 case USER: 1725 icon = user; 1726 tooltip = "Dataset created or altered by you!"; 1727 break; 1728 } 1729 renderer.setIcon(icon); 1730 renderer.setToolTipText(tooltip); 1731 renderer.setText(null); 1732 return comp; 1733 } 1734 } 1735 1736 public class EntryValidityRenderer extends DefaultTableCellRenderer { 1737 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 1738 Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 1739 EntryValidity validity = EntryValidity.valueOf((String)value); 1740 EntryValidityRenderer renderer = (EntryValidityRenderer)comp; 1741 Icon icon = null; 1742 String msg = null; 1743 String tooltip = null; 1744 switch (validity) { 1745 case INVALID: 1746 icon = invalid; 1747 tooltip = "Dataset verification failed."; 1748 break; 1749 case VERIFIED: 1750 break; 1751 case UNVERIFIED: 1752 icon = unverified; 1753 tooltip = "Dataset has not been verified."; 1754 break; 1755 case VALIDATING: 1756 msg = "Checking..."; 1757 break; 1758 } 1759 renderer.setIcon(icon); 1760 renderer.setToolTipText(tooltip); 1761 renderer.setText(msg); 1762 return comp; 1763 } 1764 } 1765 1766 public static class TextRenderer extends DefaultTableCellRenderer { 1767 1768 /** */ 1769 private Font bold; 1770 1771 /** */ 1772 private Font boldItalic; 1773 1774 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 1775 Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 1776 Font currentFont = comp.getFont(); 1777 if (bold == null) { 1778 bold = currentFont.deriveFont(Font.BOLD); 1779 } 1780 if (boldItalic == null) { 1781 boldItalic = currentFont.deriveFont(Font.BOLD | Font.ITALIC); 1782 } 1783 if (column == 2) { 1784 comp.setFont(bold); 1785 } else if (column == 3) { 1786 // why can't i set the color for just a single column!? 1787 } else if (column == 4) { 1788 comp.setFont(boldItalic); 1789 } 1790 return comp; 1791 } 1792 } 1793 1794 /** 1795 * 1796 * 1797 * @param path 1798 * 1799 * @return 1800 */ 1801 private static Icon icon(final String path) { 1802 return GuiUtils.getImageIcon(path, TabbedAddeManager.class, true); 1803 } 1804 1805 /** 1806 * Launch the application. Makes for a simplistic test. 1807 * 1808 * @param args Command line arguments. These are currently ignored. 1809 */ 1810 public static void main(String[] args) { 1811 SwingUtilities.invokeLater(new Runnable() { 1812 public void run() { 1813 try { 1814 TabbedAddeManager frame = new TabbedAddeManager(); 1815 frame.setVisible(true); 1816 } catch (Exception e) { 1817 e.printStackTrace(); 1818 } 1819 } 1820 }); 1821 } 1822 1823 private JPanel contentPane; 1824 private JTable remoteTable; 1825 private JTable localTable; 1826 private JTabbedPane tabbedPane; 1827 private JLabel statusLabel; 1828 private JButton newRemoteButton; 1829 private JButton editRemoteButton; 1830 private JButton removeRemoteButton; 1831 private JButton importRemoteButton; 1832 private JButton newLocalButton; 1833 private JButton editLocalButton; 1834 private JButton removeLocalButton; 1835 // private JButton applyButton; 1836 private JButton okButton; 1837 // private JButton cancelButton; 1838 private JMenuItem editMenuItem; 1839 private JMenuItem removeMenuItem; 1840 private JCheckBox importAccountBox; 1841 1842 /** Icon for datasets that are part of a default McIDAS-V install. */ 1843 private Icon system; 1844 1845 /** Icon for datasets that originate from a MCTABLE.TXT. */ 1846 private Icon mctable; 1847 1848 /** Icon for datasets that the user has provided. */ 1849 private Icon user; 1850 1851 /** Icon for invalid datasets. */ 1852 private Icon invalid; 1853 1854 /** Icon for datasets that have not been verified. */ 1855 private Icon unverified; 1856 }