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