/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.explorer.view; import java.awt.*; import java.awt.dnd.DnDConstants; import java.awt.dnd.Autoscroll; import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.util.ArrayList; import java.util.Arrays; import javax.swing.*; import javax.swing.event.*; import javax.swing.tree.*; import javax.accessibility.*; import org.openide.actions.PopupAction; import org.openide.awt.MouseUtils; import org.openide.ErrorManager; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.NodeOp; import org.openide.util.Lookup; import org.openide.util.RequestProcessor; import org.openide.util.WeakListener; import org.openide.util.Utilities; import org.openide.util.actions.CallbackSystemAction; import org.openide.util.actions.SystemAction; import org.openide.util.actions.ActionPerformer; /** Tree view abstract class. * * @author Petr Hamernik, Ian Formanek, Jaroslav Tulach */ public abstract class TreeView extends JScrollPane { // // static fields // /** generated Serialized Version UID */ static final long serialVersionUID = -1639001987693376168L; /** How long it takes before collapsed nodes are released from the tree's cache */ private static final int TIME_TO_COLLAPSE = System.getProperty ("netbeans.debug.heap") != null ? 0 : 15000; /** Minimum width of this component. */ private static final int MIN_TREEVIEW_WIDTH = 400; /** Minimum height of this component. */ private static final int MIN_TREEVIEW_HEIGHT = 400; // // components // /** Main <code>JTree</code> component. */ transient protected JTree tree; /** model */ transient NodeTreeModel treeModel; /** Explorer manager, valid when this view is showing */ transient ExplorerManager manager; // Attributes /** Mouse and action listener. */ transient PopupSupport defaultActionListener; /** Property indicating whether the default action is enabled. */ transient boolean defaultActionEnabled; /** not null if popup menu enabled */ transient PopupAdapter popupListener; /** the most important listener (on four types of events */ transient TreePropertyListener managerListener = null; /** weak variation of the listener for property change on the explorer manager */ transient PropertyChangeListener wlpc; /** weak variation of the listener for vetoable change on the explorer manager */ transient VetoableChangeListener wlvc; /** true if drag support is active */ transient boolean dragActive = false; /** true if drop support is active */ transient boolean dropActive = false; /** Drag support */ transient TreeViewDragSupport dragSupport; /** Drop support */ transient TreeViewDropSupport dropSupport; transient boolean dropTargetPopupAllowed = true; transient private Container contentPane; transient Boolean waitCursorDisabled; /** Constructor. */ public TreeView () { this (true, true); } /** Constructor. * @param defaultAction should double click on a node open its default action? * @param popupAllowed should right-click open popup? */ public TreeView (boolean defaultAction, boolean popupAllowed) { initializeTree (); // activation of drop target if (DragDropUtilities.dragAndDropEnabled) { ExplorerDnDManager.getDefault ().addFutureDropTarget (this); // note: drag target is activated on focus gained } setPopupAllowed (popupAllowed); setDefaultActionAllowed (defaultAction); Dimension dim = null; try { dim = getPreferredSize(); if (dim == null) { dim = new Dimension(MIN_TREEVIEW_WIDTH, MIN_TREEVIEW_HEIGHT); } } catch (NullPointerException npe) { dim = new Dimension(MIN_TREEVIEW_WIDTH, MIN_TREEVIEW_HEIGHT); } if (dim.width < MIN_TREEVIEW_WIDTH) dim.width = MIN_TREEVIEW_WIDTH; if (dim.height < MIN_TREEVIEW_HEIGHT) dim.height = MIN_TREEVIEW_HEIGHT; setPreferredSize(dim); } /** Initializes the tree & model. * [dafe] Horrible technique - overridable method called from constructor * may result in subclass code invoked when this object is not fully * constructed. * However I don't have enough knowledge about this code to change it. */ void initializeTree () { // initilizes the JTree treeModel = createModel (); tree = new ExplorerTree(treeModel); NodeRenderer rend = NodeRenderer.sharedInstance (); tree.setCellRenderer(rend); tree.putClientProperty("JTree.lineStyle", "Angled"); // NOI18N setViewportView (tree); // set selection mode to DISCONTIGUOUS_TREE_SELECTION as default setSelectionMode (TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); ToolTipManager.sharedInstance().registerComponent(tree); // init listener & attach it to closing of managerListener = new TreePropertyListener(); tree.addTreeExpansionListener (managerListener); tree.addTreeWillExpandListener (managerListener); // do not care about focus setRequestFocusEnabled (false); defaultActionListener = new PopupSupport(); tree.addFocusListener(defaultActionListener); tree.addMouseListener(defaultActionListener); } /** Is it permitted to display a popup menu? * @return <code>true</code> if so */ public boolean isPopupAllowed () { return popupListener != null; } /** Enable/disable displaying popup menus on tree view items. * Default is enabled. * @param value <code>true</code> to enable */ public void setPopupAllowed (boolean value) { if (popupListener == null && value) { // on popupListener = new PopupAdapter (); tree.addMouseListener (popupListener); return; } if (popupListener != null && !value) { // off tree.removeMouseListener (popupListener); popupListener = null; return; } } void setDropTargetPopupAllowed(boolean value) { dropTargetPopupAllowed=value; if (dropSupport!=null) { dropSupport.setDropTargetPopupAllowed(value); } } boolean isDropTargetPopupAllowed() { return dropSupport!=null ? dropSupport.isDropTargetPopupAllowed() : dropTargetPopupAllowed; } /** Does a double click invoke the default node action? * @return <code>true</code> if so */ public boolean isDefaultActionEnabled () { return defaultActionEnabled; } /** Requests focus for the tree component. Overrides superclass method. */ public void requestFocus () { tree.requestFocus(); } /** Enable/disable double click to invoke default action. * If defaultAction is not enabled double click expand/collapse node. * @param value <code>true</code> to enable */ public void setDefaultActionAllowed(boolean value) { defaultActionEnabled = value; if(value) { tree.registerKeyboardAction( defaultActionListener, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), JComponent.WHEN_FOCUSED ); } else { // Switch off. tree.unregisterKeyboardAction( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false) ); } } /** * Is the root node of the tree displayed? * * @return <code>true</code> if so */ public boolean isRootVisible() { return tree.isRootVisible(); } /** Set whether or not the root node from * the <code>TreeModel</code> is visible. * * @param visible <code>true</code> if it is to be displayed * @beaninfo * bound: true * description: Whether or not the root node * from the TreeModel is visible. */ public void setRootVisible (boolean visible) { tree.setRootVisible (visible); tree.setShowsRootHandles(!visible); } /********** Support for the Drag & Drop operations *********/ /** Drag support is disabled by default. * @return true if dragging from the view is enabled, false * otherwise. */ public boolean isDragSource () { return dragActive; } /** Enables/disables dragging support. * @param state true enables dragging support, false disables it. */ public void setDragSource (boolean state) { if (state == dragActive) return; dragActive = state; // create drag support if needed if (dragActive && (dragSupport == null)) dragSupport = new TreeViewDragSupport(this, tree); // activate / deactivate support according to the state dragSupport.activate(dragActive); } /** Drop support is disabled by default. * @return true if dropping to the view is enabled, false * otherwise<br> */ public boolean isDropTarget () { return dropActive; } /** Enables/disables dropping support. * @param state true means drops into view are allowed, * false forbids any drops into this view. */ public void setDropTarget (boolean state) { if (state == dropActive) return; dropActive = state; // create drop support if needed if (dropActive && (dropSupport == null)) dropSupport = new TreeViewDropSupport(this, tree, dropTargetPopupAllowed); // activate / deactivate support according to the state dropSupport.activate(dropActive); } /** Actions constants comes from DnDConstants.XXX constants. * All actions (copy, move, link) are allowed by default. * @return Set of actions which are allowed when dragging from * asociated component. */ public int getAllowedDragActions () { // PENDING return DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK; } /** Sets allowed actions for dragging * @param actions new drag actions, using DnDConstants.XXX */ public void setAllowedDragActions (int actions) { // PENDING } /** Actions constants comes from DnDConstants.XXX constants. * All actions are allowed by default. * @return Set of actions which are allowed when dropping * into the asociated component. */ public int getAllowedDropActions () { // PENDING return DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK; } /** Sets allowed actions for dropping. * @param actions new allowed drop actions, using DnDConstants.XXX */ public void setAllowedDropActions (int actions) { // PENDING } // // Control over expanded state // /** Collapses the tree under given node. * * @param n node to collapse */ public void collapseNode (Node n) { TreePath treePath = new TreePath ( treeModel.getPathToRoot ( VisualizerNode.getVisualizer (null, n) ) ); tree.collapsePath (treePath); } /** Expandes the node in the tree. * * @param n node */ public void expandNode (Node n) { lookupExplorerManager (); TreePath treePath = new TreePath ( treeModel.getPathToRoot ( VisualizerNode.getVisualizer (null, n) ) ); tree.expandPath (treePath); } /** Test whether a node is expanded in the tree or not * @param n the node to test * @return true if the node is expanded */ public boolean isExpanded (Node n) { TreePath treePath = new TreePath ( treeModel.getPathToRoot ( VisualizerNode.getVisualizer (null, n) ) ); return tree.isExpanded (treePath); } /** Expands all paths. */ public void expandAll () { int i = 0, j, k = tree.getRowCount (); do { do { j = tree.getRowCount (); tree.expandRow (i); } while (j != tree.getRowCount ()); i++; } while (i < tree.getRowCount ()); } // // Processing functions // /** Initializes the component and lookup explorer manager. */ public void addNotify () { super.addNotify (); lookupExplorerManager (); } /** Registers in the tree of components. */ private void lookupExplorerManager () { // Enter key in the tree ExplorerManager newManager = ExplorerManager.find (TreeView.this); if (newManager != manager) { if (manager != null) { manager.removeVetoableChangeListener (wlvc); manager.removePropertyChangeListener (wlpc); } manager = newManager; manager.addVetoableChangeListener(wlvc = WeakListener.vetoableChange (managerListener, manager)); manager.addPropertyChangeListener(wlpc = WeakListener.propertyChange (managerListener, manager)); synchronizeRootContext (); synchronizeExploredContext (); synchronizeSelectedNodes (); } // Sometimes the listener is registered twice and we get the // selection events twice. Removing the listener before adding it // should be a safe fix. tree.getSelectionModel().removeTreeSelectionListener(managerListener); tree.getSelectionModel().addTreeSelectionListener(managerListener); } /** Deinitializes listeners. */ public void removeNotify () { super.removeNotify (); tree.getSelectionModel().removeTreeSelectionListener(managerListener); } // ************************************* // Methods to be overriden by subclasses // ************************************* /** Allows subclasses to provide own model for displaying nodes. * @return the model to use for this view */ protected abstract NodeTreeModel createModel(); /** Called to allow subclasses to define the behaviour when a * node(s) are selected in the tree. * * @param nodes the selected nodes * @param em explorer manager to work on (change nodes to it) * @throws PropertyVetoException if the change cannot be done by the explorer * (the exception is silently consumed) */ protected abstract void selectionChanged (Node[] nodes, ExplorerManager em) throws PropertyVetoException; /** Called when explorer manager is about to change the current selection. * The view can forbid the change if it is not able to display such * selection. * * @param nodes the nodes to select * @return false if the view is not able to change the selection */ protected abstract boolean selectionAccept (Node[] nodes); /** Show a given path in the screen. It depends on the kind of <code>TreeView</code> * if the path should be expanded or just made visible. * * @param path the path */ protected abstract void showPath(TreePath path); /** Shows selection to reflect the current state of the selection in the explorer. * * @param paths array of paths that should be selected */ protected abstract void showSelection (TreePath[] paths); /** Specify whether a context menu of the explored context should be used. * Applicable when no nodes are selected and the user wants to invoke * a context menu (clicks right mouse button). * * @return <code>true</code> if so; <code>false</code> in the default implementation */ protected boolean useExploredContextMenu() { return false; } /** Check if selection of the nodes could break the selection mode set in TreeSelectionModel. * @param nodes the nodes for selection * @return true if the selection mode is broken */ private boolean isSelectionModeBroken (Node[] nodes) { // if nodes are empty or single the everthing is ok // or if discontiguous selection then everthing ok if (nodes.length <= 1 || getSelectionMode()==TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) return false; // if many nodes // brakes single selection mode if (getSelectionMode()==TreeSelectionModel.SINGLE_TREE_SELECTION) return true; // check the contiguous selection mode TreePath[] paths = new TreePath[nodes.length]; RowMapper rowMapper = tree.getSelectionModel().getRowMapper(); // if rowMapper is null then tree bahaves as discontiguous selection mode is set if (rowMapper==null) { return false; } ArrayList toBeExpaned = new ArrayList (3); for (int i = 0; i < nodes.length; i++) { toBeExpaned.clear (); Node n = nodes[i]; while (n.getParentNode ()!=null) { if (!isExpanded (n)) toBeExpaned.add (n); n = n.getParentNode (); } for (int j = toBeExpaned.size ()-1; j>=0; j--) { expandNode ((Node)toBeExpaned.get (j)); } TreePath treePath = new TreePath (treeModel.getPathToRoot (VisualizerNode.getVisualizer (null, nodes[i]))); paths[i] = treePath; } int[] rows = rowMapper.getRowsForPaths(paths); // check selection's rows Arrays.sort (rows); for (int i = 1; i < rows.length; i++) { if ( rows[i] != rows[i - 1] + 1 ) { return true; } } // all is ok return false; } // // synchronizations // /** Called when selection in tree is changed. */ final void callSelectionChanged (Node[] nodes) { manager.removePropertyChangeListener (wlpc); manager.removeVetoableChangeListener (wlvc); try { selectionChanged (nodes, manager); } catch (PropertyVetoException e) { synchronizeSelectedNodes (); } finally { manager.addPropertyChangeListener (wlpc); manager.addVetoableChangeListener (wlvc); } } /** Synchronize the root context from the manager of this Explorer. */ final void synchronizeRootContext() { treeModel.setNode (manager.getRootContext ()); } /** Synchronize the explored context from the manager of this Explorer. */ final void synchronizeExploredContext() { Node n = manager.getExploredContext (); if (n != null) { TreePath treePath = new TreePath (treeModel.getPathToRoot (VisualizerNode.getVisualizer (null, n))); showPath(treePath); } } /** Sets the selection model, which must be one of SINGLE_TREE_SELECTION, * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. * <p> * This may change the selection if the current selection is not valid * for the new mode. For example, if three TreePaths are * selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>, * only one TreePath will remain selected. It is up to the particular * implementation to decide what TreePath remains selected. * Note: DISCONTIGUOUS_TREE_SELECTION is set as default. * @since 2.15 * @param mode selection mode */ public void setSelectionMode (int mode) { tree.getSelectionModel ().setSelectionMode (mode); } /** Returns the current selection mode, one of * <code>SINGLE_TREE_SELECTION</code>, * <code>CONTIGUOUS_TREE_SELECTION</code> or * <code>DISCONTIGUOUS_TREE_SELECTION</code>. * @since 2.15 * @return selection mode */ public int getSelectionMode () { return tree.getSelectionModel ().getSelectionMode (); } // // showing and removing the wait cursor // private void showWaitCursor () { if (getRootPane () == null) return ; contentPane = getRootPane ().getContentPane (); if (SwingUtilities.isEventDispatchThread ()) { contentPane.setCursor (Utilities.createProgressCursor (contentPane)); } else { SwingUtilities.invokeLater (new Runnable () { public void run () { contentPane.setCursor (Utilities.createProgressCursor (contentPane)); } }); } } private void showNormalCursor () { if (contentPane == null) return ; if (SwingUtilities.isEventDispatchThread ()) { contentPane.setCursor (null); } else { SwingUtilities.invokeLater (new Runnable () { public void run () { contentPane.setCursor (null); } }); } } private void prepareWaitCursor (final Node node) { // check type of node if (node == null) { showNormalCursor (); } showWaitCursor (); RequestProcessor.getDefault ().post (new Runnable () { public void run () { try { node.getChildren ().getNodes (true); } catch (Exception e) { // log a exception ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, e); } finally { // show normal cursor above all showNormalCursor (); } } }); } /** Synchronize the selected nodes from the manager of this Explorer. * The default implementation does nothing. */ final void synchronizeSelectedNodes() { Node[] arr = manager.getSelectedNodes (); TreePath[] paths = new TreePath[arr.length]; for (int i = 0; i < arr.length; i++) { TreePath treePath = new TreePath (treeModel.getPathToRoot (VisualizerNode.getVisualizer (null, arr[i]))); paths[i] = treePath; } tree.getSelectionModel().removeTreeSelectionListener(managerListener); showSelection (paths); tree.getSelectionModel().addTreeSelectionListener(managerListener); } void scrollTreeToVisible(TreePath path, TreeNode child) { Rectangle base = tree.getVisibleRect(); Rectangle b1 = tree.getPathBounds(path); Rectangle b2 = tree.getPathBounds(new TreePath(treeModel.getPathToRoot(child))); if (base != null && b1 != null && b2 != null) { tree.scrollRectToVisible(new Rectangle(base.x, b1.y, 1, b2.y - b1.y + b2.height)); } } /** Listens to the property changes on tree */ class TreePropertyListener implements VetoableChangeListener, PropertyChangeListener, TreeExpansionListener, TreeWillExpandListener, TreeSelectionListener, Runnable { private RequestProcessor.Task scheduled; TreePropertyListener() {} public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { // issue 11928 check if selecetion mode will be broken if (isSelectionModeBroken ((Node[])evt.getNewValue ())) { throw new PropertyVetoException ("", evt); // NOI18N } if (!selectionAccept ((Node[])evt.getNewValue ())) { throw new PropertyVetoException ("", evt); // NOI18N } } } public final void propertyChange(final PropertyChangeEvent evt) { if (manager == null) return; // the tree view has been removed before the event got delivered if (evt.getPropertyName().equals(ExplorerManager.PROP_ROOT_CONTEXT)) synchronizeRootContext(); if (evt.getPropertyName().equals(ExplorerManager.PROP_EXPLORED_CONTEXT)) synchronizeExploredContext(); if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) synchronizeSelectedNodes(); } public synchronized void treeExpanded (TreeExpansionEvent ev) { final TreePath path = ev.getPath (); RequestProcessor.Task t = scheduled; if (t != null) { t.cancel (); } // It is OK to use multithreaded shared RP as the requests // will be serialized in event queue later scheduled = RequestProcessor.getDefault().post (new Runnable () { public void run () { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater (this); return; } if (!tree.isVisible(path)) { // if the path is not visible - don't check the children return; } if (treeModel == null) { // no model, no action, no problem return; } TreeNode myNode = (TreeNode)path.getLastPathComponent(); if (treeModel.getPathToRoot(myNode)[0] != treeModel.getRoot()) { // the way from the path no longer // goes to the root, probably someone // has removed the node on the way up // System.out.println("different roots."); return; } // show wait cursor //showWaitCursor (); int lastChildIndex = myNode.getChildCount()-1; if (lastChildIndex >= 0) { TreeNode lastChild = myNode.getChildAt(lastChildIndex); Rectangle base = tree.getVisibleRect(); Rectangle b1 = tree.getPathBounds(path); Rectangle b2 = tree.getPathBounds(new TreePath(treeModel.getPathToRoot(lastChild))); if (base != null && b1 != null && b2 != null) { tree.scrollRectToVisible(new Rectangle(base.x, b1.y, 1, b2.y - b1.y + b2.height)); } // scrollTreeToVisible(path, lastChild); } } }, 250); // hope that all children are there after this time } public synchronized void treeCollapsed (final TreeExpansionEvent ev) { final TreePath path = ev.getPath (); showNormalCursor (); RequestProcessor.Task t = scheduled; if (t != null) { t.cancel (); } // It is OK to use multithreaded shared RP as the requests // will be serialized in event queue later scheduled = RequestProcessor.getDefault().post (new Runnable () { public void run () { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater (this); return; } if (tree.isExpanded(path)) { // the tree shows the path - do not collapse // the tree return; } if (!tree.isVisible(path)) { // if the path is not visible do not collapse // the tree return; } if (treeModel == null) { // no model, no action, no problem return; } TreeNode myNode = (TreeNode)path.getLastPathComponent(); if (treeModel.getPathToRoot(myNode)[0] != treeModel.getRoot()) { // the way from the path no longer // goes to the root, probably someone // has removed the node on the way up // System.out.println("different roots."); return; } treeModel.nodeStructureChanged(myNode); } }, TIME_TO_COLLAPSE); } /* Called whenever the value of the selection changes. * @param ev the event that characterizes the change. */ public void valueChanged(TreeSelectionEvent ev) { TreePath[] paths = tree.getSelectionPaths (); if (paths == null) { callSelectionChanged (new Node[0]); } else { // we need to force no changes to nodes hierarchy => // we are requesting read request, but it is not necessary // to execute the next action immediatelly, so postReadRequest // should be enough readAccessPaths = paths; Children.MUTEX.postReadRequest(this); } } private TreePath[] readAccessPaths; /** Called under Children.MUTEX to refresh the currently selected nodes. */ public void run () { if (readAccessPaths == null) { return; } TreePath[] paths = readAccessPaths; // non null value caused leak in // ComponentInspector // When the last Form was closed then the ComponentInspector was // closed as well. Since this variable was not null - // last selected Node (RADComponentNode) was held ---> FormManager2 was held, etc. readAccessPaths = null; java.util.List ll = new java.util.ArrayList(paths.length); for (int i = 0; i < paths.length; i++) { Node n = Visualizer.findNode (paths[i].getLastPathComponent ()); if (n == manager.getRootContext() || n.getParentNode() != null) { ll.add (n); } } callSelectionChanged ((Node[])ll.toArray (new Node[ll.size ()])); } public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { } public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { // prepare wait cursor and optionally show it TreePath path = event.getPath (); prepareWaitCursor (DragDropUtilities.secureFindNode (path.getLastPathComponent ())); } } // end of TreePropertyListener /** Popup adapter. */ class PopupAdapter extends MouseUtils.PopupMouseAdapter { PopupAdapter() {} protected void showPopup (MouseEvent e) { int selRow = tree.getRowForLocation(e.getX(), e.getY()); if (!tree.isRowSelected(selRow)) { // try { // manager.setSelectedNodes(new Node[0]); // David's workaround // } catch (PropertyVetoException exc) { // ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, exc); // } // This will set ExplorerManager selection, no need to push // it more (the commented out code above). tree.setSelectionRow(selRow); } if (selRow != -1) { Point p = SwingUtilities.convertPoint(e.getComponent(),e.getX(), e.getY(),TreeView.this); createPopup((int)p.getX(), (int)p.getY()); } } } /** * Method created to fix #12520. It returns false in the situation * where the popup is invoked on non-activated top component or * dialog. * Note: Using lookup and reflection bacause we are in standalone * explorer library. */ static boolean shouldPopupBeDisplayed(Component comp) { try { Class c = Class.forName("org.openide.windows.TopComponent$Registry"); // NOI18N Object registry = Lookup.getDefault().lookup(c); if (registry == null) { // in case of standalone library we always return true return true; } java.lang.reflect.Method m = c.getMethod("getActivated", new Class[0]); // NOI18N Object activated = m.invoke(registry, new Object[0]); boolean fromActivated = SwingUtilities.isDescendingFrom(comp, (Component)activated); if (fromActivated) { return true; } // check for dialogs (they are not managed by the window system) Window w = SwingUtilities.getWindowAncestor(comp); if (w instanceof Dialog) { return true; } return false; } catch (Exception x) { ErrorManager.getDefault ().notify(ErrorManager.INFORMATIONAL, x); } // if we had any problems it is safe to just say "popup go" return true; } private void createPopup(int xpos, int ypos, JPopupMenu popup) { if ((popup != null) && (popup.getSubElements().length > 0) && (shouldPopupBeDisplayed(TreeView.this))) { popup.show(TreeView.this, xpos, ypos); } } void createPopup(int xpos, int ypos) { // bugfix #23932, don't create if it's disabled if (isPopupAllowed ()) { Node[] arr = manager.getSelectedNodes (); Action[] actions = NodeOp.findActions (arr); if (actions.length > 0) { createPopup ( xpos, ypos, Utilities.actionsToPopup(actions, this) ); } } } /* create standard popup menu and add newMenu to it */ void createExtendedPopup(int xpos, int ypos, JMenu newMenu) { Node[] ns = manager.getSelectedNodes (); JPopupMenu popup = null; if (ns.length > 0) { // if any nodes are selected --> find theirs actions Action[] actions = NodeOp.findActions (ns); popup = Utilities.actionsToPopup (actions, this); } else { // if none node is selected --> get context actions from view's root if (manager.getRootContext () != null) { popup = manager.getRootContext ().getContextMenu (); } } int cnt = 0; if ( popup != null && ( cnt = popup.getComponentCount() ) > 1 ) { popup.insert( newMenu, cnt - 1 ); popup.insert( new JPopupMenu.Separator(), cnt ); } else { if ( popup == null ) popup = SystemAction.createPopupMenu( new SystemAction[] {} ); popup.add( newMenu ); } createPopup ( xpos, ypos, popup ); } /** Utility method for invoking actions in separate thread. Note: * it uses reflection because it should work without * the rest of the IDE classes. */ static void invokeAction(SystemAction sa, ActionEvent ev) { Throwable t = null; try { Class c = Class.forName("org.openide.actions.ActionManager"); // NOI18N Object o = org.openide.util.Lookup.getDefault ().lookup(c); if (o != null) { // lookup has found the instance // use reflection now java.lang.reflect.Method m = c.getMethod("invokeAction", // NOI18N new Class[] { javax.swing.Action.class, java.awt.event.ActionEvent.class }); m.invoke(o, new Object[] { sa, ev } ); // everything went ok --> return; } } // exceptions from forName: catch (ClassNotFoundException x) { } catch (ExceptionInInitializerError x) { } catch (LinkageError x) { } // exceptions from getMethod: catch (SecurityException x) { t = x; } catch (NoSuchMethodException x) { t = x;} // exceptions from invoke catch (IllegalAccessException x) { t = x;} catch (IllegalArgumentException x) { t = x;} catch (java.lang.reflect.InvocationTargetException x) { t = x; } if (t != null) { ErrorManager.getDefault ().notify(ErrorManager.INFORMATIONAL, t); } // something went wrong --> invoke the action directly sa.actionPerformed(ev); } /** Returns the the point at which the popup menu is to be showed. May return null. * @return the point or null */ Point getPositionForPopup () { int i = tree.getLeadSelectionRow(); if (i < 0) return null; Rectangle rect = tree.getRowBounds(i); if (rect == null) return null; // bugfix #28360, convert the coordinates by the root pane Point p = new Point (rect.x, rect.y); if (tree.getRootPane () != null) { p = SwingUtilities.convertPoint (tree, rect.x, rect.y, tree.getRootPane ()); } return p; } final class PopupSupport extends MouseAdapter implements ActionPerformer, Runnable, FocusListener, ActionListener { CallbackSystemAction csa; boolean firstFocus = true; public void performAction(SystemAction act) { SwingUtilities.invokeLater(this); } public void run() { Point p = getPositionForPopup (); if (p == null) { return ; } createPopup(p.x, p.y); } public void focusGained(java.awt.event.FocusEvent ev) { if (csa == null) { csa = (CallbackSystemAction) SystemAction.get (PopupAction.class); } csa.setActionPerformer(this); if (firstFocus) { firstFocus = false; // lazy activation of drag source if (DragDropUtilities.dragAndDropEnabled) { setDragSource(true); // note: dropTarget is activated in constructor } // lazy cell editor init tree.setCellEditor(new TreeViewCellEditor(tree, new NodeRenderer.Tree ())); tree.setEditable(true); } } public void focusLost(java.awt.event.FocusEvent ev) { if (csa != null && (csa.getActionPerformer() instanceof PopupSupport)) { csa.setActionPerformer(null); } } /* clicking adapter */ public void mouseClicked(MouseEvent e) { int selRow = tree.getRowForLocation(e.getX(), e.getY()); if((selRow != -1) && SwingUtilities.isLeftMouseButton(e) && MouseUtils.isDoubleClick(e)) { // Default action. if(defaultActionEnabled) { TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); Node node = Visualizer.findNode (selPath.getLastPathComponent()); SystemAction sa = node.getDefaultAction (); if (sa != null) { TreeView.invokeAction (sa, new ActionEvent (node, ActionEvent.ACTION_PERFORMED, "")); // NOI18N e.consume(); return; } } if(tree.isExpanded(selRow)) { tree.collapseRow(selRow); } else { tree.expandRow(selRow); } } } /* VK_ENTER key processor */ public void actionPerformed(ActionEvent evt) { Node[] nodes = manager.getSelectedNodes(); if (nodes.length == 1) { SystemAction sa = nodes[0].getDefaultAction (); if (sa != null) { TreeView.invokeAction (sa, new ActionEvent (nodes[0], ActionEvent.ACTION_PERFORMED, "")); // NOI18N } } } } private final class ExplorerTree extends JTree implements Autoscroll { AutoscrollSupport support; private String maxPrefix; ExplorerTree(TreeModel model) { super(model); toggleClickCount = 0; if (System.getProperty("java.version").startsWith("1.4")) { // fix for #18292 (only for JDK 1.4) // default action map for JTree defines these shortcuts // but we use our own mechanism for handling them // following lines disable default L&F handling (if it is // defined on Ctrl-c, Ctrl-v and Ctrl-x) getInputMap().put(KeyStroke.getKeyStroke("control C"), "none"); // NOI18N getInputMap().put(KeyStroke.getKeyStroke("control V"), "none"); // NOI18N getInputMap().put(KeyStroke.getKeyStroke("control X"), "none"); // NOI18N getInputMap().put(KeyStroke.getKeyStroke("COPY"), "none"); // NOI18N getInputMap().put(KeyStroke.getKeyStroke("PASTE"), "none"); // NOI18N getInputMap().put(KeyStroke.getKeyStroke("CUT"), "none"); // NOI18N } else { // fix for #19094. the bug does not occur on JDK1.4 because // TreeCancelEditingAction was fixed in BasicTreeUI for JDK1.4 getActionMap().put("cancel", new OurTreeCancelEditingAction()); // NOI18N } setupSearch(); } // searchTextField manages focus because it handles VK_TAB key private JTextField searchTextField = new JTextField() { public boolean isManagingFocus() { return true; } public void processKeyEvent (KeyEvent ke) { //override the default handling so that //the parent will never receive the escape key and //close a modal dialog if (ke.getKeyCode() == ke.VK_ESCAPE) { removeSearchField(); } else { super.processKeyEvent(ke); } } }; final private int heightOfTextField = searchTextField.getPreferredSize().height; private void setupSearch() { // Remove the default key listeners KeyListener keyListeners[] = (KeyListener[]) (getListeners(KeyListener.class)); for (int i = 0; i < keyListeners.length; i++) { removeKeyListener(keyListeners[i]); } // Add new key listeners addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { int modifiers = e.getModifiers(); int keyCode = e.getKeyCode(); if (modifiers > 0 || e.isActionKey()) return ; char c = e.getKeyChar(); if (!Character.isISOControl(c)) { searchTextField.setText(String.valueOf(c)); displaySearchField(); } } }); // Create a the "multi-event" listener for the text field. Instead of // adding separate instances of each needed listener, we're using a // class which implements them all. This approach is used in order // to avoid the creation of 4 instances which takes some time SearchFieldListener searchFieldListener = new SearchFieldListener(); searchTextField.addKeyListener(searchFieldListener); searchTextField.addFocusListener(searchFieldListener); searchTextField.getDocument().addDocumentListener(searchFieldListener); } private class SearchFieldListener extends KeyAdapter implements DocumentListener, FocusListener { SearchFieldListener() {} /** The last search results */ private ArrayList results = new ArrayList(); /** The last selected index from the search results. */ private int currentSelectionIndex; public void changedUpdate(DocumentEvent e) { searchForNode(); } public void insertUpdate(DocumentEvent e) { searchForNode(); } public void removeUpdate(DocumentEvent e) { searchForNode(); } public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); if (keyCode == KeyEvent.VK_ESCAPE) { removeSearchField(); ExplorerTree.this.requestFocus(); } else if (keyCode == KeyEvent.VK_UP) { currentSelectionIndex--; displaySearchResult(); // Stop processing the event here. Otherwise it's dispatched // to the tree too (which scrolls) e.consume(); } else if (keyCode == KeyEvent.VK_DOWN) { currentSelectionIndex++; displaySearchResult(); // Stop processing the event here. Otherwise it's dispatched // to the tree too (which scrolls) e.consume(); } else if (keyCode == KeyEvent.VK_TAB) { if (maxPrefix != null) searchTextField.setText (maxPrefix); e.consume(); } else if (keyCode == KeyEvent.VK_ENTER) { removeSearchField(); expandPath(getSelectionPath()); ExplorerTree.this.requestFocus(); ExplorerTree.this.dispatchEvent(e); } } /** Searches for a node in the tree. */ private void searchForNode() { currentSelectionIndex = 0; results.clear(); maxPrefix = null; String text = searchTextField.getText().toUpperCase(); if (text.length() > 0) { doSearch(text, results); displaySearchResult(); } } private void displaySearchResult() { int sz = results.size(); if (sz > 0) { if (currentSelectionIndex < 0) { currentSelectionIndex = sz - 1; } else if (currentSelectionIndex >= sz) { currentSelectionIndex = 0; } TreePath path = (TreePath) results.get(currentSelectionIndex); setSelectionPath(path); scrollPathToVisible(path); } else { clearSelection(); } } public void focusGained(FocusEvent e) { // Do nothing } public void focusLost(FocusEvent e) { removeSearchField(); } } private int originalScrollMode; private void doSearch(String str, ArrayList results) { int rows[] = getSelectionRows(); int row = (rows == null || rows.length == 0) ? 0 : rows[0]; int rowCount = getRowCount(); for (int i = row; i < getRowCount(); i++) { addPathIfMatches(str, i, results); } for (int i = 0; i < row && i < rowCount; i++) { addPathIfMatches(str, i, results); } } private void addPathIfMatches(String str, int row, ArrayList results) { TreePath path = getPathForRow(row); TreeNode node = (TreeNode) path.getLastPathComponent(); String nodeName = node.toString(); if (nodeName.toUpperCase().startsWith(str)) { if (maxPrefix == null) maxPrefix = nodeName; else maxPrefix = findMaxPrefix(maxPrefix, nodeName); results.add(path); } } private String findMaxPrefix (String str1, String str2) { String res = null; for (int i = 0; str1.regionMatches (true, 0, str2, 0, i); i++) { res = str1.substring (0, i); } return res; } /** * Adds the search field to the tree. */ private void displaySearchField() { if (!searchTextField.isDisplayable()) { JViewport viewport = TreeView.this.getViewport(); originalScrollMode = viewport.getScrollMode(); viewport.setScrollMode(JViewport.SIMPLE_SCROLL_MODE); Rectangle visibleTreeRect = getVisibleRect(); add(searchTextField); repaint(); // bugfix #28501, avoid the chars duplicated on jdk1.3 SwingUtilities.invokeLater (new Runnable () { public void run () { searchTextField.requestFocus (); } }); } } public void paint(Graphics g) { Rectangle visibleRect = getVisibleRect(); if (searchTextField.isDisplayable()) { searchTextField.setBounds( Math.max(3, visibleRect.x + visibleRect.width - 163), visibleRect.y + 3, Math.min(getPreferredSize().width - 6, 160), heightOfTextField); } super.paint(g); } /** * Removes the search field from the tree. */ private void removeSearchField() { if (searchTextField.isDisplayable ()) { remove(searchTextField); TreeView.this.getViewport().setScrollMode(originalScrollMode); this.repaint(); } } /** notify the Component to autoscroll */ public void autoscroll (Point cursorLoc) { getSupport().autoscroll(cursorLoc); } /** @return the Insets describing the autoscrolling * region or border relative to the geometry of the * implementing Component. */ public Insets getAutoscrollInsets () { return getSupport().getAutoscrollInsets(); } /** Safe getter for autoscroll support. */ AutoscrollSupport getSupport() { if (support == null) support = new AutoscrollSupport( this, new Insets(15, 10, 15, 10)); return support; } public String getToolTipText(MouseEvent event) { if(event != null) { Point p = event.getPoint(); int selRow = getRowForLocation(p.x, p.y); if(selRow != -1) { TreePath path = getPathForRow(selRow); VisualizerNode v = (VisualizerNode)path.getLastPathComponent(); String tooltip = v.getShortDescription(); String displayName = v.getDisplayName (); if ((tooltip != null) && !tooltip.equals (displayName)) return tooltip; } } return null; } protected TreeModelListener createTreeModelListener() { return new ModelHandler(); } public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleExplorerTree(); } return accessibleContext; } private class AccessibleExplorerTree extends AccessibleJTree { AccessibleExplorerTree() {} public String getAccessibleName() { return TreeView.this.getAccessibleContext().getAccessibleName(); } public String getAccessibleDescription() { return TreeView.this.getAccessibleContext().getAccessibleDescription(); } } private class ModelHandler extends JTree.TreeModelHandler { ModelHandler() {} public void treeStructureChanged(TreeModelEvent e) { // Remember selections and expansions TreePath selectionPaths[] = getSelectionPaths(); java.util.Enumeration expanded = getExpandedDescendants( e.getTreePath() ); // Restructure the node super.treeStructureChanged( e ); // Expand previously expanded paths if ( expanded != null ) { while( expanded.hasMoreElements() ) { expandPath( (TreePath)expanded.nextElement() ); } } // Select previously selected paths if ( selectionPaths != null && selectionPaths.length > 0 ) { boolean wasSelected = isPathSelected(selectionPaths[0]); setSelectionPaths( selectionPaths ); if (!wasSelected) { // do not scroll if the first selection path survived structure change scrollPathToVisible( selectionPaths[0] ); } } } public void treeNodesRemoved (TreeModelEvent e) { // called to removed from JTree.expandedState super.treeNodesRemoved (e); if (tree.getSelectionCount () == 0) { TreePath path = findSiblingTreePath (e.getTreePath (), e.getChildIndices ()); if (path != null && path.getPathCount () > 0) { tree.setSelectionPath (path); } } } } /** * This class is copy of BasicTreeUI.TreeCancelEditingAction * from JDK1.4 - in JDK 1.3 the isEnabled method is wrong. */ private class OurTreeCancelEditingAction extends AbstractAction { OurTreeCancelEditingAction() {} public void actionPerformed(ActionEvent e) { if(tree != null) { tree.cancelEditing(); } } public boolean isEnabled() { return (tree != null && tree.isEnabled() && ExplorerTree.this.getUI().isEditing(tree)); } } // End of class TreeCancelEditingAction } /** Returns the tree path nearby to given tree node. Either a sibling if there is or the parent. * @param parentPath tree path to parent of changed nodes * @param childIndices indexes of changed children * @return the tree path or null if there no changed children */ final static TreePath findSiblingTreePath (TreePath parentPath, int[] childIndices) { if (childIndices == null) { throw new IllegalArgumentException ("Indexes of changed children are null."); // NOI18N } if (parentPath == null) { throw new IllegalArgumentException ("The tree path to parent is null."); // NOI18N } // bugfix #29342, if childIndices is the empty then don't change the selection if (childIndices.length == 0) { return null; } TreeNode parent = (TreeNode)parentPath.getLastPathComponent (); Object[] parentPaths = parentPath.getPath(); TreePath newSelection = null; if (parent.getChildCount() > 0) { // get parent path, add child to it int childPathLength = parentPaths.length + 1; Object[] childPath = new Object[childPathLength]; System.arraycopy(parentPaths, 0, childPath, 0, parentPaths.length); int selectedChild = childIndices[0] - 1; if (selectedChild < 0) { selectedChild = 0; } childPath[childPathLength - 1] = parent.getChildAt(selectedChild); newSelection = new TreePath(childPath); } else { // all children removed, select parent newSelection = new TreePath(parentPaths); } return newSelection; } }