/* * 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.builder.CatalogBuilder; import ucar.nc2.ui.widget.BAMutil; import ucar.nc2.ui.widget.PopupMenu; import javax.swing.*; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeWillExpandListener; import javax.swing.tree.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; /** * A Swing widget for THREDDS clients to display catalogs in a JTree, and allows * the user to select a dataset. * When a new catalog is read, or a dataset is selected, a java.beans.PropertyChangeEvent is * thrown, see addPropertyChangeListener. * <p> * Example: * <pre> * CatalogTreeView tree = new CatalogTreeView(); tree.addPropertyChangeListener( new java.beans.PropertyChangeListener() { public void propertyChange( java.beans.PropertyChangeEvent e) { if (e.getPropertyName().equals("Selection")) { ... } else if (e.getPropertyName().equals("Dataset")) { ... } else if (e.getPropertyName().equals("Catalog")) { ... } } }); </pre> * * * Handles Catalog References internally. Catalogs are read in a background thread, which can be cancelled by the user. * <p> You probably want to use CatalogChooser or ThreddsDatasetChooser for more complete * functionality. * @see CatalogChooser * @see ThreddsDatasetChooser * * @author John Caron */ public class CatalogTreeView extends JPanel { private Catalog catalog; // state // private DatasetFilter filter = null; private boolean accessOnly = true; private String catalogURL = ""; private boolean openCatalogReferences = true; private boolean openDatasetScans = true; // ui private JTree tree; private InvCatalogTreeModel model; private boolean debugRef = false; private boolean debugTree = false; /** * Constructor. */ public CatalogTreeView() { // the catalog tree tree = new JTree(new DefaultTreeModel(new DefaultMutableTreeNode(null, false))); tree.setCellRenderer(new MyTreeCellRenderer()); tree.putClientProperty("JTree.lineStyle", "Angled"); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setToggleClickCount(1); tree.addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent e) { if (!SwingUtilities.isLeftMouseButton(e)) return; // left button only int selRow = tree.getRowForLocation(e.getX(), e.getY()); if (selRow != -1) { checkForCatref(); InvCatalogTreeNode node = (InvCatalogTreeNode) tree.getLastSelectedPathComponent(); if (node != null) firePropertyChangeEvent(new PropertyChangeEvent(this, "Selection", null, node.ds)); } if ((selRow != -1) && (e.getClickCount() == 2)) { acceptSelected(); } } }); tree.addTreeWillExpandListener( new TreeWillExpandListener() { public void treeWillCollapse(TreeExpansionEvent evt) { } public void treeWillExpand(TreeExpansionEvent evt) { InvCatalogTreeNode node = (InvCatalogTreeNode) evt.getPath().getLastPathComponent(); if (node.ds instanceof CatalogRef) { CatalogRef catref = (CatalogRef) node.ds; if (!catref.isRead()) { if (openCatalogReferences) // && (openDatasetScans || !isDatasetScan)) node.readCatref(); } } } }); PopupMenu varPopup = new PopupMenu(tree, "Options"); varPopup.addAction("Open all children", new AbstractAction() { public void actionPerformed(ActionEvent e) { InvCatalogTreeNode node = (InvCatalogTreeNode) tree.getLastSelectedPathComponent(); if (node != null) open(node, true); } }); varPopup.addAction("Open one level of children", new AbstractAction() { public void actionPerformed(ActionEvent e) { InvCatalogTreeNode node = (InvCatalogTreeNode) tree.getLastSelectedPathComponent(); if (node != null) { node.makeChildren( true); for (InvCatalogTreeNode child : node.children) tree.expandPath(makeTreePath(child)); } } }); ToolTipManager.sharedInstance().registerComponent(tree); // layout setLayout(new BorderLayout()); add(new JScrollPane(tree), BorderLayout.CENTER); } /** * Set whether catalog references are opened. default is true. */ public void setOpenCatalogReferences( boolean openCatalogReferences) { this.openCatalogReferences = openCatalogReferences; } /** * Set whether catalog references from dataset scans are opened. default is true. */ public void setOpenDatasetScans( boolean openDatasetScans) { this.openDatasetScans = openDatasetScans; } private void firePropertyChangeEvent(DatasetNode ds) { PropertyChangeEvent event = new PropertyChangeEvent(this, "Dataset", null, ds); firePropertyChangeEvent( event); } /** * Fires a PropertyChangeEvent: * <ul><li> when a new catalog is read and displayed: * propertyName = "Catalog", getNewValue() = catalog URL string * <li> when a node is selected: * propertyName = "Selection", getNewValue() = InvDataset chosen. * <li> when a node is double-clicked: * propertyName = "Dataset", getNewValue() = InvDataset chosen. * <li> when a TreeNode is added: * propertyName = "TreeNode", getNewValue() = InvDataset added. * </ul> */ private void firePropertyChangeEvent(PropertyChangeEvent event) { firePropertyChange(event.getPropertyName(), event.getOldValue(), event.getNewValue()); } /** 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) { this.accessOnly = accessOnly; } /** Get the current catalog. */ public Catalog getCatalog() { return catalog; } /** Get the URL of the current catalog. */ public String getCatalogURL() { return catalogURL; } private void setCatalogURL( String catalogURL) { this.catalogURL = catalogURL; } /** * Get the currently selected InvDataset. * @return selected InvDataset, or null if none. */ public DatasetNode getSelectedDataset() { InvCatalogTreeNode tnode = getSelectedNode(); return tnode == null ? null : tnode.ds; } private InvCatalogTreeNode getSelectedNode() { Object node = tree.getLastSelectedPathComponent(); if (node == null) return null; if ( !(node instanceof InvCatalogTreeNode)) return null; return (InvCatalogTreeNode) node; } /** * Set the currently selected InvDataset. * @param ds select this InvDataset, must be already in the tree. * LOOK does this work ?? doesnt throw event */ public void setSelectedDataset(Dataset ds) { if (ds == null) return; TreePath path = makePath(ds); if (path == null) return; tree.setSelectionPath( path); tree.scrollPathToVisible( path); } /** * Create the TreePath corresponding to the InvDataset. * @param ds the InvDataset, must be already in the tree. * @return the corresponding TreePath. */ TreePath makePath(Dataset ds) { return null; //TreeNode node = (TreeNode) ds.getUserProperty("TreeNode"); // LOOK //return makeTreePath( node); } /** * Create the TreePath corresponding to the given TreeNode. * @param node the TreeNode; already in the Tree. * @return the corresponding TreePath. */ TreePath makeTreePath(TreeNode node) { ArrayList<TreeNode> path = new ArrayList<>(); path.add( node); TreeNode parent = node.getParent(); while (parent != null) { path.add(0, parent); parent = parent.getParent(); } Object[] paths = path.toArray(); return new TreePath(paths); } /** * Open all nodes of the tree. * @param includeCatref open catrefs? */ public void openAll( boolean includeCatref) { if (catalog == null) return; open( (InvCatalogTreeNode) model.getRoot(), includeCatref); tree.repaint(); } private void open( InvCatalogTreeNode node, boolean includeCatref) { if (node == null) return; node.makeChildren( includeCatref); tree.expandPath(makeTreePath(node)); //System.out.printf("open %s%n", node); Enumeration e = node.children(); while (e.hasMoreElements()) { InvCatalogTreeNode child = (InvCatalogTreeNode) e.nextElement(); //System.out.printf(" child %s%n", child); open(child, includeCatref); } } void checkForCatref() { DatasetNode ds = getSelectedDataset(); if (ds == null) return; if ( (ds instanceof CatalogRef)) { CatalogRef catref = (CatalogRef) ds; //boolean isDatasetScan = catref.isDatasetScan(); if (!catref.isRead()) { if (openCatalogReferences) { // && (openDatasetScans || !isDatasetScan)) { InvCatalogTreeNode tnode = getSelectedNode(); if (tnode != null) tnode.readCatref(); } } } } void acceptSelected() { DatasetNode dsn = getSelectedDataset(); if (dsn == null) return; if (accessOnly && dsn instanceof Dataset) { Dataset ds = (Dataset) dsn; if (!ds.hasAccess()) return; } //setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); firePropertyChangeEvent( dsn); //setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } /** * Set the InvCatalog to display. * The catalog is read asynchronously and displayed if successfully read. * You must use a PropertyChangeEventListener to be notified if successful. * * @param location The URL of the InvCatalog. */ public void setCatalog(String location) { CatalogBuilder builder = new CatalogBuilder(); try { Catalog cat = builder.buildFromLocation(location); setCatalog(cat); } catch (Exception ioe) { JOptionPane.showMessageDialog(this, "Error opening catalog location " + location+" err="+builder.getErrorMessage()); } } public void redisplay() { setCatalog( catalog); } /** * Set the catalog to be displayed. If ok, then a "Catalog" PropertyChangeEvent is sent. * @param catalog to be displayed */ public void setCatalog(Catalog catalog) { if (catalog == null) return; String catalogName = catalog.getBaseURI().toString(); this.catalog = catalog; // send catalog event setCatalogURL( catalogName); // display tree // this sends TreeNode events model = new InvCatalogTreeModel(catalog); tree.setModel( model); // debug if (debugTree) { System.out.println("*** catalog/showJTree ="); showNode(tree.getModel(), tree.getModel().getRoot()); System.out.println("*** "); } // look for a specific dataset int pos = catalogName.indexOf('#'); if (pos >= 0) { String id = catalogName.substring( pos+1); Dataset dataset = catalog.findDatasetByID( id); if (dataset != null) { setSelectedDataset(dataset); firePropertyChangeEvent( new PropertyChangeEvent(this, "Selection", null, dataset)); } } // send catalog event firePropertyChangeEvent(new PropertyChangeEvent(this, "Catalog", null, catalogName)); } // debug private void showNode(TreeModel tree, Object node) { if (node == null) return; InvCatalogTreeNode tnode = (InvCatalogTreeNode) node; DatasetNode cp = tnode.ds; System.out.println(" node= "+cp.getName()+" leaf= "+tree.isLeaf( node)); for (int i=0; i< tree.getChildCount(node); i++) showNode(tree, tree.getChild(node, i)); } // make an InvCatalog into a TreeModel private class InvCatalogTreeModel extends javax.swing.tree.DefaultTreeModel { InvCatalogTreeModel (DatasetNode top) { super( new InvCatalogTreeNode( null, top), false); } } // make an InvDataset into a TreeNode // defer opening catalogRefs private class InvCatalogTreeNode implements javax.swing.tree.TreeNode, CatalogBuilder.Callback { DatasetNode ds; private InvCatalogTreeNode parent; private ArrayList<InvCatalogTreeNode> children = null; private boolean isReading = false; InvCatalogTreeNode( InvCatalogTreeNode parent, DatasetNode ds) { this.parent = parent; this.ds = ds; // ds.setUserProperty("TreeNode", this); if (debugTree) System.out.println("new="+ds.getName()+" "); firePropertyChangeEvent(new PropertyChangeEvent(this, "TreeNode", null, ds)); } public Enumeration children() { if (debugTree) System.out.println("children="+ds.getName()+" "); if (children == null) return Collections.enumeration( new ArrayList<InvCatalogTreeNode>()); return Collections.enumeration(children); } public boolean getAllowsChildren() { return true; } public TreeNode getChildAt(int index) { if (debugTree) System.out.println("getChildAt="+ds.getName()+" "+index); return children.get(index); } public int getChildCount() { if (children == null) makeChildren( false); if (children == null) return 0; return children.size(); } void makeChildren(boolean force) { if (children == null) { if (ds instanceof CatalogRef) { CatalogRef catref = (CatalogRef) ds; if (debugRef) System.out.println("getChildCount on catref="+ds.getName()+" " + catref.isRead()+" "+isReading); if (!catref.isRead() && !force) { // dont open it until explicitly asked return; } } if (debugRef) System.out.println("getChildCount on ds="+ds.getName()+" "); children = new ArrayList<>(); for (Dataset nested : ds.getDatasets()) children.add( new InvCatalogTreeNode( this, nested)); } } void readCatref() { CatalogRef catref = (CatalogRef) ds; if (debugRef) System.out.println("readCatref on ="+ds.getName()+" "+isReading); if (!isReading) { isReading = true; CatalogBuilder builder = new CatalogBuilder(); try { Catalog cat = builder.buildFromCatref(catref); if (builder.hasFatalError() || cat == null) { javax.swing.JOptionPane.showMessageDialog(CatalogTreeView.this, "Error reading catref " + catref.getName() + " err=" + builder.getErrorMessage()); return; } setCatalog(cat); } catch (IOException e) { javax.swing.JOptionPane.showMessageDialog(CatalogTreeView.this, "Error reading catref " + catref.getName()+" err="+e.getMessage()); } } } public int getIndex(TreeNode child) { if (debugTree) System.out.println("getIndex="+ds.getName()+" "+child); return children.indexOf(child); } public TreeNode getParent() { return parent; } public boolean isLeaf() { if (debugTree) System.out.println("isLeaf="+ds.getName()); if (ds instanceof CatalogRef) { return false; } return !ds.hasNestedDatasets(); } public String toString() { return ds.getName(); } public void setCatalog(Catalog catalog) { children = new ArrayList<>(); java.util.List<Dataset> datasets = catalog.getDatasets(); if (datasets.size() == 1) { Dataset top = datasets.get(0); if (top.getName().equalsIgnoreCase(ds.getName())) { ds = top; // ?? datasets = top.getDatasets(); } } int[] childIndices = new int[ datasets.size()]; for (int count = 0; count < datasets.size(); count++) { children.add( new InvCatalogTreeNode( this, datasets.get(count))); childIndices[count] = count; } model.nodesWereInserted(this, childIndices); // model.nodeStructureChanged( this); tree.expandPath( makeTreePath(this)); if (debugRef) System.out.println("model.nodeStructureChanged on "+this); isReading = false; } public void failed() { if (debugRef) System.out.println("failed called on "+this); isReading = false; } } // this is to get the inline documentation into a tooltip private static class MyTreeCellRenderer extends javax.swing.tree.DefaultTreeCellRenderer { ImageIcon refIcon, refReadIcon, gridIcon, imageIcon, dqcIcon, dsScanIcon; public MyTreeCellRenderer() { refIcon = BAMutil.getIcon( "CatalogRef", true); refReadIcon = BAMutil.getIcon( "CatalogRefRead", true); gridIcon = BAMutil.getIcon( "GridData", true); imageIcon = BAMutil.getIcon( "ImageData", true); dqcIcon = BAMutil.getIcon( "DQCData", true); dsScanIcon = BAMutil.getIcon( "DatasetScan", true); } public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,boolean leaf, int row, boolean hasFocus) { Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); if (value instanceof InvCatalogTreeNode) { InvCatalogTreeNode node = (InvCatalogTreeNode) value; DatasetNode ds = node.ds; String doc = ds.toString(); if (doc != null) ((JComponent)c).setToolTipText( doc); if (ds instanceof CatalogRef) { if (((CatalogRef)ds).isRead()) setIcon(refReadIcon); else setIcon(refIcon); } else if (leaf) { setIcon( gridIcon); } } return c; } } }