001 /* 002 * $Id: TreeTableCellEditor.java,v 1.11 2006/05/14 08:19:46 dmouse Exp $ 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 022 package org.jdesktop.swingx.treetable; 023 024 import java.awt.Component; 025 import java.awt.Rectangle; 026 import java.awt.event.MouseEvent; 027 import java.util.EventObject; 028 029 import javax.swing.DefaultCellEditor; 030 import javax.swing.Icon; 031 import javax.swing.JLabel; 032 import javax.swing.JTable; 033 import javax.swing.JTextField; 034 import javax.swing.JTree; 035 import javax.swing.tree.TreeCellRenderer; 036 037 /** 038 * An editor that can be used to edit the tree column. This extends 039 * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField) 040 * to perform the actual editing. 041 * <p>To support editing of the tree column we can not make the tree 042 * editable. The reason this doesn't work is that you can not use 043 * the same component for editing and renderering. The table may have 044 * the need to paint cells, while a cell is being edited. If the same 045 * component were used for the rendering and editing the component would 046 * be moved around, and the contents would change. When editing, this 047 * is undesirable, the contents of the text field must stay the same, 048 * including the caret blinking, and selections persisting. For this 049 * reason the editing is done via a TableCellEditor. 050 * <p>Another interesting thing to be aware of is how tree positions 051 * its render and editor. The render/editor is responsible for drawing the 052 * icon indicating the type of node (leaf, branch...). The tree is 053 * responsible for drawing any other indicators, perhaps an additional 054 * +/- sign, or lines connecting the various nodes. So, the renderer 055 * is positioned based on depth. On the other hand, table always makes 056 * its editor fill the contents of the cell. To get the allusion 057 * that the table cell editor is part of the tree, we don't want the 058 * table cell editor to fill the cell bounds. We want it to be placed 059 * in the same manner as tree places it editor, and have table message 060 * the tree to paint any decorations the tree wants. Then, we would 061 * only have to worry about the editing part. The approach taken 062 * here is to determine where tree would place the editor, and to override 063 * the <code>reshape</code> method in the JTextField component to 064 * nudge the textfield to the location tree would place it. Since 065 * JXTreeTable will paint the tree behind the editor everything should 066 * just work. So, that is what we are doing here. Determining of 067 * the icon position will only work if the TreeCellRenderer is 068 * an instance of DefaultTreeCellRenderer. If you need custom 069 * TreeCellRenderers, that don't descend from DefaultTreeCellRenderer, 070 * and you want to support editing in JXTreeTable, you will have 071 * to do something similiar. 072 * 073 * @author Scott Violet 074 * @author Ramesh Gupta 075 */ 076 public class TreeTableCellEditor extends DefaultCellEditor { 077 public TreeTableCellEditor(JTree tree) { 078 super(new TreeTableTextField()); 079 if (tree == null) { 080 throw new IllegalArgumentException("null tree"); 081 } 082 // JW: no need to... 083 this.tree = tree; // immutable 084 } 085 086 /** 087 * Overriden to determine an offset that tree would place the editor at. The 088 * offset is determined from the <code>getRowBounds</code> JTree method, 089 * and additionaly from the icon DefaultTreeCellRenderer will use. 090 * <p> 091 * The offset is then set on the TreeTableTextField component created in the 092 * constructor, and returned. 093 */ 094 @Override 095 public Component getTableCellEditorComponent(JTable table, Object value, 096 boolean isSelected, int row, int column) { 097 Component component = super.getTableCellEditorComponent(table, value, 098 isSelected, row, column); 099 // JW: this implementation is not bidi-compliant, need to do better 100 initEditorOffset(table, row, column, isSelected); 101 return component; 102 } 103 104 /** 105 * @param row 106 * @param isSelected 107 */ 108 protected void initEditorOffset(JTable table, int row, int column, boolean isSelected) { 109 if (tree == null) return; 110 Rectangle bounds = tree.getRowBounds(row); 111 int offset = bounds.x; 112 Object node = tree.getPathForRow(row).getLastPathComponent(); 113 boolean leaf = tree.getModel().isLeaf(node); 114 boolean expanded = tree.isExpanded(row); 115 TreeCellRenderer tcr = tree.getCellRenderer(); 116 Component treeComponent = tcr.getTreeCellRendererComponent(tree, node, 117 isSelected, expanded, leaf, row, false); 118 if ((treeComponent instanceof JLabel) 119 // adjust the offset to account for the icon - at least 120 // in LToR orientation. RToL is hard to tackle anyway... 121 && table.getComponentOrientation().isLeftToRight()) { 122 JLabel label = (JLabel) treeComponent; 123 124 Icon icon = label.getIcon(); 125 offset += icon.getIconWidth() + label.getIconTextGap(); 126 } 127 ((TreeTableTextField) getComponent()).init(offset, column, bounds.width, table); 128 } 129 130 /** 131 * This is overriden to forward the event to the tree. This will 132 * return true if the click count >= clickCountToStart, or the event is null. 133 */ 134 @Override 135 public boolean isCellEditable(EventObject e) { 136 if (e == null) { 137 return true; 138 } 139 else if (e instanceof MouseEvent) { 140 return (((MouseEvent) e).getClickCount() >= clickCountToStart); 141 } 142 143 // e is some other type of event... 144 return false; 145 } 146 147 /** 148 * Component used by TreeTableCellEditor. The only thing this does 149 * is to override the <code>reshape</code> method, and to ALWAYS 150 * make the x location be <code>offset</code>. 151 */ 152 static class TreeTableTextField extends JTextField { 153 void init(int offset, int column, int width, JTable table) { 154 this.offset = offset; 155 this.column = column; 156 this.width = width; 157 this.table = table; 158 setComponentOrientation(table.getComponentOrientation()); 159 } 160 161 private int offset; // changed to package private instead of public 162 private int column; 163 private int width; 164 private JTable table; 165 @Override 166 public void reshape(int x, int y, int width, int height) { 167 // Allows precise positioning of text field in the tree cell. 168 //Border border = this.getBorder(); // get this text field's border 169 //Insets insets = border == null ? null : border.getBorderInsets(this); 170 //int newOffset = offset - (insets == null ? 0 : insets.left); 171 if(table.getComponentOrientation().isLeftToRight()) { 172 int newOffset = offset - getInsets().left; 173 // this is LtR version 174 super.reshape(x + newOffset, y, width - newOffset, height); 175 } else { 176 // right to left version 177 int newOffset = offset + getInsets().left; 178 int pos = getColumnPositionBidi(); 179 width = table.getColumnModel().getColumn(getBidiTreeColumn()).getWidth(); 180 width = width - (width - newOffset - this.width); 181 super.reshape(pos, y, width, height); 182 } 183 } 184 185 /** 186 * Returns the column for the tree in a bidi situation 187 */ 188 private int getBidiTreeColumn() { 189 // invert the column offet since this method will always be invoked 190 // in a bidi situation 191 return table.getColumnCount() - this.column - 1; 192 } 193 194 private int getColumnPositionBidi() { 195 int width = 0; 196 197 int column = getBidiTreeColumn(); 198 for(int iter = 0 ; iter < column ; iter++) { 199 width += table.getColumnModel().getColumn(iter).getWidth(); 200 } 201 return width; 202 } 203 } 204 205 private final JTree tree; // immutable 206 }