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