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    }