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 * Helper field used to save the last path seen while the timer was
312 * running.
313 */
314 private TreePath tPath;
315
316 /**
317 * Constructs a DefaultTreeCellEditor object for a JTree using the
318 * specified renderer and a default editor. (Use this constructor
319 * for normal editing.)
320 *
321 * @param tree - a JTree object
322 * @param renderer - a DefaultTreeCellRenderer object
323 */
324 public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer)
325 {
326 this(tree, renderer, null);
327 }
328
329 /**
330 * Constructs a DefaultTreeCellEditor object for a JTree using the specified
331 * renderer and the specified editor. (Use this constructor
332 * for specialized editing.)
333 *
334 * @param tree - a JTree object
335 * @param renderer - a DefaultTreeCellRenderer object
336 * @param editor - a TreeCellEditor object
337 */
338 public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,
339 TreeCellEditor editor)
340 {
341 this.renderer = renderer;
342 realEditor = editor;
343 if (realEditor == null)
344 realEditor = createTreeCellEditor();
345 editingContainer = createContainer();
346 setTree(tree);
347 Color c = UIManager.getColor("Tree.editorBorderSelectionColor");
348 setBorderSelectionColor(c);
349 }
350
351 /**
352 * Configures the editing component whenever it is null.
353 *
354 * @param tree the tree to configure to component for.
355 * @param renderer the renderer used to set up the nodes
356 * @param editor the editor used
357 */
358 private void configureEditingComponent(JTree tree,
359 DefaultTreeCellRenderer renderer,
360 TreeCellEditor editor)
361 {
362 if (tree != null && lastPath != null)
363 {
364 Object val = lastPath.getLastPathComponent();
365 boolean isLeaf = tree.getModel().isLeaf(val);
366 boolean expanded = tree.isExpanded(lastPath);
367 determineOffset(tree, val, true, expanded, isLeaf, lastRow);
368
369 // set up icon
370 if (isLeaf)
371 renderer.setIcon(renderer.getLeafIcon());
372 else if (expanded)
373 renderer.setIcon(renderer.getOpenIcon());
374 else
375 renderer.setIcon(renderer.getClosedIcon());
376 editingIcon = renderer.getIcon();
377
378 editingComponent = getTreeCellEditorComponent(tree, val, true,
379 expanded, isLeaf, lastRow);
380 }
381 }
382
383 /**
384 * writeObject
385 *
386 * @param value0
387 * TODO
388 * @exception IOException
389 * TODO
390 */
391 private void writeObject(ObjectOutputStream value0) throws IOException
392 {
393 // TODO
394 }
395
396 /**
397 * readObject
398 * @param value0 TODO
399 * @exception IOException TODO
400 * @exception ClassNotFoundException TODO
401 */
402 private void readObject(ObjectInputStream value0)
403 throws IOException, ClassNotFoundException
404 {
405 // TODO
406 }
407
408 /**
409 * Sets the color to use for the border.
410 * @param newColor - the new border color
411 */
412 public void setBorderSelectionColor(Color newColor)
413 {
414 this.borderSelectionColor = newColor;
415 }
416
417 /**
418 * Returns the color the border is drawn.
419 * @return Color
420 */
421 public Color getBorderSelectionColor()
422 {
423 return borderSelectionColor;
424 }
425
426 /**
427 * Sets the font to edit with. null indicates the renderers
428 * font should be used. This will NOT override any font you have
429 * set in the editor the receiver was instantied with. If null for
430 * an editor was passed in, a default editor will be created that
431 * will pick up this font.
432 *
433 * @param font - the editing Font
434 */
435 public void setFont(Font font)
436 {
437 if (font != null)
438 this.font = font;
439 else
440 this.font = renderer.getFont();
441 }
442
443 /**
444 * Gets the font used for editing.
445 *
446 * @return the editing font
447 */
448 public Font getFont()
449 {
450 return font;
451 }
452
453 /**
454 * Configures the editor. Passed onto the realEditor.
455 * Sets an initial value for the editor. This will cause
456 * the editor to stopEditing and lose any partially edited value
457 * if the editor is editing when this method is called.
458 * Returns the component that should be added to the client's Component
459 * hierarchy. Once installed in the client's hierarchy this component will
460 * then be able to draw and receive user input.
461 *
462 * @param tree - the JTree that is asking the editor to edit; this parameter can be null
463 * @param value - the value of the cell to be edited
464 * @param isSelected - true is the cell is to be rendered with selection highlighting
465 * @param expanded - true if the node is expanded
466 * @param leaf - true if the node is a leaf node
467 * @param row - the row index of the node being edited
468 *
469 * @return the component for editing
470 */
471 public Component getTreeCellEditorComponent(JTree tree, Object value,
472 boolean isSelected,
473 boolean expanded,
474 boolean leaf, int row)
475 {
476 setTree(tree);
477 lastRow = row;
478 determineOffset(tree, value, isSelected, expanded, leaf, row);
479 if (editingComponent != null)
480 editingContainer.remove(editingComponent);
481
482 editingComponent = realEditor.getTreeCellEditorComponent(tree, value,
483 isSelected,
484 expanded, leaf,
485 row);
486 Font f = getFont();
487 if (f == null)
488 {
489 if (renderer != null)
490 f = renderer.getFont();
491 if (f == null)
492 f = tree.getFont();
493 }
494 editingContainer.setFont(f);
495 prepareForEditing();
496 return editingContainer;
497 }
498
499 /**
500 * Returns the value currently being edited (requests it from the
501 * {@link #realEditor}.
502 *
503 * @return the value currently being edited
504 */
505 public Object getCellEditorValue()
506 {
507 return realEditor.getCellEditorValue();
508 }
509
510 /**
511 * If the realEditor returns true to this message, prepareForEditing
512 * is messaged and true is returned.
513 *
514 * @param event - the event the editor should use to consider whether to
515 * begin editing or not
516 * @return true if editing can be started
517 */
518 public boolean isCellEditable(EventObject event)
519 {
520 boolean ret = false;
521 boolean ed = false;
522 if (event != null)
523 {
524 if (event.getSource() instanceof JTree)
525 {
526 setTree((JTree) event.getSource());
527 if (event instanceof MouseEvent)
528 {
529 MouseEvent me = (MouseEvent) event;
530 TreePath path = tree.getPathForLocation(me.getX(), me.getY());
531 ed = lastPath != null && path != null && lastPath.equals(path);
532 if (path != null)
533 {
534 lastRow = tree.getRowForPath(path);
535 Object val = path.getLastPathComponent();
536 boolean isSelected = tree.isRowSelected(lastRow);
537 boolean isExpanded = tree.isExpanded(path);
538 TreeModel m = tree.getModel();
539 boolean isLeaf = m.isLeaf(val);
540 determineOffset(tree, val, isSelected, isExpanded, isLeaf,
541 lastRow);
542 }
543 }
544 }
545 }
546 if (! realEditor.isCellEditable(event))
547 ret = false;
548 else
549 {
550 if (canEditImmediately(event))
551 ret = true;
552 else if (ed && shouldStartEditingTimer(event))
553 startEditingTimer();
554 else if (timer != null && timer.isRunning())
555 timer.stop();
556 }
557 if (ret)
558 prepareForEditing();
559 return ret;
560
561 }
562
563 /**
564 * Messages the realEditor for the return value.
565 *
566 * @param event -
567 * the event the editor should use to start editing
568 * @return true if the editor would like the editing cell to be selected;
569 * otherwise returns false
570 */
571 public boolean shouldSelectCell(EventObject event)
572 {
573 return true;
574 }
575
576 /**
577 * If the realEditor will allow editing to stop, the realEditor
578 * is removed and true is returned, otherwise false is returned.
579 * @return true if editing was stopped; false otherwise
580 */
581 public boolean stopCellEditing()
582 {
583 boolean ret = false;
584 if (realEditor.stopCellEditing())
585 {
586 finish();
587 ret = true;
588 }
589 return ret;
590 }
591
592 /**
593 * Messages cancelCellEditing to the realEditor and removes it
594 * from this instance.
595 */
596 public void cancelCellEditing()
597 {
598 realEditor.cancelCellEditing();
599 finish();
600 }
601
602 private void finish()
603 {
604 if (editingComponent != null)
605 editingContainer.remove(editingComponent);
606 editingComponent = null;
607 }
608
609 /**
610 * Adds a <code>CellEditorListener</code> object to this editor.
611 *
612 * @param listener
613 * the listener to add
614 */
615 public void addCellEditorListener(CellEditorListener listener)
616 {
617 realEditor.addCellEditorListener(listener);
618 }
619
620 /**
621 * Removes a <code>CellEditorListener</code> object.
622 *
623 * @param listener the listener to remove
624 */
625 public void removeCellEditorListener(CellEditorListener listener)
626 {
627 realEditor.removeCellEditorListener(listener);
628 }
629
630 /**
631 * Returns all added <code>CellEditorListener</code> objects to this editor.
632 *
633 * @return an array of listeners
634 *
635 * @since 1.4
636 */
637 public CellEditorListener[] getCellEditorListeners()
638 {
639 return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class);
640 }
641
642 /**
643 * Resets lastPath.
644 *
645 * @param e - the event that characterizes the change.
646 */
647 public void valueChanged(TreeSelectionEvent e)
648 {
649 if (tree != null)
650 {
651 if (tree.getSelectionCount() == 1)
652 lastPath = tree.getSelectionPath();
653 else
654 lastPath = null;
655 }
656 // TODO: We really should do the following here, but can't due
657 // to buggy DefaultTreeSelectionModel. This selection model
658 // should only fire if the selection actually changes.
659 // if (timer != null)
660 // timer.stop();
661 }
662
663 /**
664 * Messaged when the timer fires.
665 *
666 * @param e the event that characterizes the action.
667 */
668 public void actionPerformed(ActionEvent e)
669 {
670 if (tree != null && lastPath != null)
671 tree.startEditingAtPath(lastPath);
672 }
673
674 /**
675 * Sets the tree currently editing for. This is needed to add a selection
676 * listener.
677 *
678 * @param newTree -
679 * the new tree to be edited
680 */
681 protected void setTree(JTree newTree)
682 {
683 if (tree != newTree)
684 {
685 if (tree != null)
686 tree.removeTreeSelectionListener(this);
687 tree = newTree;
688 if (tree != null)
689 tree.addTreeSelectionListener(this);
690
691 if (timer != null)
692 timer.stop();
693 }
694 }
695
696 /**
697 * Returns true if event is a MouseEvent and the click count is 1.
698 *
699 * @param event - the event being studied
700 * @return true if editing should start
701 */
702 protected boolean shouldStartEditingTimer(EventObject event)
703 {
704 boolean ret = false;
705 if (event instanceof MouseEvent)
706 {
707 MouseEvent me = (MouseEvent) event;
708 ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1
709 && inHitRegion(me.getX(), me.getY());
710 }
711 return ret;
712 }
713
714 /**
715 * Starts the editing timer (if one installed).
716 */
717 protected void startEditingTimer()
718 {
719 if (timer == null)
720 {
721 timer = new Timer(1200, this);
722 timer.setRepeats(false);
723 }
724 timer.start();
725 }
726
727 /**
728 * Returns true if event is null, or it is a MouseEvent with
729 * a click count > 2 and inHitRegion returns true.
730 *
731 * @param event - the event being studied
732 * @return true if event is null, or it is a MouseEvent with
733 * a click count > 2 and inHitRegion returns true
734 */
735 protected boolean canEditImmediately(EventObject event)
736 {
737 if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event).
738 getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(),
739 ((MouseEvent) event).getY())))
740 return true;
741 return false;
742 }
743
744 /**
745 * Returns true if the passed in location is a valid mouse location
746 * to start editing from. This is implemented to return false if x is
747 * less than or equal to the width of the icon and icon
748 * gap displayed by the renderer. In other words this returns true if
749 * the user clicks over the text part displayed by the renderer, and
750 * false otherwise.
751 *
752 * @param x - the x-coordinate of the point
753 * @param y - the y-coordinate of the point
754 *
755 * @return true if the passed in location is a valid mouse location
756 */
757 protected boolean inHitRegion(int x, int y)
758 {
759 Rectangle bounds = tree.getPathBounds(lastPath);
760 return bounds.contains(x, y);
761 }
762
763 /**
764 * determineOffset
765 * @param tree -
766 * @param value -
767 * @param isSelected -
768 * @param expanded -
769 * @param leaf -
770 * @param row -
771 */
772 protected void determineOffset(JTree tree, Object value, boolean isSelected,
773 boolean expanded, boolean leaf, int row)
774 {
775 if (renderer != null)
776 {
777 if (leaf)
778 editingIcon = renderer.getLeafIcon();
779 else if (expanded)
780 editingIcon = renderer.getOpenIcon();
781 else
782 editingIcon = renderer.getClosedIcon();
783 if (editingIcon != null)
784 offset = renderer.getIconTextGap() + editingIcon.getIconWidth();
785 else
786 offset = renderer.getIconTextGap();
787 }
788 else
789 {
790 editingIcon = null;
791 offset = 0;
792 }
793 }
794
795 /**
796 * Invoked just before editing is to start. Will add the
797 * editingComponent to the editingContainer.
798 */
799 protected void prepareForEditing()
800 {
801 if (editingComponent != null)
802 editingContainer.add(editingComponent);
803 }
804
805 /**
806 * Creates the container to manage placement of editingComponent.
807 *
808 * @return the container to manage the placement of the editingComponent.
809 */
810 protected Container createContainer()
811 {
812 return new DefaultTreeCellEditor.EditorContainer();
813 }
814
815 /**
816 * This is invoked if a TreeCellEditor is not supplied in the constructor.
817 * It returns a TextField editor.
818 *
819 * @return a new TextField editor
820 */
821 protected TreeCellEditor createTreeCellEditor()
822 {
823 Border border = UIManager.getBorder("Tree.editorBorder");
824 JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border);
825 DefaultCellEditor editor = new DefaultCellEditor(tf);
826 editor.setClickCountToStart(1);
827 realEditor = editor;
828 return editor;
829 }
830 }