001 /*
002 * $Id: TreeTableCellEditor.java 3183 2009-01-19 12:01:06Z 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
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 rendering. 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 similar.
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,
109 boolean isSelected) {
110 if (tree == null)
111 return;
112 Rectangle bounds = tree.getRowBounds(row);
113 int offset = bounds.x;
114 Object node = tree.getPathForRow(row).getLastPathComponent();
115 boolean leaf = tree.getModel().isLeaf(node);
116 boolean expanded = tree.isExpanded(row);
117 TreeCellRenderer tcr = tree.getCellRenderer();
118 Component treeComponent = tcr.getTreeCellRendererComponent(tree, node,
119 isSelected, expanded, leaf, row, false);
120 // start patch from armond
121 // int boundsWidth = bounds.width;
122 // if (treeComponent instanceof JLabel) {
123 // JLabel label = (JLabel) treeComponent;
124 //
125 // Icon icon = label.getIcon();
126 // if (icon != null) {
127 // if( table.getComponentOrientation().isLeftToRight())
128 // offset += icon.getIconWidth() + label.getIconTextGap();
129 //
130 // boundsWidth -= icon.getIconWidth();
131 // }
132 // }
133 // ((TreeTableTextField) getComponent()).init(offset, column, boundsWidth, table);
134
135 // start old version
136 if ((treeComponent instanceof JLabel)
137 // adjust the offset to account for the icon - at least
138 // in LToR orientation. RToL is hard to tackle anyway...
139 && table.getComponentOrientation().isLeftToRight()) {
140 JLabel label = (JLabel) treeComponent;
141
142 Icon icon = label.getIcon();
143 if (icon != null) {
144 offset += icon.getIconWidth() + label.getIconTextGap();
145 }
146
147 }
148 ((TreeTableTextField) getComponent()).init(offset, column,
149 bounds.width, table);
150 }
151
152 /**
153 * This is overriden to forward the event to the tree. This will
154 * return true if the click count >= clickCountToStart, or the event is null.
155 */
156 @Override
157 public boolean isCellEditable(EventObject e) {
158 // JW: quick fix for #592-swingx -
159 // editing not started on keyEvent in hierarchical column (1.6)
160 if (e instanceof MouseEvent) {
161 return (((MouseEvent) e).getClickCount() >= clickCountToStart);
162 }
163 return true;
164 // if (e == null) {
165 // return true;
166 // }
167 // else if (e instanceof MouseEvent) {
168 // return (((MouseEvent) e).getClickCount() >= clickCountToStart);
169 // }
170 //
171 // // e is some other type of event...
172 // return false;
173 }
174
175 /**
176 * Component used by TreeTableCellEditor. The only thing this does
177 * is to override the <code>reshape</code> method, and to ALWAYS
178 * make the x location be <code>offset</code>.
179 */
180 static class TreeTableTextField extends JTextField {
181 void init(int offset, int column, int width, JTable table) {
182 this.offset = offset;
183 this.column = column;
184 this.width = width;
185 this.table = table;
186 setComponentOrientation(table.getComponentOrientation());
187 }
188
189 private int offset; // changed to package private instead of public
190 private int column;
191 private int width;
192 private JTable table;
193 @SuppressWarnings("deprecation")
194 @Override
195 public void reshape(int x, int y, int width, int height) {
196 // Allows precise positioning of text field in the tree cell.
197 // following three lines didn't work out
198 //Border border = this.getBorder(); // get this text field's border
199 //Insets insets = border == null ? null : border.getBorderInsets(this);
200 //int newOffset = offset - (insets == null ? 0 : insets.left);
201
202 // start of old version
203 if(table.getComponentOrientation().isLeftToRight()) {
204 int newOffset = offset - getInsets().left;
205 // this is LtR version
206 super.reshape(x + newOffset, y, width - newOffset, height);
207 } else {
208 // right to left version
209 int newOffset = offset + getInsets().left;
210 int pos = getColumnPositionBidi();
211 width = table.getColumnModel().getColumn(getBidiTreeColumn()).getWidth();
212 width = width - (width - newOffset - this.width);
213 super.reshape(pos, y, width, height);
214 }
215
216 // start of patch from armond
217 // if(table.getComponentOrientation().isLeftToRight()) {
218 // int newOffset = offset - getInsets().left;
219 // // this is LtR version
220 // super.reshape(x + newOffset, y, width - newOffset, height);
221 // } else {
222 // // right to left version
223 // int newWidth = width + offset + this.width;
224 //
225 // super.reshape(x, y, newWidth, height);
226 // }
227
228 }
229
230 /**
231 * Returns the column for the tree in a bidi situation
232 */
233 private int getBidiTreeColumn() {
234 // invert the column offet since this method will always be invoked
235 // in a bidi situation
236 return table.getColumnCount() - this.column - 1;
237 }
238
239 private int getColumnPositionBidi() {
240 int width = 0;
241
242 int column = getBidiTreeColumn();
243 for(int iter = 0 ; iter < column ; iter++) {
244 width += table.getColumnModel().getColumn(iter).getWidth();
245 }
246 return width;
247 }
248 }
249
250 private final JTree tree; // immutable
251 }