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