/*
* Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata
*
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package thredds.ui.catalog;
import thredds.client.catalog.*;
import thredds.client.catalog.writer.DatasetHtmlWriter;
import ucar.nc2.ui.widget.*;
import ucar.util.prefs.PreferencesExt;
import ucar.util.prefs.ui.ComboBox;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URISyntaxException;
import java.util.Formatter;
/**
* A Swing widget for THREDDS clients to access and choose from Dataset Inventory catalogs.
* State is maintained in a ucar.util.Preferences store.
* <p>
* A list of catalogs is kept in a ComboBox, and the user can choose from them and add new ones.
* When a catalog is chosen, its contents are displayed in a CatalogTreeView. As the datasets
* are browsed, the metadata is displayed in an HtmlBrowser widget.
* <p>
* When a new dataset is selected, a java.beans.PropertyChangeEvent is thrown, see
* addPropertyChangeListener.
* <p>
* Use Example:
*
* <pre>
// create widgets
catalogChooser = new thredds.ui.CatalogChooser( prefs);
catalogChooserDialog = catalogChooser.makeDialog(rootPaneContainer, "Open THREDDS dataset", true);
// listen for selection
catalogChooser.addPropertyChangeListener( new java.beans.PropertyChangeListener() {
public void propertyChange( java.beans.PropertyChangeEvent e) {
if (e.getPropertyName().equals("Dataset")) {
..
}
}
});
// popup dialog
catalogChooserDialog.show();
* </pre>
*
* You can use the CatalogChooser alone, wrap it into a JDialog for popping up, or
* use a ThreddsDatasetChooser instead, for a more complete interface.
*
* @see ThreddsDatasetChooser
*
* @author John Caron
*/
public class CatalogChooser extends JPanel {
private static final String HDIVIDER = "HSplit_Divider";
private static final String FILECHOOSER_DEFAULTDIR = "FileChooserDefaultDir";
private PreferencesExt prefs;
private String eventType = null;
// ui
private ComboBox catListBox;
private CatalogTreeView tree;
private HtmlBrowser htmlViewer;
private FileManager fileChooser;
private JSplitPane split;
private JLabel statusLabel;
private JPanel buttPanel;
private JLabel sourceText;
private RootPaneContainer parent = null;
private boolean datasetEvents = true;
private boolean catrefEvents = false;
private String currentURL = "";
//private boolean catgenShow = true;
private FileManager catgenFileChooser;
private boolean debugEvents = false;
//private boolean debugTree = false;
private boolean showHTML = false;
/**
* Constructor, with control over whether a comboBox of previous catalogs is shown.
*
* @param prefs persistent storage, may be null.
* @param showComboChooser comboBox persists catalog URLs
* @param showOpenButton show the "open" button.
* @param showFileChooser show a FileChooser (must have showComboChooser true)
*/
public CatalogChooser(PreferencesExt prefs, boolean showComboChooser,
boolean showOpenButton, boolean showFileChooser) {
this.prefs = prefs;
JPanel topPanel = null;
if (showComboChooser) {
// combo box holds the catalogs
catListBox = new ComboBox(prefs);
// top panel buttons
JButton connectButton = new JButton("Connect");
connectButton.setToolTipText("read this catalog");
connectButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
String catalogURL = (String) catListBox.getSelectedItem();
tree.setCatalog(catalogURL.trim()); // will get "Catalog" property change event if ok
}
});
JPanel topButtons = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
topButtons.add(connectButton);
topPanel = new JPanel(new BorderLayout());
topPanel.add(new JLabel("Catalog URL"), BorderLayout.WEST);
topPanel.add(catListBox, BorderLayout.CENTER);
topPanel.add(topButtons, BorderLayout.EAST);
if (showFileChooser) {
// add a file chooser
PreferencesExt fcPrefs = (PreferencesExt) prefs.node("FileManager");
FileFilter[] filters = new FileFilter[] {new FileManager.XMLExtFilter()};
fileChooser = new FileManager(null, null, filters, fcPrefs);
AbstractAction fileAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
String filename = fileChooser.chooseFilename();
if (filename == null) return;
tree.setCatalog("file:"+filename);
}
};
BAMutil.setActionProperties( fileAction, "FileChooser", "open Local catalog...", false, 'L', -1);
BAMutil.addActionToContainer( topButtons, fileAction);
// a file chooser used for catgen on a directory
PreferencesExt catgenPrefs = (PreferencesExt) prefs.node("CatgenFileManager");
catgenFileChooser = new FileManager(null, null, null, catgenPrefs);
catgenFileChooser.getFileChooser().setFileSelectionMode( JFileChooser.FILES_AND_DIRECTORIES);
catgenFileChooser.getFileChooser().setDialogTitle( "Run CatGen on Directory");
AbstractAction srcEditAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
TextGetPutPane sourceEditor = new TextGetPutPane(null);
IndependentWindow sourceEditorWindow = new IndependentWindow( "Source", BAMutil.getImage("thredds"), sourceEditor);
sourceEditorWindow.setBounds(new Rectangle(50, 50, 725, 450));
sourceEditorWindow.show();
}
};
BAMutil.setActionProperties(srcEditAction, "Edit", "Source Editor", false, 'E', -1);
BAMutil.addActionToContainer(topButtons, srcEditAction);
}
}
// the catalog tree
tree = new CatalogTreeView();
tree.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
public void propertyChange(java.beans.PropertyChangeEvent e) {
if (debugEvents)
System.out.println("CatalogChooser propertyChange name=" +e.getPropertyName() + "=");
if (e.getPropertyName().equals("Catalog")) {
String catalogURL = (String) e.getNewValue();
setCurrentURL(catalogURL);
if (catListBox != null)
catListBox.addItem(catalogURL);
firePropertyChangeEvent(e);
} else if (e.getPropertyName().equals("Selection")) {
DatasetNode ds = tree.getSelectedDataset();
if (ds == null)
return;
if (ds instanceof Dataset)
showDatasetInfo((Dataset)ds);
if (ds instanceof CatalogRef) {
CatalogRef ref = (CatalogRef) ds;
String href = ref.getXlinkHref();
if (href != null) {
try {
java.net.URI uri = ref.getParentCatalog().resolveUri(href);
setCurrentURL(uri.toString());
} catch (URISyntaxException ee) {
throw new RuntimeException(ee);
}
}
}
else if (ds.getParent() == null) { // top
setCurrentURL(tree.getCatalogURL());
}
} else if (e.getNewValue() instanceof Dataset) { // Dataset or File
firePropertyChangeEvent((Dataset) e.getNewValue(), e.getPropertyName());
}
}
});
// htmlViewer Viewer
htmlViewer = new HtmlBrowser();
// listen for selection
htmlViewer.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
public void propertyChange(java.beans.PropertyChangeEvent e) {
if (e.getPropertyName().equals("datasetURL")) {
String datasetURL = (String) e.getNewValue();
if (debugEvents) System.out.println("***datasetURL= " + datasetURL);
DatasetNode node = tree.getSelectedDataset();
if (node == null) return;
if (node instanceof Dataset) {
Dataset dataset = (Dataset) node;
Access access = dataset.findAccess(datasetURL);
firePropertyChangeEvent(new PropertyChangeEvent(this, "InvAccess", null, access));
}
} else if (e.getPropertyName().equals("catrefURL")) {
String urlString = (String) e.getNewValue();
if (debugEvents) System.out.println("***catrefURL= " + urlString);
tree.setCatalog(urlString.trim());
}
}
});
// splitter
split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false, tree, htmlViewer);
if (prefs != null)
split.setDividerLocation(prefs.getInt(HDIVIDER, 400));
//status label
JPanel statusPanel = new JPanel(new BorderLayout());
statusLabel = new JLabel("not connected");
sourceText = new JLabel();
statusPanel.add(statusLabel, BorderLayout.WEST);
statusPanel.add(sourceText, BorderLayout.EAST);
// button panel
buttPanel = new JPanel();
JButton openfileButton = new JButton("Open File");
buttPanel.add(openfileButton, null);
openfileButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
eventType = "File";
try {
tree.acceptSelected();
} catch (Throwable t) {
t.printStackTrace();
JOptionPane.showMessageDialog(CatalogChooser.this, "ERROR "+t.getMessage());
} finally {
eventType = null;
}
}
});
JButton openCoordButton = new JButton("Open CoordSys");
buttPanel.add(openCoordButton, null);
openCoordButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
eventType = "CoordSys";
try {
tree.acceptSelected();
} catch (Throwable t) {
t.printStackTrace();
JOptionPane.showMessageDialog(CatalogChooser.this, "ERROR "+t.getMessage());
} finally {
eventType = null;
}
}
});
JButton acceptButton = new JButton("Open dataset");
buttPanel.add(acceptButton, null);
acceptButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
eventType = "Dataset";
try {
tree.acceptSelected();
} catch (Throwable t) {
t.printStackTrace();
JOptionPane.showMessageDialog(CatalogChooser.this, "ERROR "+t.getMessage());
} finally {
eventType = null;
}
}
});
// put it all together
setLayout(new BorderLayout());
if (showComboChooser)
add(topPanel, BorderLayout.NORTH);
add(split, BorderLayout.CENTER);
if (showOpenButton) {
JPanel botPanel = new JPanel(new BorderLayout());
botPanel.add(buttPanel, BorderLayout.NORTH);
botPanel.add(statusPanel, BorderLayout.SOUTH);
add(botPanel, BorderLayout.SOUTH);
}
}
private void makeSourceEditWindow() {
TextGetPutPane sourceEditor = new TextGetPutPane(null);
IndependentWindow sourceEditorWindow = new IndependentWindow( "Source", BAMutil.getImage("thredds"), sourceEditor);
sourceEditorWindow.setBounds(new Rectangle(50, 50, 725, 450));
sourceEditorWindow.show();
}
/**
* Save persistent state.
*/
public void save() {
if (catListBox != null) catListBox.save();
if (prefs != null) {
if (fileChooser != null)
fileChooser.save();
if (catgenFileChooser != null)
catgenFileChooser.save();
prefs.putInt(HDIVIDER, split.getDividerLocation());
}
}
private void firePropertyChangeEvent(Dataset ds, String oldPropertyName) {
String propertyName = (eventType != null) ? eventType : oldPropertyName;
PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, null, ds);
firePropertyChangeEvent( event);
}
/**
* Fires a PropertyChangeEvent:
* <ul><li> propertyName = "Catalog", getNewValue() = catalog URL string
* <li> propertyName = "Dataset" or "File", getNewValue() = InvDataset chosen.
* <li> propertyName = "InvAccess" getNewValue() = InvAccess chosen.
* <li> propertyName = "catrefURL", getNewValue() = catref URL was chosen.
* </ul>
*/
private void firePropertyChangeEvent(PropertyChangeEvent event) {
firePropertyChange(event.getPropertyName(), event.getOldValue(), event.getNewValue());
}
/**
* Add this button to the button panel.
* @param b button to add
*/
public void addButton( JButton b) {
buttPanel.add(b, null);
buttPanel.revalidate();
}
// public void useDQCpopup( boolean use) { tree.useDQCpopup( use); }
/** Whether to throw events only if dataset has an Access.
* @param accessOnly if true, throw events only if dataset has an Access
*/
public void setAccessOnly( boolean accessOnly) { tree.setAccessOnly(accessOnly); }
/** Whether to throw events if catref URL was chosen catref URL was chosen in HtmlViewer (default false).
*/
public void setCatrefEvents( boolean catrefEvents) { this.catrefEvents = catrefEvents; }
/** Whether to throw events if dataset URL was chosen in HtmlViewer (default true).
*/
public void setDatasetEvents( boolean datasetEvents) { this.datasetEvents = datasetEvents; }
/**
* Set the factory to create catalogs.
* If you do not set this, it will use the default factory.
* @param factory : read XML with this factory
*
public void setCatalogFactory(InvCatalogFactory factory) { tree.setCatalogFactory(factory); } */
/**
* Use this to set the string value in the combo box
* @param item
*/
public void setSelectedItem( String item) {
if (catListBox != null)
catListBox.setSelectedItem( item);
}
/**
* Set the currently selected InvDataset.
* @param ds select this InvDataset, must be already in the tree.
*/
public void setSelectedDataset(Dataset ds) {
tree.setSelectedDataset( ds);
showDatasetInfo(ds);
}
public DatasetNode getSelectedDataset() {
return tree.getSelectedDataset();
}
/**
* Get the current catalog being shown.
* @return current catalog, or null.
*/
public Catalog getCurrentCatalog() { return tree.getCatalog(); }
/**
* Get the TreeView component.
* @return the TreeView component.
*/
public CatalogTreeView getTreeView() { return tree; }
/**
* Get the current URL string. This may be the top catalog, or a catalogRef, depending on
* what was last selected. Used to implement the " showSource" debugging tool.
* @return current URL string
*/
public String getCurrentURL() { return currentURL; }
private void setCurrentURL( String currentURL) {
this.currentURL = currentURL;
sourceText.setText( currentURL);
statusLabel.setText("Connected...");
}
/**
* Set the current catalog.
*/
public void setCatalog(Catalog catalog) {
tree.setCatalog( catalog);
}
/**
* Set the current catalog with a string URL.
* May be of form catalog#datasetId
*/
public void setCatalog(String catalogURL) {
tree.setCatalog(catalogURL.trim());
}
private void showDatasetInfo(Dataset ds) {
if (ds == null) return;
Formatter sbuff = new Formatter();
DatasetHtmlWriter writer = new DatasetHtmlWriter();
writer.writeHtmlDescription(sbuff, ds, true, false, datasetEvents, catrefEvents, true);
if (showHTML) System.out.println("HTML=\n"+sbuff);
htmlViewer.setContent( ds.getName(), sbuff.toString());
}
/** Wrap this in a JDialog component.
*
* @param parent JFrame (application) or JApplet (applet) or null
* @param title dialog window title
* @param modal is modal
*/
public JDialog makeDialog( RootPaneContainer parent, String title, boolean modal) {
this.parent = parent;
return new Dialog( parent, title, modal);
}
private class Dialog extends JDialog {
private Dialog(RootPaneContainer parent, String title, boolean modal) {
super(parent instanceof Frame ? (Frame) parent : null, title, modal);
// L&F may change
UIManager.addPropertyChangeListener( new PropertyChangeListener() {
public void propertyChange( PropertyChangeEvent e) {
if (e.getPropertyName().equals("lookAndFeel"))
SwingUtilities.updateComponentTreeUI( CatalogChooser.Dialog.this);
}
});
// add a dismiss button
JButton dismissButton = new JButton("Dismiss");
buttPanel.add(dismissButton, null);
dismissButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
setVisible(false);
}
});
// add it to contentPane
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add( CatalogChooser.this, BorderLayout.CENTER);
pack();
}
}
}