/*
* Copyright 2003 Association for Universities for Research in Astronomy, Inc.,
* Observatory Control System, Gemini Telescopes Project.
*
* $Id: CatalogNavigator.java,v 1.12 2009/04/14 10:56:02 abrighto Exp $
*/
package jsky.catalog.gui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import jsky.catalog.Catalog;
import jsky.catalog.CatalogDirectory;
import jsky.catalog.HTMLQueryResultHandler;
import jsky.catalog.QueryResult;
import jsky.catalog.TableQueryResult;
import jsky.catalog.URLQueryResult;
import jsky.catalog.astrocat.AstroCatConfig;
import jsky.util.*;
import jsky.util.SwingWorker;
import jsky.util.gui.DialogUtil;
import jsky.util.gui.GenericToolBarTarget;
import jsky.util.gui.ProgressException;
import jsky.util.gui.ProgressPanel;
import jsky.util.gui.SwingUtil;
import uk.ac.starlink.table.StarTable;
/**
* Used to navigate the catalog hierarchy. This class displays a tree of catalogs in one panel and the interface for
* searching the catalog, or the query results in the other panel.
*
* The tree display is based on a top level catalog directory. The details must be defined in a derived class.
*
* ** This class has been imported into Osp Project from Jsky library.
*/
public abstract class CatalogNavigator extends JPanel
implements QueryResultDisplay, GenericToolBarTarget, HTMLQueryResultHandler {
// Used to access internationalized strings (see i18n/gui*.proprties)
private static final I18N _I18N = I18N.getInstance(CatalogNavigator.class);
// // True if this is the main application window (enables exit menu item)
// private static boolean _mainWindowFlag = false;
// Set to true to query catalogs automatically when selected
private boolean _autoQuery = false;
// Displays the catalog tree and the catalog query widgets
private JPanel _queryPanel;
// Displays query results, such as tabular data.
private JPanel _resultPanel;
// Tree displaying catalog hierarchy
private CatalogTree _catalogTree;
// Query panel currently being displayed
private JComponent _queryComponent;
// Result panel currently being displayed
private JComponent _resultComponent;
// The original URL for the display component's data (for history list)
private URL _origURL;
// reuse file chooser widget for open
private static JFileChooser _fileChooser;
// reuse file chooser widget for saveAs
private static JFileChooser _saveFileChooser;
// Panel used to display download progress information
private ProgressPanel _progressPanel;
// list of listeners for change events
private EventListenerList _listenerList = new EventListenerList();
// Stack of CatalogHistoryItems, used to go back to a previous panel
private Stack _backStack = new Stack();
// Stack of CatalogHistoryItems, used to go forward to the next panel
private Stack _forwStack = new Stack();
// Set when the back or forward actions are active to avoid the normal history stack handling
private boolean _noStack = false;
// Saved query result (set in background thread)
private QueryResult _queryResult;
// Optional object to use to plot table data
private TablePlotter _plotter;
// Utility object used to control background thread
private SwingWorker _worker;
// // Top level frame for viewing an HTML page
// private HTMLViewerFrame _htmlViewerFrame;
// Hash table associating each panel with a tree node
private Hashtable _panelTreeNodeTable = new Hashtable(10);
// Manages a list of previously viewed catalogs or query results.
private CatalogHistoryList _historyList;
// Manages a list of settings for stored querries, so that you can repeat the query later on
private CatalogQueryList _queryList;
// Maps query components to their corresponding result components
private Hashtable _queryResultComponentMap = new Hashtable();
// Maps table ids and url strings to their corresponding query components
private Hashtable _queryComponentMap = new Hashtable();
// The pane dividing the catalog tree and the query panel
private JSplitPane _querySplitPane;
// The pane dividing the query and the results panel
private JSplitPane _resultSplitPane;
// Action to use for the "Open..." menu and toolbar items
private AbstractAction _openAction = new AbstractAction(
_I18N.getString("open"),
Resources.getIcon("Open24.gif")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("catalogOpenTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
open();
}
};
// Action to use for the "Open URL..." menu and toolbar items
private AbstractAction _openUrlAction = new AbstractAction(
_I18N.getString("openURL")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("openURLTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
openURL();
}
};
// Action to use for the "Clear" menu and toolbar items
private AbstractAction _clearAction = new AbstractAction(
_I18N.getString("clear")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("clearTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
clear();
}
};
// May be set to override the default _openAction
private AbstractAction _openActionOverride;
// Action to use for the "Save as..." menu and toolbar items
private AbstractAction _saveAsAction = new AbstractAction(
_I18N.getString("saveAs"),
Resources.getIcon("SaveAs24.gif")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("saveAsTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
saveAs();
}
};
// Action to use for the "Save With Image..." menu and toolbar items
private AbstractAction _saveWithImageAction = new AbstractAction(
_I18N.getString("saveCatalogWithImage")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("saveCatalogWithImageTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
saveWithImage();
}
};
// Action to use for the "Save as HTML..." menu and toolbar items
private AbstractAction _saveAsHTMLAction = new AbstractAction(
_I18N.getString("saveAsHTML")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("saveAsHTMLTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
saveAsHTML();
}
};
// Action to use for the "Print..." menu and toolbar items
private AbstractAction _printAction = new AbstractAction(
_I18N.getString("print") + "...",
Resources.getIcon("Print24.gif")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("printTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
print();
}
};
// Action to use for the "Back" menu and toolbar items
private AbstractAction _backAction = new AbstractAction(
_I18N.getString("back"),
Resources.getIcon("Back24.gif")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("catalogBackTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
back();
}
};
// Action to use for the "Forward" menu and toolbar items
private AbstractAction _forwAction = new AbstractAction(
_I18N.getString("forward"),
Resources.getIcon("Forward24.gif")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("catalogForwardTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
forward();
}
};
// Action to use for the "Add Row" menu item
private AbstractAction _addRowAction = new AbstractAction(
_I18N.getString("addRow")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("addRowTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
addRow();
}
};
// Action to use for the "Delete Rows..." menu item
private AbstractAction _deleteSelectedRowsAction = new AbstractAction(
_I18N.getString("deleteSelectedRows")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("deleteSelectedRowsTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
deleteSelectedRows();
}
};
// Action to use for the "Query => Store => New Query..." menu item
private AbstractAction _storeNewQueryAction = new AbstractAction(
_I18N.getString("new")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("newQueryTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
storeNewQuery();
}
};
// Action to use for the "Query => Delete => All" menu item
private AbstractAction _deleteAllQueryAction = new AbstractAction(
_I18N.getString("all")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("deleteAllQueryTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
clearQueryList();
}
};
// Action to use for the "File => Close" menu item
private AbstractAction _closeAction = new AbstractAction(
_I18N.getString("close")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("closeTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
close();
}
};
// Action to use for the "File => Exit" menu item
private AbstractAction _exitAction = new AbstractAction(
_I18N.getString("exit")) {
{
putValue(SHORT_DESCRIPTION, _I18N.getString("exitTip"));
}
@Override
public void actionPerformed(ActionEvent evt) {
exit();
}
};
/**
* Returns the top level catalog directory to use The contents of this are determined by the AstroCat.xml file found
* either under ~/.jsky3 or as a resource (jsky-catalog/src/main/resources).
*
* @param load if true, load the catalog directory (which can be slow) if not already loaded, otherwise an empty
* catalog directory is returned if not already initialized. This is just a placeholder that a listener
* can be added to, to be notified later when the directory has been initialized.
* @return the top level catalog directory
*/
public static CatalogDirectory getCatalogDirectory(boolean load) {
return AstroCatConfig.getConfigFile(load);
}
/**
* Returns the top level catalog directory to use The contents of this are determined by the AstroCat.xml file found
* either under ~/.jsky3 or as a resource (jsky-catalog/src/main/resources).
*
* @return the top level catalog directory
*/
public static CatalogDirectory getCatalogDirectory() {
return AstroCatConfig.getConfigFile();
}
/**
* Construct a CatalogNavigator using the given CatalogTree widget (Call setQueryResult to set the root catalog to
* display).
*
* @param catalogTree a CatalogTree (normally a subclass of CatalogTree that knows about certain types of catalogs)
*/
public CatalogNavigator(CatalogTree catalogTree) {
setLayout(new BorderLayout());
_catalogTree = catalogTree;
catalogTree.setQueryResultDisplay(this);
catalogTree.setHTMLQueryResultHandler(this);
catalogTree.setPreferredSize(new Dimension(300, 0));
// Turn autoQuery off when the user selects the catalog in the tree
// (Its turned on when selected from the main menu)
catalogTree.getTree().getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent treeSelectionEvent) {
_autoQuery = false;
}
});
_queryPanel = new JPanel();
_queryPanel.setLayout(new BorderLayout());
_resultPanel = new JPanel();
_resultPanel.setLayout(new BorderLayout());
_querySplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, catalogTree, _queryPanel);
_querySplitPane.setOneTouchExpandable(false);
_querySplitPane.setDividerLocation(385);
_querySplitPane.setBorder(null);
_resultSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, _querySplitPane, _resultPanel);
_resultSplitPane.setOneTouchExpandable(false);
_resultSplitPane.setDividerLocation(320);
_resultSplitPane.setBorder(null);
add(_resultSplitPane, BorderLayout.CENTER);
_queryList = new CatalogQueryList();
_historyList = new CatalogHistoryList();
setQueryComponent(new EmptyPanel());
setResultComponent(new EmptyPanel());
}
/**
* Construct a CatalogNavigator using the given CatalogTree widget and TablePlotter (Call setQueryResult to set the
* root catalog to display).
*
* @param catalogTree a CatalogTree (normally a subclass of CatalogTree that knows about certain types of catalogs)
* @param plotter the object to use to plot catalog table data (when the plot button is pressed)
*/
public CatalogNavigator(CatalogTree catalogTree, TablePlotter plotter) {
this(catalogTree);
_plotter = plotter;
}
/**
* @return the object displaying the catalog tree
*/
public CatalogTree getCatalogTree() {
return _catalogTree;
}
/**
* @return the pane dividing the catalog tree and the query panel
*/
protected JSplitPane getQuerySplitPane() {
return _querySplitPane;
}
/**
* @return the pane dividing the query and the results panel
*/
public JSplitPane getResultSplitPane() {
return _resultSplitPane;
}
/**
* @param b Set to true to query catalogs automatically when selected
*/
public void setAutoQuery(boolean b) {
_autoQuery = b;
}
/**
* @return the object used to plot table data, or null if none was defined.
*/
public TablePlotter getPlotter() {
return _plotter;
}
/**
* @param tp the object used to plot table data.
*/
public void setPlotter(TablePlotter tp) {
_plotter = tp;
}
/**
* Set the query or result component to display. The choice is made based on which interfaces the component
* implements. If the component implements QueryResultDisplay, it is considered a result component.
*
* @param component the query or result component to display
*/
public void setComponent(JComponent component) {
if (component instanceof QueryResultDisplay) {
setResultComponent(component);
} else {
setQueryComponent(component);
//System.out.println("XXX _autoQuery = " + _autoQuery + ", component is a " + component.getClass());
if ((component instanceof CatalogQueryTool)
&& (_autoQuery || ((CatalogQueryTool) component).getCatalog().isLocal())) {
((CatalogQueryTool) component).search();
}
}
}
/**
* @param component the query component to display
*/
public void setQueryComponent(JComponent component) {
if (component == null || component == _queryComponent) {
return;
}
if (_queryComponent != null) {
addToHistory();
_queryPanel.remove(_queryComponent);
_queryComponent = null;
}
_queryComponent = component;
Catalog cat = _catalogTree.getSelectedCatalog();
if (cat != null) {
_panelTreeNodeTable.put(_queryComponent, cat);
}
_queryPanel.add(_queryComponent, BorderLayout.CENTER);
// restore the query result corresponding to this catalog, if known
Object resultComp = _queryResultComponentMap.get(_queryComponent);
if (resultComp == null) {
setResultComponent(new EmptyPanel());
} else {
setResultComponent((JComponent) resultComp);
}
update();
}
/**
* @return the panel currently being displayed
*/
public JComponent getQueryComponent() {
return _queryComponent;
}
/**
* @param component the result component to display
*/
public void setResultComponent(JComponent component) {
if (component == null || component == _resultComponent) {
return;
}
if (_resultComponent != null) {
// if (_resultComponent instanceof TableDisplayTool) {
// if we're not reusing the current table window, tell it to hide any related popup
// windows before replacing it (It might be needed again later though, if the user
// goes back to it).
//((TableDisplayTool)_resultComponent).hidePopups();
// }
_resultPanel.remove(_resultComponent);
_resultComponent = null;
}
_resultComponent = component;
if (_queryComponent != null) {
_queryResultComponentMap.put(_queryComponent, _resultComponent);
}
_resultPanel.add(_resultComponent, BorderLayout.CENTER);
update();
_resultComponentChanged();
// try to display the right amount of the query window
// SwingUtilities.invokeLater(new Runnable() {
// public void run() {
// _resultSplitPane.resetToPreferredSizes();
// }
// });
}
/**
* @return the panel currently being displayed
*/
public JComponent getResultComponent() {
return _resultComponent;
}
/**
* Called whenever the display component is changed
*/
protected void _resultComponentChanged() {
// set the state of the "Save As..." menu item
_saveAsAction.setEnabled(_resultComponent instanceof Saveable);
_printAction.setEnabled(_resultComponent instanceof PrintableWithDialog);
boolean isTable = (_resultComponent instanceof TableDisplayTool);
_saveWithImageAction.setEnabled(isTable);
_deleteSelectedRowsAction.setEnabled(isTable);
_addRowAction.setEnabled(isTable);
fireChange(new ChangeEvent(this));
}
/**
* Register to receive change events from this object whenever a new query result is displayed.
*
* @param l the listener
*/
public void addChangeListener(ChangeListener l) {
_listenerList.add(ChangeListener.class, l);
}
/**
* Stop receiving change events from this object.
*
* @param l the listener
*/
public void removeChangeListener(ChangeListener l) {
_listenerList.remove(ChangeListener.class, l);
}
/**
* Notify any listeners that a new query result is being displayed.
*
* @param e the event
*/
protected void fireChange(ChangeEvent e) {
Object[] listeners = _listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ChangeListener.class) {
((ChangeListener) listeners[i + 1]).stateChanged(e);
}
}
}
/**
* Add the current catalog to the history stack, removing duplicates.
*/
protected void addToHistory() {
if (_queryComponent == null) {
return;
}
CatalogHistoryItem historyItem = makeCatalogHistoryItem();
if (historyItem == null) {
return;
}
if (!_noStack) {
_backStack.push(historyItem);
_backAction.setEnabled(true);
if (_forwStack.size() != 0) {
_cleanupHistoryStack(_forwStack);
_forwStack.clear();
_forwAction.setEnabled(false);
}
}
_historyList.add(historyItem);
}
/**
* @return a new CatalogHistoryItem for the currently displayed catalog.
*/
protected CatalogHistoryItem makeCatalogHistoryItem() {
String s = _queryComponent.getName();
if (s != null) {
return new CatalogHistoryItem(s, _origURL, _queryComponent);
}
return null;
}
/**
* Add history items (for previously displayed components) to the given menu
*
* @param menu the menu to add the items to
*/
public void addHistoryMenuItems(JMenu menu) {
Iterator it = _historyList.iterator();
while (it.hasNext()) {
menu.add(it.next());
}
}
// This method may be redefined in subclasses to do cleanup work before components are
// removed from the given history stack (_backStack or _forwStack).
private void _cleanupHistoryStack(Stack stack) {
unplot(stack);
}
/**
* Clear out the history and back/forward stacks
*/
protected void clearHistory() {
_backAction.setEnabled(false);
_backStack.clear();
_forwAction.setEnabled(false);
_forwStack.clear();
_historyList.clear();
}
/**
* Set the original URL for the current catalog or table.
*
* @param url the URL of the catalog, table or FITS file
*/
public void setOrigURL(URL url) {
_origURL = url;
}
/**
* Returns the original URL for the current catalog or table.
*
* @return the URL of the catalog, table or FITS file
*/
public URL getOrigURL() {
return _origURL;
}
/**
* Remove any plot symbols or graphics managed by any of the display components in the given stack
*
* @param stack history stack
*/
protected void unplot(Stack stack) {
// Unplot any catalog symbols before loosing the information
int n = stack.size();
for (int i = 0; i < n; i++) {
CatalogHistoryItem item = (CatalogHistoryItem) (stack.get(i));
Object resultComp = _queryResultComponentMap.get(item.getQueryComponent());
if (resultComp instanceof TableDisplayTool) {
((TableDisplayTool) resultComp).unplot();
}
}
}
/**
* Remove any plot symbols or graphics managed by any of the display components
*/
public void unplot() {
Enumeration e = _queryResultComponentMap.elements();
while (e.hasMoreElements()) {
JComponent comp = (JComponent) e.nextElement();
if (comp instanceof TableDisplayTool) {
((TableDisplayTool) comp).unplot();
}
}
}
/**
* Update the layout after a new component has been inserted
*/
protected void update() {
_queryPanel.revalidate();
_resultPanel.revalidate();
JFrame parent = SwingUtil.getFrame(this);
if (parent != null) {
parent.repaint();
}
}
/**
* Select the node in the catalog directory tree corresponding to the current display component
*/
protected void updateTreeSelection() {
if (_queryComponent instanceof CatalogQueryTool) {
_catalogTree.setSelectedCatalog(((CatalogQueryTool) _queryComponent).getCatalog(), true);
_updateTitle(((CatalogQueryTool) _queryComponent).getCatalog());
} else if (_queryComponent instanceof TableDisplayTool) {
_catalogTree.setSelectedCatalog(((TableDisplayTool) _queryComponent).getTable(), true);
}
}
public QueryResult getQueryResult() {
return _queryResult;
}
/**
* Display the given query result.
*/
@Override
public void setQueryResult(QueryResult queryResult) {
if (queryResult == null) {
return;
}
if (_worker != null) {
// shouldn't happen if user interface disables it
DialogUtil.error(_I18N.getString("queryInProgress"));
return;
}
_queryResult = queryResult;
// Use a background thread for remote catalog access, local catalogs are handled in this thread
boolean isLocal = true;
if (queryResult instanceof URLQueryResult) {
URLQueryResult uqr = (URLQueryResult) queryResult;
URL url = uqr.getURL();
isLocal = (url.getProtocol().equals("file"));
} else if (queryResult instanceof Catalog) {
isLocal = ((Catalog) queryResult).isLocal();
}
if (isLocal) {
setComponent(makeQueryResultComponent(queryResult));
} else {
// remote catalog: run in a separate thread, so the user can monitor progress
makeProgressPanel();
_worker = new SwingWorker() {
@Override
public Object construct() {
try {
return makeQueryResultComponent(_queryResult);
} catch (Exception e) {
return e;
}
}
@Override
public void finished() {
_worker = null;
_progressPanel.stop();
Object o = getValue();
if (o instanceof Exception) {
DialogUtil.error((Exception) o);
} else if (o instanceof JComponent) {
setComponent((JComponent) o);
}
}
};
_worker.start();
}
}
/**
* Update the frame's title to display the name of the given catalog
*
* @param catalog the given catalog
*/
private void _updateTitle(Catalog catalog) {
String title = _I18N.getString("catalogNavigator");
String s = catalog.getTitle();
if (s != null && s.length() > 0) {
title += " - " + s;
}
JFrame parent = SwingUtil.getFrame(this);
if (parent != null) {
parent.setTitle(title);
}
}
/**
* If it does not already exist, make the panel used to display the progress of network access.
*/
protected void makeProgressPanel() {
if (_progressPanel == null) {
JFrame parent = SwingUtil.getFrame(this);
_progressPanel = ProgressPanel.makeProgressPanel(_I18N.getString("accessingCatalogServer"), parent);
_progressPanel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (_worker != null) {
_worker.interrupt();
_worker = null;
}
}
});
}
}
/**
* Create and return a JComponent displaying the given query result.
*
* @param queryResult result of a catalog query
* @return a JComponent displaying the given query result
*/
protected JComponent makeQueryResultComponent(QueryResult queryResult) {
_origURL = null;
try {
// See if there is a user interface handler for the query result
if (queryResult instanceof CatalogUIHandler) {
JComponent c = ((CatalogUIHandler) queryResult).makeComponent(this);
if (c != null) {
return c;
}
}
// No UI handler, return the default component for the query result
if (queryResult instanceof CatalogDirectory) {
return makeCatalogDirectoryComponent((CatalogDirectory) queryResult);
}
if (queryResult instanceof TableQueryResult) {
return makeTableQueryResultComponent((TableQueryResult) queryResult);
}
if (queryResult instanceof Catalog) {
return makeCatalogComponent((Catalog) queryResult);
}
if (queryResult instanceof URLQueryResult) {
return makeURLComponent((URLQueryResult) queryResult);
}
} catch (Exception e) {
if (_progressPanel != null) {
_progressPanel.stop();
}
DialogUtil.error(e);
}
return new EmptyPanel();
}
/**
* @param catalogDirectory a catalog directory
* @return a new JComponent displaying the contents of the given catalog directory
*/
protected JComponent makeCatalogDirectoryComponent(CatalogDirectory catalogDirectory) {
// get the number of catalogs in the directory
int numCatalogs = catalogDirectory.getNumCatalogs();
if (numCatalogs == 0) {
return makeCatalogComponent(catalogDirectory);
}
if (numCatalogs == 1) {
return makeCatalogComponent(catalogDirectory.getCatalog(0));
}
return new EmptyPanel();
}
/**
* Return a new JComponent displaying the contents of the given table query result.
*
* @param tableQueryResult a table as the result of a query
* @return a component displaying the table
*/
protected JComponent makeTableQueryResultComponent(TableQueryResult tableQueryResult) {
if (_resultComponent instanceof TableDisplayTool) {
TableDisplayTool tdt = (TableDisplayTool) _resultComponent;
if (tdt.getTable().getName().equals(tableQueryResult.getName())) {
tdt.setQueryResult(tableQueryResult);
return tdt;
}
}
TableDisplayTool t = new TableDisplayTool(tableQueryResult, this, _plotter);
// add a popup menu to the table
makeTablePopupMenu(t);
return t;
}
/**
* Add a popup menu to the given TableDisplayTool
*
* @param t component for displaying tables
*/
protected void makeTablePopupMenu(TableDisplayTool t) {
final JPopupMenu m = new JPopupMenu();
m.add(_addRowAction);
m.add(_deleteSelectedRowsAction);
t.getTableDisplay().getTable().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
m.show(e.getComponent(), e.getX(), e.getY());
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
m.show(e.getComponent(), e.getX(), e.getY());
}
}
});
}
/**
* @param catalog a catalog
* @return a new JComponent displaying the contents of (or the interface for searching) the given catalog
*/
protected JComponent makeCatalogComponent(Catalog catalog) {
// catalog may contain multiple tables and implement the CatalogDirectory interface
if (catalog instanceof CatalogDirectory) {
CatalogDirectory catalogDirectory = (CatalogDirectory) catalog;
int numCatalogs = catalogDirectory.getNumCatalogs();
if (numCatalogs == 1) {
Catalog c = catalogDirectory.getCatalog(0);
if (c instanceof TableQueryResult) {
return makeTableQueryResultComponent((TableQueryResult) c);
} else {
DialogUtil.error(_I18N.getString("subCatalogError") + ": " + c);
return new EmptyPanel();
}
} else if (numCatalogs > 1) {
return makeTableQueryResultComponent(catalogDirectory.getCatalogList());
}
}
if (catalog instanceof TableQueryResult) {
return makeTableQueryResultComponent((TableQueryResult) catalog);
}
// Default to normal catalog query component
return makeCatalogQueryTool(catalog);
}
/**
* Make a panel for querying a catalog
*
* @param catalog the catalog
* @return the query panel to display
*/
protected CatalogQueryTool makeCatalogQueryTool(Catalog catalog) {
return new CatalogQueryTool(catalog, this);
}
/**
* Makes a component to display the contents of a URL
*
* @param queryResult contains the URL are related inforation
* @return a new JComponent displaying the contents of the URL.
* @throws java.io.IOException if the URL can't be read
*/
protected JComponent makeURLComponent(URLQueryResult queryResult) throws IOException {
try {
URL url = queryResult.getURL();
URLConnection connection;
if (url.getProtocol().equals("file")) {
connection = url.openConnection();
} else {
connection = _progressPanel.openConnection(url);
}
if (connection == null) {
return _queryComponent;
}
String format = queryResult.getFormat();
String contentType = connection.getContentType();
if (contentType == null) {
contentType = "unknown";
}
return makeURLComponent(url, contentType, format);
} catch (ProgressException e) {
// ignore: user pressed the stop button in the progress panel
} catch (FileNotFoundException e) {
DialogUtil.error(_I18N.getString("fileNotFound", e.getMessage()));
} catch (Exception e) {
DialogUtil.error(e);
}
if (_resultComponent != null) {
return _resultComponent;
}
return new EmptyPanel();
}
/**
* @param url the URL to read
* @param contentType the content type from the http server
* @param format a format string from the table row, or null if not defined
* @return a new JComponent displaying the contents of the given URL.
* @throws java.io.IOException on read error
*/
protected JComponent makeURLComponent(URL url, String contentType, String format) throws IOException {
String filename = url.getFile();
if (contentType.startsWith("text/plain")) {
displayPlainText(url);
return _resultComponent;
}
if (contentType.startsWith("text/")) {
// assume HTML or something that a browser can display...
displayHTMLPage(url);
return _resultComponent;
}
// If it is not one of the known content types, call a method that may be
// redefined in a derived class to handle that type
return makeUnknownURLComponent(url, contentType);
}
// /*
// * Attempt to show a URL in the default web browser and return true if successful.
// */
// protected boolean displayHTMLPageWithDefaultBrowser(URL url) {
// }
/**
* Display the given HTML URL in a popup window containing a JEditorPane.
*/
@Override
public void displayHTMLPage(URL url) {
// if (_htmlViewerFrame != null) {
// _htmlViewerFrame.getHTMLViewer().setPage(url);
// _htmlViewerFrame.setState(Frame.NORMAL);
// _htmlViewerFrame.setVisible(true);
// return;
// }
// _htmlViewerFrame = new HTMLViewerFrame();
// _htmlViewerFrame.getHTMLViewer().setPage(url);
try {
Desktop.getDesktop().browse(url.toURI());
} catch (Exception e) {
DialogUtil.error(e);
}
}
/**
* Display the text pointed to by the given URL.
*
* @param url the URL to read
*/
public void displayPlainText(URL url) {
try {
String msg = FileUtil.getURL(url);
if (_progressPanel != null) {
_progressPanel.stop();
}
if (msg.length() < 256) {
DialogUtil.error(msg);
} else {
displayHTMLPage(url);
}
} catch (IOException e) {
DialogUtil.error(e);
}
}
/**
* @param url the URL to read
* @param contentType the content type from the http server
* @return a new JComponent displaying the contents of the given URL. A null return value causes an empty panel to
* be displayed. Returning the current component (_resultComponent) will cause no change. This should be
* done if the URL is displayed in a separate window.
*/
@SuppressWarnings({"UnusedDeclaration"})
protected JComponent makeUnknownURLComponent(URL url, String contentType) {
if (_resultComponent != null) {
return _resultComponent;
}
return new EmptyPanel();
}
/**
* Display a file chooser to select a local catalog file to open
*/
public void open() {
if (_openActionOverride != null) {
_openActionOverride.actionPerformed(null);
return;
}
if (_fileChooser == null) {
_fileChooser = makeFileChooser();
}
int option = _fileChooser.showOpenDialog(this);
if (option == JFileChooser.APPROVE_OPTION && _fileChooser.getSelectedFile() != null) {
open(_fileChooser.getSelectedFile().getAbsolutePath());
}
}
/**
* Create and return a new file chooser to be used to select a local file to open.
*
* @return a new file chooser
*/
protected JFileChooser makeFileChooser() {
return new JFileChooser(new File("."));
}
/**
* @return the existing file chooser, or a new one if none exists
*/
public JFileChooser getFileChooser() {
if (_fileChooser == null) {
_fileChooser = makeFileChooser();
}
return _fileChooser;
}
/**
* Create and return a new file chooser to be used for saving to a file.
*
* @return a new file chooser
*/
protected JFileChooser makeSaveFileChooser() {
return new JFileChooser(new File("."));
}
/**
* Open the given file or URL
*
* @param fileOrUrl a file or URL
*/
public void open(String fileOrUrl) {
try {
setQueryComponent(new EmptyPanel());
URL url = FileUtil.makeURL(null, fileOrUrl);
URLQueryResult _queryResult = new URLQueryResult(url);
setQueryResult(_queryResult);
} catch (Exception e) {
DialogUtil.error(e);
}
}
/**
* Exit the application with the given status.
*/
public void exit() {
System.exit(0);
}
/**
* Close the window
*/
public void close() {
JFrame parent = SwingUtil.getFrame(this);
if (parent != null) {
parent.setVisible(false);
}
}
/**
* Go back to the previous component in the history list
*/
public void back() {
if (_backStack.size() == 0) {
return;
}
if (_queryComponent != null) {
_queryPanel.remove(_queryComponent);
URL url = _origURL; // save and restore this
CatalogHistoryItem item = makeCatalogHistoryItem();
_origURL = url;
if (item != null) {
_forwStack.push(item);
_forwAction.setEnabled(true);
}
}
CatalogHistoryItem historyItem = _backStack.pop();
if (_backStack.size() == 0) {
_backAction.setEnabled(false);
}
// select the related tree node
if (historyItem.getQueryComponent() != null) {
Catalog cat = _panelTreeNodeTable.get(historyItem.getQueryComponent());
if (cat != null) {
_catalogTree.setSelectedCatalog(cat, true);
}
}
CatalogNavigatorMenuBar.setCurrentCatalogNavigator(this);
_noStack = true;
try {
historyItem.actionPerformed(null);
} catch (Exception e) {
DialogUtil.error(e);
}
_noStack = false;
update();
}
/**
* Go forward to the next component in the history list
*/
public void forward() {
if (_forwStack.size() == 0) {
return;
}
if (_queryComponent != null) {
_queryPanel.remove(_queryComponent);
URL url = _origURL; // save and restore this
CatalogHistoryItem item = makeCatalogHistoryItem();
_origURL = url;
if (item != null) {
_backStack.push(item);
_backAction.setEnabled(true);
}
}
CatalogHistoryItem historyItem = _forwStack.pop();
if (_forwStack.size() == 0) {
_forwAction.setEnabled(false);
}
// select the related tree node
if (historyItem.getQueryComponent() != null) {
Catalog cat = _panelTreeNodeTable.get(historyItem.getQueryComponent());
if (cat != null) {
_catalogTree.setSelectedCatalog(cat, true);
}
}
CatalogNavigatorMenuBar.setCurrentCatalogNavigator(this);
_noStack = true;
try {
historyItem.actionPerformed(null);
} catch (Exception e) {
DialogUtil.error(e);
}
_noStack = false;
update();
}
// These are for the GenericToolBarTarget interface
@Override
public AbstractAction getOpenAction() {
return _openAction;
}
public AbstractAction getOpenUrlAction() {
return _openUrlAction;
}
public AbstractAction getClearAction() {
return _clearAction;
}
/**
* Override the default Open action.
*
* @param openAction the new action
*/
public void setOpenAction(AbstractAction openAction) {
_openActionOverride = openAction;
}
public AbstractAction getSaveAsAction() {
return _saveAsAction;
}
public AbstractAction getSaveAsHTMLAction() {
return _saveAsHTMLAction;
}
public AbstractAction getSaveWithImageAction() {
return _saveWithImageAction;
}
public AbstractAction getPrintAction() {
return _printAction;
}
@Override
public AbstractAction getBackAction() {
return _backAction;
}
@Override
public AbstractAction getForwAction() {
return _forwAction;
}
public AbstractAction getAddRowAction() {
return _addRowAction;
}
public AbstractAction getDeleteSelectedRowsAction() {
return _deleteSelectedRowsAction;
}
public AbstractAction getStoreNewQueryAction() {
return _storeNewQueryAction;
}
public AbstractAction getDeleteAllQueryAction() {
return _deleteAllQueryAction;
}
public AbstractAction getCloseAction() {
return _closeAction;
}
public AbstractAction getExitAction() {
return _exitAction;
}
/**
* Display a dialog to enter a URL to display
*/
public void openURL() {
String urlStr = DialogUtil.input(_I18N.getString("enterURLDisplay") + ":");
if (urlStr != null) {
URL url;
try {
url = new URL(urlStr);
} catch (Exception e) {
DialogUtil.error(e);
return;
}
setQueryResult(new URLQueryResult(url));
}
}
/**
* Clear the display.
*/
public void clear() {
setQueryComponent(new EmptyPanel());
_origURL = null;
}
/**
* Pop up a dialog to ask the user for a file name, and then save the current query result to the selected file.
*/
public void saveAs() {
if (_resultComponent instanceof SaveableWithDialog) {
((SaveableWithDialog) _resultComponent).saveAs();
} else {
DialogUtil.error(_I18N.getString("saveNotSupportedForObjType"));
}
}
/**
* Save the current query result to the selected file.
*
* @param filename the file name
*/
public void saveAs(String filename) {
if (_resultComponent instanceof Saveable) {
try {
((Saveable) _resultComponent).saveAs(filename);
} catch (Exception e) {
DialogUtil.error(e);
}
} else {
DialogUtil.error(_I18N.getString("saveNotSupportedForObjType"));
}
}
/**
* Save the current table as a FITS table in the current FITS image (Should be defined in a derived class).
*/
public void saveWithImage() {
}
/**
* Pop up a dialog to ask the user for a file name, and then save the current query result to the selected file in
* HTML format.
*/
public void saveAsHTML() {
if (_resultComponent instanceof SaveableAsHTML) {
if (_saveFileChooser == null) {
_saveFileChooser = makeSaveFileChooser();
}
int option = _saveFileChooser.showSaveDialog(this);
if (option == JFileChooser.APPROVE_OPTION && _saveFileChooser.getSelectedFile() != null) {
saveAsHTML(_saveFileChooser.getSelectedFile().getAbsolutePath());
}
} else {
DialogUtil.error(_I18N.getString("htmlOutputNotSupportedForObjType"));
}
}
/**
* Save the current query result to the selected file in HTML format.
*
* @param filename the file name
*/
public void saveAsHTML(String filename) {
if (_resultComponent instanceof SaveableAsHTML) {
try {
((SaveableAsHTML) _resultComponent).saveAsHTML(filename);
} catch (Exception e) {
DialogUtil.error(e);
}
} else {
DialogUtil.error(_I18N.getString("htmlOutputNotSupportedForObjType"));
}
}
/**
* Pop up a dialog for printing the query results.
*/
public void print() {
if (_resultComponent instanceof PrintableWithDialog) {
try {
((PrintableWithDialog) _resultComponent).print();
} catch (Exception e) {
DialogUtil.error(e);
}
} else {
DialogUtil.error(_I18N.getString("printingNotSupportedForObjType"));
}
}
/**
* If a table is being displayed, add an empty row in the table.
*/
public void addRow() {
if (_resultComponent instanceof TableDisplayTool) {
((TableDisplayTool) _resultComponent).addRow();
}
}
/**
* If a table is being displayed, delete the selected rows.
*/
public void deleteSelectedRows() {
if (_resultComponent instanceof TableDisplayTool) {
((TableDisplayTool) _resultComponent).deleteSelectedRows();
}
}
/**
* Set the editable state of the cells in the displayed table.
*
* @param b set to true if editable
*/
public void setTableCellsEditable(boolean b) {
if (_resultComponent instanceof TableDisplayTool) {
((TableDisplayTool) _resultComponent).setTableCellsEditable(b);
}
}
// /**
// * @return true if this is the main application window (enables exit menu item)
// */
// public static boolean isMainWindow() {
// return _mainWindowFlag;
// }
// /**
// * @param b Set to true if this is the main application window (enables exit menu item)
// */
// public static void setMainWindow(boolean b) {
// _mainWindowFlag = b;
// }
/**
* Used to identify an empty query or result panel
*/
public class EmptyPanel extends JPanel implements QueryResultDisplay {
public EmptyPanel() {
// There is no border by default on Mac OS X, so add one
if ("Mac OS X".equals(UIManager.getLookAndFeel().getName())) {
setBorder(BorderFactory.createEtchedBorder());
}
}
@Override
public void setQueryResult(QueryResult queryResult) {
throw new RuntimeException(_I18N.getString("queryResultDisplayError"));
}
}
/**
* @return the panel used to display download progress information
*/
protected ProgressPanel getProgressPanel() {
return _progressPanel;
}
/**
* @return the stack of CatalogHistoryItems, used to go back to a previous panel
*/
protected Stack getBackStack() {
return _backStack;
}
/**
* @return the stack of CatalogHistoryItems, used to go forward to the next panel
*/
protected Stack getForwStack() {
return _forwStack;
}
/**
* Ask the user for a name, and then store the current query and display settings under that name for later use.
*/
public void storeNewQuery() {
String name = DialogUtil.input(this, _I18N.getString("enterNameForQuery"));
if (name == null || name.length() == 0) {
return;
}
storeQuery(name);
}
/**
* Store the current query and display settings under the given name for later use.
*
* @param name the query name
*/
public void storeQuery(String name) {
if (_queryComponent instanceof Storeable) {
try {
Object queryInfo = ((Storeable) _queryComponent).storeSettings();
Object resultInfo = null;
if (_resultComponent instanceof Storeable) {
resultInfo = ((Storeable) _resultComponent).storeSettings();
}
CatalogQueryItem item = new CatalogQueryItem(name, queryInfo, resultInfo);
_queryList.add(item);
} catch (Exception e) {
DialogUtil.error(e);
}
}
}
/**
* Delete the named query and display settings.
*
* @param name the query name
*/
public void deleteQuery(String name) {
_queryList.remove(name);
}
/**
* Remove all items from the query list.
*/
public void clearQueryList() {
_queryList.clear();
}
/**
* Add Query items (for previously stored queries) to the given menu, using the given listener, if supplied,
* otherwise the default (restore the query).
*
* @param menu the menu to add to
* @param l the listener
*/
public void addQueryMenuItems(JMenu menu, ActionListener l) {
Iterator it = _queryList.iterator();
if (l == null) {
while (it.hasNext()) {
menu.add((CatalogQueryItem) it.next());
}
} else {
while (it.hasNext()) {
CatalogQueryItem item = (CatalogQueryItem) it.next();
JMenuItem menuItem = new JMenuItem(item.getName());
menuItem.addActionListener(l);
menu.add(menuItem);
}
}
}
/**
* @return a StarTable for the currently displayed table, or null if no table is displayed
*/
public StarTable getStarTable() {
JComponent c = getResultComponent();
if (c instanceof TableDisplayTool) {
TableDisplayTool t = (TableDisplayTool) c;
TableQueryResult tqr = t.getTable();
if (tqr != null) {
return tqr.getStarTable();
}
}
return null;
}
/**
* Register the currently displayed query component with the given URL and table id for later reference in the
* {@link #selectTableRows} method.
*
* @param url the table URL
* @param tableId a unique value used to identify the table
*/
public void registerTable(URL url, String tableId) {
if (_queryComponent != null) {
if (tableId != null) {
_queryComponentMap.put(tableId, _queryComponent);
}
if (url != null) {
_queryComponentMap.put(url.toString(), _queryComponent);
}
}
}
/**
* Selects a single row of an identified table by row index. The table to operate on is identified by one or both of
* the table-id or url arguments. At least one of these must be supplied; if both are given they should refer to the
* same thing. Exactly what highlighting means is left to the receiving application.
*
* @param tableId identifier associated with a table
* @param url URL of a table
* @param rows array of row indexes to highlight
*/
public void selectTableRows(String tableId, URL url, int[] rows) {
JComponent queryComp = null;
if (tableId != null) {
queryComp = _queryComponentMap.get(tableId);
}
if (queryComp == null && url != null) {
queryComp = _queryComponentMap.get(url.toString());
}
if (queryComp != null) {
JComponent resultComp = _queryResultComponentMap.get(queryComp);
if (resultComp instanceof TableDisplayTool) {
TableDisplay tableDisplay = ((TableDisplayTool) resultComp).getTableDisplay();
tableDisplay.getTable().clearSelection();
for (int row : rows) {
tableDisplay.selectRow(row);
}
// Display the catalog and table, if not already displayed
CatalogHistoryItem historyItem = _historyList.getItemForQueryComponent(queryComp);
if (historyItem != null) {
historyItem.actionPerformed(null);
}
}
}
}
}