1 /*
2  * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.plaf.basic;
27 
28 import javax.swing.*;
29 import javax.swing.event.*;
30 import java.awt.*;
31 import java.awt.event.*;
32 import java.awt.datatransfer.*;
33 import java.beans.*;
34 import java.util.Enumeration;
35 import java.util.Hashtable;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.Comparator;
39 import javax.swing.plaf.ComponentUI;
40 import javax.swing.plaf.UIResource;
41 import javax.swing.plaf.TreeUI;
42 import javax.swing.tree.*;
43 import javax.swing.text.Position;
44 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
45 import sun.awt.AWTAccessor;
46 import sun.swing.SwingUtilities2;
47 
48 import sun.swing.DefaultLookup;
49 import sun.swing.UIAction;
50 
51 /**
52  * The basic L&F for a hierarchical data structure.
53  *
54  * @author Scott Violet
55  * @author Shannon Hickey (drag and drop)
56  */
57 
58 public class BasicTreeUI extends TreeUI
59 {
60     private static final StringBuilder BASELINE_COMPONENT_KEY =
61         new StringBuilder("Tree.baselineComponent");
62 
63     // Old actions forward to an instance of this.
64     private static final Actions SHARED_ACTION = new Actions();
65 
66     /**
67      * The collapsed icon.
68      */
69     protected transient Icon        collapsedIcon;
70     /**
71      * The expanded icon.
72      */
73     protected transient Icon        expandedIcon;
74 
75     /**
76       * Color used to draw hash marks.  If <code>null</code> no hash marks
77       * will be drawn.
78       */
79     private Color hashColor;
80 
81     /** Distance between left margin and where vertical dashes will be
82       * drawn. */
83     protected int               leftChildIndent;
84     /** Distance to add to leftChildIndent to determine where cell
85       * contents will be drawn. */
86     protected int               rightChildIndent;
87     /** Total distance that will be indented.  The sum of leftChildIndent
88       * and rightChildIndent. */
89     protected int               totalChildIndent;
90 
91     /** Minimum preferred size. */
92     protected Dimension         preferredMinSize;
93 
94     /** Index of the row that was last selected. */
95     protected int               lastSelectedRow;
96 
97     /** Component that we're going to be drawing into. */
98     protected JTree             tree;
99 
100     /** Renderer that is being used to do the actual cell drawing. */
101     protected transient TreeCellRenderer   currentCellRenderer;
102 
103     /** Set to true if the renderer that is currently in the tree was
104      * created by this instance. */
105     protected boolean           createdRenderer;
106 
107     /** Editor for the tree. */
108     protected transient TreeCellEditor     cellEditor;
109 
110     /** Set to true if editor that is currently in the tree was
111      * created by this instance. */
112     protected boolean           createdCellEditor;
113 
114     /** Set to false when editing and shouldSelectCell() returns true meaning
115       * the node should be selected before editing, used in completeEditing. */
116     protected boolean           stopEditingInCompleteEditing;
117 
118     /** Used to paint the TreeCellRenderer. */
119     protected CellRendererPane  rendererPane;
120 
121     /** Size needed to completely display all the nodes. */
122     protected Dimension         preferredSize;
123 
124     /** Is the preferredSize valid? */
125     protected boolean           validCachedPreferredSize;
126 
127     /** Object responsible for handling sizing and expanded issues. */
128     // WARNING: Be careful with the bounds held by treeState. They are
129     // always in terms of left-to-right. They get mapped to right-to-left
130     // by the various methods of this class.
131     protected AbstractLayoutCache  treeState;
132 
133 
134     /** Used for minimizing the drawing of vertical lines. */
135     protected Hashtable<TreePath,Boolean> drawingCache;
136 
137     /** True if doing optimizations for a largeModel. Subclasses that
138      * don't support this may wish to override createLayoutCache to not
139      * return a FixedHeightLayoutCache instance. */
140     protected boolean           largeModel;
141 
142     /** Reponsible for telling the TreeState the size needed for a node. */
143     protected AbstractLayoutCache.NodeDimensions     nodeDimensions;
144 
145     /** Used to determine what to display. */
146     protected TreeModel         treeModel;
147 
148     /** Model maintaining the selection. */
149     protected TreeSelectionModel treeSelectionModel;
150 
151     /** How much the depth should be offset to properly calculate
152      * x locations. This is based on whether or not the root is visible,
153      * and if the root handles are visible. */
154     protected int               depthOffset;
155 
156     // Following 4 ivars are only valid when editing.
157 
158     /** When editing, this will be the Component that is doing the actual
159       * editing. */
160     protected Component         editingComponent;
161 
162     /** Path that is being edited. */
163     protected TreePath          editingPath;
164 
165     /** Row that is being edited. Should only be referenced if
166      * editingComponent is not null. */
167     protected int               editingRow;
168 
169     /** Set to true if the editor has a different size than the renderer. */
170     protected boolean           editorHasDifferentSize;
171 
172     /** Row correspondin to lead path. */
173     private int                 leadRow;
174     /** If true, the property change event for LEAD_SELECTION_PATH_PROPERTY,
175      * or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint. */
176     private boolean             ignoreLAChange;
177 
178     /** Indicates the orientation. */
179     private boolean             leftToRight;
180 
181     // Cached listeners
182     private PropertyChangeListener propertyChangeListener;
183     private PropertyChangeListener selectionModelPropertyChangeListener;
184     private MouseListener mouseListener;
185     private FocusListener focusListener;
186     private KeyListener keyListener;
187     /** Used for large models, listens for moved/resized events and
188      * updates the validCachedPreferredSize bit accordingly. */
189     private ComponentListener   componentListener;
190     /** Listens for CellEditor events. */
191     private CellEditorListener  cellEditorListener;
192     /** Updates the display when the selection changes. */
193     private TreeSelectionListener treeSelectionListener;
194     /** Is responsible for updating the display based on model events. */
195     private TreeModelListener treeModelListener;
196     /** Updates the treestate as the nodes expand. */
197     private TreeExpansionListener treeExpansionListener;
198 
199     /** UI property indicating whether to paint lines */
200     private boolean paintLines = true;
201 
202     /** UI property for painting dashed lines */
203     private boolean lineTypeDashed;
204 
205     /**
206      * The time factor to treate the series of typed alphanumeric key
207      * as prefix for first letter navigation.
208      */
209     private long timeFactor = 1000L;
210 
211     private Handler handler;
212 
213     /**
214      * A temporary variable for communication between startEditingOnRelease
215      * and startEditing.
216      */
217     private MouseEvent releaseEvent;
218 
219     /**
220      * Constructs a new instance of {@code BasicTreeUI}.
221      *
222      * @param x a component
223      * @return a new instance of {@code BasicTreeUI}
224      */
createUI(JComponent x)225     public static ComponentUI createUI(JComponent x) {
226         return new BasicTreeUI();
227     }
228 
229 
loadActionMap(LazyActionMap map)230     static void loadActionMap(LazyActionMap map) {
231         map.put(new Actions(Actions.SELECT_PREVIOUS));
232         map.put(new Actions(Actions.SELECT_PREVIOUS_CHANGE_LEAD));
233         map.put(new Actions(Actions.SELECT_PREVIOUS_EXTEND_SELECTION));
234 
235         map.put(new Actions(Actions.SELECT_NEXT));
236         map.put(new Actions(Actions.SELECT_NEXT_CHANGE_LEAD));
237         map.put(new Actions(Actions.SELECT_NEXT_EXTEND_SELECTION));
238 
239         map.put(new Actions(Actions.SELECT_CHILD));
240         map.put(new Actions(Actions.SELECT_CHILD_CHANGE_LEAD));
241 
242         map.put(new Actions(Actions.SELECT_PARENT));
243         map.put(new Actions(Actions.SELECT_PARENT_CHANGE_LEAD));
244 
245         map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION));
246         map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
247         map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION));
248 
249         map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION));
250         map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION));
251         map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
252 
253         map.put(new Actions(Actions.SELECT_FIRST));
254         map.put(new Actions(Actions.SELECT_FIRST_CHANGE_LEAD));
255         map.put(new Actions(Actions.SELECT_FIRST_EXTEND_SELECTION));
256 
257         map.put(new Actions(Actions.SELECT_LAST));
258         map.put(new Actions(Actions.SELECT_LAST_CHANGE_LEAD));
259         map.put(new Actions(Actions.SELECT_LAST_EXTEND_SELECTION));
260 
261         map.put(new Actions(Actions.TOGGLE));
262 
263         map.put(new Actions(Actions.CANCEL_EDITING));
264 
265         map.put(new Actions(Actions.START_EDITING));
266 
267         map.put(new Actions(Actions.SELECT_ALL));
268 
269         map.put(new Actions(Actions.CLEAR_SELECTION));
270 
271         map.put(new Actions(Actions.SCROLL_LEFT));
272         map.put(new Actions(Actions.SCROLL_RIGHT));
273 
274         map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION));
275         map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION));
276 
277         map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_LEAD));
278         map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_LEAD));
279 
280         map.put(new Actions(Actions.EXPAND));
281         map.put(new Actions(Actions.COLLAPSE));
282         map.put(new Actions(Actions.MOVE_SELECTION_TO_PARENT));
283 
284         map.put(new Actions(Actions.ADD_TO_SELECTION));
285         map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
286         map.put(new Actions(Actions.EXTEND_TO));
287         map.put(new Actions(Actions.MOVE_SELECTION_TO));
288 
289         map.put(TransferHandler.getCutAction());
290         map.put(TransferHandler.getCopyAction());
291         map.put(TransferHandler.getPasteAction());
292     }
293 
294     /**
295      * Constructs a new instance of {@code BasicTreeUI}.
296      */
BasicTreeUI()297     public BasicTreeUI() {
298         super();
299     }
300 
301     /**
302      * Returns the hash color.
303      *
304      * @return the hash color
305      */
getHashColor()306     protected Color getHashColor() {
307         return hashColor;
308     }
309 
310     /**
311      * Sets the hash color.
312      *
313      * @param color the hash color
314      */
setHashColor(Color color)315     protected void setHashColor(Color color) {
316         hashColor = color;
317     }
318 
319     /**
320      * Sets the left child indent.
321      *
322      * @param newAmount the left child indent
323      */
setLeftChildIndent(int newAmount)324     public void setLeftChildIndent(int newAmount) {
325         leftChildIndent = newAmount;
326         totalChildIndent = leftChildIndent + rightChildIndent;
327         if(treeState != null)
328             treeState.invalidateSizes();
329         updateSize();
330     }
331 
332     /**
333      * Returns the left child indent.
334      *
335      * @return the left child indent
336      */
getLeftChildIndent()337     public int getLeftChildIndent() {
338         return leftChildIndent;
339     }
340 
341     /**
342      * Sets the right child indent.
343      *
344      * @param newAmount the right child indent
345      */
setRightChildIndent(int newAmount)346     public void setRightChildIndent(int newAmount) {
347         rightChildIndent = newAmount;
348         totalChildIndent = leftChildIndent + rightChildIndent;
349         if(treeState != null)
350             treeState.invalidateSizes();
351         updateSize();
352     }
353 
354     /**
355      * Returns the right child indent.
356      *
357      * @return the right child indent
358      */
getRightChildIndent()359     public int getRightChildIndent() {
360         return rightChildIndent;
361     }
362 
363     /**
364      * Sets the expanded icon.
365      *
366      * @param newG the expanded icon
367      */
setExpandedIcon(Icon newG)368     public void setExpandedIcon(Icon newG) {
369         expandedIcon = newG;
370     }
371 
372     /**
373      * Returns the expanded icon.
374      *
375      * @return the expanded icon
376      */
getExpandedIcon()377     public Icon getExpandedIcon() {
378         return expandedIcon;
379     }
380 
381     /**
382      * Sets the collapsed icon.
383      *
384      * @param newG the collapsed icon
385      */
setCollapsedIcon(Icon newG)386     public void setCollapsedIcon(Icon newG) {
387         collapsedIcon = newG;
388     }
389 
390     /**
391      * Returns the collapsed icon.
392      *
393      * @return the collapsed icon
394      */
getCollapsedIcon()395     public Icon getCollapsedIcon() {
396         return collapsedIcon;
397     }
398 
399     //
400     // Methods for configuring the behavior of the tree. None of them
401     // push the value to the JTree instance. You should really only
402     // call these methods on the JTree.
403     //
404 
405     /**
406      * Updates the componentListener, if necessary.
407      *
408      * @param largeModel the new value
409      */
setLargeModel(boolean largeModel)410     protected void setLargeModel(boolean largeModel) {
411         if(getRowHeight() < 1)
412             largeModel = false;
413         if(this.largeModel != largeModel) {
414             completeEditing();
415             this.largeModel = largeModel;
416             treeState = createLayoutCache();
417             configureLayoutCache();
418             updateLayoutCacheExpandedNodesIfNecessary();
419             updateSize();
420         }
421     }
422 
423     /**
424      * Returns {@code true} if large model is set.
425      *
426      * @return {@code true} if large model is set
427      */
isLargeModel()428     protected boolean isLargeModel() {
429         return largeModel;
430     }
431 
432     /**
433      * Sets the row height, this is forwarded to the treeState.
434      *
435      * @param rowHeight the row height
436      */
setRowHeight(int rowHeight)437     protected void setRowHeight(int rowHeight) {
438         completeEditing();
439         if(treeState != null) {
440             setLargeModel(tree.isLargeModel());
441             treeState.setRowHeight(rowHeight);
442             updateSize();
443         }
444     }
445 
446     /**
447      * Returns the row height.
448      *
449      * @return the row height
450      */
getRowHeight()451     protected int getRowHeight() {
452         return (tree == null) ? -1 : tree.getRowHeight();
453     }
454 
455     /**
456      * Sets the {@code TreeCellRenderer} to {@code tcr}. This invokes
457      * {@code updateRenderer}.
458      *
459      * @param tcr the new value
460      */
setCellRenderer(TreeCellRenderer tcr)461     protected void setCellRenderer(TreeCellRenderer tcr) {
462         completeEditing();
463         updateRenderer();
464         if(treeState != null) {
465             treeState.invalidateSizes();
466             updateSize();
467         }
468     }
469 
470     /**
471      * Return {@code currentCellRenderer}, which will either be the trees
472      * renderer, or {@code defaultCellRenderer}, which ever wasn't null.
473      *
474      * @return an instance of {@code TreeCellRenderer}
475      */
getCellRenderer()476     protected TreeCellRenderer getCellRenderer() {
477         return currentCellRenderer;
478     }
479 
480     /**
481      * Sets the {@code TreeModel}.
482      *
483      * @param model the new value
484      */
setModel(TreeModel model)485     protected void setModel(TreeModel model) {
486         completeEditing();
487         if(treeModel != null && treeModelListener != null)
488             treeModel.removeTreeModelListener(treeModelListener);
489         treeModel = model;
490         if(treeModel != null) {
491             if(treeModelListener != null)
492                 treeModel.addTreeModelListener(treeModelListener);
493         }
494         if(treeState != null) {
495             treeState.setModel(model);
496             updateLayoutCacheExpandedNodesIfNecessary();
497             updateSize();
498         }
499     }
500 
501     /**
502      * Returns the tree model.
503      *
504      * @return the tree model
505      */
getModel()506     protected TreeModel getModel() {
507         return treeModel;
508     }
509 
510     /**
511      * Sets the root to being visible.
512      *
513      * @param newValue the new value
514      */
setRootVisible(boolean newValue)515     protected void setRootVisible(boolean newValue) {
516         completeEditing();
517         updateDepthOffset();
518         if(treeState != null) {
519             treeState.setRootVisible(newValue);
520             treeState.invalidateSizes();
521             updateSize();
522         }
523     }
524 
525     /**
526      * Returns {@code true} if the tree root is visible.
527      *
528      * @return {@code true} if the tree root is visible
529      */
isRootVisible()530     protected boolean isRootVisible() {
531         return (tree != null) ? tree.isRootVisible() : false;
532     }
533 
534     /**
535      * Determines whether the node handles are to be displayed.
536      *
537      * @param newValue the new value
538      */
setShowsRootHandles(boolean newValue)539     protected void setShowsRootHandles(boolean newValue) {
540         completeEditing();
541         updateDepthOffset();
542         if(treeState != null) {
543             treeState.invalidateSizes();
544             updateSize();
545         }
546     }
547 
548     /**
549      * Returns {@code true} if the root handles are to be displayed.
550      *
551      * @return {@code true} if the root handles are to be displayed
552      */
getShowsRootHandles()553     protected boolean getShowsRootHandles() {
554         return (tree != null) ? tree.getShowsRootHandles() : false;
555     }
556 
557     /**
558      * Sets the cell editor.
559      *
560      * @param editor the new cell editor
561      */
setCellEditor(TreeCellEditor editor)562     protected void setCellEditor(TreeCellEditor editor) {
563         updateCellEditor();
564     }
565 
566     /**
567      * Returns an instance of {@code TreeCellEditor}.
568      *
569      * @return an instance of {@code TreeCellEditor}
570      */
getCellEditor()571     protected TreeCellEditor getCellEditor() {
572         return (tree != null) ? tree.getCellEditor() : null;
573     }
574 
575     /**
576      * Configures the receiver to allow, or not allow, editing.
577      *
578      * @param newValue the new value
579      */
setEditable(boolean newValue)580     protected void setEditable(boolean newValue) {
581         updateCellEditor();
582     }
583 
584     /**
585      * Returns {@code true} if the tree is editable.
586      *
587      * @return {@code true} if the tree is editable
588      */
isEditable()589     protected boolean isEditable() {
590         return (tree != null) ? tree.isEditable() : false;
591     }
592 
593     /**
594      * Resets the selection model. The appropriate listener are installed
595      * on the model.
596      *
597      * @param newLSM new selection model
598      */
setSelectionModel(TreeSelectionModel newLSM)599     protected void setSelectionModel(TreeSelectionModel newLSM) {
600         completeEditing();
601         if(selectionModelPropertyChangeListener != null &&
602            treeSelectionModel != null)
603             treeSelectionModel.removePropertyChangeListener
604                               (selectionModelPropertyChangeListener);
605         if(treeSelectionListener != null && treeSelectionModel != null)
606             treeSelectionModel.removeTreeSelectionListener
607                                (treeSelectionListener);
608         treeSelectionModel = newLSM;
609         if(treeSelectionModel != null) {
610             if(selectionModelPropertyChangeListener != null)
611                 treeSelectionModel.addPropertyChangeListener
612                               (selectionModelPropertyChangeListener);
613             if(treeSelectionListener != null)
614                 treeSelectionModel.addTreeSelectionListener
615                                    (treeSelectionListener);
616             if(treeState != null)
617                 treeState.setSelectionModel(treeSelectionModel);
618         }
619         else if(treeState != null)
620             treeState.setSelectionModel(null);
621         if(tree != null)
622             tree.repaint();
623     }
624 
625     /**
626      * Returns the tree selection model.
627      *
628      * @return the tree selection model
629      */
getSelectionModel()630     protected TreeSelectionModel getSelectionModel() {
631         return treeSelectionModel;
632     }
633 
634     //
635     // TreeUI methods
636     //
637 
638     /**
639       * Returns the Rectangle enclosing the label portion that the
640       * last item in path will be drawn into.  Will return null if
641       * any component in path is currently valid.
642       */
getPathBounds(JTree tree, TreePath path)643     public Rectangle getPathBounds(JTree tree, TreePath path) {
644         if(tree != null && treeState != null) {
645             return getPathBounds(path, tree.getInsets(), new Rectangle());
646         }
647         return null;
648     }
649 
getPathBounds(TreePath path, Insets insets, Rectangle bounds)650     private Rectangle getPathBounds(TreePath path, Insets insets,
651                                     Rectangle bounds) {
652         bounds = treeState.getBounds(path, bounds);
653         if (bounds != null) {
654             if (leftToRight) {
655                 bounds.x += insets.left;
656             } else {
657                 bounds.x = tree.getWidth() - (bounds.x + bounds.width) -
658                         insets.right;
659             }
660             bounds.y += insets.top;
661         }
662         return bounds;
663     }
664 
665     /**
666       * Returns the path for passed in row.  If row is not visible
667       * null is returned.
668       */
getPathForRow(JTree tree, int row)669     public TreePath getPathForRow(JTree tree, int row) {
670         return (treeState != null) ? treeState.getPathForRow(row) : null;
671     }
672 
673     /**
674       * Returns the row that the last item identified in path is visible
675       * at.  Will return -1 if any of the elements in path are not
676       * currently visible.
677       */
getRowForPath(JTree tree, TreePath path)678     public int getRowForPath(JTree tree, TreePath path) {
679         return (treeState != null) ? treeState.getRowForPath(path) : -1;
680     }
681 
682     /**
683       * Returns the number of rows that are being displayed.
684       */
getRowCount(JTree tree)685     public int getRowCount(JTree tree) {
686         return (treeState != null) ? treeState.getRowCount() : 0;
687     }
688 
689     /**
690       * Returns the path to the node that is closest to x,y.  If
691       * there is nothing currently visible this will return null, otherwise
692       * it'll always return a valid path.  If you need to test if the
693       * returned object is exactly at x, y you should get the bounds for
694       * the returned path and test x, y against that.
695       */
getClosestPathForLocation(JTree tree, int x, int y)696     public TreePath getClosestPathForLocation(JTree tree, int x, int y) {
697         if(tree != null && treeState != null) {
698             // TreeState doesn't care about the x location, hence it isn't
699             // adjusted
700             y -= tree.getInsets().top;
701             return treeState.getPathClosestTo(x, y);
702         }
703         return null;
704     }
705 
706     /**
707       * Returns true if the tree is being edited.  The item that is being
708       * edited can be returned by getEditingPath().
709       */
isEditing(JTree tree)710     public boolean isEditing(JTree tree) {
711         return (editingComponent != null);
712     }
713 
714     /**
715       * Stops the current editing session.  This has no effect if the
716       * tree isn't being edited.  Returns true if the editor allows the
717       * editing session to stop.
718       */
stopEditing(JTree tree)719     public boolean stopEditing(JTree tree) {
720         if(editingComponent != null && cellEditor.stopCellEditing()) {
721             completeEditing(false, false, true);
722             return true;
723         }
724         return false;
725     }
726 
727     /**
728       * Cancels the current editing session.
729       */
cancelEditing(JTree tree)730     public void cancelEditing(JTree tree) {
731         if(editingComponent != null) {
732             completeEditing(false, true, false);
733         }
734     }
735 
736     /**
737       * Selects the last item in path and tries to edit it.  Editing will
738       * fail if the CellEditor won't allow it for the selected item.
739       */
startEditingAtPath(JTree tree, TreePath path)740     public void startEditingAtPath(JTree tree, TreePath path) {
741         tree.scrollPathToVisible(path);
742         if(path != null && tree.isVisible(path))
743             startEditing(path, null);
744     }
745 
746     /**
747      * Returns the path to the element that is being edited.
748      */
getEditingPath(JTree tree)749     public TreePath getEditingPath(JTree tree) {
750         return editingPath;
751     }
752 
753     //
754     // Install methods
755     //
756 
installUI(JComponent c)757     public void installUI(JComponent c) {
758         if ( c == null ) {
759             throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" );
760         }
761 
762         tree = (JTree)c;
763 
764         prepareForUIInstall();
765 
766         // Boilerplate install block
767         installDefaults();
768         installKeyboardActions();
769         installComponents();
770         installListeners();
771 
772         completeUIInstall();
773     }
774 
775     /**
776      * Invoked after the {@code tree} instance variable has been
777      * set, but before any defaults/listeners have been installed.
778      */
prepareForUIInstall()779     protected void prepareForUIInstall() {
780         drawingCache = new Hashtable<TreePath,Boolean>(7);
781 
782         // Data member initializations
783         leftToRight = BasicGraphicsUtils.isLeftToRight(tree);
784         stopEditingInCompleteEditing = true;
785         lastSelectedRow = -1;
786         leadRow = -1;
787         preferredSize = new Dimension();
788 
789         largeModel = tree.isLargeModel();
790         if(getRowHeight() <= 0)
791             largeModel = false;
792         setModel(tree.getModel());
793     }
794 
795     /**
796      * Invoked from installUI after all the defaults/listeners have been
797      * installed.
798      */
completeUIInstall()799     protected void completeUIInstall() {
800         // Custom install code
801 
802         this.setShowsRootHandles(tree.getShowsRootHandles());
803 
804         updateRenderer();
805 
806         updateDepthOffset();
807 
808         setSelectionModel(tree.getSelectionModel());
809 
810         // Create, if necessary, the TreeState instance.
811         treeState = createLayoutCache();
812         configureLayoutCache();
813 
814         updateSize();
815     }
816 
817     /**
818      * Installs default properties.
819      */
installDefaults()820     protected void installDefaults() {
821         if(tree.getBackground() == null ||
822            tree.getBackground() instanceof UIResource) {
823             tree.setBackground(UIManager.getColor("Tree.background"));
824         }
825         if(getHashColor() == null || getHashColor() instanceof UIResource) {
826             setHashColor(UIManager.getColor("Tree.hash"));
827         }
828         if (tree.getFont() == null || tree.getFont() instanceof UIResource)
829             tree.setFont( UIManager.getFont("Tree.font") );
830         // JTree's original row height is 16.  To correctly display the
831         // contents on Linux we should have set it to 18, Windows 19 and
832         // Solaris 20.  As these values vary so much it's too hard to
833         // be backward compatable and try to update the row height, we're
834         // therefor NOT going to adjust the row height based on font.  If the
835         // developer changes the font, it's there responsibility to update
836         // the row height.
837 
838         setExpandedIcon( (Icon)UIManager.get( "Tree.expandedIcon" ) );
839         setCollapsedIcon( (Icon)UIManager.get( "Tree.collapsedIcon" ) );
840 
841         setLeftChildIndent(((Integer)UIManager.get("Tree.leftChildIndent")).
842                            intValue());
843         setRightChildIndent(((Integer)UIManager.get("Tree.rightChildIndent")).
844                            intValue());
845 
846         LookAndFeel.installProperty(tree, "rowHeight",
847                                     UIManager.get("Tree.rowHeight"));
848 
849         largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0);
850 
851         Object scrollsOnExpand = UIManager.get("Tree.scrollsOnExpand");
852         if (scrollsOnExpand != null) {
853             LookAndFeel.installProperty(tree, "scrollsOnExpand", scrollsOnExpand);
854         }
855 
856         paintLines = UIManager.getBoolean("Tree.paintLines");
857         lineTypeDashed = UIManager.getBoolean("Tree.lineTypeDashed");
858 
859         Long l = (Long)UIManager.get("Tree.timeFactor");
860         timeFactor = (l!=null) ? l.longValue() : 1000L;
861 
862         Object showsRootHandles = UIManager.get("Tree.showsRootHandles");
863         if (showsRootHandles != null) {
864             LookAndFeel.installProperty(tree,
865                     JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles);
866         }
867     }
868 
869     /**
870      * Registers listeners.
871      */
installListeners()872     protected void installListeners() {
873         if ( (propertyChangeListener = createPropertyChangeListener())
874              != null ) {
875             tree.addPropertyChangeListener(propertyChangeListener);
876         }
877         if ( (mouseListener = createMouseListener()) != null ) {
878             tree.addMouseListener(mouseListener);
879             if (mouseListener instanceof MouseMotionListener) {
880                 tree.addMouseMotionListener((MouseMotionListener)mouseListener);
881             }
882         }
883         if ((focusListener = createFocusListener()) != null ) {
884             tree.addFocusListener(focusListener);
885         }
886         if ((keyListener = createKeyListener()) != null) {
887             tree.addKeyListener(keyListener);
888         }
889         if((treeExpansionListener = createTreeExpansionListener()) != null) {
890             tree.addTreeExpansionListener(treeExpansionListener);
891         }
892         if((treeModelListener = createTreeModelListener()) != null &&
893            treeModel != null) {
894             treeModel.addTreeModelListener(treeModelListener);
895         }
896         if((selectionModelPropertyChangeListener =
897             createSelectionModelPropertyChangeListener()) != null &&
898            treeSelectionModel != null) {
899             treeSelectionModel.addPropertyChangeListener
900                 (selectionModelPropertyChangeListener);
901         }
902         if((treeSelectionListener = createTreeSelectionListener()) != null &&
903            treeSelectionModel != null) {
904             treeSelectionModel.addTreeSelectionListener(treeSelectionListener);
905         }
906 
907         TransferHandler th = tree.getTransferHandler();
908         if (th == null || th instanceof UIResource) {
909             tree.setTransferHandler(defaultTransferHandler);
910             // default TransferHandler doesn't support drop
911             // so we don't want drop handling
912             if (tree.getDropTarget() instanceof UIResource) {
913                 tree.setDropTarget(null);
914             }
915         }
916 
917         LookAndFeel.installProperty(tree, "opaque", Boolean.TRUE);
918     }
919 
920     /**
921      * Registers keyboard actions.
922      */
installKeyboardActions()923     protected void installKeyboardActions() {
924         InputMap km = getInputMap(JComponent.
925                                   WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
926 
927         SwingUtilities.replaceUIInputMap(tree, JComponent.
928                                          WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
929                                          km);
930         km = getInputMap(JComponent.WHEN_FOCUSED);
931         SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, km);
932 
933         LazyActionMap.installLazyActionMap(tree, BasicTreeUI.class,
934                                            "Tree.actionMap");
935     }
936 
getInputMap(int condition)937     InputMap getInputMap(int condition) {
938         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
939             return (InputMap)DefaultLookup.get(tree, this,
940                                                "Tree.ancestorInputMap");
941         }
942         else if (condition == JComponent.WHEN_FOCUSED) {
943             InputMap keyMap = (InputMap)DefaultLookup.get(tree, this,
944                                                       "Tree.focusInputMap");
945             InputMap rtlKeyMap;
946 
947             if (tree.getComponentOrientation().isLeftToRight() ||
948                   ((rtlKeyMap = (InputMap)DefaultLookup.get(tree, this,
949                   "Tree.focusInputMap.RightToLeft")) == null)) {
950                 return keyMap;
951             } else {
952                 rtlKeyMap.setParent(keyMap);
953                 return rtlKeyMap;
954             }
955         }
956         return null;
957     }
958 
959     /**
960      * Intalls the subcomponents of the tree, which is the renderer pane.
961      */
installComponents()962     protected void installComponents() {
963         if ((rendererPane = createCellRendererPane()) != null) {
964             tree.add( rendererPane );
965         }
966     }
967 
968     //
969     // Create methods.
970     //
971 
972     /**
973      * Creates an instance of {@code NodeDimensions} that is able to determine
974      * the size of a given node in the tree.
975      *
976      * @return an instance of {@code NodeDimensions}
977      */
createNodeDimensions()978     protected AbstractLayoutCache.NodeDimensions createNodeDimensions() {
979         return new NodeDimensionsHandler();
980     }
981 
982     /**
983      * Creates a listener that is responsible that updates the UI based on
984      * how the tree changes.
985      *
986      * @return an instance of the {@code PropertyChangeListener}
987      */
createPropertyChangeListener()988     protected PropertyChangeListener createPropertyChangeListener() {
989         return getHandler();
990     }
991 
getHandler()992     private Handler getHandler() {
993         if (handler == null) {
994             handler = new Handler();
995         }
996         return handler;
997     }
998 
999     /**
1000      * Creates the listener responsible for updating the selection based on
1001      * mouse events.
1002      *
1003      * @return an instance of the {@code MouseListener}
1004      */
createMouseListener()1005     protected MouseListener createMouseListener() {
1006         return getHandler();
1007     }
1008 
1009     /**
1010      * Creates a listener that is responsible for updating the display
1011      * when focus is lost/gained.
1012      *
1013      * @return an instance of the {@code FocusListener}
1014      */
createFocusListener()1015     protected FocusListener createFocusListener() {
1016         return getHandler();
1017     }
1018 
1019     /**
1020      * Creates the listener responsible for getting key events from
1021      * the tree.
1022      *
1023      * @return an instance of the {@code KeyListener}
1024      */
createKeyListener()1025     protected KeyListener createKeyListener() {
1026         return getHandler();
1027     }
1028 
1029     /**
1030      * Creates the listener responsible for getting property change
1031      * events from the selection model.
1032      *
1033      * @return an instance of the {@code PropertyChangeListener}
1034      */
createSelectionModelPropertyChangeListener()1035     protected PropertyChangeListener createSelectionModelPropertyChangeListener() {
1036         return getHandler();
1037     }
1038 
1039     /**
1040      * Creates the listener that updates the display based on selection change
1041      * methods.
1042      *
1043      * @return an instance of the {@code TreeSelectionListener}
1044      */
createTreeSelectionListener()1045     protected TreeSelectionListener createTreeSelectionListener() {
1046         return getHandler();
1047     }
1048 
1049     /**
1050      * Creates a listener to handle events from the current editor.
1051      *
1052      * @return an instance of the {@code CellEditorListener}
1053      */
createCellEditorListener()1054     protected CellEditorListener createCellEditorListener() {
1055         return getHandler();
1056     }
1057 
1058     /**
1059      * Creates and returns a new ComponentHandler. This is used for
1060      * the large model to mark the validCachedPreferredSize as invalid
1061      * when the component moves.
1062      *
1063      * @return an instance of the {@code ComponentListener}
1064      */
createComponentListener()1065     protected ComponentListener createComponentListener() {
1066         return new ComponentHandler();
1067     }
1068 
1069     /**
1070      * Creates and returns the object responsible for updating the treestate
1071      * when nodes expanded state changes.
1072      *
1073      * @return an instance of the {@code TreeExpansionListener}
1074      */
createTreeExpansionListener()1075     protected TreeExpansionListener createTreeExpansionListener() {
1076         return getHandler();
1077     }
1078 
1079     /**
1080      * Creates the object responsible for managing what is expanded, as
1081      * well as the size of nodes.
1082      *
1083      * @return the object responsible for managing what is expanded
1084      */
createLayoutCache()1085     protected AbstractLayoutCache createLayoutCache() {
1086         if(isLargeModel() && getRowHeight() > 0) {
1087             return new FixedHeightLayoutCache();
1088         }
1089         return new VariableHeightLayoutCache();
1090     }
1091 
1092     /**
1093      * Returns the renderer pane that renderer components are placed in.
1094      *
1095      * @return an instance of the {@code CellRendererPane}
1096      */
createCellRendererPane()1097     protected CellRendererPane createCellRendererPane() {
1098         return new CellRendererPane();
1099     }
1100 
1101     /**
1102      * Creates a default cell editor.
1103      *
1104      * @return a default cell editor
1105      */
createDefaultCellEditor()1106     protected TreeCellEditor createDefaultCellEditor() {
1107         if(currentCellRenderer != null &&
1108            (currentCellRenderer instanceof DefaultTreeCellRenderer)) {
1109             DefaultTreeCellEditor editor = new DefaultTreeCellEditor
1110                         (tree, (DefaultTreeCellRenderer)currentCellRenderer);
1111 
1112             return editor;
1113         }
1114         return new DefaultTreeCellEditor(tree, null);
1115     }
1116 
1117     /**
1118      * Returns the default cell renderer that is used to do the
1119      * stamping of each node.
1120      *
1121      * @return an instance of {@code TreeCellRenderer}
1122      */
createDefaultCellRenderer()1123     protected TreeCellRenderer createDefaultCellRenderer() {
1124         return new DefaultTreeCellRenderer();
1125     }
1126 
1127     /**
1128      * Returns a listener that can update the tree when the model changes.
1129      *
1130      * @return an instance of the {@code TreeModelListener}.
1131      */
createTreeModelListener()1132     protected TreeModelListener createTreeModelListener() {
1133         return getHandler();
1134     }
1135 
1136     //
1137     // Uninstall methods
1138     //
1139 
uninstallUI(JComponent c)1140     public void uninstallUI(JComponent c) {
1141         completeEditing();
1142 
1143         prepareForUIUninstall();
1144 
1145         uninstallDefaults();
1146         uninstallListeners();
1147         uninstallKeyboardActions();
1148         uninstallComponents();
1149 
1150         completeUIUninstall();
1151     }
1152 
1153     /**
1154      * Invoked before unstallation of UI.
1155      */
prepareForUIUninstall()1156     protected void prepareForUIUninstall() {
1157     }
1158 
1159     /**
1160      * Uninstalls UI.
1161      */
completeUIUninstall()1162     protected void completeUIUninstall() {
1163         if(createdRenderer) {
1164             tree.setCellRenderer(null);
1165         }
1166         if(createdCellEditor) {
1167             tree.setCellEditor(null);
1168         }
1169         cellEditor = null;
1170         currentCellRenderer = null;
1171         rendererPane = null;
1172         componentListener = null;
1173         propertyChangeListener = null;
1174         mouseListener = null;
1175         focusListener = null;
1176         keyListener = null;
1177         setSelectionModel(null);
1178         treeState = null;
1179         drawingCache = null;
1180         selectionModelPropertyChangeListener = null;
1181         tree = null;
1182         treeModel = null;
1183         treeSelectionModel = null;
1184         treeSelectionListener = null;
1185         treeExpansionListener = null;
1186     }
1187 
1188     /**
1189      * Uninstalls default properties.
1190      */
uninstallDefaults()1191     protected void uninstallDefaults() {
1192         if (tree.getTransferHandler() instanceof UIResource) {
1193             tree.setTransferHandler(null);
1194         }
1195     }
1196 
1197     /**
1198      * Unregisters listeners.
1199      */
uninstallListeners()1200     protected void uninstallListeners() {
1201         if(componentListener != null) {
1202             tree.removeComponentListener(componentListener);
1203         }
1204         if (propertyChangeListener != null) {
1205             tree.removePropertyChangeListener(propertyChangeListener);
1206         }
1207         if (mouseListener != null) {
1208             tree.removeMouseListener(mouseListener);
1209             if (mouseListener instanceof MouseMotionListener) {
1210                 tree.removeMouseMotionListener((MouseMotionListener)mouseListener);
1211             }
1212         }
1213         if (focusListener != null) {
1214             tree.removeFocusListener(focusListener);
1215         }
1216         if (keyListener != null) {
1217             tree.removeKeyListener(keyListener);
1218         }
1219         if(treeExpansionListener != null) {
1220             tree.removeTreeExpansionListener(treeExpansionListener);
1221         }
1222         if(treeModel != null && treeModelListener != null) {
1223             treeModel.removeTreeModelListener(treeModelListener);
1224         }
1225         if(selectionModelPropertyChangeListener != null &&
1226            treeSelectionModel != null) {
1227             treeSelectionModel.removePropertyChangeListener
1228                 (selectionModelPropertyChangeListener);
1229         }
1230         if(treeSelectionListener != null && treeSelectionModel != null) {
1231             treeSelectionModel.removeTreeSelectionListener
1232                                (treeSelectionListener);
1233         }
1234         handler = null;
1235     }
1236 
1237     /**
1238      * Unregisters keyboard actions.
1239      */
uninstallKeyboardActions()1240     protected void uninstallKeyboardActions() {
1241         SwingUtilities.replaceUIActionMap(tree, null);
1242         SwingUtilities.replaceUIInputMap(tree, JComponent.
1243                                          WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1244                                          null);
1245         SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, null);
1246     }
1247 
1248     /**
1249      * Uninstalls the renderer pane.
1250      */
uninstallComponents()1251     protected void uninstallComponents() {
1252         if(rendererPane != null) {
1253             tree.remove(rendererPane);
1254         }
1255     }
1256 
1257     /**
1258      * Recomputes the right margin, and invalidates any tree states
1259      */
redoTheLayout()1260     private void redoTheLayout() {
1261         if (treeState != null) {
1262             treeState.invalidateSizes();
1263         }
1264     }
1265 
1266     /**
1267      * Returns the baseline.
1268      *
1269      * @throws NullPointerException {@inheritDoc}
1270      * @throws IllegalArgumentException {@inheritDoc}
1271      * @see javax.swing.JComponent#getBaseline(int, int)
1272      * @since 1.6
1273      */
getBaseline(JComponent c, int width, int height)1274     public int getBaseline(JComponent c, int width, int height) {
1275         super.getBaseline(c, width, height);
1276         UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
1277         Component renderer = (Component)lafDefaults.get(
1278                 BASELINE_COMPONENT_KEY);
1279         if (renderer == null) {
1280             TreeCellRenderer tcr = createDefaultCellRenderer();
1281             renderer = tcr.getTreeCellRendererComponent(
1282                     tree, "a", false, false, false, -1, false);
1283             lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
1284         }
1285         int rowHeight = tree.getRowHeight();
1286         int baseline;
1287         if (rowHeight > 0) {
1288             baseline = renderer.getBaseline(Integer.MAX_VALUE, rowHeight);
1289         }
1290         else {
1291             Dimension pref = renderer.getPreferredSize();
1292             baseline = renderer.getBaseline(pref.width, pref.height);
1293         }
1294         return baseline + tree.getInsets().top;
1295     }
1296 
1297     /**
1298      * Returns an enum indicating how the baseline of the component
1299      * changes as the size changes.
1300      *
1301      * @throws NullPointerException {@inheritDoc}
1302      * @see javax.swing.JComponent#getBaseline(int, int)
1303      * @since 1.6
1304      */
getBaselineResizeBehavior( JComponent c)1305     public Component.BaselineResizeBehavior getBaselineResizeBehavior(
1306             JComponent c) {
1307         super.getBaselineResizeBehavior(c);
1308         return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
1309     }
1310 
1311     //
1312     // Painting routines.
1313     //
1314 
paint(Graphics g, JComponent c)1315     public void paint(Graphics g, JComponent c) {
1316         if (tree != c) {
1317             throw new InternalError("incorrect component");
1318         }
1319 
1320         // Should never happen if installed for a UI
1321         if(treeState == null) {
1322             return;
1323         }
1324 
1325         Rectangle        paintBounds = g.getClipBounds();
1326         Insets           insets = tree.getInsets();
1327         TreePath         initialPath = getClosestPathForLocation
1328                                        (tree, 0, paintBounds.y);
1329         Enumeration<?>   paintingEnumerator = treeState.getVisiblePathsFrom
1330                                               (initialPath);
1331         int              row = treeState.getRowForPath(initialPath);
1332         int              endY = paintBounds.y + paintBounds.height;
1333 
1334         drawingCache.clear();
1335 
1336         if(initialPath != null && paintingEnumerator != null) {
1337             TreePath   parentPath = initialPath;
1338 
1339             // Draw the lines, knobs, and rows
1340 
1341             // Find each parent and have them draw a line to their last child
1342             parentPath = parentPath.getParentPath();
1343             while(parentPath != null) {
1344                 paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
1345                 drawingCache.put(parentPath, Boolean.TRUE);
1346                 parentPath = parentPath.getParentPath();
1347             }
1348 
1349             boolean         done = false;
1350             // Information for the node being rendered.
1351             boolean         isExpanded;
1352             boolean         hasBeenExpanded;
1353             boolean         isLeaf;
1354             Rectangle       boundsBuffer = new Rectangle();
1355             Rectangle       bounds;
1356             TreePath        path;
1357             boolean         rootVisible = isRootVisible();
1358 
1359             while(!done && paintingEnumerator.hasMoreElements()) {
1360                 path = (TreePath)paintingEnumerator.nextElement();
1361                 if(path != null) {
1362                     isLeaf = treeModel.isLeaf(path.getLastPathComponent());
1363                     if(isLeaf)
1364                         isExpanded = hasBeenExpanded = false;
1365                     else {
1366                         isExpanded = treeState.getExpandedState(path);
1367                         hasBeenExpanded = tree.hasBeenExpanded(path);
1368                     }
1369                     bounds = getPathBounds(path, insets, boundsBuffer);
1370                     if(bounds == null)
1371                         // This will only happen if the model changes out
1372                         // from under us (usually in another thread).
1373                         // Swing isn't multithreaded, but I'll put this
1374                         // check in anyway.
1375                         return;
1376                     // See if the vertical line to the parent has been drawn.
1377                     parentPath = path.getParentPath();
1378                     if(parentPath != null) {
1379                         if(drawingCache.get(parentPath) == null) {
1380                             paintVerticalPartOfLeg(g, paintBounds,
1381                                                    insets, parentPath);
1382                             drawingCache.put(parentPath, Boolean.TRUE);
1383                         }
1384                         paintHorizontalPartOfLeg(g, paintBounds, insets,
1385                                                  bounds, path, row,
1386                                                  isExpanded,
1387                                                  hasBeenExpanded, isLeaf);
1388                     }
1389                     else if(rootVisible && row == 0) {
1390                         paintHorizontalPartOfLeg(g, paintBounds, insets,
1391                                                  bounds, path, row,
1392                                                  isExpanded,
1393                                                  hasBeenExpanded, isLeaf);
1394                     }
1395                     if(shouldPaintExpandControl(path, row, isExpanded,
1396                                                 hasBeenExpanded, isLeaf)) {
1397                         paintExpandControl(g, paintBounds, insets, bounds,
1398                                            path, row, isExpanded,
1399                                            hasBeenExpanded, isLeaf);
1400                     }
1401                     paintRow(g, paintBounds, insets, bounds, path,
1402                                  row, isExpanded, hasBeenExpanded, isLeaf);
1403                     if((bounds.y + bounds.height) >= endY)
1404                         done = true;
1405                 }
1406                 else {
1407                     done = true;
1408                 }
1409                 row++;
1410             }
1411         }
1412 
1413         paintDropLine(g);
1414 
1415         // Empty out the renderer pane, allowing renderers to be gc'ed.
1416         rendererPane.removeAll();
1417 
1418         drawingCache.clear();
1419     }
1420 
1421     /**
1422      * Tells if a {@code DropLocation} should be indicated by a line between
1423      * nodes. This is meant for {@code javax.swing.DropMode.INSERT} and
1424      * {@code javax.swing.DropMode.ON_OR_INSERT} drop modes.
1425      *
1426      * @param loc a {@code DropLocation}
1427      * @return {@code true} if the drop location should be shown as a line
1428      * @since 1.7
1429      */
isDropLine(JTree.DropLocation loc)1430     protected boolean isDropLine(JTree.DropLocation loc) {
1431         return loc != null && loc.getPath() != null && loc.getChildIndex() != -1;
1432     }
1433 
1434     /**
1435      * Paints the drop line.
1436      *
1437      * @param g {@code Graphics} object to draw on
1438      * @since 1.7
1439      */
paintDropLine(Graphics g)1440     protected void paintDropLine(Graphics g) {
1441         JTree.DropLocation loc = tree.getDropLocation();
1442         if (!isDropLine(loc)) {
1443             return;
1444         }
1445 
1446         Color c = UIManager.getColor("Tree.dropLineColor");
1447         if (c != null) {
1448             g.setColor(c);
1449             Rectangle rect = getDropLineRect(loc);
1450             g.fillRect(rect.x, rect.y, rect.width, rect.height);
1451         }
1452     }
1453 
1454     /**
1455      * Returns a unbounding box for the drop line.
1456      *
1457      * @param loc a {@code DropLocation}
1458      * @return bounding box for the drop line
1459      * @since 1.7
1460      */
getDropLineRect(JTree.DropLocation loc)1461     protected Rectangle getDropLineRect(JTree.DropLocation loc) {
1462         Rectangle rect;
1463         TreePath path = loc.getPath();
1464         int index = loc.getChildIndex();
1465         boolean ltr = leftToRight;
1466 
1467         Insets insets = tree.getInsets();
1468 
1469         if (tree.getRowCount() == 0) {
1470             rect = new Rectangle(insets.left,
1471                                  insets.top,
1472                                  tree.getWidth() - insets.left - insets.right,
1473                                  0);
1474         } else {
1475             TreeModel model = getModel();
1476             Object root = model.getRoot();
1477 
1478             if (path.getLastPathComponent() == root
1479                     && index >= model.getChildCount(root)) {
1480 
1481                 rect = tree.getRowBounds(tree.getRowCount() - 1);
1482                 rect.y = rect.y + rect.height;
1483                 Rectangle xRect;
1484 
1485                 if (!tree.isRootVisible()) {
1486                     xRect = tree.getRowBounds(0);
1487                 } else if (model.getChildCount(root) == 0){
1488                     xRect = tree.getRowBounds(0);
1489                     xRect.x += totalChildIndent;
1490                     xRect.width -= totalChildIndent + totalChildIndent;
1491                 } else {
1492                     TreePath lastChildPath = path.pathByAddingChild(
1493                         model.getChild(root, model.getChildCount(root) - 1));
1494                     xRect = tree.getPathBounds(lastChildPath);
1495                 }
1496 
1497                 rect.x = xRect.x;
1498                 rect.width = xRect.width;
1499             } else {
1500                 if (index >= model.getChildCount(path.getLastPathComponent())) {
1501                     rect = tree.getPathBounds(path.pathByAddingChild(
1502                             model.getChild(path.getLastPathComponent(),
1503                                     index - 1)));
1504                     rect.y = rect.y + rect.height;
1505                 } else {
1506                     rect = tree.getPathBounds(path.pathByAddingChild(
1507                             model.getChild(path.getLastPathComponent(),
1508                                     index)));
1509                 }
1510             }
1511         }
1512 
1513         if (rect.y != 0) {
1514             rect.y--;
1515         }
1516 
1517         if (!ltr) {
1518             rect.x = rect.x + rect.width - 100;
1519         }
1520 
1521         rect.width = 100;
1522         rect.height = 2;
1523 
1524         return rect;
1525     }
1526 
1527     /**
1528      * Paints the horizontal part of the leg. The receiver should
1529      * NOT modify {@code clipBounds}, or {@code insets}.<p>
1530      * NOTE: {@code parentRow} can be -1 if the root is not visible.
1531      *
1532      * @param g a graphics context
1533      * @param clipBounds a clipped rectangle
1534      * @param insets insets
1535      * @param bounds a bounding rectangle
1536      * @param path a tree path
1537      * @param row a row
1538      * @param isExpanded {@code true} if the path is expanded
1539      * @param hasBeenExpanded {@code true} if the path has been expanded
1540      * @param isLeaf {@code true} if the path is leaf
1541      */
paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)1542     protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
1543                                             Insets insets, Rectangle bounds,
1544                                             TreePath path, int row,
1545                                             boolean isExpanded,
1546                                             boolean hasBeenExpanded, boolean
1547                                             isLeaf) {
1548         if (!paintLines) {
1549             return;
1550         }
1551 
1552         // Don't paint the legs for the root'ish node if the
1553         int depth = path.getPathCount() - 1;
1554         if((depth == 0 || (depth == 1 && !isRootVisible())) &&
1555            !getShowsRootHandles()) {
1556             return;
1557         }
1558 
1559         int clipLeft = clipBounds.x;
1560         int clipRight = clipBounds.x + clipBounds.width;
1561         int clipTop = clipBounds.y;
1562         int clipBottom = clipBounds.y + clipBounds.height;
1563         int lineY = bounds.y + bounds.height / 2;
1564 
1565         if (leftToRight) {
1566             int leftX = bounds.x - getRightChildIndent();
1567             int nodeX = bounds.x - getHorizontalLegBuffer();
1568 
1569             if(lineY >= clipTop
1570                     && lineY < clipBottom
1571                     && nodeX >= clipLeft
1572                     && leftX < clipRight
1573                     && leftX < nodeX) {
1574 
1575                 g.setColor(getHashColor());
1576                 paintHorizontalLine(g, tree, lineY, leftX, nodeX - 1);
1577             }
1578         } else {
1579             int nodeX = bounds.x + bounds.width + getHorizontalLegBuffer();
1580             int rightX = bounds.x + bounds.width + getRightChildIndent();
1581 
1582             if(lineY >= clipTop
1583                     && lineY < clipBottom
1584                     && rightX >= clipLeft
1585                     && nodeX < clipRight
1586                     && nodeX < rightX) {
1587 
1588                 g.setColor(getHashColor());
1589                 paintHorizontalLine(g, tree, lineY, nodeX, rightX - 1);
1590             }
1591         }
1592     }
1593 
1594     /**
1595      * Paints the vertical part of the leg. The receiver should
1596      * NOT modify {@code clipBounds}, {@code insets}.
1597      *
1598      * @param g a graphics context
1599      * @param clipBounds a clipped rectangle
1600      * @param insets insets
1601      * @param path a tree path
1602      */
paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path)1603     protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
1604                                           Insets insets, TreePath path) {
1605         if (!paintLines) {
1606             return;
1607         }
1608 
1609         int depth = path.getPathCount() - 1;
1610         if (depth == 0 && !getShowsRootHandles() && !isRootVisible()) {
1611             return;
1612         }
1613         int lineX = getRowX(-1, depth + 1);
1614         if (leftToRight) {
1615             lineX = lineX - getRightChildIndent() + insets.left;
1616         }
1617         else {
1618             lineX = tree.getWidth() - lineX - insets.right +
1619                     getRightChildIndent() - 1;
1620         }
1621         int clipLeft = clipBounds.x;
1622         int clipRight = clipBounds.x + (clipBounds.width - 1);
1623 
1624         if (lineX >= clipLeft && lineX <= clipRight) {
1625             int clipTop = clipBounds.y;
1626             int clipBottom = clipBounds.y + clipBounds.height;
1627             Rectangle parentBounds = getPathBounds(tree, path);
1628             Rectangle lastChildBounds = getPathBounds(tree,
1629                                                      getLastChildPath(path));
1630 
1631             if(lastChildBounds == null)
1632                 // This shouldn't happen, but if the model is modified
1633                 // in another thread it is possible for this to happen.
1634                 // Swing isn't multithreaded, but I'll add this check in
1635                 // anyway.
1636                 return;
1637 
1638             int       top;
1639 
1640             if(parentBounds == null) {
1641                 top = Math.max(insets.top + getVerticalLegBuffer(),
1642                                clipTop);
1643             }
1644             else
1645                 top = Math.max(parentBounds.y + parentBounds.height +
1646                                getVerticalLegBuffer(), clipTop);
1647             if(depth == 0 && !isRootVisible()) {
1648                 TreeModel      model = getModel();
1649 
1650                 if(model != null) {
1651                     Object        root = model.getRoot();
1652 
1653                     if(model.getChildCount(root) > 0) {
1654                         parentBounds = getPathBounds(tree, path.
1655                                   pathByAddingChild(model.getChild(root, 0)));
1656                         if(parentBounds != null)
1657                             top = Math.max(insets.top + getVerticalLegBuffer(),
1658                                            parentBounds.y +
1659                                            parentBounds.height / 2);
1660                     }
1661                 }
1662             }
1663 
1664             int bottom = Math.min(lastChildBounds.y +
1665                                   (lastChildBounds.height / 2), clipBottom);
1666 
1667             if (top <= bottom) {
1668                 g.setColor(getHashColor());
1669                 paintVerticalLine(g, tree, lineX, top, bottom);
1670             }
1671         }
1672     }
1673 
1674     /**
1675      * Paints the expand (toggle) part of a row. The receiver should
1676      * NOT modify {@code clipBounds}, or {@code insets}.
1677      *
1678      * @param g a graphics context
1679      * @param clipBounds a clipped rectangle
1680      * @param insets insets
1681      * @param bounds a bounding rectangle
1682      * @param path a tree path
1683      * @param row a row
1684      * @param isExpanded {@code true} if the path is expanded
1685      * @param hasBeenExpanded {@code true} if the path has been expanded
1686      * @param isLeaf {@code true} if the row is leaf
1687      */
paintExpandControl(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)1688     protected void paintExpandControl(Graphics g,
1689                                       Rectangle clipBounds, Insets insets,
1690                                       Rectangle bounds, TreePath path,
1691                                       int row, boolean isExpanded,
1692                                       boolean hasBeenExpanded,
1693                                       boolean isLeaf) {
1694         Object       value = path.getLastPathComponent();
1695 
1696         // Draw icons if not a leaf and either hasn't been loaded,
1697         // or the model child count is > 0.
1698         if (!isLeaf && (!hasBeenExpanded ||
1699                         treeModel.getChildCount(value) > 0)) {
1700             int middleXOfKnob;
1701             if (leftToRight) {
1702                 middleXOfKnob = bounds.x - getRightChildIndent() + 1;
1703             } else {
1704                 middleXOfKnob = bounds.x + bounds.width + getRightChildIndent() - 1;
1705             }
1706             int middleYOfKnob = bounds.y + (bounds.height / 2);
1707 
1708             if (isExpanded) {
1709                 Icon expandedIcon = getExpandedIcon();
1710                 if(expandedIcon != null)
1711                   drawCentered(tree, g, expandedIcon, middleXOfKnob,
1712                                middleYOfKnob );
1713             }
1714             else {
1715                 Icon collapsedIcon = getCollapsedIcon();
1716                 if(collapsedIcon != null)
1717                   drawCentered(tree, g, collapsedIcon, middleXOfKnob,
1718                                middleYOfKnob);
1719             }
1720         }
1721     }
1722 
1723     /**
1724      * Paints the renderer part of a row. The receiver should
1725      * NOT modify {@code clipBounds}, or {@code insets}.
1726      *
1727      * @param g a graphics context
1728      * @param clipBounds a clipped rectangle
1729      * @param insets insets
1730      * @param bounds a bounding rectangle
1731      * @param path a tree path
1732      * @param row a row
1733      * @param isExpanded {@code true} if the path is expanded
1734      * @param hasBeenExpanded {@code true} if the path has been expanded
1735      * @param isLeaf {@code true} if the path is leaf
1736      */
paintRow(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)1737     protected void paintRow(Graphics g, Rectangle clipBounds,
1738                             Insets insets, Rectangle bounds, TreePath path,
1739                             int row, boolean isExpanded,
1740                             boolean hasBeenExpanded, boolean isLeaf) {
1741         // Don't paint the renderer if editing this row.
1742         if(editingComponent != null && editingRow == row)
1743             return;
1744 
1745         int leadIndex;
1746 
1747         if(tree.hasFocus()) {
1748             leadIndex = getLeadSelectionRow();
1749         }
1750         else
1751             leadIndex = -1;
1752 
1753         Component component;
1754 
1755         component = currentCellRenderer.getTreeCellRendererComponent
1756                       (tree, path.getLastPathComponent(),
1757                        tree.isRowSelected(row), isExpanded, isLeaf, row,
1758                        (leadIndex == row));
1759 
1760         rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
1761                                     bounds.width, bounds.height, true);
1762     }
1763 
1764     /**
1765      * Returns {@code true} if the expand (toggle) control should be drawn for
1766      * the specified row.
1767      *
1768      * @param path a tree path
1769      * @param row a row
1770      * @param isExpanded {@code true} if the path is expanded
1771      * @param hasBeenExpanded {@code true} if the path has been expanded
1772      * @param isLeaf {@code true} if the row is leaf
1773      * @return {@code true} if the expand (toggle) control should be drawn
1774      *         for the specified row
1775      */
shouldPaintExpandControl(TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)1776     protected boolean shouldPaintExpandControl(TreePath path, int row,
1777                                                boolean isExpanded,
1778                                                boolean hasBeenExpanded,
1779                                                boolean isLeaf) {
1780         if(isLeaf)
1781             return false;
1782 
1783         int              depth = path.getPathCount() - 1;
1784 
1785         if((depth == 0 || (depth == 1 && !isRootVisible())) &&
1786            !getShowsRootHandles())
1787             return false;
1788         return true;
1789     }
1790 
1791     /**
1792      * Paints a vertical line.
1793      *
1794      * @param g a graphics context
1795      * @param c a component
1796      * @param x an X coordinate
1797      * @param top an Y1 coordinate
1798      * @param bottom an Y2 coordinate
1799      */
paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom)1800     protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
1801                                     int bottom) {
1802         if (lineTypeDashed) {
1803             drawDashedVerticalLine(g, x, top, bottom);
1804         } else {
1805             g.drawLine(x, top, x, bottom);
1806         }
1807     }
1808 
1809     /**
1810      * Paints a horizontal line.
1811      *
1812      * @param g a graphics context
1813      * @param c a component
1814      * @param y an Y coordinate
1815      * @param left an X1 coordinate
1816      * @param right an X2 coordinate
1817      */
paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right)1818     protected void paintHorizontalLine(Graphics g, JComponent c, int y,
1819                                       int left, int right) {
1820         if (lineTypeDashed) {
1821             drawDashedHorizontalLine(g, y, left, right);
1822         } else {
1823             g.drawLine(left, y, right, y);
1824         }
1825     }
1826 
1827     /**
1828      * The vertical element of legs between nodes starts at the bottom of the
1829      * parent node by default.  This method makes the leg start below that.
1830      *
1831      * @return the vertical leg buffer
1832      */
getVerticalLegBuffer()1833     protected int getVerticalLegBuffer() {
1834         return 0;
1835     }
1836 
1837     /**
1838      * The horizontal element of legs between nodes starts at the
1839      * right of the left-hand side of the child node by default.  This
1840      * method makes the leg end before that.
1841      *
1842      * @return the horizontal leg buffer
1843      */
getHorizontalLegBuffer()1844     protected int getHorizontalLegBuffer() {
1845         return 0;
1846     }
1847 
findCenteredX(int x, int iconWidth)1848     private int findCenteredX(int x, int iconWidth) {
1849         return leftToRight
1850                ? x - (int)Math.ceil(iconWidth / 2.0)
1851                : x - (int)Math.floor(iconWidth / 2.0);
1852     }
1853 
1854     //
1855     // Generic painting methods
1856     //
1857 
1858     /**
1859      * Draws the {@code icon} centered at (x,y).
1860      *
1861      * @param c a component
1862      * @param graphics a graphics context
1863      * @param icon an icon
1864      * @param x an X coordinate
1865      * @param y an Y coordinate
1866      */
drawCentered(Component c, Graphics graphics, Icon icon, int x, int y)1867     protected void drawCentered(Component c, Graphics graphics, Icon icon,
1868                                 int x, int y) {
1869         icon.paintIcon(c, graphics,
1870                       findCenteredX(x, icon.getIconWidth()),
1871                       y - icon.getIconHeight() / 2);
1872     }
1873 
1874     /**
1875      * Draws a horizontal dashed line. It is assumed {@code x1} &lt;= {@code x2}.
1876      * If {@code x1} is greater than {@code x2}, the method draws nothing.
1877      *
1878      * @param g an instance of {@code Graphics}
1879      * @param y an Y coordinate
1880      * @param x1 an X1 coordinate
1881      * @param x2 an X2 coordinate
1882      */
drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)1883     protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2) {
1884         // Drawing only even coordinates helps join line segments so they
1885         // appear as one line.  This can be defeated by translating the
1886         // Graphics by an odd amount.
1887         drawDashedLine(g, y, x1, x2, false);
1888     }
1889 
1890     /**
1891      * Draws a vertical dashed line. It is assumed {@code y1} &lt;= {@code y2}.
1892      * If {@code y1} is greater than {@code y2}, the method draws nothing.
1893      *
1894      * @param g an instance of {@code Graphics}
1895      * @param x an X coordinate
1896      * @param y1 an Y1 coordinate
1897      * @param y2 an Y2 coordinate
1898      */
drawDashedVerticalLine(Graphics g, int x, int y1, int y2)1899     protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) {
1900         // Drawing only even coordinates helps join line segments so they
1901         // appear as one line.  This can be defeated by translating the
1902         // Graphics by an odd amount.
1903         drawDashedLine(g, x, y1, y2, true);
1904     }
1905 
drawDashedLine(Graphics g, int v, int v1, int v2, boolean isVertical)1906     private void drawDashedLine(Graphics g, int v, int v1, int v2, boolean isVertical) {
1907         if (v1 >= v2) {
1908             return;
1909         }
1910         v1 += (v1 % 2);
1911         Graphics2D g2d = (Graphics2D) g;
1912         Stroke oldStroke = g2d.getStroke();
1913 
1914         BasicStroke dashedStroke = new BasicStroke(1, BasicStroke.CAP_BUTT,
1915                 BasicStroke.JOIN_ROUND, 0, new float[]{1}, 0);
1916         g2d.setStroke(dashedStroke);
1917         if (isVertical) {
1918             g2d.drawLine(v, v1, v, v2);
1919         } else {
1920             g2d.drawLine(v1, v, v2, v);
1921         }
1922 
1923         g2d.setStroke(oldStroke);
1924     }
1925     //
1926     // Various local methods
1927     //
1928 
1929     /**
1930      * Returns the location, along the x-axis, to render a particular row
1931      * at. The return value does not include any Insets specified on the JTree.
1932      * This does not check for the validity of the row or depth, it is assumed
1933      * to be correct and will not throw an Exception if the row or depth
1934      * doesn't match that of the tree.
1935      *
1936      * @param row Row to return x location for
1937      * @param depth Depth of the row
1938      * @return amount to indent the given row.
1939      * @since 1.5
1940      */
getRowX(int row, int depth)1941     protected int getRowX(int row, int depth) {
1942         return totalChildIndent * (depth + depthOffset);
1943     }
1944 
1945     /**
1946      * Makes all the nodes that are expanded in JTree expanded in LayoutCache.
1947      * This invokes updateExpandedDescendants with the root path.
1948      */
updateLayoutCacheExpandedNodes()1949     protected void updateLayoutCacheExpandedNodes() {
1950         if(treeModel != null && treeModel.getRoot() != null)
1951             updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1952     }
1953 
updateLayoutCacheExpandedNodesIfNecessary()1954     private void updateLayoutCacheExpandedNodesIfNecessary() {
1955         if (treeModel != null && treeModel.getRoot() != null) {
1956             TreePath rootPath = new TreePath(treeModel.getRoot());
1957             if (tree.isExpanded(rootPath)) {
1958                 updateLayoutCacheExpandedNodes();
1959             } else {
1960                 treeState.setExpandedState(rootPath, false);
1961             }
1962         }
1963     }
1964 
1965     /**
1966      * Updates the expanded state of all the descendants of {@code path}
1967      * by getting the expanded descendants from the tree and forwarding
1968      * to the tree state.
1969      *
1970      * @param path a tree path
1971      */
updateExpandedDescendants(TreePath path)1972     protected void updateExpandedDescendants(TreePath path) {
1973         completeEditing();
1974         if(treeState != null) {
1975             treeState.setExpandedState(path, true);
1976 
1977             Enumeration<?> descendants = tree.getExpandedDescendants(path);
1978 
1979             if(descendants != null) {
1980                 while(descendants.hasMoreElements()) {
1981                     path = (TreePath)descendants.nextElement();
1982                     treeState.setExpandedState(path, true);
1983                 }
1984             }
1985             updateLeadSelectionRow();
1986             updateSize();
1987         }
1988     }
1989 
1990     /**
1991      * Returns a path to the last child of {@code parent}.
1992      *
1993      * @param parent a tree path
1994      * @return a path to the last child of {@code parent}
1995      */
getLastChildPath(TreePath parent)1996     protected TreePath getLastChildPath(TreePath parent) {
1997         if(treeModel != null) {
1998             int         childCount = treeModel.getChildCount
1999                 (parent.getLastPathComponent());
2000 
2001             if(childCount > 0)
2002                 return parent.pathByAddingChild(treeModel.getChild
2003                            (parent.getLastPathComponent(), childCount - 1));
2004         }
2005         return null;
2006     }
2007 
2008     /**
2009      * Updates how much each depth should be offset by.
2010      */
updateDepthOffset()2011     protected void updateDepthOffset() {
2012         if(isRootVisible()) {
2013             if(getShowsRootHandles())
2014                 depthOffset = 1;
2015             else
2016                 depthOffset = 0;
2017         }
2018         else if(!getShowsRootHandles())
2019             depthOffset = -1;
2020         else
2021             depthOffset = 0;
2022     }
2023 
2024     /**
2025       * Updates the cellEditor based on the editability of the JTree that
2026       * we're contained in.  If the tree is editable but doesn't have a
2027       * cellEditor, a basic one will be used.
2028       */
updateCellEditor()2029     protected void updateCellEditor() {
2030         TreeCellEditor        newEditor;
2031 
2032         completeEditing();
2033         if(tree == null)
2034             newEditor = null;
2035         else {
2036             if(tree.isEditable()) {
2037                 newEditor = tree.getCellEditor();
2038                 if(newEditor == null) {
2039                     newEditor = createDefaultCellEditor();
2040                     if(newEditor != null) {
2041                         tree.setCellEditor(newEditor);
2042                         createdCellEditor = true;
2043                     }
2044                 }
2045             }
2046             else
2047                 newEditor = null;
2048         }
2049         if(newEditor != cellEditor) {
2050             if(cellEditor != null && cellEditorListener != null)
2051                 cellEditor.removeCellEditorListener(cellEditorListener);
2052             cellEditor = newEditor;
2053             if(cellEditorListener == null)
2054                 cellEditorListener = createCellEditorListener();
2055             if(newEditor != null && cellEditorListener != null)
2056                 newEditor.addCellEditorListener(cellEditorListener);
2057             createdCellEditor = false;
2058         }
2059     }
2060 
2061     /**
2062       * Messaged from the tree we're in when the renderer has changed.
2063       */
updateRenderer()2064     protected void updateRenderer() {
2065         if(tree != null) {
2066             TreeCellRenderer      newCellRenderer;
2067 
2068             newCellRenderer = tree.getCellRenderer();
2069             if(newCellRenderer == null) {
2070                 tree.setCellRenderer(createDefaultCellRenderer());
2071                 createdRenderer = true;
2072             }
2073             else {
2074                 createdRenderer = false;
2075                 currentCellRenderer = newCellRenderer;
2076                 if(createdCellEditor) {
2077                     tree.setCellEditor(null);
2078                 }
2079             }
2080         }
2081         else {
2082             createdRenderer = false;
2083             currentCellRenderer = null;
2084         }
2085         updateCellEditor();
2086     }
2087 
2088     /**
2089      * Resets the TreeState instance based on the tree we're providing the
2090      * look and feel for.
2091      */
configureLayoutCache()2092     protected void configureLayoutCache() {
2093         if(treeState != null && tree != null) {
2094             if(nodeDimensions == null)
2095                 nodeDimensions = createNodeDimensions();
2096             treeState.setNodeDimensions(nodeDimensions);
2097             treeState.setRootVisible(tree.isRootVisible());
2098             treeState.setRowHeight(tree.getRowHeight());
2099             treeState.setSelectionModel(getSelectionModel());
2100             // Only do this if necessary, may loss state if call with
2101             // same model as it currently has.
2102             if(treeState.getModel() != tree.getModel())
2103                 treeState.setModel(tree.getModel());
2104             updateLayoutCacheExpandedNodesIfNecessary();
2105             // Create a listener to update preferred size when bounds
2106             // changes, if necessary.
2107             if(isLargeModel()) {
2108                 if(componentListener == null) {
2109                     componentListener = createComponentListener();
2110                     if(componentListener != null)
2111                         tree.addComponentListener(componentListener);
2112                 }
2113             }
2114             else if(componentListener != null) {
2115                 tree.removeComponentListener(componentListener);
2116                 componentListener = null;
2117             }
2118         }
2119         else if(componentListener != null) {
2120             tree.removeComponentListener(componentListener);
2121             componentListener = null;
2122         }
2123     }
2124 
2125     /**
2126      * Marks the cached size as being invalid, and messages the
2127      * tree with <code>treeDidChange</code>.
2128      */
updateSize()2129     protected void updateSize() {
2130         validCachedPreferredSize = false;
2131         tree.treeDidChange();
2132     }
2133 
updateSize0()2134     private void updateSize0() {
2135         validCachedPreferredSize = false;
2136         tree.revalidate();
2137     }
2138 
2139     /**
2140      * Updates the <code>preferredSize</code> instance variable,
2141      * which is returned from <code>getPreferredSize()</code>.<p>
2142      * For left to right orientations, the size is determined from the
2143      * current AbstractLayoutCache. For RTL orientations, the preferred size
2144      * becomes the width minus the minimum x position.
2145      */
updateCachedPreferredSize()2146     protected void updateCachedPreferredSize() {
2147         if(treeState != null) {
2148             Insets               i = tree.getInsets();
2149 
2150             if(isLargeModel()) {
2151                 Rectangle            visRect = tree.getVisibleRect();
2152 
2153                 if (visRect.x == 0 && visRect.y == 0 &&
2154                         visRect.width == 0 && visRect.height == 0 &&
2155                         tree.getVisibleRowCount() > 0) {
2156                     // The tree doesn't have a valid bounds yet. Calculate
2157                     // based on visible row count.
2158                     visRect.width = 1;
2159                     visRect.height = tree.getRowHeight() *
2160                             tree.getVisibleRowCount();
2161                 } else {
2162                     visRect.x -= i.left;
2163                     visRect.y -= i.top;
2164                 }
2165                 // we should consider a non-visible area above
2166                 Component component = SwingUtilities.getUnwrappedParent(tree);
2167                 if (component instanceof JViewport) {
2168                     component = component.getParent();
2169                     if (component instanceof JScrollPane) {
2170                         JScrollPane pane = (JScrollPane) component;
2171                         JScrollBar bar = pane.getHorizontalScrollBar();
2172                         if ((bar != null) && bar.isVisible()) {
2173                             int height = bar.getHeight();
2174                             visRect.y -= height;
2175                             visRect.height += height;
2176                         }
2177                     }
2178                 }
2179                 preferredSize.width = treeState.getPreferredWidth(visRect);
2180             }
2181             else {
2182                 preferredSize.width = treeState.getPreferredWidth(null);
2183             }
2184             preferredSize.height = treeState.getPreferredHeight();
2185             preferredSize.width += i.left + i.right;
2186             preferredSize.height += i.top + i.bottom;
2187         }
2188         validCachedPreferredSize = true;
2189     }
2190 
2191     /**
2192      * Messaged from the {@code VisibleTreeNode} after it has been expanded.
2193      *
2194      * @param path a tree path
2195      */
pathWasExpanded(TreePath path)2196     protected void pathWasExpanded(TreePath path) {
2197         if(tree != null) {
2198             tree.fireTreeExpanded(path);
2199         }
2200     }
2201 
2202     /**
2203      * Messaged from the {@code VisibleTreeNode} after it has collapsed.
2204      *
2205      * @param path a tree path
2206      */
pathWasCollapsed(TreePath path)2207     protected void pathWasCollapsed(TreePath path) {
2208         if(tree != null) {
2209             tree.fireTreeCollapsed(path);
2210         }
2211     }
2212 
2213     /**
2214      * Ensures that the rows identified by {@code beginRow} through
2215      * {@code endRow} are visible.
2216      *
2217      * @param beginRow the begin row
2218      * @param endRow the end row
2219      */
ensureRowsAreVisible(int beginRow, int endRow)2220     protected void ensureRowsAreVisible(int beginRow, int endRow) {
2221         if(tree != null && beginRow >= 0 && endRow < getRowCount(tree)) {
2222             boolean scrollVert = DefaultLookup.getBoolean(tree, this,
2223                               "Tree.scrollsHorizontallyAndVertically", false);
2224             if(beginRow == endRow) {
2225                 Rectangle     scrollBounds = getPathBounds(tree, getPathForRow
2226                                                            (tree, beginRow));
2227 
2228                 if(scrollBounds != null) {
2229                     if (!scrollVert) {
2230                         scrollBounds.x = tree.getVisibleRect().x;
2231                         scrollBounds.width = 1;
2232                     }
2233                     tree.scrollRectToVisible(scrollBounds);
2234                 }
2235             }
2236             else {
2237                 Rectangle   beginRect = getPathBounds(tree, getPathForRow
2238                                                       (tree, beginRow));
2239                 if (beginRect != null) {
2240                     Rectangle   visRect = tree.getVisibleRect();
2241                     Rectangle   testRect = beginRect;
2242                     int         beginY = beginRect.y;
2243                     int         maxY = beginY + visRect.height;
2244 
2245                     for(int counter = beginRow + 1; counter <= endRow; counter++) {
2246                             testRect = getPathBounds(tree,
2247                                     getPathForRow(tree, counter));
2248                         if (testRect == null) {
2249                             return;
2250                         }
2251                         if((testRect.y + testRect.height) > maxY)
2252                                 counter = endRow;
2253                             }
2254                         tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1,
2255                                                       testRect.y + testRect.height-
2256                                                       beginY));
2257                 }
2258             }
2259         }
2260     }
2261 
2262     /**
2263      * Sets the preferred minimum size.
2264      *
2265      * @param newSize the new preferred size
2266      */
setPreferredMinSize(Dimension newSize)2267     public void setPreferredMinSize(Dimension newSize) {
2268         preferredMinSize = newSize;
2269     }
2270 
2271     /**
2272      * Returns the minimum preferred size.
2273      *
2274      * @return the minimum preferred size
2275      */
getPreferredMinSize()2276     public Dimension getPreferredMinSize() {
2277         if(preferredMinSize == null)
2278             return null;
2279         return new Dimension(preferredMinSize);
2280     }
2281 
2282     /**
2283      * Returns the preferred size to properly display the tree,
2284      * this is a cover method for {@code getPreferredSize(c, true)}.
2285      *
2286      * @param c a component
2287      * @return the preferred size to represent the tree in the component
2288      */
getPreferredSize(JComponent c)2289     public Dimension getPreferredSize(JComponent c) {
2290         return getPreferredSize(c, true);
2291     }
2292 
2293     /**
2294      * Returns the preferred size to represent the tree in
2295      * <I>c</I>.  If <I>checkConsistency</I> is {@code true}
2296      * <b>checkConsistency</b> is messaged first.
2297      *
2298      * @param c a component
2299      * @param checkConsistency if {@code true} consistency is checked
2300      * @return the preferred size to represent the tree in the component
2301      */
getPreferredSize(JComponent c, boolean checkConsistency)2302     public Dimension getPreferredSize(JComponent c,
2303                                       boolean checkConsistency) {
2304         Dimension       pSize = this.getPreferredMinSize();
2305 
2306         if(!validCachedPreferredSize)
2307             updateCachedPreferredSize();
2308         if(tree != null) {
2309             if(pSize != null)
2310                 return new Dimension(Math.max(pSize.width,
2311                                               preferredSize.width),
2312                               Math.max(pSize.height, preferredSize.height));
2313             return new Dimension(preferredSize.width, preferredSize.height);
2314         }
2315         else if(pSize != null)
2316             return pSize;
2317         else
2318             return new Dimension(0, 0);
2319     }
2320 
2321     /**
2322       * Returns the minimum size for this component.  Which will be
2323       * the min preferred size or 0, 0.
2324       */
getMinimumSize(JComponent c)2325     public Dimension getMinimumSize(JComponent c) {
2326         if(this.getPreferredMinSize() != null)
2327             return this.getPreferredMinSize();
2328         return new Dimension(0, 0);
2329     }
2330 
2331     /**
2332       * Returns the maximum size for this component, which will be the
2333       * preferred size if the instance is currently in a JTree, or 0, 0.
2334       */
getMaximumSize(JComponent c)2335     public Dimension getMaximumSize(JComponent c) {
2336         if(tree != null)
2337             return getPreferredSize(tree);
2338         if(this.getPreferredMinSize() != null)
2339             return this.getPreferredMinSize();
2340         return new Dimension(0, 0);
2341     }
2342 
2343 
2344     /**
2345      * Messages to stop the editing session. If the UI the receiver
2346      * is providing the look and feel for returns true from
2347      * <code>getInvokesStopCellEditing</code>, stopCellEditing will
2348      * invoked on the current editor. Then completeEditing will
2349      * be messaged with false, true, false to cancel any lingering
2350      * editing.
2351      */
completeEditing()2352     protected void completeEditing() {
2353         /* If should invoke stopCellEditing, try that */
2354         if(tree.getInvokesStopCellEditing() &&
2355            stopEditingInCompleteEditing && editingComponent != null) {
2356             cellEditor.stopCellEditing();
2357         }
2358         /* Invoke cancelCellEditing, this will do nothing if stopCellEditing
2359            was successful. */
2360         completeEditing(false, true, false);
2361     }
2362 
2363     /**
2364      * Stops the editing session. If {@code messageStop} is {@code true} the editor
2365      * is messaged with {@code stopEditing}, if {@code messageCancel}
2366      * is {@code true} the editor is messaged with {@code cancelEditing}.
2367      * If {@code messageTree} is {@code true} the {@code treeModel} is messaged
2368      * with {@code valueForPathChanged}.
2369      *
2370      * @param messageStop message to stop editing
2371      * @param messageCancel message to cancel editing
2372      * @param messageTree message to tree
2373      */
2374     @SuppressWarnings("deprecation")
completeEditing(boolean messageStop, boolean messageCancel, boolean messageTree)2375     protected void completeEditing(boolean messageStop,
2376                                    boolean messageCancel,
2377                                    boolean messageTree) {
2378         if(stopEditingInCompleteEditing && editingComponent != null) {
2379             Component             oldComponent = editingComponent;
2380             TreePath              oldPath = editingPath;
2381             TreeCellEditor        oldEditor = cellEditor;
2382             Object                newValue = oldEditor.getCellEditorValue();
2383             Rectangle             editingBounds = getPathBounds(tree,
2384                                                                 editingPath);
2385             boolean               requestFocus = (tree != null &&
2386                                    (tree.hasFocus() || SwingUtilities.
2387                                     findFocusOwner(editingComponent) != null));
2388 
2389             editingComponent = null;
2390             editingPath = null;
2391             if(messageStop)
2392                 oldEditor.stopCellEditing();
2393             else if(messageCancel)
2394                 oldEditor.cancelCellEditing();
2395             tree.remove(oldComponent);
2396             if(editorHasDifferentSize) {
2397                 treeState.invalidatePathBounds(oldPath);
2398                 updateSize();
2399             }
2400             else if (editingBounds != null) {
2401                 editingBounds.x = 0;
2402                 editingBounds.width = tree.getSize().width;
2403                 tree.repaint(editingBounds);
2404             }
2405             if(requestFocus)
2406                 tree.requestFocus();
2407             if(messageTree)
2408                 treeModel.valueForPathChanged(oldPath, newValue);
2409         }
2410     }
2411 
2412     // cover method for startEditing that allows us to pass extra
2413     // information into that method via a class variable
startEditingOnRelease(TreePath path, MouseEvent event, MouseEvent releaseEvent)2414     private boolean startEditingOnRelease(TreePath path,
2415                                           MouseEvent event,
2416                                           MouseEvent releaseEvent) {
2417         this.releaseEvent = releaseEvent;
2418         try {
2419             return startEditing(path, event);
2420         } finally {
2421             this.releaseEvent = null;
2422         }
2423     }
2424 
2425     /**
2426      * Will start editing for node if there is a {@code cellEditor} and
2427      * {@code shouldSelectCell} returns {@code true}.<p>
2428      * This assumes that path is valid and visible.
2429      *
2430      * @param path a tree path
2431      * @param event a mouse event
2432      * @return {@code true} if the editing is successful
2433      */
startEditing(TreePath path, MouseEvent event)2434     protected boolean startEditing(TreePath path, MouseEvent event) {
2435         if (isEditing(tree) && tree.getInvokesStopCellEditing() &&
2436                                !stopEditing(tree)) {
2437             return false;
2438         }
2439         completeEditing();
2440         if(cellEditor != null && tree.isPathEditable(path)) {
2441             int           row = getRowForPath(tree, path);
2442 
2443             if(cellEditor.isCellEditable(event)) {
2444                 editingComponent = cellEditor.getTreeCellEditorComponent
2445                       (tree, path.getLastPathComponent(),
2446                        tree.isPathSelected(path), tree.isExpanded(path),
2447                        treeModel.isLeaf(path.getLastPathComponent()), row);
2448                 Rectangle           nodeBounds = getPathBounds(tree, path);
2449                 if (nodeBounds == null) {
2450                     return false;
2451                 }
2452 
2453                 editingRow = row;
2454 
2455                 Dimension editorSize = editingComponent.getPreferredSize();
2456 
2457                 // Only allow odd heights if explicitly set.
2458                 if(editorSize.height != nodeBounds.height &&
2459                    getRowHeight() > 0)
2460                     editorSize.height = getRowHeight();
2461 
2462                 if(editorSize.width != nodeBounds.width ||
2463                    editorSize.height != nodeBounds.height) {
2464                     // Editor wants different width or height, invalidate
2465                     // treeState and relayout.
2466                     editorHasDifferentSize = true;
2467                     treeState.invalidatePathBounds(path);
2468                     updateSize();
2469                     // To make sure x/y are updated correctly, fetch
2470                     // the bounds again.
2471                     nodeBounds = getPathBounds(tree, path);
2472                     if (nodeBounds == null) {
2473                         return false;
2474                     }
2475                 }
2476                 else
2477                     editorHasDifferentSize = false;
2478                 tree.add(editingComponent);
2479                 editingComponent.setBounds(nodeBounds.x, nodeBounds.y,
2480                                            nodeBounds.width,
2481                                            nodeBounds.height);
2482                 editingPath = path;
2483                 AWTAccessor.getComponentAccessor().revalidateSynchronously(editingComponent);
2484                 editingComponent.repaint();
2485                 if(cellEditor.shouldSelectCell(event)) {
2486                     stopEditingInCompleteEditing = false;
2487                     tree.setSelectionRow(row);
2488                     stopEditingInCompleteEditing = true;
2489                 }
2490 
2491                 Component focusedComponent = SwingUtilities2.
2492                                  compositeRequestFocus(editingComponent);
2493                 boolean selectAll = true;
2494 
2495                 if(event != null) {
2496                     /* Find the component that will get forwarded all the
2497                        mouse events until mouseReleased. */
2498                     Point          componentPoint = SwingUtilities.convertPoint
2499                         (tree, new Point(event.getX(), event.getY()),
2500                          editingComponent);
2501 
2502                     /* Create an instance of BasicTreeMouseListener to handle
2503                        passing the mouse/motion events to the necessary
2504                        component. */
2505                     // We really want similar behavior to getMouseEventTarget,
2506                     // but it is package private.
2507                     Component activeComponent = SwingUtilities.
2508                                     getDeepestComponentAt(editingComponent,
2509                                        componentPoint.x, componentPoint.y);
2510                     if (activeComponent != null) {
2511                         MouseInputHandler handler =
2512                             new MouseInputHandler(tree, activeComponent,
2513                                                   event, focusedComponent);
2514 
2515                         if (releaseEvent != null) {
2516                             handler.mouseReleased(releaseEvent);
2517                         }
2518 
2519                         selectAll = false;
2520                     }
2521                 }
2522                 if (selectAll && focusedComponent instanceof JTextField) {
2523                     ((JTextField)focusedComponent).selectAll();
2524                 }
2525                 return true;
2526             }
2527             else
2528                 editingComponent = null;
2529         }
2530         return false;
2531     }
2532 
2533     //
2534     // Following are primarily for handling mouse events.
2535     //
2536 
2537     /**
2538      * If the {@code mouseX} and {@code mouseY} are in the
2539      * expand/collapse region of the {@code row}, this will toggle
2540      * the row.
2541      *
2542      * @param path a tree path
2543      * @param mouseX an X coordinate
2544      * @param mouseY an Y coordinate
2545      */
checkForClickInExpandControl(TreePath path, int mouseX, int mouseY)2546     protected void checkForClickInExpandControl(TreePath path,
2547                                                 int mouseX, int mouseY) {
2548       if (isLocationInExpandControl(path, mouseX, mouseY)) {
2549           handleExpandControlClick(path, mouseX, mouseY);
2550         }
2551     }
2552 
2553     /**
2554      * Returns {@code true} if {@code mouseX} and {@code mouseY} fall
2555      * in the area of row that is used to expand/collapse the node and
2556      * the node at {@code row} does not represent a leaf.
2557      *
2558      * @param path a tree path
2559      * @param mouseX an X coordinate
2560      * @param mouseY an Y coordinate
2561      * @return {@code true} if the mouse cursor fall in the area of row that
2562      *         is used to expand/collapse the node and the node is not a leaf.
2563      */
isLocationInExpandControl(TreePath path, int mouseX, int mouseY)2564     protected boolean isLocationInExpandControl(TreePath path,
2565                                                 int mouseX, int mouseY) {
2566         if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){
2567             int                     boxWidth;
2568             Insets                  i = tree.getInsets();
2569 
2570             if(getExpandedIcon() != null)
2571                 boxWidth = getExpandedIcon().getIconWidth();
2572             else
2573                 boxWidth = 8;
2574 
2575             int boxLeftX = getRowX(tree.getRowForPath(path),
2576                                    path.getPathCount() - 1);
2577 
2578             if (leftToRight) {
2579                 boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1;
2580             } else {
2581                 boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1;
2582             }
2583 
2584             boxLeftX = findCenteredX(boxLeftX, boxWidth);
2585 
2586             return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth));
2587         }
2588         return false;
2589     }
2590 
2591     /**
2592      * Messaged when the user clicks the particular row, this invokes
2593      * {@code toggleExpandState}.
2594      *
2595      * @param path a tree path
2596      * @param mouseX an X coordinate
2597      * @param mouseY an Y coordinate
2598      */
handleExpandControlClick(TreePath path, int mouseX, int mouseY)2599     protected void handleExpandControlClick(TreePath path, int mouseX,
2600                                             int mouseY) {
2601         toggleExpandState(path);
2602     }
2603 
2604     /**
2605      * Expands path if it is not expanded, or collapses row if it is expanded.
2606      * If expanding a path and {@code JTree} scrolls on expand,
2607      * {@code ensureRowsAreVisible} is invoked to scroll as many of the children
2608      * to visible as possible (tries to scroll to last visible descendant of path).
2609      *
2610      * @param path a tree path
2611      */
toggleExpandState(TreePath path)2612     protected void toggleExpandState(TreePath path) {
2613         if(!tree.isExpanded(path)) {
2614             int       row = getRowForPath(tree, path);
2615 
2616             tree.expandPath(path);
2617             updateSize();
2618             if(row != -1) {
2619                 if(tree.getScrollsOnExpand())
2620                     ensureRowsAreVisible(row, row + treeState.
2621                                          getVisibleChildCount(path));
2622                 else
2623                     ensureRowsAreVisible(row, row);
2624             }
2625         }
2626         else {
2627             tree.collapsePath(path);
2628             updateSize();
2629         }
2630     }
2631 
2632     /**
2633      * Returning {@code true} signifies a mouse event on the node should toggle
2634      * the selection of only the row under mouse.
2635      *
2636      * @param event a mouse event
2637      * @return {@code true} if a mouse event on the node should toggle the selection
2638      */
isToggleSelectionEvent(MouseEvent event)2639     protected boolean isToggleSelectionEvent(MouseEvent event) {
2640         return (SwingUtilities.isLeftMouseButton(event) &&
2641                 BasicGraphicsUtils.isMenuShortcutKeyDown(event));
2642     }
2643 
2644     /**
2645      * Returning {@code true} signifies a mouse event on the node should select
2646      * from the anchor point.
2647      *
2648      * @param event a mouse event
2649      * @return {@code true} if a mouse event on the node should select
2650      *         from the anchor point
2651      */
isMultiSelectEvent(MouseEvent event)2652     protected boolean isMultiSelectEvent(MouseEvent event) {
2653         return (SwingUtilities.isLeftMouseButton(event) &&
2654                 event.isShiftDown());
2655     }
2656 
2657     /**
2658      * Returning {@code true} indicates the row under the mouse should be toggled
2659      * based on the event. This is invoked after {@code checkForClickInExpandControl},
2660      * implying the location is not in the expand (toggle) control.
2661      *
2662      * @param event a mouse event
2663      * @return {@code true} if the row under the mouse should be toggled
2664      */
isToggleEvent(MouseEvent event)2665     protected boolean isToggleEvent(MouseEvent event) {
2666         if(!SwingUtilities.isLeftMouseButton(event)) {
2667             return false;
2668         }
2669         int           clickCount = tree.getToggleClickCount();
2670 
2671         if(clickCount <= 0) {
2672             return false;
2673         }
2674         return ((event.getClickCount() % clickCount) == 0);
2675     }
2676 
2677     /**
2678      * Messaged to update the selection based on a {@code MouseEvent} over a
2679      * particular row. If the event is a toggle selection event, the
2680      * row is either selected, or deselected. If the event identifies
2681      * a multi selection event, the selection is updated from the
2682      * anchor point. Otherwise the row is selected, and if the event
2683      * specified a toggle event the row is expanded/collapsed.
2684      *
2685      * @param path the selected path
2686      * @param event the mouse event
2687      */
selectPathForEvent(TreePath path, MouseEvent event)2688     protected void selectPathForEvent(TreePath path, MouseEvent event) {
2689         /* Adjust from the anchor point. */
2690         if(isMultiSelectEvent(event)) {
2691             TreePath    anchor = getAnchorSelectionPath();
2692             int         anchorRow = (anchor == null) ? -1 :
2693                                     getRowForPath(tree, anchor);
2694 
2695             if(anchorRow == -1 || tree.getSelectionModel().
2696                       getSelectionMode() == TreeSelectionModel.
2697                       SINGLE_TREE_SELECTION) {
2698                 tree.setSelectionPath(path);
2699             }
2700             else {
2701                 int          row = getRowForPath(tree, path);
2702                 TreePath     lastAnchorPath = anchor;
2703 
2704                 if (isToggleSelectionEvent(event)) {
2705                     if (tree.isRowSelected(anchorRow)) {
2706                         tree.addSelectionInterval(anchorRow, row);
2707                     } else {
2708                         tree.removeSelectionInterval(anchorRow, row);
2709                         tree.addSelectionInterval(row, row);
2710                     }
2711                 } else if(row < anchorRow) {
2712                     tree.setSelectionInterval(row, anchorRow);
2713                 } else {
2714                     tree.setSelectionInterval(anchorRow, row);
2715                 }
2716                 lastSelectedRow = row;
2717                 setAnchorSelectionPath(lastAnchorPath);
2718                 setLeadSelectionPath(path);
2719             }
2720         }
2721 
2722         // Should this event toggle the selection of this row?
2723         /* Control toggles just this node. */
2724         else if(isToggleSelectionEvent(event)) {
2725             if(tree.isPathSelected(path))
2726                 tree.removeSelectionPath(path);
2727             else
2728                 tree.addSelectionPath(path);
2729             lastSelectedRow = getRowForPath(tree, path);
2730             setAnchorSelectionPath(path);
2731             setLeadSelectionPath(path);
2732         }
2733 
2734         /* Otherwise set the selection to just this interval. */
2735         else if(SwingUtilities.isLeftMouseButton(event)) {
2736             tree.setSelectionPath(path);
2737             if(isToggleEvent(event)) {
2738                 toggleExpandState(path);
2739             }
2740         }
2741     }
2742 
2743     /**
2744      * Returns {@code true} if the node at {@code row} is a leaf.
2745      *
2746      * @param row a row
2747      * @return {@code true} if the node at {@code row} is a leaf
2748      */
isLeaf(int row)2749     protected boolean isLeaf(int row) {
2750         TreePath          path = getPathForRow(tree, row);
2751 
2752         if(path != null)
2753             return treeModel.isLeaf(path.getLastPathComponent());
2754         // Have to return something here...
2755         return true;
2756     }
2757 
2758     //
2759     // The following selection methods (lead/anchor) are covers for the
2760     // methods in JTree.
2761     //
setAnchorSelectionPath(TreePath newPath)2762     private void setAnchorSelectionPath(TreePath newPath) {
2763         ignoreLAChange = true;
2764         try {
2765             tree.setAnchorSelectionPath(newPath);
2766         } finally{
2767             ignoreLAChange = false;
2768         }
2769     }
2770 
getAnchorSelectionPath()2771     private TreePath getAnchorSelectionPath() {
2772         return tree.getAnchorSelectionPath();
2773     }
2774 
setLeadSelectionPath(TreePath newPath)2775     private void setLeadSelectionPath(TreePath newPath) {
2776         setLeadSelectionPath(newPath, false);
2777     }
2778 
setLeadSelectionPath(TreePath newPath, boolean repaint)2779     private void setLeadSelectionPath(TreePath newPath, boolean repaint) {
2780         Rectangle       bounds = repaint ?
2781                             getPathBounds(tree, getLeadSelectionPath()) : null;
2782 
2783         ignoreLAChange = true;
2784         try {
2785             tree.setLeadSelectionPath(newPath);
2786         } finally {
2787             ignoreLAChange = false;
2788         }
2789         leadRow = getRowForPath(tree, newPath);
2790 
2791         if (repaint) {
2792             if (bounds != null) {
2793                 tree.repaint(getRepaintPathBounds(bounds));
2794             }
2795             bounds = getPathBounds(tree, newPath);
2796             if (bounds != null) {
2797                 tree.repaint(getRepaintPathBounds(bounds));
2798             }
2799         }
2800     }
2801 
getRepaintPathBounds(Rectangle bounds)2802     private Rectangle getRepaintPathBounds(Rectangle bounds) {
2803         if (UIManager.getBoolean("Tree.repaintWholeRow")) {
2804            bounds.x = 0;
2805            bounds.width = tree.getWidth();
2806         }
2807         return bounds;
2808     }
2809 
getLeadSelectionPath()2810     private TreePath getLeadSelectionPath() {
2811         return tree.getLeadSelectionPath();
2812     }
2813 
2814     /**
2815      * Updates the lead row of the selection.
2816      * @since 1.7
2817      */
updateLeadSelectionRow()2818     protected void updateLeadSelectionRow() {
2819         leadRow = getRowForPath(tree, getLeadSelectionPath());
2820     }
2821 
2822     /**
2823      * Returns the lead row of the selection.
2824      *
2825      * @return selection lead row
2826      * @since 1.7
2827      */
getLeadSelectionRow()2828     protected int getLeadSelectionRow() {
2829         return leadRow;
2830     }
2831 
2832     /**
2833      * Extends the selection from the anchor to make <code>newLead</code>
2834      * the lead of the selection. This does not scroll.
2835      */
extendSelection(TreePath newLead)2836     private void extendSelection(TreePath newLead) {
2837         TreePath           aPath = getAnchorSelectionPath();
2838         int                aRow = (aPath == null) ? -1 :
2839                                   getRowForPath(tree, aPath);
2840         int                newIndex = getRowForPath(tree, newLead);
2841 
2842         if(aRow == -1) {
2843             tree.setSelectionRow(newIndex);
2844         }
2845         else {
2846             if(aRow < newIndex) {
2847                 tree.setSelectionInterval(aRow, newIndex);
2848             }
2849             else {
2850                 tree.setSelectionInterval(newIndex, aRow);
2851             }
2852             setAnchorSelectionPath(aPath);
2853             setLeadSelectionPath(newLead);
2854         }
2855     }
2856 
2857     /**
2858      * Invokes <code>repaint</code> on the JTree for the passed in TreePath,
2859      * <code>path</code>.
2860      */
repaintPath(TreePath path)2861     private void repaintPath(TreePath path) {
2862         if (path != null) {
2863             Rectangle bounds = getPathBounds(tree, path);
2864             if (bounds != null) {
2865                 tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
2866             }
2867         }
2868     }
2869 
2870     /**
2871      * Updates the TreeState in response to nodes expanding/collapsing.
2872      */
2873     public class TreeExpansionHandler implements TreeExpansionListener {
2874         // NOTE: This class exists only for backward compatibility. All
2875         // its functionality has been moved into Handler. If you need to add
2876         // new functionality add it to the Handler, but make sure this
2877         // class calls into the Handler.
2878 
2879         /**
2880          * Called whenever an item in the tree has been expanded.
2881          */
treeExpanded(TreeExpansionEvent event)2882         public void treeExpanded(TreeExpansionEvent event) {
2883             getHandler().treeExpanded(event);
2884         }
2885 
2886         /**
2887          * Called whenever an item in the tree has been collapsed.
2888          */
treeCollapsed(TreeExpansionEvent event)2889         public void treeCollapsed(TreeExpansionEvent event) {
2890             getHandler().treeCollapsed(event);
2891         }
2892     } // BasicTreeUI.TreeExpansionHandler
2893 
2894 
2895     /**
2896      * Updates the preferred size when scrolling (if necessary).
2897      */
2898     public class ComponentHandler extends ComponentAdapter implements
2899                  ActionListener {
2900         /** Timer used when inside a scrollpane and the scrollbar is
2901          * adjusting. */
2902         protected Timer                timer;
2903         /** ScrollBar that is being adjusted. */
2904         protected JScrollBar           scrollBar;
2905 
componentMoved(ComponentEvent e)2906         public void componentMoved(ComponentEvent e) {
2907             if(timer == null) {
2908                 JScrollPane   scrollPane = getScrollPane();
2909 
2910                 if(scrollPane == null)
2911                     updateSize();
2912                 else {
2913                     scrollBar = scrollPane.getVerticalScrollBar();
2914                     if(scrollBar == null ||
2915                         !scrollBar.getValueIsAdjusting()) {
2916                         // Try the horizontal scrollbar.
2917                         if((scrollBar = scrollPane.getHorizontalScrollBar())
2918                             != null && scrollBar.getValueIsAdjusting())
2919                             startTimer();
2920                         else
2921                             updateSize();
2922                     }
2923                     else
2924                         startTimer();
2925                 }
2926             }
2927         }
2928 
2929         /**
2930          * Creates, if necessary, and starts a Timer to check if need to
2931          * resize the bounds.
2932          */
startTimer()2933         protected void startTimer() {
2934             if(timer == null) {
2935                 timer = new Timer(200, this);
2936                 timer.setRepeats(true);
2937             }
2938             timer.start();
2939         }
2940 
2941         /**
2942          * Returns the {@code JScrollPane} housing the {@code JTree},
2943          * or null if one isn't found.
2944          *
2945          * @return the {@code JScrollPane} housing the {@code JTree}
2946          */
getScrollPane()2947         protected JScrollPane getScrollPane() {
2948             Component       c = tree.getParent();
2949 
2950             while(c != null && !(c instanceof JScrollPane))
2951                 c = c.getParent();
2952             if(c instanceof JScrollPane)
2953                 return (JScrollPane)c;
2954             return null;
2955         }
2956 
2957         /**
2958          * Public as a result of Timer. If the scrollBar is null, or
2959          * not adjusting, this stops the timer and updates the sizing.
2960          */
actionPerformed(ActionEvent ae)2961         public void actionPerformed(ActionEvent ae) {
2962             if(scrollBar == null || !scrollBar.getValueIsAdjusting()) {
2963                 if(timer != null)
2964                     timer.stop();
2965                 updateSize();
2966                 timer = null;
2967                 scrollBar = null;
2968             }
2969         }
2970     } // End of BasicTreeUI.ComponentHandler
2971 
2972 
2973     /**
2974      * Forwards all TreeModel events to the TreeState.
2975      */
2976     public class TreeModelHandler implements TreeModelListener {
2977 
2978         // NOTE: This class exists only for backward compatibility. All
2979         // its functionality has been moved into Handler. If you need to add
2980         // new functionality add it to the Handler, but make sure this
2981         // class calls into the Handler.
2982 
treeNodesChanged(TreeModelEvent e)2983         public void treeNodesChanged(TreeModelEvent e) {
2984             getHandler().treeNodesChanged(e);
2985         }
2986 
treeNodesInserted(TreeModelEvent e)2987         public void treeNodesInserted(TreeModelEvent e) {
2988             getHandler().treeNodesInserted(e);
2989         }
2990 
treeNodesRemoved(TreeModelEvent e)2991         public void treeNodesRemoved(TreeModelEvent e) {
2992             getHandler().treeNodesRemoved(e);
2993         }
2994 
treeStructureChanged(TreeModelEvent e)2995         public void treeStructureChanged(TreeModelEvent e) {
2996             getHandler().treeStructureChanged(e);
2997         }
2998     } // End of BasicTreeUI.TreeModelHandler
2999 
3000 
3001     /**
3002      * Listens for changes in the selection model and updates the display
3003      * accordingly.
3004      */
3005     public class TreeSelectionHandler implements TreeSelectionListener {
3006 
3007         // NOTE: This class exists only for backward compatibility. All
3008         // its functionality has been moved into Handler. If you need to add
3009         // new functionality add it to the Handler, but make sure this
3010         // class calls into the Handler.
3011 
3012         /**
3013          * Messaged when the selection changes in the tree we're displaying
3014          * for.  Stops editing, messages super and displays the changed paths.
3015          */
valueChanged(TreeSelectionEvent event)3016         public void valueChanged(TreeSelectionEvent event) {
3017             getHandler().valueChanged(event);
3018         }
3019     }// End of BasicTreeUI.TreeSelectionHandler
3020 
3021 
3022     /**
3023      * Listener responsible for getting cell editing events and updating
3024      * the tree accordingly.
3025      */
3026     public class CellEditorHandler implements CellEditorListener {
3027 
3028         // NOTE: This class exists only for backward compatibility. All
3029         // its functionality has been moved into Handler. If you need to add
3030         // new functionality add it to the Handler, but make sure this
3031         // class calls into the Handler.
3032 
3033         /** Messaged when editing has stopped in the tree. */
editingStopped(ChangeEvent e)3034         public void editingStopped(ChangeEvent e) {
3035             getHandler().editingStopped(e);
3036         }
3037 
3038         /** Messaged when editing has been canceled in the tree. */
editingCanceled(ChangeEvent e)3039         public void editingCanceled(ChangeEvent e) {
3040             getHandler().editingCanceled(e);
3041         }
3042     } // BasicTreeUI.CellEditorHandler
3043 
3044 
3045     /**
3046      * This is used to get multiple key down events to appropriately generate
3047      * events.
3048      */
3049     public class KeyHandler extends KeyAdapter {
3050 
3051         // NOTE: This class exists only for backward compatibility. All
3052         // its functionality has been moved into Handler. If you need to add
3053         // new functionality add it to the Handler, but make sure this
3054         // class calls into the Handler.
3055 
3056         // Also note these fields aren't use anymore, nor does Handler have
3057         // the old functionality. This behavior worked around an old bug
3058         // in JComponent that has long since been fixed.
3059 
3060         /** Key code that is being generated for. */
3061         protected Action              repeatKeyAction;
3062 
3063         /** Set to true while keyPressed is active. */
3064         protected boolean            isKeyDown;
3065 
3066         /**
3067          * Invoked when a key has been typed.
3068          *
3069          * Moves the keyboard focus to the first element
3070          * whose first letter matches the alphanumeric key
3071          * pressed by the user. Subsequent same key presses
3072          * move the keyboard focus to the next object that
3073          * starts with the same letter.
3074          */
keyTyped(KeyEvent e)3075         public void keyTyped(KeyEvent e) {
3076             getHandler().keyTyped(e);
3077         }
3078 
keyPressed(KeyEvent e)3079         public void keyPressed(KeyEvent e) {
3080             getHandler().keyPressed(e);
3081         }
3082 
keyReleased(KeyEvent e)3083         public void keyReleased(KeyEvent e) {
3084             getHandler().keyReleased(e);
3085         }
3086     } // End of BasicTreeUI.KeyHandler
3087 
3088 
3089     /**
3090      * Repaints the lead selection row when focus is lost/gained.
3091      */
3092     public class FocusHandler implements FocusListener {
3093         // NOTE: This class exists only for backward compatibility. All
3094         // its functionality has been moved into Handler. If you need to add
3095         // new functionality add it to the Handler, but make sure this
3096         // class calls into the Handler.
3097 
3098         /**
3099          * Invoked when focus is activated on the tree we're in, redraws the
3100          * lead row.
3101          */
focusGained(FocusEvent e)3102         public void focusGained(FocusEvent e) {
3103             getHandler().focusGained(e);
3104         }
3105 
3106         /**
3107          * Invoked when focus is activated on the tree we're in, redraws the
3108          * lead row.
3109          */
focusLost(FocusEvent e)3110         public void focusLost(FocusEvent e) {
3111             getHandler().focusLost(e);
3112         }
3113     } // End of class BasicTreeUI.FocusHandler
3114 
3115 
3116     /**
3117      * Class responsible for getting size of node, method is forwarded
3118      * to BasicTreeUI method. X location does not include insets, that is
3119      * handled in getPathBounds.
3120      */
3121     // This returns locations that don't include any Insets.
3122     public class NodeDimensionsHandler extends
3123                  AbstractLayoutCache.NodeDimensions {
3124         /**
3125          * Responsible for getting the size of a particular node.
3126          */
getNodeDimensions(Object value, int row, int depth, boolean expanded, Rectangle size)3127         public Rectangle getNodeDimensions(Object value, int row,
3128                                            int depth, boolean expanded,
3129                                            Rectangle size) {
3130             // Return size of editing component, if editing and asking
3131             // for editing row.
3132             if(editingComponent != null && editingRow == row) {
3133                 Dimension        prefSize = editingComponent.
3134                                               getPreferredSize();
3135                 int              rh = getRowHeight();
3136 
3137                 if(rh > 0 && rh != prefSize.height)
3138                     prefSize.height = rh;
3139                 if(size != null) {
3140                     size.x = getRowX(row, depth);
3141                     size.width = prefSize.width;
3142                     size.height = prefSize.height;
3143                 }
3144                 else {
3145                     size = new Rectangle(getRowX(row, depth), 0,
3146                                          prefSize.width, prefSize.height);
3147                 }
3148                 return size;
3149             }
3150             // Not editing, use renderer.
3151             if(currentCellRenderer != null) {
3152                 Component          aComponent;
3153 
3154                 aComponent = currentCellRenderer.getTreeCellRendererComponent
3155                     (tree, value, tree.isRowSelected(row),
3156                      expanded, treeModel.isLeaf(value), row,
3157                      false);
3158                 if(tree != null) {
3159                     // Only ever removed when UI changes, this is OK!
3160                     rendererPane.add(aComponent);
3161                     aComponent.validate();
3162                 }
3163                 Dimension        prefSize = aComponent.getPreferredSize();
3164 
3165                 if(size != null) {
3166                     size.x = getRowX(row, depth);
3167                     size.width = prefSize.width;
3168                     size.height = prefSize.height;
3169                 }
3170                 else {
3171                     size = new Rectangle(getRowX(row, depth), 0,
3172                                          prefSize.width, prefSize.height);
3173                 }
3174                 return size;
3175             }
3176             return null;
3177         }
3178 
3179         /**
3180          * Returns amount to indent the given row.
3181          *
3182          * @param row a row
3183          * @param depth a depth
3184          * @return amount to indent the given row
3185          */
getRowX(int row, int depth)3186         protected int getRowX(int row, int depth) {
3187             return BasicTreeUI.this.getRowX(row, depth);
3188         }
3189 
3190     } // End of class BasicTreeUI.NodeDimensionsHandler
3191 
3192 
3193     /**
3194      * TreeMouseListener is responsible for updating the selection
3195      * based on mouse events.
3196      */
3197     public class MouseHandler extends MouseAdapter implements MouseMotionListener
3198  {
3199         // NOTE: This class exists only for backward compatibility. All
3200         // its functionality has been moved into Handler. If you need to add
3201         // new functionality add it to the Handler, but make sure this
3202         // class calls into the Handler.
3203 
3204         /**
3205          * Invoked when a mouse button has been pressed on a component.
3206          */
mousePressed(MouseEvent e)3207         public void mousePressed(MouseEvent e) {
3208             getHandler().mousePressed(e);
3209         }
3210 
mouseDragged(MouseEvent e)3211         public void mouseDragged(MouseEvent e) {
3212             getHandler().mouseDragged(e);
3213         }
3214 
3215         /**
3216          * Invoked when the mouse button has been moved on a component
3217          * (with no buttons no down).
3218          * @since 1.4
3219          */
mouseMoved(MouseEvent e)3220         public void mouseMoved(MouseEvent e) {
3221             getHandler().mouseMoved(e);
3222         }
3223 
mouseReleased(MouseEvent e)3224         public void mouseReleased(MouseEvent e) {
3225             getHandler().mouseReleased(e);
3226         }
3227     } // End of BasicTreeUI.MouseHandler
3228 
3229 
3230     /**
3231      * PropertyChangeListener for the tree. Updates the appropriate
3232      * variable, or TreeState, based on what changes.
3233      */
3234     public class PropertyChangeHandler implements
3235                        PropertyChangeListener {
3236 
3237         // NOTE: This class exists only for backward compatibility. All
3238         // its functionality has been moved into Handler. If you need to add
3239         // new functionality add it to the Handler, but make sure this
3240         // class calls into the Handler.
3241 
propertyChange(PropertyChangeEvent event)3242         public void propertyChange(PropertyChangeEvent event) {
3243             getHandler().propertyChange(event);
3244         }
3245     } // End of BasicTreeUI.PropertyChangeHandler
3246 
3247 
3248     /**
3249      * Listener on the TreeSelectionModel, resets the row selection if
3250      * any of the properties of the model change.
3251      */
3252     public class SelectionModelPropertyChangeHandler implements
3253                       PropertyChangeListener {
3254 
3255         // NOTE: This class exists only for backward compatibility. All
3256         // its functionality has been moved into Handler. If you need to add
3257         // new functionality add it to the Handler, but make sure this
3258         // class calls into the Handler.
3259 
propertyChange(PropertyChangeEvent event)3260         public void propertyChange(PropertyChangeEvent event) {
3261             getHandler().propertyChange(event);
3262         }
3263     } // End of BasicTreeUI.SelectionModelPropertyChangeHandler
3264 
3265 
3266     /**
3267      * <code>TreeTraverseAction</code> is the action used for left/right keys.
3268      * Will toggle the expandedness of a node, as well as potentially
3269      * incrementing the selection.
3270      */
3271     @SuppressWarnings("serial") // Superclass is not serializable across versions
3272     public class TreeTraverseAction extends AbstractAction {
3273         /** Determines direction to traverse, 1 means expand, -1 means
3274           * collapse. */
3275         protected int direction;
3276         /** True if the selection is reset, false means only the lead path
3277          * changes. */
3278         private boolean changeSelection;
3279 
3280         /**
3281          * Constructs a new instance of {@code TreeTraverseAction}.
3282          *
3283          * @param direction the direction
3284          * @param name the name of action
3285          */
TreeTraverseAction(int direction, String name)3286         public TreeTraverseAction(int direction, String name) {
3287             this(direction, name, true);
3288         }
3289 
TreeTraverseAction(int direction, String name, boolean changeSelection)3290         private TreeTraverseAction(int direction, String name,
3291                                    boolean changeSelection) {
3292             this.direction = direction;
3293             this.changeSelection = changeSelection;
3294         }
3295 
actionPerformed(ActionEvent e)3296         public void actionPerformed(ActionEvent e) {
3297             if (tree != null) {
3298                 SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction,
3299                                        changeSelection);
3300             }
3301         }
3302 
isEnabled()3303         public boolean isEnabled() { return (tree != null &&
3304                                              tree.isEnabled()); }
3305     } // BasicTreeUI.TreeTraverseAction
3306 
3307 
3308     /** TreePageAction handles page up and page down events.
3309       */
3310     @SuppressWarnings("serial") // Superclass is not serializable across versions
3311     public class TreePageAction extends AbstractAction {
3312         /** Specifies the direction to adjust the selection by. */
3313         protected int         direction;
3314         /** True indicates should set selection from anchor path. */
3315         private boolean       addToSelection;
3316         private boolean       changeSelection;
3317 
3318         /**
3319          * Constructs a new instance of {@code TreePageAction}.
3320          *
3321          * @param direction the direction
3322          * @param name the name of action
3323          */
TreePageAction(int direction, String name)3324         public TreePageAction(int direction, String name) {
3325             this(direction, name, false, true);
3326         }
3327 
TreePageAction(int direction, String name, boolean addToSelection, boolean changeSelection)3328         private TreePageAction(int direction, String name,
3329                                boolean addToSelection,
3330                                boolean changeSelection) {
3331             this.direction = direction;
3332             this.addToSelection = addToSelection;
3333             this.changeSelection = changeSelection;
3334         }
3335 
actionPerformed(ActionEvent e)3336         public void actionPerformed(ActionEvent e) {
3337             if (tree != null) {
3338                 SHARED_ACTION.page(tree, BasicTreeUI.this, direction,
3339                                    addToSelection, changeSelection);
3340             }
3341         }
3342 
isEnabled()3343         public boolean isEnabled() { return (tree != null &&
3344                                              tree.isEnabled()); }
3345 
3346     } // BasicTreeUI.TreePageAction
3347 
3348 
3349     /** TreeIncrementAction is used to handle up/down actions.  Selection
3350       * is moved up or down based on direction.
3351       */
3352     @SuppressWarnings("serial") // Superclass is not serializable across versions
3353     public class TreeIncrementAction extends AbstractAction  {
3354         /** Specifies the direction to adjust the selection by. */
3355         protected int         direction;
3356         /** If true the new item is added to the selection, if false the
3357          * selection is reset. */
3358         private boolean       addToSelection;
3359         private boolean       changeSelection;
3360 
3361         /**
3362          * Constructs a new instance of {@code TreeIncrementAction}.
3363          *
3364          * @param direction the direction
3365          * @param name the name of action
3366          */
TreeIncrementAction(int direction, String name)3367         public TreeIncrementAction(int direction, String name) {
3368             this(direction, name, false, true);
3369         }
3370 
TreeIncrementAction(int direction, String name, boolean addToSelection, boolean changeSelection)3371         private TreeIncrementAction(int direction, String name,
3372                                    boolean addToSelection,
3373                                     boolean changeSelection) {
3374             this.direction = direction;
3375             this.addToSelection = addToSelection;
3376             this.changeSelection = changeSelection;
3377         }
3378 
actionPerformed(ActionEvent e)3379         public void actionPerformed(ActionEvent e) {
3380             if (tree != null) {
3381                 SHARED_ACTION.increment(tree, BasicTreeUI.this, direction,
3382                                         addToSelection, changeSelection);
3383             }
3384         }
3385 
isEnabled()3386         public boolean isEnabled() { return (tree != null &&
3387                                              tree.isEnabled()); }
3388 
3389     } // End of class BasicTreeUI.TreeIncrementAction
3390 
3391     /**
3392       * TreeHomeAction is used to handle end/home actions.
3393       * Scrolls either the first or last cell to be visible based on
3394       * direction.
3395       */
3396     @SuppressWarnings("serial") // Superclass is not serializable across versions
3397     public class TreeHomeAction extends AbstractAction {
3398         /**
3399          * The direction.
3400          */
3401         protected int            direction;
3402         /** Set to true if append to selection. */
3403         private boolean          addToSelection;
3404         private boolean          changeSelection;
3405 
3406         /**
3407          * Constructs a new instance of {@code TreeHomeAction}.
3408          *
3409          * @param direction the direction
3410          * @param name the name of action
3411          */
TreeHomeAction(int direction, String name)3412         public TreeHomeAction(int direction, String name) {
3413             this(direction, name, false, true);
3414         }
3415 
TreeHomeAction(int direction, String name, boolean addToSelection, boolean changeSelection)3416         private TreeHomeAction(int direction, String name,
3417                                boolean addToSelection,
3418                                boolean changeSelection) {
3419             this.direction = direction;
3420             this.changeSelection = changeSelection;
3421             this.addToSelection = addToSelection;
3422         }
3423 
actionPerformed(ActionEvent e)3424         public void actionPerformed(ActionEvent e) {
3425             if (tree != null) {
3426                 SHARED_ACTION.home(tree, BasicTreeUI.this, direction,
3427                                    addToSelection, changeSelection);
3428             }
3429         }
3430 
isEnabled()3431         public boolean isEnabled() { return (tree != null &&
3432                                              tree.isEnabled()); }
3433 
3434     } // End of class BasicTreeUI.TreeHomeAction
3435 
3436 
3437     /**
3438       * For the first selected row expandedness will be toggled.
3439       */
3440     @SuppressWarnings("serial") // Superclass is not serializable across versions
3441     public class TreeToggleAction extends AbstractAction {
3442         /**
3443          * Constructs a new instance of {@code TreeToggleAction}.
3444          *
3445          * @param name the name of action
3446          */
TreeToggleAction(String name)3447         public TreeToggleAction(String name) {
3448         }
3449 
actionPerformed(ActionEvent e)3450         public void actionPerformed(ActionEvent e) {
3451             if(tree != null) {
3452                 SHARED_ACTION.toggle(tree, BasicTreeUI.this);
3453             }
3454         }
3455 
isEnabled()3456         public boolean isEnabled() { return (tree != null &&
3457                                              tree.isEnabled()); }
3458 
3459     } // End of class BasicTreeUI.TreeToggleAction
3460 
3461 
3462     /**
3463      * ActionListener that invokes cancelEditing when action performed.
3464      */
3465     @SuppressWarnings("serial") // Superclass is not serializable across versions
3466     public class TreeCancelEditingAction extends AbstractAction {
3467         /**
3468          * Constructs a new instance of {@code TreeCancelEditingAction}.
3469          *
3470          * @param name the name of action
3471          */
TreeCancelEditingAction(String name)3472         public TreeCancelEditingAction(String name) {
3473         }
3474 
actionPerformed(ActionEvent e)3475         public void actionPerformed(ActionEvent e) {
3476             if(tree != null) {
3477                 SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this);
3478             }
3479         }
3480 
isEnabled()3481         public boolean isEnabled() { return (tree != null &&
3482                                              tree.isEnabled() &&
3483                                              isEditing(tree)); }
3484     } // End of class BasicTreeUI.TreeCancelEditingAction
3485 
3486 
3487     /**
3488       * MouseInputHandler handles passing all mouse events,
3489       * including mouse motion events, until the mouse is released to
3490       * the destination it is constructed with. It is assumed all the
3491       * events are currently target at source.
3492       */
3493     public class MouseInputHandler implements
3494                      MouseInputListener
3495     {
3496         /** Source that events are coming from. */
3497         protected Component        source;
3498         /** Destination that receives all events. */
3499         protected Component        destination;
3500         private Component          focusComponent;
3501         private boolean            dispatchedEvent;
3502 
3503         /**
3504          * Constructs a new instance of {@code MouseInputHandler}.
3505          *
3506          * @param source a source component
3507          * @param destination a destination component
3508          * @param event a mouse event
3509          */
MouseInputHandler(Component source, Component destination, MouseEvent event)3510         public MouseInputHandler(Component source, Component destination,
3511                                       MouseEvent event){
3512             this(source, destination, event, null);
3513         }
3514 
MouseInputHandler(Component source, Component destination, MouseEvent event, Component focusComponent)3515         MouseInputHandler(Component source, Component destination,
3516                           MouseEvent event, Component focusComponent) {
3517             this.source = source;
3518             this.destination = destination;
3519             this.source.addMouseListener(this);
3520             this.source.addMouseMotionListener(this);
3521 
3522             SwingUtilities2.setSkipClickCount(destination,
3523                                               event.getClickCount() - 1);
3524 
3525             /* Dispatch the editing event! */
3526             destination.dispatchEvent(SwingUtilities.convertMouseEvent
3527                                           (source, event, destination));
3528             this.focusComponent = focusComponent;
3529         }
3530 
mouseClicked(MouseEvent e)3531         public void mouseClicked(MouseEvent e) {
3532             if(destination != null) {
3533                 dispatchedEvent = true;
3534                 destination.dispatchEvent(SwingUtilities.convertMouseEvent
3535                                           (source, e, destination));
3536             }
3537         }
3538 
mousePressed(MouseEvent e)3539         public void mousePressed(MouseEvent e) {
3540         }
3541 
mouseReleased(MouseEvent e)3542         public void mouseReleased(MouseEvent e) {
3543             if(destination != null)
3544                 destination.dispatchEvent(SwingUtilities.convertMouseEvent
3545                                           (source, e, destination));
3546             removeFromSource();
3547         }
3548 
mouseEntered(MouseEvent e)3549         public void mouseEntered(MouseEvent e) {
3550             if (!SwingUtilities.isLeftMouseButton(e)) {
3551                 removeFromSource();
3552             }
3553         }
3554 
mouseExited(MouseEvent e)3555         public void mouseExited(MouseEvent e) {
3556             if (!SwingUtilities.isLeftMouseButton(e)) {
3557                 removeFromSource();
3558             }
3559         }
3560 
mouseDragged(MouseEvent e)3561         public void mouseDragged(MouseEvent e) {
3562             if(destination != null) {
3563                 dispatchedEvent = true;
3564                 destination.dispatchEvent(SwingUtilities.convertMouseEvent
3565                                           (source, e, destination));
3566             }
3567         }
3568 
mouseMoved(MouseEvent e)3569         public void mouseMoved(MouseEvent e) {
3570             removeFromSource();
3571         }
3572 
3573         /**
3574          * Removes an event from the source.
3575          */
removeFromSource()3576         protected void removeFromSource() {
3577             if(source != null) {
3578                 source.removeMouseListener(this);
3579                 source.removeMouseMotionListener(this);
3580                 if (focusComponent != null &&
3581                       focusComponent == destination && !dispatchedEvent &&
3582                       (focusComponent instanceof JTextField)) {
3583                     ((JTextField)focusComponent).selectAll();
3584                 }
3585             }
3586             source = destination = null;
3587         }
3588 
3589     } // End of class BasicTreeUI.MouseInputHandler
3590 
3591     private static final TransferHandler defaultTransferHandler = new TreeTransferHandler();
3592 
3593     @SuppressWarnings("serial") // JDK-implementation class
3594     static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator<TreePath> {
3595 
3596         private JTree tree;
3597 
3598         /**
3599          * Create a Transferable to use as the source for a data transfer.
3600          *
3601          * @param c  The component holding the data to be transfered.  This
3602          *  argument is provided to enable sharing of TransferHandlers by
3603          *  multiple components.
3604          * @return  The representation of the data to be transfered.
3605          *
3606          */
createTransferable(JComponent c)3607         protected Transferable createTransferable(JComponent c) {
3608             if (c instanceof JTree) {
3609                 tree = (JTree) c;
3610                 TreePath[] paths = tree.getSelectionPaths();
3611 
3612                 if (paths == null || paths.length == 0) {
3613                     return null;
3614                 }
3615 
3616                 StringBuilder plainStr = new StringBuilder();
3617                 StringBuilder htmlStr = new StringBuilder();
3618 
3619                 htmlStr.append("<html>\n<body>\n<ul>\n");
3620 
3621                 TreeModel model = tree.getModel();
3622                 TreePath lastPath = null;
3623                 TreePath[] displayPaths = getDisplayOrderPaths(paths);
3624 
3625                 for (TreePath path : displayPaths) {
3626                     Object node = path.getLastPathComponent();
3627                     boolean leaf = model.isLeaf(node);
3628                     String label = getDisplayString(path, true, leaf);
3629 
3630                     plainStr.append(label).append('\n');
3631                     htmlStr.append("  <li>").append(label).append('\n');
3632                 }
3633 
3634                 // remove the last newline
3635                 plainStr.deleteCharAt(plainStr.length() - 1);
3636                 htmlStr.append("</ul>\n</body>\n</html>");
3637 
3638                 tree = null;
3639 
3640                 return new BasicTransferable(plainStr.toString(), htmlStr.toString());
3641             }
3642 
3643             return null;
3644         }
3645 
compare(TreePath o1, TreePath o2)3646         public int compare(TreePath o1, TreePath o2) {
3647             int row1 = tree.getRowForPath(o1);
3648             int row2 = tree.getRowForPath(o2);
3649             return row1 - row2;
3650         }
3651 
getDisplayString(TreePath path, boolean selected, boolean leaf)3652         String getDisplayString(TreePath path, boolean selected, boolean leaf) {
3653             int row = tree.getRowForPath(path);
3654             boolean hasFocus = tree.getLeadSelectionRow() == row;
3655             Object node = path.getLastPathComponent();
3656             return tree.convertValueToText(node, selected, tree.isExpanded(row),
3657                                            leaf, row, hasFocus);
3658         }
3659 
3660         /**
3661          * Selection paths are in selection order.  The conversion to
3662          * HTML requires display order.  This method resorts the paths
3663          * to be in the display order.
3664          */
getDisplayOrderPaths(TreePath[] paths)3665         TreePath[] getDisplayOrderPaths(TreePath[] paths) {
3666             // sort the paths to display order rather than selection order
3667             ArrayList<TreePath> selOrder = new ArrayList<TreePath>();
3668             for (TreePath path : paths) {
3669                 selOrder.add(path);
3670             }
3671             Collections.sort(selOrder, this);
3672             int n = selOrder.size();
3673             TreePath[] displayPaths = new TreePath[n];
3674             for (int i = 0; i < n; i++) {
3675                 displayPaths[i] = selOrder.get(i);
3676             }
3677             return displayPaths;
3678         }
3679 
getSourceActions(JComponent c)3680         public int getSourceActions(JComponent c) {
3681             return COPY;
3682         }
3683 
3684     }
3685 
3686 
3687     private class Handler implements CellEditorListener, FocusListener,
3688                   KeyListener, MouseListener, MouseMotionListener,
3689                   PropertyChangeListener, TreeExpansionListener,
3690                   TreeModelListener, TreeSelectionListener,
3691                   BeforeDrag {
3692         //
3693         // KeyListener
3694         //
3695         private String prefix = "";
3696         private String typedString = "";
3697         private long lastTime = 0L;
3698 
3699         /**
3700          * Invoked when a key has been typed.
3701          *
3702          * Moves the keyboard focus to the first element whose prefix matches the
3703          * sequence of alphanumeric keys pressed by the user with delay less
3704          * than value of <code>timeFactor</code> property (or 1000 milliseconds
3705          * if it is not defined). Subsequent same key presses move the keyboard
3706          * focus to the next object that starts with the same letter until another
3707          * key is pressed, then it is treated as the prefix with appropriate number
3708          * of the same letters followed by first typed another letter.
3709          */
keyTyped(KeyEvent e)3710         public void keyTyped(KeyEvent e) {
3711             // handle first letter navigation
3712             if(tree != null && tree.getRowCount()>0 && tree.hasFocus() &&
3713                tree.isEnabled()) {
3714                 if (e.isAltDown() || BasicGraphicsUtils.isMenuShortcutKeyDown(e) ||
3715                     isNavigationKey(e)) {
3716                     return;
3717                 }
3718                 boolean startingFromSelection = true;
3719 
3720                 char c = e.getKeyChar();
3721 
3722                 long time = e.getWhen();
3723                 int startingRow = tree.getLeadSelectionRow();
3724                 if (time - lastTime < timeFactor) {
3725                     typedString += c;
3726                     if((prefix.length() == 1) && (c == prefix.charAt(0))) {
3727                         // Subsequent same key presses move the keyboard focus to the next
3728                         // object that starts with the same letter.
3729                         startingRow++;
3730                     } else {
3731                         prefix = typedString;
3732                     }
3733                 } else {
3734                     startingRow++;
3735                     typedString = "" + c;
3736                     prefix = typedString;
3737                 }
3738                 lastTime = time;
3739 
3740                 if (startingRow < 0 || startingRow >= tree.getRowCount()) {
3741                     startingFromSelection = false;
3742                     startingRow = 0;
3743                 }
3744                 TreePath path = tree.getNextMatch(prefix, startingRow,
3745                                                   Position.Bias.Forward);
3746                 if (path != null) {
3747                     tree.setSelectionPath(path);
3748                     int row = getRowForPath(tree, path);
3749                     ensureRowsAreVisible(row, row);
3750                 } else if (startingFromSelection) {
3751                     path = tree.getNextMatch(prefix, 0,
3752                                              Position.Bias.Forward);
3753                     if (path != null) {
3754                         tree.setSelectionPath(path);
3755                         int row = getRowForPath(tree, path);
3756                         ensureRowsAreVisible(row, row);
3757                     }
3758                 }
3759             }
3760         }
3761 
3762         /**
3763          * Invoked when a key has been pressed.
3764          *
3765          * Checks to see if the key event is a navigation key to prevent
3766          * dispatching these keys for the first letter navigation.
3767          */
keyPressed(KeyEvent e)3768         public void keyPressed(KeyEvent e) {
3769             if (tree != null && isNavigationKey(e)) {
3770                 prefix = "";
3771                 typedString = "";
3772                 lastTime = 0L;
3773             }
3774         }
3775 
keyReleased(KeyEvent e)3776         public void keyReleased(KeyEvent e) {
3777         }
3778 
3779         /**
3780          * Returns whether or not the supplied key event maps to a key that is used for
3781          * navigation.  This is used for optimizing key input by only passing non-
3782          * navigation keys to the first letter navigation mechanism.
3783          */
isNavigationKey(KeyEvent event)3784         private boolean isNavigationKey(KeyEvent event) {
3785             InputMap inputMap = tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
3786             KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
3787 
3788             return inputMap != null && inputMap.get(key) != null;
3789         }
3790 
3791 
3792         //
3793         // PropertyChangeListener
3794         //
propertyChange(PropertyChangeEvent event)3795         public void propertyChange(PropertyChangeEvent event) {
3796             if (event.getSource() == treeSelectionModel) {
3797                 treeSelectionModel.resetRowSelection();
3798             }
3799             else if(event.getSource() == tree) {
3800                 String              changeName = event.getPropertyName();
3801 
3802                 if (changeName == JTree.LEAD_SELECTION_PATH_PROPERTY) {
3803                     if (!ignoreLAChange) {
3804                         updateLeadSelectionRow();
3805                         repaintPath((TreePath)event.getOldValue());
3806                         repaintPath((TreePath)event.getNewValue());
3807                     }
3808                 }
3809                 else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) {
3810                     if (!ignoreLAChange) {
3811                         repaintPath((TreePath)event.getOldValue());
3812                         repaintPath((TreePath)event.getNewValue());
3813                     }
3814                 }
3815                 if(changeName == JTree.CELL_RENDERER_PROPERTY) {
3816                     setCellRenderer((TreeCellRenderer)event.getNewValue());
3817                     redoTheLayout();
3818                 }
3819                 else if(changeName == JTree.TREE_MODEL_PROPERTY) {
3820                     setModel((TreeModel)event.getNewValue());
3821                 }
3822                 else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) {
3823                     setRootVisible(((Boolean)event.getNewValue()).
3824                                    booleanValue());
3825                 }
3826                 else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) {
3827                     setShowsRootHandles(((Boolean)event.getNewValue()).
3828                                         booleanValue());
3829                 }
3830                 else if(changeName == JTree.ROW_HEIGHT_PROPERTY) {
3831                     setRowHeight(((Integer)event.getNewValue()).
3832                                  intValue());
3833                 }
3834                 else if(changeName == JTree.CELL_EDITOR_PROPERTY) {
3835                     setCellEditor((TreeCellEditor)event.getNewValue());
3836                 }
3837                 else if(changeName == JTree.EDITABLE_PROPERTY) {
3838                     setEditable(((Boolean)event.getNewValue()).booleanValue());
3839                 }
3840                 else if(changeName == JTree.LARGE_MODEL_PROPERTY) {
3841                     setLargeModel(tree.isLargeModel());
3842                 }
3843                 else if(changeName == JTree.SELECTION_MODEL_PROPERTY) {
3844                     setSelectionModel(tree.getSelectionModel());
3845                 }
3846                 else if(changeName == "font"
3847                         || SwingUtilities2.isScaleChanged(event)) {
3848                     completeEditing();
3849                     if(treeState != null)
3850                         treeState.invalidateSizes();
3851                     updateSize();
3852                 }
3853                 else if (changeName == "componentOrientation") {
3854                     if (tree != null) {
3855                         leftToRight = BasicGraphicsUtils.isLeftToRight(tree);
3856                         redoTheLayout();
3857                         tree.treeDidChange();
3858 
3859                         InputMap km = getInputMap(JComponent.WHEN_FOCUSED);
3860                         SwingUtilities.replaceUIInputMap(tree,
3861                                                 JComponent.WHEN_FOCUSED, km);
3862                     }
3863                 } else if ("dropLocation" == changeName) {
3864                     JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue();
3865                     repaintDropLocation(oldValue);
3866                     repaintDropLocation(tree.getDropLocation());
3867                 }
3868             }
3869         }
3870 
repaintDropLocation(JTree.DropLocation loc)3871         private void repaintDropLocation(JTree.DropLocation loc) {
3872             if (loc == null) {
3873                 return;
3874             }
3875 
3876             Rectangle r;
3877 
3878             if (isDropLine(loc)) {
3879                 r = getDropLineRect(loc);
3880             } else {
3881                 r = tree.getPathBounds(loc.getPath());
3882             }
3883 
3884             if (r != null) {
3885                 tree.repaint(r);
3886             }
3887         }
3888 
3889         //
3890         // MouseListener
3891         //
3892 
3893         // Whether or not the mouse press (which is being considered as part
3894         // of a drag sequence) also caused the selection change to be fully
3895         // processed.
3896         private boolean dragPressDidSelection;
3897 
3898         // Set to true when a drag gesture has been fully recognized and DnD
3899         // begins. Use this to ignore further mouse events which could be
3900         // delivered if DnD is cancelled (via ESCAPE for example)
3901         private boolean dragStarted;
3902 
3903         // The path over which the press occurred and the press event itself
3904         private TreePath pressedPath;
3905         private MouseEvent pressedEvent;
3906 
3907         // Used to detect whether the press event causes a selection change.
3908         // If it does, we won't try to start editing on the release.
3909         private boolean valueChangedOnPress;
3910 
isActualPath(TreePath path, int x, int y)3911         private boolean isActualPath(TreePath path, int x, int y) {
3912             if (path == null) {
3913                 return false;
3914             }
3915 
3916             Rectangle bounds = getPathBounds(tree, path);
3917             if (bounds == null || y > (bounds.y + bounds.height)) {
3918                 return false;
3919             }
3920 
3921             return (x >= bounds.x) && (x <= (bounds.x + bounds.width));
3922         }
3923 
mouseClicked(MouseEvent e)3924         public void mouseClicked(MouseEvent e) {
3925         }
3926 
mouseEntered(MouseEvent e)3927         public void mouseEntered(MouseEvent e) {
3928         }
3929 
mouseExited(MouseEvent e)3930         public void mouseExited(MouseEvent e) {
3931         }
3932 
3933         /**
3934          * Invoked when a mouse button has been pressed on a component.
3935          */
mousePressed(MouseEvent e)3936         public void mousePressed(MouseEvent e) {
3937             if (SwingUtilities2.shouldIgnore(e, tree)) {
3938                 return;
3939             }
3940 
3941             // if we can't stop any ongoing editing, do nothing
3942             if (isEditing(tree) && tree.getInvokesStopCellEditing()
3943                                 && !stopEditing(tree)) {
3944                 return;
3945             }
3946 
3947             completeEditing();
3948 
3949             pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY());
3950 
3951             if (tree.getDragEnabled()) {
3952                 mousePressedDND(e);
3953             } else {
3954                 SwingUtilities2.adjustFocus(tree);
3955                 handleSelection(e);
3956             }
3957         }
3958 
mousePressedDND(MouseEvent e)3959         private void mousePressedDND(MouseEvent e) {
3960             pressedEvent = e;
3961             boolean grabFocus = true;
3962             dragStarted = false;
3963             valueChangedOnPress = false;
3964 
3965             // if we have a valid path and this is a drag initiating event
3966             if (isActualPath(pressedPath, e.getX(), e.getY()) &&
3967                     DragRecognitionSupport.mousePressed(e)) {
3968 
3969                 dragPressDidSelection = false;
3970 
3971                 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
3972                     // do nothing for control - will be handled on release
3973                     // or when drag starts
3974                     return;
3975                 } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) {
3976                     // clicking on something that's already selected
3977                     // and need to make it the lead now
3978                     setAnchorSelectionPath(pressedPath);
3979                     setLeadSelectionPath(pressedPath, true);
3980                     return;
3981                 }
3982 
3983                 dragPressDidSelection = true;
3984 
3985                 // could be a drag initiating event - don't grab focus
3986                 grabFocus = false;
3987             }
3988 
3989             if (grabFocus) {
3990                 SwingUtilities2.adjustFocus(tree);
3991             }
3992 
3993             handleSelection(e);
3994         }
3995 
handleSelection(MouseEvent e)3996         void handleSelection(MouseEvent e) {
3997             if(pressedPath != null) {
3998                 Rectangle bounds = getPathBounds(tree, pressedPath);
3999 
4000                 if (bounds == null || e.getY() >= (bounds.y + bounds.height)) {
4001                     return;
4002                 }
4003 
4004                 // Preferably checkForClickInExpandControl could take
4005                 // the Event to do this it self!
4006                 if(SwingUtilities.isLeftMouseButton(e)) {
4007                     checkForClickInExpandControl(pressedPath, e.getX(), e.getY());
4008                 }
4009 
4010                 int x = e.getX();
4011 
4012                 // Perhaps they clicked the cell itself. If so,
4013                 // select it.
4014                 if (x >= bounds.x && x < (bounds.x + bounds.width)) {
4015                     if (tree.getDragEnabled() || !startEditing(pressedPath, e)) {
4016                         selectPathForEvent(pressedPath, e);
4017                     }
4018                 }
4019             }
4020         }
4021 
dragStarting(MouseEvent me)4022         public void dragStarting(MouseEvent me) {
4023             dragStarted = true;
4024 
4025             if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) {
4026                 tree.addSelectionPath(pressedPath);
4027                 setAnchorSelectionPath(pressedPath);
4028                 setLeadSelectionPath(pressedPath, true);
4029             }
4030 
4031             pressedEvent = null;
4032             pressedPath = null;
4033         }
4034 
mouseDragged(MouseEvent e)4035         public void mouseDragged(MouseEvent e) {
4036             if (SwingUtilities2.shouldIgnore(e, tree)) {
4037                 return;
4038             }
4039 
4040             if (tree.getDragEnabled()) {
4041                 DragRecognitionSupport.mouseDragged(e, this);
4042             }
4043         }
4044 
4045         /**
4046          * Invoked when the mouse button has been moved on a component
4047          * (with no buttons no down).
4048          */
mouseMoved(MouseEvent e)4049         public void mouseMoved(MouseEvent e) {
4050         }
4051 
mouseReleased(MouseEvent e)4052         public void mouseReleased(MouseEvent e) {
4053             if (SwingUtilities2.shouldIgnore(e, tree)) {
4054                 return;
4055             }
4056 
4057             if (tree.getDragEnabled()) {
4058                 mouseReleasedDND(e);
4059             }
4060 
4061             pressedEvent = null;
4062             pressedPath = null;
4063         }
4064 
mouseReleasedDND(MouseEvent e)4065         private void mouseReleasedDND(MouseEvent e) {
4066             MouseEvent me = DragRecognitionSupport.mouseReleased(e);
4067             if (me != null) {
4068                 SwingUtilities2.adjustFocus(tree);
4069                 if (!dragPressDidSelection) {
4070                     handleSelection(me);
4071                 }
4072             }
4073 
4074             if (!dragStarted) {
4075 
4076                 // Note: We don't give the tree a chance to start editing if the
4077                 // mouse press caused a selection change. Otherwise the default
4078                 // tree cell editor will start editing on EVERY press and
4079                 // release. If it turns out that this affects some editors, we
4080                 // can always parameterize this with a client property. ex:
4081                 //
4082                 // if (pressedPath != null &&
4083                 //         (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") ||
4084                 //          !valueChangedOnPress) && ...
4085                 if (pressedPath != null && !valueChangedOnPress &&
4086                         isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) {
4087 
4088                     startEditingOnRelease(pressedPath, pressedEvent, e);
4089                 }
4090             }
4091         }
4092 
4093         //
4094         // FocusListener
4095         //
focusGained(FocusEvent e)4096         public void focusGained(FocusEvent e) {
4097             if(tree != null) {
4098                 Rectangle                 pBounds;
4099 
4100                 pBounds = getPathBounds(tree, tree.getLeadSelectionPath());
4101                 if(pBounds != null)
4102                     tree.repaint(getRepaintPathBounds(pBounds));
4103                 pBounds = getPathBounds(tree, getLeadSelectionPath());
4104                 if(pBounds != null)
4105                     tree.repaint(getRepaintPathBounds(pBounds));
4106             }
4107         }
4108 
focusLost(FocusEvent e)4109         public void focusLost(FocusEvent e) {
4110             focusGained(e);
4111         }
4112 
4113         //
4114         // CellEditorListener
4115         //
editingStopped(ChangeEvent e)4116         public void editingStopped(ChangeEvent e) {
4117             completeEditing(false, false, true);
4118         }
4119 
4120         /** Messaged when editing has been canceled in the tree. */
editingCanceled(ChangeEvent e)4121         public void editingCanceled(ChangeEvent e) {
4122             completeEditing(false, false, false);
4123         }
4124 
4125 
4126         //
4127         // TreeSelectionListener
4128         //
valueChanged(TreeSelectionEvent event)4129         public void valueChanged(TreeSelectionEvent event) {
4130             valueChangedOnPress = true;
4131 
4132             // Stop editing
4133             completeEditing();
4134             // Make sure all the paths are visible, if necessary.
4135             // PENDING: This should be tweaked when isAdjusting is added
4136             if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) {
4137                 TreePath[]           paths = treeSelectionModel
4138                                          .getSelectionPaths();
4139 
4140                 if(paths != null) {
4141                     for(int counter = paths.length - 1; counter >= 0;
4142                         counter--) {
4143                         TreePath path = paths[counter].getParentPath();
4144                         boolean expand = true;
4145 
4146                         while (path != null) {
4147                             // Indicates this path isn't valid anymore,
4148                             // we shouldn't attempt to expand it then.
4149                             if (treeModel.isLeaf(path.getLastPathComponent())){
4150                                 expand = false;
4151                                 path = null;
4152                             }
4153                             else {
4154                                 path = path.getParentPath();
4155                             }
4156                         }
4157                         if (expand) {
4158                             tree.makeVisible(paths[counter]);
4159                         }
4160                     }
4161                 }
4162             }
4163 
4164             TreePath oldLead = getLeadSelectionPath();
4165             lastSelectedRow = tree.getMinSelectionRow();
4166             TreePath lead = tree.getSelectionModel().getLeadSelectionPath();
4167             setAnchorSelectionPath(lead);
4168             setLeadSelectionPath(lead);
4169 
4170             TreePath[]       changedPaths = event.getPaths();
4171             Rectangle        nodeBounds;
4172             Rectangle        visRect = tree.getVisibleRect();
4173             boolean          paintPaths = true;
4174             int              nWidth = tree.getWidth();
4175 
4176             if(changedPaths != null) {
4177                 int              counter, maxCounter = changedPaths.length;
4178 
4179                 if(maxCounter > 4) {
4180                     tree.repaint();
4181                     paintPaths = false;
4182                 }
4183                 else {
4184                     for (counter = 0; counter < maxCounter; counter++) {
4185                         nodeBounds = getPathBounds(tree,
4186                                                    changedPaths[counter]);
4187                         if(nodeBounds != null &&
4188                            visRect.intersects(nodeBounds))
4189                             tree.repaint(0, nodeBounds.y, nWidth,
4190                                          nodeBounds.height);
4191                     }
4192                 }
4193             }
4194             if(paintPaths) {
4195                 nodeBounds = getPathBounds(tree, oldLead);
4196                 if(nodeBounds != null && visRect.intersects(nodeBounds))
4197                     tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
4198                 nodeBounds = getPathBounds(tree, lead);
4199                 if(nodeBounds != null && visRect.intersects(nodeBounds))
4200                     tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
4201             }
4202         }
4203 
4204 
4205         //
4206         // TreeExpansionListener
4207         //
treeExpanded(TreeExpansionEvent event)4208         public void treeExpanded(TreeExpansionEvent event) {
4209             if(event != null && tree != null) {
4210                 TreePath      path = event.getPath();
4211 
4212                 updateExpandedDescendants(path);
4213             }
4214         }
4215 
treeCollapsed(TreeExpansionEvent event)4216         public void treeCollapsed(TreeExpansionEvent event) {
4217             if(event != null && tree != null) {
4218                 TreePath        path = event.getPath();
4219 
4220                 completeEditing();
4221                 if(path != null && tree.isVisible(path)) {
4222                     treeState.setExpandedState(path, false);
4223                     updateLeadSelectionRow();
4224                     updateSize();
4225                 }
4226             }
4227         }
4228 
4229         //
4230         // TreeModelListener
4231         //
treeNodesChanged(TreeModelEvent e)4232         public void treeNodesChanged(TreeModelEvent e) {
4233             if(treeState != null && e != null) {
4234                 TreePath parentPath = SwingUtilities2.getTreePath(e, getModel());
4235                 int[] indices = e.getChildIndices();
4236                 if (indices == null || indices.length == 0) {
4237                     // The root has changed
4238                     treeState.treeNodesChanged(e);
4239                     updateSize();
4240                 }
4241                 else if (treeState.isExpanded(parentPath)) {
4242                     // Changed nodes are visible
4243                     // Find the minimum index, we only need paint from there
4244                     // down.
4245                     int minIndex = indices[0];
4246                     for (int i = indices.length - 1; i > 0; i--) {
4247                         minIndex = Math.min(indices[i], minIndex);
4248                     }
4249                     Object minChild = treeModel.getChild(
4250                             parentPath.getLastPathComponent(), minIndex);
4251                     TreePath minPath = parentPath.pathByAddingChild(minChild);
4252                     Rectangle minBounds = getPathBounds(tree, minPath);
4253 
4254                     // Forward to the treestate
4255                     treeState.treeNodesChanged(e);
4256 
4257                     // Mark preferred size as bogus.
4258                     updateSize0();
4259 
4260                     // And repaint
4261                     Rectangle newMinBounds = getPathBounds(tree, minPath);
4262                     if (minBounds == null || newMinBounds == null) {
4263                         return;
4264                     }
4265 
4266                     if (indices.length == 1 &&
4267                             newMinBounds.height == minBounds.height) {
4268                         tree.repaint(0, minBounds.y, tree.getWidth(),
4269                                      minBounds.height);
4270                     }
4271                     else {
4272                         tree.repaint(0, minBounds.y, tree.getWidth(),
4273                                      tree.getHeight() - minBounds.y);
4274                     }
4275                 }
4276                 else {
4277                     // Nodes that changed aren't visible.  No need to paint
4278                     treeState.treeNodesChanged(e);
4279                 }
4280             }
4281         }
4282 
treeNodesInserted(TreeModelEvent e)4283         public void treeNodesInserted(TreeModelEvent e) {
4284             if(treeState != null && e != null) {
4285                 treeState.treeNodesInserted(e);
4286 
4287                 updateLeadSelectionRow();
4288 
4289                 TreePath       path = SwingUtilities2.getTreePath(e, getModel());
4290 
4291                 if(treeState.isExpanded(path)) {
4292                     updateSize();
4293                 }
4294                 else {
4295                     // PENDING(sky): Need a method in TreeModelEvent
4296                     // that can return the count, getChildIndices allocs
4297                     // a new array!
4298                     int[]      indices = e.getChildIndices();
4299                     int        childCount = treeModel.getChildCount
4300                                             (path.getLastPathComponent());
4301 
4302                     if(indices != null && (childCount - indices.length) == 0)
4303                         updateSize();
4304                 }
4305             }
4306         }
4307 
treeNodesRemoved(TreeModelEvent e)4308         public void treeNodesRemoved(TreeModelEvent e) {
4309             if(treeState != null && e != null) {
4310                 treeState.treeNodesRemoved(e);
4311 
4312                 updateLeadSelectionRow();
4313 
4314                 TreePath       path = SwingUtilities2.getTreePath(e, getModel());
4315 
4316                 if(treeState.isExpanded(path) ||
4317                    treeModel.getChildCount(path.getLastPathComponent()) == 0)
4318                     updateSize();
4319             }
4320         }
4321 
treeStructureChanged(TreeModelEvent e)4322         public void treeStructureChanged(TreeModelEvent e) {
4323             if(treeState != null && e != null) {
4324                 treeState.treeStructureChanged(e);
4325 
4326                 updateLeadSelectionRow();
4327 
4328                 TreePath       pPath = SwingUtilities2.getTreePath(e, getModel());
4329 
4330                 if (pPath != null) {
4331                     pPath = pPath.getParentPath();
4332                 }
4333                 if(pPath == null || treeState.isExpanded(pPath))
4334                     updateSize();
4335             }
4336         }
4337     }
4338 
4339 
4340 
4341     private static class Actions extends UIAction {
4342         private static final String SELECT_PREVIOUS = "selectPrevious";
4343         private static final String SELECT_PREVIOUS_CHANGE_LEAD =
4344                              "selectPreviousChangeLead";
4345         private static final String SELECT_PREVIOUS_EXTEND_SELECTION =
4346                              "selectPreviousExtendSelection";
4347         private static final String SELECT_NEXT = "selectNext";
4348         private static final String SELECT_NEXT_CHANGE_LEAD =
4349                                     "selectNextChangeLead";
4350         private static final String SELECT_NEXT_EXTEND_SELECTION =
4351                                     "selectNextExtendSelection";
4352         private static final String SELECT_CHILD = "selectChild";
4353         private static final String SELECT_CHILD_CHANGE_LEAD =
4354                                     "selectChildChangeLead";
4355         private static final String SELECT_PARENT = "selectParent";
4356         private static final String SELECT_PARENT_CHANGE_LEAD =
4357                                     "selectParentChangeLead";
4358         private static final String SCROLL_UP_CHANGE_SELECTION =
4359                                     "scrollUpChangeSelection";
4360         private static final String SCROLL_UP_CHANGE_LEAD =
4361                                     "scrollUpChangeLead";
4362         private static final String SCROLL_UP_EXTEND_SELECTION =
4363                                     "scrollUpExtendSelection";
4364         private static final String SCROLL_DOWN_CHANGE_SELECTION =
4365                                     "scrollDownChangeSelection";
4366         private static final String SCROLL_DOWN_EXTEND_SELECTION =
4367                                     "scrollDownExtendSelection";
4368         private static final String SCROLL_DOWN_CHANGE_LEAD =
4369                                     "scrollDownChangeLead";
4370         private static final String SELECT_FIRST = "selectFirst";
4371         private static final String SELECT_FIRST_CHANGE_LEAD =
4372                                     "selectFirstChangeLead";
4373         private static final String SELECT_FIRST_EXTEND_SELECTION =
4374                                     "selectFirstExtendSelection";
4375         private static final String SELECT_LAST = "selectLast";
4376         private static final String SELECT_LAST_CHANGE_LEAD =
4377                                     "selectLastChangeLead";
4378         private static final String SELECT_LAST_EXTEND_SELECTION =
4379                                     "selectLastExtendSelection";
4380         private static final String TOGGLE = "toggle";
4381         private static final String CANCEL_EDITING = "cancel";
4382         private static final String START_EDITING = "startEditing";
4383         private static final String SELECT_ALL = "selectAll";
4384         private static final String CLEAR_SELECTION = "clearSelection";
4385         private static final String SCROLL_LEFT = "scrollLeft";
4386         private static final String SCROLL_RIGHT = "scrollRight";
4387         private static final String SCROLL_LEFT_EXTEND_SELECTION =
4388                                     "scrollLeftExtendSelection";
4389         private static final String SCROLL_RIGHT_EXTEND_SELECTION =
4390                                     "scrollRightExtendSelection";
4391         private static final String SCROLL_RIGHT_CHANGE_LEAD =
4392                                     "scrollRightChangeLead";
4393         private static final String SCROLL_LEFT_CHANGE_LEAD =
4394                                     "scrollLeftChangeLead";
4395         private static final String EXPAND = "expand";
4396         private static final String COLLAPSE = "collapse";
4397         private static final String MOVE_SELECTION_TO_PARENT =
4398                                     "moveSelectionToParent";
4399 
4400         // add the lead item to the selection without changing lead or anchor
4401         private static final String ADD_TO_SELECTION = "addToSelection";
4402 
4403         // toggle the selected state of the lead item and move the anchor to it
4404         private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
4405 
4406         // extend the selection to the lead item
4407         private static final String EXTEND_TO = "extendTo";
4408 
4409         // move the anchor to the lead and ensure only that item is selected
4410         private static final String MOVE_SELECTION_TO = "moveSelectionTo";
4411 
Actions()4412         Actions() {
4413             super(null);
4414         }
4415 
Actions(String key)4416         Actions(String key) {
4417             super(key);
4418         }
4419 
4420         @Override
accept(Object o)4421         public boolean accept(Object o) {
4422             if (o instanceof JTree) {
4423                 if (getName() == CANCEL_EDITING) {
4424                     return ((JTree)o).isEditing();
4425                 }
4426             }
4427             return true;
4428         }
4429 
actionPerformed(ActionEvent e)4430         public void actionPerformed(ActionEvent e) {
4431             JTree tree = (JTree)e.getSource();
4432             BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType(
4433                              tree.getUI(), BasicTreeUI.class);
4434             if (ui == null) {
4435                 return;
4436             }
4437             String key = getName();
4438             if (key == SELECT_PREVIOUS) {
4439                 increment(tree, ui, -1, false, true);
4440             }
4441             else if (key == SELECT_PREVIOUS_CHANGE_LEAD) {
4442                 increment(tree, ui, -1, false, false);
4443             }
4444             else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) {
4445                 increment(tree, ui, -1, true, true);
4446             }
4447             else if (key == SELECT_NEXT) {
4448                 increment(tree, ui, 1, false, true);
4449             }
4450             else if (key == SELECT_NEXT_CHANGE_LEAD) {
4451                 increment(tree, ui, 1, false, false);
4452             }
4453             else if (key == SELECT_NEXT_EXTEND_SELECTION) {
4454                 increment(tree, ui, 1, true, true);
4455             }
4456             else if (key == SELECT_CHILD) {
4457                 traverse(tree, ui, 1, true);
4458             }
4459             else if (key == SELECT_CHILD_CHANGE_LEAD) {
4460                 traverse(tree, ui, 1, false);
4461             }
4462             else if (key == SELECT_PARENT) {
4463                 traverse(tree, ui, -1, true);
4464             }
4465             else if (key == SELECT_PARENT_CHANGE_LEAD) {
4466                 traverse(tree, ui, -1, false);
4467             }
4468             else if (key == SCROLL_UP_CHANGE_SELECTION) {
4469                 page(tree, ui, -1, false, true);
4470             }
4471             else if (key == SCROLL_UP_CHANGE_LEAD) {
4472                 page(tree, ui, -1, false, false);
4473             }
4474             else if (key == SCROLL_UP_EXTEND_SELECTION) {
4475                 page(tree, ui, -1, true, true);
4476             }
4477             else if (key == SCROLL_DOWN_CHANGE_SELECTION) {
4478                 page(tree, ui, 1, false, true);
4479             }
4480             else if (key == SCROLL_DOWN_EXTEND_SELECTION) {
4481                 page(tree, ui, 1, true, true);
4482             }
4483             else if (key == SCROLL_DOWN_CHANGE_LEAD) {
4484                 page(tree, ui, 1, false, false);
4485             }
4486             else if (key == SELECT_FIRST) {
4487                 home(tree, ui, -1, false, true);
4488             }
4489             else if (key == SELECT_FIRST_CHANGE_LEAD) {
4490                 home(tree, ui, -1, false, false);
4491             }
4492             else if (key == SELECT_FIRST_EXTEND_SELECTION) {
4493                 home(tree, ui, -1, true, true);
4494             }
4495             else if (key == SELECT_LAST) {
4496                 home(tree, ui, 1, false, true);
4497             }
4498             else if (key == SELECT_LAST_CHANGE_LEAD) {
4499                 home(tree, ui, 1, false, false);
4500             }
4501             else if (key == SELECT_LAST_EXTEND_SELECTION) {
4502                 home(tree, ui, 1, true, true);
4503             }
4504             else if (key == TOGGLE) {
4505                 toggle(tree, ui);
4506             }
4507             else if (key == CANCEL_EDITING) {
4508                 cancelEditing(tree, ui);
4509             }
4510             else if (key == START_EDITING) {
4511                 startEditing(tree, ui);
4512             }
4513             else if (key == SELECT_ALL) {
4514                 selectAll(tree, ui, true);
4515             }
4516             else if (key == CLEAR_SELECTION) {
4517                 selectAll(tree, ui, false);
4518             }
4519             else if (key == ADD_TO_SELECTION) {
4520                 if (ui.getRowCount(tree) > 0) {
4521                     int lead = ui.getLeadSelectionRow();
4522                     if (!tree.isRowSelected(lead)) {
4523                         TreePath aPath = ui.getAnchorSelectionPath();
4524                         tree.addSelectionRow(lead);
4525                         ui.setAnchorSelectionPath(aPath);
4526                     }
4527                 }
4528             }
4529             else if (key == TOGGLE_AND_ANCHOR) {
4530                 if (ui.getRowCount(tree) > 0) {
4531                     int lead = ui.getLeadSelectionRow();
4532                     TreePath lPath = ui.getLeadSelectionPath();
4533                     if (!tree.isRowSelected(lead)) {
4534                         tree.addSelectionRow(lead);
4535                     } else {
4536                         tree.removeSelectionRow(lead);
4537                         ui.setLeadSelectionPath(lPath);
4538                     }
4539                     ui.setAnchorSelectionPath(lPath);
4540                 }
4541             }
4542             else if (key == EXTEND_TO) {
4543                 extendSelection(tree, ui);
4544             }
4545             else if (key == MOVE_SELECTION_TO) {
4546                 if (ui.getRowCount(tree) > 0) {
4547                     int lead = ui.getLeadSelectionRow();
4548                     tree.setSelectionInterval(lead, lead);
4549                 }
4550             }
4551             else if (key == SCROLL_LEFT) {
4552                 scroll(tree, ui, SwingConstants.HORIZONTAL, -10);
4553             }
4554             else if (key == SCROLL_RIGHT) {
4555                 scroll(tree, ui, SwingConstants.HORIZONTAL, 10);
4556             }
4557             else if (key == SCROLL_LEFT_EXTEND_SELECTION) {
4558                 scrollChangeSelection(tree, ui, -1, true, true);
4559             }
4560             else if (key == SCROLL_RIGHT_EXTEND_SELECTION) {
4561                 scrollChangeSelection(tree, ui, 1, true, true);
4562             }
4563             else if (key == SCROLL_RIGHT_CHANGE_LEAD) {
4564                 scrollChangeSelection(tree, ui, 1, false, false);
4565             }
4566             else if (key == SCROLL_LEFT_CHANGE_LEAD) {
4567                 scrollChangeSelection(tree, ui, -1, false, false);
4568             }
4569             else if (key == EXPAND) {
4570                 expand(tree, ui);
4571             }
4572             else if (key == COLLAPSE) {
4573                 collapse(tree, ui);
4574             }
4575             else if (key == MOVE_SELECTION_TO_PARENT) {
4576                 moveSelectionToParent(tree, ui);
4577             }
4578         }
4579 
scrollChangeSelection(JTree tree, BasicTreeUI ui, int direction, boolean addToSelection, boolean changeSelection)4580         private void scrollChangeSelection(JTree tree, BasicTreeUI ui,
4581                            int direction, boolean addToSelection,
4582                            boolean changeSelection) {
4583             int           rowCount;
4584 
4585             if((rowCount = ui.getRowCount(tree)) > 0 &&
4586                 ui.treeSelectionModel != null) {
4587                 TreePath          newPath;
4588                 Rectangle         visRect = tree.getVisibleRect();
4589 
4590                 if (direction == -1) {
4591                     newPath = ui.getClosestPathForLocation(tree, visRect.x,
4592                                                         visRect.y);
4593                     visRect.x = Math.max(0, visRect.x - visRect.width);
4594                 }
4595                 else {
4596                     visRect.x = Math.min(Math.max(0, tree.getWidth() -
4597                                    visRect.width), visRect.x + visRect.width);
4598                     newPath = ui.getClosestPathForLocation(tree, visRect.x,
4599                                                  visRect.y + visRect.height);
4600                 }
4601                 // Scroll
4602                 tree.scrollRectToVisible(visRect);
4603                 // select
4604                 if (addToSelection) {
4605                     ui.extendSelection(newPath);
4606                 }
4607                 else if(changeSelection) {
4608                     tree.setSelectionPath(newPath);
4609                 }
4610                 else {
4611                     ui.setLeadSelectionPath(newPath, true);
4612                 }
4613             }
4614         }
4615 
scroll(JTree component, BasicTreeUI ui, int direction, int amount)4616         private void scroll(JTree component, BasicTreeUI ui, int direction,
4617                             int amount) {
4618             Rectangle visRect = component.getVisibleRect();
4619             Dimension size = component.getSize();
4620             if (direction == SwingConstants.HORIZONTAL) {
4621                 visRect.x += amount;
4622                 visRect.x = Math.max(0, visRect.x);
4623                 visRect.x = Math.min(Math.max(0, size.width - visRect.width),
4624                                      visRect.x);
4625             }
4626             else {
4627                 visRect.y += amount;
4628                 visRect.y = Math.max(0, visRect.y);
4629                 visRect.y = Math.min(Math.max(0, size.width - visRect.height),
4630                                      visRect.y);
4631             }
4632             component.scrollRectToVisible(visRect);
4633         }
4634 
extendSelection(JTree tree, BasicTreeUI ui)4635         private void extendSelection(JTree tree, BasicTreeUI ui) {
4636             if (ui.getRowCount(tree) > 0) {
4637                 int       lead = ui.getLeadSelectionRow();
4638 
4639                 if (lead != -1) {
4640                     TreePath      leadP = ui.getLeadSelectionPath();
4641                     TreePath      aPath = ui.getAnchorSelectionPath();
4642                     int           aRow = ui.getRowForPath(tree, aPath);
4643 
4644                     if(aRow == -1)
4645                         aRow = 0;
4646                     tree.setSelectionInterval(aRow, lead);
4647                     ui.setLeadSelectionPath(leadP);
4648                     ui.setAnchorSelectionPath(aPath);
4649                 }
4650             }
4651         }
4652 
selectAll(JTree tree, BasicTreeUI ui, boolean selectAll)4653         private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) {
4654             int                   rowCount = ui.getRowCount(tree);
4655 
4656             if(rowCount > 0) {
4657                 if(selectAll) {
4658                     if (tree.getSelectionModel().getSelectionMode() ==
4659                             TreeSelectionModel.SINGLE_TREE_SELECTION) {
4660 
4661                         int lead = ui.getLeadSelectionRow();
4662                         if (lead != -1) {
4663                             tree.setSelectionRow(lead);
4664                         } else if (tree.getMinSelectionRow() == -1) {
4665                             tree.setSelectionRow(0);
4666                             ui.ensureRowsAreVisible(0, 0);
4667                         }
4668                         return;
4669                     }
4670 
4671                     TreePath      lastPath = ui.getLeadSelectionPath();
4672                     TreePath      aPath = ui.getAnchorSelectionPath();
4673 
4674                     if(lastPath != null && !tree.isVisible(lastPath)) {
4675                         lastPath = null;
4676                     }
4677                     tree.setSelectionInterval(0, rowCount - 1);
4678                     if(lastPath != null) {
4679                         ui.setLeadSelectionPath(lastPath);
4680                     }
4681                     if(aPath != null && tree.isVisible(aPath)) {
4682                         ui.setAnchorSelectionPath(aPath);
4683                     }
4684                 }
4685                 else {
4686                     TreePath      lastPath = ui.getLeadSelectionPath();
4687                     TreePath      aPath = ui.getAnchorSelectionPath();
4688 
4689                     tree.clearSelection();
4690                     ui.setAnchorSelectionPath(aPath);
4691                     ui.setLeadSelectionPath(lastPath);
4692                 }
4693             }
4694         }
4695 
startEditing(JTree tree, BasicTreeUI ui)4696         private void startEditing(JTree tree, BasicTreeUI ui) {
4697             TreePath   lead = ui.getLeadSelectionPath();
4698             int        editRow = (lead != null) ?
4699                                      ui.getRowForPath(tree, lead) : -1;
4700 
4701             if(editRow != -1) {
4702                 tree.startEditingAtPath(lead);
4703             }
4704         }
4705 
cancelEditing(JTree tree, BasicTreeUI ui)4706         private void cancelEditing(JTree tree, BasicTreeUI ui) {
4707             tree.cancelEditing();
4708         }
4709 
toggle(JTree tree, BasicTreeUI ui)4710         private void toggle(JTree tree, BasicTreeUI ui) {
4711             int            selRow = ui.getLeadSelectionRow();
4712 
4713             if(selRow != -1 && !ui.isLeaf(selRow)) {
4714                 TreePath aPath = ui.getAnchorSelectionPath();
4715                 TreePath lPath = ui.getLeadSelectionPath();
4716 
4717                 ui.toggleExpandState(ui.getPathForRow(tree, selRow));
4718                 ui.setAnchorSelectionPath(aPath);
4719                 ui.setLeadSelectionPath(lPath);
4720             }
4721         }
4722 
expand(JTree tree, BasicTreeUI ui)4723         private void expand(JTree tree, BasicTreeUI ui) {
4724             int selRow = ui.getLeadSelectionRow();
4725             tree.expandRow(selRow);
4726         }
4727 
collapse(JTree tree, BasicTreeUI ui)4728         private void collapse(JTree tree, BasicTreeUI ui) {
4729             int selRow = ui.getLeadSelectionRow();
4730             tree.collapseRow(selRow);
4731         }
4732 
increment(JTree tree, BasicTreeUI ui, int direction, boolean addToSelection, boolean changeSelection)4733         private void increment(JTree tree, BasicTreeUI ui, int direction,
4734                                boolean addToSelection,
4735                                boolean changeSelection) {
4736 
4737             // disable moving of lead unless in discontiguous mode
4738             if (!addToSelection && !changeSelection &&
4739                     tree.getSelectionModel().getSelectionMode() !=
4740                         TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
4741                 changeSelection = true;
4742             }
4743 
4744             int              rowCount;
4745 
4746             if(ui.treeSelectionModel != null &&
4747                   (rowCount = tree.getRowCount()) > 0) {
4748                 int                  selIndex = ui.getLeadSelectionRow();
4749                 int                  newIndex;
4750 
4751                 if(selIndex == -1) {
4752                     if(direction == 1)
4753                         newIndex = 0;
4754                     else
4755                         newIndex = rowCount - 1;
4756                 }
4757                 else
4758                     /* Aparently people don't like wrapping;( */
4759                     newIndex = Math.min(rowCount - 1, Math.max
4760                                         (0, (selIndex + direction)));
4761                 if(addToSelection && ui.treeSelectionModel.
4762                         getSelectionMode() != TreeSelectionModel.
4763                         SINGLE_TREE_SELECTION) {
4764                     ui.extendSelection(tree.getPathForRow(newIndex));
4765                 }
4766                 else if(changeSelection) {
4767                     tree.setSelectionInterval(newIndex, newIndex);
4768                 }
4769                 else {
4770                     ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true);
4771                 }
4772                 ui.ensureRowsAreVisible(newIndex, newIndex);
4773                 ui.lastSelectedRow = newIndex;
4774             }
4775         }
4776 
traverse(JTree tree, BasicTreeUI ui, int direction, boolean changeSelection)4777         private void traverse(JTree tree, BasicTreeUI ui, int direction,
4778                               boolean changeSelection) {
4779 
4780             // disable moving of lead unless in discontiguous mode
4781             if (!changeSelection &&
4782                     tree.getSelectionModel().getSelectionMode() !=
4783                         TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
4784                 changeSelection = true;
4785             }
4786 
4787             int                rowCount;
4788 
4789             if((rowCount = tree.getRowCount()) > 0) {
4790                 int               minSelIndex = ui.getLeadSelectionRow();
4791                 int               newIndex;
4792 
4793                 if(minSelIndex == -1)
4794                     newIndex = 0;
4795                 else {
4796                     /* Try and expand the node, otherwise go to next
4797                        node. */
4798                     if(direction == 1) {
4799                         TreePath minSelPath = ui.getPathForRow(tree, minSelIndex);
4800                         int childCount = tree.getModel().
4801                             getChildCount(minSelPath.getLastPathComponent());
4802                         newIndex = -1;
4803                         if (!ui.isLeaf(minSelIndex)) {
4804                             if (!tree.isExpanded(minSelIndex)) {
4805                                 ui.toggleExpandState(minSelPath);
4806                             }
4807                             else if (childCount > 0) {
4808                                 newIndex = Math.min(minSelIndex + 1, rowCount - 1);
4809                             }
4810                         }
4811                     }
4812                     /* Try to collapse node. */
4813                     else {
4814                         if(!ui.isLeaf(minSelIndex) &&
4815                            tree.isExpanded(minSelIndex)) {
4816                             ui.toggleExpandState(ui.getPathForRow
4817                                               (tree, minSelIndex));
4818                             newIndex = -1;
4819                         }
4820                         else {
4821                             TreePath         path = ui.getPathForRow(tree,
4822                                                                   minSelIndex);
4823 
4824                             if(path != null && path.getPathCount() > 1) {
4825                                 newIndex = ui.getRowForPath(tree, path.
4826                                                          getParentPath());
4827                             }
4828                             else
4829                                 newIndex = -1;
4830                         }
4831                     }
4832                 }
4833                 if(newIndex != -1) {
4834                     if(changeSelection) {
4835                         tree.setSelectionInterval(newIndex, newIndex);
4836                     }
4837                     else {
4838                         ui.setLeadSelectionPath(ui.getPathForRow(
4839                                                     tree, newIndex), true);
4840                     }
4841                     ui.ensureRowsAreVisible(newIndex, newIndex);
4842                 }
4843             }
4844         }
4845 
moveSelectionToParent(JTree tree, BasicTreeUI ui)4846         private void moveSelectionToParent(JTree tree, BasicTreeUI ui) {
4847             int selRow = ui.getLeadSelectionRow();
4848             TreePath path = ui.getPathForRow(tree, selRow);
4849             if (path != null && path.getPathCount() > 1) {
4850                 int  newIndex = ui.getRowForPath(tree, path.getParentPath());
4851                 if (newIndex != -1) {
4852                     tree.setSelectionInterval(newIndex, newIndex);
4853                     ui.ensureRowsAreVisible(newIndex, newIndex);
4854                 }
4855             }
4856         }
4857 
page(JTree tree, BasicTreeUI ui, int direction, boolean addToSelection, boolean changeSelection)4858         private void page(JTree tree, BasicTreeUI ui, int direction,
4859                           boolean addToSelection, boolean changeSelection) {
4860 
4861             // disable moving of lead unless in discontiguous mode
4862             if (!addToSelection && !changeSelection &&
4863                     tree.getSelectionModel().getSelectionMode() !=
4864                         TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
4865                 changeSelection = true;
4866             }
4867 
4868             int           rowCount;
4869 
4870             if((rowCount = ui.getRowCount(tree)) > 0 &&
4871                            ui.treeSelectionModel != null) {
4872                 Dimension         maxSize = tree.getSize();
4873                 TreePath          lead = ui.getLeadSelectionPath();
4874                 TreePath          newPath;
4875                 Rectangle         visRect = tree.getVisibleRect();
4876 
4877                 if(direction == -1) {
4878                     // up.
4879                     newPath = ui.getClosestPathForLocation(tree, visRect.x,
4880                                                          visRect.y);
4881                     if(newPath.equals(lead)) {
4882                         visRect.y = Math.max(0, visRect.y - visRect.height);
4883                         newPath = tree.getClosestPathForLocation(visRect.x,
4884                                                                  visRect.y);
4885                     }
4886                 }
4887                 else {
4888                     // down
4889                     visRect.y = Math.min(maxSize.height, visRect.y +
4890                                          visRect.height - 1);
4891                     newPath = tree.getClosestPathForLocation(visRect.x,
4892                                                              visRect.y);
4893                     if(newPath.equals(lead)) {
4894                         visRect.y = Math.min(maxSize.height, visRect.y +
4895                                              visRect.height - 1);
4896                         newPath = tree.getClosestPathForLocation(visRect.x,
4897                                                                  visRect.y);
4898                     }
4899                 }
4900                 Rectangle            newRect = ui.getPathBounds(tree, newPath);
4901                 if (newRect != null) {
4902                     newRect.x = visRect.x;
4903                     newRect.width = visRect.width;
4904                     if(direction == -1) {
4905                         newRect.height = visRect.height;
4906                     }
4907                     else {
4908                         newRect.y -= (visRect.height - newRect.height);
4909                         newRect.height = visRect.height;
4910                     }
4911 
4912                     if(addToSelection) {
4913                         ui.extendSelection(newPath);
4914                     }
4915                     else if(changeSelection) {
4916                         tree.setSelectionPath(newPath);
4917                     }
4918                     else {
4919                         ui.setLeadSelectionPath(newPath, true);
4920                     }
4921                     tree.scrollRectToVisible(newRect);
4922                 }
4923             }
4924         }
4925 
home(JTree tree, final BasicTreeUI ui, int direction, boolean addToSelection, boolean changeSelection)4926         private void home(JTree tree, final BasicTreeUI ui, int direction,
4927                           boolean addToSelection, boolean changeSelection) {
4928 
4929             // disable moving of lead unless in discontiguous mode
4930             if (!addToSelection && !changeSelection &&
4931                     tree.getSelectionModel().getSelectionMode() !=
4932                         TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
4933                 changeSelection = true;
4934             }
4935 
4936             final int rowCount = ui.getRowCount(tree);
4937 
4938             if (rowCount > 0) {
4939                 if(direction == -1) {
4940                     ui.ensureRowsAreVisible(0, 0);
4941                     if (addToSelection) {
4942                         TreePath        aPath = ui.getAnchorSelectionPath();
4943                         int             aRow = (aPath == null) ? -1 :
4944                                         ui.getRowForPath(tree, aPath);
4945 
4946                         if (aRow == -1) {
4947                             tree.setSelectionInterval(0, 0);
4948                         }
4949                         else {
4950                             tree.setSelectionInterval(0, aRow);
4951                             ui.setAnchorSelectionPath(aPath);
4952                             ui.setLeadSelectionPath(ui.getPathForRow(tree, 0));
4953                         }
4954                     }
4955                     else if(changeSelection) {
4956                         tree.setSelectionInterval(0, 0);
4957                     }
4958                     else {
4959                         ui.setLeadSelectionPath(ui.getPathForRow(tree, 0),
4960                                                 true);
4961                     }
4962                 }
4963                 else {
4964                     ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1);
4965                     if (addToSelection) {
4966                         TreePath        aPath = ui.getAnchorSelectionPath();
4967                         int             aRow = (aPath == null) ? -1 :
4968                                         ui.getRowForPath(tree, aPath);
4969 
4970                         if (aRow == -1) {
4971                             tree.setSelectionInterval(rowCount - 1,
4972                                                       rowCount -1);
4973                         }
4974                         else {
4975                             tree.setSelectionInterval(aRow, rowCount - 1);
4976                             ui.setAnchorSelectionPath(aPath);
4977                             ui.setLeadSelectionPath(ui.getPathForRow(tree,
4978                                                                rowCount -1));
4979                         }
4980                     }
4981                     else if(changeSelection) {
4982                         tree.setSelectionInterval(rowCount - 1, rowCount - 1);
4983                     }
4984                     else {
4985                         ui.setLeadSelectionPath(ui.getPathForRow(tree,
4986                                                           rowCount - 1), true);
4987                     }
4988                     if (ui.isLargeModel()){
4989                         SwingUtilities.invokeLater(new Runnable() {
4990                             public void run() {
4991                                 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1);
4992                             }
4993                         });
4994                     }
4995                 }
4996             }
4997         }
4998     }
4999 } // End of class BasicTreeUI
5000