History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/plaf/basic/BasicTreeUI.java (1 of 18352 files). Show all

Unified · Split

java/pkg/javax/swing/plaf/basic/BasicTreeUI.java+4557−0
@@ -0,0 +1,4557 @@
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
26package javax.swing.plaf.basic;
27
28import javax.swing.*;
29import javax.swing.event.*;
30import java.awt.*;
31import java.awt.event.*;
32import java.awt.datatransfer.*;
33import java.beans.*;
34import java.util.Enumeration;
35import java.util.Hashtable;
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.Comparator;
39import javax.swing.plaf.ComponentUI;
40import javax.swing.plaf.UIResource;
41import javax.swing.plaf.TreeUI;
42import javax.swing.tree.*;
43import javax.swing.text.Position;
44import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
45import sun.swing.SwingUtilities2;
46
47import sun.swing.DefaultLookup;
48import 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
58public 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 Rectangle visRect = tree.getVisibleRect();
1936 Rectangle testRect = beginRect;
1937 int beginY = beginRect.y;
1938 int maxY = beginY + visRect.height;
1939
1940 for(int counter = beginRow + 1; counter <= endRow; counter++) {
1941 testRect = getPathBounds(tree,
1942 getPathForRow(tree, counter));
1943 if((testRect.y + testRect.height) > maxY)
1944 counter = endRow;
1945 }
1946 tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1,
1947 testRect.y + testRect.height-
1948 beginY));
1949 }
1950 }
1951 }
1952
1953 /** Sets the preferred minimum size.
1954 */
1955 public void setPreferredMinSize(Dimension newSize) {
1956 preferredMinSize = newSize;
1957 }
1958
1959 /** Returns the minimum preferred size.
1960 */
1961 public Dimension getPreferredMinSize() {
1962 if(preferredMinSize == null)
1963 return null;
1964 return new Dimension(preferredMinSize);
1965 }
1966
1967 /** Returns the preferred size to properly display the tree,
1968 * this is a cover method for getPreferredSize(c, true).
1969 */
1970 public Dimension getPreferredSize(JComponent c) {
1971 return getPreferredSize(c, true);
1972 }
1973
1974 /** Returns the preferred size to represent the tree in
1975 * <I>c</I>. If <I>checkConsistency</I> is true
1976 * <b>checkConsistency</b> is messaged first.
1977 */
1978 public Dimension getPreferredSize(JComponent c,
1979 boolean checkConsistency) {
1980 Dimension pSize = this.getPreferredMinSize();
1981
1982 if(!validCachedPreferredSize)
1983 updateCachedPreferredSize();
1984 if(tree != null) {
1985 if(pSize != null)
1986 return new Dimension(Math.max(pSize.width,
1987 preferredSize.width),
1988 Math.max(pSize.height, preferredSize.height));
1989 return new Dimension(preferredSize.width, preferredSize.height);
1990 }
1991 else if(pSize != null)
1992 return pSize;
1993 else
1994 return new Dimension(0, 0);
1995 }
1996
1997 /**
1998 * Returns the minimum size for this component. Which will be
1999 * the min preferred size or 0, 0.
2000 */
2001 public Dimension getMinimumSize(JComponent c) {
2002 if(this.getPreferredMinSize() != null)
2003 return this.getPreferredMinSize();
2004 return new Dimension(0, 0);
2005 }
2006
2007 /**
2008 * Returns the maximum size for this component, which will be the
2009 * preferred size if the instance is currently in a JTree, or 0, 0.
2010 */
2011 public Dimension getMaximumSize(JComponent c) {
2012 if(tree != null)
2013 return getPreferredSize(tree);
2014 if(this.getPreferredMinSize() != null)
2015 return this.getPreferredMinSize();
2016 return new Dimension(0, 0);
2017 }
2018
2019
2020 /**
2021 * Messages to stop the editing session. If the UI the receiver
2022 * is providing the look and feel for returns true from
2023 * <code>getInvokesStopCellEditing</code>, stopCellEditing will
2024 * invoked on the current editor. Then completeEditing will
2025 * be messaged with false, true, false to cancel any lingering
2026 * editing.
2027 */
2028 protected void completeEditing() {
2029 /* If should invoke stopCellEditing, try that */
2030 if(tree.getInvokesStopCellEditing() &&
2031 stopEditingInCompleteEditing && editingComponent != null) {
2032 cellEditor.stopCellEditing();
2033 }
2034 /* Invoke cancelCellEditing, this will do nothing if stopCellEditing
2035 was successful. */
2036 completeEditing(false, true, false);
2037 }
2038
2039 /**
2040 * Stops the editing session. If messageStop is true the editor
2041 * is messaged with stopEditing, if messageCancel is true the
2042 * editor is messaged with cancelEditing. If messageTree is true
2043 * the treeModel is messaged with valueForPathChanged.
2044 */
2045 protected void completeEditing(boolean messageStop,
2046 boolean messageCancel,
2047 boolean messageTree) {
2048 if(stopEditingInCompleteEditing && editingComponent != null) {
2049 Component oldComponent = editingComponent;
2050 TreePath oldPath = editingPath;
2051 TreeCellEditor oldEditor = cellEditor;
2052 Object newValue = oldEditor.getCellEditorValue();
2053 Rectangle editingBounds = getPathBounds(tree,
2054 editingPath);
2055 boolean requestFocus = (tree != null &&
2056 (tree.hasFocus() || SwingUtilities.
2057 findFocusOwner(editingComponent) != null));
2058
2059 editingComponent = null;
2060 editingPath = null;
2061 if(messageStop)
2062 oldEditor.stopCellEditing();
2063 else if(messageCancel)
2064 oldEditor.cancelCellEditing();
2065 tree.remove(oldComponent);
2066 if(editorHasDifferentSize) {
2067 treeState.invalidatePathBounds(oldPath);
2068 updateSize();
2069 }
2070 else {
2071 editingBounds.x = 0;
2072 editingBounds.width = tree.getSize().width;
2073 tree.repaint(editingBounds);
2074 }
2075 if(requestFocus)
2076 tree.requestFocus();
2077 if(messageTree)
2078 treeModel.valueForPathChanged(oldPath, newValue);
2079 }
2080 }
2081
2082 // cover method for startEditing that allows us to pass extra
2083 // information into that method via a class variable
2084 private boolean startEditingOnRelease(TreePath path,
2085 MouseEvent event,
2086 MouseEvent releaseEvent) {
2087 this.releaseEvent = releaseEvent;
2088 try {
2089 return startEditing(path, event);
2090 } finally {
2091 this.releaseEvent = null;
2092 }
2093 }
2094
2095 /**
2096 * Will start editing for node if there is a cellEditor and
2097 * shouldSelectCell returns true.<p>
2098 * This assumes that path is valid and visible.
2099 */
2100 protected boolean startEditing(TreePath path, MouseEvent event) {
2101 if (isEditing(tree) && tree.getInvokesStopCellEditing() &&
2102 !stopEditing(tree)) {
2103 return false;
2104 }
2105 completeEditing();
2106 if(cellEditor != null && tree.isPathEditable(path)) {
2107 int row = getRowForPath(tree, path);
2108
2109 if(cellEditor.isCellEditable(event)) {
2110 editingComponent = cellEditor.getTreeCellEditorComponent
2111 (tree, path.getLastPathComponent(),
2112 tree.isPathSelected(path), tree.isExpanded(path),
2113 treeModel.isLeaf(path.getLastPathComponent()), row);
2114 Rectangle nodeBounds = getPathBounds(tree, path);
2115
2116 editingRow = row;
2117
2118 Dimension editorSize = editingComponent.getPreferredSize();
2119
2120 // Only allow odd heights if explicitly set.
2121 if(editorSize.height != nodeBounds.height &&
2122 getRowHeight() > 0)
2123 editorSize.height = getRowHeight();
2124
2125 if(editorSize.width != nodeBounds.width ||
2126 editorSize.height != nodeBounds.height) {
2127 // Editor wants different width or height, invalidate
2128 // treeState and relayout.
2129 editorHasDifferentSize = true;
2130 treeState.invalidatePathBounds(path);
2131 updateSize();
2132 // To make sure x/y are updated correctly, fetch
2133 // the bounds again.
2134 nodeBounds = getPathBounds(tree, path);
2135 }
2136 else
2137 editorHasDifferentSize = false;
2138 tree.add(editingComponent);
2139 editingComponent.setBounds(nodeBounds.x, nodeBounds.y,
2140 nodeBounds.width,
2141 nodeBounds.height);
2142 editingPath = path;
2143 if (editingComponent instanceof JComponent) {
2144 ((JComponent)editingComponent).revalidate();
2145 } else {
2146 editingComponent.validate();
2147 }
2148 editingComponent.repaint();
2149 if(cellEditor.shouldSelectCell(event)) {
2150 stopEditingInCompleteEditing = false;
2151 tree.setSelectionRow(row);
2152 stopEditingInCompleteEditing = true;
2153 }
2154
2155 Component focusedComponent = SwingUtilities2.
2156 compositeRequestFocus(editingComponent);
2157 boolean selectAll = true;
2158
2159 if(event != null) {
2160 /* Find the component that will get forwarded all the
2161 mouse events until mouseReleased. */
2162 Point componentPoint = SwingUtilities.convertPoint
2163 (tree, new Point(event.getX(), event.getY()),
2164 editingComponent);
2165
2166 /* Create an instance of BasicTreeMouseListener to handle
2167 passing the mouse/motion events to the necessary
2168 component. */
2169 // We really want similar behavior to getMouseEventTarget,
2170 // but it is package private.
2171 Component activeComponent = SwingUtilities.
2172 getDeepestComponentAt(editingComponent,
2173 componentPoint.x, componentPoint.y);
2174 if (activeComponent != null) {
2175 MouseInputHandler handler =
2176 new MouseInputHandler(tree, activeComponent,
2177 event, focusedComponent);
2178
2179 if (releaseEvent != null) {
2180 handler.mouseReleased(releaseEvent);
2181 }
2182
2183 selectAll = false;
2184 }
2185 }
2186 if (selectAll && focusedComponent instanceof JTextField) {
2187 ((JTextField)focusedComponent).selectAll();
2188 }
2189 return true;
2190 }
2191 else
2192 editingComponent = null;
2193 }
2194 return false;
2195 }
2196
2197 //
2198 // Following are primarily for handling mouse events.
2199 //
2200
2201 /**
2202 * If the <code>mouseX</code> and <code>mouseY</code> are in the
2203 * expand/collapse region of the <code>row</code>, this will toggle
2204 * the row.
2205 */
2206 protected void checkForClickInExpandControl(TreePath path,
2207 int mouseX, int mouseY) {
2208 if (isLocationInExpandControl(path, mouseX, mouseY)) {
2209 handleExpandControlClick(path, mouseX, mouseY);
2210 }
2211 }
2212
2213 /**
2214 * Returns true if <code>mouseX</code> and <code>mouseY</code> fall
2215 * in the area of row that is used to expand/collapse the node and
2216 * the node at <code>row</code> does not represent a leaf.
2217 */
2218 protected boolean isLocationInExpandControl(TreePath path,
2219 int mouseX, int mouseY) {
2220 if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){
2221 int boxWidth;
2222 Insets i = tree.getInsets();
2223
2224 if(getExpandedIcon() != null)
2225 boxWidth = getExpandedIcon().getIconWidth();
2226 else
2227 boxWidth = 8;
2228
2229 int boxLeftX = getRowX(tree.getRowForPath(path),
2230 path.getPathCount() - 1);
2231
2232 if (leftToRight) {
2233 boxLeftX = boxLeftX + i.left - getRightChildIndent() + 1;
2234 } else {
2235 boxLeftX = tree.getWidth() - boxLeftX - i.right + getRightChildIndent() - 1;
2236 }
2237
2238 boxLeftX = findCenteredX(boxLeftX, boxWidth);
2239
2240 return (mouseX >= boxLeftX && mouseX < (boxLeftX + boxWidth));
2241 }
2242 return false;
2243 }
2244
2245 /**
2246 * Messaged when the user clicks the particular row, this invokes
2247 * toggleExpandState.
2248 */
2249 protected void handleExpandControlClick(TreePath path, int mouseX,
2250 int mouseY) {
2251 toggleExpandState(path);
2252 }
2253
2254 /**
2255 * Expands path if it is not expanded, or collapses row if it is expanded.
2256 * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible
2257 * is invoked to scroll as many of the children to visible as possible
2258 * (tries to scroll to last visible descendant of path).
2259 */
2260 protected void toggleExpandState(TreePath path) {
2261 if(!tree.isExpanded(path)) {
2262 int row = getRowForPath(tree, path);
2263
2264 tree.expandPath(path);
2265 updateSize();
2266 if(row != -1) {
2267 if(tree.getScrollsOnExpand())
2268 ensureRowsAreVisible(row, row + treeState.
2269 getVisibleChildCount(path));
2270 else
2271 ensureRowsAreVisible(row, row);
2272 }
2273 }
2274 else {
2275 tree.collapsePath(path);
2276 updateSize();
2277 }
2278 }
2279
2280 /**
2281 * Returning true signifies a mouse event on the node should toggle
2282 * the selection of only the row under mouse.
2283 */
2284 protected boolean isToggleSelectionEvent(MouseEvent event) {
2285 return (SwingUtilities.isLeftMouseButton(event) &&
2286 BasicGraphicsUtils.isMenuShortcutKeyDown(event));
2287 }
2288
2289 /**
2290 * Returning true signifies a mouse event on the node should select
2291 * from the anchor point.
2292 */
2293 protected boolean isMultiSelectEvent(MouseEvent event) {
2294 return (SwingUtilities.isLeftMouseButton(event) &&
2295 event.isShiftDown());
2296 }
2297
2298 /**
2299 * Returning true indicates the row under the mouse should be toggled
2300 * based on the event. This is invoked after checkForClickInExpandControl,
2301 * implying the location is not in the expand (toggle) control
2302 */
2303 protected boolean isToggleEvent(MouseEvent event) {
2304 if(!SwingUtilities.isLeftMouseButton(event)) {
2305 return false;
2306 }
2307 int clickCount = tree.getToggleClickCount();
2308
2309 if(clickCount <= 0) {
2310 return false;
2311 }
2312 return ((event.getClickCount() % clickCount) == 0);
2313 }
2314
2315 /**
2316 * Messaged to update the selection based on a MouseEvent over a
2317 * particular row. If the event is a toggle selection event, the
2318 * row is either selected, or deselected. If the event identifies
2319 * a multi selection event, the selection is updated from the
2320 * anchor point. Otherwise the row is selected, and if the event
2321 * specified a toggle event the row is expanded/collapsed.
2322 */
2323 protected void selectPathForEvent(TreePath path, MouseEvent event) {
2324 /* Adjust from the anchor point. */
2325 if(isMultiSelectEvent(event)) {
2326 TreePath anchor = getAnchorSelectionPath();
2327 int anchorRow = (anchor == null) ? -1 :
2328 getRowForPath(tree, anchor);
2329
2330 if(anchorRow == -1 || tree.getSelectionModel().
2331 getSelectionMode() == TreeSelectionModel.
2332 SINGLE_TREE_SELECTION) {
2333 tree.setSelectionPath(path);
2334 }
2335 else {
2336 int row = getRowForPath(tree, path);
2337 TreePath lastAnchorPath = anchor;
2338
2339 if (isToggleSelectionEvent(event)) {
2340 if (tree.isRowSelected(anchorRow)) {
2341 tree.addSelectionInterval(anchorRow, row);
2342 } else {
2343 tree.removeSelectionInterval(anchorRow, row);
2344 tree.addSelectionInterval(row, row);
2345 }
2346 } else if(row < anchorRow) {
2347 tree.setSelectionInterval(row, anchorRow);
2348 } else {
2349 tree.setSelectionInterval(anchorRow, row);
2350 }
2351 lastSelectedRow = row;
2352 setAnchorSelectionPath(lastAnchorPath);
2353 setLeadSelectionPath(path);
2354 }
2355 }
2356
2357 // Should this event toggle the selection of this row?
2358 /* Control toggles just this node. */
2359 else if(isToggleSelectionEvent(event)) {
2360 if(tree.isPathSelected(path))
2361 tree.removeSelectionPath(path);
2362 else
2363 tree.addSelectionPath(path);
2364 lastSelectedRow = getRowForPath(tree, path);
2365 setAnchorSelectionPath(path);
2366 setLeadSelectionPath(path);
2367 }
2368
2369 /* Otherwise set the selection to just this interval. */
2370 else if(SwingUtilities.isLeftMouseButton(event)) {
2371 tree.setSelectionPath(path);
2372 if(isToggleEvent(event)) {
2373 toggleExpandState(path);
2374 }
2375 }
2376 }
2377
2378 /**
2379 * @return true if the node at <code>row</code> is a leaf.
2380 */
2381 protected boolean isLeaf(int row) {
2382 TreePath path = getPathForRow(tree, row);
2383
2384 if(path != null)
2385 return treeModel.isLeaf(path.getLastPathComponent());
2386 // Have to return something here...
2387 return true;
2388 }
2389
2390 //
2391 // The following selection methods (lead/anchor) are covers for the
2392 // methods in JTree.
2393 //
2394 private void setAnchorSelectionPath(TreePath newPath) {
2395 ignoreLAChange = true;
2396 try {
2397 tree.setAnchorSelectionPath(newPath);
2398 } finally{
2399 ignoreLAChange = false;
2400 }
2401 }
2402
2403 private TreePath getAnchorSelectionPath() {
2404 return tree.getAnchorSelectionPath();
2405 }
2406
2407 private void setLeadSelectionPath(TreePath newPath) {
2408 setLeadSelectionPath(newPath, false);
2409 }
2410
2411 private void setLeadSelectionPath(TreePath newPath, boolean repaint) {
2412 Rectangle bounds = repaint ?
2413 getPathBounds(tree, getLeadSelectionPath()) : null;
2414
2415 ignoreLAChange = true;
2416 try {
2417 tree.setLeadSelectionPath(newPath);
2418 } finally {
2419 ignoreLAChange = false;
2420 }
2421 leadRow = getRowForPath(tree, newPath);
2422
2423 if (repaint) {
2424 if (bounds != null) {
2425 tree.repaint(getRepaintPathBounds(bounds));
2426 }
2427 bounds = getPathBounds(tree, newPath);
2428 if (bounds != null) {
2429 tree.repaint(getRepaintPathBounds(bounds));
2430 }
2431 }
2432 }
2433
2434 private Rectangle getRepaintPathBounds(Rectangle bounds) {
2435 if (UIManager.getBoolean("Tree.repaintWholeRow")) {
2436 bounds.x = 0;
2437 bounds.width = tree.getWidth();
2438 }
2439 return bounds;
2440 }
2441
2442 private TreePath getLeadSelectionPath() {
2443 return tree.getLeadSelectionPath();
2444 }
2445
2446 /**
2447 * Updates the lead row of the selection.
2448 * @since 1.7
2449 */
2450 protected void updateLeadSelectionRow() {
2451 leadRow = getRowForPath(tree, getLeadSelectionPath());
2452 }
2453
2454 /**
2455 * Returns the lead row of the selection.
2456 *
2457 * @return selection lead row
2458 * @since 1.7
2459 */
2460 protected int getLeadSelectionRow() {
2461 return leadRow;
2462 }
2463
2464 /**
2465 * Extends the selection from the anchor to make <code>newLead</code>
2466 * the lead of the selection. This does not scroll.
2467 */
2468 private void extendSelection(TreePath newLead) {
2469 TreePath aPath = getAnchorSelectionPath();
2470 int aRow = (aPath == null) ? -1 :
2471 getRowForPath(tree, aPath);
2472 int newIndex = getRowForPath(tree, newLead);
2473
2474 if(aRow == -1) {
2475 tree.setSelectionRow(newIndex);
2476 }
2477 else {
2478 if(aRow < newIndex) {
2479 tree.setSelectionInterval(aRow, newIndex);
2480 }
2481 else {
2482 tree.setSelectionInterval(newIndex, aRow);
2483 }
2484 setAnchorSelectionPath(aPath);
2485 setLeadSelectionPath(newLead);
2486 }
2487 }
2488
2489 /**
2490 * Invokes <code>repaint</code> on the JTree for the passed in TreePath,
2491 * <code>path</code>.
2492 */
2493 private void repaintPath(TreePath path) {
2494 if (path != null) {
2495 Rectangle bounds = getPathBounds(tree, path);
2496 if (bounds != null) {
2497 tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
2498 }
2499 }
2500 }
2501
2502 /**
2503 * Updates the TreeState in response to nodes expanding/collapsing.
2504 */
2505 public class TreeExpansionHandler implements TreeExpansionListener {
2506 // NOTE: This class exists only for backward compatability. All
2507 // its functionality has been moved into Handler. If you need to add
2508 // new functionality add it to the Handler, but make sure this
2509 // class calls into the Handler.
2510
2511 /**
2512 * Called whenever an item in the tree has been expanded.
2513 */
2514 public void treeExpanded(TreeExpansionEvent event) {
2515 getHandler().treeExpanded(event);
2516 }
2517
2518 /**
2519 * Called whenever an item in the tree has been collapsed.
2520 */
2521 public void treeCollapsed(TreeExpansionEvent event) {
2522 getHandler().treeCollapsed(event);
2523 }
2524 } // BasicTreeUI.TreeExpansionHandler
2525
2526
2527 /**
2528 * Updates the preferred size when scrolling (if necessary).
2529 */
2530 public class ComponentHandler extends ComponentAdapter implements
2531 ActionListener {
2532 /** Timer used when inside a scrollpane and the scrollbar is
2533 * adjusting. */
2534 protected Timer timer;
2535 /** ScrollBar that is being adjusted. */
2536 protected JScrollBar scrollBar;
2537
2538 public void componentMoved(ComponentEvent e) {
2539 if(timer == null) {
2540 JScrollPane scrollPane = getScrollPane();
2541
2542 if(scrollPane == null)
2543 updateSize();
2544 else {
2545 scrollBar = scrollPane.getVerticalScrollBar();
2546 if(scrollBar == null ||
2547 !scrollBar.getValueIsAdjusting()) {
2548 // Try the horizontal scrollbar.
2549 if((scrollBar = scrollPane.getHorizontalScrollBar())
2550 != null && scrollBar.getValueIsAdjusting())
2551 startTimer();
2552 else
2553 updateSize();
2554 }
2555 else
2556 startTimer();
2557 }
2558 }
2559 }
2560
2561 /**
2562 * Creates, if necessary, and starts a Timer to check if need to
2563 * resize the bounds.
2564 */
2565 protected void startTimer() {
2566 if(timer == null) {
2567 timer = new Timer(200, this);
2568 timer.setRepeats(true);
2569 }
2570 timer.start();
2571 }
2572
2573 /**
2574 * Returns the JScrollPane housing the JTree, or null if one isn't
2575 * found.
2576 */
2577 protected JScrollPane getScrollPane() {
2578 Component c = tree.getParent();
2579
2580 while(c != null && !(c instanceof JScrollPane))
2581 c = c.getParent();
2582 if(c instanceof JScrollPane)
2583 return (JScrollPane)c;
2584 return null;
2585 }
2586
2587 /**
2588 * Public as a result of Timer. If the scrollBar is null, or
2589 * not adjusting, this stops the timer and updates the sizing.
2590 */
2591 public void actionPerformed(ActionEvent ae) {
2592 if(scrollBar == null || !scrollBar.getValueIsAdjusting()) {
2593 if(timer != null)
2594 timer.stop();
2595 updateSize();
2596 timer = null;
2597 scrollBar = null;
2598 }
2599 }
2600 } // End of BasicTreeUI.ComponentHandler
2601
2602
2603 /**
2604 * Forwards all TreeModel events to the TreeState.
2605 */
2606 public class TreeModelHandler implements TreeModelListener {
2607
2608 // NOTE: This class exists only for backward compatability. All
2609 // its functionality has been moved into Handler. If you need to add
2610 // new functionality add it to the Handler, but make sure this
2611 // class calls into the Handler.
2612
2613 public void treeNodesChanged(TreeModelEvent e) {
2614 getHandler().treeNodesChanged(e);
2615 }
2616
2617 public void treeNodesInserted(TreeModelEvent e) {
2618 getHandler().treeNodesInserted(e);
2619 }
2620
2621 public void treeNodesRemoved(TreeModelEvent e) {
2622 getHandler().treeNodesRemoved(e);
2623 }
2624
2625 public void treeStructureChanged(TreeModelEvent e) {
2626 getHandler().treeStructureChanged(e);
2627 }
2628 } // End of BasicTreeUI.TreeModelHandler
2629
2630
2631 /**
2632 * Listens for changes in the selection model and updates the display
2633 * accordingly.
2634 */
2635 public class TreeSelectionHandler implements TreeSelectionListener {
2636
2637 // NOTE: This class exists only for backward compatability. All
2638 // its functionality has been moved into Handler. If you need to add
2639 // new functionality add it to the Handler, but make sure this
2640 // class calls into the Handler.
2641
2642 /**
2643 * Messaged when the selection changes in the tree we're displaying
2644 * for. Stops editing, messages super and displays the changed paths.
2645 */
2646 public void valueChanged(TreeSelectionEvent event) {
2647 getHandler().valueChanged(event);
2648 }
2649 }// End of BasicTreeUI.TreeSelectionHandler
2650
2651
2652 /**
2653 * Listener responsible for getting cell editing events and updating
2654 * the tree accordingly.
2655 */
2656 public class CellEditorHandler implements CellEditorListener {
2657
2658 // NOTE: This class exists only for backward compatability. All
2659 // its functionality has been moved into Handler. If you need to add
2660 // new functionality add it to the Handler, but make sure this
2661 // class calls into the Handler.
2662
2663 /** Messaged when editing has stopped in the tree. */
2664 public void editingStopped(ChangeEvent e) {
2665 getHandler().editingStopped(e);
2666 }
2667
2668 /** Messaged when editing has been canceled in the tree. */
2669 public void editingCanceled(ChangeEvent e) {
2670 getHandler().editingCanceled(e);
2671 }
2672 } // BasicTreeUI.CellEditorHandler
2673
2674
2675 /**
2676 * This is used to get mutliple key down events to appropriately generate
2677 * events.
2678 */
2679 public class KeyHandler extends KeyAdapter {
2680
2681 // NOTE: This class exists only for backward compatability. All
2682 // its functionality has been moved into Handler. If you need to add
2683 // new functionality add it to the Handler, but make sure this
2684 // class calls into the Handler.
2685
2686 // Also note these fields aren't use anymore, nor does Handler have
2687 // the old functionality. This behavior worked around an old bug
2688 // in JComponent that has long since been fixed.
2689
2690 /** Key code that is being generated for. */
2691 protected Action repeatKeyAction;
2692
2693 /** Set to true while keyPressed is active. */
2694 protected boolean isKeyDown;
2695
2696 /**
2697 * Invoked when a key has been typed.
2698 *
2699 * Moves the keyboard focus to the first element
2700 * whose first letter matches the alphanumeric key
2701 * pressed by the user. Subsequent same key presses
2702 * move the keyboard focus to the next object that
2703 * starts with the same letter.
2704 */
2705 public void keyTyped(KeyEvent e) {
2706 getHandler().keyTyped(e);
2707 }
2708
2709 public void keyPressed(KeyEvent e) {
2710 getHandler().keyPressed(e);
2711 }
2712
2713 public void keyReleased(KeyEvent e) {
2714 getHandler().keyReleased(e);
2715 }
2716 } // End of BasicTreeUI.KeyHandler
2717
2718
2719 /**
2720 * Repaints the lead selection row when focus is lost/gained.
2721 */
2722 public class FocusHandler implements FocusListener {
2723 // NOTE: This class exists only for backward compatability. All
2724 // its functionality has been moved into Handler. If you need to add
2725 // new functionality add it to the Handler, but make sure this
2726 // class calls into the Handler.
2727
2728 /**
2729 * Invoked when focus is activated on the tree we're in, redraws the
2730 * lead row.
2731 */
2732 public void focusGained(FocusEvent e) {
2733 getHandler().focusGained(e);
2734 }
2735
2736 /**
2737 * Invoked when focus is activated on the tree we're in, redraws the
2738 * lead row.
2739 */
2740 public void focusLost(FocusEvent e) {
2741 getHandler().focusLost(e);
2742 }
2743 } // End of class BasicTreeUI.FocusHandler
2744
2745
2746 /**
2747 * Class responsible for getting size of node, method is forwarded
2748 * to BasicTreeUI method. X location does not include insets, that is
2749 * handled in getPathBounds.
2750 */
2751 // This returns locations that don't include any Insets.
2752 public class NodeDimensionsHandler extends
2753 AbstractLayoutCache.NodeDimensions {
2754 /**
2755 * Responsible for getting the size of a particular node.
2756 */
2757 public Rectangle getNodeDimensions(Object value, int row,
2758 int depth, boolean expanded,
2759 Rectangle size) {
2760 // Return size of editing component, if editing and asking
2761 // for editing row.
2762 if(editingComponent != null && editingRow == row) {
2763 Dimension prefSize = editingComponent.
2764 getPreferredSize();
2765 int rh = getRowHeight();
2766
2767 if(rh > 0 && rh != prefSize.height)
2768 prefSize.height = rh;
2769 if(size != null) {
2770 size.x = getRowX(row, depth);
2771 size.width = prefSize.width;
2772 size.height = prefSize.height;
2773 }
2774 else {
2775 size = new Rectangle(getRowX(row, depth), 0,
2776 prefSize.width, prefSize.height);
2777 }
2778 return size;
2779 }
2780 // Not editing, use renderer.
2781 if(currentCellRenderer != null) {
2782 Component aComponent;
2783
2784 aComponent = currentCellRenderer.getTreeCellRendererComponent
2785 (tree, value, tree.isRowSelected(row),
2786 expanded, treeModel.isLeaf(value), row,
2787 false);
2788 if(tree != null) {
2789 // Only ever removed when UI changes, this is OK!
2790 rendererPane.add(aComponent);
2791 aComponent.validate();
2792 }
2793 Dimension prefSize = aComponent.getPreferredSize();
2794
2795 if(size != null) {
2796 size.x = getRowX(row, depth);
2797 size.width = prefSize.width;
2798 size.height = prefSize.height;
2799 }
2800 else {
2801 size = new Rectangle(getRowX(row, depth), 0,
2802 prefSize.width, prefSize.height);
2803 }
2804 return size;
2805 }
2806 return null;
2807 }
2808
2809 /**
2810 * @return amount to indent the given row.
2811 */
2812 protected int getRowX(int row, int depth) {
2813 return BasicTreeUI.this.getRowX(row, depth);
2814 }
2815
2816 } // End of class BasicTreeUI.NodeDimensionsHandler
2817
2818
2819 /**
2820 * TreeMouseListener is responsible for updating the selection
2821 * based on mouse events.
2822 */
2823 public class MouseHandler extends MouseAdapter implements MouseMotionListener
2824 {
2825 // NOTE: This class exists only for backward compatability. All
2826 // its functionality has been moved into Handler. If you need to add
2827 // new functionality add it to the Handler, but make sure this
2828 // class calls into the Handler.
2829
2830 /**
2831 * Invoked when a mouse button has been pressed on a component.
2832 */
2833 public void mousePressed(MouseEvent e) {
2834 getHandler().mousePressed(e);
2835 }
2836
2837 public void mouseDragged(MouseEvent e) {
2838 getHandler().mouseDragged(e);
2839 }
2840
2841 /**
2842 * Invoked when the mouse button has been moved on a component
2843 * (with no buttons no down).
2844 * @since 1.4
2845 */
2846 public void mouseMoved(MouseEvent e) {
2847 getHandler().mouseMoved(e);
2848 }
2849
2850 public void mouseReleased(MouseEvent e) {
2851 getHandler().mouseReleased(e);
2852 }
2853 } // End of BasicTreeUI.MouseHandler
2854
2855
2856 /**
2857 * PropertyChangeListener for the tree. Updates the appropriate
2858 * varaible, or TreeState, based on what changes.
2859 */
2860 public class PropertyChangeHandler implements
2861 PropertyChangeListener {
2862
2863 // NOTE: This class exists only for backward compatability. All
2864 // its functionality has been moved into Handler. If you need to add
2865 // new functionality add it to the Handler, but make sure this
2866 // class calls into the Handler.
2867
2868 public void propertyChange(PropertyChangeEvent event) {
2869 getHandler().propertyChange(event);
2870 }
2871 } // End of BasicTreeUI.PropertyChangeHandler
2872
2873
2874 /**
2875 * Listener on the TreeSelectionModel, resets the row selection if
2876 * any of the properties of the model change.
2877 */
2878 public class SelectionModelPropertyChangeHandler implements
2879 PropertyChangeListener {
2880
2881 // NOTE: This class exists only for backward compatability. All
2882 // its functionality has been moved into Handler. If you need to add
2883 // new functionality add it to the Handler, but make sure this
2884 // class calls into the Handler.
2885
2886 public void propertyChange(PropertyChangeEvent event) {
2887 getHandler().propertyChange(event);
2888 }
2889 } // End of BasicTreeUI.SelectionModelPropertyChangeHandler
2890
2891
2892 /**
2893 * <code>TreeTraverseAction</code> is the action used for left/right keys.
2894 * Will toggle the expandedness of a node, as well as potentially
2895 * incrementing the selection.
2896 */
2897 public class TreeTraverseAction extends AbstractAction {
2898 /** Determines direction to traverse, 1 means expand, -1 means
2899 * collapse. */
2900 protected int direction;
2901 /** True if the selection is reset, false means only the lead path
2902 * changes. */
2903 private boolean changeSelection;
2904
2905 public TreeTraverseAction(int direction, String name) {
2906 this(direction, name, true);
2907 }
2908
2909 private TreeTraverseAction(int direction, String name,
2910 boolean changeSelection) {
2911 this.direction = direction;
2912 this.changeSelection = changeSelection;
2913 }
2914
2915 public void actionPerformed(ActionEvent e) {
2916 if (tree != null) {
2917 SHARED_ACTION.traverse(tree, BasicTreeUI.this, direction,
2918 changeSelection);
2919 }
2920 }
2921
2922 public boolean isEnabled() { return (tree != null &&
2923 tree.isEnabled()); }
2924 } // BasicTreeUI.TreeTraverseAction
2925
2926
2927 /** TreePageAction handles page up and page down events.
2928 */
2929 public class TreePageAction extends AbstractAction {
2930 /** Specifies the direction to adjust the selection by. */
2931 protected int direction;
2932 /** True indicates should set selection from anchor path. */
2933 private boolean addToSelection;
2934 private boolean changeSelection;
2935
2936 public TreePageAction(int direction, String name) {
2937 this(direction, name, false, true);
2938 }
2939
2940 private TreePageAction(int direction, String name,
2941 boolean addToSelection,
2942 boolean changeSelection) {
2943 this.direction = direction;
2944 this.addToSelection = addToSelection;
2945 this.changeSelection = changeSelection;
2946 }
2947
2948 public void actionPerformed(ActionEvent e) {
2949 if (tree != null) {
2950 SHARED_ACTION.page(tree, BasicTreeUI.this, direction,
2951 addToSelection, changeSelection);
2952 }
2953 }
2954
2955 public boolean isEnabled() { return (tree != null &&
2956 tree.isEnabled()); }
2957
2958 } // BasicTreeUI.TreePageAction
2959
2960
2961 /** TreeIncrementAction is used to handle up/down actions. Selection
2962 * is moved up or down based on direction.
2963 */
2964 public class TreeIncrementAction extends AbstractAction {
2965 /** Specifies the direction to adjust the selection by. */
2966 protected int direction;
2967 /** If true the new item is added to the selection, if false the
2968 * selection is reset. */
2969 private boolean addToSelection;
2970 private boolean changeSelection;
2971
2972 public TreeIncrementAction(int direction, String name) {
2973 this(direction, name, false, true);
2974 }
2975
2976 private TreeIncrementAction(int direction, String name,
2977 boolean addToSelection,
2978 boolean changeSelection) {
2979 this.direction = direction;
2980 this.addToSelection = addToSelection;
2981 this.changeSelection = changeSelection;
2982 }
2983
2984 public void actionPerformed(ActionEvent e) {
2985 if (tree != null) {
2986 SHARED_ACTION.increment(tree, BasicTreeUI.this, direction,
2987 addToSelection, changeSelection);
2988 }
2989 }
2990
2991 public boolean isEnabled() { return (tree != null &&
2992 tree.isEnabled()); }
2993
2994 } // End of class BasicTreeUI.TreeIncrementAction
2995
2996 /**
2997 * TreeHomeAction is used to handle end/home actions.
2998 * Scrolls either the first or last cell to be visible based on
2999 * direction.
3000 */
3001 public class TreeHomeAction extends AbstractAction {
3002 protected int direction;
3003 /** Set to true if append to selection. */
3004 private boolean addToSelection;
3005 private boolean changeSelection;
3006
3007 public TreeHomeAction(int direction, String name) {
3008 this(direction, name, false, true);
3009 }
3010
3011 private TreeHomeAction(int direction, String name,
3012 boolean addToSelection,
3013 boolean changeSelection) {
3014 this.direction = direction;
3015 this.changeSelection = changeSelection;
3016 this.addToSelection = addToSelection;
3017 }
3018
3019 public void actionPerformed(ActionEvent e) {
3020 if (tree != null) {
3021 SHARED_ACTION.home(tree, BasicTreeUI.this, direction,
3022 addToSelection, changeSelection);
3023 }
3024 }
3025
3026 public boolean isEnabled() { return (tree != null &&
3027 tree.isEnabled()); }
3028
3029 } // End of class BasicTreeUI.TreeHomeAction
3030
3031
3032 /**
3033 * For the first selected row expandedness will be toggled.
3034 */
3035 public class TreeToggleAction extends AbstractAction {
3036 public TreeToggleAction(String name) {
3037 }
3038
3039 public void actionPerformed(ActionEvent e) {
3040 if(tree != null) {
3041 SHARED_ACTION.toggle(tree, BasicTreeUI.this);
3042 }
3043 }
3044
3045 public boolean isEnabled() { return (tree != null &&
3046 tree.isEnabled()); }
3047
3048 } // End of class BasicTreeUI.TreeToggleAction
3049
3050
3051 /**
3052 * ActionListener that invokes cancelEditing when action performed.
3053 */
3054 public class TreeCancelEditingAction extends AbstractAction {
3055 public TreeCancelEditingAction(String name) {
3056 }
3057
3058 public void actionPerformed(ActionEvent e) {
3059 if(tree != null) {
3060 SHARED_ACTION.cancelEditing(tree, BasicTreeUI.this);
3061 }
3062 }
3063
3064 public boolean isEnabled() { return (tree != null &&
3065 tree.isEnabled() &&
3066 isEditing(tree)); }
3067 } // End of class BasicTreeUI.TreeCancelEditingAction
3068
3069
3070 /**
3071 * MouseInputHandler handles passing all mouse events,
3072 * including mouse motion events, until the mouse is released to
3073 * the destination it is constructed with. It is assumed all the
3074 * events are currently target at source.
3075 */
3076 public class MouseInputHandler extends Object implements
3077 MouseInputListener
3078 {
3079 /** Source that events are coming from. */
3080 protected Component source;
3081 /** Destination that receives all events. */
3082 protected Component destination;
3083 private Component focusComponent;
3084 private boolean dispatchedEvent;
3085
3086 public MouseInputHandler(Component source, Component destination,
3087 MouseEvent event){
3088 this(source, destination, event, null);
3089 }
3090
3091 MouseInputHandler(Component source, Component destination,
3092 MouseEvent event, Component focusComponent) {
3093 this.source = source;
3094 this.destination = destination;
3095 this.source.addMouseListener(this);
3096 this.source.addMouseMotionListener(this);
3097
3098 SwingUtilities2.setSkipClickCount(destination,
3099 event.getClickCount() - 1);
3100
3101 /* Dispatch the editing event! */
3102 destination.dispatchEvent(SwingUtilities.convertMouseEvent
3103 (source, event, destination));
3104 this.focusComponent = focusComponent;
3105 }
3106
3107 public void mouseClicked(MouseEvent e) {
3108 if(destination != null) {
3109 dispatchedEvent = true;
3110 destination.dispatchEvent(SwingUtilities.convertMouseEvent
3111 (source, e, destination));
3112 }
3113 }
3114
3115 public void mousePressed(MouseEvent e) {
3116 }
3117
3118 public void mouseReleased(MouseEvent e) {
3119 if(destination != null)
3120 destination.dispatchEvent(SwingUtilities.convertMouseEvent
3121 (source, e, destination));
3122 removeFromSource();
3123 }
3124
3125 public void mouseEntered(MouseEvent e) {
3126 if (!SwingUtilities.isLeftMouseButton(e)) {
3127 removeFromSource();
3128 }
3129 }
3130
3131 public void mouseExited(MouseEvent e) {
3132 if (!SwingUtilities.isLeftMouseButton(e)) {
3133 removeFromSource();
3134 }
3135 }
3136
3137 public void mouseDragged(MouseEvent e) {
3138 if(destination != null) {
3139 dispatchedEvent = true;
3140 destination.dispatchEvent(SwingUtilities.convertMouseEvent
3141 (source, e, destination));
3142 }
3143 }
3144
3145 public void mouseMoved(MouseEvent e) {
3146 removeFromSource();
3147 }
3148
3149 protected void removeFromSource() {
3150 if(source != null) {
3151 source.removeMouseListener(this);
3152 source.removeMouseMotionListener(this);
3153 if (focusComponent != null &&
3154 focusComponent == destination && !dispatchedEvent &&
3155 (focusComponent instanceof JTextField)) {
3156 ((JTextField)focusComponent).selectAll();
3157 }
3158 }
3159 source = destination = null;
3160 }
3161
3162 } // End of class BasicTreeUI.MouseInputHandler
3163
3164 private static final TransferHandler defaultTransferHandler = new TreeTransferHandler();
3165
3166 static class TreeTransferHandler extends TransferHandler implements UIResource, Comparator<TreePath> {
3167
3168 private JTree tree;
3169
3170 /**
3171 * Create a Transferable to use as the source for a data transfer.
3172 *
3173 * @param c The component holding the data to be transfered. This
3174 * argument is provided to enable sharing of TransferHandlers by
3175 * multiple components.
3176 * @return The representation of the data to be transfered.
3177 *
3178 */
3179 protected Transferable createTransferable(JComponent c) {
3180 if (c instanceof JTree) {
3181 tree = (JTree) c;
3182 TreePath[] paths = tree.getSelectionPaths();
3183
3184 if (paths == null || paths.length == 0) {
3185 return null;
3186 }
3187
3188 StringBuffer plainBuf = new StringBuffer();
3189 StringBuffer htmlBuf = new StringBuffer();
3190
3191 htmlBuf.append("<html>\n<body>\n<ul>\n");
3192
3193 TreeModel model = tree.getModel();
3194 TreePath lastPath = null;
3195 TreePath[] displayPaths = getDisplayOrderPaths(paths);
3196
3197 for (TreePath path : displayPaths) {
3198 Object node = path.getLastPathComponent();
3199 boolean leaf = model.isLeaf(node);
3200 String label = getDisplayString(path, true, leaf);
3201
3202 plainBuf.append(label + "\n");
3203 htmlBuf.append(" <li>" + label + "\n");
3204 }
3205
3206 // remove the last newline
3207 plainBuf.deleteCharAt(plainBuf.length() - 1);
3208 htmlBuf.append("</ul>\n</body>\n</html>");
3209
3210 tree = null;
3211
3212 return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
3213 }
3214
3215 return null;
3216 }
3217
3218 public int compare(TreePath o1, TreePath o2) {
3219 int row1 = tree.getRowForPath(o1);
3220 int row2 = tree.getRowForPath(o2);
3221 return row1 - row2;
3222 }
3223
3224 String getDisplayString(TreePath path, boolean selected, boolean leaf) {
3225 int row = tree.getRowForPath(path);
3226 boolean hasFocus = tree.getLeadSelectionRow() == row;
3227 Object node = path.getLastPathComponent();
3228 return tree.convertValueToText(node, selected, tree.isExpanded(row),
3229 leaf, row, hasFocus);
3230 }
3231
3232 /**
3233 * Selection paths are in selection order. The conversion to
3234 * HTML requires display order. This method resorts the paths
3235 * to be in the display order.
3236 */
3237 TreePath[] getDisplayOrderPaths(TreePath[] paths) {
3238 // sort the paths to display order rather than selection order
3239 ArrayList<TreePath> selOrder = new ArrayList<TreePath>();
3240 for (TreePath path : paths) {
3241 selOrder.add(path);
3242 }
3243 Collections.sort(selOrder, this);
3244 int n = selOrder.size();
3245 TreePath[] displayPaths = new TreePath[n];
3246 for (int i = 0; i < n; i++) {
3247 displayPaths[i] = selOrder.get(i);
3248 }
3249 return displayPaths;
3250 }
3251
3252 public int getSourceActions(JComponent c) {
3253 return COPY;
3254 }
3255
3256 }
3257
3258
3259 private class Handler implements CellEditorListener, FocusListener,
3260 KeyListener, MouseListener, MouseMotionListener,
3261 PropertyChangeListener, TreeExpansionListener,
3262 TreeModelListener, TreeSelectionListener,
3263 BeforeDrag {
3264 //
3265 // KeyListener
3266 //
3267 private String prefix = "";
3268 private String typedString = "";
3269 private long lastTime = 0L;
3270
3271 /**
3272 * Invoked when a key has been typed.
3273 *
3274 * Moves the keyboard focus to the first element whose prefix matches the
3275 * sequence of alphanumeric keys pressed by the user with delay less
3276 * than value of <code>timeFactor</code> property (or 1000 milliseconds
3277 * if it is not defined). Subsequent same key presses move the keyboard
3278 * focus to the next object that starts with the same letter until another
3279 * key is pressed, then it is treated as the prefix with appropriate number
3280 * of the same letters followed by first typed another letter.
3281 */
3282 public void keyTyped(KeyEvent e) {
3283 // handle first letter navigation
3284 if(tree != null && tree.getRowCount()>0 && tree.hasFocus() &&
3285 tree.isEnabled()) {
3286 if (e.isAltDown() || BasicGraphicsUtils.isMenuShortcutKeyDown(e) ||
3287 isNavigationKey(e)) {
3288 return;
3289 }
3290 boolean startingFromSelection = true;
3291
3292 char c = e.getKeyChar();
3293
3294 long time = e.getWhen();
3295 int startingRow = tree.getLeadSelectionRow();
3296 if (time - lastTime < timeFactor) {
3297 typedString += c;
3298 if((prefix.length() == 1) && (c == prefix.charAt(0))) {
3299 // Subsequent same key presses move the keyboard focus to the next
3300 // object that starts with the same letter.
3301 startingRow++;
3302 } else {
3303 prefix = typedString;
3304 }
3305 } else {
3306 startingRow++;
3307 typedString = "" + c;
3308 prefix = typedString;
3309 }
3310 lastTime = time;
3311
3312 if (startingRow < 0 || startingRow >= tree.getRowCount()) {
3313 startingFromSelection = false;
3314 startingRow = 0;
3315 }
3316 TreePath path = tree.getNextMatch(prefix, startingRow,
3317 Position.Bias.Forward);
3318 if (path != null) {
3319 tree.setSelectionPath(path);
3320 int row = getRowForPath(tree, path);
3321 ensureRowsAreVisible(row, row);
3322 } else if (startingFromSelection) {
3323 path = tree.getNextMatch(prefix, 0,
3324 Position.Bias.Forward);
3325 if (path != null) {
3326 tree.setSelectionPath(path);
3327 int row = getRowForPath(tree, path);
3328 ensureRowsAreVisible(row, row);
3329 }
3330 }
3331 }
3332 }
3333
3334 /**
3335 * Invoked when a key has been pressed.
3336 *
3337 * Checks to see if the key event is a navigation key to prevent
3338 * dispatching these keys for the first letter navigation.
3339 */
3340 public void keyPressed(KeyEvent e) {
3341 if (tree != null && isNavigationKey(e)) {
3342 prefix = "";
3343 typedString = "";
3344 lastTime = 0L;
3345 }
3346 }
3347
3348 public void keyReleased(KeyEvent e) {
3349 }
3350
3351 /**
3352 * Returns whether or not the supplied key event maps to a key that is used for
3353 * navigation. This is used for optimizing key input by only passing non-
3354 * navigation keys to the first letter navigation mechanism.
3355 */
3356 private boolean isNavigationKey(KeyEvent event) {
3357 InputMap inputMap = tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
3358 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
3359
3360 return inputMap != null && inputMap.get(key) != null;
3361 }
3362
3363
3364 //
3365 // PropertyChangeListener
3366 //
3367 public void propertyChange(PropertyChangeEvent event) {
3368 if (event.getSource() == treeSelectionModel) {
3369 treeSelectionModel.resetRowSelection();
3370 }
3371 else if(event.getSource() == tree) {
3372 String changeName = event.getPropertyName();
3373
3374 if (changeName == JTree.LEAD_SELECTION_PATH_PROPERTY) {
3375 if (!ignoreLAChange) {
3376 updateLeadSelectionRow();
3377 repaintPath((TreePath)event.getOldValue());
3378 repaintPath((TreePath)event.getNewValue());
3379 }
3380 }
3381 else if (changeName == JTree.ANCHOR_SELECTION_PATH_PROPERTY) {
3382 if (!ignoreLAChange) {
3383 repaintPath((TreePath)event.getOldValue());
3384 repaintPath((TreePath)event.getNewValue());
3385 }
3386 }
3387 if(changeName == JTree.CELL_RENDERER_PROPERTY) {
3388 setCellRenderer((TreeCellRenderer)event.getNewValue());
3389 redoTheLayout();
3390 }
3391 else if(changeName == JTree.TREE_MODEL_PROPERTY) {
3392 setModel((TreeModel)event.getNewValue());
3393 }
3394 else if(changeName == JTree.ROOT_VISIBLE_PROPERTY) {
3395 setRootVisible(((Boolean)event.getNewValue()).
3396 booleanValue());
3397 }
3398 else if(changeName == JTree.SHOWS_ROOT_HANDLES_PROPERTY) {
3399 setShowsRootHandles(((Boolean)event.getNewValue()).
3400 booleanValue());
3401 }
3402 else if(changeName == JTree.ROW_HEIGHT_PROPERTY) {
3403 setRowHeight(((Integer)event.getNewValue()).
3404 intValue());
3405 }
3406 else if(changeName == JTree.CELL_EDITOR_PROPERTY) {
3407 setCellEditor((TreeCellEditor)event.getNewValue());
3408 }
3409 else if(changeName == JTree.EDITABLE_PROPERTY) {
3410 setEditable(((Boolean)event.getNewValue()).booleanValue());
3411 }
3412 else if(changeName == JTree.LARGE_MODEL_PROPERTY) {
3413 setLargeModel(tree.isLargeModel());
3414 }
3415 else if(changeName == JTree.SELECTION_MODEL_PROPERTY) {
3416 setSelectionModel(tree.getSelectionModel());
3417 }
3418 else if(changeName == "font") {
3419 completeEditing();
3420 if(treeState != null)
3421 treeState.invalidateSizes();
3422 updateSize();
3423 }
3424 else if (changeName == "componentOrientation") {
3425 if (tree != null) {
3426 leftToRight = BasicGraphicsUtils.isLeftToRight(tree);
3427 redoTheLayout();
3428 tree.treeDidChange();
3429
3430 InputMap km = getInputMap(JComponent.WHEN_FOCUSED);
3431 SwingUtilities.replaceUIInputMap(tree,
3432 JComponent.WHEN_FOCUSED, km);
3433 }
3434 } else if ("dropLocation" == changeName) {
3435 JTree.DropLocation oldValue = (JTree.DropLocation)event.getOldValue();
3436 repaintDropLocation(oldValue);
3437 repaintDropLocation(tree.getDropLocation());
3438 }
3439 }
3440 }
3441
3442 private void repaintDropLocation(JTree.DropLocation loc) {
3443 if (loc == null) {
3444 return;
3445 }
3446
3447 Rectangle r;
3448
3449 if (isDropLine(loc)) {
3450 r = getDropLineRect(loc);
3451 } else {
3452 r = tree.getPathBounds(loc.getPath());
3453 }
3454
3455 if (r != null) {
3456 tree.repaint(r);
3457 }
3458 }
3459
3460 //
3461 // MouseListener
3462 //
3463
3464 // Whether or not the mouse press (which is being considered as part
3465 // of a drag sequence) also caused the selection change to be fully
3466 // processed.
3467 private boolean dragPressDidSelection;
3468
3469 // Set to true when a drag gesture has been fully recognized and DnD
3470 // begins. Use this to ignore further mouse events which could be
3471 // delivered if DnD is cancelled (via ESCAPE for example)
3472 private boolean dragStarted;
3473
3474 // The path over which the press occurred and the press event itself
3475 private TreePath pressedPath;
3476 private MouseEvent pressedEvent;
3477
3478 // Used to detect whether the press event causes a selection change.
3479 // If it does, we won't try to start editing on the release.
3480 private boolean valueChangedOnPress;
3481
3482 private boolean isActualPath(TreePath path, int x, int y) {
3483 if (path == null) {
3484 return false;
3485 }
3486
3487 Rectangle bounds = getPathBounds(tree, path);
3488 if (y > (bounds.y + bounds.height)) {
3489 return false;
3490 }
3491
3492 return (x >= bounds.x) && (x <= (bounds.x + bounds.width));
3493 }
3494
3495 public void mouseClicked(MouseEvent e) {
3496 }
3497
3498 public void mouseEntered(MouseEvent e) {
3499 }
3500
3501 public void mouseExited(MouseEvent e) {
3502 }
3503
3504 /**
3505 * Invoked when a mouse button has been pressed on a component.
3506 */
3507 public void mousePressed(MouseEvent e) {
3508 if (SwingUtilities2.shouldIgnore(e, tree)) {
3509 return;
3510 }
3511
3512 // if we can't stop any ongoing editing, do nothing
3513 if (isEditing(tree) && tree.getInvokesStopCellEditing()
3514 && !stopEditing(tree)) {
3515 return;
3516 }
3517
3518 completeEditing();
3519
3520 pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY());
3521
3522 if (tree.getDragEnabled()) {
3523 mousePressedDND(e);
3524 } else {
3525 SwingUtilities2.adjustFocus(tree);
3526 handleSelection(e);
3527 }
3528 }
3529
3530 private void mousePressedDND(MouseEvent e) {
3531 pressedEvent = e;
3532 boolean grabFocus = true;
3533 dragStarted = false;
3534 valueChangedOnPress = false;
3535
3536 // if we have a valid path and this is a drag initiating event
3537 if (isActualPath(pressedPath, e.getX(), e.getY()) &&
3538 DragRecognitionSupport.mousePressed(e)) {
3539
3540 dragPressDidSelection = false;
3541
3542 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
3543 // do nothing for control - will be handled on release
3544 // or when drag starts
3545 return;
3546 } else if (!e.isShiftDown() && tree.isPathSelected(pressedPath)) {
3547 // clicking on something that's already selected
3548 // and need to make it the lead now
3549 setAnchorSelectionPath(pressedPath);
3550 setLeadSelectionPath(pressedPath, true);
3551 return;
3552 }
3553
3554 dragPressDidSelection = true;
3555
3556 // could be a drag initiating event - don't grab focus
3557 grabFocus = false;
3558 }
3559
3560 if (grabFocus) {
3561 SwingUtilities2.adjustFocus(tree);
3562 }
3563
3564 handleSelection(e);
3565 }
3566
3567 void handleSelection(MouseEvent e) {
3568 if(pressedPath != null) {
3569 Rectangle bounds = getPathBounds(tree, pressedPath);
3570
3571 if(e.getY() >= (bounds.y + bounds.height)) {
3572 return;
3573 }
3574
3575 // Preferably checkForClickInExpandControl could take
3576 // the Event to do this it self!
3577 if(SwingUtilities.isLeftMouseButton(e)) {
3578 checkForClickInExpandControl(pressedPath, e.getX(), e.getY());
3579 }
3580
3581 int x = e.getX();
3582
3583 // Perhaps they clicked the cell itself. If so,
3584 // select it.
3585 if (x >= bounds.x && x < (bounds.x + bounds.width)) {
3586 if (tree.getDragEnabled() || !startEditing(pressedPath, e)) {
3587 selectPathForEvent(pressedPath, e);
3588 }
3589 }
3590 }
3591 }
3592
3593 public void dragStarting(MouseEvent me) {
3594 dragStarted = true;
3595
3596 if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) {
3597 tree.addSelectionPath(pressedPath);
3598 setAnchorSelectionPath(pressedPath);
3599 setLeadSelectionPath(pressedPath, true);
3600 }
3601
3602 pressedEvent = null;
3603 pressedPath = null;
3604 }
3605
3606 public void mouseDragged(MouseEvent e) {
3607 if (SwingUtilities2.shouldIgnore(e, tree)) {
3608 return;
3609 }
3610
3611 if (tree.getDragEnabled()) {
3612 DragRecognitionSupport.mouseDragged(e, this);
3613 }
3614 }
3615
3616 /**
3617 * Invoked when the mouse button has been moved on a component
3618 * (with no buttons no down).
3619 */
3620 public void mouseMoved(MouseEvent e) {
3621 }
3622
3623 public void mouseReleased(MouseEvent e) {
3624 if (SwingUtilities2.shouldIgnore(e, tree)) {
3625 return;
3626 }
3627
3628 if (tree.getDragEnabled()) {
3629 mouseReleasedDND(e);
3630 }
3631
3632 pressedEvent = null;
3633 pressedPath = null;
3634 }
3635
3636 private void mouseReleasedDND(MouseEvent e) {
3637 MouseEvent me = DragRecognitionSupport.mouseReleased(e);
3638 if (me != null) {
3639 SwingUtilities2.adjustFocus(tree);
3640 if (!dragPressDidSelection) {
3641 handleSelection(me);
3642 }
3643 }
3644
3645 if (!dragStarted) {
3646
3647 // Note: We don't give the tree a chance to start editing if the
3648 // mouse press caused a selection change. Otherwise the default
3649 // tree cell editor will start editing on EVERY press and
3650 // release. If it turns out that this affects some editors, we
3651 // can always parameterize this with a client property. ex:
3652 //
3653 // if (pressedPath != null &&
3654 // (Boolean.TRUE == tree.getClientProperty("Tree.DnD.canEditOnValueChange") ||
3655 // !valueChangedOnPress) && ...
3656 if (pressedPath != null && !valueChangedOnPress &&
3657 isActualPath(pressedPath, pressedEvent.getX(), pressedEvent.getY())) {
3658
3659 startEditingOnRelease(pressedPath, pressedEvent, e);
3660 }
3661 }
3662 }
3663
3664 //
3665 // FocusListener
3666 //
3667 public void focusGained(FocusEvent e) {
3668 if(tree != null) {
3669 Rectangle pBounds;
3670
3671 pBounds = getPathBounds(tree, tree.getLeadSelectionPath());
3672 if(pBounds != null)
3673 tree.repaint(getRepaintPathBounds(pBounds));
3674 pBounds = getPathBounds(tree, getLeadSelectionPath());
3675 if(pBounds != null)
3676 tree.repaint(getRepaintPathBounds(pBounds));
3677 }
3678 }
3679
3680 public void focusLost(FocusEvent e) {
3681 focusGained(e);
3682 }
3683
3684 //
3685 // CellEditorListener
3686 //
3687 public void editingStopped(ChangeEvent e) {
3688 completeEditing(false, false, true);
3689 }
3690
3691 /** Messaged when editing has been canceled in the tree. */
3692 public void editingCanceled(ChangeEvent e) {
3693 completeEditing(false, false, false);
3694 }
3695
3696
3697 //
3698 // TreeSelectionListener
3699 //
3700 public void valueChanged(TreeSelectionEvent event) {
3701 valueChangedOnPress = true;
3702
3703 // Stop editing
3704 completeEditing();
3705 // Make sure all the paths are visible, if necessary.
3706 // PENDING: This should be tweaked when isAdjusting is added
3707 if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) {
3708 TreePath[] paths = treeSelectionModel
3709 .getSelectionPaths();
3710
3711 if(paths != null) {
3712 for(int counter = paths.length - 1; counter >= 0;
3713 counter--) {
3714 TreePath path = paths[counter].getParentPath();
3715 boolean expand = true;
3716
3717 while (path != null) {
3718 // Indicates this path isn't valid anymore,
3719 // we shouldn't attempt to expand it then.
3720 if (treeModel.isLeaf(path.getLastPathComponent())){
3721 expand = false;
3722 path = null;
3723 }
3724 else {
3725 path = path.getParentPath();
3726 }
3727 }
3728 if (expand) {
3729 tree.makeVisible(paths[counter]);
3730 }
3731 }
3732 }
3733 }
3734
3735 TreePath oldLead = getLeadSelectionPath();
3736 lastSelectedRow = tree.getMinSelectionRow();
3737 TreePath lead = tree.getSelectionModel().getLeadSelectionPath();
3738 setAnchorSelectionPath(lead);
3739 setLeadSelectionPath(lead);
3740
3741 TreePath[] changedPaths = event.getPaths();
3742 Rectangle nodeBounds;
3743 Rectangle visRect = tree.getVisibleRect();
3744 boolean paintPaths = true;
3745 int nWidth = tree.getWidth();
3746
3747 if(changedPaths != null) {
3748 int counter, maxCounter = changedPaths.length;
3749
3750 if(maxCounter > 4) {
3751 tree.repaint();
3752 paintPaths = false;
3753 }
3754 else {
3755 for (counter = 0; counter < maxCounter; counter++) {
3756 nodeBounds = getPathBounds(tree,
3757 changedPaths[counter]);
3758 if(nodeBounds != null &&
3759 visRect.intersects(nodeBounds))
3760 tree.repaint(0, nodeBounds.y, nWidth,
3761 nodeBounds.height);
3762 }
3763 }
3764 }
3765 if(paintPaths) {
3766 nodeBounds = getPathBounds(tree, oldLead);
3767 if(nodeBounds != null && visRect.intersects(nodeBounds))
3768 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
3769 nodeBounds = getPathBounds(tree, lead);
3770 if(nodeBounds != null && visRect.intersects(nodeBounds))
3771 tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
3772 }
3773 }
3774
3775
3776 //
3777 // TreeExpansionListener
3778 //
3779 public void treeExpanded(TreeExpansionEvent event) {
3780 if(event != null && tree != null) {
3781 TreePath path = event.getPath();
3782
3783 updateExpandedDescendants(path);
3784 }
3785 }
3786
3787 public void treeCollapsed(TreeExpansionEvent event) {
3788 if(event != null && tree != null) {
3789 TreePath path = event.getPath();
3790
3791 completeEditing();
3792 if(path != null && tree.isVisible(path)) {
3793 treeState.setExpandedState(path, false);
3794 updateLeadSelectionRow();
3795 updateSize();
3796 }
3797 }
3798 }
3799
3800 //
3801 // TreeModelListener
3802 //
3803 public void treeNodesChanged(TreeModelEvent e) {
3804 if(treeState != null && e != null) {
3805 TreePath parentPath = e.getTreePath();
3806 int[] indices = e.getChildIndices();
3807 if (indices == null || indices.length == 0) {
3808 // The root has changed
3809 treeState.treeNodesChanged(e);
3810 updateSize();
3811 }
3812 else if (treeState.isExpanded(parentPath)) {
3813 // Changed nodes are visible
3814 // Find the minimum index, we only need paint from there
3815 // down.
3816 int minIndex = indices[0];
3817 for (int i = indices.length - 1; i > 0; i--) {
3818 minIndex = Math.min(indices[i], minIndex);
3819 }
3820 Object minChild = treeModel.getChild(
3821 parentPath.getLastPathComponent(), minIndex);
3822 TreePath minPath = parentPath.pathByAddingChild(minChild);
3823 Rectangle minBounds = getPathBounds(tree, minPath);
3824
3825 // Forward to the treestate
3826 treeState.treeNodesChanged(e);
3827
3828 // Mark preferred size as bogus.
3829 updateSize0();
3830
3831 // And repaint
3832 Rectangle newMinBounds = getPathBounds(tree, minPath);
3833 if (indices.length == 1 &&
3834 newMinBounds.height == minBounds.height) {
3835 tree.repaint(0, minBounds.y, tree.getWidth(),
3836 minBounds.height);
3837 }
3838 else {
3839 tree.repaint(0, minBounds.y, tree.getWidth(),
3840 tree.getHeight() - minBounds.y);
3841 }
3842 }
3843 else {
3844 // Nodes that changed aren't visible. No need to paint
3845 treeState.treeNodesChanged(e);
3846 }
3847 }
3848 }
3849
3850 public void treeNodesInserted(TreeModelEvent e) {
3851 if(treeState != null && e != null) {
3852 treeState.treeNodesInserted(e);
3853
3854 updateLeadSelectionRow();
3855
3856 TreePath path = e.getTreePath();
3857
3858 if(treeState.isExpanded(path)) {
3859 updateSize();
3860 }
3861 else {
3862 // PENDING(sky): Need a method in TreeModelEvent
3863 // that can return the count, getChildIndices allocs
3864 // a new array!
3865 int[] indices = e.getChildIndices();
3866 int childCount = treeModel.getChildCount
3867 (path.getLastPathComponent());
3868
3869 if(indices != null && (childCount - indices.length) == 0)
3870 updateSize();
3871 }
3872 }
3873 }
3874
3875 public void treeNodesRemoved(TreeModelEvent e) {
3876 if(treeState != null && e != null) {
3877 treeState.treeNodesRemoved(e);
3878
3879 updateLeadSelectionRow();
3880
3881 TreePath path = e.getTreePath();
3882
3883 if(treeState.isExpanded(path) ||
3884 treeModel.getChildCount(path.getLastPathComponent()) == 0)
3885 updateSize();
3886 }
3887 }
3888
3889 public void treeStructureChanged(TreeModelEvent e) {
3890 if(treeState != null && e != null) {
3891 treeState.treeStructureChanged(e);
3892
3893 updateLeadSelectionRow();
3894
3895 TreePath pPath = e.getTreePath();
3896
3897 if (pPath != null) {
3898 pPath = pPath.getParentPath();
3899 }
3900 if(pPath == null || treeState.isExpanded(pPath))
3901 updateSize();
3902 }
3903 }
3904 }
3905
3906
3907
3908 private static class Actions extends UIAction {
3909 private static final String SELECT_PREVIOUS = "selectPrevious";
3910 private static final String SELECT_PREVIOUS_CHANGE_LEAD =
3911 "selectPreviousChangeLead";
3912 private static final String SELECT_PREVIOUS_EXTEND_SELECTION =
3913 "selectPreviousExtendSelection";
3914 private static final String SELECT_NEXT = "selectNext";
3915 private static final String SELECT_NEXT_CHANGE_LEAD =
3916 "selectNextChangeLead";
3917 private static final String SELECT_NEXT_EXTEND_SELECTION =
3918 "selectNextExtendSelection";
3919 private static final String SELECT_CHILD = "selectChild";
3920 private static final String SELECT_CHILD_CHANGE_LEAD =
3921 "selectChildChangeLead";
3922 private static final String SELECT_PARENT = "selectParent";
3923 private static final String SELECT_PARENT_CHANGE_LEAD =
3924 "selectParentChangeLead";
3925 private static final String SCROLL_UP_CHANGE_SELECTION =
3926 "scrollUpChangeSelection";
3927 private static final String SCROLL_UP_CHANGE_LEAD =
3928 "scrollUpChangeLead";
3929 private static final String SCROLL_UP_EXTEND_SELECTION =
3930 "scrollUpExtendSelection";
3931 private static final String SCROLL_DOWN_CHANGE_SELECTION =
3932 "scrollDownChangeSelection";
3933 private static final String SCROLL_DOWN_EXTEND_SELECTION =
3934 "scrollDownExtendSelection";
3935 private static final String SCROLL_DOWN_CHANGE_LEAD =
3936 "scrollDownChangeLead";
3937 private static final String SELECT_FIRST = "selectFirst";
3938 private static final String SELECT_FIRST_CHANGE_LEAD =
3939 "selectFirstChangeLead";
3940 private static final String SELECT_FIRST_EXTEND_SELECTION =
3941 "selectFirstExtendSelection";
3942 private static final String SELECT_LAST = "selectLast";
3943 private static final String SELECT_LAST_CHANGE_LEAD =
3944 "selectLastChangeLead";
3945 private static final String SELECT_LAST_EXTEND_SELECTION =
3946 "selectLastExtendSelection";
3947 private static final String TOGGLE = "toggle";
3948 private static final String CANCEL_EDITING = "cancel";
3949 private static final String START_EDITING = "startEditing";
3950 private static final String SELECT_ALL = "selectAll";
3951 private static final String CLEAR_SELECTION = "clearSelection";
3952 private static final String SCROLL_LEFT = "scrollLeft";
3953 private static final String SCROLL_RIGHT = "scrollRight";
3954 private static final String SCROLL_LEFT_EXTEND_SELECTION =
3955 "scrollLeftExtendSelection";
3956 private static final String SCROLL_RIGHT_EXTEND_SELECTION =
3957 "scrollRightExtendSelection";
3958 private static final String SCROLL_RIGHT_CHANGE_LEAD =
3959 "scrollRightChangeLead";
3960 private static final String SCROLL_LEFT_CHANGE_LEAD =
3961 "scrollLeftChangeLead";
3962 private static final String EXPAND = "expand";
3963 private static final String COLLAPSE = "collapse";
3964 private static final String MOVE_SELECTION_TO_PARENT =
3965 "moveSelectionToParent";
3966
3967 // add the lead item to the selection without changing lead or anchor
3968 private static final String ADD_TO_SELECTION = "addToSelection";
3969
3970 // toggle the selected state of the lead item and move the anchor to it
3971 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
3972
3973 // extend the selection to the lead item
3974 private static final String EXTEND_TO = "extendTo";
3975
3976 // move the anchor to the lead and ensure only that item is selected
3977 private static final String MOVE_SELECTION_TO = "moveSelectionTo";
3978
3979 Actions() {
3980 super(null);
3981 }
3982
3983 Actions(String key) {
3984 super(key);
3985 }
3986
3987 public boolean isEnabled(Object o) {
3988 if (o instanceof JTree) {
3989 if (getName() == CANCEL_EDITING) {
3990 return ((JTree)o).isEditing();
3991 }
3992 }
3993 return true;
3994 }
3995
3996 public void actionPerformed(ActionEvent e) {
3997 JTree tree = (JTree)e.getSource();
3998 BasicTreeUI ui = (BasicTreeUI)BasicLookAndFeel.getUIOfType(
3999 tree.getUI(), BasicTreeUI.class);
4000 if (ui == null) {
4001 return;
4002 }
4003 String key = getName();
4004 if (key == SELECT_PREVIOUS) {
4005 increment(tree, ui, -1, false, true);
4006 }
4007 else if (key == SELECT_PREVIOUS_CHANGE_LEAD) {
4008 increment(tree, ui, -1, false, false);
4009 }
4010 else if (key == SELECT_PREVIOUS_EXTEND_SELECTION) {
4011 increment(tree, ui, -1, true, true);
4012 }
4013 else if (key == SELECT_NEXT) {
4014 increment(tree, ui, 1, false, true);
4015 }
4016 else if (key == SELECT_NEXT_CHANGE_LEAD) {
4017 increment(tree, ui, 1, false, false);
4018 }
4019 else if (key == SELECT_NEXT_EXTEND_SELECTION) {
4020 increment(tree, ui, 1, true, true);
4021 }
4022 else if (key == SELECT_CHILD) {
4023 traverse(tree, ui, 1, true);
4024 }
4025 else if (key == SELECT_CHILD_CHANGE_LEAD) {
4026 traverse(tree, ui, 1, false);
4027 }
4028 else if (key == SELECT_PARENT) {
4029 traverse(tree, ui, -1, true);
4030 }
4031 else if (key == SELECT_PARENT_CHANGE_LEAD) {
4032 traverse(tree, ui, -1, false);
4033 }
4034 else if (key == SCROLL_UP_CHANGE_SELECTION) {
4035 page(tree, ui, -1, false, true);
4036 }
4037 else if (key == SCROLL_UP_CHANGE_LEAD) {
4038 page(tree, ui, -1, false, false);
4039 }
4040 else if (key == SCROLL_UP_EXTEND_SELECTION) {
4041 page(tree, ui, -1, true, true);
4042 }
4043 else if (key == SCROLL_DOWN_CHANGE_SELECTION) {
4044 page(tree, ui, 1, false, true);
4045 }
4046 else if (key == SCROLL_DOWN_EXTEND_SELECTION) {
4047 page(tree, ui, 1, true, true);
4048 }
4049 else if (key == SCROLL_DOWN_CHANGE_LEAD) {
4050 page(tree, ui, 1, false, false);
4051 }
4052 else if (key == SELECT_FIRST) {
4053 home(tree, ui, -1, false, true);
4054 }
4055 else if (key == SELECT_FIRST_CHANGE_LEAD) {
4056 home(tree, ui, -1, false, false);
4057 }
4058 else if (key == SELECT_FIRST_EXTEND_SELECTION) {
4059 home(tree, ui, -1, true, true);
4060 }
4061 else if (key == SELECT_LAST) {
4062 home(tree, ui, 1, false, true);
4063 }
4064 else if (key == SELECT_LAST_CHANGE_LEAD) {
4065 home(tree, ui, 1, false, false);
4066 }
4067 else if (key == SELECT_LAST_EXTEND_SELECTION) {
4068 home(tree, ui, 1, true, true);
4069 }
4070 else if (key == TOGGLE) {
4071 toggle(tree, ui);
4072 }
4073 else if (key == CANCEL_EDITING) {
4074 cancelEditing(tree, ui);
4075 }
4076 else if (key == START_EDITING) {
4077 startEditing(tree, ui);
4078 }
4079 else if (key == SELECT_ALL) {
4080 selectAll(tree, ui, true);
4081 }
4082 else if (key == CLEAR_SELECTION) {
4083 selectAll(tree, ui, false);
4084 }
4085 else if (key == ADD_TO_SELECTION) {
4086 if (ui.getRowCount(tree) > 0) {
4087 int lead = ui.getLeadSelectionRow();
4088 if (!tree.isRowSelected(lead)) {
4089 TreePath aPath = ui.getAnchorSelectionPath();
4090 tree.addSelectionRow(lead);
4091 ui.setAnchorSelectionPath(aPath);
4092 }
4093 }
4094 }
4095 else if (key == TOGGLE_AND_ANCHOR) {
4096 if (ui.getRowCount(tree) > 0) {
4097 int lead = ui.getLeadSelectionRow();
4098 TreePath lPath = ui.getLeadSelectionPath();
4099 if (!tree.isRowSelected(lead)) {
4100 tree.addSelectionRow(lead);
4101 } else {
4102 tree.removeSelectionRow(lead);
4103 ui.setLeadSelectionPath(lPath);
4104 }
4105 ui.setAnchorSelectionPath(lPath);
4106 }
4107 }
4108 else if (key == EXTEND_TO) {
4109 extendSelection(tree, ui);
4110 }
4111 else if (key == MOVE_SELECTION_TO) {
4112 if (ui.getRowCount(tree) > 0) {
4113 int lead = ui.getLeadSelectionRow();
4114 tree.setSelectionInterval(lead, lead);
4115 }
4116 }
4117 else if (key == SCROLL_LEFT) {
4118 scroll(tree, ui, SwingConstants.HORIZONTAL, -10);
4119 }
4120 else if (key == SCROLL_RIGHT) {
4121 scroll(tree, ui, SwingConstants.HORIZONTAL, 10);
4122 }
4123 else if (key == SCROLL_LEFT_EXTEND_SELECTION) {
4124 scrollChangeSelection(tree, ui, -1, true, true);
4125 }
4126 else if (key == SCROLL_RIGHT_EXTEND_SELECTION) {
4127 scrollChangeSelection(tree, ui, 1, true, true);
4128 }
4129 else if (key == SCROLL_RIGHT_CHANGE_LEAD) {
4130 scrollChangeSelection(tree, ui, 1, false, false);
4131 }
4132 else if (key == SCROLL_LEFT_CHANGE_LEAD) {
4133 scrollChangeSelection(tree, ui, -1, false, false);
4134 }
4135 else if (key == EXPAND) {
4136 expand(tree, ui);
4137 }
4138 else if (key == COLLAPSE) {
4139 collapse(tree, ui);
4140 }
4141 else if (key == MOVE_SELECTION_TO_PARENT) {
4142 moveSelectionToParent(tree, ui);
4143 }
4144 }
4145
4146 private void scrollChangeSelection(JTree tree, BasicTreeUI ui,
4147 int direction, boolean addToSelection,
4148 boolean changeSelection) {
4149 int rowCount;
4150
4151 if((rowCount = ui.getRowCount(tree)) > 0 &&
4152 ui.treeSelectionModel != null) {
4153 TreePath newPath;
4154 Rectangle visRect = tree.getVisibleRect();
4155
4156 if (direction == -1) {
4157 newPath = ui.getClosestPathForLocation(tree, visRect.x,
4158 visRect.y);
4159 visRect.x = Math.max(0, visRect.x - visRect.width);
4160 }
4161 else {
4162 visRect.x = Math.min(Math.max(0, tree.getWidth() -
4163 visRect.width), visRect.x + visRect.width);
4164 newPath = ui.getClosestPathForLocation(tree, visRect.x,
4165 visRect.y + visRect.height);
4166 }
4167 // Scroll
4168 tree.scrollRectToVisible(visRect);
4169 // select
4170 if (addToSelection) {
4171 ui.extendSelection(newPath);
4172 }
4173 else if(changeSelection) {
4174 tree.setSelectionPath(newPath);
4175 }
4176 else {
4177 ui.setLeadSelectionPath(newPath, true);
4178 }
4179 }
4180 }
4181
4182 private void scroll(JTree component, BasicTreeUI ui, int direction,
4183 int amount) {
4184 Rectangle visRect = component.getVisibleRect();
4185 Dimension size = component.getSize();
4186 if (direction == SwingConstants.HORIZONTAL) {
4187 visRect.x += amount;
4188 visRect.x = Math.max(0, visRect.x);
4189 visRect.x = Math.min(Math.max(0, size.width - visRect.width),
4190 visRect.x);
4191 }
4192 else {
4193 visRect.y += amount;
4194 visRect.y = Math.max(0, visRect.y);
4195 visRect.y = Math.min(Math.max(0, size.width - visRect.height),
4196 visRect.y);
4197 }
4198 component.scrollRectToVisible(visRect);
4199 }
4200
4201 private void extendSelection(JTree tree, BasicTreeUI ui) {
4202 if (ui.getRowCount(tree) > 0) {
4203 int lead = ui.getLeadSelectionRow();
4204
4205 if (lead != -1) {
4206 TreePath leadP = ui.getLeadSelectionPath();
4207 TreePath aPath = ui.getAnchorSelectionPath();
4208 int aRow = ui.getRowForPath(tree, aPath);
4209
4210 if(aRow == -1)
4211 aRow = 0;
4212 tree.setSelectionInterval(aRow, lead);
4213 ui.setLeadSelectionPath(leadP);
4214 ui.setAnchorSelectionPath(aPath);
4215 }
4216 }
4217 }
4218
4219 private void selectAll(JTree tree, BasicTreeUI ui, boolean selectAll) {
4220 int rowCount = ui.getRowCount(tree);
4221
4222 if(rowCount > 0) {
4223 if(selectAll) {
4224 if (tree.getSelectionModel().getSelectionMode() ==
4225 TreeSelectionModel.SINGLE_TREE_SELECTION) {
4226
4227 int lead = ui.getLeadSelectionRow();
4228 if (lead != -1) {
4229 tree.setSelectionRow(lead);
4230 } else if (tree.getMinSelectionRow() == -1) {
4231 tree.setSelectionRow(0);
4232 ui.ensureRowsAreVisible(0, 0);
4233 }
4234 return;
4235 }
4236
4237 TreePath lastPath = ui.getLeadSelectionPath();
4238 TreePath aPath = ui.getAnchorSelectionPath();
4239
4240 if(lastPath != null && !tree.isVisible(lastPath)) {
4241 lastPath = null;
4242 }
4243 tree.setSelectionInterval(0, rowCount - 1);
4244 if(lastPath != null) {
4245 ui.setLeadSelectionPath(lastPath);
4246 }
4247 if(aPath != null && tree.isVisible(aPath)) {
4248 ui.setAnchorSelectionPath(aPath);
4249 }
4250 }
4251 else {
4252 TreePath lastPath = ui.getLeadSelectionPath();
4253 TreePath aPath = ui.getAnchorSelectionPath();
4254
4255 tree.clearSelection();
4256 ui.setAnchorSelectionPath(aPath);
4257 ui.setLeadSelectionPath(lastPath);
4258 }
4259 }
4260 }
4261
4262 private void startEditing(JTree tree, BasicTreeUI ui) {
4263 TreePath lead = ui.getLeadSelectionPath();
4264 int editRow = (lead != null) ?
4265 ui.getRowForPath(tree, lead) : -1;
4266
4267 if(editRow != -1) {
4268 tree.startEditingAtPath(lead);
4269 }
4270 }
4271
4272 private void cancelEditing(JTree tree, BasicTreeUI ui) {
4273 tree.cancelEditing();
4274 }
4275
4276 private void toggle(JTree tree, BasicTreeUI ui) {
4277 int selRow = ui.getLeadSelectionRow();
4278
4279 if(selRow != -1 && !ui.isLeaf(selRow)) {
4280 TreePath aPath = ui.getAnchorSelectionPath();
4281 TreePath lPath = ui.getLeadSelectionPath();
4282
4283 ui.toggleExpandState(ui.getPathForRow(tree, selRow));
4284 ui.setAnchorSelectionPath(aPath);
4285 ui.setLeadSelectionPath(lPath);
4286 }
4287 }
4288
4289 private void expand(JTree tree, BasicTreeUI ui) {
4290 int selRow = ui.getLeadSelectionRow();
4291 tree.expandRow(selRow);
4292 }
4293
4294 private void collapse(JTree tree, BasicTreeUI ui) {
4295 int selRow = ui.getLeadSelectionRow();
4296 tree.collapseRow(selRow);
4297 }
4298
4299 private void increment(JTree tree, BasicTreeUI ui, int direction,
4300 boolean addToSelection,
4301 boolean changeSelection) {
4302
4303 // disable moving of lead unless in discontiguous mode
4304 if (!addToSelection && !changeSelection &&
4305 tree.getSelectionModel().getSelectionMode() !=
4306 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
4307 changeSelection = true;
4308 }
4309
4310 int rowCount;
4311
4312 if(ui.treeSelectionModel != null &&
4313 (rowCount = tree.getRowCount()) > 0) {
4314 int selIndex = ui.getLeadSelectionRow();
4315 int newIndex;
4316
4317 if(selIndex == -1) {
4318 if(direction == 1)
4319 newIndex = 0;
4320 else
4321 newIndex = rowCount - 1;
4322 }
4323 else
4324 /* Aparently people don't like wrapping;( */
4325 newIndex = Math.min(rowCount - 1, Math.max
4326 (0, (selIndex + direction)));
4327 if(addToSelection && ui.treeSelectionModel.
4328 getSelectionMode() != TreeSelectionModel.
4329 SINGLE_TREE_SELECTION) {
4330 ui.extendSelection(tree.getPathForRow(newIndex));
4331 }
4332 else if(changeSelection) {
4333 tree.setSelectionInterval(newIndex, newIndex);
4334 }
4335 else {
4336 ui.setLeadSelectionPath(tree.getPathForRow(newIndex),true);
4337 }
4338 ui.ensureRowsAreVisible(newIndex, newIndex);
4339 ui.lastSelectedRow = newIndex;
4340 }
4341 }
4342
4343 private void traverse(JTree tree, BasicTreeUI ui, int direction,
4344 boolean changeSelection) {
4345
4346 // disable moving of lead unless in discontiguous mode
4347 if (!changeSelection &&
4348 tree.getSelectionModel().getSelectionMode() !=
4349 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
4350 changeSelection = true;
4351 }
4352
4353 int rowCount;
4354
4355 if((rowCount = tree.getRowCount()) > 0) {
4356 int minSelIndex = ui.getLeadSelectionRow();
4357 int newIndex;
4358
4359 if(minSelIndex == -1)
4360 newIndex = 0;
4361 else {
4362 /* Try and expand the node, otherwise go to next
4363 node. */
4364 if(direction == 1) {
4365 TreePath minSelPath = ui.getPathForRow(tree, minSelIndex);
4366 int childCount = tree.getModel().
4367 getChildCount(minSelPath.getLastPathComponent());
4368 newIndex = -1;
4369 if (!ui.isLeaf(minSelIndex)) {
4370 if (!tree.isExpanded(minSelIndex)) {
4371 ui.toggleExpandState(minSelPath);
4372 }
4373 else if (childCount > 0) {
4374 newIndex = Math.min(minSelIndex + 1, rowCount - 1);
4375 }
4376 }
4377 }
4378 /* Try to collapse node. */
4379 else {
4380 if(!ui.isLeaf(minSelIndex) &&
4381 tree.isExpanded(minSelIndex)) {
4382 ui.toggleExpandState(ui.getPathForRow
4383 (tree, minSelIndex));
4384 newIndex = -1;
4385 }
4386 else {
4387 TreePath path = ui.getPathForRow(tree,
4388 minSelIndex);
4389
4390 if(path != null && path.getPathCount() > 1) {
4391 newIndex = ui.getRowForPath(tree, path.
4392 getParentPath());
4393 }
4394 else
4395 newIndex = -1;
4396 }
4397 }
4398 }
4399 if(newIndex != -1) {
4400 if(changeSelection) {
4401 tree.setSelectionInterval(newIndex, newIndex);
4402 }
4403 else {
4404 ui.setLeadSelectionPath(ui.getPathForRow(
4405 tree, newIndex), true);
4406 }
4407 ui.ensureRowsAreVisible(newIndex, newIndex);
4408 }
4409 }
4410 }
4411
4412 private void moveSelectionToParent(JTree tree, BasicTreeUI ui) {
4413 int selRow = ui.getLeadSelectionRow();
4414 TreePath path = ui.getPathForRow(tree, selRow);
4415 if (path != null && path.getPathCount() > 1) {
4416 int newIndex = ui.getRowForPath(tree, path.getParentPath());
4417 if (newIndex != -1) {
4418 tree.setSelectionInterval(newIndex, newIndex);
4419 ui.ensureRowsAreVisible(newIndex, newIndex);
4420 }
4421 }
4422 }
4423
4424 private void page(JTree tree, BasicTreeUI ui, int direction,
4425 boolean addToSelection, boolean changeSelection) {
4426
4427 // disable moving of lead unless in discontiguous mode
4428 if (!addToSelection && !changeSelection &&
4429 tree.getSelectionModel().getSelectionMode() !=
4430 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
4431 changeSelection = true;
4432 }
4433
4434 int rowCount;
4435
4436 if((rowCount = ui.getRowCount(tree)) > 0 &&
4437 ui.treeSelectionModel != null) {
4438 Dimension maxSize = tree.getSize();
4439 TreePath lead = ui.getLeadSelectionPath();
4440 TreePath newPath;
4441 Rectangle visRect = tree.getVisibleRect();
4442
4443 if(direction == -1) {
4444 // up.
4445 newPath = ui.getClosestPathForLocation(tree, visRect.x,
4446 visRect.y);
4447 if(newPath.equals(lead)) {
4448 visRect.y = Math.max(0, visRect.y - visRect.height);
4449 newPath = tree.getClosestPathForLocation(visRect.x,
4450 visRect.y);
4451 }
4452 }
4453 else {
4454 // down
4455 visRect.y = Math.min(maxSize.height, visRect.y +
4456 visRect.height - 1);
4457 newPath = tree.getClosestPathForLocation(visRect.x,
4458 visRect.y);
4459 if(newPath.equals(lead)) {
4460 visRect.y = Math.min(maxSize.height, visRect.y +
4461 visRect.height - 1);
4462 newPath = tree.getClosestPathForLocation(visRect.x,
4463 visRect.y);
4464 }
4465 }
4466 Rectangle newRect = ui.getPathBounds(tree, newPath);
4467
4468 newRect.x = visRect.x;
4469 newRect.width = visRect.width;
4470 if(direction == -1) {
4471 newRect.height = visRect.height;
4472 }
4473 else {
4474 newRect.y -= (visRect.height - newRect.height);
4475 newRect.height = visRect.height;
4476 }
4477
4478 if(addToSelection) {
4479 ui.extendSelection(newPath);
4480 }
4481 else if(changeSelection) {
4482 tree.setSelectionPath(newPath);
4483 }
4484 else {
4485 ui.setLeadSelectionPath(newPath, true);
4486 }
4487 tree.scrollRectToVisible(newRect);
4488 }
4489 }
4490
4491 private void home(JTree tree, BasicTreeUI ui, int direction,
4492 boolean addToSelection, boolean changeSelection) {
4493
4494 // disable moving of lead unless in discontiguous mode
4495 if (!addToSelection && !changeSelection &&
4496 tree.getSelectionModel().getSelectionMode() !=
4497 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
4498 changeSelection = true;
4499 }
4500
4501 int rowCount = ui.getRowCount(tree);
4502
4503 if (rowCount > 0) {
4504 if(direction == -1) {
4505 ui.ensureRowsAreVisible(0, 0);
4506 if (addToSelection) {
4507 TreePath aPath = ui.getAnchorSelectionPath();
4508 int aRow = (aPath == null) ? -1 :
4509 ui.getRowForPath(tree, aPath);
4510
4511 if (aRow == -1) {
4512 tree.setSelectionInterval(0, 0);
4513 }
4514 else {
4515 tree.setSelectionInterval(0, aRow);
4516 ui.setAnchorSelectionPath(aPath);
4517 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0));
4518 }
4519 }
4520 else if(changeSelection) {
4521 tree.setSelectionInterval(0, 0);
4522 }
4523 else {
4524 ui.setLeadSelectionPath(ui.getPathForRow(tree, 0),
4525 true);
4526 }
4527 }
4528 else {
4529 ui.ensureRowsAreVisible(rowCount - 1, rowCount - 1);
4530 if (addToSelection) {
4531 TreePath aPath = ui.getAnchorSelectionPath();
4532 int aRow = (aPath == null) ? -1 :
4533 ui.getRowForPath(tree, aPath);
4534
4535 if (aRow == -1) {
4536 tree.setSelectionInterval(rowCount - 1,
4537 rowCount -1);
4538 }
4539 else {
4540 tree.setSelectionInterval(aRow, rowCount - 1);
4541 ui.setAnchorSelectionPath(aPath);
4542 ui.setLeadSelectionPath(ui.getPathForRow(tree,
4543 rowCount -1));
4544 }
4545 }
4546 else if(changeSelection) {
4547 tree.setSelectionInterval(rowCount - 1, rowCount - 1);
4548 }
4549 else {
4550 ui.setLeadSelectionPath(ui.getPathForRow(tree,
4551 rowCount - 1), true);
4552 }
4553 }
4554 }
4555 }
4556 }
4557} // End of class BasicTreeUI