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