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,
096: Object value, boolean isSelected, int row, int column) {
097: Component component = super .getTableCellEditorComponent(table,
098: value, 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(
119: tree, node, isSelected, expanded, leaf, row, false);
120: if ((treeComponent instanceof JLabel)
121: // adjust the offset to account for the icon - at least
122: // in LToR orientation. RToL is hard to tackle anyway...
123: && table.getComponentOrientation().isLeftToRight()) {
124: JLabel label = (JLabel) treeComponent;
125:
126: Icon icon = label.getIcon();
127: offset += icon.getIconWidth() + label.getIconTextGap();
128: }
129: ((TreeTableTextField) getComponent()).init(offset, column,
130: bounds.width, table);
131: }
132:
133: /**
134: * This is overriden to forward the event to the tree. This will
135: * return true if the click count >= clickCountToStart, or the event is null.
136: */
137: @Override
138: public boolean isCellEditable(EventObject e) {
139: if (e == null) {
140: return true;
141: } else if (e instanceof MouseEvent) {
142: return (((MouseEvent) e).getClickCount() >= clickCountToStart);
143: }
144:
145: // e is some other type of event...
146: return false;
147: }
148:
149: /**
150: * Component used by TreeTableCellEditor. The only thing this does
151: * is to override the <code>reshape</code> method, and to ALWAYS
152: * make the x location be <code>offset</code>.
153: */
154: static class TreeTableTextField extends JTextField {
155: void init(int offset, int column, int width, JTable table) {
156: this .offset = offset;
157: this .column = column;
158: this .width = width;
159: this .table = table;
160: setComponentOrientation(table.getComponentOrientation());
161: }
162:
163: private int offset; // changed to package private instead of public
164: private int column;
165: private int width;
166: private JTable table;
167:
168: @Override
169: public void reshape(int x, int y, int width, int height) {
170: // Allows precise positioning of text field in the tree cell.
171: //Border border = this.getBorder(); // get this text field's border
172: //Insets insets = border == null ? null : border.getBorderInsets(this);
173: //int newOffset = offset - (insets == null ? 0 : insets.left);
174: if (table.getComponentOrientation().isLeftToRight()) {
175: int newOffset = offset - getInsets().left;
176: // this is LtR version
177: super .reshape(x + newOffset, y, width - newOffset,
178: height);
179: } else {
180: // right to left version
181: int newOffset = offset + getInsets().left;
182: int pos = getColumnPositionBidi();
183: width = table.getColumnModel().getColumn(
184: getBidiTreeColumn()).getWidth();
185: width = width - (width - newOffset - this .width);
186: super .reshape(pos, y, width, height);
187: }
188: }
189:
190: /**
191: * Returns the column for the tree in a bidi situation
192: */
193: private int getBidiTreeColumn() {
194: // invert the column offet since this method will always be invoked
195: // in a bidi situation
196: return table.getColumnCount() - this .column - 1;
197: }
198:
199: private int getColumnPositionBidi() {
200: int width = 0;
201:
202: int column = getBidiTreeColumn();
203: for (int iter = 0; iter < column; iter++) {
204: width += table.getColumnModel().getColumn(iter)
205: .getWidth();
206: }
207: return width;
208: }
209: }
210:
211: private final JTree tree; // immutable
212: }
|