001 /* BasicTreeUI.java --
002 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import gnu.javax.swing.tree.GnuPath;
042
043 import java.awt.Color;
044 import java.awt.Component;
045 import java.awt.Container;
046 import java.awt.Dimension;
047 import java.awt.Graphics;
048 import java.awt.Insets;
049 import java.awt.Label;
050 import java.awt.Point;
051 import java.awt.Rectangle;
052 import java.awt.event.ActionEvent;
053 import java.awt.event.ActionListener;
054 import java.awt.event.ComponentAdapter;
055 import java.awt.event.ComponentEvent;
056 import java.awt.event.ComponentListener;
057 import java.awt.event.FocusEvent;
058 import java.awt.event.FocusListener;
059 import java.awt.event.InputEvent;
060 import java.awt.event.KeyAdapter;
061 import java.awt.event.KeyEvent;
062 import java.awt.event.KeyListener;
063 import java.awt.event.MouseAdapter;
064 import java.awt.event.MouseEvent;
065 import java.awt.event.MouseListener;
066 import java.awt.event.MouseMotionListener;
067 import java.beans.PropertyChangeEvent;
068 import java.beans.PropertyChangeListener;
069 import java.util.Enumeration;
070 import java.util.Hashtable;
071
072 import javax.swing.AbstractAction;
073 import javax.swing.Action;
074 import javax.swing.ActionMap;
075 import javax.swing.CellRendererPane;
076 import javax.swing.Icon;
077 import javax.swing.InputMap;
078 import javax.swing.JComponent;
079 import javax.swing.JScrollBar;
080 import javax.swing.JScrollPane;
081 import javax.swing.JTree;
082 import javax.swing.LookAndFeel;
083 import javax.swing.SwingUtilities;
084 import javax.swing.Timer;
085 import javax.swing.UIManager;
086 import javax.swing.event.CellEditorListener;
087 import javax.swing.event.ChangeEvent;
088 import javax.swing.event.MouseInputListener;
089 import javax.swing.event.TreeExpansionEvent;
090 import javax.swing.event.TreeExpansionListener;
091 import javax.swing.event.TreeModelEvent;
092 import javax.swing.event.TreeModelListener;
093 import javax.swing.event.TreeSelectionEvent;
094 import javax.swing.event.TreeSelectionListener;
095 import javax.swing.plaf.ActionMapUIResource;
096 import javax.swing.plaf.ComponentUI;
097 import javax.swing.plaf.TreeUI;
098 import javax.swing.tree.AbstractLayoutCache;
099 import javax.swing.tree.DefaultTreeCellEditor;
100 import javax.swing.tree.DefaultTreeCellRenderer;
101 import javax.swing.tree.TreeCellEditor;
102 import javax.swing.tree.TreeCellRenderer;
103 import javax.swing.tree.TreeModel;
104 import javax.swing.tree.TreeNode;
105 import javax.swing.tree.TreePath;
106 import javax.swing.tree.TreeSelectionModel;
107 import javax.swing.tree.VariableHeightLayoutCache;
108
109 /**
110 * A delegate providing the user interface for <code>JTree</code> according to
111 * the Basic look and feel.
112 *
113 * @see javax.swing.JTree
114 * @author Lillian Angel (langel@redhat.com)
115 * @author Sascha Brawer (brawer@dandelis.ch)
116 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
117 */
118 public class BasicTreeUI
119 extends TreeUI
120 {
121 /**
122 * The tree cell editing may be started by the single mouse click on the
123 * selected cell. To separate it from the double mouse click, the editing
124 * session starts after this time (in ms) after that single click, and only no
125 * other clicks were performed during that time.
126 */
127 static int WAIT_TILL_EDITING = 900;
128
129 /** Collapse Icon for the tree. */
130 protected transient Icon collapsedIcon;
131
132 /** Expanded Icon for the tree. */
133 protected transient Icon expandedIcon;
134
135 /** Distance between left margin and where vertical dashes will be drawn. */
136 protected int leftChildIndent;
137
138 /**
139 * Distance between leftChildIndent and where cell contents will be drawn.
140 */
141 protected int rightChildIndent;
142
143 /**
144 * Total fistance that will be indented. The sum of leftChildIndent and
145 * rightChildIndent .
146 */
147 protected int totalChildIndent;
148
149 /** Index of the row that was last selected. */
150 protected int lastSelectedRow;
151
152 /** Component that we're going to be drawing onto. */
153 protected JTree tree;
154
155 /** Renderer that is being used to do the actual cell drawing. */
156 protected transient TreeCellRenderer currentCellRenderer;
157
158 /**
159 * Set to true if the renderer that is currently in the tree was created by
160 * this instance.
161 */
162 protected boolean createdRenderer;
163
164 /** Editor for the tree. */
165 protected transient TreeCellEditor cellEditor;
166
167 /**
168 * Set to true if editor that is currently in the tree was created by this
169 * instance.
170 */
171 protected boolean createdCellEditor;
172
173 /**
174 * Set to false when editing and shouldSelectCall() returns true meaning the
175 * node should be selected before editing, used in completeEditing.
176 * GNU Classpath editing is implemented differently, so this value is not
177 * actually read anywhere. However it is always set correctly to maintain
178 * interoperability with the derived classes that read this field.
179 */
180 protected boolean stopEditingInCompleteEditing;
181
182 /** Used to paint the TreeCellRenderer. */
183 protected CellRendererPane rendererPane;
184
185 /** Size needed to completely display all the nodes. */
186 protected Dimension preferredSize;
187
188 /** Minimum size needed to completely display all the nodes. */
189 protected Dimension preferredMinSize;
190
191 /** Is the preferredSize valid? */
192 protected boolean validCachedPreferredSize;
193
194 /** Object responsible for handling sizing and expanded issues. */
195 protected AbstractLayoutCache treeState;
196
197 /** Used for minimizing the drawing of vertical lines. */
198 protected Hashtable<TreePath, Boolean> drawingCache;
199
200 /**
201 * True if doing optimizations for a largeModel. Subclasses that don't support
202 * this may wish to override createLayoutCache to not return a
203 * FixedHeightLayoutCache instance.
204 */
205 protected boolean largeModel;
206
207 /** Responsible for telling the TreeState the size needed for a node. */
208 protected AbstractLayoutCache.NodeDimensions nodeDimensions;
209
210 /** Used to determine what to display. */
211 protected TreeModel treeModel;
212
213 /** Model maintaining the selection. */
214 protected TreeSelectionModel treeSelectionModel;
215
216 /**
217 * How much the depth should be offset to properly calculate x locations. This
218 * is based on whether or not the root is visible, and if the root handles are
219 * visible.
220 */
221 protected int depthOffset;
222
223 /**
224 * When editing, this will be the Component that is doing the actual editing.
225 */
226 protected Component editingComponent;
227
228 /** Path that is being edited. */
229 protected TreePath editingPath;
230
231 /**
232 * Row that is being edited. Should only be referenced if editingComponent is
233 * null.
234 */
235 protected int editingRow;
236
237 /** Set to true if the editor has a different size than the renderer. */
238 protected boolean editorHasDifferentSize;
239
240 /** Boolean to keep track of editing. */
241 boolean isEditing;
242
243 /** The current path of the visible nodes in the tree. */
244 TreePath currentVisiblePath;
245
246 /** The gap between the icon and text. */
247 int gap = 4;
248
249 /** The max height of the nodes in the tree. */
250 int maxHeight;
251
252 /** The hash color. */
253 Color hashColor;
254
255 /** Listeners */
256 PropertyChangeListener propertyChangeListener;
257
258 FocusListener focusListener;
259
260 TreeSelectionListener treeSelectionListener;
261
262 MouseListener mouseListener;
263
264 KeyListener keyListener;
265
266 PropertyChangeListener selectionModelPropertyChangeListener;
267
268 ComponentListener componentListener;
269
270 CellEditorListener cellEditorListener;
271
272 TreeExpansionListener treeExpansionListener;
273
274 TreeModelListener treeModelListener;
275
276 /**
277 * The zero size icon, used for expand controls, if they are not visible.
278 */
279 static Icon nullIcon;
280
281 /**
282 * Creates a new BasicTreeUI object.
283 */
284 public BasicTreeUI()
285 {
286 validCachedPreferredSize = false;
287 drawingCache = new Hashtable();
288 nodeDimensions = createNodeDimensions();
289 configureLayoutCache();
290
291 editingRow = - 1;
292 lastSelectedRow = - 1;
293 }
294
295 /**
296 * Returns an instance of the UI delegate for the specified component.
297 *
298 * @param c the <code>JComponent</code> for which we need a UI delegate for.
299 * @return the <code>ComponentUI</code> for c.
300 */
301 public static ComponentUI createUI(JComponent c)
302 {
303 return new BasicTreeUI();
304 }
305
306 /**
307 * Returns the Hash color.
308 *
309 * @return the <code>Color</code> of the Hash.
310 */
311 protected Color getHashColor()
312 {
313 return hashColor;
314 }
315
316 /**
317 * Sets the Hash color.
318 *
319 * @param color the <code>Color</code> to set the Hash to.
320 */
321 protected void setHashColor(Color color)
322 {
323 hashColor = color;
324 }
325
326 /**
327 * Sets the left child's indent value.
328 *
329 * @param newAmount is the new indent value for the left child.
330 */
331 public void setLeftChildIndent(int newAmount)
332 {
333 leftChildIndent = newAmount;
334 }
335
336 /**
337 * Returns the indent value for the left child.
338 *
339 * @return the indent value for the left child.
340 */
341 public int getLeftChildIndent()
342 {
343 return leftChildIndent;
344 }
345
346 /**
347 * Sets the right child's indent value.
348 *
349 * @param newAmount is the new indent value for the right child.
350 */
351 public void setRightChildIndent(int newAmount)
352 {
353 rightChildIndent = newAmount;
354 }
355
356 /**
357 * Returns the indent value for the right child.
358 *
359 * @return the indent value for the right child.
360 */
361 public int getRightChildIndent()
362 {
363 return rightChildIndent;
364 }
365
366 /**
367 * Sets the expanded icon.
368 *
369 * @param newG is the new expanded icon.
370 */
371 public void setExpandedIcon(Icon newG)
372 {
373 expandedIcon = newG;
374 }
375
376 /**
377 * Returns the current expanded icon.
378 *
379 * @return the current expanded icon.
380 */
381 public Icon getExpandedIcon()
382 {
383 return expandedIcon;
384 }
385
386 /**
387 * Sets the collapsed icon.
388 *
389 * @param newG is the new collapsed icon.
390 */
391 public void setCollapsedIcon(Icon newG)
392 {
393 collapsedIcon = newG;
394 }
395
396 /**
397 * Returns the current collapsed icon.
398 *
399 * @return the current collapsed icon.
400 */
401 public Icon getCollapsedIcon()
402 {
403 return collapsedIcon;
404 }
405
406 /**
407 * Updates the componentListener, if necessary.
408 *
409 * @param largeModel sets this.largeModel to it.
410 */
411 protected void setLargeModel(boolean largeModel)
412 {
413 if (largeModel != this.largeModel)
414 {
415 completeEditing();
416 tree.removeComponentListener(componentListener);
417 this.largeModel = largeModel;
418 tree.addComponentListener(componentListener);
419 }
420 }
421
422 /**
423 * Returns true if largeModel is set
424 *
425 * @return true if largeModel is set, otherwise false.
426 */
427 protected boolean isLargeModel()
428 {
429 return largeModel;
430 }
431
432 /**
433 * Sets the row height.
434 *
435 * @param rowHeight is the height to set this.rowHeight to.
436 */
437 protected void setRowHeight(int rowHeight)
438 {
439 completeEditing();
440 if (rowHeight == 0)
441 rowHeight = getMaxHeight(tree);
442 treeState.setRowHeight(rowHeight);
443 }
444
445 /**
446 * Returns the current row height.
447 *
448 * @return current row height.
449 */
450 protected int getRowHeight()
451 {
452 return tree.getRowHeight();
453 }
454
455 /**
456 * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
457 * <code>updateRenderer</code>.
458 *
459 * @param tcr is the new TreeCellRenderer.
460 */
461 protected void setCellRenderer(TreeCellRenderer tcr)
462 {
463 // Finish editing before changing the renderer.
464 completeEditing();
465
466 // The renderer is set in updateRenderer.
467 updateRenderer();
468
469 // Refresh the layout if necessary.
470 if (treeState != null)
471 {
472 treeState.invalidateSizes();
473 updateSize();
474 }
475 }
476
477 /**
478 * Return currentCellRenderer, which will either be the trees renderer, or
479 * defaultCellRenderer, which ever was not null.
480 *
481 * @return the current Cell Renderer
482 */
483 protected TreeCellRenderer getCellRenderer()
484 {
485 if (currentCellRenderer != null)
486 return currentCellRenderer;
487
488 return createDefaultCellRenderer();
489 }
490
491 /**
492 * Sets the tree's model.
493 *
494 * @param model to set the treeModel to.
495 */
496 protected void setModel(TreeModel model)
497 {
498 completeEditing();
499
500 if (treeModel != null && treeModelListener != null)
501 treeModel.removeTreeModelListener(treeModelListener);
502
503 treeModel = tree.getModel();
504
505 if (treeModel != null && treeModelListener != null)
506 treeModel.addTreeModelListener(treeModelListener);
507
508 if (treeState != null)
509 {
510 treeState.setModel(treeModel);
511 updateLayoutCacheExpandedNodes();
512 updateSize();
513 }
514 }
515
516 /**
517 * Returns the tree's model
518 *
519 * @return treeModel
520 */
521 protected TreeModel getModel()
522 {
523 return treeModel;
524 }
525
526 /**
527 * Sets the root to being visible.
528 *
529 * @param newValue sets the visibility of the root
530 */
531 protected void setRootVisible(boolean newValue)
532 {
533 completeEditing();
534 tree.setRootVisible(newValue);
535 }
536
537 /**
538 * Returns true if the root is visible.
539 *
540 * @return true if the root is visible.
541 */
542 protected boolean isRootVisible()
543 {
544 return tree.isRootVisible();
545 }
546
547 /**
548 * Determines whether the node handles are to be displayed.
549 *
550 * @param newValue sets whether or not node handles should be displayed.
551 */
552 protected void setShowsRootHandles(boolean newValue)
553 {
554 completeEditing();
555 updateDepthOffset();
556 if (treeState != null)
557 {
558 treeState.invalidateSizes();
559 updateSize();
560 }
561 }
562
563 /**
564 * Returns true if the node handles are to be displayed.
565 *
566 * @return true if the node handles are to be displayed.
567 */
568 protected boolean getShowsRootHandles()
569 {
570 return tree.getShowsRootHandles();
571 }
572
573 /**
574 * Sets the cell editor.
575 *
576 * @param editor to set the cellEditor to.
577 */
578 protected void setCellEditor(TreeCellEditor editor)
579 {
580 updateCellEditor();
581 }
582
583 /**
584 * Returns the <code>TreeCellEditor</code> for this tree.
585 *
586 * @return the cellEditor for this tree.
587 */
588 protected TreeCellEditor getCellEditor()
589 {
590 return cellEditor;
591 }
592
593 /**
594 * Configures the receiver to allow, or not allow, editing.
595 *
596 * @param newValue sets the receiver to allow editing if true.
597 */
598 protected void setEditable(boolean newValue)
599 {
600 updateCellEditor();
601 }
602
603 /**
604 * Returns true if the receiver allows editing.
605 *
606 * @return true if the receiver allows editing.
607 */
608 protected boolean isEditable()
609 {
610 return tree.isEditable();
611 }
612
613 /**
614 * Resets the selection model. The appropriate listeners are installed on the
615 * model.
616 *
617 * @param newLSM resets the selection model.
618 */
619 protected void setSelectionModel(TreeSelectionModel newLSM)
620 {
621 completeEditing();
622 if (newLSM != null)
623 {
624 treeSelectionModel = newLSM;
625 tree.setSelectionModel(treeSelectionModel);
626 }
627 }
628
629 /**
630 * Returns the current selection model.
631 *
632 * @return the current selection model.
633 */
634 protected TreeSelectionModel getSelectionModel()
635 {
636 return treeSelectionModel;
637 }
638
639 /**
640 * Returns the Rectangle enclosing the label portion that the last item in
641 * path will be drawn to. Will return null if any component in path is
642 * currently valid.
643 *
644 * @param tree is the current tree the path will be drawn to.
645 * @param path is the current path the tree to draw to.
646 * @return the Rectangle enclosing the label portion that the last item in the
647 * path will be drawn to.
648 */
649 public Rectangle getPathBounds(JTree tree, TreePath path)
650 {
651 Rectangle bounds = null;
652 if (tree != null && treeState != null)
653 {
654 bounds = treeState.getBounds(path, null);
655 Insets i = tree.getInsets();
656 if (bounds != null && i != null)
657 {
658 bounds.x += i.left;
659 bounds.y += i.top;
660 }
661 }
662 return bounds;
663 }
664
665 /**
666 * Returns the max height of all the nodes in the tree.
667 *
668 * @param tree - the current tree
669 * @return the max height.
670 */
671 int getMaxHeight(JTree tree)
672 {
673 if (maxHeight != 0)
674 return maxHeight;
675
676 Icon e = UIManager.getIcon("Tree.openIcon");
677 Icon c = UIManager.getIcon("Tree.closedIcon");
678 Icon l = UIManager.getIcon("Tree.leafIcon");
679 int rc = getRowCount(tree);
680 int iconHeight = 0;
681
682 for (int row = 0; row < rc; row++)
683 {
684 if (isLeaf(row))
685 iconHeight = l.getIconHeight();
686 else if (tree.isExpanded(row))
687 iconHeight = e.getIconHeight();
688 else
689 iconHeight = c.getIconHeight();
690
691 maxHeight = Math.max(maxHeight, iconHeight + gap);
692 }
693
694 treeState.setRowHeight(maxHeight);
695 return maxHeight;
696 }
697
698 /**
699 * Get the tree node icon.
700 */
701 Icon getNodeIcon(TreePath path)
702 {
703 Object node = path.getLastPathComponent();
704 if (treeModel.isLeaf(node))
705 return UIManager.getIcon("Tree.leafIcon");
706 else if (treeState.getExpandedState(path))
707 return UIManager.getIcon("Tree.openIcon");
708 else
709 return UIManager.getIcon("Tree.closedIcon");
710 }
711
712 /**
713 * Returns the path for passed in row. If row is not visible null is returned.
714 *
715 * @param tree is the current tree to return path for.
716 * @param row is the row number of the row to return.
717 * @return the path for passed in row. If row is not visible null is returned.
718 */
719 public TreePath getPathForRow(JTree tree, int row)
720 {
721 return treeState.getPathForRow(row);
722 }
723
724 /**
725 * Returns the row that the last item identified in path is visible at. Will
726 * return -1 if any of the elments in the path are not currently visible.
727 *
728 * @param tree is the current tree to return the row for.
729 * @param path is the path used to find the row.
730 * @return the row that the last item identified in path is visible at. Will
731 * return -1 if any of the elments in the path are not currently
732 * visible.
733 */
734 public int getRowForPath(JTree tree, TreePath path)
735 {
736 return treeState.getRowForPath(path);
737 }
738
739 /**
740 * Returns the number of rows that are being displayed.
741 *
742 * @param tree is the current tree to return the number of rows for.
743 * @return the number of rows being displayed.
744 */
745 public int getRowCount(JTree tree)
746 {
747 return treeState.getRowCount();
748 }
749
750 /**
751 * Returns the path to the node that is closest to x,y. If there is nothing
752 * currently visible this will return null, otherwise it'll always return a
753 * valid path. If you need to test if the returned object is exactly at x,y
754 * you should get the bounds for the returned path and test x,y against that.
755 *
756 * @param tree the tree to search for the closest path
757 * @param x is the x coordinate of the location to search
758 * @param y is the y coordinate of the location to search
759 * @return the tree path closes to x,y.
760 */
761 public TreePath getClosestPathForLocation(JTree tree, int x, int y)
762 {
763 return treeState.getPathClosestTo(x, y);
764 }
765
766 /**
767 * Returns true if the tree is being edited. The item that is being edited can
768 * be returned by getEditingPath().
769 *
770 * @param tree is the tree to check for editing.
771 * @return true if the tree is being edited.
772 */
773 public boolean isEditing(JTree tree)
774 {
775 return isEditing;
776 }
777
778 /**
779 * Stops the current editing session. This has no effect if the tree is not
780 * being edited. Returns true if the editor allows the editing session to
781 * stop.
782 *
783 * @param tree is the tree to stop the editing on
784 * @return true if the editor allows the editing session to stop.
785 */
786 public boolean stopEditing(JTree tree)
787 {
788 boolean ret = false;
789 if (editingComponent != null && cellEditor.stopCellEditing())
790 {
791 completeEditing(false, false, true);
792 ret = true;
793 }
794 return ret;
795 }
796
797 /**
798 * Cancels the current editing session.
799 *
800 * @param tree is the tree to cancel the editing session on.
801 */
802 public void cancelEditing(JTree tree)
803 {
804 // There is no need to send the cancel message to the editor,
805 // as the cancellation event itself arrives from it. This would
806 // only be necessary when cancelling the editing programatically.
807 if (editingComponent != null)
808 completeEditing(false, true, false);
809 }
810
811 /**
812 * Selects the last item in path and tries to edit it. Editing will fail if
813 * the CellEditor won't allow it for the selected item.
814 *
815 * @param tree is the tree to edit on.
816 * @param path is the path in tree to edit on.
817 */
818 public void startEditingAtPath(JTree tree, TreePath path)
819 {
820 tree.scrollPathToVisible(path);
821 if (path != null && tree.isVisible(path))
822 startEditing(path, null);
823 }
824
825 /**
826 * Returns the path to the element that is being editted.
827 *
828 * @param tree is the tree to get the editing path from.
829 * @return the path that is being edited.
830 */
831 public TreePath getEditingPath(JTree tree)
832 {
833 return editingPath;
834 }
835
836 /**
837 * Invoked after the tree instance variable has been set, but before any
838 * default/listeners have been installed.
839 */
840 protected void prepareForUIInstall()
841 {
842 lastSelectedRow = -1;
843 preferredSize = new Dimension();
844 largeModel = tree.isLargeModel();
845 preferredSize = new Dimension();
846 stopEditingInCompleteEditing = true;
847 setModel(tree.getModel());
848 }
849
850 /**
851 * Invoked from installUI after all the defaults/listeners have been
852 * installed.
853 */
854 protected void completeUIInstall()
855 {
856 setShowsRootHandles(tree.getShowsRootHandles());
857 updateRenderer();
858 updateDepthOffset();
859 setSelectionModel(tree.getSelectionModel());
860 configureLayoutCache();
861 treeState.setRootVisible(tree.isRootVisible());
862 treeSelectionModel.setRowMapper(treeState);
863 updateSize();
864 }
865
866 /**
867 * Invoked from uninstallUI after all the defaults/listeners have been
868 * uninstalled.
869 */
870 protected void completeUIUninstall()
871 {
872 tree = null;
873 }
874
875 /**
876 * Installs the subcomponents of the tree, which is the renderer pane.
877 */
878 protected void installComponents()
879 {
880 currentCellRenderer = createDefaultCellRenderer();
881 rendererPane = createCellRendererPane();
882 createdRenderer = true;
883 setCellRenderer(currentCellRenderer);
884 }
885
886 /**
887 * Creates an instance of NodeDimensions that is able to determine the size of
888 * a given node in the tree. The node dimensions must be created before
889 * configuring the layout cache.
890 *
891 * @return the NodeDimensions of a given node in the tree
892 */
893 protected AbstractLayoutCache.NodeDimensions createNodeDimensions()
894 {
895 return new NodeDimensionsHandler();
896 }
897
898 /**
899 * Creates a listener that is reponsible for the updates the UI based on how
900 * the tree changes.
901 *
902 * @return the PropertyChangeListener that is reposnsible for the updates
903 */
904 protected PropertyChangeListener createPropertyChangeListener()
905 {
906 return new PropertyChangeHandler();
907 }
908
909 /**
910 * Creates the listener responsible for updating the selection based on mouse
911 * events.
912 *
913 * @return the MouseListener responsible for updating.
914 */
915 protected MouseListener createMouseListener()
916 {
917 return new MouseHandler();
918 }
919
920 /**
921 * Creates the listener that is responsible for updating the display when
922 * focus is lost/grained.
923 *
924 * @return the FocusListener responsible for updating.
925 */
926 protected FocusListener createFocusListener()
927 {
928 return new FocusHandler();
929 }
930
931 /**
932 * Creates the listener reponsible for getting key events from the tree.
933 *
934 * @return the KeyListener responsible for getting key events.
935 */
936 protected KeyListener createKeyListener()
937 {
938 return new KeyHandler();
939 }
940
941 /**
942 * Creates the listener responsible for getting property change events from
943 * the selection model.
944 *
945 * @returns the PropertyChangeListener reponsible for getting property change
946 * events from the selection model.
947 */
948 protected PropertyChangeListener createSelectionModelPropertyChangeListener()
949 {
950 return new SelectionModelPropertyChangeHandler();
951 }
952
953 /**
954 * Creates the listener that updates the display based on selection change
955 * methods.
956 *
957 * @return the TreeSelectionListener responsible for updating.
958 */
959 protected TreeSelectionListener createTreeSelectionListener()
960 {
961 return new TreeSelectionHandler();
962 }
963
964 /**
965 * Creates a listener to handle events from the current editor
966 *
967 * @return the CellEditorListener that handles events from the current editor
968 */
969 protected CellEditorListener createCellEditorListener()
970 {
971 return new CellEditorHandler();
972 }
973
974 /**
975 * Creates and returns a new ComponentHandler. This is used for the large
976 * model to mark the validCachedPreferredSize as invalid when the component
977 * moves.
978 *
979 * @return a new ComponentHandler.
980 */
981 protected ComponentListener createComponentListener()
982 {
983 return new ComponentHandler();
984 }
985
986 /**
987 * Creates and returns the object responsible for updating the treestate when
988 * a nodes expanded state changes.
989 *
990 * @return the TreeExpansionListener responsible for updating the treestate
991 */
992 protected TreeExpansionListener createTreeExpansionListener()
993 {
994 return new TreeExpansionHandler();
995 }
996
997 /**
998 * Creates the object responsible for managing what is expanded, as well as
999 * the size of nodes.
1000 *
1001 * @return the object responsible for managing what is expanded.
1002 */
1003 protected AbstractLayoutCache createLayoutCache()
1004 {
1005 return new VariableHeightLayoutCache();
1006 }
1007
1008 /**
1009 * Returns the renderer pane that renderer components are placed in.
1010 *
1011 * @return the rendererpane that render components are placed in.
1012 */
1013 protected CellRendererPane createCellRendererPane()
1014 {
1015 return new CellRendererPane();
1016 }
1017
1018 /**
1019 * Creates a default cell editor.
1020 *
1021 * @return the default cell editor.
1022 */
1023 protected TreeCellEditor createDefaultCellEditor()
1024 {
1025 DefaultTreeCellEditor ed;
1026 if (currentCellRenderer != null
1027 && currentCellRenderer instanceof DefaultTreeCellRenderer)
1028 ed = new DefaultTreeCellEditor(tree,
1029 (DefaultTreeCellRenderer) currentCellRenderer);
1030 else
1031 ed = new DefaultTreeCellEditor(tree, null);
1032 return ed;
1033 }
1034
1035 /**
1036 * Returns the default cell renderer that is used to do the stamping of each
1037 * node.
1038 *
1039 * @return the default cell renderer that is used to do the stamping of each
1040 * node.
1041 */
1042 protected TreeCellRenderer createDefaultCellRenderer()
1043 {
1044 return new DefaultTreeCellRenderer();
1045 }
1046
1047 /**
1048 * Returns a listener that can update the tree when the model changes.
1049 *
1050 * @return a listener that can update the tree when the model changes.
1051 */
1052 protected TreeModelListener createTreeModelListener()
1053 {
1054 return new TreeModelHandler();
1055 }
1056
1057 /**
1058 * Uninstall all registered listeners
1059 */
1060 protected void uninstallListeners()
1061 {
1062 tree.removePropertyChangeListener(propertyChangeListener);
1063 tree.removeFocusListener(focusListener);
1064 tree.removeTreeSelectionListener(treeSelectionListener);
1065 tree.removeMouseListener(mouseListener);
1066 tree.removeKeyListener(keyListener);
1067 tree.removePropertyChangeListener(selectionModelPropertyChangeListener);
1068 tree.removeComponentListener(componentListener);
1069 tree.removeTreeExpansionListener(treeExpansionListener);
1070
1071 TreeCellEditor tce = tree.getCellEditor();
1072 if (tce != null)
1073 tce.removeCellEditorListener(cellEditorListener);
1074 if (treeModel != null)
1075 treeModel.removeTreeModelListener(treeModelListener);
1076 }
1077
1078 /**
1079 * Uninstall all keyboard actions.
1080 */
1081 protected void uninstallKeyboardActions()
1082 {
1083 tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1084 null);
1085 tree.getActionMap().setParent(null);
1086 }
1087
1088 /**
1089 * Uninstall the rendererPane.
1090 */
1091 protected void uninstallComponents()
1092 {
1093 currentCellRenderer = null;
1094 rendererPane = null;
1095 createdRenderer = false;
1096 setCellRenderer(currentCellRenderer);
1097 }
1098
1099 /**
1100 * The vertical element of legs between nodes starts at the bottom of the
1101 * parent node by default. This method makes the leg start below that.
1102 *
1103 * @return the vertical leg buffer
1104 */
1105 protected int getVerticalLegBuffer()
1106 {
1107 return getRowHeight() / 2;
1108 }
1109
1110 /**
1111 * The horizontal element of legs between nodes starts at the right of the
1112 * left-hand side of the child node by default. This method makes the leg end
1113 * before that.
1114 *
1115 * @return the horizontal leg buffer
1116 */
1117 protected int getHorizontalLegBuffer()
1118 {
1119 return rightChildIndent / 2;
1120 }
1121
1122 /**
1123 * Make all the nodes that are expanded in JTree expanded in LayoutCache. This
1124 * invokes updateExpandedDescendants with the root path.
1125 */
1126 protected void updateLayoutCacheExpandedNodes()
1127 {
1128 if (treeModel != null && treeModel.getRoot() != null)
1129 updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1130 }
1131
1132 /**
1133 * Updates the expanded state of all the descendants of the <code>path</code>
1134 * by getting the expanded descendants from the tree and forwarding to the
1135 * tree state.
1136 *
1137 * @param path the path used to update the expanded states
1138 */
1139 protected void updateExpandedDescendants(TreePath path)
1140 {
1141 completeEditing();
1142 Enumeration expanded = tree.getExpandedDescendants(path);
1143 while (expanded.hasMoreElements())
1144 treeState.setExpandedState((TreePath) expanded.nextElement(), true);
1145 }
1146
1147 /**
1148 * Returns a path to the last child of <code>parent</code>
1149 *
1150 * @param parent is the topmost path to specified
1151 * @return a path to the last child of parent
1152 */
1153 protected TreePath getLastChildPath(TreePath parent)
1154 {
1155 return (TreePath) parent.getLastPathComponent();
1156 }
1157
1158 /**
1159 * Updates how much each depth should be offset by.
1160 */
1161 protected void updateDepthOffset()
1162 {
1163 depthOffset += getVerticalLegBuffer();
1164 }
1165
1166 /**
1167 * Updates the cellEditor based on editability of the JTree that we're
1168 * contained in. If the tree is editable but doesn't have a cellEditor, a
1169 * basic one will be used.
1170 */
1171 protected void updateCellEditor()
1172 {
1173 completeEditing();
1174 TreeCellEditor newEd = null;
1175 if (tree != null && tree.isEditable())
1176 {
1177 newEd = tree.getCellEditor();
1178 if (newEd == null)
1179 {
1180 newEd = createDefaultCellEditor();
1181 if (newEd != null)
1182 {
1183 tree.setCellEditor(newEd);
1184 createdCellEditor = true;
1185 }
1186 }
1187 }
1188 // Update listeners.
1189 if (newEd != cellEditor)
1190 {
1191 if (cellEditor != null && cellEditorListener != null)
1192 cellEditor.removeCellEditorListener(cellEditorListener);
1193 cellEditor = newEd;
1194 if (cellEditorListener == null)
1195 cellEditorListener = createCellEditorListener();
1196 if (cellEditor != null && cellEditorListener != null)
1197 cellEditor.addCellEditorListener(cellEditorListener);
1198 createdCellEditor = false;
1199 }
1200 }
1201
1202 /**
1203 * Messaged from the tree we're in when the renderer has changed.
1204 */
1205 protected void updateRenderer()
1206 {
1207 if (tree != null)
1208 {
1209 TreeCellRenderer rend = tree.getCellRenderer();
1210 if (rend != null)
1211 {
1212 createdRenderer = false;
1213 currentCellRenderer = rend;
1214 if (createdCellEditor)
1215 tree.setCellEditor(null);
1216 }
1217 else
1218 {
1219 tree.setCellRenderer(createDefaultCellRenderer());
1220 createdRenderer = true;
1221 }
1222 }
1223 else
1224 {
1225 currentCellRenderer = null;
1226 createdRenderer = false;
1227 }
1228
1229 updateCellEditor();
1230 }
1231
1232 /**
1233 * Resets the treeState instance based on the tree we're providing the look
1234 * and feel for. The node dimensions handler is required and must be created
1235 * in advance.
1236 */
1237 protected void configureLayoutCache()
1238 {
1239 treeState = createLayoutCache();
1240 treeState.setNodeDimensions(nodeDimensions);
1241 }
1242
1243 /**
1244 * Marks the cached size as being invalid, and messages the tree with
1245 * <code>treeDidChange</code>.
1246 */
1247 protected void updateSize()
1248 {
1249 preferredSize = null;
1250 updateCachedPreferredSize();
1251 tree.treeDidChange();
1252 }
1253
1254 /**
1255 * Updates the <code>preferredSize</code> instance variable, which is
1256 * returned from <code>getPreferredSize()</code>.
1257 */
1258 protected void updateCachedPreferredSize()
1259 {
1260 validCachedPreferredSize = false;
1261 }
1262
1263 /**
1264 * Messaged from the VisibleTreeNode after it has been expanded.
1265 *
1266 * @param path is the path that has been expanded.
1267 */
1268 protected void pathWasExpanded(TreePath path)
1269 {
1270 validCachedPreferredSize = false;
1271 treeState.setExpandedState(path, true);
1272 tree.repaint();
1273 }
1274
1275 /**
1276 * Messaged from the VisibleTreeNode after it has collapsed
1277 */
1278 protected void pathWasCollapsed(TreePath path)
1279 {
1280 validCachedPreferredSize = false;
1281 treeState.setExpandedState(path, false);
1282 tree.repaint();
1283 }
1284
1285 /**
1286 * Install all defaults for the tree.
1287 */
1288 protected void installDefaults()
1289 {
1290 LookAndFeel.installColorsAndFont(tree, "Tree.background",
1291 "Tree.foreground", "Tree.font");
1292
1293 hashColor = UIManager.getColor("Tree.hash");
1294 if (hashColor == null)
1295 hashColor = Color.black;
1296
1297 tree.setOpaque(true);
1298
1299 rightChildIndent = UIManager.getInt("Tree.rightChildIndent");
1300 leftChildIndent = UIManager.getInt("Tree.leftChildIndent");
1301 totalChildIndent = rightChildIndent + leftChildIndent;
1302 setRowHeight(UIManager.getInt("Tree.rowHeight"));
1303 tree.setRowHeight(getRowHeight());
1304 tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand"));
1305 setExpandedIcon(UIManager.getIcon("Tree.expandedIcon"));
1306 setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon"));
1307 }
1308
1309 /**
1310 * Install all keyboard actions for this
1311 */
1312 protected void installKeyboardActions()
1313 {
1314 InputMap focusInputMap =
1315 (InputMap) SharedUIDefaults.get("Tree.focusInputMap");
1316 SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED,
1317 focusInputMap);
1318 InputMap ancestorInputMap =
1319 (InputMap) SharedUIDefaults.get("Tree.ancestorInputMap");
1320 SwingUtilities.replaceUIInputMap(tree,
1321 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1322 ancestorInputMap);
1323
1324 SwingUtilities.replaceUIActionMap(tree, getActionMap());
1325 }
1326
1327 /**
1328 * Creates and returns the shared action map for JTrees.
1329 *
1330 * @return the shared action map for JTrees
1331 */
1332 private ActionMap getActionMap()
1333 {
1334 ActionMap am = (ActionMap) UIManager.get("Tree.actionMap");
1335 if (am == null)
1336 {
1337 am = createDefaultActions();
1338 UIManager.getLookAndFeelDefaults().put("Tree.actionMap", am);
1339 }
1340 return am;
1341 }
1342
1343 /**
1344 * Creates the default actions when there are none specified by the L&F.
1345 *
1346 * @return the default actions
1347 */
1348 private ActionMap createDefaultActions()
1349 {
1350 ActionMapUIResource am = new ActionMapUIResource();
1351 Action action;
1352
1353 // TreeHomeAction.
1354 action = new TreeHomeAction(-1, "selectFirst");
1355 am.put(action.getValue(Action.NAME), action);
1356 action = new TreeHomeAction(-1, "selectFirstChangeLead");
1357 am.put(action.getValue(Action.NAME), action);
1358 action = new TreeHomeAction(-1, "selectFirstExtendSelection");
1359 am.put(action.getValue(Action.NAME), action);
1360 action = new TreeHomeAction(1, "selectLast");
1361 am.put(action.getValue(Action.NAME), action);
1362 action = new TreeHomeAction(1, "selectLastChangeLead");
1363 am.put(action.getValue(Action.NAME), action);
1364 action = new TreeHomeAction(1, "selectLastExtendSelection");
1365 am.put(action.getValue(Action.NAME), action);
1366
1367 // TreeIncrementAction.
1368 action = new TreeIncrementAction(-1, "selectPrevious");
1369 am.put(action.getValue(Action.NAME), action);
1370 action = new TreeIncrementAction(-1, "selectPreviousExtendSelection");
1371 am.put(action.getValue(Action.NAME), action);
1372 action = new TreeIncrementAction(-1, "selectPreviousChangeLead");
1373 am.put(action.getValue(Action.NAME), action);
1374 action = new TreeIncrementAction(1, "selectNext");
1375 am.put(action.getValue(Action.NAME), action);
1376 action = new TreeIncrementAction(1, "selectNextExtendSelection");
1377 am.put(action.getValue(Action.NAME), action);
1378 action = new TreeIncrementAction(1, "selectNextChangeLead");
1379 am.put(action.getValue(Action.NAME), action);
1380
1381 // TreeTraverseAction.
1382 action = new TreeTraverseAction(-1, "selectParent");
1383 am.put(action.getValue(Action.NAME), action);
1384 action = new TreeTraverseAction(1, "selectChild");
1385 am.put(action.getValue(Action.NAME), action);
1386
1387 // TreeToggleAction.
1388 action = new TreeToggleAction("toggleAndAnchor");
1389 am.put(action.getValue(Action.NAME), action);
1390
1391 // TreePageAction.
1392 action = new TreePageAction(-1, "scrollUpChangeSelection");
1393 am.put(action.getValue(Action.NAME), action);
1394 action = new TreePageAction(-1, "scrollUpExtendSelection");
1395 am.put(action.getValue(Action.NAME), action);
1396 action = new TreePageAction(-1, "scrollUpChangeLead");
1397 am.put(action.getValue(Action.NAME), action);
1398 action = new TreePageAction(1, "scrollDownChangeSelection");
1399 am.put(action.getValue(Action.NAME), action);
1400 action = new TreePageAction(1, "scrollDownExtendSelection");
1401 am.put(action.getValue(Action.NAME), action);
1402 action = new TreePageAction(1, "scrollDownChangeLead");
1403 am.put(action.getValue(Action.NAME), action);
1404
1405 // Tree editing actions
1406 action = new TreeStartEditingAction("startEditing");
1407 am.put(action.getValue(Action.NAME), action);
1408 action = new TreeCancelEditingAction("cancel");
1409 am.put(action.getValue(Action.NAME), action);
1410
1411
1412 return am;
1413 }
1414
1415 /**
1416 * Converts the modifiers.
1417 *
1418 * @param mod - modifier to convert
1419 * @returns the new modifier
1420 */
1421 private int convertModifiers(int mod)
1422 {
1423 if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
1424 {
1425 mod |= KeyEvent.SHIFT_MASK;
1426 mod &= ~ KeyEvent.SHIFT_DOWN_MASK;
1427 }
1428 if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
1429 {
1430 mod |= KeyEvent.CTRL_MASK;
1431 mod &= ~ KeyEvent.CTRL_DOWN_MASK;
1432 }
1433 if ((mod & KeyEvent.META_DOWN_MASK) != 0)
1434 {
1435 mod |= KeyEvent.META_MASK;
1436 mod &= ~ KeyEvent.META_DOWN_MASK;
1437 }
1438 if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
1439 {
1440 mod |= KeyEvent.ALT_MASK;
1441 mod &= ~ KeyEvent.ALT_DOWN_MASK;
1442 }
1443 if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
1444 {
1445 mod |= KeyEvent.ALT_GRAPH_MASK;
1446 mod &= ~ KeyEvent.ALT_GRAPH_DOWN_MASK;
1447 }
1448 return mod;
1449 }
1450
1451 /**
1452 * Install all listeners for this
1453 */
1454 protected void installListeners()
1455 {
1456 propertyChangeListener = createPropertyChangeListener();
1457 tree.addPropertyChangeListener(propertyChangeListener);
1458
1459 focusListener = createFocusListener();
1460 tree.addFocusListener(focusListener);
1461
1462 treeSelectionListener = createTreeSelectionListener();
1463 tree.addTreeSelectionListener(treeSelectionListener);
1464
1465 mouseListener = createMouseListener();
1466 tree.addMouseListener(mouseListener);
1467
1468 keyListener = createKeyListener();
1469 tree.addKeyListener(keyListener);
1470
1471 selectionModelPropertyChangeListener =
1472 createSelectionModelPropertyChangeListener();
1473 if (treeSelectionModel != null
1474 && selectionModelPropertyChangeListener != null)
1475 {
1476 treeSelectionModel.addPropertyChangeListener(
1477 selectionModelPropertyChangeListener);
1478 }
1479
1480 componentListener = createComponentListener();
1481 tree.addComponentListener(componentListener);
1482
1483 treeExpansionListener = createTreeExpansionListener();
1484 tree.addTreeExpansionListener(treeExpansionListener);
1485
1486 treeModelListener = createTreeModelListener();
1487 if (treeModel != null)
1488 treeModel.addTreeModelListener(treeModelListener);
1489
1490 cellEditorListener = createCellEditorListener();
1491 }
1492
1493 /**
1494 * Install the UI for the component
1495 *
1496 * @param c the component to install UI for
1497 */
1498 public void installUI(JComponent c)
1499 {
1500 tree = (JTree) c;
1501
1502 prepareForUIInstall();
1503 installDefaults();
1504 installComponents();
1505 installKeyboardActions();
1506 installListeners();
1507 completeUIInstall();
1508 }
1509
1510 /**
1511 * Uninstall the defaults for the tree
1512 */
1513 protected void uninstallDefaults()
1514 {
1515 tree.setFont(null);
1516 tree.setForeground(null);
1517 tree.setBackground(null);
1518 }
1519
1520 /**
1521 * Uninstall the UI for the component
1522 *
1523 * @param c the component to uninstall UI for
1524 */
1525 public void uninstallUI(JComponent c)
1526 {
1527 completeEditing();
1528
1529 prepareForUIUninstall();
1530 uninstallDefaults();
1531 uninstallKeyboardActions();
1532 uninstallListeners();
1533 uninstallComponents();
1534 completeUIUninstall();
1535 }
1536
1537 /**
1538 * Paints the specified component appropriate for the look and feel. This
1539 * method is invoked from the ComponentUI.update method when the specified
1540 * component is being painted. Subclasses should override this method and use
1541 * the specified Graphics object to render the content of the component.
1542 *
1543 * @param g the Graphics context in which to paint
1544 * @param c the component being painted; this argument is often ignored, but
1545 * might be used if the UI object is stateless and shared by multiple
1546 * components
1547 */
1548 public void paint(Graphics g, JComponent c)
1549 {
1550 JTree tree = (JTree) c;
1551
1552 int rows = treeState.getRowCount();
1553
1554 if (rows == 0)
1555 // There is nothing to do if the tree is empty.
1556 return;
1557
1558 Rectangle clip = g.getClipBounds();
1559
1560 Insets insets = tree.getInsets();
1561
1562 if (clip != null && treeModel != null)
1563 {
1564 int startIndex = tree.getClosestRowForLocation(clip.x, clip.y);
1565 int endIndex = tree.getClosestRowForLocation(clip.x + clip.width,
1566 clip.y + clip.height);
1567 // Also paint dashes to the invisible nodes below.
1568 // These should be painted first, otherwise they may cover
1569 // the control icons.
1570 if (endIndex < rows)
1571 for (int i = endIndex + 1; i < rows; i++)
1572 {
1573 TreePath path = treeState.getPathForRow(i);
1574 if (isLastChild(path))
1575 paintVerticalPartOfLeg(g, clip, insets, path);
1576 }
1577
1578 // The two loops are required to ensure that the lines are not
1579 // painted over the other tree components.
1580
1581 int n = endIndex - startIndex + 1;
1582 Rectangle[] bounds = new Rectangle[n];
1583 boolean[] isLeaf = new boolean[n];
1584 boolean[] isExpanded = new boolean[n];
1585 TreePath[] path = new TreePath[n];
1586 int k;
1587
1588 k = 0;
1589 for (int i = startIndex; i <= endIndex; i++, k++)
1590 {
1591 path[k] = treeState.getPathForRow(i);
1592 if (path[k] != null)
1593 {
1594 isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent());
1595 isExpanded[k] = tree.isExpanded(path[k]);
1596 bounds[k] = getPathBounds(tree, path[k]);
1597
1598 paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k],
1599 i, isExpanded[k], false, isLeaf[k]);
1600 }
1601 if (isLastChild(path[k]))
1602 paintVerticalPartOfLeg(g, clip, insets, path[k]);
1603 }
1604
1605 k = 0;
1606 for (int i = startIndex; i <= endIndex; i++, k++)
1607 {
1608 if (path[k] != null)
1609 paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k],
1610 false, isLeaf[k]);
1611 }
1612 }
1613 }
1614
1615 /**
1616 * Check if the path is referring to the last child of some parent.
1617 */
1618 private boolean isLastChild(TreePath path)
1619 {
1620 if (path == null)
1621 return false;
1622 else if (path instanceof GnuPath)
1623 {
1624 // Except the seldom case when the layout cache is changed, this
1625 // optimized code will be executed.
1626 return ((GnuPath) path).isLastChild;
1627 }
1628 else
1629 {
1630 // Non optimized general case.
1631 TreePath parent = path.getParentPath();
1632 if (parent == null)
1633 return false;
1634 int childCount = treeState.getVisibleChildCount(parent);
1635 int p = treeModel.getIndexOfChild(parent, path.getLastPathComponent());
1636 return p == childCount - 1;
1637 }
1638 }
1639
1640 /**
1641 * Ensures that the rows identified by beginRow through endRow are visible.
1642 *
1643 * @param beginRow is the first row
1644 * @param endRow is the last row
1645 */
1646 protected void ensureRowsAreVisible(int beginRow, int endRow)
1647 {
1648 if (beginRow < endRow)
1649 {
1650 int temp = endRow;
1651 endRow = beginRow;
1652 beginRow = temp;
1653 }
1654
1655 for (int i = beginRow; i < endRow; i++)
1656 {
1657 TreePath path = getPathForRow(tree, i);
1658 if (! tree.isVisible(path))
1659 tree.makeVisible(path);
1660 }
1661 }
1662
1663 /**
1664 * Sets the preferred minimum size.
1665 *
1666 * @param newSize is the new preferred minimum size.
1667 */
1668 public void setPreferredMinSize(Dimension newSize)
1669 {
1670 preferredMinSize = newSize;
1671 }
1672
1673 /**
1674 * Gets the preferred minimum size.
1675 *
1676 * @returns the preferred minimum size.
1677 */
1678 public Dimension getPreferredMinSize()
1679 {
1680 if (preferredMinSize == null)
1681 return getPreferredSize(tree);
1682 else
1683 return preferredMinSize;
1684 }
1685
1686 /**
1687 * Returns the preferred size to properly display the tree, this is a cover
1688 * method for getPreferredSize(c, false).
1689 *
1690 * @param c the component whose preferred size is being queried; this argument
1691 * is often ignored but might be used if the UI object is stateless
1692 * and shared by multiple components
1693 * @return the preferred size
1694 */
1695 public Dimension getPreferredSize(JComponent c)
1696 {
1697 return getPreferredSize(c, false);
1698 }
1699
1700 /**
1701 * Returns the preferred size to represent the tree in c. If checkConsistancy
1702 * is true, checkConsistancy is messaged first.
1703 *
1704 * @param c the component whose preferred size is being queried.
1705 * @param checkConsistancy if true must check consistancy
1706 * @return the preferred size
1707 */
1708 public Dimension getPreferredSize(JComponent c, boolean checkConsistancy)
1709 {
1710 if (! validCachedPreferredSize)
1711 {
1712 Rectangle size = tree.getBounds();
1713 // Add the scrollbar dimensions to the preferred size.
1714 preferredSize = new Dimension(treeState.getPreferredWidth(size),
1715 treeState.getPreferredHeight());
1716 validCachedPreferredSize = true;
1717 }
1718 return preferredSize;
1719 }
1720
1721 /**
1722 * Returns the minimum size for this component. Which will be the min
1723 * preferred size or (0,0).
1724 *
1725 * @param c the component whose min size is being queried.
1726 * @returns the preferred size or null
1727 */
1728 public Dimension getMinimumSize(JComponent c)
1729 {
1730 return preferredMinSize = getPreferredSize(c);
1731 }
1732
1733 /**
1734 * Returns the maximum size for the component, which will be the preferred
1735 * size if the instance is currently in JTree or (0,0).
1736 *
1737 * @param c the component whose preferred size is being queried
1738 * @return the max size or null
1739 */
1740 public Dimension getMaximumSize(JComponent c)
1741 {
1742 return getPreferredSize(c);
1743 }
1744
1745 /**
1746 * Messages to stop the editing session. If the UI the receiver is providing
1747 * the look and feel for returns true from
1748 * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked
1749 * on the current editor. Then completeEditing will be messaged with false,
1750 * true, false to cancel any lingering editing.
1751 */
1752 protected void completeEditing()
1753 {
1754 if (tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing
1755 && editingComponent != null)
1756 cellEditor.stopCellEditing();
1757
1758 completeEditing(false, true, false);
1759 }
1760
1761 /**
1762 * Stops the editing session. If messageStop is true, the editor is messaged
1763 * with stopEditing, if messageCancel is true the editor is messaged with
1764 * cancelEditing. If messageTree is true, the treeModel is messaged with
1765 * valueForPathChanged.
1766 *
1767 * @param messageStop message to stop editing
1768 * @param messageCancel message to cancel editing
1769 * @param messageTree message to treeModel
1770 */
1771 protected void completeEditing(boolean messageStop, boolean messageCancel,
1772 boolean messageTree)
1773 {
1774 // Make no attempt to complete the non existing editing session.
1775 if (stopEditingInCompleteEditing && editingComponent != null)
1776 {
1777 Component comp = editingComponent;
1778 TreePath p = editingPath;
1779 editingComponent = null;
1780 editingPath = null;
1781 if (messageStop)
1782 cellEditor.stopCellEditing();
1783 else if (messageCancel)
1784 cellEditor.cancelCellEditing();
1785
1786 tree.remove(comp);
1787
1788 if (editorHasDifferentSize)
1789 {
1790 treeState.invalidatePathBounds(p);
1791 updateSize();
1792 }
1793 else
1794 {
1795 // Need to refresh the tree.
1796 Rectangle b = getPathBounds(tree, p);
1797 tree.repaint(0, b.y, tree.getWidth(), b.height);
1798 }
1799
1800 if (messageTree)
1801 {
1802 Object value = cellEditor.getCellEditorValue();
1803 treeModel.valueForPathChanged(p, value);
1804 }
1805 }
1806 }
1807
1808 /**
1809 * Will start editing for node if there is a cellEditor and shouldSelectCall
1810 * returns true. This assumes that path is valid and visible.
1811 *
1812 * @param path is the path to start editing
1813 * @param event is the MouseEvent performed on the path
1814 * @return true if successful
1815 */
1816 protected boolean startEditing(TreePath path, MouseEvent event)
1817 {
1818 // Maybe cancel editing.
1819 if (isEditing(tree) && tree.getInvokesStopCellEditing()
1820 && ! stopEditing(tree))
1821 return false;
1822
1823 completeEditing();
1824 TreeCellEditor ed = cellEditor;
1825 if (ed != null && tree.isPathEditable(path))
1826 {
1827 if (ed.isCellEditable(event))
1828 {
1829 editingRow = getRowForPath(tree, path);
1830 Object value = path.getLastPathComponent();
1831 boolean isSelected = tree.isPathSelected(path);
1832 boolean isExpanded = tree.isExpanded(editingPath);
1833 boolean isLeaf = treeModel.isLeaf(value);
1834 editingComponent = ed.getTreeCellEditorComponent(tree, value,
1835 isSelected,
1836 isExpanded,
1837 isLeaf,
1838 editingRow);
1839
1840 Rectangle bounds = getPathBounds(tree, path);
1841
1842 Dimension size = editingComponent.getPreferredSize();
1843 int rowHeight = getRowHeight();
1844 if (size.height != bounds.height && rowHeight > 0)
1845 size.height = rowHeight;
1846
1847 if (size.width != bounds.width || size.height != bounds.height)
1848 {
1849 editorHasDifferentSize = true;
1850 treeState.invalidatePathBounds(path);
1851 updateSize();
1852 }
1853 else
1854 editorHasDifferentSize = false;
1855
1856 // The editing component must be added to its container. We add the
1857 // container, not the editing component itself.
1858 tree.add(editingComponent);
1859 editingComponent.setBounds(bounds.x, bounds.y, size.width,
1860 size.height);
1861 editingComponent.validate();
1862 editingPath = path;
1863
1864 if (ed.shouldSelectCell(event))
1865 {
1866 stopEditingInCompleteEditing = false;
1867 tree.setSelectionRow(editingRow);
1868 stopEditingInCompleteEditing = true;
1869 }
1870
1871 editorRequestFocus(editingComponent);
1872 // Register MouseInputHandler to redispatch initial mouse events
1873 // correctly.
1874 if (event instanceof MouseEvent)
1875 {
1876 Point p = SwingUtilities.convertPoint(tree, event.getX(), event.getY(),
1877 editingComponent);
1878 Component active =
1879 SwingUtilities.getDeepestComponentAt(editingComponent, p.x, p.y);
1880 if (active != null)
1881 {
1882 MouseInputHandler ih = new MouseInputHandler(tree, active, event);
1883
1884 }
1885 }
1886
1887 return true;
1888 }
1889 else
1890 editingComponent = null;
1891 }
1892 return false;
1893 }
1894
1895 /**
1896 * Requests focus on the editor. The method is necessary since the
1897 * DefaultTreeCellEditor returns a container that contains the
1898 * actual editor, and we want to request focus on the editor, not the
1899 * container.
1900 */
1901 private void editorRequestFocus(Component c)
1902 {
1903 if (c instanceof Container)
1904 {
1905 // TODO: Maybe do something more reasonable here, like queriying the
1906 // FocusTraversalPolicy.
1907 Container cont = (Container) c;
1908 if (cont.getComponentCount() > 0)
1909 cont.getComponent(0).requestFocus();
1910 }
1911 else if (c.isFocusable())
1912 c.requestFocus();
1913
1914 }
1915
1916 /**
1917 * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or
1918 * collapse region of the row, this will toggle the row.
1919 *
1920 * @param path the path we are concerned with
1921 * @param mouseX is the cursor's x position
1922 * @param mouseY is the cursor's y position
1923 */
1924 protected void checkForClickInExpandControl(TreePath path, int mouseX,
1925 int mouseY)
1926 {
1927 if (isLocationInExpandControl(path, mouseX, mouseY))
1928 handleExpandControlClick(path, mouseX, mouseY);
1929 }
1930
1931 /**
1932 * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in
1933 * the area of row that is used to expand/collpse the node and the node at row
1934 * does not represent a leaf.
1935 *
1936 * @param path the path we are concerned with
1937 * @param mouseX is the cursor's x position
1938 * @param mouseY is the cursor's y position
1939 * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in
1940 * the area of row that is used to expand/collpse the node and the
1941 * node at row does not represent a leaf.
1942 */
1943 protected boolean isLocationInExpandControl(TreePath path, int mouseX,
1944 int mouseY)
1945 {
1946 boolean cntlClick = false;
1947 if (! treeModel.isLeaf(path.getLastPathComponent()))
1948 {
1949 int width;
1950 Icon expandedIcon = getExpandedIcon();
1951 if (expandedIcon != null)
1952 width = expandedIcon.getIconWidth();
1953 else
1954 // Only guessing. This is the width of
1955 // the tree control icon in Metal L&F.
1956 width = 18;
1957
1958 Insets i = tree.getInsets();
1959
1960 int depth;
1961 if (isRootVisible())
1962 depth = path.getPathCount()-1;
1963 else
1964 depth = path.getPathCount()-2;
1965
1966 int left = getRowX(tree.getRowForPath(path), depth)
1967 - width + i.left;
1968 cntlClick = mouseX >= left && mouseX <= left + width;
1969 }
1970 return cntlClick;
1971 }
1972
1973 /**
1974 * Messaged when the user clicks the particular row, this invokes
1975 * toggleExpandState.
1976 *
1977 * @param path the path we are concerned with
1978 * @param mouseX is the cursor's x position
1979 * @param mouseY is the cursor's y position
1980 */
1981 protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY)
1982 {
1983 toggleExpandState(path);
1984 }
1985
1986 /**
1987 * Expands path if it is not expanded, or collapses row if it is expanded. If
1988 * expanding a path and JTree scroll on expand, ensureRowsAreVisible is
1989 * invoked to scroll as many of the children to visible as possible (tries to
1990 * scroll to last visible descendant of path).
1991 *
1992 * @param path the path we are concerned with
1993 */
1994 protected void toggleExpandState(TreePath path)
1995 {
1996 // tree.isExpanded(path) would do the same, but treeState knows faster.
1997 if (treeState.isExpanded(path))
1998 tree.collapsePath(path);
1999 else
2000 tree.expandPath(path);
2001 }
2002
2003 /**
2004 * Returning true signifies a mouse event on the node should toggle the
2005 * selection of only the row under the mouse. The BasisTreeUI treats the
2006 * event as "toggle selection event" if the CTRL button was pressed while
2007 * clicking. The event is not counted as toggle event if the associated
2008 * tree does not support the multiple selection.
2009 *
2010 * @param event is the MouseEvent performed on the row.
2011 * @return true signifies a mouse event on the node should toggle the
2012 * selection of only the row under the mouse.
2013 */
2014 protected boolean isToggleSelectionEvent(MouseEvent event)
2015 {
2016 return
2017 (tree.getSelectionModel().getSelectionMode() !=
2018 TreeSelectionModel.SINGLE_TREE_SELECTION) &&
2019 ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0);
2020 }
2021
2022 /**
2023 * Returning true signifies a mouse event on the node should select from the
2024 * anchor point. The BasisTreeUI treats the event as "multiple selection
2025 * event" if the SHIFT button was pressed while clicking. The event is not
2026 * counted as multiple selection event if the associated tree does not support
2027 * the multiple selection.
2028 *
2029 * @param event is the MouseEvent performed on the node.
2030 * @return true signifies a mouse event on the node should select from the
2031 * anchor point.
2032 */
2033 protected boolean isMultiSelectEvent(MouseEvent event)
2034 {
2035 return
2036 (tree.getSelectionModel().getSelectionMode() !=
2037 TreeSelectionModel.SINGLE_TREE_SELECTION) &&
2038 ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0);
2039 }
2040
2041 /**
2042 * Returning true indicates the row under the mouse should be toggled based on
2043 * the event. This is invoked after checkForClickInExpandControl, implying the
2044 * location is not in the expand (toggle) control.
2045 *
2046 * @param event is the MouseEvent performed on the row.
2047 * @return true indicates the row under the mouse should be toggled based on
2048 * the event.
2049 */
2050 protected boolean isToggleEvent(MouseEvent event)
2051 {
2052 boolean toggle = false;
2053 if (SwingUtilities.isLeftMouseButton(event))
2054 {
2055 int clickCount = tree.getToggleClickCount();
2056 if (clickCount > 0 && event.getClickCount() == clickCount)
2057 toggle = true;
2058 }
2059 return toggle;
2060 }
2061
2062 /**
2063 * Messaged to update the selection based on a MouseEvent over a particular
2064 * row. If the even is a toggle selection event, the row is either selected,
2065 * or deselected. If the event identifies a multi selection event, the
2066 * selection is updated from the anchor point. Otherwise, the row is selected,
2067 * and the previous selection is cleared.</p>
2068 *
2069 * @param path is the path selected for an event
2070 * @param event is the MouseEvent performed on the path.
2071 *
2072 * @see #isToggleSelectionEvent(MouseEvent)
2073 * @see #isMultiSelectEvent(MouseEvent)
2074 */
2075 protected void selectPathForEvent(TreePath path, MouseEvent event)
2076 {
2077 if (isToggleSelectionEvent(event))
2078 {
2079 // The event selects or unselects the clicked row.
2080 if (tree.isPathSelected(path))
2081 tree.removeSelectionPath(path);
2082 else
2083 {
2084 tree.addSelectionPath(path);
2085 tree.setAnchorSelectionPath(path);
2086 }
2087 }
2088 else if (isMultiSelectEvent(event))
2089 {
2090 // The event extends selection form anchor till the clicked row.
2091 TreePath anchor = tree.getAnchorSelectionPath();
2092 if (anchor != null)
2093 {
2094 int aRow = getRowForPath(tree, anchor);
2095 tree.addSelectionInterval(aRow, getRowForPath(tree, path));
2096 }
2097 else
2098 tree.addSelectionPath(path);
2099 }
2100 else
2101 {
2102 // This is an ordinary event that just selects the clicked row.
2103 tree.setSelectionPath(path);
2104 if (isToggleEvent(event))
2105 toggleExpandState(path);
2106 }
2107 }
2108
2109 /**
2110 * Returns true if the node at <code>row</code> is a leaf.
2111 *
2112 * @param row is the row we are concerned with.
2113 * @return true if the node at <code>row</code> is a leaf.
2114 */
2115 protected boolean isLeaf(int row)
2116 {
2117 TreePath pathForRow = getPathForRow(tree, row);
2118 if (pathForRow == null)
2119 return true;
2120
2121 Object node = pathForRow.getLastPathComponent();
2122 return treeModel.isLeaf(node);
2123 }
2124
2125 /**
2126 * The action to start editing at the current lead selection path.
2127 */
2128 class TreeStartEditingAction
2129 extends AbstractAction
2130 {
2131 /**
2132 * Creates the new tree cancel editing action.
2133 *
2134 * @param name the name of the action (used in toString).
2135 */
2136 public TreeStartEditingAction(String name)
2137 {
2138 super(name);
2139 }
2140
2141 /**
2142 * Start editing at the current lead selection path.
2143 *
2144 * @param e the ActionEvent that caused this action.
2145 */
2146 public void actionPerformed(ActionEvent e)
2147 {
2148 TreePath lead = tree.getLeadSelectionPath();
2149 if (!tree.isEditing())
2150 tree.startEditingAtPath(lead);
2151 }
2152 }
2153
2154 /**
2155 * Updates the preferred size when scrolling, if necessary.
2156 */
2157 public class ComponentHandler
2158 extends ComponentAdapter
2159 implements ActionListener
2160 {
2161 /**
2162 * Timer used when inside a scrollpane and the scrollbar is adjusting
2163 */
2164 protected Timer timer;
2165
2166 /** ScrollBar that is being adjusted */
2167 protected JScrollBar scrollBar;
2168
2169 /**
2170 * Constructor
2171 */
2172 public ComponentHandler()
2173 {
2174 // Nothing to do here.
2175 }
2176
2177 /**
2178 * Invoked when the component's position changes.
2179 *
2180 * @param e the event that occurs when moving the component
2181 */
2182 public void componentMoved(ComponentEvent e)
2183 {
2184 if (timer == null)
2185 {
2186 JScrollPane scrollPane = getScrollPane();
2187 if (scrollPane == null)
2188 updateSize();
2189 else
2190 {
2191 // Determine the scrollbar that is adjusting, if any, and
2192 // start the timer for that. If no scrollbar is adjusting,
2193 // we simply call updateSize().
2194 scrollBar = scrollPane.getVerticalScrollBar();
2195 if (scrollBar == null || !scrollBar.getValueIsAdjusting())
2196 {
2197 // It's not the vertical scrollbar, try the horizontal one.
2198 scrollBar = scrollPane.getHorizontalScrollBar();
2199 if (scrollBar != null && scrollBar.getValueIsAdjusting())
2200 startTimer();
2201 else
2202 updateSize();
2203 }
2204 else
2205 {
2206 startTimer();
2207 }
2208 }
2209 }
2210 }
2211
2212 /**
2213 * Creates, if necessary, and starts a Timer to check if needed to resize
2214 * the bounds
2215 */
2216 protected void startTimer()
2217 {
2218 if (timer == null)
2219 {
2220 timer = new Timer(200, this);
2221 timer.setRepeats(true);
2222 }
2223 timer.start();
2224 }
2225
2226 /**
2227 * Returns the JScrollPane housing the JTree, or null if one isn't found.
2228 *
2229 * @return JScrollPane housing the JTree, or null if one isn't found.
2230 */
2231 protected JScrollPane getScrollPane()
2232 {
2233 JScrollPane found = null;
2234 Component p = tree.getParent();
2235 while (p != null && !(p instanceof JScrollPane))
2236 p = p.getParent();
2237 if (p instanceof JScrollPane)
2238 found = (JScrollPane) p;
2239 return found;
2240 }
2241
2242 /**
2243 * Public as a result of Timer. If the scrollBar is null, or not adjusting,
2244 * this stops the timer and updates the sizing.
2245 *
2246 * @param ae is the action performed
2247 */
2248 public void actionPerformed(ActionEvent ae)
2249 {
2250 if (scrollBar == null || !scrollBar.getValueIsAdjusting())
2251 {
2252 if (timer != null)
2253 timer.stop();
2254 updateSize();
2255 timer = null;
2256 scrollBar = null;
2257 }
2258 }
2259 }
2260
2261 /**
2262 * Listener responsible for getting cell editing events and updating the tree
2263 * accordingly.
2264 */
2265 public class CellEditorHandler
2266 implements CellEditorListener
2267 {
2268 /**
2269 * Constructor
2270 */
2271 public CellEditorHandler()
2272 {
2273 // Nothing to do here.
2274 }
2275
2276 /**
2277 * Messaged when editing has stopped in the tree. Tells the listeners
2278 * editing has stopped.
2279 *
2280 * @param e is the notification event
2281 */
2282 public void editingStopped(ChangeEvent e)
2283 {
2284 completeEditing(false, false, true);
2285 }
2286
2287 /**
2288 * Messaged when editing has been canceled in the tree. This tells the
2289 * listeners the editor has canceled editing.
2290 *
2291 * @param e is the notification event
2292 */
2293 public void editingCanceled(ChangeEvent e)
2294 {
2295 completeEditing(false, false, false);
2296 }
2297 } // CellEditorHandler
2298
2299 /**
2300 * Repaints the lead selection row when focus is lost/grained.
2301 */
2302 public class FocusHandler
2303 implements FocusListener
2304 {
2305 /**
2306 * Constructor
2307 */
2308 public FocusHandler()
2309 {
2310 // Nothing to do here.
2311 }
2312
2313 /**
2314 * Invoked when focus is activated on the tree we're in, redraws the lead
2315 * row. Invoked when a component gains the keyboard focus. The method
2316 * repaints the lead row that is shown differently when the tree is in
2317 * focus.
2318 *
2319 * @param e is the focus event that is activated
2320 */
2321 public void focusGained(FocusEvent e)
2322 {
2323 repaintLeadRow();
2324 }
2325
2326 /**
2327 * Invoked when focus is deactivated on the tree we're in, redraws the lead
2328 * row. Invoked when a component loses the keyboard focus. The method
2329 * repaints the lead row that is shown differently when the tree is in
2330 * focus.
2331 *
2332 * @param e is the focus event that is deactivated
2333 */
2334 public void focusLost(FocusEvent e)
2335 {
2336 repaintLeadRow();
2337 }
2338
2339 /**
2340 * Repaint the lead row.
2341 */
2342 void repaintLeadRow()
2343 {
2344 TreePath lead = tree.getLeadSelectionPath();
2345 if (lead != null)
2346 tree.repaint(tree.getPathBounds(lead));
2347 }
2348 }
2349
2350 /**
2351 * This is used to get multiple key down events to appropriately genereate
2352 * events.
2353 */
2354 public class KeyHandler
2355 extends KeyAdapter
2356 {
2357 /** Key code that is being generated for. */
2358 protected Action repeatKeyAction;
2359
2360 /** Set to true while keyPressed is active */
2361 protected boolean isKeyDown;
2362
2363 /**
2364 * Constructor
2365 */
2366 public KeyHandler()
2367 {
2368 // Nothing to do here.
2369 }
2370
2371 /**
2372 * Invoked when a key has been typed. Moves the keyboard focus to the first
2373 * element whose first letter matches the alphanumeric key pressed by the
2374 * user. Subsequent same key presses move the keyboard focus to the next
2375 * object that starts with the same letter.
2376 *
2377 * @param e the key typed
2378 */
2379 public void keyTyped(KeyEvent e)
2380 {
2381 char typed = Character.toLowerCase(e.getKeyChar());
2382 for (int row = tree.getLeadSelectionRow() + 1;
2383 row < tree.getRowCount(); row++)
2384 {
2385 if (checkMatch(row, typed))
2386 {
2387 tree.setSelectionRow(row);
2388 tree.scrollRowToVisible(row);
2389 return;
2390 }
2391 }
2392
2393 // Not found below, search above:
2394 for (int row = 0; row < tree.getLeadSelectionRow(); row++)
2395 {
2396 if (checkMatch(row, typed))
2397 {
2398 tree.setSelectionRow(row);
2399 tree.scrollRowToVisible(row);
2400 return;
2401 }
2402 }
2403 }
2404
2405 /**
2406 * Check if the given tree row starts with this character
2407 *
2408 * @param row the tree row
2409 * @param typed the typed char, must be converted to lowercase
2410 * @return true if the given tree row starts with this character
2411 */
2412 boolean checkMatch(int row, char typed)
2413 {
2414 TreePath path = treeState.getPathForRow(row);
2415 String node = path.getLastPathComponent().toString();
2416 if (node.length() > 0)
2417 {
2418 char x = node.charAt(0);
2419 if (typed == Character.toLowerCase(x))
2420 return true;
2421 }
2422 return false;
2423 }
2424
2425 /**
2426 * Invoked when a key has been pressed.
2427 *
2428 * @param e the key pressed
2429 */
2430 public void keyPressed(KeyEvent e)
2431 {
2432 // Nothing to do here.
2433 }
2434
2435 /**
2436 * Invoked when a key has been released
2437 *
2438 * @param e the key released
2439 */
2440 public void keyReleased(KeyEvent e)
2441 {
2442 // Nothing to do here.
2443 }
2444 }
2445
2446 /**
2447 * MouseListener is responsible for updating the selection based on mouse
2448 * events.
2449 */
2450 public class MouseHandler
2451 extends MouseAdapter
2452 implements MouseMotionListener
2453 {
2454
2455 /**
2456 * If the cell has been selected on mouse press.
2457 */
2458 private boolean selectedOnPress;
2459
2460 /**
2461 * Constructor
2462 */
2463 public MouseHandler()
2464 {
2465 // Nothing to do here.
2466 }
2467
2468 /**
2469 * Invoked when a mouse button has been pressed on a component.
2470 *
2471 * @param e is the mouse event that occured
2472 */
2473 public void mousePressed(MouseEvent e)
2474 {
2475 if (! e.isConsumed())
2476 {
2477 handleEvent(e);
2478 selectedOnPress = true;
2479 }
2480 else
2481 {
2482 selectedOnPress = false;
2483 }
2484 }
2485
2486 /**
2487 * Invoked when a mouse button is pressed on a component and then dragged.
2488 * MOUSE_DRAGGED events will continue to be delivered to the component where
2489 * the drag originated until the mouse button is released (regardless of
2490 * whether the mouse position is within the bounds of the component).
2491 *
2492 * @param e is the mouse event that occured
2493 */
2494 public void mouseDragged(MouseEvent e)
2495 {
2496 // Nothing to do here.
2497 }
2498
2499 /**
2500 * Invoked when the mouse button has been moved on a component (with no
2501 * buttons no down).
2502 *
2503 * @param e the mouse event that occured
2504 */
2505 public void mouseMoved(MouseEvent e)
2506 {
2507 // Nothing to do here.
2508 }
2509
2510 /**
2511 * Invoked when a mouse button has been released on a component.
2512 *
2513 * @param e is the mouse event that occured
2514 */
2515 public void mouseReleased(MouseEvent e)
2516 {
2517 if (! e.isConsumed() && ! selectedOnPress)
2518 handleEvent(e);
2519 }
2520
2521 /**
2522 * Handles press and release events.
2523 *
2524 * @param e the mouse event
2525 */
2526 private void handleEvent(MouseEvent e)
2527 {
2528 if (tree != null && tree.isEnabled())
2529 {
2530 // Maybe stop editing.
2531 if (isEditing(tree) && tree.getInvokesStopCellEditing()
2532 && ! stopEditing(tree))
2533 return;
2534
2535 // Explicitly request focus.
2536 tree.requestFocusInWindow();
2537
2538 int x = e.getX();
2539 int y = e.getY();
2540 TreePath path = getClosestPathForLocation(tree, x, y);
2541 if (path != null)
2542 {
2543 Rectangle b = getPathBounds(tree, path);
2544 if (y <= b.y + b.height)
2545 {
2546 if (SwingUtilities.isLeftMouseButton(e))
2547 checkForClickInExpandControl(path, x, y);
2548 if (x > b.x && x <= b.x + b.width)
2549 {
2550 if (! startEditing(path, e))
2551 selectPathForEvent(path, e);
2552 }
2553 }
2554 }
2555 }
2556 }
2557 }
2558
2559 /**
2560 * MouseInputHandler handles passing all mouse events, including mouse motion
2561 * events, until the mouse is released to the destination it is constructed
2562 * with.
2563 */
2564 public class MouseInputHandler
2565 implements MouseInputListener
2566 {
2567 /** Source that events are coming from */
2568 protected Component source;
2569
2570 /** Destination that receives all events. */
2571 protected Component destination;
2572
2573 /**
2574 * Constructor
2575 *
2576 * @param source that events are coming from
2577 * @param destination that receives all events
2578 * @param e is the event received
2579 */
2580 public MouseInputHandler(Component source, Component destination,
2581 MouseEvent e)
2582 {
2583 this.source = source;
2584 this.destination = destination;
2585 source.addMouseListener(this);
2586 source.addMouseMotionListener(this);
2587 dispatch(e);
2588 }
2589
2590 /**
2591 * Invoked when the mouse button has been clicked (pressed and released) on
2592 * a component.
2593 *
2594 * @param e mouse event that occured
2595 */
2596 public void mouseClicked(MouseEvent e)
2597 {
2598 dispatch(e);
2599 }
2600
2601 /**
2602 * Invoked when a mouse button has been pressed on a component.
2603 *
2604 * @param e mouse event that occured
2605 */
2606 public void mousePressed(MouseEvent e)
2607 {
2608 // Nothing to do here.
2609 }
2610
2611 /**
2612 * Invoked when a mouse button has been released on a component.
2613 *
2614 * @param e mouse event that occured
2615 */
2616 public void mouseReleased(MouseEvent e)
2617 {
2618 dispatch(e);
2619 removeFromSource();
2620 }
2621
2622 /**
2623 * Invoked when the mouse enters a component.
2624 *
2625 * @param e mouse event that occured
2626 */
2627 public void mouseEntered(MouseEvent e)
2628 {
2629 if (! SwingUtilities.isLeftMouseButton(e))
2630 removeFromSource();
2631 }
2632
2633 /**
2634 * Invoked when the mouse exits a component.
2635 *
2636 * @param e mouse event that occured
2637 */
2638 public void mouseExited(MouseEvent e)
2639 {
2640 if (! SwingUtilities.isLeftMouseButton(e))
2641 removeFromSource();
2642 }
2643
2644 /**
2645 * Invoked when a mouse button is pressed on a component and then dragged.
2646 * MOUSE_DRAGGED events will continue to be delivered to the component where
2647 * the drag originated until the mouse button is released (regardless of
2648 * whether the mouse position is within the bounds of the component).
2649 *
2650 * @param e mouse event that occured
2651 */
2652 public void mouseDragged(MouseEvent e)
2653 {
2654 dispatch(e);
2655 }
2656
2657 /**
2658 * Invoked when the mouse cursor has been moved onto a component but no
2659 * buttons have been pushed.
2660 *
2661 * @param e mouse event that occured
2662 */
2663 public void mouseMoved(MouseEvent e)
2664 {
2665 removeFromSource();
2666 }
2667
2668 /**
2669 * Removes event from the source
2670 */
2671 protected void removeFromSource()
2672 {
2673 if (source != null)
2674 {
2675 source.removeMouseListener(this);
2676 source.removeMouseMotionListener(this);
2677 }
2678 source = null;
2679 destination = null;
2680 }
2681
2682 /**
2683 * Redispatches mouse events to the destination.
2684 *
2685 * @param e the mouse event to redispatch
2686 */
2687 private void dispatch(MouseEvent e)
2688 {
2689 if (destination != null)
2690 {
2691 MouseEvent e2 = SwingUtilities.convertMouseEvent(source, e,
2692 destination);
2693 destination.dispatchEvent(e2);
2694 }
2695 }
2696 }
2697
2698 /**
2699 * Class responsible for getting size of node, method is forwarded to
2700 * BasicTreeUI method. X location does not include insets, that is handled in
2701 * getPathBounds.
2702 */
2703 public class NodeDimensionsHandler
2704 extends AbstractLayoutCache.NodeDimensions
2705 {
2706 /**
2707 * Constructor
2708 */
2709 public NodeDimensionsHandler()
2710 {
2711 // Nothing to do here.
2712 }
2713
2714 /**
2715 * Returns, by reference in bounds, the size and x origin to place value at.
2716 * The calling method is responsible for determining the Y location. If
2717 * bounds is null, a newly created Rectangle should be returned, otherwise
2718 * the value should be placed in bounds and returned.
2719 *
2720 * @param cell the value to be represented
2721 * @param row row being queried
2722 * @param depth the depth of the row
2723 * @param expanded true if row is expanded
2724 * @param size a Rectangle containing the size needed to represent value
2725 * @return containing the node dimensions, or null if node has no dimension
2726 */
2727 public Rectangle getNodeDimensions(Object cell, int row, int depth,
2728 boolean expanded, Rectangle size)
2729 {
2730 Dimension prefSize;
2731 if (editingComponent != null && editingRow == row)
2732 {
2733 // Editing, ask editor for preferred size.
2734 prefSize = editingComponent.getPreferredSize();
2735 int rowHeight = getRowHeight();
2736 if (rowHeight > 0 && rowHeight != prefSize.height)
2737 prefSize.height = rowHeight;
2738 }
2739 else
2740 {
2741 // Not editing, ask renderer for preferred size.
2742 Component rend =
2743 currentCellRenderer.getTreeCellRendererComponent(tree, cell,
2744 tree.isRowSelected(row),
2745 expanded,
2746 treeModel.isLeaf(cell),
2747 row, false);
2748 // Make sure the layout is valid.
2749 rendererPane.add(rend);
2750 rend.validate();
2751 prefSize = rend.getPreferredSize();
2752 }
2753 if (size != null)
2754 {
2755 size.x = getRowX(row, depth);
2756 // FIXME: This should be handled by the layout cache.
2757 size.y = prefSize.height * row;
2758 size.width = prefSize.width;
2759 size.height = prefSize.height;
2760 }
2761 else
2762 // FIXME: The y should be handled by the layout cache.
2763 size = new Rectangle(getRowX(row, depth), prefSize.height * row, prefSize.width,
2764 prefSize.height);
2765
2766 return size;
2767 }
2768
2769 /**
2770 * Returns the amount to indent the given row
2771 *
2772 * @return amount to indent the given row.
2773 */
2774 protected int getRowX(int row, int depth)
2775 {
2776 return BasicTreeUI.this.getRowX(row, depth);
2777 }
2778 } // NodeDimensionsHandler
2779
2780 /**
2781 * PropertyChangeListener for the tree. Updates the appropriate variable, or
2782 * TreeState, based on what changes.
2783 */
2784 public class PropertyChangeHandler
2785 implements PropertyChangeListener
2786 {
2787
2788 /**
2789 * Constructor
2790 */
2791 public PropertyChangeHandler()
2792 {
2793 // Nothing to do here.
2794 }
2795
2796 /**
2797 * This method gets called when a bound property is changed.
2798 *
2799 * @param event A PropertyChangeEvent object describing the event source and
2800 * the property that has changed.
2801 */
2802 public void propertyChange(PropertyChangeEvent event)
2803 {
2804 String property = event.getPropertyName();
2805 if (property.equals(JTree.ROOT_VISIBLE_PROPERTY))
2806 {
2807 validCachedPreferredSize = false;
2808 treeState.setRootVisible(tree.isRootVisible());
2809 tree.repaint();
2810 }
2811 else if (property.equals(JTree.SELECTION_MODEL_PROPERTY))
2812 {
2813 treeSelectionModel = tree.getSelectionModel();
2814 treeSelectionModel.setRowMapper(treeState);
2815 }
2816 else if (property.equals(JTree.TREE_MODEL_PROPERTY))
2817 {
2818 setModel(tree.getModel());
2819 }
2820 else if (property.equals(JTree.CELL_RENDERER_PROPERTY))
2821 {
2822 setCellRenderer(tree.getCellRenderer());
2823 // Update layout.
2824 if (treeState != null)
2825 treeState.invalidateSizes();
2826 }
2827 else if (property.equals(JTree.EDITABLE_PROPERTY))
2828 setEditable(((Boolean) event.getNewValue()).booleanValue());
2829
2830 }
2831 }
2832
2833 /**
2834 * Listener on the TreeSelectionModel, resets the row selection if any of the
2835 * properties of the model change.
2836 */
2837 public class SelectionModelPropertyChangeHandler
2838 implements PropertyChangeListener
2839 {
2840
2841 /**
2842 * Constructor
2843 */
2844 public SelectionModelPropertyChangeHandler()
2845 {
2846 // Nothing to do here.
2847 }
2848
2849 /**
2850 * This method gets called when a bound property is changed.
2851 *
2852 * @param event A PropertyChangeEvent object describing the event source and
2853 * the property that has changed.
2854 */
2855 public void propertyChange(PropertyChangeEvent event)
2856 {
2857 treeSelectionModel.resetRowSelection();
2858 }
2859 }
2860
2861 /**
2862 * The action to cancel editing on this tree.
2863 */
2864 public class TreeCancelEditingAction
2865 extends AbstractAction
2866 {
2867 /**
2868 * Creates the new tree cancel editing action.
2869 *
2870 * @param name the name of the action (used in toString).
2871 */
2872 public TreeCancelEditingAction(String name)
2873 {
2874 super(name);
2875 }
2876
2877 /**
2878 * Invoked when an action occurs, cancels the cell editing (if the
2879 * tree cell is being edited).
2880 *
2881 * @param e event that occured
2882 */
2883 public void actionPerformed(ActionEvent e)
2884 {
2885 if (isEnabled() && tree.isEditing())
2886 tree.cancelEditing();
2887 }
2888 }
2889
2890 /**
2891 * Updates the TreeState in response to nodes expanding/collapsing.
2892 */
2893 public class TreeExpansionHandler
2894 implements TreeExpansionListener
2895 {
2896
2897 /**
2898 * Constructor
2899 */
2900 public TreeExpansionHandler()
2901 {
2902 // Nothing to do here.
2903 }
2904
2905 /**
2906 * Called whenever an item in the tree has been expanded.
2907 *
2908 * @param event is the event that occured
2909 */
2910 public void treeExpanded(TreeExpansionEvent event)
2911 {
2912 validCachedPreferredSize = false;
2913 treeState.setExpandedState(event.getPath(), true);
2914 // The maximal cell height may change
2915 maxHeight = 0;
2916 tree.revalidate();
2917 tree.repaint();
2918 }
2919
2920 /**
2921 * Called whenever an item in the tree has been collapsed.
2922 *
2923 * @param event is the event that occured
2924 */
2925 public void treeCollapsed(TreeExpansionEvent event)
2926 {
2927 completeEditing();
2928 validCachedPreferredSize = false;
2929 treeState.setExpandedState(event.getPath(), false);
2930 // The maximal cell height may change
2931 maxHeight = 0;
2932 tree.revalidate();
2933 tree.repaint();
2934 }
2935 } // TreeExpansionHandler
2936
2937 /**
2938 * TreeHomeAction is used to handle end/home actions. Scrolls either the first
2939 * or last cell to be visible based on direction.
2940 */
2941 public class TreeHomeAction
2942 extends AbstractAction
2943 {
2944
2945 /** The direction, either home or end */
2946 protected int direction;
2947
2948 /**
2949 * Creates a new TreeHomeAction instance.
2950 *
2951 * @param dir the direction to go to, <code>-1</code> for home,
2952 * <code>1</code> for end
2953 * @param name the name of the action
2954 */
2955 public TreeHomeAction(int dir, String name)
2956 {
2957 direction = dir;
2958 putValue(Action.NAME, name);
2959 }
2960
2961 /**
2962 * Invoked when an action occurs.
2963 *
2964 * @param e is the event that occured
2965 */
2966 public void actionPerformed(ActionEvent e)
2967 {
2968 if (tree != null)
2969 {
2970 String command = (String) getValue(Action.NAME);
2971 if (command.equals("selectFirst"))
2972 {
2973 ensureRowsAreVisible(0, 0);
2974 tree.setSelectionInterval(0, 0);
2975 }
2976 if (command.equals("selectFirstChangeLead"))
2977 {
2978 ensureRowsAreVisible(0, 0);
2979 tree.setLeadSelectionPath(getPathForRow(tree, 0));
2980 }
2981 if (command.equals("selectFirstExtendSelection"))
2982 {
2983 ensureRowsAreVisible(0, 0);
2984 TreePath anchorPath = tree.getAnchorSelectionPath();
2985 if (anchorPath == null)
2986 tree.setSelectionInterval(0, 0);
2987 else
2988 {
2989 int anchorRow = getRowForPath(tree, anchorPath);
2990 tree.setSelectionInterval(0, anchorRow);
2991 tree.setAnchorSelectionPath(anchorPath);
2992 tree.setLeadSelectionPath(getPathForRow(tree, 0));
2993 }
2994 }
2995 else if (command.equals("selectLast"))
2996 {
2997 int end = getRowCount(tree) - 1;
2998 ensureRowsAreVisible(end, end);
2999 tree.setSelectionInterval(end, end);
3000 }
3001 else if (command.equals("selectLastChangeLead"))
3002 {
3003 int end = getRowCount(tree) - 1;
3004 ensureRowsAreVisible(end, end);
3005 tree.setLeadSelectionPath(getPathForRow(tree, end));
3006 }
3007 else if (command.equals("selectLastExtendSelection"))
3008 {
3009 int end = getRowCount(tree) - 1;
3010 ensureRowsAreVisible(end, end);
3011 TreePath anchorPath = tree.getAnchorSelectionPath();
3012 if (anchorPath == null)
3013 tree.setSelectionInterval(end, end);
3014 else
3015 {
3016 int anchorRow = getRowForPath(tree, anchorPath);
3017 tree.setSelectionInterval(end, anchorRow);
3018 tree.setAnchorSelectionPath(anchorPath);
3019 tree.setLeadSelectionPath(getPathForRow(tree, end));
3020 }
3021 }
3022 }
3023
3024 // Ensure that the lead path is visible after the increment action.
3025 tree.scrollPathToVisible(tree.getLeadSelectionPath());
3026 }
3027
3028 /**
3029 * Returns true if the action is enabled.
3030 *
3031 * @return true if the action is enabled.
3032 */
3033 public boolean isEnabled()
3034 {
3035 return (tree != null) && tree.isEnabled();
3036 }
3037 }
3038
3039 /**
3040 * TreeIncrementAction is used to handle up/down actions. Selection is moved
3041 * up or down based on direction.
3042 */
3043 public class TreeIncrementAction
3044 extends AbstractAction
3045 {
3046
3047 /**
3048 * Specifies the direction to adjust the selection by.
3049 */
3050 protected int direction;
3051
3052 /**
3053 * Creates a new TreeIncrementAction.
3054 *
3055 * @param dir up or down, <code>-1</code> for up, <code>1</code> for down
3056 * @param name is the name of the direction
3057 */
3058 public TreeIncrementAction(int dir, String name)
3059 {
3060 direction = dir;
3061 putValue(Action.NAME, name);
3062 }
3063
3064 /**
3065 * Invoked when an action occurs.
3066 *
3067 * @param e is the event that occured
3068 */
3069 public void actionPerformed(ActionEvent e)
3070 {
3071 TreePath currentPath = tree.getLeadSelectionPath();
3072 int currentRow;
3073
3074 if (currentPath != null)
3075 currentRow = treeState.getRowForPath(currentPath);
3076 else
3077 currentRow = 0;
3078
3079 int rows = treeState.getRowCount();
3080
3081 int nextRow = currentRow + 1;
3082 int prevRow = currentRow - 1;
3083 boolean hasNext = nextRow < rows;
3084 boolean hasPrev = prevRow >= 0 && rows > 0;
3085 TreePath newPath;
3086 String command = (String) getValue(Action.NAME);
3087
3088 if (command.equals("selectPreviousChangeLead") && hasPrev)
3089 {
3090 newPath = treeState.getPathForRow(prevRow);
3091 tree.setSelectionPath(newPath);
3092 tree.setAnchorSelectionPath(newPath);
3093 tree.setLeadSelectionPath(newPath);
3094 }
3095 else if (command.equals("selectPreviousExtendSelection") && hasPrev)
3096 {
3097 newPath = treeState.getPathForRow(prevRow);
3098
3099 // If the new path is already selected, the selection shrinks,
3100 // unselecting the previously current path.
3101 if (tree.isPathSelected(newPath))
3102 tree.getSelectionModel().removeSelectionPath(currentPath);
3103
3104 // This must be called in any case because it updates the model
3105 // lead selection index.
3106 tree.addSelectionPath(newPath);
3107 tree.setLeadSelectionPath(newPath);
3108 }
3109 else if (command.equals("selectPrevious") && hasPrev)
3110 {
3111 newPath = treeState.getPathForRow(prevRow);
3112 tree.setSelectionPath(newPath);
3113 }
3114 else if (command.equals("selectNext") && hasNext)
3115 {
3116 newPath = treeState.getPathForRow(nextRow);
3117 tree.setSelectionPath(newPath);
3118 }
3119 else if (command.equals("selectNextExtendSelection") && hasNext)
3120 {
3121 newPath = treeState.getPathForRow(nextRow);
3122
3123 // If the new path is already selected, the selection shrinks,
3124 // unselecting the previously current path.
3125 if (tree.isPathSelected(newPath))
3126 tree.getSelectionModel().removeSelectionPath(currentPath);
3127
3128 // This must be called in any case because it updates the model
3129 // lead selection index.
3130 tree.addSelectionPath(newPath);
3131
3132 tree.setLeadSelectionPath(newPath);
3133 }
3134 else if (command.equals("selectNextChangeLead") && hasNext)
3135 {
3136 newPath = treeState.getPathForRow(nextRow);
3137 tree.setSelectionPath(newPath);
3138 tree.setAnchorSelectionPath(newPath);
3139 tree.setLeadSelectionPath(newPath);
3140 }
3141
3142 // Ensure that the lead path is visible after the increment action.
3143 tree.scrollPathToVisible(tree.getLeadSelectionPath());
3144 }
3145
3146 /**
3147 * Returns true if the action is enabled.
3148 *
3149 * @return true if the action is enabled.
3150 */
3151 public boolean isEnabled()
3152 {
3153 return (tree != null) && tree.isEnabled();
3154 }
3155 }
3156
3157 /**
3158 * Forwards all TreeModel events to the TreeState.
3159 */
3160 public class TreeModelHandler
3161 implements TreeModelListener
3162 {
3163 /**
3164 * Constructor
3165 */
3166 public TreeModelHandler()
3167 {
3168 // Nothing to do here.
3169 }
3170
3171 /**
3172 * Invoked after a node (or a set of siblings) has changed in some way. The
3173 * node(s) have not changed locations in the tree or altered their children
3174 * arrays, but other attributes have changed and may affect presentation.
3175 * Example: the name of a file has changed, but it is in the same location
3176 * in the file system. To indicate the root has changed, childIndices and
3177 * children will be null. Use e.getPath() to get the parent of the changed
3178 * node(s). e.getChildIndices() returns the index(es) of the changed
3179 * node(s).
3180 *
3181 * @param e is the event that occured
3182 */
3183 public void treeNodesChanged(TreeModelEvent e)
3184 {
3185 validCachedPreferredSize = false;
3186 treeState.treeNodesChanged(e);
3187 tree.repaint();
3188 }
3189
3190 /**
3191 * Invoked after nodes have been inserted into the tree. Use e.getPath() to
3192 * get the parent of the new node(s). e.getChildIndices() returns the
3193 * index(es) of the new node(s) in ascending order.
3194 *
3195 * @param e is the event that occured
3196 */
3197 public void treeNodesInserted(TreeModelEvent e)
3198 {
3199 validCachedPreferredSize = false;
3200 treeState.treeNodesInserted(e);
3201 tree.repaint();
3202 }
3203
3204 /**
3205 * Invoked after nodes have been removed from the tree. Note that if a
3206 * subtree is removed from the tree, this method may only be invoked once
3207 * for the root of the removed subtree, not once for each individual set of
3208 * siblings removed. Use e.getPath() to get the former parent of the deleted
3209 * node(s). e.getChildIndices() returns, in ascending order, the index(es)
3210 * the node(s) had before being deleted.
3211 *
3212 * @param e is the event that occured
3213 */
3214 public void treeNodesRemoved(TreeModelEvent e)
3215 {
3216 validCachedPreferredSize = false;
3217 treeState.treeNodesRemoved(e);
3218 tree.repaint();
3219 }
3220
3221 /**
3222 * Invoked after the tree has drastically changed structure from a given
3223 * node down. If the path returned by e.getPath() is of length one and the
3224 * first element does not identify the current root node the first element
3225 * should become the new root of the tree. Use e.getPath() to get the path
3226 * to the node. e.getChildIndices() returns null.
3227 *
3228 * @param e is the event that occured
3229 */
3230 public void treeStructureChanged(TreeModelEvent e)
3231 {
3232 if (e.getPath().length == 1
3233 && ! e.getPath()[0].equals(treeModel.getRoot()))
3234 tree.expandPath(new TreePath(treeModel.getRoot()));
3235 validCachedPreferredSize = false;
3236 treeState.treeStructureChanged(e);
3237 tree.repaint();
3238 }
3239 } // TreeModelHandler
3240
3241 /**
3242 * TreePageAction handles page up and page down events.
3243 */
3244 public class TreePageAction
3245 extends AbstractAction
3246 {
3247 /** Specifies the direction to adjust the selection by. */
3248 protected int direction;
3249
3250 /**
3251 * Constructor
3252 *
3253 * @param direction up or down
3254 * @param name is the name of the direction
3255 */
3256 public TreePageAction(int direction, String name)
3257 {
3258 this.direction = direction;
3259 putValue(Action.NAME, name);
3260 }
3261
3262 /**
3263 * Invoked when an action occurs.
3264 *
3265 * @param e is the event that occured
3266 */
3267 public void actionPerformed(ActionEvent e)
3268 {
3269 String command = (String) getValue(Action.NAME);
3270 boolean extendSelection = command.equals("scrollUpExtendSelection")
3271 || command.equals("scrollDownExtendSelection");
3272 boolean changeSelection = command.equals("scrollUpChangeSelection")
3273 || command.equals("scrollDownChangeSelection");
3274
3275 // Disable change lead, unless we are in discontinuous mode.
3276 if (!extendSelection && !changeSelection
3277 && tree.getSelectionModel().getSelectionMode() !=
3278 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
3279 {
3280 changeSelection = true;
3281 }
3282
3283 int rowCount = getRowCount(tree);
3284 if (rowCount > 0 && treeSelectionModel != null)
3285 {
3286 Dimension maxSize = tree.getSize();
3287 TreePath lead = tree.getLeadSelectionPath();
3288 TreePath newPath = null;
3289 Rectangle visible = tree.getVisibleRect();
3290 if (direction == -1) // The RI handles -1 as up.
3291 {
3292 newPath = getClosestPathForLocation(tree, visible.x, visible.y);
3293 if (newPath.equals(lead)) // Corner case, adjust one page up.
3294 {
3295 visible.y = Math.max(0, visible.y - visible.height);
3296 newPath = getClosestPathForLocation(tree, visible.x,
3297 visible.y);
3298 }
3299 }
3300 else // +1 is down.
3301 {
3302 visible.y = Math.min(maxSize.height,
3303 visible.y + visible.height - 1);
3304 newPath = getClosestPathForLocation(tree, visible.x, visible.y);
3305 if (newPath.equals(lead)) // Corner case, adjust one page down.
3306 {
3307 visible.y = Math.min(maxSize.height,
3308 visible.y + visible.height - 1);
3309 newPath = getClosestPathForLocation(tree, visible.x,
3310 visible.y);
3311 }
3312 }
3313
3314 // Determine new visible rect.
3315 Rectangle newVisible = getPathBounds(tree, newPath);
3316 newVisible.x = visible.x;
3317 newVisible.width = visible.width;
3318 if (direction == -1)
3319 {
3320 newVisible.height = visible.height;
3321 }
3322 else
3323 {
3324 newVisible.y -= visible.height - newVisible.height;
3325 newVisible.height = visible.height;
3326 }
3327
3328 if (extendSelection)
3329 {
3330 // Extend selection.
3331 TreePath anchorPath = tree.getAnchorSelectionPath();
3332 if (anchorPath == null)
3333 {
3334 tree.setSelectionPath(newPath);
3335 }
3336 else
3337 {
3338 int newIndex = getRowForPath(tree, newPath);
3339 int anchorIndex = getRowForPath(tree, anchorPath);
3340 tree.setSelectionInterval(Math.min(anchorIndex, newIndex),
3341 Math.max(anchorIndex, newIndex));
3342 tree.setAnchorSelectionPath(anchorPath);
3343 tree.setLeadSelectionPath(newPath);
3344 }
3345 }
3346 else if (changeSelection)
3347 {
3348 tree.setSelectionPath(newPath);
3349 }
3350 else // Change lead.
3351 {
3352 tree.setLeadSelectionPath(newPath);
3353 }
3354
3355 tree.scrollRectToVisible(newVisible);
3356 }
3357 }
3358
3359 /**
3360 * Returns true if the action is enabled.
3361 *
3362 * @return true if the action is enabled.
3363 */
3364 public boolean isEnabled()
3365 {
3366 return (tree != null) && tree.isEnabled();
3367 }
3368 } // TreePageAction
3369
3370 /**
3371 * Listens for changes in the selection model and updates the display
3372 * accordingly.
3373 */
3374 public class TreeSelectionHandler
3375 implements TreeSelectionListener
3376 {
3377 /**
3378 * Constructor
3379 */
3380 public TreeSelectionHandler()
3381 {
3382 // Nothing to do here.
3383 }
3384
3385 /**
3386 * Messaged when the selection changes in the tree we're displaying for.
3387 * Stops editing, messages super and displays the changed paths.
3388 *
3389 * @param event the event that characterizes the change.
3390 */
3391 public void valueChanged(TreeSelectionEvent event)
3392 {
3393 completeEditing();
3394
3395 TreePath op = event.getOldLeadSelectionPath();
3396 TreePath np = event.getNewLeadSelectionPath();
3397
3398 // Repaint of the changed lead selection path.
3399 if (op != np)
3400 {
3401 Rectangle o = treeState.getBounds(event.getOldLeadSelectionPath(),
3402 new Rectangle());
3403 Rectangle n = treeState.getBounds(event.getNewLeadSelectionPath(),
3404 new Rectangle());
3405
3406 if (o != null)
3407 tree.repaint(o);
3408 if (n != null)
3409 tree.repaint(n);
3410 }
3411 }
3412 } // TreeSelectionHandler
3413
3414 /**
3415 * For the first selected row expandedness will be toggled.
3416 */
3417 public class TreeToggleAction
3418 extends AbstractAction
3419 {
3420 /**
3421 * Creates a new TreeToggleAction.
3422 *
3423 * @param name is the name of <code>Action</code> field
3424 */
3425 public TreeToggleAction(String name)
3426 {
3427 putValue(Action.NAME, name);
3428 }
3429
3430 /**
3431 * Invoked when an action occurs.
3432 *
3433 * @param e the event that occured
3434 */
3435 public void actionPerformed(ActionEvent e)
3436 {
3437 int selected = tree.getLeadSelectionRow();
3438 if (selected != -1 && isLeaf(selected))
3439 {
3440 TreePath anchorPath = tree.getAnchorSelectionPath();
3441 TreePath leadPath = tree.getLeadSelectionPath();
3442 toggleExpandState(getPathForRow(tree, selected));
3443 // Need to do this, so that the toggling doesn't mess up the lead
3444 // and anchor.
3445 tree.setLeadSelectionPath(leadPath);
3446 tree.setAnchorSelectionPath(anchorPath);
3447
3448 // Ensure that the lead path is visible after the increment action.
3449 tree.scrollPathToVisible(tree.getLeadSelectionPath());
3450 }
3451 }
3452
3453 /**
3454 * Returns true if the action is enabled.
3455 *
3456 * @return true if the action is enabled, false otherwise
3457 */
3458 public boolean isEnabled()
3459 {
3460 return (tree != null) && tree.isEnabled();
3461 }
3462 } // TreeToggleAction
3463
3464 /**
3465 * TreeTraverseAction is the action used for left/right keys. Will toggle the
3466 * expandedness of a node, as well as potentially incrementing the selection.
3467 */
3468 public class TreeTraverseAction
3469 extends AbstractAction
3470 {
3471 /**
3472 * Determines direction to traverse, 1 means expand, -1 means collapse.
3473 */
3474 protected int direction;
3475
3476 /**
3477 * Constructor
3478 *
3479 * @param direction to traverse
3480 * @param name is the name of the direction
3481 */
3482 public TreeTraverseAction(int direction, String name)
3483 {
3484 this.direction = direction;
3485 putValue(Action.NAME, name);
3486 }
3487
3488 /**
3489 * Invoked when an action occurs.
3490 *
3491 * @param e the event that occured
3492 */
3493 public void actionPerformed(ActionEvent e)
3494 {
3495 TreePath current = tree.getLeadSelectionPath();
3496 if (current == null)
3497 return;
3498
3499 String command = (String) getValue(Action.NAME);
3500 if (command.equals("selectParent"))
3501 {
3502 if (current == null)
3503 return;
3504
3505 if (tree.isExpanded(current))
3506 {
3507 tree.collapsePath(current);
3508 }
3509 else
3510 {
3511 // If the node is not expanded (also, if it is a leaf node),
3512 // we just select the parent. We do not select the root if it
3513 // is not visible.
3514 TreePath parent = current.getParentPath();
3515 if (parent != null &&
3516 ! (parent.getPathCount() == 1 && ! tree.isRootVisible()))
3517 tree.setSelectionPath(parent);
3518 }
3519 }
3520 else if (command.equals("selectChild"))
3521 {
3522 Object node = current.getLastPathComponent();
3523 int nc = treeModel.getChildCount(node);
3524 if (nc == 0 || treeState.isExpanded(current))
3525 {
3526 // If the node is leaf or it is already expanded,
3527 // we just select the next row.
3528 int nextRow = tree.getLeadSelectionRow() + 1;
3529 if (nextRow <= tree.getRowCount())
3530 tree.setSelectionRow(nextRow);
3531 }
3532 else
3533 {
3534 tree.expandPath(current);
3535 }
3536 }
3537
3538 // Ensure that the lead path is visible after the increment action.
3539 tree.scrollPathToVisible(tree.getLeadSelectionPath());
3540 }
3541
3542 /**
3543 * Returns true if the action is enabled.
3544 *
3545 * @return true if the action is enabled, false otherwise
3546 */
3547 public boolean isEnabled()
3548 {
3549 return (tree != null) && tree.isEnabled();
3550 }
3551 }
3552
3553 /**
3554 * Returns true if the LookAndFeel implements the control icons. Package
3555 * private for use in inner classes.
3556 *
3557 * @returns true if there are control icons
3558 */
3559 boolean hasControlIcons()
3560 {
3561 if (expandedIcon != null || collapsedIcon != null)
3562 return true;
3563 return false;
3564 }
3565
3566 /**
3567 * Returns control icon. It is null if the LookAndFeel does not implements the
3568 * control icons. Package private for use in inner classes.
3569 *
3570 * @return control icon if it exists.
3571 */
3572 Icon getCurrentControlIcon(TreePath path)
3573 {
3574 if (hasControlIcons())
3575 {
3576 if (tree.isExpanded(path))
3577 return expandedIcon;
3578 else
3579 return collapsedIcon;
3580 }
3581 else
3582 {
3583 if (nullIcon == null)
3584 nullIcon = new Icon()
3585 {
3586 public int getIconHeight()
3587 {
3588 return 0;
3589 }
3590
3591 public int getIconWidth()
3592 {
3593 return 0;
3594 }
3595
3596 public void paintIcon(Component c, Graphics g, int x, int y)
3597 {
3598 // No action here.
3599 }
3600 };
3601 return nullIcon;
3602 }
3603 }
3604
3605 /**
3606 * Returns the parent of the current node
3607 *
3608 * @param root is the root of the tree
3609 * @param node is the current node
3610 * @return is the parent of the current node
3611 */
3612 Object getParent(Object root, Object node)
3613 {
3614 if (root == null || node == null || root.equals(node))
3615 return null;
3616
3617 if (node instanceof TreeNode)
3618 return ((TreeNode) node).getParent();
3619 return findNode(root, node);
3620 }
3621
3622 /**
3623 * Recursively checks the tree for the specified node, starting at the root.
3624 *
3625 * @param root is starting node to start searching at.
3626 * @param node is the node to search for
3627 * @return the parent node of node
3628 */
3629 private Object findNode(Object root, Object node)
3630 {
3631 if (! treeModel.isLeaf(root) && ! root.equals(node))
3632 {
3633 int size = treeModel.getChildCount(root);
3634 for (int j = 0; j < size; j++)
3635 {
3636 Object child = treeModel.getChild(root, j);
3637 if (node.equals(child))
3638 return root;
3639
3640 Object n = findNode(child, node);
3641 if (n != null)
3642 return n;
3643 }
3644 }
3645 return null;
3646 }
3647
3648 /**
3649 * Selects the specified path in the tree depending on modes. Package private
3650 * for use in inner classes.
3651 *
3652 * @param tree is the tree we are selecting the path in
3653 * @param path is the path we are selecting
3654 */
3655 void selectPath(JTree tree, TreePath path)
3656 {
3657 if (path != null)
3658 {
3659 tree.setSelectionPath(path);
3660 tree.setLeadSelectionPath(path);
3661 tree.makeVisible(path);
3662 tree.scrollPathToVisible(path);
3663 }
3664 }
3665
3666 /**
3667 * Returns the path from node to the root. Package private for use in inner
3668 * classes.
3669 *
3670 * @param node the node to get the path to
3671 * @param depth the depth of the tree to return a path for
3672 * @return an array of tree nodes that represent the path to node.
3673 */
3674 Object[] getPathToRoot(Object node, int depth)
3675 {
3676 if (node == null)
3677 {
3678 if (depth == 0)
3679 return null;
3680
3681 return new Object[depth];
3682 }
3683
3684 Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node),
3685 depth + 1);
3686 path[path.length - depth - 1] = node;
3687 return path;
3688 }
3689
3690 /**
3691 * Draws a vertical line using the given graphic context
3692 *
3693 * @param g is the graphic context
3694 * @param c is the component the new line will belong to
3695 * @param x is the horizonal position
3696 * @param top specifies the top of the line
3697 * @param bottom specifies the bottom of the line
3698 */
3699 protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
3700 int bottom)
3701 {
3702 // FIXME: Check if drawing a dashed line or not.
3703 g.setColor(getHashColor());
3704 g.drawLine(x, top, x, bottom);
3705 }
3706
3707 /**
3708 * Draws a horizontal line using the given graphic context
3709 *
3710 * @param g is the graphic context
3711 * @param c is the component the new line will belong to
3712 * @param y is the vertical position
3713 * @param left specifies the left point of the line
3714 * @param right specifies the right point of the line
3715 */
3716 protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left,
3717 int right)
3718 {
3719 // FIXME: Check if drawing a dashed line or not.
3720 g.setColor(getHashColor());
3721 g.drawLine(left, y, right, y);
3722 }
3723
3724 /**
3725 * Draws an icon at around a specific position
3726 *
3727 * @param c is the component the new line will belong to
3728 * @param g is the graphic context
3729 * @param icon is the icon which will be drawn
3730 * @param x is the center position in x-direction
3731 * @param y is the center position in y-direction
3732 */
3733 protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y)
3734 {
3735 x -= icon.getIconWidth() / 2;
3736 y -= icon.getIconHeight() / 2;
3737
3738 if (x < 0)
3739 x = 0;
3740 if (y < 0)
3741 y = 0;
3742
3743 icon.paintIcon(c, g, x, y);
3744 }
3745
3746 /**
3747 * Draws a dashed horizontal line.
3748 *
3749 * @param g - the graphics configuration.
3750 * @param y - the y location to start drawing at
3751 * @param x1 - the x location to start drawing at
3752 * @param x2 - the x location to finish drawing at
3753 */
3754 protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)
3755 {
3756 g.setColor(getHashColor());
3757 for (int i = x1; i < x2; i += 2)
3758 g.drawLine(i, y, i + 1, y);
3759 }
3760
3761 /**
3762 * Draws a dashed vertical line.
3763 *
3764 * @param g - the graphics configuration.
3765 * @param x - the x location to start drawing at
3766 * @param y1 - the y location to start drawing at
3767 * @param y2 - the y location to finish drawing at
3768 */
3769 protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2)
3770 {
3771 g.setColor(getHashColor());
3772 for (int i = y1; i < y2; i += 2)
3773 g.drawLine(x, i, x, i + 1);
3774 }
3775
3776 /**
3777 * Paints the expand (toggle) part of a row. The receiver should NOT modify
3778 * clipBounds, or insets.
3779 *
3780 * @param g - the graphics configuration
3781 * @param clipBounds -
3782 * @param insets -
3783 * @param bounds - bounds of expand control
3784 * @param path - path to draw control for
3785 * @param row - row to draw control for
3786 * @param isExpanded - is the row expanded
3787 * @param hasBeenExpanded - has the row already been expanded
3788 * @param isLeaf - is the path a leaf
3789 */
3790 protected void paintExpandControl(Graphics g, Rectangle clipBounds,
3791 Insets insets, Rectangle bounds,
3792 TreePath path, int row, boolean isExpanded,
3793 boolean hasBeenExpanded, boolean isLeaf)
3794 {
3795 if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf))
3796 {
3797 Icon icon = getCurrentControlIcon(path);
3798 int iconW = icon.getIconWidth();
3799 int x = bounds.x - iconW - gap;
3800 icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2
3801 - icon.getIconHeight() / 2);
3802 }
3803 }
3804
3805 /**
3806 * Paints the horizontal part of the leg. The receiver should NOT modify
3807 * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not
3808 * visible.
3809 *
3810 * @param g - the graphics configuration
3811 * @param clipBounds -
3812 * @param insets -
3813 * @param bounds - bounds of the cell
3814 * @param path - path to draw leg for
3815 * @param row - row to start drawing at
3816 * @param isExpanded - is the row expanded
3817 * @param hasBeenExpanded - has the row already been expanded
3818 * @param isLeaf - is the path a leaf
3819 */
3820 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
3821 Insets insets, Rectangle bounds,
3822 TreePath path, int row,
3823 boolean isExpanded,
3824 boolean hasBeenExpanded,
3825 boolean isLeaf)
3826 {
3827 if (row != 0)
3828 {
3829 paintHorizontalLine(g, tree, bounds.y + bounds.height / 2,
3830 bounds.x - leftChildIndent - gap, bounds.x - gap);
3831 }
3832 }
3833
3834 /**
3835 * Paints the vertical part of the leg. The receiver should NOT modify
3836 * clipBounds, insets.
3837 *
3838 * @param g - the graphics configuration.
3839 * @param clipBounds -
3840 * @param insets -
3841 * @param path - the path to draw the vertical part for.
3842 */
3843 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
3844 Insets insets, TreePath path)
3845 {
3846 Rectangle bounds = getPathBounds(tree, path);
3847 TreePath parent = path.getParentPath();
3848
3849 boolean paintLine;
3850 if (isRootVisible())
3851 paintLine = parent != null;
3852 else
3853 paintLine = parent != null && parent.getPathCount() > 1;
3854 if (paintLine)
3855 {
3856 Rectangle parentBounds = getPathBounds(tree, parent);
3857 paintVerticalLine(g, tree, parentBounds.x + 2 * gap,
3858 parentBounds.y + parentBounds.height / 2,
3859 bounds.y + bounds.height / 2);
3860 }
3861 }
3862
3863 /**
3864 * Paints the renderer part of a row. The receiver should NOT modify
3865 * clipBounds, or insets.
3866 *
3867 * @param g - the graphics configuration
3868 * @param clipBounds -
3869 * @param insets -
3870 * @param bounds - bounds of expand control
3871 * @param path - path to draw control for
3872 * @param row - row to draw control for
3873 * @param isExpanded - is the row expanded
3874 * @param hasBeenExpanded - has the row already been expanded
3875 * @param isLeaf - is the path a leaf
3876 */
3877 protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
3878 Rectangle bounds, TreePath path, int row,
3879 boolean isExpanded, boolean hasBeenExpanded,
3880 boolean isLeaf)
3881 {
3882 boolean selected = tree.isPathSelected(path);
3883 boolean hasIcons = false;
3884 Object node = path.getLastPathComponent();
3885
3886 paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded,
3887 hasBeenExpanded, isLeaf);
3888
3889 TreeCellRenderer dtcr = currentCellRenderer;
3890
3891 boolean focused = false;
3892 if (treeSelectionModel != null)
3893 focused = treeSelectionModel.getLeadSelectionRow() == row
3894 && tree.isFocusOwner();
3895
3896 Component c = dtcr.getTreeCellRendererComponent(tree, node, selected,
3897 isExpanded, isLeaf, row,
3898 focused);
3899
3900 rendererPane.paintComponent(g, c, c.getParent(), bounds);
3901 }
3902
3903 /**
3904 * Prepares for the UI to uninstall.
3905 */
3906 protected void prepareForUIUninstall()
3907 {
3908 // Nothing to do here yet.
3909 }
3910
3911 /**
3912 * Returns true if the expand (toggle) control should be drawn for the
3913 * specified row.
3914 *
3915 * @param path - current path to check for.
3916 * @param row - current row to check for.
3917 * @param isExpanded - true if the path is expanded
3918 * @param hasBeenExpanded - true if the path has been expanded already
3919 * @param isLeaf - true if the row is a lead
3920 */
3921 protected boolean shouldPaintExpandControl(TreePath path, int row,
3922 boolean isExpanded,
3923 boolean hasBeenExpanded,
3924 boolean isLeaf)
3925 {
3926 Object node = path.getLastPathComponent();
3927 return ! isLeaf && hasControlIcons();
3928 }
3929
3930 /**
3931 * Returns the amount to indent the given row
3932 *
3933 * @return amount to indent the given row.
3934 */
3935 protected int getRowX(int row, int depth)
3936 {
3937 return depth * totalChildIndent;
3938 }
3939 } // BasicTreeUI