001/* 002 * $Id: TabbedAddeManager.java,v 1.42 2011/03/24 16:06:34 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2011 007 * Space Science and Engineering Center (SSEC) 008 * University of Wisconsin - Madison 009 * 1225 W. Dayton Street, Madison, WI 53706, USA 010 * https://www.ssec.wisc.edu/mcidas 011 * 012 * All Rights Reserved 013 * 014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 015 * some McIDAS-V source code is based on IDV and VisAD source code. 016 * 017 * McIDAS-V is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser Public License as published by 019 * the Free Software Foundation; either version 3 of the License, or 020 * (at your option) any later version. 021 * 022 * McIDAS-V is distributed in the hope that it will be useful, 023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 025 * GNU Lesser Public License for more details. 026 * 027 * You should have received a copy of the GNU Lesser Public License 028 * along with this program. If not, see http://www.gnu.org/licenses. 029 */ 030package edu.wisc.ssec.mcidasv.servermanager; 031 032import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 033import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashSet; 034import static edu.wisc.ssec.mcidasv.util.Contract.notNull; 035import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.runOnEDT; 036 037import java.awt.Component; 038import java.awt.Font; 039import java.awt.event.WindowAdapter; 040import java.awt.event.WindowEvent; 041import java.io.File; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.EnumSet; 045import java.util.List; 046import java.util.Set; 047import java.util.concurrent.Callable; 048import java.util.concurrent.CompletionService; 049import java.util.concurrent.ExecutionException; 050import java.util.concurrent.ExecutorCompletionService; 051import java.util.concurrent.ExecutorService; 052import java.util.concurrent.Executors; 053 054import javax.swing.BoxLayout; 055import javax.swing.GroupLayout; 056import javax.swing.Icon; 057import javax.swing.JButton; 058import javax.swing.JCheckBox; 059import javax.swing.JFileChooser; 060import javax.swing.JFrame; 061import javax.swing.JLabel; 062import javax.swing.JMenu; 063import javax.swing.JMenuBar; 064import javax.swing.JMenuItem; 065import javax.swing.JPanel; 066import javax.swing.JPopupMenu; 067import javax.swing.JScrollPane; 068import javax.swing.JTabbedPane; 069import javax.swing.JTable; 070import javax.swing.JTextField; 071import javax.swing.LayoutStyle; 072import javax.swing.ListSelectionModel; 073import javax.swing.SwingUtilities; 074import javax.swing.UIManager; 075import javax.swing.WindowConstants; 076import javax.swing.event.ChangeEvent; 077import javax.swing.event.ChangeListener; 078import javax.swing.event.ListSelectionEvent; 079import javax.swing.event.ListSelectionListener; 080import javax.swing.table.AbstractTableModel; 081import javax.swing.table.DefaultTableCellRenderer; 082 083import org.bushe.swing.event.EventBus; 084import org.bushe.swing.event.annotation.EventSubscriber; 085 086import org.slf4j.Logger; 087import org.slf4j.LoggerFactory; 088 089import ucar.unidata.idv.IdvObjectStore; 090import ucar.unidata.util.GuiUtils; 091import ucar.unidata.util.LogUtil; 092 093import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource; 094import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 095import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity; 096import edu.wisc.ssec.mcidasv.servermanager.AddeThread.McservEvent; 097import edu.wisc.ssec.mcidasv.servermanager.RemoteEntryEditor.AddeStatus; 098import edu.wisc.ssec.mcidasv.ui.BetterJTable; 099import edu.wisc.ssec.mcidasv.util.McVTextField.Prompt; 100 101/** 102 * This class is the GUI frontend to {@link EntryStore} (the server manager). 103 * It allows users to manipulate their local and remote ADDE data. 104 */ 105// TODO(jon): don't forget to persist tab choice and window position. maybe also the "positions" of the scrollpanes (if possible). 106// TODO(jon): GUI could look much better. 107// TODO(jon): finish up the javadocs. 108@SuppressWarnings("serial") 109public class TabbedAddeManager extends JFrame { 110 111 /** Pretty typical logger object. */ 112 private final static Logger logger = LoggerFactory.getLogger(TabbedAddeManager.class); 113 114 private static final Icon system = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/padlock_closed.png"); 115 private static final Icon mctable = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/bug.png"); 116 private static final Icon user = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/hand_pro.png"); 117 private static final Icon invalid = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/emotion_sad.png"); 118// private static final Icon verified = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/emotion_smile.png"); 119 private static final Icon unverified = icon("/edu/wisc/ssec/mcidasv/resources/icons/servermanager/eye_inv.png"); 120 121 /** Path to the help resources. */ 122 private static final String HELP_TOP_DIR = "/docs/userguide"; 123 124 /** Help target for the remote servers. */ 125 private static final String REMOTE_HELP_TARGET = "idv.tools.remotedata"; 126 127 /** Help target for the local servers. */ 128 private static final String LOCAL_HELP_TARGET = "idv.tools.localdata"; 129 130 /** ID used to save/restore the last visible tab between sessions. */ 131 private static final String LAST_TAB = "mcv.adde.lasttab"; 132 133 /** ID used to save/restore the last directory that contained a MCTABLE.TXT. */ 134 private static final String LAST_IMPORTED = "mcv.adde.lastmctabledir"; 135 136 /** Size of the ADDE entry verification thread pool. */ 137 private static final int POOL = 2; 138 139 // not the best idea, bub. 140 private static TabbedAddeManager staticTabbedManager; 141 142 /** 143 * These are the various {@literal "events"} that the server manager GUI 144 * supports. These are published via the wonderful {@link EventBus#publish(Object)} method. 145 */ 146 public enum Event { 147 /** The GUI was created. */ 148 OPENED, 149 /** The GUI was hidden/minimized/etc. */ 150 HIDDEN, 151 /** GUI was unhidden or some such thing. */ 152 SHOWN, 153 /** The GUI was closed. */ 154 CLOSED 155 }; 156 157 /** Reference back to the McV god object. */ 158// private final McIDASV mcv; 159 160 /** Reference to the actual server manager. */ 161 private final EntryStore serverManager; 162 163 /** The currently selected {@link RemoteAddeEntry} or {@code null} if nothing is selected. */ 164// private RemoteAddeEntry selectedRemoteEntry = null; 165 166 private final List<RemoteAddeEntry> selectedRemoteEntries; 167 168 /** The currently selected {@link LocalAddeEntry} or {@code null} if nothing is selected. */ 169// private LocalAddeEntry selectedLocalEntry = null; 170 171 private final List<LocalAddeEntry> selectedLocalEntries; 172 173 /** 174 * Creates a standalone server manager GUI. 175 */ 176 public TabbedAddeManager() { 177 this.serverManager = null; 178 this.selectedLocalEntries = arrList(); 179 this.selectedRemoteEntries = arrList(); 180 SwingUtilities.invokeLater(new Runnable() { 181 @Override public void run() { 182 initComponents(); 183 } 184 }); 185 } 186 187 /** 188 * Creates a server manager GUI that's linked back to the rest of McIDAS-V. 189 * 190 * @param entryStore Server manager reference. 191 * 192 * @throws NullPointerException if {@code entryStore} is {@code null}. 193 */ 194 public TabbedAddeManager(final EntryStore entryStore) { 195 notNull(entryStore, "Cannot pass a null server manager"); 196 this.serverManager = entryStore; 197 this.selectedLocalEntries = arrList(); 198 this.selectedRemoteEntries = arrList(); 199 SwingUtilities.invokeLater(new Runnable() { 200 @Override public void run() { 201 initComponents(); 202 } 203 }); 204 } 205 206 protected static TabbedAddeManager getTabbedManager() { 207 return staticTabbedManager; 208 } 209 210 /** 211 * If the GUI isn't shown, this method will display things. If the GUI <i>is 212 * shown</i>, bring it to the front. 213 * 214 * <p>This method publishes {@link Event#SHOWN}. 215 */ 216 public void showManager() { 217 if (!isVisible()) { 218 setVisible(true); 219 } else { 220 toFront(); 221 } 222 staticTabbedManager = this; 223 EventBus.publish(Event.SHOWN); 224 } 225 226 /** 227 * Closes and disposes (if needed) the GUI. 228 */ 229 public void closeManager() { 230 staticTabbedManager = null; 231 EventBus.publish(Event.CLOSED); 232 if (isDisplayable()) { 233 dispose(); 234 } 235 } 236 237 // TODO(jon): still needs to refresh the local table. 238 protected void refreshDisplay() { 239 ((RemoteAddeTableModel)remoteTable.getModel()).refreshEntries(); 240 ((LocalAddeTableModel)localTable.getModel()).refreshEntries(); 241 } 242 243 public void showRemoteEditor() { 244 if (tabbedPane.getSelectedIndex() != 0) { 245 tabbedPane.setSelectedIndex(0); 246 } 247 RemoteEntryEditor editor = new RemoteEntryEditor(this, true, this, serverManager); 248 editor.setVisible(true); 249 } 250 251 public void showRemoteEditor(final List<RemoteAddeEntry> entries) { 252 if (tabbedPane.getSelectedIndex() != 0) { 253 tabbedPane.setSelectedIndex(0); 254 } 255 RemoteEntryEditor editor = new RemoteEntryEditor(this, true, this, serverManager, entries); 256 editor.setVisible(true); 257 } 258 259 public void removeRemoteEntries(final List<RemoteAddeEntry> entries) { 260 if (entries == null) { 261 return; 262 } 263 List<RemoteAddeEntry> removable = arrList(entries.size()); 264 for (RemoteAddeEntry entry : entries) { 265// if (!EntrySource.SYSTEM.equals(entry.getEntrySource())) { 266 if (entry.getEntrySource() != EntrySource.SYSTEM) { 267 removable.add(entry); 268 } 269 } 270 if (serverManager.removeEntries(removable)) { 271 RemoteAddeTableModel tableModel = ((RemoteAddeTableModel)remoteTable.getModel()); 272 int first = Integer.MAX_VALUE; 273 int last = Integer.MIN_VALUE; 274 for (RemoteAddeEntry entry : removable) { 275 int index = tableModel.getRowForEntry(entry); 276 if (index < 0) { 277 continue; 278 } else { 279 if (index < first) { 280 first = index; 281 } 282 if (index > last) { 283 last = index; 284 } 285 } 286 } 287 tableModel.fireTableDataChanged(); 288// tableModel.fireTableRowsDeleted(first, last); 289 refreshDisplay(); 290 remoteTable.revalidate(); 291 if (first < remoteTable.getRowCount()) { 292 remoteTable.setRowSelectionInterval(first, first); 293 } 294 } else { 295 logger.debug("could not remove entries={}", removable); 296 } 297 } 298 299 public void showLocalEditor() { 300 if (tabbedPane.getSelectedIndex() != 1) { 301 tabbedPane.setSelectedIndex(1); 302 } 303 LocalEntryEditor editor = new LocalEntryEditor(this, true, this, serverManager); 304 editor.setVisible(true); 305 } 306 307 public void showLocalEditor(final LocalAddeEntry entry) { 308 if (tabbedPane.getSelectedIndex() != 1) { 309 tabbedPane.setSelectedIndex(1); 310 } 311 LocalEntryEditor editor = new LocalEntryEditor(this, true, this, serverManager, entry); 312 editor.setVisible(true); 313 } 314 315 public void removeLocalEntries(final List<LocalAddeEntry> entries) { 316 if (entries == null) { 317 return; 318 } 319 if (serverManager.removeEntries(entries)) { 320 LocalAddeTableModel tableModel = ((LocalAddeTableModel)localTable.getModel()); 321 int first = Integer.MAX_VALUE; 322 int last = Integer.MIN_VALUE; 323 for (LocalAddeEntry entry : entries) { 324 int index = tableModel.getRowForEntry(entry); 325 if (index < 0) { 326 continue; 327 } else { 328 if (index < first) { 329 first = index; 330 } 331 if (index > last) { 332 last = index; 333 } 334 } 335 } 336 tableModel.fireTableDataChanged(); 337 // tableModel.fireTableRowsDeleted(first, last); 338 refreshDisplay(); 339 localTable.revalidate(); 340 if (first < localTable.getRowCount()) { 341 localTable.setRowSelectionInterval(first, first); 342 } 343 } else { 344 logger.debug("could not remove entries={}", entries); 345 } 346 } 347 348 public void importMctable(final String path, final String username, final String project) { 349 final Set<RemoteAddeEntry> imported = EntryTransforms.extractMctableEntries(path, username, project); 350 if (imported == Collections.EMPTY_SET) { 351 LogUtil.userErrorMessage("Selection does not appear to a valid MCTABLE.TXT file:\n"+path); 352 } else { 353 // verify entries first! 354 serverManager.addEntries(imported); 355 refreshDisplay(); 356 repaint(); 357 Runnable r = new Runnable() { 358 public void run() { 359 checkDatasets(imported); 360 } 361 }; 362 Thread t = new Thread(r); 363 t.start(); 364 } 365 } 366 367 public void restartLocalServer() { 368 serverManager.restartLocalServer(); 369 } 370 371 @EventSubscriber(eventClass=McservEvent.class) 372 public void mcservUpdated(final McservEvent event) { 373 final String msg; 374 switch (event) { 375 case ACTIVE: 376 msg = "Local servers are already running."; 377 break; 378 case DIED: 379 msg = "Local servers quit unexpectedly..."; 380 break; 381 case STARTED: 382 msg = "Local servers are listening on port "+EntryStore.getLocalPort(); 383 break; 384 case STOPPED: 385 msg = "Local servers have been stopped."; 386 break; 387 default: 388 msg = "Unknown local servers status: "+event.toString(); 389 break; 390 } 391 SwingUtilities.invokeLater(new Runnable() { 392 public void run() { 393 statusLabel.setText(msg); 394 } 395 }); 396 } 397 398 @SuppressWarnings("unchecked") 399 private void initComponents() { 400 assert SwingUtilities.isEventDispatchThread(); 401 ucar.unidata.ui.Help.setTopDir(HELP_TOP_DIR); 402 403 tabbedPane = new JTabbedPane(); 404 remoteTab = new JPanel(); 405 remoteTable = new BetterJTable(); 406 remoteScroller = BetterJTable.createStripedJScrollPane(remoteTable); 407 408 actionPanel = new JPanel(); 409 newEntryButton = new JButton(); 410 editEntryButton = new JButton(); 411 removeEntryButton = new JButton(); 412 importButton = new JButton(); 413 localTab = new JPanel(); 414 localTable = new BetterJTable(); 415 localScroller = BetterJTable.createStripedJScrollPane(localTable); 416 statusPanel = new JPanel(); 417 statusLabel = new JLabel(); 418 restartButton = new JButton(); 419 menuBar = new JMenuBar(); 420 fileMenu = new JMenu(); 421 newRemoteItem = new JMenuItem(); 422 newLocalItem = new JMenuItem(); 423 fileSeparator1 = new JPopupMenu.Separator(); 424 closeItem = new JMenuItem(); 425 editMenu = new JMenu(); 426 editEntryItem = new JMenuItem(); 427 removeEntryItem = new JMenuItem(); 428 helpMenu = new JMenu(); 429 remoteHelpItem = new JMenuItem(); 430 localHelpItem = new JMenuItem(); 431 432 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 433 setTitle("ADDE Data Manager"); 434 addWindowListener(new WindowAdapter() { 435 public void windowClosed(WindowEvent evt) { 436 formWindowClosed(evt); 437 } 438 }); 439 440 remoteTable.setModel(new RemoteAddeTableModel(serverManager)); 441 remoteTable.setColumnSelectionAllowed(false); 442 remoteTable.setRowSelectionAllowed(true); 443 remoteTable.getTableHeader().setReorderingAllowed(false); 444 remoteTable.setFont(UIManager.getFont("Table.font").deriveFont(11.0f)); 445 remoteTable.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 446 remoteTable.setDefaultRenderer(String.class, new TextRenderer()); 447 remoteTable.getColumnModel().getColumn(0).setPreferredWidth(10); 448 remoteTable.getColumnModel().getColumn(1).setPreferredWidth(10); 449// remoteTable.getColumnModel().getColumn(2).setPreferredWidth(10); 450 remoteTable.getColumnModel().getColumn(3).setPreferredWidth(50); 451 remoteTable.getColumnModel().getColumn(4).setPreferredWidth(50); 452 remoteTable.getColumnModel().getColumn(0).setCellRenderer(new EntryValidityRenderer()); 453 remoteTable.getColumnModel().getColumn(1).setCellRenderer(new EntrySourceRenderer()); 454 remoteTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { 455 public void valueChanged(final ListSelectionEvent e) { 456 remoteSelectionModelChanged(e); 457 } 458 }); 459 remoteTable.addMouseListener(new java.awt.event.MouseAdapter() { 460 public void mouseClicked(final java.awt.event.MouseEvent e) { 461 if ((e.getClickCount() == 2) && (hasSingleRemoteSelection())) { 462 showRemoteEditor(getSelectedRemoteEntries()); 463 } 464 } 465 }); 466 467 newEntryButton.setText("Add New Dataset"); 468 newEntryButton.addActionListener(new java.awt.event.ActionListener() { 469 public void actionPerformed(java.awt.event.ActionEvent evt) { 470 showRemoteEditor(); 471 } 472 }); 473 474 editEntryButton.setText("Edit Dataset"); 475 editEntryButton.setEnabled(false); 476 editEntryButton.addActionListener(new java.awt.event.ActionListener() { 477 public void actionPerformed(java.awt.event.ActionEvent evt) { 478 if (tabbedPane.getSelectedIndex() == 0) { 479 showRemoteEditor(getSelectedRemoteEntries()); 480 } else { 481 showLocalEditor(getSingleLocalSelection()); 482 } 483 } 484 }); 485 486 removeEntryButton.setText("Remove Selection"); 487 removeEntryButton.setEnabled(false); 488 removeEntryButton.addActionListener(new java.awt.event.ActionListener() { 489 public void actionPerformed(java.awt.event.ActionEvent evt) { 490 if (tabbedPane.getSelectedIndex() == 0) { 491 removeRemoteEntries(getSelectedRemoteEntries()); 492 } else { 493 removeLocalEntries(getSelectedLocalEntries()); 494 } 495 } 496 }); 497 498 importButton.setText("Import MCTABLE..."); 499 importButton.addActionListener(new java.awt.event.ActionListener() { 500 public void actionPerformed(java.awt.event.ActionEvent evt) { 501 importButtonActionPerformed(evt); 502 } 503 }); 504 505 GroupLayout actionPanelLayout = new GroupLayout(actionPanel); 506 actionPanel.setLayout(actionPanelLayout); 507 actionPanelLayout.setHorizontalGroup( 508 actionPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) 509 .addGroup(actionPanelLayout.createSequentialGroup() 510 .addContainerGap() 511 .addComponent(newEntryButton) 512 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 513 .addComponent(editEntryButton) 514 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 515 .addComponent(removeEntryButton) 516 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 517 .addComponent(importButton) 518 .addContainerGap(77, Short.MAX_VALUE)) 519 ); 520 actionPanelLayout.setVerticalGroup( 521 actionPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) 522 .addGroup(actionPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) 523 .addComponent(newEntryButton) 524 .addComponent(editEntryButton) 525 .addComponent(removeEntryButton) 526 .addComponent(importButton)) 527 ); 528 529 GroupLayout remoteTabLayout = new GroupLayout(remoteTab); 530 remoteTab.setLayout(remoteTabLayout); 531 remoteTabLayout.setHorizontalGroup( 532 remoteTabLayout.createParallelGroup(GroupLayout.Alignment.LEADING) 533 .addGroup(GroupLayout.Alignment.TRAILING, remoteTabLayout.createSequentialGroup() 534 .addContainerGap() 535 .addGroup(remoteTabLayout.createParallelGroup(GroupLayout.Alignment.TRAILING) 536 .addComponent(remoteScroller, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 533, Short.MAX_VALUE) 537 .addComponent(actionPanel, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) 538 .addContainerGap()) 539 ); 540 remoteTabLayout.setVerticalGroup( 541 remoteTabLayout.createParallelGroup(GroupLayout.Alignment.LEADING) 542 .addGroup(remoteTabLayout.createSequentialGroup() 543 .addContainerGap() 544 .addComponent(remoteScroller, GroupLayout.PREFERRED_SIZE, 291, GroupLayout.PREFERRED_SIZE) 545 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 546 .addComponent(actionPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) 547 .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) 548 ); 549 550 tabbedPane.addTab("Remote Data", remoteTab); 551 552 localTable.setModel(new LocalAddeTableModel(serverManager)); 553 localTable.setColumnSelectionAllowed(false); 554 localTable.setRowSelectionAllowed(true); 555 localTable.getTableHeader().setReorderingAllowed(false); 556 localTable.setFont(UIManager.getFont("Table.font").deriveFont(11.0f)); 557 localTable.setDefaultRenderer(String.class, new TextRenderer()); 558 localScroller.setViewportView(localTable); 559 localTable.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 560 localTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { 561 public void valueChanged(final ListSelectionEvent e) { 562 localSelectionModelChanged(e); 563 } 564 }); 565 localTable.addMouseListener(new java.awt.event.MouseAdapter() { 566 public void mouseClicked(final java.awt.event.MouseEvent e) { 567 if ((e.getClickCount() == 2) && (hasSingleLocalSelection())) { 568 showLocalEditor(getSingleLocalSelection()); 569 } 570 } 571 }); 572 573 if (!serverManager.checkLocalServer()) { 574 statusLabel.setText("Local server is not running."); 575 restartButton.setText("Start Me!"); 576 } 577 else { 578 statusLabel.setText("Local server is running."); 579 restartButton.setText("Restart Me!"); 580 } 581 restartButton.addActionListener(new java.awt.event.ActionListener() { 582 public void actionPerformed(java.awt.event.ActionEvent evt) { 583 restartLocalServer(); 584 } 585 }); 586 587 GroupLayout statusPanelLayout = new GroupLayout(statusPanel); 588 statusPanel.setLayout(statusPanelLayout); 589 statusPanelLayout.setHorizontalGroup( 590 statusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) 591 .addGroup(GroupLayout.Alignment.TRAILING, statusPanelLayout.createSequentialGroup() 592 .addComponent(statusLabel) 593 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, 314, Short.MAX_VALUE) 594 .addComponent(restartButton)) 595 ); 596 statusPanelLayout.setVerticalGroup( 597 statusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) 598 .addGroup(statusPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) 599 .addComponent(restartButton) 600 .addComponent(statusLabel)) 601 ); 602 603 GroupLayout localTabLayout = new GroupLayout(localTab); 604 localTab.setLayout(localTabLayout); 605 localTabLayout.setHorizontalGroup( 606 localTabLayout.createParallelGroup(GroupLayout.Alignment.LEADING) 607 .addGroup(GroupLayout.Alignment.TRAILING, localTabLayout.createSequentialGroup() 608 .addContainerGap() 609 .addGroup(localTabLayout.createParallelGroup(GroupLayout.Alignment.TRAILING) 610 .addComponent(localScroller, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 533, Short.MAX_VALUE) 611 .addComponent(statusPanel, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) 612 .addContainerGap()) 613 ); 614 localTabLayout.setVerticalGroup( 615 localTabLayout.createParallelGroup(GroupLayout.Alignment.LEADING) 616 .addGroup(localTabLayout.createSequentialGroup() 617 .addContainerGap() 618 .addComponent(localScroller, GroupLayout.PREFERRED_SIZE, 289, GroupLayout.PREFERRED_SIZE) 619 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 620 .addComponent(statusPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) 621 .addContainerGap(13, Short.MAX_VALUE)) 622 ); 623 624 tabbedPane.addTab("Local Data", localTab); 625 626 fileMenu.setText("File"); 627 628 newRemoteItem.setText("New Remote Dataset"); 629 newRemoteItem.addActionListener(new java.awt.event.ActionListener() { 630 public void actionPerformed(java.awt.event.ActionEvent evt) { 631 showRemoteEditor(); 632 } 633 }); 634 fileMenu.add(newRemoteItem); 635 636 newLocalItem.setText("New Local Dataset"); 637 newLocalItem.addActionListener(new java.awt.event.ActionListener() { 638 public void actionPerformed(java.awt.event.ActionEvent evt) { 639 showLocalEditor(); 640 } 641 }); 642 fileMenu.add(newLocalItem); 643 fileMenu.add(fileSeparator1); 644 645 closeItem.setText("Close"); 646 closeItem.addActionListener(new java.awt.event.ActionListener() { 647 public void actionPerformed(java.awt.event.ActionEvent evt) { 648 logger.debug("evt={}", evt.toString()); 649 closeManager(); 650 } 651 }); 652 fileMenu.add(closeItem); 653 654 menuBar.add(fileMenu); 655 656 editMenu.setText("Edit"); 657 editEntryItem.setText("Edit Entry..."); 658 editEntryItem.setEnabled(false); 659 editEntryItem.addActionListener(new java.awt.event.ActionListener() { 660 public void actionPerformed(java.awt.event.ActionEvent evt) { 661 if (tabbedPane.getSelectedIndex() == 0) { 662 showRemoteEditor(getSelectedRemoteEntries()); 663 } else { 664 showLocalEditor(getSingleLocalSelection()); 665 } 666 } 667 }); 668 669 removeEntryItem.setText("Remove Selection"); 670 removeEntryItem.setEnabled(false); 671 removeEntryItem.addActionListener(new java.awt.event.ActionListener() { 672 public void actionPerformed(java.awt.event.ActionEvent evt) { 673 if (tabbedPane.getSelectedIndex() == 0) { 674 removeRemoteEntries(getSelectedRemoteEntries()); 675 } else { 676 removeLocalEntries(getSelectedLocalEntries()); 677 } 678 } 679 }); 680 editMenu.add(editEntryItem); 681 editMenu.add(removeEntryItem); 682 menuBar.add(editMenu); 683 684 helpMenu.setText("Help"); 685 686 remoteHelpItem.setText("Show Remote Data Help"); 687 remoteHelpItem.addActionListener(new java.awt.event.ActionListener() { 688 public void actionPerformed(java.awt.event.ActionEvent evt) { 689 ucar.unidata.ui.Help.getDefaultHelp().gotoTarget(REMOTE_HELP_TARGET); 690 } 691 }); 692 helpMenu.add(remoteHelpItem); 693 694 localHelpItem.setText("Show Local Data Help"); 695 localHelpItem.addActionListener(new java.awt.event.ActionListener() { 696 public void actionPerformed(java.awt.event.ActionEvent evt) { 697 ucar.unidata.ui.Help.getDefaultHelp().gotoTarget(LOCAL_HELP_TARGET); 698 } 699 }); 700 helpMenu.add(localHelpItem); 701 702 menuBar.add(helpMenu); 703 704 setJMenuBar(menuBar); 705 706 GroupLayout layout = new GroupLayout(getContentPane()); 707 getContentPane().setLayout(layout); 708 layout.setHorizontalGroup( 709 layout.createParallelGroup(GroupLayout.Alignment.LEADING) 710 .addComponent(tabbedPane, GroupLayout.DEFAULT_SIZE, 558, Short.MAX_VALUE) 711 ); 712 layout.setVerticalGroup( 713 layout.createParallelGroup(GroupLayout.Alignment.LEADING) 714 .addGroup(layout.createSequentialGroup() 715 .addContainerGap() 716 .addComponent(tabbedPane, GroupLayout.DEFAULT_SIZE, 370, Short.MAX_VALUE)) 717 ); 718 719 tabbedPane.setSelectedIndex(getLastTab()); 720 tabbedPane.getAccessibleContext().setAccessibleName("Remote Data"); 721 tabbedPane.addChangeListener(new ChangeListener() { 722 public void stateChanged(ChangeEvent evt) { 723 boolean hasSelection = false; 724 int index = tabbedPane.getSelectedIndex(); 725 if (index == 0) { 726 hasSelection = hasRemoteSelection(); 727 } else { 728 hasSelection = hasLocalSelection(); 729 } 730 731 editEntryButton.setEnabled(hasSelection); 732 editEntryItem.setEnabled(hasSelection); 733 removeEntryButton.setEnabled(hasSelection); 734 removeEntryItem.setEnabled(hasSelection); 735 setLastTab(index); 736 } 737 }); 738 pack(); 739 }// </editor-fold> 740 741 /** 742 * I respond to events! Yyyyaaaaaaayyyyyy!!!! 743 * 744 * @param e 745 */ 746 private void remoteSelectionModelChanged(final ListSelectionEvent e) { 747 if (e.getValueIsAdjusting()) { 748 return; 749 } 750 751 int selectedRowCount = 0; 752 ListSelectionModel selModel = (ListSelectionModel)e.getSource(); 753 Set<RemoteAddeEntry> selectedEntries; 754 if (selModel.isSelectionEmpty()) { 755 selectedEntries = Collections.emptySet(); 756 } else { 757 int min = selModel.getMinSelectionIndex(); 758 int max = selModel.getMaxSelectionIndex(); 759 RemoteAddeTableModel tableModel = ((RemoteAddeTableModel)remoteTable.getModel()); 760 selectedEntries = newLinkedHashSet(); 761 for (int i = min; i <= max; i++) { 762 if (selModel.isSelectedIndex(i)) { 763 List<RemoteAddeEntry> entries = tableModel.getEntriesAtRow(i); 764 selectedEntries.addAll(entries); 765 selectedRowCount++; 766 } 767 } 768 } 769 770 boolean onlyDefaultEntries = true; 771 for (RemoteAddeEntry entry : selectedEntries) { 772 if (entry.getEntrySource() != EntrySource.SYSTEM) { 773 onlyDefaultEntries = false; 774 break; 775 } 776 } 777 setSelectedRemoteEntries(selectedEntries); 778 779 // the current "edit" dialog doesn't work so well with multiple 780 // servers/datasets, so only allow the user to edit entries one at a time. 781 boolean singleSelection = selectedRowCount == 1; 782 editEntryButton.setEnabled(singleSelection); 783 editEntryItem.setEnabled(singleSelection); 784 785 boolean hasSelection = (selectedRowCount >= 1) && (!onlyDefaultEntries); 786 removeEntryButton.setEnabled(hasSelection); 787 removeEntryItem.setEnabled(hasSelection); 788 } 789 790 private void localSelectionModelChanged(final ListSelectionEvent e) { 791 if (e.getValueIsAdjusting()) { 792 return; 793 } 794 ListSelectionModel selModel = (ListSelectionModel)e.getSource(); 795 Set<LocalAddeEntry> selectedEntries; 796 if (selModel.isSelectionEmpty()) { 797 selectedEntries = Collections.emptySet(); 798 } else { 799 int min = selModel.getMinSelectionIndex(); 800 int max = selModel.getMaxSelectionIndex(); 801 LocalAddeTableModel tableModel = ((LocalAddeTableModel)localTable.getModel()); 802 selectedEntries = newLinkedHashSet(); 803 for (int i = min; i <= max; i++) { 804 if (selModel.isSelectedIndex(i)) { 805 selectedEntries.add(tableModel.getEntryAtRow(i)); 806 } 807 } 808 } 809 810 setSelectedLocalEntries(selectedEntries); 811 812 // the current "edit" dialog doesn't work so well with multiple 813 // servers/datasets, so only allow the user to edit entries one at a time. 814 boolean singleSelection = (selectedEntries.size() == 1); 815 editEntryButton.setEnabled(singleSelection); 816 editEntryItem.setEnabled(singleSelection); 817 818 boolean hasSelection = !selectedEntries.isEmpty(); 819 removeEntryButton.setEnabled(hasSelection); 820 removeEntryItem.setEnabled(hasSelection); 821 } 822 823 /** 824 * Checks to see if {@link #selectedRemoteEntries} contains any 825 * {@link RemoteAddeEntry}s. 826 */ 827 private boolean hasRemoteSelection() { 828 return !selectedRemoteEntries.isEmpty(); 829 } 830 831 /** 832 * Checks to see if {@link {@link #selectedLocalEntries} contains any 833 * {@link LocalAddeEntry}s. 834 */ 835 private boolean hasLocalSelection() { 836 return !selectedLocalEntries.isEmpty(); 837 } 838 839 private boolean hasSingleRemoteSelection() { 840 String entryText = null; 841 for (RemoteAddeEntry entry : selectedRemoteEntries) { 842 if (entryText == null) { 843 entryText = entry.getEntryText(); 844 } 845 if (!entry.getEntryText().equals(entryText)) { 846 return false; 847 } 848 } 849 return true; 850 } 851 852 private boolean hasSingleLocalSelection() { 853 return (selectedLocalEntries.size() == 1); 854 } 855 856 private LocalAddeEntry getSingleLocalSelection() { 857 LocalAddeEntry entry = LocalAddeEntry.INVALID_ENTRY; 858 if (selectedLocalEntries.size() == 1) { 859 entry = selectedLocalEntries.get(0); 860 } 861 return entry; 862 } 863 864 private void setSelectedRemoteEntries(final Collection<RemoteAddeEntry> entries) { 865 selectedRemoteEntries.clear(); 866 selectedRemoteEntries.addAll(entries); 867 logger.trace("remote entries={}", entries); 868 } 869 870 private List<RemoteAddeEntry> getSelectedRemoteEntries() { 871 if (selectedRemoteEntries.isEmpty()) { 872 return Collections.emptyList(); 873 } else { 874 return arrList(selectedRemoteEntries); 875 } 876 } 877 878 private void setSelectedLocalEntries(final Collection<LocalAddeEntry> entries) { 879 selectedLocalEntries.clear(); 880 selectedLocalEntries.addAll(entries); 881 logger.trace("local entries={}", entries); 882 } 883 884 private List<LocalAddeEntry> getSelectedLocalEntries() { 885 if (selectedLocalEntries.isEmpty()) { 886 return Collections.emptyList(); 887 } else { 888 return arrList(selectedLocalEntries); 889 } 890 } 891 892 private void formWindowClosed(java.awt.event.WindowEvent evt) { 893 logger.debug("evt={}", evt.toString()); 894 closeManager(); 895 } 896 897 private JPanel makeFileChooserAccessory() { 898 assert SwingUtilities.isEventDispatchThread(); 899 JPanel accessory = new JPanel(); 900 accessory.setLayout(new BoxLayout(accessory, BoxLayout.PAGE_AXIS)); 901 importAccountBox = new JCheckBox("Use ADDE Accounting?"); 902 importAccountBox.setSelected(false); 903 importAccountBox.addActionListener(new java.awt.event.ActionListener() { 904 public void actionPerformed(java.awt.event.ActionEvent evt) { 905 boolean selected = importAccountBox.isSelected(); 906 importUser.setEnabled(selected); 907 importProject.setEnabled(selected); 908 } 909 }); 910 String clientProp = "JComponent.sizeVariant"; 911 String propVal = "mini"; 912 913 importUser = new JTextField(); 914 importUser.putClientProperty(clientProp, propVal); 915 Prompt userPrompt = new Prompt(importUser, "Username"); 916 userPrompt.putClientProperty(clientProp, propVal); 917 importUser.setEnabled(importAccountBox.isSelected()); 918 919 importProject = new JTextField(); 920 Prompt projPrompt = new Prompt(importProject, "Project Number"); 921 projPrompt.putClientProperty(clientProp, propVal); 922 importProject.putClientProperty(clientProp, propVal); 923 importProject.setEnabled(importAccountBox.isSelected()); 924 925 GroupLayout layout = new GroupLayout(accessory); 926 accessory.setLayout(layout); 927 layout.setHorizontalGroup( 928 layout.createParallelGroup(GroupLayout.Alignment.LEADING) 929 .addComponent(importAccountBox) 930 .addGroup(layout.createSequentialGroup() 931 .addContainerGap() 932 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING, false) 933 .addComponent(importProject, GroupLayout.Alignment.LEADING) 934 .addComponent(importUser, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 131, Short.MAX_VALUE))) 935 ); 936 layout.setVerticalGroup( 937 layout.createParallelGroup(GroupLayout.Alignment.LEADING) 938 .addGroup(layout.createSequentialGroup() 939 .addComponent(importAccountBox) 940 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 941 .addComponent(importUser, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) 942 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 943 .addComponent(importProject, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) 944 .addContainerGap(55, Short.MAX_VALUE)) 945 ); 946 return accessory; 947 } 948 949 private void importButtonActionPerformed(java.awt.event.ActionEvent evt) { 950 assert SwingUtilities.isEventDispatchThread(); 951 JFileChooser fc = new JFileChooser(getLastImportPath()); 952 fc.setAccessory(makeFileChooserAccessory()); 953 fc.setFileSelectionMode(JFileChooser.FILES_ONLY); 954 int ret = fc.showOpenDialog(TabbedAddeManager.this); 955 if (ret == JFileChooser.APPROVE_OPTION) { 956 File f = fc.getSelectedFile(); 957 String path = f.getPath(); 958 959 boolean defaultUser = false; 960 String forceUser = importUser.getText(); 961 if (forceUser.length() == 0) { 962 forceUser = AddeEntry.DEFAULT_ACCOUNT.getUsername(); 963 defaultUser = true; 964 } 965 966 boolean defaultProj = false; 967 String forceProj = importProject.getText(); 968 if (forceProj.length() == 0) { 969 forceProj = AddeEntry.DEFAULT_ACCOUNT.getProject(); 970 defaultProj = true; 971 } 972 973 974 if ((importAccountBox.isSelected()) && (defaultUser || defaultProj)) { 975 logger.warn("bad acct dialog: forceUser={} forceProj={}", forceUser, forceProj); 976 } else { 977 logger.warn("acct appears valid: forceUser={} forceProj={}", forceUser, forceProj); 978 importMctable(path, forceUser, forceProj); 979 // don't worry about file validity; i'll just assume the user clicked 980 // on the wrong entry by accident. 981 setLastImportPath(f.getParent()); 982 } 983 } 984 } 985 986 /** 987 * Returns the directory that contained the most recently imported MCTABLE.TXT. 988 */ 989 private String getLastImportPath() { 990 return serverManager.getIdvStore().get(LAST_IMPORTED, ""); 991 } 992 993 /** 994 * Saves the directory that contained the most recently imported MCTABLE.TXT. 995 */ 996 private void setLastImportPath(final String path) { 997 String okayPath = (path == null) ? "" : path; 998 serverManager.getIdvStore().put(LAST_IMPORTED, okayPath); 999 } 1000 1001 /** 1002 * Returns the index of the user's last server manager tab. 1003 */ 1004 private int getLastTab() { 1005 return serverManager.getIdvStore().get(LAST_TAB, 0); 1006 } 1007 1008 /** 1009 * Saves the index of the last server manager tab the user was looking at. 1010 */ 1011 private void setLastTab(final int index) { 1012 int okayIndex = ((index >= 0) && (index < 2)) ? index : 0; 1013 IdvObjectStore store = serverManager.getIdvStore(); 1014 store.put(LAST_TAB, okayIndex); 1015 } 1016 1017 // stupid adde.ucar.edu entries never seem to time out! great! making the gui hang is just so awesome! 1018 public Set<RemoteAddeEntry> checkDatasets(final Collection<RemoteAddeEntry> entries) { 1019 notNull(entries, "can't check a null collection of entries"); 1020 if (entries.isEmpty()) { 1021 return Collections.emptySet(); 1022 } 1023 1024 Set<RemoteAddeEntry> valid = newLinkedHashSet(); 1025 ExecutorService exec = Executors.newFixedThreadPool(POOL); 1026 CompletionService<List<RemoteAddeEntry>> ecs = new ExecutorCompletionService<List<RemoteAddeEntry>>(exec); 1027 final RemoteAddeTableModel tableModel = (RemoteAddeTableModel)remoteTable.getModel(); 1028 1029 // place entries 1030 for (RemoteAddeEntry entry : entries) { 1031 ecs.submit(new BetterCheckTask(entry)); 1032 logger.trace("submitting entry={}", entry); 1033 final int row = tableModel.getRowForEntry(entry); 1034 runOnEDT(new Runnable() { 1035 public void run() { 1036 tableModel.fireTableRowsUpdated(row, row); 1037 } 1038 }); 1039 } 1040 1041 // work through the entries 1042 try { 1043 for (int i = 0; i < entries.size(); i++) { 1044 final List<RemoteAddeEntry> checkedEntries = ecs.take().get(); 1045 if (!checkedEntries.isEmpty()) { 1046 final int row = tableModel.getRowForEntry(checkedEntries.get(0)); 1047 runOnEDT(new Runnable() { 1048 public void run() { 1049 List<RemoteAddeEntry> oldEntries = tableModel.getEntriesAtRow(row); 1050 serverManager.replaceEntries(oldEntries, checkedEntries); 1051 tableModel.fireTableRowsUpdated(row, row); 1052 } 1053 }); 1054 } 1055 valid.addAll(checkedEntries); 1056 } 1057 } catch (InterruptedException e) { 1058 LogUtil.logException("Interrupted while validating entries", e); 1059 } catch (ExecutionException e) { 1060 LogUtil.logException("ADDE validation execution error", e); 1061 } finally { 1062 exec.shutdown(); 1063 } 1064 return valid; 1065 } 1066 1067 1068 private class BetterCheckTask implements Callable<List<RemoteAddeEntry>> { 1069 private final RemoteAddeEntry entry; 1070 public BetterCheckTask(final RemoteAddeEntry entry) { 1071 this.entry = entry; 1072 this.entry.setEntryValidity(EntryValidity.VALIDATING); 1073 } 1074 public List<RemoteAddeEntry> call() { 1075 List<RemoteAddeEntry> valid = arrList(); 1076 if (RemoteAddeEntry.checkHost(entry)) { 1077 for (RemoteAddeEntry tmp : EntryTransforms.createEntriesFrom(entry)) { 1078 if (RemoteAddeEntry.checkEntry(false, tmp) == AddeStatus.OK) { 1079 tmp.setEntryValidity(EntryValidity.VERIFIED); 1080 valid.add(tmp); 1081 } 1082 } 1083 } 1084 if (!valid.isEmpty()) { 1085 entry.setEntryValidity(EntryValidity.VERIFIED); 1086// serverManager.replaceEntries(Collections.singletonList(entry), valid); 1087 } else { 1088 entry.setEntryValidity(EntryValidity.INVALID); 1089 } 1090 return valid; 1091 } 1092 } 1093 1094 private class CheckEntryTask implements Callable<RemoteAddeEntry> { 1095 private final RemoteAddeEntry entry; 1096 public CheckEntryTask(final RemoteAddeEntry entry) { 1097 notNull(entry); 1098 this.entry = entry; 1099 this.entry.setEntryValidity(EntryValidity.VALIDATING); 1100 } 1101 public RemoteAddeEntry call() { 1102 AddeStatus status = RemoteAddeEntry.checkEntry(entry); 1103 switch (status) { 1104 case OK: entry.setEntryValidity(EntryValidity.VERIFIED); break; 1105 default: entry.setEntryValidity(EntryValidity.INVALID); break; 1106 } 1107 return entry; 1108 } 1109 } 1110 1111 private static class RemoteAddeTableModel extends AbstractTableModel { 1112 1113 // TODO(jon): these constants can go once things calm down 1114 private static final int VALID = 0; 1115 private static final int SOURCE = 1; 1116 private static final int DATASET = 2; 1117 private static final int ACCT = 3; 1118 private static final int TYPES = 4; 1119 1120 /** Labels that appear as the column headers. */ 1121 private final String[] columnNames = { 1122 "Valid", "Source", "Dataset", "Accounting", "Data Types" 1123 }; 1124 1125 private final List<String> servers; 1126 1127 /** {@link EntryStore} used to query and apply changes. */ 1128 private final EntryStore entryStore; 1129 1130 /** 1131 * 1132 * 1133 * @param entryStore 1134 */ 1135 public RemoteAddeTableModel(final EntryStore entryStore) { 1136 notNull(entryStore, "Cannot query a null EntryStore"); 1137 this.entryStore = entryStore; 1138 this.servers = arrList(entryStore.getRemoteEntryTexts()); 1139 } 1140 1141 /** 1142 * Returns the {@link RemoteAddeEntry} at the given index. 1143 * 1144 * @param row Index of the entry. 1145 * 1146 * @return The {@code RemoteAddeEntry} at the index specified by {@code row}. 1147 */ 1148 protected List<RemoteAddeEntry> getEntriesAtRow(final int row) { 1149 String server = servers.get(row).replace('/', '!'); 1150 List<RemoteAddeEntry> matches = arrList(); 1151 for (AddeEntry entry : entryStore.searchWithPrefix(server)) { 1152 if (entry instanceof RemoteAddeEntry) { 1153 matches.add((RemoteAddeEntry)entry); 1154 } 1155 } 1156 return matches; 1157 } 1158 1159 /** 1160 * Returns the index of the given {@code entry}. 1161 * 1162 * @see List#indexOf(Object) 1163 */ 1164 protected int getRowForEntry(final RemoteAddeEntry entry) { 1165 return getRowForEntry(entry.getEntryText()); 1166 } 1167 1168 protected int getRowForEntry(final String entryText) { 1169 return servers.indexOf(entryText); 1170 } 1171 1172 /** 1173 * Clears and re-adds all {@link RemoteAddeEntry}s within {@link #entries}. 1174 */ 1175 public void refreshEntries() { 1176 servers.clear(); 1177 servers.addAll(entryStore.getRemoteEntryTexts()); 1178 this.fireTableDataChanged(); 1179 } 1180 1181 /** 1182 * Returns the length of {@link #columnNames}. 1183 * 1184 * @return The number of columns. 1185 */ 1186 @Override public int getColumnCount() { 1187 return columnNames.length; 1188 } 1189 1190 /** 1191 * Returns the number of entries being managed. 1192 */ 1193 @Override public int getRowCount() { 1194 return servers.size(); 1195 } 1196 1197 /** 1198 * Finds the value at the given coordinates. 1199 * 1200 * @param row 1201 * @param column 1202 * 1203 * @return 1204 * 1205 * @throws IndexOutOfBoundsException 1206 */ 1207 @Override public Object getValueAt(int row, int column) { 1208 String serverText = servers.get(row); 1209 String prefix = serverText.replace('/', '!'); 1210 switch (column) { 1211 case VALID: return formattedValidity(prefix, entryStore); 1212 case SOURCE: return formattedSource(prefix, entryStore); 1213 case DATASET: return serverText; 1214 case ACCT: return formattedAccounting(prefix, entryStore); 1215 case TYPES: return formattedTypes(prefix, entryStore); 1216 default: throw new IndexOutOfBoundsException(); 1217 } 1218 } 1219 1220 private static String formattedSource(final String serv, final EntryStore manager) { 1221 List<AddeEntry> matches = manager.searchWithPrefix(serv); 1222 EntrySource source = EntrySource.INVALID; 1223 if (!matches.isEmpty()) { 1224 for (AddeEntry entry : matches) { 1225 if (entry.getEntrySource() == EntrySource.USER) { 1226 return EntrySource.USER.toString(); 1227 } 1228 } 1229 source = matches.get(0).getEntrySource(); 1230 } 1231 return source.toString(); 1232 } 1233 1234 private static String formattedValidity(final String serv, final EntryStore manager) { 1235 List<AddeEntry> matches = manager.searchWithPrefix(serv); 1236 EntryValidity validity = EntryValidity.INVALID; 1237 if (!matches.isEmpty()) { 1238 validity = matches.get(0).getEntryValidity(); 1239 } 1240 return validity.toString(); 1241 } 1242 1243 private static String formattedAccounting(final String serv, final EntryStore manager) { 1244 List<AddeEntry> matches = manager.searchWithPrefix(serv); 1245 AddeAccount acct = AddeEntry.DEFAULT_ACCOUNT; 1246 if (!matches.isEmpty()) { 1247 acct = matches.get(0).getAccount(); 1248 } 1249 if (AddeEntry.DEFAULT_ACCOUNT.equals(acct)) { 1250 return "public dataset"; 1251 } 1252 return acct.friendlyString(); 1253 } 1254 1255 private static boolean hasType(final String serv, final EntryStore manager, final EntryType type) { 1256 String[] chunks = serv.split("!"); 1257 Set<EntryType> types = Collections.emptySet(); 1258 if (chunks.length == 2) { 1259 types = manager.getTypes(chunks[0], chunks[1]); 1260 } 1261 return types.contains(type); 1262 } 1263 1264 private static String formattedTypes(final String serv, final EntryStore manager) { 1265 String[] chunks = serv.split("!"); 1266 Set<EntryType> types = Collections.emptySet(); 1267 if (chunks.length == 2) { 1268 types = manager.getTypes(chunks[0], chunks[1]); 1269 } 1270 1271 StringBuilder sb = new StringBuilder(30); 1272 for (EntryType type : EnumSet.of(EntryType.IMAGE, EntryType.GRID, EntryType.NAV, EntryType.POINT, EntryType.RADAR, EntryType.TEXT)) { 1273 if (types.contains(type)) { 1274 sb.append(type.toString()).append(' '); 1275 } 1276 } 1277 return sb.toString().toLowerCase(); 1278 } 1279 1280 /** 1281 * Returns the column name associated with {@code column}. 1282 * 1283 * @return One of {@link #columnNames}. 1284 */ 1285 @Override public String getColumnName(final int column) { 1286 return columnNames[column]; 1287 } 1288 1289 @Override public Class<?> getColumnClass(final int column) { 1290 return String.class; 1291 } 1292 1293 @Override public boolean isCellEditable(final int row, final int column) { 1294 return false; 1295 } 1296 } 1297 1298 private static class LocalAddeTableModel extends AbstractTableModel { 1299 1300 /** Labels that appear as the column headers. */ 1301 private final String[] columnNames = { 1302 "Dataset (e.g. MYDATA)", "Image Type (e.g. JAN 07 GOES)", "Format", "Directory" 1303 }; 1304 1305 /** Entries that currently populate the server manager. */ 1306 private final List<LocalAddeEntry> entries; 1307 1308 /** {@link EntryStore} used to query and apply changes. */ 1309 private final EntryStore entryStore; 1310 1311 public LocalAddeTableModel(final EntryStore entryStore) { 1312 notNull(entryStore, "Cannot query a null EntryStore"); 1313 this.entryStore = entryStore; 1314 this.entries = arrList(entryStore.getLocalEntries()); 1315 } 1316 1317 /** 1318 * Returns the {@link LocalAddeEntry} at the given index. 1319 * 1320 * @param row Index of the entry. 1321 * 1322 * @return The {@code LocalAddeEntry} at the index specified by {@code row}. 1323 */ 1324 protected LocalAddeEntry getEntryAtRow(final int row) { 1325 return entries.get(row); 1326 } 1327 1328 protected int getRowForEntry(final LocalAddeEntry entry) { 1329 return entries.indexOf(entry); 1330 } 1331 1332 protected List<LocalAddeEntry> getSelectedEntries(final int[] rows) { 1333 List<LocalAddeEntry> selected = arrList(); 1334 int rowCount = entries.size(); 1335 for (int i = 0; i < rows.length; i++) { 1336 int tmpIdx = rows[i]; 1337 if ((tmpIdx >= 0) && (tmpIdx < rowCount)) { 1338 selected.add(entries.get(tmpIdx)); 1339 } else { 1340 throw new IndexOutOfBoundsException(); 1341 } 1342 } 1343 return selected; 1344 } 1345 1346 public void refreshEntries() { 1347 entries.clear(); 1348 entries.addAll(entryStore.getLocalEntries()); 1349 this.fireTableDataChanged(); 1350 } 1351 1352 /** 1353 * Returns the length of {@link #columnNames}. 1354 * 1355 * @return The number of columns. 1356 */ 1357 @Override public int getColumnCount() { 1358 return columnNames.length; 1359 } 1360 1361 /** 1362 * Returns the number of entries being managed. 1363 */ 1364 @Override public int getRowCount() { 1365 return entries.size(); 1366 } 1367 1368 /** 1369 * Finds the value at the given coordinates. 1370 * 1371 * @param row 1372 * @param column 1373 * 1374 * @return 1375 * 1376 * @throws IndexOutOfBoundsException 1377 */ 1378 @Override public Object getValueAt(int row, int column) { 1379 LocalAddeEntry entry = entries.get(row); 1380 if (entry == null) { 1381 throw new IndexOutOfBoundsException(); // still questionable... 1382 } 1383 1384 switch (column) { 1385 case 0: return entry.getGroup(); 1386 case 1: return entry.getName(); 1387 case 2: return entry.getFormat(); 1388 case 3: return entry.getMask(); 1389 default: throw new IndexOutOfBoundsException(); 1390 } 1391 } 1392 1393 /** 1394 * Returns the column name associated with {@code column}. 1395 * 1396 * @return One of {@link #columnNames}. 1397 */ 1398 @Override public String getColumnName(final int column) { 1399 return columnNames[column]; 1400 } 1401 } 1402 1403 // i need the following icons: 1404 // something to convey entry validity: invalid, verified, unverified 1405 // a "system" entry icon (thinking of something with prominent "V") 1406 // a "mctable" entry icon (similar to above, but with a prominent "X") 1407 // a "user" entry icon (no idea yet!) 1408 public class EntrySourceRenderer extends DefaultTableCellRenderer { 1409 1410 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 1411 Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 1412 EntrySource source = EntrySource.valueOf((String)value); 1413 EntrySourceRenderer renderer = (EntrySourceRenderer)comp; 1414 Icon icon = null; 1415 String tooltip = null; 1416 switch (source) { 1417 case SYSTEM: 1418 icon = system; 1419 tooltip = "Default dataset and cannot be removed, only disabled."; 1420 break; 1421 case MCTABLE: 1422 icon = mctable; 1423 tooltip = "Dataset imported from a MCTABLE.TXT."; 1424 break; 1425 case USER: 1426 icon = user; 1427 tooltip = "Dataset created or altered by you!"; 1428 break; 1429 } 1430 renderer.setIcon(icon); 1431 renderer.setToolTipText(tooltip); 1432 renderer.setText(null); 1433 return comp; 1434 } 1435 } 1436 1437 public class EntryValidityRenderer extends DefaultTableCellRenderer { 1438 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 1439 Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 1440 EntryValidity validity = EntryValidity.valueOf((String)value); 1441 EntryValidityRenderer renderer = (EntryValidityRenderer)comp; 1442 Icon icon = null; 1443 String msg = null; 1444 String tooltip = null; 1445 switch (validity) { 1446 case INVALID: 1447 icon = invalid; 1448 tooltip = "Dataset verification failed."; 1449 break; 1450 case VERIFIED: 1451 break; 1452 case UNVERIFIED: 1453 icon = unverified; 1454 tooltip = "Dataset has not been verified."; 1455 break; 1456 case VALIDATING: 1457 msg = "Checking..."; 1458 break; 1459 } 1460 renderer.setIcon(icon); 1461 renderer.setToolTipText(tooltip); 1462 renderer.setText(msg); 1463 return comp; 1464 } 1465 } 1466 1467 public class TextRenderer extends DefaultTableCellRenderer { 1468 1469 private Font bold; 1470 private Font boldItalic; 1471 1472 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 1473 Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 1474 Font currentFont = comp.getFont(); 1475 if (bold == null) { 1476 bold = currentFont.deriveFont(Font.BOLD); 1477 } 1478 if (boldItalic == null) { 1479 boldItalic = currentFont.deriveFont(Font.BOLD | Font.ITALIC); 1480 } 1481 if (column == 2) { 1482 comp.setFont(bold); 1483 } else if (column == 3) { 1484 // why can't i set the color for just a single column!? 1485 } else if (column == 4) { 1486 comp.setFont(boldItalic); 1487 } 1488 return comp; 1489 } 1490 } 1491 1492 private static Icon icon(final String path) { 1493 return GuiUtils.getImageIcon(path, TabbedAddeManager.class, true); 1494 } 1495 1496 /** 1497 * @param args the command line arguments 1498 */ 1499 public static void main(String args[]) { 1500 java.awt.EventQueue.invokeLater(new Runnable() { 1501 public void run() { 1502 new TabbedAddeManager().setVisible(true); 1503 } 1504 }); 1505 } 1506 1507 // Variables declaration - do not modify 1508 private JPanel actionPanel; 1509 private JMenuItem closeItem; 1510 private JButton editEntryButton; 1511 private JMenu fileMenu; 1512 private JMenu editMenu; 1513 private JPopupMenu.Separator fileSeparator1; 1514 private JMenu helpMenu; 1515 private JButton importButton; 1516 private JTable localTable; 1517 private JScrollPane localScroller; 1518 private JMenuItem localHelpItem; 1519 private JPanel localTab; 1520 private JMenuBar menuBar; 1521 private JButton newEntryButton; 1522 private JMenuItem newLocalItem; 1523 private JMenuItem newRemoteItem; 1524 private JMenuItem remoteHelpItem; 1525 private JMenuItem editEntryItem; 1526 private JMenuItem removeEntryItem; 1527 private JScrollPane remoteScroller; 1528 private JPanel remoteTab; 1529 private JTable remoteTable; 1530 private JButton removeEntryButton; 1531 private JButton restartButton; 1532 private JLabel statusLabel; 1533 private JPanel statusPanel; 1534 private JTabbedPane tabbedPane; 1535 private JCheckBox importAccountBox; 1536 private JTextField importUser; 1537 private JTextField importProject; 1538 // End of variables declaration 1539 1540}