/*------------------------------------------------------------------------------*
 * File Name: VGraphObj.h														*
 * Creation: Kenny 05/08/2009													*
 * Purpose: OriginC Source file													*
 * Copyright (c) OriginLab Corp. 2009											*
 * All Rights Reserved															*
 *																				*
 * Modification Log:															*
 * Kenny 06/17/2009 GET_DATA_POINTS_IN_CORRECT_RANGE							*
 * Kenny 06/17/2009 IMPROVE_SPEED_OF_SNAP_TO_DATA								*
 *	Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA				*
 * Jasmine 01/20/10 SHOW_NEAREST_POINT											*
 *	Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE			*
 *	Folger 01/27/10 VERTICAL_CURSOR_SNAP_TO_NEAREST_X_DATA						*
 *	Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG						*
 *	RVD 1/28/2010 QA70-14966-p4 POLYGON_REVERSE_VIDEO							*
 *------------------------------------------------------------------------------*/

#ifndef __VGRAPH_OBJ_H__
#define __VGRAPH_OBJ_H__

#include <Origin.h>
#include <..\OriginLab\grobj_utils.h>
#include <..\OriginLab\graph_utils.h>
#include <Array.h>

/*----------------------------------------------------------------------------*/
/* Class Diagram
/*----------------------------------------------------------------------------*/

/*
                             +----------------+
                             |  GraphObject   |
                             +--------.-------+
                                     /_\
                                      |
                                      |
                             +---------------------+
                             |  VGraphObject       |
                             |---------------------|
                             |   Create()          |
                             | SetLabTalkScript()  |
                             +--------.------------+
                                     /_\
       +------------------------------+-------------------------------------+
       |                              |                                     |
+------+--------+          +----------------------+             +-----------+----------+
|     VTag      |          |        VLabel        |             |  VGraphLine          |
+---------------|          |----------------------|             |----------------------|
|  Create()     |          |  UpdateFontSize()    |             |  Create()            |
+---------------+          +-----------.----------+             |  GetMovedPosition()  |
                                      /_\                       |  GetMovingPosition() |
                                       |                        |  SetPosition()       |
                                       |                        +----------------------+
                           +-----------+------------+
                           |                        |
                   +-------+-------+        +-------+-------+
                   |   VTagLabel   |        |   VTextLabel  |
                   +---------------|        |---------------|
                   |    Create()   |        |    Create()   |
                   +---------------+        +---------------+
*/

/*----------------------------------------------------------------------------*/
/* Global macros/typedef/enum...
/*----------------------------------------------------------------------------*/

#define ASSIGN_VGRAPH_OBJECT_NAME	// Kenny: Setting the name of these GraphObjects is not a must but only for debugging/developing convenient

#ifdef ASSIGN_VGRAPH_OBJECT_NAME
	#define STR_VGRAPH_LINE_NAME_PREFIX					"VLine"
	#define STR_VTAG_NAME_PREFIX						"VTag"
	#define STR_VTAG_LABEL_NAME_PREFIX					"VTagLabel"
	#define STR_VTEXT_LABEL_NAME_PREFIX					"VTextLabel"
#endif // ASSIGN_VGRAPH_OBJECT_NAME

#define DEFAULT_VTAG_LENGTH_SCALE_PERCENT				0.1
#define DEFAULT_VTAG_WIDTH								3

#define GAP_BETWEEN_TAG_AND_LABEL_SCALE_PERCENT			0.01
#define GAP_BETWEEN_VCURSOR_AND_LABEL_PERCENT			0.02

#define DEFAULT_GRAPH_LINE_INIT_PAGE_PERCENT			50

#define INC_DEC_FIXED_FONT_SIZE_PERCENT					0.2
#define INCREASE_UPDATE_FONT_SIZE_PERCENT				(1.0 + INC_DEC_FIXED_FONT_SIZE_PERCENT)
#define DECREASE_UPDATE_FONT_SIZE_PERCENT				(1.0 - INC_DEC_FIXED_FONT_SIZE_PERCENT)

#define TEXT_LABEL_MAX_OFFSET_SCALE_PERCENT				0.2

#define GET_LAYER_SCALE_WIDTH(gl)						fabs(gl.X.To - gl.X.From)
#define GET_LAYER_SCALE_HEIGHT(gl)						fabs(gl.Y.To - gl.Y.From)

#define GET_LAYER_SCALE_WIDTH_PERCENT(gl, dPercent)		(GET_LAYER_SCALE_WIDTH(gl) * fabs(dPercent) )
#define GET_LAYER_SCALE_HEIGHT_PERCENT(gl, dPercent)	(GET_LAYER_SCALE_HEIGHT(gl) * fabs(dPercent) )

typedef int (*PFN_GET_PLOT_COLOR)(const DataPlot& dp);
#define GET_PFN_GET_PLOT_COLOR							(PFN_GET_PLOT_COLOR)Project.FindFunction("get_plot_color", "OriginLab\\graph_utils")

#define STR_MARKED_X_STORAGE_NAME						"X"
#define STR_MARKED_Y_STORAGE_NAME						"Y"

/*----------------------------------------------------------------------------*/

#define DECLARE_METHOD_ATTACH_OBJ_TO_UID(baseobjType)	\	
	BOOL AttachTo(UINT uid)\
	{\
		baseobjType obj;\
		obj = (baseobjType)Project.GetObject(uid);\
		if (obj)\
		{\
			*this = obj;\
			return TRUE;\
		}\
		return FALSE;\
	}

/*----------------------------------------------------------------------------*/
/* Utilities
/*----------------------------------------------------------------------------*/

#ifdef _DEBUG
void  _my_dbg_output(LPCSTR lpcszText, LPCSTR lpcszFile = NULL)
{
	if (!lpcszText)
	{
		ASSERT(FALSE);
		return;
	}

	SYSTEMTIME st;
	TM tmLocal;
	time_t aclock;

	time( &aclock );
	convert_time_to_local( &aclock , &tmLocal);
	tm_to_systemtime(&tmLocal, &st);
	double dDate;
	SystemTimeToJulianDate(&dDate, &st);
	string strText;
	strText.Format("%s %s", get_date_str(dDate, LDF_SHORT_AND_HHMMSS_SEPARCOLON), lpcszText);

	ERR_MSG(strText);

	if (lpcszFile)
	{
		FILE* fstream;
		if( fstream = fopen(lpcszFile, "a+") )
		{
			fputs(strText, fstream);
			fputs("\r\n", fstream);
			fclose( fstream );
		}
		else
			ASSERT(FALSE);
	}
}
//#define MY_DBG_OUTPUT(str)					do{ _my_dbg_output(str, "C:\\vlog.txt")}while(0)
#define MY_DBG_OUTPUT(str)						do{ _my_dbg_output(str); }while(0)
#define MY_DBG_OUTPUT1(fm, arg)					do{ string junk; junk.Format(fm, arg); _my_dbg_output(junk); }while(0)
#define MY_DBG_OUTPUT2(fm, arg1, arg2)			do{ string junk; junk.Format(fm, arg1, arg2); _my_dbg_output(junk); }while(0)
#define MY_DBG_OUTPUT3(fm, arg1, arg2, arg3)	do{ string junk; junk.Format(fm, arg1, arg2, arg3); _my_dbg_output(junk); }while(0)
#else
#define MY_DBG_OUTPUT(str)				
#define MY_DBG_OUTPUT1(fm, arg)				
#define MY_DBG_OUTPUT2(fm, arg1, arg2)		
#define MY_DBG_OUTPUT3(fm, arg1, arg2, arg3)
#endif

class CStopWatch
{
public:
	CStopWatch(LPCSTR lpcszTips = NULL)
	{
#ifdef _DEBUG
		m_nStartTime = GetTickCount();
		m_strTips = lpcszTips;
#endif // _DEBUG
	}
	~CStopWatch()
	{
		MY_DBG_OUTPUT2( "%-30s = %5u ms", m_strTips, GetTickCount() - m_nStartTime );
	}
private:
#ifdef _DEBUG
	UINT	m_nStartTime;
	string	m_strTips;
#endif // _DEBUG
};

// Kenny: Maybe we should move this function to grobj_utils.h/c
string ftoa_sd(double dVal, int nSignificantDigits = -1)
{
	string strFmt;
	if (nSignificantDigits < MIN_SIGNIFICANT_DIGITS || nSignificantDigits > MAX_SIGNIFICANT_DIGITS)
		strFmt = "*";	// Free option
	else
		strFmt.Format("*%d*", nSignificantDigits);
	return ftoa(dVal, strFmt);
}

bool is_object_valid_child_of_page(OriginObject& obj, GraphPage& gpSupposedParent)
{
	if ( obj.IsValid() )
	{
		GraphLayer glParent;
		obj.GetParent(glParent);
		if ( glParent.IsValid() )
		{
			GraphPage gpParent;
			glParent.GetParent(gpParent);
			if ( gpParent.IsValid() )
				return ( gpParent.GetUID(TRUE) == gpSupposedParent.GetUID(TRUE) );
		}
	}
	return false;
}

/*----------------------------------------------------------------------------*/
/* VGraphLayer
/*----------------------------------------------------------------------------*/

class VTextLabel;

class VGraphLayer : public GraphLayer
{
public:
	VGraphLayer( Layer& layer = NULL ) : GraphLayer(layer)
	{
		m_arrTextLabels.SetAsOwner(TRUE);
	}
public:
	DECLARE_METHOD_ATTACH_OBJ_TO_UID(GraphLayer)

	BOOL ScaleToPageCoordinate(double dScaleCoord, double& rPageCoord, bool bX = true)
	{
		if (IsValid())
		{
			return WorldToView(&dScaleCoord, &rPageCoord, 1, bX ? 1 : 2);
		}
		return FALSE;
	}
	BOOL PageToScaleCoordinate(double rPageCoord, double& rScaleCoord, bool bX = true)
	{
		if (IsValid())
		{
			return ViewToWorld(&rPageCoord, &rScaleCoord, 1, bX ? 1 : 2);
		}
		return FALSE;
	}
	BOOL ScaleToPageUnit(double dScaleCoord, double& rdPageCoord, int nPageUnitsTo = -1, bool bX = true)
	{
		int nXPage, nYPage;
		if ( !WorldToPage(nXPage, nYPage, dScaleCoord, dScaleCoord) )
			return FALSE;

		double dPosVal[TOTAL_POS] = {0};
		if (bX)
			dPosVal[LEFT_POS] = nXPage;
		else
			dPosVal[TOP_POS] = nYPage;
		if( !UnitsConvert(nPageUnitsTo, dPosVal, M_PIXEL) )
			return FALSE;
		rdPageCoord = bX ? dPosVal[LEFT_POS] : dPosVal[TOP_POS];
		return TRUE;
	}
	BOOL PageUnitToScale(double dPageCoord, double& rdScaleCoord, int nPageUnitsFrom = -1, bool bX = true)
	{
		double dPosVal[TOTAL_POS] = {0};
		if (bX)
			dPosVal[LEFT_POS] = dPageCoord;
		else
			dPosVal[TOP_POS] = dPageCoord;
		if( !UnitsConvert(M_PIXEL, dPosVal, nPageUnitsFrom) )
			return FALSE;

		double dXScale, dYScale;
		if ( !PageToWorld(dXScale, dYScale, dPosVal[LEFT_POS], dPosVal[TOP_POS]) )
			return FALSE;

		rdScaleCoord = bX ? dXScale : dYScale;
		return TRUE;
	}
	
	Array<VTextLabel&>&		AccessTextLabels(bool bDelete = false, bool bForceInit = false)
	{
		/*
		Tree				tr;
		string				strStorageName = "VGraphLayer";
		Array<VTextLabel&>*	pArr = NULL;
			
		if ( bForceInit || !GetBinaryStorage(strStorageName, tr) )
		{
			if ( bDelete )
				return NULL;
			
			pArr = new Array<VTextLabel&>(TRUE);			
			tr.TextLabels.nVal = (DWORD)pArr;
			PutBinaryStorage(strStorageName, tr);
		}
		else if ( tr.TextLabels )
		{
			try
			{
				pArr = (Array<VTextLabel&>*)(DWORD)(tr.TextLabels.nVal);
			}
			catch ( int nErr )
			{
				return NULL;
			}
			
			if ( bDelete )
			{
				delete pArr;
				SetMemory(strStorageName, NULL);
				return NULL;
			}
		}
		if (NULL == pArr)
			return NULL;
		return *pArr;
		*/
		return m_arrTextLabels;
	}
	
	void					RemoveTextLabels()
	{
		AccessTextLabels(true);
	}
	
	Array<VTextLabel&>		m_arrTextLabels;
};

/*----------------------------------------------------------------------------*/
/* VDataPlot
/*----------------------------------------------------------------------------*/

class VDataPlot : public DataPlot
{
public:
	VDataPlot( DataPlot& dp ) : DataPlot(dp)
	{
	}
public:
	DECLARE_METHOD_ATTACH_OBJ_TO_UID(DataPlot)

	//int GetYValues(double dXScale, vector& rvYScales, vector<BOOL>& vbIsInterpolateds = NULL)	///Jasmine 01/20/10 SHOW_NEAREST_POINT
	int GetYValues(double dXScale, vector& rvYScales, vector<BOOL>& vbIsInterpolateds = NULL, vector<int>& vnIndex = NULL)
	{
		// TODO, need to support multiple points in the same X position
		int iPointIndex = -1;
		int iRet = XIndex(dXScale, iPointIndex, XIC_NEAREST_ANY);
		
		///Jasmine 01/20/10 SHOW_NEAREST_POINT
		if(vnIndex)
			vnIndex.Add(iPointIndex);
		//End SHOW_NEAREST_POINT
		
		bool bNeedInterpolated = true;
		if (XIR_WITHIN_RANGE == iRet)
		{
			double dDataPlotX, dDataPlotY;
			GetDataPoint(iPointIndex, &dDataPlotX, &dDataPlotY);
			if ( is_equal(dXScale, dDataPlotX) )
			{
				rvYScales.Add(dDataPlotY);
				bNeedInterpolated = false;
				if (vbIsInterpolateds)
					vbIsInterpolateds.Add(false);
			}
		}
		if (bNeedInterpolated)
		{
			vector vX, vY;
			///Kenny 06/17/2009 GET_DATA_POINTS_IN_CORRECT_RANGE
			//GetDataPoints(0, -1, vX, vY);
			DataPlot::GetDataPoints(i1, i2, vX, vY);
			///End GET_DATA_POINTS_IN_CORRECT_RANGE

			double dY;
			iRet = ocmath_interpolate(&dXScale, &dY, 1, vX, vY, vX.GetSize());
			if (OE_NOERROR == iRet)
			{
				rvYScales.Add(dY);
				if (vbIsInterpolateds)
					vbIsInterpolateds.Add(true);
			}
		}
		return rvYScales.GetSize();
	}

	int GetYValues(int nXPage, vector& rvYScales, vector<BOOL>& vbIsInterpolateds = NULL)
	{
		GraphLayer gl;
		GetParent(gl);
		VGraphLayer vgl(gl);
		double dXScale;
		if ( vgl.PageToScaleCoordinate(nXPage, dXScale) )
			return GetYValues(dXScale, rvYScales, vbIsInterpolateds);
		return -1;
	}
	
	///Kenny 06/17/2009 IMPROVE_SPEED_OF_SNAP_TO_DATA
	/*
	int GetDataPoints( int nrFrom, int nrTo, vector<int>& vPageX, vector<int>& vPageY = NULL, vector& vz = NULL, vector& vErr = NULL, DWORD dwFirstCntrl = 0, int nPts = 800)
	{
		vector vdScaleX, vdScaleY;
		int nRet = DataPlot::GetDataPoints(nrFrom, nrTo, vdScaleX, vdScaleY, vz, vErr, dwFirstCntrl, nPts);
		const int nXSize = vdScaleX.GetSize();
		const int nYSize = vdScaleY.GetSize();
		GraphLayer gl;
		GetParent(gl);
		if (!gl)
		{
			ASSERT(FALSE);
			return -1;
		}

		vPageX.SetSize(nXSize);
		VGraphLayer vgl(gl);
		for (int ii = 0; ii < nXSize; ++ii)
		{
			vgl.ScaleToPageCoordinate(vdScaleX[ii], vPageX[ii]);
		}
		if (vPageY)
		{
			vPageY.SetSize(nYSize);
			for (int ii = 0; ii < nXSize; ++ii)
			{
				vgl.ScaleToPageCoordinate(vdScaleY[ii], vPageY[ii], false);
			}
		}
		return nRet;
	}
	*/
	///End IMPROVE_SPEED_OF_SNAP_TO_DATA

	///Kenny 06/17/2009 IMPROVE_SPEED_OF_SNAP_TO_DATA
	BOOL GetNearestPoint( double rRefPageX, double& rNearestPageX, DWORD dwFirstCntrl = XIC_NEAREST_ANY, double* prNearestX = NULL)
	{
		GraphLayer gl;
		GetParent(gl);
		VGraphLayer vgl(gl);
		double dScaleX;

		vgl.PageToScaleCoordinate(rRefPageX, dScaleX);

		if ( dScaleX < vgl.X.From )
			dScaleX = vgl.X.From;
		else if ( dScaleX > vgl.X.To )
			dScaleX = vgl.X.To;

		int nPointIndex;
		if ( XIR_ERROR == XIndex(dScaleX, nPointIndex, dwFirstCntrl) && XIR_ERROR == XIndex(dScaleX, nPointIndex, XIC_NEAREST_ANY) )
			return FALSE;

		if ( !GetDataPoint(nPointIndex, &dScaleX, NULL) )
			return FALSE;

		if ( prNearestX )
			*prNearestX = dScaleX;
		vgl.ScaleToPageCoordinate(dScaleX, rNearestPageX);
		
		return TRUE;
	}
	///End IMPROVE_SPEED_OF_SNAP_TO_DATA
};

/*----------------------------------------------------------------------------*/
/* VGraphObject/VTag/VLabel
/*----------------------------------------------------------------------------*/

struct VGROBJ_PROPERTIES 
{
	UINT	uAttachedObjUID;	// The attached GraphObject's UID. Note: this variable name must be suffixed with "UID" since we depend on it to reuid in VC level.
};

#define CREATE_VGRAPH_OBJECT(create_act)	\
	if (IsValid())\
	{\
		ASSERT(FALSE);\
		return TRUE;\
	}\
	create_act;\
	return UpdateUID();

#define DECLARE_METHOD_GET_VALID_OBJ(objT)	\
	virtual objT& GetValidObj()\
	{\
		if ( !IsValid() && 0 != GetAttachedObjUID() )\
		{\
			AttachTo( GetAttachedObjUID() );\
		}\
		return *this;\
	}

class VGraphObject : public GraphObject
{
public:
	VGraphObject(BOOL bDestroyOnDestruct = TRUE)
	{
		m_bDestroyOnDestruct = bDestroyOnDestruct;
		m_Properties.uAttachedObjUID = 0;
	}
	VGraphObject(GraphObject & goOriginal) : GraphObject(goOriginal)
	{
		UpdateUID();
	}
	~VGraphObject()
	{
		if (m_bDestroyOnDestruct && IsValid())
		{
			MY_DBG_OUTPUT("Destroying " + GetRangeString() );
			if (!Destroy())
				ASSERT(FALSE);
		}
	}
public:
	// Kenny: Since the GraphObject may be invalid after specific operations ( e.g. made some change in plot detail dialog )
	// we have to "re-attach" the original GraphObject for these cases, so this GetValidObj() method is supposed to be called to access
	// any member of this/derived class.
	// That means we can't access the member directly, any code like "vgo.X += 1;" should be replaced to "vgo.GetValidObj().X += 1;".
	DECLARE_METHOD_GET_VALID_OBJ(VGraphObject)

	// The derived classes should NOT override these following Create(...) methods, unless the same codes are included/called in it as below.
	BOOL Create(DWORD dwParam = 0)
	{
		CREATE_VGRAPH_OBJECT( CreateObj(dwParam) );
	}

	BOOL Create(const GraphLayer& gl, double dXScaleMark, double dYScaleMark, DWORD dwExtraParam = 0)
	{
		CREATE_VGRAPH_OBJECT( CreateObj(gl, dXScaleMark, dYScaleMark, dwExtraParam) );
	}

	virtual UINT GetUID()
	{
		if (0 == GetAttachedObjUID() )
			UpdateUID();
		return GetAttachedObjUID();
	}

	virtual BOOL SetLabTalkScriptEvent(LPCSTR lpcszScript, int nExecMode = GRCT_ANY_EVENT, int nSetDeletable = 1)
	{
		return set_LT_script(*this, lpcszScript, nExecMode, nSetDeletable);
	}

	virtual BOOL GetLabTalkScriptEvent(string& strLTScript, int& nExecMode = NULL)
	{
		return get_go_LTScript_event(*this, strLTScript, nExecMode);
	}

	virtual string GetRangeString()
	{
		string strRange;
		GraphLayer glParent;
		GetParent(glParent);
		if (glParent)
		{
			glParent.GetRangeString(strRange);
			strRange += GetName();
		}
		return strRange;
	}

	double	GetCenterPageX()
	{
		return WorldToView(TRUE);
	}

	double	GetCenterPageY()
	{
		return WorldToView(FALSE);
	}

	virtual BOOL GetProperties(TreeNode& tnProperties)
	{
		if ( tnProperties.IsValid() )
		{
			tnProperties += m_Properties;
			return TRUE;
		}
		return FALSE;
	}
	virtual BOOL SetProperties(const TreeNode& tnProperties)
	{
		if (tnProperties.IsValid())
		{
			m_Properties = tnProperties;
			GetValidObj();
			return TRUE;
		}
		return FALSE;
	}

	virtual BOOL IsValid(GraphPage& gpSupposedParent)
	{
		return is_object_valid_child_of_page(*this, gpSupposedParent);
	}

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	virtual	void	SnapToPlot(DataPlot& dp)
	{
		m_dpSnapTo = dp;
	}

	void	SnapToNone()
	{
		DataPlot	dp;
		m_dpSnapTo = dp;
	}

	BOOL	GetParent(GraphLayer& gl)
	{
		if ( m_dpSnapTo )
			return m_dpSnapTo.GetParent(gl);
		
		return GraphObject::GetParent(gl);
	}
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
protected:
	// Comments last updated by Kenny on 06/02/2009
	/**$
		Remarks:
			Create a GraphObject at the proper position to mark/indicate a specified point.
			There may be some offset towards the marked point in separate cases.
			!!!*** All the derived classes must implement one of this two virtual functions for Create() to work ***!!!!!!!!!!!!!!!!!!!!!!!!!!!
		Parameters:
			gl = [input] The GraphLayer this GraphObject belongs to.
			dXScaleMark = [input] The X scale coordinate this GraphObject indicating/marking, the real position depends on separate case.
			dYScaleMark = [input] The Y scale coordinate this GraphObject indicating/marking, the real position depends on separate case.
		Return:
			Return TRUE if success, or FALSE if failed.
	*/
	virtual BOOL CreateObj(const GraphLayer& gl, double dXScaleMark, double dYScaleMark, DWORD dwExtraParam = 0)
	{
		ASSERT(FALSE);
		return FALSE;
	}
	virtual BOOL CreateObj(DWORD dwParam = 0)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	virtual BOOL UpdateUID()
	{
		if (IsValid())
		{
			m_Properties.uAttachedObjUID = GetUID(TRUE);
			return TRUE;
		}
		ASSERT(FALSE);
		return FALSE;
	}

	UINT GetAttachedObjUID() { return m_Properties.uAttachedObjUID; }

	DECLARE_METHOD_ATTACH_OBJ_TO_UID(GraphObject)

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	double	WorldToView(BOOL bX)
	{
		VGraphLayer	vgl;
		GetParent(vgl);
		double	rView;
		vgl.ScaleToPageCoordinate(bX ? X() : Y(), rView);
		return rView;
	}

	virtual	double	X()
	{
		return X;
	}

	virtual	double	Y()
	{
		return Y;
	}

protected:
	DataPlot				m_dpSnapTo;
	
	BOOL					m_bDestroyOnDestruct;	// A flag indicating whether or not destroy the GraphObject when destructing this object.
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE

private:
	VGROBJ_PROPERTIES		m_Properties;			// Properties that have to be saved into project/page
};

class VTag : public VGraphObject
{
public:
	VTag(BOOL bDestroyOnDestruct = FALSE) : VGraphObject(bDestroyOnDestruct)
	{
		;
	}
	DECLARE_METHOD_GET_VALID_OBJ(VTag)
protected:
	virtual BOOL CreateObj(const GraphLayer& gl, double dXScaleMark, double dYScaleMark, DWORD dwExtraParam = 0)
	{
		///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE	
		//const double dLineLen		= GET_LAYER_SCALE_HEIGHT_PERCENT(gl, DEFAULT_VTAG_LENGTH_SCALE_PERCENT);
		//const double dHalfLineLen	= dLineLen / 2;
		//const double dY0			= dYScaleMark - dHalfLineLen;
		//const double dY1			= dYScaleMark + dHalfLineLen;
		const double dY0 = 0;
		const double dY1 = 100;
		///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE

		const bool bSpan	= false;
		const bool bPercent	= true;

		bool bRet = add_line(gl, *this, dXScaleMark, dY0, ATTACH_TO_PAGE/*ATTACH_TO_SCALE*/, LN_VERTICAL, bSpan, bPercent, dXScaleMark, dY1, SYSCOLOR_BLUE);
		if ( !bRet || !IsValid() )
		{
			ASSERT( FALSE );
			MY_DBG_OUTPUT("Failed to create VTag object!");
			return FALSE;
		}

		update_go_states(*this, GOC_NO_STATE|GOC_NO_IN_PLACE_EDIT);
		//_set_go_width(*this, DEFAULT_VTAG_WIDTH);
		GraphObject go(*this);
		go.Attach = ATTACH_TO_SCALE;

#ifdef ASSIGN_VGRAPH_OBJECT_NAME
		SetName(STR_VTAG_NAME_PREFIX, OCD_ENUM_NEXT);
#endif // ASSIGN_VGRAPH_OBJECT_NAME
		return TRUE;
	}
};

struct VLABEL_PROPERTIES 
{
	UINT	uAttachedPlotUID;	// The attached plot's UID. Note: this variable name must be suffixed with "UID" since we depend on it to reuid in VC level.
};

class VLabel : public VGraphObject
{
public:
	VLabel(BOOL bDestroyOnDestruct = TRUE) : VGraphObject(bDestroyOnDestruct)
	{
	}
public:
	BOOL Create(const GraphLayer& gl, double dXScaleMark, double dYScaleMark, DWORD dwExtraParam = 0)
	{
		BOOL bRet = VGraphObject::Create(gl, dXScaleMark, dYScaleMark, dwExtraParam);
		if (bRet)
			bRet = MarkTo(dXScaleMark, dYScaleMark);
		return bRet;
	}
	// Comments last updated by Kenny on 06/12/2009
	/**$
		Remarks:
			Make this graph object marks/indicates to the specified point.
		Example1:
			
		Parameters:
			dXScaleMark = [input] X scale coordinate.
			dYScaleMark = [input] Y scale coordinate.
		Return:
			Return TRUE if success, or FALSE if failed.
	*/
	virtual BOOL MarkTo(double dXScaleMark, double dYScaleMark)
	{
		//MY_DBG_OUTPUT3("%-35s MarkTo(%g, %g)", GetRangeString(), dXScaleMark, dYScaleMark);
		StringArray	saStorageNames;
		vector		vdStorageValues;
		GetStorageNumericVars(&saStorageNames, &vdStorageValues);

		if ( saStorageNames.GetSize() != vdStorageValues.GetSize() )
		{
			ASSERT(FALSE);
			return error_report("MarkTo() failed since the size of the two storage array is not the same!", true);
		}

		const int nXStorageIndex = saStorageNames.Find(STR_MARKED_X_STORAGE_NAME);
		const int nYStorageIndex = saStorageNames.Find(STR_MARKED_Y_STORAGE_NAME);

		if ( nXStorageIndex < 0 )
		{
			saStorageNames.Add(STR_MARKED_X_STORAGE_NAME);
			vdStorageValues.Add(dXScaleMark);
		}
		else
		{
			vdStorageValues[nXStorageIndex] = dXScaleMark;
		}

		if ( nYStorageIndex < 0 )
		{
			saStorageNames.Add(STR_MARKED_Y_STORAGE_NAME);
			vdStorageValues.Add(dYScaleMark);
		}
		else
		{
			vdStorageValues[nYStorageIndex] = dYScaleMark;
		}

		BOOL bRet = SetStorageNumericVars(&saStorageNames, &vdStorageValues) > 0;
		Invalidate();	// Invalidate this object to force redrawing it.
		return bRet;
	}

	virtual BOOL UpdateFontSize(bool bIncrease = true)
	{
		if ( IsValid() )
		{
			Tree trFormat;
			const BOOL bGetTagNames	= TRUE;
			const BOOL bRelative	= TRUE;
			const BOOL bRepaint		= TRUE;
			trFormat = GetFormat(FPB_ALL, FOB_ALL, bGetTagNames, bRelative);

			const int iOriginSize = trFormat.Root.Font.Size.nVal;
			const double dNewFontSizePercent = bIncrease ? INCREASE_UPDATE_FONT_SIZE_PERCENT : DECREASE_UPDATE_FONT_SIZE_PERCENT;
			int iNewSize = nint(iOriginSize * dNewFontSizePercent);

			if (iNewSize == iOriginSize)
				iNewSize += bIncrease ? 1 : -1;

			if (iNewSize > 0)
			{
				trFormat.Root.Font.Size.nVal = iNewSize;
				int nErr = UpdateThemeIDs(trFormat.Root);
				if(0 == nErr)
					return ApplyFormat(trFormat, bRepaint, bRelative);
			}
		}
		return FALSE;
	}

	virtual BOOL AttachToPlot(const DataPlot& dp)
	{
		if (dp.IsValid())
		{
			m_Properties.uAttachedPlotUID = dp.GetUID(TRUE);
			PFN_GET_PLOT_COLOR pfn_get_plot_color = GET_PFN_GET_PLOT_COLOR;
			if (pfn_get_plot_color)
			{
				const int nColor = pfn_get_plot_color(dp);
				return set_go_color(*this, nColor);
			}
			else
				return FALSE;
		}
		return FALSE;
	}

	virtual UINT GetAttachedPlotUID() { return m_Properties.uAttachedPlotUID; }

	virtual string GetAttachedPlotName()
	{
		DataPlot dp;
		dp = (DataPlot)Project.GetObject(m_Properties.uAttachedPlotUID);
		if (dp)
			return dp.GetName();
		return "";	// "Invalid";
	}

	virtual BOOL IsAttachedPlotValid(GraphPage& gpSupposedParent)
	{
		VDataPlot vdp;
		vdp.AttachTo(m_Properties.uAttachedPlotUID);
		return is_object_valid_child_of_page(vdp, gpSupposedParent);
	}

	virtual BOOL SetTextFormat(LPCSTR lpcszCustomText)
	{
		const string strText = lpcszCustomText;
		if (strText.IsEmpty())
			return FALSE;

		Text = strText;

		Tree trFormat;
		const BOOL bRelative	= TRUE;
		const BOOL bRepaint		= TRUE;

		trFormat.Root.LinkToVars.nVal	= 1;

		int nErr = UpdateThemeIDs(trFormat.Root);
		if(0 == nErr)
			ApplyFormat(trFormat, bRepaint, bRelative);
		return TRUE;
	}

	virtual BOOL SetSignificantDigits(int nSignificantDigits)
	{
		StringArray	saStorageNames;
		vector		vdStorageValues;
		GetStorageNumericVars(&saStorageNames, &vdStorageValues);

		if ( saStorageNames.GetSize() != vdStorageValues.GetSize() )
		{
			ASSERT(FALSE);
			return error_report("SetSignificantDigits() failed since the size of the two storage array is not the same!", true);
		}

		const int nSDStorageIndex = saStorageNames.Find(STR_GROBJ_NUMERIC_VARS_SIGNIFICANT_DIGITS);

		if ( nSDStorageIndex < 0 )
		{
			saStorageNames.Add(STR_GROBJ_NUMERIC_VARS_SIGNIFICANT_DIGITS);
			vdStorageValues.Add(nSignificantDigits);
		}
		else
		{
			vdStorageValues[nSDStorageIndex] = nSignificantDigits;
		}

		BOOL bRet = SetStorageNumericVars(&saStorageNames, &vdStorageValues) > 0;
		return bRet;
	}

	virtual BOOL GetProperties(TreeNode& tnProperties)
	{
		if ( VGraphObject::GetProperties(tnProperties) )
		{
			tnProperties += m_Properties;
			return TRUE;
		}
		return FALSE;
	}
	virtual BOOL SetProperties(const TreeNode& tnProperties)
	{
		if ( VGraphObject::SetProperties(tnProperties))
		{
			m_Properties = tnProperties;
			return TRUE;
		}
		return FALSE;
	}
protected:
	void ShowText(double dXScaleMark, double dYScaleMark)
	{
		//MY_DBG_OUTPUT3("%-35s ShowText(%g, %g)", GetRangeString(), dXScaleMark, dYScaleMark);
		const string strNewText = ftoa(dYScaleMark);
		Text = strNewText;
	}
protected:
	VLABEL_PROPERTIES m_Properties;
};

/*----------------------------------------------------------------------------*/
/* VTagLabel/VTextLabel/
/*----------------------------------------------------------------------------*/

struct VTAG_UNIT_PROPERTIES 
{
	FPOINT	fMarkedPoint;		// The marked point's position in scale coordinate
};

class VTagLabel : public VLabel
{
public:
	VTagLabel(BOOL bDestroyOnDestruct = FALSE) : VLabel(bDestroyOnDestruct)
	{
		;
	}
public:
	DECLARE_METHOD_GET_VALID_OBJ(VTagLabel)

	// Since we want the uAttachedPlotUID store right under VTagUnit's branch, we have to
	// override the GetProperties/SetProperties method and add two more methods for accessing m_Properties by VTagUnit
	virtual BOOL GetProperties(TreeNode& tnProperties)
	{
		return VGraphObject::GetProperties(tnProperties);
	}
	virtual BOOL SetProperties(const TreeNode& tnProperties)
	{
		return VLabel::SetProperties(tnProperties);
	}
	virtual void GetProperties(VLABEL_PROPERTIES& vpProperties)
	{
		ASSERT(0);//vpProperties = m_Properties;
	}
	virtual void SetProperties(const VLABEL_PROPERTIES& vpProperties)
	{
		ASSERT(0);//m_Properties = vpProperties;
	}
	virtual double GetMarkedPointXScale() { return m_Properties.fMarkedPoint.x; }
	virtual double GetMarkedPointYScale() { return m_Properties.fMarkedPoint.y; }
protected:
	virtual BOOL CreateObj(const GraphLayer& gl, double dXScaleMark, double dYScaleMark, DWORD dwExtraParam = 0)
	{
		*this = gl.CreateGraphObject(GROT_TEXT);
		if ( !IsValid() )
		{
			ASSERT( FALSE );
			MY_DBG_OUTPUT("Failed to create VTagLabel object!");
			return FALSE;
		}

		const double dGapLen = GET_LAYER_SCALE_WIDTH_PERCENT(gl, GAP_BETWEEN_TAG_AND_LABEL_SCALE_PERCENT);

		ShowText(dXScaleMark, dYScaleMark);

		X = dXScaleMark + dGapLen + DX/2;
		Y = dYScaleMark;

		m_Properties.fMarkedPoint.x = dXScaleMark;
		m_Properties.fMarkedPoint.y = dYScaleMark;
		
		Attach	= ATTACH_TO_SCALE;
		Show	= FALSE;

		update_go_states(*this, GOC_NO_STATE|GOC_NO_SELECT);
		set_go_color(*this, SYSCOLOR_RED);

#ifdef ASSIGN_VGRAPH_OBJECT_NAME
		SetName(STR_VTAG_LABEL_NAME_PREFIX, OCD_ENUM_NEXT);
#endif // ASSIGN_VGRAPH_OBJECT_NAME
		return TRUE;
	}
	
private:	
	VTAG_UNIT_PROPERTIES	m_Properties;
};

struct VTEXT_LABEL_PROPERTIES 
{
	FPOINT	fMarkedPoint;			// The marked point's position in scale coordinate
	double	dHorizOffsetPercent;	// Horizontal offset to the marked position in layer/scale percent units
	double	dVertOffsetPercent;		// Vertical offset to the marked position in layer/scale percent units
};

class VTextLabel : public VLabel
{
public:
	VTextLabel(BOOL bDestroyOnDestruct = TRUE)	: VLabel(bDestroyOnDestruct)
	{
		m_Properties.dHorizOffsetPercent = 0;
		m_Properties.dVertOffsetPercent = 0;
		m_bAllowtLogOffset = false;
	}
public:
	DECLARE_METHOD_GET_VALID_OBJ(VTextLabel)

	virtual BOOL MarkTo(double dXScaleMark, double dYScaleMark)
	{
		GraphLayer gl;
		GetParent(gl);
		if (gl.IsValid())
		{
			m_bAllowtLogOffset = false;

			double dXOffset = GET_LAYER_SCALE_WIDTH_PERCENT(gl, m_Properties.dHorizOffsetPercent);
			double dYOffset = GET_LAYER_SCALE_HEIGHT_PERCENT(gl, m_Properties.dVertOffsetPercent);

			if (m_Properties.dHorizOffsetPercent < 0)
				dXOffset = -dXOffset;
			if (m_Properties.dVertOffsetPercent < 0)
				dYOffset = -dYOffset;

			X = dXScaleMark + dXOffset;
			Y = dYScaleMark + dYOffset;
			
			m_Properties.fMarkedPoint.x = dXScaleMark;
			m_Properties.fMarkedPoint.y = dYScaleMark;

			VLabel::MarkTo(dXScaleMark, dYScaleMark);

			m_bAllowtLogOffset = true;
			return TRUE;
		}
		return FALSE;
	}
	BOOL LogOffsetToMarkedPos()
	{
		if (!m_bAllowtLogOffset)
			return FALSE;
		MoveToBoundaryIfOutsideRange();
		GraphLayer gl;
		GetParent(gl);
		if (gl.IsValid())
		{
			m_Properties.dHorizOffsetPercent = (X - GetMarkedPointXScale()) / GET_LAYER_SCALE_WIDTH(gl);
			m_Properties.dVertOffsetPercent = (Y - GetMarkedPointYScale()) / GET_LAYER_SCALE_HEIGHT(gl);
			return TRUE;
		}
		return FALSE;
	}
	virtual double GetMarkedPointXScale() { return m_Properties.fMarkedPoint.x; }
	virtual double GetMarkedPointYScale() { return m_Properties.fMarkedPoint.y; }

	virtual BOOL GetProperties(TreeNode& tnProperties)
	{
		if ( VLabel::GetProperties(tnProperties) )
		{
			tnProperties += m_Properties;
			return TRUE;
		}
		return FALSE;
	}
	virtual BOOL SetProperties(const TreeNode& tnProperties)
	{
		if ( VLabel::SetProperties(tnProperties))
		{
			m_Properties = tnProperties;
			return TRUE;
		}
		return FALSE;
	}
protected:
	virtual BOOL CreateObj(const GraphLayer& gl, double dXScaleMark, double dYScaleMark, DWORD dwExtraParam = 0)
	{
		*this = gl.CreateGraphObject(GROT_TEXT);
		if ( !IsValid() )
		{
			ASSERT( FALSE );
			MY_DBG_OUTPUT("Failed to create VTextLabel object!");
			return FALSE;
		}

		Attach = ATTACH_TO_SCALE;

		// We have to initialize/show the TextObject before we can use the data member DX for calculating the default offset.
		ShowText(dXScaleMark, dYScaleMark);
	#ifdef REMOVE_LABEL_FEATURE
		Show = FALSE;
	#endif

		// By default, we want to put the TextLabel on the right side of the vertical line with some gap.
		// To do this, we can just simply set the horizontal offset and then the base class VGraphObject 
		// will call MarkTo() after this method returns.
		const double dDefaultGap = GET_LAYER_SCALE_WIDTH_PERCENT(gl, GAP_BETWEEN_VCURSOR_AND_LABEL_PERCENT);
		m_Properties.dHorizOffsetPercent = (dDefaultGap + DX/2) / GET_LAYER_SCALE_WIDTH(gl);

		update_go_states(*this, GOC_NO_RESIZE|GOC_NO_ROTATE|GOC_NO_SKEW|GOC_NO_EDIT);

		Tree trFormat;
		const BOOL bGetTagNames	= TRUE;
		const BOOL bRelative	= TRUE;
		const BOOL bRepaint		= TRUE;
		//trFormat = GetFormat(FPB_ALL, FOB_ALL, bGetTagNames, bRelative);

		trFormat.Root.TextWhiteOut.nVal				= 1;
		//trFormat.Root.Background.Fill.Color.nVal	= SYSCOLOR_WHITE;
		trFormat.Root.Color.nVal					= SYSCOLOR_BLUE;
		int nErr = UpdateThemeIDs(trFormat.Root);
		if(0 == nErr)
			ApplyFormat(trFormat, bRepaint, bRelative);

		//out_tree(trFormat);

#ifdef ASSIGN_VGRAPH_OBJECT_NAME
		SetName(STR_VTEXT_LABEL_NAME_PREFIX, OCD_ENUM_NEXT);
#endif // ASSIGN_VGRAPH_OBJECT_NAME
		return TRUE;
	}

	void MoveToBoundaryIfOutsideRange()
	{
		GraphLayer gl;
		GetParent(gl);
		if (gl.IsValid())
		{
			const double dXOffset = X - GetMarkedPointXScale();
			const double dYOffset = Y - GetMarkedPointYScale();
			const double dMaxXOffset = GET_LAYER_SCALE_WIDTH_PERCENT(gl, TEXT_LABEL_MAX_OFFSET_SCALE_PERCENT);
			const double dMaxYOffset = GET_LAYER_SCALE_HEIGHT_PERCENT(gl, TEXT_LABEL_MAX_OFFSET_SCALE_PERCENT);
			if ( fabs(dXOffset) > dMaxXOffset )
			{
				if (dXOffset > 0)
					X = GetMarkedPointXScale() + dMaxXOffset;
				else
					X = GetMarkedPointXScale() - dMaxXOffset;
			}
			if ( fabs(dYOffset) > dMaxYOffset )
			{
				if (dYOffset > 0)
					Y = GetMarkedPointYScale() + dMaxYOffset;
				else
					Y = GetMarkedPointYScale() - dMaxYOffset;
			}
		}
	}
private:
	VTEXT_LABEL_PROPERTIES	m_Properties;
	bool					m_bAllowtLogOffset;		// For preventing logging offset in MarkTo()
};

/*----------------------------------------------------------------------------*/
/* VGraphLine
/*----------------------------------------------------------------------------*/

///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
#define		VERTICAL_CURSOR_DATAPLOT_GET_DESCRIPTION_STYLE			GETLC_DATAPLOTS | GETLC_PAGE_SHORT_NAME | GETLC_COL_INDEX_NAME
///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG

enum {MOVED_TO_LESS = -1, NOT_MOVED, MOVED_TO_GREATER};

class VGraphLine : public VGraphObject
{
public:
	VGraphLine(BOOL bDestroyOnDestruct = TRUE) : VGraphObject(bDestroyOnDestruct)
	{
		m_bVertical = true;
		m_rLastMovedPagePosition = NANUM;
		///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
		m_bSnappingToData = FALSE;
		m_arrHandles.SetAsOwner(TRUE);
		///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
	}
public:
	DECLARE_METHOD_GET_VALID_OBJ(VGraphLine)

	virtual double GetPosition(bool bIsMoving = false)
	{
		if (bIsMoving)
			return m_bVertical ? (m_X = XTemp()) : (m_Y = YTemp());
		else
			return m_bVertical ? m_X : m_Y;
	}
	
	virtual double GetPagePosition()
	{
		return m_bVertical ? GetCenterPageX() : GetCenterPageY();
	}
	virtual void SetPosition(double dScalePos, bool bRemoveHandleSelection = true)
	{
		///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
		if ( bRemoveHandleSelection )
			RemoveHandleSelection();
		///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
		
		if(m_bVertical)
		{
			X = ConvertScale(m_X = dScalePos, true, false);
		}
		else
		{
			Y = ConvertScale(m_Y = dScalePos, false, false);
		}
	}
	
	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	virtual void SetPositionView(double dPagePos)
	{
		VGraphLayer		vgl;
		GetParent(vgl);
		double			dScalePos;
		vgl.PageToScaleCoordinate(dPagePos, dScalePos, m_bVertical);

		SetHandlePosition(dScalePos);
		SetPosition(dScalePos, false);
	}
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	
	virtual void SetPosition(int nPagePos)
	{
		///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
		SetHandlePosition(nPagePos);
		///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
		
		if (m_bVertical)
		{
			Left = nPagePos - Width/2;
		}
		else
			Top = nPagePos - Height/2;
	}

	#define		STR_HANDLES_PROPERTIES	"Handles"
	virtual BOOL SetProperties(const TreeNode& tnProperties)
	{
		if ( VGraphObject::SetProperties(tnProperties))
		{
			SetXY();
			if ( m_bDestroyOnDestruct )
			{
				foreach ( TreeNode trNode in tnProperties.Children )
				{
					if ( trNode.tagName.Find(STR_HANDLES_PROPERTIES) == 0 )
					{
						VGraphObject*	pvgo = new VGraphObject(FALSE);
						pvgo->SetProperties(trNode);
						m_arrHandles.Add(*pvgo);
					}
				}
			}

			return TRUE;
		}
		return FALSE;
	}
	
	virtual BOOL GetProperties(TreeNode& tnProperties)
	{
		if ( VGraphObject::GetProperties(tnProperties) )
		{
			for ( int ii=0; ii<m_arrHandles.GetSize(); ++ii )
			{
				string		strNode;
				strNode.Format("%s%d", STR_HANDLES_PROPERTIES, ii + 1);
				TreeNode	trHandles = tree_check_get_node(tnProperties, strNode);
				
				VGraphObject&	vgo = m_arrHandles.GetAt(ii);
				vgo.GetProperties(trHandles);
			}

			return TRUE;
		}
		return FALSE;
	}

	/*virtual BOOL ScaleToPageCoordinate(double dScaleCoord, int& rnPageCoord)
	{
		GraphLayer gl;
		GetParent(gl);
		VGraphLayer vgl(gl);
		return vgl.ScaleToPageCoordinate(dScaleCoord, rnPageCoord, m_bVertical);
	}*/

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	double	GetSnapToX()
	{
		return m_rSnapToX;
	}

	virtual	void	SnapToPlot(DataPlot& dp)
	{
		VGraphObject::SnapToPlot(dp);
		SnapToData(TRUE);
	}
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE

	#define		SNAPTODATA_RETURN_FALSE \
				do \
				{ \
					bRet = FALSE; \
					goto quit; \
				} while (0)

	BOOL SnapToData(BOOL bForceSnapToData = FALSE)
	{
		///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
		if ( m_bSnappingToData )
			return FALSE;
		
		m_bSnappingToData = TRUE;
		BOOL	bRet = TRUE;
		///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
		
		CStopWatch watch("SnapToData()");

		GraphLayer gl;
		GraphPage gp;
		GetParent(gl);
		if (!gl)
		{
			ASSERT(FALSE);
			SNAPTODATA_RETURN_FALSE;
		}
		gl.GetParent(gp);
		if (!gp)
		{
			ASSERT(FALSE);
			SNAPTODATA_RETURN_FALSE;
		}

		const double rLinePagePosition = GetPagePosition();
		const int nMovedDirection = GetMovedDirection();

		if (bForceSnapToData || NOT_MOVED != nMovedDirection)
		{
			const bool bMovedToLess = MOVED_TO_LESS == nMovedDirection;
			const DWORD dwGetNearestPointCntrl = bMovedToLess ? XIC_EXACT_OR_NEAREST_LEFT : XIC_EXACT_OR_NEAREST_RIGHT;

			///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
			/*
			vector<int> vnAllPointsPageX;
			foreach(GraphLayer gl in gp.Layers)
			{
				foreach (DataPlot dp in gl.DataPlots)
				{
 					VDataPlot vdp(dp);
					///Kenny 06/17/2009 IMPROVE_SPEED_OF_SNAP_TO_DATA
// 					vector<int> vnPointsPageX;
// 					///Kenny 06/17/2009 GET_DATA_POINTS_IN_CORRECT_RANGE
// 					//vdp.GetDataPoints(0, -1, vnPointsPageX);
// 					vdp.GetDataPoints(vdp.i1, vdp.i2, vnPointsPageX);
// 					///End GET_DATA_POINTS_IN_CORRECT_RANGE
// 					vnAllPointsPageX.Append(vnPointsPageX);

					int nNearestPointPageX;
					if ( vdp.GetNearestPoint(nLinePagePosition, nNearestPointPageX, dwGetNearestPointCntrl) )
					{
						vnAllPointsPageX.Add(nNearestPointPageX);
					}
					///End IMPROVE_SPEED_OF_SNAP_TO_DATA
				}
			}

			const int nAllPointsSize = vnAllPointsPageX.GetSize();
			if (nAllPointsSize <= 0)
				///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
				//return FALSE;
				{
					bRet = FALSE;
					goto quit;
				}
				///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA

			// We have to find the first point that <= the current position if bMovedToLess
			// otherwise the first point that >= the current position
			// So sort the points first for finding.
			vnAllPointsPageX.Sort(bMovedToLess ? SORT_DESCENDING : SORT_ASCENDING);
			vector<UINT> vFindResultIndices;
			const UINT wBitwiseOption = (bMovedToLess ? MATREPL_TEST_LESSTHAN : MATREPL_TEST_GREATER) | MATREPL_TEST_EQUAL;
			vnAllPointsPageX.Find(wBitwiseOption, nLinePagePosition, vFindResultIndices);

			int nSnapToPageX;
			if ( vFindResultIndices.GetSize() > 0)
			{
				nSnapToPageX = vnAllPointsPageX[ vFindResultIndices[0] ];	// Snap to the nearest point towards to the moved direction
			}
			else
			{
				nSnapToPageX = vnAllPointsPageX[ nAllPointsSize-1 ];	// Otherwise snap to the leftmost or rightmost point.
			}
			*/

			///------ Folger 01/27/10 VERTICAL_CURSOR_SNAP_TO_NEAREST_X_DATA
			double		rSnapToPageX = NANUM;
			if ( m_dpSnapTo )
			{
				VDataPlot	vdp(m_dpSnapTo);
				if ( !vdp.GetNearestPoint(rLinePagePosition, rSnapToPageX, dwGetNearestPointCntrl, &m_rSnapToX) )
					SNAPTODATA_RETURN_FALSE;
				
				XYRange xyRange;
				m_dpSnapTo.GetDataRange(xyRange);
				///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
				get_dataplot_range_description(m_strLastSnapToPlotDescription, m_dpSnapTo, VERTICAL_CURSOR_DATAPLOT_GET_DESCRIPTION_STYLE);
				///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
			}
			else	/// snap to nearest X
			{
				vector			vPageXs;
				vector			vScaleXs;
				///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
				vector<int>		vnUIDs;
				///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
				foreach(GraphLayer gl in gp.Layers)
				{
					foreach (DataPlot dp in gl.DataPlots)
					{
						VDataPlot	vdp(dp);
						double		rPageX;
						double		rScaleX;
						if ( !vdp.GetNearestPoint(rLinePagePosition, rPageX, dwGetNearestPointCntrl, &rScaleX) )
							SNAPTODATA_RETURN_FALSE;

						vPageXs.Add(rPageX);
						vScaleXs.Add(rScaleX);
						///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
						vnUIDs.Add(dp.GetUID(TRUE));
						///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
					}
				}
				
				if ( vPageXs.GetSize() <= 0 )
					SNAPTODATA_RETURN_FALSE;
				
				vector<uint>	vnOrder;
				vPageXs.Sort(bMovedToLess ? SORT_DESCENDING : SORT_ASCENDING, TRUE, vnOrder);
				vScaleXs.Reorder(vnOrder);
				///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
				vnUIDs.Reorder(vnOrder);
				///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
				
				BOOL			bFound = FALSE;
				for ( int ii=0; ii<vPageXs.GetSize(); ++ii )
				{
					if ( (bMovedToLess && rLinePagePosition >= vPageXs[ii]) || (!bMovedToLess && rLinePagePosition <= vPageXs[ii]) )
					{
						bFound = TRUE;
						break;
					}
				}
				
				if ( !bFound )
					ii = vPageXs.GetSize() - 1;

				rSnapToPageX = vPageXs[ii];
				m_rSnapToX = vScaleXs[ii];

				///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
				DataPlot	dp;
				dp = (DataPlot)Project.GetObject(vnUIDs[ii]);
				get_dataplot_range_description(m_strLastSnapToPlotDescription, dp, VERTICAL_CURSOR_DATAPLOT_GET_DESCRIPTION_STYLE);
				///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
			}
			///------ End VERTICAL_CURSOR_SNAP_TO_NEAREST_X_DATA
			///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE

			///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
			if ( bForceSnapToData )
				RemoveHandleSelection();
			///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
			SetPositionView(rSnapToPageX);
			
			LogLastMovedPosition();
		}
		///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
		quit:
			m_bSnappingToData = FALSE;

		return bRet;
		///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
	}

	void	ReattachAllObjects()
	{
		GetValidObj();
		for ( int ii=0; ii<m_arrHandles.GetSize(); ++ii )
		{
			m_arrHandles.GetAt(ii).GetValidObj();
		}
	}

	///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	string	GetLastSnapToPlotDescription()
	{
		return m_strLastSnapToPlotDescription;
	}
	///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG

protected:
	virtual BOOL	CreateObj(DWORD dwExtraParam = 0)
	{
		const bool bVertical = dwExtraParam;
		GraphLayer glActive = Project.ActiveLayer(); 
		if(!glActive)
			return FALSE;

		double dX0, dX1, dY0, dY1;
		///Jasmine 01/08/10 ADD_HANDLER_TO_MAKE_SELECT_EASY
		int nHandleSize = 2;
		
		if(bVertical)
		{
			dX0 = dX1 = DEFAULT_GRAPH_LINE_INIT_PAGE_PERCENT;
			dY0 = 0;
			dY1 = 100;
			
			if(nHandleSize > 0)
			{				
				dY0 += nHandleSize*2;
				dY1 -= nHandleSize*2;
			}
		}
		else
		{
			dX0 = 0;
			dX1 = 100;
			dY0 = dY1 = DEFAULT_GRAPH_LINE_INIT_PAGE_PERCENT;
			if(nHandleSize > 0)
			{				
				dX0 += nHandleSize*2;
				dX1 -= nHandleSize*2;
			}
		}
		///End ADD_HANDLER_TO_MAKE_SELECT_EASY

		const int 	nDirection 	= bVertical ? LN_VERTICAL : LN_HORIZONTAL;
		const bool	bSpan 		= false;
		const bool	bPercent 	= true;
		const DWORD	dwGoState	= bVertical ? GOC_NO_VMOVE : GOC_NO_HMOVE;

		bool bRet = add_line(glActive, *this, dX0, dY0, ATTACH_TO_PAGE, nDirection, bSpan, bPercent, dX1, dY1, SYSCOLOR_RED);//SYSCOLOR_BLUE
		if ( !bRet || !IsValid() )
		{
			ASSERT( FALSE );
			MY_DBG_OUTPUT("Failed to create VGraphLine object!");
			return FALSE;
		}
		update_go_states(*this, dwGoState|GOC_NO_RESIZE|GOC_NO_ROTATE|GOC_NO_SKEW|GOC_NO_EDIT);
		//_set_go_width(*this, DEFAULT_VTAG_WIDTH);
		
		///Jasmine 01/08/10 ADD_HANDLER_TO_MAKE_SELECT_EASY
		if(nHandleSize > 0)
			AddHandleToLine(glActive, *this, dX0, dX1, dY0, dY1, nHandleSize, bVertical, bPercent, dwGoState);
		///End ADD_HANDLER_TO_MAKE_SELECT_EASY
		
		this->SetReverseVideo(true);

		ASSERT(this->IsReverseVideo() == true);
		m_bVertical = bVertical;
		LogLastMovedPosition();

#ifdef ASSIGN_VGRAPH_OBJECT_NAME
		SetName(STR_VGRAPH_LINE_NAME_PREFIX, OCD_ENUM_NEXT);
#endif // ASSIGN_VGRAPH_OBJECT_NAME

		///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
		SetXY();
		///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
		return TRUE;
	}

	void LogLastMovedPosition() { m_rLastMovedPagePosition = GetPagePosition(); }

	double GetMovedDirection()
	{
		const double rCurrentPagePosition = GetPagePosition();
		if ( m_rLastMovedPagePosition == rCurrentPagePosition )
			return NOT_MOVED;
		return rCurrentPagePosition < m_rLastMovedPagePosition ? MOVED_TO_LESS : MOVED_TO_GREATER;
	}

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	virtual	double	X()
	{
		return m_X;
	}

	virtual	double	Y()
	{
		return m_Y;
	}
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE

	double	XTemp()
	{
		return ConvertScale(XTemp, true);
	}

	double	YTemp()
	{
		return ConvertScale(YTemp, false);
	}
	
	double	ConvertScale(double rr, bool bX, bool bFromParentToSnap = true)
	{
		VGraphLayer	vglParent;
		GraphObject::GetParent(vglParent);
		
		VGraphLayer	vglSnap;
		GetParent(vglSnap);
		if ( is_same_layer(vglSnap, vglParent) )
			return rr;
		
		double	rPage, rScale;
		if ( bFromParentToSnap )
		{
			vglParent.ScaleToPageCoordinate(rr, rPage, bX);
			vglSnap.PageToScaleCoordinate(rPage, rScale, bX);
		}
		else
		{
			vglSnap.ScaleToPageCoordinate(rr, rPage, bX);
			vglParent.PageToScaleCoordinate(rPage, rScale, bX);
		}
		return rScale;
	}
	
private:
	///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
	void		SetHandlePosition(int nPagePos)
	{
		if (m_bVertical)
		{
			for ( int ii=0; ii<m_arrHandles.GetSize(); ++ii )
			{
				VGraphObject&		vgo = m_arrHandles.GetAt(ii).GetValidObj();
				vgo.Left = nPagePos - vgo.Width / 2;
			}
		}
		else
		{
			ASSERT(FALSE);
		}
	}

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	void		SetHandlePosition(double dScalePos)
	{
		if (m_bVertical)
		{
			double	rr = ConvertScale(dScalePos, true, false);
			for ( int ii=0; ii<m_arrHandles.GetSize(); ++ii )
			{
				VGraphObject&		vgo = m_arrHandles.GetAt(ii).GetValidObj();
				vgo.X = rr;
			}
		}
		else
		{
			ASSERT(FALSE);
		}
	}

	void		SetXY()
	{
		m_X = X;
		m_Y = Y;
	}
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	
	void		RemoveHandleSelection()
	{
		Selection	mySelection;
		if ( m_bVertical )
		{
			for ( int ii=0; ii<m_arrHandles.GetSize(); ++ii )
			{
				VGraphObject&		go = m_arrHandles.GetAt(ii).GetValidObj();
				mySelection.Remove(go);
			}
		}
		else
		{
			ASSERT(FALSE);
		}
	}
	///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
	
	void AddHandleToLine(GraphLayer& gl, GraphObject& line, int x0, int x1, int y0, int y1, int nSize, bool bVertical, bool bPercent, DWORD dwGoState)
	{
		if(!line)
			return;
		
		ASSERT(nSize > 0 && x0 <= x1 && y0 <= y1);
		
		m_arrHandles.SetSize(0);

		//handle 1			
		vector vx0, vy0;
		GenerateHandlePoints(vx0, x0, nSize, bVertical);
		GenerateHandlePoints(vy0, y0, nSize, !bVertical);
		AddHandle(gl, line, vx0, vy0, bPercent, dwGoState);
					
		//handle 2			
		vector vx1, vy1;
		GenerateHandlePoints(vx1, x1, nSize, bVertical);
		GenerateHandlePoints(vy1, y1, nSize, !bVertical);
		if(bVertical)
			vy1 = y1 + (y1 - vy1);
		else
			vx1 = x1 + (x1 - vx1);
		AddHandle(gl, line, vx1, vy1, bPercent, dwGoState);
	}
	
	void GenerateHandlePoints(vector& vx, int x0, int nSize, bool bX)
	{
		vx.SetSize(5);
		
		vx[0] = x0;
		vx[1] = bX? (x0-nSize/2) : (x0-nSize);
		vx[2] = bX? (x0-nSize/2) : (x0-nSize*2);
		vx[3] = bX? (x0+nSize/2) : (x0-nSize*2);
		vx[4] = bX? (x0+nSize/2) : (x0-nSize);
	}
	
	bool AddHandle(GraphLayer& gl, GraphObject& line, const vector& vx, const vector& vy, bool bPercent, DWORD dwGoState)
	{
		GraphObject go;
		if( add_polygon(gl, go, vx, vy, ATTACH_TO_PAGE, bPercent) )
		{
			go.SetName("VUpper", OCD_ENUM_NEXT);
			Tree tr;
			tr.Root.Fill.Color.nVal = SYSCOLOR_RED;
			tr.Root.Border.Width.dVal = 0;/// RVD 1/28/2010 QA70-14966-p4 POLYGON_REVERSE_VIDEO
			tr.Root.KeepInside.nVal = 1;
			go.UpdateThemeIDs(tr.Root, "Error", "Unknown tag");
			go.ApplyFormat(tr, true, true);
			update_go_states(go, dwGoState|GOC_NO_RESIZE|GOC_NO_ROTATE|GOC_NO_SKEW|GOC_NO_EDIT);
			go.SetReverseVideo(true);	/// RVD 1/28/2010 QA70-14966-p4 POLYGON_REVERSE_VIDEO
			go.ConnectTo(line, -1, -1, false);

			VGraphObject*	pvgo = new VGraphObject(go);
			m_arrHandles.Add(*pvgo);
			
			return true;
		}
		
		return false;
	}
	
private:
	bool	m_bVertical;
	double	m_rLastMovedPagePosition;
	///------ Folger 01/18/09 FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA
	BOOL					m_bSnappingToData;
	Array<VGraphObject&>	m_arrHandles;
	///------ End FAILS_TO_MOVE_LINE_BY_HANDLE_IF_SNAP_TO_DATA

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	double	m_rSnapToX;
	double	m_X;
	double	m_Y;
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE

	///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	string	m_strLastSnapToPlotDescription;
	///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
};
#endif // __VGRAPH_OBJ_H__