1. /*
  2. * @(#)BasicTreeUI.java 1.107 01/11/29
  3. *
  4. * Copyright 2002 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 javax.swing.text.DefaultTextUI;
  11. import java.awt.*;
  12. import java.awt.event.*;
  13. import java.beans.*;
  14. import java.io.*;
  15. import java.util.*;
  16. import javax.swing.plaf.ComponentUI;
  17. import javax.swing.plaf.UIResource;
  18. import javax.swing.plaf.TreeUI;
  19. import javax.swing.tree.*;
  20. /**
  21. * The basic L&F for a hierarchical data structure.
  22. * <p>
  23. *
  24. * @version 1.107 11/29/01
  25. * @author Scott Violet
  26. */
  27. public class BasicTreeUI extends TreeUI
  28. {
  29. static private final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
  30. transient protected Icon collapsedIcon;
  31. transient protected Icon expandedIcon;
  32. /** Color used to draw hash marks. If null no hash marks will be
  33. * drawn. */
  34. private Color hashColor;
  35. /** Distance between left margin and where verical dashes will be
  36. * drawn. */
  37. protected int leftChildIndent;
  38. /** Distance to add to leftChildIndent to determine where cell
  39. * contents will be drawn. */
  40. protected int rightChildIndent;
  41. /** Total distance that will be indented. The sum of leftChildIndent
  42. * and rightChildIndent. */
  43. protected int totalChildIndent;
  44. /** Minimum preferred size. */
  45. protected Dimension preferredMinSize;
  46. /** Index of the row that was last selected. */
  47. protected int lastSelectedRow;
  48. /** Component that we're going to be drawing into. */
  49. protected JTree tree;
  50. /** Renderer that is being used to do the actual cell drawing. */
  51. transient protected TreeCellRenderer currentCellRenderer;
  52. /** Set to true if the renderer that is currently in the tree was
  53. * created by this instance. */
  54. protected boolean createdRenderer;
  55. /** Editor for the tree. */
  56. transient protected TreeCellEditor cellEditor;
  57. /** Set to true if editor that is currently in the tree was
  58. * created by this instance. */
  59. protected boolean createdCellEditor;
  60. /** Set to false when editing and shouldSelectCell() returns true meaning
  61. * the node should be selected before editing, used in completeEditing. */
  62. protected boolean stopEditingInCompleteEditing;
  63. /** Used to paint the TreeCellRenderer. */
  64. protected CellRendererPane rendererPane;
  65. /** Size needed to completely display all the nodes. */
  66. protected Dimension preferredSize;
  67. /** Is the preferredSize valid? */
  68. protected boolean validCachedPreferredSize;
  69. /** Object responsible for handling sizing and expanded issues. */
  70. protected AbstractLayoutCache treeState;
  71. /** Used for minimizing the drawing of vertical lines. */
  72. protected Hashtable drawingCache;
  73. /** True if doing optimizations for a largeModel. Subclasses that
  74. * don't support this may wish to override createLayoutCache to not
  75. * return a FixedHeightLayoutCache instance. */
  76. protected boolean largeModel;
  77. /** Reponsible for telling the TreeState the size needed for a node. */
  78. protected AbstractLayoutCache.NodeDimensions nodeDimensions;
  79. /** Used to determine what to display. */
  80. protected TreeModel treeModel;
  81. /** Model maintaing the selection. */
  82. protected TreeSelectionModel treeSelectionModel;
  83. /** How much the depth should be offset to properly calculate
  84. * x locations. This is based on whether or not the root is visible,
  85. * and if the root handles are visible. */
  86. protected int depthOffset;
  87. // Following 4 ivars are only valid when editing.
  88. /** When editing, this will be the Component that is doing the actual
  89. * editing. */
  90. protected Component editingComponent;
  91. /** Path that is being edited. */
  92. protected TreePath editingPath;
  93. /** Row that is being edited. Should only be referenced if
  94. * editingComponent is not null. */
  95. protected int editingRow;
  96. /** Set to true if the editor has a different size than the renderer. */
  97. protected boolean editorHasDifferentSize;
  98. /** Updated when selection changes. This really needs to be in JTree, and
  99. * public at some point. */
  100. private TreePath anchorPath;
  101. /** Path of last selected item. */
  102. private TreePath leadPath;
  103. /** Row correspondin to lead path. */
  104. private int leadRow;
  105. /** If true, the selection will be changed with the focus, otherwise
  106. * both the selection and focus will change at the same time. This comes
  107. * from the property Tree.changeSelectionWithFocus. */
  108. private boolean changeSelectionWithFocus;
  109. /** KeyStrokes registered actions under. */
  110. private Vector keyActions;
  111. // Cached listeners
  112. private PropertyChangeListener propertyChangeListener;
  113. private PropertyChangeListener selectionModelPropertyChangeListener;
  114. private MouseListener mouseListener;
  115. private FocusListener focusListener;
  116. private KeyListener keyListener;
  117. /** Used for large models, listens for moved/resized events and
  118. * updates the validCachedPreferredSize bit accordingly. */
  119. private ComponentListener componentListener;
  120. /** Listens for CellEditor events. */
  121. private CellEditorListener cellEditorListener;
  122. /** Updates the display when the selection changes. */
  123. private TreeSelectionListener treeSelectionListener;
  124. /** Is responsible for updating the display based on model events. */
  125. private TreeModelListener treeModelListener;
  126. /** Updates the treestate as the nodes expand. */
  127. private TreeExpansionListener treeExpansionListener;
  128. public static ComponentUI createUI(JComponent x) {
  129. return new BasicTreeUI();
  130. }
  131. public BasicTreeUI() {
  132. super();
  133. }
  134. protected Color getHashColor() {
  135. return hashColor;
  136. }
  137. protected void setHashColor(Color color) {
  138. hashColor = color;
  139. }
  140. public void setLeftChildIndent(int newAmount) {
  141. leftChildIndent = newAmount;
  142. totalChildIndent = leftChildIndent + rightChildIndent;
  143. if(treeState != null)
  144. treeState.invalidateSizes();
  145. updateSize();
  146. }
  147. public int getLeftChildIndent() {
  148. return leftChildIndent;
  149. }
  150. public void setRightChildIndent(int newAmount) {
  151. rightChildIndent = newAmount;
  152. totalChildIndent = leftChildIndent + rightChildIndent;
  153. if(treeState != null)
  154. treeState.invalidateSizes();
  155. updateSize();
  156. }
  157. public int getRightChildIndent() {
  158. return rightChildIndent;
  159. }
  160. public void setExpandedIcon(Icon newG) {
  161. expandedIcon = newG;
  162. }
  163. public Icon getExpandedIcon() {
  164. return expandedIcon;
  165. }
  166. public void setCollapsedIcon(Icon newG) {
  167. collapsedIcon = newG;
  168. }
  169. public Icon getCollapsedIcon() {
  170. return collapsedIcon;
  171. }
  172. //
  173. // Methods for configuring the behavior of the tree. None of them
  174. // push the value to the JTree instance. You should really only
  175. // call these methods on the JTree.
  176. //
  177. /**
  178. * Updates the componentListener, if necessary.
  179. */
  180. protected void setLargeModel(boolean largeModel) {
  181. if(getRowHeight() < 1)
  182. largeModel = false;
  183. if(this.largeModel != largeModel) {
  184. completeEditing();
  185. this.largeModel = largeModel;
  186. treeState = createLayoutCache();
  187. configureLayoutCache();
  188. updateLayoutCacheExpandedNodes();
  189. updateSize();
  190. }
  191. }
  192. protected boolean isLargeModel() {
  193. return largeModel;
  194. }
  195. /**
  196. * Sets the row height, this is forwarded to the treeState.
  197. */
  198. protected void setRowHeight(int rowHeight) {
  199. completeEditing();
  200. if(treeState != null) {
  201. setLargeModel(tree.isLargeModel());
  202. treeState.setRowHeight(rowHeight);
  203. updateSize();
  204. }
  205. }
  206. protected int getRowHeight() {
  207. return (tree == null) ? -1 : tree.getRowHeight();
  208. }
  209. /**
  210. * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
  211. * <code>updateRenderer</code>.
  212. */
  213. protected void setCellRenderer(TreeCellRenderer tcr) {
  214. completeEditing();
  215. updateRenderer();
  216. if(treeState != null) {
  217. treeState.invalidateSizes();
  218. updateSize();
  219. }
  220. }
  221. /**
  222. * Return currentCellRenderer, which will either be the trees
  223. * renderer, or defaultCellRenderer, which ever wasn't null.
  224. */
  225. protected TreeCellRenderer getCellRenderer() {
  226. return currentCellRenderer;
  227. }
  228. /**
  229. * Sets the TreeModel.
  230. */
  231. protected void setModel(TreeModel model) {
  232. completeEditing();
  233. if(treeModel != null && treeModelListener != null)
  234. treeModel.removeTreeModelListener(treeModelListener);
  235. treeModel = model;
  236. if(treeModel != null) {
  237. if(treeModelListener != null)
  238. treeModel.addTreeModelListener(treeModelListener);
  239. }
  240. if(treeState != null) {
  241. treeState.setModel(model);
  242. updateLayoutCacheExpandedNodes();
  243. updateSize();
  244. }
  245. }
  246. protected TreeModel getModel() {
  247. return treeModel;
  248. }
  249. /**
  250. * Sets the root to being visible.
  251. */
  252. protected void setRootVisible(boolean newValue) {
  253. completeEditing();
  254. updateDepthOffset();
  255. if(treeState != null) {
  256. treeState.setRootVisible(newValue);
  257. treeState.invalidateSizes();
  258. updateSize();
  259. }
  260. }
  261. protected boolean isRootVisible() {
  262. return (tree != null) ? tree.isRootVisible() : false;
  263. }
  264. /**
  265. * Determines whether the node handles are to be displayed.
  266. */
  267. protected void setShowsRootHandles(boolean newValue) {
  268. completeEditing();
  269. updateDepthOffset();
  270. if(treeState != null) {
  271. treeState.invalidateSizes();
  272. updateSize();
  273. }
  274. }
  275. protected boolean getShowsRootHandles() {
  276. return (tree != null) ? tree.getShowsRootHandles() : false;
  277. }
  278. /**
  279. * Sets the cell editor.
  280. */
  281. protected void setCellEditor(TreeCellEditor editor) {
  282. updateCellEditor();
  283. }
  284. protected TreeCellEditor getCellEditor() {
  285. return (tree != null) ? tree.getCellEditor() : null;
  286. }
  287. /**
  288. * Configures the receiver to allow, or not allow, editing.
  289. */
  290. protected void setEditable(boolean newValue) {
  291. updateCellEditor();
  292. }
  293. protected boolean isEditable() {
  294. return (tree != null) ? tree.isEditable() : false;
  295. }
  296. /**
  297. * Resets the selection model. The appropriate listener are installed
  298. * on the model.
  299. */
  300. protected void setSelectionModel(TreeSelectionModel newLSM) {
  301. completeEditing();
  302. if(selectionModelPropertyChangeListener != null &&
  303. treeSelectionModel != null)
  304. treeSelectionModel.removePropertyChangeListener
  305. (selectionModelPropertyChangeListener);
  306. if(treeSelectionListener != null && treeSelectionModel != null)
  307. treeSelectionModel.removeTreeSelectionListener
  308. (treeSelectionListener);
  309. treeSelectionModel = newLSM;
  310. if(treeSelectionModel != null) {
  311. if(selectionModelPropertyChangeListener != null)
  312. treeSelectionModel.addPropertyChangeListener
  313. (selectionModelPropertyChangeListener);
  314. if(treeSelectionListener != null)
  315. treeSelectionModel.addTreeSelectionListener
  316. (treeSelectionListener);
  317. if(treeState != null)
  318. treeState.setSelectionModel(treeSelectionModel);
  319. }
  320. else if(treeState != null)
  321. treeState.setSelectionModel(null);
  322. if(tree != null)
  323. tree.repaint();
  324. }
  325. protected TreeSelectionModel getSelectionModel() {
  326. return treeSelectionModel;
  327. }
  328. //
  329. // TreeUI methods
  330. //
  331. /**
  332. * Returns the Rectangle enclosing the label portion that the
  333. * last item in path will be drawn into. Will return null if
  334. * any component in path is currently valid.
  335. */
  336. public Rectangle getPathBounds(JTree tree, TreePath path) {
  337. if(tree != null && treeState != null) {
  338. Insets i = tree.getInsets();
  339. Rectangle bounds = treeState.getBounds(path, null);
  340. if(bounds != null && i != null) {
  341. bounds.x += i.left;
  342. bounds.y += i.top;
  343. }
  344. return bounds;
  345. }
  346. return null;
  347. }
  348. /**
  349. * Returns the path for passed in row. If row is not visible
  350. * null is returned.
  351. */
  352. public TreePath getPathForRow(JTree tree, int row) {
  353. return (treeState != null) ? treeState.getPathForRow(row) : null;
  354. }
  355. /**
  356. * Returns the row that the last item identified in path is visible
  357. * at. Will return -1 if any of the elements in path are not
  358. * currently visible.
  359. */
  360. public int getRowForPath(JTree tree, TreePath path) {
  361. return (treeState != null) ? treeState.getRowForPath(path) : -1;
  362. }
  363. /**
  364. * Returns the number of rows that are being displayed.
  365. */
  366. public int getRowCount(JTree tree) {
  367. return (treeState != null) ? treeState.getRowCount() : 0;
  368. }
  369. /**
  370. * Returns the path to the node that is closest to x,y. If
  371. * there is nothing currently visible this will return null, otherwise
  372. * it'll always return a valid path. If you need to test if the
  373. * returned object is exactly at x, y you should get the bounds for
  374. * the returned path and test x, y against that.
  375. */
  376. public TreePath getClosestPathForLocation(JTree tree, int x, int y) {
  377. if(tree != null && treeState != null) {
  378. Insets i = tree.getInsets();
  379. if(i == null)
  380. i = EMPTY_INSETS;
  381. return treeState.getPathClosestTo(x - i.left, y - i.top);
  382. }
  383. return null;
  384. }
  385. /**
  386. * Returns true if the tree is being edited. The item that is being
  387. * edited can be returned by getEditingPath().
  388. */
  389. public boolean isEditing(JTree tree) {
  390. return (editingComponent != null);
  391. }
  392. /**
  393. * Stops the current editing session. This has no effect if the
  394. * tree isn't being edited. Returns true if the editor allows the
  395. * editing session to stop.
  396. */
  397. public boolean stopEditing(JTree tree) {
  398. if(editingComponent != null && cellEditor.stopCellEditing()) {
  399. completeEditing(false, false, true);
  400. return true;
  401. }
  402. return false;
  403. }
  404. /**
  405. * Cancels the current editing session.
  406. */
  407. public void cancelEditing(JTree tree) {
  408. if(editingComponent != null) {
  409. completeEditing(false, true, false);
  410. }
  411. }
  412. /**
  413. * Selects the last item in path and tries to edit it. Editing will
  414. * fail if the CellEditor won't allow it for the selected item.
  415. */
  416. public void startEditingAtPath(JTree tree, TreePath path) {
  417. tree.scrollPathToVisible(path);
  418. if(path != null && tree.isVisible(path))
  419. startEditing(path, null);
  420. }
  421. /**
  422. * Returns the path to the element that is being edited.
  423. */
  424. public TreePath getEditingPath(JTree tree) {
  425. return editingPath;
  426. }
  427. //
  428. // Install methods
  429. //
  430. public void installUI(JComponent c) {
  431. if ( c == null ) {
  432. throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" );
  433. }
  434. tree = (JTree)c;
  435. prepareForUIInstall();
  436. // Boilerplate install block
  437. installDefaults();
  438. installListeners();
  439. installKeyboardActions();
  440. installComponents();
  441. completeUIInstall();
  442. }
  443. /**
  444. * Invoked after the <code>tree</code> instance variable has been
  445. * set, but before any defaults/listeners have been installed.
  446. */
  447. protected void prepareForUIInstall() {
  448. drawingCache = new Hashtable(7);
  449. // Data member initializations
  450. stopEditingInCompleteEditing = true;
  451. lastSelectedRow = -1;
  452. leadPath = anchorPath = null;
  453. leadRow = -1;
  454. preferredSize = new Dimension();
  455. tree.setRowHeight(UIManager.getInt("Tree.rowHeight"));
  456. Object b = UIManager.get("Tree.scrollsOnExpand");
  457. if(b != null)
  458. tree.setScrollsOnExpand(((Boolean)b).booleanValue());
  459. largeModel = tree.isLargeModel();
  460. if(getRowHeight() <= 0)
  461. largeModel = false;
  462. setModel(tree.getModel());
  463. }
  464. /**
  465. * Invoked from installUI after all the defaults/listeners have been
  466. * installed.
  467. */
  468. protected void completeUIInstall() {
  469. // Custom install code
  470. this.setShowsRootHandles(tree.getShowsRootHandles());
  471. updateRenderer();
  472. updateDepthOffset();
  473. setSelectionModel(tree.getSelectionModel());
  474. // Create, if necessary, the TreeState instance.
  475. treeState = createLayoutCache();
  476. configureLayoutCache();
  477. updateSize();
  478. }
  479. protected void installDefaults() {
  480. if(tree.getBackground() == null ||
  481. tree.getBackground() instanceof UIResource) {
  482. tree.setBackground(UIManager.getColor("Tree.background"));
  483. }
  484. if(getHashColor() == null || getHashColor() instanceof UIResource) {
  485. setHashColor(UIManager.getColor("Tree.hash"));
  486. }
  487. if (tree.getFont() == null || tree.getFont() instanceof UIResource)
  488. tree.setFont( UIManager.getFont("Tree.font") );
  489. setExpandedIcon( (Icon)UIManager.get( "Tree.expandedIcon" ) );
  490. setCollapsedIcon( (Icon)UIManager.get( "Tree.collapsedIcon" ) );
  491. setLeftChildIndent(((Integer)UIManager.get("Tree.leftChildIndent")).
  492. intValue());
  493. setRightChildIndent(((Integer)UIManager.get("Tree.rightChildIndent")).
  494. intValue());
  495. changeSelectionWithFocus = ((Boolean)UIManager.get
  496. ("Tree.changeSelectionWithFocus")).booleanValue();
  497. }
  498. protected void installListeners() {
  499. if ( (propertyChangeListener = createPropertyChangeListener())
  500. != null ) {
  501. tree.addPropertyChangeListener(propertyChangeListener);
  502. }
  503. if ( (mouseListener = createMouseListener()) != null ) {
  504. tree.addMouseListener(mouseListener);
  505. }
  506. if ((focusListener = createFocusListener()) != null ) {
  507. tree.addFocusListener(focusListener);
  508. }
  509. if ((keyListener = createKeyListener()) != null) {
  510. tree.addKeyListener(keyListener);
  511. }
  512. if((treeExpansionListener = createTreeExpansionListener()) != null) {
  513. tree.addTreeExpansionListener(treeExpansionListener);
  514. }
  515. if((treeModelListener = createTreeModelListener()) != null &&
  516. treeModel != null) {
  517. treeModel.addTreeModelListener(treeModelListener);
  518. }
  519. if((selectionModelPropertyChangeListener =
  520. createSelectionModelPropertyChangeListener()) != null &&
  521. treeSelectionModel != null) {
  522. treeSelectionModel.addPropertyChangeListener
  523. (selectionModelPropertyChangeListener);
  524. }
  525. if((treeSelectionListener = createTreeSelectionListener()) != null &&
  526. treeSelectionModel != null) {
  527. treeSelectionModel.addTreeSelectionListener(treeSelectionListener);
  528. }
  529. }
  530. private void registerKeyAction(ActionListener action, KeyStroke ks) {
  531. keyActions.addElement(ks);
  532. tree.registerKeyboardAction(action, ks, JComponent.WHEN_FOCUSED);
  533. }
  534. protected void installKeyboardActions() {
  535. // PENDING(sky): All these names need to be rethought, and the values
  536. // need to come from the defaults manager.
  537. boolean changeSelection = changeSelectionWithFocus();
  538. // YES! 33 key actions, yeesh!
  539. // hania: This became 43 when I added the keypad arrow key actions.
  540. // hania: is counding the number of calls below really the best way
  541. // to determine the size this vector needs to be initialized to?
  542. keyActions = new Vector(43);
  543. registerKeyAction(new TreeIncrementAction(-1, "UP", false,
  544. changeSelection), KeyStroke.getKeyStroke(KeyEvent.VK_UP,0));
  545. registerKeyAction(new TreeIncrementAction(-1, "UP", false,
  546. changeSelection), KeyStroke.getKeyStroke("KP_UP"));
  547. registerKeyAction(new TreeIncrementAction(-1, "SELECT_UP", true,
  548. true), KeyStroke.getKeyStroke(KeyEvent.VK_UP,
  549. InputEvent.SHIFT_MASK));
  550. registerKeyAction(new TreeIncrementAction(-1, "SELECT_UP", true,
  551. true), KeyStroke.getKeyStroke("shift KP_UP"));
  552. registerKeyAction(new TreeIncrementAction(1, "DOWN", false,
  553. changeSelection), KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0));
  554. registerKeyAction(new TreeIncrementAction(1, "DOWN", false,
  555. changeSelection), KeyStroke.getKeyStroke("KP_DOWN" ));
  556. registerKeyAction(new TreeIncrementAction(1, "SELECT_DOWN",
  557. true, true), KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,
  558. InputEvent.SHIFT_MASK));
  559. registerKeyAction(new TreeIncrementAction(1, "SELECT_DOWN",
  560. true, true), KeyStroke.getKeyStroke("shift KP_DOWN"));
  561. registerKeyAction(new TreeTraverseAction(1, "RIGHT",
  562. changeSelection),
  563. KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0));
  564. registerKeyAction(new TreeTraverseAction(1, "RIGHT",
  565. changeSelection),
  566. KeyStroke.getKeyStroke("KP_RIGHT"));
  567. registerKeyAction(new TreeTraverseAction(-1, "LEFT",
  568. changeSelection),
  569. KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0));
  570. registerKeyAction(new TreeTraverseAction(-1, "LEFT",
  571. changeSelection),
  572. KeyStroke.getKeyStroke("KP_LEFT"));
  573. registerKeyAction(new TreePageAction(-1, "P_UP", false,
  574. changeSelection),
  575. KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,0));
  576. registerKeyAction(new TreePageAction(-1, "SELECT_P_UP",
  577. true, true),
  578. KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,
  579. InputEvent.SHIFT_MASK));
  580. registerKeyAction(new TreePageAction(1, "P_DOWN", false,
  581. changeSelection),
  582. KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,0));
  583. registerKeyAction(new TreePageAction(1, "SELECT_P_DOWN",
  584. true, true), KeyStroke.getKeyStroke(KeyEvent.
  585. VK_PAGE_DOWN, InputEvent.SHIFT_MASK));
  586. registerKeyAction(new TreeHomeAction(-1, "HOME", false,
  587. changeSelection), KeyStroke.getKeyStroke(KeyEvent.VK_HOME,0));
  588. registerKeyAction(new TreeHomeAction(-1, "SELECT_HOME",true,
  589. true), KeyStroke.getKeyStroke(KeyEvent.VK_HOME,
  590. InputEvent.SHIFT_MASK));
  591. registerKeyAction(new TreeHomeAction(1, "END", false,
  592. changeSelection),KeyStroke.getKeyStroke
  593. (KeyEvent.VK_END,0));
  594. registerKeyAction(new TreeHomeAction(1, "SELECT_END", true,
  595. true), KeyStroke.getKeyStroke(KeyEvent.VK_END,
  596. InputEvent.SHIFT_MASK));
  597. registerKeyAction(new TreeToggleAction("TOGGLE"),
  598. KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0));
  599. KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
  600. tree.registerKeyboardAction(new TreeCancelEditingAction("ESCAPE"), ks,
  601. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  602. keyActions.addElement(ks);
  603. registerKeyAction(new TreeEditAction("EDIT_NODE"),
  604. KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
  605. ActionListener saAction = new TreeSelectAllAction("SELECT_ALL",true);
  606. registerKeyAction(saAction, KeyStroke.getKeyStroke
  607. (KeyEvent.VK_A, InputEvent.CTRL_MASK));
  608. registerKeyAction(saAction, KeyStroke.getKeyStroke
  609. (KeyEvent.VK_SLASH, InputEvent.CTRL_MASK));
  610. registerKeyAction(new TreeSelectAllAction("UNSELECT_ALL",
  611. false), KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SLASH,
  612. InputEvent.CTRL_MASK));
  613. registerKeyAction(new TreeAddSelectionAction("SELECT_NODE", false),
  614. KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,
  615. InputEvent.CTRL_MASK));
  616. registerKeyAction(new TreeExtendSelectionAction
  617. ("EXTEND_SELECTION"),
  618. KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,
  619. InputEvent.SHIFT_MASK));
  620. if(changeSelection) {
  621. registerKeyAction(new TreeHomeAction(-1, "HOME", false,
  622. false), KeyStroke.getKeyStroke(KeyEvent.VK_HOME,
  623. InputEvent.CTRL_MASK));
  624. registerKeyAction(new TreeHomeAction(1, "END", false,
  625. false),KeyStroke.getKeyStroke(KeyEvent.VK_END,
  626. InputEvent.CTRL_MASK));
  627. registerKeyAction(new TreeIncrementAction(-1, "UP",
  628. false, false), KeyStroke.getKeyStroke(KeyEvent.VK_UP,
  629. InputEvent.CTRL_MASK));
  630. registerKeyAction(new TreeIncrementAction(-1, "UP",
  631. false, false), KeyStroke.getKeyStroke("ctrl KP_UP"));
  632. registerKeyAction(new TreeIncrementAction(1, "DOWN",
  633. false, false), KeyStroke.getKeyStroke
  634. (KeyEvent.VK_DOWN, InputEvent.CTRL_MASK));
  635. registerKeyAction(new TreeIncrementAction(1, "DOWN",
  636. false, false), KeyStroke.getKeyStroke
  637. ("ctrl KP_DOWN"));
  638. registerKeyAction(new TreePageAction(1, "LEAD_P_DOWN", false,
  639. false), KeyStroke.getKeyStroke
  640. (KeyEvent.VK_PAGE_DOWN, InputEvent.CTRL_MASK));
  641. registerKeyAction(new TreePageAction(1, "SELECT_P_DOWN", true,
  642. true), KeyStroke.getKeyStroke
  643. (KeyEvent.VK_PAGE_DOWN, InputEvent.CTRL_MASK |
  644. InputEvent.SHIFT_MASK));
  645. registerKeyAction(new TreePageAction(-1, "LEAD_P_UP", false,
  646. false), KeyStroke.getKeyStroke
  647. (KeyEvent.VK_PAGE_UP, InputEvent.CTRL_MASK));
  648. registerKeyAction(new TreePageAction(-1, "SELECT_P_UP", true,
  649. true), KeyStroke.getKeyStroke
  650. (KeyEvent.VK_PAGE_UP, InputEvent.CTRL_MASK |
  651. InputEvent.SHIFT_MASK));
  652. registerKeyAction(new ScrollAction(tree, SwingConstants.
  653. HORIZONTAL, -10), KeyStroke.getKeyStroke
  654. (KeyEvent.VK_LEFT, InputEvent.CTRL_MASK));
  655. registerKeyAction(new ScrollAction(tree, SwingConstants.
  656. HORIZONTAL, -10), KeyStroke.getKeyStroke
  657. ("ctrl KP_LEFT"));
  658. registerKeyAction(new ScrollAction(tree, SwingConstants.
  659. HORIZONTAL, 10), KeyStroke.getKeyStroke
  660. (KeyEvent.VK_RIGHT, InputEvent.CTRL_MASK));
  661. registerKeyAction(new ScrollAction(tree, SwingConstants.
  662. HORIZONTAL, 10), KeyStroke.getKeyStroke
  663. ("ctrl KP_RIGHT"));
  664. registerKeyAction(new TreeAddSelectionAction("SELECT_NODE", false),
  665. KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0));
  666. }
  667. else {
  668. registerKeyAction(new TreeScrollLRAction(1, "CTRL_P_DOWN",
  669. false), KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,
  670. InputEvent.CTRL_MASK));
  671. registerKeyAction(new TreeScrollLRAction(-1, "CTRL_P_UP",
  672. false), KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,
  673. InputEvent.CTRL_MASK));
  674. registerKeyAction(new TreeScrollLRAction(-1,
  675. "SELECT_CTRL_P_UP", true), KeyStroke.
  676. getKeyStroke(KeyEvent.VK_PAGE_UP,
  677. InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK));
  678. registerKeyAction(new TreeScrollLRAction(1,
  679. "SELECT_CTRL_P_DOWN", true), KeyStroke.
  680. getKeyStroke(KeyEvent.VK_PAGE_DOWN,
  681. InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK));
  682. registerKeyAction(new TreeAddSelectionAction("SELECT_NODE", true),
  683. KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0));
  684. }
  685. }
  686. /**
  687. * Intalls the subcomponents of the tree, which is the renderer pane.
  688. */
  689. protected void installComponents() {
  690. if ((rendererPane = createCellRendererPane()) != null) {
  691. tree.add( rendererPane );
  692. }
  693. }
  694. //
  695. // Create methods.
  696. //
  697. /**
  698. * Creates an instance of NodeDimensions that is able to determine
  699. * the size of a given node in the tree.
  700. */
  701. protected AbstractLayoutCache.NodeDimensions createNodeDimensions() {
  702. return new NodeDimensionsHandler();
  703. }
  704. /**
  705. * Creates a listener that is responsible that updates the UI based on
  706. * how the tree changes.
  707. */
  708. protected PropertyChangeListener createPropertyChangeListener() {
  709. return new PropertyChangeHandler();
  710. }
  711. /**
  712. * Creates the listener responsible for updating the selection based on
  713. * mouse events.
  714. */
  715. protected MouseListener createMouseListener() {
  716. return new MouseHandler();
  717. }
  718. /**
  719. * Creates a listener that is responsible for updating the display
  720. * when focus is lost/gained.
  721. */
  722. protected FocusListener createFocusListener() {
  723. return new FocusHandler();
  724. }
  725. /**
  726. * Creates the listener reponsible for getting key events from
  727. * the tree.
  728. */
  729. protected KeyListener createKeyListener() {
  730. return new KeyHandler();
  731. }
  732. /**
  733. * Creates the listener responsible for getting property change
  734. * events from the selection model.
  735. */
  736. protected PropertyChangeListener createSelectionModelPropertyChangeListener() {
  737. return new SelectionModelPropertyChangeHandler();
  738. }
  739. /**
  740. * Creates the listener that updates the display based on selection change
  741. * methods.
  742. */
  743. protected TreeSelectionListener createTreeSelectionListener() {
  744. return new TreeSelectionHandler();
  745. }
  746. /**
  747. * Creates a listener to handle events from the current editor.
  748. */
  749. protected CellEditorListener createCellEditorListener() {
  750. return new CellEditorHandler();
  751. }
  752. /**
  753. * Creates and returns a new ComponentHandler. This is used for
  754. * the large model to mark the validCachedPreferredSize as invalid
  755. * when the component moves.
  756. */
  757. protected ComponentListener createComponentListener() {
  758. return new ComponentHandler();
  759. }
  760. /**
  761. * Creates and returns the object responsible for updating the treestate
  762. * when nodes expanded state changes.
  763. */
  764. protected TreeExpansionListener createTreeExpansionListener() {
  765. return new TreeExpansionHandler();
  766. }
  767. /**
  768. * Creates the object responsible for managing what is expanded, as
  769. * well as the size of nodes.
  770. */
  771. protected AbstractLayoutCache createLayoutCache() {
  772. if(isLargeModel() && getRowHeight() > 0) {
  773. return new FixedHeightLayoutCache();
  774. }
  775. return new VariableHeightLayoutCache();
  776. }
  777. /**
  778. * Returns the renderer pane that renderer components are placed in.
  779. */
  780. protected CellRendererPane createCellRendererPane() {
  781. return new CellRendererPane();
  782. }
  783. /**
  784. * Creates a default cell editor.
  785. */
  786. protected TreeCellEditor createDefaultCellEditor() {
  787. if(currentCellRenderer != null &&
  788. (currentCellRenderer instanceof DefaultTreeCellRenderer)) {
  789. DefaultTreeCellEditor editor = new DefaultTreeCellEditor
  790. (tree, (DefaultTreeCellRenderer)currentCellRenderer);
  791. return editor;
  792. }
  793. return new DefaultTreeCellEditor(tree, null);
  794. }
  795. /**
  796. * Returns the default cell renderer that is used to do the
  797. * stamping of each node.
  798. */
  799. protected TreeCellRenderer createDefaultCellRenderer() {
  800. return new DefaultTreeCellRenderer();
  801. }
  802. /**
  803. * Returns a listener that can update the tree when the model changes.
  804. */
  805. protected TreeModelListener createTreeModelListener() {
  806. return new TreeModelHandler();
  807. }
  808. //
  809. // Uninstall methods
  810. //
  811. public void uninstallUI(JComponent c) {
  812. completeEditing();
  813. prepareForUIUninstall();
  814. uninstallDefaults();
  815. uninstallListeners();
  816. uninstallKeyboardActions();
  817. uninstallComponents();
  818. completeUIUninstall();
  819. }
  820. protected void prepareForUIUninstall() {
  821. }
  822. protected void completeUIUninstall() {
  823. if(createdRenderer) {
  824. tree.setCellRenderer(null);
  825. }
  826. if(createdCellEditor) {
  827. tree.setCellEditor(null);
  828. }
  829. cellEditor = null;
  830. currentCellRenderer = null;
  831. rendererPane = null;
  832. componentListener = null;
  833. propertyChangeListener = null;
  834. mouseListener = null;
  835. focusListener = null;
  836. keyListener = null;
  837. treeState = null;
  838. setSelectionModel(null);
  839. drawingCache = null;
  840. selectionModelPropertyChangeListener = null;
  841. tree = null;
  842. treeModel = null;
  843. treeSelectionModel = null;
  844. treeSelectionListener = null;
  845. treeExpansionListener = null;
  846. }
  847. protected void uninstallDefaults() {
  848. }
  849. protected void uninstallListeners() {
  850. if(componentListener != null) {
  851. tree.removeComponentListener(componentListener);
  852. }
  853. if (propertyChangeListener != null) {
  854. tree.removePropertyChangeListener(propertyChangeListener);
  855. }
  856. if (mouseListener != null) {
  857. tree.removeMouseListener(mouseListener);
  858. }
  859. if (focusListener != null) {
  860. tree.removeFocusListener(focusListener);
  861. }
  862. if (keyListener != null) {
  863. tree.removeKeyListener(keyListener);
  864. }
  865. if(treeExpansionListener != null) {
  866. tree.removeTreeExpansionListener(treeExpansionListener);
  867. }
  868. if(treeModel != null && treeModelListener != null) {
  869. treeModel.removeTreeModelListener(treeModelListener);
  870. }
  871. if(selectionModelPropertyChangeListener != null &&
  872. treeSelectionModel != null) {
  873. treeSelectionModel.removePropertyChangeListener
  874. (selectionModelPropertyChangeListener);
  875. }
  876. if(treeSelectionListener != null && treeSelectionModel != null) {
  877. treeSelectionModel.removeTreeSelectionListener
  878. (treeSelectionListener);
  879. }
  880. }
  881. protected void uninstallKeyboardActions() {
  882. for(int counter = keyActions.size() - 1; counter >= 0; counter--) {
  883. tree.unregisterKeyboardAction((KeyStroke)keyActions.
  884. elementAt(counter));
  885. }
  886. keyActions = null;
  887. }
  888. /**
  889. * Uninstalls the renderer pane.
  890. */
  891. protected void uninstallComponents() {
  892. if(rendererPane != null) {
  893. tree.remove(rendererPane);
  894. }
  895. }
  896. //
  897. // Painting routines.
  898. //
  899. public void paint(Graphics g, JComponent c) {
  900. if (tree != c) {
  901. throw new InternalError("incorrect component");
  902. }
  903. // Should never happen if installed for a UI
  904. if(treeState == null) {
  905. return;
  906. }
  907. Rectangle paintBounds = g.getClipBounds();
  908. Insets insets = tree.getInsets();
  909. if(insets == null)
  910. insets = EMPTY_INSETS;
  911. TreePath initialPath = getClosestPathForLocation
  912. (tree, 0, paintBounds.y);
  913. Enumeration paintingEnumerator = treeState.getVisiblePathsFrom
  914. (initialPath);
  915. int row = treeState.getRowForPath(initialPath);
  916. int endY = paintBounds.y + paintBounds.height;
  917. drawingCache.clear();
  918. if(initialPath != null && paintingEnumerator != null) {
  919. TreePath parentPath = initialPath;
  920. // Draw the lines, knobs, and rows
  921. // Find each parent and have them draw a line to their last child
  922. parentPath = parentPath.getParentPath();
  923. while(parentPath != null) {
  924. paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
  925. drawingCache.put(parentPath, Boolean.TRUE);
  926. parentPath = parentPath.getParentPath();
  927. }
  928. boolean done = false;
  929. // Information for the node being rendered.
  930. boolean isExpanded;
  931. boolean hasBeenExpanded;
  932. boolean isLeaf;
  933. Rectangle boundsBuffer = new Rectangle();
  934. Rectangle bounds;
  935. TreePath path;
  936. boolean rootVisible = isRootVisible();
  937. while(!done && paintingEnumerator.hasMoreElements()) {
  938. path = (TreePath)paintingEnumerator.nextElement();
  939. if(path != null) {
  940. isLeaf = treeModel.isLeaf(path.getLastPathComponent());
  941. if(isLeaf)
  942. isExpanded = hasBeenExpanded = false;
  943. else {
  944. isExpanded = treeState.getExpandedState(path);
  945. hasBeenExpanded = tree.hasBeenExpanded(path);
  946. }
  947. bounds = treeState.getBounds(path, boundsBuffer);
  948. if(bounds == null)
  949. // This will only happen if the model changes out
  950. // from under us (usually in another thread).
  951. // Swing isn't multithreaded, but I'll put this
  952. // check in anyway.
  953. return;
  954. bounds.x += insets.left;
  955. bounds.y += insets.top;
  956. // See if the vertical line to the parent has been drawn.
  957. parentPath = path.getParentPath();
  958. if(parentPath != null) {
  959. if(drawingCache.get(parentPath) == null) {
  960. paintVerticalPartOfLeg(g, paintBounds,
  961. insets, parentPath);
  962. drawingCache.put(parentPath, Boolean.TRUE);
  963. }
  964. paintHorizontalPartOfLeg(g, paintBounds, insets,
  965. bounds, path, row,
  966. isExpanded,
  967. hasBeenExpanded, isLeaf);
  968. }
  969. else if(rootVisible && row == 0) {
  970. paintHorizontalPartOfLeg(g, paintBounds, insets,
  971. bounds, path, row,
  972. isExpanded,
  973. hasBeenExpanded, isLeaf);
  974. }
  975. if(shouldPaintExpandControl(path, row, isExpanded,
  976. hasBeenExpanded, isLeaf)) {
  977. paintExpandControl(g, paintBounds, insets, bounds,
  978. path, row, isExpanded,
  979. hasBeenExpanded, isLeaf);
  980. }
  981. paintRow(g, paintBounds, insets, bounds, path,
  982. row, isExpanded, hasBeenExpanded, isLeaf);
  983. if((bounds.y + bounds.height) >= endY)
  984. done = true;
  985. }
  986. else {
  987. done = true;
  988. }
  989. row++;
  990. }
  991. }
  992. }
  993. /**
  994. * Paints the horizontal part of the leg. The reciever should
  995. * NOT modify <code>clipBounds</code>, or <code>insets</code>.<p>
  996. * NOTE: <code>parentRow</code> can be -1 if the root is not visible.
  997. */
  998. protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
  999. Insets insets, Rectangle bounds,
  1000. TreePath path, int row,
  1001. boolean isExpanded,
  1002. boolean hasBeenExpanded, boolean
  1003. isLeaf) {
  1004. int clipLeft = clipBounds.x;
  1005. int clipRight = clipBounds.x + (clipBounds.width - 1);
  1006. int clipTop = clipBounds.y;
  1007. int clipBottom = clipBounds.y + (clipBounds.height - 1);
  1008. int lineY = bounds.y + bounds.height / 2;
  1009. // Offset leftX from parents indent.
  1010. int leftX = bounds.x - totalChildIndent + 8;
  1011. int nodeX = bounds.x - getHorizontalLegBuffer();
  1012. if(lineY > clipTop && lineY < clipBottom && nodeX > clipLeft &&
  1013. leftX < clipRight ) {
  1014. leftX = Math.max(leftX, clipLeft);
  1015. nodeX = Math.min(nodeX, clipRight);
  1016. g.setColor(getHashColor());
  1017. paintHorizontalLine(g, tree, lineY, leftX, nodeX);
  1018. }
  1019. }
  1020. /**
  1021. * Paints the vertical part of the leg. The reciever should
  1022. * NOT modify <code>clipBounds</code>, <code>insets</code>.<p>
  1023. */
  1024. protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
  1025. Insets insets, TreePath path) {
  1026. int lineX = ((path.getPathCount() - 1 + depthOffset) *
  1027. totalChildIndent) + 8 + insets.left;
  1028. int clipLeft = clipBounds.x;
  1029. int clipRight = clipBounds.x + (clipBounds.width - 1);
  1030. if (lineX > clipLeft && lineX < clipRight) {
  1031. int clipTop = clipBounds.y;
  1032. int clipBottom = clipBounds.y + clipBounds.height;
  1033. Rectangle parentBounds = getPathBounds(tree, path);
  1034. Rectangle lastChildBounds = getPathBounds(tree,
  1035. getLastChildPath(path));
  1036. if(lastChildBounds == null)
  1037. // This shouldn't happen, but if the model is modified
  1038. // in another thread it is possible for this to happen.
  1039. // Swing isn't multithreaded, but I'll add this check in
  1040. // anyway.
  1041. return;
  1042. int top;
  1043. if(parentBounds == null) {
  1044. top = Math.max(insets.top + getVerticalLegBuffer(),
  1045. clipTop);
  1046. }
  1047. else
  1048. top = Math.max(parentBounds.y + parentBounds.height +
  1049. getVerticalLegBuffer(), clipTop);
  1050. if(path.getPathCount() == 1 && !isRootVisible()) {
  1051. TreeModel model = getModel();
  1052. if(model != null) {
  1053. Object root = model.getRoot();
  1054. if(model.getChildCount(root) > 0) {
  1055. parentBounds = getPathBounds(tree, path.
  1056. pathByAddingChild(model.getChild(root, 0)));
  1057. if(parentBounds != null)
  1058. top = Math.max(insets.top + getVerticalLegBuffer(),
  1059. parentBounds.y +
  1060. parentBounds.height / 2);
  1061. }
  1062. }
  1063. }
  1064. int bottom = Math.min(lastChildBounds.y +
  1065. (lastChildBounds.height / 2), clipBottom);
  1066. g.setColor(getHashColor());
  1067. paintVerticalLine(g, tree, lineX, top, bottom);
  1068. }
  1069. }
  1070. /**
  1071. * Paints the expand (toggle) part of a row. The reciever should
  1072. * NOT modify <code>clipBounds</code>, or <code>insets</code>.
  1073. */
  1074. protected void paintExpandControl(Graphics g,
  1075. Rectangle clipBounds, Insets insets,
  1076. Rectangle bounds, TreePath path,
  1077. int row, boolean isExpanded,
  1078. boolean hasBeenExpanded,
  1079. boolean isLeaf) {
  1080. Object value = path.getLastPathComponent();
  1081. // Draw icons if not a leaf and either hasn't been loaded,
  1082. // or the model child count is > 0.
  1083. if (!isLeaf && (!hasBeenExpanded ||
  1084. treeModel.getChildCount(value) > 0)) {
  1085. int middleXOfKnob = bounds.x - (getRightChildIndent() - 1);
  1086. int middleYOfKnob = bounds.y + (bounds.height / 2);
  1087. if (isExpanded) {
  1088. Icon expandedIcon = getExpandedIcon();
  1089. if(expandedIcon != null)
  1090. drawCentered(tree, g, expandedIcon, middleXOfKnob,
  1091. middleYOfKnob );
  1092. }
  1093. else {
  1094. Icon collapsedIcon = getCollapsedIcon();
  1095. if(collapsedIcon != null)
  1096. drawCentered(tree, g, collapsedIcon, middleXOfKnob,
  1097. middleYOfKnob);
  1098. }
  1099. }
  1100. }
  1101. /**
  1102. * Paints the renderer part of a row. The reciever should
  1103. * NOT modify <code>clipBounds</code>, or <code>insets</code>.
  1104. */
  1105. protected void paintRow(Graphics g, Rectangle clipBounds,
  1106. Insets insets, Rectangle bounds, TreePath path,
  1107. int row, boolean isExpanded,
  1108. boolean hasBeenExpanded, boolean isLeaf) {
  1109. // Don't paint the renderer if editing this row.
  1110. if(editingComponent != null && editingRow == row)
  1111. return;
  1112. int leadIndex;
  1113. if(tree.hasFocus()) {
  1114. leadIndex = getLeadRow();
  1115. }
  1116. else
  1117. leadIndex = -1;
  1118. Component component;
  1119. component = currentCellRenderer.getTreeCellRendererComponent
  1120. (tree, path.getLastPathComponent(),
  1121. tree.isRowSelected(row), isExpanded, isLeaf, row,
  1122. (leadIndex == row));
  1123. rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
  1124. bounds.width, bounds.height, true);
  1125. }
  1126. /**
  1127. * Returns true if the expand (toggle) control should be drawn for
  1128. * the specified row.
  1129. */
  1130. protected boolean shouldPaintExpandControl(TreePath path, int row,
  1131. boolean isExpanded,
  1132. boolean hasBeenExpanded,
  1133. boolean isLeaf) {
  1134. if(isLeaf)
  1135. return false;
  1136. int depth = path.getPathCount() - 1;
  1137. if((depth == 0 || (depth == 1 && !isRootVisible())) &&
  1138. !getShowsRootHandles())
  1139. return false;
  1140. return true;
  1141. }
  1142. /**
  1143. * Paints a vertical line.
  1144. */
  1145. protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
  1146. int bottom) {
  1147. g.drawLine(x, top, x, bottom);
  1148. }
  1149. /**
  1150. * Paints a horizontal line.
  1151. */
  1152. protected void paintHorizontalLine(Graphics g, JComponent c, int y,
  1153. int left, int right) {
  1154. g.drawLine(left, y, right, y);
  1155. }
  1156. /**
  1157. * The vertical element of legs between nodes starts at the bottom of the
  1158. * parent node by default. This method makes the leg start below that.
  1159. */
  1160. protected int getVerticalLegBuffer() {
  1161. return 0;
  1162. }
  1163. /**
  1164. * The horizontal element of legs between nodes starts at the
  1165. * right of the left-hand side of the child node by default. This
  1166. * method makes the leg end before that.
  1167. */
  1168. protected int getHorizontalLegBuffer() {
  1169. return 0;
  1170. }
  1171. //
  1172. // Generic painting methods
  1173. //
  1174. // Draws the icon centered at (x,y)
  1175. protected void drawCentered(Component c, Graphics graphics, Icon icon,
  1176. int x, int y) {
  1177. icon.paintIcon(c, graphics, x - icon.getIconWidth()/2, y -
  1178. icon.getIconHeight()/2);
  1179. }
  1180. // This method is slow -- revisit when Java2D is ready.
  1181. // assumes x1 <= x2
  1182. protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2){
  1183. // Drawing only even coordinates helps join line segments so they
  1184. // appear as one line. This can be defeated by translating the
  1185. // Graphics by an odd amount.
  1186. x1 += (x1 % 2);
  1187. for (int x = x1; x <= x2; x+=2) {
  1188. g.drawLine(x, y, x, y);
  1189. }
  1190. }
  1191. // This method is slow -- revisit when Java2D is ready.
  1192. // assumes y1 <= y2
  1193. protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) {
  1194. // Drawing only even coordinates helps join line segments so they
  1195. // appear as one line. This can be defeated by translating the
  1196. // Graphics by an odd amount.
  1197. y1 += (y1 % 2);
  1198. for (int y = y1; y <= y2; y+=2) {
  1199. g.drawLine(x, y, x, y);
  1200. }
  1201. }
  1202. //
  1203. // Various local methods
  1204. //
  1205. /**
  1206. * Makes all the nodes that are expanded in JTree expanded in LayoutCache.
  1207. * This invokes updateExpandedDescendants with the root path.
  1208. */
  1209. protected void updateLayoutCacheExpandedNodes() {
  1210. if(treeModel != null)
  1211. updateExpandedDescendants(new TreePath(treeModel.getRoot()));
  1212. }
  1213. /**
  1214. * Updates the expanded state of all the descendants of <code>path</code>
  1215. * by getting the expanded descendants from the tree and forwarding
  1216. * to the tree state.
  1217. */
  1218. protected void updateExpandedDescendants(TreePath path) {
  1219. completeEditing();
  1220. if(treeState != null) {
  1221. treeState.setExpandedState(path, true);
  1222. Enumeration descendants = tree.getExpandedDescendants(path);
  1223. if(descendants != null) {
  1224. while(descendants.hasMoreElements()) {
  1225. path = (TreePath)descendants.nextElement();
  1226. treeState.setExpandedState(path, true);
  1227. }
  1228. }
  1229. updateLeadRow();
  1230. updateSize();
  1231. }
  1232. }
  1233. /**
  1234. * Returns a path to the last child of <code>parent</code>.
  1235. */
  1236. protected TreePath getLastChildPath(TreePath parent) {
  1237. if(treeModel != null) {
  1238. int childCount = treeModel.getChildCount
  1239. (parent.getLastPathComponent());
  1240. if(childCount > 0)
  1241. return parent.pathByAddingChild(treeModel.getChild
  1242. (parent.getLastPathComponent(), childCount - 1));
  1243. }
  1244. return null;
  1245. }
  1246. /**
  1247. * Updates how much each depth should be offset by.
  1248. */
  1249. protected void updateDepthOffset() {
  1250. if(isRootVisible()) {
  1251. if(getShowsRootHandles())
  1252. depthOffset = 1;
  1253. else
  1254. depthOffset = 0;
  1255. }
  1256. else if(!getShowsRootHandles())
  1257. depthOffset = -1;
  1258. else
  1259. depthOffset = 0;
  1260. }
  1261. /**
  1262. * Updates the cellEditor based on the editability of the JTree that
  1263. * we're contained in. If the tree is editable but doesn't have a
  1264. * cellEditor, a basic one will be used.
  1265. */
  1266. protected void updateCellEditor() {
  1267. TreeCellEditor newEditor;
  1268. completeEditing();
  1269. if(tree == null)
  1270. newEditor = null;
  1271. else {
  1272. if(tree.isEditable()) {
  1273. newEditor = tree.getCellEditor();
  1274. if(newEditor == null) {
  1275. newEditor = createDefaultCellEditor();
  1276. if(newEditor != null) {
  1277. tree.setCellEditor(newEditor);
  1278. createdCellEditor = true;
  1279. }
  1280. }
  1281. }
  1282. else
  1283. newEditor = null;
  1284. }
  1285. if(newEditor != cellEditor) {
  1286. if(cellEditor != null && cellEditorListener != null)
  1287. cellEditor.removeCellEditorListener(cellEditorListener);
  1288. cellEditor = newEditor;
  1289. if(cellEditorListener == null)
  1290. cellEditorListener = createCellEditorListener();
  1291. if(newEditor != null && cellEditorListener != null)
  1292. newEditor.addCellEditorListener(cellEditorListener);
  1293. createdCellEditor = false;
  1294. }
  1295. }
  1296. /**
  1297. * Messaged from the tree we're in when the renderer has changed.
  1298. */
  1299. protected void updateRenderer() {
  1300. if(tree != null) {
  1301. TreeCellRenderer newCellRenderer;
  1302. newCellRenderer = tree.getCellRenderer();
  1303. if(newCellRenderer == null) {
  1304. tree.setCellRenderer(createDefaultCellRenderer());
  1305. createdRenderer = true;
  1306. }
  1307. else {
  1308. createdRenderer = false;
  1309. currentCellRenderer = newCellRenderer;
  1310. if(createdCellEditor) {
  1311. tree.setCellEditor(null);
  1312. }
  1313. }
  1314. }
  1315. else {
  1316. createdRenderer = false;
  1317. currentCellRenderer = null;
  1318. }
  1319. updateCellEditor();
  1320. }
  1321. /**
  1322. * Resets the TreeState instance based on the tree we're providing the
  1323. * look and feel for.
  1324. */
  1325. protected void configureLayoutCache() {
  1326. if(treeState != null && tree != null) {
  1327. if(nodeDimensions == null)
  1328. nodeDimensions = createNodeDimensions();
  1329. treeState.setNodeDimensions(nodeDimensions);
  1330. treeState.setRootVisible(tree.isRootVisible());
  1331. treeState.setRowHeight(tree.getRowHeight());
  1332. treeState.setSelectionModel(getSelectionModel());
  1333. // Only do this if necessary, may loss state if call with
  1334. // same model as it currently has.
  1335. if(treeState.getModel() != tree.getModel())
  1336. treeState.setModel(tree.getModel());
  1337. updateLayoutCacheExpandedNodes();
  1338. // Create a listener to update preferred size when bounds
  1339. // changes, if necessary.
  1340. if(isLargeModel()) {
  1341. if(componentListener == null) {
  1342. componentListener = createComponentListener();
  1343. if(componentListener != null)
  1344. tree.addComponentListener(componentListener);
  1345. }
  1346. }
  1347. else if(componentListener != null) {
  1348. tree.removeComponentListener(componentListener);
  1349. componentListener = null;
  1350. }
  1351. }
  1352. else if(componentListener != null) {
  1353. tree.removeComponentListener(componentListener);
  1354. componentListener = null;
  1355. }
  1356. }
  1357. /**
  1358. * Marks the cached size as being invalid, and messages the
  1359. * tree with <code>treeDidChange</code>.
  1360. */
  1361. protected void updateSize() {
  1362. validCachedPreferredSize = false;
  1363. tree.treeDidChange();
  1364. }
  1365. /**
  1366. * Updates the <code>preferredSize</code> instance variable,
  1367. * which is returned from <code>getPreferredSize()</code>.<p>
  1368. * The size is determined from the current AbstractLayoutCache.
  1369. */
  1370. protected void updateCachedPreferredSize() {
  1371. if(treeState != null) {
  1372. Insets i = tree.getInsets();
  1373. if(isLargeModel()) {
  1374. Rectangle visRect = tree.getVisibleRect();
  1375. if(i != null) {
  1376. visRect.x -= i.left;
  1377. visRect.y -= i.top;
  1378. }
  1379. preferredSize.width = treeState.getPreferredWidth(visRect);
  1380. }
  1381. else
  1382. preferredSize.width = treeState.getPreferredWidth(null);
  1383. preferredSize.height = treeState.getPreferredHeight();
  1384. if(i != null) {
  1385. preferredSize.width += i.left + i.right;
  1386. preferredSize.height += i.top + i.bottom;
  1387. }
  1388. }
  1389. validCachedPreferredSize = true;
  1390. }
  1391. /**
  1392. * Messaged from the VisibleTreeNode after it has been expanded.
  1393. */
  1394. protected void pathWasExpanded(TreePath path) {
  1395. if(tree != null) {
  1396. tree.fireTreeExpanded(path);
  1397. }
  1398. }
  1399. /**
  1400. * Messaged from the VisibleTreeNode after it has collapsed.
  1401. */
  1402. protected void pathWasCollapsed(TreePath path) {
  1403. if(tree != null) {
  1404. tree.fireTreeCollapsed(path);
  1405. }
  1406. }
  1407. /**
  1408. * Ensures that the rows identified by beginRow through endRow are
  1409. * visible.
  1410. */
  1411. protected void ensureRowsAreVisible(int beginRow, int endRow) {
  1412. if(tree != null && beginRow >= 0 && endRow < getRowCount(tree)) {
  1413. if(beginRow == endRow) {
  1414. Rectangle scrollBounds = getPathBounds(tree, getPathForRow
  1415. (tree, beginRow));
  1416. if(scrollBounds != null) {
  1417. scrollBounds.width += scrollBounds.x;
  1418. scrollBounds.x = 0;
  1419. tree.scrollRectToVisible(scrollBounds);
  1420. }
  1421. }
  1422. else {
  1423. Rectangle beginRect = getPathBounds(tree, getPathForRow
  1424. (tree, beginRow));
  1425. Rectangle visRect = tree.getVisibleRect();
  1426. Rectangle testRect = beginRect;
  1427. int beginY = beginRect.y;
  1428. int maxY = beginY + visRect.height;
  1429. for(int counter = beginRow + 1; counter <= endRow; counter++) {
  1430. testRect = getPathBounds(tree,
  1431. getPathForRow(tree, counter));
  1432. if((testRect.y + testRect.height) > maxY)
  1433. counter = endRow;
  1434. }
  1435. tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1,
  1436. testRect.y + testRect.height-
  1437. beginY));
  1438. }
  1439. }
  1440. }
  1441. /** Sets the preferred minimum size.
  1442. */
  1443. public void setPreferredMinSize(Dimension newSize) {
  1444. preferredMinSize = newSize;
  1445. }
  1446. /** Returns the minimum preferred size.
  1447. */
  1448. public Dimension getPreferredMinSize() {
  1449. if(preferredMinSize == null)
  1450. return null;
  1451. return new Dimension(preferredMinSize);
  1452. }
  1453. /** Returns the preferred size to properly display the tree,
  1454. * this is a cover method for getPreferredSize(c, false).
  1455. */
  1456. public Dimension getPreferredSize(JComponent c) {
  1457. return getPreferredSize(c, true);
  1458. }
  1459. /** Returns the preferred size to represent the tree in
  1460. * <I>c</I>. If <I>checkConsistancy</I> is true
  1461. * <b>checkConsistancy</b> is messaged first.
  1462. */
  1463. public Dimension getPreferredSize(JComponent c,
  1464. boolean checkConsistancy) {
  1465. Dimension pSize = this.getPreferredMinSize();
  1466. if(!validCachedPreferredSize)
  1467. updateCachedPreferredSize();
  1468. if(tree != null) {
  1469. if(pSize != null)
  1470. return new Dimension(Math.max(pSize.width,
  1471. preferredSize.width),
  1472. Math.max(pSize.height, preferredSize.height));
  1473. return new Dimension(preferredSize.width, preferredSize.height);
  1474. }
  1475. else if(pSize != null)
  1476. return pSize;
  1477. else
  1478. return new Dimension(0, 0);
  1479. }
  1480. /**
  1481. * Returns the minimum size for this component. Which will be
  1482. * the min preferred size or 0, 0.
  1483. */
  1484. public Dimension getMinimumSize(JComponent c) {
  1485. if(this.getPreferredMinSize() != null)
  1486. return this.getPreferredMinSize();
  1487. return new Dimension(0, 0);
  1488. }
  1489. /**
  1490. * Returns the maximum size for this component, which will be the
  1491. * preferred size if the instance is currently in a JTree, or 0, 0.
  1492. */
  1493. public Dimension getMaximumSize(JComponent c) {
  1494. if(tree != null)
  1495. return getPreferredSize(tree);
  1496. if(this.getPreferredMinSize() != null)
  1497. return this.getPreferredMinSize();
  1498. return new Dimension(0, 0);
  1499. }
  1500. /**
  1501. * Messages to stop the editing session. If the UI the receiver
  1502. * is providing the look and feel for returns true from
  1503. * <code>getInvokesStopCellEditing</code>, stopCellEditing will
  1504. * invoked on the current editor. Then completeEditing will
  1505. * be messaged with false, true, false to cancel any lingering
  1506. * editing.
  1507. */
  1508. protected void completeEditing() {
  1509. /* If should invoke stopCellEditing, try that */
  1510. if(tree.getInvokesStopCellEditing() &&
  1511. stopEditingInCompleteEditing && editingComponent != null) {
  1512. cellEditor.stopCellEditing();
  1513. }
  1514. /* Invoke cancelCellEditing, this will do nothing if stopCellEditing
  1515. was succesful. */
  1516. completeEditing(false, true, false);
  1517. }
  1518. /**
  1519. * Stops the editing session. If messageStop is true the editor
  1520. * is messaged with stopEditing, if messageCancel is true the
  1521. * editor is messaged with cancelEditing. If messageTree is true
  1522. * the treeModel is messaged with valueForPathChanged.
  1523. */
  1524. protected void completeEditing(boolean messageStop,
  1525. boolean messageCancel,
  1526. boolean messageTree) {
  1527. if(stopEditingInCompleteEditing && editingComponent != null) {
  1528. Component oldComponent = editingComponent;
  1529. TreePath oldPath = editingPath;
  1530. TreeCellEditor oldEditor = cellEditor;
  1531. Object newValue = oldEditor.getCellEditorValue();
  1532. Rectangle editingBounds = getPathBounds(tree,
  1533. editingPath);
  1534. boolean requestFocus = (tree != null &&
  1535. (tree.hasFocus() || SwingUtilities.
  1536. findFocusOwner(editingComponent) != null));
  1537. editingComponent = null;
  1538. editingPath = null;
  1539. if(messageStop)
  1540. oldEditor.stopCellEditing();
  1541. else if(messageCancel)
  1542. oldEditor.cancelCellEditing();
  1543. tree.remove(oldComponent);
  1544. if(editorHasDifferentSize) {
  1545. treeState.invalidatePathBounds(oldPath);
  1546. updateSize();
  1547. }
  1548. else {
  1549. editingBounds.x = 0;
  1550. editingBounds.width = tree.getSize().width;
  1551. tree.repaint(editingBounds);
  1552. }
  1553. if(requestFocus)
  1554. tree.requestFocus();
  1555. if(messageTree)
  1556. treeModel.valueForPathChanged(oldPath, newValue);
  1557. }
  1558. }
  1559. /**
  1560. * Will start editing for node if there is a cellEditor and
  1561. * shouldSelectCell returns true.<p>
  1562. * This assumes that path is valid and visible.
  1563. */
  1564. protected boolean startEditing(TreePath path, MouseEvent event) {
  1565. completeEditing();
  1566. if(cellEditor != null && tree.isPathEditable(path)) {
  1567. int row = getRowForPath(tree, path);
  1568. editingComponent = cellEditor.getTreeCellEditorComponent
  1569. (tree, path.getLastPathComponent(),
  1570. tree.isPathSelected(path), tree.isExpanded(path),
  1571. treeModel.isLeaf(path.getLastPathComponent()), row);
  1572. if(cellEditor.isCellEditable(event)) {
  1573. Rectangle nodeBounds = getPathBounds(tree, path);
  1574. editingRow = row;
  1575. Dimension editorSize = editingComponent.getPreferredSize();
  1576. // Only allow odd heights if explicitly set.
  1577. if(editorSize.height != nodeBounds.height &&
  1578. getRowHeight() > 0)
  1579. editorSize.height = getRowHeight();
  1580. if(editorSize.width != nodeBounds.width ||
  1581. editorSize.height != nodeBounds.height) {
  1582. // Editor wants different width or height, invalidate
  1583. // treeState and relayout.
  1584. editorHasDifferentSize = true;
  1585. treeState.invalidatePathBounds(path);
  1586. updateSize();
  1587. }
  1588. else
  1589. editorHasDifferentSize = false;
  1590. tree.add(editingComponent);
  1591. editingComponent.setBounds(nodeBounds.x, nodeBounds.y,
  1592. editorSize.width,
  1593. editorSize.height);
  1594. editingPath = path;
  1595. editingComponent.validate();
  1596. Rectangle visRect = tree.getVisibleRect();
  1597. tree.paintImmediately(nodeBounds.x, nodeBounds.y,
  1598. visRect.width + visRect.x - nodeBounds.x,
  1599. editorSize.height);
  1600. if(cellEditor.shouldSelectCell(event)) {
  1601. stopEditingInCompleteEditing = false;
  1602. try {
  1603. tree.setSelectionRow(row);
  1604. } catch (Exception e) {
  1605. System.err.println("Editing exception: " + e);
  1606. }
  1607. stopEditingInCompleteEditing = true;
  1608. }
  1609. if(event != null && event instanceof MouseEvent) {
  1610. /* Find the component that will get forwarded all the
  1611. mouse events until mouseReleased. */
  1612. Point componentPoint = SwingUtilities.convertPoint
  1613. (tree, new Point(event.getX(), event.getY()),
  1614. editingComponent);
  1615. /* Create an instance of BasicTreeMouseListener to handle
  1616. passing the mouse/motion events to the necessary
  1617. component. */
  1618. // We really want similiar behavior to getMouseEventTarget,
  1619. // but it is package private.
  1620. Component activeComponent = SwingUtilities.
  1621. getDeepestComponentAt(editingComponent,
  1622. componentPoint.x, componentPoint.y);
  1623. if (activeComponent != null) {
  1624. new MouseInputHandler(tree, activeComponent, event);
  1625. }
  1626. }
  1627. return true;
  1628. }
  1629. else
  1630. editingComponent = null;
  1631. }
  1632. return false;
  1633. }
  1634. //
  1635. // Following are primarily for handling mouse events.
  1636. //
  1637. /**
  1638. * If the <code>mouseX</code> and <code>mouseY</code> are in the
  1639. * expand/collapse region of the <code>row</code>, this will toggle
  1640. * the row.
  1641. */
  1642. protected void checkForClickInExpandControl(TreePath path,
  1643. int mouseX, int mouseY) {
  1644. if (isLocationInExpandControl(path, mouseX, mouseY)) {
  1645. handleExpandControlClick(path, mouseX, mouseY);
  1646. }
  1647. }
  1648. /**
  1649. * Returns true if <code>mouseX</code> and <code>mouseY</code> fall
  1650. * in the area of row that is used to expand/collapse the node and
  1651. * the node at <code>row</code> does not represent a leaf.
  1652. */
  1653. protected boolean isLocationInExpandControl(TreePath path,
  1654. int mouseX, int mouseY) {
  1655. if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){
  1656. int boxWidth;
  1657. Insets i = tree.getInsets();
  1658. if(getExpandedIcon() != null)
  1659. boxWidth = getExpandedIcon().getIconWidth();
  1660. else
  1661. boxWidth = 8;
  1662. int boxLeftX = (i != null) ? i.left : 0;
  1663. boxLeftX += (((path.getPathCount() + depthOffset - 2) *
  1664. totalChildIndent) + getLeftChildIndent()) -
  1665. boxWidth / 2;
  1666. int boxRightX = boxLeftX + boxWidth;
  1667. return mouseX >= boxLeftX && mouseX <= boxRightX;
  1668. }
  1669. return false;
  1670. }
  1671. /**
  1672. * Messaged when the user clicks the particular row, this invokes
  1673. * toggleExpandState.
  1674. */
  1675. protected void handleExpandControlClick(TreePath path, int mouseX,
  1676. int mouseY) {
  1677. toggleExpandState(path);
  1678. }
  1679. /**
  1680. * Expands path if it is not expanded, or collapses row if it is expanded.
  1681. * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible
  1682. * is invoked to scroll as many of the children to visible as possible
  1683. * (tries to scroll to last visible descendant of path).
  1684. */
  1685. protected void toggleExpandState(TreePath path) {
  1686. if(!tree.isExpanded(path)) {
  1687. int row = getRowForPath(tree, path);
  1688. tree.expandPath(path);
  1689. updateSize();
  1690. if(row != -1) {
  1691. if(tree.getScrollsOnExpand())
  1692. ensureRowsAreVisible(row, row + treeState.
  1693. getVisibleChildCount(path));
  1694. else
  1695. ensureRowsAreVisible(row, row);
  1696. }
  1697. }
  1698. else {
  1699. tree.collapsePath(path);
  1700. updateSize();
  1701. }
  1702. }
  1703. /**
  1704. * Returning true signifies a mouse event on the node should toggle
  1705. * the selection of only the row under mouse.
  1706. */
  1707. protected boolean isToggleSelectionEvent(MouseEvent event) {
  1708. return (SwingUtilities.isLeftMouseButton(event) &&
  1709. event.isControlDown());
  1710. }
  1711. /**
  1712. * Returning true signifies a mouse event on the node should select
  1713. * from the anchor point.
  1714. */
  1715. protected boolean isMultiSelectEvent(MouseEvent event) {
  1716. return (SwingUtilities.isLeftMouseButton(event) &&
  1717. event.isShiftDown());
  1718. }
  1719. /**
  1720. * Returning true indicates the row under the mouse should be toggled
  1721. * based on the event. This is invoked after checkForClickInExpandControl,
  1722. * implying the location is not in the expand (toggle) control
  1723. */
  1724. protected boolean isToggleEvent(MouseEvent event) {
  1725. if(!SwingUtilities.isLeftMouseButton(event)) {
  1726. return false;
  1727. }
  1728. // NOTE: This will be enabled in a future release.
  1729. int clickCount = 2;//tree.getToggleClickCount();
  1730. if(clickCount <= 0) {
  1731. return false;
  1732. }
  1733. return (event.getClickCount() == clickCount);
  1734. }
  1735. /**
  1736. * Messaged to update the selection based on a MouseEvent over a
  1737. * particular row. If the event is a toggle selection event, the
  1738. * row is either selected, or deselected. If the event identifies
  1739. * a multi selection event, the selection is updated from the
  1740. * anchor point. Otherwise the row is selected, and if the event
  1741. * specified a toggle event the row is expanded/collapsed.
  1742. */
  1743. protected void selectPathForEvent(TreePath path, MouseEvent event) {
  1744. // Should this event toggle the selection of this row?
  1745. /* Control toggles just this node. */
  1746. if(isToggleSelectionEvent(event)) {
  1747. if(tree.isPathSelected(path))
  1748. tree.removeSelectionPath(path);
  1749. else
  1750. tree.addSelectionPath(path);
  1751. lastSelectedRow = getRowForPath(tree, path);
  1752. setAnchorPath(path);
  1753. setLeadPath(path);
  1754. }
  1755. /* Adjust from the anchor point. */
  1756. else if(isMultiSelectEvent(event)) {
  1757. TreePath anchor = getAnchorPath();
  1758. int anchorRow = (anchor == null) ? -1 :
  1759. getRowForPath(tree, anchor);
  1760. if(anchorRow == -1) {
  1761. tree.setSelectionPath(path);
  1762. }
  1763. else {
  1764. int row = getRowForPath(tree, path);
  1765. TreePath lastAnchorPath = anchor;
  1766. if(row < anchorRow)
  1767. tree.setSelectionInterval(row, anchorRow);
  1768. else
  1769. tree.setSelectionInterval(anchorRow, row);
  1770. lastSelectedRow = row;
  1771. setAnchorPath(lastAnchorPath);
  1772. }
  1773. }
  1774. /* Otherwise set the selection to just this interval. */
  1775. else if(SwingUtilities.isLeftMouseButton(event)) {
  1776. tree.setSelectionPath(path);
  1777. if(isToggleEvent(event)) {
  1778. toggleExpandState(path);
  1779. }
  1780. }
  1781. }
  1782. /**
  1783. * @return true if the node at <code>row</code> is a leaf.
  1784. */
  1785. protected boolean isLeaf(int row) {
  1786. TreePath path = getPathForRow(tree, row);
  1787. if(path != null)
  1788. return treeModel.isLeaf(path.getLastPathComponent());
  1789. // Have to return something here...
  1790. return true;
  1791. }
  1792. //
  1793. // PENDING(sky): The following selection methods will eventually be
  1794. // moved in TreeSelectionModel or JTree proper. They are put here
  1795. // until new API can be added to account for it.
  1796. //
  1797. private void setAnchorPath(TreePath newPath) {
  1798. anchorPath = newPath;
  1799. }
  1800. private TreePath getAnchorPath() {
  1801. return anchorPath;
  1802. }
  1803. private void setLeadPath(TreePath newPath) {
  1804. setLeadPath(newPath, false);
  1805. }
  1806. private void setLeadPath(TreePath newPath, boolean repaint) {
  1807. Rectangle bounds = repaint ? getPathBounds(tree, leadPath) :
  1808. null;
  1809. leadPath = newPath;
  1810. leadRow = getRowForPath(tree, leadPath);
  1811. if(repaint) {
  1812. if(bounds != null)
  1813. tree.repaint(bounds);
  1814. bounds = getPathBounds(tree, leadPath);
  1815. if(bounds != null)
  1816. tree.repaint(bounds);
  1817. }
  1818. }
  1819. private TreePath getLeadPath() {
  1820. return leadPath;
  1821. }
  1822. private void updateLeadRow() {
  1823. leadRow = getRowForPath(tree, leadPath);
  1824. }
  1825. private int getLeadRow() {
  1826. return leadRow;
  1827. }
  1828. private boolean changeSelectionWithFocus() {
  1829. return changeSelectionWithFocus;
  1830. }
  1831. /**
  1832. * Extends the selection from the anchor to make <code>newLead</code>
  1833. * the lead of the selection. This does not scroll.
  1834. */
  1835. private void extendSelection(TreePath newLead) {
  1836. TreePath aPath = getAnchorPath();
  1837. int aRow = (aPath == null) ? -1 :
  1838. getRowForPath(tree, aPath);
  1839. int newIndex = getRowForPath(tree, newLead);
  1840. if(aRow == -1) {
  1841. tree.setSelectionRow(newIndex);
  1842. }
  1843. else {
  1844. if(aRow < newIndex) {
  1845. tree.setSelectionInterval(aRow, newIndex);
  1846. }
  1847. else {
  1848. tree.setSelectionInterval(newIndex, aRow);
  1849. }
  1850. setAnchorPath(aPath);
  1851. setLeadPath(newLead);
  1852. }
  1853. }
  1854. /**
  1855. * Updates the TreeState in response to nodes expanding/collapsing.
  1856. */
  1857. public class TreeExpansionHandler implements TreeExpansionListener {
  1858. /**
  1859. * Called whenever an item in the tree has been expanded.
  1860. */
  1861. public void treeExpanded(TreeExpansionEvent event) {
  1862. if(event != null && tree != null) {
  1863. TreePath path = event.getPath();
  1864. updateExpandedDescendants(path);
  1865. }
  1866. }
  1867. /**
  1868. * Called whenever an item in the tree has been collapsed.
  1869. */
  1870. public void treeCollapsed(TreeExpansionEvent event) {
  1871. if(event != null && tree != null) {
  1872. TreePath path = event.getPath();
  1873. completeEditing();
  1874. if(path != null && tree.isVisible(path)) {
  1875. treeState.setExpandedState(path, false);
  1876. updateLeadRow();
  1877. updateSize();
  1878. }
  1879. }
  1880. }
  1881. } // BasicTreeUI.TreeExpansionHandler
  1882. /**
  1883. * Updates the preferred size when scrolling (if necessary).
  1884. */
  1885. public class ComponentHandler extends ComponentAdapter implements
  1886. ActionListener {
  1887. /** Timer used when inside a scrollpane and the scrollbar is
  1888. * adjusting. */
  1889. protected Timer timer;
  1890. /** ScrollBar that is being adjusted. */
  1891. protected JScrollBar scrollBar;
  1892. public void componentMoved(ComponentEvent e) {
  1893. if(timer == null) {
  1894. JScrollPane scrollPane = getScrollPane();
  1895. if(scrollPane == null)
  1896. updateSize();
  1897. else {
  1898. scrollBar = scrollPane.getVerticalScrollBar();
  1899. if(scrollBar == null ||
  1900. !scrollBar.getValueIsAdjusting()) {
  1901. // Try the horizontal scrollbar.
  1902. if((scrollBar = scrollPane.getHorizontalScrollBar())
  1903. != null && scrollBar.getValueIsAdjusting())
  1904. startTimer();
  1905. else
  1906. updateSize();
  1907. }
  1908. else
  1909. startTimer();
  1910. }
  1911. }
  1912. }
  1913. /**
  1914. * Creates, if necessary, and starts a Timer to check if need to
  1915. * resize the bounds.
  1916. */
  1917. protected void startTimer() {
  1918. if(timer == null) {
  1919. timer = new Timer(200, this);
  1920. timer.setRepeats(true);
  1921. }
  1922. timer.start();
  1923. }
  1924. /**
  1925. * Returns the JScrollPane housing the JTree, or null if one isn't
  1926. * found.
  1927. */
  1928. protected JScrollPane getScrollPane() {
  1929. Component c = tree.getParent();
  1930. while(c != null && !(c instanceof JScrollPane))
  1931. c = c.getParent();
  1932. if(c instanceof JScrollPane)
  1933. return (JScrollPane)c;
  1934. return null;
  1935. }
  1936. /**
  1937. * Public as a result of Timer. If the scrollBar is null, or
  1938. * not adjusting, this stops the timer and updates the sizing.
  1939. */
  1940. public void actionPerformed(ActionEvent ae) {
  1941. if(scrollBar == null || !scrollBar.getValueIsAdjusting()) {
  1942. if(timer != null)
  1943. timer.stop();
  1944. updateSize();
  1945. timer = null;
  1946. scrollBar = null;
  1947. }
  1948. }
  1949. } // End of BasicTreeUI.ComponentHandler
  1950. /**
  1951. * Forwards all TreeModel events to the TreeState.
  1952. */
  1953. public class TreeModelHandler implements TreeModelListener {
  1954. public void treeNodesChanged(TreeModelEvent e) {
  1955. if(treeState != null && e != null) {
  1956. treeState.treeNodesChanged(e);
  1957. TreePath pPath = e.getTreePath().getParentPath();
  1958. if(pPath == null || treeState.isExpanded(pPath))
  1959. updateSize();
  1960. }
  1961. }
  1962. public void treeNodesInserted(TreeModelEvent e) {
  1963. if(treeState != null && e != null) {
  1964. treeState.treeNodesInserted(e);
  1965. updateLeadRow();
  1966. TreePath path = e.getTreePath();
  1967. if(treeState.isExpanded(path)) {
  1968. updateSize();
  1969. }
  1970. else {
  1971. // PENDING(sky): Need a method in TreeModelEvent
  1972. // that can return the count, getChildIndices allocs
  1973. // a new array!
  1974. int[] indices = e.getChildIndices();
  1975. int childCount = treeModel.getChildCount
  1976. (path.getLastPathComponent());
  1977. if(indices != null && (childCount - indices.length) == 0)
  1978. updateSize();
  1979. }
  1980. }
  1981. }
  1982. public void treeNodesRemoved(TreeModelEvent e) {
  1983. if(treeState != null && e != null) {
  1984. treeState.treeNodesRemoved(e);
  1985. updateLeadRow();
  1986. TreePath path = e.getTreePath();
  1987. if(treeState.isExpanded(path) ||
  1988. treeModel.getChildCount(path.getLastPathComponent()) == 0)
  1989. updateSize();
  1990. }
  1991. }
  1992. public void treeStructureChanged(TreeModelEvent e) {
  1993. if(treeState != null && e != null) {
  1994. treeState.treeStructureChanged(e);
  1995. updateLeadRow();
  1996. TreePath pPath = e.getTreePath().getParentPath();
  1997. if(pPath == null || treeState.isExpanded(pPath))
  1998. updateSize();
  1999. }
  2000. }
  2001. } // End of BasicTreeUI.TreeModelHandler
  2002. /**
  2003. * Listens for changes in the selection model and updates the display
  2004. * accordingly.
  2005. */
  2006. public class TreeSelectionHandler implements TreeSelectionListener {
  2007. /**
  2008. * Messaged when the selection changes in the tree we're displaying
  2009. * for. Stops editing, messages super and displays the changed paths.
  2010. */
  2011. public void valueChanged(TreeSelectionEvent event) {
  2012. // Stop editing
  2013. completeEditing();
  2014. // Make sure all the paths are visible.
  2015. if(treeSelectionModel != null) {
  2016. TreePath[] paths = treeSelectionModel
  2017. .getSelectionPaths();
  2018. if(paths != null) {
  2019. for(int counter = paths.length - 1; counter >= 0;
  2020. counter--) {
  2021. tree.makeVisible(paths[counter]);
  2022. }
  2023. }
  2024. }
  2025. TreePath oldLead = leadPath;
  2026. lastSelectedRow = tree.getMinSelectionRow();
  2027. TreePath lead = tree.getLeadSelectionPath();
  2028. setAnchorPath(lead);
  2029. setLeadPath(lead);
  2030. TreePath[] changedPaths = event.getPaths();
  2031. Rectangle nodeBounds;
  2032. Rectangle visRect = tree.getVisibleRect();
  2033. boolean paintPaths = true;
  2034. int nWidth = tree.getSize().width;
  2035. if(changedPaths != null) {
  2036. int counter, maxCounter = changedPaths.length;
  2037. if(maxCounter > 4) {
  2038. tree.repaint();
  2039. paintPaths = false;
  2040. }
  2041. else {
  2042. for (counter = 0; counter < maxCounter; counter++) {
  2043. nodeBounds = getPathBounds(tree,
  2044. changedPaths[counter]);
  2045. if(nodeBounds != null &&
  2046. visRect.intersects(nodeBounds))
  2047. tree.repaint(0, nodeBounds.y, nWidth,
  2048. nodeBounds.height);
  2049. }
  2050. }
  2051. }
  2052. if(paintPaths) {
  2053. nodeBounds = getPathBounds(tree,
  2054. event.getOldLeadSelectionPath());
  2055. if(nodeBounds != null && visRect.intersects(nodeBounds))
  2056. tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
  2057. nodeBounds = getPathBounds(tree,
  2058. event.getNewLeadSelectionPath());
  2059. if(nodeBounds != null && visRect.intersects(nodeBounds))
  2060. tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
  2061. nodeBounds = getPathBounds(tree, oldLead);
  2062. if(nodeBounds != null && visRect.intersects(nodeBounds))
  2063. tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
  2064. }
  2065. }
  2066. }// End of BasicTreeUI.TreeSelectionHandler
  2067. /**
  2068. * Listener responsible for getting cell editing events and updating
  2069. * the tree accordingly.
  2070. */
  2071. public class CellEditorHandler implements CellEditorListener {
  2072. /** Messaged when editing has stopped in the tree. */
  2073. public void editingStopped(ChangeEvent e) {
  2074. completeEditing(false, false, true);
  2075. }
  2076. /** Messaged when editing has been canceled in the tree. */
  2077. public void editingCanceled(ChangeEvent e) {
  2078. completeEditing(false, false, false);
  2079. }
  2080. } // BasicTreeUI.CellEditorHandler
  2081. /**
  2082. * This is used to get mutliple key down events to appropriately generate
  2083. * events.
  2084. */
  2085. // PENDING(sky): Is this still needed?
  2086. public class KeyHandler extends KeyAdapter {
  2087. /** Key code that is being generated for. */
  2088. protected Action repeatKeyAction;
  2089. /** Set to true while keyPressed is active. */
  2090. protected boolean isKeyDown;
  2091. public void keyPressed(KeyEvent e) {
  2092. if(tree.hasFocus() && tree.isEnabled()) {
  2093. KeyStroke keyStroke = KeyStroke.getKeyStroke
  2094. (e.getKeyCode(), e.getModifiers());
  2095. if(tree.getConditionForKeyStroke(keyStroke) ==
  2096. JComponent.WHEN_FOCUSED) {
  2097. ActionListener listener = tree.
  2098. getActionForKeyStroke(keyStroke);
  2099. if(listener instanceof Action)
  2100. repeatKeyAction = (Action)listener;
  2101. else
  2102. repeatKeyAction = null;
  2103. }
  2104. else
  2105. repeatKeyAction = null;
  2106. if(isKeyDown && repeatKeyAction != null) {
  2107. repeatKeyAction.actionPerformed(null);
  2108. e.consume();
  2109. }
  2110. else
  2111. isKeyDown = true;
  2112. }
  2113. }
  2114. public void keyReleased(KeyEvent e) {
  2115. isKeyDown = false;
  2116. }
  2117. } // End of BasicTreeUI.KeyHandler
  2118. /**
  2119. * Repaints the lead selection row when focus is lost/gained.
  2120. */
  2121. public class FocusHandler implements FocusListener {
  2122. /**
  2123. * Invoked when focus is activated on the tree we're in, redraws the
  2124. * lead row.
  2125. */
  2126. public void focusGained(FocusEvent e) {
  2127. if(tree != null) {
  2128. Rectangle pBounds;
  2129. pBounds = getPathBounds(tree, tree.getLeadSelectionPath());
  2130. if(pBounds != null)
  2131. tree.repaint(pBounds);
  2132. pBounds = getPathBounds(tree, getLeadPath());
  2133. if(pBounds != null)
  2134. tree.repaint(pBounds);
  2135. }
  2136. }
  2137. /**
  2138. * Invoked when focus is activated on the tree we're in, redraws the
  2139. * lead row.
  2140. */
  2141. public void focusLost(FocusEvent e) {
  2142. focusGained(e);
  2143. }
  2144. } // End of class BasicTreeUI.FocusHandler
  2145. /**
  2146. * Class responsible for getting size of node, method is forwarded
  2147. * to BasicTreeUI method. X location does not include insets, that is
  2148. * handled in getPathBounds.
  2149. */
  2150. // This returns locations that don't include any Insets.
  2151. public class NodeDimensionsHandler extends
  2152. AbstractLayoutCache.NodeDimensions {
  2153. /**
  2154. * Responsible for getting the size of a particular node.
  2155. */
  2156. public Rectangle getNodeDimensions(Object value, int row,
  2157. int depth, boolean expanded,
  2158. Rectangle size) {
  2159. // Return size of editing component, if editing and asking
  2160. // for editing row.
  2161. if(editingComponent != null && editingRow == row) {
  2162. Dimension prefSize = editingComponent.
  2163. getPreferredSize();
  2164. int rh = getRowHeight();
  2165. if(rh > 0 && rh != prefSize.height)
  2166. prefSize.height = rh;
  2167. if(size != null) {
  2168. size.x = getRowX(row, depth);
  2169. size.width = prefSize.width;
  2170. size.height = prefSize.height;
  2171. }
  2172. else {
  2173. size = new Rectangle(getRowX(row, depth), 0,
  2174. prefSize.width, prefSize.height);
  2175. }
  2176. return size;
  2177. }
  2178. // Not editing, use renderer.
  2179. if(currentCellRenderer != null) {
  2180. Component aComponent;
  2181. aComponent = currentCellRenderer.getTreeCellRendererComponent
  2182. (tree, value, tree.isRowSelected(row),
  2183. expanded, treeModel.isLeaf(value), row,
  2184. false);
  2185. if(tree != null) {
  2186. // Only ever removed when UI changes, this is OK!
  2187. rendererPane.add(aComponent);
  2188. aComponent.validate();
  2189. }
  2190. Dimension prefSize = aComponent.getPreferredSize();
  2191. if(size != null) {
  2192. size.x = getRowX(row, depth);
  2193. size.width = prefSize.width;
  2194. size.height = prefSize.height;
  2195. }
  2196. else {
  2197. size = new Rectangle(getRowX(row, depth), 0,
  2198. prefSize.width, prefSize.height);
  2199. }
  2200. return size;
  2201. }
  2202. return null;
  2203. }
  2204. /**
  2205. * @return amount to indent the given row.
  2206. */
  2207. protected int getRowX(int row, int depth) {
  2208. return totalChildIndent * (depth + depthOffset);
  2209. }
  2210. } // End of class BasicTreeUI.NodeDimensionsHandler
  2211. /**
  2212. * TreeMouseListener is responsible for updating the selection
  2213. * based on mouse events.
  2214. */
  2215. public class MouseHandler extends MouseAdapter {
  2216. /**
  2217. * Invoked when a mouse button has been pressed on a component.
  2218. */
  2219. public void mousePressed(MouseEvent e) {
  2220. if(tree != null && tree.isEnabled()) {
  2221. tree.requestFocus();
  2222. TreePath path = getClosestPathForLocation(tree, e.getX(),
  2223. e.getY());
  2224. if(path != null) {
  2225. Rectangle bounds = getPathBounds(tree, path);
  2226. if(e.getY() > (bounds.y + bounds.height)) {
  2227. return;
  2228. }
  2229. // Preferably checkForClickInExpandControl could take
  2230. // the Event to do this it self!
  2231. if(SwingUtilities.isLeftMouseButton(e))
  2232. checkForClickInExpandControl(path, e.getX(), e.getY());
  2233. int x = e.getX();
  2234. // Perhaps they clicked the cell itself. If so,
  2235. // select it.
  2236. if (x > bounds.x) {
  2237. if (x <= (bounds.x + bounds.width) &&
  2238. !startEditing(path, e)) {
  2239. selectPathForEvent(path, e);
  2240. }
  2241. }
  2242. }
  2243. }
  2244. // PENDING: Should select on mouse down, start a drag if
  2245. // the mouse moves, and fire selection change notice on
  2246. // mouse up. That is, the explorer highlights on mouse
  2247. // down, but doesn't update the pane to the right (and
  2248. // open the folder icon) until mouse up.
  2249. }
  2250. } // End of BasicTreeUI.MouseHandler
  2251. /**
  2252. * PropertyChangeListener for the tree. Updates the appropriate
  2253. * varaible, or TreeState, based on what changes.
  2254. */
  2255. public class PropertyChangeHandler implements
  2256. PropertyChangeListener {
  2257. public void propertyChange(PropertyChangeEvent event) {
  2258. if(event.getSource() == tree) {
  2259. String changeName = event.getPropertyName();
  2260. if(changeName.equals(JTree.CELL_RENDERER_PROPERTY)) {
  2261. setCellRenderer((TreeCellRenderer)event.getNewValue());
  2262. }
  2263. else if(changeName.equals(JTree.TREE_MODEL_PROPERTY)) {
  2264. setModel((TreeModel)event.getNewValue());
  2265. }
  2266. else if(changeName.equals(JTree.ROOT_VISIBLE_PROPERTY)) {
  2267. setRootVisible(((Boolean)event.getNewValue()).
  2268. booleanValue());
  2269. }
  2270. else if(changeName.equals(JTree.SHOWS_ROOT_HANDLES_PROPERTY)) {
  2271. setShowsRootHandles(((Boolean)event.getNewValue()).
  2272. booleanValue());
  2273. }
  2274. else if(changeName.equals(JTree.ROW_HEIGHT_PROPERTY)) {
  2275. setRowHeight(((Integer)event.getNewValue()).
  2276. intValue());
  2277. }
  2278. else if(changeName.equals(JTree.CELL_EDITOR_PROPERTY)) {
  2279. setCellEditor((TreeCellEditor)event.getNewValue());
  2280. }
  2281. else if(changeName.equals(JTree.EDITABLE_PROPERTY)) {
  2282. setEditable(((Boolean)event.getNewValue()).booleanValue());
  2283. }
  2284. else if(changeName.equals(JTree.LARGE_MODEL_PROPERTY)) {
  2285. setLargeModel(tree.isLargeModel());
  2286. }
  2287. else if(changeName.equals(JTree.SELECTION_MODEL_PROPERTY)) {
  2288. setSelectionModel(tree.getSelectionModel());
  2289. }
  2290. else if(changeName.equals("font")) {
  2291. completeEditing();
  2292. if(treeState != null)
  2293. treeState.invalidateSizes();
  2294. updateSize();
  2295. }
  2296. }
  2297. }
  2298. } // End of BasicTreeUI.PropertyChangeHandler
  2299. /**
  2300. * Listener on the TreeSelectionModel, resets the row selection if
  2301. * any of the properties of the model change.
  2302. */
  2303. public class SelectionModelPropertyChangeHandler implements
  2304. PropertyChangeListener {
  2305. public void propertyChange(PropertyChangeEvent event) {
  2306. if(event.getSource() == treeSelectionModel)
  2307. treeSelectionModel.resetRowSelection();
  2308. }
  2309. } // End of BasicTreeUI.SelectionModelPropertyChangeHandler
  2310. /**
  2311. * TreeTraverseAction is the action used for left/right keys.
  2312. * Will toggle the expandedness of a node, as well as potentially
  2313. * incrementing the selection.
  2314. */
  2315. public class TreeTraverseAction extends AbstractAction {
  2316. /** Determines direction to traverse, 1 means expand, -1 means
  2317. * collapse. */
  2318. protected int direction;
  2319. /** True if the selection is reset, false means only the lead path
  2320. * changes. */
  2321. private boolean changeSelection;
  2322. public TreeTraverseAction(int direction, String name) {
  2323. this(direction, name, true);
  2324. }
  2325. private TreeTraverseAction(int direction, String name,
  2326. boolean changeSelection) {
  2327. this.direction = direction;
  2328. this.changeSelection = changeSelection;
  2329. }
  2330. public void actionPerformed(ActionEvent e) {
  2331. int rowCount;
  2332. if(tree != null && (rowCount = getRowCount(tree)) > 0) {
  2333. int minSelIndex = getLeadRow();
  2334. int newIndex;
  2335. if(minSelIndex == -1)
  2336. newIndex = 0;
  2337. else {
  2338. /* Try and expand the node, otherwise go to next
  2339. node. */
  2340. if(direction == 1) {
  2341. if(!isLeaf(minSelIndex) &&
  2342. !tree.isExpanded(minSelIndex)) {
  2343. toggleExpandState(getPathForRow
  2344. (tree, minSelIndex));
  2345. newIndex = -1;
  2346. }
  2347. else
  2348. newIndex = Math.min(minSelIndex + 1, rowCount - 1);
  2349. }
  2350. /* Try to collapse node. */
  2351. else {
  2352. if(!isLeaf(minSelIndex) &&
  2353. tree.isExpanded(minSelIndex)) {
  2354. toggleExpandState(getPathForRow
  2355. (tree, minSelIndex));
  2356. newIndex = -1;
  2357. }
  2358. else {
  2359. TreePath path = getPathForRow(tree,
  2360. minSelIndex);
  2361. if(path != null && path.getPathCount() > 1) {
  2362. newIndex = getRowForPath(tree, path.
  2363. getParentPath());
  2364. }
  2365. else
  2366. newIndex = -1;
  2367. }
  2368. }
  2369. }
  2370. if(newIndex != -1) {
  2371. if(changeSelection) {
  2372. tree.setSelectionInterval(newIndex, newIndex);
  2373. }
  2374. else {
  2375. setLeadPath(getPathForRow(tree, newIndex), true);
  2376. }
  2377. ensureRowsAreVisible(newIndex, newIndex);
  2378. }
  2379. }
  2380. }
  2381. public boolean isEnabled() { return (tree != null &&
  2382. tree.isEnabled()); }
  2383. } // BasicTreeUI.TreeTraverseAction
  2384. /** TreePageAction handles page up and page down events.
  2385. */
  2386. public class TreePageAction extends AbstractAction {
  2387. /** Specifies the direction to adjust the selection by. */
  2388. protected int direction;
  2389. /** True indicates should set selection from anchor path. */
  2390. private boolean addToSelection;
  2391. private boolean changeSelection;
  2392. public TreePageAction(int direction, String name) {
  2393. this(direction, name, false, true);
  2394. }
  2395. private TreePageAction(int direction, String name,
  2396. boolean addToSelection,
  2397. boolean changeSelection) {
  2398. this.direction = direction;
  2399. this.addToSelection = addToSelection;
  2400. this.changeSelection = changeSelection;
  2401. }
  2402. public void actionPerformed(ActionEvent e) {
  2403. int rowCount;
  2404. if(tree != null && (rowCount = getRowCount(tree)) > 0 &&
  2405. treeSelectionModel != null) {
  2406. Dimension maxSize = tree.getSize();
  2407. TreePath lead = getLeadPath();
  2408. TreePath newPath;
  2409. Rectangle visRect = tree.getVisibleRect();
  2410. if(direction == -1) {
  2411. // up.
  2412. newPath = getClosestPathForLocation(tree, visRect.x,
  2413. visRect.y);
  2414. if(newPath.equals(lead)) {
  2415. visRect.y = Math.max(0, visRect.y - visRect.height);
  2416. newPath = tree.getClosestPathForLocation(visRect.x,
  2417. visRect.y);
  2418. }
  2419. }
  2420. else {
  2421. // down
  2422. visRect.y = Math.min(maxSize.height, visRect.y +
  2423. visRect.height - 1);
  2424. newPath = tree.getClosestPathForLocation(visRect.x,
  2425. visRect.y);
  2426. if(newPath.equals(lead)) {
  2427. visRect.y = Math.min(maxSize.height, visRect.y +
  2428. visRect.height - 1);
  2429. newPath = tree.getClosestPathForLocation(visRect.x,
  2430. visRect.y);
  2431. }
  2432. }
  2433. Rectangle newRect = getPathBounds(tree, newPath);
  2434. newRect.x = visRect.x;
  2435. newRect.width = visRect.width;
  2436. if(direction == -1) {
  2437. newRect.height = visRect.height;
  2438. }
  2439. else {
  2440. newRect.y -= (visRect.height - newRect.height);
  2441. newRect.height = visRect.height;
  2442. }
  2443. if(addToSelection) {
  2444. extendSelection(newPath);
  2445. }
  2446. else if(changeSelection) {
  2447. tree.setSelectionPath(newPath);
  2448. }
  2449. else {
  2450. setLeadPath(newPath, true);
  2451. }
  2452. tree.scrollRectToVisible(newRect);
  2453. }
  2454. }
  2455. public boolean isEnabled() { return (tree != null &&
  2456. tree.isEnabled()); }
  2457. } // BasicTreeUI.TreePageAction
  2458. /**
  2459. * Scrolls the tree left/right the visible width of the tree. Will select
  2460. * either the first/last visible node.
  2461. */
  2462. // Will be made public later.
  2463. private class TreeScrollLRAction extends AbstractAction {
  2464. /** Specifies the direction to adjust the selection by. */
  2465. protected int direction;
  2466. private boolean addToSelection;
  2467. public TreeScrollLRAction(int direction, String name,
  2468. boolean addToSelection) {
  2469. this.direction = direction;
  2470. this.addToSelection = addToSelection;
  2471. }
  2472. public void actionPerformed(ActionEvent e) {
  2473. int rowCount;
  2474. if(tree != null && (rowCount = getRowCount(tree)) > 0 &&
  2475. treeSelectionModel != null) {
  2476. TreePath newPath;
  2477. Rectangle visRect = tree.getVisibleRect();
  2478. if (direction == -1) {
  2479. newPath = getClosestPathForLocation(tree, visRect.x,
  2480. visRect.y);
  2481. visRect.x = Math.max(0, visRect.x - visRect.width);
  2482. }
  2483. else {
  2484. visRect.x = Math.min(Math.max(0, tree.getWidth() -
  2485. visRect.width), visRect.x + visRect.width);
  2486. newPath = getClosestPathForLocation(tree, visRect.x,
  2487. visRect.y + visRect.height);
  2488. }
  2489. // Scroll
  2490. tree.scrollRectToVisible(visRect);
  2491. // select
  2492. if (addToSelection) {
  2493. extendSelection(newPath);
  2494. }
  2495. else if(changeSelectionWithFocus()) {
  2496. tree.setSelectionPath(newPath);
  2497. }
  2498. else {
  2499. setLeadPath(newPath, true);
  2500. }
  2501. }
  2502. }
  2503. public boolean isEnabled() { return (tree != null &&
  2504. tree.isEnabled()); }
  2505. } // End of BasicTreeUI.TreeScrollLRAction
  2506. /** TreeIncrementAction is used to handle up/down actions. Selection
  2507. * is moved up or down based on direction.
  2508. */
  2509. public class TreeIncrementAction extends AbstractAction {
  2510. /** Specifies the direction to adjust the selection by. */
  2511. protected int direction;
  2512. /** If true the new item is added to the selection, if false the
  2513. * selection is reset. */
  2514. private boolean addToSelection;
  2515. private boolean changeSelection;
  2516. public TreeIncrementAction(int direction, String name) {
  2517. this(direction, name, false, true);
  2518. }
  2519. private TreeIncrementAction(int direction, String name,
  2520. boolean addToSelection,
  2521. boolean changeSelection) {
  2522. this.direction = direction;
  2523. this.addToSelection = addToSelection;
  2524. this.changeSelection = changeSelection;
  2525. }
  2526. public void actionPerformed(ActionEvent e) {
  2527. int rowCount;
  2528. if(tree != null && treeSelectionModel != null &&
  2529. (rowCount = getRowCount(tree)) > 0) {
  2530. int selIndex = getLeadRow();
  2531. int newIndex;
  2532. if(selIndex == -1) {
  2533. if(direction == 1)
  2534. newIndex = 0;
  2535. else
  2536. newIndex = rowCount - 1;
  2537. }
  2538. else
  2539. /* Aparently people don't like wrapping;( */
  2540. newIndex = Math.min(rowCount - 1, Math.max
  2541. (0, (selIndex + direction)));
  2542. if(addToSelection) {
  2543. extendSelection(getPathForRow(tree, newIndex));
  2544. }
  2545. else if(changeSelection) {
  2546. tree.setSelectionInterval(newIndex, newIndex);
  2547. }
  2548. else {
  2549. setLeadPath(getPathForRow(tree, newIndex), true);
  2550. }
  2551. ensureRowsAreVisible(newIndex, newIndex);
  2552. lastSelectedRow = newIndex;
  2553. }
  2554. }
  2555. public boolean isEnabled() { return (tree != null &&
  2556. tree.isEnabled()); }
  2557. } // End of class BasicTreeUI.TreeIncrementAction
  2558. /**
  2559. * TreeHomeAction is used to handle end/home actions.
  2560. * Scrolls either the first or last cell to be visible based on
  2561. * direction.
  2562. */
  2563. public class TreeHomeAction extends AbstractAction {
  2564. protected int direction;
  2565. /** Set to true if append to selection. */
  2566. private boolean addToSelection;
  2567. private boolean changeSelection;
  2568. public TreeHomeAction(int direction, String name) {
  2569. this(direction, name, false, true);
  2570. }
  2571. private TreeHomeAction(int direction, String name,
  2572. boolean addToSelection,
  2573. boolean changeSelection) {
  2574. this.direction = direction;
  2575. this.changeSelection = changeSelection;
  2576. this.addToSelection = addToSelection;
  2577. }
  2578. public void actionPerformed(ActionEvent e) {
  2579. int rowCount = getRowCount(tree);
  2580. if(tree != null && rowCount > 0) {
  2581. if(direction == -1) {
  2582. ensureRowsAreVisible(0, 0);
  2583. if (addToSelection) {
  2584. TreePath aPath = getAnchorPath();
  2585. int aRow = (aPath == null) ? -1 :
  2586. getRowForPath(tree, aPath);
  2587. if (aRow == -1) {
  2588. tree.setSelectionInterval(0, 0);
  2589. }
  2590. else {
  2591. tree.setSelectionInterval(0, aRow);
  2592. setAnchorPath(aPath);
  2593. setLeadPath(getPathForRow(tree, 0));
  2594. }
  2595. }
  2596. else if(changeSelection) {
  2597. tree.setSelectionInterval(0, 0);
  2598. }
  2599. else {
  2600. setLeadPath(getPathForRow(tree, 0), true);
  2601. }
  2602. }
  2603. else {
  2604. ensureRowsAreVisible(rowCount - 1, rowCount - 1);
  2605. if (addToSelection) {
  2606. TreePath aPath = getAnchorPath();
  2607. int aRow = (aPath == null) ? -1 :
  2608. getRowForPath(tree, aPath);
  2609. if (aRow == -1) {
  2610. tree.setSelectionInterval(rowCount - 1,
  2611. rowCount -1);
  2612. }
  2613. else {
  2614. tree.setSelectionInterval(aRow, rowCount - 1);
  2615. setAnchorPath(aPath);
  2616. setLeadPath(getPathForRow(tree, rowCount -1));
  2617. }
  2618. }
  2619. else if(changeSelection) {
  2620. tree.setSelectionInterval(rowCount - 1, rowCount - 1);
  2621. }
  2622. else {
  2623. setLeadPath(getPathForRow(tree, rowCount - 1), true);
  2624. }
  2625. }
  2626. }
  2627. }
  2628. public boolean isEnabled() { return (tree != null &&
  2629. tree.isEnabled()); }
  2630. } // End of class BasicTreeUI.TreeHomeAction
  2631. /**
  2632. * For the first selected row expandedness will be toggled.
  2633. */
  2634. public class TreeToggleAction extends AbstractAction {
  2635. public TreeToggleAction(String name) {
  2636. }
  2637. public void actionPerformed(ActionEvent e) {
  2638. if(tree != null) {
  2639. int selRow = getLeadRow();
  2640. if(selRow != -1 && !isLeaf(selRow)) {
  2641. TreePath aPath = getAnchorPath();
  2642. TreePath lPath = getLeadPath();
  2643. toggleExpandState(getPathForRow(tree, selRow));
  2644. setAnchorPath(aPath);
  2645. setLeadPath(lPath);
  2646. }
  2647. }
  2648. }
  2649. public boolean isEnabled() { return (tree != null &&
  2650. tree.isEnabled()); }
  2651. } // End of class BasicTreeUI.TreeToggleAction
  2652. /**
  2653. * Scrolls the component it is created with a specified amount.
  2654. */
  2655. private static class ScrollAction implements ActionListener {
  2656. private JComponent component;
  2657. private int direction;
  2658. private int amount;
  2659. public ScrollAction(JComponent component, int direction, int amount) {
  2660. this.component = component;
  2661. this.direction = direction;
  2662. this.amount = amount;
  2663. }
  2664. public void actionPerformed(ActionEvent e) {
  2665. Rectangle visRect = component.getVisibleRect();
  2666. Dimension size = component.getSize();
  2667. if (direction == SwingConstants.HORIZONTAL) {
  2668. visRect.x += amount;
  2669. visRect.x = Math.max(0, visRect.x);
  2670. visRect.x = Math.min(Math.max(0, size.width - visRect.width),
  2671. visRect.x);
  2672. }
  2673. else {
  2674. visRect.y += amount;
  2675. visRect.y = Math.max(0, visRect.y);
  2676. visRect.y = Math.min(Math.max(0, size.width - visRect.height),
  2677. visRect.y);
  2678. }
  2679. component.scrollRectToVisible(visRect);
  2680. }
  2681. }
  2682. /**
  2683. * ActionListener that invokes cancelEditing when action performed.
  2684. */
  2685. public class TreeCancelEditingAction extends AbstractAction {
  2686. public TreeCancelEditingAction(String name) {
  2687. }
  2688. public void actionPerformed(ActionEvent e) {
  2689. if(tree != null) {
  2690. tree.cancelEditing();
  2691. }
  2692. }
  2693. public boolean isEnabled() { return (tree != null &&
  2694. tree.isEnabled()); }
  2695. } // End of class BasicTreeUI.TreeCancelEditingAction
  2696. /**
  2697. * ActionListener invoked to start editing on the leadPath.
  2698. */
  2699. private class TreeEditAction extends AbstractAction {
  2700. public TreeEditAction(String name) {
  2701. }
  2702. public void actionPerformed(ActionEvent ae) {
  2703. if(tree != null && tree.isEnabled()) {
  2704. TreePath lead = getLeadPath();
  2705. int editRow = (lead != null) ?
  2706. getRowForPath(tree, lead) : -1;
  2707. if(editRow != -1) {
  2708. tree.startEditingAtPath(lead);
  2709. }
  2710. }
  2711. }
  2712. public boolean isEnabled() { return (tree != null &&
  2713. tree.isEnabled()); }
  2714. } // End of BasicTreeUI.TreeEditAction
  2715. /**
  2716. * Action to select everything in tree.
  2717. */
  2718. private class TreeSelectAllAction extends AbstractAction {
  2719. private boolean selectAll;
  2720. public TreeSelectAllAction(String name, boolean selectAll) {
  2721. this.selectAll = selectAll;
  2722. }
  2723. public void actionPerformed(ActionEvent ae) {
  2724. int rowCount = getRowCount(tree);
  2725. if(tree != null && rowCount > 0) {
  2726. if(selectAll) {
  2727. TreePath lastPath = getLeadPath();
  2728. TreePath aPath = getAnchorPath();
  2729. if(lastPath != null && !tree.isVisible(lastPath)) {
  2730. lastPath = null;
  2731. }
  2732. tree.setSelectionInterval(0, rowCount - 1);
  2733. if(lastPath != null) {
  2734. setLeadPath(lastPath);
  2735. }
  2736. if(aPath != null && tree.isVisible(aPath)) {
  2737. setAnchorPath(aPath);
  2738. }
  2739. else if(lastPath != null) {
  2740. setAnchorPath(lastPath);
  2741. }
  2742. }
  2743. else {
  2744. TreePath lastPath = getLeadPath();
  2745. TreePath aPath = getAnchorPath();
  2746. tree.clearSelection();
  2747. setAnchorPath(aPath);
  2748. setLeadPath(lastPath);
  2749. }
  2750. }
  2751. }
  2752. public boolean isEnabled() { return (tree != null &&
  2753. tree.isEnabled()); }
  2754. } // End of BasicTreeUI.TreeSelectAllAction
  2755. /**
  2756. * Action to select everything in tree.
  2757. */
  2758. private class TreeAddSelectionAction extends AbstractAction {
  2759. private boolean changeAnchor;
  2760. public TreeAddSelectionAction(String name, boolean changeAnchor) {
  2761. this.changeAnchor = changeAnchor;
  2762. }
  2763. public void actionPerformed(ActionEvent ae) {
  2764. int rowCount = getRowCount(tree);
  2765. if(tree != null && rowCount > 0) {
  2766. int lead = getLeadRow();
  2767. TreePath aPath = getAnchorPath();
  2768. TreePath lPath = getLeadPath();
  2769. if(lead == -1) {
  2770. lead = 0;
  2771. }
  2772. if(!changeAnchor) {
  2773. if(tree.isRowSelected(lead)) {
  2774. tree.removeSelectionRow(lead);
  2775. setLeadPath(lPath);
  2776. }
  2777. else {
  2778. tree.addSelectionRow(lead);
  2779. }
  2780. setAnchorPath(aPath);
  2781. }
  2782. else {
  2783. tree.setSelectionRow(lead);
  2784. }
  2785. }
  2786. }
  2787. public boolean isEnabled() { return (tree != null &&
  2788. tree.isEnabled()); }
  2789. } // End of BasicTreeUI.TreeAddSelectionAction
  2790. /**
  2791. * Action to select everything in tree.
  2792. */
  2793. private class TreeExtendSelectionAction extends AbstractAction {
  2794. public TreeExtendSelectionAction(String name) {
  2795. }
  2796. public void actionPerformed(ActionEvent ae) {
  2797. if(tree != null && getRowCount(tree) > 0) {
  2798. int lead = getLeadRow();
  2799. if(lead != -1) {
  2800. TreePath leadP = getLeadPath();
  2801. TreePath aPath = getAnchorPath();
  2802. int aRow = getRowForPath(tree, aPath);
  2803. if(aRow == -1)
  2804. aRow = 0;
  2805. tree.setSelectionInterval(aRow, lead);
  2806. setLeadPath(leadP);
  2807. setAnchorPath(aPath);
  2808. }
  2809. }
  2810. }
  2811. public boolean isEnabled() { return (tree != null &&
  2812. tree.isEnabled()); }
  2813. } // End of BasicTreeUI.TreeExtendSelectionAction
  2814. /**
  2815. * MouseInputHandler handles passing all mouse events,
  2816. * including mouse motion events, until the mouse is released to
  2817. * the destination it is constructed with. It is assumed all the
  2818. * events are currently target at source.
  2819. */
  2820. // PENDING(sky): this could actually be moved into a general
  2821. // location, no reason to be in here.
  2822. public class MouseInputHandler extends Object implements
  2823. MouseInputListener
  2824. {
  2825. /** Source that events are coming from. */
  2826. protected Component source;
  2827. /** Destination that recieves all events. */
  2828. protected Component destination;
  2829. public MouseInputHandler(Component source, Component destination,
  2830. MouseEvent event){
  2831. this.source = source;
  2832. this.destination = destination;
  2833. this.source.addMouseListener(this);
  2834. this.source.addMouseMotionListener(this);
  2835. /* Dispatch the editing event! */
  2836. destination.dispatchEvent(SwingUtilities.convertMouseEvent
  2837. (source, event, destination));
  2838. }
  2839. public void mouseClicked(MouseEvent e) {
  2840. if(destination != null)
  2841. destination.dispatchEvent(SwingUtilities.convertMouseEvent
  2842. (source, e, destination));
  2843. }
  2844. public void mousePressed(MouseEvent e) {
  2845. }
  2846. public void mouseReleased(MouseEvent e) {
  2847. if(destination != null)
  2848. destination.dispatchEvent(SwingUtilities.convertMouseEvent
  2849. (source, e, destination));
  2850. removeFromSource();
  2851. }
  2852. public void mouseEntered(MouseEvent e) {
  2853. if (!SwingUtilities.isLeftMouseButton(e)) {
  2854. removeFromSource();
  2855. }
  2856. }
  2857. public void mouseExited(MouseEvent e) {
  2858. if (!SwingUtilities.isLeftMouseButton(e)) {
  2859. removeFromSource();
  2860. }
  2861. }
  2862. public void mouseDragged(MouseEvent e) {
  2863. if(destination != null)
  2864. destination.dispatchEvent(SwingUtilities.convertMouseEvent
  2865. (source, e, destination));
  2866. }
  2867. public void mouseMoved(MouseEvent e) {
  2868. removeFromSource();
  2869. }
  2870. protected void removeFromSource() {
  2871. if(source != null) {
  2872. source.removeMouseListener(this);
  2873. source.removeMouseMotionListener(this);
  2874. }
  2875. source = destination = null;
  2876. }
  2877. } // End of class BasicTreeUI.MouseInputHandler
  2878. } // End of class BasicTreeUI