package abbot.editor; import java.awt.*; import javax.swing.*; import java.util.*; import abbot.util.AWT; import abbot.finder.Hierarchy; /** Provides a condensed, more easily readable version of the original hierarchy. */ // TODO: figure out how to more cleanly put popups under their invokers (maybe // have to scan the whole hierarchy each time). public class CompactHierarchy implements Hierarchy { private boolean compact = true; private Hierarchy hierarchy; public CompactHierarchy(Hierarchy original) { this.hierarchy = original; } public void setCompact(boolean compact) { this.compact = compact; } public boolean isCompact() { return compact; } public Collection getRoots() { return hierarchy.getRoots(); } public Container getParent(Component c) { // In the component hierarchy, show popup menus directly beneath their // invoker if (compact && c instanceof JPopupMenu) { Component invoker = ((JPopupMenu)c).getInvoker(); if (invoker instanceof Container) return (Container)invoker; } if (compact && c instanceof JToolTip) { return ((JToolTip)c).getComponent(); } Container parent = hierarchy.getParent(c); if (compact) { while (parent != null && isElided(parent)) { parent = getParent(parent); } } return parent; } public boolean contains(Component c) { return hierarchy.contains(c); } public void dispose(Window w) { hierarchy.dispose(w); } /** Returns whether the given component is completely ignored (including its children) in the hierarchy. */ private boolean isIgnored(Component c) { if (AWT.isTransientPopup(c)) return true; return c instanceof JScrollBar && c.getParent() instanceof JScrollPane; } /** Returns whether the given component is omitted from the component * hierarchy when compact is turned on (its children may be shown). * For example, a JScrollPane's viewport is elided so that the scrolled * content shows up directly beneath the scroll pane. */ private boolean isElided(Component c) { // JPanel or JWindow transient popups if (AWT.isTransientPopup(c)) { return true; } if (c instanceof Container) { // Only windows that are heavyweight popups are elided if (c instanceof Window) return false; if (c instanceof JPopupMenu && ((JPopupMenu)c).getInvoker() instanceof JMenu) return true; // Content pane on heavyweight popup if (AWT.isContentPane(c) && AWT.isTransientPopup(SwingUtilities.getWindowAncestor(c))) return true; } Container parent = c.getParent(); if (parent instanceof JScrollPane) { // Ignore scrollbars and viewport, but not headers return c instanceof JScrollBar || c instanceof JViewport; } return parent instanceof RootPaneContainer || parent instanceof JRootPane; } /** Provide a list of a Component's children, sans any transient popups * Keep track of any popups encountered. */ // heavyweights are subwindows of Window? // lightweights are subpanels of Window? public Collection getComponents(Component c) { if (c == null || !(c instanceof Container)) return new ArrayList(); ArrayList list = new ArrayList(); if (compact) { // Display menu contents directly beneath menus if (c instanceof JMenu) { return getComponents(((JMenu)c).getPopupMenu()); } } Iterator iter = hierarchy.getComponents(c).iterator(); while (iter.hasNext()) { Component k = (Component)iter.next(); if (compact && isElided(k)) { // Only add children if the component itself is not totally // ignored. if (!isIgnored(k)) { list.addAll(getComponents(k)); } } else { list.add(k); } } list.addAll(findInvokerPopups(c)); return list; } /** Scan for popups which have been invoked by the given invoker. */ private Collection findInvokerPopups(Component invoker) { ArrayList popups = new ArrayList(); Window root = AWT.getWindow(invoker); // heavyweight popups are sub-windows of the invoker's window Collection parents = new ArrayList(Arrays.asList(root.getOwnedWindows())); // lightweight popups show up on the window's layered pane if (root instanceof JWindow || root instanceof JFrame || root instanceof JDialog) { JRootPane rp = ((RootPaneContainer)root).getRootPane(); // work around VM bug on RootPaneContainer.getLayeredPane() // which sometimes results in a NPE if (rp != null) { JLayeredPane lp = rp.getLayeredPane(); if (lp != null) { parents.addAll(Arrays.asList(lp.getComponents())); } } } Iterator iter = parents.iterator(); while (iter.hasNext()) { Component c = (Component)iter.next(); JComponent popup = findInvokedPopup(invoker, c); if (popup != null) { popups.add(popup); } } return popups; } /** Returns the invoked popup found under the given parent (if any). */ private JComponent findInvokedPopup(Component invoker, Component parent) { if (AWT.isTransientPopup(parent)) { JComponent popup = findPopup((Container)parent); if (popup != null && (!(popup instanceof JPopupMenu) || !(getParent(popup) instanceof JMenu))) { if (getParent(popup) == invoker) { return popup; } } } return null; } /** Return the popup descendent of the given known transient popup * container. */ private JComponent findPopup(Container c) { JComponent popup = null; Component[] kids = c.getComponents(); for (int i=0;i < kids.length && popup == null;i++) { if (kids[i] instanceof JPopupMenu || kids[i] instanceof JToolTip) { popup = (JComponent)kids[i]; } else if (kids[i] instanceof Container) { popup = findPopup((Container)kids[i]); } } return popup; } }