1. /*
  2. * @(#)DefaultTreeCellEditor.java 1.28 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.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.28 01/23/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. }
  216. }
  217. }
  218. if(!realEditor.isCellEditable(event))
  219. return false;
  220. if(canEditImmediately(event))
  221. retValue = true;
  222. else if(editable && shouldStartEditingTimer(event)) {
  223. startEditingTimer();
  224. }
  225. else if(timer != null && timer.isRunning())
  226. timer.stop();
  227. if(retValue)
  228. prepareForEditing();
  229. return retValue;
  230. }
  231. /**
  232. * Messages the <code>realEditor</code> for the return value.
  233. */
  234. public boolean shouldSelectCell(EventObject event) {
  235. return realEditor.shouldSelectCell(event);
  236. }
  237. /**
  238. * If the <code>realEditor</code> will allow editing to stop,
  239. * the <code>realEditor</code> is removed and true is returned,
  240. * otherwise false is returned.
  241. */
  242. public boolean stopCellEditing() {
  243. if(realEditor.stopCellEditing()) {
  244. cleanupAfterEditing();
  245. return true;
  246. }
  247. return false;
  248. }
  249. /**
  250. * Messages <code>cancelCellEditing</code> to the
  251. * <code>realEditor</code> and removes it from this instance.
  252. */
  253. public void cancelCellEditing() {
  254. realEditor.cancelCellEditing();
  255. cleanupAfterEditing();
  256. }
  257. /**
  258. * Adds the <code>CellEditorListener</code>.
  259. * @param l the listener to be added
  260. */
  261. public void addCellEditorListener(CellEditorListener l) {
  262. realEditor.addCellEditorListener(l);
  263. }
  264. /**
  265. * Removes the previously added <code>CellEditorListener</code>.
  266. * @param l the listener to be removed
  267. */
  268. public void removeCellEditorListener(CellEditorListener l) {
  269. realEditor.removeCellEditorListener(l);
  270. }
  271. /**
  272. * Returns an array of all the <code>CellEditorListener</code>s added
  273. * to this DefaultTreeCellEditor with addCellEditorListener().
  274. *
  275. * @return all of the <code>CellEditorListener</code>s added or an empty
  276. * array if no listeners have been added
  277. * @since 1.4
  278. */
  279. public CellEditorListener[] getCellEditorListeners() {
  280. return ((DefaultCellEditor)realEditor).getCellEditorListeners();
  281. }
  282. //
  283. // TreeSelectionListener
  284. //
  285. /**
  286. * Resets <code>lastPath</code>.
  287. */
  288. public void valueChanged(TreeSelectionEvent e) {
  289. if(tree != null) {
  290. if(tree.getSelectionCount() == 1)
  291. lastPath = tree.getSelectionPath();
  292. else
  293. lastPath = null;
  294. }
  295. if(timer != null) {
  296. timer.stop();
  297. }
  298. }
  299. //
  300. // ActionListener (for Timer).
  301. //
  302. /**
  303. * Messaged when the timer fires, this will start the editing
  304. * session.
  305. */
  306. public void actionPerformed(ActionEvent e) {
  307. if(tree != null && lastPath != null) {
  308. tree.startEditingAtPath(lastPath);
  309. }
  310. }
  311. //
  312. // Local methods
  313. //
  314. /**
  315. * Sets the tree currently editing for. This is needed to add
  316. * a selection listener.
  317. * @param newTree the new tree to be edited
  318. */
  319. protected void setTree(JTree newTree) {
  320. if(tree != newTree) {
  321. if(tree != null)
  322. tree.removeTreeSelectionListener(this);
  323. tree = newTree;
  324. if(tree != null)
  325. tree.addTreeSelectionListener(this);
  326. if(timer != null) {
  327. timer.stop();
  328. }
  329. }
  330. }
  331. /**
  332. * Returns true if <code>event</code> is a <code>MouseEvent</code>
  333. * and the click count is 1.
  334. * @param event the event being studied
  335. */
  336. protected boolean shouldStartEditingTimer(EventObject event) {
  337. if((event instanceof MouseEvent) &&
  338. SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
  339. MouseEvent me = (MouseEvent)event;
  340. return (me.getClickCount() == 1 &&
  341. inHitRegion(me.getX(), me.getY()));
  342. }
  343. return false;
  344. }
  345. /**
  346. * Starts the editing timer.
  347. */
  348. protected void startEditingTimer() {
  349. if(timer == null) {
  350. timer = new Timer(1200, this);
  351. timer.setRepeats(false);
  352. }
  353. timer.start();
  354. }
  355. /**
  356. * Returns true if <code>event</code> is <code>null</code>,
  357. * or it is a <code>MouseEvent</code> with a click count > 2
  358. * and <code>inHitRegion</code> returns true.
  359. * @param event the event being studied
  360. */
  361. protected boolean canEditImmediately(EventObject event) {
  362. if((event instanceof MouseEvent) &&
  363. SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
  364. MouseEvent me = (MouseEvent)event;
  365. return ((me.getClickCount() > 2) &&
  366. inHitRegion(me.getX(), me.getY()));
  367. }
  368. return (event == null);
  369. }
  370. /**
  371. * Returns true if the passed in location is a valid mouse location
  372. * to start editing from. This is implemented to return false if
  373. * <code>x</code> is <= the width of the icon and icon gap displayed
  374. * by the renderer. In other words this returns true if the user
  375. * clicks over the text part displayed by the renderer, and false
  376. * otherwise.
  377. * @param x the x-coordinate of the point
  378. * @param y the y-coordinate of the point
  379. * @return true if the passed in location is a valid mouse location
  380. */
  381. protected boolean inHitRegion(int x, int y) {
  382. if(lastRow != -1 && tree != null) {
  383. Rectangle bounds = tree.getRowBounds(lastRow);
  384. if(bounds != null && x <= (bounds.x + offset) &&
  385. offset < (bounds.width - 5)) {
  386. return false;
  387. }
  388. }
  389. return true;
  390. }
  391. protected void determineOffset(JTree tree, Object value,
  392. boolean isSelected, boolean expanded,
  393. boolean leaf, int row) {
  394. if(renderer != null) {
  395. if(leaf)
  396. editingIcon = renderer.getLeafIcon();
  397. else if(expanded)
  398. editingIcon = renderer.getOpenIcon();
  399. else
  400. editingIcon = renderer.getClosedIcon();
  401. if(editingIcon != null)
  402. offset = renderer.getIconTextGap() +
  403. editingIcon.getIconWidth();
  404. else
  405. offset = renderer.getIconTextGap();
  406. }
  407. else {
  408. editingIcon = null;
  409. offset = 0;
  410. }
  411. }
  412. /**
  413. * Invoked just before editing is to start. Will add the
  414. * <code>editingComponent</code> to the
  415. * <code>editingContainer</code>.
  416. */
  417. protected void prepareForEditing() {
  418. if (editingComponent != null) {
  419. editingContainer.add(editingComponent);
  420. }
  421. }
  422. /**
  423. * Creates the container to manage placement of
  424. * <code>editingComponent</code>.
  425. */
  426. protected Container createContainer() {
  427. return new EditorContainer();
  428. }
  429. /**
  430. * This is invoked if a <code>TreeCellEditor</code>
  431. * is not supplied in the constructor.
  432. * It returns a <code>TextField</code> editor.
  433. * @return a new <code>TextField</code> editor
  434. */
  435. protected TreeCellEditor createTreeCellEditor() {
  436. Border aBorder = UIManager.getBorder("Tree.editorBorder");
  437. DefaultCellEditor editor = new DefaultCellEditor
  438. (new DefaultTextField(aBorder)) {
  439. public boolean shouldSelectCell(EventObject event) {
  440. boolean retValue = super.shouldSelectCell(event);
  441. return retValue;
  442. }
  443. };
  444. // One click to edit.
  445. editor.setClickCountToStart(1);
  446. return editor;
  447. }
  448. /**
  449. * Cleans up any state after editing has completed. Removes the
  450. * <code>editingComponent</code> the <code>editingContainer</code>.
  451. */
  452. private void cleanupAfterEditing() {
  453. if (editingComponent != null) {
  454. editingContainer.remove(editingComponent);
  455. }
  456. editingComponent = null;
  457. }
  458. // Serialization support.
  459. private void writeObject(ObjectOutputStream s) throws IOException {
  460. Vector values = new Vector();
  461. s.defaultWriteObject();
  462. // Save the realEditor, if its Serializable.
  463. if(realEditor != null && realEditor instanceof Serializable) {
  464. values.addElement("realEditor");
  465. values.addElement(realEditor);
  466. }
  467. s.writeObject(values);
  468. }
  469. private void readObject(ObjectInputStream s)
  470. throws IOException, ClassNotFoundException {
  471. s.defaultReadObject();
  472. Vector values = (Vector)s.readObject();
  473. int indexCounter = 0;
  474. int maxCounter = values.size();
  475. if(indexCounter < maxCounter && values.elementAt(indexCounter).
  476. equals("realEditor")) {
  477. realEditor = (TreeCellEditor)values.elementAt(++indexCounter);
  478. indexCounter++;
  479. }
  480. }
  481. /**
  482. * <code>TextField</code> used when no editor is supplied.
  483. * This textfield locks into the border it is constructed with.
  484. * It also prefers its parents font over its font. And if the
  485. * renderer is not <code>null</code> and no font
  486. * has been specified the preferred height is that of the renderer.
  487. */
  488. public class DefaultTextField extends JTextField {
  489. /** Border to use. */
  490. protected Border border;
  491. /**
  492. * Constructs a
  493. * <code>DefaultTreeCellEditor.DefaultTextField</code> object.
  494. *
  495. * @param border a <code>Border</code> object
  496. */
  497. public DefaultTextField(Border border) {
  498. setBorder(border);
  499. }
  500. /**
  501. * Sets the border of this component.<p>
  502. * This is a bound property.
  503. *
  504. * @param border the border to be rendered for this component
  505. * @see Border
  506. * @see CompoundBorder
  507. * @beaninfo
  508. * bound: true
  509. * preferred: true
  510. * attribute: visualUpdate true
  511. * description: The component's border.
  512. */
  513. public void setBorder(Border border) {
  514. super.setBorder(border);
  515. this.border = border;
  516. }
  517. /**
  518. * Overrides <code>JComponent.getBorder</code> to
  519. * returns the current border.
  520. */
  521. public Border getBorder() {
  522. return border;
  523. }
  524. // implements java.awt.MenuContainer
  525. public Font getFont() {
  526. Font font = super.getFont();
  527. // Prefer the parent containers font if our font is a
  528. // FontUIResource
  529. if(font instanceof FontUIResource) {
  530. Container parent = getParent();
  531. if(parent != null && parent.getFont() != null)
  532. font = parent.getFont();
  533. }
  534. return font;
  535. }
  536. /**
  537. * Overrides <code>JTextField.getPreferredSize</code> to
  538. * return the preferred size based on current font, if set,
  539. * or else use renderer's font.
  540. * @return a <code>Dimension</code> object containing
  541. * the preferred size
  542. */
  543. public Dimension getPreferredSize() {
  544. Dimension size = super.getPreferredSize();
  545. // If not font has been set, prefer the renderers height.
  546. if(renderer != null &&
  547. DefaultTreeCellEditor.this.getFont() == null) {
  548. Dimension rSize = renderer.getPreferredSize();
  549. size.height = rSize.height;
  550. }
  551. return size;
  552. }
  553. }
  554. /**
  555. * Container responsible for placing the <code>editingComponent</code>.
  556. */
  557. public class EditorContainer extends Container {
  558. /**
  559. * Constructs an <code>EditorContainer</code> object.
  560. */
  561. public EditorContainer() {
  562. setLayout(null);
  563. }
  564. // This should not be used. It will be removed when new API is
  565. // allowed.
  566. public void EditorContainer() {
  567. setLayout(null);
  568. }
  569. /**
  570. * Overrides <code>Container.paint</code> to paint the node's
  571. * icon and use the selection color for the background.
  572. */
  573. public void paint(Graphics g) {
  574. Dimension size = getSize();
  575. // Then the icon.
  576. if(editingIcon != null) {
  577. int yLoc = Math.max(0, (getSize().height -
  578. editingIcon.getIconHeight()) / 2);
  579. editingIcon.paintIcon(this, g, 0, yLoc);
  580. }
  581. // Border selection color
  582. Color background = getBorderSelectionColor();
  583. if(background != null) {
  584. g.setColor(background);
  585. g.drawRect(0, 0, size.width - 1, size.height - 1);
  586. }
  587. super.paint(g);
  588. }
  589. /**
  590. * Lays out this <code>Container</code>. If editing,
  591. * the editor will be placed at
  592. * <code>offset</code> in the x direction and 0 for y.
  593. */
  594. public void doLayout() {
  595. if(editingComponent != null) {
  596. Dimension cSize = getSize();
  597. editingComponent.getPreferredSize();
  598. editingComponent.setLocation(offset, 0);
  599. editingComponent.setBounds(offset, 0,
  600. cSize.width - offset,
  601. cSize.height);
  602. }
  603. }
  604. /**
  605. * Returns the preferred size for the <code>Container</code>.
  606. * This will be at least preferred size of the editor plus
  607. * <code>offset</code>.
  608. * @return a <code>Dimension</code> containing the preferred
  609. * size for the <code>Container</code> if
  610. * <code>editingComponent</code> is <code>null</code> the
  611. * <code>Dimension</code> returned is 0, 0
  612. */
  613. public Dimension getPreferredSize() {
  614. if(editingComponent != null) {
  615. Dimension pSize = editingComponent.getPreferredSize();
  616. pSize.width += offset + 5;
  617. Dimension rSize = (renderer != null) ?
  618. renderer.getPreferredSize() : null;
  619. if(rSize != null)
  620. pSize.height = Math.max(pSize.height, rSize.height);
  621. if(editingIcon != null)
  622. pSize.height = Math.max(pSize.height,
  623. editingIcon.getIconHeight());
  624. // Make sure width is at least 100.
  625. pSize.width = Math.max(pSize.width, 100);
  626. return pSize;
  627. }
  628. return new Dimension(0, 0);
  629. }
  630. }
  631. }