
package jp.riken.brain.ni.samuraigraph.figure;

import java.awt.Cursor;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JComponent;
import javax.swing.JPopupMenu;

import jp.riken.brain.ni.samuraigraph.base.SGAxis;
import jp.riken.brain.ni.samuraigraph.base.SGData;
import jp.riken.brain.ni.samuraigraph.base.SGDrawingElement;
import jp.riken.brain.ni.samuraigraph.base.SGICopiable;
import jp.riken.brain.ni.samuraigraph.base.SGIDisposable;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElement;
import jp.riken.brain.ni.samuraigraph.base.SGIMovable;
import jp.riken.brain.ni.samuraigraph.base.SGISelectable;
import jp.riken.brain.ni.samuraigraph.base.SGIUndoable;
import jp.riken.brain.ni.samuraigraph.base.SGIVisible;
import jp.riken.brain.ni.samuraigraph.base.SGProperties;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2d;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2f;
import jp.riken.brain.ni.samuraigraph.base.SGUndoManager;
import jp.riken.brain.ni.samuraigraph.base.SGUtility;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityNumber;

import org.w3c.dom.Document;
import org.w3c.dom.Element;


/**
 * An object in the figure.
 */
public abstract class SGFigureElement //extends JPanel
	implements SGIFigureElement, SGIFigureElementConstants
{

	private int mMode = MODE_DISPLAY;
	
	public void setMode( final int mode )
	{
		this.mMode = mode;
	}

	public int getMode()
	{
		return this.mMode;
	}


	/**
	 * The list of "visible" and "selectable" child objects.
	 */
	protected List mChildList = new ArrayList();


	private JComponent mComponent = null;


	public JComponent getComponent()
	{
		return this.mComponent;
	}


	public void setComponent( JComponent com )
	{
		this.mComponent = com;
	}

	public void repaint()
	{
//System.out.println("FigureElement");
		this.mComponent.repaint();
	}


	/**
	 * X-coordinate of the rectangle of the graph area.
	 */
	protected float mGraphRectX;

	
	/**
	 * Y-coordinate of the rectangle of the graph area.
	 */
	protected float mGraphRectY;

	
	/**
	 * Width of the rectangle of the graph area.
	 */
	protected float mGraphRectWidth;

	
	/**
	 * Height of the rectangle of the graph area.
	 */
	protected float mGraphRectHeight;


	/**
	 * Magnification.
	 */
	protected float mMagnification = 1.0f;


	/**
	 * 
	 */
	protected Rectangle2D mViewBounds = null;


	/**
	 * List of the SGData objects.
	 */
	protected List mDataList = new ArrayList();


	/**
	 * List of action listeners.
	 */
	protected List mActionListenerList = new ArrayList();


	/**
	 * 
	 */
	private Cursor mCursor = null;



	/**
	 * Owner of property dialogs.
	 */
	protected Frame mDialogOwner = null;



	protected Point mPressedPoint = null;


	protected boolean mDraggableFlag = false;



	/**
	 * Default constructor.
	 */
	public SGFigureElement()
	{
		super();
	}


	public void finalize()
	{
//		System.out.println("finalize:"+this);
	}


	/**
	 * 
	 */
	public void dispose()
	{
		this.mComponent = null;

		this.mActionListenerList.clear();
		this.mActionListenerList = null;

		this.mDataList.clear();
		this.mDataList = null;

		List cList = this.mChildList;
		for( int ii=0; ii<cList.size(); ii++ )
		{
			Object obj = cList.get(ii);
			if( obj instanceof SGIDisposable )
			{
				SGIDisposable d = (SGIDisposable)obj;
				d.dispose();
			}
		}
		this.mChildList.clear();
		this.mChildList = null;

		this.mUndoManager.dispose();
		this.mUndoManager = null;

		this.mDialogOwner = null;
		this.mViewBounds = null;
		this.mPressedPoint = null;
		this.mCursor = null;
	}


	/**
	 * 
	 * @param data
	 * @param name
	 * @return
	 */
	public boolean addData( final SGData data, final String name )
	{
		this.addDataToList(data);
		return true;
	}


	private void addDataToList( SGData data )
	{
		this.mDataList.add(data);
	}



	// couner for ID number
	private int mIDCounter = 1;


	// add object to the list
	protected void addToList( ChildObject el )
	{
		this.mChildList.add( el );

		el.setID( this.mIDCounter );
		this.mIDCounter++;
	}


	//
	protected ChildObject getChildObject( final int id )
	{
		List list = this.mChildList;
		for( int ii=0; ii<list.size(); ii++ )
		{
			ChildObject el = (ChildObject)list.get(ii);
			if( el.getID() == id )
			{
				return el;
			}
		}
		return null;
	}


	/**
	 * Remove a child object.
	 * @param obj - An object to be removed.
	 * @return - true if this object exists
	 */
	protected boolean removeChild( final Object obj )
	{
		if( obj instanceof SGIDisposable )
		{
			SGIDisposable d = (SGIDisposable)obj;
			d.dispose();
		}
		return this.mChildList.remove(obj);
	}



	/**
	 * Remove a data object from the list.
	 */
	public boolean removeData( final SGData data )
	{
		List dList = this.mDataList;
		for( int ii=dList.size()-1; ii>=0; ii-- )
		{
			final SGData data_ = (SGData)dList.get(ii);
			if( data_.equals(data) )
			{
				dList.remove(ii);
			}
		}
		
		return true;
	}



	/**
	 * Returns a list of data.
	 */
	public List getDataList()
	{
		return new ArrayList( this.mDataList );
	}



	/**
	 * 
	 */
	public boolean getMarginAroundGraphRect(
		final SGTuple2f topAndBottom,
		final SGTuple2f leftAndRight )
	{

		if( topAndBottom==null || leftAndRight==null )
		{
			return false;
		}

		topAndBottom.clear();
		leftAndRight.clear();

		return true;
	}



	/**
	 * 
	 */
	public boolean setGraphRect(
		final float x,
		final float y,
		final float width,
		final float height )
	{
		this.setGraphRectLocation(x,y);
		this.setGraphRectSize(width,height);
		return true;
	}


	/**
	 * 
	 */
	public boolean setGraphRect( Rectangle2D rect )
	{
		return this.setGraphRect(
			(float)rect.getX(),
			(float)rect.getY(),
			(float)rect.getWidth(),
			(float)rect.getHeight()
		);
	}



	/**
	 * 
	 */
	public boolean setGraphRectLocation( final float x, final float y )
	{
		this.mGraphRectX = x;
		this.mGraphRectY = y;
		return true;
	}


	/**
	 * 
	 */
	public boolean setGraphRectSize( final float width, final float height )
	{
		if( width<0.0 || height<0.0 )
		{
			throw new IllegalArgumentException("width<0.0 || height<0.0");
		}
		this.mGraphRectWidth = width;
		this.mGraphRectHeight = height;
		return true;
	}



	/**
	 * 
	 */
	public Rectangle2D getGraphRect()
	{
		final Rectangle2D rect = new Rectangle2D.Float(
			this.mGraphRectX,
			this.mGraphRectY,
			this.mGraphRectWidth,
			this.mGraphRectHeight );
		
		return rect;
	}



	/**
	 * 
	 */
	public boolean isInsideGraphArea( final SGTuple2f pos )
	{
		final float x = pos.x;
		final float y = pos.y;


		final float gx = this.mGraphRectX;
		final float gy = this.mGraphRectY;
		final float gw = this.mGraphRectWidth;
		final float gh = this.mGraphRectHeight;


		if( x < gx || x > gx + gw )
		{
			return false;
		}

		if( y < gy || y > gy + gh )
		{
			return false;
		}

		return true;		
	}



	/**
	 * 
	 */
	public boolean isInsideGraphArea( final Point2D pos )
	{
		return this.isInsideGraphArea( (int)pos.getX(), (int)pos.getY() );
	}



	/**
	 * 
	 */
	public boolean isInsideGraphArea( final int x, final int y )
	{
		final SGTuple2f pos = new SGTuple2f( x, y );
		return this.isInsideGraphArea( pos );
	}


	/**
	 * 
	 */
	public boolean setDialogOwner( final Frame frame )
	{
		this.mDialogOwner = frame;
		return true;
	}



	/**
	 * 
	 */
	public float getMagnification()
	{
		return this.mMagnification;
	}


	/**
	 * 
	 */
	public boolean setMagnification( final float mag )
	{
		this.mMagnification = mag;
		return true;
	}


	/**
	 * 
	 */
	public Cursor getFigureElementCursor()
	{
		return this.mCursor;
	}


	/**
	 * 
	 * @param type
	 */
	protected void setMouseCursor( int type )
	{
		this.mCursor = Cursor.getPredefinedCursor(type);
		this.notifyChangeCursor();
	}


	/**
	 * 
	 * @param cur
	 */
	protected void setMouseCursor( Cursor cur )
	{
		this.mCursor = cur;
		this.notifyChangeCursor();
	}

	
	/**
	 *
	 */
	public void notifyChangeCursor()
	{
		this.notifyToListener( SGIFigureElement.NOTIFY_CHANGE_CURSOR );
	}


	/**
	 * Zoom in/out this component.<BR>
	 */
	public boolean zoom( final float ratio )
	{
		this.setMagnification(ratio);
		return true;
	}



//	/**
//	 * Synchronize this component to the other component. <BR>
//	 * @param element the SGFigureElement object whose property has changed.
//	 * @return true:succeeded, false:failed
//	 */
//	public abstract boolean synchronize( SGIFigureElement element );
//
//
//	/**
//	 * 
//	 */
//	public abstract boolean synchronizeArgument( SGIFigureElement element );



	/**
	 * 
	 */
	public boolean setViewBounds( final Rectangle2D rect )
	{
		this.mViewBounds = rect;
		return true;
	}


	/**
	 * 
	 * @return
	 */
	public Rectangle2D getViewBounds()
	{
		return this.mViewBounds;
	}



//	/**
//	 * 
//	 */
//	public void paintComponent( final Graphics g )
//	{
////		super.paintComponent(g);
//		this.paintGraphics( g, true );
//	}


	/**
	 * 
	 * @param el
	 * @param data
	 * @return
	 */
	public boolean createDataObject( Element el, SGData data )
	{
		if( el==null || data==null )
		{
			throw new IllegalArgumentException();
		}
		this.mDataList.add(data);		
		return true;
	}
	


	/**
	 * 
	 * @param el
	 * @param mod
	 * @return
	 */
	protected boolean updateFocusedObjectsList(
		final SGISelectable el, final int mod )
	{
		final ArrayList fList = this.getFocusedObjectsList();

		// Neither CTRL key nor SHIFT key is pressed.
		if( ( ( mod & InputEvent.CTRL_MASK ) == 0 )
			& ( ( mod & InputEvent.SHIFT_MASK ) == 0 ) )
		{
			// If the list already contains this object.
			if( fList.contains(el) )
			{
				// There is nothing to do.
			}
			else
			{
				// set all objects unselected
//				this.clearFocusedObjects();
				this.notifyToListener( SGIFigureElement.CLEAR_FOCUSED_OBJECTS );
				
				// set given object selected
				el.setSelected(true);
			}

		}
		// otherwise
		else
		{
			// If the list already contains this object.
			el.setSelected( !el.isSelected() );
		}

		return true;
	}


	/**
	 * Update the focused object with a mouse event.
	 * @param el - a selectable object
	 * @param e - a mouse event
	 * @return
	 */
	protected boolean updateFocusedObjectsList(
		final SGISelectable el, final MouseEvent e )
	{
		final int mod = e.getModifiers();
		return this.updateFocusedObjectsList( el, mod );
	}
	
	
	/**
	 * 
	 */
	public void translateFocusedObjects( final int dx, final int dy )
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			Object obj = list.get(ii);
			if( obj instanceof SGIMovable )
			{
				SGIMovable m = (SGIMovable)obj;
				m.translate(dx,dy);
			}
		}
	}

	
	
	/**
	 * 
	 * @return
	 */
	public boolean clearFocusedObjects()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGISelectable s = (SGISelectable)list.get(ii);
			s.setSelected(false);
		}
		return true;
	}




	/**
	 * 
	 */
	public void addActionListener( final ActionListener listener )
	{
		for( int ii=0; ii<this.mActionListenerList.size(); ii++ )
		{
			final ActionListener el = (ActionListener)this.mActionListenerList.get(ii);
			if( el.equals(listener) )
			{
				return;
			}
		}
		this.mActionListenerList.add(listener);
	}


	/**
	 * 
	 */
	public void removeActionListener( ActionListener listener )
	{
		for( int ii=this.mActionListenerList.size()-1; ii>=0; ii-- )
		{
			final ActionListener el = (ActionListener)this.mActionListenerList.get(ii);
			if( el.equals(listener) )
			{
				this.mActionListenerList.remove(listener);
			}
		}
	}


	/**
	 *
	 */
	public void notifyChange()
	{
		this.notifyToListener( SGIFigureElement.NOTIFY_CHANGE );
	}


	/**
	 *
	 */
	public void notifyChangeOnUndo()
	{
		this.notifyToListener( SGIFigureElement.NOTIFY_CHANGE_ON_UNDO );
	}


	/**
	 * 
	 * @return
	 */
	public boolean setPropertiesOfSelectedObjects()
	{
		this.notifyToListener( SGIFigureElement.SET_PROPERTIES_OF_SELECTED_OBJECTS );
		return true;
	}


	/**
	 * 
	 */
	public void notifyToListener( final String command )
	{
		for( int ii=0; ii<this.mActionListenerList.size(); ii++ )
		{
			final ActionListener el = (ActionListener)this.mActionListenerList.get(ii);
			el.actionPerformed( this.getActionEvent( command ) );
		}
	}


	/**
	 * 
	 */
	private ActionEvent getActionEvent( final String command )
	{
		return new ActionEvent( this, 0, command );
	}



	/**
	 * 
	 */
	public float getGraphRectX()
	{
		return this.mGraphRectX;
	}


	/**
	 * 
	 */
	public float getGraphRectY()
	{
		return this.mGraphRectY;
	}


	/**
	 * 
	 */
	public float getGraphRectWidth()
	{
		return this.mGraphRectWidth;
	}


	/**
	 * 
	 */
	public float getGraphRectHeight()
	{
		return this.mGraphRectHeight;
	}



	/**
	 * 値に対応するグラフ内での座標値を計算
	 * flagはtrueなら水平方向、falseなら垂直方向
	 */
	protected float calcLocation(
		final double value, final SGAxis axis, final boolean flag )
	{

		final SGTuple2d range = axis.getRange();
		final double min = range.x;
		final double max = range.y;

		final int type = axis.getScaleType();
		final boolean invcoord = axis.isInvertCoordinates();

		// グラフ領域内でのデータ点の比率を計算
		float ratio = 0.0f;
		if( type == SGAxis.LINEAR_SCALE )
		{
			if( invcoord )
				ratio = (float)( (max-value)/(max-min));
			else
				ratio = (float)( (value-min)/(max-min) );
		}
		else if( type == SGAxis.LOG_SCALE )
		{
			if( value<=0.0 )
			{
				return Float.NaN;
			}

			final double logMin = Math.log(min);
			final double logMax = Math.log(max);
			final double logValue = Math.log(value);
			
			if( invcoord )
				ratio = (float)( ( logMax - logValue )/( logMax - logMin ) );
			else
				ratio = (float)( ( logValue - logMin)/( logMax - logMin ) );
		}


		// グラフ全体における位置を計算
		float pos = 0.0f;
		if( flag )
		{
			pos = ( this.mGraphRectX + ratio * this.mGraphRectWidth );
		}
		else
		{
			pos = ( this.mGraphRectY + (1.0f - ratio) * this.mGraphRectHeight );
		}

		return pos;

	}



	/**
	 * 
	 */
	protected double calcValue(
		final float pos, final SGAxis axis, final boolean flag )
	{

		final SGTuple2d range = axis.getRange();
		final double min = range.x;
		final double max = range.y;

		if( min>=max )
		{
			throw new IllegalArgumentException("min>=max");
		}

		final int type = axis.getScaleType();
		final boolean invcoord = axis.isInvertCoordinates();

		// グラフ領域内での点の比の値を計算
		float ratio;
		if( flag )
		{
			if ( invcoord )
				ratio = 1.0f - ( pos - this.mGraphRectX )/this.mGraphRectWidth;
			else
				ratio = ( pos - this.mGraphRectX )/this.mGraphRectWidth;
		}
		else
		{
			if ( invcoord )
				ratio = ( pos - this.mGraphRectY )/this.mGraphRectHeight;
			else
				ratio = 1.0f - ( pos - this.mGraphRectY )/this.mGraphRectHeight;
		}

		// 値を計算
		double value = 0.0;
		if( type == SGAxis.LINEAR_SCALE )
		{
			value = min + ratio*(max-min);
		}
		else if( type == SGAxis.LOG_SCALE )
		{
			if( min>=0.0 & max>=0.0f )
			{
				final double logMin = Math.log(min);
				final double logMax = Math.log(max);
				value = Math.exp( logMin + ratio*(logMax-logMin) );
			}
			else
			{
				value = Double.NaN;
			}
		}

		return value;
	}



	/**
	 * 
	 * @return
	 */
	protected boolean showPopupMenu( JPopupMenu menu, final int x, final int y )
	{
		menu.show( this.getComponent(), x, y );
		return true;
	}



	/**
	 * 
	 */
	public abstract SGProperties getProperties();

	
	/**
	 * initialize compatible properties for previous property file.
	 * @return
	 */
	public boolean initCompatibleProperty()
	{
		return true;
	}

	/**
	 * @uml.property  name="properties"
	 */
	public abstract boolean setProperties( final SGProperties p );


	/**
	 * 
	 * @return
	 */
	public SGProperties getMemento()
	{
		return this.getProperties();
	}


	/**
	 * 
	 * @param p
	 * @return
	 */
	public boolean setMemento( SGProperties p )
	{
		return this.setProperties(p);
	}


	/**
	 * 
	 * @return
	 */
	public boolean isUndoable()
	{
		return this.mUndoManager.isUndoable();
	}

	
	/**
	 * 
	 * @return
	 */
	public boolean isRedoable()
	{
		return this.mUndoManager.isRedoable();
	}


	//
	private SGUndoManager mUndoManager = new SGUndoManager( this );


	/**
	 * 
	 */
	public boolean initPropertiesHistory()
	{
		return this.mUndoManager.initPropertiesHistory();
	}


	/**
	 * 
	 */
	protected boolean updateHistory( ArrayList list )
	{
		return this.mUndoManager.updateHistory( list );
	}


	/**
	 * 
	 */
	public boolean undo()
	{
		return this.mUndoManager.undo();
	}

	
	/**
	 * 
	 */
	public boolean redo()
	{
		return this.mUndoManager.redo();
	}


	/**
	 * 
	 */
	public boolean setMementoBackward()
	{
		return this.mUndoManager.setMementoBackward();
	}


	/**
	 * 
	 */
	public boolean setMementoForward()
	{
		return this.mUndoManager.setMementoForward();
	}

	/**
	 * 
	 * @return
	 */
	protected List getMementoList()
	{
		return 	this.mUndoManager.getMementoList();
	}
	/**
	 * 
	 *
	 */
	public void notifyToRoot()
	{
		this.notifyToListener( SGIFigureElement.NOTIFY_CHANGE_TO_ROOT );
	}


	public void setChanged( final boolean b )
	{
		this.mUndoManager.setChanged(b);
	}
	
	
	/**
	 * 
	 * @param listAll
	 * @param listVisible
	 * @return
	 */
	protected boolean setVisibleList(
		final List listAll, final List listVisible )
	{
		return SGUtility.setVisibleList( listAll, listVisible );
	}



	/**
	 * 
	 * @param el
	 * @param list
	 * @return
	 */
	protected boolean hideObject( final SGDrawingElement el )
	{
		el.setVisible(false);
		notifyChange();
		this.setChanged(true);
		this.notifyToRoot();
		return true;
	}

	

	/**
	 * 
	 *
	 */
	public boolean hideSelectedObjects()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGISelectable s = (SGISelectable)list.get(ii);
			if( this.hideSelectedObject(s) == false )
			{
				return false;
			}
		}

		if( list.size()!=0 )
		{
			this.setChanged(true);
			notifyChange();
		}

		return true;
	}

	
	/**
	 * 
	 * @return
	 */
	public abstract String getTagName();

	
	/**
	 * 
	 * @param el
	 * @return
	 */
	public abstract boolean writeProperty( final Element el );

	
	/**
	 * 
	 * @param document
	 * @return
	 */
	protected Element createThisElement( final Document document )
	{
		Element el = document.createElement( this.getTagName() );

		// set common properties
		if( this.writeProperty(el) == false )
		{
			return null;
		}

		return el;
	}


	/**
	 * 
	 */
	protected boolean mSymbolsVisibleFlagAroundFocusedObjects = true;

	
	/**
	 *
	 *
	 */
	public void setSymbolsVisibleAroundFocusedObjects( final boolean b )
	{
		this.mSymbolsVisibleFlagAroundFocusedObjects = b;
	}


	/**
	 * 
	 */
	protected boolean mSymbolsVisibleFlagAroundAllObjects = false;


	/**
	 * 
	 * @param b visibility
	 */
	public void setSymbolsVisibleAroundAllObjects( final boolean b )
	{
		this.mSymbolsVisibleFlagAroundAllObjects = b;
	}


	
	/**
	 * 
	 */
	protected double getNumberInRangeOrder(
		final double value, final SGAxis axis )
	{
		final int type = axis.getScaleType();
		if( type == SGAxis.LINEAR_SCALE )
		{
			return SGUtilityNumber.getNumberInRangeOrder(
				value, axis.getMinValue(), axis.getMaxValue(),
				AXIS_SCALE_EFFECTIVE_DIGIT );
		}
		else if( type == SGAxis.LOG_SCALE )
		{
			final int order = SGUtilityNumber.getOrder(value);
			final double min = SGUtilityNumber.getPowersOfTen(order);
			final double max = min*10.0;
			return SGUtilityNumber.getNumberInRangeOrder(
				value, min, max,
				AXIS_SCALE_EFFECTIVE_DIGIT );
		}
		else
		{
			throw new IllegalArgumentException("scale type is invalid");
		}
	}

	
	/**
	 * 
	 * @param list
	 * @return
	 */
	protected ArrayList getCopyList( ArrayList list )
	{
		ArrayList cList = new ArrayList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGICopiable obj = (SGICopiable)list.get(ii);
			cList.add( obj.copy() );
		}
		return cList;
	}



	/**
	 * 
	 * @return
	 */
	public ArrayList getFocusedObjectsList()
	{
		ArrayList list = new ArrayList();
		this.getFocusedObjectsList(list);
		return list;
	}


	/**
	 * Returns the list of focused copiable objects.
	 * @return a list of focused copiable objects
	 */
	protected ArrayList getCopiableFocusedObjectsList()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			Object obj = list.get(ii);
			if( ( obj instanceof SGICopiable ) == false )
			{
				list.remove(ii);
			}
		}
		return list;
	}



	/**
	 * Offset x-value of the location for duplicated objects.
	 */
	public static final int OFFSET_DUPLICATED_OBJECT_X = 10;


	/**
	 * Offset y-value of the location for duplicated objects.
	 */
	public static final int OFFSET_DUPLICATED_OBJECT_Y = 10;



	/**
	 * Create copies of the focused objects.
	 * @return
	 */
	public boolean duplicateFocusedObjects()
	{
		return true;
	}


	/**
	 * Duplicate the focused objects.
	 * @return list of duplicated objects
	 */
	protected ArrayList duplicateObjects()
	{
		// get the list of copiable focused objects
		ArrayList list = this.getCopiableFocusedObjectsList();

		// create copies
		ArrayList cList = this.getCopyList( list );

		// clear all focused objects
		this.clearFocusedObjects();		

		return cList;		
	}



	/**
	 * Returns the list of copied objects.
	 * @return list of copied objects
	 */
	public ArrayList getCopiedObjectsList()
	{
		// get the list of copiable focused objects
		ArrayList list = this.getCopiableFocusedObjectsList();

		// create copies
		return this.getCopyList( list );
	}


	/**
	 * Paste the objects.
	 * @param list of the objects to be pasted
	 * @return true:succeeded, false:failed
	 */
	public boolean paste( ArrayList list )
	{
		return true;
	}



	/**
	 * Cut focused copiable objects.
	 * @return a list of cut objects
	 */
	public ArrayList cutFocusedObjects()
	{
		ArrayList list = this.getCopiedObjectsList();
		this.hideSelectedObjects();
		return list;
	}


	/**
	 * 
	 */
	public SGProperties getDataProperties( SGData data )
	{
		return null;
	}


	/**
	 * 
	 * @param data
	 * @param name
	 * @param p
	 * @return
	 */
	public boolean addData( final SGData data, final String name, final SGProperties p )
	{
		this.addDataToList(data);
		return true;
	}



	/**
	 * Calculate the step value from axis.
	 * @param axis
	 * @return
	 */
	protected double calcStepValue( SGAxis axis )
	{
		SGTuple2d range = axis.getRange();
	
		// minimum and maximum values of axis range
		final double min = range.x;
		final double max = range.y;
		final double width = max - min;

		// get the order of axis range
		final int order = SGUtilityNumber.getOrder(width);
		final double power = SGUtilityNumber.getPowersOfTen( order );

		final double value = width - 0.10*power;
		final int order_ = SGUtilityNumber.getOrder(value);
		final double power_ = SGUtilityNumber.getPowersOfTen( order_ );
		final double ratio = value/power_;

		double increment = 0.0;
		if( ratio<=1.1 )
		{
			increment = 0.20*power_;
		}
		else if( 1.1<ratio & ratio<=2.0 )
		{
			increment = 0.50*power_;
		}
		else if( 2.0<ratio & ratio<=6.0 )
		{
			increment = 1.0*power_;
		}
		else if( 6.0<ratio )
		{
			increment = 2.0*power_;
		}

		increment = getNumberInRangeOrder( increment, axis );
	
		return increment;
	}


	/**
	 * 
	 * @return
	 */
	public ArrayList getPropertyDialogObserverList()
	{
		return new ArrayList();
	}



	/**
	 * 
	 * @return
	 */
	public String getInstanceDescription()
	{
		return this.getClassDescription();
	}


	/**
	 * 
	 * @param x
	 * @return
	 */
	protected float getXFromGraphRectValue( final float x )
	{
		final float xx = this.getMagnification()*x + this.mGraphRectX;
		return xx;
	}


	/**
	 * 
	 * @param y
	 * @return
	 */
	protected float getYFromGraphRectValue( final float y )
	{
		final float yy = this.getMagnification()*y + this.mGraphRectY;
		return yy;
	}


	/**
	 * 
	 * @param x
	 * @return
	 */
	protected float getGraphRectValueX( final float x )
	{
		final float xx = ( x - this.mGraphRectX )/this.getMagnification();
		return xx;
	}


	/**
	 * 
	 * @param y
	 * @return
	 */
	protected float getGraphRectValueY( final float y )
	{
		final float yy = ( y - this.mGraphRectY )/this.getMagnification();
		return yy;
	}



//
// Image
//

	protected abstract void paintGraphics( Graphics g, boolean clip );


	protected Image mImg = null;

	protected void updateImage()
	{
////System.out.println("updateImage");
////final long before = System.currentTimeMillis();
//
//		final int w = this.getComponent().getWidth();
//		final int h = this.getComponent().getHeight();
//
//		if( w<=0 | h<=0 )
//		{
//			return;
//		}
//
//		BufferedImage bImg = new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB );
//
//		Graphics2D g2d = bImg.createGraphics();
//		this.paintGraphics( g2d, true );
//
//		this.mImg = bImg;
//
////final long after = System.currentTimeMillis();
////System.out.println(after-before);
//
//		this.repaint();
	}


	public void paint( final Graphics g, final boolean clip )
	{
		this.paintGraphics( g, clip );
	}



	/**
	 * 
	 */
	public ArrayList getVisibleChildList()
	{
		final ArrayList list = new ArrayList();
		final ArrayList aList = new ArrayList( this.mChildList );
		for( int ii=0; ii<aList.size(); ii++ )
		{
			final SGIVisible el = (SGIVisible)aList.get(ii);
			if( el.isVisible() )
			{
				list.add(el);
			}
		}

		return list;
	}


	/**
	 * 
	 */
	protected boolean setVisibleChildList( final List listVisible )
	{
		return this.setVisibleList( this.mChildList, listVisible );
	}


	/**
	 * 
	 */
	public boolean getFocusedObjectsList( ArrayList list )
	{
		ArrayList elList = this.getVisibleChildList();
		for( int ii=0; ii<elList.size(); ii++ )
		{
			SGISelectable s = (SGISelectable)elList.get(ii);
			if( s.isSelected() )
			{
				list.add(s);
			}
		}
		return true;
	}



	/**
	 * 
	 */
	public boolean hideSelectedObject( final SGISelectable s )
	{
		SGIVisible el = (SGIVisible)s;
		el.setVisible(false);
		
		return true;
	}



	/**
	 * Move the focused objects to front or back.
	 * @param toFront - flag whether to front or back
	 * @return true:succeeded, false:failed
	 */
	public boolean moveFocusedObjects( final boolean toFront )
	{
		return this.moveFocusedObjects( toFront, this.mChildList );
	}


	/**
	 * Move the focused objects to front or back.
	 * @param toFront - flag whether to front or back
	 * @return true:succeeded, false:failed
	 */
	public boolean moveChildObject( final int id, final boolean toFront )
	{
		ChildObject el = this.getChildObject(id);
		if( el==null )
		{
			return false;
		}

		if( el.isVisible() == false )
		{
			return false;
		}

		// record the list before edited
		ArrayList objListOld = new ArrayList( this.mChildList );

		// move focused objects
		if( toFront )
		{
			this.moveObjectToHead( el, this.mChildList );
		}
		else
		{
			this.moveObjectToTail( el, this.mChildList );
		}

		final boolean ch = !this.mChildList.equals( objListOld );
		if( ch )
		{
			this.setChanged(true);
			this.notifyChange();
			this.notifyToRoot();
			this.repaint();
		}

		return true;
	}



	/**
	 * Move focused objects to back or front.
	 * @param toFront - whether move to front
	 * @param objList - the list to be modified
	 * @return
	 */
	private boolean moveFocusedObjects(
		final boolean toFront, final List objList )
	{
		// get the focused objects
		ArrayList list = this.getFocusedObjectsList();
		if( list.size()==0 )
		{
			return true;
		}

		// record the list before edited
		ArrayList objListOld = new ArrayList( objList );

		// move focused objects
		if( toFront )
		{
			for( int ii=0; ii<list.size(); ii++ )
			{
				Object el = list.get(ii);
				this.moveObjectToHead( el, objList );
			}
		}
		else
		{
			for( int ii=list.size()-1; ii>=0; ii-- )
			{
				Object el = list.get(ii);
				this.moveObjectToTail( el, objList );
			}
		}


		// set changed flag
		if( objList.equals( objListOld ) == false )
		{
			notifyChange();
			this.setChanged(true);
		}

		return true;
	}


	/**
	 * 
	 * @param obj
	 * @param list
	 * @return
	 */
	protected boolean moveObjectToHead(
		final Object obj, final List list )
	{
		return SGUtility.moveObject( obj, list, list.size()-1 );
	}



	/**
	 * 
	 * @param obj
	 * @param list
	 * @return
	 */
	protected boolean moveObjectToTail(
		final Object obj, final List list )
	{
		return SGUtility.moveObject( obj, list, 0 );
	}



	/**
	 * Returns a list of child nodes.
	 * @return a list of chid nodes
	 */
	public ArrayList getChildNodes()
	{
		return this.getVisibleChildList();
	}


	/**
	 * 
	 */
	public boolean isChanged()
	{
		return this.mUndoManager.isChanged();
	}

	/**
	 * 
	 */
	public boolean isChangedRoot()
	{
		if( this.isChanged() )
		{
			return true;
		}

		final ArrayList list = this.getVisibleChildList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			final Object obj = list.get(ii);
			if( obj instanceof SGIUndoable )
			{
				final SGIUndoable el = (SGIUndoable)obj;
				if( el.isChangedRoot() )
				{
					return true;
				}
			}
		}
		return false;
	}



	/**
	 * 
	 */
	public boolean updateHistory()
	{
		final boolean changed = this.isChanged();

		if( this.updateHistory( this.getVisibleChildList() ) == false )
		{
			return false;
		}

		// if the properties of window itself was changed,
		// remove useless figures
		if( changed )
		{
			this.removeUselessChild();
		}

		return true;
	}


	// remove useless child objects.
	protected boolean removeUselessChild()
	{
		Set set = this.getAvailableChildSet();
		if( set.size()!=0 )
		{
			boolean gc = false;
			List cList = new ArrayList( this.mChildList );
			for( int ii=cList.size()-1; ii>=0; ii-- )
			{
				Object obj = cList.get(ii);
				if( set.contains(obj) == false )
				{
					this.removeChild(obj);
					gc = true;
				}
				obj = null;
			}

			if( gc )
			{
				cList.clear();
				set.clear();
//				System.gc();
			}
		}

		return true;
	}


	/**
	 * Returns a set of available child objects.
	 * By default, this method returns an empty set.
	 * @return
	 */
	protected Set getAvailableChildSet()
	{
		return new HashSet();
	}



	/**
	 * 
	 */
	public void initUndoBuffer()
	{
		// this object
		this.mUndoManager.initUndoBuffer();
		
		List cList = this.mChildList;
		for( int ii=0; ii<cList.size(); ii++ )
		{
			Object obj = cList.get(ii);
			if( obj instanceof SGIUndoable )
			{
				SGIUndoable u = (SGIUndoable)obj;
				u.initUndoBuffer();
			}
		}
	}



	/**
	 * @author  okumura
	 */
	protected interface ChildObject extends SGIVisible
	{
		/**
		 * @return
		 * @uml.property  name="iD"
		 */
		public int getID();
		/**
		 * @param id
		 * @return
		 * @uml.property  name="iD"
		 */
		public boolean setID( final int id );
	}


	/**
	 * 
	 */
	public boolean hideChildObject( final int id )
	{
		ChildObject obj = this.getChildObject(id);
		if( obj==null )
		{
			return false;
		}

		obj.setVisible(false);

		this.clearFocusedObjects();
		this.setChanged(true);
		this.notifyChange();
		this.notifyToRoot();
		this.repaint();

		return true;
	}


	public boolean onKeyPressed( final KeyEvent e )
	{
		boolean effective = false;
//		ArrayList eList = this.getVisibleChildList();
//		for( int ii=0; ii < eList.size(); ii++ ){
//			SGIChildObject s = (SGISelectable)elList.get(ii);
//			if( s.onKeyPressed( e ) )
//				effective = true;
		return effective;
	}
}

