/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ package de.cismet.tools.gui.treetable; /* * $Id: TreeTableCellEditor.java,v 1.1.1.1 2009-08-14 11:22:08 spuhl Exp $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. */ import java.awt.Component; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.util.EventObject; import javax.swing.DefaultCellEditor; import javax.swing.Icon; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeCellRenderer; /** * An editor that can be used to edit the tree column. This extends DefaultCellEditor and uses a JTextField (actually, * TreeTableTextField) to perform the actual editing. * * <p>To support editing of the tree column we can not make the tree editable. The reason this doesn't work is that you * can not use the same component for editing and renderering. The table may have the need to paint cells, while a cell * is being edited. If the same component were used for the rendering and editing the component would be moved around, * and the contents would change. When editing, this is undesirable, the contents of the text field must stay the same, * including the caret blinking, and selections persisting. For this reason the editing is done via a TableCellEditor. * </p> * * <p>Another interesting thing to be aware of is how tree positions its render and editor. The render/editor is * responsible for drawing the icon indicating the type of node (leaf, branch...). The tree is responsible for drawing * any other indicators, perhaps an additional +/- sign, or lines connecting the various nodes. So, the renderer is * positioned based on depth. On the other hand, table always makes its editor fill the contents of the cell. To get the * allusion that the table cell editor is part of the tree, we don't want the table cell editor to fill the cell bounds. * We want it to be placed in the same manner as tree places it editor, and have table message the tree to paint any * decorations the tree wants. Then, we would only have to worry about the editing part. The approach taken here is to * determine where tree would place the editor, and to override the <code>reshape</code> method in the JTextField * component to nudge the textfield to the location tree would place it. Since JXTreeTable will paint the tree behind * the editor everything should just work. So, that is what we are doing here. Determining of the icon position will * only work if the TreeCellRenderer is an instance of DefaultTreeCellRenderer. If you need custom TreeCellRenderers, * that don't descend from DefaultTreeCellRenderer, and you want to support editing in JXTreeTable, you will have to do * something similiar.</p> * * @author Scott Violet * @author Ramesh Gupta * @version $Revision$, $Date$ */ public class TreeTableCellEditor extends DefaultCellEditor { //~ Instance fields -------------------------------------------------------- private final JTreeTable treeTable; // immutable private final JTree tree; // immutable //~ Constructors ----------------------------------------------------------- /** * Creates a new TreeTableCellEditor object. * * @param treeTable DOCUMENT ME! * @param tree DOCUMENT ME! * * @throws IllegalArgumentException DOCUMENT ME! */ public TreeTableCellEditor(final JTreeTable treeTable, final JTree tree) { super(new TreeTableTextField()); if (treeTable == null) { throw new IllegalArgumentException("null treeTable"); // NOI18N } if (tree == null) { throw new IllegalArgumentException("null tree"); // NOI18N } this.treeTable = treeTable; // immutable this.tree = tree; // immutable } //~ Methods ---------------------------------------------------------------- /** * Overriden to determine an offset that tree would place the editor at. The offset is determined from the <code> * getRowBounds</code> JTree method, and additionaly from the icon DefaultTreeCellRenderer will use. * * <p>The offset is then set on the TreeTableTextField component created in the constructor, and returned.</p> * * @param table DOCUMENT ME! * @param value DOCUMENT ME! * @param isSelected DOCUMENT ME! * @param row DOCUMENT ME! * @param column DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public Component getTableCellEditorComponent(final JTable table, final Object value, final boolean isSelected, final int row, final int column) { final Component component = super.getTableCellEditorComponent(table, value, isSelected, row, column); // boolean isRootVisible = tree.isRootVisible(); final Rectangle bounds = tree.getRowBounds(row); int offset = bounds.x; final TreeCellRenderer tcr = tree.getCellRenderer(); if (tcr instanceof DefaultTreeCellRenderer) { final Object node = tree.getPathForRow(row).getLastPathComponent(); Icon icon; if (tree.getModel().isLeaf(node)) { icon = ((DefaultTreeCellRenderer)tcr).getLeafIcon(); } else if (tree.isExpanded(row)) { icon = ((DefaultTreeCellRenderer)tcr).getOpenIcon(); } else { icon = ((DefaultTreeCellRenderer)tcr).getClosedIcon(); } if (icon != null) { offset += ((DefaultTreeCellRenderer)tcr).getIconTextGap() + icon.getIconWidth(); } } ((TreeTableTextField)getComponent()).offset = offset; // ((TreeTableTextField) getComponent()).selectAll(); return component; } /** * This is overriden to forward the event to the tree. This will return true if the click count >= * clickCountToStart, or the event is null. * * @param e DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public boolean isCellEditable(final EventObject e) { if (e == null) { return true; } else if (e instanceof MouseEvent) { for (int counter = treeTable.getColumnCount() - 1; counter >= 0; counter--) { if (treeTable.getColumnClass(counter) == TreeTableModel.class) { if (((MouseEvent)e).getClickCount() >= clickCountToStart) { return true; } else { final MouseEvent me = (MouseEvent)e; final MouseEvent newME = new MouseEvent( tree, me.getID(), me.getWhen(), me.getModifiers(), me.getX() - treeTable.getCellRect(0, counter, true).x, me.getY(), me.getClickCount(), me.isPopupTrigger()); tree.dispatchEvent(newME); return false; } } } // RG: Fix Issue 49 -- Move cell expansion/collapse logic to // JXTreeTable.editCellAt(); return (((MouseEvent)e).getClickCount() >= clickCountToStart); } // e is some other type of event... return false; } //~ Inner Classes ---------------------------------------------------------- /** * Component used by TreeTableCellEditor. The only thing this does is to override the <code>reshape</code> method, * and to ALWAYS make the x location be <code>offset</code>. * * @version $Revision$, $Date$ */ static class TreeTableTextField extends JTextField { //~ Instance fields ---------------------------------------------------- int offset; // changed to package private instead of public //~ Methods ------------------------------------------------------------ @Override public void reshape(final int x, final int y, final int width, final int height) { // Allows precise positioning of text field in the tree cell. // Border border = this.getBorder(); // get this text field's border // Insets insets = border == null ? null : border.getBorderInsets(this); // int newOffset = offset - (insets == null ? 0 : insets.left); final int newOffset = offset - getInsets().left; super.reshape(x + newOffset, y, width - newOffset, height); } } }