001 /* DefaultTreeCellEditor.java --
002 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.tree;
040
041 import java.awt.Color;
042 import java.awt.Component;
043 import java.awt.Container;
044 import java.awt.Dimension;
045 import java.awt.Font;
046 import java.awt.Graphics;
047 import java.awt.Rectangle;
048 import java.awt.event.ActionEvent;
049 import java.awt.event.ActionListener;
050 import java.awt.event.MouseEvent;
051 import java.io.IOException;
052 import java.io.ObjectInputStream;
053 import java.io.ObjectOutputStream;
054 import java.util.EventObject;
055
056 import javax.swing.DefaultCellEditor;
057 import javax.swing.Icon;
058 import javax.swing.JTextField;
059 import javax.swing.JTree;
060 import javax.swing.SwingUtilities;
061 import javax.swing.Timer;
062 import javax.swing.UIManager;
063 import javax.swing.border.Border;
064 import javax.swing.event.CellEditorListener;
065 import javax.swing.event.EventListenerList;
066 import javax.swing.event.TreeSelectionEvent;
067 import javax.swing.event.TreeSelectionListener;
068
069 /**
070 * Participates in the tree cell editing.
071 *
072 * @author Andrew Selkirk
073 * @author Audrius Meskauskas
074 */
075 public 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 }