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