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