/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.antlr.netbeans.editor.navigation; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.tree.TreePath; import org.antlr.netbeans.editor.navigation.actions.FilterSubmenuAction; import org.antlr.netbeans.editor.navigation.actions.SortByNameAction; import org.antlr.netbeans.editor.navigation.actions.SortBySourceAction; import org.netbeans.api.annotations.common.NonNull; import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.BeanTreeView; import org.openide.filesystems.FileObject; import org.openide.nodes.Node; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; import org.openide.util.Parameters; import org.openide.util.RequestProcessor; import org.openide.util.Utilities; import org.openide.util.lookup.AbstractLookup; import org.openide.util.lookup.InstanceContent; /** * * @author Sam Harwell */ @NbBundle.Messages({ "# {0} - shortcut", "TIP_CollapsiblePanel=Click or press {0} to hide/show when the Navigator is active" }) public abstract class NavigatorPanelUI extends javax.swing.JPanel implements ExplorerManager.Provider, FiltersManager.FilterChangeListener, PropertyChangeListener { private static final Rectangle ZERO = new Rectangle(0, 0, 1, 1); private static final Logger PERF_LOG = Logger.getLogger(NavigatorPanelUI.class.getName() + ".perf"); private static final RequestProcessor RP = new RequestProcessor(NavigatorPanelUI.class.getName(), 1); private final NavigatorNode.Factory nodeFactory; private final ExplorerManager explorerManager = new ExplorerManager(); private final NavigatorBeanTreeView treeView; private final CollapsiblePanel filtersPanel; private final InstanceContent selectedNodes = new InstanceContent(); private final Lookup lookup = new AbstractLookup(selectedNodes); private final Filters filters; private final Action[] actions; private long lastShowWaitNodeTime = -1; @SuppressWarnings({"LeakingThisInConstructor", "OverridableMethodCallInConstructor"}) public NavigatorPanelUI(NavigatorNode.Factory nodeFactory) { this.nodeFactory = nodeFactory; setLayout(new BorderLayout()); explorerManager.addPropertyChangeListener(this); // tree view of rules treeView = new NavigatorBeanTreeView(this); this.add(treeView, BorderLayout.CENTER); // filters filtersPanel = new CollapsiblePanel(); filtersPanel.setOrientation(CollapsiblePanel.DOWN); // tooltip KeyStroke toggleKey = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); String keyText = Utilities.keyToString(toggleKey); filtersPanel.setToolTipText(Bundle.TIP_CollapsiblePanel(keyText)); filters = createFilters(); filters.getInstance().hookChangeListener(this); JComponent buttons = filters.getComponent(); buttons.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 0)); filtersPanel.add(buttons); if ("Aqua".equals(UIManager.getLookAndFeel().getID())) { filtersPanel.setBackground(UIManager.getColor("NbExplorerView.background")); } actions = new Action[] { new SortByNameAction(filters), new SortBySourceAction(filters), null, new FilterSubmenuAction(filters.getInstance()), }; this.add(filtersPanel, BorderLayout.SOUTH); explorerManager.setRootContext(NavigatorNode.getWaitNode()); boolean expanded = NbPreferences.forModule(getClass()).getBoolean("filtersCollapsiblePanel.expanded", true); filtersPanel.setExpanded(expanded); filtersPanel.addPropertyChangeListener(this); } protected NavigatorBeanTreeView getTreeView() { return treeView; } @Override public boolean requestFocusInWindow() { boolean result = super.requestFocusInWindow(); treeView.requestFocusInWindow(); return result; } @Override public void requestFocus() { super.requestFocus(); treeView.requestFocus(); } public Lookup getLookup() { return this.lookup; } public void showWaitNode() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { treeView.setRootVisible(true); explorerManager.setRootContext(NavigatorNode.getWaitNode()); lastShowWaitNodeTime = System.currentTimeMillis(); } }); } public boolean isShowingWaitNode() { return explorerManager.getRootContext() == NavigatorNode.getWaitNode(); } protected abstract Filters createFilters(); public boolean getScrollOnExpand() { return treeView == null ? true : treeView.getScrollOnExpand(); } public void setScrollOnExpand(boolean value) { if (treeView != null) { treeView.setScrollOnExpand(value); } } public void expandNode(Node node) { treeView.expandNode(node); } public Filters getFilters() { return filters; } public Action[] getActions() { return actions; } public void sort() { NavigatorNode node = getRootNode(); if (node != null) { node.refreshRecursively(); } } public FileObject getFileObject() { NavigatorNode node = getRootNode(); if (node != null) { return node.getDescription().getFileObject(); } else { return null; } } @Override public void filterStateChanged(ChangeEvent e) { NavigatorNode root = getRootNode(); if ( root != null ) { root.refreshRecursively(); } } private NavigatorNode getRootNode() { Node node = explorerManager.getRootContext(); if (node instanceof NavigatorNode) { return (NavigatorNode)node; } else { return null; } } @Override public ExplorerManager getExplorerManager() { return explorerManager; } @Override public void propertyChange(PropertyChangeEvent event) { switch (event.getPropertyName()) { case ExplorerManager.PROP_SELECTED_NODES: for (Node node : (Node[])event.getOldValue()) { selectedNodes.remove(node); } for (Node node : (Node[])event.getNewValue()) { selectedNodes.add(node); } break; case CollapsiblePanel.EXPANDED_PROPERTY: NbPreferences.forModule(getClass()).putBoolean("filtersCollapsiblePanel.expanded", filtersPanel.isExpanded()); break; } } public void refresh(@NonNull final Description description) { Parameters.notNull("description", description); final NavigatorNode rootNode = getRootNode(); FileObject rootFileObject = rootNode != null ? rootNode.getDescription().getFileObject() : null; if (rootFileObject != null && rootFileObject.equals(description.getFileObject())) { RP.post(new Runnable() { @Override public void run() { rootNode.updateRecursively(description); } }); } else { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { treeView.setRootVisible(false); explorerManager.setRootContext(nodeFactory.createNode(NavigatorPanelUI.this, description)); boolean scrollOnExpand = getScrollOnExpand(); treeView.setAutoWaitCursor(false); treeView.expandAll(); treeView.setAutoWaitCursor(true); setScrollOnExpand(scrollOnExpand); if (PERF_LOG.isLoggable(Level.FINE)) { long tm2 = System.currentTimeMillis(); long tm1 = lastShowWaitNodeTime; if (tm1 != -1) { lastShowWaitNodeTime = -1; PERF_LOG.log(Level.FINE, String.format("GrammarRulesPanelUI refresh took: %d ms", (tm2 - tm1)), new Object[] { description.getFileObject().getName(), (tm2 - tm1) }); } } } }); } } protected static class NavigatorBeanTreeView extends BeanTreeView implements ToolTipManagerEx.ToolTipProvider { private final NavigatorPanelUI navigatorPanelUI; private final ToolTipManagerEx toolTipManager; public NavigatorBeanTreeView(NavigatorPanelUI ui) { this.navigatorPanelUI = ui; this.toolTipManager = new ToolTipManagerEx( this ); } public ToolTipManagerEx getToolTipManager() { return toolTipManager; } public boolean getScrollOnExpand() { return tree.getScrollsOnExpand(); } public void setScrollOnExpand( boolean scroll ) { this.tree.setScrollsOnExpand( scroll ); } @Override public JComponent getComponent() { return tree; } @Override public String getToolTipText(Point loc) { return null; //ElementJavadoc doc = getDocumentation(loc); //return null == doc ? null : doc.getText(); } /*private ElementJavadoc getDocumentation( Point loc ) { TreePath path = tree.getPathForLocation( loc.x, loc.y ); if( null == path ) return null; Node node = Visualizer.findNode( path.getLastPathComponent() ); if( node instanceof ElementNode ) { return getJavaDocFor( (ElementNode)node ); } return null; }*/ @Override public Rectangle getToolTipSourceBounds(Point loc) { NavigatorNode root = navigatorPanelUI.getRootNode(); if ( root == null ) { return null; } TreePath path = tree.getPathForLocation( loc.x, loc.y ); return null == path ? null : tree.getPathBounds( path ); } @Override public Point getToolTipLocation( Point mouseLocation, Dimension tipSize ) { Point screenLocation = getLocationOnScreen(); Rectangle sBounds = getGraphicsConfiguration().getBounds(); Dimension compSize = getSize(); Point res = new Point(); Rectangle tooltipSrcRect = getToolTipSourceBounds( mouseLocation ); //May be null, prevent the NPE, nothing will be shown anyway. if (tooltipSrcRect == null) { tooltipSrcRect = new Rectangle(); } Point viewPosition = getViewport().getViewPosition(); screenLocation.x -= viewPosition.x; screenLocation.y -= viewPosition.y; //first try bottom right res.x = screenLocation.x + compSize.width; res.y = screenLocation.y + tooltipSrcRect.y+tooltipSrcRect.height; if( res.x + tipSize.width <= sBounds.x+sBounds.width && res.y + tipSize.height <= sBounds.y+sBounds.height ) { return res; } //upper right res.x = screenLocation.x + compSize.width; res.y = screenLocation.y + tooltipSrcRect.y - tipSize.height; if( res.x + tipSize.width <= sBounds.x+sBounds.width && res.y >= sBounds.y ) { return res; } //lower left res.x = screenLocation.x - tipSize.width; res.y = screenLocation.y + tooltipSrcRect.y; if( res.x >= sBounds.x && res.y + tipSize.height <= sBounds.y+sBounds.height ) { return res; } //upper left res.x = screenLocation.x - tipSize.width; res.y = screenLocation.y + tooltipSrcRect.y + tooltipSrcRect.height - tipSize.height; if( res.x >= sBounds.x && res.y >= sBounds.y ) { return res; } //give up (who's got such a small display anyway?) res.x = screenLocation.x + tooltipSrcRect.x; if( sBounds.y + sBounds.height - (screenLocation.y + tooltipSrcRect.y + tooltipSrcRect.height) > screenLocation.y + tooltipSrcRect.y - sBounds.y ) { res.y = screenLocation.y + tooltipSrcRect.y + tooltipSrcRect.height; } else { res.y = screenLocation.y + tooltipSrcRect.y - tipSize.height; } return res; } @Override public void invokeUserAction(final MouseEvent me) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { if( null != me ) { /*ElementJavadoc doc = getDocumentation( me.getPoint() ); JavadocTopComponent tc = JavadocTopComponent.findInstance(); if( null != tc ) { tc.open(); tc.setJavadoc( doc ); tc.requestActive(); }*/ } } }); } //#123940 start private boolean inHierarchy; private boolean doExpandAll; @Override public void addNotify() { super.addNotify(); inHierarchy = true; if (doExpandAll) { super.expandAll(); doExpandAll = false; } } @Override public void removeNotify() { super.removeNotify(); inHierarchy = false; this.toolTipManager.hideTipWindow(); } @Override public void expandAll() { super.expandAll(); if (!inHierarchy) { doExpandAll = true; } } //#123940 end } }