/*
 * 作成日： 2004/11/26
 *
 * TODO この生成されたファイルのテンプレートを変更するには次を参照。
 * ウィンドウ ＞ 設定 ＞ Java ＞ コード・スタイル ＞ コード・テンプレート
 */
package jp.riken.brain.ni.samuraigraph.figure.java2d;

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

import javax.swing.JDialog;
import javax.swing.JLabel;
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.SGIGridElement;
import jp.riken.brain.ni.samuraigraph.base.SGILegendElement;
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.SGDrawingElementLine;
import jp.riken.brain.ni.samuraigraph.figure.SGFigureElement;

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 SGTimingLineElement extends SGFigureElement
	implements SGITimingLineElement
{

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


	/**
	 * 
	 */
	protected ArrayList mTimingLineList = new ArrayList();


	/**
	 * 
	 */
	protected SGTimingLineDialog mTimingLineDialog = null;



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



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



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


	
	/**
	 * 
	 * @param axis
	 * @param value
	 */
	public void addTimingLine( final SGAxis axis, final double value )
	{
		final TimingLine line = new TimingLine();

		line.setAxis( axis );
		line.setValue( value );
		line.setMagnification(this.mMagnification);
		this.addTimingLine_( line );

		this.setAllDrawingElementsLocation();

		this.updateFocusedObjectsList( line, 0 );
		
		this.setChanged(true);
		this.notifyToRoot();
	}
	

	
	/**
	 * 
	 *
	 */
	private void addTimingLine_( final TimingLine line )
	{
		line.initDrawingElement(2);
		line.setPropertiesOfDrawingElements();
		line.initPropertiesHistory();
		this.mTimingLineList.add( line );
	}
	
	
	

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


	/**
	 * 同期を取る
	 */
	public boolean synchronize( final SGIFigureElement element )
	{

		boolean flag = true;
		if( element instanceof SGILegendElement )
		{

		}
		else if( element instanceof SGIAxisElement )
		{
			final SGIAxisElement aElement = (SGIAxisElement)element;
			flag = this.synchronizeToAxisElement( aElement );
		}
		else if( element instanceof SGIStringElement )
		{
			
		}
		else if( element instanceof SGIGraphElement )
		{
			
		}
		else if( element instanceof SGIAxisBreakElement )
		{
			
		}
		else if( element instanceof SGISignificantDifferenceElement )
		{
			
		}
		else if( element instanceof SGITimingLineElement )
		{
			
		}
		else if( element instanceof SGIGridElement )
		{

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

		return flag;
	}


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


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


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



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

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


	
	/**
	 * 
	 */
	public boolean readProperty( final Element element )
	{
		NodeList nList = element.getElementsByTagName( TimingLine.TAG_NAME_TIMING_LINE );
		for( int ii=0; ii<nList.getLength(); ii++ )
		{
			Node node = nList.item(ii);
			if( node instanceof Element )
			{
				Element el = (Element)node;
				TimingLine line = new TimingLine();
				if( line.readProperty(el) == false )
				{
					return false;
				}
				this.addTimingLine_( line );
			}
		}
		
		return true;
	}

	
	/**
	 * 
	 */
	public Element createElement( Document document )
	{
		Element el = this.createThisElement( document );
		if( el==null )
		{
			return null;
		}

		ArrayList list = this.getVisibleTimingElementList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			TimingLine line
				= (TimingLine)list.get(ii);
			Element elTiming = line.createElement( document );
			if( elTiming==null )
			{
				return null;
			}
			el.appendChild( elTiming );
		}
		return el;
	}



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



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

		for( int ii=0; ii<this.mTimingLineList.size(); ii++ )
		{
			final SGTimingLine line
				= (SGTimingLine)this.mTimingLineList.get(ii);
			line.zoom(ratio);
		}

		return true;
	}


	/**
	 * 
	 */
	public boolean setGraphRect(
		final float x, final float y, final float width, final float height )
	{
		super.setGraphRect(x,y,width,height);
		if( this.setAllDrawingElementsLocation() == false )
		{
			return false;
		}
		return true;
	}


	/**
	 * タイミング線の位置を決定
	 */

	private boolean calcLocationOfTimingLine(
		final double value,
		final SGAxis axis,
		final boolean horizontal,
		final SGTuple2f startPoint,
		final SGTuple2f endPoint )
	{

		final float location = this.calcLocation( value, axis, horizontal );
		Rectangle2D rect = this.getGraphRect();
		if( horizontal )
		{
			startPoint.x = location;
			startPoint.y = (float)rect.getY();
			endPoint.x = location;
			endPoint.y = startPoint.y + (float)( rect.getHeight() );
		}
		else
		{
			startPoint.x = (float)rect.getX();
			startPoint.y = location;
			endPoint.x = startPoint.x + (float)( rect.getWidth() );
			endPoint.y = location;
		}

		return true;
	}



	/**
	 * 
	 */
	private boolean createTimingLineDialog()
	{
		final SGTimingLineDialog dg = new SGTimingLineDialog( mDialogOwner, true );

		this.mTimingLineDialog = dg;

		return true;
	}



	/**
	 * 
	 */
	protected ArrayList getVisibleTimingElementList()
	{
		ArrayList list = new ArrayList();
		for( int ii=0; ii<this.mTimingLineList.size(); ii++ )
		{
			SGTimingLine groupSet
				= (SGTimingLine)this.mTimingLineList.get(ii);
			if( groupSet.isVisible() )
			{
				if( this.isInsideRange( groupSet ) )
				{
					list.add( groupSet );
				}
			}
		}

		return list;
	}



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

	
	/**
	 * 
	 * @param groupSet
	 * @return
	 */
	private boolean isInsideRange( SGTimingLine groupSet )
	{
		SGAxis axis = groupSet.mAxis;
		double value = groupSet.mValue;
		return ( axis.insideRange(value) );
	}
	
	
	/**
	 * 
	 */
	protected boolean setVisibleTimingElements( final ArrayList list )
	{

		ArrayList visibleList = new ArrayList( list );
		ArrayList invisibleList = new ArrayList();

		for( int ii=0; ii<this.mTimingLineList.size(); ii++ )
		{
			SGTimingLine groupSet
				= (SGTimingLine)this.mTimingLineList.get(ii);
			final boolean b = list.contains(groupSet);
			groupSet.setVisible(b);
			if(!b)
			{
				invisibleList.add( groupSet );
			}
		}


		this.mTimingLineList.clear();
		for( int ii=0; ii<visibleList.size(); ii++ )
		{
			this.mTimingLineList.add( visibleList.get(ii) );
		}
		for( int ii=0; ii<invisibleList.size(); ii++ )
		{
			this.mTimingLineList.add( invisibleList.get(ii) );
		}

		return true;
	}





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

		ArrayList list = this.getVisibleTimingElementList();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			final TimingLine line
				= (TimingLine)list.get(ii);
			if( this.clickDrawingElements(line,e) )
			{
				return true;
			}
		}

		return false;
	}

	
	/**
	 * 
	 */
	private boolean clickDrawingElements(
		final TimingLine 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( cnt==1 )
			{
				this.updateFocusedObjectsList( el, e );

				if( SwingUtilities.isLeftMouseButton(e) )
				{

				}
				else if( SwingUtilities.isRightMouseButton(e) )
				{
					el.getPopupMenu().show( this, x, y );
				}
			}
			else if( cnt==2 )
			{
				if( SwingUtilities.isLeftMouseButton(e) )
				{
					el.getDialog().setVisible(true);
				}
			}

			return true;
		}

		return false;
	}




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

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

		ArrayList list = this.getVisibleTimingElementList();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			final SGTimingLine el = (SGTimingLine)list.get(ii);
			if( el.contains(x,y) )
			{
				return true;
			}
		}

		return false;
	}


	/**
	 * 
	 */
	public boolean onMouseDragged( final MouseEvent e )
	{
		return true;
	}


	/**
	 * 
	 */
	public boolean onMouseReleased( final MouseEvent e )
	{
		return true;
	}
	
	
	/**
	 * 
	 */
	public boolean onDrawingElement( final int x, final int y )
	{
		ArrayList list = this.getVisibleTimingElementList();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			final SGTimingLine el
				= (SGTimingLine)list.get(ii);
			final boolean flag = el.contains(x,y);
			if( flag )
			{
				this.setMouseCursor( Cursor.HAND_CURSOR );
				return true;
			}
		}

		this.setMouseCursor( Cursor.DEFAULT_CURSOR );

		return false;
	}


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



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


	
	/**
	 * 
	 */
	public void paintGraphics( Graphics g, boolean clip )
	{

		Graphics2D g2d = (Graphics2D)g;
		
		ArrayList list = this.getVisibleTimingElementList();


		// グラフ描画領域のクリッピング
		if( clip )
		{
			SGUtilityForFigureElement.clipGraphRect(this,g2d);
		}


		// draw timing line
		Rectangle2D gRect = this.getGraphRect();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGTimingLine tl = (SGTimingLine)list.get(ii);
			if( clip )
			{
				tl.paintElement(g2d);
			}
			else
			{
				tl.paintElement(g2d,gRect);
			}
		}


		if( clip )
		{
			g2d.setClip( this.getBounds() );
		}


		// draw symbols around all objects
		if( this.mSymbolsVisibleFlagAroundAllObjects )
		{
			for( int ii=0; ii<list.size(); ii++ )
			{
				TimingLine el = (TimingLine)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++ )
			{
				TimingLine el = (TimingLine)fList.get(ii);
				ArrayList pList = el.getAnchorPointList();
				SGUtilityForFigureElement.drawAnchorAsFocusedObject(
					pList, g2d );
			}
		}

	}



	/**
	 * 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 )
	{
		ArrayList list = this.getFocusedObjectsList();

		if( toFront )
		{
			for( int ii=0; ii<list.size(); ii++ )
			{
				TimingLine el = (TimingLine)list.get(ii);
				this.moveTimingLineToFront(el);
			}
		}
		else
		{
			for( int ii=0; ii<list.size(); ii++ )
			{
				TimingLine el = (TimingLine)list.get(ii);
				this.moveTimingLineToBack(el);
			}
		}

		return true;
	}


	/**
	 * 
	 */
	protected boolean moveTimingLineToFront( final SGTimingLine line )
	{
		return this.moveObjectToFront( line, this.mTimingLineList );
	}


	/**
	 * 
	 */
	protected boolean moveTimingLineToBack( final SGTimingLine line )
	{
		return this.moveObjectToBack( line, this.mTimingLineList );
	}


	/**
	 * 
	 * @param line
	 * @return
	 */
	protected boolean hideTimingElementGroupSet( final SGTimingLine line )
	{
		line.setVisible(false);
		notifyChange();
		this.setChanged(true);
		this.notifyToRoot();
		return true;
	}



	/**
	 * 
	 */
	protected boolean removeTimingElementGroupSet( final SGTimingLine line )
	{
		this.mTimingLineList.remove(line);
		return false;
	}



	/**
	 * 
	 */
	protected boolean synchronizeToAxisElement( final SGIAxisElement aElement )
	{
		return this.setAllDrawingElementsLocation();
	}


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

		return true;
	}



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

		TimingElementProperties gp = (TimingElementProperties)p;
		gp.visibleTimingElementList = this.getVisibleTimingElementList();

		return true;
	}


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

		TimingElementProperties gp = (TimingElementProperties)p;


		boolean flag;
		flag = this.setVisibleTimingElements( gp.visibleTimingElementList );
		if( !flag )
		{
			return false;
		}

		return true;

	}



	/**
	 * Create copies of the focused objects.
	 * @return
	 */
	public boolean duplicateFocusedObjects()
	{
		final int ox = (int)( this.mMagnification*OFFSET_DUPLICATED_OBJECT_X );
		final int oy = (int)( this.mMagnification*OFFSET_DUPLICATED_OBJECT_Y );

		ArrayList cList = this.duplicateObjects();
		for( int ii=0; ii<cList.size(); ii++ )
		{
			// duplicate
			TimingLine el = (TimingLine)cList.get(ii);

			// translate the duplicate
			SGAxis axis = el.mAxis;
			final boolean horizontal = el.isHorizontal();
			float location = this.calcLocation( el.getValue(), axis, horizontal );
			float locationNew;
			if( el.isHorizontal() )
			{
				locationNew = location + ox;
			}
			else
			{
				locationNew = location + oy;
			}
			double value = this.calcValue( locationNew, axis, horizontal );
			value = this.getNumberInRangeOrder( value, axis );
			el.setValue( value );


			el.setDrawingElementsLocation();

			// set selected
			el.setSelected(true);
			
			// add to the list
			this.addTimingLine_( el );
		}

		if( cList.size()!=0 )
		{
			this.setChanged(true);
		}
		
		// set the location of drawing elements
		this.setAllDrawingElementsLocation();

		return true;
	}



	/**
	 * Paste the objects.
	 * @param list of the objects to be pasted
	 * @return true:succeeded, false:failed
	 */
	public boolean paste( ArrayList list )
	{
		final int ox = (int)( this.mMagnification*OFFSET_DUPLICATED_OBJECT_X );
		final int oy = (int)( this.mMagnification*OFFSET_DUPLICATED_OBJECT_Y );

		int cnt = 0;
		for( int ii=0; ii<list.size(); ii++ )
		{
			Object obj = list.get(ii);
			if( obj instanceof TimingLine )
			{
				TimingLine line = (TimingLine)obj;
				SGProperties p = line.getProperties();
				
				TimingLine el = new TimingLine();
				el.setProperties(p);

				el.mAxis = this.mAxisElement.getAxisInCube( line.mTempAxis );

				el.setDrawingElementsLocation();

				// add to the list
				this.addTimingLine_(el);

				// initialize history
				el.initPropertiesHistory();

				cnt++;
			}
		}

		this.setAllDrawingElementsLocation();
		
		if( cnt!=0 )
		{
			this.setChanged(true);
		}

		this.repaint();

		return true;
	}



	/**
	 * 
	 * @author kuromaru
	 *
	 * この生成されたコメントの挿入されるテンプレートを変更するため
	 * ウィンドウ > 設定 > Java > コード生成 > コードとコメント
	 */
	class TimingLine extends SGTimingLine
		implements ActionListener, WindowListener, SGIUndoable, SGISelectable, SGICopiable
	{
		
		
		/**
		 * 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;
		}

		
		/**
		 * 
		 */
		private SGProperties mTemporaryProperties = null;


		/**
		 * 
		 */
		private JPopupMenu mPopupMenu = new JPopupMenu();

		
		/**
		 * 
		 *
		 */
		protected TimingLine()
		{
			super();
			this.init();
		}
		
		
		/**
		 * 
		 */
		private boolean init()
		{
			this.setLineWidth( SGDefaultValues.TIMING_LINE_WIDTH );
			Integer n = SGDrawingElementLine.getLineTypeFromName( SGDefaultValues.TIMING_LINE_TYPE );
			if( n==null )
			{
				return false;
			}
			this.setLineType( n.intValue() );
			this.setColor( SGDefaultValues.TIMING_LINE_COLOR );

			this.createPopupMenu();

			return true;
		}

		
		
		/**
		 * 
		 * @return
		 */
		private ArrayList getAnchorPointList()
		{
			ArrayList list = new ArrayList();

			Point2D pos0 = new Point2D.Float();
			Point2D pos1 = new Point2D.Float();
			
			final boolean flag = this.isHorizontal();
			float location = calcLocation( this.mValue, this.mAxis, flag );
			Rectangle2D rect = getGraphRect();
			if( flag )
			{
				final float y0 = (float)rect.getY();
				final float y1 = y0 + (float)rect.getHeight();
				pos0.setLocation( location, y0 );
				pos1.setLocation( location, y1 );
			}
			else
			{
				final float x0 = (float)rect.getX();
				final float x1 = x0 + (float)rect.getWidth();
				pos0.setLocation( x0, location );
				pos1.setLocation( x1, location );
			}
			
			list.add( pos0 );
			list.add( pos1 );

			return list;
		}

		
		
		/**
		 * 
		 */
		private boolean createPopupMenu()
		{
			JPopupMenu p = this.mPopupMenu;
			
			p.setBounds( 0, 0, 100, 100 );

			p.add( new JLabel( "  -- Timing Line --" ) );
			p.addSeparator();

			SGUtility.addItem( p, this, MENUCMD_MOVE_TO_FRONT );
			SGUtility.addItem( p, this, MENUCMD_MOVE_TO_BACK );

			p.addSeparator();

			SGUtility.addItem( p, this, MENUCMD_CUT );
			SGUtility.addItem( p, this, MENUCMD_COPY );
			SGUtility.addItem( p, this, MENUCMD_PASTE );

			p.addSeparator();

			SGUtility.addItem( p, this, MENUCMD_DELETE );
			SGUtility.addItem( p, this, MENUCMD_DUPLICATE );

			p.addSeparator();

			SGUtility.addItem( p, this, MENUCMD_PROPERTY );

			return true;
		}


		/**
		 * 
		 */
		public JPopupMenu getPopupMenu()
		{
			return this.mPopupMenu;
		}

		
		/**
		 * 
		 */
		public boolean setDialogProperty()
		{
			final SGTimingLineDialog dg = mTimingLineDialog;

			final int config = mAxisElement.getConfigurationInPlane( this.mAxis );
			dg.setAxisRadioButton( config );
			
			dg.setTiminValueString( this.mValue );
			dg.setLineType( this.mLineType );
			dg.setLineWidth( this.mLineWidth );
			dg.setLineColorList( this.mColorList );

			return true;

		}


		/**
		 * 
		 */
		public boolean setPropertyWithDialog()
		{
			final SGTimingLineDialog dg = mTimingLineDialog;

			TimingLineProperties p = dg.getTimingLineProperties();
			if( p==null )
			{
				return false;
			}

			
			String location = dg.getAxisLocation();
			final int config = mAxisElement.getConfigurationInCube( location );
			SGAxis axis = mAxisElement.getAxisInCube( config );
			p.setAxis( axis );
			

			// not from the dialog
			p.setVisible( this.isVisible() );


			// set properties
			if( this.setProperties(p) == false )
			{
				return false;
			}

			return true;
		}


		/**
		 * Called when the location of data points have changed.
		 */
		public boolean setDrawingElementsLocation()
		{
			final SGAxis axis = this.mAxis;
			final double value = this.mValue;
			final boolean horizontal = this.isHorizontal();

			SGTuple2f[] array = new SGTuple2f[2];
			array[0] = new SGTuple2f();
			array[1] = new SGTuple2f();
			
			if( calcLocationOfTimingLine(
					value,
					axis,
					horizontal,
					array[0],
					array[1] ) == false )
			{
				return false;
			}
			
			if( this.setLocation( array ) == false )
			{
				return false;
			}

			return true;
		}

		
		
		/**
		 * 
		 * @return
		 */
		private boolean isHorizontal()
		{
			final ArrayList hAxisList = mAxisElement.getHorizontalAxisList();
			final ArrayList pAxisList = mAxisElement.getPerpendicularAxisList();

			Boolean hFlag = null;
			if( hAxisList.contains( this.mAxis ) )
			{
				hFlag = Boolean.TRUE;
			}
			if( pAxisList.contains( this.mAxis ) )
			{
				hFlag = Boolean.FALSE;
			}
			if( hFlag == null )
			{
				throw new Error("");
			}

			return hFlag.booleanValue();
		}
		


		/**
		 * 
		 */
		public JDialog getDialog()
		{

			final SGTimingLineDialog dg = mTimingLineDialog;

			this.setDialogProperty();

			dg.setLocation( mDialogOwner.getLocation() );

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


			dg.setActionListener(this);
			dg.addWindowListener(this);


			//
			this.mTemporaryProperties = this.getProperties();


			return dg;
		}



		/**
		 * 
		 */
		public void windowActivated(final WindowEvent e)
		{
		}


		/**
		 * 
		 */
		public void windowDeactivated(final WindowEvent e)
		{
		}


		/**
		 * 
		 */
		public void windowIconified(final WindowEvent e)
		{
		}


		/**
		 * 
		 */
		public void windowDeiconified(final WindowEvent e)
		{
		}


		/**
		 * 
		 */
		public void windowOpened(final WindowEvent e)
		{
		}


		/**
		 * 
		 */
		public void windowClosed(final WindowEvent e)
		{
		}


		/**
		 * 
		 */
		public void windowClosing(final WindowEvent e)
		{
			Object obj = e.getSource();
			if( obj.equals( mTimingLineDialog ) )
			{
				this.onCanceled();
			}
		}


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

			if( command.equals( MENUCMD_PROPERTY ) )
			{
				this.getDialog().setVisible(true);
			}
			else if(
				command.equals( MENUCMD_COPY )
				| command.equals( MENUCMD_CUT )
				| command.equals( MENUCMD_PASTE )
				| command.equals( MENUCMD_DELETE )
				| command.equals( MENUCMD_DUPLICATE )
				| command.equals( MENUCMD_MOVE_TO_FRONT )
				| command.equals( MENUCMD_MOVE_TO_BACK )
			)
			{
				notifyToListener( command );
			}


			final SGTimingLineDialog dg = mTimingLineDialog;
			if( command.equals("OK") || dg.getActiveComponentList().contains(source) )
			{
				if( this.onOK() == false ) return;
			}
			else if( command.equals("Cancel") )
			{
				if( this.onCanceled() == false ) return;
			}
			else if( command.equals("Preview") )
			{
				if( this.onPreviewed() == false ) return;
			}

		}


		/**
		 * 
		 */
		private boolean onOK()
		{

			boolean flag;

			// ダイアログからプロパティを取得して設定
			flag = this.setPropertyWithDialog();
			if( !flag )
			{
				return false;
			}


			// ダイアログを出す前後でプロパティが変更されている場合のみ、
			// 履歴を更新する

			SGProperties pTemp = this.mTemporaryProperties;
			SGProperties pPresent = this.getProperties();
			if( pTemp.equals(pPresent) == false )
			{
				this.setChanged(true);
			}

			this.mTemporaryProperties = null;
			mTimingLineDialog.setVisible(false);

			mTimingLineDialog.removeActionListener();
			mTimingLineDialog.removeWindowListener(this);

			if( this.setDrawingElementsLocation() == false )
			{
				return false;
			}

			notifyChange();

			this.notifyToRoot();

			return true;
		}



		/**
		 * 
		 */
		private boolean onCanceled()
		{
			boolean flag = this.recover();
			if( !flag ) return false;

			this.mTemporaryProperties = null;
			mTimingLineDialog.setVisible(false);

			mTimingLineDialog.removeActionListener();
			mTimingLineDialog.removeWindowListener(this);

			if( this.setDrawingElementsLocation() == false )
			{
				return false;
			}

			notifyChange();

			return true;
		}


		/**
		 * 
		 */
		private boolean onPreviewed()
		{
			boolean flag = this.setPropertyWithDialog();
			if( !flag ) return false;

			if( this.setDrawingElementsLocation() == false )
			{
				return false;
			}

			notifyChange();

			return true;
		}



		/**
		 * 
		 */
		public boolean recover()
		{

			if( this.setProperties( this.mTemporaryProperties ) == false )
			{
				return false;
			}

			this.mTemporaryProperties = null;
			
			return true;
		}

		
		

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


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


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



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

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

			this.mElementGroupSetPropertyHistoryList = list;

			return true;

		}



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


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


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

			SGProperties p
				= (SGProperties)this.mElementGroupSetPropertyHistoryList.get(
					this.mElementGroupSetStateCounter);

			if( this.setProperties(p) == false )
			{
				return false;
			}

			setAllDrawingElementsLocation();
			notifyChange();

			return true;
		}


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

			SGProperties p
				= (SGProperties)this.mElementGroupSetPropertyHistoryList.get(
					this.mElementGroupSetStateCounter);

			if( this.setProperties(p) == false )
			{
				return false;
			}

			setAllDrawingElementsLocation();
			notifyChange();

			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++;

			return true;
		}



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



		/**
		 * 
		 */
		public boolean updateThisObjectHistory()
		{
			this.mElementGroupSetStateCounter++;
			this.addElementGroupSetPropertyHistory( this.getProperties() );
			return true;
		}


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


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

		
		private boolean mChangedFlag = false;

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

		
	
		
		/**
		 * 
		 */
		public Element createElement( final Document document )
		{
			Element element = document.createElement( this.getTagName() );
			if( this.writeProperty( element ) == false )
			{
				return null;
			}
			return element;
		}


		
		public static final String TAG_NAME_TIMING_LINE = "TimingLine";
		
		public String getTagName()
		{
			return TAG_NAME_TIMING_LINE;
		}
		
		

		/**
		 * 
		 */
		public boolean writeProperty( final Element el )
		{
			el.setAttribute( KEY_AXIS_POSITION, mAxisElement.getAxisLocation( this.mAxis ) );
			el.setAttribute( KEY_VALUE, Double.toString( this.mValue ) );
			el.setAttribute( KEY_LINE_WIDTH, Float.toString( this.mLineWidth ) + SGUtilityNumber.pt );
			el.setAttribute( KEY_LINE_TYPE, SGDrawingElementLine.getLineTypeName( this.mLineType ) );
			el.setAttribute( KEY_COLOR_LIST, SGUtilityText.getColorListString( this.mColorList ) );
			return true;
		}

		
		/**
		 * 
		 * @param el
		 * @param p
		 * @return
		 */
		public boolean readProperties( final Element el, SGProperties p )
		{
			if( ( p instanceof TimingLineProperties ) == false ) return false;
			
			TimingLineProperties tp = (TimingLineProperties)p;
			
			
			final String cm = SGUtilityNumber.cm;
			final String pt = SGUtilityNumber.pt;
			final float ratio = SGIConstants.CM_POINT_RATIO;

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


			// line width
			str = el.getAttribute( KEY_LINE_WIDTH );
			if( str.length()==0 )
			{
				return false;
			}
			num = SGUtilityText.getDouble(str,pt);
			if( num==null )
			{
				return false;
			}
			tp.setLineWidth( num.floatValue() );


			// line type
			str = el.getAttribute( KEY_LINE_TYPE );
			if( str.length()==0 )
			{
				return false;
			}
			num = SGDrawingElementLine.getLineTypeFromName(str);
			if( num==null )
			{
				return false;
			}
			tp.setLineType( num.intValue() );


			// color list
			str = el.getAttribute( KEY_COLOR_LIST );
			if( str.length()==0 )
			{
				return false;
			}
			list = SGUtilityText.getColorList(str);
			if( list==null )
			{
				return false;
			}
			tp.setColorList( list );

			
			// value
			str = el.getAttribute( KEY_VALUE );
			if( str.length()==0 )
			{
				return false;
			}
			num = SGUtilityText.getDouble(str);
			if( num==null )
			{
				return false;
			}
			tp.setValue( num.doubleValue() );

			
			str = el.getAttribute( KEY_AXIS_POSITION );
			if( str.length()==0 )
			{
				return false;
			}
			final int config = mAxisElement.getConfigurationInCube(str);
			SGAxis axis = mAxisElement.getAxisInCube( config );
			if( axis==null )
			{
				return false;
			}
			tp.setAxis( axis );

			
			// not from the property file
			tp.setVisible(true);
			
			
			return true;
		}

	
	
	
		/**
		 * 
		 */
		public Object copy()
		{
			TimingLine el = new TimingLine();
			el.setMagnification( this.mMagnification );
			el.setProperties( this.getProperties() );
			el.mTempAxis = mAxisElement.getConfigurationInCube( this.mAxis );
			return el;
		}

		private int mTempAxis = -1;

	}

	
	
	
	/**
	 * 
	 */
	public static class TimingElementProperties extends SGProperties
	{

		ArrayList visibleTimingElementList = new ArrayList();


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


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

			TimingElementProperties p = (TimingElementProperties)obj;
			if( p.visibleTimingElementList.equals(this.visibleTimingElementList) == false )
			{
				return false;
			}

			return true;
		}


		/**
		 * 
		 */
		public String toString()
		{
			String str = new String("[");
			str += this.visibleTimingElementList.toString();
			str += new String("]");

			return str;
		}
		
	}



}
