History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/plaf/basic/BasicTreeUI.java (1 of 18352 files). Show all

Unified · Split

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