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