1. /*
  2. * @(#)BasicTreeUI.java 1.122 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.plaf.basic;
  11. import javax.swing.*;
  12. import javax.swing.event.*;
  13. import javax.swing.text.DefaultTextUI;
  14. import java.awt.*;
  15. import java.awt.event.*;
  16. import java.beans.*;
  17. import java.io.*;
  18. import java.util.Enumeration;
  19. import java.util.Hashtable;
  20. import javax.swing.plaf.ActionMapUIResource;
  21. import javax.swing.plaf.ComponentUI;
  22. import javax.swing.plaf.UIResource;
  23. import javax.swing.plaf.TreeUI;
  24. import javax.swing.tree.*;
  25. /**
  26. * The basic L&F for a hierarchical data structure.
  27. * <p>
  28. *
  29. * @version 1.113 07/20/99
  30. * @author Scott Violet
  31. */
  32. public class BasicTreeUI extends TreeUI
  33. {
  34. static private final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
  35. transient protected Icon collapsedIcon;
  36. transient protected Icon expandedIcon;
  37. /** Color used to draw hash marks. If null no hash marks will be
  38. * drawn. */
  39. private Color hashColor;
  40. /** Distance between left margin and where verical dashes will be
  41. * drawn. */
  42. protected int leftChildIndent;
  43. /** Distance to add to leftChildIndent to determine where cell
  44. * contents will be drawn. */
  45. protected int rightChildIndent;
  46. /** Total distance that will be indented. The sum of leftChildIndent
  47. * and rightChildIndent. */
  48. protected int totalChildIndent;
  49. /** Minimum preferred size. */
  50. protected Dimension preferredMinSize;
  51. /** Index of the row that was last selected. */
  52. protected int lastSelectedRow;
  53. /** Component that we're going to be drawing into. */
  54. protected JTree tree;
  55. /** Renderer that is being used to do the actual cell drawing. */
  56. transient protected TreeCellRenderer currentCellRenderer;
  57. /** Set to true if the renderer that is currently in the tree was
  58. * created by this instance. */
  59. protected boolean createdRenderer;
  60. /** Editor for the tree. */
  61. transient protected TreeCellEditor cellEditor;
  62. /** Set to true if editor that is currently in the tree was
  63. * created by this instance. */
  64. protected boolean createdCellEditor;
  65. /** Set to false when editing and shouldSelectCell() returns true meaning
  66. * the node should be selected before editing, used in completeEditing. */
  67. protected boolean stopEditingInCompleteEditing;
  68. /** Used to paint the TreeCellRenderer. */
  69. protected CellRendererPane rendererPane;
  70. /** Size needed to completely display all the nodes. */
  71. protected Dimension preferredSize;
  72. /** Is the preferredSize valid? */
  73. protected boolean validCachedPreferredSize;
  74. /** Object responsible for handling sizing and expanded issues. */
  75. protected AbstractLayoutCache treeState;
  76. /** Used for minimizing the drawing of vertical lines. */
  77. protected Hashtable drawingCache;
  78. /** True if doing optimizations for a largeModel. Subclasses that
  79. * don't support this may wish to override createLayoutCache to not
  80. * return a FixedHeightLayoutCache instance. */
  81. protected boolean largeModel;
  82. /** Reponsible for telling the TreeState the size needed for a node. */
  83. protected AbstractLayoutCache.NodeDimensions nodeDimensions;
  84. /** Used to determine what to display. */
  85. protected TreeModel treeModel;
  86. /** Model maintaing the selection. */
  87. protected TreeSelectionModel treeSelectionModel;
  88. /** How much the depth should be offset to properly calculate
  89. * x locations. This is based on whether or not the root is visible,
  90. * and if the root handles are visible. */
  91. protected int depthOffset;
  92. /** Last width the tree was at when painted. This is used when
  93. * !leftToRigth to notice the bounds have changed so that we can instruct
  94. * the TreeState to relayout. */
  95. private int lastWidth;
  96. // Following 4 ivars are only valid when editing.
  97. /** When editing, this will be the Component that is doing the actual
  98. * editing. */
  99. protected Component editingComponent;
  100. /** Path that is being edited. */
  101. protected TreePath editingPath;
  102. /** Row that is being edited. Should only be referenced if
  103. * editingComponent is not null. */
  104. protected int editingRow;
  105. /** Set to true if the editor has a different size than the renderer. */
  106. protected boolean editorHasDifferentSize;
  107. /** Row correspondin to lead path. */
  108. private int leadRow;
  109. /** If true, the property change event for LEAD_SELECTION_PATH_PROPERTY,
  110. * or ANCHOR_SELECTION_PATH_PROPERTY will not generate a repaint. */
  111. private boolean ignoreLAChange;
  112. /** Indicates the orientation. */
  113. private boolean leftToRight;
  114. // Cached listeners
  115. private PropertyChangeListener propertyChangeListener;
  116. private PropertyChangeListener selectionModelPropertyChangeListener;
  117. private MouseListener mouseListener;
  118. private FocusListener focusListener;
  119. private KeyListener keyListener;
  120. /** Used for large models, listens for moved/resized events and
  121. * updates the validCachedPreferredSize bit accordingly. */
  122. private ComponentListener componentListener;
  123. /** Listens for CellEditor events. */
  124. private CellEditorListener cellEditorListener;
  125. /** Updates the display when the selection changes. */
  126. private TreeSelectionListener treeSelectionListener;
  127. /** Is responsible for updating the display based on model events. */
  128. private TreeModelListener treeModelListener;
  129. /** Updates the treestate as the nodes expand. */
  130. private TreeExpansionListener treeExpansionListener;
  131. public static ComponentUI createUI(JComponent x) {
  132. return new BasicTreeUI();
  133. }
  134. public BasicTreeUI() {
  135. super();
  136. }
  137. protected Color getHashColor() {
  138. return hashColor;
  139. }
  140. protected void setHashColor(Color color) {
  141. hashColor = color;
  142. }
  143. public void setLeftChildIndent(int newAmount) {
  144. leftChildIndent = newAmount;
  145. totalChildIndent = leftChildIndent + rightChildIndent;
  146. if(treeState != null)
  147. treeState.invalidateSizes();
  148. updateSize();
  149. }
  150. public int getLeftChildIndent() {
  151. return leftChildIndent;
  152. }
  153. public void setRightChildIndent(int newAmount) {
  154. rightChildIndent = newAmount;
  155. totalChildIndent = leftChildIndent + rightChildIndent;
  156. if(treeState != null)
  157. treeState.invalidateSizes();
  158. updateSize();
  159. }
  160. public int getRightChildIndent() {
  161. return rightChildIndent;
  162. }
  163. public void setExpandedIcon(Icon newG) {
  164. expandedIcon = newG;
  165. }
  166. public Icon getExpandedIcon() {
  167. return expandedIcon;
  168. }
  169. public void setCollapsedIcon(Icon newG) {
  170. collapsedIcon = newG;
  171. }
  172. public Icon getCollapsedIcon() {
  173. return collapsedIcon;
  174. }
  175. //
  176. // Methods for configuring the behavior of the tree. None of them
  177. // push the value to the JTree instance. You should really only
  178. // call these methods on the JTree.
  179. //
  180. /**
  181. * Updates the componentListener, if necessary.
  182. */
  183. protected void setLargeModel(boolean largeModel) {
  184. if(getRowHeight() < 1)
  185. largeModel = false;
  186. if(this.largeModel != largeModel) {
  187. completeEditing();
  188. this.largeModel = largeModel;
  189. treeState = createLayoutCache();
  190. configureLayoutCache();
  191. updateLayoutCacheExpandedNodes();
  192. updateSize();
  193. }
  194. }
  195. protected boolean isLargeModel() {
  196. return largeModel;
  197. }
  198. /**
  199. * Sets the row height, this is forwarded to the treeState.
  200. */
  201. protected void setRowHeight(int rowHeight) {
  202. completeEditing();
  203. if(treeState != null) {
  204. setLargeModel(tree.isLargeModel());
  205. treeState.setRowHeight(rowHeight);
  206. updateSize();
  207. }
  208. }
  209. protected int getRowHeight() {
  210. return (tree == null) ? -1 : tree.getRowHeight();
  211. }
  212. /**
  213. * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
  214. * <code>updateRenderer</code>.
  215. */
  216. protected void setCellRenderer(TreeCellRenderer tcr) {
  217. completeEditing();
  218. updateRenderer();
  219. if(treeState != null) {
  220. treeState.invalidateSizes();
  221. updateSize();
  222. }
  223. }
  224. /**
  225. * Return currentCellRenderer, which will either be the trees
  226. * renderer, or defaultCellRenderer, which ever wasn't null.
  227. */
  228. protected TreeCellRenderer getCellRenderer() {
  229. return currentCellRenderer;
  230. }
  231. /**
  232. * Sets the TreeModel.
  233. */
  234. protected void setModel(TreeModel model) {
  235. completeEditing();
  236. if(treeModel != null && treeModelListener != null)
  237. treeModel.removeTreeModelListener(treeModelListener);
  238. treeModel = model;
  239. if(treeModel != null) {
  240. if(treeModelListener != null)
  241. treeModel.addTreeModelListener(treeModelListener);
  242. }
  243. if(treeState != null) {
  244. treeState.setModel(model);
  245. updateLayoutCacheExpandedNodes();
  246. updateSize();
  247. }
  248. }
  249. protected TreeModel getModel() {
  250. return treeModel;
  251. }
  252. /**
  253. * Sets the root to being visible.
  254. */
  255. protected void setRootVisible(boolean newValue) {
  256. completeEditing();
  257. updateDepthOffset();
  258. if(treeState != null) {
  259. treeState.setRootVisible(newValue);
  260. treeState.invalidateSizes();
  261. updateSize();
  262. }
  263. }
  264. protected boolean isRootVisible() {
  265. return (tree != null) ? tree.isRootVisible() : false;
  266. }
  267. /**
  268. * Determines whether the node handles are to be displayed.
  269. */
  270. protected void setShowsRootHandles(boolean newValue) {
  271. completeEditing();
  272. updateDepthOffset();
  273. if(treeState != null) {
  274. treeState.invalidateSizes();
  275. updateSize();
  276. }
  277. }
  278. protected boolean getShowsRootHandles() {
  279. return (tree != null) ? tree.getShowsRootHandles() : false;
  280. }
  281. /**
  282. * Sets the cell editor.
  283. */
  284. protected void setCellEditor(TreeCellEditor editor) {
  285. updateCellEditor();
  286. }
  287. protected TreeCellEditor getCellEditor() {
  288. return (tree != null) ? tree.getCellEditor() : null;
  289. }
  290. /**
  291. * Configures the receiver to allow, or not allow, editing.
  292. */
  293. protected void setEditable(boolean newValue) {
  294. updateCellEditor();
  295. }
  296. protected boolean isEditable() {
  297. return (tree != null) ? tree.isEditable() : false;
  298. }
  299. /**
  300. * Resets the selection model. The appropriate listener are installed
  301. * on the model.
  302. */
  303. protected void setSelectionModel(TreeSelectionModel newLSM) {
  304. completeEditing();
  305. if(selectionModelPropertyChangeListener != null &&
  306. treeSelectionModel != null)
  307. treeSelectionModel.removePropertyChangeListener
  308. (selectionModelPropertyChangeListener);
  309. if(treeSelectionListener != null && treeSelectionModel != null)
  310. treeSelectionModel.removeTreeSelectionListener
  311. (treeSelectionListener);
  312. treeSelectionModel = newLSM;
  313. if(treeSelectionModel != null) {
  314. if(selectionModelPropertyChangeListener != null)
  315. treeSelectionModel.addPropertyChangeListener
  316. (selectionModelPropertyChangeListener);
  317. if(treeSelectionListener != null)
  318. treeSelectionModel.addTreeSelectionListener
  319. (treeSelectionListener);
  320. if(treeState != null)
  321. treeState.setSelectionModel(treeSelectionModel);
  322. }
  323. else if(treeState != null)
  324. treeState.setSelectionModel(null);
  325. if(tree != null)
  326. tree.repaint();
  327. }
  328. protected TreeSelectionModel getSelectionModel() {
  329. return treeSelectionModel;
  330. }
  331. //
  332. // TreeUI methods
  333. //
  334. /**
  335. * Returns the Rectangle enclosing the label portion that the
  336. * last item in path will be drawn into. Will return null if
  337. * any component in path is currently valid.
  338. */
  339. public Rectangle getPathBounds(JTree tree, TreePath path) {
  340. if(tree != null && treeState != null) {
  341. Insets i = tree.getInsets();
  342. Rectangle bounds = treeState.getBounds(path, null);
  343. if(bounds != null && i != null) {
  344. bounds.x += i.left;
  345. bounds.y += i.top;
  346. }
  347. return bounds;
  348. }
  349. return null;
  350. }
  351. /**
  352. * Returns the path for passed in row. If row is not visible
  353. * null is returned.
  354. */
  355. public TreePath getPathForRow(JTree tree, int row) {
  356. return (treeState != null) ? treeState.getPathForRow(row) : null;
  357. }
  358. /**
  359. * Returns the row that the last item identified in path is visible
  360. * at. Will return -1 if any of the elements in path are not
  361. * currently visible.
  362. */
  363. public int getRowForPath(JTree tree, TreePath path) {
  364. return (treeState != null) ? treeState.getRowForPath(path) : -1;
  365. }
  366. /**
  367. * Returns the number of rows that are being displayed.
  368. */
  369. public int getRowCount(JTree tree) {
  370. return (treeState != null) ? treeState.getRowCount() : 0;
  371. }
  372. /**
  373. * Returns the path to the node that is closest to x,y. If
  374. * there is nothing currently visible this will return null, otherwise
  375. * it'll always return a valid path. If you need to test if the
  376. * returned object is exactly at x, y you should get the bounds for
  377. * the returned path and test x, y against that.
  378. */
  379. public TreePath getClosestPathForLocation(JTree tree, int x, int y) {
  380. if(tree != null && treeState != null) {
  381. Insets i = tree.getInsets();
  382. if(i == null)
  383. i = EMPTY_INSETS;
  384. return treeState.getPathClosestTo(x - i.left, y - i.top);
  385. }
  386. return null;
  387. }
  388. /**
  389. * Returns true if the tree is being edited. The item that is being
  390. * edited can be returned by getEditingPath().
  391. */
  392. public boolean isEditing(JTree tree) {
  393. return (editingComponent != null);
  394. }
  395. /**
  396. * Stops the current editing session. This has no effect if the
  397. * tree isn't being edited. Returns true if the editor allows the
  398. * editing session to stop.
  399. */
  400. public boolean stopEditing(JTree tree) {
  401. if(editingComponent != null && cellEditor.stopCellEditing()) {
  402. completeEditing(false, false, true);
  403. return true;
  404. }
  405. return false;
  406. }
  407. /**
  408. * Cancels the current editing session.
  409. */
  410. public void cancelEditing(JTree tree) {
  411. if(editingComponent != null) {
  412. completeEditing(false, true, false);
  413. }
  414. }
  415. /**
  416. * Selects the last item in path and tries to edit it. Editing will
  417. * fail if the CellEditor won't allow it for the selected item.
  418. */
  419. public void startEditingAtPath(JTree tree, TreePath path) {
  420. tree.scrollPathToVisible(path);
  421. if(path != null && tree.isVisible(path))
  422. startEditing(path, null);
  423. }
  424. /**
  425. * Returns the path to the element that is being edited.
  426. */
  427. public TreePath getEditingPath(JTree tree) {
  428. return editingPath;
  429. }
  430. //
  431. // Install methods
  432. //
  433. public void installUI(JComponent c) {
  434. if ( c == null ) {
  435. throw new NullPointerException( "null component passed to BasicTreeUI.installUI()" );
  436. }
  437. tree = (JTree)c;
  438. prepareForUIInstall();
  439. // Boilerplate install block
  440. installDefaults();
  441. installListeners();
  442. installKeyboardActions();
  443. installComponents();
  444. completeUIInstall();
  445. }
  446. /**
  447. * Invoked after the <code>tree</code> instance variable has been
  448. * set, but before any defaults/listeners have been installed.
  449. */
  450. protected void prepareForUIInstall() {
  451. drawingCache = new Hashtable(7);
  452. // Data member initializations
  453. leftToRight = BasicGraphicsUtils.isLeftToRight(tree);
  454. lastWidth = tree.getWidth();
  455. stopEditingInCompleteEditing = true;
  456. lastSelectedRow = -1;
  457. leadRow = -1;
  458. preferredSize = new Dimension();
  459. tree.setRowHeight(UIManager.getInt("Tree.rowHeight"));
  460. Object b = UIManager.get("Tree.scrollsOnExpand");
  461. if(b != null)
  462. tree.setScrollsOnExpand(((Boolean)b).booleanValue());
  463. largeModel = tree.isLargeModel();
  464. if(getRowHeight() <= 0)
  465. largeModel = false;
  466. setModel(tree.getModel());
  467. }
  468. /**
  469. * Invoked from installUI after all the defaults/listeners have been
  470. * installed.
  471. */
  472. protected void completeUIInstall() {
  473. // Custom install code
  474. this.setShowsRootHandles(tree.getShowsRootHandles());
  475. updateRenderer();
  476. updateDepthOffset();
  477. setSelectionModel(tree.getSelectionModel());
  478. // Create, if necessary, the TreeState instance.
  479. treeState = createLayoutCache();
  480. configureLayoutCache();
  481. updateSize();
  482. }
  483. protected void installDefaults() {
  484. if(tree.getBackground() == null ||
  485. tree.getBackground() instanceof UIResource) {
  486. tree.setBackground(UIManager.getColor("Tree.background"));
  487. }
  488. if(getHashColor() == null || getHashColor() instanceof UIResource) {
  489. setHashColor(UIManager.getColor("Tree.hash"));
  490. }
  491. if (tree.getFont() == null || tree.getFont() instanceof UIResource)
  492. tree.setFont( UIManager.getFont("Tree.font") );
  493. setExpandedIcon( (Icon)UIManager.get( "Tree.expandedIcon" ) );
  494. setCollapsedIcon( (Icon)UIManager.get( "Tree.collapsedIcon" ) );
  495. setLeftChildIndent(((Integer)UIManager.get("Tree.leftChildIndent")).
  496. intValue());
  497. setRightChildIndent(((Integer)UIManager.get("Tree.rightChildIndent")).
  498. intValue());
  499. }
  500. protected void installListeners() {
  501. if ( (propertyChangeListener = createPropertyChangeListener())
  502. != null ) {
  503. tree.addPropertyChangeListener(propertyChangeListener);
  504. }
  505. if ( (mouseListener = createMouseListener()) != null ) {
  506. tree.addMouseListener(mouseListener);
  507. }
  508. if ((focusListener = createFocusListener()) != null ) {
  509. tree.addFocusListener(focusListener);
  510. }
  511. if ((keyListener = createKeyListener()) != null) {
  512. tree.addKeyListener(keyListener);
  513. }
  514. if((treeExpansionListener = createTreeExpansionListener()) != null) {
  515. tree.addTreeExpansionListener(treeExpansionListener);
  516. }
  517. if((treeModelListener = createTreeModelListener()) != null &&
  518. treeModel != null) {
  519. treeModel.addTreeModelListener(treeModelListener);
  520. }
  521. if((selectionModelPropertyChangeListener =
  522. createSelectionModelPropertyChangeListener()) != null &&
  523. treeSelectionModel != null) {
  524. treeSelectionModel.addPropertyChangeListener
  525. (selectionModelPropertyChangeListener);
  526. }
  527. if((treeSelectionListener = createTreeSelectionListener()) != null &&
  528. treeSelectionModel != null) {
  529. treeSelectionModel.addTreeSelectionListener(treeSelectionListener);
  530. }
  531. }
  532. protected void installKeyboardActions() {
  533. InputMap km = getInputMap(JComponent.
  534. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  535. SwingUtilities.replaceUIInputMap(tree, JComponent.
  536. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  537. km);
  538. km = getInputMap(JComponent.WHEN_FOCUSED);
  539. SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, km);
  540. ActionMap am = getActionMap();
  541. SwingUtilities.replaceUIActionMap(tree, am);
  542. }
  543. InputMap getInputMap(int condition) {
  544. if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
  545. return (InputMap)UIManager.get("Tree.ancestorInputMap");
  546. }
  547. else if (condition == JComponent.WHEN_FOCUSED) {
  548. return (InputMap)UIManager.get("Tree.focusInputMap");
  549. }
  550. return null;
  551. }
  552. ActionMap getActionMap() {
  553. return createActionMap();
  554. }
  555. ActionMap createActionMap() {
  556. ActionMap map = new ActionMapUIResource();
  557. map.put("selectPrevious", new TreeIncrementAction(-1, "selectPrevious",
  558. false, true));
  559. map.put("selectPreviousChangeLead", new TreeIncrementAction
  560. (-1, "selectPreviousLead", false, false));
  561. map.put("selectPreviousExtendSelection", new TreeIncrementAction
  562. (-1, "selectPreviousExtendSelection", true, true));
  563. map.put("selectNext", new TreeIncrementAction
  564. (1, "selectNext", false, true));
  565. map.put("selectNextChangeLead", new TreeIncrementAction
  566. (1, "selectNextLead", false, false));
  567. map.put("selectNextExtendSelection", new TreeIncrementAction
  568. (1, "selectNextExtendSelection", true, true));
  569. map.put("selectChild", new TreeTraverseAction
  570. (1, "selectChild", true));
  571. map.put("selectChildChangeLead", new TreeTraverseAction
  572. (1, "selectChildLead", false));
  573. map.put("selectParent", new TreeTraverseAction
  574. (-1, "selectParent", true));
  575. map.put("selectParentChangeLead", new TreeTraverseAction
  576. (-1, "selectParentLead", false));
  577. map.put("scrollUpChangeSelection", new TreePageAction
  578. (-1, "scrollUpChangeSelection", false, true));
  579. map.put("scrollUpChangeLead", new TreePageAction
  580. (-1, "scrollUpChangeLead", false, false));
  581. map.put("scrollUpExtendSelection", new TreePageAction
  582. (-1, "scrollUpExtendSelection", true, true));
  583. map.put("scrollDownChangeSelection", new TreePageAction
  584. (1, "scrollDownChangeSelection", false, true));
  585. map.put("scrollDownExtendSelection", new TreePageAction
  586. (1, "scrollDownExtendSelection", true, true));
  587. map.put("scrollDownChangeLead", new TreePageAction
  588. (1, "scrollDownChangeLead", false, false));
  589. map.put("selectFirst", new TreeHomeAction
  590. (-1, "selectFirst", false, true));
  591. map.put("selectFirstChangeLead", new TreeHomeAction
  592. (-1, "selectFirst", false, false));
  593. map.put("selectFirstExtendSelection",new TreeHomeAction
  594. (-1, "selectFirstExtendSelection", true, true));
  595. map.put("selectLast", new TreeHomeAction
  596. (1, "selectLast", false, true));
  597. map.put("selectLastChangeLead", new TreeHomeAction
  598. (1, "selectLast", false, false));
  599. map.put("selectLastExtendSelection", new TreeHomeAction
  600. (1, "selectLastExtendSelection", true, true));
  601. map.put("toggle", new TreeToggleAction("toggle"));
  602. map.put("cancel", new TreeCancelEditingAction("cancel"));
  603. map.put("startEditing", new TreeEditAction("startEditing"));
  604. map.put("selectAll", new TreeSelectAllAction("selectAll", true));
  605. map.put("clearSelection", new TreeSelectAllAction
  606. ("clearSelection", false));
  607. map.put("toggleSelectionPreserveAnchor",
  608. new TreeAddSelectionAction("toggleSelectionPreserveAnchor",
  609. false));
  610. map.put("toggleSelection",
  611. new TreeAddSelectionAction("toggleSelection", true));
  612. map.put("extendSelection", new TreeExtendSelectionAction
  613. ("extendSelection"));
  614. map.put("scrollLeft", new ScrollAction
  615. (tree, SwingConstants.HORIZONTAL, -10));
  616. map.put("scrollLeftExtendSelection", new TreeScrollLRAction
  617. (-1, "scrollLeftExtendSelection", true, true));
  618. map.put("scrollRight", new ScrollAction
  619. (tree, SwingConstants.HORIZONTAL, 10));
  620. map.put("scrollRightExtendSelection", new TreeScrollLRAction
  621. (1, "scrollRightExtendSelection", true, true));
  622. map.put("scrollRightChangeLead", new TreeScrollLRAction
  623. (1, "scrollRightChangeLead", false, false));
  624. map.put("scrollLeftChangeLead", new TreeScrollLRAction
  625. (-1, "scrollLeftChangeLead", false, false));
  626. return map;
  627. }
  628. /**
  629. * Intalls the subcomponents of the tree, which is the renderer pane.
  630. */
  631. protected void installComponents() {
  632. if ((rendererPane = createCellRendererPane()) != null) {
  633. tree.add( rendererPane );
  634. }
  635. }
  636. //
  637. // Create methods.
  638. //
  639. /**
  640. * Creates an instance of NodeDimensions that is able to determine
  641. * the size of a given node in the tree.
  642. */
  643. protected AbstractLayoutCache.NodeDimensions createNodeDimensions() {
  644. return new NodeDimensionsHandler();
  645. }
  646. /**
  647. * Creates a listener that is responsible that updates the UI based on
  648. * how the tree changes.
  649. */
  650. protected PropertyChangeListener createPropertyChangeListener() {
  651. return new PropertyChangeHandler();
  652. }
  653. /**
  654. * Creates the listener responsible for updating the selection based on
  655. * mouse events.
  656. */
  657. protected MouseListener createMouseListener() {
  658. return new MouseHandler();
  659. }
  660. /**
  661. * Creates a listener that is responsible for updating the display
  662. * when focus is lost/gained.
  663. */
  664. protected FocusListener createFocusListener() {
  665. return new FocusHandler();
  666. }
  667. /**
  668. * Creates the listener reponsible for getting key events from
  669. * the tree.
  670. */
  671. protected KeyListener createKeyListener() {
  672. return new KeyHandler();
  673. }
  674. /**
  675. * Creates the listener responsible for getting property change
  676. * events from the selection model.
  677. */
  678. protected PropertyChangeListener createSelectionModelPropertyChangeListener() {
  679. return new SelectionModelPropertyChangeHandler();
  680. }
  681. /**
  682. * Creates the listener that updates the display based on selection change
  683. * methods.
  684. */
  685. protected TreeSelectionListener createTreeSelectionListener() {
  686. return new TreeSelectionHandler();
  687. }
  688. /**
  689. * Creates a listener to handle events from the current editor.
  690. */
  691. protected CellEditorListener createCellEditorListener() {
  692. return new CellEditorHandler();
  693. }
  694. /**
  695. * Creates and returns a new ComponentHandler. This is used for
  696. * the large model to mark the validCachedPreferredSize as invalid
  697. * when the component moves.
  698. */
  699. protected ComponentListener createComponentListener() {
  700. return new ComponentHandler();
  701. }
  702. /**
  703. * Creates and returns the object responsible for updating the treestate
  704. * when nodes expanded state changes.
  705. */
  706. protected TreeExpansionListener createTreeExpansionListener() {
  707. return new TreeExpansionHandler();
  708. }
  709. /**
  710. * Creates the object responsible for managing what is expanded, as
  711. * well as the size of nodes.
  712. */
  713. protected AbstractLayoutCache createLayoutCache() {
  714. if(isLargeModel() && getRowHeight() > 0) {
  715. return new FixedHeightLayoutCache();
  716. }
  717. return new VariableHeightLayoutCache();
  718. }
  719. /**
  720. * Returns the renderer pane that renderer components are placed in.
  721. */
  722. protected CellRendererPane createCellRendererPane() {
  723. return new CellRendererPane();
  724. }
  725. /**
  726. * Creates a default cell editor.
  727. */
  728. protected TreeCellEditor createDefaultCellEditor() {
  729. if(currentCellRenderer != null &&
  730. (currentCellRenderer instanceof DefaultTreeCellRenderer)) {
  731. DefaultTreeCellEditor editor = new DefaultTreeCellEditor
  732. (tree, (DefaultTreeCellRenderer)currentCellRenderer);
  733. return editor;
  734. }
  735. return new DefaultTreeCellEditor(tree, null);
  736. }
  737. /**
  738. * Returns the default cell renderer that is used to do the
  739. * stamping of each node.
  740. */
  741. protected TreeCellRenderer createDefaultCellRenderer() {
  742. return new DefaultTreeCellRenderer();
  743. }
  744. /**
  745. * Returns a listener that can update the tree when the model changes.
  746. */
  747. protected TreeModelListener createTreeModelListener() {
  748. return new TreeModelHandler();
  749. }
  750. //
  751. // Uninstall methods
  752. //
  753. public void uninstallUI(JComponent c) {
  754. completeEditing();
  755. prepareForUIUninstall();
  756. uninstallDefaults();
  757. uninstallListeners();
  758. uninstallKeyboardActions();
  759. uninstallComponents();
  760. completeUIUninstall();
  761. }
  762. protected void prepareForUIUninstall() {
  763. }
  764. protected void completeUIUninstall() {
  765. if(createdRenderer) {
  766. tree.setCellRenderer(null);
  767. }
  768. if(createdCellEditor) {
  769. tree.setCellEditor(null);
  770. }
  771. cellEditor = null;
  772. currentCellRenderer = null;
  773. rendererPane = null;
  774. componentListener = null;
  775. propertyChangeListener = null;
  776. mouseListener = null;
  777. focusListener = null;
  778. keyListener = null;
  779. setSelectionModel(null);
  780. treeState = null;
  781. drawingCache = null;
  782. selectionModelPropertyChangeListener = null;
  783. tree = null;
  784. treeModel = null;
  785. treeSelectionModel = null;
  786. treeSelectionListener = null;
  787. treeExpansionListener = null;
  788. }
  789. protected void uninstallDefaults() {
  790. }
  791. protected void uninstallListeners() {
  792. if(componentListener != null) {
  793. tree.removeComponentListener(componentListener);
  794. }
  795. if (propertyChangeListener != null) {
  796. tree.removePropertyChangeListener(propertyChangeListener);
  797. }
  798. if (mouseListener != null) {
  799. tree.removeMouseListener(mouseListener);
  800. }
  801. if (focusListener != null) {
  802. tree.removeFocusListener(focusListener);
  803. }
  804. if (keyListener != null) {
  805. tree.removeKeyListener(keyListener);
  806. }
  807. if(treeExpansionListener != null) {
  808. tree.removeTreeExpansionListener(treeExpansionListener);
  809. }
  810. if(treeModel != null && treeModelListener != null) {
  811. treeModel.removeTreeModelListener(treeModelListener);
  812. }
  813. if(selectionModelPropertyChangeListener != null &&
  814. treeSelectionModel != null) {
  815. treeSelectionModel.removePropertyChangeListener
  816. (selectionModelPropertyChangeListener);
  817. }
  818. if(treeSelectionListener != null && treeSelectionModel != null) {
  819. treeSelectionModel.removeTreeSelectionListener
  820. (treeSelectionListener);
  821. }
  822. }
  823. protected void uninstallKeyboardActions() {
  824. SwingUtilities.replaceUIActionMap(tree, null);
  825. SwingUtilities.replaceUIInputMap(tree, JComponent.
  826. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  827. null);
  828. SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED, null);
  829. }
  830. /**
  831. * Uninstalls the renderer pane.
  832. */
  833. protected void uninstallComponents() {
  834. if(rendererPane != null) {
  835. tree.remove(rendererPane);
  836. }
  837. }
  838. /**
  839. * Recomputes the right margin, and invalidates any tree states
  840. */
  841. private void redoTheLayout() {
  842. if (treeState != null) {
  843. treeState.invalidateSizes();
  844. }
  845. }
  846. //
  847. // Painting routines.
  848. //
  849. public void paint(Graphics g, JComponent c) {
  850. if (tree != c) {
  851. throw new InternalError("incorrect component");
  852. }
  853. // Should never happen if installed for a UI
  854. if(treeState == null) {
  855. return;
  856. }
  857. // Update the lastWidth if necessary.
  858. // This should really come from a ComponentListener installed on
  859. // the JTree, but for the time being it is here.
  860. int width = tree.getWidth();
  861. if (width != lastWidth) {
  862. lastWidth = width;
  863. if (!leftToRight) {
  864. // For RTL when the size changes, we have to refresh the
  865. // cache as the X position is based off the width.
  866. redoTheLayout();
  867. updateSize();
  868. }
  869. }
  870. Rectangle paintBounds = g.getClipBounds();
  871. Insets insets = tree.getInsets();
  872. if(insets == null)
  873. insets = EMPTY_INSETS;
  874. TreePath initialPath = getClosestPathForLocation
  875. (tree, 0, paintBounds.y);
  876. Enumeration paintingEnumerator = treeState.getVisiblePathsFrom
  877. (initialPath);
  878. int row = treeState.getRowForPath(initialPath);
  879. int endY = paintBounds.y + paintBounds.height;
  880. drawingCache.clear();
  881. if(initialPath != null && paintingEnumerator != null) {
  882. TreePath parentPath = initialPath;
  883. // Draw the lines, knobs, and rows
  884. // Find each parent and have them draw a line to their last child
  885. parentPath = parentPath.getParentPath();
  886. while(parentPath != null) {
  887. paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
  888. drawingCache.put(parentPath, Boolean.TRUE);
  889. parentPath = parentPath.getParentPath();
  890. }
  891. boolean done = false;
  892. // Information for the node being rendered.
  893. boolean isExpanded;
  894. boolean hasBeenExpanded;
  895. boolean isLeaf;
  896. Rectangle boundsBuffer = new Rectangle();
  897. Rectangle bounds;
  898. TreePath path;
  899. boolean rootVisible = isRootVisible();
  900. while(!done && paintingEnumerator.hasMoreElements()) {
  901. path = (TreePath)paintingEnumerator.nextElement();
  902. if(path != null) {
  903. isLeaf = treeModel.isLeaf(path.getLastPathComponent());
  904. if(isLeaf)
  905. isExpanded = hasBeenExpanded = false;
  906. else {
  907. isExpanded = treeState.getExpandedState(path);
  908. hasBeenExpanded = tree.hasBeenExpanded(path);
  909. }
  910. bounds = treeState.getBounds(path, boundsBuffer);
  911. if(bounds == null)
  912. // This will only happen if the model changes out
  913. // from under us (usually in another thread).
  914. // Swing isn't multithreaded, but I'll put this
  915. // check in anyway.
  916. return;
  917. bounds.x += insets.left;
  918. bounds.y += insets.top;
  919. // See if the vertical line to the parent has been drawn.
  920. parentPath = path.getParentPath();
  921. if(parentPath != null) {
  922. if(drawingCache.get(parentPath) == null) {
  923. paintVerticalPartOfLeg(g, paintBounds,
  924. insets, parentPath);
  925. drawingCache.put(parentPath, Boolean.TRUE);
  926. }
  927. paintHorizontalPartOfLeg(g, paintBounds, insets,
  928. bounds, path, row,
  929. isExpanded,
  930. hasBeenExpanded, isLeaf);
  931. }
  932. else if(rootVisible && row == 0) {
  933. paintHorizontalPartOfLeg(g, paintBounds, insets,
  934. bounds, path, row,
  935. isExpanded,
  936. hasBeenExpanded, isLeaf);
  937. }
  938. if(shouldPaintExpandControl(path, row, isExpanded,
  939. hasBeenExpanded, isLeaf)) {
  940. paintExpandControl(g, paintBounds, insets, bounds,
  941. path, row, isExpanded,
  942. hasBeenExpanded, isLeaf);
  943. }
  944. //This is the quick fix for bug 4259260. Somewhere we
  945. //are out by 4 pixels in the RTL layout. Its probably
  946. //due to built in right-side padding in some icons. Rather
  947. //than ferret out problem at the source, this compensates.
  948. if (!leftToRight) {
  949. bounds.x +=4;
  950. }
  951. paintRow(g, paintBounds, insets, bounds, path,
  952. row, isExpanded, hasBeenExpanded, isLeaf);
  953. if((bounds.y + bounds.height) >= endY)
  954. done = true;
  955. }
  956. else {
  957. done = true;
  958. }
  959. row++;
  960. }
  961. }
  962. // Empty out the renderer pane, allowing renderers to be gc'ed.
  963. rendererPane.removeAll();
  964. }
  965. /**
  966. * Paints the horizontal part of the leg. The reciever should
  967. * NOT modify <code>clipBounds</code>, or <code>insets</code>.<p>
  968. * NOTE: <code>parentRow</code> can be -1 if the root is not visible.
  969. */
  970. protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
  971. Insets insets, Rectangle bounds,
  972. TreePath path, int row,
  973. boolean isExpanded,
  974. boolean hasBeenExpanded, boolean
  975. isLeaf) {
  976. int clipLeft = clipBounds.x;
  977. int clipRight = clipBounds.x + (clipBounds.width - 1);
  978. int clipTop = clipBounds.y;
  979. int clipBottom = clipBounds.y + (clipBounds.height - 1);
  980. int lineY = bounds.y + bounds.height / 2;
  981. // Offset leftX from parents indent.
  982. if (leftToRight) {
  983. int leftX = bounds.x - getRightChildIndent();
  984. int nodeX = bounds.x - getHorizontalLegBuffer();
  985. if(lineY > clipTop && lineY < clipBottom && nodeX > clipLeft &&
  986. leftX < clipRight ) {
  987. leftX = Math.max(leftX, clipLeft);
  988. nodeX = Math.min(nodeX, clipRight);
  989. g.setColor(getHashColor());
  990. paintHorizontalLine(g, tree, lineY, leftX, nodeX);
  991. }
  992. }
  993. else {
  994. int leftX = bounds.x + bounds.width + getRightChildIndent();
  995. int nodeX = bounds.x + bounds.width +
  996. getHorizontalLegBuffer() - 1;
  997. if(lineY > clipTop && lineY < clipBottom &&
  998. leftX > clipLeft && nodeX < clipRight) {
  999. leftX = Math.min(leftX, clipRight);
  1000. nodeX = Math.max(nodeX, clipLeft);
  1001. g.setColor(getHashColor());
  1002. paintHorizontalLine(g, tree, lineY, nodeX, leftX);
  1003. }
  1004. }
  1005. }
  1006. /**
  1007. * Paints the vertical part of the leg. The reciever should
  1008. * NOT modify <code>clipBounds</code>, <code>insets</code>.<p>
  1009. */
  1010. protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
  1011. Insets insets, TreePath path) {
  1012. int lineX;
  1013. if (leftToRight) {
  1014. lineX = ((path.getPathCount() + depthOffset) *
  1015. totalChildIndent) - getRightChildIndent() + insets.left;
  1016. }
  1017. else {
  1018. lineX = lastWidth - ((path.getPathCount() - 1 + depthOffset) *
  1019. totalChildIndent) - 9;
  1020. }
  1021. int clipLeft = clipBounds.x;
  1022. int clipRight = clipBounds.x + (clipBounds.width - 1);
  1023. if (lineX > clipLeft && lineX < clipRight) {
  1024. int clipTop = clipBounds.y;
  1025. int clipBottom = clipBounds.y + clipBounds.height;
  1026. Rectangle parentBounds = getPathBounds(tree, path);
  1027. Rectangle lastChildBounds = getPathBounds(tree,
  1028. getLastChildPath(path));
  1029. if(lastChildBounds == null)
  1030. // This shouldn't happen, but if the model is modified
  1031. // in another thread it is possible for this to happen.
  1032. // Swing isn't multithreaded, but I'll add this check in
  1033. // anyway.
  1034. return;
  1035. int top;
  1036. if(parentBounds == null) {
  1037. top = Math.max(insets.top + getVerticalLegBuffer(),
  1038. clipTop);
  1039. }
  1040. else
  1041. top = Math.max(parentBounds.y + parentBounds.height +
  1042. getVerticalLegBuffer(), clipTop);
  1043. if(path.getPathCount() == 1 && !isRootVisible()) {
  1044. TreeModel model = getModel();
  1045. if(model != null) {
  1046. Object root = model.getRoot();
  1047. if(model.getChildCount(root) > 0) {
  1048. parentBounds = getPathBounds(tree, path.
  1049. pathByAddingChild(model.getChild(root, 0)));
  1050. if(parentBounds != null)
  1051. top = Math.max(insets.top + getVerticalLegBuffer(),
  1052. parentBounds.y +
  1053. parentBounds.height / 2);
  1054. }
  1055. }
  1056. }
  1057. int bottom = Math.min(lastChildBounds.y +
  1058. (lastChildBounds.height / 2), clipBottom);
  1059. g.setColor(getHashColor());
  1060. paintVerticalLine(g, tree, lineX, top, bottom);
  1061. }
  1062. }
  1063. /**
  1064. * Paints the expand (toggle) part of a row. The reciever should
  1065. * NOT modify <code>clipBounds</code>, or <code>insets</code>.
  1066. */
  1067. protected void paintExpandControl(Graphics g,
  1068. Rectangle clipBounds, Insets insets,
  1069. Rectangle bounds, TreePath path,
  1070. int row, boolean isExpanded,
  1071. boolean hasBeenExpanded,
  1072. boolean isLeaf) {
  1073. Object value = path.getLastPathComponent();
  1074. // Draw icons if not a leaf and either hasn't been loaded,
  1075. // or the model child count is > 0.
  1076. if (!isLeaf && (!hasBeenExpanded ||
  1077. treeModel.getChildCount(value) > 0)) {
  1078. int middleXOfKnob;
  1079. if (leftToRight) {
  1080. middleXOfKnob = bounds.x - (getRightChildIndent() - 1);
  1081. }
  1082. else {
  1083. middleXOfKnob = bounds.x + bounds.width + getRightChildIndent();
  1084. }
  1085. int middleYOfKnob = bounds.y + (bounds.height / 2);
  1086. if (isExpanded) {
  1087. Icon expandedIcon = getExpandedIcon();
  1088. if(expandedIcon != null)
  1089. drawCentered(tree, g, expandedIcon, middleXOfKnob,
  1090. middleYOfKnob );
  1091. }
  1092. else {
  1093. Icon collapsedIcon = getCollapsedIcon();
  1094. if(collapsedIcon != null)
  1095. drawCentered(tree, g, collapsedIcon, middleXOfKnob,
  1096. middleYOfKnob);
  1097. }
  1098. }
  1099. }
  1100. /**
  1101. * Paints the renderer part of a row. The reciever should
  1102. * NOT modify <code>clipBounds</code>, or <code>insets</code>.
  1103. */
  1104. protected void paintRow(Graphics g, Rectangle clipBounds,
  1105. Insets insets, Rectangle bounds, TreePath path,
  1106. int row, boolean isExpanded,
  1107. boolean hasBeenExpanded, boolean isLeaf) {
  1108. // Don't paint the renderer if editing this row.
  1109. if(editingComponent != null && editingRow == row)
  1110. return;
  1111. int leadIndex;
  1112. if(tree.hasFocus()) {
  1113. leadIndex = getLeadSelectionRow();
  1114. }
  1115. else
  1116. leadIndex = -1;
  1117. Component component;
  1118. component = currentCellRenderer.getTreeCellRendererComponent
  1119. (tree, path.getLastPathComponent(),
  1120. tree.isRowSelected(row), isExpanded, isLeaf, row,
  1121. (leadIndex == row));
  1122. rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
  1123. bounds.width, bounds.height, true);
  1124. }
  1125. /**
  1126. * Returns true if the expand (toggle) control should be drawn for
  1127. * the specified row.
  1128. */
  1129. protected boolean shouldPaintExpandControl(TreePath path, int row,
  1130. boolean isExpanded,
  1131. boolean hasBeenExpanded,
  1132. boolean isLeaf) {
  1133. if(isLeaf)
  1134. return false;
  1135. int depth = path.getPathCount() - 1;
  1136. if((depth == 0 || (depth == 1 && !isRootVisible())) &&
  1137. !getShowsRootHandles())
  1138. return false;
  1139. return true;
  1140. }
  1141. /**
  1142. * Paints a vertical line.
  1143. */
  1144. protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
  1145. int bottom) {
  1146. g.drawLine(x, top, x, bottom);
  1147. }
  1148. /**
  1149. * Paints a horizontal line.
  1150. */
  1151. protected void paintHorizontalLine(Graphics g, JComponent c, int y,
  1152. int left, int right) {
  1153. g.drawLine(left, y, right, y);
  1154. }
  1155. /**
  1156. * The vertical element of legs between nodes starts at the bottom of the
  1157. * parent node by default. This method makes the leg start below that.
  1158. */
  1159. protected int getVerticalLegBuffer() {
  1160. return 0;
  1161. }
  1162. /**
  1163. * The horizontal element of legs between nodes starts at the
  1164. * right of the left-hand side of the child node by default. This
  1165. * method makes the leg end before that.
  1166. */
  1167. protected int getHorizontalLegBuffer() {
  1168. return 0;
  1169. }
  1170. //
  1171. // Generic painting methods
  1172. //
  1173. // Draws the icon centered at (x,y)
  1174. protected void drawCentered(Component c, Graphics graphics, Icon icon,
  1175. int x, int y) {
  1176. icon.paintIcon(c, graphics, x - icon.getIconWidth()/2, y -
  1177. icon.getIconHeight()/2);
  1178. }
  1179. // This method is slow -- revisit when Java2D is ready.
  1180. // assumes x1 <= x2
  1181. protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2){
  1182. // Drawing only even coordinates helps join line segments so they
  1183. // appear as one line. This can be defeated by translating the
  1184. // Graphics by an odd amount.
  1185. x1 += (x1 % 2);
  1186. for (int x = x1; x <= x2; x+=2) {
  1187. g.drawLine(x, y, x, y);
  1188. }
  1189. }
  1190. // This method is slow -- revisit when Java2D is ready.
  1191. // assumes y1 <= y2
  1192. protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2) {
  1193. // Drawing only even coordinates helps join line segments so they
  1194. // appear as one line. This can be defeated by translating the
  1195. // Graphics by an odd amount.
  1196. y1 += (y1 % 2);
  1197. for (int y = y1; y <= y2; y+=2) {
  1198. g.drawLine(x, y, x, y);
  1199. }
  1200. }
  1201. //
  1202. // Various local methods
  1203. //
  1204. /**
  1205. * Makes all the nodes that are expanded in JTree expanded in LayoutCache.
  1206. * This invokes updateExpandedDescendants with the root path.
  1207. */
  1208. protected void updateLayoutCacheExpandedNodes() {
  1209. if(treeModel != null)
  1210. updateExpandedDescendants(new TreePath(treeModel.getRoot()));
  1211. }
  1212. /**
  1213. * Updates the expanded state of all the descendants of <code>path</code>
  1214. * by getting the expanded descendants from the tree and forwarding
  1215. * to the tree state.
  1216. */
  1217. protected void updateExpandedDescendants(TreePath path) {
  1218. completeEditing();
  1219. if(treeState != null) {
  1220. treeState.setExpandedState(path, true);
  1221. Enumeration descendants = tree.getExpandedDescendants(path);
  1222. if(descendants != null) {
  1223. while(descendants.hasMoreElements()) {
  1224. path = (TreePath)descendants.nextElement();
  1225. treeState.setExpandedState(path, true);
  1226. }
  1227. }
  1228. updateLeadRow();
  1229. updateSize();
  1230. }
  1231. }
  1232. /**
  1233. * Returns a path to the last child of <code>parent</code>.
  1234. */
  1235. protected TreePath getLastChildPath(TreePath parent) {
  1236. if(treeModel != null) {
  1237. int childCount = treeModel.getChildCount
  1238. (parent.getLastPathComponent());
  1239. if(childCount > 0)
  1240. return parent.pathByAddingChild(treeModel.getChild
  1241. (parent.getLastPathComponent(), childCount - 1));
  1242. }
  1243. return null;
  1244. }
  1245. /**
  1246. * Updates how much each depth should be offset by.
  1247. */
  1248. protected void updateDepthOffset() {
  1249. if(isRootVisible()) {
  1250. if(getShowsRootHandles())
  1251. depthOffset = 1;
  1252. else
  1253. depthOffset = 0;
  1254. }
  1255. else if(!getShowsRootHandles())
  1256. depthOffset = -1;
  1257. else
  1258. depthOffset = 0;
  1259. }
  1260. /**
  1261. * Updates the cellEditor based on the editability of the JTree that
  1262. * we're contained in. If the tree is editable but doesn't have a
  1263. * cellEditor, a basic one will be used.
  1264. */
  1265. protected void updateCellEditor() {
  1266. TreeCellEditor newEditor;
  1267. completeEditing();
  1268. if(tree == null)
  1269. newEditor = null;
  1270. else {
  1271. if(tree.isEditable()) {
  1272. newEditor = tree.getCellEditor();
  1273. if(newEditor == null) {
  1274. newEditor = createDefaultCellEditor();
  1275. if(newEditor != null) {
  1276. tree.setCellEditor(newEditor);
  1277. createdCellEditor = true;
  1278. }
  1279. }
  1280. }
  1281. else
  1282. newEditor = null;
  1283. }
  1284. if(newEditor != cellEditor) {
  1285. if(cellEditor != null && cellEditorListener != null)
  1286. cellEditor.removeCellEditorListener(cellEditorListener);
  1287. cellEditor = newEditor;
  1288. if(cellEditorListener == null)
  1289. cellEditorListener = createCellEditorListener();
  1290. if(newEditor != null && cellEditorListener != null)
  1291. newEditor.addCellEditorListener(cellEditorListener);
  1292. createdCellEditor = false;
  1293. }
  1294. }
  1295. /**
  1296. * Messaged from the tree we're in when the renderer has changed.
  1297. */
  1298. protected void updateRenderer() {
  1299. if(tree != null) {
  1300. TreeCellRenderer newCellRenderer;
  1301. newCellRenderer = tree.getCellRenderer();
  1302. if(newCellRenderer == null) {
  1303. tree.setCellRenderer(createDefaultCellRenderer());
  1304. createdRenderer = true;
  1305. }
  1306. else {
  1307. createdRenderer = false;
  1308. currentCellRenderer = newCellRenderer;
  1309. if(createdCellEditor) {
  1310. tree.setCellEditor(null);
  1311. }
  1312. }
  1313. }
  1314. else {
  1315. createdRenderer = false;
  1316. currentCellRenderer = null;
  1317. }
  1318. updateCellEditor();
  1319. }
  1320. /**
  1321. * Resets the TreeState instance based on the tree we're providing the
  1322. * look and feel for.
  1323. */
  1324. protected void configureLayoutCache() {
  1325. if(treeState != null && tree != null) {
  1326. if(nodeDimensions == null)
  1327. nodeDimensions = createNodeDimensions();
  1328. treeState.setNodeDimensions(nodeDimensions);
  1329. treeState.setRootVisible(tree.isRootVisible());
  1330. treeState.setRowHeight(tree.getRowHeight());
  1331. treeState.setSelectionModel(getSelectionModel());
  1332. // Only do this if necessary, may loss state if call with
  1333. // same model as it currently has.
  1334. if(treeState.getModel() != tree.getModel())
  1335. treeState.setModel(tree.getModel());
  1336. updateLayoutCacheExpandedNodes();
  1337. // Create a listener to update preferred size when bounds
  1338. // changes, if necessary.
  1339. if(isLargeModel()) {
  1340. if(componentListener == null) {
  1341. componentListener = createComponentListener();
  1342. if(componentListener != null)
  1343. tree.addComponentListener(componentListener);
  1344. }
  1345. }
  1346. else if(componentListener != null) {
  1347. tree.removeComponentListener(componentListener);
  1348. componentListener = null;
  1349. }
  1350. }
  1351. else if(componentListener != null) {
  1352. tree.removeComponentListener(componentListener);
  1353. componentListener = null;
  1354. }
  1355. }
  1356. /**
  1357. * Marks the cached size as being invalid, and messages the
  1358. * tree with <code>treeDidChange</code>.
  1359. */
  1360. protected void updateSize() {
  1361. validCachedPreferredSize = false;
  1362. tree.treeDidChange();
  1363. }
  1364. /**
  1365. * Updates the <code>preferredSize</code> instance variable,
  1366. * which is returned from <code>getPreferredSize()</code>.<p>
  1367. * For left to right orientations, the size is determined from the
  1368. * current AbstractLayoutCache. For RTL orientations, the preferred size
  1369. * becomes the width minus the minimum x position.
  1370. */
  1371. protected void updateCachedPreferredSize() {
  1372. if(treeState != null) {
  1373. Insets i = tree.getInsets();
  1374. if(isLargeModel()) {
  1375. Rectangle visRect = tree.getVisibleRect();
  1376. if(i != null) {
  1377. visRect.x -= i.left;
  1378. visRect.y -= i.top;
  1379. }
  1380. if (leftToRight) {
  1381. preferredSize.width = treeState.getPreferredWidth(visRect);
  1382. }
  1383. else {
  1384. if (getRowCount(tree) == 0) {
  1385. preferredSize.width = 0;
  1386. }
  1387. else {
  1388. preferredSize.width = lastWidth - getMinX(visRect);
  1389. }
  1390. }
  1391. }
  1392. else if (leftToRight) {
  1393. preferredSize.width = treeState.getPreferredWidth(null);
  1394. }
  1395. else {
  1396. Rectangle tempRect = null;
  1397. int rowCount = tree.getRowCount();
  1398. int width = 0;
  1399. for (int counter = 0; counter < rowCount; counter++) {
  1400. tempRect = treeState.getBounds
  1401. (treeState.getPathForRow(counter), tempRect);
  1402. if (tempRect != null) {
  1403. width = Math.max(lastWidth - tempRect.x, width);
  1404. }
  1405. }
  1406. preferredSize.width = width;
  1407. }
  1408. preferredSize.height = treeState.getPreferredHeight();
  1409. if(i != null) {
  1410. preferredSize.width += i.left + i.right;
  1411. preferredSize.height += i.top + i.bottom;
  1412. }
  1413. }
  1414. validCachedPreferredSize = true;
  1415. }
  1416. /**
  1417. * Returns the minimum x location for the nodes in <code>bounds</code>.
  1418. */
  1419. private int getMinX(Rectangle bounds) {
  1420. TreePath firstPath;
  1421. int endY;
  1422. if(bounds == null) {
  1423. firstPath = getPathForRow(tree, 0);
  1424. endY = Integer.MAX_VALUE;
  1425. }
  1426. else {
  1427. firstPath = treeState.getPathClosestTo(bounds.x, bounds.y);
  1428. endY = bounds.height + bounds.y;
  1429. }
  1430. Enumeration paths = treeState.getVisiblePathsFrom(firstPath);
  1431. int minX = 0;
  1432. if(paths != null && paths.hasMoreElements()) {
  1433. Rectangle pBounds = treeState.getBounds
  1434. ((TreePath)paths.nextElement(), null);
  1435. int width;
  1436. if(pBounds != null) {
  1437. minX = pBounds.x + pBounds.width;
  1438. if (pBounds.y >= endY) {
  1439. return minX;
  1440. }
  1441. }
  1442. while (pBounds != null && paths.hasMoreElements()) {
  1443. pBounds = treeState.getBounds((TreePath)paths.nextElement(),
  1444. pBounds);
  1445. if (pBounds != null && pBounds.y < endY) {
  1446. minX = Math.min(minX, pBounds.x);
  1447. }
  1448. else {
  1449. pBounds = null;
  1450. }
  1451. }
  1452. return minX;
  1453. }
  1454. return minX;
  1455. }
  1456. /**
  1457. * Messaged from the VisibleTreeNode after it has been expanded.
  1458. */
  1459. protected void pathWasExpanded(TreePath path) {
  1460. if(tree != null) {
  1461. tree.fireTreeExpanded(path);
  1462. }
  1463. }
  1464. /**
  1465. * Messaged from the VisibleTreeNode after it has collapsed.
  1466. */
  1467. protected void pathWasCollapsed(TreePath path) {
  1468. if(tree != null) {
  1469. tree.fireTreeCollapsed(path);
  1470. }
  1471. }
  1472. /**
  1473. * Ensures that the rows identified by beginRow through endRow are
  1474. * visible.
  1475. */
  1476. protected void ensureRowsAreVisible(int beginRow, int endRow) {
  1477. if(tree != null && beginRow >= 0 && endRow < getRowCount(tree))