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