001: package abbot.editor;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005: import javax.swing.*;
006: import javax.swing.event.*;
007: import javax.swing.tree.*;
008: import java.net.URL;
009:
010: import abbot.finder.*;
011: import abbot.util.*;
012:
013: /** Provides a Tree view into a given Hierarchy of components. Refreshes its
014: display based on changes to the default AWT hierarchy.
015: The selection path is preserved to the extent possible across changes in
016: the hierarchy.
017: */
018: public class ComponentTree extends JTree {
019: private Hierarchy hierarchy;
020: private ComponentNode root;
021: private HierarchyMonitor monitor;
022: private DefaultTreeModel model;
023: private transient boolean ignoreSelectionChanges;
024: /** Hash of class name to icon. */
025: private ComponentTreeIcons icons = new ComponentTreeIcons();
026:
027: /** Supports optionally suppressing selection notifications while
028: the hierarchy is reloading.
029: */
030: private class SelectionModel extends DefaultTreeSelectionModel {
031: private transient boolean settingSelection;
032:
033: protected void fireValueChanged(TreeSelectionEvent e) {
034: if (settingSelection || ignoreSelectionChanges)
035: return;
036: super .fireValueChanged(e);
037: }
038: }
039:
040: private class HierarchyMonitor implements AWTEventListener {
041: public void eventDispatched(AWTEvent ev) {
042: switch (ev.getID()) {
043: case WindowEvent.WINDOW_OPENED:
044: case WindowEvent.WINDOW_CLOSED:
045: case ComponentEvent.COMPONENT_SHOWN: {
046: Component c = ((ComponentEvent) ev).getComponent();
047: if (hierarchy.contains(c)) {
048: reload(hierarchy.getParent(c));
049: }
050: break;
051: }
052: case ContainerEvent.COMPONENT_ADDED:
053: case ContainerEvent.COMPONENT_REMOVED: {
054: ContainerEvent e = (ContainerEvent) ev;
055: Component c = e.getComponent();
056: Window w = AWT.getWindow(c);
057: // CellRendererPanes send out these events in swarms,
058: // every time they repaint a cell, so ignore them
059: if (!(c instanceof CellRendererPane)
060: && hierarchy.contains(c) && w != null
061: && hierarchy.contains(w)) {
062: // TODO: delay reload on tooltip hide to allow access to
063: // the tooltip; otherwise the tooltip will go away and be
064: // removed from the tree as soon as the cursor moves to
065: // the tree to manipulate it.
066: reload(c);
067: }
068: break;
069: }
070: default:
071: break;
072: }
073: }
074: }
075:
076: private class Renderer extends DefaultTreeCellRenderer {
077: public Renderer() {
078: URL url = getClass().getResource("icons/component.gif");
079: if (url != null) {
080: setLeafIcon(new ImageIcon(url));
081: }
082: url = getClass().getResource("icons/container.gif");
083: if (url != null) {
084: setOpenIcon(new ImageIcon(url));
085: setClosedIcon(new ImageIcon(url));
086: }
087: }
088:
089: public Component getTreeCellRendererComponent(JTree tree,
090: Object value, boolean sel, boolean exp, boolean leaf,
091: int row, boolean focus) {
092: Component c = super .getTreeCellRendererComponent(tree,
093: value, sel, exp, leaf, row, focus);
094: if (c instanceof JLabel) {
095: JLabel label = (JLabel) c;
096: Icon icon = null;
097: if (value == root) {
098: URL url = getClass().getResource(
099: "icons/hierarchy-root.gif");
100: if (url != null)
101: icon = new ImageIcon(url);
102: } else {
103: Component c1 = ((ComponentNode) value)
104: .getComponent();
105: if (c1 != null) {
106: icon = icons.getIcon(c1.getClass());
107: }
108: }
109: if (icon != null) {
110: label.setIcon(icon);
111: }
112: }
113: return c;
114: }
115: }
116:
117: public ComponentTree(Hierarchy h) {
118: super (new ComponentNode(h));
119: setSelectionModel(new SelectionModel());
120: setCellRenderer(new Renderer());
121: setShowsRootHandles(true);
122: setScrollsOnExpand(true);
123:
124: hierarchy = h;
125: model = (DefaultTreeModel) getModel();
126: root = (ComponentNode) model.getRoot();
127:
128: monitor = new HierarchyMonitor();
129: long mask = ContainerEvent.CONTAINER_EVENT_MASK
130: | ComponentEvent.COMPONENT_EVENT_MASK
131: | WindowEvent.WINDOW_EVENT_MASK;
132: new WeakAWTEventListener(monitor, mask);
133: }
134:
135: public void setHierarchy(Hierarchy h) {
136: hierarchy = h;
137: root.reload(h);
138: reload();
139: }
140:
141: /** Set the current selection path, ensuring that it is visible. */
142: public void setSelectionPath(TreePath path) {
143: super .setSelectionPath(path);
144: makeVisible(path);
145: Rectangle rect = getPathBounds(path);
146: if (rect != null)
147: scrollRectToVisible(rect);
148: }
149:
150: /** Returns the path to the given component. If the component does not
151: * exist in the current hierarchy, returns as much of its parent path as
152: * does exist.
153: */
154: public TreePath getPath(Component comp) {
155: return root.getPath(comp);
156: }
157:
158: /** Reloads the entire hierarchy. */
159: public void reload() {
160: reload(null);
161: }
162:
163: /** Reloads the hierarchy starting at the given component. */
164: public void reload(Component comp) {
165: ComponentNode node = comp != null ? root.getNode(comp) : root;
166: TreePath path = getSelectionPath();
167: Component selected = path == null ? null
168: : ((ComponentNode) path.getLastPathComponent())
169: .getComponent();
170: if (node == null)
171: node = root;
172:
173: // suppress selection change notifications until we're certain we
174: // can't restore the original selection.
175: ignoreSelectionChanges = true;
176: node.reload();
177: model.reload(node);
178: if (selected != null) {
179: TreePath newPath = root.getPath(selected);
180: // if the selection is exactly the same as it was before the
181: // reload, suppress selection change notifications
182: ignoreSelectionChanges = path.equals(newPath);
183: setSelectionPath(newPath);
184: }
185: ignoreSelectionChanges = false;
186: }
187: }
|