/*
 * Id: TreeTableCellEditor.java,v 1.10 2006/04/03 14:46:33 kleopatra Exp
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.jdesktop.swingx.treetable;

import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.EventObject;

import javax.swing.*;
import javax.swing.tree.TreeCellRenderer;

/**
 * An editor that can be used to edit the tree column. This extends
 * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField)
 * to perform the actual editing.
 * <p>To support editing of the tree column we can not make the tree
 * editable. The reason this doesn't work is that you can not use
 * the same component for editing and renderering. The table may have
 * the need to paint cells, while a cell is being edited. If the same
 * component were used for the rendering and editing the component would
 * be moved around, and the contents would change. When editing, this
 * is undesirable, the contents of the text field must stay the same,
 * including the caret blinking, and selections persisting. For this
 * reason the editing is done via a TableCellEditor.
 * <p>Another interesting thing to be aware of is how tree positions
 * its render and editor. The render/editor is responsible for drawing the
 * icon indicating the type of node (leaf, branch...). The tree is
 * responsible for drawing any other indicators, perhaps an additional
 * +/- sign, or lines connecting the various nodes. So, the renderer
 * is positioned based on depth. On the other hand, table always makes
 * its editor fill the contents of the cell. To get the allusion
 * that the table cell editor is part of the tree, we don't want the
 * table cell editor to fill the cell bounds. We want it to be placed
 * in the same manner as tree places it editor, and have table message
 * the tree to paint any decorations the tree wants. Then, we would
 * only have to worry about the editing part. The approach taken
 * here is to determine where tree would place the editor, and to override
 * the <code>reshape</code> method in the JTextField component to
 * nudge the textfield to the location tree would place it. Since
 * JXTreeTable will paint the tree behind the editor everything should
 * just work. So, that is what we are doing here. Determining of
 * the icon position will only work if the TreeCellRenderer is
 * an instance of DefaultTreeCellRenderer. If you need custom
 * TreeCellRenderers, that don't descend from DefaultTreeCellRenderer,
 * and you want to support editing in JXTreeTable, you will have
 * to do something similiar.
 *
 * @author Scott Violet
 * @author Ramesh Gupta
 */
public class TreeTableCellEditor extends DefaultCellEditor {
    public TreeTableCellEditor(JTree tree) {
        super(new TreeTableTextField());
        if (tree == null) {
            throw new IllegalArgumentException("null tree");
        }
        // JW: no need to...
        this.tree = tree; // immutable
    }

    /**
     * Overriden to determine an offset that tree would place the editor at. The
     * offset is determined from the <code>getRowBounds</code> JTree method,
     * and additionaly from the icon DefaultTreeCellRenderer will use.
     * <p>
     * The offset is then set on the TreeTableTextField component created in the
     * constructor, and returned.
     */
    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int row, int column) {
        Component component = super.getTableCellEditorComponent(table, value,
                isSelected, row, column);
        // JW: this implementation is not bidi-compliant, need to do better
        initEditorOffset(table, row, column, isSelected);
        return component;
    }

    /**
     * @param row
     * @param isSelected
     * @return
     */
    protected void initEditorOffset(JTable table, int row, int column, boolean isSelected) {
        if (tree == null) return;
        Rectangle bounds = tree.getRowBounds(row);
        int offset = bounds.x;
        Object node = tree.getPathForRow(row).getLastPathComponent();
        boolean leaf = tree.getModel().isLeaf(node);
        boolean expanded = tree.isExpanded(row);
        TreeCellRenderer tcr = tree.getCellRenderer();
        Component treeComponent = tcr.getTreeCellRendererComponent(tree, node,
                isSelected, expanded, leaf, row, false);
        if ((treeComponent instanceof JLabel) 
                // adjust the offset to account for the icon - at least
                // in LToR orientation. RToL is hard to tackle anyway...
                && table.getComponentOrientation().isLeftToRight()) {
            JLabel label = (JLabel) treeComponent;

            Icon icon = label.getIcon();
            offset += icon.getIconWidth() + label.getIconTextGap();
        }
        ((TreeTableTextField) getComponent()).init(offset, column, bounds.width, table);
    }

    /**
     * This is overriden to forward the event to the tree. This will
     * return true if the click count >= clickCountToStart, or the event is null.
     */
    public boolean isCellEditable(EventObject e) {
        if (e == null) {
            return true;
        }
        else if (e instanceof MouseEvent) {
            return (((MouseEvent) e).getClickCount() >= clickCountToStart);
        }

    // e is some other type of event...
        return false;
    }

    /**
     * Component used by TreeTableCellEditor. The only thing this does
     * is to override the <code>reshape</code> method, and to ALWAYS
     * make the x location be <code>offset</code>.
     */
    static class TreeTableTextField extends JTextField {
        void init(int offset, int column, int width, JTable table) {
            this.offset = offset;
            this.column = column;
            this.width = width;
            this.table = table;
            setComponentOrientation(table.getComponentOrientation());
        }
        
        private int offset; // changed to package private instead of public
        private int column;
        private int width;
        private JTable table;
        public void reshape(int x, int y, int width, int height) {
            // Allows precise positioning of text field in the tree cell.
            //Border border = this.getBorder(); // get this text field's border
            //Insets insets = border == null ? null : border.getBorderInsets(this);
            //int newOffset = offset - (insets == null ? 0 : insets.left);
            if(table.getComponentOrientation().isLeftToRight()) {
                int newOffset = offset - getInsets().left;
                // this is LtR version
                super.reshape(x + newOffset, y, width - newOffset, height);
            } else {
                // right to left version
                int newOffset = offset + getInsets().left;
                int pos = getColumnPositionBidi();
                width = table.getColumnModel().getColumn(getBidiTreeColumn()).getWidth();
                width = width - (width - newOffset - this.width);
                super.reshape(pos, y, width, height);
            }
        }
        
        /**
         * Returns the column for the tree in a bidi situation
         */
        private int getBidiTreeColumn() {
            // invert the column offet since this method will always be invoked
            // in a bidi situation
            return table.getColumnCount() - this.column - 1;
        }
        
        private int getColumnPositionBidi() {
            int width = 0;
            
            int column = getBidiTreeColumn();
            for(int iter = 0 ; iter < column ; iter++) {
                width += table.getColumnModel().getColumn(iter).getWidth();
            }
            return width;
        }
    }

    private final JTree tree; // immutable
}