package abbot.editor; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.tree.*; import java.net.URL; import abbot.finder.*; import abbot.util.*; /** Provides a Tree view into a given Hierarchy of components. Refreshes its display based on changes to the default AWT hierarchy. The selection path is preserved to the extent possible across changes in the hierarchy. */ public class ComponentTree extends JTree { private Hierarchy hierarchy; private ComponentNode root; private HierarchyMonitor monitor; private DefaultTreeModel model; private transient boolean ignoreSelectionChanges; /** Hash of class name to icon. */ private ComponentTreeIcons icons = new ComponentTreeIcons(); /** Supports optionally suppressing selection notifications while the hierarchy is reloading. */ private class SelectionModel extends DefaultTreeSelectionModel { private transient boolean settingSelection; protected void fireValueChanged(TreeSelectionEvent e) { if (settingSelection || ignoreSelectionChanges) return; super.fireValueChanged(e); } } private class HierarchyMonitor implements AWTEventListener { public void eventDispatched(AWTEvent ev) { switch(ev.getID()) { case WindowEvent.WINDOW_OPENED: case WindowEvent.WINDOW_CLOSED: case ComponentEvent.COMPONENT_SHOWN: { Component c = ((ComponentEvent)ev).getComponent(); if (hierarchy.contains(c)) { reload(hierarchy.getParent(c)); } break; } case ContainerEvent.COMPONENT_ADDED: case ContainerEvent.COMPONENT_REMOVED: { ContainerEvent e = (ContainerEvent)ev; Component c = e.getComponent(); Window w = AWT.getWindow(c); // CellRendererPanes send out these events in swarms, // every time they repaint a cell, so ignore them if (!(c instanceof CellRendererPane) && hierarchy.contains(c) && w != null && hierarchy.contains(w)) { // TODO: delay reload on tooltip hide to allow access to // the tooltip; otherwise the tooltip will go away and be // removed from the tree as soon as the cursor moves to // the tree to manipulate it. reload(c); } break; } default: break; } } } private class Renderer extends DefaultTreeCellRenderer { public Renderer() { URL url = getClass().getResource("icons/component.gif"); if (url != null) { setLeafIcon(new ImageIcon(url)); } url = getClass().getResource("icons/container.gif"); if (url != null) { setOpenIcon(new ImageIcon(url)); setClosedIcon(new ImageIcon(url)); } } public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean exp, boolean leaf, int row, boolean focus) { Component c = super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, focus); if (c instanceof JLabel) { JLabel label = (JLabel)c; Icon icon = null; if (value == root) { URL url = getClass(). getResource("icons/hierarchy-root.gif"); if (url != null) icon = new ImageIcon(url); } else { Component c1 = ((ComponentNode)value).getComponent(); if (c1 != null) { icon = icons.getIcon(c1.getClass()); } } if (icon != null) { label.setIcon(icon); } } return c; } } public ComponentTree(Hierarchy h) { super(new ComponentNode(h)); setSelectionModel(new SelectionModel()); setCellRenderer(new Renderer()); setShowsRootHandles(true); setScrollsOnExpand(true); hierarchy = h; model = (DefaultTreeModel)getModel(); root = (ComponentNode)model.getRoot(); monitor = new HierarchyMonitor(); long mask = ContainerEvent.CONTAINER_EVENT_MASK | ComponentEvent.COMPONENT_EVENT_MASK | WindowEvent.WINDOW_EVENT_MASK; new WeakAWTEventListener(monitor, mask); } public void setHierarchy(Hierarchy h) { hierarchy = h; root.reload(h); reload(); } /** Set the current selection path, ensuring that it is visible. */ public void setSelectionPath(TreePath path) { super.setSelectionPath(path); makeVisible(path); Rectangle rect = getPathBounds(path); if (rect != null) scrollRectToVisible(rect); } /** Returns the path to the given component. If the component does not * exist in the current hierarchy, returns as much of its parent path as * does exist. */ public TreePath getPath(Component comp) { return root.getPath(comp); } /** Reloads the entire hierarchy. */ public void reload() { reload(null); } /** Reloads the hierarchy starting at the given component. */ public void reload(Component comp) { ComponentNode node = comp != null ? root.getNode(comp) : root; TreePath path = getSelectionPath(); Component selected = path == null ? null : ((ComponentNode)path.getLastPathComponent()).getComponent(); if (node == null) node = root; // suppress selection change notifications until we're certain we // can't restore the original selection. ignoreSelectionChanges = true; node.reload(); model.reload(node); if (selected != null) { TreePath newPath = root.getPath(selected); // if the selection is exactly the same as it was before the // reload, suppress selection change notifications ignoreSelectionChanges = path.equals(newPath); setSelectionPath(newPath); } ignoreSelectionChanges = false; } }