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}