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

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

import jp.riken.brain.ni.samuraigraph.base.SGAxis;
import jp.riken.brain.ni.samuraigraph.base.SGDefaultValues;
import jp.riken.brain.ni.samuraigraph.base.SGIAxisBreakElement;
import jp.riken.brain.ni.samuraigraph.base.SGIAxisElement;
import jp.riken.brain.ni.samuraigraph.base.SGIConstants;
import jp.riken.brain.ni.samuraigraph.base.SGICopiable;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElement;
import jp.riken.brain.ni.samuraigraph.base.SGIGraphElement;
import jp.riken.brain.ni.samuraigraph.base.SGILegendElement;
import jp.riken.brain.ni.samuraigraph.base.SGIMovable;
import jp.riken.brain.ni.samuraigraph.base.SGIPropertySettingListener;
import jp.riken.brain.ni.samuraigraph.base.SGISelectable;
import jp.riken.brain.ni.samuraigraph.base.SGISignificantDifferenceElement;
import jp.riken.brain.ni.samuraigraph.base.SGIStringElement;
import jp.riken.brain.ni.samuraigraph.base.SGITimingLineElement;
import jp.riken.brain.ni.samuraigraph.base.SGIUndoable;
import jp.riken.brain.ni.samuraigraph.base.SGProperties;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2f;
import jp.riken.brain.ni.samuraigraph.base.SGUtility;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityNumber;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityText;
import jp.riken.brain.ni.samuraigraph.figure.SGAxisBreakSymbol;
import jp.riken.brain.ni.samuraigraph.figure.SGFigureElement;
import jp.riken.brain.ni.samuraigraph.figure.SGIAxisBreakSymbolConstants;
import jp.riken.brain.ni.samuraigraph.figure.SGAxisBreakSymbol.AxisBreakSymbolProperties;

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


/**
 * 
 * @author kuromaru
 *
 * TODO この生成された型コメントのテンプレートを変更するには次を参照。
 * ウィンドウ ＞ 設定 ＞ Java ＞ コード・スタイル ＞ コード・テンプレート
 */
public class SGAxisBreakElement extends SGFigureElement
	implements SGIAxisBreakElement, SGIAxisBreakSymbolConstants
{

	/**
	 * 
	 */
	private SGIAxisElement mAxisElement = null;

	
	/**
	 * 
	 */
	private ArrayList mAxisBreakSymbolList = new ArrayList();


	/**
	 * 
	 */
	private SGAxisBreakSymbolDialog mDialog = null;


	/**
	 * 
	 *
	 */
	public SGAxisBreakElement()
	{
		super();
	}


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

	
	/**
	 * create a dialog of axis break symbols
	 */
	private boolean createDialog()
	{
		final SGAxisBreakSymbolDialog absdg
			= new SGAxisBreakSymbolDialog( this.mDialogOwner, true );
		this.mDialog = absdg;
		return true;
	}


	/**
	 * 
	 */
	public String toString()
	{
		return "SGAxisBreakElement";
	}

	
	
	/**
	 * 
	 */
	public boolean synchronize( final SGIFigureElement element )
	{

		boolean flag = true;
		if( element instanceof SGIGraphElement )
		{
			
		}
		else if( element instanceof SGIStringElement )
		{
			
		}
		else if( element instanceof SGILegendElement )
		{
			
		}
		else if( element instanceof SGIAxisElement )
		{
			flag = this.synchronizeToAxisElement( (SGIAxisElement)element );
		}
		else if( element instanceof SGIAxisBreakElement )
		{
			
		}
		else if( element instanceof SGISignificantDifferenceElement )
		{
			
		}
		else if( element instanceof SGITimingLineElement )
		{
			
		}

		else
		{
			flag = element.synchronizeArgument( this );
		}


		return flag;
	}


	
	private boolean synchronizeToAxisElement( SGIAxisElement element )
	{
		ArrayList list = this.getVisibleAxisBreakSymbolList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			el.setDrawingElementsLocation();
		}
		
		return true;
	}
	
	

	/**
	 * Synchronize the element given by the argument.
	 * @param element An object to be synchronized.
	 */
	public boolean synchronizeArgument( final SGIFigureElement element )
	{
	    // this shouldn't happen
	    throw new Error();
	}


	
	/**
	 * 
	 * @param element
	 */
	public void setAxisElement( final SGIAxisElement element )
	{
		this.mAxisElement = element;
	}


	/**
	 * 
	 */
	public String getTagName()
	{
		return TAG_NAME_AXIS_BREAK;
	}

	

	/**
	 * 
	 */
	public boolean writeProperty( Element el )
	{
		return true;
	}


	
	/**
	 * 
	 */
	public boolean readProperty( final Element element )
	{
		NodeList nList = element.getElementsByTagName( SGAxisBreakSymbol.TAG_NAME_AXIS_BREAK_SYMBOL );
		for( int ii=0; ii<nList.getLength(); ii++ )
		{
			Node node = nList.item(ii);
			if( node instanceof Element )
			{
				Element el = (Element)node;
				AxisBreakSymbol abs = new AxisBreakSymbol();
				if( abs.readProperty(el) == false )
				{
					return false;
				}
				abs.initPropertiesHistory();
				this.mAxisBreakSymbolList.add(abs);
			}
		}
		
		return true;
	}



	/**
	 * 
	 */
	public boolean setGraphRect(
		final float x, final float y, final float width, final float height )
	{
		super.setGraphRect(x,y,width,height);
		this.setLocationOfAllDrawingElements();

		return true;
	}

	
	/**
	 * 
	 */
	private boolean setLocationOfAllDrawingElements()
	{
		for( int ii=0; ii<this.mAxisBreakSymbolList.size(); ii++ )
		{
			AxisBreakSymbol abs = (AxisBreakSymbol)this.mAxisBreakSymbolList.get(ii);
			abs.setDrawingElementsLocation();
		}
		return true;
	}

	
	/**
	 * ズーム
	 */
	public boolean zoom( final float ratio )
	{
		super.zoom(ratio);

		// Axis Break Symbols
		for( int ii=0; ii<this.mAxisBreakSymbolList.size(); ii++ )
		{
			final AxisBreakSymbol el
				= (AxisBreakSymbol)this.mAxisBreakSymbolList.get(ii);
			el.zoom(ratio);
		}

		return true;
	}



	/**
	 * 
	 */
	public void paintComponent( final Graphics g )
	{
		super.paintComponent(g);
		final Graphics2D g2d = (Graphics2D) g;

		// draw all visible symbols
		ArrayList list = this.getVisibleAxisBreakSymbolList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			el.drawSymbol(g2d);
			if( el.mFrameFlag )
			{
				el.drawBoundingBox(g2d);
			}
		}
		
		
		// draw symbols around all objects
		if( this.mSymbolsVisibleFlagAroundAllObjects )
		{
			for( int ii=0; ii<list.size(); ii++ )
			{
				AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
				ArrayList pList = el.getAnchorPointList();
				SGUtilityForFigureElement.drawAnchorAsChildObject( pList, g2d );
			}
		}

		// draw symbols around focused objects
		if( this.mSymbolsVisibleFlagAroundFocusedObjects )
		{
			ArrayList fList = new ArrayList();
			this.getFocusedObjectsList( fList );
			for( int ii=0; ii<fList.size(); ii++ )
			{
				AxisBreakSymbol el = (AxisBreakSymbol)fList.get(ii);
				ArrayList pList = el.getAnchorPointList();
				SGUtilityForFigureElement.drawAnchorAsFocusedObject( pList, g2d );
			}
		}

	}

	
	
	/**
	 * 
	 */
	protected ArrayList getVisibleAxisBreakSymbolList()
	{
		ArrayList list = new ArrayList();

		for( int ii=0; ii<this.mAxisBreakSymbolList.size(); ii++ )
		{
			AxisBreakSymbol el
				= (AxisBreakSymbol)this.mAxisBreakSymbolList.get(ii);
			if( el.isVisible() )
			{
				list.add(el);
			}
		}

		return list;
	}



	/**
	 * 
	 */
	public boolean getFocusedObjectsList( ArrayList list )
	{
		ArrayList elList = this.getVisibleAxisBreakSymbolList();
		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( SGISelectable s )
	{
		AxisBreakSymbol el = (AxisBreakSymbol)s;
		el.setVisible(false);
		return true;
	}



	/**
	 * 
	 * @param document
	 * @return
	 */
	public Element createElement( final Document document )
	{
		// create an Element object
		Element el = this.createThisElement( document );
		if( el==null )
		{
			return null;
		}

		// axis symbol
		ArrayList abList = this.getVisibleAxisBreakSymbolList();
		for( int ii=0; ii<abList.size(); ii++ )
		{
			AxisBreakSymbol abs = (AxisBreakSymbol)abList.get(ii);
			Element elAbs = abs.createElement( document );
			if( elAbs==null )
			{
				return null;
			}
			el.appendChild( elAbs );
		}
		
		return el;
	}

	
	
	/**
	 * 
	 */
	public boolean isChanged()
	{
		if( super.isChanged() )
		{
			return true;
		}
		ArrayList list = this.getVisibleAxisBreakSymbolList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			if( el.isChanged() )
			{
				return true;
			}
		}
		return false;
	}


	
	/**
	 * 
	 */
	private boolean setVisibleAxisBreakSymbols( final ArrayList listVisible )
	{
		return this.setVisibleDrawingElement( this.mAxisBreakSymbolList, listVisible );
	}


	
	/**
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public boolean addAxisBreakSymbol( final int x, final int y )
	{
		if( this.getGraphRect().contains( x, y ) == false )
		{
			return false;
		}

		SGAxis xAxis = mAxisElement.getAxis( SGDefaultValues.AXIS_BREAK_HORIZONTAL_AXIS );
		SGAxis yAxis = mAxisElement.getAxis( SGDefaultValues.AXIS_BREAK_PERPENDICULAR_AXIS );
		return this.addAxisBreakSymbol( xAxis, yAxis, x, y );
	}

	
	

	/**
	 * 
	 */
	public boolean addAxisBreakSymbol(
		final SGAxis xAxis, final SGAxis yAxis, final int x, final int y )
	{
		
		AxisBreakSymbol el = new AxisBreakSymbol(
			SGDefaultValues.AXIS_BREAK_LENGTH,
			SGDefaultValues.AXIS_BREAK_INTERVAL,
			SGDefaultValues.AXIS_BREAK_DISTORTION,
			SGDefaultValues.AXIS_BREAK_ANGLE,
			SGDefaultValues.AXIS_BREAK_FOR_HORIZONTAL
		);
		el.addColor( SGDefaultValues.AXIS_BREAK_INNER_COLOR );
		el.setLineColor( SGDefaultValues.AXIS_BREAK_LINE_COLOR );
		el.setLineWidth( SGDefaultValues.AXIS_BREAK_LINE_WIDTH );
		el.setMagnification( this.mMagnification );
		el.mXAxis = xAxis;
		el.mYAxis = yAxis;

		el.setLocation(x,y);
		el.setAxisValue();

		//
		this.mAxisBreakSymbolList.add( el );

		//
		this.setChanged(true);

		// initialize history
		el.initPropertiesHistory();
		
		this.notifyToRoot();

		return true;
	}

	
	
	/**
	 * 全描画要素の位置を決定
	 */
	protected boolean setAllDrawingElementsLocation()
	{
		ArrayList list = this.getVisibleAxisBreakSymbolList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			final AxisBreakSymbol el
				= (AxisBreakSymbol)list.get(ii);
			if( el.setDrawingElementsLocation() == false )
			{
				return false;
			}
		}

		return true;
	}


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

	

	/**
	 * 
	 */
	public boolean undo()
	{
		boolean flag = super.undo();
		if( !flag )
		{
			return false;
		}

		this.clearFocusedObjects();
		this.notifyChange();

		return true;
	}



	/**
	 * 
	 */
	public boolean redo()
	{
		boolean flag = super.redo();
		if( !flag )
		{
			return false;
		}

		this.clearFocusedObjects();
		this.notifyChange();

		return true;
	}

	
	
	/**
	 * 
	 */
	public void translateSelectedObjects( final int dx, final int dy )
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			el.translate(dx,dy);
		}
	}

	
	
	/**
	 * 
	 * @return
	 */
	public boolean clearFocusedObjects()
	{
		if( super.clearFocusedObjects() == false )
		{
			return false;
		}
		return true;
	}



	/**
	 * 
	 */
	private boolean moveAxisBreakSymbolToFront( final AxisBreakSymbol el )
	{
		return this.moveObjectToFront( el, this.mAxisBreakSymbolList );
	}


	/**
	 * 
	 */
	private boolean moveAxisBreakSymbolToBack( final AxisBreakSymbol el )
	{
		return this.moveObjectToBack( el, this.mAxisBreakSymbolList );
	}


	
	/**
	 * 
	 */
	public boolean onMouseClicked( final MouseEvent e )
	{
		if( super.onMouseClicked(e) == false )
		{
			return false;
		}

		// Axis Break Symbols
		ArrayList list = this.getVisibleAxisBreakSymbolList();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			final AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			if( this.clickDrawingElements(el,e) )
			{
				return true;
			}
		}

		return false;
	}

	
	
	/**
	 * 
	 * @return
	 */
	public boolean setTemporaryPropertiesOfFocusedObjects()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			el.mTemporaryProperties = el.getProperties();
		}
		return true;
	}

	
	
	/**
	 * 
	 */
	private boolean clickDrawingElements(
		final AxisBreakSymbol el, final MouseEvent e )
	{
		final int x = e.getX();
		final int y = e.getY();
		final int cnt = e.getClickCount();

		// clicked on the line elements
		if( el.contains(x,y) )
		{
			if( SwingUtilities.isLeftMouseButton(e) & cnt==1 )
			{
				this.updateFocusedObjectsList( el, e );
				if( el.isSelected() )
				{
					el.mTempLocationInSymbol = new Point(
						x - (int)el.getX(),
						y - (int)el.getY() );
				}
			}
			else if( SwingUtilities.isLeftMouseButton(e) & cnt==2 )
			{
				el.showProeprtyDialog();
			}
			else if( SwingUtilities.isRightMouseButton(e) & cnt==1 )
			{
				el.getPopupMenu().show( this, x, y );
			}

			return true;
		}


		return false;
	}



	private Point mPressedPoint = null;

	
	/**
	 * 
	 */
	public boolean onMousePressed( final MouseEvent e )
	{

		if( super.onMousePressed(e) == false )
		{
			return false;
		}

		final int x = e.getX();
		final int y = e.getY();


		// Axis Break Symbols
		ArrayList fList = this.getFocusedObjectsList();
		ArrayList list = this.getVisibleAxisBreakSymbolList();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			final AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			if( el.contains(x,y) )
			{
				this.mPressedPoint = e.getPoint();
				this.setMouseCursor( Cursor.MOVE_CURSOR );
				return true;
			}
		}

		return false;
	}

	
	
	/**
	 * 
	 * @param e
	 */
	public boolean onMouseDragged( final MouseEvent e )
	{

		if( super.onMouseDragged(e) == false )
		{
			return false;
		}

		if( this.mPressedPoint==null )
		{
			return false;
		}

		
		final int dx = e.getX() - this.mPressedPoint.x;
		final int dy = e.getY() - this.mPressedPoint.y;
		
		
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			el.translate(dx,dy);
			el.setAxisValue();
			el.setDrawingElementsLocation();
		}
		
		this.mPressedPoint = e.getPoint();

		return true;
	}


	

	/**
	 * 
	 * @return
	 */
	public boolean setChangedFocusedObjects()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			SGProperties temp = el.mTemporaryProperties;
			SGProperties p = el.getProperties();
			if( p.equals(temp)==false )
			{
				el.setChanged(true);
			}
		}
		return true;
	}
	
	
	
	/**
	 * 
	 * @param e
	 */
	public boolean onMouseReleased( final MouseEvent e )
	{

		if( super.onMouseReleased(e) == false )
		{
			return false;
		}

		// Axis Break Symbol
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)list.get(ii);
			Rectangle2D rect = el.getElementBounds();
			if( rect.contains(e.getPoint()) )
			{
				this.setMouseCursor( Cursor.HAND_CURSOR );
			}
			else
			{
				this.setMouseCursor( Cursor.DEFAULT_CURSOR );
			}
		}


		this.notifyToRoot();
		
		return true;
	}
	
	
	
	/**
	 * 
	 */
	public boolean onDrawingElement( final int x, final int y )
	{

		// axis break symbols
		for( int ii=this.mAxisBreakSymbolList.size()-1; ii>=0; ii-- )
		{
			final AxisBreakSymbol el = (AxisBreakSymbol)mAxisBreakSymbolList.get(ii);
			final boolean flag = el.contains(x,y);
			el.mFrameFlag = flag;
			if( flag )
			{
				this.setMouseCursor( Cursor.HAND_CURSOR );
				repaint();
				return true;
			}
		}

		this.setMouseCursor( Cursor.DEFAULT_CURSOR );

		return false;
	}



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

		if( super.getMarginAroundGraphRect(topAndBottom,leftAndRight) == false )
		{
			return false;
		}

		ArrayList axisBreakRectList = new ArrayList();
		for( int ii=0; ii<this.mAxisBreakSymbolList.size(); ii++ )
		{
			AxisBreakSymbol el = (AxisBreakSymbol)this.mAxisBreakSymbolList.get(ii);
			if( el.isVisible() == false )
			{
				continue;
			}
			Rectangle2D rect = el.getElementBounds();
			axisBreakRectList.add( rect );
		}
		
		Rectangle2D rectAxisBreakSymbols = null;
		if( axisBreakRectList.size()!=0 )
		{
			rectAxisBreakSymbols = SGUtility.createUnion( axisBreakRectList );
		}
		
		
		Rectangle2D gRect = this.getGraphRect();
		
		ArrayList rectList = new ArrayList();
		rectList.add( gRect );
		
		if( rectAxisBreakSymbols!=null )
		{
			rectList.add( rectAxisBreakSymbols );
		}
		
		Rectangle2D rectAll = SGUtility.createUnion( rectList );
		
		final float top = (float)( gRect.getY() - rectAll.getY() );
		final float bottom = (float)(
			( rectAll.getY() + rectAll.getHeight() )
			- ( gRect.getY() + gRect.getHeight() ) );
		final float left = (float)( gRect.getX() - rectAll.getX() );
		final float right = (float)(
			( rectAll.getX() + rectAll.getWidth() )
			- ( gRect.getX() + gRect.getWidth() ) );
		
		topAndBottom.x += top;
		topAndBottom.y += bottom;
		leftAndRight.x += left;
		leftAndRight.y += right;
		
		return true;
	}



	/**
	 * 
	 */
	public static class AxisProperties extends SGProperties
	{
		ArrayList visibleAxisBreakSymbolList = new ArrayList();

		AxisProperties(){}
		
		/**
		 * 
		 */
		public boolean equals( final Object obj )
		{
			if( ( obj instanceof AxisProperties) == false )
			{
				return false;
			}
			
			AxisProperties p = (AxisProperties)obj;
			if( p.visibleAxisBreakSymbolList.equals(this.visibleAxisBreakSymbolList) == false )
			{
				return false;
			}

			return true;
		}
	}


	/**
	 * 
	 */
	public SGProperties getProperties()
	{
		AxisProperties wp = new AxisProperties();
		wp.visibleAxisBreakSymbolList = this.getVisibleAxisBreakSymbolList();
		return wp;
	}



	/**
	 * 
	 */
	public boolean setProperties( final SGProperties p )
	{
		if( ( p instanceof AxisProperties ) == false ) return false;

		AxisProperties wp = (AxisProperties)p;
		final boolean flag = this.setVisibleAxisBreakSymbols( wp.visibleAxisBreakSymbolList );
		if( !flag )
		{
			return false;
		}

		return true;
	}


	
	/**
	 * 
	 */	
	public Object copy()
	{
		return null;
	}
	


	
	/**
	 * 
	 */
	class AxisBreakSymbol extends SGAxisBreakSymbol2D
		implements ActionListener, SGIPropertySettingListener,
			SGIUndoable, SGISelectable, SGIMovable, SGICopiable
	{


		/**
		 * 
		 */
		protected SGAxis mXAxis = null;
		
		
		/**
		 * 
		 */
		protected SGAxis mYAxis = null;

		
		/**
		 * 
		 */
		protected double mXValue;

		
		/**
		 * 
		 */
		protected double mYValue;

		
		/**
		 * 
		 */
		protected boolean mFrameFlag = false;


		/**
		 * 
		 */
		protected SGProperties mTemporaryProperties = null;


		/**
		 * 
		 */
		protected Point mTempLocationInSymbol = null;

		
		/**
		 * Pop-up menu.
		 */
		private JPopupMenu mPopupMenu = new JPopupMenu();


		/**
		 * 
		 */
		protected AxisBreakSymbol()
		{
			super();
			this.init();
		}


		/**
		 * 
		 */
		protected AxisBreakSymbol(
			final float length,
			final float interval,
			final float dist,
			final float angle,
			final boolean horizontal )
		{
			super(length,interval,dist,angle,horizontal);
			this.init();
		}


		/**
		 * 
		 * @return
		 */
		private boolean init()
		{
			this.createPopupMenu();
			return true;
		}


		/**
		 * 
		 */
		public Object copy()
		{
			AxisBreakSymbol el = new AxisBreakSymbol();
			el.setProperties( this.getProperties() );
			el.setLocation( this.getX(), this.getY() );
			el.setMagnification( this.mMagnification );
			return el;
		}


		/**
		 * 
		 * @return
		 */
		JPopupMenu getPopupMenu()
		{
			return this.mPopupMenu;
		}
		
		
		/**
		 * 
		 */
		private boolean createPopupMenu()
		{
			this.mPopupMenu.setBounds( 0, 0, 100, 100 );

			{
				final JMenuItem item = new JMenuItem( MENUCMD_MOVE_TO_FRONT );
				item.addActionListener(this);
				mPopupMenu.add(item);
			}

			{
				final JMenuItem item = new JMenuItem( MENUCMD_MOVE_TO_BACK );
				item.addActionListener(this);
				mPopupMenu.add(item);
			}

			{
				final JMenuItem item = new JMenuItem( MENUCMD_DELETE );
				item.addActionListener(this);
				mPopupMenu.add(item);
			}
			
			mPopupMenu.addSeparator();

			{
				final JMenuItem item = new JMenuItem( MENUCMD_PROPERTY );
				item.addActionListener(this);
				mPopupMenu.add(item);
			}

			return true;
		}


		/**
		 * 
		 */
		public SGTuple2f getLocation()
		{
			final float x = this.mMagnification*this.mLocation.x + mGraphRectX;
			final float y = this.mMagnification*this.mLocation.y + mGraphRectY;
			return new SGTuple2f(x,y);
		}


		/**
		 * 
		 */
		public void setLocation( final float x, final float y)
		{
			final float dx = x - this.getX();
			final float dy = y - this.getY();

			this.mLocation.x = ( x - mGraphRectX )/this.mMagnification;
			this.mLocation.y = ( y - mGraphRectY )/this.mMagnification;
			this.create();
		}


		/**
		 * 
		 */
		public void translate( final int dx, final int dy )
		{
			this.setLocation( this.getX() + dx, this.getY() + dy );
			this.setAxisValue();
			this.setDrawingElementsLocation();
		}
		
		
		/**
		 * 
		 * @param g2d
		 */
		private void drawBoundingBox( final Graphics2D g2d )
		{
			g2d.setStroke( new BasicStroke(1) );
			g2d.setPaint( Color.BLACK );
			g2d.draw( this.getElementBounds() );
		}



		/**
		 * 
		 * @return
		 */
		private ArrayList getAnchorPointList()
		{
			ArrayList pList1 = this.getAnchorPointList( this.mCurve1 );
			ArrayList pList2 = this.getAnchorPointList( this.mCurve2 );
			ArrayList list = new ArrayList();
			list.addAll( pList1 );
			list.addAll( pList2 );
			return list;
		}


		/**
		 * 
		 * @return
		 */
		private ArrayList getAnchorPointList( final Shape sh )
		{
			ArrayList pList = new ArrayList();

			PathIterator itr = sh.getPathIterator(null);
			ArrayList list = SGUtilityJava2D.getSegmentList( itr );
			Point2D start = (Point2D)list.get(0);
			Point2D end = (Point2D)list.get(list.size()-1);
			Point2D middle = new Point2D.Float();
			middle.setLocation(
				( start.getX() + end.getX() )/2.0,
				( start.getY() + end.getY() )/2.0
			);
			pList.add( start );
			pList.add( end );
			pList.add( middle );

			return pList;
		}
	

		/**
		 * 
		 * @param e
		 */
		public void actionPerformed( final ActionEvent e )
		{
			final String command = e.getActionCommand();
			final Object source = e.getSource();

			if( command.equals( MENUCMD_MOVE_TO_FRONT ) )
			{
				moveAxisBreakSymbolToFront(this);
			}
			else if( command.equals( MENUCMD_MOVE_TO_BACK ) )
			{
				moveAxisBreakSymbolToBack(this);
			}
			else if( command.equals( MENUCMD_DELETE ) )
			{
				hideObject(this);
				clearFocusedObjects();
			}
			else if( command.equals( MENUCMD_PROPERTY ) )
			{
				this.showProeprtyDialog();
			}

		}


		/**
		 * 
		 */
		public boolean setDrawingElementsLocation()
		{
			final float x = calcLocation( this.mXValue, this.mXAxis, true );
			final float y = calcLocation( this.mYValue, this.mYAxis, false );
			this.setLocation(x,y);
			return true;
		}


		
		/**
		 * 
		 * @return
		 */
		public boolean setAxisValue()
		{
			final double xValue = calcValue( this.getX(), this.mXAxis, true );
			final double yValue = calcValue( this.getY(), this.mYAxis, false );

			this.mXValue = getNumberInRangeOrder( xValue, this.mXAxis );
			this.mYValue = getNumberInRangeOrder( yValue, this.mYAxis );
			
			return true;
		}
		
		
		
		/**
		 * @return
		 */
		public String getTagName()
		{
			return TAG_NAME_AXIS_BREAK_SYMBOL;
		}
		

		/**
		 * 
		 * @param document
		 * @return
		 */
		public Element createElement( final Document document )
		{
			Element el = document.createElement( this.getTagName() );
			if( this.writeProperty(el) == false )
			{
				return null;
			}
			return el;
		}
		
		
		/**
		 * 
		 * @param el
		 * @return
		 */
		public boolean writeProperty( final Element el )
		{
			String cm = SGUtilityNumber.cm;
			String pt = SGUtilityNumber.pt;
			String degree = SGUtilityNumber.degree;

			el.setAttribute( KEY_X_VALUE, Double.toString( this.mXValue ) );
			el.setAttribute( KEY_Y_VALUE, Double.toString( this.mYValue ) );
			el.setAttribute( KEY_X_AXIS_POSITION, mAxisElement.getAxisLocation( this.mXAxis ) );
			el.setAttribute( KEY_Y_AXIS_POSITION, mAxisElement.getAxisLocation( this.mYAxis ) );

			el.setAttribute( KEY_LENGTH, Float.toString( this.mLength*SGIConstants.CM_POINT_RATIO ) + cm );
			el.setAttribute( KEY_INTERVAL, Float.toString( this.mInterval*SGIConstants.CM_POINT_RATIO ) + cm );
			el.setAttribute( KEY_DISTORTION, Float.toString( this.mDistortion ) );
			el.setAttribute( KEY_ANGLE, Float.toString( this.mAngle/RADIAN_DEGREE_RATIO ) + degree );
			el.setAttribute( KEY_LINE_WIDTH, Float.toString( this.mLineWidth ) + pt );
			el.setAttribute( KEY_HORIZONTAL, Boolean.toString( this.mForHorizontalAxisFlag ) );
			el.setAttribute( KEY_LINE_COLOR, SGUtilityText.getColorString( this.mLineColor ) );
			el.setAttribute( KEY_INNER_COLOR, SGUtilityText.getColorListString( this.getColorList() ) );
			return true;
		}
		
		

		/**
		 * 
		 */
		public boolean readProperty( final Element element )
		{
			SGAxisBreakSymbol.AxisBreakSymbolProperties p = this.readProperty_(element);
			if( p==null )
			{
				return false;
			}
			if( this.setProperties(p) == false )
			{
				return false;
			}
			return true;
		}

		

		/**
		 * 
		 * @param element
		 * @return
		 */
		public SGAxisBreakSymbol.AxisBreakSymbolProperties readProperty_( final Element el )
		{
			
			final String cm = SGUtilityNumber.cm;
			final String pt = SGUtilityNumber.pt;
			final String degree = SGUtilityNumber.degree;
			final float ratio = SGIConstants.CM_POINT_RATIO;

			String str = null;
			Number num = null;
			Color cl = null;
			Boolean b = null;
			ArrayList list = null;


			// x value
			str = el.getAttribute( SGAxisBreakSymbol.KEY_X_VALUE );
			if( str.length()==0 )
			{
				return null;
			}
			num = SGUtilityText.getDouble(str);
			if( num==null )
			{
				return null;
			}
			final double xValue = num.doubleValue();
			

			// y value
			str = el.getAttribute( SGAxisBreakSymbol.KEY_Y_VALUE );
			if( str.length()==0 )
			{
				return null;
			}
			num = SGUtilityText.getDouble(str);
			if( num==null )
			{
				return null;
			}
			final double yValue = num.doubleValue();
			
			
			// x axis
			str = el.getAttribute( KEY_X_AXIS_POSITION );
			if( str.length()==0 )
			{
				return null;
			}
			final int configX = mAxisElement.getConfigurationInCube( str );
			SGAxis xAxis = mAxisElement.getAxisInCube( configX );
			if( xAxis==null )
			{
				return null;
			}
			
			
			// y axis
			str = el.getAttribute( KEY_Y_AXIS_POSITION );
			if( str.length()==0 )
			{
				return null;
			}
			final int configY = mAxisElement.getConfigurationInCube( str );
			SGAxis yAxis = mAxisElement.getAxisInCube( configY );
			if( yAxis==null )
			{
				return null;
			}

			
			// width
			str = el.getAttribute( SGAxisBreakSymbol.KEY_LENGTH );
			if( str.length()==0 )
			{
				return null;
			}
			num = SGUtilityText.getFloat(str,cm);
			if( num==null )
			{
				return null;
			}
			final float length = num.floatValue()/ratio;
			
			
			// interval
			str = el.getAttribute( SGAxisBreakSymbol.KEY_INTERVAL );
			if( str.length()==0 )
			{
				return null;
			}
			num = SGUtilityText.getFloat(str,cm);
			if( num==null )
			{
				return null;
			}
			final float interval = num.floatValue()/ratio;


			// distortion
			str = el.getAttribute( SGAxisBreakSymbol.KEY_DISTORTION );
			if( str.length()==0 )
			{
				return null;
			}
			num = SGUtilityText.getFloat(str);
			if( num==null )
			{
				return null;
			}
			final float distortion = num.floatValue();


			// angle
			str = el.getAttribute( SGAxisBreakSymbol.KEY_ANGLE );
			if( str.length()==0 )
			{
				return null;
			}
			num = SGUtilityText.getFloat(str,degree);
			if( num==null )
			{
				return null;
			}
			final float angle = num.floatValue()*RADIAN_DEGREE_RATIO;
			
			
			// line width
			str = el.getAttribute( SGAxisBreakSymbol.KEY_LINE_WIDTH );
			if( str.length()==0 )
			{
				return null;
			}
			num = SGUtilityText.getFloat(str,pt);
			if( num==null )
			{
				return null;
			}
			final float lineWidth = num.floatValue();


			// horizontal
			str = el.getAttribute( SGAxisBreakSymbol.KEY_HORIZONTAL );
			if( str.length()==0 )
			{
				return null;
			}
			b = SGUtilityText.getBoolean(str);
			if( b==null )
			{
				return null;
			}
			final boolean horizontal = b.booleanValue();
		

			// line color
			str = el.getAttribute( SGAxisBreakSymbol.KEY_LINE_COLOR );
			if( str.length()==0 )
			{
				return null;
			}
			cl = SGUtilityText.getColorFromString(str);
			if( cl==null )
			{
				return null;
			}
			final Color lineColor = cl;
		

			// inner color
			str = el.getAttribute( SGAxisBreakSymbol.KEY_INNER_COLOR );
			if( str.length()==0 )
			{
				return null;
			}
			list = SGUtilityText.getColorList(str);
			if( list==null )
			{
				return null;
			}
			final ArrayList innerColorList = list;


			AxisBreakSymbolWithAxesProperties p = new AxisBreakSymbolWithAxesProperties();

			p.setXValue( xValue );
			p.setYValue( yValue );
			p.mXAxis = xAxis;
			p.mYAxis = yAxis;
			p.setLength(length);
			p.setInterval(interval);
			p.setDistortion(distortion);
			p.setAngle(angle);
			p.setLineWidth(lineWidth);
			p.setHorizontal(horizontal);
			p.setLineColor(lineColor);
			p.setColorList(innerColorList);

			
			//
			p.setVisible(true);
		
			return p;
		}

		
		
		/**
		 * 
		 */
		private boolean showProeprtyDialog()
		{

			final SGAxisBreakSymbolDialog dg = mDialog;


			//
			dg.addPropertySettingListener( this );


			//
			dg.setLocation( mDialogOwner.getLocation() );


			// ダイアログにプロパティ設定
			this.setDialogProperty();

			// 色選択ボタンに装飾
			dg.setColorButtonBorder( true );


			// テンポラリオブジェクトを作成
			this.createTemporaryPropertyObject();


			dg.setVisible(true);

			dg.removeAllPropertySettingListeners();


			return true;
		}



		/**
		 * 
		 * @return
		 */
		private boolean setDialogProperty()
		{
			final SGAxisBreakSymbolDialog dg = mDialog;

			// 軸の設定
			this.setDialogPropertyOfAxis();

			dg.setXValue( new Double( this.mXValue ) );
			dg.setYValue( new Double( this.mYValue ) );
			dg.setLength( new Float( this.mLength*SGIConstants.CM_POINT_RATIO ) );
			dg.setInterval( new Float( this.mInterval*SGIConstants.CM_POINT_RATIO ) );
			dg.setDistortion( new Float( this.mDistortion ) );
			dg.setAngle( new Float( this.mAngle/RADIAN_DEGREE_RATIO ) );
			dg.setLineWidth( new Float( this.mLineWidth ) );
			dg.setHorizontal( this.mForHorizontalAxisFlag );
			dg.setLineColor( this.mLineColor );
			dg.setInnerColor( this.getColor(0) );

			return true;
		}


		
		/**
		 * 
		 */
		private boolean setDialogPropertyOfAxis()
		{
			final SGAxisBreakSymbolDialog dg = mDialog;
			final int configX = mAxisElement.getConfigurationInPlane( this.mXAxis );
			final int configY = mAxisElement.getConfigurationInPlane( this.mYAxis );
			boolean flag = dg.setAxisRadioButton( configX, configY );
			if( !flag )
			{
				return false;
			}
			return true;
		}

		
		

		// テンポラリオブジェクトを作成させる
		public boolean createTemporaryPropertyObject()
		{
			this.mTemporaryProperties = this.getProperties();
			return true;
		}


		// ダイアログに設定された値を元に設定し、終了
		public boolean commit()
		{
//System.out.println("commit");
			if( this.setPropertiesFromDialog() == false )
			{
				return false;
			}

//			this.updateHistory();
this.notifyToRoot();

			return true;
		}


		// 設定前の状態に戻す
		public boolean cancel()
		{
//System.out.println("cancel");
			if( this.setProperties( this.mTemporaryProperties ) == false )
			{
				return false;
			}
			if( this.create() == false )
			{
				return false;
			}
			if( this.setDrawingElementsLocation() == false )
			{
				return false;
			}
			return true;
		}


		// ダイアログに設定された値を元に設定する
		public boolean preview()
		{
//System.out.println("preview");
			if( this.setPropertiesFromDialog() == false )
			{
				return false;
			}
			return true;
		}


		/**
		 * 
		 * @return
		 */
		private boolean setPropertiesFromDialog()
		{
			final SGAxisBreakSymbolDialog dg = mDialog;
			AxisBreakSymbolWithAxesProperties p = dg.getProperties();

			if( p==null )
			{
				return false;
			}

			p.setProperty( KEY_DRAWING_ELEMENT_VISIBLE, Boolean.TRUE.toString() );
			
			final String xAxisLocation = dg.getXAxisLocation();
			final SGAxis xAxis = mAxisElement.getAxis( xAxisLocation );
			final String yAxisLocation = dg.getYAxisLocation();
			final SGAxis yAxis = mAxisElement.getAxis( yAxisLocation );
			p.mXAxis = xAxis;
			p.mYAxis = yAxis;
			

			if( p.equals(this.mTemporaryProperties) == false )
			{
				this.setChanged(true);
			}
			
			if( this.setProperties(p) == false )
			{
				return false;
			}
			if( this.create() == false )
			{
				return false;
			}
			
			if( this.setDrawingElementsLocation() == false )
			{
				return false;
			}

			repaint();

			return true;
		}





		/**
		 * 
		 * @return
		 */
		private ArrayList mAxisBreakSymbolPropertyHistoryList = new ArrayList();



		/**
		 * 
		 * @return
		 */
		private int mElementStateCounter = 0;



		/**
		 * 
		 */
		public boolean initPropertiesHistory()
		{
			this.addAxisBreakSymbolPropertyHistory( this.getProperties() );
			return true;
		}



		/**
		 * 
		 * @return
		 */
		private boolean addAxisBreakSymbolPropertyHistory( SGProperties p )
		{

			if( ( p instanceof AxisBreakSymbolProperties ) == false )
			{
				return false; 
			}

//System.out.println("<< addFigurePropertyHistory >>");
//System.out.println(this.mFigureStateCounter);

			ArrayList list = new ArrayList();
			for( int ii=0; ii<this.mElementStateCounter; ii++ )
			{
				list.add( this.mAxisBreakSymbolPropertyHistoryList.get(ii) );
			}
			list.add(p);

			this.mAxisBreakSymbolPropertyHistoryList = list;

//System.out.println(this.mFigurePropertyHistoryList);
//System.out.println("x="+p.x+"  y="+p.y);
//System.out.println("w="+p.width+"  h="+p.height);
//System.out.println();

			return true;
		}



		/**
		 * 現在、状態が履歴のどの位置にあるのかを示すカウンタ
		 */
		private int mCurrentStateCounter = 0;


		/**
		 * アンドゥ対象オブジェクトの履歴リスト
		 */
		protected ArrayList mUndoableObjectHistoryList = new ArrayList();




		/**
		 * アンドゥを行う
		 */
		public boolean undo()
		{
			this.mElementStateCounter--;

			AxisBreakSymbolProperties p
				= (AxisBreakSymbolProperties)this.mAxisBreakSymbolPropertyHistoryList.get(
					this.mElementStateCounter);
			this.setProperties(p);

			this.create();
			this.setDrawingElementsLocation();

			return true;
		}


		/**
		 * リドゥを行う
		 */
		public boolean redo()
		{
			this.mElementStateCounter++;

			AxisBreakSymbolProperties p
				= (AxisBreakSymbolProperties)this.mAxisBreakSymbolPropertyHistoryList.get(
					this.mElementStateCounter);

			this.setProperties(p);

			this.create();
			this.setDrawingElementsLocation();

			return true;
		}



		/**
		 * アンドゥ操作を依頼する
		 */
		public boolean onUndo()
		{
			return this.undo();
		}

	
	
		/**
		 * リドゥ操作を依頼する
		 */
		public boolean onRedo()
		{
			return this.redo();
		}


		/**
		 * オブジェクトの履歴を更新する
		 * @return
		 */
		public boolean updateObjectHistory( final SGIUndoable obj )
		{
			ArrayList objList = new ArrayList();
			objList.add(obj);
			boolean flag = this.updateObjectHistory(objList);
			if( !flag )
			{
				return false;
			}

			return true;
		}



		/**
		 * オブジェクトの履歴を更新する
		 */
		public boolean updateObjectHistory( final ArrayList objList )
		{

			ArrayList list = new ArrayList();
			for( int ii=0; ii<this.mCurrentStateCounter; ii++ )
			{
				Object obj = this.mUndoableObjectHistoryList.get(ii);
				list.add(obj);
			}
			list.add( new ArrayList(objList) );

			this.mUndoableObjectHistoryList = list;
			this.mCurrentStateCounter++;

//System.out.println("<< updateObjectHistory >>");
//System.out.println(this.mUndoableObjectHistoryList);
//System.out.println(this.mCurrentStateCounter);
//System.out.println();

			return true;
		}



		/**
		 * 履歴の更新の実行。アンドゥ対象の操作が行われたときに、真っ先に呼ばれる。
		 */
		public boolean updateHistory()
		{
			if( this.isChanged() )
			{
				this.updateThisObjectHistory();
				this.setChanged(false);
				this.updateObjectHistory(this);
			}
			return true;
		}


		/**
		 * 
		 */
		public boolean updateThisObjectHistory()
		{
			this.mElementStateCounter++;
			this.addAxisBreakSymbolPropertyHistory( this.getProperties() );

			return true;
		}


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


		private boolean mChangedFlag = false;

		
		public void setChanged( final boolean b )
		{
			this.mChangedFlag = b;
		}


		/**
		 * 
		 *
		 */
		public void notifyToRoot()
		{
			notifyToRootFromFigureElement();
		}

		
		
		
		/**
		 * 
		 */
		public boolean setProperties( SGProperties p )
		{

			if( ( p instanceof AxisBreakSymbolWithAxesProperties ) == false ) return false;

			if( super.setProperties(p) == false ) return false;

			AxisBreakSymbolWithAxesProperties ap = (AxisBreakSymbolWithAxesProperties)p;


			final Double x = ap.getXValue();
			if( x==null )
			{
				return false;
			}
			this.mXValue = x.doubleValue();
			
			
			final Double y = ap.getYValue();
			if( y==null )
			{
				return false;
			}
			this.mYValue = y.doubleValue();
			
			this.mXAxis = ap.mXAxis;
			this.mYAxis = ap.mYAxis;
			
			return true;
		}



		/**
		 * 
		 * @return
		 */
		public SGProperties getProperties()
		{
			final AxisBreakSymbolWithAxesProperties p = new AxisBreakSymbolWithAxesProperties();
			if( this.getProperties(p) == false )
			{
				return null;
			}
			return p;
		}

		
		/**
		 * 
		 * @param p
		 * @return
		 */
		public boolean getProperties( final SGProperties p )
		{
			if( ( p instanceof AxisBreakSymbolWithAxesProperties ) == false )
			{
				return false;
			}

			if( super.getProperties(p) == false )
			{
				return false;
			}

			final AxisBreakSymbolWithAxesProperties dp = (AxisBreakSymbolWithAxesProperties)p;
			dp.setXValue( this.mXValue );
			dp.setYValue( this.mYValue );
			dp.mXAxis = this.mXAxis;
			dp.mYAxis = this.mYAxis;

			return true;
		}



		/**
		 * Flag whether this object is focused.
		 */
		private boolean mSelectedFlag = false;


		/**
		 * Get the flag as a focused object.
		 * @return whether this object is focused.
		 */
		public boolean isSelected()
		{
			return this.mSelectedFlag;
		}


		/**
		 * Set the flag as a focused object.
		 * @param b focused
		 */
		public void setSelected( final boolean b )
		{
			this.mSelectedFlag = b;
		}

	}


	
	/**
	 * Property of Axis Break Symbol.
	 */
	public static class AxisBreakSymbolWithAxesProperties
		extends AxisBreakSymbolProperties
	{

		public static final String[] keys = {
			KEY_X_VALUE, KEY_Y_VALUE
		};

		protected SGAxis mXAxis = null;
		protected SGAxis mYAxis = null;

		/**
		 * 
		 */
		public AxisBreakSymbolWithAxesProperties()
		{
			super();
		}


		/**
		 * 
		 */
		public boolean equals( final Object obj )
		{
			if( ( obj instanceof AxisBreakSymbolWithAxesProperties ) == false )
			{
				return false;
			}

			if( super.equals(obj) == false ) return false;


			AxisBreakSymbolWithAxesProperties p = (AxisBreakSymbolWithAxesProperties)obj;

			for( int ii=0; ii<keys.length; ii++ )
			{
				String key = keys[ii];
				String str = this.getProperty(key);
				if( str.equals(p.getProperty(keys[ii])) == false )
				{
					return false;
				}
			}
			
			if( this.mXAxis.equals( p.mXAxis ) == false ) return false;

			if( this.mYAxis.equals( p.mYAxis ) == false ) return false;

			return true;
		}


		public Double getXValue()
		{
			final String value = this.getProperty(KEY_X_VALUE);
			return SGUtilityText.getDouble(value);
		}
		
		public Double getYValue()
		{
			final String value = this.getProperty(KEY_Y_VALUE);
			return SGUtilityText.getDouble(value);
		}

		public boolean setXValue( final double value )
		{
			this.setProperty( KEY_X_VALUE, Double.toString( value ) );
			return true;
		}

		public boolean setYValue( final double value )
		{
			this.setProperty( KEY_Y_VALUE, Double.toString( value ) );
			return true;
		}

	}


}

