package net.sourceforge.swingx.jspread;


import java.awt.BorderLayout;
import java.awt.Event;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.EventObject;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;

import net.sourceforge.swingx.Logger;
import net.sourceforge.swingx.jspread.JSpreadSheet.CurrentFocus;


/**
 * スプレッドを扱うクラスです。
 * 
 * 
 * @author Asan
 */
public class JSpread extends JPanel implements JSpreadConstant {
	static Logger logger = Logger.getLogger(JSpread.class);

	/** 現在のシート. */
	JSpreadSheet sheet;
	
	JSpreadProperty properties = new JSpreadProperty();
	/** ArrayList<JSpreadSelectionListener>[0..*] 要素はnull不可 */
	private ArrayList selectionListeners = null;

	// <<controller>>
	JSpreadControllerHeaderData headerDataController = new JSpreadControllerHeaderData(this);
	JSpreadControllerBuilder viewController= new JSpreadControllerBuilder(this);
	JSpreadControllerHeader headerController = new JSpreadControllerHeader(this);
	JSpreadControllerCellRenderer rendererController = new JSpreadControllerCellRenderer(this);
	JSpreadControllerCellEditor editorController = new JSpreadControllerCellEditor(this);


	//---------------------------------------------------------------------
	// コンストラクタ関係
	//---------------------------------------------------------------------
	
	/**
	 * スプレッドを作成します.
	 * 256*256のシートを作成します。
	 */
	public JSpread() {
		this(256, 256);
	}
	/**
	 * 指定された列*行のスプレッドを作成します.
	 * @param	numRows		列(縦)の数。０以上。
	 * @param	numCols		行(横)の数。０以上。
	 */
	public JSpread(int numRows, int numCols) {
		assert numRows >= 0: numRows;
		assert numCols >= 0: numCols;
		sheet = new JSpreadSheet(this, "Sheet", numRows, numCols);

		// アクションを登録 TODO:他のも
		ActionMap actionmap = getActionMap();
		Action selectAllAction = new JSpreadActions.SelectAllAction(this);
		Action copyClipboardAction = new JSpreadActions.CopyClipboardAction(this);
		actionmap.put("SelectAllAction", selectAllAction);
		actionmap.put("CopyClipboardAction", copyClipboardAction);
		InputMap inputmap = getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
		inputmap.put(KeyStroke.getKeyStroke('A', Event.CTRL_MASK), "SelectAllAction");
		inputmap.put(KeyStroke.getKeyStroke('C', Event.CTRL_MASK), "CopyClipboardAction");
//      enableEvents(AWTEvent.KEY_EVENT_MASK);
//      setFocusable(true);		
//		setEnabled(true);

		// JTableのソースからコピー　begin
        setOpaque(true);
		//setSurrendersFocusOnKeystroke(false);
        //setPreferredScrollableViewportSize(new Dimension(450, 400));
        // I'm registered to do tool tips so we can draw tips for the renderers
//        ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
//        toolTipManager.registerComponent(this);
        setAutoscrolls(true);
        // TODO:ToolTipManagerに対する２行は必要と思われるが、setAutoScrolls(true)は必要かどうか不明
		// JTableのソースからコピー　end

	}

	//---------------------------------------------------------------------
	// データ関係
	//---------------------------------------------------------------------
	public JSpreadSheet getSheet() {
		return sheet;
	}
	
	/**
	 * row、column に位置するセルの値を取得します.
	 * @param	rowDataIndex	列のデータ番号(dataIndex)(0..*)
	 * @param	colDataIndex	行のデータ番号(dataIndex)(0..*)
	 * @return	値(null可)
	 */
	public Object getValueAt(int rowDataIndex, int colDataIndex) {
		//logger.debug("getValueAt()"+sheet+" rowDataIndex="+rowDataIndex+" colDataIndex="+colDataIndex);
		assert 0 <= rowDataIndex: rowDataIndex;
		assert 0 <= colDataIndex: colDataIndex;
		Object obj = sheet.getValueAt(rowDataIndex, colDataIndex);
		//logger.debug("getValueAt() exit obj="+obj);
		return obj;
	}
	
	/**
	 * row、column に位置するセルの値を設定します.
	 * @param	value	値。null可
	 * @param	rowDataIndex	列のデータ番号(dataIndex)(0..*)
	 * @param	colDataIndex	行のデータ番号(dataIndex)(0..*)
	 */
	public void setValueAt(Object value, int rowDataIndex, int colDataIndex) {
		//logger.debug("setValue() value="+value+" rowDataIndex="+rowDataIndex+" colDataIndex="+colDataIndex);
		assert 0 <= rowDataIndex: rowDataIndex;
		assert 0 <= colDataIndex: colDataIndex;
		sheet.setValueAt(value, rowDataIndex, colDataIndex);
		//logger.debug("exit");
	}

	/**
	 * ヘッダの値を取得します.
	 * デフォルトでは<code> null </code>が設定されています。
	 * @param	orientation	ヘッダの種類. ROW/COLUMN/CORNER
	 * @param	dataIndex	データ番号(dataIndex)(0..*).CORNERの場合は無視される。
	 * @return	値(null可)
	 */
	public Object getHeaderValueAt(int header, int dataIndex) {
		logger.debug("enter");
		assert header != ROW || header != COLUMN: header;
		assert 0 <= dataIndex: dataIndex;
		Object obj = sheet.getHeaderValueAt(header, dataIndex);
		logger.debug("exit");
		return obj;
	}
	/**
	 * ヘッダの値を設定します.
	 * <code> null </code>の場合は、行の場合は"A","B",...,"Z","AA","AB",...の値が、
	 * 列の場合は"1","2","3",...が表示されます。
	 * @param	value	値。null可
	 * @param	header		ヘッダの種類. ROW/COLUMN
	 * @param	dataIndex	データ番号(dataIndex)(0..*)
	 */
	public void setHeaderValueAt(Object value, int header, int dataIndex) {
		logger.debug("enter");
		assert header != ROW || header != COLUMN: header;
		assert 0 <= dataIndex: dataIndex;
		logger.debug("setHeaderValueAt() value="+value+" header="+header+" dataIndex="+dataIndex);
		sheet.setHeaderValueAt(value, header, dataIndex);
		logger.debug("exit");
	}
	/**
	 * 左上のコーナーの値を取得します。
	 * @return	値(null可)
	 */
	public Object getCornerValue() {
		return sheet.getCornerValue();
	}
	/**
	 * 左上のコーナーの値を設定します.
	 * @param	value	値。null可
	 */
	public void setCornerValue(Object value) {
		logger.debug("setCornerValue() value="+value);
		sheet.setCornerValue(value);
		logger.debug("exit");
	}
	/**
	 * ヘッダの縦横の数を返します。
	 * @param orientation	ヘッダの種類. ROW/COLUMN
	 * @return	データの数(DataIndex)
	 */
	public int getCellCount(int orientation) {
		logger.debug("enter");
		assert orientation == JSpread.ROW || orientation == JSpread.COLUMN: orientation;
		int count = sheet.getCellCount(orientation);
		logger.debug("exit");
		return count;
	}
	/**
	 * セルのサイズを設定します。
	 * 通常は、初期処理やビューの切り替えなど、外見が大きく切り替わる場合に使用します。
	 * このメソッドはなるべく多くの情報を残すように努力しますが、
	 * いくつかのデータや表示順番などは無効になります。
	 * サイズが変わらない場合は何も行いません。
	 * @param rowCount	列の数。0以上
	 * @param colCount	行の数。0以上
	 * @param initValue	初期値(null可)
	 */
	public void setCellCount(int rowCount, int colCount, Object initValue) {
		logger.debug("enter");
		assert rowCount >= 0: rowCount;
		assert colCount >= 0: colCount;
		sheet.setCellCount(rowCount, colCount, initValue);
		// TODO:サイズが変わるのだけれど、どうやって設定すればよい？親のJScrollPaneとか。
		//setPreferredSize(new Dimension(sheet.colHeader.getTotalWidth(), sheet.rowHeader.getTotalWidth()));
		revalidate();
		// 選択されているセルの位置も変える。
		CurrentFocus prev = sheet.getCurrentFocus();
		sheet.focusCol = Math.min(sheet.focusCol, colCount-1);
		sheet.focusRow = Math.min(sheet.focusRow, rowCount-1);
		sheet.curCol = Math.min(sheet.curCol, colCount-1);
		sheet.curRow = Math.min(sheet.curRow, rowCount-1);
		CurrentFocus next = sheet.getCurrentFocus();
		if (! prev.equals(next)) {
			fireSelectionListener(new JSpreadSelectionEvent(this, prev, next));
		}
		logger.debug("exit");
	}

	/**
	 * 指定された場所に列/行を挿入します。dataIndexに-1を指定すると末尾に追加します。
	 * @param value			セルの値。null可
	 * @param header		ヘッダの種類. ROW/COLUMN
	 * @param dataIndex		挿入する場所。-1の場合、末尾に追加されます。
	 * @param count			挿入する行/列の数. 1以上
	 * @param headerValue	ヘッダの値。null可
	 */
	public void insertValueAt(Object value, int header, int dataIndex, int count, Object headerValue) {
		assert header != ROW || header != COLUMN: header;
		assert 0 <= dataIndex || dataIndex == -1: dataIndex;
		assert 1 <= count: count;
		logger.debug("insertValueAt() value="+value+" header="+header+" dataIndex="+dataIndex+
				" count="+count+" headerValue="+headerValue);
		sheet.insertValueAt(value, header, dataIndex, count, headerValue);
	}
	//---------------------------------------------------------------------
	// ヘッダ関係
	//---------------------------------------------------------------------
	/**
	 * 指定された場所に列/行を挿入します。dataIndexに-1を指定すると末尾に追加します。
	 * @param value			セルの値。null可
	 * @param header		ヘッダの種類. ROW/COLUMN
	 * @param dataIndex		挿入する場所。-1の場合、末尾に追加されます。
	 * @param count			挿入する行/列の数. 1以上
	 * @param headerValue	ヘッダの値。null可
	 */
	public JSpreadHeader getHeader(int header) {
		assert header != ROW || header != COLUMN: header;
		return header == ROW ? sheet.rowHeader : sheet.colHeader;
	}
	
	//---------------------------------------------------------------------
	// ビュー関係
	//---------------------------------------------------------------------
	/**
	 * スプレッドのビューを作成します。
	 * この戻り値をJPanelなどに追加してください。
	 * @return	Swingコンポーネント
	 */
	public JComponent createView() {
		viewController.createView(this);
		//getComponent(CELL_PANE_NAME).grabFocus();
		return this;
	}
	/**
	 * スクロールバー付きのビューを作成します.
	 * @param vsbPolicy	垂直スクロールバーの表示ポリシーです。
	 * 		JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
	 * 		JScrollPane.VERTICAL_SCROLLBAR_NEVER,
	 * 		JScrollPane.VERTICAL_SCROLLBAR_ALWAYS のいずれかを指定します。
	 * @param hsbPolicy	水平スクロールバーの表示ポリシーです。
	 * 		JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED,
	 * 		JScrollPane.HORIZONTAL_SCROLLBAR_NEVER,
	 * 		JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS のいずれかを指定します。
	 * @return	スクロールバー付きのビュー.
	 */
	public JPanel createScrollView(int vsbPolicy, int hsbPolicy) {
		logger.debug("enter");
		assert 
			vsbPolicy == JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED ||
			vsbPolicy == JScrollPane.VERTICAL_SCROLLBAR_NEVER ||
			vsbPolicy == JScrollPane.VERTICAL_SCROLLBAR_ALWAYS: vsbPolicy;
		assert
			hsbPolicy == JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED ||
			hsbPolicy == JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ||
			hsbPolicy == JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS: hsbPolicy;
		viewController.createScrollView(this, vsbPolicy, hsbPolicy);
		//getComponent(CELL_PANE_NAME).grabFocus();
		logger.debug("exit");
		return this;
	}
	/** convinienceなメソッド. */
	public JPanel createScrollView() {
		return createScrollView(
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	}
	/**
	 * 指定された名前を持つコンポーネントを返します.
	 * @param name
	 * @return
	 */
	public JComponent getComponent(String name) {
		assert name != null;
		JComponent comp = JSpreadUtilities.getComponent(this, name);
		assert comp != null: name;
		return comp;
	}
		
	//---------------------------------------------------------------------
	// ヘッダ関係
	//---------------------------------------------------------------------
	/**
	 * 表示位置1から表示位置2までの行or列を非表示にします.
	 * 表示位置1 > 表示位置2 で指定しても構いません。 
	 * @param	rowOrColumn	行か列か(JSpread.ROW or JSpread.COLUMN)
	 * @param	viewpos1	表示位置1(0..)(viewIndex)
	 * @param	viewpos2	表示位置2(0..)(viewIndex)
	 */
	public void hideHeader(int rowOrColumn, int viewpos1, int viewpos2) {
		assert rowOrColumn == ROW || rowOrColumn == COLUMN: rowOrColumn;
		assert 0 <= viewpos1: viewpos1;
		assert 0 <= viewpos2: viewpos2;
		headerController.hideHeader(rowOrColumn, viewpos1, viewpos2);
	}
	/**
	 * すべての行or列を表示し、順番を元に戻します. 
	 * @param	rowOrColumn	行か列か(JSpread.ROW or JSpread.COLUMN)
	 */
	public void showHeaderAll(int rowOrColumn) {
		assert rowOrColumn == ROW || rowOrColumn == COLUMN || rowOrColumn == BOTH: rowOrColumn;
		headerController.showHeaderAll(rowOrColumn);
	}
	
	public int getViewPositionAtPoint(int rowOrColumn, int xy) {
		assert rowOrColumn == ROW || rowOrColumn == COLUMN: rowOrColumn;
		return (rowOrColumn == ROW) ?
					sheet.rowHeader.getViewPositionAtPoint(xy):
					sheet.colHeader.getViewPositionAtPoint(xy);
	}
	
	//---------------------------------------------------------------------
	// レイアウト関係
	//---------------------------------------------------------------------
//	public void doLayout() {
//		logger.debug("enter");
//		new JSpreadControllerResize().doLayout(this);
//		logger.debug("exit");
//	}

	// ---------------------------------------------------------------------
	// セル・レンダラー関係
	// ---------------------------------------------------------------------
	/**
	 * セルを描画するのに使用されるコンポーネントを返します.
	 * @param value				セルの値(null可)
	 * @param rowDataIndex		列表示番号(dataIndex)(0..)
	 * @param columnDataIndex	行表示番号(dataIndex)(0..)
	 * @return セルを描画するのに使用されるコンポーネント(null可)
	 */ 
	public JSpreadCellRenderer getCellRenderer(Object value, 
			int rowDataIndex, int columnDataIndex) {
		assert 0 <= rowDataIndex: rowDataIndex;
		assert 0 <= columnDataIndex: columnDataIndex;
		return rendererController.getCellRenderer(value, 
				rowDataIndex, columnDataIndex);
	}
	/**
	 * ヘッダを描画するのに使用されるコンポーネントを返します.
	 * @param value			セルの値(null可)
	 * @param header		ヘッダの種類. ROW/COLUMN/CORNER
	 * @param ｄataIndex		行表示番号(dataIndex)(0..)
	 * @return セルを描画するのに使用されるコンポーネント(null可)
	 */ 
    public JSpreadCellRenderer getHeaderRenderer(Object value,
    		int header, int dataIndex) {
		assert header == ROW || header == COLUMN || header == CORNER: header;
		assert 0 <= dataIndex: dataIndex;
		return rendererController.getHeaderRenderer(value, header, dataIndex);
    }
	//---------------------------------------------------------------------
	// セル・エディタ関係
	//---------------------------------------------------------------------

	/**
	 * 指定されたセルが編集可能かどうかを取得します.
	 * デフォルトは<code> false </code>です。
	 * @param	rowDataIndex	列データ番号(dataIndex)(0..)
	 * @param	colDataIndex	行データ番号(dataIndex)(0..)
	 * @return	true：編集可 / false:編集不可 
	 */
	public boolean isCellEditable(int rowDataIndex, int colDataIndex) {
		assert 0 <= rowDataIndex: rowDataIndex;
		assert 0 <= colDataIndex: colDataIndex;
		return editorController.isCellEditable(rowDataIndex, colDataIndex);
	}
	/**
	 * 指定したセルの編集を開始します。
	 * @param	rowViewIndex	列表示番号(viewIndex)(0..)
	 * @param	colViewIndex	行表示番号(viewIndex)(0..)
	 * @param event	イベント。編集を開始するかどうかの判断に使われる。nullのとき、イベントのチェックは行わない。
	 * @return	true:編集を開始した / false:編集を開始しなかった
	 */
	public boolean editCellAt(int rowViewIndex, int colViewIndex, EventObject event) {
		logger.debug("editCellAt() enter");
		boolean rc = editorController.editCellAt(rowViewIndex, colViewIndex, event);
		logger.debug("editCellAt() exit rc="+rc);
		return rc;
	}
	
	/**
	 * セルの編集を行うコンポーネントを返します.
	 * isCellEditable()がfalseを返す場合は、呼ばれません。
	 * @param	value	データの値(null可)
	 * @param	rowDataIndex	列データ番号(dataIndex)(0..)
	 * @param	colDataIndex	行データ番号(dataIndex)(0..)
	 * @return	セル・エディタ(null可)
	 */
	public JSpreadCellEditor getCellEditor(Object value, 
			int rowDataIndex, int colDataIndex) {
		return editorController.getCellEditor(value, rowDataIndex, colDataIndex);
	}	
	/**
	 * セルの編集を終了します.
	 * 戻り値がtrueの場合は、編集中の値はセルに格納されます。
	 * 編集された値が不正な場合など、編集を終了させたくない場合はfalseを返します。
	 * @return	true:編集を終了した / false:終了できなかった
	 */
	public boolean stopCellEditing() {
		return editorController.stopCellEditing();
	}
	/**
	 * セルの編集をキャンセルします.
	 * 編集中の値はセルには格納されません。
	 */
	public void cancelCellEditing() {
		editorController.cancelCellEditing();
	}
	/**
	 * 編集中かどうかを返します.
	 * @return	true:編集中 / false:編集中でない
	 */
	public boolean isEditing() {
		return editorController.isEditing();
	}
	/**
	 * 編集中の場合、セルエディタのコンポーネントを返します。
	 * 編集中でない場合、nullを返します。
	 * @return	セルエディタのコンポーネント.null可
	 */
	public JComponent getEditorComponent() {
		return editorController.getEditorComponent();
	}
	//---------------------------------------------------------------------
	// プロパティ関係
	//---------------------------------------------------------------------
	public static void putClassProperty(String key, Object value) {
		JSpreadProperty.classProperties.put(key, value);
	}
	public void putObjectProperty(String key, Object value) {
		properties.objectProperties.put(key, value);
	}
	//---------------------------------------------------------------------
	// セルのフォーカスの位置関係 
	//---------------------------------------------------------------------
	public int getFocusColumn() { return sheet.focusCol; }
	public void setFocusColumn(int column) { sheet.focusCol = column; }
	public int getFocusRow() 	{ return sheet.focusRow; }
	public void setFocusRow(int row) { sheet.focusRow = row; }
	public int getCurrentColumn() 	{ return sheet.curCol; }
	public void setCurrentColumn(int column) { sheet.curCol = column; } 
	public int getCurrentRow()		{ return sheet.curRow; }
	public void setCurrentRow(int row) { sheet.curRow = row; } 
	
	/**
	 * 列、または行の表示されているセルの数を返します。
	 * 列、行は非表示に出来るので、実際のデータのサイズと表示されているサイズは異なることがあります。
	 * @param header	ROW、またはCOLUMN
	 * @return	表示されている行または列の数を返します。
	 */
	public int getViewCount(int header) {
		assert header == COLUMN || header == ROW: header;
		return header == COLUMN ? sheet.colHeader.getViewCount() : sheet.rowHeader.getViewCount();
	}
	/**
	 * ヘッダの幅のリサイズ方法を指定します。
	 * @param header	COLUMN,ROW,BOTHのいずれかを指定します。
	 * @param mode		AUTO_RESIZE_OFF,AUTO_RESIZE_NEXT_COLUMN,
	 * AUTO_RESIZE_LAST_COLUMN のいずれかを指定します。
	 */ 			
	public void setAutoResizeMode(int header, int mode) {
		assert header == COLUMN || header == ROW || header == BOTH: header; 
		assert  mode == AUTO_RESIZE_OFF ||
				mode == AUTO_RESIZE_NEXT_COLUMN ||
				mode == AUTO_RESIZE_LAST_COLUMN:
				mode;
		if ((header & COLUMN) != 0) {
			sheet.colHeader.autoResizeMode = mode;
		}
		if ((header & ROW) != 0) {
			sheet.rowHeader.autoResizeMode = mode;
		}
	}
	/**
	 * ヘッダを表示するかどうか取得します。
	 * @param header	COLUMN,ROWのいずれかを指定します。
	 * @return	trueのときヘッダを表示します。
	 */
	public boolean getHeaderVisible(int header) {
		assert header == COLUMN || header == ROW: header;
		return header == COLUMN ? sheet.colHeaderVisible : sheet.rowHeaderVisible;
	}
	/**
	 * ヘッダを表示するかどうか指定します。
	 * このメソッドを呼んでも直ちにビューは変わりません。呼び出した後に、rebuildView()を呼んで下さい。
	 * @param header	COLUMN,ROW,BOTHのいずれかを指定します。
	 * @param visible	trueのときヘッダを表示します。
	 */
	public void setHeaderVisible(int header, boolean visible) {
		assert header == ROW || header == COLUMN || header == BOTH: header;
		if ((header & ROW) != 0) {
			sheet.rowHeaderVisible = visible;
		}
		if ((header & COLUMN) != 0) {
			sheet.colHeaderVisible = visible;
		}
	}
	//---------------------------------------------------------------------
	// セルの選択関係 
	//---------------------------------------------------------------------
	/**
	 * セルの選択状態を設定します。
	 * @param	rowViewIndex	列表示番号(viewIndex)(0..)
	 * @param	colViewIndex	行表示番号(viewIndex)(0..)
	 * @param 	sel				選択状態(true/false)	
	 */
	public void setCellSelection(int rowViewIndex, int colViewIndex, boolean sel) {
		assert 0 <= rowViewIndex && rowViewIndex < getViewCount(ROW): rowViewIndex;
		assert 0 <= colViewIndex && colViewIndex < getViewCount(COLUMN): colViewIndex;
		sheet.cellSelection[rowViewIndex][colViewIndex] = sel;
	}
	/**
	 * セルの選択状態を取得します。
	 * @param	rowViewIndex	列表示番号(viewIndex)(0..)
	 * @param	colViewIndex	行表示番号(viewIndex)(0..)
	 * @return	選択状態(true/false)
	 */
	public boolean getCellSelection(int rowViewIndex, int colViewIndex) {
		assert 0 <= rowViewIndex && rowViewIndex < getViewCount(ROW): rowViewIndex;
		assert 0 <= colViewIndex && colViewIndex < getViewCount(COLUMN): colViewIndex;
		return sheet.getSelection(rowViewIndex, colViewIndex);
//		return sheet.cellSelection[rowViewIndex][colViewIndex];
	}
	/**
	 * ヘッダの選択状態を取得します。
	 * @param	orientation	COLUMN,ROWのいずれかを指定します。
	 * @param	viewIndex	表示番号(viewIndex)(0..)
	 * @return	選択状態(true/false)
	 */
	public boolean getHeaderSelection(int orientation, int viewIndex) {
		assert orientation == ROW || orientation == COLUMN: orientation;
		assert 0 <= viewIndex && viewIndex < getViewCount(orientation): viewIndex;
		return sheet.getHeaderSelection(orientation, viewIndex);
	}
	/**
	 * 指定されたヘッダの選択された表示番号の配列を返します。
	 * 選択されていないときには要素数が０の配列を返します（nullは返しません）。
	 * @param	orientation	COLUMN,ROWのいずれかを指定します。
	 * @return	選択された表示番号の配列。(not null,[viewIndex])
	 */
	public int[] getHeaderSelections(int orientation) {
		assert orientation == ROW || orientation == COLUMN: orientation;
		// 選択しているヘッダの数を数える。
		int count = 0;
		for (int i=0; i<getViewCount(orientation); i++) {
			if (getHeaderSelection(orientation, i)) count++;
		}
		// 選択しているヘッダの表示indexを集める。
		int[] result = new int[count];
		int j = 0;
		for (int i=0; i<getViewCount(orientation); i++) {
			if (getHeaderSelection(orientation, i)) result[j++] = i;
		}
		return result;
	}
	public void addSelectionListener(JSpreadSelectionListener listener) {
		logger.debug(listener);
		if (selectionListeners == null) {
			selectionListeners = new ArrayList();
		}
		selectionListeners.add(listener);
	}
	void fireSelectionListener(JSpreadSelectionEvent e) {
		assert e != null; 
		if (selectionListeners == null) return;
		for (int i=0; i<selectionListeners.size(); i++) {
			JSpreadSelectionListener listener = (JSpreadSelectionListener) selectionListeners.get(i);
			logger.debug(listener);
			listener.valueChanged(e);
		}
	}
	
	
	
	
	
	
	
	
	// TODO:再描画()名前を変える
	public void 再描画() {
		logger.debug("enter 再描画()");
		this.repaint();
		logger.debug("exit");
	}
	// TODO:再配置()名前を変える
	public void 再配置() {
		logger.debug("enter 再配置()");
		this.revalidate();
		// JScrollPaneのとき、なぜか内部のビューに再配置が行われないので、明示的に呼ぶ。
		if (this.getComponent(0) instanceof JScrollPane) {
			JScrollPane sp = (JScrollPane) this.getComponent(0);
			JComponent c = (JComponent) sp.getViewport().getView();
			c.revalidate();
		}
		再描画();
		logger.debug("exit");
	}
	/** ビューを作り直します. */
//	public void rebuildView() {
//		logger.debug("enter ビューの作り直し()");
//		controller.createView(this);
//		再配置();
//		logger.debug("exit");
//	}
	//---------------------------------------------------------------------
	// ツールチップ関係 
	//---------------------------------------------------------------------
	/**
	 * ツールチップを有効にするには、このメソッドを呼びます.
	 * setToolTipText(String)は使用しないでください。
	 * @param panename	窓の名前。"CellPane"など
	 * @param enabled
	 */
	public void setToolTipEnabled(String panename, boolean enabled) {
		assert panename != null;
		getComponent(panename).setToolTipText(enabled ? "JSpread" : null);
	}
	/**
	 * ツールチップを有効にしたときは、このメソッドをオーバーライドしてください。
	 * @param panename	窓の名前。"CellPane"など
	 * @param event		マウスイベント
	　* @return	ツールチップのテキスト。表示しない場合は、nullを返してください。HTML形式にも対応しています。
	 */
	protected String getToolTipText(String panename, MouseEvent event) {
		return null;
	}
	
	
	public boolean inv() {
		return sheet.inv();
	}
	/**
	 * 自己診断の結果を返します（デバッグ用）.
	 * JSpreadの内部状態を文字列として返します。
	 * @return	JSpreadの内部状態
	 */
	public String toString() {
		StringBuffer sb = new StringBuffer();
		// 現在のシートに関する情報
		sb.append("focusCol="+this.getFocusColumn()+
				" focusRow="+this.getFocusRow()+"\n");
		sb.append("curCol="+this.getCurrentColumn()+
				" curRow="+this.getCurrentRow()+"\n");
		// 各ビューのサイズ
		JComponent comp = this.getComponent("CellPane");			
		sb.append("CellPane.bounds="+comp.getBounds()+"\n");
		comp = this.getComponent("RowHeaderPane");			
		sb.append("RowHeaderPane.bounds="+comp.getBounds()+"\n");

		sb.append("inv="+this.inv());
//		sb.append(panel.toString());
		return sb.toString();
	}

	public static void main(String[] args) {
		JSpread spread = new JSpread(10, 10);
		JFrame frame = new JFrame("HelloJSpread");
		spread.setValueAt("Hello", 0, 0);
		spread.setValueAt("JSpread", 0, 1);
		spread.createView();
		spread.doLayout();
		//spread.createScrollView();
		frame.getContentPane().setLayout(new BorderLayout());
		frame.getContentPane().add(spread, BorderLayout.CENTER);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(480, 200);
		frame.setVisible(true);
	}	
}

