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

import java.awt.Cursor;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

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.SGFigure;
import jp.riken.brain.ni.samuraigraph.base.SGICopiable;
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.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;

/**
 *
 */

public abstract class SGFigureElement //extends JPanel
	implements SGIFigureElement, SGIFigureElementConstants
{

	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 ArrayList mDataList = new ArrayList();


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


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



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


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


	/**
	 * 
	 * @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);
	}


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



	/**
	 * 
	 */
	public ArrayList getDataList()
	{
		return 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 = mGraphRectX;
		final float gy = mGraphRectY;
		final float gw = mGraphRectWidth;
		final float gh = 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 SGTuple2f getISize()
	{
		final SGTuple2f size = new SGTuple2f();
		size.x = this.getComponent().getWidth();
		size.y = this.getComponent().getHeight();
		return size;
	}


	/**
	 * 
	 */
	public SGTuple2f getILocation()
	{
		final SGTuple2f pos = new SGTuple2f();
		pos.x = this.getComponent().getX();
		pos.y = this.getComponent().getY();
		return pos;
	}


	/**
	 * 
	 */
	public boolean setISize( final SGTuple2f size )
	{
		this.getComponent().setSize( (int)size.x, (int)size.y );

		return true;
	}


	/**
	 * 
	 */
	public boolean setILocation( final SGTuple2f pos )
	{
		this.getComponent().setLocation( (int)pos.x, (int)pos.y );

		return true;
	}



	/**
	 * 
	 */
	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( SGFigure.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 & MouseEvent.CTRL_MASK ) == 0 )
			& ( ( mod & MouseEvent.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( SGFigure.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 );
	}
	
	
	/**
	 * 
	 * @param list
	 * @return
	 */
	public abstract boolean getFocusedObjectsList( ArrayList list );
	


	/**
	 * 
	 */
	public void translateSelectedObjects( 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<mActionListenerList.size(); ii++ )
		{
			final ActionListener el = (ActionListener)mActionListenerList.get(ii);
			if( el.equals(listener) )
			{
				return;
			}
		}
		mActionListenerList.add(listener);
	}


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


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



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


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


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



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


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


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


	/**
	 * 
	 */
	public float getGraphRectHeight()
	{
		return 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();


		// グラフ領域内でのデータ点の比率を計算
		float ratio = 0.0f;
		if( type == SGAxis.LINEAR_TYPE )
		{
			ratio = (float)( (value-min)/(max-min) );
		}
		else if( type == SGAxis.LOG_TYPE )
		{
			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);
			ratio = (float)( ( logValue - logMin)/( logMax - logMin ) );
		}


		// グラフ全体における位置を計算
		float pos = 0.0f;
		if( flag )
		{
			pos = ( mGraphRectX + ratio*mGraphRectWidth );
		}
		else
		{
			pos = ( mGraphRectY + (1.0f-ratio)*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();

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

		// 値を計算
		double value = 0.0;
		if( type == SGAxis.LINEAR_TYPE )
		{
			value = min + ratio*(max-min);
		}
		else if( type == SGAxis.LOG_TYPE )
		{
			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();



	/**
	 * 
	 */
	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();
	}


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


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


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

	
	/**
	 * 
	 */
	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();
	}


	/**
	 * 
	 *
	 */
	public void notifyToRoot()
	{
		this.notifyToListener( SGFigure.NOTIFY_CHANGE_TO_ROOT );
	}


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


	/**
	 * 
	 */
	protected boolean mChangedFlag = false;

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



	/**
	 * 
	 * @param obj
	 * @param list
	 * @return
	 */
	protected boolean moveObjectToFront(
		final Object obj, final ArrayList list )
	{
		return this.moveObject( obj, list, list.size()-1 );
	}



	/**
	 * 
	 * @param obj
	 * @param list
	 * @return
	 */
	protected boolean moveObjectToBack(
		final Object obj, final ArrayList list )
	{
		return this.moveObject( obj, list, 0 );
	}



	/**
	 * Move the object to the location of given index.
	 * @param obj - object to be moved
	 * @param list - the list which contains the object
	 * @param index - array index
	 * @return
	 */
	protected boolean moveObject(
		final Object obj, final ArrayList list, final int index )
	{
		return SGUtility.moveObject( obj, list, index );
	}



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

	
	/**
	 * 
	 *
	 */
	public abstract boolean hideSelectedObject( SGISelectable s );


	/**
	 * 
	 *
	 */
	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 )
	{
		return SGUtilityNumber.getNumberInRangeOrder(
			value, axis.getMinValue(), axis.getMaxValue(),
			AXIS_SCALE_EFFECTIVE_DIGIT );
	}

	
	/**
	 * 
	 * @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;
	}



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



	/**
	 * Move focused objects to back or front.
	 * @param toFront - whether move to front
	 * @param objList - the list to be modified
	 * @return
	 */
	protected boolean moveFocusedObjects(
		final boolean toFront, final ArrayList 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 = (Object)list.get(ii);
				this.moveObjectToFront( el, objList );
			}
		}
		else
		{
			for( int ii=list.size()-1; ii>=0; ii-- )
			{
				Object el = (Object)list.get(ii);
				this.moveObjectToBack( el, objList );
			}
		}


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

		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 );
	}


}


