KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > tree > DefaultTreeCellEditor


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
8 package javax.swing.tree;
9
10 import javax.swing.*;
11 import javax.swing.border.*;
12 import javax.swing.event.*;
13 import javax.swing.plaf.FontUIResource JavaDoc;
14 import java.awt.*;
15 import java.awt.event.*;
16 import java.beans.*;
17 import java.io.*;
18 import java.util.EventObject JavaDoc;
19 import java.util.Vector JavaDoc;
20
21 /**
22  * A <code>TreeCellEditor</code>. You need to supply an
23  * instance of <code>DefaultTreeCellRenderer</code>
24  * so that the icons can be obtained. You can optionally supply
25  * a <code>TreeCellEditor</code> that will be layed out according
26  * to the icon in the <code>DefaultTreeCellRenderer</code>.
27  * If you do not supply a <code>TreeCellEditor</code>,
28  * a <code>TextField</code> will be used. Editing is started
29  * on a triple mouse click, or after a click, pause, click and
30  * a delay of 1200 miliseconds.
31  *<p>
32  * <strong>Warning:</strong>
33  * Serialized objects of this class will not be compatible with
34  * future Swing releases. The current serialization support is
35  * appropriate for short term storage or RMI between applications running
36  * the same version of Swing. As of 1.4, support for long term storage
37  * of all JavaBeans<sup><font size="-2">TM</font></sup>
38  * has been added to the <code>java.beans</code> package.
39  * Please see {@link java.beans.XMLEncoder}.
40  *
41  * @see javax.swing.JTree
42  *
43  * @version 1.30 12/19/03
44  * @author Scott Violet
45  */

46 public class DefaultTreeCellEditor implements ActionListener, TreeCellEditor JavaDoc,
47             TreeSelectionListener {
48     /** Editor handling the editing. */
49     protected TreeCellEditor JavaDoc realEditor;
50
51     /** Renderer, used to get border and offsets from. */
52     protected DefaultTreeCellRenderer JavaDoc renderer;
53
54     /** Editing container, will contain the <code>editorComponent</code>. */
55     protected Container editingContainer;
56
57     /**
58      * Component used in editing, obtained from the
59      * <code>editingContainer</code>.
60      */

61     transient protected Component editingComponent;
62
63     /**
64      * As of Java 2 platform v1.4 this field should no longer be used. If
65      * you wish to provide similar behavior you should directly override
66      * <code>isCellEditable</code>.
67      */

68     protected boolean canEdit;
69
70     /**
71      * Used in editing. Indicates x position to place
72      * <code>editingComponent</code>.
73      */

74     protected transient int offset;
75
76     /** <code>JTree</code> instance listening too. */
77     protected transient JTree tree;
78
79     /** Last path that was selected. */
80     protected transient TreePath JavaDoc lastPath;
81
82     /** Used before starting the editing session. */
83     protected transient Timer timer;
84
85     /**
86      * Row that was last passed into
87      * <code>getTreeCellEditorComponent</code>.
88      */

89     protected transient int lastRow;
90
91     /** True if the border selection color should be drawn. */
92     protected Color borderSelectionColor;
93
94     /** Icon to use when editing. */
95     protected transient Icon editingIcon;
96
97     /**
98      * Font to paint with, <code>null</code> indicates
99      * font of renderer is to be used.
100      */

101     protected Font font;
102
103
104     /**
105      * Constructs a <code>DefaultTreeCellEditor</code>
106      * object for a JTree using the specified renderer and
107      * a default editor. (Use this constructor for normal editing.)
108      *
109      * @param tree a <code>JTree</code> object
110      * @param renderer a <code>DefaultTreeCellRenderer</code> object
111      */

112     public DefaultTreeCellEditor(JTree tree,
113                  DefaultTreeCellRenderer JavaDoc renderer) {
114     this(tree, renderer, null);
115     }
116
117     /**
118      * Constructs a <code>DefaultTreeCellEditor</code>
119      * object for a <code>JTree</code> using the
120      * specified renderer and the specified editor. (Use this constructor
121      * for specialized editing.)
122      *
123      * @param tree a <code>JTree</code> object
124      * @param renderer a <code>DefaultTreeCellRenderer</code> object
125      * @param editor a <code>TreeCellEditor</code> object
126      */

127     public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer JavaDoc renderer,
128                  TreeCellEditor JavaDoc editor) {
129     this.renderer = renderer;
130     realEditor = editor;
131     if(realEditor == null)
132         realEditor = createTreeCellEditor();
133     editingContainer = createContainer();
134     setTree(tree);
135     setBorderSelectionColor(UIManager.getColor
136                 ("Tree.editorBorderSelectionColor"));
137     }
138
139     /**
140       * Sets the color to use for the border.
141       * @param newColor the new border color
142       */

143     public void setBorderSelectionColor(Color newColor) {
144     borderSelectionColor = newColor;
145     }
146
147     /**
148       * Returns the color the border is drawn.
149       * @return the border selection color
150       */

151     public Color getBorderSelectionColor() {
152     return borderSelectionColor;
153     }
154
155     /**
156      * Sets the font to edit with. <code>null</code> indicates
157      * the renderers font should be used. This will NOT
158      * override any font you have set in the editor
159      * the receiver was instantied with. If <code>null</code>
160      * for an editor was passed in a default editor will be
161      * created that will pick up this font.
162      *
163      * @param font the editing <code>Font</code>
164      * @see #getFont
165      */

166     public void setFont(Font font) {
167     this.font = font;
168     }
169
170     /**
171      * Gets the font used for editing.
172      *
173      * @return the editing <code>Font</code>
174      * @see #setFont
175      */

176     public Font getFont() {
177     return font;
178     }
179
180     //
181
// TreeCellEditor
182
//
183

184     /**
185      * Configures the editor. Passed onto the <code>realEditor</code>.
186      */

187     public Component getTreeCellEditorComponent(JTree tree, Object JavaDoc value,
188                         boolean isSelected,
189                         boolean expanded,
190                         boolean leaf, int row) {
191     setTree(tree);
192     lastRow = row;
193     determineOffset(tree, value, isSelected, expanded, leaf, row);
194
195         if (editingComponent != null) {
196             editingContainer.remove(editingComponent);
197         }
198     editingComponent = realEditor.getTreeCellEditorComponent(tree, value,
199                     isSelected, expanded,leaf, row);
200
201
202         // this is kept for backwards compatability but isn't really needed
203
// with the current BasicTreeUI implementation.
204
TreePath JavaDoc newPath = tree.getPathForRow(row);
205
206     canEdit = (lastPath != null && newPath != null &&
207            lastPath.equals(newPath));
208
209     Font font = getFont();
210
211     if(font == null) {
212         if(renderer != null)
213         font = renderer.getFont();
214         if(font == null)
215         font = tree.getFont();
216     }
217     editingContainer.setFont(font);
218         prepareForEditing();
219     return editingContainer;
220     }
221
222     /**
223      * Returns the value currently being edited.
224      * @return the value currently being edited
225      */

226     public Object JavaDoc getCellEditorValue() {
227     return realEditor.getCellEditorValue();
228     }
229
230     /**
231      * If the <code>realEditor</code> returns true to this
232      * message, <code>prepareForEditing</code>
233      * is messaged and true is returned.
234      */

235     public boolean isCellEditable(EventObject JavaDoc event) {
236     boolean retValue = false;
237         boolean editable = false;
238
239         if (event != null) {
240             if (event.getSource() instanceof JTree) {
241                 setTree((JTree)event.getSource());
242                 if (event instanceof MouseEvent) {
243                     TreePath JavaDoc path = tree.getPathForLocation(
244                                          ((MouseEvent)event).getX(),
245                                          ((MouseEvent)event).getY());
246                     editable = (lastPath != null && path != null &&
247                                lastPath.equals(path));
248             if (path!=null) {
249             lastRow = tree.getRowForPath(path);
250             Object JavaDoc value = path.getLastPathComponent();
251             boolean isSelected = tree.isRowSelected(lastRow);
252             boolean expanded = tree.isExpanded(path);
253             TreeModel JavaDoc treeModel = tree.getModel();
254             boolean leaf = treeModel.isLeaf(value);
255             determineOffset(tree, value, isSelected,
256                     expanded, leaf, lastRow);
257             }
258                 }
259             }
260         }
261     if(!realEditor.isCellEditable(event))
262         return false;
263     if(canEditImmediately(event))
264         retValue = true;
265     else if(editable && shouldStartEditingTimer(event)) {
266         startEditingTimer();
267     }
268     else if(timer != null && timer.isRunning())
269         timer.stop();
270     if(retValue)
271         prepareForEditing();
272     return retValue;
273     }
274
275     /**
276      * Messages the <code>realEditor</code> for the return value.
277      */

278     public boolean shouldSelectCell(EventObject JavaDoc event) {
279     return realEditor.shouldSelectCell(event);
280     }
281
282     /**
283      * If the <code>realEditor</code> will allow editing to stop,
284      * the <code>realEditor</code> is removed and true is returned,
285      * otherwise false is returned.
286      */

287     public boolean stopCellEditing() {
288     if(realEditor.stopCellEditing()) {
289             cleanupAfterEditing();
290         return true;
291     }
292     return false;
293     }
294
295     /**
296      * Messages <code>cancelCellEditing</code> to the
297      * <code>realEditor</code> and removes it from this instance.
298      */

299     public void cancelCellEditing() {
300     realEditor.cancelCellEditing();
301         cleanupAfterEditing();
302     }
303
304     /**
305      * Adds the <code>CellEditorListener</code>.
306      * @param l the listener to be added
307      */

308     public void addCellEditorListener(CellEditorListener l) {
309     realEditor.addCellEditorListener(l);
310     }
311
312     /**
313       * Removes the previously added <code>CellEditorListener</code>.
314       * @param l the listener to be removed
315       */

316     public void removeCellEditorListener(CellEditorListener l) {
317     realEditor.removeCellEditorListener(l);
318     }
319
320     /**
321      * Returns an array of all the <code>CellEditorListener</code>s added
322      * to this DefaultTreeCellEditor with addCellEditorListener().
323      *
324      * @return all of the <code>CellEditorListener</code>s added or an empty
325      * array if no listeners have been added
326      * @since 1.4
327      */

328     public CellEditorListener[] getCellEditorListeners() {
329         return ((DefaultCellEditor)realEditor).getCellEditorListeners();
330     }
331
332     //
333
// TreeSelectionListener
334
//
335

336     /**
337      * Resets <code>lastPath</code>.
338      */

339     public void valueChanged(TreeSelectionEvent e) {
340     if(tree != null) {
341         if(tree.getSelectionCount() == 1)
342         lastPath = tree.getSelectionPath();
343         else
344         lastPath = null;
345     }
346     if(timer != null) {
347         timer.stop();
348     }
349     }
350
351     //
352
// ActionListener (for Timer).
353
//
354

355     /**
356      * Messaged when the timer fires, this will start the editing
357      * session.
358      */

359     public void actionPerformed(ActionEvent e) {
360     if(tree != null && lastPath != null) {
361         tree.startEditingAtPath(lastPath);
362     }
363     }
364
365     //
366
// Local methods
367
//
368

369     /**
370      * Sets the tree currently editing for. This is needed to add
371      * a selection listener.
372      * @param newTree the new tree to be edited
373      */

374     protected void setTree(JTree newTree) {
375     if(tree != newTree) {
376         if(tree != null)
377         tree.removeTreeSelectionListener(this);
378         tree = newTree;
379         if(tree != null)
380         tree.addTreeSelectionListener(this);
381         if(timer != null) {
382         timer.stop();
383         }
384     }
385     }
386
387     /**
388      * Returns true if <code>event</code> is a <code>MouseEvent</code>
389      * and the click count is 1.
390      * @param event the event being studied
391      */

392     protected boolean shouldStartEditingTimer(EventObject JavaDoc event) {
393     if((event instanceof MouseEvent) &&
394         SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
395         MouseEvent me = (MouseEvent)event;
396
397         return (me.getClickCount() == 1 &&
398             inHitRegion(me.getX(), me.getY()));
399     }
400     return false;
401     }
402
403     /**
404      * Starts the editing timer.
405      */

406     protected void startEditingTimer() {
407     if(timer == null) {
408         timer = new Timer(1200, this);
409         timer.setRepeats(false);
410     }
411     timer.start();
412     }
413
414     /**
415      * Returns true if <code>event</code> is <code>null</code>,
416      * or it is a <code>MouseEvent</code> with a click count > 2
417      * and <code>inHitRegion</code> returns true.
418      * @param event the event being studied
419      */

420     protected boolean canEditImmediately(EventObject JavaDoc event) {
421     if((event instanceof MouseEvent) &&
422        SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
423         MouseEvent me = (MouseEvent)event;
424
425         return ((me.getClickCount() > 2) &&
426             inHitRegion(me.getX(), me.getY()));
427     }
428     return (event == null);
429     }
430
431     /**
432      * Returns true if the passed in location is a valid mouse location
433      * to start editing from. This is implemented to return false if
434      * <code>x</code> is <= the width of the icon and icon gap displayed
435      * by the renderer. In other words this returns true if the user
436      * clicks over the text part displayed by the renderer, and false
437      * otherwise.
438      * @param x the x-coordinate of the point
439      * @param y the y-coordinate of the point
440      * @return true if the passed in location is a valid mouse location
441      */

442     protected boolean inHitRegion(int x, int y) {
443     if(lastRow != -1 && tree != null) {
444         Rectangle bounds = tree.getRowBounds(lastRow);
445         ComponentOrientation treeOrientation = tree.getComponentOrientation();
446         
447         if ( treeOrientation.isLeftToRight() ) {
448         if (bounds != null && x <= (bounds.x + offset) &&
449             offset < (bounds.width - 5)) {
450             return false;
451         }
452         } else if ( bounds != null &&
453             ( x >= (bounds.x+bounds.width-offset+5) ||
454               x <= (bounds.x + 5) ) &&
455             offset < (bounds.width - 5) ) {
456         return false;
457         }
458     }
459     return true;
460     }
461
462     protected void determineOffset(JTree tree, Object JavaDoc value,
463                    boolean isSelected, boolean expanded,
464                    boolean leaf, int row) {
465     if(renderer != null) {
466         if(leaf)
467         editingIcon = renderer.getLeafIcon();
468         else if(expanded)
469         editingIcon = renderer.getOpenIcon();
470         else
471         editingIcon = renderer.getClosedIcon();
472         if(editingIcon != null)
473         offset = renderer.getIconTextGap() +
474                  editingIcon.getIconWidth();
475         else
476         offset = renderer.getIconTextGap();
477     }
478     else {
479         editingIcon = null;
480         offset = 0;
481     }
482     }
483
484     /**
485      * Invoked just before editing is to start. Will add the
486      * <code>editingComponent</code> to the
487      * <code>editingContainer</code>.
488      */

489     protected void prepareForEditing() {
490         if (editingComponent != null) {
491             editingContainer.add(editingComponent);
492         }
493     }
494
495     /**
496      * Creates the container to manage placement of
497      * <code>editingComponent</code>.
498      */

499     protected Container createContainer() {
500     return new EditorContainer();
501     }
502
503     /**
504      * This is invoked if a <code>TreeCellEditor</code>
505      * is not supplied in the constructor.
506      * It returns a <code>TextField</code> editor.
507      * @return a new <code>TextField</code> editor
508      */

509     protected TreeCellEditor JavaDoc createTreeCellEditor() {
510     Border aBorder = UIManager.getBorder("Tree.editorBorder");
511     DefaultCellEditor editor = new DefaultCellEditor
512         (new DefaultTextField(aBorder)) {
513         public boolean shouldSelectCell(EventObject JavaDoc event) {
514         boolean retValue = super.shouldSelectCell(event);
515         return retValue;
516         }
517     };
518
519     // One click to edit.
520
editor.setClickCountToStart(1);
521     return editor;
522     }
523
524     /**
525      * Cleans up any state after editing has completed. Removes the
526      * <code>editingComponent</code> the <code>editingContainer</code>.
527      */

528     private void cleanupAfterEditing() {
529     if (editingComponent != null) {
530         editingContainer.remove(editingComponent);
531         }
532     editingComponent = null;
533     }
534
535     // Serialization support.
536
private void writeObject(ObjectOutputStream s) throws IOException {
537     Vector JavaDoc values = new Vector JavaDoc();
538
539     s.defaultWriteObject();
540     // Save the realEditor, if its Serializable.
541
if(realEditor != null && realEditor instanceof Serializable) {
542         values.addElement("realEditor");
543         values.addElement(realEditor);
544     }
545     s.writeObject(values);
546     }
547
548     private void readObject(ObjectInputStream s)
549     throws IOException, ClassNotFoundException JavaDoc {
550     s.defaultReadObject();
551
552     Vector JavaDoc values = (Vector JavaDoc)s.readObject();
553     int indexCounter = 0;
554     int maxCounter = values.size();
555
556     if(indexCounter < maxCounter && values.elementAt(indexCounter).
557        equals("realEditor")) {
558         realEditor = (TreeCellEditor JavaDoc)values.elementAt(++indexCounter);
559         indexCounter++;
560     }
561     }
562
563
564     /**
565      * <code>TextField</code> used when no editor is supplied.
566      * This textfield locks into the border it is constructed with.
567      * It also prefers its parents font over its font. And if the
568      * renderer is not <code>null</code> and no font
569      * has been specified the preferred height is that of the renderer.
570      */

571     public class DefaultTextField extends JTextField {
572     /** Border to use. */
573     protected Border border;
574
575         /**
576          * Constructs a
577          * <code>DefaultTreeCellEditor.DefaultTextField</code> object.
578          *
579          * @param border a <code>Border</code> object
580          */

581     public DefaultTextField(Border border) {
582             setBorder(border);
583     }
584
585         /**
586          * Sets the border of this component.<p>
587          * This is a bound property.
588          *
589          * @param border the border to be rendered for this component
590          * @see Border
591          * @see CompoundBorder
592          * @beaninfo
593          * bound: true
594          * preferred: true
595          * attribute: visualUpdate true
596          * description: The component's border.
597          */

598         public void setBorder(Border border) {
599             super.setBorder(border);
600             this.border = border;
601         }
602
603         /**
604          * Overrides <code>JComponent.getBorder</code> to
605          * returns the current border.
606          */

607     public Border getBorder() {
608         return border;
609     }
610
611         // implements java.awt.MenuContainer
612
public Font getFont() {
613         Font font = super.getFont();
614
615         // Prefer the parent containers font if our font is a
616
// FontUIResource
617
if(font instanceof FontUIResource JavaDoc) {
618         Container parent = getParent();
619
620         if(parent != null && parent.getFont() != null)
621             font = parent.getFont();
622         }
623         return font;
624     }
625
626         /**
627          * Overrides <code>JTextField.getPreferredSize</code> to
628          * return the preferred size based on current font, if set,
629          * or else use renderer's font.
630          * @return a <code>Dimension</code> object containing
631          * the preferred size
632          */

633     public Dimension getPreferredSize() {
634         Dimension size = super.getPreferredSize();
635
636         // If not font has been set, prefer the renderers height.
637
if(renderer != null &&
638            DefaultTreeCellEditor.this.getFont() == null) {
639         Dimension rSize = renderer.getPreferredSize();
640
641         size.height = rSize.height;
642         }
643         return size;
644     }
645     }
646
647
648     /**
649      * Container responsible for placing the <code>editingComponent</code>.
650      */

651     public class EditorContainer extends Container {
652         /**
653          * Constructs an <code>EditorContainer</code> object.
654          */

655     public EditorContainer() {
656         setLayout(null);
657     }
658
659     // This should not be used. It will be removed when new API is
660
// allowed.
661
public void EditorContainer() {
662         setLayout(null);
663     }
664
665         /**
666          * Overrides <code>Container.paint</code> to paint the node's
667          * icon and use the selection color for the background.
668          */

669     public void paint(Graphics g) {
670         Dimension size = getSize();
671
672         // Then the icon.
673
if(editingIcon != null) {
674         int yLoc = Math.max(0, (getSize().height -
675                       editingIcon.getIconHeight()) / 2);
676
677         editingIcon.paintIcon(this, g, 0, yLoc);
678         }
679
680         // Border selection color
681
Color background = getBorderSelectionColor();
682         if(background != null) {
683         g.setColor(background);
684         g.drawRect(0, 0, size.width - 1, size.height - 1);
685         }
686         super.paint(g);
687     }
688
689     /**
690      * Lays out this <code>Container</code>. If editing,
691          * the editor will be placed at
692      * <code>offset</code> in the x direction and 0 for y.
693      */

694     public void doLayout() {
695         if(editingComponent != null) {
696         Dimension cSize = getSize();
697
698         editingComponent.getPreferredSize();
699         editingComponent.setLocation(offset, 0);
700         editingComponent.setBounds(offset, 0,
701                        cSize.width - offset,
702                        cSize.height);
703         }
704     }
705
706     /**
707      * Returns the preferred size for the <code>Container</code>.
708          * This will be at least preferred size of the editor plus
709          * <code>offset</code>.
710          * @return a <code>Dimension</code> containing the preferred
711          * size for the <code>Container</code>; if
712          * <code>editingComponent</code> is <code>null</code> the
713          * <code>Dimension</code> returned is 0, 0
714      */

715     public Dimension getPreferredSize() {
716         if(editingComponent != null) {
717         Dimension pSize = editingComponent.getPreferredSize();
718
719         pSize.width += offset + 5;
720
721         Dimension rSize = (renderer != null) ?
722                                   renderer.getPreferredSize() : null;
723
724         if(rSize != null)
725             pSize.height = Math.max(pSize.height, rSize.height);
726         if(editingIcon != null)
727             pSize.height = Math.max(pSize.height,
728                         editingIcon.getIconHeight());
729
730         // Make sure width is at least 100.
731
pSize.width = Math.max(pSize.width, 100);
732         return pSize;
733         }
734         return new Dimension(0, 0);
735     }
736     }
737 }
738
Popular Tags