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

import java.util.*;
import java.io.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import jp.riken.brain.ni.samuraigraph.base.*;
import jp.riken.brain.ni.samuraigraph.data.*;
import jp.riken.brain.ni.samuraigraph.figure.*;


/**
 * 文字列描画用パネルのクラス
 */

public class SGStringElement extends SGFigureElement
	implements SGIStringElement, CaretListener
{

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



	/**
	 * 
	 */
	private ElementString mFocusedString = null;


	/**
	 * 
	 */
	private int mFocusedX = 0;


	/**
	 * 
	 */
	private int mFocusedY = 0;



	/**
	 * 
	 */
	private float mFrameLineWidth = 1.0f;



	/**
	 * 
	 */
	private Point mPressedStringOrigin = null;



	/**
	 * 
	 */
	private JTextField mEditField = null;



	/**
	 * 
	 */
	private Icon mBackgroundImageIcon = null;



	/**
	 * 
	 */
	protected static final int BOUNDARY_LINE_WIDTH = 10;



	/**
	 * コンストラクタ
	 */
	public SGStringElement()
	{
		super();

		this.initEditField();

		this.mBackgroundImageIcon = this.createIcon(SGConstants.RESOURCES_DIRNAME+"Lines.gif");
	}



	/**
	 * 
	 * @return
	 */
	public String toString()
	{
		return new String("SGStringElement");
	}



	/**
	 * 
	 */
	private boolean initEditField()
	{
		this.mEditField = new JTextField(10);
		this.setLayout(null);
		this.add(this.mEditField);
		this.mEditField.setVisible(false);
		this.mEditField.addActionListener(this);
		this.mEditField.addCaretListener(this);

		return true;
	}



	/**
	 * 
	 */
	private ElementString createElementString(
		final String str,
		final float x,
		final float y )
	{
//System.out.println("-- createElementString --");
		final ElementString es = new ElementString( str );
		es.setMagnification( mMagnification );
		es.setLocation( x, y );

		return es;		
	}



	/**
	 * 
	 */
	public boolean addString( final ElementString element )
	{
		if( element==null )
		{
			return false;
		}

		final String str = element.getString();

		// 文字列の長さが0の場合には追加しない
		if( str.length() == 0 )
		{
			return false;
		}

		// 文字列が空白だけの場合にも追加しない
		for( int ii=0; ii<str.length(); ii++ )
		{
			final char c = str.charAt(ii);
			if( c!=' ' && c!='　' )
			{
				break;
			}

			return false;
		}

		final ElementString es = new ElementString( element );
		mElementStringList.add(es);

		// initialize history
		es.initPropertiesHistory();

		return true;
	}



	/**
	 * 引数で指定された点にテキストフィールドを出して、
	 * 文字列を追加する処理を行う
	 */
	public boolean addString( final int x, final int y )
	{

//System.out.println("addString  "+x+" "+y);

		// 
		if( this.getGraphAreaRect().contains( x, y ) == false )
		{
			return false;
		}



		// 前処理
		this.clearFocusedString();


		this.mFocusedX = x;
		this.mFocusedY = y;


		this.mEditField.setVisible(true);

		final String fontFamilyName = SGDefaultValues.LABEL_FONT_FAMILY_NAME;
		final float fontSize = SGDefaultValues.LABEL_FONT_SIZE;
		final int fontStyle = SGUtilityText.getFontStyle( SGDefaultValues.LABEL_FONT_STYLE );
		Font font = new Font(
			fontFamilyName,
			fontStyle,
			(int)(this.mMagnification*fontSize) );
		this.mEditField.setFont( font );

		final String str = "   ";
		final Rectangle2D stringRect = font.getStringBounds(
			str, new FontRenderContext( null, false, false ) );

		final int lx = this.mFocusedX;
		final int ly = this.mFocusedY - (int)(this.mMagnification*fontSize/2.0f);
		final int width = (int)( stringRect.getWidth() + this.mMagnification*fontSize );
		final int height = (int)( stringRect.getHeight() + this.mMagnification*fontSize );

		this.mEditField.setLocation( lx, ly );
		this.mEditField.setSize( width, height );
		this.mEditField.setText("");

		this.repaint();

		return true;

	}



	/**
	 * 
	 * @param g2d
	 */
	public void paintGraphics2D( final Graphics2D g2d )
	{

		super.paintGraphics2D(g2d);


		for( int ii=0; ii<mElementStringList.size(); ii++ )
		{
//System.out.println("ii="+ii);

			final ElementString el
				= (ElementString)mElementStringList.get(ii);

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

//System.out.println(el.getFontSize());

			// draw string
			el.drawString(g2d);


			// 境界線描画
			if( el.mFrameFlag )
			{
				drawFrameLines( el, g2d );
			}
		}


		// テキストフィールドの境界線を描画
		if( this.mEditField.isVisible() )
		{

/*
Border border = new LineBorder(
	Color.BLACK,
//	SGDrawingElementString2D.BOUNDARY_LINE_WIDTH
2
	);
this.mEditField.setBorder( border );
this.mEditField.repaint();
*/


/*
// テキストフィールドを斜線で囲む
Border border = new MatteBorder(this.mBackgroundImageIcon);
this.mEditField.setBorder(border);
*/

			g2d.setPaint(Color.black);
			g2d.setStroke( new BasicStroke(
				BOUNDARY_LINE_WIDTH ) );
			g2d.draw(this.mEditField.getBounds());
			g2d.setStroke( new BasicStroke(2) );
			this.mEditField.repaint();

		}


	}




	/**
	 * 境界線描画
	 */
	private void drawFrameLines( final SGDrawingElementString2DExtended el, final Graphics2D g2d )
	{
		final Rectangle2D rect = el.getElementBounds();
		g2d.setPaint(Color.black);
		g2d.setStroke( new BasicStroke( this.mFrameLineWidth ) );
		g2d.draw( rect );
	}



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

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

		return list;
	}


	/**
	 * 
	 */
	protected boolean setVisibleStringElement( final ArrayList list )
	{
		if( list==null )
		{
			return false;
		}

		for( int ii=0; ii<this.mElementStringList.size(); ii++ )
		{
			ElementString el = (ElementString)this.mElementStringList.get(ii);
			el.setVisible( list.contains(el) );			
		}

		return true;
	}




	/**
	 * 同期を取る。
	 */
	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 )
		{

//System.out.println("******************** From SGIAxisElement ***********************");

			final SGIAxisElement aElement = (SGIAxisElement)element;

			//
			// グラフの描画領域を軸の描画領域に合わせる
			//
		
			final float ratioX = aElement.getGraphAreaX();
			final float ratioY = aElement.getGraphAreaY();
			final float ratioW = aElement.getGraphAreaWidth();
			final float ratioH = aElement.getGraphAreaHeight();

			flag = this.setGraphAreaRect( ratioX, ratioY, ratioW, ratioH );

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


		return flag;
	}



	/**
	 * 同期させる
	 */
	public boolean synchronizeArgument( final SGIFigureElement element )
	{
		if( true )
		{
			throw new Error("");
		}

		return false;
	}



	/**
	 * 
	 */
	public boolean setISize( final SGTuple2f size )
	{
		super.setISize(size);

		this.drawBackAllStringElements();


		// 描画要素を作成
//		repaint();

		return true;

	}



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

		for( int ii=0; ii<mElementStringList.size(); ii++ )
		{
			final ElementString element = (ElementString)mElementStringList.get(ii);
			element.zoom(ratio);
		}

		return true;
	}




	/**
	 * 
	 */
	public boolean setGraphAreaRect(
		final float x, final float y, final float w, final float h )
	{
		super.setGraphAreaRect(x,y,w,h);
		for( int ii=0; ii<this.mElementStringList.size(); ii++ )
		{
			ElementString el = (ElementString)this.mElementStringList.get(ii);
			el.updateDrawingElements();
		}

		return true;
	}





	/**
	 * 
	 */
	public boolean getMarginAroundGraphAreaRect(
		SGTuple2f topAndBottom, SGTuple2f leftAndRight )
	{

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


		Rectangle2D graphRect = this.getGraphAreaRect();

		ArrayList sList = this.getVisibleStringElementList();
		ArrayList rectList = new ArrayList();
		for( int ii=0; ii<sList.size(); ii++ )
		{
			ElementString el = (ElementString)sList.get(ii);
			rectList.add( el.getElementBounds() );
		}

		if( rectList.size()==0 )
		{
			return true;
		}


		Rectangle2D sRect = SGUtility.createUnion(rectList);

		ArrayList list = new ArrayList();
		list.add( graphRect );
		list.add( sRect );

		Rectangle2D uniRect = SGUtility.createUnion(list);

//System.out.println(graphRect);
//System.out.println(lRect);
//System.out.println(uniRect);		

		final float top = (float)( graphRect.getY() - uniRect.getY() );
		final float bottom = (float)( ( uniRect.getY() + uniRect.getHeight() )
			- ( graphRect.getY() + graphRect.getHeight() ) );
		final float left = (float)( graphRect.getX() - uniRect.getX() );
		final float right = (float)( ( uniRect.getX() + uniRect.getWidth() )
			- ( graphRect.getX() + graphRect.getWidth() ) );

		topAndBottom.x += top;
		topAndBottom.y += bottom;
		leftAndRight.x += left;
		leftAndRight.y += right;

//System.out.println(topAndBottom+"  "+leftAndRight);

//System.out.println();

		return true;
	}




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

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

		final int x = e.getX();
		final int y = e.getY();
		final boolean onEdgeFlag = this.onEdge(x,y);

//System.out.println("string:onMouseClicked");
//System.out.println("size="+this.mElementStringList.size());
//System.out.println();

		for( int ii=mElementStringList.size()-1; ii>=0; ii-- )
		{
			final ElementString el = (ElementString)mElementStringList.get(ii);

//System.out.println("ii="+ii+"  "+el);

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


			if( el.contains(x,y) )
			{
//System.out.println("clicked!");

				if( (SwingUtilities.isLeftMouseButton(e) )
				&& ( e.getClickCount() == 1 ) )
				{
					this.mFocusedString = el;
					this.mFocusedX = (int)el.getX();
					this.mFocusedY = (int)el.getY();
//this.mElementStringList.remove(ii);
					this.showEditField( this.mFocusedString );
//					repaint();
				}

				return true;
			}

//System.out.println("onEdgeFlag="+onEdgeFlag);
//System.out.println("focused:"+this.mFocusedString);
//System.out.println();

			if( onEdgeFlag && el.equals(this.mFocusedString) )
			{


				if( (SwingUtilities.isLeftMouseButton(e) )
				&& ( e.getClickCount() == 1 ) )
				{
					return true;
				}
				else if( (SwingUtilities.isLeftMouseButton(e) )
				&& ( e.getClickCount() == 2 ) )
				{
					JDialog dialog = el.getDialog();
					dialog.show();

					this.mEditField.setVisible(false);
				}
				else if( (SwingUtilities.isRightMouseButton(e) )
				&& ( e.getClickCount() == 1 ) )
				{
					JPopupMenu menu = el.getPopupMenu();
					menu.show( this, x, y );
				}

				return true;
			}

		}

		return false;

	}



	/**
	 * 
	 */
	private boolean terminateEditField()
	{

		if( this.mFocusedString==null )
		{
			this.addStringElementFromTextField();
			this.hideEditField();
			return true;
		}

		this.commitEdit();
		this.hideEditField();
		
		return true;
	}



	/**
	 * 
	 * @return
	 */
	private boolean commitEdit()
	{
		String before = this.mFocusedString.getString();
		String after = this.mEditField.getText();
		this.mFocusedString.setString( after );
		if( before.equals(after) == false )
		{
			this.mFocusedString.updateHistory();
		}

		return true;
	}



	/**
	 * 
	 */
	private boolean hideEditField()
	{
		this.mEditField.setText("");
		this.mEditField.setVisible(false);

		return true;
	}



	/**
	 * 
	 * @return
	 */
	private boolean clearFocusedString()
	{
		this.mFocusedString = null;
		this.mPressedStringOrigin = null;
		return true;
	}




	/**
	 * 
	 */
	private boolean showEditField( final ElementString el )
	{

		final Rectangle2D rect = el.getElementBounds();
		final Rectangle2D sRect = el.getStringRect();

		final float fontSize = el.getMagnification()*el.getFontSize();

		final int x = (int)( rect.getX() );
		final int y = (int)( rect.getY() - fontSize/2.0f );
		final int w = (int)( sRect.getWidth() + fontSize );
		final int h = (int)( sRect.getHeight() + fontSize );

		this.mEditField.setVisible(true);
		this.mEditField.setLocation( x, y );
		this.mEditField.setSize( w, h );
		this.mEditField.setFont(
			new Font( el.getFontFamilyName(), el.getFontStyle(), (int)fontSize )
		);
		this.mEditField.setForeground( el.getColor(0) );
		this.mEditField.setText( el.getString() );


		return true;

	}



	/**
	 * 
	 * @return
	 */
	public boolean clearSelectedElements()
	{
		super.clearSelectedElements();

		// 
		if( this.mEditField.isVisible() )
		{
			this.terminateEditField();
		}
		this.clearFocusedString();

		return true;
	}



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


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

		for( int ii=mElementStringList.size()-1; ii>=0; ii-- )
		{
			final ElementString el = (ElementString)mElementStringList.get(ii);
			if( el.isVisible() == false )
			{
				continue;
			}


			if( el.contains(x,y) )
			{
				mFocusedString = el;
				el.mFrameFlag = true;
				mPressedStringOrigin
					= new Point(
						x - (int)el.getX(),
						y - (int)el.getY()
					);

				el.mTemporaryProperties = el.getProperties();

				this.mElementStringList.remove(el);
				this.mElementStringList.add(el);
				this.repaint();

				return true;
			}

		}


		if( this.mFocusedString!=null && this.onEdge(x,y) )
		{
			return true;

		}
		else
		{
			if( this.mEditField.isVisible() )
			{
				this.terminateEditField();
			}

			this.clearFocusedString();
			return false;
		}


	}



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

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

		if( mFocusedString!=null && this.mPressedStringOrigin!=null )
		{
			// 文字列の位置設定
			final float x = (float)(e.getX()-mPressedStringOrigin.x);
			final float y = (float)(e.getY()-mPressedStringOrigin.y);
			mFocusedString.setLocation(x,y);

			return true;
		}
		
		return false;
	}


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

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

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


		// 文字列がフィギュアの外に出ていたら戻す
		this.drawBackStringElement( this.mFocusedString );

		//
		if( this.mFocusedString.isChanged() )
		{
			this.mFocusedString.updateHistory();
		}

		//
		if( this.mFocusedString!=null && this.onEdge(x,y) )
		{
			
		}
		else
		{
//System.out.println("called in onMouseReleased");
			this.clearFocusedString();
//			repaint();
		}

		return true;
	}



	/**
	 * 
	 */
	public boolean onEdge( final int x, final int y )
	{
		final int bWidth = BOUNDARY_LINE_WIDTH;

		final Rectangle rect = this.mEditField.getBounds();
		final int xx = rect.x - bWidth;
		final int yy = rect.y - bWidth;
		final int ww = rect.width + 2*bWidth;
		final int hh = rect.height + 2*bWidth;
		final Rectangle rectNew = new Rectangle( xx, yy, ww, hh );

		final boolean flagShape =  rect.contains( x, y );
		final boolean flagRectNew = rectNew.contains( x, y );

		final boolean flag = (!flagShape)&&(flagRectNew);
		
		return flag;
	}



	/**
	 * 
	 */
	public boolean isResizable( final double w, final double h )
	{
		for( int ii=0; ii<this.mElementStringList.size(); ii++ )
		{
			final ElementString el = (ElementString)this.mElementStringList.get(ii);
			Rectangle2D rect = el.getElementBounds();

			if( w<rect.getWidth() || h<rect.getHeight() )
			{
//System.out.println(w+"<"+rect.getWidth());
				return false;
			}
		}

		return true;

	}



	/**
	 * 
	 */
	public boolean drawBackAllStringElements()
	{
		for( int ii=0; ii<this.mElementStringList.size(); ii++ )
		{
			final ElementString el = (ElementString)this.mElementStringList.get(ii);
			this.drawBackStringElement(el);
		}
		
		return true;
	}



	/**
	 * 
	 */
	public boolean chkBoundsVisible()
	{
/*
		if( this.mElementStringList.size()==0 )
		{
			this.setComponentBoundsVisibleFlag(false);
			return true;
		}


		Rectangle fRect = this.getBounds();

		for( int ii=0; ii<this.mElementStringList.size(); ii++ )
		{
			ElementString el = (ElementString)this.mElementStringList.get(ii);
			Rectangle2D lRect = el.getElementBounds();
			boolean flag = !fRect.contains( lRect );
			if( flag )
			{
				this.setComponentBoundsVisibleFlag(true);
				return true;
			}
		}

		this.setComponentBoundsVisibleFlag(false);
*/

		return true;
	}



	/**
	 * 文字列をパネル内部に引き戻す処理
	 */
	private boolean drawBackStringElement( final ElementString el )
	{

/*
//System.out.println("<< drawBackStringElement >>");

		Rectangle2D elRect = el.getElementBounds();

		if( !this.getBounds().contains( elRect ) )
		{
//System.out.println("Not included!");

			// show the bounds of this component
			this.setComponentBoundsVisibleFlag(true);


			// 矩形の位置とサイズ
			final double x = elRect.getX();
			final double y = elRect.getY();
			final double w = elRect.getWidth();
			final double h = elRect.getHeight();

//System.out.println(x+"  "+y+"  "+w+"  "+h);

			double xx = this.mMagnification*el.getX();
			double yy = this.mMagnification*el.getY();
			if( x < 0.0 )
			{
//System.out.println("x<0.0");
				xx = 0.0;
				el.setLocation( (float)xx, (float)yy );
				elRect = el.getElementBounds();
				xx = this.mMagnification*el.getX() - elRect.getX();
			}
			else if( x + w > this.getWidth() )
			{
//System.out.println("x+w>this.getWidth()");
				xx = this.getWidth() - elRect.getWidth();
				el.setLocation( (float)xx, (float)yy );
				elRect = el.getElementBounds();
				xx = this.mMagnification*el.getX()
						+ ( this.mMagnification*el.getX() - elRect.getX() );
				xx -= this.mFrameLineWidth;
			}

			if( y < 0.0 )
			{
//System.out.println("y<0.0");
				yy = 0.0;
				el.setLocation( (float)xx, (float)yy );
				elRect = el.getElementBounds();
				yy = this.mMagnification*el.getY() - elRect.getY();
			}
			else if( y + h > this.getHeight() )
			{
// System.out.println("y+h>this.getHeight()");
				yy = this.getHeight() - elRect.getHeight();
				el.setLocation( (float)xx, (float)yy );
				elRect = el.getElementBounds();
				yy = this.mMagnification*el.getY()
						+ ( this.mMagnification*el.getY() - elRect.getY() );
				yy -= this.mFrameLineWidth;
			}

			el.setLocation( (float)xx, (float)yy );

		}
		else
		{
			this.setComponentBoundsVisibleFlag(false);
		}

		this.repaint();
*/

		return true;

	}




	/**
	 * 
	 */
	public boolean onKeyPressed( final KeyEvent e )
	{
		return true;
	}


	/**
	 * 
	 */
	public boolean onKeyReleased( final KeyEvent e )
	{
		return true;
	}


	/**
	 * 
	 */
	public boolean onKeyTyped( final KeyEvent e )
	{
		return true;
	}



	/**
	 * 
	 * @param e
	 */
	public boolean onDrawingElement( final int x, final int y )
	{

		for( int ii=mElementStringList.size()-1; ii>=0; ii-- )
		{
			final ElementString el = (ElementString)mElementStringList.get(ii);
			if( el.contains(x,y) )
			{
				el.mFrameFlag = true;
				repaint();
				return true;
			}
			else
			{
				el.mFrameFlag = false;
			}
		}

		repaint();

		return false;
	}




	/**
	 * 
	 */
	public void actionPerformed(final ActionEvent e)
	{

		final Object source = e.getSource();

		if( source.equals( this.mEditField ) )
		{
			this.terminateEditField();
		}

	}



	/**
	 * 
	 */
	public void caretUpdate( final CaretEvent e )
	{

		final String str = this.mEditField.getText();

//System.out.println("@"+str+"@");
//System.out.println(this.mStringBuffer);
//System.out.println(this.mFocusedString);
//System.out.println();


		float fontSize;
		Font font = null;

		// edit
		if( this.mFocusedString != null )
		{
			fontSize = this.mFocusedString.getFontSize();
			font = new Font(
				this.mFocusedString.getFontFamilyName(),
				this.mFocusedString.getFontStyle(),
				(int)(this.mFocusedString.getMagnification()*fontSize) );
		}
		// add
		else
		{
			fontSize = SGDefaultValues.LABEL_FONT_SIZE;
			font = new Font(
				SGDefaultValues.LABEL_FONT_FAMILY_NAME,
				SGUtilityText.getFontStyle( SGDefaultValues.LABEL_FONT_STYLE ),
				(int)(this.mMagnification*fontSize) );
		}

		Rectangle2D stringRect = font.getStringBounds(
			str, new FontRenderContext( null, false, false ) );


		final double width = stringRect.getWidth();
		if( width > this.mEditField.getWidth() )
		{
			this.mEditField.setSize(
				(int)( stringRect.getWidth() + this.mMagnification*fontSize ),
				this.mEditField.getHeight()
			);
		}


		this.repaint();
	}



	/**
	 * 
	 */
	private boolean addStringElementFromTextField()
	{

//System.out.println("addStringElementFromTextField");
//System.out.println(this.mElementStringList);
//System.out.println();

		final ElementString es
			= new ElementString( this.mEditField.getText() );
		es.setMagnification( this.mMagnification );
		es.setLocation( this.mFocusedX, this.mFocusedY );

		this.addString(es);

		//
		this.updateHistory();

		this.hideEditField();

		this.repaint();

		return true;
	}




	/**
	 * 
	 */
	private boolean removeString( final ElementString string )
	{
		for( int ii=this.mElementStringList.size()-1; ii>=0; ii-- )
		{
			final ElementString el = (ElementString)this.mElementStringList.get(ii);
			if( el.equals(string) )
			{
				this.mElementStringList.remove(string);
				return true;
			}
		}

		return false;
	}


	/**
	 * 
	 */
	public boolean createDataObject( final BufferedReader reader, final SGData data ) throws IOException
	{
		super.createDataObject( reader, data );

		return true;
	}



	/**
	 * 
	 */
	public boolean writeProperty( final Writer writer ) throws IOException
	{
		writer.write( PF_STRING_TITLE + "\n" );

//		SGUtilityForPropertyFile.writePropertyLine( writer, PF_CLASS_NAME, this.getClass().getName() );
//		writer.write("\n");

		for( int ii=0; ii<this.mElementStringList.size(); ii++ )
		{
			ElementString el = (ElementString)this.mElementStringList.get(ii);
			el.writeProperty(writer);
		}
		writer.write("\n\n");

		return true;
	}



	/**
	 * 
	 */
	public boolean readProperty( final BufferedReader br ) throws IOException
	{

//System.out.println("SGStringElement : readProperty");

		String line = null;


		// mark
		br.mark(256);


		//
		// string elements
		//

		while( true )
		{

			if( ( line = SGUtilityText.readLine(br) ) == null )
			{
				return false;
			}
//System.out.println(line);

			if( line.equals( PF_STRING_ELEMENT_TITLE ) )
			{
				// create a SGDrawingElementString instance
				ElementString el = new ElementString();

				// set the properties
				boolean flag = this.setElementStringProperty( br, el );
				if( !flag )
				{
					return false;
				}

				// add to the SGStringElement
				this.addString(el);
			}
			else
			{
				br.reset();
				break;
			}


			br.mark(256);

		}

		return true;

	}



	/**
	 * 
	 */
	private SGProperties readProperties( final BufferedReader br )
		throws IOException
	{
	
		String line = null;		
	
		// x-coordinate
		if( ( line = SGUtilityText.readLine(br) ) == null )
		{
			return null;
		}
		Double dX = SGUtilityText.getDoublePropertyFromLine( line, SGStringElement.PF_STRING_X );
		if( dX==null )
		{
			return null;
		}
		float x = dX.floatValue()/SGConstants.CM_POINT_RATIO;
	
	
		// y-coordinate
		if( ( line = SGUtilityText.readLine(br) ) == null )
		{
			return null;
		}
		Double dY = SGUtilityText.getDoublePropertyFromLine( line, SGStringElement.PF_STRING_Y );
		if( dY==null )
		{
			return null;
		}
		float y = dY.floatValue()/SGConstants.CM_POINT_RATIO;
	
	
		// set the text
		if( ( line = SGUtilityText.readLine(br) ) == null )
		{
			return null;
		}
		String text = SGUtilityText.getStringPropertyFromLine( line, SGStringElement.PF_STRING_TEXT );
		if( text==null )
		{
			return null;
		}
	
	
		// set font family name
		if( ( line = SGUtilityText.readLine(br) ) == null )
		{
			return null;
		}
		String fontFamilyName = SGUtilityText.getPropertyFromLine(line,SGStringElement.PF_FONT_FAMILY_NAME);
		if( fontFamilyName==null )
		{
			return null;
		}
	
	
		// set font size
		if( ( line = SGUtilityText.readLine(br) ) == null )
		{
			return null;
		}
		Double dFontSize = SGUtilityText.getDoublePropertyFromLine(line,SGStringElement.PF_FONT_SIZE);
		if( dFontSize==null )
		{
			return null;
		}
		float fontSize = dFontSize.floatValue();
	
	
		// set font style
		if( ( line = SGUtilityText.readLine(br) ) == null )
		{
			return null;
		}
		Integer nStyle = SGUtilityText.getFontStyleFromLine(line,SGStringElement.PF_FONT_STYLE);
		if( nStyle==null )
		{
			return null;
		}
		int fontStyle = nStyle.intValue();
	
	
		// set color
		if( ( line = SGUtilityText.readLine(br) ) == null )
		{
			return null;
		}
		ArrayList colorList = SGUtilityText.getColorListFromLine(line,SGStringElement.PF_STRING_COLOR_LIST);
		if( colorList==null )
		{
			return null;
		}
	
	
		// set angle
		if( ( line = SGUtilityText.readLine(br) ) == null )
		{
			return null;
		}
		Double dAngle = SGUtilityText.getDoublePropertyFromLine(line,SGStringElement.PF_STRING_ANGLE);
		if( dAngle==null )
		{
			return null;
		}
		float angle = dAngle.floatValue()*((float)Math.PI/180.0f);
	
	
		ElementStringProperties p = new ElementStringProperties();
		p.x = x;
		p.y = y;
		p.text = text;
		p.fontFamilyName = fontFamilyName;
		p.fontSize = fontSize;
		p.fontStyle = fontStyle;
		p.colorList = colorList;
		p.angle = angle;
	
	
		return p;
	
	}



	/**
	 * 
	 */
	private boolean setElementStringProperty( final BufferedReader br, final ElementString el )
		throws IOException
	{

		ElementStringProperties p = (ElementStringProperties)this.readProperties(br);
		if( p==null )
		{
			return false;
		}
		if( el.setProperties(p) == false )
		{
			return false;
		}

		return true;
	}



	/**
	 * 親オブジェクトに対して自身の変更を伝える。
	 */
	private boolean updateParentHistoryFromStringElement( ElementString es )
	{
		this.updateParentHistory();
		this.updateObjectHistory(es);

		return true;
	}



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

		this.notifyChange();

//		repaint();

		return true;
	}



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

		this.notifyChange();

//		repaint();

		return true;
	}



	/**
	 *
	 */
	private SGStringElementDialog mDialog = null;
	
	
	/**
	 * 
	 */
	private boolean createDialog()
	{
	
		final SGStringElementDialog dg = new SGStringElementDialog( mDialogOwner, true );
	
		this.mDialog = dg;
	
		return true;
	}
	
	/**
	 * 
	 * @return
	 */
	public boolean setDialogOwner( final Frame frame )
	{
		super.setDialogOwner(frame);
		this.createDialog();
		return true;
	}



	/**
	 * 
	 */
	class ElementString extends SGDrawingElementString2DExtended
		implements ActionListener, WindowListener, SGIUndoable
	{

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


		/**
		 * 
		 */
		private final String REMOVE_STRING = "remove this string";



		/**
		 * 
		 */
		private boolean mVisibleFlag = true;



		/**
		 * 
		 */
		private boolean mFrameFlag = false;



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


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


		/**
		 * 
		 */
		protected ElementString( final String str )
		{
			super(str);
			this.init();
		}


		/**
		 * 
		 */
		private boolean init()
		{
			final int style = SGUtilityText.getFontStyle( SGDefaultValues.LABEL_FONT_STYLE );
			if( style==-1 )
			{
				return false;
			}
			this.setFont(
				SGDefaultValues.LABEL_FONT_FAMILY_NAME,
				style,
				SGDefaultValues.LABEL_FONT_SIZE
			);

			this.setAngle( SGDefaultValues.LABEL_ANGLE );
			this.setColor( SGDefaultValues.LABEL_COLOR );

			this.createPopupMenu();

			return true;
		}


		/**
		 * 
		 */
		protected ElementString( final ElementString element )
		{
			super(element);

			this.mX = element.mX;
			this.mY = element.mY;
			this.mMagnification = element.mMagnification;

this.updateDrawingElements();

			this.createPopupMenu();
		}



		/**
		 * 
		 */
		private boolean createPopupMenu()
		{
			mPopupMenu.setBounds( 0, 0, 100, 100 );

			final String[] commandArray = {
				REMOVE_STRING
			};

			for( int ii=0; ii<commandArray.length; ii++ )
			{
				final JMenuItem item = new JMenuItem(commandArray[ii]);
				item.addActionListener(this);
				mPopupMenu.add(item);
			}

			return true;
		}



		/**
		 * 
		 */
		private JPopupMenu getPopupMenu()
		{
			return mPopupMenu;
		}


		
		/**
		 * 
		 */
		private JDialog getDialog()
		{
//System.out.println("<< getDialog >>");
//System.out.println(this);
//System.out.println();

			mDialog.setActionListener(this);
			mDialog.addWindowListener(this);


			mDialog.setLocation( mDialogOwner.getLocation() );


			// set the frame flag
			this.mFrameFlag = true;

			mDialog.setColorButtonBorder(true);

			this.setDialogProperty();

			this.mTemporaryProperties = this.getProperties();


			mEditField.setVisible(false);
//			repaint();

			return mDialog;
		}



		/**
		 * 
		 */
		public boolean writeProperty( final Writer writer ) throws IOException
		{

			writer.write( PF_STRING_ELEMENT_TITLE + "\n");

			SGUtilityText.writePropertyLine( writer, PF_STRING_X, new Float( this.mX*SGConstants.CM_POINT_RATIO ) );
			SGUtilityText.writePropertyLine( writer, PF_STRING_Y, new Float( this.mY*SGConstants.CM_POINT_RATIO) );
			SGUtilityText.writeStringPropertyLine( writer, PF_STRING_TEXT, this.getString() );

			SGUtilityText.writePropertyLine( writer, PF_FONT_FAMILY_NAME, this.getFontFamilyName() );
			SGUtilityText.writePropertyLine( writer, PF_FONT_SIZE, new Float( this.getFontSize() ) );
			SGUtilityText.writePropertyLine( writer, PF_FONT_STYLE, SGUtilityText.getFontStyleName( this.getFontStyle() ) );
			SGUtilityText.writeColorListPropertyLine( writer, PF_STRING_COLOR_LIST, this.getColorList() );
			SGUtilityText.writePropertyLine( writer, PF_STRING_ANGLE, new Float( this.getAngle()/(Math.PI/180.0) ) );

			return true;
		}



		/**
		 * 
		 */
		private ElementStringProperties getPropertiesFromDialg()
		{

			SGStringElementDialog dg = mDialog;

			ElementStringProperties p = dg.getProperties();
			if( p==null )
			{
				return null;
			}

			p.x = this.mX;
			p.y = this.mY;
			p.text = new String( this.mString );

/*
			p.fontSize = dg.getFontSize();
			p.fontFamilyName = dg.getFontFamilyName();
			p.fontStyle = dg.getFontStyle();
			p.angle = (float)( dg.getAngle()*( (float)Math.PI/180.0f ) );
			p.colorList = dg.getTextColorList();
*/

			return p;

		}




		/**
		 * 
		 */
		private boolean setDialogProperty()
		{

			SGStringElementDialog dg = mDialog;


			// font size
			dg.setFontSize( this.mFontSize );


			// font family name
			dg.setFontFamilyName( this.mFontFamilyName );			


			// font style
			dg.setFontStyle( this.mFontStyle );
			

			// angle
			dg.setAngle( this.mAngle / (Math.PI/180.0) );


			// color
			dg.setTextColorList( this.mColorList );


			return true;
		}



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

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


			ElementStringProperties ep = (ElementStringProperties)p;

			if( p==null ) return false;

			this.mX = ep.x;
			this.mY = ep.y;
			this.setString( ep.text );
//			this.setFontFamilyName( ep.fontFamilyName );
//			this.setFontSize( ep.fontSize );
//			this.setFontStyle( ep.fontStyle );
			this.setFont( ep.fontFamilyName, ep.fontStyle, ep.fontSize );
			this.setAngle( ep.angle );
			this.setColorList( ep.colorList );

			return true;

		}


		/**
		 * 
		 */
		public SGProperties getProperties()
		{
			ElementStringProperties p = new ElementStringProperties();
			p.x = this.mX;
			p.y = this.mY;
			p.text = this.mString;
			p.fontFamilyName = this.mFontFamilyName;
			p.fontSize = this.mFontSize;
			p.fontStyle = this.mFontStyle;
			p.colorList = this.mColorList;
			p.angle = this.mAngle;
			
			return p;
		}




		/**
		 * 
		 */
		public boolean setPropertyWithDialog()
		{

			final SGStringElementDialog dg = mDialog;
			ElementStringProperties p = this.getPropertiesFromDialg();
			if( p==null )
			{
				return false;
			}

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

			return true;

		}


		/**
		 * 
		 */
		public SGTuple2f getLocation()
		{
			final float x = this.mMagnification*this.mX + mGraphAreaX;
			final float y = this.mMagnification*this.mY + mGraphAreaY;
			return new SGTuple2f(x,y);
		}


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

			this.mX = ( x - mGraphAreaX )/this.mMagnification;
			this.mY = ( y - mGraphAreaY )/this.mMagnification;

			for( int ii=0; ii<this.mBaseElementList.size(); ii++ )
			{
				SGDrawingElementString el
					= (SGDrawingElementString)this.mBaseElementList.get(ii);
				el.translate( dx, dy );
			}
			for( int ii=0; ii<this.mSubscriptElementList.size(); ii++ )
			{
				SGDrawingElementString el
					= (SGDrawingElementString)this.mSubscriptElementList.get(ii);
				if( el!=null )
				{
					el.translate( dx, dy );
				}
			}
			for( int ii=0; ii<this.mSuperscriptElementList.size(); ii++ )
			{
				SGDrawingElementString el
					= (SGDrawingElementString)this.mSuperscriptElementList.get(ii);
				if( el!=null )
				{
					el.translate( dx, dy );
				}
			}

			return true;
		}



		/**
		 * 
		 */
		private boolean isChanged()
		{
			final boolean b = !(this.getProperties().equals(this.mTemporaryProperties));
			return b;
		}


		/**
		 * 
		 */
		public void windowActivated(final WindowEvent e)
		{
//System.out.println("windowActivated");
		}


		/**
		 * 
		 */
		public void windowDeactivated(final WindowEvent e)
		{
//System.out.println("windowDeactivated");
		}


		/**
		 * 
		 */
		public void windowIconified(final WindowEvent e)
		{
//System.out.println("windowIconified");
		}


		/**
		 * 
		 */
		public void windowDeiconified(final WindowEvent e)
		{
//System.out.println("windowDeiconified");
		}


		/**
		 * 
		 */
		public void windowOpened(final WindowEvent e)
		{
//System.out.println("windowOpened");
		}


		/**
		 * 
		 */
		public void windowClosed(final WindowEvent e)
		{
//System.out.println("windowClosed");
		}


		/**
		 * 
		 */
		public void windowClosing(final WindowEvent e)
		{
//System.out.println("windowClosing");

			Object obj = e.getSource();
//System.out.println(obj);
			if( obj.equals( mDialog ) )
			{
				this.onCanceled();
			}

		}


		/**
		 * 
		 */
		public void actionPerformed( final ActionEvent e )
		{

			final String strCommand = e.getActionCommand();
			final Object source = e.getSource();


			if( strCommand.equals(REMOVE_STRING) )
			{
				this.setVisible(false);
				callParentUpdateHistory();
				terminateEditField();
				return;
			}


			//
			// for the properties of group
			//

			if( strCommand.equals("OK")
				|| mDialog.getActiveComponentList().contains(source) )
			{
				if( this.onOK() == false ) return;
			}
			else if( strCommand.equals("Cancel") )
			{
				if( this.onCanceled() == false ) return;
			}
			else if( strCommand.equals("Preview") )
			{
				if( this.onPreviewed() == false ) return;
			}

//			repaint();

		}



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

			boolean flag;

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


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

			SGProperties pTemp = this.mTemporaryProperties;
			SGProperties pPresent = this.getProperties();
//System.out.println("before:"+pTemp);
//System.out.println("after:"+pPresent);
			if( pTemp.equals(pPresent) == false )
			{
				// オブジェクト履歴を更新
				this.updateHistory();
			}
//System.out.println();


			this.mTemporaryProperties = null;
			this.mFrameFlag = false;

			notifyChange();


			mDialog.hide();
			mDialog.removeActionListener();
			mDialog.removeWindowListener(this);

			return true;
		}



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

			notifyChange();

			this.mTemporaryProperties = null;

			mDialog.hide();
			mDialog.removeActionListener();
			mDialog.removeWindowListener(this);

			return true;
		}


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

			notifyChange();

			return true;
		}




		/**
		 * 
		 * @return
		 */
		private boolean recover()
		{
			if( this.setProperties( this.mTemporaryProperties ) == false )
			{
				return false;
			}

			mTemporaryProperties = null;

			return true;
		}



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



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



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



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

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

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

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

			this.mStringElementPropertyHistoryList = 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()
		{
//System.out.println("undo : ElemntString");
//System.out.println();

			this.mStringElementStateCounter--;

			ElementStringProperties p
				= (ElementStringProperties)this.mStringElementPropertyHistoryList.get(
					this.mStringElementStateCounter);
//System.out.println(p);
//System.out.println();

			this.setProperties(p);
//			repaint();

			return true;
		}


		/**
		 * リドゥを行う
		 */
		public boolean redo()
		{
//System.out.println("redo : ElemntString");
//System.out.println();

			this.mStringElementStateCounter++;

			ElementStringProperties p
				= (ElementStringProperties)this.mStringElementPropertyHistoryList.get(
					this.mStringElementStateCounter);
//System.out.println(p);
//System.out.println();

			this.setProperties(p);
//			repaint();

			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()
		{

			// 親オブジェクトのオブジェクト履歴を更新
			this.updateParentHistory();


			this.updateThisObjectHistory();

			return true;
		}



		/**
		 * 
		 * @return
		 */
		public boolean updateThisObjectHistory()
		{

			// オブジェクト履歴を更新
			this.updateObjectHistory(this);


			// フィギュアのプロパティ履歴を更新
			this.mStringElementStateCounter++;
			this.addStringPropertyHistory( this.getProperties() );

			return true;
		}



		/**
		 * 親オブジェクトに対して自身の変更を伝える。
		 */
		public boolean updateParentHistory()
		{
			return updateParentHistoryFromStringElement(this);
		}


	}


	/**
	 * 
	 */
	public SGProperties getProperties()
	{
		StringElementProperties p = new StringElementProperties();
		p.visibleStringElementList = this.getVisibleStringElementList();
		return p;
	}



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

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

		StringElementProperties sp = (StringElementProperties)p;

		this.setVisibleStringElement( sp.visibleStringElementList );

		return true;

	}




	/**
	 * 
	 */
	public static class StringElementProperties extends SGProperties
	{
		ArrayList visibleStringElementList = new ArrayList();

		public StringElementProperties()
		{
			super();
		}

		public boolean equals( final Object obj )
		{

			if( ( obj instanceof StringElementProperties ) == false )
			{
				return false;
			}

			StringElementProperties p = (StringElementProperties)obj;
			
			if( p.visibleStringElementList.equals(this.visibleStringElementList) == false )
			{
				return false;
			}

			return true;

		}

	}




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

		float x;
		float y;
		String text;
		float fontSize;
		int fontStyle;
		String fontFamilyName;
		float angle;
		ArrayList colorList;


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


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

			if( p.x != this.x ) return false;	
			if( p.y != this.y ) return false;
			if( p.text.equals(this.text) == false ) return false;
			if( p.fontSize != this.fontSize ) return false;
			if( p.fontStyle != this.fontStyle ) return false;
			if( p.fontFamilyName.equals(this.fontFamilyName) == false ) return false;
			if( p.angle != this.angle ) return false;
			if( p.colorList.equals(this.colorList) == false ) return false;
	
			return true;
		}


		/**
		 * 
		 */
		public String toString()
		{
			String str = new String("[");
			str += new String("x="+x+", ");
			str += new String("y="+y+", ");
			str += new String("text="+text+", ");
			str += new String("fontSize="+fontSize+", ");
			str += new String("fontStyle="+fontStyle+", ");
			str += new String("fontFamilyName="+fontFamilyName+", ");
			str += new String("angle="+angle+", ");
			str += new String("colorList="+colorList+", ");
			str += new String("]");

			return str;
		}
	
	}
	




}
