/*
 * 作成日: 2006/04/30
 */
package net.sourceforge.swingx.jspread;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.lang.reflect.Constructor;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
//import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;

import net.sourceforge.swingx.Logger;



/**
 * セルの編集を行うために必要なインターフェースです。
 * デフォルトでは以下のクラスに対して、以下のセル・エディタがあります。セルの値が該当するクラスの場合は、
 * それに対応するエディタが使用されます。
 * もし、セルの値がjava.lang.Doubleの場合、以下の該当するクラスはjava.lang.Numberとjava.lang.Objectの
 * ２つになりますが、最も継承の近いjava.lang.Numberのセル・エディタ、すなわちJSpreadCellEditor.NumberEditorが
 * 使用されます。データの型がnullの場合は、java.lang.ObjectのJSpreadCellEditor.DefaultCellEditorが
 * 使われます。
 * <table border="1">
 * <tr><th>データの型</ht><th>JSpreadCellEditor</th></tr>
 * <tr><td>java.lang.Object</td><td>JSpreadCellEditor.DefaultCellEditor</td></tr>
 * <tr><td>java.lang.Number</td><td>JSpreadCellEditor.NumberEditor</td></tr>
 * <tr><td>java.lang.Boolean</td><td>JSpreadCellEditor.BooleanEditor</td></tr>
 * <tr><td>java.util.Date</td><td>JSpreadCellEditor.DateEditor</td></tr>
 * <tr><td>-</td><td>JSpreadCellEditor.CheckBoxEditor</td></tr>
 * <tr><td>-</td><td>JSpreadCellEditor.ComboBoxEditor</td></tr>
 * <tr><td>-</td><td>JSpreadCellEditor.ButtonEditor</td></tr>
 * <tr><td>-</td><td>JSpreadCellEditor.ButtonedCellEditor</td></tr>
 * </table>
 * dataがハイフン"-"のセルエディタはデフォルトでは何かの型に割り当てられているわけではありません。
 * 利用者が独自のセル・エディタを作る際に、基本的な機能を提供します。
 * @author Asan
 */
public interface JSpreadCellEditor {
	
	/**
	 * スプレッドを設定します。
	 * セル編集前に呼ばれます。
	 * @param	spread	null不可
	 */
	void setSpread(JSpread spread);
	
    /**
     * エディタコンポーネントへの参照を返します。
     * @return	セルの編集を行うコンポーネント(null不可)
     */ 
	JComponent getCellEditorComponent();
	
	/**
	 * エディタに保持された値を返します.
	 * @return	エディタの値。null可
	 */ 
	Object getCellValue();
	
	/**
	 * エディタで編集する値を設定します.
	 * @param	value	null可
	 */
	void setCellValue(Object value);
	
	/**
	 * anEvent を使って編集を始められるかどうかをエディタに問い合わせます.
	 * @param anEvent	編集を開始するかどうかを判断するイベント
	 * @return	trueのとき編集を開始します.
	 */
	boolean isCellEditable(EventObject anEvent);
     
//    /**
//     * 編集を停止して一部編集済みの値をエディタの値として受け付けるように、エディタに通知します.
//     */ 
//	boolean stopCellEditing(); 
//    /**
//     * 編集を取り消して一部編集済みの値を受け付けないようにエディタに通知します。
//     */  
//	void cancelCellEditing(); 

	//boolean shouldSelectCell(EventObject anEvent) 
//     編集セルを選択する場合は true を返し、そうでない場合は false を返します。 


	/**
	 * デフォルトのセルエディタです。
	 * JTextFieldを使います。
	 * 文字列にすることができるオブジェクトや、文字列からオブジェクトを生成できる場合
	 * (文字列の引数のコンストラクタがある場合)に使えます。
	 * @author Asan
	 */
	public static class DefaultCellEditor implements JSpreadCellEditor {
		private static final Logger logger = Logger.getLogger(DefaultCellEditor.class);
		protected JSpread spread;
		protected Class[] argTypes = new Class[]{String.class}; 
		protected Constructor constructor; 
		protected Object value; 
		protected JTextField textField;
	    protected int clickCountToStart = 1;
		
	    public DefaultCellEditor() {
			textField = new JTextField();
			clickCountToStart = 2;
		}
		public final void setSpread(JSpread spread) {
			assert spread != null;
			this.spread = spread;
		}
	    public boolean isCellEditable(EventObject anEvent) {
		    if (anEvent instanceof MouseEvent) { 
		    	return ((MouseEvent)anEvent).getClickCount() >= clickCountToStart;
		    }
		    return true;
		}

		public JComponent getCellEditorComponent() {
			return textField;
		}
		public void setCellValue(Object value) {
			textField.setBorder(new LineBorder(Color.black));
			try {
				Class type = (value != null) ? value.getClass() : String.class;
				// Since our obligation is to produce a value which is
				// assignable for the required type it is OK to use the
				// String constructor for columns which are declared
				// to contain Objects. A String is an Object.
				if (type == Object.class) {
					type = String.class;
				}
				constructor = type.getConstructor(argTypes);
			} catch (Exception e) {
			}
			textField.setText(value != null ? "" + value : "");
			textField.selectAll();
		}
		public Object getCellValue() { 
			String s = textField.getText();
		    if ("".equals(s)) { 
				if (constructor.getDeclaringClass() == String.class) { 
				    value = s; 
				}
		    }
		    try { 
		    	value = constructor.newInstance(new Object[]{s}); 
		    } catch (Exception e) { 
				textField.setBorder(new LineBorder(Color.red));
				return value; 
		    }
		    logger.debug("constructor="+constructor+" value.class="+value.getClass());
		    return value; 
		}
		
	}
	/**
	 * 数字を編集するセル・エディタです.
	 * javax.swing.JTextField を右詰めで編集を行います。
	 * @author Asan
	 */
	public static class NumberEditor extends DefaultCellEditor {
		public NumberEditor() {
			super();
			textField.setHorizontalAlignment(JTextField.RIGHT);
		}
	}
	/**
	 * 日時を編集するセル・エディタです.
	 * javax.swing.JTextField で編集を行います。
	 * 日時を表示・解釈する日時フォーマットを指定することができます。
	 * @author Asan
	 */
	public static class DateEditor extends DefaultCellEditor {
		private static Logger logger = Logger.getLogger(DateEditor.class);
		protected DateFormat formatter = null;
		public DateEditor() {
			super();
		}
		/**
		 * 日時フォーマットを指定します。このメソッドが呼ばれなければ、デフォルトの日時フォーマット
		 * <code> DateFormat.getInstance() </code>が使用されます。
		 * @param formatter	日時フォーマット(null不可)
		 */
		public void setFormat(DateFormat formatter) {
			assert formatter != null;
			this.formatter = formatter;
		}
		/**
		 * 値を設定します.
		 * 任意のオブジェクトを、JLabelのアイコンに設定します.
		 * @param value	セルに表示する値(null可)
		 */
		public void setCellValue(Object value) {
			if (formatter == null) {
				formatter = DateFormat.getDateInstance();
			}
			textField.setText((value == null) ? "" : formatter.format(value));
		}
		public Object getCellValue() { 
			String s = textField.getText();
		    try { 
		    	value = formatter.parse(s); 
		    } catch (ParseException e) {
		    	logger.debug(s, e);
				textField.setBorder(new LineBorder(Color.red));
				return value; 
		    }
		    return value; 
		}
	}	

	/**
	 * javax.swing.JCheckBox で編集するセル・エディタです.
	 * @author Asan
	 */
	public static abstract class CheckBoxEditor implements JSpreadCellEditor, ActionListener {
    	private static Logger logger = Logger.getLogger(CheckBoxEditor.class);
    	JCheckBox checkBox = new JCheckBox();
    	JSpread spread;
        protected int clickCountToStart = 1;
    	public CheckBoxEditor() {
    	    checkBox.setHorizontalAlignment(JCheckBox.CENTER);
    	    checkBox.addActionListener(this);
    	}
    	/**
    	 * スプレッドを設定します。
    	 * @param	spread	null不可
    	 */
    	public final void setSpread(JSpread spread) {
    		assert spread != null;
    		this.spread = spread;
    	}
        public boolean isCellEditable(EventObject anEvent) {
    	    if (anEvent instanceof MouseEvent) { 
    	    	return ((MouseEvent)anEvent).getClickCount() >= clickCountToStart;
    	    }
    	    return true;
    	}
        public JComponent getCellEditorComponent() {
    		return checkBox;
    	}
    	public void setCellValue(Object value) {
           	boolean selected = false; 
    		if (value instanceof Boolean) {
    		    selected = ((Boolean)value).booleanValue();
    		}
    		else if (value instanceof String) {
    		    selected = value.equals("true");
    		}
    		checkBox.setSelected(selected);
    	}
    	public abstract Object getCellValue();
        /**
         * アクションがきたら、編集をやめる.
         * @param e the action event
         */
         public void actionPerformed(ActionEvent e) {
        	 logger.debug("actionPerformed() "+e);
        	 spread.stopCellEditing();
         }
    }
	/**
	 * Booleanを編集するセル・エディタです.
	 * JCheckBoxで編集します。 
	 * @author Asan
	 */
	public static class BooleanEditor extends CheckBoxEditor {
	   	public Object getCellValue() { 
    		return Boolean.valueOf(checkBox.isSelected());
    	}
 		
	}

	/**
	 * コンボボックスで編集可能なセルです。
	 * セルエディタとセルレンダラとして使います。選択はできますが、テキスト編集はできません。
	 * ただし、セルレンダラーとセルエディタは別のインスタンスにする必要があります。
	 * 値と実際に表示するモノの２つを設定することができます。 
	 * @author Asan
	 */
	public static class ComboBoxEditor implements JSpreadCellRenderer, JSpreadCellEditor, ItemListener {
		private static Logger logger = Logger.getLogger(ComboBoxEditor.class);
		protected JSpread spread;
		protected JComboBox comboBox; 
		protected Object[] keys;
		protected Object[] values;
		protected Map keyValueMap = new HashMap();
		protected Map valueKeyMap = new HashMap();
		
		public ComboBoxEditor(Object[] keys, Object[] values) {
			assert keys != null && keys.length > 0: keys;
			assert values != null && values.length > 0: values;
			assert keys.length == values.length: "keys.length="+keys.length + " values.length="+values.length;
			this.keys = keys;
			this.values = values;
			for (int i=0; i<keys.length; i++) {
				keyValueMap.put(keys[i], values[i]);
				valueKeyMap.put(values[i], keys[i]);
			}
			comboBox = new JComboBox(values);
			comboBox.addItemListener(this);
		}
		public final void setSpread(JSpread spread) {
			assert spread != null;
			this.spread = spread;
		}
		/** セルを描画するのに使用されるコンポーネントを返します. */ 
		public JComponent getSpreadRendererComponent(JSpread spread, Object value, 
				boolean isSelected, boolean hasFocus, 
				int type, int row, int column) {
			setCellValue(value);
			return comboBox;
		}
	    /**
	     * エディタコンポーネントへの参照を返します。
	     */ 
		public JComponent getCellEditorComponent() {
			return comboBox;
		}
		
		/**
		 * エディタに保持された値を返します.
		 * @return	エディタの値。null可
		 */ 
		public Object getCellValue() {
			Object value = comboBox.getSelectedItem();
			return valueKeyMap.get(value);
		}
		
		/**
		 * エディタで編集するキーに対する値を設定します.
		 * @param	key	キーを指定します。null可
		 */
		public void setCellValue(Object key) {
			comboBox.setSelectedItem(keyValueMap.get(key));
		}
		
		/**
		 * anEvent を使って編集を始められるかどうかをエディタに問い合わせます.
		 * @param anEvent	編集を開始するかどうかを判断するイベント
		 * @return	trueのとき編集を開始します.
		 */
		public boolean isCellEditable(EventObject anEvent) {
			return true;
		}
		public void itemStateChanged(ItemEvent e) {
			logger.debug("itemStateChanged() e="+e);
			if (spread != null && spread.isEditing() && spread.getEditorComponent() == comboBox) {
				spread.stopCellEditing();
			}
		}
	}

	/**
	 * ボタン付きセル・エディタです。セルレンダラーとしても使えます。
	 * ただし、セルレンダラーとセルエディタは別のインスタンスにする必要があります。
	 * 描画を行うJComponentとJButtonで構成されています。
	 * JComponentはコンストラクタで指定します。
	 * ボタンが押されたときの処理を継承先で実装する必要があります。
	 * 
	 * @author Asan
	 */
	public static abstract class ButtonedCellEditor extends JPanel implements JSpreadCellRenderer, JSpreadCellEditor, ActionListener {
		private static Logger logger = Logger.getLogger(ButtonedCellEditor.class);
		private static final int BUTTON_WIDTH = 15;
		protected JSpread spread;
		protected JComponent comp;
		protected JButton button;
		protected Object value;
		
		public ButtonedCellEditor(JComponent comp) {
			assert comp != null: comp;
			this.comp = comp;
			button = new JButton("...");
			add(comp);
			add(button);
			setLayout(new ButtonedCellLayout());
			button.addActionListener(this);
		}
		public final void setSpread(JSpread spread) {
			assert spread != null;
			this.spread = spread;
		}
		/** セルを描画するのに使用されるコンポーネントを返します. */ 
		public JComponent getSpreadCellRendererComponent(JSpread spread, Object value, 
				boolean isSelected, boolean hasFocus, 
				int row, int column) {
			setCellValue(value);
			return this;
		}

	    /**
	     * エディタコンポーネントへの参照を返します。
	     */ 
		public JComponent getCellEditorComponent() {
			return this;
		}
		
		/**
		 * エディタに保持された値を返します.
		 * @return	エディタの値。null可
		 */ 
		public Object getCellValue() {
			return value;
		}
		
		/**
		 * エディタで編集する値を設定します.
		 * @param	value	null可
		 */
		public void setCellValue(Object value) {
			logger.debug("setCellValue() value="+value);
			this.value = value;
		}
		
		/**
		 * anEvent を使って編集を始められるかどうかをエディタに問い合わせます.
		 * @param anEvent	編集を開始するかどうかを判断するイベント
		 * @return	trueのとき編集を開始します.
		 */
		public boolean isCellEditable(EventObject anEvent) {
			return true;
		}
		/**
		 * ボタンが押されたときの処理を継承先で実装する必要があります.
		 * 編集が完了したときには、spread.stopCellEditing()を、
		 * キャンセルしたときは、spread.cancelCellEditing()を呼んで下さい。
		 * @param	event	イベント(null不可)
		 */
	    public abstract void actionPerformed(ActionEvent event);
//	    {
//	    	JOptionPane.showMessageDialog(this, 
//	    			"ButtonedCellを継承し、actionPerformed(ActionEvent event)を実装してください。\n"+
//	    			"value="+value);
//			spread.stopCellEditing();
//	    }

		/**
		 * ボタン付きセル専用のレイアウトマネージャ.
		 */
		private class ButtonedCellLayout implements LayoutManager {
			public void addLayoutComponent(String name, Component comp) {
			}
			public void removeLayoutComponent(Component comp) {
			}
			/** 指定されたパネルにコンテナを配置します。 */
			public void layoutContainer(Container parent) {
				synchronized(parent.getTreeLock()) {
					comp.setBounds(0, 0, parent.getWidth() - BUTTON_WIDTH, parent.getHeight());
					button.setBounds(parent.getWidth()-BUTTON_WIDTH, 0, BUTTON_WIDTH, parent.getHeight());
				}
			}
			/** 指定された親コンテナにコンポーネントを配置した時のパネルの最小サイズを計算します。 */
			public Dimension minimumLayoutSize(Container parent) {
				return parent.getMaximumSize();
			}
			/** 指定された親コンテナにコンポーネントを配置した時のパネルの推奨サイズを計算します。 */
			public Dimension preferredLayoutSize(Container parent) {
				return parent.getPreferredSize();
			}
		}
	}

	/**
	 * ボタンのセル・エディタです。セル・レンダラとしても使うことができます。
	 * ただし、セルレンダラーとセルエディタは別のインスタンスにする必要があります。
	 * 
	 * @author Asan
	 */
	public static abstract class ButtonEditor 
			implements JSpreadCellRenderer, JSpreadCellEditor, ActionListener {
		protected JButton button;
		protected JSpread spread;
		protected Object value;
		
		/**
		 * ボタンのセルを生成します。
		 * 引数にボタンを設定します。
		 * @param button	ボタン(null不可)
		 */
		public ButtonEditor(JButton button) {
			assert button != null;
			this.button = button;
			button.addActionListener(this);
		}
		
		/**
		 * セルを描画するのに使用されるコンポーネントを返します.
		 * @param	spread	スプレッド(null不可)
		 * @param	value	値(null可)
		 * @param	isSelected	セルが選択されているかどうか？
		 * @param	hasFocus	セルにフォーカスがあるかどうか？
		 * @param	rowDataIndex	列データ番号(dataIndex)(0..)
		 * @param	colDataIndex	行データ番号(dataIndex)(0..)
		 * @return	セルを描画するのに使用するコンポーネント(null不可)
		 */ 
		public JComponent getSpreadCellRendererComponent(JSpread spread, Object value, 
				boolean isSelected, boolean hasFocus, 
				int rowDataIndex, int colDataIndex) {
			return button;
		}
		
		/**
		 * スプレッドを設定します。
		 * セル編集前に呼ばれます。
		 * @param	spread	null不可
		 */
		public final void setSpread(JSpread spread) {
			assert spread != null;
			this.spread = spread;
		}

	    /**
	     * エディタコンポーネントへの参照を返します。
	     * @return	セルの編集を行うコンポーネント(null不可)
	     */ 
		public JComponent getCellEditorComponent() {
			return button;
		}
		
		/**
		 * エディタに保持された値を返します.
		 * @return	エディタの値。null可
		 */ 
		public Object getCellValue() {
			return value;
		}
		
		/**
		 * エディタで編集する値を設定します.
		 * @param	value	null可
		 */
		public void setCellValue(Object value) {
			this.value = value;
		}

		/**
		 * anEvent を使って編集を始められるかどうかをエディタに問い合わせます.
		 * @param anEvent	編集を開始するかどうかを判断するイベント
		 * @return	trueのとき編集を開始します.
		 */
		public boolean isCellEditable(EventObject anEvent) {
			return true;
		}

		/**
		 * ボタンが押されたときの処理を記述します。
		 * このクラスを継承したら必ずこのメソッドをオーバーライドしてください。
		 * 編集が完了したときには、spread.stopCellEditing()を、
		 * キャンセルしたときは、spread.cancelCellEditing()を呼んで下さい。
		 * @param	event	ボタンが押されたことを表すイベント。(null不可)
		 */
		public abstract void actionPerformed(ActionEvent event);
	}



}
