001/* DefaultTreeCellEditor.java --
002   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.tree;
040
041import java.awt.Color;
042import java.awt.Component;
043import java.awt.Container;
044import java.awt.Dimension;
045import java.awt.Font;
046import java.awt.Graphics;
047import java.awt.Rectangle;
048import java.awt.event.ActionEvent;
049import java.awt.event.ActionListener;
050import java.awt.event.MouseEvent;
051import java.io.IOException;
052import java.io.ObjectInputStream;
053import java.io.ObjectOutputStream;
054import java.util.EventObject;
055
056import javax.swing.DefaultCellEditor;
057import javax.swing.Icon;
058import javax.swing.JTextField;
059import javax.swing.JTree;
060import javax.swing.SwingUtilities;
061import javax.swing.Timer;
062import javax.swing.UIManager;
063import javax.swing.border.Border;
064import javax.swing.event.CellEditorListener;
065import javax.swing.event.EventListenerList;
066import javax.swing.event.TreeSelectionEvent;
067import javax.swing.event.TreeSelectionListener;
068
069/**
070 * Participates in the tree cell editing.
071 *
072 * @author Andrew Selkirk
073 * @author Audrius Meskauskas
074 */
075public class DefaultTreeCellEditor
076  implements ActionListener, TreeCellEditor, TreeSelectionListener
077{
078  /**
079   * This container that appears on the tree during editing session.
080   * It contains the editing component displays various other editor -
081   * specific parts like editing icon.
082   */
083  public class EditorContainer extends Container
084  {
085   /**
086    * Use v 1.5 serial version UID for interoperability.
087    */
088    static final long serialVersionUID = 6470339600449699810L;
089
090    /**
091     * Creates an <code>EditorContainer</code> object.
092     */
093    public EditorContainer()
094    {
095      setLayout(null);
096    }
097
098    /**
099     * This method only exists for API compatibility and is useless as it does
100     * nothing. It got probably introduced by accident.
101     */
102    public void EditorContainer()
103    {
104      // Do nothing here.
105    }
106
107    /**
108     * Overrides Container.paint to paint the node's icon and use the selection
109     * color for the background.
110     *
111     * @param g -
112     *          the specified Graphics window
113     */
114    public void paint(Graphics g)
115    {
116      // Paint editing icon.
117      if (editingIcon != null)
118        {
119          // From the previous version, the left margin is taken as half
120          // of the icon width.
121          int y = Math.max(0, (getHeight() - editingIcon.getIconHeight()) / 2);
122          editingIcon.paintIcon(this, g, 0, y);
123        }
124      // Paint border.
125      Color c = getBorderSelectionColor();
126      if (c != null)
127        {
128          g.setColor(c);
129          g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
130        }
131      super.paint(g);
132    }
133
134    /**
135     * Lays out this Container, moving the editor component to the left
136     * (leaving place for the icon).
137     */
138    public void doLayout()
139    {
140      if (editingComponent != null)
141        {
142          editingComponent.getPreferredSize();
143          editingComponent.setBounds(offset, 0, getWidth() - offset,
144                                     getHeight());
145        }
146      }
147
148    public Dimension getPreferredSize()
149    {
150      Dimension dim;
151      if (editingComponent != null)
152        {
153          dim = editingComponent.getPreferredSize();
154          dim.width += offset + 5;
155          if (renderer != null)
156            {
157              Dimension r = renderer.getPreferredSize();
158              dim.height = Math.max(dim.height, r.height);
159            }
160          if (editingIcon != null)
161            dim.height = Math.max(dim.height, editingIcon.getIconHeight());
162          dim.width = Math.max(100, dim.width);
163        }
164      else
165        dim = new Dimension(0, 0);
166      return dim;
167    }
168  }
169
170  /**
171   * The default text field, used in the editing sessions.
172   */
173  public class DefaultTextField extends JTextField
174  {
175   /**
176    * Use v 1.5 serial version UID for interoperability.
177    */
178    static final long serialVersionUID = -6629304544265300143L;
179
180    /**
181     * The border of the text field.
182     */
183    protected Border border;
184
185    /**
186     * Creates a <code>DefaultTextField</code> object.
187     *
188     * @param aBorder the border to use
189     */
190    public DefaultTextField(Border aBorder)
191    {
192      border = aBorder;
193    }
194
195    /**
196     * Gets the font of this component.
197     * @return this component's font; if a font has not been set for
198     * this component, the font of its parent is returned (if the parent
199     * is not null, otherwise null is returned).
200     */
201    public Font getFont()
202    {
203      Font font = super.getFont();
204      if (font == null)
205        {
206          Component parent = getParent();
207          if (parent != null)
208            return parent.getFont();
209          return null;
210        }
211      return font;
212    }
213
214    /**
215     * Returns the border of the text field.
216     *
217     * @return the border
218     */
219    public Border getBorder()
220    {
221      return border;
222    }
223
224    /**
225     * Overrides JTextField.getPreferredSize to return the preferred size
226     * based on current font, if set, or else use renderer's font.
227     *
228     * @return the Dimension of this textfield.
229     */
230    public Dimension getPreferredSize()
231    {
232      Dimension size = super.getPreferredSize();
233      if (renderer != null && DefaultTreeCellEditor.this.getFont() == null)
234        {
235          size.height = renderer.getPreferredSize().height;
236        }
237      return renderer.getPreferredSize();
238    }
239  }
240
241  private EventListenerList listenerList = new EventListenerList();
242
243  /**
244   * Editor handling the editing.
245   */
246  protected TreeCellEditor realEditor;
247
248  /**
249   * Renderer, used to get border and offsets from.
250   */
251  protected DefaultTreeCellRenderer renderer;
252
253  /**
254   * Editing container, will contain the editorComponent.
255   */
256  protected Container editingContainer;
257
258  /**
259   * Component used in editing, obtained from the editingContainer.
260   */
261  protected transient Component editingComponent;
262
263  /**
264   * As of Java 2 platform v1.4 this field should no longer be used.
265   * If you wish to provide similar behavior you should directly
266   * override isCellEditable.
267   */
268  protected boolean canEdit;
269
270  /**
271   * Used in editing. Indicates x position to place editingComponent.
272   */
273  protected transient int offset;
274
275  /**
276   * JTree instance listening too.
277   */
278  protected transient JTree tree;
279
280  /**
281   * Last path that was selected.
282   */
283  protected transient TreePath lastPath;
284
285  /**
286   * Used before starting the editing session.
287   */
288  protected transient javax.swing.Timer timer;
289
290  /**
291   * Row that was last passed into getTreeCellEditorComponent.
292   */
293  protected transient int lastRow;
294
295  /**
296   * True if the border selection color should be drawn.
297   */
298  protected Color borderSelectionColor;
299
300  /**
301   * Icon to use when editing.
302   */
303  protected transient Icon editingIcon;
304
305  /**
306   * Font to paint with, null indicates font of renderer is to be used.
307   */
308  protected Font font;
309
310  /**
311   * Constructs a DefaultTreeCellEditor object for a JTree using the
312   * specified renderer and a default editor. (Use this constructor
313   * for normal editing.)
314   *
315   * @param tree - a JTree object
316   * @param renderer - a DefaultTreeCellRenderer object
317   */
318  public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer)
319  {
320    this(tree, renderer, null);
321  }
322
323  /**
324   * Constructs a DefaultTreeCellEditor  object for a JTree using the specified
325   * renderer and the specified editor. (Use this constructor
326   * for specialized editing.)
327   *
328   * @param tree - a JTree object
329   * @param renderer - a DefaultTreeCellRenderer object
330   * @param editor - a TreeCellEditor object
331   */
332  public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,
333                               TreeCellEditor editor)
334  {
335    this.renderer = renderer;
336    realEditor = editor;
337    if (realEditor == null)
338      realEditor = createTreeCellEditor();
339    editingContainer = createContainer();
340    setTree(tree);
341    Color c = UIManager.getColor("Tree.editorBorderSelectionColor");
342    setBorderSelectionColor(c);
343  }
344
345  /**
346   * writeObject
347   *
348   * @param value0
349   *          TODO
350   * @exception IOException
351   *              TODO
352   */
353  private void writeObject(ObjectOutputStream value0) throws IOException
354  {
355    // TODO
356  }
357
358  /**
359   * readObject
360   * @param value0 TODO
361   * @exception IOException TODO
362   * @exception ClassNotFoundException TODO
363   */
364  private void readObject(ObjectInputStream value0)
365    throws IOException, ClassNotFoundException
366  {
367    // TODO
368  }
369
370  /**
371   * Sets the color to use for the border.
372   * @param newColor - the new border color
373   */
374  public void setBorderSelectionColor(Color newColor)
375  {
376    this.borderSelectionColor = newColor;
377  }
378
379  /**
380   * Returns the color the border is drawn.
381   * @return Color
382   */
383  public Color getBorderSelectionColor()
384  {
385    return borderSelectionColor;
386  }
387
388  /**
389   * Sets the font to edit with. null indicates the renderers
390   * font should be used. This will NOT override any font you have
391   * set in the editor the receiver was instantied with. If null for
392   * an editor was passed in, a default editor will be created that
393   * will pick up this font.
394   *
395   * @param font - the editing Font
396   */
397  public void setFont(Font font)
398  {
399    if (font != null)
400      this.font = font;
401    else
402      this.font = renderer.getFont();
403  }
404
405  /**
406   * Gets the font used for editing.
407   *
408   * @return the editing font
409   */
410  public Font getFont()
411  {
412    return font;
413  }
414
415  /**
416   * Configures the editor. Passed onto the realEditor.
417   * Sets an initial value for the editor. This will cause
418   * the editor to stopEditing and lose any partially edited value
419   * if the editor is editing when this method is called.
420   * Returns the component that should be added to the client's Component
421   * hierarchy. Once installed in the client's hierarchy this component will
422   * then be able to draw and receive user input.
423   *
424   * @param tree - the JTree that is asking the editor to edit; this parameter can be null
425   * @param value - the value of the cell to be edited
426   * @param isSelected - true is the cell is to be rendered with selection highlighting
427   * @param expanded - true if the node is expanded
428   * @param leaf - true if the node is a leaf node
429   * @param row - the row index of the node being edited
430   *
431   * @return the component for editing
432   */
433  public Component getTreeCellEditorComponent(JTree tree, Object value,
434                                              boolean isSelected,
435                                              boolean expanded,
436                                              boolean leaf, int row)
437  {
438    setTree(tree);
439    lastRow = row;
440    determineOffset(tree, value, isSelected, expanded, leaf, row);
441    if (editingComponent != null)
442      editingContainer.remove(editingComponent);
443
444    editingComponent = realEditor.getTreeCellEditorComponent(tree, value,
445                                                             isSelected,
446                                                             expanded, leaf,
447                                                             row);
448    Font f = getFont();
449    if (f == null)
450      {
451        if (renderer != null)
452          f = renderer.getFont();
453        if (f == null)
454          f = tree.getFont();
455      }
456    editingContainer.setFont(f);
457    prepareForEditing();
458    return editingContainer;
459  }
460
461  /**
462   * Returns the value currently being edited (requests it from the
463   * {@link #realEditor}.
464   *
465   * @return the value currently being edited
466   */
467  public Object getCellEditorValue()
468  {
469    return realEditor.getCellEditorValue();
470  }
471
472  /**
473   * If the realEditor returns true to this message, prepareForEditing
474   * is messaged and true is returned.
475   *
476   * @param event - the event the editor should use to consider whether to
477   * begin editing or not
478   * @return true if editing can be started
479   */
480  public boolean isCellEditable(EventObject event)
481  {
482    boolean ret = false;
483    boolean ed = false;
484    if (event != null)
485      {
486        if (event.getSource() instanceof JTree)
487          {
488            setTree((JTree) event.getSource());
489            if (event instanceof MouseEvent)
490              {
491                MouseEvent me = (MouseEvent) event;
492                TreePath path = tree.getPathForLocation(me.getX(), me.getY());
493                ed = lastPath != null && path != null && lastPath.equals(path);
494                if (path != null)
495                  {
496                    lastRow = tree.getRowForPath(path);
497                    Object val = path.getLastPathComponent();
498                    boolean isSelected = tree.isRowSelected(lastRow);
499                    boolean isExpanded = tree.isExpanded(path);
500                    TreeModel m = tree.getModel();
501                    boolean isLeaf = m.isLeaf(val);
502                    determineOffset(tree, val, isSelected, isExpanded, isLeaf,
503                                    lastRow);
504                  }
505              }
506          }
507      }
508    if (! realEditor.isCellEditable(event))
509      ret = false;
510    else
511      {
512        if (canEditImmediately(event))
513          ret = true;
514        else if (ed && shouldStartEditingTimer(event))
515          startEditingTimer();
516        else if (timer != null && timer.isRunning())
517          timer.stop();
518      }
519    if (ret)
520      prepareForEditing();
521    return ret;
522
523  }
524
525  /**
526   * Messages the realEditor for the return value.
527   *
528   * @param event -
529   *          the event the editor should use to start editing
530   * @return true if the editor would like the editing cell to be selected;
531   *         otherwise returns false
532   */
533  public boolean shouldSelectCell(EventObject event)
534  {
535    return true;
536  }
537
538  /**
539   * If the realEditor will allow editing to stop, the realEditor
540   * is removed and true is returned, otherwise false is returned.
541   * @return true if editing was stopped; false otherwise
542   */
543  public boolean stopCellEditing()
544  {
545    boolean ret = false;
546    if (realEditor.stopCellEditing())
547      {
548        finish();
549        ret = true;
550      }
551    return ret;
552  }
553
554  /**
555   * Messages cancelCellEditing to the realEditor and removes it
556   * from this instance.
557   */
558  public void cancelCellEditing()
559  {
560    realEditor.cancelCellEditing();
561    finish();
562  }
563
564  private void finish()
565  {
566    if (editingComponent != null)
567      editingContainer.remove(editingComponent);
568    editingComponent = null;
569  }
570
571  /**
572   * Adds a <code>CellEditorListener</code> object to this editor.
573   *
574   * @param listener
575   *          the listener to add
576   */
577  public void addCellEditorListener(CellEditorListener listener)
578  {
579    realEditor.addCellEditorListener(listener);
580  }
581
582  /**
583   * Removes a <code>CellEditorListener</code> object.
584   *
585   * @param listener the listener to remove
586   */
587  public void removeCellEditorListener(CellEditorListener listener)
588  {
589    realEditor.removeCellEditorListener(listener);
590  }
591
592  /**
593   * Returns all added <code>CellEditorListener</code> objects to this editor.
594   *
595   * @return an array of listeners
596   *
597   * @since 1.4
598   */
599  public CellEditorListener[] getCellEditorListeners()
600  {
601    return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class);
602  }
603
604  /**
605   * Resets lastPath.
606   *
607   * @param e - the event that characterizes the change.
608   */
609  public void valueChanged(TreeSelectionEvent e)
610  {
611    if (tree != null)
612      {
613        if (tree.getSelectionCount() == 1)
614          lastPath = tree.getSelectionPath();
615        else
616          lastPath = null;
617      }
618    // TODO: We really should do the following here, but can't due
619    // to buggy DefaultTreeSelectionModel. This selection model
620    // should only fire if the selection actually changes.
621//    if (timer != null)
622//      timer.stop();
623  }
624
625  /**
626   * Messaged when the timer fires.
627   *
628   * @param e the event that characterizes the action.
629   */
630  public void actionPerformed(ActionEvent e)
631  {
632    if (tree != null && lastPath != null)
633      tree.startEditingAtPath(lastPath);
634  }
635
636  /**
637   * Sets the tree currently editing for. This is needed to add a selection
638   * listener.
639   *
640   * @param newTree -
641   *          the new tree to be edited
642   */
643  protected void setTree(JTree newTree)
644  {
645    if (tree != newTree)
646      {
647        if (tree != null)
648          tree.removeTreeSelectionListener(this);
649        tree = newTree;
650        if (tree != null)
651          tree.addTreeSelectionListener(this);
652
653        if (timer != null)
654          timer.stop();
655      }
656  }
657
658  /**
659   * Returns true if event is a MouseEvent and the click count is 1.
660   *
661   * @param event - the event being studied
662   * @return true if editing should start
663   */
664  protected boolean shouldStartEditingTimer(EventObject event)
665  {
666    boolean ret = false;
667    if (event instanceof MouseEvent)
668      {
669        MouseEvent me = (MouseEvent) event;
670        ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1
671              && inHitRegion(me.getX(), me.getY());
672      }
673    return ret;
674  }
675
676  /**
677   * Starts the editing timer (if one installed).
678   */
679  protected void startEditingTimer()
680  {
681    if (timer == null)
682      {
683        timer = new Timer(1200, this);
684        timer.setRepeats(false);
685      }
686    timer.start();
687  }
688
689  /**
690   * Returns true if event is null, or it is a MouseEvent with
691   * a click count > 2 and inHitRegion returns true.
692   *
693   * @param event - the event being studied
694   * @return true if event is null, or it is a MouseEvent with
695   * a click count > 2 and inHitRegion returns true
696   */
697  protected boolean canEditImmediately(EventObject event)
698  {
699    if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event).
700        getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(),
701                                         ((MouseEvent) event).getY())))
702      return true;
703    return false;
704  }
705
706  /**
707   * Returns true if the passed in location is a valid mouse location
708   * to start editing from. This is implemented to return false if x is
709   * less than or equal to the width of the icon and icon
710   * gap displayed by the renderer. In other words this returns true if
711   * the user clicks over the text part displayed by the renderer, and
712   * false otherwise.
713   *
714   * @param x - the x-coordinate of the point
715   * @param y - the y-coordinate of the point
716   *
717   * @return true if the passed in location is a valid mouse location
718   */
719  protected boolean inHitRegion(int x, int y)
720  {
721    Rectangle bounds = tree.getPathBounds(lastPath);
722    return bounds.contains(x, y);
723  }
724
725  /**
726   * determineOffset
727   * @param tree -
728   * @param value -
729   * @param isSelected -
730   * @param expanded -
731   * @param leaf -
732   * @param row -
733   */
734  protected void determineOffset(JTree tree, Object value, boolean isSelected,
735                                 boolean expanded, boolean leaf, int row)
736  {
737    if (renderer != null)
738      {
739        if (leaf)
740          editingIcon = renderer.getLeafIcon();
741        else if (expanded)
742          editingIcon = renderer.getOpenIcon();
743        else
744          editingIcon = renderer.getClosedIcon();
745        if (editingIcon != null)
746          offset = renderer.getIconTextGap() + editingIcon.getIconWidth();
747        else
748          offset = renderer.getIconTextGap();
749      }
750    else
751      {
752        editingIcon = null;
753        offset = 0;
754      }
755  }
756
757  /**
758   * Invoked just before editing is to start. Will add the
759   * editingComponent to the editingContainer.
760   */
761  protected void prepareForEditing()
762  {
763    if (editingComponent != null)
764      editingContainer.add(editingComponent);
765  }
766
767  /**
768   * Creates the container to manage placement of editingComponent.
769   *
770   * @return the container to manage the placement of the editingComponent.
771   */
772  protected Container createContainer()
773  {
774    return new DefaultTreeCellEditor.EditorContainer();
775  }
776
777  /**
778   * This is invoked if a TreeCellEditor is not supplied in the constructor.
779   * It returns a TextField editor.
780   *
781   * @return a new TextField editor
782   */
783  protected TreeCellEditor createTreeCellEditor()
784  {
785    Border border = UIManager.getBorder("Tree.editorBorder");
786    JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border);
787    DefaultCellEditor editor = new DefaultCellEditor(tf);
788    editor.setClickCountToStart(1);
789    realEditor = editor;
790    return editor;
791  }
792}