/*
* Copyright 1998-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package javax.swing.tree;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.plaf.FontUIResource;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.util.EventObject;
import java.util.Vector;
/**
* A TreeCellEditor
. You need to supply an
* instance of DefaultTreeCellRenderer
* so that the icons can be obtained. You can optionally supply
* a TreeCellEditor
that will be layed out according
* to the icon in the DefaultTreeCellRenderer
.
* If you do not supply a TreeCellEditor
,
* a TextField
will be used. Editing is started
* on a triple mouse click, or after a click, pause, click and
* a delay of 1200 miliseconds.
*
* Warning:
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeansTM
* has been added to the java.beans
package.
* Please see {@link java.beans.XMLEncoder}.
*
* @see javax.swing.JTree
*
* @author Scott Violet
*/
public class DefaultTreeCellEditor implements ActionListener, TreeCellEditor,
TreeSelectionListener {
/** Editor handling the editing. */
protected TreeCellEditor realEditor;
/** Renderer, used to get border and offsets from. */
protected DefaultTreeCellRenderer renderer;
/** Editing container, will contain the editorComponent
. */
protected Container editingContainer;
/**
* Component used in editing, obtained from the
* editingContainer
.
*/
transient protected Component editingComponent;
/**
* As of Java 2 platform v1.4 this field should no longer be used. If
* you wish to provide similar behavior you should directly override
* isCellEditable
.
*/
protected boolean canEdit;
/**
* Used in editing. Indicates x position to place
* editingComponent
.
*/
protected transient int offset;
/** JTree
instance listening too. */
protected transient JTree tree;
/** Last path that was selected. */
protected transient TreePath lastPath;
/** Used before starting the editing session. */
protected transient Timer timer;
/**
* Row that was last passed into
* getTreeCellEditorComponent
.
*/
protected transient int lastRow;
/** True if the border selection color should be drawn. */
protected Color borderSelectionColor;
/** Icon to use when editing. */
protected transient Icon editingIcon;
/**
* Font to paint with, null
indicates
* font of renderer is to be used.
*/
protected Font font;
/**
* Constructs a DefaultTreeCellEditor
* object for a JTree using the specified renderer and
* a default editor. (Use this constructor for normal editing.)
*
* @param tree a JTree
object
* @param renderer a DefaultTreeCellRenderer
object
*/
public DefaultTreeCellEditor(JTree tree,
DefaultTreeCellRenderer renderer) {
this(tree, renderer, null);
}
/**
* Constructs a DefaultTreeCellEditor
* object for a JTree
using the
* specified renderer and the specified editor. (Use this constructor
* for specialized editing.)
*
* @param tree a JTree
object
* @param renderer a DefaultTreeCellRenderer
object
* @param editor a TreeCellEditor
object
*/
public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,
TreeCellEditor editor) {
this.renderer = renderer;
realEditor = editor;
if(realEditor == null)
realEditor = createTreeCellEditor();
editingContainer = createContainer();
setTree(tree);
setBorderSelectionColor(UIManager.getColor
("Tree.editorBorderSelectionColor"));
}
/**
* Sets the color to use for the border.
* @param newColor the new border color
*/
public void setBorderSelectionColor(Color newColor) {
borderSelectionColor = newColor;
}
/**
* Returns the color the border is drawn.
* @return the border selection color
*/
public Color getBorderSelectionColor() {
return borderSelectionColor;
}
/**
* Sets the font to edit with. null
indicates
* the renderers font should be used. This will NOT
* override any font you have set in the editor
* the receiver was instantied with. If null
* for an editor was passed in a default editor will be
* created that will pick up this font.
*
* @param font the editing Font
* @see #getFont
*/
public void setFont(Font font) {
this.font = font;
}
/**
* Gets the font used for editing.
*
* @return the editing Font
* @see #setFont
*/
public Font getFont() {
return font;
}
//
// TreeCellEditor
//
/**
* Configures the editor. Passed onto the realEditor
.
*/
public Component getTreeCellEditorComponent(JTree tree, Object value,
boolean isSelected,
boolean expanded,
boolean leaf, int row) {
setTree(tree);
lastRow = row;
determineOffset(tree, value, isSelected, expanded, leaf, row);
if (editingComponent != null) {
editingContainer.remove(editingComponent);
}
editingComponent = realEditor.getTreeCellEditorComponent(tree, value,
isSelected, expanded,leaf, row);
// this is kept for backwards compatability but isn't really needed
// with the current BasicTreeUI implementation.
TreePath newPath = tree.getPathForRow(row);
canEdit = (lastPath != null && newPath != null &&
lastPath.equals(newPath));
Font font = getFont();
if(font == null) {
if(renderer != null)
font = renderer.getFont();
if(font == null)
font = tree.getFont();
}
editingContainer.setFont(font);
prepareForEditing();
return editingContainer;
}
/**
* Returns the value currently being edited.
* @return the value currently being edited
*/
public Object getCellEditorValue() {
return realEditor.getCellEditorValue();
}
/**
* If the realEditor
returns true to this
* message, prepareForEditing
* is messaged and true is returned.
*/
public boolean isCellEditable(EventObject event) {
boolean retValue = false;
boolean editable = false;
if (event != null) {
if (event.getSource() instanceof JTree) {
setTree((JTree)event.getSource());
if (event instanceof MouseEvent) {
TreePath path = tree.getPathForLocation(
((MouseEvent)event).getX(),
((MouseEvent)event).getY());
editable = (lastPath != null && path != null &&
lastPath.equals(path));
if (path!=null) {
lastRow = tree.getRowForPath(path);
Object value = path.getLastPathComponent();
boolean isSelected = tree.isRowSelected(lastRow);
boolean expanded = tree.isExpanded(path);
TreeModel treeModel = tree.getModel();
boolean leaf = treeModel.isLeaf(value);
determineOffset(tree, value, isSelected,
expanded, leaf, lastRow);
}
}
}
}
if(!realEditor.isCellEditable(event))
return false;
if(canEditImmediately(event))
retValue = true;
else if(editable && shouldStartEditingTimer(event)) {
startEditingTimer();
}
else if(timer != null && timer.isRunning())
timer.stop();
if(retValue)
prepareForEditing();
return retValue;
}
/**
* Messages the realEditor
for the return value.
*/
public boolean shouldSelectCell(EventObject event) {
return realEditor.shouldSelectCell(event);
}
/**
* If the realEditor
will allow editing to stop,
* the realEditor
is removed and true is returned,
* otherwise false is returned.
*/
public boolean stopCellEditing() {
if(realEditor.stopCellEditing()) {
cleanupAfterEditing();
return true;
}
return false;
}
/**
* Messages cancelCellEditing
to the
* realEditor
and removes it from this instance.
*/
public void cancelCellEditing() {
realEditor.cancelCellEditing();
cleanupAfterEditing();
}
/**
* Adds the CellEditorListener
.
* @param l the listener to be added
*/
public void addCellEditorListener(CellEditorListener l) {
realEditor.addCellEditorListener(l);
}
/**
* Removes the previously added CellEditorListener
.
* @param l the listener to be removed
*/
public void removeCellEditorListener(CellEditorListener l) {
realEditor.removeCellEditorListener(l);
}
/**
* Returns an array of all the CellEditorListener
s added
* to this DefaultTreeCellEditor with addCellEditorListener().
*
* @return all of the CellEditorListener
s added or an empty
* array if no listeners have been added
* @since 1.4
*/
public CellEditorListener[] getCellEditorListeners() {
return ((DefaultCellEditor)realEditor).getCellEditorListeners();
}
//
// TreeSelectionListener
//
/**
* Resets lastPath
.
*/
public void valueChanged(TreeSelectionEvent e) {
if(tree != null) {
if(tree.getSelectionCount() == 1)
lastPath = tree.getSelectionPath();
else
lastPath = null;
}
if(timer != null) {
timer.stop();
}
}
//
// ActionListener (for Timer).
//
/**
* Messaged when the timer fires, this will start the editing
* session.
*/
public void actionPerformed(ActionEvent e) {
if(tree != null && lastPath != null) {
tree.startEditingAtPath(lastPath);
}
}
//
// Local methods
//
/**
* Sets the tree currently editing for. This is needed to add
* a selection listener.
* @param newTree the new tree to be edited
*/
protected void setTree(JTree newTree) {
if(tree != newTree) {
if(tree != null)
tree.removeTreeSelectionListener(this);
tree = newTree;
if(tree != null)
tree.addTreeSelectionListener(this);
if(timer != null) {
timer.stop();
}
}
}
/**
* Returns true if event
is a MouseEvent
* and the click count is 1.
* @param event the event being studied
*/
protected boolean shouldStartEditingTimer(EventObject event) {
if((event instanceof MouseEvent) &&
SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
MouseEvent me = (MouseEvent)event;
return (me.getClickCount() == 1 &&
inHitRegion(me.getX(), me.getY()));
}
return false;
}
/**
* Starts the editing timer.
*/
protected void startEditingTimer() {
if(timer == null) {
timer = new Timer(1200, this);
timer.setRepeats(false);
}
timer.start();
}
/**
* Returns true if event
is null
,
* or it is a MouseEvent
with a click count > 2
* and inHitRegion
returns true.
* @param event the event being studied
*/
protected boolean canEditImmediately(EventObject event) {
if((event instanceof MouseEvent) &&
SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
MouseEvent me = (MouseEvent)event;
return ((me.getClickCount() > 2) &&
inHitRegion(me.getX(), me.getY()));
}
return (event == null);
}
/**
* Returns true if the passed in location is a valid mouse location
* to start editing from. This is implemented to return false if
* x
is <= the width of the icon and icon gap displayed
* by the renderer. In other words this returns true if the user
* clicks over the text part displayed by the renderer, and false
* otherwise.
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
* @return true if the passed in location is a valid mouse location
*/
protected boolean inHitRegion(int x, int y) {
if(lastRow != -1 && tree != null) {
Rectangle bounds = tree.getRowBounds(lastRow);
ComponentOrientation treeOrientation = tree.getComponentOrientation();
if ( treeOrientation.isLeftToRight() ) {
if (bounds != null && x <= (bounds.x + offset) &&
offset < (bounds.width - 5)) {
return false;
}
} else if ( bounds != null &&
( x >= (bounds.x+bounds.width-offset+5) ||
x <= (bounds.x + 5) ) &&
offset < (bounds.width - 5) ) {
return false;
}
}
return true;
}
protected void determineOffset(JTree tree, Object value,
boolean isSelected, boolean expanded,
boolean leaf, int row) {
if(renderer != null) {
if(leaf)
editingIcon = renderer.getLeafIcon();
else if(expanded)
editingIcon = renderer.getOpenIcon();
else
editingIcon = renderer.getClosedIcon();
if(editingIcon != null)
offset = renderer.getIconTextGap() +
editingIcon.getIconWidth();
else
offset = renderer.getIconTextGap();
}
else {
editingIcon = null;
offset = 0;
}
}
/**
* Invoked just before editing is to start. Will add the
* editingComponent
to the
* editingContainer
.
*/
protected void prepareForEditing() {
if (editingComponent != null) {
editingContainer.add(editingComponent);
}
}
/**
* Creates the container to manage placement of
* editingComponent
.
*/
protected Container createContainer() {
return new EditorContainer();
}
/**
* This is invoked if a TreeCellEditor
* is not supplied in the constructor.
* It returns a TextField
editor.
* @return a new TextField
editor
*/
protected TreeCellEditor createTreeCellEditor() {
Border aBorder = UIManager.getBorder("Tree.editorBorder");
DefaultCellEditor editor = new DefaultCellEditor
(new DefaultTextField(aBorder)) {
public boolean shouldSelectCell(EventObject event) {
boolean retValue = super.shouldSelectCell(event);
return retValue;
}
};
// One click to edit.
editor.setClickCountToStart(1);
return editor;
}
/**
* Cleans up any state after editing has completed. Removes the
* editingComponent
the editingContainer
.
*/
private void cleanupAfterEditing() {
if (editingComponent != null) {
editingContainer.remove(editingComponent);
}
editingComponent = null;
}
// Serialization support.
private void writeObject(ObjectOutputStream s) throws IOException {
Vector