KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > explorer > view > TreeView


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.openide.explorer.view;
20
21 import org.openide.awt.MouseUtils;
22 import org.openide.explorer.ExplorerManager;
23 import org.openide.nodes.Children;
24 import org.openide.nodes.Node;
25 import org.openide.nodes.NodeOp;
26 import org.openide.util.ContextAwareAction;
27 import org.openide.util.Lookup;
28 import org.openide.util.Mutex;
29 import org.openide.util.NbBundle;
30 import org.openide.util.RequestProcessor;
31 import org.openide.util.Utilities;
32 import org.openide.util.WeakListeners;
33 import org.openide.util.actions.SystemAction;
34 import org.openide.util.lookup.Lookups;
35 import org.openide.util.lookup.ProxyLookup;
36 import java.awt.Container JavaDoc;
37 import java.awt.Cursor JavaDoc;
38 import java.awt.Dimension JavaDoc;
39 import java.awt.Font JavaDoc;
40 import java.awt.Graphics JavaDoc;
41 import java.awt.Insets JavaDoc;
42 import java.awt.Point JavaDoc;
43 import java.awt.Rectangle JavaDoc;
44 import java.awt.Toolkit JavaDoc;
45 import java.awt.datatransfer.Clipboard JavaDoc;
46 import java.awt.datatransfer.DataFlavor JavaDoc;
47 import java.awt.datatransfer.Transferable JavaDoc;
48 import java.awt.dnd.Autoscroll JavaDoc;
49 import java.awt.dnd.DnDConstants JavaDoc;
50 import java.awt.event.ActionEvent JavaDoc;
51 import java.awt.event.ActionListener JavaDoc;
52 import java.awt.event.FocusEvent JavaDoc;
53 import java.awt.event.FocusListener JavaDoc;
54 import java.awt.event.InputEvent JavaDoc;
55 import java.awt.event.KeyAdapter JavaDoc;
56 import java.awt.event.KeyEvent JavaDoc;
57 import java.awt.event.KeyListener JavaDoc;
58 import java.awt.event.MouseAdapter JavaDoc;
59 import java.awt.event.MouseEvent JavaDoc;
60 import java.beans.PropertyChangeEvent JavaDoc;
61 import java.beans.PropertyChangeListener JavaDoc;
62 import java.beans.PropertyVetoException JavaDoc;
63 import java.beans.VetoableChangeListener JavaDoc;
64 import java.util.ArrayList JavaDoc;
65 import java.util.Arrays JavaDoc;
66 import java.util.Iterator JavaDoc;
67 import java.util.List JavaDoc;
68 import java.util.logging.Level JavaDoc;
69 import java.util.logging.Logger JavaDoc;
70 import javax.accessibility.AccessibleContext JavaDoc;
71 import javax.swing.AbstractAction JavaDoc;
72 import javax.swing.Action JavaDoc;
73 import javax.swing.BorderFactory JavaDoc;
74 import javax.swing.BoxLayout JavaDoc;
75 import javax.swing.JComponent JavaDoc;
76 import javax.swing.JLabel JavaDoc;
77 import javax.swing.JMenu JavaDoc;
78 import javax.swing.JPanel JavaDoc;
79 import javax.swing.JPopupMenu JavaDoc;
80 import javax.swing.JScrollPane JavaDoc;
81 import javax.swing.JTextField JavaDoc;
82 import javax.swing.JTree JavaDoc;
83 import javax.swing.JViewport JavaDoc;
84 import javax.swing.KeyStroke JavaDoc;
85 import javax.swing.SwingUtilities JavaDoc;
86 import javax.swing.ToolTipManager JavaDoc;
87 import javax.swing.TransferHandler JavaDoc;
88 import javax.swing.UIManager JavaDoc;
89 import javax.swing.border.Border JavaDoc;
90 import javax.swing.event.DocumentEvent JavaDoc;
91 import javax.swing.event.DocumentListener JavaDoc;
92 import javax.swing.event.TreeExpansionEvent JavaDoc;
93 import javax.swing.event.TreeExpansionListener JavaDoc;
94 import javax.swing.event.TreeModelEvent JavaDoc;
95 import javax.swing.event.TreeModelListener JavaDoc;
96 import javax.swing.event.TreeSelectionEvent JavaDoc;
97 import javax.swing.event.TreeSelectionListener JavaDoc;
98 import javax.swing.event.TreeWillExpandListener JavaDoc;
99 import javax.swing.plaf.UIResource JavaDoc;
100 import javax.swing.text.Position JavaDoc;
101 import javax.swing.tree.ExpandVetoException JavaDoc;
102 import javax.swing.tree.RowMapper JavaDoc;
103 import javax.swing.tree.TreeModel JavaDoc;
104 import javax.swing.tree.TreeNode JavaDoc;
105 import javax.swing.tree.TreePath JavaDoc;
106 import javax.swing.tree.TreeSelectionModel JavaDoc;
107
108
109 /**
110  * Base class for tree-style explorer views.
111  * @see BeanTreeView
112  * @see ContextTreeView
113  */

114 public abstract class TreeView extends JScrollPane JavaDoc {
115     static {
116         // Workaround for issue #42794 on JDK1.5
117
UIManager.put("Tree.scrollsHorizontallyAndVertically", Boolean.TRUE);
118     }
119
120     //
121
// static fields
122
//
123

124     /** generated Serialized Version UID */
125     static final long serialVersionUID = -1639001987693376168L;
126
127     /** How long it takes before collapsed nodes are released from the tree's cache
128     */

129     private static final int TIME_TO_COLLAPSE = (System.getProperty("netbeans.debug.heap") != null) ? 0 : 15000;
130
131     /** Minimum width of this component. */
132     private static final int MIN_TREEVIEW_WIDTH = 400;
133
134     /** Minimum height of this component. */
135     private static final int MIN_TREEVIEW_HEIGHT = 400;
136
137     //GTK Look and feel hack
138
private static boolean isSynth = UIManager.getLookAndFeel().getClass().getName().indexOf(
139             "com.sun.java.swing.plaf.gtk"
140         ) != -1;
141
142     //
143
// components
144
//
145

146     /** Main <code>JTree</code> component. */
147     transient protected JTree JavaDoc tree;
148
149     /** model */
150     transient NodeTreeModel treeModel;
151
152     /** Explorer manager, valid when this view is showing */
153     transient ExplorerManager manager;
154
155     // Attributes
156

157     /** Mouse and action listener. */
158     transient PopupSupport defaultActionListener;
159
160     /** Property indicating whether the default action is enabled. */
161     transient boolean defaultActionEnabled;
162
163     /** not null if popup menu enabled */
164     transient PopupAdapter popupListener;
165
166     /** the most important listener (on four types of events */
167     transient TreePropertyListener managerListener = null;
168
169     /** weak variation of the listener for property change on the explorer manager */
170     transient PropertyChangeListener JavaDoc wlpc;
171
172     /** weak variation of the listener for vetoable change on the explorer manager */
173     transient VetoableChangeListener JavaDoc wlvc;
174
175     /** true if drag support is active */
176     private transient boolean dragActive = true;
177
178     /** true if drop support is active */
179     private transient boolean dropActive = true;
180
181     /** Drag support */
182     transient TreeViewDragSupport dragSupport;
183
184     /** Drop support */
185     transient TreeViewDropSupport dropSupport;
186     transient boolean dropTargetPopupAllowed = true;
187     transient private Container JavaDoc contentPane;
188     transient private List JavaDoc storeSelectedPaths;
189
190     // default DnD actions
191
transient private int allowedDragActions = DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_REFERENCE;
192     transient private int allowedDropActions = DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_REFERENCE;
193
194     /** Constructor.
195     */

196     public TreeView() {
197         this(true, true);
198     }
199
200     /** Constructor.
201     * @param defaultAction should double click on a node open its default action?
202     * @param popupAllowed should right-click open popup?
203     */

204     public TreeView(boolean defaultAction, boolean popupAllowed) {
205         initializeTree();
206
207 // // activation of drop target
208
// if (DragDropUtilities.dragAndDropEnabled) {
209
// setdroptExplorerDnDManager.getDefault().addFutureDropTarget(this);
210
//
211
// // note: drag target is activated on focus gained
212
// }
213
setDropTarget( DragDropUtilities.dragAndDropEnabled );
214
215         setPopupAllowed(popupAllowed);
216         setDefaultActionAllowed(defaultAction);
217
218         Dimension JavaDoc dim = null;
219
220         try {
221             dim = getPreferredSize();
222
223             if (dim == null) {
224                 dim = new Dimension JavaDoc(MIN_TREEVIEW_WIDTH, MIN_TREEVIEW_HEIGHT);
225             }
226         } catch (NullPointerException JavaDoc npe) {
227             dim = new Dimension JavaDoc(MIN_TREEVIEW_WIDTH, MIN_TREEVIEW_HEIGHT);
228         }
229
230         if (dim.width < MIN_TREEVIEW_WIDTH) {
231             dim.width = MIN_TREEVIEW_WIDTH;
232         }
233
234         if (dim.height < MIN_TREEVIEW_HEIGHT) {
235             dim.height = MIN_TREEVIEW_HEIGHT;
236         }
237
238         setPreferredSize(dim);
239     }
240
241     public void updateUI() {
242         super.updateUI();
243
244         //On GTK L&F, the viewport border must be set to empty (not null!) or we still get border buildup
245
setViewportBorder(BorderFactory.createEmptyBorder());
246         setBorder(BorderFactory.createEmptyBorder());
247     }
248
249     public Border JavaDoc getBorder() {
250         if (isSynth) {
251             return BorderFactory.createEmptyBorder();
252         } else {
253             return super.getBorder();
254         }
255     }
256
257     /** Initializes the tree & model.
258      * [dafe] Horrible technique - overridable method called from constructor
259      * may result in subclass code invoked when this object is not fully
260      * constructed.
261      * However I don't have enough knowledge about this code to change it.
262     */

263     void initializeTree() {
264         // initilizes the JTree
265
treeModel = createModel();
266         treeModel.addView(this);
267
268         tree = new ExplorerTree(treeModel);
269
270         NodeRenderer rend = new NodeRenderer();
271         tree.setCellRenderer(rend);
272         tree.putClientProperty("JTree.lineStyle", "Angled"); // NOI18N
273
setViewportView(tree);
274
275         // Init of the editor
276
tree.setCellEditor(new TreeViewCellEditor(tree));
277         tree.setEditable(true);
278
279         // set selection mode to DISCONTIGUOUS_TREE_SELECTION as default
280
setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
281
282         ToolTipManager.sharedInstance().registerComponent(tree);
283
284         // init listener & attach it to closing of
285
managerListener = new TreePropertyListener();
286         tree.addTreeExpansionListener(managerListener);
287         tree.addTreeWillExpandListener(managerListener);
288
289         // do not care about focus
290
setRequestFocusEnabled(false);
291
292         defaultActionListener = new PopupSupport();
293         getInputMap( JTree.WHEN_FOCUSED ).put(
294                 KeyStroke.getKeyStroke( KeyEvent.VK_F10, KeyEvent.SHIFT_DOWN_MASK ), "org.openide.actions.PopupAction" );
295         getActionMap().put("org.openide.actions.PopupAction", defaultActionListener.popup);
296         tree.addFocusListener(defaultActionListener);
297         tree.addMouseListener(defaultActionListener);
298     }
299
300     /** Is it permitted to display a popup menu?
301      * @return <code>true</code> if so
302      */

303     public boolean isPopupAllowed() {
304         return popupListener != null;
305     }
306
307     /** Enable/disable displaying popup menus on tree view items.
308     * Default is enabled.
309     * @param value <code>true</code> to enable
310     */

311     public void setPopupAllowed(boolean value) {
312         if ((popupListener == null) && value) {
313             // on
314
popupListener = new PopupAdapter();
315             tree.addMouseListener(popupListener);
316
317             return;
318         }
319
320         if ((popupListener != null) && !value) {
321             // off
322
tree.removeMouseListener(popupListener);
323             popupListener = null;
324
325             return;
326         }
327     }
328
329     void setDropTargetPopupAllowed(boolean value) {
330         dropTargetPopupAllowed = value;
331
332         if (dropSupport != null) {
333             dropSupport.setDropTargetPopupAllowed(value);
334         }
335     }
336
337     boolean isDropTargetPopupAllowed() {
338         return (dropSupport != null) ? dropSupport.isDropTargetPopupAllowed() : dropTargetPopupAllowed;
339     }
340
341     /** Does a double click invoke the default node action?
342      * @return <code>true</code> if so
343      */

344     public boolean isDefaultActionEnabled() {
345         return defaultActionEnabled;
346     }
347
348     /** Requests focus for the tree component. Overrides superclass method. */
349     public void requestFocus() {
350         tree.requestFocus();
351     }
352
353     /** Requests focus for the tree component. Overrides superclass method. */
354     public boolean requestFocusInWindow() {
355         return tree.requestFocusInWindow();
356     }
357
358     /** Enable/disable double click to invoke default action.
359      * If defaultAction is not enabled double click expand/collapse node.
360      * @param value <code>true</code> to enable
361      */

362     public void setDefaultActionAllowed(boolean value) {
363         defaultActionEnabled = value;
364
365         if (value) {
366             tree.registerKeyboardAction(
367                 defaultActionListener, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), JComponent.WHEN_FOCUSED
368             );
369         } else {
370             // Switch off.
371
tree.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false));
372         }
373     }
374
375     /**
376     * Is the root node of the tree displayed?
377     *
378     * @return <code>true</code> if so
379     */

380     public boolean isRootVisible() {
381         return tree.isRootVisible();
382     }
383
384     /** Set whether or not the root node from
385     * the <code>TreeModel</code> is visible.
386     *
387     * @param visible <code>true</code> if it is to be displayed
388     */

389     public void setRootVisible(boolean visible) {
390         tree.setRootVisible(visible);
391         tree.setShowsRootHandles(!visible);
392     }
393
394     /********** Support for the Drag & Drop operations *********/
395     /** Drag support is enabled by default.
396     * @return true if dragging from the view is enabled, false
397     * otherwise.
398     */

399     public boolean isDragSource() {
400         return dragActive;
401     }
402
403     /** Enables/disables dragging support.
404     * @param state true enables dragging support, false disables it.
405     */

406     public void setDragSource(boolean state) {
407         // create drag support if needed
408
if (state && (dragSupport == null)) {
409             dragSupport = new TreeViewDragSupport(this, tree);
410         }
411
412         // activate / deactivate support according to the state
413
dragActive = state;
414
415         if (dragSupport != null) {
416             dragSupport.activate(dragActive);
417         }
418     }
419
420     /** Drop support is enabled by default.
421     * @return true if dropping to the view is enabled, false
422     * otherwise<br>
423     */

424     public boolean isDropTarget() {
425         return dropActive;
426     }
427
428     /** Enables/disables dropping support.
429     * @param state true means drops into view are allowed,
430     * false forbids any drops into this view.
431     */

432     public void setDropTarget(boolean state) {
433         // create drop support if needed
434
if (dropActive && (dropSupport == null)) {
435             dropSupport = new TreeViewDropSupport(this, tree, dropTargetPopupAllowed);
436         }
437
438         // activate / deactivate support according to the state
439
dropActive = state;
440
441         if (dropSupport != null) {
442             dropSupport.activate(dropActive);
443         }
444     }
445
446     /** Actions constants comes from {@link java.awt.dnd.DnDConstants}.
447     * All actions (copy, move, link) are allowed by default.
448     * @return int representing set of actions which are allowed when dragging from
449     * asociated component.
450      */

451     public int getAllowedDragActions() {
452         return allowedDragActions;
453     }
454
455     /** Sets allowed actions for dragging
456     * @param actions new drag actions, using {@link java.awt.dnd.DnDConstants}
457     */

458     public void setAllowedDragActions(int actions) {
459         // PENDING: check parameters
460
allowedDragActions = actions;
461     }
462
463     /** Actions constants comes from {@link java.awt.dnd.DnDConstants}.
464     * All actions are allowed by default.
465     * @return int representing set of actions which are allowed when dropping
466     * into the asociated component.
467     */

468     public int getAllowedDropActions() {
469         return allowedDropActions;
470     }
471
472     /** Sets allowed actions for dropping.
473     * @param actions new allowed drop actions, using {@link java.awt.dnd.DnDConstants}
474     */

475     public void setAllowedDropActions(int actions) {
476         // PENDING: check parameters
477
allowedDropActions = actions;
478     }
479
480     //
481
// Control over expanded state
482
//
483

484     /** Collapses the tree under given node.
485     *
486     * @param n node to collapse
487     */

488     public void collapseNode(Node n) {
489         if (n == null) {
490             throw new IllegalArgumentException JavaDoc();
491         }
492
493         TreePath JavaDoc treePath = new TreePath JavaDoc(treeModel.getPathToRoot(VisualizerNode.getVisualizer(null, n)));
494         tree.collapsePath(treePath);
495     }
496
497     /** Expandes the node in the tree.
498     *
499     * @param n node
500     */

501     public void expandNode(Node n) {
502         if (n == null) {
503             throw new IllegalArgumentException JavaDoc();
504         }
505
506         lookupExplorerManager();
507
508         TreePath JavaDoc treePath = new TreePath JavaDoc(treeModel.getPathToRoot(VisualizerNode.getVisualizer(null, n)));
509
510         tree.expandPath(treePath);
511     }
512
513     /** Test whether a node is expanded in the tree or not
514     * @param n the node to test
515     * @return true if the node is expanded
516     */

517     public boolean isExpanded(Node n) {
518         TreePath JavaDoc treePath = new TreePath JavaDoc(treeModel.getPathToRoot(VisualizerNode.getVisualizer(null, n)));
519
520         return tree.isExpanded(treePath);
521     }
522
523     /** Expands all paths.
524     */

525     public void expandAll() {
526         int i = 0;
527         int j /*, k = tree.getRowCount()*/;
528
529         do {
530             do {
531                 j = tree.getRowCount();
532                 tree.expandRow(i);
533             } while (j != tree.getRowCount());
534
535             i++;
536         } while (i < tree.getRowCount());
537     }
538
539     //
540
// Processing functions
541
//
542

543     public void validate() {
544         Children.MUTEX.readAccess(new Runnable JavaDoc() {
545             public void run() {
546                 TreeView.super.validate();
547             }
548         });
549     }
550
551     /** Initializes the component and lookup explorer manager.
552     */

553     public void addNotify() {
554         super.addNotify();
555         lookupExplorerManager();
556     }
557
558     /** Registers in the tree of components.
559      */

560     private void lookupExplorerManager() {
561         // Enter key in the tree
562
ExplorerManager newManager = ExplorerManager.find(TreeView.this);
563
564         if (newManager != manager) {
565             if (manager != null) {
566                 manager.removeVetoableChangeListener(wlvc);
567                 manager.removePropertyChangeListener(wlpc);
568             }
569
570             manager = newManager;
571
572             manager.addVetoableChangeListener(wlvc = WeakListeners.vetoableChange(managerListener, manager));
573             manager.addPropertyChangeListener(wlpc = WeakListeners.propertyChange(managerListener, manager));
574
575             synchronizeRootContext();
576             synchronizeExploredContext();
577             synchronizeSelectedNodes();
578         }
579
580         // Sometimes the listener is registered twice and we get the
581
// selection events twice. Removing the listener before adding it
582
// should be a safe fix.
583
tree.getSelectionModel().removeTreeSelectionListener(managerListener);
584         tree.getSelectionModel().addTreeSelectionListener(managerListener);
585     }
586
587     /** Deinitializes listeners.
588     */

589     public void removeNotify() {
590         super.removeNotify();
591
592         tree.getSelectionModel().removeTreeSelectionListener(managerListener);
593     }
594
595     // *************************************
596
// Methods to be overriden by subclasses
597
// *************************************
598

599     /** Allows subclasses to provide own model for displaying nodes.
600     * @return the model to use for this view
601     */

602     protected abstract NodeTreeModel createModel();
603
604     /** Called to allow subclasses to define the behaviour when a
605     * node(s) are selected in the tree.
606     *
607     * @param nodes the selected nodes
608     * @param em explorer manager to work on (change nodes to it)
609     * @throws PropertyVetoException if the change cannot be done by the explorer
610     * (the exception is silently consumed)
611     */

612     protected abstract void selectionChanged(Node[] nodes, ExplorerManager em)
613     throws PropertyVetoException JavaDoc;
614
615     /** Called when explorer manager is about to change the current selection.
616     * The view can forbid the change if it is not able to display such
617     * selection.
618     *
619     * @param nodes the nodes to select
620     * @return false if the view is not able to change the selection
621     */

622     protected abstract boolean selectionAccept(Node[] nodes);
623
624     /** Show a given path in the screen. It depends on the kind of <code>TreeView</code>
625     * if the path should be expanded or just made visible.
626     *
627     * @param path the path
628     */

629     protected abstract void showPath(TreePath JavaDoc path);
630
631     /** Shows selection to reflect the current state of the selection in the explorer.
632     *
633     * @param paths array of paths that should be selected
634     */

635     protected abstract void showSelection(TreePath JavaDoc[] paths);
636
637     /** Specify whether a context menu of the explored context should be used.
638     * Applicable when no nodes are selected and the user wants to invoke
639     * a context menu (clicks right mouse button).
640     *
641     * @return <code>true</code> if so; <code>false</code> in the default implementation
642     */

643     protected boolean useExploredContextMenu() {
644         return false;
645     }
646
647     /** Check if selection of the nodes could break the selection mode set in TreeSelectionModel.
648      * @param nodes the nodes for selection
649      * @return true if the selection mode is broken */

650     private boolean isSelectionModeBroken(Node[] nodes) {
651         // if nodes are empty or single the everthing is ok
652
// or if discontiguous selection then everthing ok
653
if ((nodes.length <= 1) || (getSelectionMode() == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)) {
654             return false;
655         }
656
657         // if many nodes
658
// brakes single selection mode
659
if (getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION) {
660             return true;
661         }
662
663         // check the contiguous selection mode
664
TreePath JavaDoc[] paths = new TreePath JavaDoc[nodes.length];
665         RowMapper JavaDoc rowMapper = tree.getSelectionModel().getRowMapper();
666
667         // if rowMapper is null then tree bahaves as discontiguous selection mode is set
668
if (rowMapper == null) {
669             return false;
670         }
671
672         ArrayList JavaDoc<Node> toBeExpaned = new ArrayList JavaDoc<Node>(3);
673
674         for (int i = 0; i < nodes.length; i++) {
675             toBeExpaned.clear();
676
677             Node n = nodes[i];
678
679             while (n.getParentNode() != null) {
680                 if (!isExpanded(n)) {
681                     toBeExpaned.add(n);
682                 }
683
684                 n = n.getParentNode();
685             }
686
687             for (int j = toBeExpaned.size() - 1; j >= 0; j--) {
688                 expandNode((Node) toBeExpaned.get(j));
689             }
690
691             TreePath JavaDoc treePath = new TreePath JavaDoc(treeModel.getPathToRoot(VisualizerNode.getVisualizer(null, nodes[i])));
692             paths[i] = treePath;
693         }
694
695         int[] rows = rowMapper.getRowsForPaths(paths);
696
697         // check selection's rows
698
Arrays.sort(rows);
699
700         for (int i = 1; i < rows.length; i++) {
701             if (rows[i] != (rows[i - 1] + 1)) {
702                 return true;
703             }
704         }
705
706         // all is ok
707
return false;
708     }
709
710     //
711
// synchronizations
712
//
713

714     /** Called when selection in tree is changed.
715     */

716     final void callSelectionChanged(Node[] nodes) {
717         manager.removePropertyChangeListener(wlpc);
718         manager.removeVetoableChangeListener(wlvc);
719
720         try {
721             selectionChanged(nodes, manager);
722         } catch (PropertyVetoException JavaDoc e) {
723             synchronizeSelectedNodes();
724         } finally {
725             manager.addPropertyChangeListener(wlpc);
726             manager.addVetoableChangeListener(wlvc);
727         }
728     }
729
730     /** Synchronize the root context from the manager of this Explorer.
731     */

732     final void synchronizeRootContext() {
733         treeModel.setNode(manager.getRootContext());
734     }
735
736     /** Synchronize the explored context from the manager of this Explorer.
737     */

738     final void synchronizeExploredContext() {
739         Node n = manager.getExploredContext();
740
741         if (n != null) {
742             TreePath JavaDoc treePath = new TreePath JavaDoc(treeModel.getPathToRoot(VisualizerNode.getVisualizer(null, n)));
743             showPath(treePath);
744         }
745     }
746
747     /** Sets the selection model, which must be one of
748      * TreeSelectionModel.SINGLE_TREE_SELECTION,
749      * TreeSelectionModel.CONTIGUOUS_TREE_SELECTION or
750      * TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION.
751      * <p>
752      * This may change the selection if the current selection is not valid
753      * for the new mode. For example, if three TreePaths are
754      * selected when the mode is changed to <code>TreeSelectionModel.SINGLE_TREE_SELECTION</code>,
755      * only one TreePath will remain selected. It is up to the particular
756      * implementation to decide what TreePath remains selected.
757      * Note: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION is set as default.
758      * @since 2.15
759      * @param mode selection mode
760      */

761     public void setSelectionMode(int mode) {
762         tree.getSelectionModel().setSelectionMode(mode);
763     }
764
765     /** Returns the current selection mode, one of
766      * <code>TreeSelectionModel.SINGLE_TREE_SELECTION</code>,
767      * <code>TreeSelectionModel.CONTIGUOUS_TREE_SELECTION</code> or
768      * <code>TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION</code>.
769      * @since 2.15
770      * @return selection mode
771      */

772     public int getSelectionMode() {
773         return tree.getSelectionModel().getSelectionMode();
774     }
775
776     //
777
// showing and removing the wait cursor
778
//
779
private void showWaitCursor() {
780         if (getRootPane() == null) {
781             return;
782         }
783
784         contentPane = getRootPane().getContentPane();
785
786         if (SwingUtilities.isEventDispatchThread()) {
787             contentPane.setCursor(Utilities.createProgressCursor(contentPane));
788         } else {
789             SwingUtilities.invokeLater(new CursorR(contentPane, Utilities.createProgressCursor(contentPane)));
790         }
791     }
792
793     private void showNormalCursor() {
794         if (contentPane == null) {
795             return;
796         }
797
798         if (SwingUtilities.isEventDispatchThread()) {
799             contentPane.setCursor(null);
800         } else {
801             SwingUtilities.invokeLater(new CursorR(contentPane, null));
802         }
803     }
804
805     private void prepareWaitCursor(final Node node) {
806         // check type of node
807
if (node == null) {
808             showNormalCursor();
809         }
810
811         showWaitCursor();
812         RequestProcessor.getDefault().post(new Runnable JavaDoc() {
813
814                                                public void run() {
815                                                    try {
816                                                        node.getChildren().getNodes(true);
817                                                    }
818                                                    catch (Exception JavaDoc e) {
819                                                        // log a exception
820
Logger.getLogger(TreeView.class.getName()).log(Level.WARNING,
821                                                                          null, e);
822                                                    }
823                                                    finally {
824                                                        // show normal cursor above all
825
showNormalCursor();
826                                                    }
827                                                }
828                                            });
829     }
830
831     /** Synchronize the selected nodes from the manager of this Explorer.
832     * The default implementation does nothing.
833     */

834     final void synchronizeSelectedNodes() {
835         // #40152: if there is any scheduled change to view, perform it now
836
VisualizerNode.runQueue();
837
838         Node[] arr = manager.getSelectedNodes();
839         TreePath JavaDoc[] paths = new TreePath JavaDoc[arr.length];
840
841         for (int i = 0; i < arr.length; i++) {
842             TreePath JavaDoc treePath = new TreePath JavaDoc(treeModel.getPathToRoot(VisualizerNode.getVisualizer(null, arr[i])));
843             paths[i] = treePath;
844         }
845
846         tree.getSelectionModel().removeTreeSelectionListener(managerListener);
847         showSelection(paths);
848         tree.getSelectionModel().addTreeSelectionListener(managerListener);
849     }
850
851     void scrollTreeToVisible(TreePath JavaDoc path, TreeNode JavaDoc child) {
852         Rectangle JavaDoc base = tree.getVisibleRect();
853         Rectangle JavaDoc b1 = tree.getPathBounds(path);
854         Rectangle JavaDoc b2 = tree.getPathBounds(new TreePath JavaDoc(treeModel.getPathToRoot(child)));
855
856         if ((base != null) && (b1 != null) && (b2 != null)) {
857             tree.scrollRectToVisible(new Rectangle JavaDoc(base.x, b1.y, 1, b2.y - b1.y + b2.height));
858         }
859     }
860
861     private void createPopup(int xpos, int ypos, JPopupMenu JavaDoc popup) {
862         if (popup.getSubElements().length > 0) {
863             popup.show(TreeView.this, xpos, ypos);
864         }
865     }
866
867     void createPopup(int xpos, int ypos) {
868         // bugfix #23932, don't create if it's disabled
869
if (isPopupAllowed()) {
870             Node[] arr = manager.getSelectedNodes();
871
872             if (arr.length == 0) {
873                 // Should probably not happen when shown from right-click, but may well when from S-F10.
874
// Create popup menu for the root node, and make sure it is selected so that action context is correct.
875
arr = new Node[] { manager.getRootContext() };
876
877                 try {
878                     manager.setSelectedNodes(arr);
879                 } catch (PropertyVetoException JavaDoc e) {
880                     assert false : e; // not permitted to be thrown
881
}
882             }
883
884             Action[] actions = NodeOp.findActions(arr);
885
886             if (actions.length > 0) {
887                 createPopup(xpos, ypos, Utilities.actionsToPopup(actions, this));
888             }
889         }
890     }
891
892     /* create standard popup menu and add newMenu to it
893      */

894     void createExtendedPopup(int xpos, int ypos, JMenu JavaDoc newMenu) {
895         Node[] ns = manager.getSelectedNodes();
896         JPopupMenu JavaDoc popup = null;
897
898         if (ns.length > 0) {
899             // if any nodes are selected --> find theirs actions
900
Action[] actions = NodeOp.findActions(ns);
901             popup = Utilities.actionsToPopup(actions, this);
902         } else {
903             // if none node is selected --> get context actions from view's root
904
if (manager.getRootContext() != null) {
905                 popup = manager.getRootContext().getContextMenu();
906             }
907         }
908
909         int cnt = 0;
910
911         if (popup == null) {
912             popup = SystemAction.createPopupMenu(new SystemAction[] { });
913         }
914
915         popup.add(newMenu);
916
917         createPopup(xpos, ypos, popup);
918     }
919
920     /** Returns the the point at which the popup menu is to be showed. May return null.
921      * @return the point or null
922      */

923     Point JavaDoc getPositionForPopup() {
924         int i = tree.getLeadSelectionRow();
925
926         if (i < 0) {
927             return null;
928         }
929
930         Rectangle JavaDoc rect = tree.getRowBounds(i);
931
932         if (rect == null) {
933             return null;
934         }
935
936         Point JavaDoc p = new Point JavaDoc(rect.x, rect.y);
937
938         // bugfix #36984, convert point by TreeView.this
939
p = SwingUtilities.convertPoint(tree, p, TreeView.this);
940
941         return p;
942     }
943
944     static Action takeAction(Action action, Node node) {
945         // bugfix #42843, use ContextAwareAction if possible
946
if (action instanceof ContextAwareAction) {
947             Lookup contextLookup = node.getLookup();
948             Lookup.Result<Node> res = contextLookup.lookup(new Lookup.Template<Node>(Node.class));
949
950             // #55826, don't added the node twice
951
Iterator JavaDoc it = res.allInstances().iterator();
952
953             // temporary workaround #55938
954
boolean add = true;
955
956             while (it.hasNext() && add) {
957                 add = !node.equals(it.next());
958             }
959
960             if (add) {
961                 contextLookup = new ProxyLookup(new Lookup[] { Lookups.singleton(node), node.getLookup() });
962             }
963
964             Action contextInstance = ((ContextAwareAction) action).createContextAwareInstance(contextLookup);
965             assert contextInstance != action : "Cannot be same. ContextAwareAction: " + action +
966             ", ContextAwareInstance: " + contextInstance;
967             action = contextInstance;
968         }
969
970         return action;
971     }
972
973     /** Returns the tree path nearby to given tree node. Either a sibling if there is or the parent.
974      * @param parentPath tree path to parent of changed nodes
975      * @param childIndices indexes of changed children
976      * @return the tree path or null if there no changed children
977      */

978     final static TreePath JavaDoc findSiblingTreePath(TreePath JavaDoc parentPath, int[] childIndices) {
979         if (childIndices == null) {
980             throw new IllegalArgumentException JavaDoc("Indexes of changed children are null."); // NOI18N
981
}
982
983         if (parentPath == null) {
984             throw new IllegalArgumentException JavaDoc("The tree path to parent is null."); // NOI18N
985
}
986
987         // bugfix #29342, if childIndices is the empty then don't change the selection
988
if (childIndices.length == 0) {
989             return null;
990         }
991
992         TreeNode JavaDoc parent = (TreeNode JavaDoc) parentPath.getLastPathComponent();
993         Object JavaDoc[] parentPaths = parentPath.getPath();
994         TreePath JavaDoc newSelection = null;
995         
996         int childCount = parent.getChildCount();
997         if (childCount > 0) {
998             // get parent path, add child to it
999
int childPathLength = parentPaths.length + 1;
1000            Object JavaDoc[] childPath = new Object JavaDoc[childPathLength];
1001            System.arraycopy(parentPaths, 0, childPath, 0, parentPaths.length);
1002
1003            int selectedChild = Math.min(childIndices[0], childCount-1);
1004
1005            childPath[childPathLength - 1] = parent.getChildAt(selectedChild);
1006            newSelection = new TreePath JavaDoc(childPath);
1007        } else {
1008            // all children removed, select parent
1009
newSelection = new TreePath JavaDoc(parentPaths);
1010        }
1011
1012        return newSelection;
1013    }
1014
1015    // Workaround for JDK issue 6472844 (NB #84970)
1016
void removedNodes(List JavaDoc<VisualizerNode> removed) {
1017        TreeSelectionModel JavaDoc sm = tree.getSelectionModel();
1018    TreePath JavaDoc[] selPaths = (sm != null) ? sm.getSelectionPaths() : null;
1019        if (selPaths == null) return;
1020        
1021        List JavaDoc<TreePath JavaDoc> remSel = null;
1022        for (VisualizerNode vn : removed) {
1023            TreePath JavaDoc path = new TreePath JavaDoc(treeModel.getPathToRoot(vn));
1024        for(TreePath JavaDoc tp : selPaths) {
1025                if (path.isDescendant(tp)) {
1026                    if (remSel == null) remSel = new ArrayList JavaDoc();
1027                    remSel.add(tp);
1028                }
1029        }
1030        }
1031        
1032        if (remSel != null) {
1033            sm.removeSelectionPaths(remSel.toArray(new TreePath JavaDoc[remSel.size()]));
1034        }
1035    }
1036
1037    private static class CursorR implements Runnable JavaDoc {
1038        private Container JavaDoc contentPane;
1039        private Cursor JavaDoc c;
1040
1041        private CursorR(Container JavaDoc cont, Cursor JavaDoc c) {
1042            contentPane = cont;
1043            this.c = c;
1044        }
1045
1046        public void run() {
1047            contentPane.setCursor(c);
1048        }
1049    }
1050
1051    /** Listens to the property changes on tree */
1052    class TreePropertyListener implements VetoableChangeListener JavaDoc, PropertyChangeListener JavaDoc, TreeExpansionListener JavaDoc,
1053        TreeWillExpandListener JavaDoc, TreeSelectionListener JavaDoc, Runnable JavaDoc {
1054        private RequestProcessor.Task scheduled;
1055        private TreePath JavaDoc[] readAccessPaths;
1056
1057        TreePropertyListener() {
1058        }
1059
1060        public void vetoableChange(PropertyChangeEvent JavaDoc evt)
1061        throws PropertyVetoException JavaDoc {
1062            if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
1063                // issue 11928 check if selecetion mode will be broken
1064
Node[] nodes = (Node[]) evt.getNewValue();
1065
1066                if (isSelectionModeBroken(nodes)) {
1067                    throw new PropertyVetoException JavaDoc(
1068                        "selection mode " + getSelectionMode() + " broken by " + Arrays.asList(nodes), evt
1069                    ); // NOI18N
1070
}
1071
1072                if (!selectionAccept(nodes)) {
1073                    throw new PropertyVetoException JavaDoc("selection " + Arrays.asList(nodes) + " rejected", evt); // NOI18N
1074
}
1075            }
1076        }
1077
1078        public final void propertyChange(final PropertyChangeEvent JavaDoc evt) {
1079            if (manager == null) {
1080                return; // the tree view has been removed before the event got delivered
1081
}
1082
1083            if (evt.getPropertyName().equals(ExplorerManager.PROP_ROOT_CONTEXT)) {
1084                synchronizeRootContext();
1085            }
1086
1087            if (evt.getPropertyName().equals(ExplorerManager.PROP_EXPLORED_CONTEXT)) {
1088                synchronizeExploredContext();
1089            }
1090
1091            if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
1092                synchronizeSelectedNodes();
1093            }
1094        }
1095
1096        public synchronized void treeExpanded(TreeExpansionEvent JavaDoc ev) {
1097            
1098            if (!tree.getScrollsOnExpand()) {
1099                return;
1100            }
1101            
1102            RequestProcessor.Task t = scheduled;
1103
1104            if (t != null) {
1105                t.cancel();
1106            }
1107
1108            class Request implements Runnable JavaDoc {
1109                private TreePath JavaDoc path;
1110
1111                public Request(TreePath JavaDoc path) {
1112                    this.path = path;
1113                }
1114
1115                public void run() {
1116                    if (!SwingUtilities.isEventDispatchThread()) {
1117                        SwingUtilities.invokeLater(this);
1118
1119                        return;
1120                    }
1121
1122                    try {
1123                        if (!tree.isVisible(path)) {
1124                            // if the path is not visible - don't check the children
1125
return;
1126                        }
1127
1128                        if (treeModel == null) {
1129                            // no model, no action, no problem
1130
return;
1131                        }
1132
1133                        TreeNode JavaDoc myNode = (TreeNode JavaDoc) path.getLastPathComponent();
1134
1135                        if (treeModel.getPathToRoot(myNode)[0] != treeModel.getRoot()) {
1136                            // the way from the path no longer
1137
// goes to the root, probably someone
1138
// has removed the node on the way up
1139
// System.out.println("different roots.");
1140
return;
1141                        }
1142
1143                        // show wait cursor
1144
//showWaitCursor ();
1145
int lastChildIndex = myNode.getChildCount() - 1;
1146
1147                        if (lastChildIndex >= 0) {
1148                            TreeNode JavaDoc lastChild = myNode.getChildAt(lastChildIndex);
1149
1150                            Rectangle JavaDoc base = tree.getVisibleRect();
1151                            Rectangle JavaDoc b1 = tree.getPathBounds(path);
1152                            Rectangle JavaDoc b2 = tree.getPathBounds(new TreePath JavaDoc(treeModel.getPathToRoot(lastChild)));
1153
1154                            if ((base != null) && (b1 != null) && (b2 != null)) {
1155                                tree.scrollRectToVisible(new Rectangle JavaDoc(base.x, b1.y, 1, b2.y - b1.y + b2.height));
1156                            }
1157
1158                            // scrollTreeToVisible(path, lastChild);
1159
}
1160                    } finally {
1161                        path = null;
1162                    }
1163                }
1164            }
1165
1166            // It is OK to use multithreaded shared RP as the requests
1167
// will be serialized in event queue later
1168
scheduled = RequestProcessor.getDefault().post(new Request(ev.getPath()), 250); // hope that all children are there after this time
1169
}
1170
1171        public synchronized void treeCollapsed(final TreeExpansionEvent JavaDoc ev) {
1172            showNormalCursor();
1173            class Request implements Runnable JavaDoc {
1174                private TreePath JavaDoc path;
1175
1176                public Request(TreePath JavaDoc path) {
1177                    this.path = path;
1178                }
1179
1180                public void run() {
1181                    if (!SwingUtilities.isEventDispatchThread()) {
1182                        SwingUtilities.invokeLater(this);
1183
1184                        return;
1185                    }
1186
1187                    try {
1188                        if (tree.isExpanded(path)) {
1189                            // the tree shows the path - do not collapse
1190
// the tree
1191
return;
1192                        }
1193
1194                        if (!tree.isVisible(path)) {
1195                            // if the path is not visible do not collapse
1196
// the tree
1197
return;
1198                        }
1199
1200                        if (treeModel == null) {
1201                            // no model, no action, no problem
1202
return;
1203                        }
1204
1205                        TreeNode JavaDoc myNode = (TreeNode JavaDoc) path.getLastPathComponent();
1206
1207                        if (treeModel.getPathToRoot(myNode)[0] != treeModel.getRoot()) {
1208                            // the way from the path no longer
1209
// goes to the root, probably someone
1210
// has removed the node on the way up
1211
// System.out.println("different roots.");
1212
return;
1213                        }
1214
1215                        treeModel.nodeStructureChanged(myNode);
1216                    } finally {
1217                        this.path = null;
1218                    }
1219                }
1220            }
1221
1222            // It is OK to use multithreaded shared RP as the requests
1223
// will be serialized in event queue later
1224
// bugfix #37420, children of all collapsed folders will be throw out
1225
RequestProcessor.getDefault().post(new Request(ev.getPath()), TIME_TO_COLLAPSE);
1226        }
1227
1228        /* Called whenever the value of the selection changes.
1229        * @param ev the event that characterizes the change.
1230        */

1231        public void valueChanged(TreeSelectionEvent JavaDoc ev) {
1232            TreePath JavaDoc[] paths = tree.getSelectionPaths();
1233            storeSelectedPaths = Arrays.asList((paths == null) ? new TreePath JavaDoc[0] : paths);
1234
1235            if (paths == null) {
1236                // part of bugfix #37279, if DnD is active then is useless select a nearby node
1237
if (ExplorerDnDManager.getDefault().isDnDActive()) {
1238                    return;
1239                }
1240
1241                callSelectionChanged(new Node[0]);
1242            } else {
1243                // we need to force no changes to nodes hierarchy =>
1244
// we are requesting read request, but it is not necessary
1245
// to execute the next action immediatelly, so postReadRequest
1246
// should be enough
1247
readAccessPaths = paths;
1248                Children.MUTEX.postReadRequest(this);
1249            }
1250        }
1251
1252        /** Called under Children.MUTEX to refresh the currently selected nodes.
1253        */

1254        public void run() {
1255            if (readAccessPaths == null) {
1256                return;
1257            }
1258
1259            TreePath JavaDoc[] paths = readAccessPaths;
1260
1261            // non null value caused leak in
1262
// ComponentInspector
1263
// When the last Form was closed then the ComponentInspector was
1264
// closed as well. Since this variable was not null -
1265
// last selected Node (RADComponentNode) was held ---> FormManager2 was held, etc.
1266
readAccessPaths = null;
1267
1268            java.util.List JavaDoc<Node> ll = new java.util.ArrayList JavaDoc<Node>(paths.length);
1269
1270            for (int i = 0; i < paths.length; i++) {
1271                Node n = Visualizer.findNode(paths[i].getLastPathComponent());
1272
1273                if( isUnderRoot( manager.getRootContext(), n ) ) {
1274                    ll.add(n);
1275                }
1276            }
1277            callSelectionChanged(ll.toArray(new Node[ll.size()]));
1278        }
1279        
1280        /** Checks whether given Node is a subnode of rootContext.
1281        * @return true if specified Node is under current rootContext
1282        */

1283        private boolean isUnderRoot(Node rootContext, Node node) {
1284            while (node != null) {
1285                if (node.equals(rootContext)) {
1286                    return true;
1287                }
1288
1289                node = node.getParentNode();
1290            }
1291
1292            return false;
1293        }
1294            
1295        public void treeWillCollapse(TreeExpansionEvent JavaDoc event)
1296        throws ExpandVetoException JavaDoc {
1297        }
1298
1299        public void treeWillExpand(TreeExpansionEvent JavaDoc event)
1300        throws ExpandVetoException JavaDoc {
1301            // prepare wait cursor and optionally show it
1302
TreePath JavaDoc path = event.getPath();
1303            prepareWaitCursor(DragDropUtilities.secureFindNode(path.getLastPathComponent()));
1304        }
1305    }
1306     // end of TreePropertyListener
1307

1308    /** Popup adapter.
1309    */

1310    class PopupAdapter extends MouseUtils.PopupMouseAdapter {
1311        PopupAdapter() {
1312        }
1313
1314        protected void showPopup(MouseEvent JavaDoc e) {
1315            int selRow = tree.getRowForLocation(e.getX(), e.getY());
1316
1317            if ((selRow == -1) && !isRootVisible()) {
1318                // Use the invisible root node as a fake selection, and show its popup.
1319
try {
1320                    manager.setSelectedNodes(new Node[] { manager.getRootContext() });
1321                } catch (PropertyVetoException JavaDoc exc) {
1322                    assert false : exc; // not permitted to be thrown
1323
}
1324            } else if (!tree.isRowSelected(selRow)) {
1325                // This will set ExplorerManager selection as well.
1326
// If selRow == -1 the selection will be cleared.
1327
tree.setSelectionRow(selRow);
1328            }
1329
1330            if ((selRow != -1) || !isRootVisible()) {
1331                Point JavaDoc p = SwingUtilities.convertPoint(e.getComponent(), e.getX(), e.getY(), TreeView.this);
1332
1333                createPopup((int) p.getX(), (int) p.getY());
1334            }
1335        }
1336    }
1337
1338    final class PopupSupport extends MouseAdapter JavaDoc implements Runnable JavaDoc, FocusListener JavaDoc, ActionListener JavaDoc {
1339        public final Action popup = new AbstractAction JavaDoc() {
1340                public void actionPerformed(ActionEvent JavaDoc evt) {
1341                    SwingUtilities.invokeLater(PopupSupport.this);
1342                }
1343
1344                /**
1345                 * Returns true if the action is enabled.
1346                 *
1347                 * @return true if the action is enabled, false otherwise
1348                 * @see Action#isEnabled
1349                 */

1350                public boolean isEnabled() {
1351                    return TreeView.this.isFocusOwner() || tree.isFocusOwner();
1352                }
1353            };
1354
1355        //CallbackSystemAction csa;
1356
public void run() {
1357            Point JavaDoc p = getPositionForPopup();
1358
1359            if (p == null) {
1360                //we're going to create a popup menu for the root node
1361
p = new Point JavaDoc(0, 0);
1362            }
1363
1364            createPopup(p.x, p.y);
1365        }
1366
1367        public void focusGained(java.awt.event.FocusEvent JavaDoc ev) {
1368            // unregister
1369
ev.getComponent().removeFocusListener(this);
1370
1371            // lazy activation of drag source
1372
if (DragDropUtilities.dragAndDropEnabled && dragActive) {
1373                setDragSource(true);
1374
1375                // note: dropTarget is activated in constructor
1376
}
1377        }
1378
1379        public void focusLost(FocusEvent JavaDoc ev) {
1380        }
1381
1382        /* clicking adapter */
1383        public void mouseClicked(MouseEvent JavaDoc e) {
1384            int selRow = tree.getRowForLocation(e.getX(), e.getY());
1385
1386            if ((selRow != -1) && SwingUtilities.isLeftMouseButton(e) && MouseUtils.isDoubleClick(e)) {
1387                // Default action.
1388
if (defaultActionEnabled) {
1389                    TreePath JavaDoc selPath = tree.getPathForLocation(e.getX(), e.getY());
1390                    Node node = Visualizer.findNode(selPath.getLastPathComponent());
1391
1392                    Action a = takeAction(node.getPreferredAction(), node);
1393
1394                    if (a != null) {
1395                        if (a.isEnabled()) {
1396                            a.actionPerformed(new ActionEvent JavaDoc(node, ActionEvent.ACTION_PERFORMED, "")); // NOI18N
1397
} else {
1398                            Toolkit.getDefaultToolkit().beep();
1399                        }
1400
1401                        e.consume();
1402
1403                        return;
1404                    }
1405                }
1406
1407                if (tree.isExpanded(selRow)) {
1408                    tree.collapseRow(selRow);
1409                } else {
1410                    tree.expandRow(selRow);
1411                }
1412            }
1413        }
1414
1415        /* VK_ENTER key processor */
1416        public void actionPerformed(ActionEvent JavaDoc evt) {
1417            Node[] nodes = manager.getSelectedNodes();
1418
1419            if (nodes.length == 1) {
1420                Action a = takeAction(nodes[0].getPreferredAction(), nodes[0]);
1421
1422                if (a != null) {
1423                    if (a.isEnabled()) {
1424                        a.actionPerformed(new ActionEvent JavaDoc(nodes[0], ActionEvent.ACTION_PERFORMED, "")); // NOI18N
1425
} else {
1426                        Toolkit.getDefaultToolkit().beep();
1427                    }
1428                }
1429            }
1430        }
1431    }
1432
1433    private final class ExplorerTree extends JTree JavaDoc implements Autoscroll JavaDoc {
1434        AutoscrollSupport support;
1435        private String JavaDoc maxPrefix;
1436        int SEARCH_FIELD_PREFERRED_SIZE = 160;
1437        int SEARCH_FIELD_SPACE = 3;
1438        private boolean firstPaint = true;
1439
1440        // searchTextField manages focus because it handles VK_TAB key
1441
private JTextField JavaDoc searchTextField = new JTextField JavaDoc() {
1442                public boolean isManagingFocus() {
1443                    return true;
1444                }
1445
1446                public void processKeyEvent(KeyEvent JavaDoc ke) {
1447                    //override the default handling so that
1448
//the parent will never receive the escape key and
1449
//close a modal dialog
1450
if (ke.getKeyCode() == ke.VK_ESCAPE) {
1451                        removeSearchField();
1452                        ke.consume();
1453
1454                        // bugfix #32909, reqest focus when search field is removed
1455
SwingUtilities.invokeLater(
1456                            new Runnable JavaDoc() {
1457                                //additional bugfix - do focus change later or removing
1458
//the component while it's focused will cause focus to
1459
//get transferred to the next component in the
1460
//parent focusTraversalPolicy *after* our request
1461
//focus completes, so focus goes into a black hole - Tim
1462
public void run() {
1463                                    ExplorerTree.this.requestFocus();
1464                                }
1465                            }
1466                        );
1467                    } else {
1468                        super.processKeyEvent(ke);
1469                    }
1470                }
1471            };
1472
1473        private JPanel JavaDoc searchpanel = null;
1474        final private int heightOfTextField = searchTextField.getPreferredSize().height;
1475        private int originalScrollMode;
1476
1477        ExplorerTree(TreeModel JavaDoc model) {
1478            super(model);
1479            toggleClickCount = 0;
1480
1481            // fix for #18292
1482
// default action map for JTree defines these shortcuts
1483
// but we use our own mechanism for handling them
1484
// following lines disable default L&F handling (if it is
1485
// defined on Ctrl-c, Ctrl-v and Ctrl-x)
1486
getInputMap().put(KeyStroke.getKeyStroke("control C"), "none"); // NOI18N
1487
getInputMap().put(KeyStroke.getKeyStroke("control V"), "none"); // NOI18N
1488
getInputMap().put(KeyStroke.getKeyStroke("control X"), "none"); // NOI18N
1489
getInputMap().put(KeyStroke.getKeyStroke("COPY"), "none"); // NOI18N
1490
getInputMap().put(KeyStroke.getKeyStroke("PASTE"), "none"); // NOI18N
1491
getInputMap().put(KeyStroke.getKeyStroke("CUT"), "none"); // NOI18N
1492

1493            if (Utilities.isMac()) {
1494                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.META_MASK), "none"); // NOI18N
1495
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.META_MASK), "none"); // NOI18N
1496
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.META_MASK), "none"); // NOI18N
1497
}
1498
1499            setupSearch();
1500            
1501            setDragEnabled( true );
1502        }
1503
1504        public void addNotify() {
1505            super.addNotify();
1506            ViewTooltips.register(this);
1507        }
1508        
1509        public void removeNotify() {
1510            super.removeNotify();
1511            ViewTooltips.unregister(this);
1512        }
1513
1514        public void updateUI() {
1515            super.updateUI();
1516            setBorder(BorderFactory.createEmptyBorder());
1517            if( getTransferHandler() != null && getTransferHandler() instanceof UIResource JavaDoc ) {
1518                //we handle drag and drop in our own way, so let's just fool the UI with a dummy
1519
//TransferHandler to ensure that multiple selection is not lost when drag starts
1520
setTransferHandler( new DummyTransferHandler() );
1521            }
1522        }
1523        
1524        private void calcRowHeight(Graphics JavaDoc g) {
1525            int height = Math.max(18, 2 + g.getFontMetrics(getFont()).getHeight());
1526
1527            //Issue 42743/"Jesse mode"
1528
String JavaDoc s = System.getProperty("nb.cellrenderer.fixedheight"); //NOI18N
1529

1530            if (s != null) {
1531                try {
1532                    height = Integer.parseInt(s);
1533                } catch (Exception JavaDoc e) {
1534                    //do nothing, height not changed
1535
}
1536            }
1537
1538            if (getRowHeight() != height) {
1539            setRowHeight(height);
1540            } else {
1541                revalidate();
1542                repaint();
1543            }
1544        }
1545
1546        //
1547
// Certain operation should be executed in guarded mode - e.g.
1548
// not allow changes in nodes during the operation being executed
1549
//
1550
public void paint(final Graphics JavaDoc g) {
1551            new GuardedActions(0, g);
1552        }
1553
1554        protected void validateTree() {
1555            new GuardedActions(1, null);
1556        }
1557
1558        public void doLayout() {
1559            new GuardedActions(2, null);
1560        }
1561
1562        private void guardedPaint(Graphics JavaDoc g) {
1563            if (firstPaint) {
1564                firstPaint = false;
1565                calcRowHeight(g);
1566
1567                //This will generate a repaint, so don't bother continuing with super.paint()
1568
//but do paint the background color so it doesn't paint gray the first time
1569
g.setColor(getBackground());
1570                g.fillRect(0, 0, getWidth(), getHeight());
1571
1572                return;
1573            }
1574
1575            ExplorerTree.super.paint(g);
1576        }
1577
1578        private void guardedValidateTree() {
1579            super.validateTree();
1580        }
1581
1582        private void guardedDoLayout() {
1583            super.doLayout();
1584
1585            Rectangle JavaDoc visibleRect = getVisibleRect();
1586
1587            if ((searchpanel != null) && searchpanel.isDisplayable()) {
1588                int width = Math.min(
1589                        getPreferredSize().width - (SEARCH_FIELD_SPACE * 2),
1590                        SEARCH_FIELD_PREFERRED_SIZE - SEARCH_FIELD_SPACE
1591                    );
1592
1593                searchpanel.setBounds(
1594                    Math.max(SEARCH_FIELD_SPACE, (visibleRect.x + visibleRect.width) - width),
1595                    visibleRect.y + SEARCH_FIELD_SPACE, Math.min(visibleRect.width, width) - SEARCH_FIELD_SPACE,
1596                    heightOfTextField
1597                );
1598            }
1599        }
1600
1601        public void setFont(Font JavaDoc f) {
1602            if (f != getFont()) {
1603                firstPaint = true;
1604                super.setFont(f);
1605            }
1606        }
1607
1608        protected void processFocusEvent(FocusEvent JavaDoc fe) {
1609            super.processFocusEvent(fe);
1610
1611            //Since the selected when focused is different, we need to force a
1612
//repaint of the entire selection, but let's do it in guarded more
1613
//as any other repaint
1614
new GuardedActions(3, null);
1615        }
1616
1617        private void repaintSelection() {
1618            int first = getSelectionModel().getMinSelectionRow();
1619            int last = getSelectionModel().getMaxSelectionRow();
1620
1621            if (first != -1) {
1622                if (first == last) {
1623                    Rectangle JavaDoc r = getRowBounds(first);
1624                    repaint(r.x, r.y, r.width, r.height);
1625                } else {
1626                    Rectangle JavaDoc top = getRowBounds(first);
1627                    Rectangle JavaDoc bottom = getRowBounds(last);
1628                    Rectangle JavaDoc r = new Rectangle JavaDoc();
1629                    r.x = Math.min(top.x, bottom.x);
1630                    r.y = top.y;
1631                    r.width = getWidth();
1632                    r.height = (bottom.y + bottom.height) - top.y;
1633                    repaint(r.x, r.y, r.width, r.height);
1634                }
1635            }
1636        }
1637
1638        private void prepareSearchPanel() {
1639            if (searchpanel == null) {
1640                searchpanel = new JPanel JavaDoc();
1641
1642                JLabel JavaDoc lbl = new JLabel JavaDoc(NbBundle.getMessage(TreeView.class, "LBL_QUICKSEARCH")); //NOI18N
1643
searchpanel.setLayout(new BoxLayout JavaDoc(searchpanel, BoxLayout.X_AXIS));
1644                searchpanel.add(lbl);
1645                searchpanel.add(searchTextField);
1646                lbl.setLabelFor(searchTextField);
1647                searchpanel.setBorder(BorderFactory.createRaisedBevelBorder());
1648                lbl.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
1649            }
1650        }
1651
1652        private void setupSearch() {
1653            // Remove the default key listeners
1654
KeyListener JavaDoc[] keyListeners = getListeners(KeyListener JavaDoc.class);
1655
1656            for (int i = 0; i < keyListeners.length; i++) {
1657                removeKeyListener(keyListeners[i]);
1658            }
1659
1660            // Add new key listeners
1661
addKeyListener(
1662                new KeyAdapter JavaDoc() {
1663                    public void keyTyped(KeyEvent JavaDoc e) {
1664                        int modifiers = e.getModifiers();
1665                        int keyCode = e.getKeyCode();
1666                        char c = e.getKeyChar();
1667
1668                        //#43617 - don't eat + and -
1669
if ((c == '+') || (c == '-')) return;
1670
1671                        if (((modifiers > 0) && (modifiers != KeyEvent.SHIFT_MASK)) || e.isActionKey()) {
1672                            return;
1673                        }
1674
1675                        if (Character.isISOControl(c) ||
1676                              (keyCode == KeyEvent.VK_SHIFT) ||
1677                  (keyCode == KeyEvent.VK_ESCAPE)) return;
1678
1679                        final KeyStroke JavaDoc stroke = KeyStroke.getKeyStrokeForEvent(e);
1680                        searchTextField.setText(String.valueOf(stroke.getKeyChar()));
1681
1682                        displaySearchField();
1683                        e.consume();
1684                    }
1685                }
1686            );
1687
1688            // Create a the "multi-event" listener for the text field. Instead of
1689
// adding separate instances of each needed listener, we're using a
1690
// class which implements them all. This approach is used in order
1691
// to avoid the creation of 4 instances which takes some time
1692
SearchFieldListener searchFieldListener = new SearchFieldListener();
1693            searchTextField.addKeyListener(searchFieldListener);
1694            searchTextField.addFocusListener(searchFieldListener);
1695            searchTextField.getDocument().addDocumentListener(searchFieldListener);
1696        }
1697
1698        private List JavaDoc<TreePath JavaDoc> doSearch(String JavaDoc prefix) {
1699            List JavaDoc<TreePath JavaDoc> results = new ArrayList JavaDoc<TreePath JavaDoc>();
1700
1701            // do search forward the selected index
1702
int[] rows = getSelectionRows();
1703            int startIndex = ((rows == null) || (rows.length == 0)) ? 0 : rows[0];
1704
1705            int size = getRowCount();
1706
1707            if (size == 0) {
1708                // Empty tree (no root visible); cannot match anything.
1709
return results;
1710            }
1711
1712            while (true) {
1713                startIndex = startIndex % size;
1714
1715                TreePath JavaDoc path = getNextMatch(prefix, startIndex, Position.Bias.Forward);
1716
1717                if ((path != null) && !results.contains(path)) {
1718                    startIndex = tree.getRowForPath(path);
1719                    results.add(path);
1720
1721                    String JavaDoc elementName = ((VisualizerNode) path.getLastPathComponent()).getDisplayName();
1722
1723                    // initialize prefix
1724
if (maxPrefix == null) {
1725                        maxPrefix = elementName;
1726                    }
1727
1728                    maxPrefix = findMaxPrefix(maxPrefix, elementName);
1729
1730                    // try next element
1731
startIndex++;
1732                } else {
1733                    break;
1734                }
1735            }
1736
1737            return results;
1738        }
1739
1740        private String JavaDoc findMaxPrefix(String JavaDoc str1, String JavaDoc str2) {
1741            String JavaDoc res = null;
1742
1743            for (int i = 0; str1.regionMatches(true, 0, str2, 0, i); i++) {
1744                res = str1.substring(0, i);
1745            }
1746
1747            return res;
1748        }
1749
1750        /**
1751         * Adds the search field to the tree.
1752         */

1753        private void displaySearchField() {
1754            if (!searchTextField.isDisplayable()) {
1755                JViewport JavaDoc viewport = TreeView.this.getViewport();
1756                originalScrollMode = viewport.getScrollMode();
1757                viewport.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
1758                searchTextField.setFont(ExplorerTree.this.getFont());
1759                prepareSearchPanel();
1760                add(searchpanel);
1761                revalidate();
1762                repaint();
1763
1764                // bugfix #28501, avoid the chars duplicated on jdk1.3
1765
SwingUtilities.invokeLater(
1766                    new Runnable JavaDoc() {
1767                        public void run() {
1768                            searchTextField.requestFocus();
1769                        }
1770                    }
1771                );
1772            }
1773        }
1774
1775        /**
1776         * Removes the search field from the tree.
1777         */

1778        private void removeSearchField() {
1779            if (searchpanel.isDisplayable()) {
1780                remove(searchpanel);
1781                TreeView.this.getViewport().setScrollMode(originalScrollMode);
1782
1783                Rectangle JavaDoc r = searchpanel.getBounds();
1784                this.repaint(r);
1785            }
1786        }
1787
1788        /** notify the Component to autoscroll */
1789        public void autoscroll(Point JavaDoc cursorLoc) {
1790            getSupport().autoscroll(cursorLoc);
1791        }
1792
1793        /** @return the Insets describing the autoscrolling
1794         * region or border relative to the geometry of the
1795         * implementing Component.
1796         */

1797        public Insets JavaDoc getAutoscrollInsets() {
1798            return getSupport().getAutoscrollInsets();
1799        }
1800
1801        /** Safe getter for autoscroll support. */
1802        AutoscrollSupport getSupport() {
1803            if (support == null) {
1804                support = new AutoscrollSupport(this, new Insets JavaDoc(15, 10, 15, 10));
1805            }
1806
1807            return support;
1808        }
1809
1810        public String JavaDoc getToolTipText(MouseEvent JavaDoc event) {
1811            if (event != null) {
1812                Point JavaDoc p = event.getPoint();
1813                int selRow = getRowForLocation(p.x, p.y);
1814
1815                if (selRow != -1) {
1816                    TreePath JavaDoc path = getPathForRow(selRow);
1817                    VisualizerNode v = (VisualizerNode) path.getLastPathComponent();
1818                    String JavaDoc tooltip = v.getShortDescription();
1819                    String JavaDoc displayName = v.getDisplayName();
1820
1821                    if ((tooltip != null) && !tooltip.equals(displayName)) {
1822                        return tooltip;
1823                    }
1824                }
1825            }
1826
1827            return null;
1828        }
1829
1830        protected TreeModelListener JavaDoc createTreeModelListener() {
1831            return new ModelHandler();
1832        }
1833
1834        public AccessibleContext JavaDoc getAccessibleContext() {
1835            if (accessibleContext == null) {
1836                accessibleContext = new AccessibleExplorerTree();
1837            }
1838
1839            return accessibleContext;
1840        }
1841
1842        private class GuardedActions implements Mutex.Action<Void JavaDoc> {
1843            private int type;
1844            private Object JavaDoc p1;
1845
1846            public GuardedActions(int type, Object JavaDoc p1) {
1847                this.type = type;
1848                this.p1 = p1;
1849                Children.MUTEX.readAccess(this);
1850            }
1851
1852            public Void JavaDoc run() {
1853                switch (type) {
1854                case 0:
1855                    guardedPaint((Graphics JavaDoc) p1);
1856
1857                    break;
1858
1859                case 1:
1860                    guardedValidateTree();
1861
1862                    break;
1863
1864                case 2:
1865                    guardedDoLayout();
1866
1867                    break;
1868
1869                case 3:
1870                    repaintSelection();
1871
1872                    break;
1873
1874                default:
1875                    throw new IllegalStateException JavaDoc("type: " + type);
1876                }
1877
1878                return null;
1879            }
1880        }
1881
1882        private class SearchFieldListener extends KeyAdapter JavaDoc implements DocumentListener JavaDoc, FocusListener JavaDoc {
1883            /** The last search results */
1884            private List JavaDoc<TreePath JavaDoc> results = new ArrayList JavaDoc<TreePath JavaDoc>();
1885
1886            /** The last selected index from the search results. */
1887            private int currentSelectionIndex;
1888
1889            SearchFieldListener() {
1890            }
1891
1892            public void changedUpdate(DocumentEvent JavaDoc e) {
1893                searchForNode();
1894            }
1895
1896            public void insertUpdate(DocumentEvent JavaDoc e) {
1897                searchForNode();
1898            }
1899
1900            public void removeUpdate(DocumentEvent JavaDoc e) {
1901                searchForNode();
1902            }
1903
1904            public void keyPressed(KeyEvent JavaDoc e) {
1905                int keyCode = e.getKeyCode();
1906
1907                if (keyCode == KeyEvent.VK_ESCAPE) {
1908                    removeSearchField();
1909                    ExplorerTree.this.requestFocus();
1910                } else if (keyCode == KeyEvent.VK_UP) {
1911                    currentSelectionIndex--;
1912                    displaySearchResult();
1913
1914                    // Stop processing the event here. Otherwise it's dispatched
1915
// to the tree too (which scrolls)
1916
e.consume();
1917                } else if (keyCode == KeyEvent.VK_DOWN) {
1918                    currentSelectionIndex++;
1919                    displaySearchResult();
1920
1921                    // Stop processing the event here. Otherwise it's dispatched
1922
// to the tree too (which scrolls)
1923
e.consume();
1924                } else if (keyCode == KeyEvent.VK_TAB) {
1925                    if (maxPrefix != null) {
1926                        searchTextField.setText(maxPrefix);
1927                    }
1928
1929                    e.consume();
1930                } else if (keyCode == KeyEvent.VK_ENTER) {
1931                    removeSearchField();
1932
1933                    // bugfix #39607, don't expand selected node when default action invoked
1934
TreePath JavaDoc selectedTPath = getSelectionPath();
1935
1936                    if (selectedTPath != null) {
1937                        TreeNode JavaDoc selectedTNode = (TreeNode JavaDoc) selectedTPath.getLastPathComponent();
1938                        Node selectedNode = Visualizer.findNode(selectedTNode);
1939
1940                        if (
1941                            (selectedNode.getPreferredAction() != null) &&
1942                                selectedNode.getPreferredAction().isEnabled()
1943                        ) {
1944                            selectedNode.getPreferredAction().actionPerformed(
1945                                new ActionEvent JavaDoc(this, ActionEvent.ACTION_PERFORMED, "")
1946                            );
1947                        } else {
1948                            expandPath(getSelectionPath());
1949                        }
1950                    }
1951
1952                    ExplorerTree.this.requestFocus();
1953                    ExplorerTree.this.dispatchEvent(e);
1954                }
1955            }
1956
1957            /** Searches for a node in the tree. */
1958            private void searchForNode() {
1959                currentSelectionIndex = 0;
1960                results.clear();
1961                maxPrefix = null;
1962
1963                String JavaDoc text = searchTextField.getText().toUpperCase();
1964
1965                if (text.length() > 0) {
1966                    results = doSearch(text);
1967                    displaySearchResult();
1968                }
1969            }
1970
1971            private void displaySearchResult() {
1972                int sz = results.size();
1973
1974                if (sz > 0) {
1975                    if (currentSelectionIndex < 0) {
1976                        currentSelectionIndex = sz - 1;
1977                    } else if (currentSelectionIndex >= sz) {
1978                        currentSelectionIndex = 0;
1979                    }
1980
1981                    TreePath JavaDoc path = results.get(currentSelectionIndex);
1982                    setSelectionPath(path);
1983                    scrollPathToVisible(path);
1984                } else {
1985                    clearSelection();
1986                }
1987            }
1988
1989            public void focusGained(FocusEvent JavaDoc e) {
1990                // Do nothing
1991
}
1992
1993            public void focusLost(FocusEvent JavaDoc e) {
1994                removeSearchField();
1995            }
1996        }
1997
1998        private class AccessibleExplorerTree extends JTree.AccessibleJTree JavaDoc {
1999            AccessibleExplorerTree() {
2000            }
2001
2002            public String JavaDoc getAccessibleName() {
2003                return TreeView.this.getAccessibleContext().getAccessibleName();
2004            }
2005
2006            public String JavaDoc getAccessibleDescription() {
2007                return TreeView.this.getAccessibleContext().getAccessibleDescription();
2008            }
2009        }
2010
2011        private class ModelHandler extends JTree.TreeModelHandler JavaDoc {
2012            ModelHandler() {
2013            }
2014
2015            public void treeStructureChanged(TreeModelEvent JavaDoc e) {
2016                // Remember selections and expansions
2017
TreePath JavaDoc[] selectionPaths = getSelectionPaths();
2018                java.util.Enumeration JavaDoc expanded = getExpandedDescendants(e.getTreePath());
2019
2020                // Restructure the node
2021
super.treeStructureChanged(e);
2022
2023                // Expand previously expanded paths
2024
if (expanded != null) {
2025                    while (expanded.hasMoreElements()) {
2026                        expandPath((TreePath JavaDoc) expanded.nextElement());
2027                    }
2028                }
2029
2030                // Select previously selected paths
2031
if ((selectionPaths != null) && (selectionPaths.length > 0)) {
2032                    boolean wasSelected = isPathSelected(selectionPaths[0]);
2033
2034                    setSelectionPaths(selectionPaths);
2035
2036                    if (!wasSelected) {
2037                        // do not scroll if the first selection path survived structure change
2038
scrollPathToVisible(selectionPaths[0]);
2039                    }
2040                }
2041            }
2042
2043            public void treeNodesRemoved(TreeModelEvent JavaDoc e) {
2044                // called to removed from JTree.expandedState
2045
super.treeNodesRemoved(e);
2046
2047                // part of bugfix #37279, if DnD is active then is useless select a nearby node
2048
if (ExplorerDnDManager.getDefault().isDnDActive()) {
2049                    return;
2050                }
2051
2052                if (tree.getSelectionCount() == 0) {
2053                    TreePath JavaDoc path = findSiblingTreePath(e.getTreePath(), e.getChildIndices());
2054
2055                    // bugfix #39564, don't select again the same object
2056
if ((path == null) || path.equals(e.getTreePath())) {
2057                        return;
2058                    } else if (path.getPathCount() > 0) {
2059                        tree.setSelectionPath(path);
2060                    }
2061                }
2062            }
2063        }
2064    }
2065    
2066    private static class DummyTransferHandler extends TransferHandler JavaDoc /*implements UIResource*/ {
2067        public void exportAsDrag(JComponent JavaDoc comp, InputEvent JavaDoc e, int action) {
2068            //do nothing - ExplorerDnDManager will kick in when necessary
2069
}
2070        public void exportToClipboard(JComponent JavaDoc comp, Clipboard JavaDoc clip, int action)
2071                                                      throws IllegalStateException JavaDoc {
2072            //do nothing - Node actions will hande this
2073
}
2074        public boolean canImport(JComponent JavaDoc comp, DataFlavor JavaDoc[] transferFlavors) {
2075            return false; //TreeViewDropSupport will decided
2076
}
2077        public boolean importData(JComponent JavaDoc comp, Transferable JavaDoc t) {
2078            return false;
2079        }
2080        public int getSourceActions(JComponent JavaDoc c) {
2081            return COPY_OR_MOVE;
2082        }
2083    }
2084}
2085
Popular Tags