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)) {
  1478. if(beginRow == endRow) {
  1479. Rectangle scrollBounds = getPathBounds(tree, getPathForRow
  1480. (tree, beginRow));
  1481. if(scrollBounds != null) {
  1482. tree.scrollRectToVisible(scrollBounds);
  1483. }
  1484. }
  1485. else {
  1486. Rectangle beginRect = getPathBounds(tree, getPathForRow
  1487. (tree, beginRow));
  1488. Rectangle visRect = tree.getVisibleRect();
  1489. Rectangle testRect = beginRect;
  1490. int beginY = beginRect.y;
  1491. int maxY = beginY + visRect.height;
  1492. for(int counter = beginRow + 1; counter <= endRow; counter++) {
  1493. testRect = getPathBounds(tree,
  1494. getPathForRow(tree, counter));
  1495. if((testRect.y + testRect.height) > maxY)
  1496. counter = endRow;
  1497. }
  1498. tree.scrollRectToVisible(new Rectangle(visRect.x, beginY, 1,
  1499. testRect.y + testRect.height-
  1500. beginY));
  1501. }
  1502. }
  1503. }
  1504. /** Sets the preferred minimum size.
  1505. */
  1506. public void setPreferredMinSize(Dimension newSize) {
  1507. preferredMinSize = newSize;
  1508. }
  1509. /** Returns the minimum preferred size.
  1510. */
  1511. public Dimension getPreferredMinSize() {
  1512. if(preferredMinSize == null)
  1513. return null;
  1514. return new Dimension(preferredMinSize);
  1515. }
  1516. /** Returns the preferred size to properly display the tree,
  1517. * this is a cover method for getPreferredSize(c, false).
  1518. */
  1519. public Dimension getPreferredSize(JComponent c) {
  1520. return getPreferredSize(c, true);
  1521. }
  1522. /** Returns the preferred size to represent the tree in
  1523. * <I>c</I>. If <I>checkConsistancy</I> is true
  1524. * <b>checkConsistancy</b> is messaged first.
  1525. */
  1526. public Dimension getPreferredSize(JComponent c,
  1527. boolean checkConsistancy) {
  1528. Dimension pSize = this.getPreferredMinSize();
  1529. if(!validCachedPreferredSize)
  1530. updateCachedPreferredSize();
  1531. if(tree != null) {
  1532. if(pSize != null)
  1533. return new Dimension(Math.max(pSize.width,
  1534. preferredSize.width),
  1535. Math.max(pSize.height, preferredSize.height));
  1536. return new Dimension(preferredSize.width, preferredSize.height);
  1537. }
  1538. else if(pSize != null)
  1539. return pSize;
  1540. else
  1541. return new Dimension(0, 0);
  1542. }
  1543. /**
  1544. * Returns the minimum size for this component. Which will be
  1545. * the min preferred size or 0, 0.
  1546. */
  1547. public Dimension getMinimumSize(JComponent c) {
  1548. if(this.getPreferredMinSize() != null)
  1549. return this.getPreferredMinSize();
  1550. return new Dimension(0, 0);
  1551. }
  1552. /**
  1553. * Returns the maximum size for this component, which will be the
  1554. * preferred size if the instance is currently in a JTree, or 0, 0.
  1555. */
  1556. public Dimension getMaximumSize(JComponent c) {
  1557. if(tree != null)
  1558. return getPreferredSize(tree);
  1559. if(this.getPreferredMinSize() != null)
  1560. return this.getPreferredMinSize();
  1561. return new Dimension(0, 0);
  1562. }
  1563. /**
  1564. * Messages to stop the editing session. If the UI the receiver
  1565. * is providing the look and feel for returns true from
  1566. * <code>getInvokesStopCellEditing</code>, stopCellEditing will
  1567. * invoked on the current editor. Then completeEditing will
  1568. * be messaged with false, true, false to cancel any lingering
  1569. * editing.
  1570. */
  1571. protected void completeEditing() {
  1572. /* If should invoke stopCellEditing, try that */
  1573. if(tree.getInvokesStopCellEditing() &&
  1574. stopEditingInCompleteEditing && editingComponent != null) {
  1575. cellEditor.stopCellEditing();
  1576. }
  1577. /* Invoke cancelCellEditing, this will do nothing if stopCellEditing
  1578. was succesful. */
  1579. completeEditing(false, true, false);
  1580. }
  1581. /**
  1582. * Stops the editing session. If messageStop is true the editor
  1583. * is messaged with stopEditing, if messageCancel is true the
  1584. * editor is messaged with cancelEditing. If messageTree is true
  1585. * the treeModel is messaged with valueForPathChanged.
  1586. */
  1587. protected void completeEditing(boolean messageStop,
  1588. boolean messageCancel,
  1589. boolean messageTree) {
  1590. if(stopEditingInCompleteEditing && editingComponent != null) {
  1591. Component oldComponent = editingComponent;
  1592. TreePath oldPath = editingPath;
  1593. TreeCellEditor oldEditor = cellEditor;
  1594. Object newValue = oldEditor.getCellEditorValue();
  1595. Rectangle editingBounds = getPathBounds(tree,
  1596. editingPath);
  1597. boolean requestFocus = (tree != null &&
  1598. (tree.hasFocus() || SwingUtilities.
  1599. findFocusOwner(editingComponent) != null));
  1600. editingComponent = null;
  1601. editingPath = null;
  1602. if(messageStop)
  1603. oldEditor.stopCellEditing();
  1604. else if(messageCancel)
  1605. oldEditor.cancelCellEditing();
  1606. tree.remove(oldComponent);
  1607. if(editorHasDifferentSize) {
  1608. treeState.invalidatePathBounds(oldPath);
  1609. updateSize();
  1610. }
  1611. else {
  1612. editingBounds.x = 0;
  1613. editingBounds.width = tree.getSize().width;
  1614. tree.repaint(editingBounds);
  1615. }
  1616. if(requestFocus)
  1617. tree.requestFocus();
  1618. if(messageTree)
  1619. treeModel.valueForPathChanged(oldPath, newValue);
  1620. }
  1621. }
  1622. /**
  1623. * Will start editing for node if there is a cellEditor and
  1624. * shouldSelectCell returns true.<p>
  1625. * This assumes that path is valid and visible.
  1626. */
  1627. protected boolean startEditing(TreePath path, MouseEvent event) {
  1628. completeEditing();
  1629. if(cellEditor != null && tree.isPathEditable(path)) {
  1630. int row = getRowForPath(tree, path);
  1631. editingComponent = cellEditor.getTreeCellEditorComponent
  1632. (tree, path.getLastPathComponent(),
  1633. tree.isPathSelected(path), tree.isExpanded(path),
  1634. treeModel.isLeaf(path.getLastPathComponent()), row);
  1635. if(cellEditor.isCellEditable(event)) {
  1636. Rectangle nodeBounds = getPathBounds(tree, path);
  1637. editingRow = row;
  1638. Dimension editorSize = editingComponent.getPreferredSize();
  1639. // Only allow odd heights if explicitly set.
  1640. if(editorSize.height != nodeBounds.height &&
  1641. getRowHeight() > 0)
  1642. editorSize.height = getRowHeight();
  1643. if(editorSize.width != nodeBounds.width ||
  1644. editorSize.height != nodeBounds.height) {
  1645. // Editor wants different width or height, invalidate
  1646. // treeState and relayout.
  1647. editorHasDifferentSize = true;
  1648. treeState.invalidatePathBounds(path);
  1649. updateSize();
  1650. }
  1651. else
  1652. editorHasDifferentSize = false;
  1653. tree.add(editingComponent);
  1654. editingComponent.setBounds(nodeBounds.x, nodeBounds.y,
  1655. editorSize.width,
  1656. editorSize.height);
  1657. editingPath = path;
  1658. editingComponent.validate();
  1659. Rectangle visRect = tree.getVisibleRect();
  1660. tree.paintImmediately(nodeBounds.x, nodeBounds.y,
  1661. visRect.width + visRect.x - nodeBounds.x,
  1662. editorSize.height);
  1663. if(cellEditor.shouldSelectCell(event)) {
  1664. stopEditingInCompleteEditing = false;
  1665. try {
  1666. tree.setSelectionRow(row);
  1667. } catch (Exception e) {
  1668. System.err.println("Editing exception: " + e);
  1669. }
  1670. stopEditingInCompleteEditing = true;
  1671. }
  1672. if(event != null && event instanceof MouseEvent) {
  1673. /* Find the component that will get forwarded all the
  1674. mouse events until mouseReleased. */
  1675. Point componentPoint = SwingUtilities.convertPoint
  1676. (tree, new Point(event.getX(), event.getY()),
  1677. editingComponent);
  1678. /* Create an instance of BasicTreeMouseListener to handle
  1679. passing the mouse/motion events to the necessary
  1680. component. */
  1681. // We really want similiar behavior to getMouseEventTarget,
  1682. // but it is package private.
  1683. Component activeComponent = SwingUtilities.
  1684. getDeepestComponentAt(editingComponent,
  1685. componentPoint.x, componentPoint.y);
  1686. if (activeComponent != null) {
  1687. new MouseInputHandler(tree, activeComponent, event);
  1688. }
  1689. }
  1690. return true;
  1691. }
  1692. else
  1693. editingComponent = null;
  1694. }
  1695. return false;
  1696. }
  1697. //
  1698. // Following are primarily for handling mouse events.
  1699. //
  1700. /**
  1701. * If the <code>mouseX</code> and <code>mouseY</code> are in the
  1702. * expand/collapse region of the <code>row</code>, this will toggle
  1703. * the row.
  1704. */
  1705. protected void checkForClickInExpandControl(TreePath path,
  1706. int mouseX, int mouseY) {
  1707. if (isLocationInExpandControl(path, mouseX, mouseY)) {
  1708. handleExpandControlClick(path, mouseX, mouseY);
  1709. }
  1710. }
  1711. /**
  1712. * Returns true if <code>mouseX</code> and <code>mouseY</code> fall
  1713. * in the area of row that is used to expand/collapse the node and
  1714. * the node at <code>row</code> does not represent a leaf.
  1715. */
  1716. protected boolean isLocationInExpandControl(TreePath path,
  1717. int mouseX, int mouseY) {
  1718. if(path != null && !treeModel.isLeaf(path.getLastPathComponent())){
  1719. int boxWidth;
  1720. Insets i = tree.getInsets();
  1721. if(getExpandedIcon() != null)
  1722. boxWidth = getExpandedIcon().getIconWidth();
  1723. else
  1724. boxWidth = 8;
  1725. int boxLeftX = (i != null) ? i.left : 0;
  1726. if (leftToRight) {
  1727. boxLeftX += (((path.getPathCount() + depthOffset - 2) *
  1728. totalChildIndent) + getLeftChildIndent()) -
  1729. boxWidth / 2;
  1730. }
  1731. else {
  1732. boxLeftX += lastWidth - 1 -
  1733. ((path.getPathCount() - 2 + depthOffset) *
  1734. totalChildIndent) - getLeftChildIndent() -
  1735. boxWidth / 2;
  1736. }
  1737. int boxRightX = boxLeftX + boxWidth;
  1738. return mouseX >= boxLeftX && mouseX <= boxRightX;
  1739. }
  1740. return false;
  1741. }
  1742. /**
  1743. * Messaged when the user clicks the particular row, this invokes
  1744. * toggleExpandState.
  1745. */
  1746. protected void handleExpandControlClick(TreePath path, int mouseX,
  1747. int mouseY) {
  1748. toggleExpandState(path);
  1749. }
  1750. /**
  1751. * Expands path if it is not expanded, or collapses row if it is expanded.
  1752. * If expanding a path and JTree scrolls on expand, ensureRowsAreVisible
  1753. * is invoked to scroll as many of the children to visible as possible
  1754. * (tries to scroll to last visible descendant of path).
  1755. */
  1756. protected void toggleExpandState(TreePath path) {
  1757. if(!tree.isExpanded(path)) {
  1758. int row = getRowForPath(tree, path);
  1759. tree.expandPath(path);
  1760. updateSize();
  1761. if(row != -1) {
  1762. if(tree.getScrollsOnExpand())
  1763. ensureRowsAreVisible(row, row + treeState.
  1764. getVisibleChildCount(path));
  1765. else
  1766. ensureRowsAreVisible(row, row);
  1767. }
  1768. }
  1769. else {
  1770. tree.collapsePath(path);
  1771. updateSize();
  1772. }
  1773. }
  1774. /**
  1775. * Returning true signifies a mouse event on the node should toggle
  1776. * the selection of only the row under mouse.
  1777. */
  1778. protected boolean isToggleSelectionEvent(MouseEvent event) {
  1779. return (SwingUtilities.isLeftMouseButton(event) &&
  1780. event.isControlDown());
  1781. }
  1782. /**
  1783. * Returning true signifies a mouse event on the node should select
  1784. * from the anchor point.
  1785. */
  1786. protected boolean isMultiSelectEvent(MouseEvent event) {
  1787. return (SwingUtilities.isLeftMouseButton(event) &&
  1788. event.isShiftDown());
  1789. }
  1790. /**
  1791. * Returning true indicates the row under the mouse should be toggled
  1792. * based on the event. This is invoked after checkForClickInExpandControl,
  1793. * implying the location is not in the expand (toggle) control
  1794. */
  1795. protected boolean isToggleEvent(MouseEvent event) {
  1796. if(!SwingUtilities.isLeftMouseButton(event)) {
  1797. return false;
  1798. }
  1799. int clickCount = tree.getToggleClickCount();
  1800. if(clickCount <= 0) {
  1801. return false;
  1802. }
  1803. return (event.getClickCount() == clickCount);
  1804. }
  1805. /**
  1806. * Messaged to update the selection based on a MouseEvent over a
  1807. * particular row. If the event is a toggle selection event, the
  1808. * row is either selected, or deselected. If the event identifies
  1809. * a multi selection event, the selection is updated from the
  1810. * anchor point. Otherwise the row is selected, and if the event
  1811. * specified a toggle event the row is expanded/collapsed.
  1812. */
  1813. protected void selectPathForEvent(TreePath path, MouseEvent event) {
  1814. // Should this event toggle the selection of this row?
  1815. /* Control toggles just this node. */
  1816. if(isToggleSelectionEvent(event)) {
  1817. if(tree.isPathSelected(path))
  1818. tree.removeSelectionPath(path);
  1819. else
  1820. tree.addSelectionPath(path);
  1821. lastSelectedRow = getRowForPath(tree, path);
  1822. setAnchorSelectionPath(path);
  1823. setLeadSelectionPath(path);
  1824. }
  1825. /* Adjust from the anchor point. */
  1826. else if(isMultiSelectEvent(event)) {
  1827. TreePath anchor = getAnchorSelectionPath();
  1828. int anchorRow = (anchor == null) ? -1 :
  1829. getRowForPath(tree, anchor);
  1830. if(anchorRow == -1) {
  1831. tree.setSelectionPath(path);
  1832. }
  1833. else {
  1834. int row = getRowForPath(tree, path);
  1835. TreePath lastAnchorPath = anchor;
  1836. if(row < anchorRow)
  1837. tree.setSelectionInterval(row, anchorRow);
  1838. else
  1839. tree.setSelectionInterval(anchorRow, row);
  1840. lastSelectedRow = row;
  1841. setAnchorSelectionPath(lastAnchorPath);
  1842. setLeadSelectionPath(path);
  1843. }
  1844. }
  1845. /* Otherwise set the selection to just this interval. */
  1846. else if(SwingUtilities.isLeftMouseButton(event)) {
  1847. tree.setSelectionPath(path);
  1848. if(isToggleEvent(event)) {
  1849. toggleExpandState(path);
  1850. }
  1851. }
  1852. }
  1853. /**
  1854. * @return true if the node at <code>row</code> is a leaf.
  1855. */
  1856. protected boolean isLeaf(int row) {
  1857. TreePath path = getPathForRow(tree, row);
  1858. if(path != null)
  1859. return treeModel.isLeaf(path.getLastPathComponent());
  1860. // Have to return something here...
  1861. return true;
  1862. }
  1863. //
  1864. // The following selection methods (lead/anchor) are covers for the
  1865. // methods in JTree.
  1866. //
  1867. private void setAnchorSelectionPath(TreePath newPath) {
  1868. ignoreLAChange = true;
  1869. try {
  1870. tree.setAnchorSelectionPath(newPath);
  1871. } finally{
  1872. ignoreLAChange = false;
  1873. }
  1874. }
  1875. private TreePath getAnchorSelectionPath() {
  1876. return tree.getAnchorSelectionPath();
  1877. }
  1878. private void setLeadSelectionPath(TreePath newPath) {
  1879. setLeadSelectionPath(newPath, false);
  1880. }
  1881. private void setLeadSelectionPath(TreePath newPath, boolean repaint) {
  1882. Rectangle bounds = repaint ?
  1883. getPathBounds(tree, getLeadSelectionPath()) : null;
  1884. ignoreLAChange = true;
  1885. try {
  1886. tree.setLeadSelectionPath(newPath);
  1887. } finally {
  1888. ignoreLAChange = false;
  1889. }
  1890. leadRow = getRowForPath(tree, newPath);
  1891. if(repaint) {
  1892. if(bounds != null)
  1893. tree.repaint(bounds);
  1894. bounds = getPathBounds(tree, newPath);
  1895. if(bounds != null)
  1896. tree.repaint(bounds);
  1897. }
  1898. }
  1899. private TreePath getLeadSelectionPath() {
  1900. return tree.getLeadSelectionPath();
  1901. }
  1902. private void updateLeadRow() {
  1903. leadRow = getRowForPath(tree, getLeadSelectionPath());
  1904. }
  1905. private int getLeadSelectionRow() {
  1906. return leadRow;
  1907. }
  1908. /**
  1909. * Extends the selection from the anchor to make <code>newLead</code>
  1910. * the lead of the selection. This does not scroll.
  1911. */
  1912. private void extendSelection(TreePath newLead) {
  1913. TreePath aPath = getAnchorSelectionPath();
  1914. int aRow = (aPath == null) ? -1 :
  1915. getRowForPath(tree, aPath);
  1916. int newIndex = getRowForPath(tree, newLead);
  1917. if(aRow == -1) {
  1918. tree.setSelectionRow(newIndex);
  1919. }
  1920. else {
  1921. if(aRow < newIndex) {
  1922. tree.setSelectionInterval(aRow, newIndex);
  1923. }
  1924. else {
  1925. tree.setSelectionInterval(newIndex, aRow);
  1926. }
  1927. setAnchorSelectionPath(aPath);
  1928. setLeadSelectionPath(newLead);
  1929. }
  1930. }
  1931. /**
  1932. * Invokes <code>repaint</code> on the JTree for the passed in TreePath,
  1933. * <code>path</code>.
  1934. */
  1935. private void repaintPath(TreePath path) {
  1936. if (path != null) {
  1937. Rectangle bounds = getPathBounds(tree, path);
  1938. if (bounds != null) {
  1939. tree.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
  1940. }
  1941. }
  1942. }
  1943. /**
  1944. * Updates the TreeState in response to nodes expanding/collapsing.
  1945. */
  1946. public class TreeExpansionHandler implements TreeExpansionListener {
  1947. /**
  1948. * Called whenever an item in the tree has been expanded.
  1949. */
  1950. public void treeExpanded(TreeExpansionEvent event) {
  1951. if(event != null && tree != null) {
  1952. TreePath path = event.getPath();
  1953. updateExpandedDescendants(path);
  1954. }
  1955. }
  1956. /**
  1957. * Called whenever an item in the tree has been collapsed.
  1958. */
  1959. public void treeCollapsed(TreeExpansionEvent event) {
  1960. if(event != null && tree != null) {
  1961. TreePath path = event.getPath();
  1962. completeEditing();
  1963. if(path != null && tree.isVisible(path)) {
  1964. treeState.setExpandedState(path, false);
  1965. updateLeadRow();
  1966. updateSize();
  1967. }
  1968. }
  1969. }
  1970. } // BasicTreeUI.TreeExpansionHandler
  1971. /**
  1972. * Updates the preferred size when scrolling (if necessary).
  1973. */
  1974. public class ComponentHandler extends ComponentAdapter implements
  1975. ActionListener {
  1976. /** Timer used when inside a scrollpane and the scrollbar is
  1977. * adjusting. */
  1978. protected Timer timer;
  1979. /** ScrollBar that is being adjusted. */
  1980. protected JScrollBar scrollBar;
  1981. public void componentMoved(ComponentEvent e) {
  1982. if(timer == null) {
  1983. JScrollPane scrollPane = getScrollPane();
  1984. if(scrollPane == null)
  1985. updateSize();
  1986. else {
  1987. scrollBar = scrollPane.getVerticalScrollBar();
  1988. if(scrollBar == null ||
  1989. !scrollBar.getValueIsAdjusting()) {
  1990. // Try the horizontal scrollbar.
  1991. if((scrollBar = scrollPane.getHorizontalScrollBar())
  1992. != null && scrollBar.getValueIsAdjusting())
  1993. startTimer();
  1994. else
  1995. updateSize();
  1996. }
  1997. else
  1998. startTimer();
  1999. }
  2000. }
  2001. }
  2002. /**
  2003. * Creates, if necessary, and starts a Timer to check if need to
  2004. * resize the bounds.
  2005. */
  2006. protected void startTimer() {
  2007. if(timer == null) {
  2008. timer = new Timer(200, this);
  2009. timer.setRepeats(true);
  2010. }
  2011. timer.start();
  2012. }
  2013. /**
  2014. * Returns the JScrollPane housing the JTree, or null if one isn't
  2015. * found.
  2016. */
  2017. protected JScrollPane getScrollPane() {
  2018. Component c = tree.getParent();
  2019. while(c != null && !(c instanceof JScrollPane))
  2020. c = c.getParent();
  2021. if(c instanceof JScrollPane)
  2022. return (JScrollPane)c;
  2023. return null;
  2024. }
  2025. /**
  2026. * Public as a result of Timer. If the scrollBar is null, or
  2027. * not adjusting, this stops the timer and updates the sizing.
  2028. */
  2029. public void actionPerformed(ActionEvent ae) {
  2030. if(scrollBar == null || !scrollBar.getValueIsAdjusting()) {
  2031. if(timer != null)
  2032. timer.stop();
  2033. updateSize();
  2034. timer = null;
  2035. scrollBar = null;
  2036. }
  2037. }
  2038. } // End of BasicTreeUI.ComponentHandler
  2039. /**
  2040. * Forwards all TreeModel events to the TreeState.
  2041. */
  2042. public class TreeModelHandler implements TreeModelListener {
  2043. public void treeNodesChanged(TreeModelEvent e) {
  2044. if(treeState != null && e != null) {
  2045. treeState.treeNodesChanged(e);
  2046. TreePath pPath = e.getTreePath().getParentPath();
  2047. if(pPath == null || treeState.isExpanded(pPath))
  2048. updateSize();
  2049. }
  2050. }
  2051. public void treeNodesInserted(TreeModelEvent e) {
  2052. if(treeState != null && e != null) {
  2053. treeState.treeNodesInserted(e);
  2054. updateLeadRow();
  2055. TreePath path = e.getTreePath();
  2056. if(treeState.isExpanded(path)) {
  2057. updateSize();
  2058. }
  2059. else {
  2060. // PENDING(sky): Need a method in TreeModelEvent
  2061. // that can return the count, getChildIndices allocs
  2062. // a new array!
  2063. int[] indices = e.getChildIndices();
  2064. int childCount = treeModel.getChildCount
  2065. (path.getLastPathComponent());
  2066. if(indices != null && (childCount - indices.length) == 0)
  2067. updateSize();
  2068. }
  2069. }
  2070. }
  2071. public void treeNodesRemoved(TreeModelEvent e) {
  2072. if(treeState != null && e != null) {
  2073. treeState.treeNodesRemoved(e);
  2074. updateLeadRow();
  2075. TreePath path = e.getTreePath();
  2076. if(treeState.isExpanded(path) ||
  2077. treeModel.getChildCount(path.getLastPathComponent()) == 0)
  2078. updateSize();
  2079. }
  2080. }
  2081. public void treeStructureChanged(TreeModelEvent e) {
  2082. if(treeState != null && e != null) {
  2083. treeState.treeStructureChanged(e);
  2084. updateLeadRow();
  2085. TreePath pPath = e.getTreePath().getParentPath();
  2086. if(pPath == null || treeState.isExpanded(pPath))
  2087. updateSize();
  2088. }
  2089. }
  2090. } // End of BasicTreeUI.TreeModelHandler
  2091. /**
  2092. * Listens for changes in the selection model and updates the display
  2093. * accordingly.
  2094. */
  2095. public class TreeSelectionHandler implements TreeSelectionListener {
  2096. /**
  2097. * Messaged when the selection changes in the tree we're displaying
  2098. * for. Stops editing, messages super and displays the changed paths.
  2099. */
  2100. public void valueChanged(TreeSelectionEvent event) {
  2101. // Stop editing
  2102. completeEditing();
  2103. // Make sure all the paths are visible, if necessary.
  2104. if(tree.getExpandsSelectedPaths() && treeSelectionModel != null) {
  2105. TreePath[] paths = treeSelectionModel
  2106. .getSelectionPaths();
  2107. if(paths != null) {
  2108. for(int counter = paths.length - 1; counter >= 0;
  2109. counter--) {
  2110. tree.makeVisible(paths[counter]);
  2111. }
  2112. }
  2113. }
  2114. TreePath oldLead = getLeadSelectionPath();
  2115. lastSelectedRow = tree.getMinSelectionRow();
  2116. TreePath lead = tree.getSelectionModel().getLeadSelectionPath();
  2117. setAnchorSelectionPath(lead);
  2118. setLeadSelectionPath(lead);
  2119. TreePath[] changedPaths = event.getPaths();
  2120. Rectangle nodeBounds;
  2121. Rectangle visRect = tree.getVisibleRect();
  2122. boolean paintPaths = true;
  2123. int nWidth = tree.getWidth();
  2124. if(changedPaths != null) {
  2125. int counter, maxCounter = changedPaths.length;
  2126. if(maxCounter > 4) {
  2127. tree.repaint();
  2128. paintPaths = false;
  2129. }
  2130. else {
  2131. for (counter = 0; counter < maxCounter; counter++) {
  2132. nodeBounds = getPathBounds(tree,
  2133. changedPaths[counter]);
  2134. if(nodeBounds != null &&
  2135. visRect.intersects(nodeBounds))
  2136. tree.repaint(0, nodeBounds.y, nWidth,
  2137. nodeBounds.height);
  2138. }
  2139. }
  2140. }
  2141. if(paintPaths) {
  2142. nodeBounds = getPathBounds(tree, oldLead);
  2143. if(nodeBounds != null && visRect.intersects(nodeBounds))
  2144. tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
  2145. nodeBounds = getPathBounds(tree, lead);
  2146. if(nodeBounds != null && visRect.intersects(nodeBounds))
  2147. tree.repaint(0, nodeBounds.y, nWidth, nodeBounds.height);
  2148. }
  2149. }
  2150. }// End of BasicTreeUI.TreeSelectionHandler
  2151. /**
  2152. * Listener responsible for getting cell editing events and updating
  2153. * the tree accordingly.
  2154. */
  2155. public class CellEditorHandler implements CellEditorListener {
  2156. /** Messaged when editing has stopped in the tree. */
  2157. public void editingStopped(ChangeEvent e) {
  2158. completeEditing(false, false, true);
  2159. }
  2160. /** Messaged when editing has been canceled in the tree. */
  2161. public void editingCanceled(ChangeEvent e) {
  2162. completeEditing(false, false, false);
  2163. }
  2164. } // BasicTreeUI.CellEditorHandler
  2165. /**
  2166. * This is used to get mutliple key down events to appropriately generate
  2167. * events.
  2168. */
  2169. // PENDING(sky): Is this still needed?
  2170. public class KeyHandler extends KeyAdapter {
  2171. /** Key code that is being generated for. */
  2172. protected Action repeatKeyAction;
  2173. /** Set to true while keyPressed is active. */
  2174. protected boolean isKeyDown;
  2175. public void keyPressed(KeyEvent e) {
  2176. if(tree != null && tree.hasFocus() && tree.isEnabled()) {
  2177. KeyStroke keyStroke = KeyStroke.getKeyStroke
  2178. (e.getKeyCode(), e.getModifiers());
  2179. if(tree.getConditionForKeyStroke(keyStroke) ==
  2180. JComponent.WHEN_FOCUSED) {
  2181. ActionListener listener = tree.
  2182. getActionForKeyStroke(keyStroke);
  2183. if(listener instanceof Action)
  2184. repeatKeyAction = (Action)listener;
  2185. else
  2186. repeatKeyAction = null;
  2187. }
  2188. else
  2189. repeatKeyAction = null;
  2190. if(isKeyDown && repeatKeyAction != null) {
  2191. repeatKeyAction.actionPerformed(new ActionEvent(tree,
  2192. ActionEvent.ACTION_PERFORMED,
  2193. "")); //tree.getActionCommand()
  2194. e.consume();
  2195. }
  2196. else
  2197. isKeyDown = true;
  2198. }
  2199. }
  2200. public void keyReleased(KeyEvent e) {
  2201. isKeyDown = false;
  2202. }
  2203. } // End of BasicTreeUI.KeyHandler
  2204. /**
  2205. * Repaints the lead selection row when focus is lost/gained.
  2206. */
  2207. public class FocusHandler implements FocusListener {
  2208. /**
  2209. * Invoked when focus is activated on the tree we're in, redraws the
  2210. * lead row.
  2211. */
  2212. public void focusGained(FocusEvent e) {
  2213. if(tree != null) {
  2214. Rectangle pBounds;
  2215. pBounds = getPathBounds(tree, tree.getLeadSelectionPath());
  2216. if(pBounds != null)
  2217. tree.repaint(pBounds);
  2218. pBounds = getPathBounds(tree, getLeadSelectionPath());
  2219. if(pBounds != null)
  2220. tree.repaint(pBounds);
  2221. }
  2222. }
  2223. /**
  2224. * Invoked when focus is activated on the tree we're in, redraws the
  2225. * lead row.
  2226. */
  2227. public void focusLost(FocusEvent e) {
  2228. focusGained(e);
  2229. }
  2230. } // End of class BasicTreeUI.FocusHandler
  2231. /**
  2232. * Class responsible for getting size of node, method is forwarded
  2233. * to BasicTreeUI method. X location does not include insets, that is
  2234. * handled in getPathBounds.
  2235. */
  2236. // This returns locations that don't include any Insets.
  2237. public class NodeDimensionsHandler extends
  2238. AbstractLayoutCache.NodeDimensions {
  2239. /**
  2240. * Responsible for getting the size of a particular node.
  2241. */
  2242. public Rectangle getNodeDimensions(Object value, int row,
  2243. int depth, boolean expanded,
  2244. Rectangle size) {
  2245. // Return size of editing component, if editing and asking
  2246. // for editing row.
  2247. if(editingComponent != null && editingRow == row) {
  2248. Dimension prefSize = editingComponent.
  2249. getPreferredSize();
  2250. int rh = getRowHeight();
  2251. if(rh > 0 && rh != prefSize.height)
  2252. prefSize.height = rh;
  2253. if(size != null) {
  2254. size.x = getRowX(row, depth);
  2255. size.width = prefSize.width;
  2256. size.height = prefSize.height;
  2257. }
  2258. else {
  2259. size = new Rectangle(getRowX(row, depth), 0,
  2260. prefSize.width, prefSize.height);
  2261. }
  2262. if(!leftToRight) {
  2263. size.x = lastWidth - size.width - size.x - 2;
  2264. }
  2265. return size;
  2266. }
  2267. // Not editing, use renderer.
  2268. if(currentCellRenderer != null) {
  2269. Component aComponent;
  2270. aComponent = currentCellRenderer.getTreeCellRendererComponent
  2271. (tree, value, tree.isRowSelected(row),
  2272. expanded, treeModel.isLeaf(value), row,
  2273. false);
  2274. if(tree != null) {
  2275. // Only ever removed when UI changes, this is OK!
  2276. rendererPane.add(aComponent);
  2277. aComponent.validate();
  2278. }
  2279. Dimension prefSize = aComponent.getPreferredSize();
  2280. if(size != null) {
  2281. size.x = getRowX(row, depth);
  2282. size.width = prefSize.width;
  2283. size.height = prefSize.height;
  2284. }
  2285. else {
  2286. size = new Rectangle(getRowX(row, depth), 0,
  2287. prefSize.width, prefSize.height);
  2288. }
  2289. if(!leftToRight) {
  2290. size.x = lastWidth - size.width - size.x - 2;
  2291. }
  2292. return size;
  2293. }
  2294. return null;
  2295. }
  2296. /**
  2297. * @return amount to indent the given row.
  2298. */
  2299. protected int getRowX(int row, int depth) {
  2300. return totalChildIndent * (depth + depthOffset);
  2301. }
  2302. } // End of class BasicTreeUI.NodeDimensionsHandler
  2303. /**
  2304. * TreeMouseListener is responsible for updating the selection
  2305. * based on mouse events.
  2306. */
  2307. public class MouseHandler extends MouseAdapter {
  2308. /**
  2309. * Invoked when a mouse button has been pressed on a component.
  2310. */
  2311. public void mousePressed(MouseEvent e) {
  2312. if(tree != null && tree.isEnabled()) {
  2313. tree.requestFocus();
  2314. TreePath path = getClosestPathForLocation(tree, e.getX(),
  2315. e.getY());
  2316. if(path != null) {
  2317. Rectangle bounds = getPathBounds(tree, path);
  2318. if(e.getY() > (bounds.y + bounds.height)) {
  2319. return;
  2320. }
  2321. // Preferably checkForClickInExpandControl could take
  2322. // the Event to do this it self!
  2323. if(SwingUtilities.isLeftMouseButton(e))
  2324. checkForClickInExpandControl(path, e.getX(), e.getY());
  2325. int x = e.getX();
  2326. // Perhaps they clicked the cell itself. If so,
  2327. // select it.
  2328. if (x > bounds.x) {
  2329. if (x <= (bounds.x + bounds.width) &&
  2330. !startEditing(path, e)) {
  2331. selectPathForEvent(path, e);
  2332. }
  2333. }
  2334. }
  2335. }
  2336. // PENDING: Should select on mouse down, start a drag if
  2337. // the mouse moves, and fire selection change notice on
  2338. // mouse up. That is, the explorer highlights on mouse
  2339. // down, but doesn't update the pane to the right (and
  2340. // open the folder icon) until mouse up.
  2341. }
  2342. } // End of BasicTreeUI.MouseHandler
  2343. /**
  2344. * PropertyChangeListener for the tree. Updates the appropriate
  2345. * varaible, or TreeState, based on what changes.
  2346. */
  2347. public class PropertyChangeHandler implements
  2348. PropertyChangeListener {
  2349. public void propertyChange(PropertyChangeEvent event) {
  2350. if(event.getSource() == tree) {
  2351. String changeName = event.getPropertyName();
  2352. if (changeName.equals(JTree.LEAD_SELECTION_PATH_PROPERTY)) {
  2353. if (!ignoreLAChange) {
  2354. updateLeadRow();
  2355. repaintPath((TreePath)event.getOldValue());
  2356. repaintPath((TreePath)event.getNewValue());
  2357. }
  2358. }
  2359. else if (changeName.equals(JTree.
  2360. ANCHOR_SELECTION_PATH_PROPERTY)) {
  2361. if (!ignoreLAChange) {
  2362. repaintPath((TreePath)event.getOldValue());
  2363. repaintPath((TreePath)event.getNewValue());
  2364. }
  2365. }
  2366. if(changeName.equals(JTree.CELL_RENDERER_PROPERTY)) {
  2367. setCellRenderer((TreeCellRenderer)event.getNewValue());
  2368. redoTheLayout();
  2369. }
  2370. else if(changeName.equals(JTree.TREE_MODEL_PROPERTY)) {
  2371. setModel((TreeModel)event.getNewValue());
  2372. }
  2373. else if(changeName.equals(JTree.ROOT_VISIBLE_PROPERTY)) {
  2374. setRootVisible(((Boolean)event.getNewValue()).
  2375. booleanValue());
  2376. }
  2377. else if(changeName.equals(JTree.SHOWS_ROOT_HANDLES_PROPERTY)) {
  2378. setShowsRootHandles(((Boolean)event.getNewValue()).
  2379. booleanValue());
  2380. }
  2381. else if(changeName.equals(JTree.ROW_HEIGHT_PROPERTY)) {
  2382. setRowHeight(((Integer)event.getNewValue()).
  2383. intValue());
  2384. }
  2385. else if(changeName.equals(JTree.CELL_EDITOR_PROPERTY)) {
  2386. setCellEditor((TreeCellEditor)event.getNewValue());
  2387. }
  2388. else if(changeName.equals(JTree.EDITABLE_PROPERTY)) {
  2389. setEditable(((Boolean)event.getNewValue()).booleanValue());
  2390. }
  2391. else if(changeName.equals(JTree.LARGE_MODEL_PROPERTY)) {
  2392. setLargeModel(tree.isLargeModel());
  2393. }
  2394. else if(changeName.equals(JTree.SELECTION_MODEL_PROPERTY)) {
  2395. setSelectionModel(tree.getSelectionModel());
  2396. }
  2397. else if(changeName.equals("font")) {
  2398. completeEditing();
  2399. if(treeState != null)
  2400. treeState.invalidateSizes();
  2401. updateSize();
  2402. }
  2403. else if (changeName.equals("componentOrientation")) {
  2404. if (tree != null) {
  2405. leftToRight = BasicGraphicsUtils.isLeftToRight(tree);
  2406. redoTheLayout();
  2407. tree.treeDidChange();
  2408. }
  2409. }
  2410. }
  2411. }
  2412. } // End of BasicTreeUI.PropertyChangeHandler
  2413. /**
  2414. * Listener on the TreeSelectionModel, resets the row selection if
  2415. * any of the properties of the model change.
  2416. */
  2417. public class SelectionModelPropertyChangeHandler implements
  2418. PropertyChangeListener {
  2419. public void propertyChange(PropertyChangeEvent event) {
  2420. if(event.getSource() == treeSelectionModel)
  2421. treeSelectionModel.resetRowSelection();
  2422. }
  2423. } // End of BasicTreeUI.SelectionModelPropertyChangeHandler
  2424. /**
  2425. * TreeTraverseAction is the action used for left/right keys.
  2426. * Will toggle the expandedness of a node, as well as potentially
  2427. * incrementing the selection.
  2428. */
  2429. public class TreeTraverseAction extends AbstractAction {
  2430. /** Determines direction to traverse, 1 means expand, -1 means
  2431. * collapse. */
  2432. protected int direction;
  2433. /** True if the selection is reset, false means only the lead path
  2434. * changes. */
  2435. private boolean changeSelection;
  2436. public TreeTraverseAction(int direction, String name) {
  2437. this(direction, name, true);
  2438. }
  2439. private TreeTraverseAction(int direction, String name,
  2440. boolean changeSelection) {
  2441. this.direction = direction;
  2442. this.changeSelection = changeSelection;
  2443. }
  2444. public void actionPerformed(ActionEvent e) {
  2445. int rowCount;
  2446. if(tree != null && (rowCount = getRowCount(tree)) > 0) {
  2447. int minSelIndex = getLeadSelectionRow();
  2448. int newIndex;
  2449. if(minSelIndex == -1)
  2450. newIndex = 0;
  2451. else {
  2452. /* Try and expand the node, otherwise go to next
  2453. node. */
  2454. if(direction == 1) {
  2455. if(!isLeaf(minSelIndex) &&
  2456. !tree.isExpanded(minSelIndex)) {
  2457. toggleExpandState(getPathForRow
  2458. (tree, minSelIndex));
  2459. newIndex = -1;
  2460. }
  2461. else
  2462. newIndex = Math.min(minSelIndex + 1, rowCount - 1);
  2463. }
  2464. /* Try to collapse node. */
  2465. else {
  2466. if(!isLeaf(minSelIndex) &&
  2467. tree.isExpanded(minSelIndex)) {
  2468. toggleExpandState(getPathForRow
  2469. (tree, minSelIndex));
  2470. newIndex = -1;
  2471. }
  2472. else {
  2473. TreePath path = getPathForRow(tree,
  2474. minSelIndex);
  2475. if(path != null && path.getPathCount() > 1) {
  2476. newIndex = getRowForPath(tree, path.
  2477. getParentPath());
  2478. }
  2479. else
  2480. newIndex = -1;
  2481. }
  2482. }
  2483. }
  2484. if(newIndex != -1) {
  2485. if(changeSelection) {
  2486. tree.setSelectionInterval(newIndex, newIndex);
  2487. }
  2488. else {
  2489. setLeadSelectionPath(getPathForRow(tree, newIndex),
  2490. true);
  2491. }
  2492. ensureRowsAreVisible(newIndex, newIndex);
  2493. }
  2494. }
  2495. }
  2496. public boolean isEnabled() { return (tree != null &&
  2497. tree.isEnabled()); }
  2498. } // BasicTreeUI.TreeTraverseAction
  2499. /** TreePageAction handles page up and page down events.
  2500. */
  2501. public class TreePageAction extends AbstractAction {
  2502. /** Specifies the direction to adjust the selection by. */
  2503. protected int direction;
  2504. /** True indicates should set selection from anchor path. */
  2505. private boolean addToSelection;
  2506. private boolean changeSelection;
  2507. public TreePageAction(int direction, String name) {
  2508. this(direction, name, false, true);
  2509. }
  2510. private TreePageAction(int direction, String name,
  2511. boolean addToSelection,
  2512. boolean changeSelection) {
  2513. this.direction = direction;
  2514. this.addToSelection = addToSelection;
  2515. this.changeSelection = changeSelection;
  2516. }
  2517. public void actionPerformed(ActionEvent e) {
  2518. int rowCount;
  2519. if(tree != null && (rowCount = getRowCount(tree)) > 0 &&
  2520. treeSelectionModel != null) {
  2521. Dimension maxSize = tree.getSize();
  2522. TreePath lead = getLeadSelectionPath();
  2523. TreePath newPath;
  2524. Rectangle visRect = tree.getVisibleRect();
  2525. if(direction == -1) {
  2526. // up.
  2527. newPath = getClosestPathForLocation(tree, visRect.x,
  2528. visRect.y);
  2529. if(newPath.equals(lead)) {
  2530. visRect.y = Math.max(0, visRect.y - visRect.height);
  2531. newPath = tree.getClosestPathForLocation(visRect.x,
  2532. visRect.y);
  2533. }
  2534. }
  2535. else {
  2536. // down
  2537. visRect.y = Math.min(maxSize.height, visRect.y +
  2538. visRect.height - 1);
  2539. newPath = tree.getClosestPathForLocation(visRect.x,
  2540. visRect.y);
  2541. if(newPath.equals(lead)) {
  2542. visRect.y = Math.min(maxSize.height, visRect.y +
  2543. visRect.height - 1);
  2544. newPath = tree.getClosestPathForLocation(visRect.x,
  2545. visRect.y);
  2546. }
  2547. }
  2548. Rectangle newRect = getPathBounds(tree, newPath);
  2549. newRect.x = visRect.x;
  2550. newRect.width = visRect.width;
  2551. if(direction == -1) {
  2552. newRect.height = visRect.height;
  2553. }
  2554. else {
  2555. newRect.y -= (visRect.height - newRect.height);
  2556. newRect.height = visRect.height;
  2557. }
  2558. if(addToSelection) {
  2559. extendSelection(newPath);
  2560. }
  2561. else if(changeSelection) {
  2562. tree.setSelectionPath(newPath);
  2563. }
  2564. else {
  2565. setLeadSelectionPath(newPath, true);
  2566. }
  2567. tree.scrollRectToVisible(newRect);
  2568. }
  2569. }
  2570. public boolean isEnabled() { return (tree != null &&
  2571. tree.isEnabled()); }
  2572. } // BasicTreeUI.TreePageAction
  2573. /**
  2574. * Scrolls the tree left/right the visible width of the tree. Will select
  2575. * either the first/last visible node.
  2576. */
  2577. // Will be made public later.
  2578. private class TreeScrollLRAction extends AbstractAction {
  2579. /** Specifies the direction to adjust the selection by. */
  2580. protected int direction;
  2581. private boolean addToSelection;
  2582. private boolean changeSelection;
  2583. TreeScrollLRAction(int direction, String name, boolean addToSelection,
  2584. boolean changeSelection) {
  2585. this.direction = direction;
  2586. this.addToSelection = addToSelection;
  2587. this.changeSelection = changeSelection;
  2588. }
  2589. public void actionPerformed(ActionEvent e) {
  2590. int rowCount;
  2591. if(tree != null && (rowCount = getRowCount(tree)) > 0 &&
  2592. treeSelectionModel != null) {
  2593. TreePath newPath;
  2594. Rectangle visRect = tree.getVisibleRect();
  2595. if (direction == -1) {
  2596. newPath = getClosestPathForLocation(tree, visRect.x,
  2597. visRect.y);
  2598. visRect.x = Math.max(0, visRect.x - visRect.width);
  2599. }
  2600. else {
  2601. visRect.x = Math.min(Math.max(0, tree.getWidth() -
  2602. visRect.width), visRect.x + visRect.width);
  2603. newPath = getClosestPathForLocation(tree, visRect.x,
  2604. visRect.y + visRect.height);
  2605. }
  2606. // Scroll
  2607. tree.scrollRectToVisible(visRect);
  2608. // select
  2609. if (addToSelection) {
  2610. extendSelection(newPath);
  2611. }
  2612. else if(changeSelection) {
  2613. tree.setSelectionPath(newPath);
  2614. }
  2615. else {
  2616. setLeadSelectionPath(newPath, true);
  2617. }
  2618. }
  2619. }
  2620. public boolean isEnabled() { return (tree != null &&
  2621. tree.isEnabled()); }
  2622. } // End of BasicTreeUI.TreeScrollLRAction
  2623. /** TreeIncrementAction is used to handle up/down actions. Selection
  2624. * is moved up or down based on direction.
  2625. */
  2626. public class TreeIncrementAction extends AbstractAction {
  2627. /** Specifies the direction to adjust the selection by. */
  2628. protected int direction;
  2629. /** If true the new item is added to the selection, if false the
  2630. * selection is reset. */
  2631. private boolean addToSelection;
  2632. private boolean changeSelection;
  2633. public TreeIncrementAction(int direction, String name) {
  2634. this(direction, name, false, true);
  2635. }
  2636. private TreeIncrementAction(int direction, String name,
  2637. boolean addToSelection,
  2638. boolean changeSelection) {
  2639. this.direction = direction;
  2640. this.addToSelection = addToSelection;
  2641. this.changeSelection = changeSelection;
  2642. }
  2643. public void actionPerformed(ActionEvent e) {
  2644. int rowCount;
  2645. if(tree != null && treeSelectionModel != null &&
  2646. (rowCount = getRowCount(tree)) > 0) {
  2647. int selIndex = getLeadSelectionRow();
  2648. int newIndex;
  2649. if(selIndex == -1) {
  2650. if(direction == 1)
  2651. newIndex = 0;
  2652. else
  2653. newIndex = rowCount - 1;
  2654. }
  2655. else
  2656. /* Aparently people don't like wrapping;( */
  2657. newIndex = Math.min(rowCount - 1, Math.max
  2658. (0, (selIndex + direction)));
  2659. if(addToSelection) {
  2660. extendSelection(getPathForRow(tree, newIndex));
  2661. }
  2662. else if(changeSelection) {
  2663. tree.setSelectionInterval(newIndex, newIndex);
  2664. }
  2665. else {
  2666. setLeadSelectionPath(getPathForRow(tree, newIndex), true);
  2667. }
  2668. ensureRowsAreVisible(newIndex, newIndex);
  2669. lastSelectedRow = newIndex;
  2670. }
  2671. }
  2672. public boolean isEnabled() { return (tree != null &&
  2673. tree.isEnabled()); }
  2674. } // End of class BasicTreeUI.TreeIncrementAction
  2675. /**
  2676. * TreeHomeAction is used to handle end/home actions.
  2677. * Scrolls either the first or last cell to be visible based on
  2678. * direction.
  2679. */
  2680. public class TreeHomeAction extends AbstractAction {
  2681. protected int direction;
  2682. /** Set to true if append to selection. */
  2683. private boolean addToSelection;
  2684. private boolean changeSelection;
  2685. public TreeHomeAction(int direction, String name) {
  2686. this(direction, name, false, true);
  2687. }
  2688. private TreeHomeAction(int direction, String name,
  2689. boolean addToSelection,
  2690. boolean changeSelection) {
  2691. this.direction = direction;
  2692. this.changeSelection = changeSelection;
  2693. this.addToSelection = addToSelection;
  2694. }
  2695. public void actionPerformed(ActionEvent e) {
  2696. int rowCount = getRowCount(tree);
  2697. if(tree != null && rowCount > 0) {
  2698. if(direction == -1) {
  2699. ensureRowsAreVisible(0, 0);
  2700. if (addToSelection) {
  2701. TreePath aPath = getAnchorSelectionPath();
  2702. int aRow = (aPath == null) ? -1 :
  2703. getRowForPath(tree, aPath);
  2704. if (aRow == -1) {
  2705. tree.setSelectionInterval(0, 0);
  2706. }
  2707. else {
  2708. tree.setSelectionInterval(0, aRow);
  2709. setAnchorSelectionPath(aPath);
  2710. setLeadSelectionPath(getPathForRow(tree, 0));
  2711. }
  2712. }
  2713. else if(changeSelection) {
  2714. tree.setSelectionInterval(0, 0);
  2715. }
  2716. else {
  2717. setLeadSelectionPath(getPathForRow(tree, 0), true);
  2718. }
  2719. }
  2720. else {
  2721. ensureRowsAreVisible(rowCount - 1, rowCount - 1);
  2722. if (addToSelection) {
  2723. TreePath aPath = getAnchorSelectionPath();
  2724. int aRow = (aPath == null) ? -1 :
  2725. getRowForPath(tree, aPath);
  2726. if (aRow == -1) {
  2727. tree.setSelectionInterval(rowCount - 1,
  2728. rowCount -1);
  2729. }
  2730. else {
  2731. tree.setSelectionInterval(aRow, rowCount - 1);
  2732. setAnchorSelectionPath(aPath);
  2733. setLeadSelectionPath(getPathForRow(tree,
  2734. rowCount -1));
  2735. }
  2736. }
  2737. else if(changeSelection) {
  2738. tree.setSelectionInterval(rowCount - 1, rowCount - 1);
  2739. }
  2740. else {
  2741. setLeadSelectionPath(getPathForRow(tree,
  2742. rowCount - 1), true);
  2743. }
  2744. }
  2745. }
  2746. }
  2747. public boolean isEnabled() { return (tree != null &&
  2748. tree.isEnabled()); }
  2749. } // End of class BasicTreeUI.TreeHomeAction
  2750. /**
  2751. * For the first selected row expandedness will be toggled.
  2752. */
  2753. public class TreeToggleAction extends AbstractAction {
  2754. public TreeToggleAction(String name) {
  2755. }
  2756. public void actionPerformed(ActionEvent e) {
  2757. if(tree != null) {
  2758. int selRow = getLeadSelectionRow();
  2759. if(selRow != -1 && !isLeaf(selRow)) {
  2760. TreePath aPath = getAnchorSelectionPath();
  2761. TreePath lPath = getLeadSelectionPath();
  2762. toggleExpandState(getPathForRow(tree, selRow));
  2763. setAnchorSelectionPath(aPath);
  2764. setLeadSelectionPath(lPath);
  2765. }
  2766. }
  2767. }
  2768. public boolean isEnabled() { return (tree != null &&
  2769. tree.isEnabled()); }
  2770. } // End of class BasicTreeUI.TreeToggleAction
  2771. /**
  2772. * Scrolls the component it is created with a specified amount.
  2773. */
  2774. private static class ScrollAction extends AbstractAction {
  2775. private JComponent component;
  2776. private int direction;
  2777. private int amount;
  2778. public ScrollAction(JComponent component, int direction, int amount) {
  2779. this.component = component;
  2780. this.direction = direction;
  2781. this.amount = amount;
  2782. }
  2783. public void actionPerformed(ActionEvent e) {
  2784. Rectangle visRect = component.getVisibleRect();
  2785. Dimension size = component.getSize();
  2786. if (direction == SwingConstants.HORIZONTAL) {
  2787. visRect.x += amount;
  2788. visRect.x = Math.max(0, visRect.x);
  2789. visRect.x = Math.min(Math.max(0, size.width - visRect.width),
  2790. visRect.x);
  2791. }
  2792. else {
  2793. visRect.y += amount;
  2794. visRect.y = Math.max(0, visRect.y);
  2795. visRect.y = Math.min(Math.max(0, size.width - visRect.height),
  2796. visRect.y);
  2797. }
  2798. component.scrollRectToVisible(visRect);
  2799. }
  2800. }
  2801. /**
  2802. * ActionListener that invokes cancelEditing when action performed.
  2803. */
  2804. public class TreeCancelEditingAction extends AbstractAction {
  2805. public TreeCancelEditingAction(String name) {
  2806. }
  2807. public void actionPerformed(ActionEvent e) {
  2808. if(tree != null) {
  2809. tree.cancelEditing();
  2810. }
  2811. }
  2812. public boolean isEnabled() { return (tree != null &&
  2813. tree.isEnabled()); }
  2814. } // End of class BasicTreeUI.TreeCancelEditingAction
  2815. /**
  2816. * ActionListener invoked to start editing on the leadPath.
  2817. */
  2818. private class TreeEditAction extends AbstractAction {
  2819. public TreeEditAction(String name) {
  2820. }
  2821. public void actionPerformed(ActionEvent ae) {
  2822. if(tree != null && tree.isEnabled()) {
  2823. TreePath lead = getLeadSelectionPath();
  2824. int editRow = (lead != null) ?
  2825. getRowForPath(tree, lead) : -1;
  2826. if(editRow != -1) {
  2827. tree.startEditingAtPath(lead);
  2828. }
  2829. }
  2830. }
  2831. public boolean isEnabled() { return (tree != null &&
  2832. tree.isEnabled()); }
  2833. } // End of BasicTreeUI.TreeEditAction
  2834. /**
  2835. * Action to select everything in tree.
  2836. */
  2837. private class TreeSelectAllAction extends AbstractAction {
  2838. private boolean selectAll;
  2839. public TreeSelectAllAction(String name, boolean selectAll) {
  2840. this.selectAll = selectAll;
  2841. }
  2842. public void actionPerformed(ActionEvent ae) {
  2843. int rowCount = getRowCount(tree);
  2844. if(tree != null && rowCount > 0) {
  2845. if(selectAll) {
  2846. TreePath lastPath = getLeadSelectionPath();
  2847. TreePath aPath = getAnchorSelectionPath();
  2848. if(lastPath != null && !tree.isVisible(lastPath)) {
  2849. lastPath = null;
  2850. }
  2851. tree.setSelectionInterval(0, rowCount - 1);
  2852. if(lastPath != null) {
  2853. setLeadSelectionPath(lastPath);
  2854. }
  2855. if(aPath != null && tree.isVisible(aPath)) {
  2856. setAnchorSelectionPath(aPath);
  2857. }
  2858. else if(lastPath != null) {
  2859. setAnchorSelectionPath(lastPath);
  2860. }
  2861. }
  2862. else {
  2863. TreePath lastPath = getLeadSelectionPath();
  2864. TreePath aPath = getAnchorSelectionPath();
  2865. tree.clearSelection();
  2866. setAnchorSelectionPath(aPath);
  2867. setLeadSelectionPath(lastPath);
  2868. }
  2869. }
  2870. }
  2871. public boolean isEnabled() { return (tree != null &&
  2872. tree.isEnabled()); }
  2873. } // End of BasicTreeUI.TreeSelectAllAction
  2874. /**
  2875. * Action to select everything in tree.
  2876. */
  2877. private class TreeAddSelectionAction extends AbstractAction {
  2878. private boolean changeAnchor;
  2879. public TreeAddSelectionAction(String name, boolean changeAnchor) {
  2880. this.changeAnchor = changeAnchor;
  2881. }
  2882. public void actionPerformed(ActionEvent ae) {
  2883. int rowCount = getRowCount(tree);
  2884. if(tree != null && rowCount > 0) {
  2885. int lead = getLeadSelectionRow();
  2886. TreePath aPath = getAnchorSelectionPath();
  2887. TreePath lPath = getLeadSelectionPath();
  2888. if(lead == -1) {
  2889. lead = 0;
  2890. }
  2891. if(!changeAnchor) {
  2892. if(tree.isRowSelected(lead)) {
  2893. tree.removeSelectionRow(lead);
  2894. setLeadSelectionPath(lPath);
  2895. }
  2896. else {
  2897. tree.addSelectionRow(lead);
  2898. }
  2899. setAnchorSelectionPath(aPath);
  2900. }
  2901. else {
  2902. tree.setSelectionRow(lead);
  2903. }
  2904. }
  2905. }
  2906. public boolean isEnabled() { return (tree != null &&
  2907. tree.isEnabled()); }
  2908. } // End of BasicTreeUI.TreeAddSelectionAction
  2909. /**
  2910. * Action to select everything in tree.
  2911. */
  2912. private class TreeExtendSelectionAction extends AbstractAction {
  2913. public TreeExtendSelectionAction(String name) {
  2914. }
  2915. public void actionPerformed(ActionEvent ae) {
  2916. if(tree != null && getRowCount(tree) > 0) {
  2917. int lead = getLeadSelectionRow();
  2918. if(lead != -1) {
  2919. TreePath leadP = getLeadSelectionPath();
  2920. TreePath aPath = getAnchorSelectionPath();
  2921. int aRow = getRowForPath(tree, aPath);
  2922. if(aRow == -1)
  2923. aRow = 0;
  2924. tree.setSelectionInterval(aRow, lead);
  2925. setLeadSelectionPath(leadP);
  2926. setAnchorSelectionPath(aPath);
  2927. }
  2928. }
  2929. }
  2930. public boolean isEnabled() { return (tree != null &&
  2931. tree.isEnabled()); }
  2932. } // End of BasicTreeUI.TreeExtendSelectionAction
  2933. /**
  2934. * MouseInputHandler handles passing all mouse events,
  2935. * including mouse motion events, until the mouse is released to
  2936. * the destination it is constructed with. It is assumed all the
  2937. * events are currently target at source.
  2938. */
  2939. // PENDING(sky): this could actually be moved into a general
  2940. // location, no reason to be in here.
  2941. public class MouseInputHandler extends Object implements
  2942. MouseInputListener
  2943. {
  2944. /** Source that events are coming from. */
  2945. protected Component source;
  2946. /** Destination that recieves all events. */
  2947. protected Component destination;
  2948. public MouseInputHandler(Component source, Component destination,
  2949. MouseEvent event){
  2950. this.source = source;
  2951. this.destination = destination;
  2952. this.source.addMouseListener(this);
  2953. this.source.addMouseMotionListener(this);
  2954. /* Dispatch the editing event! */
  2955. destination.dispatchEvent(SwingUtilities.convertMouseEvent
  2956. (source, event, destination));
  2957. }
  2958. public void mouseClicked(MouseEvent e) {
  2959. if(destination != null)
  2960. destination.dispatchEvent(SwingUtilities.convertMouseEvent
  2961. (source, e, destination));
  2962. }
  2963. public void mousePressed(MouseEvent e) {
  2964. }
  2965. public void mouseReleased(MouseEvent e) {
  2966. if(destination != null)
  2967. destination.dispatchEvent(SwingUtilities.convertMouseEvent
  2968. (source, e, destination));
  2969. removeFromSource();
  2970. }
  2971. public void mouseEntered(MouseEvent e) {
  2972. if (!SwingUtilities.isLeftMouseButton(e)) {
  2973. removeFromSource();
  2974. }
  2975. }
  2976. public void mouseExited(MouseEvent e) {
  2977. if (!SwingUtilities.isLeftMouseButton(e)) {
  2978. removeFromSource();
  2979. }
  2980. }
  2981. public void mouseDragged(MouseEvent e) {
  2982. if(destination != null)
  2983. destination.dispatchEvent(SwingUtilities.convertMouseEvent
  2984. (source, e, destination));
  2985. }
  2986. public void mouseMoved(MouseEvent e) {
  2987. removeFromSource();
  2988. }
  2989. protected void removeFromSource() {
  2990. if(source != null) {
  2991. source.removeMouseListener(this);
  2992. source.removeMouseMotionListener(this);
  2993. }
  2994. source = destination = null;
  2995. }
  2996. } // End of class BasicTreeUI.MouseInputHandler
  2997. } // End of class BasicTreeUI