0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.openide.explorer.view;
0042:
0043: import org.openide.awt.MouseUtils;
0044: import org.openide.explorer.ExplorerManager;
0045: import org.openide.nodes.Children;
0046: import org.openide.nodes.Node;
0047: import org.openide.nodes.NodeOp;
0048: import org.openide.util.ContextAwareAction;
0049: import org.openide.util.Lookup;
0050: import org.openide.util.Mutex;
0051: import org.openide.util.NbBundle;
0052: import org.openide.util.RequestProcessor;
0053: import org.openide.util.Utilities;
0054: import org.openide.util.WeakListeners;
0055: import org.openide.util.actions.SystemAction;
0056: import org.openide.util.lookup.Lookups;
0057: import org.openide.util.lookup.ProxyLookup;
0058: import java.awt.Container;
0059: import java.awt.Cursor;
0060: import java.awt.Dimension;
0061: import java.awt.Font;
0062: import java.awt.Graphics;
0063: import java.awt.Insets;
0064: import java.awt.Point;
0065: import java.awt.Rectangle;
0066: import java.awt.Toolkit;
0067: import java.awt.datatransfer.Clipboard;
0068: import java.awt.datatransfer.DataFlavor;
0069: import java.awt.datatransfer.Transferable;
0070: import java.awt.dnd.Autoscroll;
0071: import java.awt.dnd.DnDConstants;
0072: import java.awt.event.ActionEvent;
0073: import java.awt.event.ActionListener;
0074: import java.awt.event.FocusEvent;
0075: import java.awt.event.FocusListener;
0076: import java.awt.event.InputEvent;
0077: import java.awt.event.KeyAdapter;
0078: import java.awt.event.KeyEvent;
0079: import java.awt.event.KeyListener;
0080: import java.awt.event.MouseAdapter;
0081: import java.awt.event.MouseEvent;
0082: import java.beans.PropertyChangeEvent;
0083: import java.beans.PropertyChangeListener;
0084: import java.beans.PropertyVetoException;
0085: import java.beans.VetoableChangeListener;
0086: import java.util.ArrayList;
0087: import java.util.Arrays;
0088: import java.util.Iterator;
0089: import java.util.List;
0090: import java.util.Set;
0091: import java.util.HashSet;
0092: import java.util.logging.Level;
0093: import java.util.logging.Logger;
0094: import javax.accessibility.AccessibleContext;
0095: import javax.swing.AbstractAction;
0096: import javax.swing.Action;
0097: import javax.swing.BorderFactory;
0098: import javax.swing.BoxLayout;
0099: import javax.swing.JComponent;
0100: import javax.swing.JLabel;
0101: import javax.swing.JMenu;
0102: import javax.swing.JPanel;
0103: import javax.swing.JPopupMenu;
0104: import javax.swing.JScrollPane;
0105: import javax.swing.JTextField;
0106: import javax.swing.JTree;
0107: import javax.swing.JViewport;
0108: import javax.swing.KeyStroke;
0109: import javax.swing.SwingUtilities;
0110: import javax.swing.ToolTipManager;
0111: import javax.swing.TransferHandler;
0112: import javax.swing.UIManager;
0113: import javax.swing.event.DocumentEvent;
0114: import javax.swing.event.DocumentListener;
0115: import javax.swing.event.TreeExpansionEvent;
0116: import javax.swing.event.TreeExpansionListener;
0117: import javax.swing.event.TreeModelEvent;
0118: import javax.swing.event.TreeModelListener;
0119: import javax.swing.event.TreeSelectionEvent;
0120: import javax.swing.event.TreeSelectionListener;
0121: import javax.swing.event.TreeWillExpandListener;
0122: import javax.swing.plaf.UIResource;
0123: import javax.swing.text.Position;
0124: import javax.swing.tree.ExpandVetoException;
0125: import javax.swing.tree.RowMapper;
0126: import javax.swing.tree.TreeModel;
0127: import javax.swing.tree.TreeNode;
0128: import javax.swing.tree.TreePath;
0129: import javax.swing.tree.TreeSelectionModel;
0130:
0131: /**
0132: * Base class for tree-style explorer views.
0133: * @see BeanTreeView
0134: * @see ContextTreeView
0135: */
0136: public abstract class TreeView extends JScrollPane {
0137: static {
0138: // Workaround for issue #42794 on JDK1.5
0139: UIManager.put("Tree.scrollsHorizontallyAndVertically",
0140: Boolean.TRUE);
0141: }
0142:
0143: //
0144: // static fields
0145: //
0146:
0147: /** generated Serialized Version UID */
0148: static final long serialVersionUID = -1639001987693376168L;
0149:
0150: /** How long it takes before collapsed nodes are released from the tree's cache
0151: */
0152: private static final int TIME_TO_COLLAPSE = (System
0153: .getProperty("netbeans.debug.heap") != null) ? 0 : 15000;
0154:
0155: /** Minimum width of this component. */
0156: private static final int MIN_TREEVIEW_WIDTH = 400;
0157:
0158: /** Minimum height of this component. */
0159: private static final int MIN_TREEVIEW_HEIGHT = 400;
0160:
0161: //GTK Look and feel hack
0162: private static boolean isSynth = UIManager.getLookAndFeel()
0163: .getClass().getName()
0164: .indexOf("com.sun.java.swing.plaf.gtk") != -1;
0165:
0166: //
0167: // components
0168: //
0169:
0170: /** Main <code>JTree</code> component. */
0171: transient protected JTree tree;
0172:
0173: /** model */
0174: transient NodeTreeModel treeModel;
0175:
0176: /** Explorer manager, valid when this view is showing */
0177: transient ExplorerManager manager;
0178:
0179: // Attributes
0180:
0181: /** Mouse and action listener. */
0182: transient PopupSupport defaultActionListener;
0183:
0184: /** Property indicating whether the default action is enabled. */
0185: transient boolean defaultActionEnabled;
0186:
0187: /** not null if popup menu enabled */
0188: transient PopupAdapter popupListener;
0189:
0190: /** the most important listener (on four types of events */
0191: transient TreePropertyListener managerListener = null;
0192:
0193: /** weak variation of the listener for property change on the explorer manager */
0194: transient PropertyChangeListener wlpc;
0195:
0196: /** weak variation of the listener for vetoable change on the explorer manager */
0197: transient VetoableChangeListener wlvc;
0198:
0199: /** true if drag support is active */
0200: private transient boolean dragActive = true;
0201:
0202: /** true if drop support is active */
0203: private transient boolean dropActive = true;
0204:
0205: /** Drag support */
0206: transient TreeViewDragSupport dragSupport;
0207:
0208: /** Drop support */
0209: transient TreeViewDropSupport dropSupport;
0210: transient boolean dropTargetPopupAllowed = true;
0211: transient private Container contentPane;
0212: transient private List storeSelectedPaths;
0213:
0214: // default DnD actions
0215: transient private int allowedDragActions = DnDConstants.ACTION_COPY_OR_MOVE
0216: | DnDConstants.ACTION_REFERENCE;
0217: transient private int allowedDropActions = DnDConstants.ACTION_COPY_OR_MOVE
0218: | DnDConstants.ACTION_REFERENCE;
0219:
0220: /**
0221: * Whether the quick search uses prefix or substring.
0222: * Defaults to false meaning prefix is used.
0223: */
0224: transient private boolean quickSearchUsingSubstring = false;
0225:
0226: /** Constructor.
0227: */
0228: public TreeView() {
0229: this (true, true);
0230: }
0231:
0232: /** Constructor.
0233: * @param defaultAction should double click on a node open its default action?
0234: * @param popupAllowed should right-click open popup?
0235: */
0236: public TreeView(boolean defaultAction, boolean popupAllowed) {
0237: initializeTree();
0238:
0239: // // activation of drop target
0240: // if (DragDropUtilities.dragAndDropEnabled) {
0241: // setdroptExplorerDnDManager.getDefault().addFutureDropTarget(this);
0242: //
0243: // // note: drag target is activated on focus gained
0244: // }
0245: setDropTarget(DragDropUtilities.dragAndDropEnabled);
0246:
0247: setPopupAllowed(popupAllowed);
0248: setDefaultActionAllowed(defaultAction);
0249:
0250: Dimension dim = null;
0251:
0252: try {
0253: dim = getPreferredSize();
0254:
0255: if (dim == null) {
0256: dim = new Dimension(MIN_TREEVIEW_WIDTH,
0257: MIN_TREEVIEW_HEIGHT);
0258: }
0259: } catch (NullPointerException npe) {
0260: dim = new Dimension(MIN_TREEVIEW_WIDTH, MIN_TREEVIEW_HEIGHT);
0261: }
0262:
0263: if (dim.width < MIN_TREEVIEW_WIDTH) {
0264: dim.width = MIN_TREEVIEW_WIDTH;
0265: }
0266:
0267: if (dim.height < MIN_TREEVIEW_HEIGHT) {
0268: dim.height = MIN_TREEVIEW_HEIGHT;
0269: }
0270:
0271: setPreferredSize(dim);
0272: }
0273:
0274: public void updateUI() {
0275: super .updateUI();
0276:
0277: //On GTK L&F, the viewport border must be set to empty (not null!) or we still get border buildup
0278: setViewportBorder(BorderFactory.createEmptyBorder());
0279: setBorder(BorderFactory.createEmptyBorder());
0280: }
0281:
0282: /** Initializes the tree & model.
0283: * [dafe] Horrible technique - overridable method called from constructor
0284: * may result in subclass code invoked when this object is not fully
0285: * constructed.
0286: * However I don't have enough knowledge about this code to change it.
0287: */
0288: void initializeTree() {
0289: // initilizes the JTree
0290: treeModel = createModel();
0291: treeModel.addView(this );
0292:
0293: tree = new ExplorerTree(treeModel);
0294:
0295: NodeRenderer rend = new NodeRenderer();
0296: tree.setCellRenderer(rend);
0297: tree.putClientProperty("JTree.lineStyle", "Angled"); // NOI18N
0298: setViewportView(tree);
0299:
0300: // Init of the editor
0301: tree.setCellEditor(new TreeViewCellEditor(tree));
0302: tree.setEditable(true);
0303:
0304: // set selection mode to DISCONTIGUOUS_TREE_SELECTION as default
0305: setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
0306:
0307: ToolTipManager.sharedInstance().registerComponent(tree);
0308:
0309: // init listener & attach it to closing of
0310: managerListener = new TreePropertyListener();
0311: tree.addTreeExpansionListener(managerListener);
0312: tree.addTreeWillExpandListener(managerListener);
0313:
0314: // do not care about focus
0315: setRequestFocusEnabled(false);
0316:
0317: defaultActionListener = new PopupSupport();
0318: getInputMap(JTree.WHEN_FOCUSED).put(
0319: KeyStroke.getKeyStroke(KeyEvent.VK_F10,
0320: KeyEvent.SHIFT_DOWN_MASK),
0321: "org.openide.actions.PopupAction");
0322: getActionMap().put("org.openide.actions.PopupAction",
0323: defaultActionListener.popup);
0324: tree.addFocusListener(defaultActionListener);
0325: tree.addMouseListener(defaultActionListener);
0326: }
0327:
0328: /** Is it permitted to display a popup menu?
0329: * @return <code>true</code> if so
0330: */
0331: public boolean isPopupAllowed() {
0332: return popupListener != null;
0333: }
0334:
0335: /** Enable/disable displaying popup menus on tree view items.
0336: * Default is enabled.
0337: * @param value <code>true</code> to enable
0338: */
0339: public void setPopupAllowed(boolean value) {
0340: if ((popupListener == null) && value) {
0341: // on
0342: popupListener = new PopupAdapter();
0343: tree.addMouseListener(popupListener);
0344:
0345: return;
0346: }
0347:
0348: if ((popupListener != null) && !value) {
0349: // off
0350: tree.removeMouseListener(popupListener);
0351: popupListener = null;
0352:
0353: return;
0354: }
0355: }
0356:
0357: void setDropTargetPopupAllowed(boolean value) {
0358: dropTargetPopupAllowed = value;
0359:
0360: if (dropSupport != null) {
0361: dropSupport.setDropTargetPopupAllowed(value);
0362: }
0363: }
0364:
0365: boolean isDropTargetPopupAllowed() {
0366: return (dropSupport != null) ? dropSupport
0367: .isDropTargetPopupAllowed() : dropTargetPopupAllowed;
0368: }
0369:
0370: /** Does a double click invoke the default node action?
0371: * @return <code>true</code> if so
0372: */
0373: public boolean isDefaultActionEnabled() {
0374: return defaultActionEnabled;
0375: }
0376:
0377: /** Requests focus for the tree component. Overrides superclass method. */
0378: public void requestFocus() {
0379: tree.requestFocus();
0380: }
0381:
0382: /** Requests focus for the tree component. Overrides superclass method. */
0383: public boolean requestFocusInWindow() {
0384: return tree.requestFocusInWindow();
0385: }
0386:
0387: /** Enable/disable double click to invoke default action.
0388: * If defaultAction is not enabled double click expand/collapse node.
0389: * @param value <code>true</code> to enable
0390: */
0391: public void setDefaultActionAllowed(boolean value) {
0392: defaultActionEnabled = value;
0393:
0394: if (value) {
0395: tree
0396: .registerKeyboardAction(defaultActionListener,
0397: KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,
0398: 0, false), JComponent.WHEN_FOCUSED);
0399: } else {
0400: // Switch off.
0401: tree.unregisterKeyboardAction(KeyStroke.getKeyStroke(
0402: KeyEvent.VK_ENTER, 0, false));
0403: }
0404: }
0405:
0406: /**
0407: * Is the root node of the tree displayed?
0408: *
0409: * @return <code>true</code> if so
0410: */
0411: public boolean isRootVisible() {
0412: return tree.isRootVisible();
0413: }
0414:
0415: /** Set whether or not the root node from
0416: * the <code>TreeModel</code> is visible.
0417: *
0418: * @param visible <code>true</code> if it is to be displayed
0419: */
0420: public void setRootVisible(boolean visible) {
0421: tree.setRootVisible(visible);
0422: tree.setShowsRootHandles(!visible);
0423: }
0424:
0425: /**
0426: * Set whether the quick search feature uses substring or prefix
0427: * matching for the typed characters. Defaults to prefix (false).
0428: * @since 6.11
0429: * @param useSubstring <code>true</code> if substring search is used in quick search
0430: */
0431: public void setUseSubstringInQuickSearch(boolean useSubstring) {
0432: quickSearchUsingSubstring = useSubstring;
0433: }
0434:
0435: /********** Support for the Drag & Drop operations *********/
0436: /** Drag support is enabled by default.
0437: * @return true if dragging from the view is enabled, false
0438: * otherwise.
0439: */
0440: public boolean isDragSource() {
0441: return dragActive;
0442: }
0443:
0444: /** Enables/disables dragging support.
0445: * @param state true enables dragging support, false disables it.
0446: */
0447: public void setDragSource(boolean state) {
0448: // create drag support if needed
0449: if (state && (dragSupport == null)) {
0450: dragSupport = new TreeViewDragSupport(this , tree);
0451: }
0452:
0453: // activate / deactivate support according to the state
0454: dragActive = state;
0455:
0456: if (dragSupport != null) {
0457: dragSupport.activate(dragActive);
0458: }
0459: }
0460:
0461: /** Drop support is enabled by default.
0462: * @return true if dropping to the view is enabled, false
0463: * otherwise<br>
0464: */
0465: public boolean isDropTarget() {
0466: return dropActive;
0467: }
0468:
0469: /** Enables/disables dropping support.
0470: * @param state true means drops into view are allowed,
0471: * false forbids any drops into this view.
0472: */
0473: public void setDropTarget(boolean state) {
0474: // create drop support if needed
0475: if (dropActive && (dropSupport == null)) {
0476: dropSupport = new TreeViewDropSupport(this , tree,
0477: dropTargetPopupAllowed);
0478: }
0479:
0480: // activate / deactivate support according to the state
0481: dropActive = state;
0482:
0483: if (dropSupport != null) {
0484: dropSupport.activate(dropActive);
0485: }
0486: }
0487:
0488: /** Actions constants comes from {@link java.awt.dnd.DnDConstants}.
0489: * All actions (copy, move, link) are allowed by default.
0490: * @return int representing set of actions which are allowed when dragging from
0491: * asociated component.
0492: */
0493: public int getAllowedDragActions() {
0494: return allowedDragActions;
0495: }
0496:
0497: /** Sets allowed actions for dragging
0498: * @param actions new drag actions, using {@link java.awt.dnd.DnDConstants}
0499: */
0500: public void setAllowedDragActions(int actions) {
0501: // PENDING: check parameters
0502: allowedDragActions = actions;
0503: }
0504:
0505: /** Actions constants comes from {@link java.awt.dnd.DnDConstants}.
0506: * All actions are allowed by default.
0507: * @return int representing set of actions which are allowed when dropping
0508: * into the asociated component.
0509: */
0510: public int getAllowedDropActions() {
0511: return allowedDropActions;
0512: }
0513:
0514: /** Sets allowed actions for dropping.
0515: * @param actions new allowed drop actions, using {@link java.awt.dnd.DnDConstants}
0516: */
0517: public void setAllowedDropActions(int actions) {
0518: // PENDING: check parameters
0519: allowedDropActions = actions;
0520: }
0521:
0522: //
0523: // Control over expanded state
0524: //
0525:
0526: /** Collapses the tree under given node.
0527: *
0528: * @param n node to collapse
0529: */
0530: public void collapseNode(Node n) {
0531: if (n == null) {
0532: throw new IllegalArgumentException();
0533: }
0534:
0535: TreePath treePath = new TreePath(treeModel
0536: .getPathToRoot(VisualizerNode.getVisualizer(null, n)));
0537: tree.collapsePath(treePath);
0538: }
0539:
0540: /** Expandes the node in the tree.
0541: *
0542: * @param n node
0543: */
0544: public void expandNode(Node n) {
0545: if (n == null) {
0546: throw new IllegalArgumentException();
0547: }
0548:
0549: lookupExplorerManager();
0550:
0551: TreePath treePath = new TreePath(treeModel
0552: .getPathToRoot(VisualizerNode.getVisualizer(null, n)));
0553:
0554: tree.expandPath(treePath);
0555: }
0556:
0557: /** Test whether a node is expanded in the tree or not
0558: * @param n the node to test
0559: * @return true if the node is expanded
0560: */
0561: public boolean isExpanded(Node n) {
0562: TreePath treePath = new TreePath(treeModel
0563: .getPathToRoot(VisualizerNode.getVisualizer(null, n)));
0564:
0565: return tree.isExpanded(treePath);
0566: }
0567:
0568: /** Expands all paths.
0569: */
0570: public void expandAll() {
0571: int i = 0;
0572: int j /*, k = tree.getRowCount()*/;
0573:
0574: do {
0575: do {
0576: j = tree.getRowCount();
0577: tree.expandRow(i);
0578: } while (j != tree.getRowCount());
0579:
0580: i++;
0581: } while (i < tree.getRowCount());
0582: }
0583:
0584: //
0585: // Processing functions
0586: //
0587:
0588: public void validate() {
0589: Children.MUTEX.readAccess(new Runnable() {
0590: public void run() {
0591: TreeView.super .validate();
0592: }
0593: });
0594: }
0595:
0596: /** Initializes the component and lookup explorer manager.
0597: */
0598: public void addNotify() {
0599: super .addNotify();
0600: lookupExplorerManager();
0601: }
0602:
0603: /** Registers in the tree of components.
0604: */
0605: private void lookupExplorerManager() {
0606: // Enter key in the tree
0607: ExplorerManager newManager = ExplorerManager
0608: .find(TreeView.this );
0609:
0610: if (newManager != manager) {
0611: if (manager != null) {
0612: manager.removeVetoableChangeListener(wlvc);
0613: manager.removePropertyChangeListener(wlpc);
0614: }
0615:
0616: manager = newManager;
0617:
0618: manager.addVetoableChangeListener(wlvc = WeakListeners
0619: .vetoableChange(managerListener, manager));
0620: manager.addPropertyChangeListener(wlpc = WeakListeners
0621: .propertyChange(managerListener, manager));
0622:
0623: synchronizeRootContext();
0624: synchronizeExploredContext();
0625: synchronizeSelectedNodes();
0626: }
0627:
0628: // Sometimes the listener is registered twice and we get the
0629: // selection events twice. Removing the listener before adding it
0630: // should be a safe fix.
0631: tree.getSelectionModel().removeTreeSelectionListener(
0632: managerListener);
0633: tree.getSelectionModel().addTreeSelectionListener(
0634: managerListener);
0635: }
0636:
0637: /** Deinitializes listeners.
0638: */
0639: public void removeNotify() {
0640: super .removeNotify();
0641:
0642: tree.getSelectionModel().removeTreeSelectionListener(
0643: managerListener);
0644: }
0645:
0646: // *************************************
0647: // Methods to be overriden by subclasses
0648: // *************************************
0649:
0650: /** Allows subclasses to provide own model for displaying nodes.
0651: * @return the model to use for this view
0652: */
0653: protected abstract NodeTreeModel createModel();
0654:
0655: /** Called to allow subclasses to define the behaviour when a
0656: * node(s) are selected in the tree.
0657: *
0658: * @param nodes the selected nodes
0659: * @param em explorer manager to work on (change nodes to it)
0660: * @throws PropertyVetoException if the change cannot be done by the explorer
0661: * (the exception is silently consumed)
0662: */
0663: protected abstract void selectionChanged(Node[] nodes,
0664: ExplorerManager em) throws PropertyVetoException;
0665:
0666: /** Called when explorer manager is about to change the current selection.
0667: * The view can forbid the change if it is not able to display such
0668: * selection.
0669: *
0670: * @param nodes the nodes to select
0671: * @return false if the view is not able to change the selection
0672: */
0673: protected abstract boolean selectionAccept(Node[] nodes);
0674:
0675: /** Show a given path in the screen. It depends on the kind of <code>TreeView</code>
0676: * if the path should be expanded or just made visible.
0677: *
0678: * @param path the path
0679: */
0680: protected abstract void showPath(TreePath path);
0681:
0682: /** Shows selection to reflect the current state of the selection in the explorer.
0683: *
0684: * @param paths array of paths that should be selected
0685: */
0686: protected abstract void showSelection(TreePath[] paths);
0687:
0688: /** Specify whether a context menu of the explored context should be used.
0689: * Applicable when no nodes are selected and the user wants to invoke
0690: * a context menu (clicks right mouse button).
0691: *
0692: * @return <code>true</code> if so; <code>false</code> in the default implementation
0693: */
0694: protected boolean useExploredContextMenu() {
0695: return false;
0696: }
0697:
0698: /** Check if selection of the nodes could break the selection mode set in TreeSelectionModel.
0699: * @param nodes the nodes for selection
0700: * @return true if the selection mode is broken */
0701: private boolean isSelectionModeBroken(Node[] nodes) {
0702: // if nodes are empty or single the everthing is ok
0703: // or if discontiguous selection then everthing ok
0704: if ((nodes.length <= 1)
0705: || (getSelectionMode() == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)) {
0706: return false;
0707: }
0708:
0709: // if many nodes
0710: // brakes single selection mode
0711: if (getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION) {
0712: return true;
0713: }
0714:
0715: // check the contiguous selection mode
0716: TreePath[] paths = new TreePath[nodes.length];
0717: RowMapper rowMapper = tree.getSelectionModel().getRowMapper();
0718:
0719: // if rowMapper is null then tree bahaves as discontiguous selection mode is set
0720: if (rowMapper == null) {
0721: return false;
0722: }
0723:
0724: ArrayList<Node> toBeExpaned = new ArrayList<Node>(3);
0725:
0726: for (int i = 0; i < nodes.length; i++) {
0727: toBeExpaned.clear();
0728:
0729: Node n = nodes[i];
0730:
0731: while (n.getParentNode() != null) {
0732: if (!isExpanded(n)) {
0733: toBeExpaned.add(n);
0734: }
0735:
0736: n = n.getParentNode();
0737: }
0738:
0739: for (int j = toBeExpaned.size() - 1; j >= 0; j--) {
0740: expandNode((Node) toBeExpaned.get(j));
0741: }
0742:
0743: TreePath treePath = new TreePath(treeModel
0744: .getPathToRoot(VisualizerNode.getVisualizer(null,
0745: nodes[i])));
0746: paths[i] = treePath;
0747: }
0748:
0749: int[] rows = rowMapper.getRowsForPaths(paths);
0750:
0751: // check selection's rows
0752: Arrays.sort(rows);
0753:
0754: for (int i = 1; i < rows.length; i++) {
0755: if (rows[i] != (rows[i - 1] + 1)) {
0756: return true;
0757: }
0758: }
0759:
0760: // all is ok
0761: return false;
0762: }
0763:
0764: //
0765: // synchronizations
0766: //
0767:
0768: /** Called when selection in tree is changed.
0769: */
0770: final void callSelectionChanged(Node[] nodes) {
0771: manager.removePropertyChangeListener(wlpc);
0772: manager.removeVetoableChangeListener(wlvc);
0773:
0774: try {
0775: selectionChanged(nodes, manager);
0776: } catch (PropertyVetoException e) {
0777: synchronizeSelectedNodes();
0778: } finally {
0779: manager.addPropertyChangeListener(wlpc);
0780: manager.addVetoableChangeListener(wlvc);
0781: }
0782: }
0783:
0784: /** Synchronize the root context from the manager of this Explorer.
0785: */
0786: final void synchronizeRootContext() {
0787: treeModel.setNode(manager.getRootContext());
0788: }
0789:
0790: /** Synchronize the explored context from the manager of this Explorer.
0791: */
0792: final void synchronizeExploredContext() {
0793: Node n = manager.getExploredContext();
0794:
0795: if (n != null) {
0796: TreePath treePath = new TreePath(treeModel
0797: .getPathToRoot(VisualizerNode
0798: .getVisualizer(null, n)));
0799: showPath(treePath);
0800: }
0801: }
0802:
0803: /** Sets the selection model, which must be one of
0804: * TreeSelectionModel.SINGLE_TREE_SELECTION,
0805: * TreeSelectionModel.CONTIGUOUS_TREE_SELECTION or
0806: * TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION.
0807: * <p>
0808: * This may change the selection if the current selection is not valid
0809: * for the new mode. For example, if three TreePaths are
0810: * selected when the mode is changed to <code>TreeSelectionModel.SINGLE_TREE_SELECTION</code>,
0811: * only one TreePath will remain selected. It is up to the particular
0812: * implementation to decide what TreePath remains selected.
0813: * Note: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION is set as default.
0814: * @since 2.15
0815: * @param mode selection mode
0816: */
0817: public void setSelectionMode(int mode) {
0818: tree.getSelectionModel().setSelectionMode(mode);
0819: }
0820:
0821: /** Returns the current selection mode, one of
0822: * <code>TreeSelectionModel.SINGLE_TREE_SELECTION</code>,
0823: * <code>TreeSelectionModel.CONTIGUOUS_TREE_SELECTION</code> or
0824: * <code>TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION</code>.
0825: * @since 2.15
0826: * @return selection mode
0827: */
0828: public int getSelectionMode() {
0829: return tree.getSelectionModel().getSelectionMode();
0830: }
0831:
0832: //
0833: // showing and removing the wait cursor
0834: //
0835: private void showWaitCursor() {
0836: if (getRootPane() == null) {
0837: return;
0838: }
0839:
0840: contentPane = getRootPane().getContentPane();
0841:
0842: if (SwingUtilities.isEventDispatchThread()) {
0843: contentPane.setCursor(Utilities
0844: .createProgressCursor(contentPane));
0845: } else {
0846: SwingUtilities.invokeLater(new CursorR(contentPane,
0847: Utilities.createProgressCursor(contentPane)));
0848: }
0849: }
0850:
0851: private void showNormalCursor() {
0852: if (contentPane == null) {
0853: return;
0854: }
0855:
0856: if (SwingUtilities.isEventDispatchThread()) {
0857: contentPane.setCursor(null);
0858: } else {
0859: SwingUtilities.invokeLater(new CursorR(contentPane, null));
0860: }
0861: }
0862:
0863: private void prepareWaitCursor(final Node node) {
0864: // check type of node
0865: if (node == null) {
0866: showNormalCursor();
0867: }
0868:
0869: showWaitCursor();
0870: RequestProcessor.getDefault().post(new Runnable() {
0871:
0872: public void run() {
0873: try {
0874: node.getChildren().getNodes(true);
0875: } catch (Exception e) {
0876: // log a exception
0877: Logger.getLogger(TreeView.class.getName()).log(
0878: Level.WARNING, null, e);
0879: } finally {
0880: // show normal cursor above all
0881: showNormalCursor();
0882: }
0883: }
0884: });
0885: }
0886:
0887: /** Synchronize the selected nodes from the manager of this Explorer.
0888: * The default implementation does nothing.
0889: */
0890: final void synchronizeSelectedNodes() {
0891: // #40152: if there is any scheduled change to view, perform it now
0892: VisualizerNode.runQueue();
0893:
0894: Node[] arr = manager.getSelectedNodes();
0895: TreePath[] paths = new TreePath[arr.length];
0896:
0897: for (int i = 0; i < arr.length; i++) {
0898: TreePath treePath = new TreePath(treeModel
0899: .getPathToRoot(VisualizerNode.getVisualizer(null,
0900: arr[i])));
0901: paths[i] = treePath;
0902: }
0903:
0904: tree.getSelectionModel().removeTreeSelectionListener(
0905: managerListener);
0906: showSelection(paths);
0907: tree.getSelectionModel().addTreeSelectionListener(
0908: managerListener);
0909: }
0910:
0911: void scrollTreeToVisible(TreePath path, TreeNode child) {
0912: Rectangle base = tree.getVisibleRect();
0913: Rectangle b1 = tree.getPathBounds(path);
0914: Rectangle b2 = tree.getPathBounds(new TreePath(treeModel
0915: .getPathToRoot(child)));
0916:
0917: if ((base != null) && (b1 != null) && (b2 != null)) {
0918: tree.scrollRectToVisible(new Rectangle(base.x, b1.y, 1,
0919: b2.y - b1.y + b2.height));
0920: }
0921: }
0922:
0923: private void createPopup(int xpos, int ypos, JPopupMenu popup) {
0924: if (popup.getSubElements().length > 0) {
0925: popup.show(TreeView.this , xpos, ypos);
0926: }
0927: }
0928:
0929: void createPopup(int xpos, int ypos) {
0930: // bugfix #23932, don't create if it's disabled
0931: if (isPopupAllowed()) {
0932: Node[] arr = manager.getSelectedNodes();
0933:
0934: if (arr.length == 0) {
0935: // Should probably not happen when shown from right-click, but may well when from S-F10.
0936: // Create popup menu for the root node, and make sure it is selected so that action context is correct.
0937: arr = new Node[] { manager.getRootContext() };
0938:
0939: try {
0940: manager.setSelectedNodes(arr);
0941: } catch (PropertyVetoException e) {
0942: assert false : e; // not permitted to be thrown
0943: }
0944: }
0945:
0946: Action[] actions = NodeOp.findActions(arr);
0947:
0948: if (actions.length > 0) {
0949: createPopup(xpos, ypos, Utilities.actionsToPopup(
0950: actions, this ));
0951: }
0952: }
0953: }
0954:
0955: /* create standard popup menu and add newMenu to it
0956: */
0957: void createExtendedPopup(int xpos, int ypos, JMenu newMenu) {
0958: Node[] ns = manager.getSelectedNodes();
0959: JPopupMenu popup = null;
0960:
0961: if (ns.length > 0) {
0962: // if any nodes are selected --> find theirs actions
0963: Action[] actions = NodeOp.findActions(ns);
0964: popup = Utilities.actionsToPopup(actions, this );
0965: } else {
0966: // if none node is selected --> get context actions from view's root
0967: if (manager.getRootContext() != null) {
0968: popup = manager.getRootContext().getContextMenu();
0969: }
0970: }
0971:
0972: int cnt = 0;
0973:
0974: if (popup == null) {
0975: popup = SystemAction.createPopupMenu(new SystemAction[] {});
0976: }
0977:
0978: popup.add(newMenu);
0979:
0980: createPopup(xpos, ypos, popup);
0981: }
0982:
0983: /** Returns the the point at which the popup menu is to be showed. May return null.
0984: * @return the point or null
0985: */
0986: Point getPositionForPopup() {
0987: int i = tree.getLeadSelectionRow();
0988:
0989: if (i < 0) {
0990: return null;
0991: }
0992:
0993: Rectangle rect = tree.getRowBounds(i);
0994:
0995: if (rect == null) {
0996: return null;
0997: }
0998:
0999: Point p = new Point(rect.x, rect.y);
1000:
1001: // bugfix #36984, convert point by TreeView.this
1002: p = SwingUtilities.convertPoint(tree, p, TreeView.this );
1003:
1004: return p;
1005: }
1006:
1007: static Action takeAction(Action action, Node... nodes) {
1008: // bugfix #42843, use ContextAwareAction if possible
1009: if (action instanceof ContextAwareAction) {
1010: Lookup contextLookup = getLookupFor(nodes);
1011:
1012: Action contextInstance = ((ContextAwareAction) action)
1013: .createContextAwareInstance(contextLookup);
1014: assert contextInstance != action : "Cannot be same. ContextAwareAction: "
1015: + action
1016: + ", ContextAwareInstance: "
1017: + contextInstance;
1018: action = contextInstance;
1019: }
1020:
1021: return action;
1022: }
1023:
1024: private static Lookup getLookupFor(Node... nodes) {
1025: if (nodes.length == 1) {
1026: Lookup contextLookup = nodes[0].getLookup();
1027: Object o = contextLookup.lookup(nodes[0].getClass());
1028: // #55826, don't added the node twice
1029: if (!nodes[0].equals(o)) {
1030: contextLookup = new ProxyLookup(new Lookup[] {
1031: Lookups.singleton(nodes[0]), contextLookup });
1032: }
1033: return contextLookup;
1034: } else {
1035: Lookup[] lkps = new Lookup[nodes.length];
1036: for (int i = 0; i < nodes.length; i++) {
1037: lkps[i] = nodes[i].getLookup();
1038: }
1039: Lookup contextLookup = new ProxyLookup(lkps);
1040: Set<Node> toAdd = new HashSet<Node>(Arrays.asList(nodes));
1041: toAdd.removeAll(contextLookup.lookupAll(Node.class));
1042:
1043: if (!toAdd.isEmpty()) {
1044: contextLookup = new ProxyLookup(contextLookup, Lookups
1045: .fixed((Object[]) toAdd.toArray(new Node[toAdd
1046: .size()])));
1047: }
1048: return contextLookup;
1049: }
1050: }
1051:
1052: /** Returns the tree path nearby to given tree node. Either a sibling if there is or the parent.
1053: * @param parentPath tree path to parent of changed nodes
1054: * @param childIndices indexes of changed children
1055: * @return the tree path or null if there no changed children
1056: */
1057: final static TreePath findSiblingTreePath(TreePath parentPath,
1058: int[] childIndices) {
1059: if (childIndices == null) {
1060: throw new IllegalArgumentException(
1061: "Indexes of changed children are null."); // NOI18N
1062: }
1063:
1064: if (parentPath == null) {
1065: throw new IllegalArgumentException(
1066: "The tree path to parent is null."); // NOI18N
1067: }
1068:
1069: // bugfix #29342, if childIndices is the empty then don't change the selection
1070: if (childIndices.length == 0) {
1071: return null;
1072: }
1073:
1074: TreeNode parent = (TreeNode) parentPath.getLastPathComponent();
1075: Object[] parentPaths = parentPath.getPath();
1076: TreePath newSelection = null;
1077:
1078: int childCount = parent.getChildCount();
1079: if (childCount > 0) {
1080: // get parent path, add child to it
1081: int childPathLength = parentPaths.length + 1;
1082: Object[] childPath = new Object[childPathLength];
1083: System.arraycopy(parentPaths, 0, childPath, 0,
1084: parentPaths.length);
1085:
1086: int selectedChild = Math.min(childIndices[0],
1087: childCount - 1);
1088:
1089: childPath[childPathLength - 1] = parent
1090: .getChildAt(selectedChild);
1091: newSelection = new TreePath(childPath);
1092: } else {
1093: // all children removed, select parent
1094: newSelection = new TreePath(parentPaths);
1095: }
1096:
1097: return newSelection;
1098: }
1099:
1100: // Workaround for JDK issue 6472844 (NB #84970)
1101: void removedNodes(List<VisualizerNode> removed) {
1102: TreeSelectionModel sm = tree.getSelectionModel();
1103: TreePath[] selPaths = (sm != null) ? sm.getSelectionPaths()
1104: : null;
1105: if (selPaths == null)
1106: return;
1107:
1108: List<TreePath> remSel = null;
1109: for (VisualizerNode vn : removed) {
1110: TreePath path = new TreePath(treeModel.getPathToRoot(vn));
1111: for (TreePath tp : selPaths) {
1112: if (path.isDescendant(tp)) {
1113: if (remSel == null)
1114: remSel = new ArrayList();
1115: remSel.add(tp);
1116: }
1117: }
1118: }
1119:
1120: if (remSel != null) {
1121: sm.removeSelectionPaths(remSel.toArray(new TreePath[remSel
1122: .size()]));
1123: }
1124: }
1125:
1126: private static class CursorR implements Runnable {
1127: private Container contentPane;
1128: private Cursor c;
1129:
1130: private CursorR(Container cont, Cursor c) {
1131: contentPane = cont;
1132: this .c = c;
1133: }
1134:
1135: public void run() {
1136: contentPane.setCursor(c);
1137: }
1138: }
1139:
1140: /** Listens to the property changes on tree */
1141: class TreePropertyListener implements VetoableChangeListener,
1142: PropertyChangeListener, TreeExpansionListener,
1143: TreeWillExpandListener, TreeSelectionListener, Runnable {
1144: private RequestProcessor.Task scheduled;
1145: private TreePath[] readAccessPaths;
1146:
1147: TreePropertyListener() {
1148: }
1149:
1150: public void vetoableChange(PropertyChangeEvent evt)
1151: throws PropertyVetoException {
1152: if (evt.getPropertyName().equals(
1153: ExplorerManager.PROP_SELECTED_NODES)) {
1154: // issue 11928 check if selecetion mode will be broken
1155: Node[] nodes = (Node[]) evt.getNewValue();
1156:
1157: if (isSelectionModeBroken(nodes)) {
1158: throw new PropertyVetoException("selection mode "
1159: + getSelectionMode() + " broken by "
1160: + Arrays.asList(nodes), evt); // NOI18N
1161: }
1162:
1163: if (!selectionAccept(nodes)) {
1164: throw new PropertyVetoException("selection "
1165: + Arrays.asList(nodes) + " rejected", evt); // NOI18N
1166: }
1167: }
1168: }
1169:
1170: public final void propertyChange(final PropertyChangeEvent evt) {
1171: if (manager == null) {
1172: return; // the tree view has been removed before the event got delivered
1173: }
1174:
1175: if (evt.getPropertyName().equals(
1176: ExplorerManager.PROP_ROOT_CONTEXT)) {
1177: synchronizeRootContext();
1178: }
1179:
1180: if (evt.getPropertyName().equals(
1181: ExplorerManager.PROP_EXPLORED_CONTEXT)) {
1182: synchronizeExploredContext();
1183: }
1184:
1185: if (evt.getPropertyName().equals(
1186: ExplorerManager.PROP_SELECTED_NODES)) {
1187: synchronizeSelectedNodes();
1188: }
1189: }
1190:
1191: public synchronized void treeExpanded(TreeExpansionEvent ev) {
1192:
1193: if (!tree.getScrollsOnExpand()) {
1194: return;
1195: }
1196:
1197: RequestProcessor.Task t = scheduled;
1198:
1199: if (t != null) {
1200: t.cancel();
1201: }
1202:
1203: class Request implements Runnable {
1204: private TreePath path;
1205:
1206: public Request(TreePath path) {
1207: this .path = path;
1208: }
1209:
1210: public void run() {
1211: if (!SwingUtilities.isEventDispatchThread()) {
1212: SwingUtilities.invokeLater(this );
1213:
1214: return;
1215: }
1216:
1217: try {
1218: if (!tree.isVisible(path)) {
1219: // if the path is not visible - don't check the children
1220: return;
1221: }
1222:
1223: if (treeModel == null) {
1224: // no model, no action, no problem
1225: return;
1226: }
1227:
1228: TreeNode myNode = (TreeNode) path
1229: .getLastPathComponent();
1230:
1231: if (treeModel.getPathToRoot(myNode)[0] != treeModel
1232: .getRoot()) {
1233: // the way from the path no longer
1234: // goes to the root, probably someone
1235: // has removed the node on the way up
1236: // System.out.println("different roots.");
1237: return;
1238: }
1239:
1240: // show wait cursor
1241: //showWaitCursor ();
1242: int lastChildIndex = myNode.getChildCount() - 1;
1243:
1244: if (lastChildIndex >= 0) {
1245: TreeNode lastChild = myNode
1246: .getChildAt(lastChildIndex);
1247:
1248: Rectangle base = tree.getVisibleRect();
1249: Rectangle b1 = tree.getPathBounds(path);
1250: Rectangle b2 = tree
1251: .getPathBounds(new TreePath(
1252: treeModel
1253: .getPathToRoot(lastChild)));
1254:
1255: if ((base != null) && (b1 != null)
1256: && (b2 != null)) {
1257: tree.scrollRectToVisible(new Rectangle(
1258: base.x, b1.y, 1, b2.y - b1.y
1259: + b2.height));
1260: }
1261:
1262: // scrollTreeToVisible(path, lastChild);
1263: }
1264: } finally {
1265: path = null;
1266: }
1267: }
1268: }
1269:
1270: // It is OK to use multithreaded shared RP as the requests
1271: // will be serialized in event queue later
1272: scheduled = RequestProcessor.getDefault().post(
1273: new Request(ev.getPath()), 250); // hope that all children are there after this time
1274: }
1275:
1276: public synchronized void treeCollapsed(
1277: final TreeExpansionEvent ev) {
1278: showNormalCursor();
1279: class Request implements Runnable {
1280: private TreePath path;
1281:
1282: public Request(TreePath path) {
1283: this .path = path;
1284: }
1285:
1286: public void run() {
1287: if (!SwingUtilities.isEventDispatchThread()) {
1288: SwingUtilities.invokeLater(this );
1289:
1290: return;
1291: }
1292:
1293: try {
1294: if (tree.isExpanded(path)) {
1295: // the tree shows the path - do not collapse
1296: // the tree
1297: return;
1298: }
1299:
1300: if (!tree.isVisible(path)) {
1301: // if the path is not visible do not collapse
1302: // the tree
1303: return;
1304: }
1305:
1306: if (treeModel == null) {
1307: // no model, no action, no problem
1308: return;
1309: }
1310:
1311: TreeNode myNode = (TreeNode) path
1312: .getLastPathComponent();
1313:
1314: if (treeModel.getPathToRoot(myNode)[0] != treeModel
1315: .getRoot()) {
1316: // the way from the path no longer
1317: // goes to the root, probably someone
1318: // has removed the node on the way up
1319: // System.out.println("different roots.");
1320: return;
1321: }
1322:
1323: treeModel.nodeStructureChanged(myNode);
1324: } finally {
1325: this .path = null;
1326: }
1327: }
1328: }
1329:
1330: // It is OK to use multithreaded shared RP as the requests
1331: // will be serialized in event queue later
1332: // bugfix #37420, children of all collapsed folders will be throw out
1333: RequestProcessor.getDefault().post(
1334: new Request(ev.getPath()), TIME_TO_COLLAPSE);
1335: }
1336:
1337: /* Called whenever the value of the selection changes.
1338: * @param ev the event that characterizes the change.
1339: */
1340: public void valueChanged(TreeSelectionEvent ev) {
1341: TreePath[] paths = tree.getSelectionPaths();
1342: storeSelectedPaths = Arrays
1343: .asList((paths == null) ? new TreePath[0] : paths);
1344:
1345: if (paths == null) {
1346: // part of bugfix #37279, if DnD is active then is useless select a nearby node
1347: if (ExplorerDnDManager.getDefault().isDnDActive()) {
1348: return;
1349: }
1350:
1351: callSelectionChanged(new Node[0]);
1352: } else {
1353: // we need to force no changes to nodes hierarchy =>
1354: // we are requesting read request, but it is not necessary
1355: // to execute the next action immediatelly, so postReadRequest
1356: // should be enough
1357: readAccessPaths = paths;
1358: Children.MUTEX.postReadRequest(this );
1359: }
1360: }
1361:
1362: /** Called under Children.MUTEX to refresh the currently selected nodes.
1363: */
1364: public void run() {
1365: if (readAccessPaths == null) {
1366: return;
1367: }
1368:
1369: TreePath[] paths = readAccessPaths;
1370:
1371: // non null value caused leak in
1372: // ComponentInspector
1373: // When the last Form was closed then the ComponentInspector was
1374: // closed as well. Since this variable was not null -
1375: // last selected Node (RADComponentNode) was held ---> FormManager2 was held, etc.
1376: readAccessPaths = null;
1377:
1378: java.util.List<Node> ll = new java.util.ArrayList<Node>(
1379: paths.length);
1380:
1381: for (int i = 0; i < paths.length; i++) {
1382: Node n = Visualizer.findNode(paths[i]
1383: .getLastPathComponent());
1384:
1385: if (isUnderRoot(manager.getRootContext(), n)) {
1386: ll.add(n);
1387: }
1388: }
1389: callSelectionChanged(ll.toArray(new Node[ll.size()]));
1390: }
1391:
1392: /** Checks whether given Node is a subnode of rootContext.
1393: * @return true if specified Node is under current rootContext
1394: */
1395: private boolean isUnderRoot(Node rootContext, Node node) {
1396: while (node != null) {
1397: if (node.equals(rootContext)) {
1398: return true;
1399: }
1400:
1401: node = node.getParentNode();
1402: }
1403:
1404: return false;
1405: }
1406:
1407: public void treeWillCollapse(TreeExpansionEvent event)
1408: throws ExpandVetoException {
1409: }
1410:
1411: public void treeWillExpand(TreeExpansionEvent event)
1412: throws ExpandVetoException {
1413: // prepare wait cursor and optionally show it
1414: TreePath path = event.getPath();
1415: prepareWaitCursor(DragDropUtilities.secureFindNode(path
1416: .getLastPathComponent()));
1417: }
1418: }
1419:
1420: // end of TreePropertyListener
1421:
1422: /** Popup adapter.
1423: */
1424: class PopupAdapter extends MouseUtils.PopupMouseAdapter {
1425: PopupAdapter() {
1426: }
1427:
1428: protected void showPopup(MouseEvent e) {
1429: int selRow = tree.getRowForLocation(e.getX(), e.getY());
1430:
1431: if ((selRow == -1) && !isRootVisible()) {
1432: // Use the invisible root node as a fake selection, and show its popup.
1433: try {
1434: manager.setSelectedNodes(new Node[] { manager
1435: .getRootContext() });
1436: } catch (PropertyVetoException exc) {
1437: assert false : exc; // not permitted to be thrown
1438: }
1439: } else if (!tree.isRowSelected(selRow)) {
1440: // This will set ExplorerManager selection as well.
1441: // If selRow == -1 the selection will be cleared.
1442: tree.setSelectionRow(selRow);
1443: }
1444:
1445: if ((selRow != -1) || !isRootVisible()) {
1446: Point p = SwingUtilities.convertPoint(e.getComponent(),
1447: e.getX(), e.getY(), TreeView.this );
1448:
1449: createPopup((int) p.getX(), (int) p.getY());
1450: }
1451: }
1452: }
1453:
1454: final class PopupSupport extends MouseAdapter implements Runnable,
1455: FocusListener, ActionListener {
1456: public final Action popup = new AbstractAction() {
1457: public void actionPerformed(ActionEvent evt) {
1458: SwingUtilities.invokeLater(PopupSupport.this );
1459: }
1460:
1461: /**
1462: * Returns true if the action is enabled.
1463: *
1464: * @return true if the action is enabled, false otherwise
1465: * @see Action#isEnabled
1466: */
1467: public boolean isEnabled() {
1468: return TreeView.this .isFocusOwner()
1469: || tree.isFocusOwner();
1470: }
1471: };
1472:
1473: //CallbackSystemAction csa;
1474: public void run() {
1475: Point p = getPositionForPopup();
1476:
1477: if (p == null) {
1478: //we're going to create a popup menu for the root node
1479: p = new Point(0, 0);
1480: }
1481:
1482: createPopup(p.x, p.y);
1483: }
1484:
1485: public void focusGained(java.awt.event.FocusEvent ev) {
1486: // unregister
1487: ev.getComponent().removeFocusListener(this );
1488:
1489: // lazy activation of drag source
1490: if (DragDropUtilities.dragAndDropEnabled && dragActive) {
1491: setDragSource(true);
1492:
1493: // note: dropTarget is activated in constructor
1494: }
1495: }
1496:
1497: public void focusLost(FocusEvent ev) {
1498: }
1499:
1500: /* clicking adapter */
1501: public void mouseClicked(MouseEvent e) {
1502: int selRow = tree.getRowForLocation(e.getX(), e.getY());
1503:
1504: if ((selRow != -1) && SwingUtilities.isLeftMouseButton(e)
1505: && MouseUtils.isDoubleClick(e)) {
1506: // Default action.
1507: if (defaultActionEnabled) {
1508: TreePath selPath = tree.getPathForLocation(
1509: e.getX(), e.getY());
1510: Node node = Visualizer.findNode(selPath
1511: .getLastPathComponent());
1512:
1513: Action a = takeAction(node.getPreferredAction(),
1514: node);
1515:
1516: if (a != null) {
1517: if (a.isEnabled()) {
1518: a.actionPerformed(new ActionEvent(node,
1519: ActionEvent.ACTION_PERFORMED, "")); // NOI18N
1520: } else {
1521: Toolkit.getDefaultToolkit().beep();
1522: }
1523:
1524: e.consume();
1525:
1526: return;
1527: }
1528: }
1529:
1530: if (tree.isExpanded(selRow)) {
1531: tree.collapseRow(selRow);
1532: } else {
1533: tree.expandRow(selRow);
1534: }
1535: }
1536: }
1537:
1538: /* VK_ENTER key processor */
1539: public void actionPerformed(ActionEvent evt) {
1540: Node[] nodes = manager.getSelectedNodes();
1541:
1542: if (nodes.length > 0) {
1543: Action a = nodes[0].getPreferredAction();
1544: for (int i = 1; i < nodes.length; i++) {
1545: if (!nodes[i].getPreferredAction().equals(a))
1546: return;
1547: }
1548:
1549: // switch to replacement action if there is some
1550: a = takeAction(a, nodes);
1551: if (a != null && a.isEnabled()) {
1552: a.actionPerformed(new ActionEvent(
1553: nodes.length == 1 ? nodes[0] : nodes,
1554: ActionEvent.ACTION_PERFORMED, "")); // NOI18N
1555: } else {
1556: Toolkit.getDefaultToolkit().beep();
1557: }
1558: }
1559: }
1560: }
1561:
1562: private final class ExplorerTree extends JTree implements
1563: Autoscroll {
1564: AutoscrollSupport support;
1565: private String maxPrefix;
1566: int SEARCH_FIELD_PREFERRED_SIZE = 160;
1567: int SEARCH_FIELD_SPACE = 3;
1568: private boolean firstPaint = true;
1569:
1570: // searchTextField manages focus because it handles VK_TAB key
1571: private JTextField searchTextField = new JTextField() {
1572: public boolean isManagingFocus() {
1573: return true;
1574: }
1575:
1576: public void processKeyEvent(KeyEvent ke) {
1577: //override the default handling so that
1578: //the parent will never receive the escape key and
1579: //close a modal dialog
1580: if (ke.getKeyCode() == ke.VK_ESCAPE) {
1581: removeSearchField();
1582: ke.consume();
1583:
1584: // bugfix #32909, reqest focus when search field is removed
1585: SwingUtilities.invokeLater(new Runnable() {
1586: //additional bugfix - do focus change later or removing
1587: //the component while it's focused will cause focus to
1588: //get transferred to the next component in the
1589: //parent focusTraversalPolicy *after* our request
1590: //focus completes, so focus goes into a black hole - Tim
1591: public void run() {
1592: ExplorerTree.this .requestFocus();
1593: }
1594: });
1595: } else {
1596: super .processKeyEvent(ke);
1597: }
1598: }
1599: };
1600:
1601: private JPanel searchpanel = null;
1602: final private int heightOfTextField = searchTextField
1603: .getPreferredSize().height;
1604: private int originalScrollMode;
1605:
1606: ExplorerTree(TreeModel model) {
1607: super (model);
1608: toggleClickCount = 0;
1609:
1610: // fix for #18292
1611: // default action map for JTree defines these shortcuts
1612: // but we use our own mechanism for handling them
1613: // following lines disable default L&F handling (if it is
1614: // defined on Ctrl-c, Ctrl-v and Ctrl-x)
1615: getInputMap().put(KeyStroke.getKeyStroke("control C"),
1616: "none"); // NOI18N
1617: getInputMap().put(KeyStroke.getKeyStroke("control V"),
1618: "none"); // NOI18N
1619: getInputMap().put(KeyStroke.getKeyStroke("control X"),
1620: "none"); // NOI18N
1621: getInputMap().put(KeyStroke.getKeyStroke("COPY"), "none"); // NOI18N
1622: getInputMap().put(KeyStroke.getKeyStroke("PASTE"), "none"); // NOI18N
1623: getInputMap().put(KeyStroke.getKeyStroke("CUT"), "none"); // NOI18N
1624:
1625: if (Utilities.isMac()) {
1626: getInputMap().put(
1627: KeyStroke.getKeyStroke(KeyEvent.VK_C,
1628: InputEvent.META_MASK), "none"); // NOI18N
1629: getInputMap().put(
1630: KeyStroke.getKeyStroke(KeyEvent.VK_X,
1631: InputEvent.META_MASK), "none"); // NOI18N
1632: getInputMap().put(
1633: KeyStroke.getKeyStroke(KeyEvent.VK_V,
1634: InputEvent.META_MASK), "none"); // NOI18N
1635: }
1636:
1637: setupSearch();
1638:
1639: setDragEnabled(true);
1640: }
1641:
1642: public void addNotify() {
1643: super .addNotify();
1644: ViewTooltips.register(this );
1645: }
1646:
1647: public void removeNotify() {
1648: super .removeNotify();
1649: ViewTooltips.unregister(this );
1650: }
1651:
1652: public void updateUI() {
1653: super .updateUI();
1654: setBorder(BorderFactory.createEmptyBorder());
1655: if (getTransferHandler() != null
1656: && getTransferHandler() instanceof UIResource) {
1657: //we handle drag and drop in our own way, so let's just fool the UI with a dummy
1658: //TransferHandler to ensure that multiple selection is not lost when drag starts
1659: setTransferHandler(new DummyTransferHandler());
1660: }
1661: }
1662:
1663: private void calcRowHeight(Graphics g) {
1664: int height = Math.max(18, 2 + g.getFontMetrics(getFont())
1665: .getHeight());
1666:
1667: //Issue 42743/"Jesse mode"
1668: String s = System
1669: .getProperty("nb.cellrenderer.fixedheight"); //NOI18N
1670:
1671: if (s != null) {
1672: try {
1673: height = Integer.parseInt(s);
1674: } catch (Exception e) {
1675: //do nothing, height not changed
1676: }
1677: }
1678:
1679: if (getRowHeight() != height) {
1680: setRowHeight(height);
1681: } else {
1682: revalidate();
1683: repaint();
1684: }
1685: }
1686:
1687: //
1688: // Certain operation should be executed in guarded mode - e.g.
1689: // not allow changes in nodes during the operation being executed
1690: //
1691: public void paint(final Graphics g) {
1692: new GuardedActions(0, g);
1693: }
1694:
1695: protected void validateTree() {
1696: new GuardedActions(1, null);
1697: }
1698:
1699: public void doLayout() {
1700: new GuardedActions(2, null);
1701: }
1702:
1703: private void guardedPaint(Graphics g) {
1704: if (firstPaint) {
1705: firstPaint = false;
1706: calcRowHeight(g);
1707:
1708: //This will generate a repaint, so don't bother continuing with super.paint()
1709: //but do paint the background color so it doesn't paint gray the first time
1710: g.setColor(getBackground());
1711: g.fillRect(0, 0, getWidth(), getHeight());
1712:
1713: return;
1714: }
1715:
1716: ExplorerTree.super .paint(g);
1717: }
1718:
1719: private void guardedValidateTree() {
1720: super .validateTree();
1721: }
1722:
1723: private void guardedDoLayout() {
1724: super .doLayout();
1725:
1726: Rectangle visibleRect = getVisibleRect();
1727:
1728: if ((searchpanel != null) && searchpanel.isDisplayable()) {
1729: int width = Math.min(getPreferredSize().width
1730: - (SEARCH_FIELD_SPACE * 2),
1731: SEARCH_FIELD_PREFERRED_SIZE
1732: - SEARCH_FIELD_SPACE);
1733:
1734: searchpanel
1735: .setBounds(Math.max(SEARCH_FIELD_SPACE,
1736: (visibleRect.x + visibleRect.width)
1737: - width), visibleRect.y
1738: + SEARCH_FIELD_SPACE, Math.min(
1739: visibleRect.width, width)
1740: - SEARCH_FIELD_SPACE, heightOfTextField);
1741: }
1742: }
1743:
1744: public void setFont(Font f) {
1745: if (f != getFont()) {
1746: firstPaint = true;
1747: super .setFont(f);
1748: }
1749: }
1750:
1751: protected void processFocusEvent(FocusEvent fe) {
1752: super .processFocusEvent(fe);
1753:
1754: //Since the selected when focused is different, we need to force a
1755: //repaint of the entire selection, but let's do it in guarded more
1756: //as any other repaint
1757: new GuardedActions(3, null);
1758: }
1759:
1760: private void repaintSelection() {
1761: int first = getSelectionModel().getMinSelectionRow();
1762: int last = getSelectionModel().getMaxSelectionRow();
1763:
1764: if (first != -1) {
1765: if (first == last) {
1766: Rectangle r = getRowBounds(first);
1767: repaint(r.x, r.y, r.width, r.height);
1768: } else {
1769: Rectangle top = getRowBounds(first);
1770: Rectangle bottom = getRowBounds(last);
1771: Rectangle r = new Rectangle();
1772: r.x = Math.min(top.x, bottom.x);
1773: r.y = top.y;
1774: r.width = getWidth();
1775: r.height = (bottom.y + bottom.height) - top.y;
1776: repaint(r.x, r.y, r.width, r.height);
1777: }
1778: }
1779: }
1780:
1781: private void prepareSearchPanel() {
1782: if (searchpanel == null) {
1783: searchpanel = new JPanel();
1784:
1785: JLabel lbl = new JLabel(NbBundle.getMessage(
1786: TreeView.class, "LBL_QUICKSEARCH")); //NOI18N
1787: searchpanel.setLayout(new BoxLayout(searchpanel,
1788: BoxLayout.X_AXIS));
1789: searchpanel.add(lbl);
1790: searchpanel.add(searchTextField);
1791: lbl.setLabelFor(searchTextField);
1792: searchpanel.setBorder(BorderFactory
1793: .createRaisedBevelBorder());
1794: lbl.setBorder(BorderFactory.createEmptyBorder(0, 0, 0,
1795: 5));
1796: }
1797: }
1798:
1799: private void setupSearch() {
1800: // Remove the default key listeners
1801: KeyListener[] keyListeners = getListeners(KeyListener.class);
1802:
1803: for (int i = 0; i < keyListeners.length; i++) {
1804: removeKeyListener(keyListeners[i]);
1805: }
1806:
1807: // Add new key listeners
1808: addKeyListener(new KeyAdapter() {
1809: public void keyTyped(KeyEvent e) {
1810: int modifiers = e.getModifiers();
1811: int keyCode = e.getKeyCode();
1812: char c = e.getKeyChar();
1813:
1814: //#43617 - don't eat + and -
1815: //#98634 - and all its duplicates dont't react to space
1816: if ((c == '+') || (c == '-') || (c == ' '))
1817: return; // NOI18N
1818:
1819: if (((modifiers > 0) && (modifiers != KeyEvent.SHIFT_MASK))
1820: || e.isActionKey()) {
1821: return;
1822: }
1823:
1824: if (Character.isISOControl(c)
1825: || (keyCode == KeyEvent.VK_SHIFT)
1826: || (keyCode == KeyEvent.VK_ESCAPE))
1827: return;
1828:
1829: final KeyStroke stroke = KeyStroke
1830: .getKeyStrokeForEvent(e);
1831: searchTextField.setText(String.valueOf(stroke
1832: .getKeyChar()));
1833:
1834: displaySearchField();
1835: e.consume();
1836: }
1837: });
1838:
1839: // Create a the "multi-event" listener for the text field. Instead of
1840: // adding separate instances of each needed listener, we're using a
1841: // class which implements them all. This approach is used in order
1842: // to avoid the creation of 4 instances which takes some time
1843: SearchFieldListener searchFieldListener = new SearchFieldListener();
1844: searchTextField.addKeyListener(searchFieldListener);
1845: searchTextField.addFocusListener(searchFieldListener);
1846: searchTextField.getDocument().addDocumentListener(
1847: searchFieldListener);
1848: }
1849:
1850: private List<TreePath> doSearch(String prefix) {
1851: List<TreePath> results = new ArrayList<TreePath>();
1852:
1853: // do search forward the selected index
1854: int[] rows = getSelectionRows();
1855: int startIndex = ((rows == null) || (rows.length == 0)) ? 0
1856: : rows[0];
1857:
1858: int size = getRowCount();
1859:
1860: if (size == 0) {
1861: // Empty tree (no root visible); cannot match anything.
1862: return results;
1863: }
1864:
1865: while (true) {
1866: startIndex = startIndex % size;
1867:
1868: TreePath path = null;
1869: if (quickSearchUsingSubstring) {
1870: path = getNextSubstringMatch(prefix, startIndex,
1871: Position.Bias.Forward);
1872: } else {
1873: path = getNextMatch(prefix, startIndex,
1874: Position.Bias.Forward);
1875: }
1876:
1877: if ((path != null) && !results.contains(path)) {
1878: startIndex = tree.getRowForPath(path);
1879: results.add(path);
1880:
1881: if (!quickSearchUsingSubstring) {
1882: String elementName = ((VisualizerNode) path
1883: .getLastPathComponent())
1884: .getDisplayName();
1885:
1886: // initialize prefix
1887: if (maxPrefix == null) {
1888: maxPrefix = elementName;
1889: }
1890:
1891: maxPrefix = findMaxPrefix(maxPrefix,
1892: elementName);
1893: }
1894: // try next element
1895: startIndex++;
1896: } else {
1897: break;
1898: }
1899: }
1900:
1901: return results;
1902: }
1903:
1904: private String findMaxPrefix(String str1, String str2) {
1905: String res = null;
1906:
1907: for (int i = 0; str1.regionMatches(true, 0, str2, 0, i); i++) {
1908: res = str1.substring(0, i);
1909: }
1910:
1911: return res;
1912: }
1913:
1914: /**
1915: * Copied and adapted from JTree.getNextMatch(...).
1916: */
1917: private TreePath getNextSubstringMatch(String substring,
1918: int startingRow, Position.Bias bias) {
1919:
1920: int max = getRowCount();
1921: if (substring == null) {
1922: throw new IllegalArgumentException();
1923: }
1924: if (startingRow < 0 || startingRow >= max) {
1925: throw new IllegalArgumentException();
1926: }
1927: substring = substring.toUpperCase();
1928:
1929: // start search from the next/previous element froom the
1930: // selected element
1931: int increment = (bias == Position.Bias.Forward) ? 1 : -1;
1932: int row = startingRow;
1933: do {
1934: TreePath path = getPathForRow(row);
1935: String text = convertValueToText(path
1936: .getLastPathComponent(), isRowSelected(row),
1937: isExpanded(row), true, row, false);
1938:
1939: if (text.toUpperCase().indexOf(substring) >= 0) {
1940: return path;
1941: }
1942: row = (row + increment + max) % max;
1943: } while (row != startingRow);
1944: return null;
1945: }
1946:
1947: /**
1948: * Adds the search field to the tree.
1949: */
1950: private void displaySearchField() {
1951: if (!searchTextField.isDisplayable()) {
1952: JViewport viewport = TreeView.this .getViewport();
1953: originalScrollMode = viewport.getScrollMode();
1954: viewport.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
1955: searchTextField.setFont(ExplorerTree.this .getFont());
1956: prepareSearchPanel();
1957: add(searchpanel);
1958: revalidate();
1959: repaint();
1960: searchTextField.requestFocus();
1961: }
1962: }
1963:
1964: /**
1965: * Removes the search field from the tree.
1966: */
1967: private void removeSearchField() {
1968: if (searchpanel.isDisplayable()) {
1969: remove(searchpanel);
1970: TreeView.this .getViewport().setScrollMode(
1971: originalScrollMode);
1972:
1973: Rectangle r = searchpanel.getBounds();
1974: this .repaint(r);
1975: }
1976: }
1977:
1978: /** notify the Component to autoscroll */
1979: public void autoscroll(Point cursorLoc) {
1980: getSupport().autoscroll(cursorLoc);
1981: }
1982:
1983: /** @return the Insets describing the autoscrolling
1984: * region or border relative to the geometry of the
1985: * implementing Component.
1986: */
1987: public Insets getAutoscrollInsets() {
1988: return getSupport().getAutoscrollInsets();
1989: }
1990:
1991: /** Safe getter for autoscroll support. */
1992: AutoscrollSupport getSupport() {
1993: if (support == null) {
1994: support = new AutoscrollSupport(this , new Insets(15,
1995: 10, 15, 10));
1996: }
1997:
1998: return support;
1999: }
2000:
2001: public String getToolTipText(MouseEvent event) {
2002: if (event != null) {
2003: Point p = event.getPoint();
2004: int selRow = getRowForLocation(p.x, p.y);
2005:
2006: if (selRow != -1) {
2007: TreePath path = getPathForRow(selRow);
2008: VisualizerNode v = (VisualizerNode) path
2009: .getLastPathComponent();
2010: String tooltip = v.getShortDescription();
2011: String displayName = v.getDisplayName();
2012:
2013: if ((tooltip != null)
2014: && !tooltip.equals(displayName)) {
2015: return tooltip;
2016: }
2017: }
2018: }
2019:
2020: return null;
2021: }
2022:
2023: protected TreeModelListener createTreeModelListener() {
2024: return new ModelHandler();
2025: }
2026:
2027: public AccessibleContext getAccessibleContext() {
2028: if (accessibleContext == null) {
2029: accessibleContext = new AccessibleExplorerTree();
2030: }
2031:
2032: return accessibleContext;
2033: }
2034:
2035: private class GuardedActions implements Mutex.Action<Void> {
2036: private int type;
2037: private Object p1;
2038:
2039: public GuardedActions(int type, Object p1) {
2040: this .type = type;
2041: this .p1 = p1;
2042: Children.MUTEX.readAccess(this );
2043: }
2044:
2045: public Void run() {
2046: switch (type) {
2047: case 0:
2048: guardedPaint((Graphics) p1);
2049:
2050: break;
2051:
2052: case 1:
2053: guardedValidateTree();
2054:
2055: break;
2056:
2057: case 2:
2058: guardedDoLayout();
2059:
2060: break;
2061:
2062: case 3:
2063: repaintSelection();
2064:
2065: break;
2066:
2067: default:
2068: throw new IllegalStateException("type: " + type);
2069: }
2070:
2071: return null;
2072: }
2073: }
2074:
2075: private class SearchFieldListener extends KeyAdapter implements
2076: DocumentListener, FocusListener {
2077: /** The last search results */
2078: private List<TreePath> results = new ArrayList<TreePath>();
2079:
2080: /** The last selected index from the search results. */
2081: private int currentSelectionIndex;
2082:
2083: SearchFieldListener() {
2084: }
2085:
2086: public void changedUpdate(DocumentEvent e) {
2087: searchForNode();
2088: }
2089:
2090: public void insertUpdate(DocumentEvent e) {
2091: searchForNode();
2092: }
2093:
2094: public void removeUpdate(DocumentEvent e) {
2095: searchForNode();
2096: }
2097:
2098: public void keyPressed(KeyEvent e) {
2099: int keyCode = e.getKeyCode();
2100:
2101: if (keyCode == KeyEvent.VK_ESCAPE) {
2102: removeSearchField();
2103: ExplorerTree.this .requestFocus();
2104: } else if (keyCode == KeyEvent.VK_UP) {
2105: currentSelectionIndex--;
2106: displaySearchResult();
2107:
2108: // Stop processing the event here. Otherwise it's dispatched
2109: // to the tree too (which scrolls)
2110: e.consume();
2111: } else if (keyCode == KeyEvent.VK_DOWN) {
2112: currentSelectionIndex++;
2113: displaySearchResult();
2114:
2115: // Stop processing the event here. Otherwise it's dispatched
2116: // to the tree too (which scrolls)
2117: e.consume();
2118: } else if (keyCode == KeyEvent.VK_TAB) {
2119: if (maxPrefix != null) {
2120: searchTextField.setText(maxPrefix);
2121: }
2122:
2123: e.consume();
2124: } else if (keyCode == KeyEvent.VK_ENTER) {
2125: removeSearchField();
2126:
2127: // bugfix #39607, don't expand selected node when default action invoked
2128: TreePath selectedTPath = getSelectionPath();
2129:
2130: if (selectedTPath != null) {
2131: TreeNode selectedTNode = (TreeNode) selectedTPath
2132: .getLastPathComponent();
2133: Node selectedNode = Visualizer
2134: .findNode(selectedTNode);
2135:
2136: if ((selectedNode.getPreferredAction() == null)
2137: || !selectedNode.getPreferredAction()
2138: .isEnabled()) {
2139: expandPath(getSelectionPath());
2140: }
2141: }
2142:
2143: ExplorerTree.this .requestFocus();
2144: ExplorerTree.this .dispatchEvent(e);
2145: }
2146: }
2147:
2148: /** Searches for a node in the tree. */
2149: private void searchForNode() {
2150: currentSelectionIndex = 0;
2151: results.clear();
2152: maxPrefix = null;
2153:
2154: String text = searchTextField.getText().toUpperCase();
2155:
2156: if (text.length() > 0) {
2157: results = doSearch(text);
2158: displaySearchResult();
2159: }
2160: }
2161:
2162: private void displaySearchResult() {
2163: int sz = results.size();
2164:
2165: if (sz > 0) {
2166: if (currentSelectionIndex < 0) {
2167: currentSelectionIndex = sz - 1;
2168: } else if (currentSelectionIndex >= sz) {
2169: currentSelectionIndex = 0;
2170: }
2171:
2172: TreePath path = results.get(currentSelectionIndex);
2173: setSelectionPath(path);
2174: scrollPathToVisible(path);
2175: } else {
2176: clearSelection();
2177: }
2178: }
2179:
2180: public void focusGained(FocusEvent e) {
2181: // Do nothing
2182: }
2183:
2184: public void focusLost(FocusEvent e) {
2185: removeSearchField();
2186: }
2187: }
2188:
2189: private class AccessibleExplorerTree extends
2190: JTree.AccessibleJTree {
2191: AccessibleExplorerTree() {
2192: }
2193:
2194: public String getAccessibleName() {
2195: return TreeView.this .getAccessibleContext()
2196: .getAccessibleName();
2197: }
2198:
2199: public String getAccessibleDescription() {
2200: return TreeView.this .getAccessibleContext()
2201: .getAccessibleDescription();
2202: }
2203: }
2204:
2205: private class ModelHandler extends JTree.TreeModelHandler {
2206: ModelHandler() {
2207: }
2208:
2209: public void treeStructureChanged(TreeModelEvent e) {
2210: // Remember selections and expansions
2211: TreePath[] selectionPaths = getSelectionPaths();
2212: java.util.Enumeration expanded = getExpandedDescendants(e
2213: .getTreePath());
2214:
2215: // Restructure the node
2216: super .treeStructureChanged(e);
2217:
2218: // Expand previously expanded paths
2219: if (expanded != null) {
2220: while (expanded.hasMoreElements()) {
2221: expandPath((TreePath) expanded.nextElement());
2222: }
2223: }
2224:
2225: // Select previously selected paths
2226: if ((selectionPaths != null)
2227: && (selectionPaths.length > 0)) {
2228: boolean wasSelected = isPathSelected(selectionPaths[0]);
2229:
2230: setSelectionPaths(selectionPaths);
2231:
2232: if (!wasSelected) {
2233: // do not scroll if the first selection path survived structure change
2234: scrollPathToVisible(selectionPaths[0]);
2235: }
2236: }
2237: }
2238:
2239: public void treeNodesRemoved(TreeModelEvent e) {
2240: // called to removed from JTree.expandedState
2241: super .treeNodesRemoved(e);
2242:
2243: // part of bugfix #37279, if DnD is active then is useless select a nearby node
2244: if (ExplorerDnDManager.getDefault().isDnDActive()) {
2245: return;
2246: }
2247:
2248: if (tree.getSelectionCount() == 0) {
2249: TreePath path = findSiblingTreePath(
2250: e.getTreePath(), e.getChildIndices());
2251:
2252: // bugfix #39564, don't select again the same object
2253: if ((path == null) || path.equals(e.getTreePath())) {
2254: return;
2255: } else if (path.getPathCount() > 0) {
2256: tree.setSelectionPath(path);
2257: }
2258: }
2259: }
2260: }
2261: }
2262:
2263: private static class DummyTransferHandler extends TransferHandler /*implements UIResource*/{
2264: public void exportAsDrag(JComponent comp, InputEvent e,
2265: int action) {
2266: //do nothing - ExplorerDnDManager will kick in when necessary
2267: }
2268:
2269: public void exportToClipboard(JComponent comp, Clipboard clip,
2270: int action) throws IllegalStateException {
2271: //do nothing - Node actions will hande this
2272: }
2273:
2274: public boolean canImport(JComponent comp,
2275: DataFlavor[] transferFlavors) {
2276: return false; //TreeViewDropSupport will decided
2277: }
2278:
2279: public boolean importData(JComponent comp, Transferable t) {
2280: return false;
2281: }
2282:
2283: public int getSourceActions(JComponent c) {
2284: return COPY_OR_MOVE;
2285: }
2286: }
2287: }
|