001/* 002 * $Id: TreeTableCellEditor.java 3990 2011-03-31 13:41:08Z kleopatra $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 022package org.jdesktop.swingx.treetable; 023 024import java.awt.Component; 025import java.awt.Rectangle; 026import java.awt.event.MouseEvent; 027import java.util.EventObject; 028import java.util.logging.Logger; 029 030import javax.swing.Icon; 031import javax.swing.JLabel; 032import javax.swing.JTable; 033import javax.swing.JTextField; 034import javax.swing.JTree; 035import javax.swing.tree.TreeCellRenderer; 036 037import org.jdesktop.swingx.JXTable.GenericEditor; 038 039/** 040 * An editor that can be used to edit the tree column. This extends 041 * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField) 042 * to perform the actual editing. 043 * <p>To support editing of the tree column we can not make the tree 044 * editable. The reason this doesn't work is that you can not use 045 * the same component for editing and rendering. The table may have 046 * the need to paint cells, while a cell is being edited. If the same 047 * component were used for the rendering and editing the component would 048 * be moved around, and the contents would change. When editing, this 049 * is undesirable, the contents of the text field must stay the same, 050 * including the caret blinking, and selections persisting. For this 051 * reason the editing is done via a TableCellEditor. 052 * <p>Another interesting thing to be aware of is how tree positions 053 * its render and editor. The render/editor is responsible for drawing the 054 * icon indicating the type of node (leaf, branch...). The tree is 055 * responsible for drawing any other indicators, perhaps an additional 056 * +/- sign, or lines connecting the various nodes. So, the renderer 057 * is positioned based on depth. On the other hand, table always makes 058 * its editor fill the contents of the cell. To get the allusion 059 * that the table cell editor is part of the tree, we don't want the 060 * table cell editor to fill the cell bounds. We want it to be placed 061 * in the same manner as tree places it editor, and have table message 062 * the tree to paint any decorations the tree wants. Then, we would 063 * only have to worry about the editing part. The approach taken 064 * here is to determine where tree would place the editor, and to override 065 * the <code>reshape</code> method in the JTextField component to 066 * nudge the textfield to the location tree would place it. Since 067 * JXTreeTable will paint the tree behind the editor everything should 068 * just work. So, that is what we are doing here. Determining of 069 * the icon position will only work if the TreeCellRenderer is 070 * an instance of DefaultTreeCellRenderer. If you need custom 071 * TreeCellRenderers, that don't descend from DefaultTreeCellRenderer, 072 * and you want to support editing in JXTreeTable, you will have 073 * to do something similar. 074 * 075 * @author Scott Violet 076 * @author Ramesh Gupta 077 */ 078public class TreeTableCellEditor extends GenericEditor { 079 //DefaultCellEditor { 080// JW: changed to extends GenericEditor to fix #1365-swingx - 081// borders different in hierarchical column vs. table column 082// 083 @SuppressWarnings("unused") 084 private static final Logger LOG = Logger 085 .getLogger(TreeTableCellEditor.class.getName()); 086 087 public TreeTableCellEditor(JTree tree) { 088 super(new TreeTableTextField()); 089 if (tree == null) { 090 throw new IllegalArgumentException("null tree"); 091 } 092 // JW: no need to... 093 this.tree = tree; // immutable 094 } 095 096 /** 097 * Overriden to determine an offset that tree would place the editor at. The 098 * offset is determined from the <code>getRowBounds</code> JTree method, 099 * and additionaly from the icon DefaultTreeCellRenderer will use. 100 * <p> 101 * The offset is then set on the TreeTableTextField component created in the 102 * constructor, and returned. 103 */ 104 @Override 105 public Component getTableCellEditorComponent(JTable table, Object value, 106 boolean isSelected, int row, int column) { 107 Component component = super.getTableCellEditorComponent(table, value, 108 isSelected, row, column); 109 // JW: this implementation is not bidi-compliant, need to do better 110 initEditorOffset(table, row, column, isSelected); 111 return component; 112 } 113 114 115 /** 116 * @param row 117 * @param isSelected 118 */ 119 protected void initEditorOffset(JTable table, int row, int column, 120 boolean isSelected) { 121 if (tree == null) 122 return; 123// Rectangle bounds = tree.getRowBounds(row); 124// int offset = bounds.x; 125 Object node = tree.getPathForRow(row).getLastPathComponent(); 126 boolean leaf = tree.getModel().isLeaf(node); 127 boolean expanded = tree.isExpanded(row); 128 TreeCellRenderer tcr = tree.getCellRenderer(); 129 Component editorComponent = tcr.getTreeCellRendererComponent(tree, node, 130 isSelected, expanded, leaf, row, false); 131 132 ((TreeTableTextField) getComponent()).init(row, 133 column, table, tree, editorComponent); 134 } 135 136 /** 137 * This is overriden to forward the event to the tree. This will 138 * return true if the click count >= clickCountToStart, or the event is null. 139 */ 140 @Override 141 public boolean isCellEditable(EventObject e) { 142 // JW: quick fix for #592-swingx - 143 // editing not started on keyEvent in hierarchical column (1.6) 144 if (e instanceof MouseEvent) { 145 return (((MouseEvent) e).getClickCount() >= clickCountToStart); 146 } 147 return true; 148 } 149 150 /** 151 * Component used by TreeTableCellEditor. The only thing this does 152 * is to override the <code>reshape</code> method, and to ALWAYS 153 * make the x location be <code>offset</code>. 154 */ 155 static class TreeTableTextField extends JTextField { 156 private int iconWidth; 157 158 void init(int row, int column, JTable table, JTree tree, Component editorComponent) { 159 this.column = column; 160 this.row = row; 161 this.table = table; 162 this.tree = tree; 163 updateIconWidth(editorComponent); 164 setComponentOrientation(table.getComponentOrientation()); 165 } 166 167 /** 168 * @param treeComponent 169 */ 170 private void updateIconWidth(Component treeComponent) { 171 iconWidth = 0; 172 if (!(treeComponent instanceof JLabel)) return; 173 Icon icon = ((JLabel) treeComponent).getIcon(); 174 if (icon != null) { 175 iconWidth = icon.getIconWidth() + ((JLabel) treeComponent).getIconTextGap(); 176 } 177 178 } 179 180 private int column; 181 private int row; 182 private JTable table; 183 private JTree tree; 184 185 /** 186 * {@inheritDoc} <p> 187 * 188 * Overridden to place the textfield in the node content boundaries, 189 * leaving the icon to the renderer. <p> 190 * 191 * PENDING JW: insets? 192 * 193 */ 194 @SuppressWarnings("deprecation") 195 @Override 196 public void reshape(int x, int y, int width, int height) { 197 // Allows precise positioning of text field in the tree cell. 198 // following three lines didn't work out 199 //Border border = this.getBorder(); // get this text field's border 200 //Insets insets = border == null ? null : border.getBorderInsets(this); 201 //int newOffset = offset - (insets == null ? 0 : insets.left); 202 203 Rectangle cellRect = table.getCellRect(0, column, false); 204 Rectangle nodeRect = tree.getRowBounds(row); 205 nodeRect.width -= iconWidth; 206 if(table.getComponentOrientation().isLeftToRight()) { 207 int nodeStart = cellRect.x + nodeRect.x + iconWidth; 208 int nodeEnd = cellRect.x + cellRect.width; 209 super.reshape(nodeStart, y, nodeEnd - nodeStart, height); 210// int newOffset = nodeLeftX - getInsets().left; 211// super.reshape(x + newOffset, y, width - newOffset, height); 212 } else { 213 int nodeRightX = nodeRect.x + nodeRect.width; 214 nodeRect.x = 0; //Math.max(0, nodeRect.x); 215 // ignore the parameter 216 width = nodeRightX - nodeRect.x; 217 super.reshape(cellRect.x + nodeRect.x, y, width, height); 218 } 219 } 220 221 } 222 223 private final JTree tree; // immutable 224}