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();