1 /*
2 * Copyright 1997-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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.awt.dnd;
34 import java.beans;
35 import java.io;
36 import java.util.Enumeration;
37 import java.util.Hashtable;
38 import java.util.TooManyListenersException;
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import javax.swing.plaf.ActionMapUIResource;
43 import javax.swing.plaf.ComponentUI;
44 import javax.swing.plaf.UIResource;
45 import javax.swing.plaf.TreeUI;
46 import javax.swing.tree;
47 import javax.swing.text.Position;
48 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
49 import sun.swing.SwingUtilities2;
50
51 import sun.swing.DefaultLookup;
52 import sun.swing.UIAction;
53
54 /**
55 * The basic L&F for a hierarchical data structure.
56 * <p>
57 *
58 * @author Scott Violet
59 * @author Shannon Hickey (drag and drop)
60 */
61
62 public class BasicTreeUI extends TreeUI
63 {
64 private static final StringBuilder BASELINE_COMPONENT_KEY =
65 new StringBuilder("Tree.baselineComponent");
66
67 // Old actions forward to an instance of this.
68 static private final Actions SHARED_ACTION = new Actions();
69
70 transient protected Icon collapsedIcon;
71 transient protected Icon expandedIcon;
72
73 /**
74 * Color used to draw hash marks. If <code>null</code> no hash marks
75 * will be drawn.
76 */
77 private Color hashColor;
78
79 /** Distance between left margin and where vertical dashes will be
80 * drawn. */
81 protected int leftChildIndent;
82 /** Distance to add to leftChildIndent to determine where cell
83 * contents will be drawn. */
84 protected int rightChildIndent;
85 /** Total distance that will be indented. The sum of leftChildIndent
86 * and rightChildIndent. */
87 protected int totalChildIndent;
88
89 /** Minimum preferred size. */
90 protected Dimension preferredMinSize;
91
92 /** Index of the row that was last selected. */
93 protected int lastSelectedRow;
94
95 /** Component that we're going to be drawing into. */
96 protected JTree tree;
97
98 /** Renderer that is being used to do the actual cell drawing. */
99 transient protected TreeCellRenderer currentCellRenderer;
100
101 /** Set to true if the renderer that is currently in the tree was
102 * created by this instance. */
103 protected boolean createdRenderer;
104
105 /** Editor for the tree. */
106 transient protected TreeCellEditor cellEditor;
107
108 /** Set to true if editor that is currently in the tree was
109 * created by this instance. */
110 protected boolean createdCellEditor;
111
112 /** Set to false when editing and shouldSelectCell() returns true meaning
113 * the node should be selected before editing, used in completeEditing. */
114 protected boolean stopEditingInCompleteEditing;
115
116 /** Used to paint the TreeCellRenderer. */
117 protected CellRendererPane rendererPane;
118
119 /** Size needed to completely display all the nodes. */
120 protected Dimension preferredSize;
121
122 /** Is the preferredSize valid? */
123 protected boolean validCachedPreferredSize;
124
125 /** Object responsible for handling sizing and expanded issues. */
126 // WARNING: Be careful with the bounds held by treeState. They are
127 // always in terms of left-to-right. They get mapped to right-to-left
128 // by the various methods of this class.
129 protected AbstractLayoutCache treeState;
130
131
132 /** Used for minimizing the drawing of vertical lines. */
133 protected Hashtable<TreePath,Boolean> drawingCache;
134
135 /** True if doing optimizations for a largeModel. Subclasses that
136 * don't support this may wish to override createLayoutCache to not
137 * return a FixedHeightLayoutCache instance. */
138 protected boolean largeModel;
139
140 /** Reponsible for telling the TreeState the size needed for a node. */
141 protected AbstractLayoutCache.NodeDimensions nodeDimensions;
142
143 /** Used to determine what to display. */
144 protected TreeModel treeModel;
145
146 /** Model maintaing the selection. */
147 protected TreeSelectionModel treeSelectionModel;
148
149 /** How much the depth should be offset to properly calculate
150 * x locations. This is based on whether or not the root is visible,
151 * and if the root handles are visible. */
152 protected int depthOffset;
153
154 // Following 4 ivars are only valid when editing.
155
156 /** When editing, this will be the Component that is doing the actual
157 * editing. */
158 protected Component editingComponent;
159
160 /** Path that is being edited. */
161 protected TreePath editingPath;
162
163 /** Row that is being edited. Should only be referenced if
164 * editingComponent is not null. */
165 protected int editingRow;
166
167 /** Set to true if the editor has a different size than the renderer. */
168 protected boolean editorHasDifferentSize;
169
170 /** Row correspondin to lead path. */
171 private int leadRow;
172 /** If true, the property change event for LEAD_SELECTION_PATH_PROPERTY,
173 * or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint. */
174 private boolean ignoreLAChange;
175
176 /** Indicates the orientation. */
177 private boolean leftToRight;
178
179 // Cached listeners
180 private PropertyChangeListener propertyChangeListener;
181 private PropertyChangeListener selectionModelPropertyChangeListener;
182 private MouseListener mouseListener;
183 private FocusListener focusListener;
184 private KeyListener keyListener;
185 /** Used for large models, listens for moved/resized events and
186 * updates the validCachedPreferredSize bit accordingly. */
187 private ComponentListener componentListener;
188 /** Listens for CellEditor events. */
189 private CellEditorListener cellEditorListener;
190 /** Updates the display when the selection changes. */
191 private TreeSelectionListener treeSelectionListener;
192 /** Is responsible for updating the display based on model events. */
193 private TreeModelListener treeModelListener;
194 /** Updates the treestate as the nodes expand. */
195 private TreeExpansionListener treeExpansionListener;
196
197 /** UI property indicating whether to paint lines */
198 private boolean paintLines = true;
199
200 /** UI property for painting dashed lines */
201 private boolean lineTypeDashed;
202
203 /**
204 * The time factor to treate the series of typed alphanumeric key
205 * as prefix for first letter navigation.
206 */
207 private long timeFactor = 1000L;
208
209 private Handler handler;
210
211 /**
212 * A temporary variable for communication between startEditingOnRelease
213 * and startEditing.
214 */
215 private MouseEvent releaseEvent;
216
217 public static ComponentUI createUI(JComponent x) {
218 return new BasicTreeUI();
219 }
220
221
222 static void loadActionMap(LazyActionMap map) {
223 map.put(new Actions(Actions.SELECT_PREVIOUS));
224 map.put(new Actions(Actions.SELECT_PREVIOUS_CHANGE_LEAD));
225 map.put(new Actions(Actions.SELECT_PREVIOUS_EXTEND_SELECTION));
226
227 map.put(new Actions(Actions.SELECT_NEXT));
228 map.put(new Actions(Actions.SELECT_NEXT_CHANGE_LEAD));
229 map.put(new Actions(Actions.SELECT_NEXT_EXTEND_SELECTION));
230
231 map.put(new Actions(Actions.SELECT_CHILD));
232 map.put(new Actions(Actions.SELECT_CHILD_CHANGE_LEAD));
233
234 map.put(new Actions(Actions.SELECT_PARENT));
235 map.put(new Actions(Actions.SELECT_PARENT_CHANGE_LEAD));
236
237 map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION));
238 map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
239 map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION));
240
241 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION));
242 map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION));
243 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
244
245 map.put(new Actions(Actions.SELECT_FIRST));
246 map.put(new Actions(Actions.SELECT_FIRST_CHANGE_LEAD));
247 map.put(new Actions(Actions.SELECT_FIRST_EXTEND_SELECTION));
248
249 map.put(new Actions(Actions.SELECT_LAST));
250 map.put(new Actions(Actions.SELECT_LAST_CHANGE_LEAD));
251 map.put(new Actions(Actions.SELECT_LAST_EXTEND_SELECTION));
252
253 map.put(new Actions(Actions.TOGGLE));
254
255 map.put(new Actions(Actions.CANCEL_EDITING));
256
257 map.put(new Actions(Actions.START_EDITING));
258
259 map.put(new Actions(Actions.SELECT_ALL));
260
261 map.put(new Actions(Actions.CLEAR_SELECTION));
262
263 map.put(new Actions(Actions.SCROLL_LEFT));
264 map.put(new Actions(Actions.SCROLL_RIGHT));
265
266 map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION));
267 map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION));
268
269 map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_LEAD));
270 map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_LEAD));
271
272 map.put(new Actions(Actions.EXPAND));
273 map.put(new Actions(Actions.COLLAPSE));
274 map.put(new Actions(Actions.MOVE_SELECTION_TO_PARENT));
275
276 map.put(new Actions(Actions.ADD_TO_SELECTION));
277 map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
278 map.put(new Actions(Actions.EXTEND_TO));
279 map.put(new Actions(Actions.MOVE_SELECTION_TO));
280
281 map.put(TransferHandler.getCutAction());
282 map.put(TransferHandler.getCopyAction());
283 map.put(TransferHandler.getPasteAction());
284 }
285
286
287 public BasicTreeUI() {
288 super();
289 }
290
291 protected Color getHashColor() {
292 return hashColor;
293 }
294
295 protected void setHashColor(Color color) {
296 hashColor = color;
297 }
298
299 public void setLeftChildIndent(int newAmount) {
300 leftChildIndent = newAmount;
301 totalChildIndent = leftChildIndent + rightChildIndent;
302 if(treeState != null)
303 treeState.invalidateSizes();
304 updateSize();
305 }
306
307 public int getLeftChildIndent() {
308 return leftChildIndent;
309 }
310
311 public void setRightChildIndent(int newAmount) {
312 rightChildIndent = newAmount;
313 totalChildIndent = leftChildIndent + rightChildIndent;
314 if(treeState != null)
315 treeState.invalidateSizes();
316 updateSize();
317 }
318
319 public int getRightChildIndent() {
320 return rightChildIndent;
321 }
322
323 public void setExpandedIcon(Icon newG) {
324 expandedIcon = newG;
325 }
326
327 public Icon getExpandedIcon() {
328 return expandedIcon;
329 }
330
331 public void setCollapsedIcon(Icon newG) {
332 collapsedIcon = newG;
333 }
334
335 public Icon getCollapsedIcon() {
336 return collapsedIcon;
337 }
338
339 //
340 // Methods for configuring the behavior of the tree. None of them
341 // push the value to the JTree instance. You should really only
342 // call these methods on the JTree.
343 //
344
345 /**
346 * Updates the componentListener, if necessary.
347 */
348 protected void setLargeModel(boolean largeModel) {
349 if(getRowHeight() < 1)
350 largeModel = false;
351 if(this.largeModel != largeModel) {
352 completeEditing();
353 this.largeModel = largeModel;
354 treeState = createLayoutCache();
355 configureLayoutCache();
356 updateLayoutCacheExpandedNodesIfNecessary();
357 updateSize();
358 }
359 }
360
361 protected boolean isLargeModel() {
362 return largeModel;
363 }
364
365 /**
366 * Sets the row height, this is forwarded to the treeState.
367 */
368 protected void setRowHeight(int rowHeight) {
369 completeEditing();
370 if(treeState != null) {
371 setLargeModel(tree.isLargeModel());
372 treeState.setRowHeight(rowHeight);
373 updateSize();
374 }
375 }
376
377 protected int getRowHeight() {
378 return (tree == null) ? -1 : tree.getRowHeight();
379 }
380
381 /**
382 * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
383 * <code>updateRenderer</code>.
384 */
385 protected void setCellRenderer(TreeCellRenderer tcr) {
386 completeEditing();
387 updateRenderer();
388 if(treeState != null) {
389 treeState.invalidateSizes();
390 updateSize();
391 }
392 }
393
394 /**
395 * Return currentCellRenderer, which will either be the trees
396 * renderer, or defaultCellRenderer, which ever wasn't null.
397 */
398 protected TreeCellRenderer getCellRenderer() {
399 return currentCellRenderer;
400 }
401
402 /**
403 * Sets the TreeModel.
404 */
405 protected void setModel(TreeModel model) {
406 completeEditing();
407 if(treeModel != null && treeModelListener != null)
408 treeModel.removeTreeModelListener(treeModelListener);
409 treeModel = model;
410 if(treeModel != null) {
411 if(treeModelListener != null)
412 treeModel.addTreeModelListener(treeModelListener);
413 }
414 if(treeState != null) {
415 treeState.setModel(model);
416 updateLayoutCacheExpandedNodesIfNecessary();
417 updateSize();
418 }
419 }
420
421 protected TreeModel getModel() {
422 return treeModel;
423 }
424
425 /**
426 * Sets the root to being visible.
427 */
428 protected void setRootVisible(boolean newValue) {
429 completeEditing();
430 updateDepthOffset();
431 if(treeState != null) {
432 treeState.setRootVisible(newValue);
433 treeState.invalidateSizes();
434 updateSize();
435 }
436 }
437
438 protected boolean isRootVisible() {
439 return (tree != null) ? tree.isRootVisible() : false;
440 }
441
442 /**
443 * Determines whether the node handles are to be displayed.
444 */
445 protected void setShowsRootHandles(boolean newValue) {
446 completeEditing();
447 updateDepthOffset();
448 if(treeState != null) {
449 treeState.invalidateSizes();
450 updateSize();
451 }
452 }
453
454 protected boolean getShowsRootHandles() {
455 return (tree != null) ? tree.getShowsRootHandles() : false;
456 }
457
458 /**
459 * Sets the cell editor.
460 */
461 protected void setCellEditor(TreeCellEditor editor) {
462 updateCellEditor();
463 }
464
465 protected TreeCellEditor getCellEditor() {
466 return (tree != null) ? tree.getCellEditor() : null;
467 }
468
469 /**
470 * Configures the receiver to allow, or not allow, editing.
471 */
472 protected void setEditable(boolean newValue) {
473 updateCellEditor();
474 }
475
476 protected boolean isEditable() {
477 return (tree != null) ? tree.isEditable() : false;
478 }
479
480 /**
481 * Resets the selection model. The appropriate listener are installed
482 * on the model.
483 */
484 protected void setSelectionModel(TreeSelectionModel newLSM) {
485 completeEditing();
486 if(selectionModelPropertyChangeListener != null &&
487 treeSelectionModel != null)
488 treeSelectionModel.removePropertyChangeListener
489 (selectionModelPropertyChangeListener);
490 if(treeSelectionListener != null && treeSelectionModel != null)
491 treeSelectionModel.removeTreeSelectionListener
492 (treeSelectionListener);
493 treeSelectionModel = newLSM;
494 if(treeSelectionModel != null) {
495 if(selectionModelPropertyChangeListener != null)
496 treeSelectionModel.addPropertyChangeListener
497 (selectionModelPropertyChangeListener);
498 if(treeSelectionListener != null)
499 treeSelectionModel.addTreeSelectionListener
500 (treeSelectionListener);
501 if(treeState != null)
502 treeState.setSelectionModel(treeSelectionModel);
503 }
504 else if(treeState != null)
505 treeState.setSelectionModel(null);
506 if(tree != null)
507 tree.repaint();
508 }
509
510 protected TreeSelectionModel getSelectionModel() {
511 return treeSelectionModel;
512 }
513
514 //
515 // TreeUI methods
516 //
517
518 /**
519 * Returns the Rectangle enclosing the label portion that the
520 * last item in path will be drawn into. Will return null if
521 * any component in path is currently valid.
522 */
523 public Rectangle getPathBounds(JTree tree, TreePath path) {
524 if(tree != null && treeState != null) {
525 return getPathBounds(path, tree.getInsets(), new Rectangle());
526 }
527 return null;
528 }
529
530 private Rectangle getPathBounds(TreePath path, Insets insets,
531 Rectangle bounds) {
532 bounds = treeState.getBounds(path, bounds);
533 if (bounds != null) {
534 if (leftToRight) {
535 bounds.x += insets.left;
536 } else {
537 bounds.x = tree.getWidth() - (bounds.x + bounds.width) -
538 insets.right;
539 }
540 bounds.y += insets.top;
541 }
542 return bounds;
543 }
544
545 /**
546 * Returns the path for passed in row. If row is not visible
547 * null is returned.
548 */
549 public TreePath getPathForRow(JTree tree, int row) {
550 return (treeState != null) ? treeState.getPathForRow(row) : null;
551 }
552
553 /**
554 * Returns the row that the last item identified in path is visible
555 * at. Will return -1 if any of the elements in path are not
556 * currently visible.
557 */
558 public int getRowForPath(JTree tree, TreePath path) {
559 return (treeState != null) ? treeState.getRowForPath(path) : -1;
560 }
561
562 /**
563 * Returns the number of rows that are being displayed.
564 */
565 public int getRowCount(JTree tree) {
566 return (treeState != null) ? treeState.getRowCount() : 0;
567 }
568
569 /**
570 * Returns the path to the node that is closest to x,y. If
571 * there is nothing currently visible this will return null, otherwise
572 * it'll always return a valid path. If you need to test if the
573 * returned object is exactly at x, y you should get the bounds for
574 * the returned path and test x, y against that.
575 */
576 public TreePath getClosestPathForLocation(JTree tree, int x, int y) {
577 if(tree != null && treeState != null) {
578 // TreeState doesn't care about the x location, hence it isn't
579 // adjusted
580 y -= tree.getInsets().top;
581 return treeState.getPathClosestTo(x, y);
582 }
583 return null;
584 }
585
586 /**
587 * Returns true if the tree is being edited. The item that is being
588 * edited can be returned by getEditingPath().
589 */
590 public boolean isEditing(JTree tree) {
591 return (editingComponent != null);
592 }
593
594 /**
595 * Stops the current editing session. This has no effect if the
596 * tree isn't being edited. Returns true if the editor allows the
597 * editing session to stop.
598 */
599 public boolean stopEditing(JTree tree) {
600 if(editingComponent != null && cellEditor.stopCellEditing()) {
601 completeEditing(false, false, true);
602 return true;
603 }
604 return false;
605 }
606
607 /**
608 * Cancels the current editing session.
609 */
610 public void cancelEditing(JTree tree) {
611 if(editingComponent != null) {
612 completeEditing(false, true, false);
613 }
614 }
615
616 /**
617 * Selects the last item in path and tries to edit it. Editing will
618 * fail if the CellEditor won't allow it for the selected item.
619 */
620 public void startEditingAtPath(JTree tree, TreePath path) {
621 tree.scrollPathToVisible(path);
622 if(path != null && tree.isVisible(path))
623 startEditing(path, null);
624 }
625
626 /**
627 * Returns the path to the element that is being edited.
628 */
629 public TreePath getEditingPath(JTree tree) {
630 return editingPath;
631 }
632
633 //
634 // Install methods
635 //
636
637 public void installUI(JComponent c) {
638 if ( c == null ) {
639 throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" );
640 }
641
642 tree = (JTree)c;
643
644 prepareForUIInstall();
645
646 // Boilerplate install block
647 installDefaults();
648 installKeyboardActions();
649 installComponents();
650 installListeners();
651
652 completeUIInstall();
653 }
654
655 /**
656 * Invoked after the <code>tree</code> instance variable has been
657 * set, but before any defaults/listeners have been installed.
658 */
659 protected void prepareForUIInstall() {
660 drawingCache = new Hashtable<TreePath,Boolean>(7);
661
662 // Data member initializations
663 leftToRight = BasicGraphicsUtils.isLeftToRight(tree);
664 stopEditingInCompleteEditing = true;
665 lastSelectedRow = -1;
666 leadRow = -1;
667 preferredSize = new Dimension();
668
669 largeModel = tree.isLargeModel();
670 if(getRowHeight() <= 0)
671 largeModel = false;
672 setModel(tree.getModel());
673 }
674
675 /**
676 * Invoked from installUI after all the defaults/listeners have been
677 * installed.
678 */
679 protected void completeUIInstall() {
680 // Custom install code
681
682 this.setShowsRootHandles(tree.getShowsRootHandles());
683
684 updateRenderer();
685
686 updateDepthOffset();
687
688 setSelectionModel(tree.getSelectionModel());
689
690 // Create, if necessary, the TreeState instance.
691 treeState = createLayoutCache();
692 configureLayoutCache();
693
694 updateSize();
695 }
696
697 protected void installDefaults() {
698 if(tree.getBackground() == null ||
699 tree.getBackground() instanceof UIResource) {
700 tree.setBackground(UIManager.getColor("Tree.background"));
701 }
702 if(getHashColor() == null || getHashColor() instanceof UIResource) {
703 setHashColor(UIManager.getColor("Tree.hash"));
704 }
705 if (tree.getFont() == null || tree.getFont() instanceof UIResource)
706 tree.setFont( UIManager.getFont("Tree.font") );
707 // JTree's original row height is 16. To correctly display the
708 // contents on Linux we should have set it to 18, Windows 19 and
709 // Solaris 20. As these values vary so much it's too hard to
710 // be backward compatable and try to update the row height, we're
711 // therefor NOT going to adjust the row height based on font. If the
712 // developer changes the font, it's there responsibility to update
713 // the row height.
714
715 setExpandedIcon( (Icon)UIManager.get( "Tree.expandedIcon" ) );
716 setCollapsedIcon( (Icon)UIManager.get( "Tree.collapsedIcon" ) );
717
718 setLeftChildIndent(((Integer)UIManager.get("Tree.leftChildIndent")).
719 intValue());
720 setRightChildIndent(((Integer)UIManager.get("Tree.rightChildIndent")).
721 intValue());
722
723 LookAndFeel.installProperty(tree, "rowHeight",
724 UIManager.get("Tree.rowHeight"));
725
726 largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0);
727
728 Object scrollsOnExpand = UIManager.get("Tree.scrollsOnExpand");
729 if (scrollsOnExpand != null) {
730 LookAndFeel.installProperty(tree, "scrollsOnExpand", scrollsOnExpand);
731 }
732
733 paintLines = UIManager.getBoolean("Tree.paintLines");
734 lineTypeDashed = UIManager.getBoolean("Tree.lineTypeDashed");
735
736 Long l = (Long)UIManager.get("Tree.timeFactor");
737 timeFactor = (l!=null) ? l.longValue() : 1000L;
738
739 Object showsRootHandles = UIManager.get("Tree.showsRootHandles");
740 if (showsRootHandles != null) {
741 LookAndFeel.installProperty(tree,
742 JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles);
743 }
744 }
745
746 protected void installListeners() {
747 if ( (propertyChangeListener = createPropertyChangeListener())
748 != null ) {
749 tree.addPropertyChangeListener(propertyChangeListener);
750 }
751 if ( (mouseListener = createMouseListener()) != null ) {
752 tree.addMouseListener(mouseListener);
753 if (mouseListener instanceof MouseMotionListener) {
754 tree.addMouseMotionListener((MouseMotionListener)mouseListener);
755 }
756 }
757 if ((focusListener = createFocusListener()) != null ) {
758 tree.addFocusListener(focusListener);
759 }
760 if ((keyListener = createKeyListener()) != null) {
761 tree.addKeyListener(keyListener);
762 }
763 if((treeExpansionListener = createTreeExpansionListener()) != null) {
764 tree.addTreeExpansionListener(treeExpansionListener);
765 }
766 if((treeModelListener = createTreeModelListener()) != null &&
767 treeModel != null) {
768 treeModel.addTreeModelListener(treeModelListener);
769 }
770 if((selectionModelPropertyChangeListener =
771 createSelectionModelPropertyChangeListener()) != null &&
772 treeSelectionModel != null) {
773 treeSelectionModel.addPropertyChangeListener
774 (selectionModelPropertyChangeListener);
775 }
776 if((treeSelectionListener = createTreeSelectionListener()) != null &&
777 treeSelectionModel != null) {
778 treeSelectionModel.addTreeSelectionListener(treeSelectionListener);
779 }
780
781 TransferHandler th = tree.getTransferHandler();
782 if (th == null || th instanceof UIResource) {
783 tree.setTransferHandler(defaultTransferHandler);
784 // default TransferHandler doesn't support drop
785 // so we don't want drop handling
786 if (tree.getDropTarget() instanceof UIResource) {
787 tree.setDropTarget(null);
788 }
789 }
790
791 LookAndFeel.installProperty(tree, "opaque", Boolean.TRUE);
792 }
793
794 protected void installKeyboardActions() {
795 InputMap km = getInputMap(JComponent.
796 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
797
798 SwingUtilities.replaceUIInputMap(tree, JComponent.
799 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
800 km);
801 km = getInputMap(JComponent.WHEN_FOCUSED);
802 SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, km);
803
804 LazyActionMap.installLazyActionMap(tree, BasicTreeUI.class,
805 "Tree.actionMap");
806 }
807
808 InputMap getInputMap(int condition) {
809 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
810 return (InputMap)DefaultLookup.get(tree, this,
811 "Tree.ancestorInputMap");
812 }
813 else if (condition == JComponent.WHEN_FOCUSED) {
814 InputMap keyMap = (InputMap)DefaultLookup.get(tree, this,
815 "Tree.focusInputMap");
816 InputMap rtlKeyMap;
817
818 if (tree.getComponentOrientation().isLeftToRight() ||
819 ((rtlKeyMap = (InputMap)DefaultLookup.get(tree, this,
820 "Tree.focusInputMap.RightToLeft")) == null)) {
821 return keyMap;
822 } else {
823 rtlKeyMap.setParent(keyMap);
824 return rtlKeyMap;
825 }
826 }
827 return null;
828 }
829
830 /**
831 * Intalls the subcomponents of the tree, which is the renderer pane.
832 */
833 protected void installComponents() {
834 if ((rendererPane = createCellRendererPane()) != null) {
835 tree.add( rendererPane );
836 }
837 }
838
839 //
840 // Create methods.
841 //
842
843 /**
844 * Creates an instance of NodeDimensions that is able to determine
845 * the size of a given node in the tree.
846 */
847 protected AbstractLayoutCache.NodeDimensions createNodeDimensions() {
848 return new NodeDimensionsHandler();
849 }
850
851 /**
852 * Creates a listener that is responsible that updates the UI based on
853 * how the tree changes.
854 */
855 protected PropertyChangeListener createPropertyChangeListener() {
856 return getHandler();
857 }
858
859 private Handler getHandler() {
860 if (handler == null) {
861 handler = new Handler();
862 }
863 return handler;
864 }
865
866 /**
867 * Creates the listener responsible for updating the selection based on
868 * mouse events.
869 */
870 protected MouseListener createMouseListener() {
871 return getHandler();
872 }
873
874 /**
875 * Creates a listener that is responsible for updating the display
876 * when focus is lost/gained.
877 */
878 protected FocusListener createFocusListener() {
879 return getHandler();
880 }
881
882 /**
883 * Creates the listener reponsible for getting key events from
884 * the tree.
885 */
886 protected KeyListener createKeyListener() {
887 return getHandler();
888 }
889
890 /**
891 * Creates the listener responsible for getting property change
892 * events from the selection model.
893 */
894 protected PropertyChangeListener createSelectionModelPropertyChangeListener() {
895 return getHandler();
896 }
897
898 /**
899 * Creates the listener that updates the display based on selection change
900 * methods.
901 */
902 protected TreeSelectionListener createTreeSelectionListener() {
903 return getHandler();
904 }
905
906 /**
907 * Creates a listener to handle events from the current editor.
908 */
909 protected CellEditorListener createCellEditorListener() {
910 return getHandler();
911 }
912
913 /**
914 * Creates and returns a new ComponentHandler. This is used for
915 * the large model to mark the validCachedPreferredSize as invalid
916 * when the component moves.
917 */
918 protected ComponentListener createComponentListener() {
919 return new ComponentHandler();
920 }
921
922 /**
923 * Creates and returns the object responsible for updating the treestate
924 * when nodes expanded state changes.
925 */
926 protected TreeExpansionListener createTreeExpansionListener() {
927 return getHandler();
928 }
929
930 /**
931 * Creates the object responsible for managing what is expanded, as
932 * well as the size of nodes.
933 */
934 protected AbstractLayoutCache createLayoutCache() {
935 if(isLargeModel() && getRowHeight() > 0) {
936 return new FixedHeightLayoutCache();
937 }
938 return new VariableHeightLayoutCache();
939 }
940
941 /**
942 * Returns the renderer pane that renderer components are placed in.
943 */
944 protected CellRendererPane createCellRendererPane() {
945 return new CellRendererPane();
946 }
947
948 /**
949 * Creates a default cell editor.
950 */
951 protected TreeCellEditor createDefaultCellEditor() {
952 if(currentCellRenderer != null &&
953 (currentCellRenderer instanceof DefaultTreeCellRenderer)) {
954 DefaultTreeCellEditor editor = new DefaultTreeCellEditor
955 (tree, (DefaultTreeCellRenderer)currentCellRenderer);
956
957 return editor;
958 }
959 return new DefaultTreeCellEditor(tree, null);
960 }
961
962 /**
963 * Returns the default cell renderer that is used to do the
964 * stamping of each node.
965 */
966 protected TreeCellRenderer createDefaultCellRenderer() {
967 return new DefaultTreeCellRenderer();
968 }
969
970 /**
971 * Returns a listener that can update the tree when the model changes.
972 */
973 protected TreeModelListener createTreeModelListener() {
974 return getHandler();
975 }
976
977 //
978 // Uninstall methods
979 //
980
981 public void uninstallUI(JComponent c) {
982 completeEditing();
983
984 prepareForUIUninstall();
985
986 uninstallDefaults();
987 uninstallListeners();
988 uninstallKeyboardActions();
989 uninstallComponents();
990
991 completeUIUninstall();
992 }
993
994 protected void prepareForUIUninstall() {
995 }
996
997 protected void completeUIUninstall() {
998 if(createdRenderer) {
999 tree.setCellRenderer(null);
1000 }
1001 if(createdCellEditor) {
1002 tree.setCellEditor(null);
1003 }
1004 cellEditor = null;
1005 currentCellRenderer = null;
1006 rendererPane = null;
1007 componentListener = null;
1008 propertyChangeListener = null;
1009 mouseListener = null;
1010 focusListener = null;
1011 keyListener = null;
1012 setSelectionModel(null);
1013 treeState = null;
1014 drawingCache = null;
1015 selectionModelPropertyChangeListener = null;
1016 tree = null;
1017 treeModel = null;
1018 treeSelectionModel = null;
1019 treeSelectionListener = null;
1020 treeExpansionListener = null;
1021 }
1022
1023 protected void uninstallDefaults() {
1024 if (tree.getTransferHandler() instanceof UIResource) {
1025 tree.setTransferHandler(null);
1026 }
1027 }
1028
1029 protected void uninstallListeners() {
1030 if(componentListener != null) {
1031 tree.removeComponentListener(componentListener);
1032 }
1033 if (propertyChangeListener != null) {
1034 tree.removePropertyChangeListener(propertyChangeListener);
1035 }
1036 if (mouseListener != null) {
1037 tree.removeMouseListener(mouseListener);
1038 if (mouseListener instanceof MouseMotionListener) {
1039 tree.removeMouseMotionListener((MouseMotionListener)mouseListener);
1040 }
1041 }
1042 if (focusListener != null) {
1043 tree.removeFocusListener(focusListener);
1044 }
1045 if (keyListener != null) {
1046 tree.removeKeyListener(keyListener);
1047 }
1048 if(treeExpansionListener != null) {
1049 tree.removeTreeExpansionListener(treeExpansionListener);
1050 }
1051 if(treeModel != null && treeModelListener != null) {
1052 treeModel.removeTreeModelListener(treeModelListener);
1053 }
1054 if(selectionModelPropertyChangeListener != null &&
1055 treeSelectionModel != null) {
1056 treeSelectionModel.removePropertyChangeListener
1057 (selectionModelPropertyChangeListener);
1058 }
1059 if(treeSelectionListener != null && treeSelectionModel != null) {
1060 treeSelectionModel.removeTreeSelectionListener
1061 (treeSelectionListener);
1062 }
1063 handler = null;
1064 }
1065
1066 protected void uninstallKeyboardActions() {
1067 SwingUtilities.replaceUIActionMap(tree, null);
1068 SwingUtilities.replaceUIInputMap(tree, JComponent.
1069 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1070 null);
1071 SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, null);
1072 }
1073
1074 /**
1075 * Uninstalls the renderer pane.
1076 */
1077 protected void uninstallComponents() {
1078 if(rendererPane != null) {
1079 tree.remove(rendererPane);
1080 }
1081 }
1082
1083 /**
1084 * Recomputes the right margin, and invalidates any tree states
1085 */
1086 private void redoTheLayout() {
1087 if (treeState != null) {
1088 treeState.invalidateSizes();
1089 }
1090 }
1091
1092 /**
1093 * Returns the baseline.
1094 *
1095 * @throws NullPointerException {@inheritDoc}
1096 * @throws IllegalArgumentException {@inheritDoc}
1097 * @see javax.swing.JComponent#getBaseline(int, int)
1098 * @since 1.6
1099 */
1100 public int getBaseline(JComponent c, int width, int height) {
1101 super.getBaseline(c, width, height);
1102 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
1103 Component renderer = (Component)lafDefaults.get(
1104 BASELINE_COMPONENT_KEY);
1105 if (renderer == null) {
1106 TreeCellRenderer tcr = createDefaultCellRenderer();
1107 renderer = tcr.getTreeCellRendererComponent(
1108 tree, "a", false, false, false, -1, false);
1109 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
1110 }
1111 int rowHeight = tree.getRowHeight();
1112 int baseline;
1113 if (rowHeight > 0) {
1114 baseline = renderer.getBaseline(Integer.MAX_VALUE, rowHeight);
1115 }
1116 else {
1117 Dimension pref = renderer.getPreferredSize();
1118 baseline = renderer.getBaseline(pref.width, pref.height);
1119 }
1120 return baseline + tree.getInsets().top;
1121 }
1122
1123 /**
1124 * Returns an enum indicating how the baseline of the component
1125 * changes as the size changes.
1126 *
1127 * @throws NullPointerException {@inheritDoc}
1128 * @see javax.swing.JComponent#getBaseline(int, int)
1129 * @since 1.6
1130 */
1131 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
1132 JComponent c) {
1133 super.getBaselineResizeBehavior(c);
1134 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
1135 }
1136
1137 //
1138 // Painting routines.
1139 //
1140
1141 public void paint(Graphics g, JComponent c) {
1142 if (tree != c) {
1143 throw new InternalError("incorrect component");
1144 }
1145
1146 // Should never happen if installed for a UI
1147 if(treeState == null) {
1148 return;
1149 }
1150
1151 Rectangle paintBounds = g.getClipBounds();
1152 Insets insets = tree.getInsets();
1153 TreePath initialPath = getClosestPathForLocation
1154 (tree, 0, paintBounds.y);
1155 Enumeration paintingEnumerator = treeState.getVisiblePathsFrom
1156 (initialPath);
1157 int row = treeState.getRowForPath(initialPath);
1158 int endY = paintBounds.y + paintBounds.height;
1159
1160 drawingCache.clear();
1161
1162 if(initialPath != null && paintingEnumerator != null) {
1163 TreePath parentPath = initialPath;
1164
1165 // Draw the lines, knobs, and rows
1166
1167 // Find each parent and have them draw a line to their last child
1168 parentPath = parentPath.getParentPath();
1169 while(parentPath != null) {
1170 paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
1171 drawingCache.put(parentPath, Boolean.TRUE);
1172 parentPath = parentPath.getParentPath();
1173 }
1174
1175 boolean done = false;
1176 // Information for the node being rendered.
1177 boolean isExpanded;
1178 boolean hasBeenExpanded;
1179 boolean isLeaf;
1180 Rectangle boundsBuffer = new Rectangle();
1181 Rectangle bounds;
1182 TreePath path;
1183 boolean rootVisible = isRootVisible();
1184
1185 while(!done && paintingEnumerator.hasMoreElements()) {
1186 path = (TreePath)paintingEnumerator.nextElement();
1187 if(path != null) {
1188 isLeaf = treeModel.isLeaf(path.getLastPathComponent());
1189 if(isLeaf)
1190 isExpanded = hasBeenExpanded = false;
1191 else {
1192 isExpanded = treeState.getExpandedState(path);
1193 hasBeenExpanded = tree.hasBeenExpanded(path);
1194 }
1195 bounds = getPathBounds(path, insets, boundsBuffer);
1196 if(bounds == null)
1197 // This will only happen if the model changes out
1198 // from under us (usually in another thread).
1199 // Swing isn't multithreaded, but I'll put this
1200 // check in anyway.
1201 return;
1202 // See if the vertical line to the parent has been drawn.
1203 parentPath = path.getParentPath();
1204 if(parentPath != null) {
1205 if(drawingCache.get(parentPath) == null) {
1206 paintVerticalPartOfLeg(g, paintBounds,
1207 insets, parentPath);
1208 drawingCache.put(parentPath, Boolean.TRUE);
1209 }
1210 paintHorizontalPartOfLeg(g, paintBounds, insets,
1211 bounds, path, row,
1212 isExpanded,
1213 hasBeenExpanded, isLeaf);
1214 }
1215 else if(rootVisible && row == 0) {
1216 paintHorizontalPartOfLeg(g, paintBounds, insets,
1217 bounds, path, row,
1218 isExpanded,
1219 hasBeenExpanded, isLeaf);
1220 }
1221 if(shouldPaintExpandControl(path, row, isExpanded,
1222 hasBeenExpanded, isLeaf)) {
1223 paintExpandControl(g, paintBounds, insets, bounds,
1224 path, row, isExpanded,
1225 hasBeenExpanded, isLeaf);
1226 }
1227 paintRow(g, paintBounds, insets, bounds, path,
1228 row, isExpanded, hasBeenExpanded, isLeaf);
1229 if((bounds.y + bounds.height) >= endY)
1230 done = true;
1231 }
1232 else {
1233 done = true;
1234 }
1235 row++;
1236 }
1237 }
1238
1239 paintDropLine(g);
1240
1241 // Empty out the renderer pane, allowing renderers to be gc'ed.
1242 rendererPane.removeAll();
1243
1244 drawingCache.clear();
1245 }
1246
1247 private boolean isDropLine(JTree.DropLocation loc) {
1248 return loc != null && loc.getPath() != null && loc.getChildIndex() != -1;
1249 }
1250
1251 private void paintDropLine(Graphics g) {
1252 JTree.DropLocation loc = tree.getDropLocation();
1253 if (!isDropLine(loc)) {
1254 return;
1255 }
1256
1257 Color c = UIManager.getColor("Tree.dropLineColor");
1258 if (c != null) {
1259 g.setColor(c);
1260 Rectangle rect = getDropLineRect(loc);
1261 g.fillRect(rect.x, rect.y, rect.width, rect.height);
1262 }
1263 }
1264
1265 private Rectangle getDropLineRect(JTree.DropLocation loc) {
1266 Rectangle rect = null;
1267 TreePath path = loc.getPath();
1268 int index = loc.getChildIndex();
1269 boolean ltr = leftToRight;
1270
1271 Insets insets = tree.getInsets();
1272
1273 if (tree.getRowCount() == 0) {
1274 rect = new Rectangle(insets.left,
1275 insets.top,
1276 tree.getWidth() - insets.left - insets.right,
1277 0);
1278 } else {
1279 TreeModel model = getModel();
1280 Object root = model.getRoot();
1281
1282 if (path.getLastPathComponent() == root
1283 && index >= model.getChildCount(root)) {
1284
1285 rect = tree.getRowBounds(tree.getRowCount() - 1);
1286 rect.y = rect.y + rect.height;
1287 Rectangle xRect;
1288
1289 if (!tree.isRootVisible()) {
1290 xRect = tree.getRowBounds(0);
1291 } else if (model.getChildCount(root) == 0){
1292 xRect = tree.getRowBounds(0);
1293 xRect.x += totalChildIndent;
1294 xRect.width -= totalChildIndent + totalChildIndent;
1295 } else {
1296 TreePath lastChildPath = path.pathByAddingChild(
1297 model.getChild(root, model.getChildCount(root) - 1));
1298 xRect = tree.getPathBounds(lastChildPath);
1299 }
1300
1301 rect.x = xRect.x;
1302 rect.width = xRect.width;
1303 } else {
1304 rect = tree.getPathBounds(path.pathByAddingChild(
1305 model.getChild(path.getLastPathComponent(), index)));
1306 }
1307 }
1308
1309 if (rect.y != 0) {
1310 rect.y--;
1311 }
1312
1313 if (!ltr) {
1314 rect.x = rect.x + rect.width - 100;
1315 }
1316
1317 rect.width = 100;
1318 rect.height = 2;
1319
1320 return rect;
1321 }
1322
1323 /**
1324 * Paints the horizontal part of the leg. The receiver should
1325 * NOT modify <code>clipBounds</code>, or <code>insets</code>.<p>
1326 * NOTE: <code>parentRow</code> can be -1 if the root is not visible.
1327 */
1328 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
1329 Insets insets, Rectangle bounds,
1330 TreePath path, int row,
1331 boolean isExpanded,
1332 boolean hasBeenExpanded, boolean
1333 isLeaf) {
1334 if (!paintLines) {
1335 return;
1336 }
1337
1338 // Don't paint the legs for the root'ish node if the
1339 int depth = path.getPathCount() - 1;
1340 if((depth == 0 || (depth == 1 && !isRootVisible())) &&
1341 !getShowsRootHandles()) {
1342 return;
1343 }
1344
1345 int clipLeft = clipBounds.x;
1346 int clipRight = clipBounds.x + clipBounds.width;
1347 int clipTop = clipBounds.y;
1348 int clipBottom = clipBounds.y + clipBounds.height;
1349 int lineY = bounds.y + bounds.height / 2;
1350
1351 if (leftToRight) {
1352 int leftX = bounds.x - getRightChildIndent();
1353 int nodeX = bounds.x - getHorizontalLegBuffer();
1354
1355 if(lineY >= clipTop
1356 && lineY < clipBottom
1357 && nodeX >= clipLeft
1358 && leftX < clipRight
1359 && leftX < nodeX) {
1360
1361 g.setColor(getHashColor());
1362 paintHorizontalLine(g, tree, lineY, leftX, nodeX - 1);
1363 }
1364 } else {
1365 int nodeX = bounds.x + bounds.width + getHorizontalLegBuffer();
1366 int rightX = bounds.x + bounds.width + getRightChildIndent();
1367
1368 if(lineY >= clipTop
1369 && lineY < clipBottom
1370 && rightX >= clipLeft
1371 && nodeX < clipRight
1372 && nodeX < rightX) {
1373
1374 g.setColor(getHashColor());
1375 paintHorizontalLine(g, tree, lineY, nodeX, rightX - 1);
1376 }
1377 }
1378 }
1379
1380 /**
1381 * Paints the vertical part of the leg. The receiver should
1382 * NOT modify <code>clipBounds</code>, <code>insets</code>.<p>
1383 */
1384 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
1385 Insets insets, TreePath path) {
1386 if (!paintLines) {
1387 return;
1388 }
1389
1390 int depth = path.getPathCount() - 1;
1391 if (depth == 0 && !getShowsRootHandles() && !isRootVisible()) {
1392 return;
1393 }
1394 int lineX = getRowX(-1, depth + 1);
1395 if (leftToRight) {
1396 lineX = lineX - getRightChildIndent() + insets.left;
1397 }
1398 else {
1399 lineX = tree.getWidth() - lineX - insets.right +
1400 getRightChildIndent() - 1;
1401 }
1402 int clipLeft = clipBounds.x;
1403 int clipRight = clipBounds.x + (clipBounds.width - 1);
1404
1405 if (lineX >= clipLeft && lineX <= clipRight) {
1406 int clipTop = clipBounds.y;
1407 int clipBottom = clipBounds.y + clipBounds.height;
1408 Rectangle parentBounds = getPathBounds(tree, path);
1409 Rectangle lastChildBounds = getPathBounds(tree,
1410 getLastChildPath(path));
1411
1412 if(lastChildBounds == null)
1413 // This shouldn't happen, but if the model is modified
1414 // in another thread it is possible for this to happen.
1415 // Swing isn't multithreaded, but I'll add this check in
1416 // anyway.
1417 return;
1418
1419 int top;
1420
1421 if(parentBounds == null) {
1422 top = Math.max(insets.top + getVerticalLegBuffer(),
1423 clipTop);
1424 }
1425 else
1426 top = Math.max(parentBounds.y + parentBounds.height +
1427 getVerticalLegBuffer(), clipTop);
1428 if(depth == 0 && !isRootVisible()) {
1429 TreeModel model = getModel();
1430
1431 if(model != null) {
1432 Object root = model.getRoot();
1433
1434 if(model.getChildCount(root) > 0) {
1435 parentBounds = getPathBounds(tree, path.
1436 pathByAddingChild(model.getChild(root, 0)));
1437 if(parentBounds != null)
1438 top = Math.max(insets.top + getVerticalLegBuffer(),
1439 parentBounds.y +
1440 parentBounds.height / 2);
1441 }
1442 }
1443 }
1444
1445 int bottom = Math.min(lastChildBounds.y +
1446 (lastChildBounds.height / 2), clipBottom);
1447
1448 if (top <= bottom) {
1449 g.setColor(getHashColor());
1450 paintVerticalLine(g, tree, lineX, top, bottom);
1451 }
1452 }
1453 }
1454
1455 /**
1456 * Paints the expand (toggle) part of a row. The receiver should
1457 * NOT modify <code>clipBounds</code>, or <code>insets</code>.
1458 */
1459 protected void paintExpandControl(Graphics g,
1460 Rectangle clipBounds, Insets insets,
1461 Rectangle bounds, TreePath path,
1462 int row, boolean isExpanded,
1463 boolean hasBeenExpanded,
1464 boolean isLeaf) {
1465 Object value = path.getLastPathComponent();
1466
1467 // Draw icons if not a leaf and either hasn't been loaded,
1468 // or the model child count is > 0.
1469 if (!isLeaf && (!hasBeenExpanded ||
1470 treeModel.getChildCount(value) > 0)) {
1471 int middleXOfKnob;
1472 if (leftToRight) {
1473 middleXOfKnob = bounds.x - getRightChildIndent() + 1;
1474 } else {
1475 middleXOfKnob = bounds.x + bounds.width + getRightChildIndent() - 1;
1476 }
1477 int middleYOfKnob = bounds.y + (bounds.height / 2);
1478
1479 if (isExpanded) {
1480 Icon expandedIcon = getExpandedIcon();
1481 if(expandedIcon != null)
1482 drawCentered(tree, g, expandedIcon, middleXOfKnob,
1483 middleYOfKnob );
1484 }
1485 else {
1486 Icon collapsedIcon = getCollapsedIcon();
1487 if(collapsedIcon != null)
1488 drawCentered(tree, g, collapsedIcon, middleXOfKnob,
1489 middleYOfKnob);
1490 }
1491 }
1492 }
1493
1494 /**
1495 * Paints the renderer part of a row. The receiver should
1496 * NOT modify <code>clipBounds</code>, or <code>insets</code>.
1497 */
1498 protected void paintRow(Graphics g, Rectangle clipBounds,
1499 Insets insets, Rectangle bounds, TreePath path,
1500 int row, boolean isExpanded,
1501 boolean hasBeenExpanded, boolean isLeaf) {
1502 // Don't paint the renderer if editing this row.
1503 if(editingComponent != null && editingRow == row)
1504 return;
1505
1506 int leadIndex;
1507
1508 if(tree.hasFocus()) {
1509 leadIndex = getLeadSelectionRow();
1510 }
1511 else
1512 leadIndex = -1;
1513
1514 Component component;
1515
1516 component = currentCellRenderer.getTreeCellRendererComponent
1517 (tree, path.getLastPathComponent(),
1518 tree.isRowSelected(row), isExpanded, isLeaf, row,
1519 (leadIndex == row));
1520
1521 rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
1522 bounds.width, bounds.height, true);
1523 }
1524
1525 /**
1526 * Returns true if the expand (toggle) control should be drawn for
1527 * the specified row.
1528 */
1529 protected boolean shouldPaintExpandControl(TreePath path, int row,
1530 boolean isExpanded,
1531 boolean hasBeenExpanded,
1532 boolean isLeaf) {
1533 if(isLeaf)
1534 return false;
1535
1536 int depth = path.getPathCount() - 1;
1537
1538 if((depth == 0 || (depth == 1 && !isRootVisible())) &&
1539 !getShowsRootHandles())
1540 return false;
1541 return true;
1542 }
1543
1544 /**
1545 * Paints a vertical line.
1546 */
1547 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
1548 int bottom) {
1549 if (lineTypeDashed) {
1550 drawDashedVerticalLine(g, x, top, bottom);
1551 } else {
1552 g.drawLine(x, top, x, bottom);
1553 }
1554 }
1555
1556 /**
1557 * Paints a horizontal line.
1558 */
1559 protected void paintHorizontalLine(Graphics g, JComponent c, int y,
1560 int left, int right) {
1561 if (lineTypeDashed) {
1562 drawDashedHorizontalLine(g, y, left, right);
1563 } else {
1564 g.drawLine(left, y, right, y);
1565 }
1566 }
1567
1568 /**
1569 * The vertical element of legs between nodes starts at the bottom of the
1570 * parent node by default. This method makes the leg start below that.
1571 */
1572 protected int getVerticalLegBuffer() {
1573 return 0;
1574 }
1575
1576 /**
1577 * The horizontal element of legs between nodes starts at the
1578 * right of the left-hand side of the child node by default. This
1579 * method makes the leg end before that.
1580 */
1581 protected int getHorizontalLegBuffer() {
1582 return 0;
1583 }
1584
1585 private int findCenteredX(int x, int iconWidth) {
1586 return leftToRight
1587 ? x - (int)Math.ceil(iconWidth / 2.0)
1588 : x - (int)Math.floor(iconWidth / 2.0);
1589 }
1590
1591 //
1592 // Generic painting methods
1593 //
1594
1595 // Draws the icon centered at (x,y)
1596 protected void drawCentered(Component c, Graphics graphics, Icon icon,
1597 int x, int y) {
1598 icon.paintIcon(c, graphics,
1599 findCenteredX(x, icon.getIconWidth()),
1600 y - icon.getIconHeight() / 2);
1601 }
1602
1603 // This method is slow -- revisit when Java2D is ready.
1604 // assumes x1 <= x2
1605 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2){
1606 // Drawing only even coordinates helps join line segments so they
1607 // appear as one line. This can be defeated by translating the
1608 // Graphics by an odd amount.
1609 x1 += (x1 % 2);
1610
1611 for (int x = x1; x <= x2; x+=2) {
1612 g.drawLine(x, y, x, y);
1613 }
1614 }
1615
1616 // This method is slow -- revisit when Java2D is ready.
1617 // assumes y1 <= y2
1618 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) {
1619 // Drawing only even coordinates helps join line segments so they
1620 // appear as one line. This can be defeated by translating the
1621 // Graphics by an odd amount.
1622 y1 += (y1 % 2);
1623
1624 for (int y = y1; y <= y2; y+=2) {
1625 g.drawLine(x, y, x, y);
1626 }
1627 }
1628
1629 //
1630 // Various local methods
1631 //
1632
1633 /**
1634 * Returns the location, along the x-axis, to render a particular row
1635 * at. The return value does not include any Insets specified on the JTree.
1636 * This does not check for the validity of the row or depth, it is assumed
1637 * to be correct and will not throw an Exception if the row or depth
1638 * doesn't match that of the tree.
1639 *
1640 * @param row Row to return x location for
1641 * @param depth Depth of the row
1642 * @return amount to indent the given row.
1643 * @since 1.5
1644 */
1645 protected int getRowX(int row, int depth) {
1646 return totalChildIndent * (depth + depthOffset);
1647 }
1648
1649 /**
1650 * Makes all the nodes that are expanded in JTree expanded in LayoutCache.
1651 * This invokes updateExpandedDescendants with the root path.
1652 */
1653 protected void updateLayoutCacheExpandedNodes() {
1654 if(treeModel != null && treeModel.getRoot() != null)
1655 updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1656 }
1657
1658 private void updateLayoutCacheExpandedNodesIfNecessary() {
1659 if (treeModel != null && treeModel.getRoot() != null) {
1660 TreePath rootPath = new TreePath(treeModel.getRoot());
1661 if (tree.isExpanded(rootPath)) {
1662 updateLayoutCacheExpandedNodes();
1663 } else {
1664 treeState.setExpandedState(rootPath, false);
1665 }
1666 }
1667 }
1668
1669 /**
1670 * Updates the expanded state of all the descendants of <code>path</code>
1671 * by getting the expanded descendants from the tree and forwarding
1672 * to the tree state.
1673 */
1674 protected void updateExpandedDescendants(TreePath path) {
1675 completeEditing();
1676 if(treeState != null) {
1677 treeState.setExpandedState(path, true);
1678
1679 Enumeration descendants = tree.getExpandedDescendants(path);
1680
1681 if(descendants != null) {
1682 while(descendants.hasMoreElements()) {
1683 path = (TreePath)descendants.nextElement();
1684 treeState.setExpandedState(path, true);
1685 }
1686 }
1687 updateLeadRow();
1688 updateSize();
1689 }
1690 }
1691
1692 /**
1693 * Returns a path to the last child of <code>parent</code>.
1694 */
1695 protected TreePath getLastChildPath(TreePath parent) {
1696 if(treeModel != null) {
1697 int childCount = treeModel.getChildCount
1698 (parent.getLastPathComponent());
1699
1700 if(childCount > 0)
1701 return parent.pathByAddingChild(treeModel.getChild
1702 (parent.getLastPathComponent(), childCount - 1));
1703 }
1704 return null;
1705 }
1706
1707 /**
1708 * Updates how much each depth should be offset by.
1709 */
1710 protected void updateDepthOffset() {
1711 if(isRootVisible()) {
1712 if(getShowsRootHandles())
1713 depthOffset = 1;
1714 else
1715 depthOffset = 0;
1716 }
1717 else if(!getShowsRootHandles())
1718 depthOffset = -1;
1719 else
1720 depthOffset = 0;
1721 }
1722
1723 /**
1724 * Updates the cellEditor based on the editability of the JTree that
1725 * we're contained in. If the tree is editable but doesn't have a
1726 * cellEditor, a basic one will be used.
1727 */
1728 protected void updateCellEditor() {
1729 TreeCellEditor newEditor;
1730
1731 completeEditing();
1732 if(tree == null)
1733 newEditor = null;
1734 else {
1735 if(tree.isEditable()) {
1736 newEditor = tree.getCellEditor();
1737 if(newEditor == null) {
1738 newEditor = createDefaultCellEditor();
1739 if(newEditor != null) {
1740 tree.setCellEditor(newEditor);
1741 createdCellEditor = true;
1742 }
1743 }
1744 }
1745 else
1746 newEditor = null;
1747 }
1748 if(newEditor != cellEditor) {
1749 if(cellEditor != null && cellEditorListener != null)
1750 cellEditor.removeCellEditorListener(cellEditorListener);
1751 cellEditor = newEditor;
1752 if(cellEditorListener == null)
1753 cellEditorListener = createCellEditorListener();
1754 if(newEditor != null && cellEditorListener != null)
1755 newEditor.addCellEditorListener(cellEditorListener);
1756 createdCellEditor = false;
1757 }
1758 }
1759
1760 /**
1761 * Messaged from the tree we're in when the renderer has changed.
1762 */
1763 protected void updateRenderer() {
1764 if(tree != null) {
1765 TreeCellRenderer newCellRenderer;
1766
1767 newCellRenderer = tree.getCellRenderer();
1768 if(newCellRenderer == null) {
1769 tree.setCellRenderer(createDefaultCellRenderer());
1770 createdRenderer = true;
1771 }
1772 else {
1773 createdRenderer = false;
1774 currentCellRenderer = newCellRenderer;
1775 if(createdCellEditor) {
1776 tree.setCellEditor(null);
1777 }
1778 }
1779 }
1780 else {
1781 createdRenderer = false;
1782 currentCellRenderer = null;
1783 }
1784 updateCellEditor();
1785 }
1786
1787 /**
1788 * Resets the TreeState instance based on the tree we're providing the
1789 * look and feel for.
1790 */
1791 protected void configureLayoutCache() {
1792 if(treeState != null && tree != null) {
1793 if(nodeDimensions == null)
1794 nodeDimensions = createNodeDimensions();
1795 treeState.setNodeDimensions(nodeDimensions);
1796 treeState.setRootVisible(tree.isRootVisible());
1797 treeState.setRowHeight(tree.getRowHeight());
1798 treeState.setSelectionModel(getSelectionModel());
1799 // Only do this if necessary, may loss state if call with
1800 // same model as it currently has.
1801 if(treeState.getModel() != tree.getModel())
1802 treeState.setModel(tree.getModel());
1803 updateLayoutCacheExpandedNodesIfNecessary();
1804 // Create a listener to update preferred size when bounds
1805 // changes, if necessary.
1806 if(isLargeModel()) {
1807 if(componentListener == null) {
1808 componentListener = createComponentListener();
1809 if(componentListener != null)
1810 tree.addComponentListener(componentListener);
1811 }
1812 }
1813 else if(componentListener != null) {
1814 tree.removeComponentListener(componentListener);
1815 componentListener = null;
1816 }
1817 }
1818 else if(componentListener != null) {
1819 tree.removeComponentListener(componentListener);
1820 componentListener = null;
1821 }
1822 }
1823
1824 /**
1825 * Marks the cached size as being invalid, and messages the
1826 * tree with <code>treeDidChange</code>.
1827 */
1828 protected void updateSize() {
1829 validCachedPreferredSize = false;
1830 tree.treeDidChange();
1831 }
1832
1833 private void updateSize0() {
1834 validCachedPreferredSize = false;
1835 tree.revalidate();
1836 }
1837
1838 /**
1839 * Updates the <code>preferredSize</code> instance variable,
1840 * which is returned from <code>getPreferredSize()</code>.<p>
1841 * For left to right orientations, the size is determined from the
1842 * current AbstractLayoutCache. For RTL orientations, the preferred size
1843 * becomes the width minus the minimum x position.
1844 */
1845 protected void updateCachedPreferredSize() {
1846 if(treeState != null) {
1847 Insets i = tree.getInsets();
1848
1849 if(isLargeModel()) {
1850 Rectangle visRect = tree.getVisibleRect();
1851
1852 if (visRect.x == 0 && visRect.y == 0 &&
1853 visRect.width == 0 && visRect.height == 0 &&
1854 tree.getVisibleRowCount() > 0) {
1855 // The tree doesn't have a valid bounds yet. Calculate
1856 // based on visible row count.
1857 visRect.width = 1;
1858 visRect.height = tree.getRowHeight() *
1859 tree.getVisibleRowCount();
1860 } else {
1861 visRect.x -= i.left;
1862 visRect.y -= i.top;
1863 }
1864 preferredSize.width = treeState.getPreferredWidth(visRect);
1865 }
1866 else {
1867 preferredSize.width = treeState.getPreferredWidth(null);
1868 }
1869 preferredSize.height = treeState.getPreferredHeight();
1870 preferredSize.width += i.left + i.right;
1871 preferredSize.height += i.top + i.bottom;
1872 }
1873 validCachedPreferredSize = true;
1874 }
1875
1876 /**
1877 * Messaged from the VisibleTreeNode after it has been expanded.
1878 */
1879 protected void pathWasExpanded(TreePath path) {
1880 if(tree != null) {
1881 tree.fireTreeExpanded(path);
1882 }
1883 }
1884
1885 /**
1886 * Messaged from the VisibleTreeNode after it has collapsed.
1887 */
1888 protected void pathWasCollapsed(TreePath path) {
1889 if(tree != null) {
1890 tree.fireTreeCollapsed(path);
1891 }
1892 }
1893
1894 /**
1895 * Ensures that the rows identified by beginRow through endRow are
1896 * visible.
1897 */
1898 protected void ensureRowsAreVisible(int beginRow, int endRow) {
1899 if(tree != null && beginRow >= 0 && endRow < getRowCount(tree)) {
1900 boolean scrollVert = DefaultLookup.getBoolean(tree, this,
1901 "Tree.scrollsHorizontallyAndVertically", false);
1902 if(beginRow == endRow) {
1903 Rectangle scrollBounds = getPathBounds(tree, getPathForRow
1904 (tree, beginRow));
1905
1906 if(scrollBounds != null) {
1907 if (!scrollVert) {
1908 scrollBounds.x = tree.getVisibleRect().x;
1909 scrollBounds.width = 1;
1910 }
1911 tree.scrollRectToVisible(scrollBounds);
1912 }
1913 }
1914 else {
1915 Rectangle beginRect = getPathBounds(tree, getPathForRow
1916 (tree, beginRow));
1917 Rectangle visRect = tree.getVisibleRect();
1918 Rectangle testRect = beginRect;
1919 int beginY = beginRect.y;
1920 int maxY = beginY + visRect.height;
1921
1922 for(int counter = beginRow + 1; counter <= endRow; counter++) {
1923 testRect = getPathBounds(tree,
1924 getPathForRow(tree, counter));
1925 if((testRect.y + testRect.height) > maxY)
1926 counter = endRow;
1927 }
1928 tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1,
1929 testRect.y + testRect.height-
1930 beginY));
1931 }
1932 }
1933 }
1934
1935 /** Sets the preferred minimum size.
1936 */
1937 public void setPreferredMinSize(Dimension newSize) {
1938 preferredMinSize = newSize;
1939 }
1940
1941 /** Returns the minimum preferred size.
1942 */
1943 public Dimension getPreferredMinSize() {
1944 if(preferredMinSize == null)
1945 return null;
1946 return new Dimension(preferredMinSize);
1947 }
1948
1949 /** Returns the preferred size to properly display the tree,
1950 * this is a cover method for getPreferredSize(c, false).
1951 */
1952 public Dimension getPreferredSize(JComponent c) {
1953 return getPreferredSize(c, true);
1954 }
1955
1956 /** Returns the preferred size to represent the tree in
1957 * <I>c</I>. If <I>checkConsistancy</I> is true
1958 * <b>checkConsistancy</b> is messaged first.
1959 */
1960 public Dimension getPreferredSize(JComponent c,
1961 boolean checkConsistancy) {
1962 Dimension pSize = this.getPreferredMinSize();
1963
1964 if(!validCachedPreferredSize)
1965 updateCachedPreferredSize();
1966 if(tree != null) {
1967 if(pSize != null)
1968 return new Dimension(Math.max(pSize.width,
1969 preferredSize.width),
1970 Math.max(pSize.height, preferredSize.height));
1971 return new Dimension(preferredSize.width, preferredSize.height);
1972 }
1973 else if(pSize != null)
1974 return pSize;
1975 else
1976 return new Dimension(0, 0);
1977 }
1978
1979 /**
1980 * Returns the minimum size for this component. Which will be
1981 * the min preferred size or 0, 0.
1982 */
1983 public Dimension getMinimumSize(JComponent c) {
1984 if(this.getPreferredMinSize() != null)
1985 return this.getPreferredMinSize();
1986 return new Dimension(0, 0);
1987 }
1988
1989 /**
1990 * Returns the maximum size for this component, which will be the
1991 * preferred size if the instance is currently in a JTree, or 0, 0.
1992 */
1993 public Dimension getMaximumSize(JComponent c) {
1994 if(tree != null)
1995 return getPreferredSize(tree);
1996 if(this.getPreferredMinSize() != null)
1997 return this.getPreferredMinSize();
1998 return new Dimension(0, 0);
1999 }
2000
2001
2002 /**
2003 * Messages to stop the editing session. If the UI the receiver
2004 * is providing the look and feel for returns true from
2005 * <code>getInvokesStopCellEditing</code>, stopCellEditing will
2006 * invoked on the current editor. Then completeEditing will
2007 * be messaged with false, true, false to cancel any lingering
2008 * editing.
2009 */
2010 protected void completeEditing() {
2011 /* If should invoke stopCellEditing, try that */
2012 if(tree.getInvokesStopCellEditing() &&
2013 stopEditingInCompleteEditing && editingComponent != null) {
2014 cellEditor.stopCellEditing();
2015 }
2016 /* Invoke cancelCellEditing, this will do nothing if stopCellEditing
2017 was successful. */
2018 completeEditing(false, true, false);
2019 }
2020
2021 /**
2022 * Stops the editing session. If messageStop is true the editor
2023 * is messaged with stopEditing, if messageCancel is true the
2024 * editor is messaged with cancelEditing. If messageTree is true
2025 * the treeModel is messaged with valueForPathChanged.
2026 */
2027 protected void completeEditing(boolean messageStop,
2028 boolean messageCancel,
2029 boolean messageTree) {
2030 if(stopEditingInCompleteEditing && editingComponent != null) {
2031 Component oldComponent = editingComponent;
2032 TreePath oldPath = editingPath;
2033 TreeCellEditor oldEditor = cellEditor;
2034 Object newValue = oldEditor.getCellEditorValue();
2035 Rectangle editingBounds = getPathBounds(tree,
2036 editingPath);
2037 boolean requestFocus = (tree != null &&
2038 (tree.hasFocus() || SwingUtilities.
2039 findFocusOwner(editingComponent) != null));
2040
2041 editingComponent = null;
2042 editingPath = null;
2043 if(messageStop)
2044 oldEditor.stopCellEditing();
2045 else if(messageCancel)
2046 oldEditor.cancelCellEditing();
2047 tree.remove(oldComponent);
2048 if(editorHasDifferentSize) {
2049 treeState.invalidatePathBounds(oldPath);
2050 updateSize();
2051 }
2052 else {
2053 editingBounds.x = 0;
2054 editingBounds.width = tree.getSize().width;
2055 tree.repaint(editingBounds);
2056 }
2057 if(requestFocus)
2058 tree.requestFocus();
2059 if(messageTree)
2060 treeModel.valueForPathChanged(oldPath, newValue);
2061 }
2062 }
2063
2064 // cover method for startEditing that allows us to pass extra
2065 // information into that method via a class variable
2066 private boolean startEditingOnRelease(TreePath path,
2067 MouseEvent event,
2068 MouseEvent releaseEvent) {
2069 this.releaseEvent = releaseEvent;
2070 try {
2071 return startEditing(path, event);
2072 } finally {
2073 this.releaseEvent = null;
2074 }
2075 }
2076
2077 /**
2078 * Will start editing for node if there is a cellEditor and
2079 * shouldSelectCell returns true.<p>
2080 * This assumes that path is valid and visible.
2081 */
2082 protected boolean startEditing(TreePath path, MouseEvent event) {
2083 if (isEditing(tree) && tree.getInvokesStopCellEditing() &&
2084 !stopEditing(tree)) {
2085 return false;
2086 }
2087 completeEditing();
2088 if(cellEditor != null && tree.isPathEditable(path)) {
2089 int row = getRowForPath(tree, path);
2090
2091 if(cellEditor.isCellEditable(event)) {
2092 editingComponent = cellEditor.getTreeCellEditorComponent
2093 (tree, path.getLastPathComponent(),
2094 tree.isPathSelected(path), tree.isExpanded(path),
2095 treeModel.isLeaf(path.getLastPathComponent()), row);
2096 Rectangle nodeBounds = getPathBounds(tree, path);
2097
2098 editingRow = row;
2099
2100 Dimension editorSize = editingComponent.getPreferredSize();
2101
2102 // Only allow odd heights if explicitly set.
2103 if(editorSize.height != nodeBounds.height &&
2104 getRowHeight() > 0)
2105 editorSize.height = getRowHeight();
2106
2107 if(editorSize.width != nodeBounds.width ||
2108 editorSize.height != nodeBounds.height) {
2109 // Editor wants different width or height, invalidate
2110 // treeState and relayout.
2111 editorHasDifferentSize = true;
2112 treeState.invalidatePathBounds(path);
2113 updateSize();
2114 // To make sure x/y are updated correctly, fetch
2115 // the bounds again.
2116 nodeBounds = getPathBounds(tree, path);
2117 }
2118 else
2119 editorHasDifferentSize = false;
2120 tree.add(editingComponent);
2121 editingComponent.setBounds(nodeBounds.x, nodeBounds.y,
2122 nodeBounds.width,
2123 nodeBounds.height);
2124 editingPath = path;
2125 if (editingComponent instanceof JComponent) {
2126 ((JComponent)editingComponent).revalidate();
2127 } else {
2128 editingComponent.validate();
2129 }
2130 editingComponent.repaint();
2131 if(cellEditor.shouldSelectCell(event)) {
2132 stopEditingInCompleteEditing = false;
2133 tree.setSelectionRow(row);
2134 stopEditingInCompleteEditing = true;
2135 }
2136
2137 Component focusedComponent = SwingUtilities2.
2138 compositeRequestFocus(editingComponent);
2139 boolean selectAll = true;
2140
2141 if(event != null && event instanceof MouseEvent) {
2142 /* Find the component that will get forwarded all the
2143 mouse events until mouseReleased. */
2144 Point componentPoint = SwingUtilities.convertPoint
2145 (tree, new Point(event.getX(), event.getY()),
2146 editingComponent);
2147
2148 /* Create an instance of BasicTreeMouseListener to handle
2149 passing the mouse/motion events to the necessary
2150 component. */
2151 // We really want similar behavior to getMouseEventTarget,
2152 // but it is package private.
2153 Component activeComponent = SwingUtilities.
2154 getDeepestComponentAt(editingComponent,
2155 componentPoint.x, componentPoint.y);
2156 if (activeComponent != null) {
2157 MouseInputHandler handler =
2158 new MouseInputHandler(tree, activeComponent,
2159 event, focusedComponent);
2160
2161 if (releaseEvent != null) {
2162 handler.mouseReleased(releaseEvent);
2163 }
2164
2165 selectAll = false;
2166 }
2167 }
2168 if (selectAll && focusedComponent instanceof JTextField) {
2169 ((JTextField)focusedComponent).selectAll();
2170 }
2171 return true;
2172 }
2173 else
2174 editingComponent = null;
2175 }
2176 return false;
2177 }
2178
2179 //
2180 // Following are primarily for handling mouse events.
2181 //
2182
2183 /**
2184 * If the <code>mouseX</code> and <code>mouseY</code> are in the
2185 * expand/collapse region of the <code>row</code>, this will toggle
2186 * the row.
2187 */
2188 protected void checkForClickInExpandControl(TreePath path,
2189 int mouseX, int mouseY) {
2190 if (isLocationInExpandControl(path, mouseX, mouseY)) {
2191 handleExpandControlClick(path, mouseX, mouseY);
2192 }
2193 }
2194
2195 /**
2196 * Returns true if <code>mouseX</code> and <code>mouseY</code> fall
2197 * in the area of row that is used to expand/collapse the node and
2198 * the node at <code>row</code> does not represent a leaf.
2199 */
2200 protected boolean isLocationInExpandControl(TreePath path,
2201 int mouseX, int mouseY) {
2202 if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){
2203 int boxWidth;
2204 Insets i = tree.getInsets();
2205
2206 if(getExpandedIcon() != null)
2207 boxWidth = getExpandedIcon().getIconWidth();
2208 else
2209 boxWidth = 8;
2210
2211 int boxLeftX = getRowX(tree.getRowForPath(path),
2212 path.getPathCount() - 1);
2213
2214 if (leftToRight) {
2215 boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1;
2216 } else {
2217 boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1;
2218 }
2219
2220 boxLeftX = findCenteredX(boxLeftX, boxWidth);
2221
2222 return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth));
2223 }
2224 return false;
2225 }
2226
2227 /**
2228 * Messaged when the user clicks the particular row, this invokes
2229 * toggleExpandState.
2230 */
2231 protected void handleExpandControlClick(TreePath path, int mouseX,
2232 int mouseY) {
2233 toggleExpandState(path);
2234 }
2235
2236 /**
2237 * Expands path if it is not expanded, or collapses row if it is expanded.
2238 * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible
2239 * is invoked to scroll as many of the children to visible as possible
2240 * (tries to scroll to last visible descendant of path).
2241 */
2242 protected void toggleExpandState(TreePath path) {
2243 if(!tree.isExpanded(path)) {
2244 int row = getRowForPath(tree, path);
2245
2246 tree.expandPath(path);
2247 updateSize();
2248 if(row != -1) {
2249 if(tree.getScrollsOnExpand())
2250 ensureRowsAreVisible(row, row + treeState.
2251 getVisibleChildCount(path));
2252 else
2253 ensureRowsAreVisible(row, row);
2254 }
2255 }
2256 else {
2257 tree.collapsePath(path);
2258 updateSize();
2259 }
2260 }
2261
2262 /**
2263 * Returning true signifies a mouse event on the node should toggle
2264 * the selection of only the row under mouse.
2265 */
2266 protected boolean isToggleSelectionEvent(MouseEvent event) {
2267 return (SwingUtilities.isLeftMouseButton(event) &&
2268 event.isControlDown());
2269 }
2270
2271 /**
2272 * Returning true signifies a mouse event on the node should select
2273 * from the anchor point.
2274 */
2275 protected boolean isMultiSelectEvent(MouseEvent event) {
2276 return (SwingUtilities.isLeftMouseButton(event) &&
2277 event.isShiftDown());
2278 }
2279
2280 /**
2281 * Returning true indicates the row under the mouse should be toggled
2282 * based on the event. This is invoked after checkForClickInExpandControl,
2283 * implying the location is not in the expand (toggle) control
2284 */
2285 protected boolean isToggleEvent(MouseEvent event) {
2286 if(!SwingUtilities.isLeftMouseButton(event)) {
2287 return false;
2288 }
2289 int clickCount = tree.getToggleClickCount();
2290
2291 if(clickCount <= 0) {
2292 return false;
2293 }
2294 return ((event.getClickCount() % clickCount) == 0);
2295 }
2296
2297 /**
2298 * Messaged to update the selection based on a MouseEvent over a
2299 * particular row. If the event is a toggle selection event, the
2300 * row is either selected, or deselected. If the event identifies
2301 * a multi selection event, the selection is updated from the
2302 * anchor point. Otherwise the row is selected, and if the event
2303 * specified a toggle event the row is expanded/collapsed.
2304 */
2305 protected void selectPathForEvent(TreePath path, MouseEvent event) {
2306 /* Adjust from the anchor point. */
2307 if(isMultiSelectEvent(event)) {
2308 TreePath anchor = getAnchorSelectionPath();
2309 int anchorRow = (anchor == null) ? -1 :
2310 getRowForPath(tree, anchor);
2311
2312 if(anchorRow == -1 || tree.getSelectionModel().
2313 getSelectionMode() == TreeSelectionModel.
2314 SINGLE_TREE_SELECTION) {
2315 tree.setSelectionPath(path);
2316 }
2317 else {
2318 int row = getRowForPath(tree, path);
2319 TreePath lastAnchorPath = anchor;
2320
2321 if (isToggleSelectionEvent(event)) {
2322 if (tree.isRowSelected(anchorRow)) {
2323 tree.addSelectionInterval(anchorRow, row);
2324 } else {
2325 tree.removeSelectionInterval(anchorRow, row);
2326 tree.addSelectionInterval(row, row);
2327 }
2328 } else if(row < anchorRow) {
2329 tree.setSelectionInterval(row, anchorRow);
2330 } else {
2331 tree.setSelectionInterval(anchorRow, row);
2332 }
2333 lastSelectedRow = row;
2334 setAnchorSelectionPath(lastAnchorPath);
2335 setLeadSelectionPath(path);
2336 }
2337 }
2338
2339 // Should this event toggle the selection of this row?
2340 /* Control toggles just this node. */
2341 else if(isToggleSelectionEvent(event)) {
2342 if(tree.isPathSelected(path))
2343 tree.removeSelectionPath(path);
2344 else
2345 tree.addSelectionPath(path);
2346 lastSelectedRow = getRowForPath(tree, path);
2347 setAnchorSelectionPath(path);
2348 setLeadSelectionPath(path);
2349 }
2350
2351 /* Otherwise set the selection to just this interval. */
2352 else if(SwingUtilities.isLeftMouseButton(event)) {
2353 tree.setSelectionPath(path);
2354 if(isToggleEvent(event)) {
2355 toggleExpandState(path);
2356 }
2357 }
2358 }
2359
2360 /**
2361 * @return true if the node at <code>row</code> is a leaf.
2362 */
2363 protected boolean isLeaf(int row) {
2364 TreePath path = getPathForRow(tree, row);
2365
2366 if(path != null)
2367 return treeModel.isLeaf(path.getLastPathComponent());
2368 // Have to return something here...
2369 return true;
2370 }
2371
2372 //
2373 // The following selection methods (lead/anchor) are covers for the
2374 // methods in JTree.
2375 //
2376 private void setAnchorSelectionPath(TreePath newPath) {
2377 ignoreLAChange = true;
2378 try {
2379 tree.setAnchorSelectionPath(newPath);
2380 } finally{
2381 ignoreLAChange = false;
2382 }
2383 }
2384
2385 private TreePath getAnchorSelectionPath() {
2386 return tree.getAnchorSelectionPath();
2387 }
2388
2389 private void setLeadSelectionPath(TreePath newPath) {
2390 setLeadSelectionPath(newPath, false);
2391 }
2392
2393 private void setLeadSelectionPath(TreePath newPath, boolean repaint) {
2394 Rectangle bounds = repaint ?
2395 getPathBounds(tree, getLeadSelectionPath()) : null;
2396
2397 ignoreLAChange = true;
2398 try {
2399 tree.setLeadSelectionPath(newPath);
2400 } finally {
2401 ignoreLAChange = false;
2402 }
2403 leadRow = getRowForPath(tree, newPath);
2404
2405 if(repaint) {
2406 if(bounds != null)
2407 tree.repaint(bounds);
2408 bounds = getPathBounds(tree, newPath);
2409 if(bounds != null)
2410 tree.repaint(bounds);
2411 }
2412 }
2413
2414 private TreePath getLeadSelectionPath() {
2415 return tree.getLeadSelectionPath();
2416 }
2417
2418 private void updateLeadRow() {
2419 leadRow = getRowForPath(tree, getLeadSelectionPath());
2420 }
2421
2422 private int getLeadSelectionRow() {
2423 return leadRow;
2424 }
2425
2426 /**
2427 * Extends the selection from the anchor to make <code>newLead</code>
2428 * the lead of the selection. This does not scroll.
2429 */
2430 private void extendSelection(TreePath newLead) {
2431 TreePath aPath = getAnchorSelectionPath();
2432 int aRow = (aPath == null) ? -1 :
2433 getRowForPath(tree, aPath);
2434 int newIndex = getRowForPath(tree, newLead);
2435
2436 if(aRow == -1) {
2437 tree.setSelectionRow(newIndex);
2438 }
2439 else {
2440 if(aRow < newIndex) {
2441 tree.setSelectionInterval(aRow, newIndex);
2442 }
2443 else {
2444 tree.setSelectionInterval(newIndex, aRow);
2445 }
2446 setAnchorSelectionPath(aPath);
2447 setLeadSelectionPath(newLead);
2448 }
2449 }
2450
2451 /**
2452 * Invokes <code>repaint</code> on the JTree for the passed in TreePath,
2453 * <code>path</code>.
2454 */
2455 private void repaintPath(TreePath path) {
2456 if (path != null) {
2457 Rectangle bounds = getPathBounds(tree, path);
2458 if (bounds != null) {
2459 tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
2460 }
2461 }
2462 }
2463
2464 /**
2465 * Updates the TreeState in response to nodes expanding/collapsing.
2466 */
2467 public class TreeExpansionHandler implements TreeExpansionListener {
2468 // NOTE: This class exists only for backward compatability. All
2469 // its functionality has been moved into Handler. If you need to add
2470 // new functionality add it to the Handler, but make sure this
2471 // class calls into the Handler.
2472
2473 /**
2474 * Called whenever an item in the tree has been expanded.
2475 */
2476 public void treeExpanded(TreeExpansionEvent event) {
2477 getHandler().treeExpanded(event);
2478 }
2479
2480 /**
2481 * Called whenever an item in the tree has been collapsed.
2482 */
2483 public void treeCollapsed(TreeExpansionEvent event) {
2484 getHandler().treeCollapsed(event);
2485 }
2486 } // BasicTreeUI.TreeExpansionHandler
2487
2488
2489 /**
2490 * Updates the preferred size when scrolling (if necessary).
2491 */
2492 public class ComponentHandler extends ComponentAdapter implements
2493