1 /* DefaultTreeCellEditor.java --
2    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package javax.swing.tree;
40 
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.Container;
44 import java.awt.Dimension;
45 import java.awt.Font;
46 import java.awt.FontMetrics;
47 import java.awt.Graphics;
48 import java.awt.Insets;
49 import java.awt.Rectangle;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.ActionListener;
52 import java.awt.event.MouseEvent;
53 import java.io.IOException;
54 import java.io.ObjectInputStream;
55 import java.io.ObjectOutputStream;
56 import java.util.EventObject;
57 
58 import javax.swing.DefaultCellEditor;
59 import javax.swing.Icon;
60 import javax.swing.JTextField;
61 import javax.swing.JTree;
62 import javax.swing.SwingUtilities;
63 import javax.swing.UIDefaults;
64 import javax.swing.UIManager;
65 import javax.swing.border.Border;
66 import javax.swing.event.CellEditorListener;
67 import javax.swing.event.EventListenerList;
68 import javax.swing.event.TreeSelectionEvent;
69 import javax.swing.event.TreeSelectionListener;
70 
71 /**
72  * DefaultTreeCellEditor
73  * @author Andrew Selkirk
74  */
75 public class DefaultTreeCellEditor
76   implements ActionListener, TreeCellEditor, TreeSelectionListener
77 {
78   /**
79    * EditorContainer
80    */
81   public class EditorContainer extends Container
82   {
83     /**
84      * Creates an <code>EditorContainer</code> object.
85      */
EditorContainer()86     public EditorContainer()
87     {
88       // Do nothing here.
89     }
90 
91     /**
92      * This method only exists for API compatibility and is useless as it does
93      * nothing. It got probably introduced by accident.
94      */
EditorContainer()95     public void EditorContainer()
96     {
97       // Do nothing here.
98     }
99 
100     /**
101      * Returns the preferred size for the Container.
102      *
103      * @return Dimension of EditorContainer
104      */
getPreferredSize()105     public Dimension getPreferredSize()
106     {
107       Dimension containerSize = super.getPreferredSize();
108       containerSize.width += DefaultTreeCellEditor.this.offset;
109       return containerSize;
110     }
111 
112     /**
113      * Overrides Container.paint to paint the node's icon and use the selection
114      * color for the background.
115      *
116      * @param g -
117      *          the specified Graphics window
118      */
paint(Graphics g)119     public void paint(Graphics g)
120     {
121       Rectangle tr = tree.getPathBounds(lastPath);
122       if (tr != null)
123         {
124           Insets i = ((DefaultTextField) editingComponent).getBorder()
125                                                   .getBorderInsets(this);
126           int textIconGap = 3;
127           tr.x -= i.left;
128 
129           // paints icon
130           if (editingIcon != null)
131             {
132               editingIcon.paintIcon(this, g, tr.x - editingIcon.
133                                               getIconWidth()/2, tr.y + i.top + i.bottom);
134               tr.x += editingIcon.getIconWidth()/2 + textIconGap;
135             }
136 
137           tr.width += offset;
138 
139           // paint background
140           g.translate(tr.x, tr.y);
141           editingComponent.setSize(new Dimension(tr.width, tr.height));
142           editingComponent.paint(g);
143           g.translate(-tr.x, -tr.y);
144         }
145       super.paint(g);
146     }
147 
148     /**
149      * Lays out this Container. If editing, the editor will be placed at offset
150      * in the x direction and 0 for y.
151      */
doLayout()152     public void doLayout()
153     {
154       if (DefaultTreeCellEditor.this.tree.isEditing())
155         setLocation(offset, 0);
156       super.doLayout();
157     }
158   }
159 
160   /**
161    * DefaultTextField
162    */
163   public class DefaultTextField extends JTextField
164   {
165     /**
166      * border
167      */
168     protected Border border;
169 
170     /**
171      * Creates a <code>DefaultTextField</code> object.
172      *
173      * @param border the border to use
174      */
DefaultTextField(Border border)175     public DefaultTextField(Border border)
176     {
177       this.border = border;
178     }
179 
180     /**
181      * Gets the font of this component.
182      * @return this component's font; if a font has not been set for
183      * this component, the font of its parent is returned (if the parent
184      * is not null, otherwise null is returned).
185      */
getFont()186     public Font getFont()
187     {
188       Font font = super.getFont();
189       if (font == null)
190         {
191           Component parent = getParent();
192           if (parent != null)
193             return parent.getFont();
194           return null;
195         }
196       return font;
197     }
198 
199     /**
200      * Returns the border of the text field.
201      *
202      * @return the border
203      */
getBorder()204     public Border getBorder()
205     {
206       return border;
207     }
208 
209     /**
210      * Overrides JTextField.getPreferredSize to return the preferred size
211      * based on current font, if set, or else use renderer's font.
212      *
213      * @return the Dimension of this textfield.
214      */
getPreferredSize()215     public Dimension getPreferredSize()
216     {
217       String s = getText();
218 
219       Font f = getFont();
220 
221       if (f != null)
222         {
223           FontMetrics fm = getToolkit().getFontMetrics(f);
224 
225           return new Dimension(SwingUtilities.computeStringWidth(fm, s),
226                                fm.getHeight());
227         }
228       return renderer.getPreferredSize();
229     }
230   }
231 
232   private EventListenerList listenerList = new EventListenerList();
233 
234   /**
235    * Editor handling the editing.
236    */
237   protected TreeCellEditor realEditor;
238 
239   /**
240    * Renderer, used to get border and offsets from.
241    */
242   protected DefaultTreeCellRenderer renderer;
243 
244   /**
245    * Editing container, will contain the editorComponent.
246    */
247   protected Container editingContainer;
248 
249   /**
250    * Component used in editing, obtained from the editingContainer.
251    */
252   protected transient Component editingComponent;
253 
254   /**
255    * As of Java 2 platform v1.4 this field should no longer be used.
256    * If you wish to provide similar behavior you should directly
257    * override isCellEditable.
258    */
259   protected boolean canEdit;
260 
261   /**
262    * Used in editing. Indicates x position to place editingComponent.
263    */
264   protected transient int offset;
265 
266   /**
267    * JTree instance listening too.
268    */
269   protected transient JTree tree;
270 
271   /**
272    * Last path that was selected.
273    */
274   protected transient TreePath lastPath;
275 
276   /**
277    * Used before starting the editing session.
278    */
279   protected transient javax.swing.Timer timer;
280 
281   /**
282    * Row that was last passed into getTreeCellEditorComponent.
283    */
284   protected transient int lastRow;
285 
286   /**
287    * True if the border selection color should be drawn.
288    */
289   protected Color borderSelectionColor;
290 
291   /**
292    * Icon to use when editing.
293    */
294   protected transient Icon editingIcon;
295 
296   /**
297    * Font to paint with, null indicates font of renderer is to be used.
298    */
299   protected Font font;
300 
301   /**
302    * Helper field used to save the last path seen while the timer was
303    * running.
304    */
305     private TreePath tPath;
306 
307   /**
308    * Constructs a DefaultTreeCellEditor object for a JTree using the
309    * specified renderer and a default editor. (Use this constructor
310    * for normal editing.)
311    *
312    * @param tree - a JTree object
313    * @param renderer - a DefaultTreeCellRenderer object
314    */
DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer)315   public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer)
316   {
317     this(tree, renderer, null);
318   }
319 
320   /**
321    * Constructs a DefaultTreeCellEditor  object for a JTree using the specified
322    * renderer and the specified editor. (Use this constructor
323    * for specialized editing.)
324    *
325    * @param tree - a JTree object
326    * @param renderer - a DefaultTreeCellRenderer object
327    * @param editor - a TreeCellEditor object
328    */
DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, TreeCellEditor editor)329   public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,
330                                TreeCellEditor editor)
331   {
332     setTree(tree);
333     this.renderer = renderer;
334 
335     if (editor == null)
336       editor = createTreeCellEditor();
337     realEditor = editor;
338 
339     lastPath = tree.getLeadSelectionPath();
340     tree.addTreeSelectionListener(this);
341     editingContainer = createContainer();
342     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
343     setFont(defaults.getFont("Tree.font"));
344     setBorderSelectionColor(defaults.getColor("Tree.selectionBorderColor"));
345     editingIcon = renderer.getIcon();
346     timer = new javax.swing.Timer(1200, this);
347   }
348 
349   /**
350    * Configures the editing component whenever it is null.
351    *
352    * @param tree the tree to configure to component for.
353    * @param renderer the renderer used to set up the nodes
354    * @param editor the editor used
355    */
configureEditingComponent(JTree tree, DefaultTreeCellRenderer renderer, TreeCellEditor editor)356   private void configureEditingComponent(JTree tree,
357                                          DefaultTreeCellRenderer renderer,
358                                          TreeCellEditor editor)
359   {
360     if (tree != null && lastPath != null)
361       {
362         Object val = lastPath.getLastPathComponent();
363         boolean isLeaf = tree.getModel().isLeaf(val);
364         boolean expanded = tree.isExpanded(lastPath);
365         determineOffset(tree, val, true, expanded, isLeaf, lastRow);
366 
367         // set up icon
368         if (isLeaf)
369           renderer.setIcon(renderer.getLeafIcon());
370         else if (expanded)
371           renderer.setIcon(renderer.getOpenIcon());
372         else
373           renderer.setIcon(renderer.getClosedIcon());
374         editingIcon = renderer.getIcon();
375 
376         editingComponent = getTreeCellEditorComponent(tree, val, true,
377                                                       expanded, isLeaf, lastRow);
378       }
379   }
380 
381   /**
382    * writeObject
383    *
384    * @param value0
385    *          TODO
386    * @exception IOException
387    *              TODO
388    */
writeObject(ObjectOutputStream value0)389   private void writeObject(ObjectOutputStream value0) throws IOException
390   {
391     // TODO
392   }
393 
394   /**
395    * readObject
396    * @param value0 TODO
397    * @exception IOException TODO
398    * @exception ClassNotFoundException TODO
399    */
readObject(ObjectInputStream value0)400   private void readObject(ObjectInputStream value0)
401     throws IOException, ClassNotFoundException
402   {
403     // TODO
404   }
405 
406   /**
407    * Sets the color to use for the border.
408    * @param newColor - the new border color
409    */
setBorderSelectionColor(Color newColor)410   public void setBorderSelectionColor(Color newColor)
411   {
412     this.borderSelectionColor = newColor;
413   }
414 
415   /**
416    * Returns the color the border is drawn.
417    * @return Color
418    */
getBorderSelectionColor()419   public Color getBorderSelectionColor()
420   {
421     return borderSelectionColor;
422   }
423 
424   /**
425    * Sets the font to edit with. null indicates the renderers
426    * font should be used. This will NOT override any font you have
427    * set in the editor the receiver was instantied with. If null for
428    * an editor was passed in, a default editor will be created that
429    * will pick up this font.
430    *
431    * @param font - the editing Font
432    */
setFont(Font font)433   public void setFont(Font font)
434   {
435     if (font != null)
436       this.font = font;
437     else
438       this.font = renderer.getFont();
439   }
440 
441   /**
442    * Gets the font used for editing.
443    *
444    * @return the editing font
445    */
getFont()446   public Font getFont()
447   {
448     return font;
449   }
450 
451   /**
452    * Configures the editor. Passed onto the realEditor.
453    * Sets an initial value for the editor. This will cause
454    * the editor to stopEditing and lose any partially edited value
455    * if the editor is editing when this method is called.
456    * Returns the component that should be added to the client's Component
457    * hierarchy. Once installed in the client's hierarchy this component will
458    * then be able to draw and receive user input.
459    *
460    * @param tree - the JTree that is asking the editor to edit; this parameter can be null
461    * @param value - the value of the cell to be edited
462    * @param isSelected - true is the cell is to be rendered with selection highlighting
463    * @param expanded - true if the node is expanded
464    * @param leaf - true if the node is a leaf node
465    * @param row - the row index of the node being edited
466    *
467    * @return the component for editing
468    */
getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row)469   public Component getTreeCellEditorComponent(JTree tree, Object value,
470                                               boolean isSelected, boolean expanded,
471                                               boolean leaf, int row)
472   {
473     if (realEditor == null)
474       createTreeCellEditor();
475 
476     return realEditor.getTreeCellEditorComponent(tree, value, isSelected,
477                                                         expanded, leaf, row);
478   }
479 
480   /**
481    * Returns the value currently being edited.
482    *
483    * @return the value currently being edited
484    */
getCellEditorValue()485   public Object getCellEditorValue()
486   {
487     return editingComponent;
488   }
489 
490   /**
491    * If the realEditor returns true to this message, prepareForEditing
492    * is messaged and true is returned.
493    *
494    * @param event - the event the editor should use to consider whether to begin editing or not
495    * @return true if editing can be started
496    */
isCellEditable(EventObject event)497   public boolean isCellEditable(EventObject event)
498   {
499     if (editingComponent == null)
500         configureEditingComponent(tree, renderer, realEditor);
501 
502     if (editingComponent != null && realEditor.isCellEditable(event))
503       {
504         prepareForEditing();
505         return true;
506       }
507 
508     // Cell may not be currently editable, but may need to start timer.
509     if (shouldStartEditingTimer(event))
510       startEditingTimer();
511     else if (timer.isRunning())
512       timer.stop();
513     return false;
514   }
515 
516   /**
517    * Messages the realEditor for the return value.
518    *
519    * @param event -
520    *          the event the editor should use to start editing
521    * @return true if the editor would like the editing cell to be selected;
522    *         otherwise returns false
523    */
shouldSelectCell(EventObject event)524   public boolean shouldSelectCell(EventObject event)
525   {
526     return true;
527   }
528 
529   /**
530    * If the realEditor will allow editing to stop, the realEditor
531    * is removed and true is returned, otherwise false is returned.
532    * @return true if editing was stopped; false otherwise
533    */
stopCellEditing()534   public boolean stopCellEditing()
535   {
536     if (editingComponent != null && realEditor.stopCellEditing())
537       {
538         timer.stop();
539         return true;
540       }
541     return false;
542   }
543 
544   /**
545    * Messages cancelCellEditing to the realEditor and removes it
546    * from this instance.
547    */
cancelCellEditing()548   public void cancelCellEditing()
549   {
550     if (editingComponent != null)
551       {
552         timer.stop();
553         realEditor.cancelCellEditing();
554       }
555   }
556 
557   /**
558    * Adds a <code>CellEditorListener</code> object to this editor.
559    *
560    * @param listener the listener to add
561    */
addCellEditorListener(CellEditorListener listener)562   public void addCellEditorListener(CellEditorListener listener)
563   {
564     realEditor.addCellEditorListener(listener);
565   }
566 
567   /**
568    * Removes a <code>CellEditorListener</code> object.
569    *
570    * @param listener the listener to remove
571    */
removeCellEditorListener(CellEditorListener listener)572   public void removeCellEditorListener(CellEditorListener listener)
573   {
574     realEditor.removeCellEditorListener(listener);
575   }
576 
577   /**
578    * Returns all added <code>CellEditorListener</code> objects to this editor.
579    *
580    * @return an array of listeners
581    *
582    * @since 1.4
583    */
getCellEditorListeners()584   public CellEditorListener[] getCellEditorListeners()
585   {
586     return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class);
587   }
588 
589   /**
590    * Resets lastPath.
591    *
592    * @param e - the event that characterizes the change.
593    */
valueChanged(TreeSelectionEvent e)594   public void valueChanged(TreeSelectionEvent e)
595   {
596     tPath = lastPath;
597     lastPath = e.getNewLeadSelectionPath();
598     lastRow = tree.getRowForPath(lastPath);
599     configureEditingComponent(tree, renderer, realEditor);
600   }
601 
602   /**
603    * Messaged when the timer fires, this will start the editing session.
604    *
605    * @param e the event that characterizes the action.
606    */
actionPerformed(ActionEvent e)607   public void actionPerformed(ActionEvent e)
608   {
609     if (lastPath != null && tPath != null && tPath.equals(lastPath))
610       {
611         tree.startEditingAtPath(lastPath);
612         timer.stop();
613       }
614   }
615 
616   /**
617    * Sets the tree currently editing for. This is needed to add a selection
618    * listener.
619    *
620    * @param newTree -
621    *          the new tree to be edited
622    */
setTree(JTree newTree)623   protected void setTree(JTree newTree)
624   {
625     tree = newTree;
626   }
627 
628   /**
629    * Returns true if event is a MouseEvent and the click count is 1.
630    *
631    * @param event - the event being studied
632    * @return true if editing should start
633    */
shouldStartEditingTimer(EventObject event)634   protected boolean shouldStartEditingTimer(EventObject event)
635   {
636     if ((event instanceof MouseEvent) &&
637         ((MouseEvent) event).getClickCount() == 1)
638       return true;
639     return false;
640   }
641 
642   /**
643    * Starts the editing timer.
644    */
startEditingTimer()645   protected void startEditingTimer()
646   {
647     if (timer == null)
648       timer = new javax.swing.Timer(1200, this);
649     if (!timer.isRunning())
650       timer.start();
651   }
652 
653   /**
654    * Returns true if event is null, or it is a MouseEvent with
655    * a click count > 2 and inHitRegion returns true.
656    *
657    * @param event - the event being studied
658    * @return true if event is null, or it is a MouseEvent with
659    * a click count > 2 and inHitRegion returns true
660    */
canEditImmediately(EventObject event)661   protected boolean canEditImmediately(EventObject event)
662   {
663     if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event).
664         getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(),
665                                          ((MouseEvent) event).getY())))
666       return true;
667     return false;
668   }
669 
670   /**
671    * Returns true if the passed in location is a valid mouse location
672    * to start editing from. This is implemented to return false if x is
673    * less than or equal to the width of the icon and icon
674    * gap displayed by the renderer. In other words this returns true if
675    * the user clicks over the text part displayed by the renderer, and
676    * false otherwise.
677    *
678    * @param x - the x-coordinate of the point
679    * @param y - the y-coordinate of the point
680    *
681    * @return true if the passed in location is a valid mouse location
682    */
inHitRegion(int x, int y)683   protected boolean inHitRegion(int x, int y)
684   {
685     Rectangle bounds = tree.getPathBounds(lastPath);
686 
687     return bounds.contains(x, y);
688   }
689 
690   /**
691    * determineOffset
692    * @param tree -
693    * @param value -
694    * @param isSelected -
695    * @param expanded -
696    * @param leaf -
697    * @param row -
698    */
determineOffset(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row)699   protected void determineOffset(JTree tree, Object value, boolean isSelected,
700                                  boolean expanded, boolean leaf, int row)
701   {
702     renderer.getTreeCellRendererComponent(tree, value, isSelected, expanded,
703                                           leaf, row, true);
704     Icon c = renderer.getIcon();
705     if (c != null)
706         offset = renderer.getIconTextGap() + c.getIconWidth();
707     else
708       offset = 0;
709   }
710 
711   /**
712    * Invoked just before editing is to start. Will add the
713    * editingComponent to the editingContainer.
714    */
prepareForEditing()715   protected void prepareForEditing()
716   {
717     editingContainer.add(editingComponent);
718   }
719 
720   /**
721    * Creates the container to manage placement of editingComponent.
722    *
723    * @return the container to manage the placement of the editingComponent.
724    */
createContainer()725   protected Container createContainer()
726   {
727     return new DefaultTreeCellEditor.EditorContainer();
728   }
729 
730   /**
731    * This is invoked if a TreeCellEditor is not supplied in the constructor.
732    * It returns a TextField editor.
733    *
734    * @return a new TextField editor
735    */
createTreeCellEditor()736   protected TreeCellEditor createTreeCellEditor()
737   {
738     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
739     realEditor = new DefaultCellEditor(new DefaultTreeCellEditor.DefaultTextField(
740                                   defaults.getBorder("Tree.selectionBorder")));
741     return realEditor;
742   }
743 }
744