| /* DefaultTreeCellEditor.java -- | |
| Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath 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 for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package javax.swing.tree; | |
| import java.awt.Color; | |
| import java.awt.Component; | |
| import java.awt.Container; | |
| import java.awt.Dimension; | |
| import java.awt.Font; | |
| import java.awt.Graphics; | |
| import java.awt.Rectangle; | |
| import java.awt.event.ActionEvent; | |
| import java.awt.event.ActionListener; | |
| import java.awt.event.MouseEvent; | |
| import java.io.IOException; | |
| import java.io.ObjectInputStream; | |
| import java.io.ObjectOutputStream; | |
| import java.util.EventObject; | |
| import javax.swing.DefaultCellEditor; | |
| import javax.swing.Icon; | |
| import javax.swing.JTextField; | |
| import javax.swing.JTree; | |
| import javax.swing.SwingUtilities; | |
| import javax.swing.Timer; | |
| import javax.swing.UIManager; | |
| import javax.swing.border.Border; | |
| import javax.swing.event.CellEditorListener; | |
| import javax.swing.event.EventListenerList; | |
| import javax.swing.event.TreeSelectionEvent; | |
| import javax.swing.event.TreeSelectionListener; | |
| /** | |
| * Participates in the tree cell editing. | |
| * | |
| * @author Andrew Selkirk | |
| * @author Audrius Meskauskas | |
| */ | |
| public class DefaultTreeCellEditor | |
| implements ActionListener, TreeCellEditor, TreeSelectionListener | |
| { | |
| /** | |
| * This container that appears on the tree during editing session. | |
| * It contains the editing component displays various other editor - | |
| * specific parts like editing icon. | |
| */ | |
| public class EditorContainer extends Container | |
| { | |
| /** | |
| * Use v 1.5 serial version UID for interoperability. | |
| */ | |
| static final long serialVersionUID = 6470339600449699810L; | |
| /** | |
| * Creates an <code>EditorContainer</code> object. | |
| */ | |
| public EditorContainer() | |
| { | |
| setLayout(null); | |
| } | |
| /** | |
| * This method only exists for API compatibility and is useless as it does | |
| * nothing. It got probably introduced by accident. | |
| */ | |
| public void EditorContainer() | |
| { | |
| // Do nothing here. | |
| } | |
| /** | |
| * Overrides Container.paint to paint the node's icon and use the selection | |
| * color for the background. | |
| * | |
| * @param g - | |
| * the specified Graphics window | |
| */ | |
| public void paint(Graphics g) | |
| { | |
| // Paint editing icon. | |
| if (editingIcon != null) | |
| { | |
| // From the previous version, the left margin is taken as half | |
| // of the icon width. | |
| int y = Math.max(0, (getHeight() - editingIcon.getIconHeight()) / 2); | |
| editingIcon.paintIcon(this, g, 0, y); | |
| } | |
| // Paint border. | |
| Color c = getBorderSelectionColor(); | |
| if (c != null) | |
| { | |
| g.setColor(c); | |
| g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); | |
| } | |
| super.paint(g); | |
| } | |
| /** | |
| * Lays out this Container, moving the editor component to the left | |
| * (leaving place for the icon). | |
| */ | |
| public void doLayout() | |
| { | |
| if (editingComponent != null) | |
| { | |
| editingComponent.getPreferredSize(); | |
| editingComponent.setBounds(offset, 0, getWidth() - offset, | |
| getHeight()); | |
| } | |
| } | |
| public Dimension getPreferredSize() | |
| { | |
| Dimension dim; | |
| if (editingComponent != null) | |
| { | |
| dim = editingComponent.getPreferredSize(); | |
| dim.width += offset + 5; | |
| if (renderer != null) | |
| { | |
| Dimension r = renderer.getPreferredSize(); | |
| dim.height = Math.max(dim.height, r.height); | |
| } | |
| if (editingIcon != null) | |
| dim.height = Math.max(dim.height, editingIcon.getIconHeight()); | |
| dim.width = Math.max(100, dim.width); | |
| } | |
| else | |
| dim = new Dimension(0, 0); | |
| return dim; | |
| } | |
| } | |
| /** | |
| * The default text field, used in the editing sessions. | |
| */ | |
| public class DefaultTextField extends JTextField | |
| { | |
| /** | |
| * Use v 1.5 serial version UID for interoperability. | |
| */ | |
| static final long serialVersionUID = -6629304544265300143L; | |
| /** | |
| * The border of the text field. | |
| */ | |
| protected Border border; | |
| /** | |
| * Creates a <code>DefaultTextField</code> object. | |
| * | |
| * @param aBorder the border to use | |
| */ | |
| public DefaultTextField(Border aBorder) | |
| { | |
| border = aBorder; | |
| } | |
| /** | |
| * Gets the font of this component. | |
| * @return this component's font; if a font has not been set for | |
| * this component, the font of its parent is returned (if the parent | |
| * is not null, otherwise null is returned). | |
| */ | |
| public Font getFont() | |
| { | |
| Font font = super.getFont(); | |
| if (font == null) | |
| { | |
| Component parent = getParent(); | |
| if (parent != null) | |
| return parent.getFont(); | |
| return null; | |
| } | |
| return font; | |
| } | |
| /** | |
| * Returns the border of the text field. | |
| * | |
| * @return the border | |
| */ | |
| public Border getBorder() | |
| { | |
| return border; | |
| } | |
| /** | |
| * Overrides JTextField.getPreferredSize to return the preferred size | |
| * based on current font, if set, or else use renderer's font. | |
| * | |
| * @return the Dimension of this textfield. | |
| */ | |
| public Dimension getPreferredSize() | |
| { | |
| Dimension size = super.getPreferredSize(); | |
| if (renderer != null && DefaultTreeCellEditor.this.getFont() == null) | |
| { | |
| size.height = renderer.getPreferredSize().height; | |
| } | |
| return renderer.getPreferredSize(); | |
| } | |
| } | |
| private EventListenerList listenerList = new EventListenerList(); | |
| /** | |
| * 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. | |
| */ | |
| protected transient 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 javax.swing.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); | |
| Color c = UIManager.getColor("Tree.editorBorderSelectionColor"); | |
| setBorderSelectionColor(c); | |
| } | |
| /** | |
| * writeObject | |
| * | |
| * @param value0 | |
| * TODO | |
| * @exception IOException | |
| * TODO | |
| */ | |
| private void writeObject(ObjectOutputStream value0) throws IOException | |
| { | |
| // TODO | |
| } | |
| /** | |
| * readObject | |
| * @param value0 TODO | |
| * @exception IOException TODO | |
| * @exception ClassNotFoundException TODO | |
| */ | |
| private void readObject(ObjectInputStream value0) | |
| throws IOException, ClassNotFoundException | |
| { | |
| // TODO | |
| } | |
| /** | |
| * Sets the color to use for the border. | |
| * @param newColor - the new border color | |
| */ | |
| public void setBorderSelectionColor(Color newColor) | |
| { | |
| this.borderSelectionColor = newColor; | |
| } | |
| /** | |
| * Returns the color the border is drawn. | |
| * @return 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 | |
| */ | |
| public void setFont(Font font) | |
| { | |
| if (font != null) | |
| this.font = font; | |
| else | |
| this.font = renderer.getFont(); | |
| } | |
| /** | |
| * Gets the font used for editing. | |
| * | |
| * @return the editing font | |
| */ | |
| public Font getFont() | |
| { | |
| return font; | |
| } | |
| /** | |
| * Configures the editor. Passed onto the realEditor. | |
| * Sets an initial value for the editor. This will cause | |
| * the editor to stopEditing and lose any partially edited value | |
| * if the editor is editing when this method is called. | |
| * Returns the component that should be added to the client's Component | |
| * hierarchy. Once installed in the client's hierarchy this component will | |
| * then be able to draw and receive user input. | |
| * | |
| * @param tree - the JTree that is asking the editor to edit; this parameter can be null | |
| * @param value - the value of the cell to be edited | |
| * @param isSelected - true is the cell is to be rendered with selection highlighting | |
| * @param expanded - true if the node is expanded | |
| * @param leaf - true if the node is a leaf node | |
| * @param row - the row index of the node being edited | |
| * | |
| * @return the component for editing | |
| */ | |
| 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); | |
| Font f = getFont(); | |
| if (f == null) | |
| { | |
| if (renderer != null) | |
| f = renderer.getFont(); | |
| if (f == null) | |
| f = tree.getFont(); | |
| } | |
| editingContainer.setFont(f); | |
| prepareForEditing(); | |
| return editingContainer; | |
| } | |
| /** | |
| * Returns the value currently being edited (requests it from the | |
| * {@link #realEditor}. | |
| * | |
| * @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. | |
| * | |
| * @param event - the event the editor should use to consider whether to | |
| * begin editing or not | |
| * @return true if editing can be started | |
| */ | |
| public boolean isCellEditable(EventObject event) | |
| { | |
| boolean ret = false; | |
| boolean ed = false; | |
| if (event != null) | |
| { | |
| if (event.getSource() instanceof JTree) | |
| { | |
| setTree((JTree) event.getSource()); | |
| if (event instanceof MouseEvent) | |
| { | |
| MouseEvent me = (MouseEvent) event; | |
| TreePath path = tree.getPathForLocation(me.getX(), me.getY()); | |
| ed = lastPath != null && path != null && lastPath.equals(path); | |
| if (path != null) | |
| { | |
| lastRow = tree.getRowForPath(path); | |
| Object val = path.getLastPathComponent(); | |
| boolean isSelected = tree.isRowSelected(lastRow); | |
| boolean isExpanded = tree.isExpanded(path); | |
| TreeModel m = tree.getModel(); | |
| boolean isLeaf = m.isLeaf(val); | |
| determineOffset(tree, val, isSelected, isExpanded, isLeaf, | |
| lastRow); | |
| } | |
| } | |
| } | |
| } | |
| if (! realEditor.isCellEditable(event)) | |
| ret = false; | |
| else | |
| { | |
| if (canEditImmediately(event)) | |
| ret = true; | |
| else if (ed && shouldStartEditingTimer(event)) | |
| startEditingTimer(); | |
| else if (timer != null && timer.isRunning()) | |
| timer.stop(); | |
| } | |
| if (ret) | |
| prepareForEditing(); | |
| return ret; | |
| } | |
| /** | |
| * Messages the realEditor for the return value. | |
| * | |
| * @param event - | |
| * the event the editor should use to start editing | |
| * @return true if the editor would like the editing cell to be selected; | |
| * otherwise returns false | |
| */ | |
| public boolean shouldSelectCell(EventObject event) | |
| { | |
| return true; | |
| } | |
| /** | |
| * If the realEditor will allow editing to stop, the realEditor | |
| * is removed and true is returned, otherwise false is returned. | |
| * @return true if editing was stopped; false otherwise | |
| */ | |
| public boolean stopCellEditing() | |
| { | |
| boolean ret = false; | |
| if (realEditor.stopCellEditing()) | |
| { | |
| finish(); | |
| ret = true; | |
| } | |
| return ret; | |
| } | |
| /** | |
| * Messages cancelCellEditing to the realEditor and removes it | |
| * from this instance. | |
| */ | |
| public void cancelCellEditing() | |
| { | |
| realEditor.cancelCellEditing(); | |
| finish(); | |
| } | |
| private void finish() | |
| { | |
| if (editingComponent != null) | |
| editingContainer.remove(editingComponent); | |
| editingComponent = null; | |
| } | |
| /** | |
| * Adds a <code>CellEditorListener</code> object to this editor. | |
| * | |
| * @param listener | |
| * the listener to add | |
| */ | |
| public void addCellEditorListener(CellEditorListener listener) | |
| { | |
| realEditor.addCellEditorListener(listener); | |
| } | |
| /** | |
| * Removes a <code>CellEditorListener</code> object. | |
| * | |
| * @param listener the listener to remove | |
| */ | |
| public void removeCellEditorListener(CellEditorListener listener) | |
| { | |
| realEditor.removeCellEditorListener(listener); | |
| } | |
| /** | |
| * Returns all added <code>CellEditorListener</code> objects to this editor. | |
| * | |
| * @return an array of listeners | |
| * | |
| * @since 1.4 | |
| */ | |
| public CellEditorListener[] getCellEditorListeners() | |
| { | |
| return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class); | |
| } | |
| /** | |
| * Resets lastPath. | |
| * | |
| * @param e - the event that characterizes the change. | |
| */ | |
| public void valueChanged(TreeSelectionEvent e) | |
| { | |
| if (tree != null) | |
| { | |
| if (tree.getSelectionCount() == 1) | |
| lastPath = tree.getSelectionPath(); | |
| else | |
| lastPath = null; | |
| } | |
| // TODO: We really should do the following here, but can't due | |
| // to buggy DefaultTreeSelectionModel. This selection model | |
| // should only fire if the selection actually changes. | |
| // if (timer != null) | |
| // timer.stop(); | |
| } | |
| /** | |
| * Messaged when the timer fires. | |
| * | |
| * @param e the event that characterizes the action. | |
| */ | |
| public void actionPerformed(ActionEvent e) | |
| { | |
| if (tree != null && lastPath != null) | |
| tree.startEditingAtPath(lastPath); | |
| } | |
| /** | |
| * 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 | |
| * @return true if editing should start | |
| */ | |
| protected boolean shouldStartEditingTimer(EventObject event) | |
| { | |
| boolean ret = false; | |
| if (event instanceof MouseEvent) | |
| { | |
| MouseEvent me = (MouseEvent) event; | |
| ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1 | |
| && inHitRegion(me.getX(), me.getY()); | |
| } | |
| return ret; | |
| } | |
| /** | |
| * Starts the editing timer (if one installed). | |
| */ | |
| 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 | |
| * @return true if event is null, or it is a MouseEvent with | |
| * a click count > 2 and inHitRegion returns true | |
| */ | |
| protected boolean canEditImmediately(EventObject event) | |
| { | |
| if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event). | |
| getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(), | |
| ((MouseEvent) event).getY()))) | |
| return true; | |
| return false; | |
| } | |
| /** | |
| * 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 | |
| * less than or equal to 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) | |
| { | |
| Rectangle bounds = tree.getPathBounds(lastPath); | |
| return bounds.contains(x, y); | |
| } | |
| /** | |
| * determineOffset | |
| * @param tree - | |
| * @param value - | |
| * @param isSelected - | |
| * @param expanded - | |
| * @param leaf - | |
| * @param row - | |
| */ | |
| 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. | |
| * | |
| * @return the container to manage the placement of the editingComponent. | |
| */ | |
| protected Container createContainer() | |
| { | |
| return new DefaultTreeCellEditor.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 border = UIManager.getBorder("Tree.editorBorder"); | |
| JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border); | |
| DefaultCellEditor editor = new DefaultCellEditor(tf); | |
| editor.setClickCountToStart(1); | |
| realEditor = editor; | |
| return editor; | |
| } | |
| } |