1. /*
  2. * @(#)DefaultTreeCellEditor.java 1.30 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.tree;
  8. import javax.swing.*;
  9. import javax.swing.border.*;
  10. import javax.swing.event.*;
  11. import javax.swing.plaf.FontUIResource;
  12. import java.awt.*;
  13. import java.awt.event.*;
  14. import java.beans.*;
  15. import java.io.*;
  16. import java.util.EventObject;
  17. import java.util.Vector;
  18. /**
  19. * A <code>TreeCellEditor</code>. You need to supply an
  20. * instance of <code>DefaultTreeCellRenderer</code>
  21. * so that the icons can be obtained. You can optionally supply
  22. * a <code>TreeCellEditor</code> that will be layed out according
  23. * to the icon in the <code>DefaultTreeCellRenderer</code>.
  24. * If you do not supply a <code>TreeCellEditor</code>,
  25. * a <code>TextField</code> will be used. Editing is started
  26. * on a triple mouse click, or after a click, pause, click and
  27. * a delay of 1200 miliseconds.
  28. *<p>
  29. * <strong>Warning:</strong>
  30. * Serialized objects of this class will not be compatible with
  31. * future Swing releases. The current serialization support is
  32. * appropriate for short term storage or RMI between applications running
  33. * the same version of Swing. As of 1.4, support for long term storage
  34. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  35. * has been added to the <code>java.beans</code> package.
  36. * Please see {@link java.beans.XMLEncoder}.
  37. *
  38. * @see javax.swing.JTree
  39. *
  40. * @version 1.30 12/19/03
  41. * @author Scott Violet
  42. */
  43. public class DefaultTreeCellEditor implements ActionListener, TreeCellEditor,
  44. TreeSelectionListener {
  45. /** Editor handling the editing. */
  46. protected TreeCellEditor realEditor;
  47. /** Renderer, used to get border and offsets from. */
  48. protected DefaultTreeCellRenderer renderer;
  49. /** Editing container, will contain the <code>editorComponent</code>. */
  50. protected Container editingContainer;
  51. /**
  52. * Component used in editing, obtained from the
  53. * <code>editingContainer</code>.
  54. */
  55. transient protected Component editingComponent;
  56. /**
  57. * As of Java 2 platform v1.4 this field should no longer be used. If
  58. * you wish to provide similar behavior you should directly override
  59. * <code>isCellEditable</code>.
  60. */
  61. protected boolean canEdit;
  62. /**
  63. * Used in editing. Indicates x position to place
  64. * <code>editingComponent</code>.
  65. */
  66. protected transient int offset;
  67. /** <code>JTree</code> instance listening too. */
  68. protected transient JTree tree;
  69. /** Last path that was selected. */
  70. protected transient TreePath lastPath;
  71. /** Used before starting the editing session. */
  72. protected transient Timer timer;
  73. /**
  74. * Row that was last passed into
  75. * <code>getTreeCellEditorComponent</code>.
  76. */
  77. protected transient int lastRow;
  78. /** True if the border selection color should be drawn. */
  79. protected Color borderSelectionColor;
  80. /** Icon to use when editing. */
  81. protected transient Icon editingIcon;
  82. /**
  83. * Font to paint with, <code>null</code> indicates
  84. * font of renderer is to be used.
  85. */
  86. protected Font font;
  87. /**
  88. * Constructs a <code>DefaultTreeCellEditor</code>
  89. * object for a JTree using the specified renderer and
  90. * a default editor. (Use this constructor for normal editing.)
  91. *
  92. * @param tree a <code>JTree</code> object
  93. * @param renderer a <code>DefaultTreeCellRenderer</code> object
  94. */
  95. public DefaultTreeCellEditor(JTree tree,
  96. DefaultTreeCellRenderer renderer) {
  97. this(tree, renderer, null);
  98. }
  99. /**
  100. * Constructs a <code>DefaultTreeCellEditor</code>
  101. * object for a <code>JTree</code> using the
  102. * specified renderer and the specified editor. (Use this constructor
  103. * for specialized editing.)
  104. *
  105. * @param tree a <code>JTree</code> object
  106. * @param renderer a <code>DefaultTreeCellRenderer</code> object
  107. * @param editor a <code>TreeCellEditor</code> object
  108. */
  109. public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,
  110. TreeCellEditor editor) {
  111. this.renderer = renderer;
  112. realEditor = editor;
  113. if(realEditor == null)
  114. realEditor = createTreeCellEditor();
  115. editingContainer = createContainer();
  116. setTree(tree);
  117. setBorderSelectionColor(UIManager.getColor
  118. ("Tree.editorBorderSelectionColor"));
  119. }
  120. /**
  121. * Sets the color to use for the border.
  122. * @param newColor the new border color
  123. */
  124. public void setBorderSelectionColor(Color newColor) {
  125. borderSelectionColor = newColor;
  126. }
  127. /**
  128. * Returns the color the border is drawn.
  129. * @return the border selection color
  130. */
  131. public Color getBorderSelectionColor() {
  132. return borderSelectionColor;
  133. }
  134. /**
  135. * Sets the font to edit with. <code>null</code> indicates
  136. * the renderers font should be used. This will NOT
  137. * override any font you have set in the editor
  138. * the receiver was instantied with. If <code>null</code>
  139. * for an editor was passed in a default editor will be
  140. * created that will pick up this font.
  141. *
  142. * @param font the editing <code>Font</code>
  143. * @see #getFont
  144. */
  145. public void setFont(Font font) {
  146. this.font = font;
  147. }
  148. /**
  149. * Gets the font used for editing.
  150. *
  151. * @return the editing <code>Font</code>
  152. * @see #setFont
  153. */
  154. public Font getFont() {
  155. return font;
  156. }
  157. //
  158. // TreeCellEditor
  159. //
  160. /**
  161. * Configures the editor. Passed onto the <code>realEditor</code>.
  162. */
  163. public Component getTreeCellEditorComponent(JTree tree, Object value,
  164. boolean isSelected,
  165. boolean expanded,
  166. boolean leaf, int row) {
  167. setTree(tree);
  168. lastRow = row;
  169. determineOffset(tree, value, isSelected, expanded, leaf, row);
  170. if (editingComponent != null) {
  171. editingContainer.remove(editingComponent);
  172. }
  173. editingComponent = realEditor.getTreeCellEditorComponent(tree, value,
  174. isSelected, expanded,leaf, row);
  175. // this is kept for backwards compatability but isn't really needed
  176. // with the current BasicTreeUI implementation.
  177. TreePath newPath = tree.getPathForRow(row);
  178. canEdit = (lastPath != null && newPath != null &&
  179. lastPath.equals(newPath));
  180. Font font = getFont();
  181. if(font == null) {
  182. if(renderer != null)
  183. font = renderer.getFont();
  184. if(font == null)
  185. font = tree.getFont();
  186. }
  187. editingContainer.setFont(font);
  188. prepareForEditing();
  189. return editingContainer;
  190. }
  191. /**
  192. * Returns the value currently being edited.
  193. * @return the value currently being edited
  194. */
  195. public Object getCellEditorValue() {
  196. return realEditor.getCellEditorValue();
  197. }
  198. /**
  199. * If the <code>realEditor</code> returns true to this
  200. * message, <code>prepareForEditing</code>
  201. * is messaged and true is returned.
  202. */
  203. public boolean isCellEditable(EventObject event) {
  204. boolean retValue = false;
  205. boolean editable = false;
  206. if (event != null) {
  207. if (event.getSource() instanceof JTree) {
  208. setTree((JTree)event.getSource());
  209. if (event instanceof MouseEvent) {
  210. TreePath path = tree.getPathForLocation(
  211. ((MouseEvent)event).getX(),
  212. ((MouseEvent)event).getY());
  213. editable = (lastPath != null && path != null &&
  214. lastPath.equals(path));
  215. if (path!=null) {
  216. lastRow = tree.getRowForPath(path);
  217. Object value = path.getLastPathComponent();
  218. boolean isSelected = tree.isRowSelected(lastRow);
  219. boolean expanded = tree.isExpanded(path);
  220. TreeModel treeModel = tree.getModel();
  221. boolean leaf = treeModel.isLeaf(value);
  222. determineOffset(tree, value, isSelected,
  223. expanded, leaf, lastRow);
  224. }
  225. }
  226. }
  227. }
  228. if(!realEditor.isCellEditable(event))
  229. return false;
  230. if(canEditImmediately(event))
  231. retValue = true;
  232. else if(editable && shouldStartEditingTimer(event)) {
  233. startEditingTimer();
  234. }
  235. else if(timer != null && timer.isRunning())
  236. timer.stop();
  237. if(retValue)
  238. prepareForEditing();
  239. return retValue;
  240. }
  241. /**
  242. * Messages the <code>realEditor</code> for the return value.
  243. */
  244. public boolean shouldSelectCell(EventObject event) {
  245. return realEditor.shouldSelectCell(event);
  246. }
  247. /**
  248. * If the <code>realEditor</code> will allow editing to stop,
  249. * the <code>realEditor</code> is removed and true is returned,
  250. * otherwise false is returned.
  251. */
  252. public boolean stopCellEditing() {
  253. if(realEditor.stopCellEditing()) {
  254. cleanupAfterEditing();
  255. return true;
  256. }
  257. return false;
  258. }
  259. /**
  260. * Messages <code>cancelCellEditing</code> to the
  261. * <code>realEditor</code> and removes it from this instance.
  262. */
  263. public void cancelCellEditing() {
  264. realEditor.cancelCellEditing();
  265. cleanupAfterEditing();
  266. }
  267. /**
  268. * Adds the <code>CellEditorListener</code>.
  269. * @param l the listener to be added
  270. */
  271. public void addCellEditorListener(CellEditorListener l) {
  272. realEditor.addCellEditorListener(l);
  273. }
  274. /**
  275. * Removes the previously added <code>CellEditorListener</code>.
  276. * @param l the listener to be removed
  277. */
  278. public void removeCellEditorListener(CellEditorListener l) {
  279. realEditor.removeCellEditorListener(l);
  280. }
  281. /**
  282. * Returns an array of all the <code>CellEditorListener</code>s added
  283. * to this DefaultTreeCellEditor with addCellEditorListener().
  284. *
  285. * @return all of the <code>CellEditorListener</code>s added or an empty
  286. * array if no listeners have been added
  287. * @since 1.4
  288. */
  289. public CellEditorListener[] getCellEditorListeners() {
  290. return ((DefaultCellEditor)realEditor).getCellEditorListeners();
  291. }
  292. //
  293. // TreeSelectionListener
  294. //
  295. /**
  296. * Resets <code>lastPath</code>.
  297. */
  298. public void valueChanged(TreeSelectionEvent e) {
  299. if(tree != null) {
  300. if(tree.getSelectionCount() == 1)
  301. lastPath = tree.getSelectionPath();
  302. else
  303. lastPath = null;
  304. }
  305. if(timer != null) {
  306. timer.stop();
  307. }
  308. }
  309. //
  310. // ActionListener (for Timer).
  311. //
  312. /**
  313. * Messaged when the timer fires, this will start the editing
  314. * session.
  315. */
  316. public void actionPerformed(ActionEvent e) {
  317. if(tree != null && lastPath != null) {
  318. tree.startEditingAtPath(lastPath);
  319. }
  320. }
  321. //
  322. // Local methods
  323. //
  324. /**
  325. * Sets the tree currently editing for. This is needed to add
  326. * a selection listener.
  327. * @param newTree the new tree to be edited
  328. */
  329. protected void setTree(JTree newTree) {
  330. if(tree != newTree) {
  331. if(tree != null)
  332. tree.removeTreeSelectionListener(this);
  333. tree = newTree;
  334. if(tree != null)
  335. tree.addTreeSelectionListener(this);
  336. if(timer != null) {
  337. timer.stop();
  338. }
  339. }
  340. }
  341. /**
  342. * Returns true if <code>event</code> is a <code>MouseEvent</code>
  343. * and the click count is 1.
  344. * @param event the event being studied
  345. */
  346. protected boolean shouldStartEditingTimer(EventObject event) {
  347. if((event instanceof MouseEvent) &&
  348. SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
  349. MouseEvent me = (MouseEvent)event;
  350. return (me.getClickCount() == 1 &&
  351. inHitRegion(me.getX(), me.getY()));
  352. }
  353. return false;
  354. }
  355. /**
  356. * Starts the editing timer.
  357. */
  358. protected void startEditingTimer() {
  359. if(timer == null) {
  360. timer = new Timer(1200, this);
  361. timer.setRepeats(false);
  362. }
  363. timer.start();
  364. }
  365. /**
  366. * Returns true if <code>event</code> is <code>null</code>,
  367. * or it is a <code>MouseEvent</code> with a click count > 2
  368. * and <code>inHitRegion</code> returns true.
  369. * @param event the event being studied
  370. */
  371. protected boolean canEditImmediately(EventObject event) {
  372. if((event instanceof MouseEvent) &&
  373. SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
  374. MouseEvent me = (MouseEvent)event;
  375. return ((me.getClickCount() > 2) &&
  376. inHitRegion(me.getX(), me.getY()));
  377. }
  378. return (event == null);
  379. }
  380. /**
  381. * Returns true if the passed in location is a valid mouse location
  382. * to start editing from. This is implemented to return false if
  383. * <code>x</code> is <= the width of the icon and icon gap displayed
  384. * by the renderer. In other words this returns true if the user
  385. * clicks over the text part displayed by the renderer, and false
  386. * otherwise.
  387. * @param x the x-coordinate of the point
  388. * @param y the y-coordinate of the point
  389. * @return true if the passed in location is a valid mouse location
  390. */
  391. protected boolean inHitRegion(int x, int y) {
  392. if(lastRow != -1 && tree != null) {
  393. Rectangle bounds = tree.getRowBounds(lastRow);
  394. ComponentOrientation treeOrientation = tree.getComponentOrientation();
  395. if ( treeOrientation.isLeftToRight() ) {
  396. if (bounds != null && x <= (bounds.x + offset) &&
  397. offset < (bounds.width - 5)) {
  398. return false;
  399. }
  400. } else if ( bounds != null &&
  401. ( x >= (bounds.x+bounds.width-offset+5) ||
  402. x <= (bounds.x + 5) ) &&
  403. offset < (bounds.width - 5) ) {
  404. return false;
  405. }
  406. }
  407. return true;
  408. }
  409. protected void determineOffset(JTree tree, Object value,
  410. boolean isSelected, boolean expanded,
  411. boolean leaf, int row) {
  412. if(renderer != null) {
  413. if(leaf)
  414. editingIcon = renderer.getLeafIcon();
  415. else if(expanded)
  416. editingIcon = renderer.getOpenIcon();
  417. else
  418. editingIcon = renderer.getClosedIcon();
  419. if(editingIcon != null)
  420. offset = renderer.getIconTextGap() +
  421. editingIcon.getIconWidth();
  422. else
  423. offset = renderer.getIconTextGap();
  424. }
  425. else {
  426. editingIcon = null;
  427. offset = 0;
  428. }
  429. }
  430. /**
  431. * Invoked just before editing is to start. Will add the
  432. * <code>editingComponent</code> to the
  433. * <code>editingContainer</code>.
  434. */
  435. protected void prepareForEditing() {
  436. if (editingComponent != null) {
  437. editingContainer.add(editingComponent);
  438. }
  439. }
  440. /**
  441. * Creates the container to manage placement of
  442. * <code>editingComponent</code>.
  443. */
  444. protected Container createContainer() {
  445. return new EditorContainer();
  446. }
  447. /**
  448. * This is invoked if a <code>TreeCellEditor</code>
  449. * is not supplied in the constructor.
  450. * It returns a <code>TextField</code> editor.
  451. * @return a new <code>TextField</code> editor
  452. */
  453. protected TreeCellEditor createTreeCellEditor() {
  454. Border aBorder = UIManager.getBorder("Tree.editorBorder");
  455. DefaultCellEditor editor = new DefaultCellEditor
  456. (new DefaultTextField(aBorder)) {
  457. public boolean shouldSelectCell(EventObject event) {
  458. boolean retValue = super.shouldSelectCell(event);
  459. return retValue;
  460. }
  461. };
  462. // One click to edit.
  463. editor.setClickCountToStart(1);
  464. return editor;
  465. }
  466. /**
  467. * Cleans up any state after editing has completed. Removes the
  468. * <code>editingComponent</code> the <code>editingContainer</code>.
  469. */
  470. private void cleanupAfterEditing() {
  471. if (editingComponent != null) {
  472. editingContainer.remove(editingComponent);
  473. }
  474. editingComponent = null;
  475. }
  476. // Serialization support.
  477. private void writeObject(ObjectOutputStream s) throws IOException {
  478. Vector values = new Vector();
  479. s.defaultWriteObject();
  480. // Save the realEditor, if its Serializable.
  481. if(realEditor != null && realEditor instanceof Serializable) {
  482. values.addElement("realEditor");
  483. values.addElement(realEditor);
  484. }
  485. s.writeObject(values);
  486. }
  487. private void readObject(ObjectInputStream s)
  488. throws IOException, ClassNotFoundException {
  489. s.defaultReadObject();
  490. Vector values = (Vector)s.readObject();
  491. int indexCounter = 0;
  492. int maxCounter = values.size();
  493. if(indexCounter < maxCounter && values.elementAt(indexCounter).
  494. equals("realEditor")) {
  495. realEditor = (TreeCellEditor)values.elementAt(++indexCounter);
  496. indexCounter++;
  497. }
  498. }
  499. /**
  500. * <code>TextField</code> used when no editor is supplied.
  501. * This textfield locks into the border it is constructed with.
  502. * It also prefers its parents font over its font. And if the
  503. * renderer is not <code>null</code> and no font
  504. * has been specified the preferred height is that of the renderer.
  505. */
  506. public class DefaultTextField extends JTextField {
  507. /** Border to use. */
  508. protected Border border;
  509. /**
  510. * Constructs a
  511. * <code>DefaultTreeCellEditor.DefaultTextField</code> object.
  512. *
  513. * @param border a <code>Border</code> object
  514. */
  515. public DefaultTextField(Border border) {
  516. setBorder(border);
  517. }
  518. /**
  519. * Sets the border of this component.<p>
  520. * This is a bound property.
  521. *
  522. * @param border the border to be rendered for this component
  523. * @see Border
  524. * @see CompoundBorder
  525. * @beaninfo
  526. * bound: true
  527. * preferred: true
  528. * attribute: visualUpdate true
  529. * description: The component's border.
  530. */
  531. public void setBorder(Border border) {
  532. super.setBorder(border);
  533. this.border = border;
  534. }
  535. /**
  536. * Overrides <code>JComponent.getBorder</code> to
  537. * returns the current border.
  538. */
  539. public Border getBorder() {
  540. return border;
  541. }
  542. // implements java.awt.MenuContainer
  543. public Font getFont() {
  544. Font font = super.getFont();
  545. // Prefer the parent containers font if our font is a
  546. // FontUIResource
  547. if(font instanceof FontUIResource) {
  548. Container parent = getParent();
  549. if(parent != null && parent.getFont() != null)
  550. font = parent.getFont();
  551. }
  552. return font;
  553. }
  554. /**
  555. * Overrides <code>JTextField.getPreferredSize</code> to
  556. * return the preferred size based on current font, if set,
  557. * or else use renderer's font.
  558. * @return a <code>Dimension</code> object containing
  559. * the preferred size
  560. */
  561. public Dimension getPreferredSize() {
  562. Dimension size = super.getPreferredSize();
  563. // If not font has been set, prefer the renderers height.
  564. if(renderer != null &&
  565. DefaultTreeCellEditor.this.getFont() == null) {
  566. Dimension rSize = renderer.getPreferredSize();
  567. size.height = rSize.height;
  568. }
  569. return size;
  570. }
  571. }
  572. /**
  573. * Container responsible for placing the <code>editingComponent</code>.
  574. */
  575. public class EditorContainer extends Container {
  576. /**
  577. * Constructs an <code>EditorContainer</code> object.
  578. */
  579. public EditorContainer() {
  580. setLayout(null);
  581. }
  582. // This should not be used. It will be removed when new API is
  583. // allowed.
  584. public void EditorContainer() {
  585. setLayout(null);
  586. }
  587. /**
  588. * Overrides <code>Container.paint</code> to paint the node's
  589. * icon and use the selection color for the background.
  590. */
  591. public void paint(Graphics g) {
  592. Dimension size = getSize();
  593. // Then the icon.
  594. if(editingIcon != null) {
  595. int yLoc = Math.max(0, (getSize().height -
  596. editingIcon.getIconHeight()) / 2);
  597. editingIcon.paintIcon(this, g, 0, yLoc);
  598. }
  599. // Border selection color
  600. Color background = getBorderSelectionColor();
  601. if(background != null) {
  602. g.setColor(background);
  603. g.drawRect(0, 0, size.width - 1, size.height - 1);
  604. }
  605. super.paint(g);
  606. }
  607. /**
  608. * Lays out this <code>Container</code>. If editing,
  609. * the editor will be placed at
  610. * <code>offset</code> in the x direction and 0 for y.
  611. */
  612. public void doLayout() {
  613. if(editingComponent != null) {
  614. Dimension cSize = getSize();
  615. editingComponent.getPreferredSize();
  616. editingComponent.setLocation(offset, 0);
  617. editingComponent.setBounds(offset, 0,
  618. cSize.width - offset,
  619. cSize.height);
  620. }
  621. }
  622. /**
  623. * Returns the preferred size for the <code>Container</code>.
  624. * This will be at least preferred size of the editor plus
  625. * <code>offset</code>.
  626. * @return a <code>Dimension</code> containing the preferred
  627. * size for the <code>Container</code> if
  628. * <code>editingComponent</code> is <code>null</code> the
  629. * <code>Dimension</code> returned is 0, 0
  630. */
  631. public Dimension getPreferredSize() {
  632. if(editingComponent != null) {
  633. Dimension pSize = editingComponent.getPreferredSize();
  634. pSize.width += offset + 5;
  635. Dimension rSize = (renderer != null) ?
  636. renderer.getPreferredSize() : null;
  637. if(rSize != null)
  638. pSize.height = Math.max(pSize.height, rSize.height);
  639. if(editingIcon != null)
  640. pSize.height = Math.max(pSize.height,
  641. editingIcon.getIconHeight());
  642. // Make sure width is at least 100.
  643. pSize.width = Math.max(pSize.width, 100);
  644. return pSize;
  645. }
  646. return new Dimension(0, 0);
  647. }
  648. }
  649. }