 /*-------------------------------------------------------------------------------------*
 * File Name: VerticalCursorManager.h													*
 * Creation: Kenny 05/08/2009															*
 * Purpose: OriginC Source file															*
 * Copyright (c) OriginLab Corp. 2009													*
 * All Rights Reserved																	*
 *																						*
 * Modification Log:																	*
 * Iris 6/16/2009 CHANGE_SAVE_SOURCE_COLUMN_UID_IN_STORAGE_REPLACE_PLOT_UID				*
 * Kenny 06/16/2009 ALLOW_DELETING_TAG_AND_TAG_LABEL_AFTER_DLG_IS_CLOSED				*
 * Jasmine 12/08/09 QA81-8980 MORE_CONFIGURATION_FOR_INFO_TABLE							*
 * Jasmine 01/15/10 UPDATE_LIST_WITH_SORT_ORDER											*
 *	Folger 01/20/10 VERTICAL_CURSOR_WKS_OUTPUT_SUPPORT_RANGE_NOTATION					*
 *	Folger 01/20/10 SUPPORT_SAVING_VERTICAL_CURSOR_SETTINGS_IN_PAGE						*
 * Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT												* 
 * Jasmine 01/20/10 SHOW_NEAREST_POINT													*
 *	Folger 01/21/09 VERTICAL_CURSOR_CRASH_ORIGIN_IF_ACCESS_ARRAY_MEMBER_OF_MANAGER_OBJECT_DIRECTLY
 * Jasmine 01/22/10 BTN_TO_ACTIVATE_OUTPUT_WKS											*
 *	Folger 01/22/09 ORIGIN_CRASH_WHEN_EXIT_IF_GRAPH_PAGE_HAS_VERTICAL_CURSOR_IS_NOT_ACTIVE
 * Jasmine 01/22/10 OUTPUT_WKS_XCOL_LNAME_USE_AXIS_TITLE								*
 *	Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE					*
 * Jasmine 01/23/10 USE_RELATED_DS_IS_MORE_RELIABLE										*
 * Jasmine 01/23/10 FIRST_EMPTY_COL_IS_NOT_DELETED_OR_USED								*
 * Jasmine 01/26/10 COLUMNS_IN_ONE_WKS_HAVE_SAME_LONNG_NAME_NEED_SHORT_NAME_TO_DISDINGUISH
 *	Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT											*
 * Jasmine 01/26/10 UPDATE_CURSOR_POSITION_ON_SCALE_CHANGE								*
 * Jasmine 01/26/10 OUTPUT_X_COLUMN_NEED_MORE_INFO										*
 *	Folger 01/27/10 VERTICAL_CURSOR_SNAP_TO_NEAREST_X_DATA								*
 * Jasmine 01/27/10 MATCH_EXIST_COL_TO_CONSOLIDATE										*
 * Jasmine 01/27/10 EASWAR_SAY_X_BOOK_SHEET_MEANINGLESS									*
 * Jasmine 01/28/10 RESTRICT_X_EDIT_TEXT												*
 *	Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG								*
 * Jasmine 02/08/10 HOLDER_INVALID_WHEN_ACTIVE_PAGE_NOT_GRAPH							*
 *--------------------------------------------------------------------------------------*/

#ifndef __VERTICAL_CURSOR_MANAGER_H__
#define __VERTICAL_CURSOR_MANAGER_H__

#include "VGraphObj.h"
#include "INIFileEx.h"
#include <xfutils.h>			///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
///Jasmine 12/08/09 QA81-8980 MORE_CONFIGURATION_FOR_INFO_TABLE
#include <..\Originlab\WksColLabels.h>
#define	E_STR_SHORT_NAME			"Short Name"
#define E_STR_PLOT_NAME				"Plot Name"
#define E_STR_DATASET				"Dataset"
///End MORE_CONFIGURATION_FOR_INFO_TABLE

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

/*
  +----------------------+
  |GraphVerticalCursorDlg|
  +-----------#----------+
              |
              |
              |
 +------------#-------------+
 |GraphVerticalCursorManager|
 +--------------------------+
 |      AddTags()           |        +---------------------------+
 |    ClearAllTags()        *........* GraphVerticalCursorHolder |
 |   ShowTextLabels()       |        +--------------+------------+
 |   SetLinePosition()      |                       |
 |         ...              |                       |
 +--------------------------+                       |
                                     +--------------+---------------+
                                     |              |               |
                                +----+----+   +-----+----+    +-----+----+
                                | VTagUnit|   |VTextLabel|    |VGraphLine|
                                +----+----+   +----------+    +----------+
                                     |
                               +-----+--------+
                               |              |
                             +-+--+      +----+----+
                             |VTag|      |VTagLabel|
                             +----+      +---------+
*/

#define THE_VCURSOR_MANAGER								get_graph_vertical_cursor_manager()

/// Iris Origin default template is enough
//#define STR_OUTPUT_WKS_TEMPL							"VerticalCursorOutput"
#define STR_OUTPUT_WKS_TEMPL							"Origin"
///End
///------ Folger 01/20/10 VERTICAL_CURSOR_WKS_OUTPUT_SUPPORT_RANGE_NOTATION
//#define STR_DEFAULT_OUTPUT_WKS_NAME						"Cursor"
#define STR_DEFAULT_OUTPUT_WKS_NAME						"[Cursor]Result"
///------ End VERTICAL_CURSOR_WKS_OUTPUT_SUPPORT_RANGE_NOTATION

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

#define STR_OBJ_TAG_NAME_VMANAGER						"VManager"

#define STR_OBJ_TAG_NAME_VHOLDERS						"VHolders"
#define STR_OBJ_TAG_NAME_VHOLDER_PREFIX					"VHolder"

#define STR_OBJ_TAG_NAME_VHOLDER_SETTINGS				"VHolderSettings"

#define STR_OBJ_TAG_NAME_VLINE							"VLine"

#define STR_OBJ_TAG_NAME_VTEXT_LABELS					"VTextLabels"
#define STR_OBJ_TAG_NAME_VTEXT_LABEL_PREFIX				"VTextLabel"

#define STR_OBJ_TAG_NAME_VTAG_UNITS						"VTagUnits"
#define STR_OBJ_TAG_NAME_VTAG_UNIT_PREFIX				"VTagUnit"

#define STR_OBJ_TAG_NAME_VTAG							"VTag"
#define STR_OBJ_TAG_NAME_VTAG_LABEL						"VTagLabel"

#define STR_OBJ_DELIMITER								"_"

#define STR_OBJ_EVENT_LT_SCRIPT_FORMAT					"run.section(graph_controls, VertCursor, %%1 %d);"

///Jasmine 01/27/10 MATCH_EXIST_COL_TO_CONSOLIDATE
#define LABEL_SHORT_NAME						_L("Short Name")
#define LABEL_SHEET_NAME						_L("Sheet")
#define LABEL_BOOK_NAME							_L("Book")
///End MATCH_EXIST_COL_TO_CONSOLIDATE
static int _check_add_dataset_indentifier_label(Worksheet& wks, LPCSTR lpcszLabel, bool bAdd)
{
	if(!wks)
		return -1;
	Grid grid;   
	if( !grid.Attach(wks) )
		return -2;
	
	vector<string> vsLabelNames;
	grid.GetUserDefinedLabelNames(vsLabelNames);
	
	int nSize 	= vsLabelNames.GetSize();	
	int nIndex = -1;
	bool bExist = false;
	
	if(nSize > 0)
	{
		nIndex = vsLabelNames.Find(lpcszLabel);
		bExist = (nIndex >= 0);
	}
	
	if(!bExist)
	{
		if(!bAdd)
			return -1;
		
		nIndex = nSize;
		vsLabelNames.Add(lpcszLabel);
		if( !grid.SetUserDefinedLabelNames(vsLabelNames) )
			return -1;
	}
	
	nIndex += RCLT_UDL;
	if( !wks.IsLabelTypeShown(nIndex) )
		wks.CheckAddLabelByType(nIndex);
	
	return nIndex;
}

///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
struct PlotInfo
{
	string book;
	string sheet;
	string colname;
	string collongname;
};
///End SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
///Jasmine 01/26/10 OUTPUT_X_COLUMN_NEED_MORE_INFO
struct XInfo
{
	PlotInfo	plotinfo;
	uint		plotuid;
	///Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT
	string 		custom;
	int 		format;	
	int 		display;
	///End SHOW_XVALUE_WITH_FORMAT	
};
///End OUTPUT_X_COLUMN_NEED_MORE_INFO
class VIntersectPoint
{
public:
	FPOINT		fpPoint;
	FPOINT		fpNearPoint;///Jasmine 01/20/10 SHOW_NEAREST_POINT
	PlotInfo 	plotinfo;	///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
	XInfo		xinfo;		///Jasmine 01/26/10 OUTPUT_X_COLUMN_NEED_MORE_INFO
	int			layerYPos;	///Jasmine 01/15/10 REORDER_CURSOR_VALUE_BY_LAYER_POS
	int 		layer;
	int 		plot;
	int 		index;		///Jasmine 12/08/09 QA81-8980 MORE_CONFIGURATION_FOR_INFO_TABLE	
	uint		plotuid;
};

///Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT
string _convert_double_to_date_time_str(const XInfo& xinfo, double dd)
{
	string strFormat, strOut;
	
	switch(xinfo.format)
	{
	case OKCOLTYPE_TIME:
		strFormat.Format("T%d", xinfo.display);
		break;
	case OKCOLTYPE_DATE:
		if(xinfo.display == LDF_OBJ_CUSTOM)
		{
			char szDateOut[MAXLINE];
			if( date_to_str_custom(dd, xinfo.custom, szDateOut) )
				strOut = szDateOut;
			
    	}
    	else
    		strFormat.Format("D%d", xinfo.display);
		break;
	}
	
	if( !strFormat.IsEmpty() )
		strOut = ftoa(dd, strFormat);
	
	return strOut;
}

double _convert_date_time_str_to_double(const XInfo& xinfo, LPCSTR lpcsz)
{
	double dd = NANUM;
	
	switch(xinfo.format)
	{
	case OKCOLTYPE_TIME:
		dd = str_to_time(lpcsz);
		break;
	case OKCOLTYPE_DATE:
		if(xinfo.display == LDF_OBJ_CUSTOM)
			str_to_date_custom(lpcsz, xinfo.custom, &dd);
		else
			dd = str_to_date(lpcsz, xinfo.display);
		break;
	}
	
	return dd;
}

bool _set_col_format(const XInfo& xinfo, Column& col)
{
	if(!col)
		return false;
	
	switch(xinfo.format)
	{
	case OKCOLTYPE_TIME:
	case OKCOLTYPE_DATE:
	case OKCOLTYPE_MONTH:
	case OKCOLTYPE_WEEKDAY:
		break;
	default:
		return false;
	}
	
	if( OKCOLTYPE_DATE == xinfo.format && LDF_OBJ_CUSTOM == xinfo.display && !xinfo.custom.IsEmpty() )
		col.SetCustomDisplay(xinfo.custom);
	col.SetFormat(xinfo.format, xinfo.display);
	
	return true;
}
///End SHOW_XVALUE_WITH_FORMAT

/*----------------------------------------------------------------------------*/
/* DumpDataHelper
/*----------------------------------------------------------------------------*/
#define OUTPUT_WKS_X_COL_IND		0

///Jasmine 01/23/10 USE_RELATED_DS_IS_MORE_RELIABLE
#define STR_DP_OUTPUT_Y_RELATED_DATASET				"YValue"
#define STR_DP_OUTPUT_NEAREST_X_RELATED_DATASET		"NearestXValue"
#define STR_DP_OUTPUT_NEAREST_Y_RELATED_DATASET		"NearestYValue"
///End USE_RELATED_DS_IS_MORE_RELIABLE

struct VCursorShow;	///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME 

class DumpDataHelper
{
public:
	DumpDataHelper(string& strWksName)
	{
		InitOutputWorksheet(strWksName);
	}
	
public:
	BOOL Dump(const vector& vXScale, const vector& vYScale, const vector<UINT>& vuPlotUID, 
	//const vector& vNearestX = NULL, const vector& vNearestY = NULL,		///Jasmine 01/25/10 SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
	const VCursorShow& cursorshow = NULL,									///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME  
	const XInfo& xinfo = NULL) 												///Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT
	{
		if(!m_wks)
			return error_report("no worksheet");
		
		bool bRet = dump(vXScale, vYScale, vuPlotUID, 
						//vNearestX, vNearestY, 	///Jasmine 01/25/10 SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
						cursorshow,
						xinfo);		
		
		return bRet;
	}	
	///Jasmine 01/22/10 BTN_TO_ACTIVATE_OUTPUT_WKS	
	BOOL ActivateOutputWks()
	{
		if(m_wks)
			return set_active_layer(m_wks);
		return FALSE;
	}
	///End BTN_TO_ACTIVATE_OUTPUT_WKS
protected:
	BOOL InitOutputWorksheet(string& strWksName)
	{
		ASSERT( !strWksName.IsEmpty() );
		if (strWksName.IsEmpty())
			strWksName = STR_DEFAULT_OUTPUT_WKS_NAME;

		///------ Folger 01/20/10 VERTICAL_CURSOR_WKS_OUTPUT_SUPPORT_RANGE_NOTATION
		//Worksheet wksOutput(strWksName);
		//if( !wksOutput )
		//{
			//BOOL bCreated = wksOutput.Create(STR_OUTPUT_WKS_TEMPL);
			//ASSERT(bCreated);
			
			//wksOutput.GetPage().Rename(strWksName);
			//strWksName = wksOutput.GetPage().GetName(); // page name will be auto changed when rename
			//wksOutput.SetSize(-1, 0);
		//}
		Worksheet wksOutput;
		if ( !attach_or_create_sheet(wksOutput, strWksName, CREATE_HIDDEN | CREATE_LOAD_1ST_LAYER_ONLY) )
			return FALSE;
		///------ End VERTICAL_CURSOR_WKS_OUTPUT_SUPPORT_RANGE_NOTATION

		if(!wksOutput)
			return error_report("no worksheet");

		m_strOutWksName = strWksName;
		m_wks = wksOutput;
		
		return TRUE;
	}
private:
	BOOL	dump(const vector& vXScale, const vector& vYScale, const vector<UINT>& vuPlotUID, 
				//const vector& vNearestX = NULL, const vector& vNearestY = NULL,	///Jasmine 01/25/10 SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
				const VCursorShow& cursorshow = NULL,								///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME  
				const XInfo& xinfo = NULL) 											///Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT
	{
		if( vXScale.GetSize() != vYScale.GetSize() || vXScale.GetSize() != vuPlotUID.GetSize() )
			return false;	
		
		//1. prepare labels
		///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
		int nBookLabel = cursorshow.BookName? _check_add_dataset_indentifier_label(m_wks, LABEL_BOOK_NAME, true) : -1;
		int nSheetLabel = cursorshow.SheetName? _check_add_dataset_indentifier_label(m_wks, LABEL_SHEET_NAME, true) : -1;
		int nShortNameLabel = cursorshow.ShortName? _check_add_dataset_indentifier_label(m_wks, LABEL_SHORT_NAME, true) : -1;
		int nLongName = -1;
		if(cursorshow.LongName)
		{
			nLongName = RCLT_LONG_NAME;
			if( !m_wks.IsLabelTypeShown(nLongName) )
				m_wks.CheckAddLabelByType(nLongName);
		}
		///End SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
		
		///Jasmine 01/25/10 SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
		//if( (vNearestX && vNearestX.GetSize() > 0) || (vNearestY && vNearestY.GetSize() > 0) )
		//{
			//nLongName = RCLT_COMMENT;
			//if( !m_wks.IsLabelTypeShown(nLongName) )
				//m_wks.CheckAddLabelByType(nLongName);
		//}
		///End SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
			
		//2. prepare X column
		Column colX;		
		if( !getXColumn(colX) )
			return error_report("Dump help: fail to get X column");	
		if(true)
		{
			vector<int>		vnLabelTypes;
			vector<string> 	vsLabels;
			if( getColumnLabelsFromDataPlot(xinfo.plotuid, vnLabelTypes, vsLabels, true) )
			{
				for(int nn = 0; nn < vsLabels.GetSize(); nn++)
				{
					if(RCLT_LONG_NAME == vnLabelTypes[nn])	///Jasmine 01/18/10 CONFIGURE_SHOW_HIDE_SETTING
						continue;
					colX.SetExtendedLabel(vsLabels[nn], vnLabelTypes[nn]);
				}
			}
		}
		///Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT				
		if(xinfo)
		{
			_set_col_format(xinfo, colX);
		///End SHOW_XVALUE_WITH_FORMAT
			///Jasmine 01/26/10 OUTPUT_X_COLUMN_NEED_MORE_INFO
			///Jasmine 01/27/10 EASWAR_SAY_X_BOOK_SHEET_MEANINGLESS
			//if(nBookLabel > -1)
				//colX.SetExtendedLabel(xinfo.plotinfo.book, nBookLabel);
			//if(nSheetLabel > -1)
				//colX.SetExtendedLabel(xinfo.plotinfo.sheet, nSheetLabel);
			if(nShortNameLabel > -1)
				colX.SetExtendedLabel(xinfo.plotinfo.colname, nShortNameLabel);
			if(nLongName > -1)
				colX.SetExtendedLabel(xinfo.plotinfo.collongname, nLongName);
			
			if( m_wks.IsLabelTypeShown(RCLT_COMMENT) || m_wks.CheckAddLabelByType(RCLT_COMMENT) != -1)
				colX.SetExtendedLabel(_L("Cursor Location"), RCLT_COMMENT);
			///End EASWAR_SAY_X_BOOK_SHEET_MEANINGLESS
			///End OUTPUT_X_COLUMN_NEED_MORE_INFO
		}
	
		
		//3. fill data
		// get the index of last row to append now rows
		int 	iR1, iR2;
		m_wks.GetBounds(iR1, 0, iR2, -1);
		int 	nRow = iR2 + 1;
		
		int nBegin = 0, nEnd = -1;
		while( findNextGroup(vXScale, nBegin, nEnd) ) // append one row of xyyyy
		{			
			setColData(colX, vXScale[nBegin], nRow);
			
			for(int ii = nBegin; ii <= nEnd; ii++)
			{
				Column 	colY;								
				bool bRet = checkGetColumn(colY, vuPlotUID[ii], STR_DP_OUTPUT_Y_RELATED_DATASET, NULL,
												true,
												nBookLabel, nSheetLabel, nShortNameLabel, nLongName); ///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
				if(!bRet)
					error_report("Dump help: fail to get Y column");	
				bRet = setColData(colY, vYScale[ii], nRow); 
				ASSERT( bRet );
				
				///Jasmine 01/25/10 SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
				//if(vNearestX)
				//{
					//Column colNearestX;
					//if( !checkGetColumn(colNearestX, vuPlotUID[ii], STR_DP_OUTPUT_NEAREST_X_RELATED_DATASET) )
						//error_report("Dump help: fail to get NearestX column");					
					//bRet = setColData(colNearestX, vNearestX[ii], nRow); 
					//ASSERT(bRet);
					//colNearestX.SetExtendedLabel("Nearest X", RCLT_COMMENT);
				//}
				//if(vNearestY)
				//{
					//Column colNearestY;
					//if( !checkGetColumn(colNearestY, vuPlotUID[ii], STR_DP_OUTPUT_NEAREST_Y_RELATED_DATASET) )
						//error_report("Dump help: fail to get NearestY column");					
					//bRet = setColData(colNearestY, vNearestY[ii], nRow); 
					//ASSERT(bRet);
					//colNearestY.SetExtendedLabel("Nearest Y", RCLT_COMMENT);
				//}
				///End SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
			}
			nRow++;
			nBegin = nEnd + 1;
		}
		
		if(nBookLabel > -1 || nSheetLabel > -1 || nShortNameLabel > -1 || nLongName > -1)
		{
			autosize_rowcol(m_wks, 1.5, 25, -1, -1, AS_NOHEIGHT|AS_INVALIDATE, 
						0, 0, 0, 0, 0, NULL, false, true);
		}
		
		return TRUE;
	}	
	
	BOOL	findNextGroup(const vector& vXScale, int& nBegin, int& nEnd)
	{
		if( nBegin >= vXScale.GetSize() )
			return FALSE;
		
		vector<string> 	vs;
		convert_double_vector_to_string_vector(vXScale, vs, vXScale.GetSize());
		
		vector<int> 	vnMapIndices;
		vector<uint> 	vnR1s;
		vs.FindDuplicates(vnMapIndices, vnR1s);
		
		for(int ii=nBegin+1; ii< vnMapIndices.GetSize(); ii++)
		{
			if( vnMapIndices[nBegin] == -1 || vnMapIndices[ii] == -1 )// -1 means no duplication 
				break;
			
			if( vnMapIndices[nBegin] != vnMapIndices[ii] )
				break;
		}		
		nEnd = ii-1;
		return TRUE;
	}
	
	BOOL	setColData(Column& col, double& dData, int nRow)
	{
		if( !col )
			return FALSE;
		
		return m_wks.SetCell(nRow, col.GetIndex(), dData);		
	}
	
	BOOL	getXColumn(Column& colX)
	{
		if( m_wks.GetNumCols() == 0 )
		{
			int nn = m_wks.AddCol();
			if(nn < OUTPUT_WKS_X_COL_IND)
				return false;
			m_wks.Columns(nn).SetType(OKDATAOBJ_DESIGNATION_X);
		}
		
		const int nXCol = OUTPUT_WKS_X_COL_IND;
		colX = m_wks.Columns(nXCol);
		return colX.IsValid();
	}
	
	///Jasmine 01/23/10 USE_RELATED_DS_IS_MORE_RELIABLE	
	string _make_related_dataset_name(LPCSTR lpcszPrefix, int nn)
	{
		string strRelatedName(lpcszPrefix);
		if(nn > 0)
			strRelatedName += (string)nn;
		return strRelatedName;
	}
	///End USE_RELATED_DS_IS_MORE_RELIABLE

	///Jasmine 01/20/10 SHOW_NEAREST_POINT
	// get Y column with PlotUID, if not found will create a new column
	//BOOL	checkGetYColumn(Column& colY, UINT uPlotUID, bool* pNewCreated = NULL)
	BOOL	checkGetColumn(Column& colY, UINT uPlotUID, LPCSTR lpcszDataset, bool* pNewCreated = NULL,
								bool bCopySrcColLabel = false, 
								int nBookLabel = -1, int nSheetLabel = -1, int nShortNameLabel = -1, int nLongName = -1) ///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
	///End SHOW_NEAREST_POINT
	{
		colY.Detach();
		
		bool bNewCreated = false;
		///Jasmine 01/23/10 USE_RELATED_DS_IS_MORE_RELIABLE
		DataPlot dp;
		dp = (DataPlot)Project.GetObject(uPlotUID);
		if(!dp)
			return FALSE;
		
		int nDsCount;
		vector<string> vsDatasets;
		if(dp.GetRelatedDatasetNames(vsDatasets) > 0)
			nDsCount = get_list_enum_number(vsDatasets, lpcszDataset);
			
		if(nDsCount > 0)
		{
			for(int ii = 0; ii < nDsCount; ii++)
			{
				string strRelatedName = _make_related_dataset_name(lpcszDataset, ii);
				DatasetObject dsYValue;
				if( dp.GetRelatedDataset(strRelatedName, dsYValue) && dsYValue )
				{
					string strRange;
					dsYValue.GetRangeString(strRange, NTYPE_FOR_RANGE);///Jasmine 01/26/10 COLUMNS_IN_ONE_WKS_HAVE_SAME_LONNG_NAME_NEED_SHORT_NAME_TO_DISDINGUISH
					
					DataRange drYValue;
					drYValue.Add("Range1", strRange);
					
					Worksheet wks;
					int c1,c2;
					drYValue.GetRange(wks, c1, c2);
					if( is_same_layer(wks, m_wks) )
					{
						ASSERT(c1==c2); // only one column		
						Column col = m_wks.Columns(c1);
						colY = col;
						break;
					}
				}
			}
		}
		///End USE_RELATED_DS_IS_MORE_RELIABLE
		
		string strBook, strSheet, strCol, strLongname;
		_get_notaion_by_type(dp, false, strBook, strSheet, strCol, strLongname);
		
		if( !colY )
		{
			checkAddColumn(m_wks, colY, strBook, strSheet, strCol, strLongname);
			if( colY )
			{
				bNewCreated = true;
				///Jasmine 01/23/10 USE_RELATED_DS_IS_MORE_RELIABLE
				string strRelatedName = _make_related_dataset_name(lpcszDataset, nDsCount);
				DatasetObject dsYValueNew(colY);
				dp.SetRelatedDataset(strRelatedName, dsYValueNew);
				///End USE_RELATED_DS_IS_MORE_RELIABLE				
			}
		}
		
		if( NULL != pNewCreated )
			*pNewCreated = bNewCreated;
		
		if(!colY)
			return false;
		
		if(bCopySrcColLabel)
		{
			vector<int>		vnLabelTypes;
			vector<string> 	vsLabels;
			if( getColumnLabelsFromDataPlot(uPlotUID, vnLabelTypes, vsLabels, false) )
			{
				for(int nn = 0; nn < vsLabels.GetSize(); nn++)
				{
					if(RCLT_LONG_NAME == vnLabelTypes[nn])///Jasmine 01/18/10 CONFIGURE_SHOW_HIDE_SETTING
						continue;
					colY.SetExtendedLabel(vsLabels[nn], vnLabelTypes[nn]);
				}
			}
		}
		///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME 
		if(nBookLabel > -1)
			colY.SetExtendedLabel(strBook, nBookLabel);
		if(nSheetLabel > -1)
			colY.SetExtendedLabel(strSheet, nSheetLabel);
		if(nShortNameLabel > -1)
			colY.SetExtendedLabel(strCol, nShortNameLabel);
		if(nLongName > -1)
			colY.SetExtendedLabel(strLongname, nLongName);
		///End SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
		return true;
	}
	///Jasmine 01/27/10 MATCH_EXIST_COL_TO_CONSOLIDATE	
	bool	isColLabelMatch(const Column& col, int nLabelType, LPCSTR lpcszLabel)
	{
		if(lpcszLabel == NULL)//NULL means not care
			return true;
		
		string strLabel;
		if( col.GetExtendedLabel(strLabel, nLabelType) && !strLabel.IsEmpty() )
		{
			if(strLabel.Compare(lpcszLabel) != 0)
				return false;;
		}

		return true;
	}
	BOOL	checkAddColumn(Worksheet& wks, Column& col, LPCSTR lpcszBook, LPCSTR lpcszSheet, LPCSTR lpcszCol, LPCSTR lpcszLongname)
	{
		int nShortNameLabel = _check_add_dataset_indentifier_label(wks, LABEL_SHORT_NAME, false);
		int	nBookLabel 		= _check_add_dataset_indentifier_label(wks, LABEL_BOOK_NAME, false);
		int	nSheetLabel 	= _check_add_dataset_indentifier_label(wks, LABEL_SHEET_NAME, false);
		int nLongName = RCLT_LONG_NAME;
		
		col.Detach();
		
		foreach(Column cc in wks.Columns)
		{
			if(col)
				continue;
			
			if( OUTPUT_WKS_X_COL_IND == cc.GetIndex() )
				continue;
			
			if( nShortNameLabel > -1 && !isColLabelMatch(cc, nShortNameLabel, lpcszCol) )
				continue;
			
			if( nBookLabel > -1 && !isColLabelMatch(cc, nBookLabel, lpcszBook) )
				continue;
			
			if( nSheetLabel > -1 && !isColLabelMatch(cc, nSheetLabel, lpcszSheet) )
				continue;
			
			if( nLongName > -1 && !isColLabelMatch(cc, nLongName, lpcszLongname) )
				continue;
			
			col = cc;
		}
		
		if(!col)
		{
			int nNewCol = wks.AddCol();
			if(nNewCol > -1)
				col = wks.Columns(nNewCol);
		}
		
		return col.IsValid();
	}
	///End MATCH_EXIST_COL_TO_CONSOLIDATE
	BOOL	getColumnFromDataPlot(UINT nPlotUID, Column& col, bool bGetX)
	{
		DataPlot dp;
		dp = (DataPlot)Project.GetObject(nPlotUID);
		if( !dp )
		{
			return error_report("Fail invalid plotUID");
		}

		Column colX, colY;
		get_plot_columns(dp, colX, colY);
		col = bGetX? colX : colY;
		return col.IsValid();
	}
	
	BOOL	getColumnLabelsFromDataPlot(UINT nPlotUID, vector<int>& vnLabelTypes, vector<string>& vsLabels, bool bGetX)
	{
		Column 	col;
		if( !getColumnFromDataPlot(nPlotUID, col, bGetX) )
		{
			return error_report("Fail to get column object from data plot");
		}
		
		vnLabelTypes.RemoveAll();
		vsLabels.RemoveAll();
		for(int nType = RCLT_LONG_NAME; nType <= RCLT_MAX_TYPE; nType++)
		{
			string strLabel;
			if( col.GetExtendedLabel(strLabel, nType) && !strLabel.IsEmpty() )
			{
				vnLabelTypes.Add(nType);
				vsLabels.Add(strLabel);
			}
		}
		return (0 != vsLabels.GetSize());
	}
	
private:
	string					m_strOutWksName;
	Worksheet				m_wks;
};

/*----------------------------------------------------------------------------*/
/* VTagUnit, composition of VTag and VTagLabel
/*----------------------------------------------------------------------------*/

class VTagUnit
{
public:
	VTagUnit(const DataPlot& dp)
	{
		m_bIsSelected = FALSE;
		
		m_arrTagLabels.SetAsOwner(TRUE);///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	}
	~VTagUnit()
	{
		;
	}
public:
	BOOL Create(const GraphLayer& gl, double dXScaleMark, LPCSTR lpcszLabelTextFormat = NULL)
	{
		if ( !m_vTag.GetValidObj().Create(gl, dXScaleMark, 0) )
		{
			ASSERT(FALSE);
			return FALSE;
		}

		if ( !Init(lpcszLabelTextFormat) )
		{
			m_vTag.GetValidObj().Destroy();
			ASSERT(FALSE);
		}

		return TRUE;
	}

	BOOL Init(LPCSTR lpcszLabelTextFormat = NULL)
	{
		string strLTScript;

		strLTScript.Format(STR_OBJ_EVENT_LT_SCRIPT_FORMAT, VCOT_TAG);
		BOOL bSetTagLTScriptOK = m_vTag.GetValidObj().SetLabTalkScriptEvent(strLTScript);

		return bSetTagLTScriptOK;
	}

	///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	BOOL AddTagLabel(const GraphLayer& gl, const DataPlot& dp, double dXScaleMark, double dYScaleMark, LPCSTR lpcszLabelTextFormat = NULL)
	{
		VTagLabel* pvNewTagLabel = new VTagLabel;
		
		if (pvNewTagLabel)
		{
			if ( !pvNewTagLabel->GetValidObj().Create(gl, dXScaleMark, dYScaleMark) || m_arrTagLabels.Add(*pvNewTagLabel) < 0 )
			{
				ASSERT(FALSE);
				pvNewTagLabel->GetValidObj().Destroy();
				delete pvNewTagLabel;
				return FALSE;
			}
		}		
		
		string strLTScript;
		strLTScript.Format(STR_OBJ_EVENT_LT_SCRIPT_FORMAT, VCOT_TAG_LABEL);
		BOOL bSetTagLabelLTScriptOK = pvNewTagLabel->GetValidObj().SetLabTalkScriptEvent(strLTScript);
		
		BOOL bSetTextFormatOK = TRUE;
		if (NULL != lpcszLabelTextFormat)
			bSetTextFormatOK = pvNewTagLabel->GetValidObj().SetTextFormat(lpcszLabelTextFormat);
		
		BOOL bRet = pvNewTagLabel->AttachToPlot(dp);
		return bRet && bSetTagLabelLTScriptOK && bSetTextFormatOK;
	}
	///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	
	BOOL Destroy( DWORD dwCntrl = OCD_DEL_LINKED_OBJS | OCD_DEL_PARENT_IF_LAST )
	{
		BOOL bRet = TRUE;
		if (m_vTag)
			bRet &= m_vTag.GetValidObj().Destroy(dwCntrl);
		///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		//if (m_vTagLabel)
			//bRet &= m_vTagLabel.GetValidObj().Destroy(dwCntrl);
		for(int ii = m_arrTagLabels.GetSize() - 1; ii >= 0; ii--)
		{
			if( m_arrTagLabels.GetAt(ii).Destroy() )
				bRet &= m_arrTagLabels.RemoveAt(ii);
			else
				bRet &= false;
		}
		///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		return bRet;
	}

	BOOL GetProperties(TreeNode& tnProperties)
	{
		if ( tnProperties.IsValid() )
		{
			///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
			/*VLABEL_PROPERTIES vpLabelProperties;
			m_vTagLabel.GetProperties(vpLabelProperties);

			tnProperties += vpLabelProperties;
			tnProperties += m_Properties;*/
			ASSERT(0);//doing
			///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE

			TreeNode tnTag		= tree_check_get_node(tnProperties, STR_OBJ_TAG_NAME_VTAG);
			TreeNode tnTagLabel	= tree_check_get_node(tnProperties, STR_OBJ_TAG_NAME_VTAG_LABEL);

			BOOL bRet1 = m_vTag.GetProperties(tnTag);
			///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
			//BOOL bRet2 = m_vTagLabel.GetProperties(tnTagLabel);
			BOOL bRet2 = FALSE;
			ASSERT(0);//doing
			///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
			return bRet1 && bRet2;
		}
		return FALSE;
	}
	BOOL SetProperties(const TreeNode& tnProperties)
	{
		if (tnProperties.IsValid())
		{
			///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
			/*
			m_Properties = tnProperties;
			TreeNode tnTag		= tnProperties.GetNode(STR_OBJ_TAG_NAME_VTAG);
			TreeNode tnTagLabel	= tnProperties.GetNode(STR_OBJ_TAG_NAME_VTAG_LABEL);
			BOOL bRet1 = m_vTag.SetProperties(tnTag);
			
			BOOL bRet2 = m_vTagLabel.SetProperties(tnTagLabel);

			VLABEL_PROPERTIES vpLabelProperties;
			vpLabelProperties = tnProperties;
			m_vTagLabel.SetProperties(vpLabelProperties);
			return bRet1 && bRet2;*/
			ASSERT(0);
			///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		}
		return FALSE;
	}
	BOOL CleanupEventHandlerScript()
	{
		BOOL bRet = TRUE;
		///Kenny 06/16/2009 ALLOW_DELETING_TAG_AND_TAG_LABEL_AFTER_DLG_IS_CLOSED
		if (m_vTag)
		{
			bRet &= m_vTag.GetValidObj().SetLabTalkScriptEvent("", GRCT_NONE, 0);
			bRet &= update_go_states(m_vTag, 0, GOC_NO_STATE|GOC_NO_IN_PLACE_EDIT);
		}
		///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		for(int ii = 0; ii < m_arrTagLabels.GetSize(); ii++)
		{
			VTagLabel& vTagLabel = m_arrTagLabels.GetAt(ii);
			bRet &= vTagLabel.GetValidObj().SetLabTalkScriptEvent("", GRCT_NONE, 0);
			bRet &= update_go_states(vTagLabel, 0, GOC_NO_STATE|GOC_NO_SELECT);
		}
		///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		///End ALLOW_DELETING_TAG_AND_TAG_LABEL_AFTER_DLG_IS_CLOSED
		return bRet;
	}
	BOOL IsAllObjectValid(GraphPage& gpSupposedParent)
	{
		///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		BOOL bRet = m_vTag.IsValid(gpSupposedParent);
		if (bRet)
		{
			for(int ii = 0; ii < m_arrTagLabels.GetSize(); ii++)
			{
				VTagLabel& vTagLabel = m_arrTagLabels.GetAt(ii);
				vTagLabel.IsValid(gpSupposedParent);
				VDataPlot vdp;
				vdp.AttachTo(vTagLabel.GetAttachedPlotUID());
				bRet &= is_object_valid_child_of_page(vdp, gpSupposedParent);
			}
		}
		return bRet;
		///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	}
public:
	VTag					m_vTag;
	Array<VTagLabel&>		m_arrTagLabels;//VTagLabel				m_vTagLabel;	///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE	
	
	BOOL					m_bIsSelected;

};

/*----------------------------------------------------------------------------*/
/* GraphVerticalCursorHolder
/*----------------------------------------------------------------------------*/

struct VHOLDER_SETTINGS 
{
	BOOL	bShowTextLabels;
};
	
#define		FOR_EACH_TEXT_LABEL_BEGIN(_gvcHolder, _label)										\
			GraphPage	_gp = _gvcHolder.GetPage();												\
			foreach ( GraphLayer _gl in _gp.Layers )											\
			{																					\
				VGraphLayer _vgl(_gl);															\
				Array<VTextLabel&>&		arrTextLabels = _vgl.AccessTextLabels();				\
				for ( int ii=0; ii<arrTextLabels.GetSize(); ++ii )								\
				{																				\
					VTextLabel& _label= arrTextLabels.GetAt(ii);
						
#define		FOR_EACH_TEXT_LABEL_END		\
				}						\
			}

#define GET_TAG_UNIT_ELEMENT_INDEX_BY_UID(uID, element)	\
	{\
		const int nArrSize = m_arrTagUnits.GetSize();\
		for (int ii = 0; ii < nArrSize; ++ii)\
		{\
			VTagUnit& vTagUnit = m_arrTagUnits.GetAt(ii);\
			if (vTagUnit.element.GetValidObj().IsValid() && uID == vTagUnit.element.GetUID() )\
				return ii;\
		}\
		return -1;\
	}

#define ACCESS_PAGE_HOLDER_BINARAY_STORAGE(action)	\
	{\
		GraphPage gpParent = GetPage();\
		if (gpParent)\
		{\
			BOOL bRet = gpParent.action(STR_VERTICAL_CURSOR_BINARY_STORAGE_NAME, tnProperties);\
			return bRet;\
		}\
		return FALSE;\
	}

class GraphVerticalCursorHolder
{
public:
	GraphVerticalCursorHolder(UINT uPageUID = 0)
	{
		m_nSelectedTagsCount = 0;
		m_uPageUID = uPageUID;
		m_arrTagUnits.SetAsOwner(TRUE);

		m_bIsAllTextLabelsInitValid = TRUE;
		
		m_vsPointIDs.SetSize(0);	///Jasmine 01/15/10 UPDATE_LIST_WITH_SORT_ORDER
	}
	~GraphVerticalCursorHolder()
	{
		m_line.ReattachAllObjects();
		
		if (m_bIsAllTextLabelsInitValid)
			removeAllTextLabels();
	}
public:
	int GetTagIndexByUID(const UINT uID)		GET_TAG_UNIT_ELEMENT_INDEX_BY_UID(uID, m_vTag)
	///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	//int GetTagLabelIndexByUID(const UINT uID)	GET_TAG_UNIT_ELEMENT_INDEX_BY_UID(uID, m_vTagLabel)
	int GetTagLabelIndexByUID(const UINT uID){ASSERT(0); return -1;}
	///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	double	GetSnapToX()
	{
		return m_line.GetSnapToX();
	}
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE

	GraphPage GetPage()
	{
		GraphPage gp;
		gp = (GraphPage)Project.GetObject(m_uPageUID);
		return gp;
	}

	VTagUnit* AddTagUnit(GraphLayer& gl, double dXScaleMark, LPCSTR lpcszLabelTextFormat = NULL)
	{
		VTagUnit* pvNewTagUnit = new VTagUnit;
		if (pvNewTagUnit)
		{
			if ( !pvNewTagUnit->Create(gl, dXScaleMark, lpcszLabelTextFormat) || m_arrTagUnits.Add(*pvNewTagUnit) < 0 )
			{
				ASSERT(FALSE);
				delete pvNewTagUnit;
				return NULL;
			}
		}
		return pvNewTagUnit;
	}

	BOOL InitPropertiesFromPage()
	{
		m_bIsAllTextLabelsInitValid = FALSE;
		Tree trProperties;
		if ( !GetPropertiesFromPage(trProperties) )
			return FALSE;

		BOOL bRet = SetProperties(trProperties);
		return bRet;
	}

	BOOL SaveHolderSettingsToPage()
	{
		Tree trProperties;
		BOOL bRet = GetPropertiesFromPage(trProperties);
		TreeNode tnHolder = tree_check_get_node(trProperties, STR_OBJ_TAG_NAME_VHOLDER_SETTINGS);
		tnHolder += m_Settings;
		bRet = PutPropertiesToPage(trProperties);
		ASSERT(bRet);
		return bRet;
	}

	// We don't provide a way to separately delete this properties because once we really need to delete it, the whole tree should be deleted.
	BOOL SaveLinePropertiesToPage()
	{
		Tree trProperties;
		BOOL bRet = GetPropertiesFromPage(trProperties);
		TreeNode tnLine = tree_check_get_node(trProperties, STR_OBJ_TAG_NAME_VLINE);
		bRet = m_line.GetProperties(tnLine);
		if (bRet)
			bRet = PutPropertiesToPage(trProperties);
		ASSERT(bRet);
		return bRet;
	}

	// Note: we have to call this function to remove the corresponding TreeNode in page before deleting/destroying TagUnit
	BOOL SaveTagUnitPropertiesToPage(const VTagUnit& vTagUnit, BOOL bDelete = FALSE)
	{
		Tree trProperties;
		BOOL bRet = GetPropertiesFromPage(trProperties);
		const UINT uTagUID = vTagUnit.m_vTag.GetUID();
		TreeNode tnTagUnits = tree_check_get_node(trProperties, STR_OBJ_TAG_NAME_VTAG_UNITS);
		TreeNode tnTagUnit = tnTagUnits.FindNodeByAttribute(TREE_UID, uTagUID, FALSE);

		if (bDelete)
		{
			if (tnTagUnit.IsValid())
				bRet = tnTagUnit.Remove();
		}
		else
		{
			if (!tnTagUnit.IsValid())
			{
				tnTagUnit = tnTagUnits.AddNode(STR_OBJ_TAG_NAME_VTAG_UNIT_PREFIX);
				if (tnTagUnit.IsValid())
					tnTagUnit.SetAttribute(TREE_UID, uTagUID);
			}
			bRet = vTagUnit.GetProperties(tnTagUnit);
		}

		if (bRet)
			bRet = PutPropertiesToPage(trProperties);
		ASSERT(bRet);
		return bRet;
	}

	// Note: we have to call this function to remove the corresponding TreeNode in page before deleting/destroying TextLabel
	BOOL SaveTextLabelPropertiesToPage(const VTextLabel& vTextLabel, BOOL bDelete = FALSE)
	{
		Tree trProperties;
		BOOL bRet = GetPropertiesFromPage(trProperties);
		const UINT uTextLabelUID = vTextLabel.GetUID();
		TreeNode tnTextLabels = tree_check_get_node(trProperties, STR_OBJ_TAG_NAME_VTEXT_LABELS);
		TreeNode tnTextLabel = tnTextLabels.FindNodeByAttribute(TREE_UID, uTextLabelUID, FALSE);

		if (bDelete)
		{
			if (tnTextLabel.IsValid())
				bRet = tnTextLabel.Remove();
		}
		else
		{
			if (!tnTextLabel.IsValid())
			{
				tnTextLabel = tnTextLabels.AddNode(STR_OBJ_TAG_NAME_VTEXT_LABEL_PREFIX);
				if (tnTextLabel.IsValid())
					tnTextLabel.SetAttribute(TREE_UID, uTextLabelUID);
			}
			bRet = vTextLabel.GetProperties(tnTextLabel);
		}

		if (bRet)
			bRet = PutPropertiesToPage(trProperties);
		ASSERT(bRet);
		return bRet;
	}

#ifdef _DEBUG
	BOOL GetProperties(TreeNode& tnProperties)
	{
		if ( tnProperties.IsValid() )
		{
			// Properties of holder
			tnProperties += m_Settings;

			// Properties of line
			TreeNode tnLine = tree_check_get_node(tnProperties, STR_OBJ_TAG_NAME_VLINE);
			BOOL bRet = m_line.GetProperties(tnLine);

			// Properties of TagUnit
			TreeNode tnTagUnits = tree_check_get_node(tnProperties, STR_OBJ_TAG_NAME_VTAG_UNITS);

			const int nTagUnitSize = m_arrTagUnits.GetSize();
			for (int ii = 0; ii < nTagUnitSize; ++ii)
			{
				VTagUnit& vTagUnit = m_arrTagUnits.GetAt(ii);
				const UINT uTagUID = vTagUnit.m_vTag.GetUID();
				TreeNode tnTagUnit = tnTagUnits.FindNodeByAttribute(TREE_UID, uTagUID, FALSE);
				if (!tnTagUnit.IsValid())
				{
					tnTagUnit = tnTagUnits.AddNode(STR_OBJ_TAG_NAME_VTAG_UNIT_PREFIX);
					if (tnTagUnit.IsValid())
						tnTagUnit.SetAttribute(TREE_UID, uTagUID);
				}
				bRet = vTagUnit.GetProperties(tnTagUnit);
				ASSERT(bRet);
			}

			// Properties of TextLabel
			TreeNode tnTextLabels = tree_check_get_node(tnProperties, STR_OBJ_TAG_NAME_VTEXT_LABELS);

			const GraphVerticalCursorHolder& gvcHolder = *this;

			FOR_EACH_TEXT_LABEL_BEGIN(gvcHolder, textLabel)
				const UINT uTextLabelUID = textLabel.GetUID();
				TreeNode tnTextLabel = tnTextLabels.FindNodeByAttribute(TREE_UID, uTextLabelUID, FALSE);
				if (!tnTextLabel.IsValid())
				{
					tnTextLabel = tnTextLabels.AddNode(STR_OBJ_TAG_NAME_VTEXT_LABEL_PREFIX);
					if (tnTextLabel.IsValid())
						tnTextLabel.SetAttribute(TREE_UID, uTextLabelUID);
				}
				bRet = textLabel.GetProperties(tnTextLabel);
				ASSERT(bRet);
			FOR_EACH_TEXT_LABEL_END
			return bRet;
		}
		return FALSE;
	}
#endif // _DEBUG

protected:
	BOOL IsAllObjectValid(const TreeNode& tnProperties)
	{
		if ( !tnProperties.IsValid() )
			return FALSE;

		GraphPage gpParent = GetPage();
		if (!gpParent.IsValid())
		{
			MY_DBG_OUTPUT("GraphPage is invalid!");
			return FALSE;
		}

		BOOL bRet = FALSE;
		// Properties of line
		TreeNode tnLine = tnProperties.GetNode(STR_OBJ_TAG_NAME_VLINE);
		VGraphLine tmpLine(FALSE);
		bRet = tmpLine.SetProperties(tnLine);
		if ( !bRet || !tmpLine.IsValid(gpParent) )
		{
			MY_DBG_OUTPUT("Line is invalid!");
			return FALSE;
		}

		// Properties of tagunit
		TreeNode tnTagUnits = tnProperties.GetNode(STR_OBJ_TAG_NAME_VTAG_UNITS);
		if (tnTagUnits.IsValid())
		{
			foreach(TreeNode tnTagUnit in tnTagUnits.Children)
			{
				VTagUnit tagUnit;
				bRet = tagUnit.SetProperties(tnTagUnit);
				if ( !bRet || !tagUnit.IsAllObjectValid(gpParent) )
				{
					MY_DBG_OUTPUT("TagUnit is invalid!");
					return FALSE;
				}
			}
		}

		// Properties of TextLabel
		TreeNode tnTextLabels = tnProperties.GetNode(STR_OBJ_TAG_NAME_VTEXT_LABELS);
		if (tnTextLabels.IsValid())
		{
			foreach(TreeNode tnTextLabel in tnTextLabels.Children)
			{
				VTextLabel vTextLabel(FALSE);
				bRet = vTextLabel.SetProperties(tnTextLabel);
				if ( !bRet || !vTextLabel.IsValid(gpParent) || !vTextLabel.IsAttachedPlotValid(gpParent) )
				{
					MY_DBG_OUTPUT("TextLabel is invalid!");
					return FALSE;
				}
			}
		}
		m_bIsAllTextLabelsInitValid = TRUE;
		return TRUE;
	}

	BOOL SetProperties(const TreeNode& tnProperties)
	{
		if ( !IsAllObjectValid(tnProperties) )
		{
			return FALSE;
		}

		if (m_arrTagUnits.GetSize() > 0)
		{
			ASSERT(FALSE);
			return FALSE;
		}
		GraphPage gpParent = GetPage();
		if (!gpParent.IsValid())
		{
			MY_DBG_OUTPUT("GraphPage is invalid!");
			return FALSE;
		}

		BOOL bRet = FALSE;

		// Properties of holder
		TreeNode tnHolder = tnProperties.GetNode(STR_OBJ_TAG_NAME_VHOLDER_SETTINGS);
		if ( !tnHolder.IsValid() )
		{
			MY_DBG_OUTPUT("Holder settings is invalid!");
			// It's not that necessary to return FALSE here, we can still manage to init the holder by assigning some proper values.
			//return FALSE;
		#ifdef REMOVE_LABEL_FEATURE
			m_Settings.bShowTextLabels = FALSE;
		#else
			m_Settings.bShowTextLabels = TRUE;
		#endif
		}
		else
		{
			m_Settings = tnHolder;
		}

		// Properties of line
		TreeNode tnLine = tnProperties.GetNode(STR_OBJ_TAG_NAME_VLINE);
		bRet = m_line.SetProperties(tnLine);
		if ( !bRet || !m_line.IsValid(gpParent) )
		{
			MY_DBG_OUTPUT("Line is invalid!");
			return FALSE;
		}

		// Properties of TagUnits
		TreeNode tnTagUnits = tnProperties.GetNode(STR_OBJ_TAG_NAME_VTAG_UNITS);
		if (tnTagUnits.IsValid())
		{
			foreach(TreeNode tnTagUnit in tnTagUnits.Children)
			{
				VTagUnit* pvNewTagUnit = new VTagUnit;
				if (pvNewTagUnit)
				{
					bRet = pvNewTagUnit->SetProperties(tnTagUnit);
					if ( !bRet || !pvNewTagUnit->IsAllObjectValid(gpParent) || m_arrTagUnits.Add(*pvNewTagUnit) < 0 )
					{
						delete pvNewTagUnit;
						ASSERT(FALSE);
					}
				}
			}
		}

		// Properties of TextLabel
		vector<UINT> vnInitialzedLayerUIDs;
		TreeNode tnTextLabels = tnProperties.GetNode(STR_OBJ_TAG_NAME_VTEXT_LABELS);
		if (tnTextLabels.IsValid())
		{
			foreach(TreeNode tnTextLabel in tnTextLabels.Children)
			{
				VTextLabel* pvNewTextLabel = new VTextLabel;
				if (pvNewTextLabel)
				{
					bRet = pvNewTextLabel->SetProperties(tnTextLabel);
					bRet = bRet && pvNewTextLabel->IsValid(gpParent);
					if (bRet)
					{
						GraphLayer glParent;
						pvNewTextLabel->GetParent(glParent);
						bRet = glParent.IsValid();
						if (bRet)
						{
							vector<UINT> vnIndices;
							const UINT nLayerUID = glParent.GetUID(TRUE);
							const int nFindRet = vnInitialzedLayerUIDs.Find(MATREPL_TEST_EQUAL, nLayerUID, vnIndices);
							const bool bUninitialized = nFindRet <= 0;
							if (bUninitialized)
								vnInitialzedLayerUIDs.Add(nLayerUID);
							VGraphLayer vgl(glParent);
							Array<VTextLabel&>& arrTextLabels = vgl.AccessTextLabels(false, bUninitialized);
							bRet = arrTextLabels.Add(*pvNewTextLabel) >= 0;
						}
					}
					if (!bRet)
					{
						ASSERT(FALSE);
						delete pvNewTextLabel;
					}
				}
			}
		}
		return bRet;
	}

	BOOL GetPropertiesFromPage(TreeNode& tnProperties)	ACCESS_PAGE_HOLDER_BINARAY_STORAGE(GetBinaryStorage)
	BOOL PutPropertiesToPage(const TreeNode& tnProperties)	ACCESS_PAGE_HOLDER_BINARAY_STORAGE(PutBinaryStorage)
private:
	void removeAllTextLabels()
	{
		GraphPage gp = GetPage();
		if (!gp)
		{
			ASSERT(FALSE);
			return;
		}
		foreach ( GraphLayer gl in gp.Layers )
		{
			VGraphLayer vgl(gl);
			vgl.RemoveTextLabels();
		}
	}
public:
	VGraphLine			m_line;
	Array<VTagUnit&>	m_arrTagUnits;

	VHOLDER_SETTINGS	m_Settings;

	UINT				m_uPageUID;

	// Flags/Others
	UINT				m_nSelectedTagsCount;
	
	vector<string>		m_vsPointIDs;	///Jasmine 01/15/10 UPDATE_LIST_WITH_SORT_ORDER
	
private:
	BOOL				m_bIsAllTextLabelsInitValid;
};

/*----------------------------------------------------------------------------*/
/* VLabelSetting
/*----------------------------------------------------------------------------*/

#define STR_LABEL_TYPE_Y_VALUE								"$(Y)"
#define STR_LABEL_TYPE_X_Y_VALS								"($(X),$(Y))"

enum LabelTextType
{
	LTT_Y_VALUE					= 0,
	LTT_X_Y_VALUES				= 1,
	LTT_CUSTOM					= 2,
};

#define SIGNIFICANT_DIGITS_FREE							0

struct VLabelSetting 
{
	int				nTextType;				/// Kenny, LabelTextType, but I prefer to name it nTextFormatType
	string			strLTCustomTextType;	/// Kenny, Custom text type, but I prefer to name it strLTCustomTextFormat.
	int				nSignificantDigits;
	string 			strNotation;			///Jasmine 12/08/09 QA81-8980 MORE_CONFIGURATION_FOR_INFO_TABLE
};

#define STR_INI_SECTION_NAME_VERTICAL_CURSOR				"VerticalCursor"
#define STR_INI_KEY_NAME_LABEL_SETTING_TEXT_TYPE			"TextType"
#define STR_INI_KEY_NAME_LABEL_SETTING_CUSTOM_TEXT_TYPE		"CustomTextType"
#define STR_INI_KEY_NAME_LABEL_SETTING_SD					"SignificantDigits"
#define STR_INI_KEY_NAME_LABEL_SETTING_NOTATION_TYPE		"NotationType"			///Jasmine 12/08/09 QA81-8980 MORE_CONFIGURATION_FOR_INFO_TABLE
#define STR_INI_KEY_NAME_SNAP_LINE_TO_DATA					"SnapLineToData"
#define STR_INI_KEY_NAME_DUMP_WKS_NAME						"DumpWksName"
#define STR_INI_KEY_NAME_CURSOR_SHOWS						"CursorShows"

///Jasmine 01/18/10 CONFIGURE_SHOW_HIDE_SETTING
//should not change these items value after release, since they're used in register
enum{
	VCURSOR_SHOW_INDEX = 0,
	VCURSOR_SHOW_CURSORX,
	VCURSOR_SHOW_CURSORY,
	VCURSOR_SHOW_NEAREST_X,
	VCURSOR_SHOW_NEAREST_Y,
	VCURSOR_SHOW_SHORTNAME,
	VCURSOR_SHOW_LONGNAME,
	VCURSOR_SHOW_SHEETNAME,
	VCURSOR_SHOW_BOOKNAME,
	
	VCURSOR_SHOW_TOTAL
};

struct VCursorShow
{
	int BookName;
	int SheetName;
	int ShortName;
	int LongName;
	int CursorX;
	int CursorY;
	int CursorNearX;///Jasmine 01/20/10 SHOW_NEAREST_POINT 		
	int CursorNearY;
	int Index;
};
///End CONFIGURE_SHOW_HIDE_SETTING

struct VManagerSetting 
{
	VLabelSetting	m_labelSetting;
	VCursorShow		m_cursorshow;
	
	///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
	//bool			m_bSnapLineToData;
	string			m_strSnappedPlot;
	///End SNAP_CURSOR_TO_DATA_PLOT
	///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	BOOL			m_bSnapToNearestX;
	///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	
	string			m_strDumpWksName;
public:
	BOOL SaveToIni()
	{	
		INIFileEx ini(STR_ORIGIN_INI_FILE_NAME);
		BOOL bRet = FALSE;

		bRet = ini.WriteInt(STR_INI_SECTION_NAME_VERTICAL_CURSOR, 		STR_INI_KEY_NAME_LABEL_SETTING_TEXT_TYPE, 			m_labelSetting.nTextType);
		bRet = ini.WriteString(STR_INI_SECTION_NAME_VERTICAL_CURSOR,	STR_INI_KEY_NAME_LABEL_SETTING_CUSTOM_TEXT_TYPE,	m_labelSetting.strLTCustomTextType);
		bRet = ini.WriteInt(STR_INI_SECTION_NAME_VERTICAL_CURSOR, 		STR_INI_KEY_NAME_LABEL_SETTING_SD, 					m_labelSetting.nSignificantDigits);
		
		///Jasmine 01/18/10 CONFIGURE_SHOW_HIDE_SETTING
		BitsHex bh;
		string	strShows;
		vector<byte> vbShows;
		getVCursorShowStateasVector(vbShows);
		if( bh.BitsToHexStr(vbShows, strShows) )
			bRet = ini.WriteString(STR_INI_SECTION_NAME_VERTICAL_CURSOR, STR_INI_KEY_NAME_CURSOR_SHOWS,	strShows);
		///End CONFIGURE_SHOW_HIDE_SETTING
		
		///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT, can not save this
		//bRet = ini.WriteInt(STR_INI_SECTION_NAME_VERTICAL_CURSOR, 		STR_INI_KEY_NAME_SNAP_LINE_TO_DATA,	m_bSnapLineToData);
		///End SNAP_CURSOR_TO_DATA_PLOT
		bRet = ini.WriteString(STR_INI_SECTION_NAME_VERTICAL_CURSOR,	STR_INI_KEY_NAME_DUMP_WKS_NAME, 	m_strDumpWksName);
		return bRet;
	}
	BOOL LoadFromIni()
	{
		INIFileEx ini(STR_ORIGIN_INI_FILE_NAME);
		BOOL bRet = FALSE;

		m_labelSetting.nTextType 			= ini.ReadInt(STR_INI_SECTION_NAME_VERTICAL_CURSOR, 	STR_INI_KEY_NAME_LABEL_SETTING_TEXT_TYPE, 			LTT_Y_VALUE);
		m_labelSetting.strLTCustomTextType 	= ini.ReadString(STR_INI_SECTION_NAME_VERTICAL_CURSOR,	STR_INI_KEY_NAME_LABEL_SETTING_CUSTOM_TEXT_TYPE,	STR_LABEL_TYPE_Y_VALUE);
		m_labelSetting.nSignificantDigits 	= ini.ReadInt(STR_INI_SECTION_NAME_VERTICAL_CURSOR, 	STR_INI_KEY_NAME_LABEL_SETTING_SD, 					SIGNIFICANT_DIGITS_FREE);
		
		///Jasmine 01/18/10 CONFIGURE_SHOW_HIDE_SETTING
		string strShows	= ini.ReadString(STR_INI_SECTION_NAME_VERTICAL_CURSOR,	STR_INI_KEY_NAME_CURSOR_SHOWS);
		BitsHex bh;
		vector<byte> vbShows;
		if( !strShows.IsEmpty() )
			bh.HexStrToBits(strShows, vbShows);
		setVCursorShowStatebyVector(vbShows);
		///End CONFIGURE_SHOW_HIDE_SETTING
		
		if (m_labelSetting.nTextType < LTT_Y_VALUE || m_labelSetting.nTextType > LTT_CUSTOM)
			m_labelSetting.nTextType = LTT_Y_VALUE;
		if (LTT_CUSTOM == m_labelSetting.nTextType && m_labelSetting.strLTCustomTextType.IsEmpty())
			m_labelSetting.strLTCustomTextType = STR_LABEL_TYPE_Y_VALUE;
		if (m_labelSetting.nSignificantDigits < MIN_SIGNIFICANT_DIGITS || m_labelSetting.nSignificantDigits > MAX_SIGNIFICANT_DIGITS)
			m_labelSetting.nSignificantDigits = SIGNIFICANT_DIGITS_FREE;

		///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
		//m_bSnapLineToData = ini.ReadInt(STR_INI_SECTION_NAME_VERTICAL_CURSOR, STR_INI_KEY_NAME_SNAP_LINE_TO_DATA, 0);
		m_strSnappedPlot.Empty();
		///End SNAP_CURSOR_TO_DATA_PLOT
		m_bSnapToNearestX = false;///Jasmine 02/08/10 REMEMBER_SNAP_OPTION
		
		m_strDumpWksName = ini.ReadString(STR_INI_SECTION_NAME_VERTICAL_CURSOR, STR_INI_KEY_NAME_DUMP_WKS_NAME, STR_DEFAULT_OUTPUT_WKS_NAME);

		if (m_strDumpWksName.IsEmpty())
			m_strDumpWksName = STR_DEFAULT_OUTPUT_WKS_NAME;
		return bRet;
	}
	
	///------ Folger 01/20/10 SUPPORT_SAVING_VERTICAL_CURSOR_SETTINGS_IN_PAGE
	#define			STR_VERTICAL_CURSOR_SETTINGS		"VerticalCursorSettings"
	BOOL	SaveToGraph(GraphPage& gp)
	{
		Tree	tr;
		tr.VLabelSetting = m_labelSetting;
		tr.VCursorShow = m_cursorshow;
		
		///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
		//tr.SnapLineToData.nVal = m_bSnapLineToData;
		tr.SnapLineToData.strVal = m_strSnappedPlot;
		///End SNAP_CURSOR_TO_DATA_PLOT
		tr.SnapToNearestX.nVal = m_bSnapToNearestX;///Jasmine 02/08/10 REMEMBER_SNAP_OPTION
		
		tr.DumpWksName.strVal = m_strDumpWksName;

		return gp.PutBinaryStorage(STR_VERTICAL_CURSOR_SETTINGS, tr);
	}

	BOOL	LoadFromGraph(GraphPage& gp)
	{
		Tree	tr;
		if ( !gp.GetBinaryStorage(STR_VERTICAL_CURSOR_SETTINGS, tr) )
		{
			LoadFromIni();		///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT, page changed, reset to default
			return FALSE;
		}

		m_labelSetting = tr.VLabelSetting;
		m_cursorshow = tr.VCursorShow;
		
		///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
		//m_bSnapLineToData = tr.SnapLineToData.nVal;
		m_strSnappedPlot = tr.SnapLineToData.strVal;
		///End SNAP_CURSOR_TO_DATA_PLOT
		m_bSnapToNearestX = tr.SnapToNearestX.nVal;///Jasmine 02/08/10 REMEMBER_SNAP_OPTION
		
		m_strDumpWksName = tr.DumpWksName.strVal;

		return TRUE;
	}
	///------ End SUPPORT_SAVING_VERTICAL_CURSOR_SETTINGS_IN_PAGE
	
private:
	///Jasmine 01/18/10 CONFIGURE_SHOW_HIDE_SETTING
	void getVCursorShowStateasVector(vector<byte>& vbShows)
	{
		vbShows.SetSize(VCURSOR_SHOW_TOTAL);
		vbShows[VCURSOR_SHOW_BOOKNAME] 	= m_cursorshow.BookName;
		vbShows[VCURSOR_SHOW_SHEETNAME] = m_cursorshow.SheetName;
		vbShows[VCURSOR_SHOW_SHORTNAME] = m_cursorshow.ShortName;
		vbShows[VCURSOR_SHOW_LONGNAME] 	= m_cursorshow.LongName;
		vbShows[VCURSOR_SHOW_CURSORX] 	= m_cursorshow.CursorX;
		vbShows[VCURSOR_SHOW_CURSORY] 	= m_cursorshow.CursorY;
		///Jasmine 01/20/10 SHOW_NEAREST_POINT 		
		vbShows[VCURSOR_SHOW_NEAREST_X]	= m_cursorshow.CursorNearX;
		vbShows[VCURSOR_SHOW_NEAREST_Y]	= m_cursorshow.CursorNearY;
		///End SHOW_NEAREST_POINT
		vbShows[VCURSOR_SHOW_INDEX] 	= m_cursorshow.Index;
	}
	void setVCursorShowStatebyVector(const vector<byte>& vbShows)
	{
		int nSize = vbShows.GetSize();//vbShows is from HexStrToBits, its size may not equal to VCURSOR_SHOW_TOTAL
		{	
			m_cursorshow.BookName 	= nSize > VCURSOR_SHOW_BOOKNAME? vbShows[VCURSOR_SHOW_BOOKNAME] : 1;
			m_cursorshow.SheetName 	= nSize > VCURSOR_SHOW_SHEETNAME? vbShows[VCURSOR_SHOW_SHEETNAME] : 0;
			m_cursorshow.ShortName 	= nSize > VCURSOR_SHOW_SHORTNAME? vbShows[VCURSOR_SHOW_SHORTNAME] : 1;
			m_cursorshow.LongName 	= nSize > VCURSOR_SHOW_LONGNAME? vbShows[VCURSOR_SHOW_LONGNAME] : 0;
			
			//m_cursorshow.CursorX 	= nSize > VCURSOR_SHOW_CURSORX? vbShows[VCURSOR_SHOW_CURSORX] : 1;
			//m_cursorshow.CursorY 	= nSize > VCURSOR_SHOW_CURSORY? vbShows[VCURSOR_SHOW_CURSORY] : 1;
			m_cursorshow.CursorX 	= 1;	///Jasmine 01/19/10 VCURSOR_ALWAYS_SHOW_XY_VALUE
			m_cursorshow.CursorY 	= 1;
			
			///Jasmine 01/20/10 SHOW_NEAREST_POINT 		
			m_cursorshow.CursorNearX= nSize > VCURSOR_SHOW_NEAREST_X? vbShows[VCURSOR_SHOW_NEAREST_X] : 0;
			m_cursorshow.CursorNearY= nSize > VCURSOR_SHOW_NEAREST_Y? vbShows[VCURSOR_SHOW_NEAREST_Y] : 0;
			///End SHOW_NEAREST_POINT
			
			m_cursorshow.Index 		= nSize > VCURSOR_SHOW_BOOKNAME? vbShows[VCURSOR_SHOW_INDEX] : 0;
		}
	}
	///End CONFIGURE_SHOW_HIDE_SETTING
};

/*----------------------------------------------------------------------------*/
/* GraphVerticalCursorManager
/*----------------------------------------------------------------------------*/

enum { CURSOR_MOVED, CURSOR_MOVING };

enum
{
	WM_USER_CURSOR_DELETED							= (WM_USER + 1100),
	WM_USER_UPDATE_CURSOR_POSITION,
	WM_USER_UPDATE_TEXT_LABELS_SHOW_STATE,
	WM_USER_TAG_ADDED,
	WM_USER_TAG_DELETED,
	WM_USER_UPDATE_TAG_SELECT_STATE,

	WM_USER_UPDATE_CURSOR_INTERSECT_POINT_BEGIN,
	WM_USER_NEW_CURSOR_INTERSECT_POINT,

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	WM_USER_QUERY_POSITION_CHANGE_BY_EDITING,
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
};

enum VerticalCursorObjType
{
	VCOT_LINE			= 0,
	VCOT_TEXT_LABEL		= 1,
	VCOT_TAG			= 2,
	VCOT_TAG_LABEL		= 3,
};

BOOL on_vertical_cursor_object_event(int nEvent, LPCSTR lpcszObjectName, VerticalCursorObjType objType)
{
	// If we select the line and then duplicate the graph, then we may failed to load the duplicated graph's cursor info
	// since the GetUID will make origin skip the reuid codes
	if (VCOT_LINE == objType && OE_UNSELECT == nEvent)
		return FALSE;

	const UINT uPageUID = Project.ActiveLayer().GetPage().GetUID(TRUE);
	if (VCOT_LINE == objType)
	{
		THE_VCURSOR_MANAGER.OnGraphLineEvent(nEvent, uPageUID);
		return TRUE;
	}
	else
	{
		GraphObject go = Project.ActiveLayer().GraphObjects(lpcszObjectName);
		if (go.IsValid())
		{
			const UINT uObjUID = go.GetUID(TRUE);
			switch (objType)
			{
			case VCOT_TEXT_LABEL:
				THE_VCURSOR_MANAGER.OnTextLabelEvent(nEvent, uPageUID, uObjUID);
				break;
#ifndef REMOVE_TAG_FEATURE
			case VCOT_TAG:
				THE_VCURSOR_MANAGER.OnTagEvent(nEvent, uPageUID, uObjUID);
				break;
			case VCOT_TAG_LABEL:
				THE_VCURSOR_MANAGER.OnTagLabelEvent(nEvent, uPageUID, uObjUID);
				break;
#endif//REMOVE_TAG_FEATURE
			}
			return TRUE;
		}
	}
	return FALSE;
}

#define CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN	\
	const bool bIsCurrentIndexValid = ( m_nCurrentTargetHolderIndex >= 0 && m_nCurrentTargetHolderIndex < m_arrGraphVerticalCursorHolders.GetSize() );\
	if ( bIsCurrentIndexValid )\
	{\
		GraphVerticalCursorHolder& gvcCurrentHolder = m_arrGraphVerticalCursorHolders.GetAt(m_nCurrentTargetHolderIndex);

#define CHECK_GET_CURRENT_CURSOR_HOLDER_END		\
	}

enum LoadCursorResult
{
	LOAD_CURSOR_INFO_FAILED			= -1,	// The tree is not exist in the page or failed to load

	LOAD_CURSOR_INFO_OK				= 0,
	CURSOR_INFO_EXIST_IN_MANAGER	= 1,
};

class GraphVerticalCursorManager
{
public:
	// Kenny:  Related tracker #4991
	//	By designed, user can only call the other public methods by GetInstance(), and there's only
	// one single instance of this class exist in the memory all the time (Singleton).
	// We have several reasons for doing this:
	//	1. This helps preventing creating the member data over and over when constructs the instances of this class
	//	2. To make the global event handler and the dialog access the same manager object.
	// but this is not supported by OC currently, so we have to use get_graph_vertical_cursor_manager() instead.

	//static GraphVerticalCursorManager& GetInstance()
	//{
		//static GraphVerticalCursorManager s_Manager;
		//return s_Manager;
	//}

	// The constructor and destructor are private to prevent creating this object instance by user
private:
	GraphVerticalCursorManager()
	{
		m_arrGraphVerticalCursorHolders.SetAsOwner(TRUE);
		m_pParentWnd = NULL;
		m_nCurrentTargetHolderIndex = -1;

		m_Setting.LoadFromIni();
		
		m_arrPoints.SetAsOwner(TRUE);///Jasmine 01/15/10 REORDER_CURSOR_VALUE_BY_LAYER_POS
	}
	~GraphVerticalCursorManager()
	{
	}
public:
	// Controlling tool
	BOOL	CanStartVerticalCursor();
	BOOL	StartVerticalCursor(Window* pParentWnd = NULL, UINT uPageUID = 0);
	BOOL	RemoveVerticalCursor(UINT uPageUID = 0);
	///------ Folger 01/22/09 ORIGIN_CRASH_WHEN_EXIT_IF_GRAPH_PAGE_HAS_VERTICAL_CURSOR_IS_NOT_ACTIVE
	void	ResetParentWnd();
	///------ End ORIGIN_CRASH_WHEN_EXIT_IF_GRAPH_PAGE_HAS_VERTICAL_CURSOR_IS_NOT_ACTIVE

	BOOL	HasCursorHolderForPage(UINT uPageUID = 0) { return ( getCursorHolderIndex( getActualPageUID(uPageUID) ) >= 0 ); }

	int		LoadCursorInfoFromPage(UINT uPageUID = 0, int* pnHolderIndex = NULL );	// return LoadCursorResult

	BOOL	IsPageCurrentTarget(UINT uPageUID = 0);

	BOOL	RetrieveCursorInfo(UINT uPageUID = 0);

	// Accessing holder data
	const GraphVerticalCursorHolder* GetCursorHolderPointer(UINT uPageUID = 0, int* pnHolderIndex = NULL);

// Note: The following public methods use the m_nCurrentTargetHolderIndex for speed up accessing target.

	// Accessing line
	BOOL 	IsOutOfView( double dXScale );	///Jasmine 01/28/10 RESTRICT_X_EDIT_TEXT
	BOOL	SetLinePosition(double dXScale);
	BOOL	GetLinePosition(double& rdPos, bool bIsMoving = false);

	///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
	//BOOL	IsLineSnappedToData() { return m_Setting.m_bSnapLineToData; }
	//void	SnapLineToData(BOOL bSnapToData = TRUE);
	BOOL	IsLineSnappedToData(DataPlot* pdpSnapped = NULL);
	void	SnapLineToData(LPCSTR lpcszSnappedPlot);
	///End SNAP_CURSOR_TO_DATA_PLOT

	// Accessing TextLabels
	BOOL	ShowTextLabels(bool bShow = true);
	BOOL	IsTextLabelsShown();

	// Accessing Tags
	BOOL	AddTags();
	BOOL	RemoveTag(const vector<UINT>& vnTagUID);
	BOOL	RemoveSelectedTagsGroups();
	BOOL	ClearAllTags();
	int 	GetNumTags();

	BOOL	SelectTagsGroup(BOOL bNextGroup = TRUE);

	UINT	GetSelectedTagsCount();

	// TagLabel & TextLabel
	BOOL	UpdateLabelsFontSize(bool bIncrease = true);
	BOOL	SetLabelsSignificantDigits(int nSignificantDigits);
	int		GetLabelsSignificantDigits() { return m_Setting.m_labelSetting.nSignificantDigits; }

	// Misc.
	/// Iris 
	BOOL  	GetSetting(VManagerSetting& stSetting);
	BOOL  	SetSetting(const VManagerSetting& stSetting);
	BOOL	DumpTagValues();//DumpData();
	BOOL	DumpCursorValues();
	BOOL 	ActivateOutputWks();	///Jasmine 01/22/10 BTN_TO_ACTIVATE_OUTPUT_WKS	
	
	void 	GetCursorDisplayOrder(vector<uint>& vnIndeces, UINT uPageUID);	///Jasmine 01/15/10 UPDATE_LIST_WITH_SORT_ORDER
	
	BOOL	SetLabelsText(int nSignificantDigits, int nTextType, LPCSTR lpcszCustomText = NULL);
	
	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	BOOL	WorldToView(double& rView, double rWorld);
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	
#ifdef _DEBUG
	void	ClearAllCursorHolders()
	{
		const int nHolderSize = m_arrGraphVerticalCursorHolders.GetSize();
		for (int ii = nHolderSize-1; ii >= 0; --ii)
		{
			removeCursorHolder(ii);
		}
	}
	void	ShowPropertiesTree()
	{
		CStopWatch watch("ShowPropertiesTree()");
		Tree trProperties;
		BOOL bRet = GetProperties(trProperties);
		out_tree(trProperties);
	}
#endif // _DEBUG

	///------ Folger 01/21/09 VERTICAL_CURSOR_CRASH_ORIGIN_IF_ACCESS_ARRAY_MEMBER_OF_MANAGER_OBJECT_DIRECTLY
	/// This kind of Array member accessing is not safe in Origin :
	/// 
	///			Array<VIntersectPoint&>& arrPoints = THE_VCURSOR_MANAGER.m_arrPoints;
	///			VIntersectPoint& pti = arrPoints.GetPoint( 0 );
	/// 
	/// When arrPoints is destroyed, it may crash Origin. Here just walk around it before more general fix.
	VIntersectPoint*	GetPoint(int nIndex)
	{
		if ( nIndex < 0 || nIndex >= GetPointsCount() )
			return NULL;

		VIntersectPoint&	pt = m_arrPoints.GetAt(nIndex);
		return &pt;
	}

	int					GetPointsCount()
	{
		return m_arrPoints.GetSize();
	}
	///------ End VERTICAL_CURSOR_CRASH_ORIGIN_IF_ACCESS_ARRAY_MEMBER_OF_MANAGER_OBJECT_DIRECTLY

public:
	void	OnGraphLineEvent(int nEvent, UINT uPageUID);
	void	OnTagEvent(int nEvent, UINT uPageUID, UINT uTagUID);
	void	OnTagLabelEvent(int nEvent, UINT uPageUID, UINT uTagLabelUID);
	void	OnTextLabelEvent(int nEvent, UINT uPageUID, UINT uTextLabelUID);
protected:
	void	OnLineDeleted(UINT uPageUID);
	void	OnPageWndClosed(UINT uPageUID);
	void	OnUpdateLinePosition(UINT uPageUID, bool bIsMoving);

	void	OnUpdateTextLabelsShowState(UINT uPageUID);

	void	OnTagSelected(UINT uPageUID, UINT uTagUID);
	void	OnTagUnselected(UINT uPageUID, UINT uTagUID);
	void	OnTagDeleted(UINT uPageUID, UINT uTagUID);

	void	OnTagLabelDeleted(UINT uPageUID, UINT uTagLabelUID);

	void	OnTextLabelMoved(UINT uPageUID, UINT uTextLabelUID);
	void	OnTextLabelDeleted(UINT uPageUID, UINT uTextLabelUID);

#ifdef _DEBUG
	BOOL	GetProperties(TreeNode& tnProperties);
	BOOL	SetProperties(const TreeNode& tnProperties);
#endif // _DEBUG
private:
	UINT	getActualPageUID(UINT uPageUID = 0);
	int		getCursorHolderIndex(UINT uPageUID);

	void	onUpdateTagSelectState(UINT uPageUID, UINT uTagUID, BOOL bSelected);

	BOOL	removeCursorHolder(UINT uHolderIndex);

	BOOL	cleanupTagsEventHandlerScript(GraphVerticalCursorHolder& gvcHolder);

	VTagUnit* addTag(GraphVerticalCursorHolder& gvcHolder, const GraphLayer& gl, double dXScaleMark);
	int 	addTagDone(GraphVerticalCursorHolder& gvcHolder, const VTagUnit& vTagUnit);
	int		removeTag(GraphVerticalCursorHolder& gvcHolder, UINT uTagIndex);
	//int		getAllTagsPosAndPlotInfo( const GraphVerticalCursorHolder& gvcHolder, vector<int>& vnXCenter, vector<int>& vnYCenter = NULL, vector<UINT>& vnTagPlotUIDMap = NULL );
	int		getAllTagsPosAndPlotInfo( const GraphVerticalCursorHolder& gvcHolder, vector<int>& vnXCenter);
	int		getTagsInSamePageX( const GraphVerticalCursorHolder& gvcHolder, int nPageX, vector<UINT>& vnTagIndices );

	bool	retrieveHolderCursorInfo(GraphVerticalCursorHolder& gvcHolder, bool bUpdateTextLabels = true,  bool bUpdateNotation = true);

	///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	void	GraphRefresh();

	void	ForceUpdate(GraphVerticalCursorHolder& Holder);
	///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	
private:
	Array<GraphVerticalCursorHolder&>	m_arrGraphVerticalCursorHolders;

	// Kenny: I use the index of holder ( in the array ) instead of using a pointer to holder for the following reasons:
	// 1. Get rid of the dangling pointer (a pointer that points to an invalid object) issue.
	// 2. We can directly remove the holder from the array by using the index.
	// 3. Moreover, using a pointer does not speed up the accessing ( tested comparing with using index ).
	int									m_nCurrentTargetHolderIndex;

	Window*								m_pParentWnd;

	VManagerSetting						m_Setting;
	
	Array<VIntersectPoint&>				m_arrPoints;///Jasmine 01/15/10 REORDER_CURSOR_VALUE_BY_LAYER_POS
};

BOOL GraphVerticalCursorManager::CanStartVerticalCursor()
{
	GraphLayer glActive = Project.ActiveLayer();
	if( !glActive )
		return FALSE;
	GraphPage gp = glActive.GetPage();
	if (gp)
	{
		bool bExistDataPlot = false;
		bool bExist3DGraph = false;
		foreach(GraphLayer gl in gp.Layers)
		{
			if ( is_3D_graph(gl) )
			{
				bExist3DGraph = true;
				break;
			}
			if ( !bExistDataPlot && gl.DataPlots.Count() > 0 )
			{
				bExistDataPlot = true;
			}
		}
		if (bExist3DGraph)
			return error_report("This tool is NOT available for 3D Graph!", true);
		if (!bExistDataPlot)
			return error_report("This tool can ONLY be used in a GraphPage that has at lease one dataplot in it!", true);
		return TRUE;
	}
	return FALSE;
}

BOOL GraphVerticalCursorManager::StartVerticalCursor( Window* pParentWnd /*= NULL*/, UINT uPageUID /*= 0*/ )
{
	if ( !CanStartVerticalCursor() )
		return FALSE;

	GraphVerticalCursorHolder* pHolder = NULL;
	bool bCreatedNewOne = false;

	int nHolderIndex = -1;
	LoadCursorInfoFromPage(uPageUID, &nHolderIndex);

	if (nHolderIndex < 0)
	{
		pHolder = new GraphVerticalCursorHolder( uPageUID = getActualPageUID(uPageUID) );
		if (NULL == pHolder)
		{
			ASSERT(FALSE);
			return FALSE;
		}
		bCreatedNewOne = pHolder->m_line.GetValidObj().Create(TRUE);
		if ( bCreatedNewOne )
		{
			string strLTScript;
			strLTScript.Format(STR_OBJ_EVENT_LT_SCRIPT_FORMAT, VCOT_LINE);
			bCreatedNewOne = pHolder->m_line.GetValidObj().SetLabTalkScriptEvent(strLTScript);
		}
		if (!bCreatedNewOne)
		{
			delete pHolder;
			ASSERT(FALSE);
			return FALSE;
		}
	#ifdef REMOVE_LABEL_FEATURE
		pHolder->m_Settings.bShowTextLabels = FALSE;
	#else
		pHolder->m_Settings.bShowTextLabels = TRUE;
	#endif
		nHolderIndex = m_arrGraphVerticalCursorHolders.Add(*pHolder);

		pHolder->SaveHolderSettingsToPage();
		pHolder->SaveLinePropertiesToPage();

		MY_DBG_OUTPUT1("A new GVCHolder is created! Total = %u", m_arrGraphVerticalCursorHolders.GetSize() );
	}
	else
	{
		const GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(nHolderIndex);
		pHolder = &gvcHolder;
		MY_DBG_OUTPUT2("Switched to the %dth GVC, Total = %u", (nHolderIndex+1), m_arrGraphVerticalCursorHolders.GetSize() );
	}

	m_pParentWnd = pParentWnd;
	m_nCurrentTargetHolderIndex	= nHolderIndex;

	///------ Folger 01/20/10 SUPPORT_SAVING_VERTICAL_CURSOR_SETTINGS_IN_PAGE
	const GraphVerticalCursorHolder* pgvcHolder = GetCursorHolderPointer();
	if(pgvcHolder)
		m_Setting.LoadFromGraph( pgvcHolder->GetPage() );
	///------ End SUPPORT_SAVING_VERTICAL_CURSOR_SETTINGS_IN_PAGE

	if (bCreatedNewOne)
	{
		//OnUpdateLinePosition(uPageUID, false);	// We have to call this function to create/update the TextLabels
		// The above codes may cause twice time running OnUpdateLinePosition, so we have to trigger the OnUpdateLinePosition in this way.
		ForceUpdate(*pHolder);
	}
	return pHolder;
}

BOOL GraphVerticalCursorManager::RemoveVerticalCursor(UINT uPageUID /*= 0*/)
{
	int nHolderIndex = getCursorHolderIndex( uPageUID = getActualPageUID(uPageUID) );
	if (nHolderIndex >= 0)
		return removeCursorHolder(nHolderIndex);
	return TRUE;	// True or false means nothing if the holder does not exist, so return TRUE to hint "success"
}

///------ Folger 01/22/09 ORIGIN_CRASH_WHEN_EXIT_IF_GRAPH_PAGE_HAS_VERTICAL_CURSOR_IS_NOT_ACTIVE
void	GraphVerticalCursorManager::ResetParentWnd()
{
	m_pParentWnd = NULL;
}
///------ End ORIGIN_CRASH_WHEN_EXIT_IF_GRAPH_PAGE_HAS_VERTICAL_CURSOR_IS_NOT_ACTIVE

int GraphVerticalCursorManager::LoadCursorInfoFromPage( UINT uPageUID /*= 0*/, int* pnHolderIndex /*= NULL */ )
{
	int nRetValue = CURSOR_INFO_EXIST_IN_MANAGER;
	int nHolderIndex = getCursorHolderIndex( uPageUID = getActualPageUID(uPageUID) );
	if (nHolderIndex < 0)
	{
		BOOL bRet = FALSE;
		// This may probably cause allocating/deallocating the holder frequently, 
		// but we have no choice since only the holder "knows" how to initialize itself from page storage
		GraphVerticalCursorHolder* pHolder = new GraphVerticalCursorHolder(uPageUID);
		if (pHolder)
		{
			bRet = pHolder->InitPropertiesFromPage();
			if (bRet)
			{
				nHolderIndex = m_arrGraphVerticalCursorHolders.Add(*pHolder);
				bRet = nHolderIndex >= 0;
			}
			if ( !bRet )
			{
				nHolderIndex = -1;
				delete pHolder;
				//ASSERT(FALSE);
			}

			if ( pHolder )
				ForceUpdate(*pHolder);
		}
		nRetValue = (bRet ? LOAD_CURSOR_INFO_OK : LOAD_CURSOR_INFO_FAILED);
	}
	if (pnHolderIndex)
		*pnHolderIndex = nHolderIndex;
	return nRetValue;
}

BOOL GraphVerticalCursorManager::IsPageCurrentTarget( UINT uPageUID /*= 0*/ )
{
	uPageUID = getActualPageUID(uPageUID);
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		const BOOL bIsPageCurrentTarget = gvcCurrentHolder.m_uPageUID == uPageUID;
		return bIsPageCurrentTarget;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

BOOL GraphVerticalCursorManager::RetrieveCursorInfo( UINT uPageUID /*= 0*/ )
{
	uPageUID = getActualPageUID(uPageUID);
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		return retrieveHolderCursorInfo(gvcCurrentHolder, false);
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

const GraphVerticalCursorHolder* GraphVerticalCursorManager::GetCursorHolderPointer( UINT uPageUID /*= 0*/, int* pnHolderIndex /*= NULL*/ )
{
	GraphVerticalCursorHolder* pHolder = NULL;
	const int nHolderIndex = getCursorHolderIndex( uPageUID = getActualPageUID(uPageUID) );
	if (nHolderIndex >= 0)
	{
		const GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(nHolderIndex);
		pHolder = &gvcHolder;
	}
	if (pnHolderIndex)
		*pnHolderIndex = nHolderIndex;
	return pHolder;
}
///Jasmine 01/28/10 RESTRICT_X_EDIT_TEXT
BOOL GraphVerticalCursorManager::IsOutOfView( double dXScale )
{
	if( is_missing_value(dXScale) )
		return TRUE;
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		GraphLayer glParent;
		gvcCurrentHolder.m_line.GetParent(glParent);
		
		Scale scale = glParent.X;		
		double dFrom= scale.From*1.5-scale.To*0.5;
		double dTo 	= scale.To*1.5-scale.From*0.5;
		return dXScale < min(dFrom, dTo) || dXScale > max(dFrom, dTo);
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return TRUE;
}
///End RESTRICT_X_EDIT_TEXT
BOOL GraphVerticalCursorManager::SetLinePosition( double dXScale )
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		if( is_equal(gvcCurrentHolder.m_line.GetValidObj().GetPosition(), dXScale) )
			return FALSE;
		// Kenny: These following codes helps erasing the extra "ghost line" when the line is selected by user.
		// This is a temporary solution, please see related Tracker #13620
		Selection mySelection;
		mySelection.Remove(gvcCurrentHolder.m_line);
		GraphPage gp = gvcCurrentHolder.GetPage();
		if (gp)
			gp.Refresh(TRUE);
		gvcCurrentHolder.m_line.GetValidObj().SetPosition(dXScale);
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

BOOL GraphVerticalCursorManager::GetLinePosition( double& rdPos, bool bIsMoving /*= false*/ )
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		rdPos = gvcCurrentHolder.m_line.GetValidObj().GetPosition(bIsMoving);
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
//void GraphVerticalCursorManager::SnapLineToData( BOOL bSnapToData /*= TRUE*/ )
//{
	//m_Setting.m_bSnapLineToData = bSnapToData;
	//if (bSnapToData)
	//{
		//CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
		//{
			//gvcCurrentHolder.m_line.GetValidObj().SnapToData(TRUE);
		//}
		//CHECK_GET_CURRENT_CURSOR_HOLDER_END
	//}
//}

///------ Folger 01/27/10 VERTICAL_CURSOR_SNAP_TO_NEAREST_X_DATA
#define		STR_PLOT_NEAREST_X		"NearestX"
///------ End VERTICAL_CURSOR_SNAP_TO_NEAREST_X_DATA
BOOL	GraphVerticalCursorManager::IsLineSnappedToData(DataPlot* pdpSnapped)
{
	///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	if ( m_Setting.m_bSnapToNearestX )
		return TRUE;
	///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	
	if( !m_Setting.m_strSnappedPlot.IsEmpty() )
	{
		///------ Folger 01/27/10 VERTICAL_CURSOR_SNAP_TO_NEAREST_X_DATA
		if ( m_Setting.m_strSnappedPlot.Compare(STR_PLOT_NEAREST_X) == 0 )
			return TRUE;
		///------ End VERTICAL_CURSOR_SNAP_TO_NEAREST_X_DATA

		DataPlot dp;
		XYRange rng;
		if( okxf_init_range_from_string(&rng, m_Setting.m_strSnappedPlot) && rng.IsValid() )
			rng.GetPlot(dp);

		if( dp.IsValid() )
		{
			if( pdpSnapped )
				*pdpSnapped = dp;
			return true;
		}
		else
		{
			m_Setting.m_strSnappedPlot.Empty();
		}
	}
	return false;
}

void GraphVerticalCursorManager::SnapLineToData(LPCSTR lpcszSnappedPlot)
{
	m_Setting.m_strSnappedPlot = lpcszSnappedPlot;
	DataPlot dpSnapped;
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		if( IsLineSnappedToData(&dpSnapped) )
		{
			gvcCurrentHolder.m_line.GetValidObj().SnapToPlot(dpSnapped);
			///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
			m_Setting.m_strSnappedPlot = gvcCurrentHolder.m_line.GetValidObj().GetLastSnapToPlotDescription();
			///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
		}
		else
		{
			gvcCurrentHolder.m_line.GetValidObj().SnapToNone();
		}
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
}
///End SNAP_CURSOR_TO_DATA_PLOT

BOOL GraphVerticalCursorManager::ShowTextLabels( bool bShow /*= true*/ )
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		FOR_EACH_TEXT_LABEL_BEGIN(gvcCurrentHolder, label)
			label.GetValidObj().Show = bShow;
		FOR_EACH_TEXT_LABEL_END

		gvcCurrentHolder.m_Settings.bShowTextLabels = bShow;
		gvcCurrentHolder.SaveHolderSettingsToPage();

		if (bShow)
			retrieveHolderCursorInfo(gvcCurrentHolder);
		OnUpdateTextLabelsShowState( gvcCurrentHolder.m_uPageUID );
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

BOOL GraphVerticalCursorManager::IsTextLabelsShown()
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		return gvcCurrentHolder.m_Settings.bShowTextLabels;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

BOOL GraphVerticalCursorManager::AddTags()
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		CStopWatch watch("AddTags()");
		GraphPage gp = gvcCurrentHolder.GetPage();
		if (!gp)
		{
			ASSERT(FALSE);
			return FALSE;
		}

		int nPageX = gvcCurrentHolder.m_line.GetValidObj().GetPagePosition();
		
		/*vector<int>		vnTagXCenter, vnTagYCenter;
		vector<UINT>	vnTagPlotUIDMap;
		getAllTagsPosAndPlotInfo(gvcCurrentHolder, vnTagXCenter, vnTagYCenter, vnTagPlotUIDMap);
		
		vector<UINT>	vnIndices;
		vnTagXCenter.Sort(SORT_ASCENDING, TRUE, vnIndices);
		vnTagYCenter.Reorder(vnIndices);
		vnTagPlotUIDMap.Reorder(vnIndices);*/
		vector<int>		vnTagXCenter;
		getAllTagsPosAndPlotInfo(gvcCurrentHolder, vnTagXCenter);
		vnTagXCenter.Sort();
		bool	bOKToAdd = true;
		for ( int nn=0; nn<vnTagXCenter.GetSize(); ++nn )
		{			
			if ( vnTagXCenter[nn] == nPageX )
			{
				bOKToAdd = false;
				break;
			}
			else if ( vnTagXCenter[nn] > nPageX )
			{
				break;
			}
		}
		if ( !bOKToAdd )
			return FALSE;

		Selection mySelection;

		/*for (int kk = 0; kk < vnTagXCenter.GetSize(); ++kk)
		{
			if (vnTagXCenter[kk] != nPageX)
			{
				const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt( vnIndices[kk] );
				if (vTagUnit.m_bIsSelected)
					mySelection.Remove(vTagUnit.m_vTag);
			}
		}*/
		for (int kk = 0; kk < gvcCurrentHolder.m_arrTagUnits.GetSize(); ++kk)
		{
			const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(kk);
			if (vTagUnit.m_bIsSelected)
				mySelection.Remove(vTagUnit.m_vTag);
		}
		
		bool bRet = true;
		VTagUnit* pvNewTagUnit = NULL;
		foreach(GraphLayer gl in gp.Layers)
		{
			if(!bRet)
				continue;
			
			VGraphLayer vgl(gl);
			
			double dXScale;
			vgl.PageToScaleCoordinate(nPageX, dXScale);
			
			double rdPageCoord;
			vgl.ScaleToPageUnit(dXScale, rdPageCoord, M_PERCENT);
			
			///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE	
			if(gl.GetIndex() == 0)
				pvNewTagUnit = addTag(gvcCurrentHolder, gl, rdPageCoord);
			if(pvNewTagUnit == NULL)
			{
				bRet = false;		
				continue;
			}

			foreach (DataPlot dp in gl.DataPlots)
			{
				vector vYScale;
				VDataPlot vdp(dp);
				vector<BOOL> vbIsInterpolated;
				vdp.GetYValues(dXScale, vYScale, vbIsInterpolated);

				const int nYValSize = vYScale.GetSize();

				for (int ii = 0; ii < nYValSize; ++ii)
				{
					/*int nPageY;
					vgl.ScaleToPageCoordinate(vYScale[ii], nPageY, false);
					bool	bOKToAdd = true;
					for ( int nn=0; nn<vnTagXCenter.GetSize(); ++nn )
					{
						if ( vnTagPlotUIDMap[nn] != dp.GetUID() )
							continue;
						
						if ( vnTagXCenter[nn] == nPageX && abs(vnTagYCenter[nn] - nPageY) <= 1 )
						{
							bOKToAdd = false;
							break;
						}
						else if ( vnTagXCenter[nn] > nPageX )
						{
							break;
						}
					}
					if ( !bOKToAdd )
						continue;
					*/
					pvNewTagUnit->AddTagLabel(gl, dp, dXScale, vYScale[ii], m_Setting.m_labelSetting.strLTCustomTextType);
				}
			}
			
			addTagDone(gvcCurrentHolder, *pvNewTagUnit);
			mySelection.Add(pvNewTagUnit->m_vTag);					
		}
		
		string strTempvar = "_temppagewidth";
		gp.LT_execute(strTempvar+"=page.width");
		double width;
		LT_get_var(strTempvar, &width);
		LT_execute("del -v "+strTempvar);
		int nOffsetPageX = nPageX+width*0.02;
		gvcCurrentHolder.m_line.GetValidObj().SetPosition(nOffsetPageX);
		
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}


BOOL GraphVerticalCursorManager::RemoveTag( const vector<UINT>& vnTagUID )
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
		const int nTagUnitSize = gvcCurrentHolder.m_arrTagUnits.GetSize();

		vector<UINT> vnRemoveTagIndices;
		for (int ii = 0; ii < nTagUnitSize; ++ii)
		{
			const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(ii);

			vector<UINT> vnIndices;
			const int nMatchedNum = vnTagUID.Find( MATREPL_TEST_EQUAL, vTagUnit.m_vTag.GetValidObj().GetUID(), vnIndices );
			if ( nMatchedNum > 0 && vnIndices.GetSize() > 0)
			{
				ASSERT(1 == nMatchedNum && 1 == vnIndices.GetSize());
				vnRemoveTagIndices.Add( ii );
			}
		}
		vnRemoveTagIndices.Sort(SORT_DESCENDING);
		for (int jj = 0; jj < vnRemoveTagIndices.GetSize(); ++jj)
		{
			removeTag(gvcCurrentHolder, vnRemoveTagIndices[jj]);
		}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return TRUE;
}

BOOL GraphVerticalCursorManager::RemoveSelectedTagsGroups()
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		CStopWatch watch("RemoveSelectedTagsGroups()");

		vector<int> vnSelectedTagsPageX;
		const int nTagUnitSize = gvcCurrentHolder.m_arrTagUnits.GetSize();

		for (int ii = 0; ii < nTagUnitSize; ++ii)
		{
			const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(ii);
			if (vTagUnit.m_bIsSelected)
				vnSelectedTagsPageX.Add(vTagUnit.m_vTag.GetValidObj().GetCenterPageX());
		}

		vnSelectedTagsPageX.Sort(SORT_ASCENDING);
		for (int jj = vnSelectedTagsPageX.GetSize()-2; jj >= 0; --jj)
		{
			if (vnSelectedTagsPageX[jj] == vnSelectedTagsPageX[jj+1])
				vnSelectedTagsPageX.RemoveAt(jj+1);
		}

		for (int kk = 0; kk < vnSelectedTagsPageX.GetSize(); ++kk)
		{
			vector<UINT> vnTagIndices;
			const int nRet = getTagsInSamePageX(gvcCurrentHolder, vnSelectedTagsPageX[kk], vnTagIndices);
			vnTagIndices.Sort(SORT_DESCENDING);
			for (int ii = 0; ii < vnTagIndices.GetSize(); ++ii)
			{
				removeTag(gvcCurrentHolder, vnTagIndices[ii]);
			}
		}
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

BOOL GraphVerticalCursorManager::ClearAllTags()
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		const int nTagCount = gvcCurrentHolder.m_arrTagUnits.GetSize();
		for (int ii = nTagCount-1; ii >= 0; --ii)
		{
			removeTag(gvcCurrentHolder, ii);
		}
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

int GraphVerticalCursorManager::GetNumTags()
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		return gvcCurrentHolder.m_arrTagUnits.GetSize();
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return -1;
}

BOOL GraphVerticalCursorManager::SelectTagsGroup(BOOL bNextGroup /*= TRUE*/)
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		CStopWatch watch("SelectNextTagsGroup()");
		const int nTagUnitSize = gvcCurrentHolder.m_arrTagUnits.GetSize();
		if (0 == nTagUnitSize)
			return FALSE;

		// 1. Get all the selected tags' page position/coordinate
		vector<int> vnSelectedTagsPageX;

		for (int ii = 0; ii < nTagUnitSize; ++ii)
		{
			const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(ii);
			if (vTagUnit.m_bIsSelected)
				vnSelectedTagsPageX.Add(vTagUnit.m_vTag.GetValidObj().GetCenterPageX());
		}

		// 2. Get all the tags' info ( including those selected)
		vector<int>	vnTagXCenter;
		getAllTagsPosAndPlotInfo(gvcCurrentHolder, vnTagXCenter);

		// 3. Sort the vector first for finding target tags to be selected
		vector<UINT> vnTagIndices;
		vnTagXCenter.Sort(bNextGroup ? SORT_ASCENDING : SORT_DESCENDING, TRUE, vnTagIndices);

		ASSERT(vnTagXCenter.GetSize() > 0);
		int nToBeSelectedTagsPageX = vnTagXCenter[0];	// By default, select the leftmost tag if bNextGroup, otherwise the rightmost one

		// 4. Find the next/previous nearest tags
		if (vnSelectedTagsPageX.GetSize() > 0)
		{
			vnSelectedTagsPageX.Sort(bNextGroup ? SORT_DESCENDING : SORT_ASCENDING);
			for (int ii = 0; ii < vnTagXCenter.GetSize(); ++ii)
			{
				if (	bNextGroup && vnTagXCenter[ii] > vnSelectedTagsPageX[0]
					||	!bNextGroup && vnTagXCenter[ii] < vnSelectedTagsPageX[0] )
				{
					nToBeSelectedTagsPageX = vnTagXCenter[ii];
					break;
				}
			}
		}

		Selection newSelection;

		// 5. Unselect the previous selected tags that are not locate at target position
		for (int kk = 0; kk < vnTagXCenter.GetSize(); ++kk)
		{
			if (vnTagXCenter[kk] != nToBeSelectedTagsPageX)
			{
				const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt( vnTagIndices[kk] );
				if (vTagUnit.m_bIsSelected)
					newSelection.Remove(vTagUnit.m_vTag);
			}
		}

		// 6. Select the tags that locate at target position
		vector<UINT> vnTagIndicesInSamePageX;
		const int nRet = getTagsInSamePageX(gvcCurrentHolder, nToBeSelectedTagsPageX, vnTagIndicesInSamePageX);
		for (int jj = 0; jj < vnTagIndicesInSamePageX.GetSize(); ++jj)
		{
			const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(vnTagIndicesInSamePageX[jj]);
			newSelection.Add(vTagUnit.m_vTag);
		}
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

UINT GraphVerticalCursorManager::GetSelectedTagsCount()
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		return gvcCurrentHolder.m_nSelectedTagsCount;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return 0;
}

BOOL GraphVerticalCursorManager::UpdateLabelsFontSize( bool bIncrease /*= true*/ )
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		const int nTagLabelSize = gvcCurrentHolder.m_arrTagUnits.GetSize();
		for (int ii = 0; ii < nTagLabelSize; ++ii)
		{
			const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(ii);
			///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
			//vTagUnit.m_vTagLabel.GetValidObj().UpdateFontSize(bIncrease);
			for(int ii = 0; ii < vTagUnit.m_arrTagLabels.GetSize(); ii++)
			{
				VTagLabel& vTagLabel = vTagUnit.m_arrTagLabels.GetAt(ii);
				vTagLabel.GetValidObj().UpdateFontSize(bIncrease);
			}
			///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		}
		FOR_EACH_TEXT_LABEL_BEGIN(gvcCurrentHolder, text_label)
			text_label.GetValidObj().UpdateFontSize(bIncrease);
		FOR_EACH_TEXT_LABEL_END
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}

BOOL GraphVerticalCursorManager::SetLabelsSignificantDigits( int nSignificantDigits )
{
	m_Setting.m_labelSetting.nSignificantDigits = nSignificantDigits;
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		const int nTagLabelSize = gvcCurrentHolder.m_arrTagUnits.GetSize();
		for (int ii = 0; ii < nTagLabelSize; ++ii)
		{
			const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(ii);
			///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
			//vTagUnit.m_vTagLabel.GetValidObj().SetSignificantDigits(nSignificantDigits);
			for(int ii = 0; ii < vTagUnit.m_arrTagLabels.GetSize(); ii++)
			{
				VTagLabel& vTagLabel = vTagUnit.m_arrTagLabels.GetAt(ii);
				vTagLabel.GetValidObj().SetSignificantDigits(nSignificantDigits);
			}
			///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		}
		FOR_EACH_TEXT_LABEL_BEGIN(gvcCurrentHolder, text_label)
			text_label.GetValidObj().SetSignificantDigits(nSignificantDigits);
		FOR_EACH_TEXT_LABEL_END
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return TRUE;
}

void _insert_after(string& strParent, LPCSTR lpcszSub, char cc)
{
	string strFind(cc);
	
	strFind.MakeUpper();	
	int nPos = strParent.Find(strFind);
	if(nPos >= 0)
	{
		strParent.Insert(nPos + 1, lpcszSub);
		return;
	}
	
	strFind.MakeLower();	
	nPos = strParent.Find(strFind);
	if(nPos >= 0)
	{
		strParent.Insert(nPos + 1, lpcszSub);
		return;
	}
	
	return;
}

BOOL GraphVerticalCursorManager::SetLabelsText(int nSignificantDigits, int nTextType, LPCSTR lpcszCustomText /*= NULL*/ )
{
	if (nTextType > LTT_CUSTOM)
		return FALSE;

	m_Setting.m_labelSetting.nTextType = nTextType;
	if (LTT_CUSTOM == nTextType)
	{
		const string strText = lpcszCustomText;
		if (strText.IsEmpty())
			return FALSE;
		m_Setting.m_labelSetting.strLTCustomTextType = strText;
	}
	else
	{
		string strText = LTT_Y_VALUE == nTextType? STR_LABEL_TYPE_Y_VALUE : STR_LABEL_TYPE_X_Y_VALS;
		
		if (MIN_SIGNIFICANT_DIGITS <= nSignificantDigits && nSignificantDigits <= MAX_SIGNIFICANT_DIGITS)
		{
			string strSignificantDigitFmt;
			strSignificantDigitFmt.Format(",*%d*", nSignificantDigits);
			//"($(X),$(Y))"
			_insert_after(strText, strSignificantDigitFmt, 'x');
			_insert_after(strText, strSignificantDigitFmt, 'y');
		}
		m_Setting.m_labelSetting.strLTCustomTextType = strText;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		const int nTagLabelSize = gvcCurrentHolder.m_arrTagUnits.GetSize();
		for (int ii = 0; ii < nTagLabelSize; ++ii)
		{
			const VTagUnit& vTagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(ii);
			///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
			//vTagUnit.m_vTagLabel.GetValidObj().SetTextFormat(m_Setting.m_labelSetting.strLTCustomTextType);
			for(int ii = 0; ii < vTagUnit.m_arrTagLabels.GetSize(); ii++)
			{
				VTagLabel& vTagLabel = vTagUnit.m_arrTagLabels.GetAt(ii);
				vTagLabel.GetValidObj().SetTextFormat(m_Setting.m_labelSetting.strLTCustomTextType);
			}
			///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		}
		FOR_EACH_TEXT_LABEL_BEGIN(gvcCurrentHolder, text_label)
			text_label.GetValidObj().SetTextFormat(m_Setting.m_labelSetting.strLTCustomTextType);
		FOR_EACH_TEXT_LABEL_END
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return TRUE;
}

///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
BOOL	GraphVerticalCursorManager::WorldToView(double& rView, double rWorld)
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
	{
		VGraphLayer	vgl;
		gvcCurrentHolder.m_line.GetParent(vgl);
		return vgl.ScaleToPageCoordinate(rWorld, rView, true);
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END

	return FALSE;
}
///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE

BOOL GraphVerticalCursorManager::GetSetting(VManagerSetting& stSetting)
{
	stSetting = m_Setting;
	return TRUE;
}

BOOL GraphVerticalCursorManager::SetSetting(const VManagerSetting& stSetting)
{
	Tree trOld;
	trOld += m_Setting;	
	Tree trNew;
	trNew += stSetting;	
	Tree trDiff;
	trDiff.CreateDiff(trOld, trNew);
	if(!trDiff.FirstNode)
		return TRUE;	
	
		
	m_Setting = stSetting;
	///------ Folger 01/20/10 SUPPORT_SAVING_VERTICAL_CURSOR_SETTINGS_IN_PAGE
	///Jasmine 02/08/10 HOLDER_INVALID_WHEN_ACTIVE_PAGE_NOT_GRAPH
	//change Report Sheet string and then activate a book so that GetCursorHolderPointer will fail
	//BOOL bSaveOK = m_Setting.SaveToIni() && m_Setting.SaveToGraph(GetCursorHolderPointer()->GetPage());
	BOOL bSaveOK = m_Setting.SaveToIni();
	ASSERT(bSaveOK);
	
	GraphPage gp;
	const GraphVerticalCursorHolder* pgvcHolder = GetCursorHolderPointer();
	if(pgvcHolder)
		gp = pgvcHolder->GetPage();
	else
	{
		CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN
			gp = gvcCurrentHolder.GetPage();
		CHECK_GET_CURRENT_CURSOR_HOLDER_END
	}
	bSaveOK = m_Setting.SaveToGraph(gp);
	ASSERT(bSaveOK);
	///End HOLDER_INVALID_WHEN_ACTIVE_PAGE_NOT_GRAPH
	///------ End SUPPORT_SAVING_VERTICAL_CURSOR_SETTINGS_IN_PAGE	
	
	
	if(trDiff.m_labelSetting)
	{
		SetLabelsSignificantDigits(m_Setting.m_labelSetting.nSignificantDigits);
		SetLabelsText(m_Setting.m_labelSetting.nSignificantDigits, m_Setting.m_labelSetting.nTextType, m_Setting.m_labelSetting.strLTCustomTextType);
	}
	if(trDiff.m_strSnappedPlot || trDiff.m_bSnapToNearestX)
	{
		///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
		//SnapLineToData(m_Setting.m_bSnapLineToData);
		SnapLineToData(m_Setting.m_strSnappedPlot);
		///End SNAP_CURSOR_TO_DATA_PLOT
	
		///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
		GraphRefresh();
		///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	}
	
	return TRUE;
}

///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
void	GraphVerticalCursorManager::GraphRefresh()
{
	const GraphVerticalCursorHolder* pgvcHolder = GetCursorHolderPointer();
	if ( pgvcHolder )
	{
		GraphPage gp = pgvcHolder->GetPage();
		if( gp )
			gp.Refresh();
	}
}

void	GraphVerticalCursorManager::ForceUpdate(GraphVerticalCursorHolder& Holder)
{
	Holder.m_line.GetValidObj().SetPositionView(Holder.m_line.GetValidObj().GetPagePosition());
}
///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG

// Dump Tag information from current graph to worksheet
BOOL GraphVerticalCursorManager::DumpTagValues()//DumpData()
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN 
	{	
		DumpDataHelper dumpDataHelper(m_Setting.m_strDumpWksName);
		
		const int nNumTags = GetNumTags();
		vector 			vXScale, vYScale;
		vector<uint> 	vuPlotUID;
		for(int ii = 0; ii < nNumTags; ii++)
		{			
			VTagUnit&	tagUnit = gvcCurrentHolder.m_arrTagUnits.GetAt(ii);
			///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
			for(int jj = 0; jj < tagUnit.m_arrTagLabels.GetSize(); jj++)
			{
				VTagLabel& vTagLabel = tagUnit.m_arrTagLabels.GetAt(jj);
				vXScale.Add( vTagLabel.GetMarkedPointXScale() );			
				vYScale.Add( vTagLabel.GetMarkedPointYScale() );
				vuPlotUID.Add( vTagLabel.GetAttachedPlotUID() );
			}
			///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		}
		
		// sort vector by X scale
		vector<uint> vnIndeces;
		vXScale.Sort(SORT_ASCENDING, true, vnIndeces);
		vYScale.Reorder(vnIndeces);
		vuPlotUID.Reorder(vnIndeces);
		
		bool bRet = dumpDataHelper.Dump(vXScale, vYScale, vuPlotUID);
		
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
}

BOOL GraphVerticalCursorManager::DumpCursorValues()
{
	CHECK_GET_CURRENT_CURSOR_HOLDER_BEGIN 
	{		
		///Jasmine 01/15/10 UPDATE_LIST_WITH_SORT_ORDER
		int nSize = m_arrPoints.GetSize();
		if(nSize <= 0)
			return FALSE;
	
		vector<uint> vnIndeces;
		GetCursorDisplayOrder(vnIndeces, 0);//vnIndeces will be used when the first time output to a new sheet
		///End UPDATE_LIST_WITH_SORT_ORDER
		
		vector 			vXScale, vYScale;
		vector 			vNearestX, vNearestY;	///Jasmine 01/20/10 SHOW_NEAREST_POINT
		vector<uint> 	vuPlotUID;

		for(int ii = 0; ii < nSize; ii++)
		{
			VIntersectPoint& pti = m_arrPoints.GetAt( vnIndeces[ii] );
			vXScale.Add(pti.fpPoint.x);
			vYScale.Add(pti.fpPoint.y);
			vuPlotUID.Add(pti.plotuid);
			
			///Jasmine 01/25/10 SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
			//if(m_Setting.m_cursorshow.CursorNearX)
				//vNearestX.Add(pti.fpNearPoint.x);
			//if(m_Setting.m_cursorshow.CursorNearY)
				//vNearestY.Add(pti.fpNearPoint.y);
			///End SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
		}
		
		VIntersectPoint& pti = m_arrPoints.GetAt( 0 );
		DumpDataHelper dumpDataHelper(m_Setting.m_strDumpWksName);
		bool bRet = dumpDataHelper.Dump(vXScale, vYScale, vuPlotUID, 
										//vNearestX, vNearestY, ///Jasmine 01/25/10 SHOW_NEAREST_POINT_IN_DLG_BUT_NOT_OUTPUT
										m_Setting.m_cursorshow,	///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME 
										pti.xinfo);
		
		return TRUE;
	}
	CHECK_GET_CURRENT_CURSOR_HOLDER_END
	return FALSE;
}
///Jasmine 01/22/10 BTN_TO_ACTIVATE_OUTPUT_WKS	
BOOL GraphVerticalCursorManager::ActivateOutputWks()
{
	DumpDataHelper dumpDataHelper(m_Setting.m_strDumpWksName);
	return dumpDataHelper.ActivateOutputWks();
}
///End BTN_TO_ACTIVATE_OUTPUT_WKS

///Jasmine 01/15/10 UPDATE_LIST_WITH_SORT_ORDER
void GraphVerticalCursorManager::GetCursorDisplayOrder(vector<uint>& vnIndeces, UINT uPageUID)
{
	vnIndeces.SetSize(0);
	
	vector<string> vsPointIDs;
	const GraphVerticalCursorHolder* pgvcHolder = GetCursorHolderPointer(uPageUID);
	if(pgvcHolder)
		vsPointIDs = pgvcHolder->m_vsPointIDs;
	
	int nSize = m_arrPoints.GetSize();
	bool bReorderBySort = (vsPointIDs.GetSize() > 0);
	if(bReorderBySort)
		ASSERT(vsPointIDs.GetSize() == nSize);
	
	vector vLayerYPos;
	for(int ii = 0; ii < nSize; ii++)
	{
		VIntersectPoint& pti = m_arrPoints.GetAt(ii);
		///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
		//int id = 10 * pti.layer + pti.plot;
		int	id = pti.plotuid;
		///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
		
		if(bReorderBySort)
		{
			int nFind = vsPointIDs.Find((string)id);
			if(nFind < 0)
				bReorderBySort = false;
			else
				vnIndeces.Add(nFind);
		}
		//else	///always
		{
			vLayerYPos.Add( pti.layerYPos);
		}
	}

	if(!bReorderBySort )
		vLayerYPos.Sort( SORT_ASCENDING, FALSE, vnIndeces);
	
	ASSERT(vnIndeces.GetSize() == nSize);
}
///End UPDATE_LIST_WITH_SORT_ORDER
void GraphVerticalCursorManager::OnGraphLineEvent( int nEvent, UINT uPageUID )
{
	switch (nEvent)
	{
	case OE_MOVE:
	case OE_MOVING:
		{
			const bool bIsMoving = OE_MOVING == nEvent;
			OnUpdateLinePosition(uPageUID, bIsMoving);
		}
		break;
	///Jasmine 01/26/10 UPDATE_CURSOR_POSITION_ON_SCALE_CHANGE
	case OE_SCALE_CHANGE:	
		if (m_pParentWnd)
			m_pParentWnd->SendMessage(WM_USER_UPDATE_CURSOR_POSITION, uPageUID, CURSOR_MOVING);
		OnUpdateLinePosition(uPageUID, false);
		break;
	///End UPDATE_CURSOR_POSITION_ON_SCALE_CHANGE
	case OE_DELETE:
		OnLineDeleted(uPageUID);
		break;
	case OE_WIN_CLOSE:
		OnPageWndClosed(uPageUID);
		break;
	}
}

void GraphVerticalCursorManager::OnTagEvent( int nEvent, UINT uPageUID, UINT uTagUID )
{
	switch (nEvent)
	{
	case OE_SELECT:
		OnTagSelected(uPageUID, uTagUID);
		break;
	case OE_UNSELECT:
		OnTagUnselected(uPageUID, uTagUID);
		break;
	case OE_DELETE:
		OnTagDeleted(uPageUID, uTagUID);
		break;
	}
}

void GraphVerticalCursorManager::OnTagLabelEvent( int nEvent, UINT uPageUID, UINT uTagLabelUID )
{
	switch (nEvent)
	{
	case OE_DELETE:
		OnTagLabelDeleted(uPageUID, uTagLabelUID);
		break;
	}
}

void GraphVerticalCursorManager::OnTextLabelEvent( int nEvent, UINT uPageUID, UINT uTextLabelUID )
{
	switch (nEvent)
	{
	case OE_MOVE:
		OnTextLabelMoved(uPageUID, uTextLabelUID);
		break;
	case OE_DELETE:
		OnTextLabelDeleted(uPageUID, uTextLabelUID);
		break;
	}
}

void GraphVerticalCursorManager::OnLineDeleted(UINT uPageUID)
{
	RemoveVerticalCursor(uPageUID);
}

void GraphVerticalCursorManager::OnPageWndClosed( UINT uPageUID )
{
	RemoveVerticalCursor(uPageUID);
}

void GraphVerticalCursorManager::OnUpdateLinePosition( UINT uPageUID, bool bIsMoving )
{
	int nHolderIndex = getCursorHolderIndex(uPageUID);
	if (nHolderIndex < 0)
	{
		ASSERT(FALSE);
		return;
	}

	// Kenny: When the line is moving, if we try to update the labels, some strange refresh problem will happen.
	// This is a temporary solution, please see related Tracker #13620
	if (bIsMoving)
	{
		if (m_pParentWnd)
			m_pParentWnd->SendMessage(WM_USER_UPDATE_CURSOR_POSITION, uPageUID, CURSOR_MOVING);
		return;
	}

	GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(nHolderIndex);

	CStopWatch watch("OnUpdateLinePosition()");

	///Kyle 01/26/2010 SNAP_CURSOR_TO_DATA_PLOT
	//if (m_Setting.m_bSnapLineToData)
	if( IsLineSnappedToData() )
	///End SNAP_CURSOR_TO_DATA_PLOT
	{
		gvcHolder.m_line.GetValidObj().SnapToData();
		///------ Folger 01/28/10 SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
		m_Setting.m_strSnappedPlot = gvcHolder.m_line.GetValidObj().GetLastSnapToPlotDescription();
		///------ End SHOW_SNAP_TO_DATAPLOT_ON_MAIN_DIALOG
	}

	const bool bRet = retrieveHolderCursorInfo(gvcHolder, gvcHolder.m_Settings.bShowTextLabels);

	if (m_pParentWnd)
		m_pParentWnd->SendMessage(WM_USER_UPDATE_CURSOR_POSITION, uPageUID, bIsMoving ? CURSOR_MOVING : CURSOR_MOVED);
}

void GraphVerticalCursorManager::OnUpdateTextLabelsShowState(UINT uPageUID)
{
	if (m_pParentWnd)
		m_pParentWnd->SendMessage(WM_USER_UPDATE_TEXT_LABELS_SHOW_STATE, uPageUID);
}

void GraphVerticalCursorManager::onUpdateTagSelectState( UINT uPageUID, UINT uTagUID, BOOL bSelected )
{
	int nHolderIndex = getCursorHolderIndex(uPageUID);
	if (nHolderIndex < 0)
	{
		ASSERT(FALSE);
		return;
	}

	GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(nHolderIndex);

	const int nTagIndex = gvcHolder.GetTagIndexByUID(uTagUID);
	if ( nTagIndex < 0 )
		return;

	VTagUnit& vTagUnit = gvcHolder.m_arrTagUnits.GetAt(nTagIndex);
	vTagUnit.m_bIsSelected = bSelected;

	if (bSelected)
	{
		if ( gvcHolder.m_nSelectedTagsCount <= 0)
			gvcHolder.m_nSelectedTagsCount = 1;
		else
			++gvcHolder.m_nSelectedTagsCount;
	}
	else
	{
		--gvcHolder.m_nSelectedTagsCount;
	}

	//MY_DBG_OUTPUT1("After: Selected tags count = %d", gvcHolder.m_nSelectedTagsCount);

	///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	//vTagUnit.m_vTagLabel.GetValidObj().Show = bSelected;
	for(int ii = 0; ii < vTagUnit.m_arrTagLabels.GetSize(); ii++)
	{
		VTagLabel& vTagLabel = vTagUnit.m_arrTagLabels.GetAt(ii);
		vTagLabel.GetValidObj().Show = bSelected;
	}
	///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE

	if (m_pParentWnd)
		m_pParentWnd->SendMessage(WM_USER_UPDATE_TAG_SELECT_STATE, uPageUID, nTagIndex);
}

BOOL GraphVerticalCursorManager::removeCursorHolder( UINT uHolderIndex )
{
	UINT uPageUID = 0;
	GraphPage gp;
	BOOL bCleanupOK;
	{
		// We have to put these codes here, for more detail, please refer to #13677
		GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(uHolderIndex);
		uPageUID = gvcHolder.m_uPageUID;
		gp = gvcHolder.GetPage();
		bCleanupOK = cleanupTagsEventHandlerScript( gvcHolder );
	}

	BOOL bRemoveOK = m_arrGraphVerticalCursorHolders.RemoveAt(uHolderIndex);
	ASSERT(bCleanupOK && bRemoveOK);
	if (bRemoveOK)
	{
		if (m_nCurrentTargetHolderIndex == uHolderIndex)
			m_nCurrentTargetHolderIndex = -1;
		else if (m_nCurrentTargetHolderIndex > uHolderIndex)
			--m_nCurrentTargetHolderIndex;

		if (gp.IsValid())
		{
			BOOL bRemovePageBinarayStorage = gp.SetMemory(STR_VERTICAL_CURSOR_BINARY_STORAGE_NAME, NULL);
			ASSERT(bRemovePageBinarayStorage);
		}

		MY_DBG_OUTPUT2("The %dth GVC is removed! Total = %u", (uHolderIndex+1), m_arrGraphVerticalCursorHolders.GetSize() );
		if (m_pParentWnd)
			m_pParentWnd->SendMessage(WM_USER_CURSOR_DELETED, uPageUID);
	}
	return bRemoveOK;
}

BOOL GraphVerticalCursorManager::cleanupTagsEventHandlerScript( GraphVerticalCursorHolder& gvcHolder )
{
	const int nTagLabelSize = gvcHolder.m_arrTagUnits.GetSize();
	for (int ii = 0; ii < nTagLabelSize; ++ii)
	{
		const VTagUnit& vTagUnit = gvcHolder.m_arrTagUnits.GetAt(ii);
		vTagUnit.CleanupEventHandlerScript();
	#ifdef REMOVE_LABEL_FEATURE
		///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
		//vTagUnit.m_vTagLabel.GetValidObj().Show = TRUE;
		for(int jj = 0; jj < vTagUnit.m_arrTagLabels.GetSize(); jj++)
		{
			VTagLabel& vTagLabel = vTagUnit.m_arrTagLabels.GetAt(jj);
			vTagLabel.GetValidObj().Show = TRUE;
		}
		///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	#endif//REMOVE_LABEL_FEATURE
	}
	return TRUE;
}

void GraphVerticalCursorManager::OnTagSelected(UINT uPageUID, UINT uTagUID)
{
	onUpdateTagSelectState(uPageUID, uTagUID, TRUE);
}

void GraphVerticalCursorManager::OnTagUnselected(UINT uPageUID, UINT uTagUID)
{
	onUpdateTagSelectState(uPageUID, uTagUID, FALSE);
}

void GraphVerticalCursorManager::OnTagDeleted(UINT uPageUID, UINT uTagUID)
{
	int nHolderIndex = getCursorHolderIndex(uPageUID);
	if (nHolderIndex < 0)
	{
		ASSERT(FALSE);
		return;
	}
	GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(nHolderIndex);
	const int nTagIndex = gvcHolder.GetTagIndexByUID(uTagUID);

	if ( nTagIndex >= 0 )
		removeTag(gvcHolder, nTagIndex);
}

void GraphVerticalCursorManager::OnTagLabelDeleted( UINT uPageUID, UINT uTagLabelUID )
{
	int nHolderIndex = getCursorHolderIndex(uPageUID);
	if (nHolderIndex < 0)
	{
		ASSERT(FALSE);
		return;
	}
	GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(nHolderIndex);
	const int nTagIndex = gvcHolder.GetTagLabelIndexByUID(uTagLabelUID);

	if ( nTagIndex >= 0 )
		removeTag(gvcHolder, nTagIndex);
}

void GraphVerticalCursorManager::OnTextLabelMoved( UINT uPageUID, UINT uTextLabelUID )
{
	int nHolderIndex = getCursorHolderIndex(uPageUID);
	if (nHolderIndex < 0)
	{
		ASSERT(FALSE);
		return;
	}
	GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(nHolderIndex);
	GraphPage gp = gvcHolder.GetPage();

	foreach ( GraphLayer gl in gp.Layers )
	{
		VGraphLayer vgl(gl);
		Array<VTextLabel&>& arrTextLabels = vgl.AccessTextLabels();
		for ( int ii = 0; ii < arrTextLabels.GetSize(); ++ii )
		{
			// We can NOT reference to the TextLabel here, for more detail, please see #13677
			if (arrTextLabels.GetAt(ii).GetValidObj().IsValid() && uTextLabelUID == arrTextLabels.GetAt(ii).GetValidObj().GetUID() )
			{
				arrTextLabels.GetAt(ii).GetValidObj().LogOffsetToMarkedPos();
				gvcHolder.SaveTextLabelPropertiesToPage(arrTextLabels.GetAt(ii).GetValidObj());
				break;
			}
		}
	}
}

void GraphVerticalCursorManager::OnTextLabelDeleted( UINT uPageUID, UINT uTextLabelUID )
{
	int nHolderIndex = getCursorHolderIndex(uPageUID);
	if (nHolderIndex < 0)
	{
		ASSERT(FALSE);
		return;
	}
	GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(nHolderIndex);

	GraphPage gp = gvcHolder.GetPage();
	GraphLayer	glClearArray;
	
	foreach ( GraphLayer gl in gp.Layers )
	{
		VGraphLayer vgl(gl);
		int		nIndex = -1;
		Array<VTextLabel&>&		arrTextLabels = vgl.AccessTextLabels();
		for ( int ii = 0; ii < arrTextLabels.GetSize(); ++ii )
		{
			// Folger: Referencing to an object within an array may cause run-time error.
			if (arrTextLabels.GetAt(ii).GetValidObj().IsValid() && uTextLabelUID == arrTextLabels.GetAt(ii).GetValidObj().GetUID() )
			{
				nIndex = ii;
				break;
			}
		}
		
		if ( nIndex >= 0 )
		{
			gvcHolder.SaveTextLabelPropertiesToPage(arrTextLabels.GetAt(nIndex).GetValidObj(), TRUE);
			arrTextLabels.RemoveAt(nIndex);
			if ( 0 == arrTextLabels.GetSize() )
				glClearArray = gl;
			break;
		}
	}
	
	if ( glClearArray )
	{
		VGraphLayer vgl(glClearArray);
		vgl.RemoveTextLabels();
	}
}

#ifdef _DEBUG
BOOL GraphVerticalCursorManager::GetProperties( TreeNode& tnProperties )
{
	if (tnProperties.IsValid())
	{
		tnProperties += m_Setting;
	
		TreeNode tnHolders = tree_check_get_node(tnProperties, STR_OBJ_TAG_NAME_VHOLDERS);
	
		const int nHolderSize = m_arrGraphVerticalCursorHolders.GetSize();
		for (int ii = 0; ii < nHolderSize; ++ii)
		{
			GraphVerticalCursorHolder& holder = m_arrGraphVerticalCursorHolders.GetAt(ii);
			const string strNodeName = STR_OBJ_TAG_NAME_VHOLDER_PREFIX + STR_OBJ_DELIMITER + (ii+1);
			TreeNode tnHolder = tree_check_get_node(tnHolders, strNodeName);
			BOOL bRet = holder.GetProperties(tnHolder);
			ASSERT(bRet);
		}
		return TRUE;
	}
	return FALSE;
}
#endif // _DEBUG

UINT GraphVerticalCursorManager::getActualPageUID( UINT uPageUID /*= 0*/ )
{
	if (0 == uPageUID)
	{
		GraphLayer glActive = Project.ActiveLayer();
		if (glActive)
		{
			GraphPage gp = glActive.GetPage();
			if (gp)
			{
				uPageUID = gp.GetUID(TRUE);
				ASSERT(uPageUID > 0);
			}
		}
	}
	return uPageUID;
}

int GraphVerticalCursorManager::getCursorHolderIndex( UINT uPageUID )
{
	const int nHolderSize = m_arrGraphVerticalCursorHolders.GetSize();
	for (int ii = 0; ii < nHolderSize; ++ii)
	{
		const GraphVerticalCursorHolder& gvcHolder = m_arrGraphVerticalCursorHolders.GetAt(ii);
		if (uPageUID == gvcHolder.m_uPageUID)
			return ii;
	}
	return -1;
}
///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE	
VTagUnit* GraphVerticalCursorManager::addTag( GraphVerticalCursorHolder& gvcHolder, const GraphLayer& gl, double dXScaleMark)
{
	VTagUnit* pvTagUnit = gvcHolder.AddTagUnit(gl, dXScaleMark, m_Setting.m_labelSetting.strLTCustomTextType);
	if ( !pvTagUnit )
	{
		ASSERT(FALSE);
		return NULL;
	}

	MY_DBG_OUTPUT2("Adding tag %s  (%g, 0)", pvTagUnit->m_vTag.GetValidObj().GetRangeString(), dXScaleMark);
	return pvTagUnit;
}

int GraphVerticalCursorManager::addTagDone(GraphVerticalCursorHolder& gvcHolder, const VTagUnit& vTagUnit)
{
	gvcHolder.SaveTagUnitPropertiesToPage(vTagUnit);
///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	const int nTagIndex = gvcHolder.m_arrTagUnits.GetSize()-1;

	if (m_pParentWnd)
		m_pParentWnd->SendMessage(WM_USER_TAG_ADDED, gvcHolder.m_uPageUID, nTagIndex);

	return nTagIndex;
}

int GraphVerticalCursorManager::removeTag( GraphVerticalCursorHolder& gvcHolder, UINT uTagIndex )
{
	if (uTagIndex >= gvcHolder.m_arrTagUnits.GetSize())
	{
		ASSERT(FALSE);
		return -1;
	}

	gvcHolder.SaveTagUnitPropertiesToPage( gvcHolder.m_arrTagUnits.GetAt(uTagIndex), TRUE );

	// Kenny: we can't declare an reference to TagUnit here unless we use it in a different scope to RemoveAt statement.
	// For more detail, please see #13677

	UINT uTagUID;
	{
		VTag& vTag = gvcHolder.m_arrTagUnits.GetAt(uTagIndex).m_vTag;
		uTagUID = vTag.GetValidObj().GetUID();
	}

	// Kenny: I have no idea why the following statement will lead to crash, temporary solution
	//if ( gvcHolder.m_arrTagUnits.GetAt(uTagIndex).Destroy() && gvcHolder.m_arrTagUnits.RemoveAt(uTagIndex) )

	bool bDestroy = gvcHolder.m_arrTagUnits.GetAt(uTagIndex).Destroy();
	if ( bDestroy && gvcHolder.m_arrTagUnits.RemoveAt(uTagIndex) )
	{
		if (m_pParentWnd)
		{
			m_pParentWnd->SendMessage(WM_USER_TAG_DELETED, gvcHolder.m_uPageUID, uTagUID);
		}
		return 0;
	}
	ASSERT(FALSE);
	return -2;
}

//int GraphVerticalCursorManager::getAllTagsPosAndPlotInfo( const GraphVerticalCursorHolder& gvcHolder, vector<int>& vnXCenter, vector<int>& vnYCenter /*= NULL*/, vector<UINT>& vnTagPlotUIDMap /*= NULL */ )
int GraphVerticalCursorManager::getAllTagsPosAndPlotInfo( const GraphVerticalCursorHolder& gvcHolder, vector<int>& vnXCenter)
{
	const int nTagSize = gvcHolder.m_arrTagUnits.GetSize();
	vnXCenter.SetSize(nTagSize);
	///Jasmine 12/11/09 QA81-8980 CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE
	/*
	if (vnYCenter)
		vnYCenter.SetSize(nTagSize);
	if (vnTagPlotUIDMap)
		vnTagPlotUIDMap.SetSize(nTagSize);
	for (int ii = 0; ii < nTagSize; ++ii)
	{
		const VTagUnit& vTagUnit = gvcHolder.m_arrTagUnits.GetAt(ii);
		vnXCenter[ii]		= vTagUnit.m_vTag.GetValidObj().GetCenterPageX();
		if (vnYCenter)
			vnYCenter[ii]	= vTagUnit.m_vTag.GetValidObj().GetCenterPageY();
		if (vnTagPlotUIDMap)
			vnTagPlotUIDMap[ii]	= vTagUnit.m_vTagLabel.GetAttachedPlotUID();
	}*/
	for (int ii = 0; ii < nTagSize; ++ii)
	{
		const VTagUnit& vTagUnit = gvcHolder.m_arrTagUnits.GetAt(ii);
		vnXCenter[ii]		= vTagUnit.m_vTag.GetValidObj().GetCenterPageX();
	}
	///End CHANGE_TAG_LINE_TO_ACROSS_WHOLE_PAGE

	return nTagSize;
}

int GraphVerticalCursorManager::getTagsInSamePageX( const GraphVerticalCursorHolder& gvcHolder, int nPageX, vector<UINT>& vnTagIndices )
{
	vector<int>		vnTagXCenter;

	getAllTagsPosAndPlotInfo(gvcHolder, vnTagXCenter);
	int nRet = vnTagXCenter.Find(MATREPL_TEST_EQUAL, nPageX, vnTagIndices);
	return nRet;
}

///Jasmine 12/08/09 QA81-8980 MORE_CONFIGURATION_FOR_INFO_TABLE
static bool _get_notaion_by_type(const DataPlot& dp, bool bGetX, string& strBook, string& strSheet, string& strCol, string& strLongname)
{	
	Datasheet		ds;
	OriginObject 	obj;
	XYRange 		xy;
	if( !dp.GetDataRange(xy) )
		return false;
	
	string strXData;
	xy.GetDatasetNames(strCol, strXData);//in case loose dataset
	///Jasmine 01/26/10 OUTPUT_X_COLUMN_NEED_MORE_INFO
	if(bGetX)
	{
		strCol = strXData;
		Column xcol;
		if( !xy.GetXColumn(xcol) )
			return false;
		xcol.GetParent(ds);
		obj = xcol;
	}
	///End OUTPUT_X_COLUMN_NEED_MORE_INFO
	else if( !get_main_dataobj_from_datarange(xy, ds, obj) )
		return false;
	
	strSheet = ds.GetName();
	
	Page pg = ds.GetPage();
	int nType = pg.GetType();
	strBook = pg.GetName();
	
	switch(nType)
	{
	case EXIST_WKS:
		Column col;
		col = obj;
		if(col)
		{
			strCol = col.GetName();
			strLongname = col.GetLongName();
		}
		break;
	case EXIST_MATRIX:
		MatrixObject mo;
		mo = obj;
		if(mo)
		{
			strCol = mo.GetName();
			strLongname = mo.GetLongName();
		}
		break;
	default:
		ASSERT(0);
		break;
	}
	
	return true;
}
///End MORE_CONFIGURATION_FOR_INFO_TABLE

///Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT
static bool _get_xcol_format(const DataPlot& dp, int& nFormat, int& nDisplay, string& strCustom)
{
	nFormat = nDisplay = -1;
	strCustom = "";
	string strFormat;
	
	Column xcol;
	XYRange xy;
	if( !dp.GetDataRange(xy) || !xy.GetXColumn(xcol) )
		return false;
	
	Tree trColFmt;
	trColFmt = xcol.GetFormat(FPB_ALL, FOB_ALL, true, true);
	if(trColFmt.Root.Format)
	{
		nFormat = trColFmt.Root.Format.nVal;
		switch(nFormat)
		{
		case OKCOLTYPE_TIME:
		case OKCOLTYPE_DATE:
		case OKCOLTYPE_MONTH:
		case OKCOLTYPE_WEEKDAY:
			nDisplay = trColFmt.Root.Display.nVal;
			
			strFormat.Format("%d|%d", nFormat, nDisplay);
			
			if(OKCOLTYPE_DATE == nFormat && LDF_OBJ_CUSTOM == nDisplay)
				strCustom = trColFmt.Root.CustomFormat.strVal;
			break;
		}
	}
	
	return !strFormat.IsEmpty();
}
///End SHOW_XVALUE_WITH_FORMAT

///Jasmine 01/22/10 OUTPUT_WKS_XCOL_LNAME_USE_AXIS_TITLE
string _get_layer_xaxis_title(const GraphLayer& gl)
{
	string strTitle;
	
	GraphObject go;
	AxisObject ao = gl.XAxis.AxisObjects(AXISOBJPOS_AXIS_FIRST);
	if(ao)
		go = ao.GetTitleObject();
	
	if(!go)
		return strTitle;
	
	string strVar = "_tempTitle";
	gl.LT_execute(strVar+"$="+go.Text);
	
	char	szBuffer[MAXLINE];
	LT_get_str(strVar, szBuffer, MAXLINE);
	strTitle = szBuffer;
	
	LT_execute("del -vs "+strVar);
	
	return strTitle;
}
///End OUTPUT_WKS_XCOL_LNAME_USE_AXIS_TITLE
bool GraphVerticalCursorManager::retrieveHolderCursorInfo( GraphVerticalCursorHolder& gvcHolder, bool bUpdateTextLabels /*= true*/, bool bUpdateNotation/* = true*/ )
{
	CStopWatch watch("retrieveHolderCursorInfo()");

	///------ Folger 01/22/10 CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	//const int nLinePageCoord = gvcHolder.m_line.GetValidObj().GetPagePosition();
	double rLinePageCoord = NANUM;
	double rLineScale = NANUM;
	if ( IsLineSnappedToData() )
	{
		WorldToView(rLinePageCoord, gvcHolder.GetSnapToX());
	}
	else if ( m_pParentWnd && m_pParentWnd->SendMessage(WM_USER_QUERY_POSITION_CHANGE_BY_EDITING, (DWORD)(void*)&rLineScale) )
	{
		WorldToView(rLinePageCoord, rLineScale);
	}
	else
	{
		rLinePageCoord = gvcHolder.m_line.GetValidObj().GetPagePosition();
	}
	///------ End CURSOR_X_COORDINATE_LOSE_PRECISION_IN_INFO_TABLE
	GraphPage gp = gvcHolder.GetPage();
	if (!gp)
	{
		ASSERT(FALSE);
		return false;
	}

	if (m_pParentWnd)
		m_pParentWnd->SendMessage(WM_USER_UPDATE_CURSOR_INTERSECT_POINT_BEGIN, gvcHolder.m_uPageUID);
	///Jasmine 01/15/10 REORDER_CURSOR_VALUE_BY_LAYER_POS	
	for(int ii = m_arrPoints.GetSize()-1; ii >= 0; ii--)
		m_arrPoints.RemoveAt(ii);
	///End REORDER_CURSOR_VALUE_BY_LAYER_POS

	
	///Jasmine 01/26/10 OUTPUT_X_COLUMN_NEED_MORE_INFO
	//since there's one X column in output sheet, we can only get info from one dataplot
	GraphLayer 	glToGetXInfo;
	DataPlot 	dpToGetXInfo;
	if( IsLineSnappedToData(&dpToGetXInfo) && dpToGetXInfo )
		dpToGetXInfo.GetParent(glToGetXInfo);
	else
	{
		glToGetXInfo = gp.Layers(0);
		dpToGetXInfo = glToGetXInfo.DataPlots(0);
	}
	
	if ( !dpToGetXInfo )
		return true;

	//string strXInfo = _get_layer_xaxis_title(glToGetXInfo);	///Jasmine 01/22/10 OUTPUT_WKS_XCOL_LNAME_USE_AXIS_TITLE
	string strXBook, strXSheet, strXCol, strXLongname;
	_get_notaion_by_type(dpToGetXInfo, true, strXBook, strXSheet, strXCol, strXLongname);
	uint uidplotx = dpToGetXInfo.GetUID(TRUE);
	///Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT
	int nFormat, nDisplay;
	string strCustom;
	_get_xcol_format(dpToGetXInfo, nFormat, nDisplay, strCustom);
	///End SHOW_XVALUE_WITH_FORMAT
	///End OUTPUT_X_COLUMN_NEED_MORE_INFO
	
	
	int nUpdateLabelCount = 0;
	foreach(GraphLayer gl in gp.Layers)
	{		
		VGraphLayer	vgl(gl);
		double		dXScale;
		vgl.PageToScaleCoordinate(rLinePageCoord, dXScale);

		Array<VTextLabel&>&		arrTextLabels	= vgl.AccessTextLabels();
		const int 				nLabelSize		= arrTextLabels.GetSize();

		int		nUpdateLabelCountPerLayer = 0;

		foreach (DataPlot dp in gl.DataPlots)
		{
			///Jasmine 12/08/09 QA81-8980 MORE_CONFIGURATION_FOR_INFO_TABLE
			string strBook, strSheet, strCol, strLongname;
			if(bUpdateNotation)
				_get_notaion_by_type(dp, false, strBook, strSheet, strCol, strLongname);
			///End MORE_CONFIGURATION_FOR_INFO_TABLE
			///Jasmine 01/15/10 REORDER_CURSOR_VALUE_BY_LAYER_POS
			int layPosX, layPosY;			
			if( !dp.GetAveragePageCoordinates(&layPosY, &layPosX) )
				layPosX = layPosY = -1;
			///End REORDER_CURSOR_VALUE_BY_LAYER_POS

			vector vY;
			VDataPlot vdp(dp);
			vector<BOOL> vbIsInterpolated;
			vector<int> vnIndex;	///Jasmine 01/20/10 SHOW_NEAREST_POINT
			vdp.GetYValues(dXScale, vY, vbIsInterpolated, vnIndex);

			const int nYValSize = vY.GetSize();

			for (int ii = 0; ii < nYValSize; ++ii)
			{
				if ( bUpdateTextLabels )
				{
					if ( nUpdateLabelCountPerLayer >= nLabelSize )
					{
						VTextLabel* pVTextLabel = new VTextLabel;
						if ( !pVTextLabel )
						{
							ASSERT(FALSE);
							continue;
						}
						if ( !pVTextLabel->Create(gl, dXScale, vY[ii]) )
						{
							delete pVTextLabel;
							ASSERT(FALSE);
							continue;
						}

						string strLTScript;
						strLTScript.Format(STR_OBJ_EVENT_LT_SCRIPT_FORMAT, VCOT_TEXT_LABEL);
						pVTextLabel->SetLabTalkScriptEvent(strLTScript);
					#ifdef REMOVE_LABEL_FEATURE
						pVTextLabel->Show = FALSE;
					#else
						pVTextLabel->Show = gvcHolder.m_Settings.bShowTextLabels;
					#endif
						pVTextLabel->AttachToPlot(dp);
						pVTextLabel->GetValidObj().SetTextFormat(m_Setting.m_labelSetting.strLTCustomTextType);

						arrTextLabels.Add(*pVTextLabel);

						gvcHolder.SaveTextLabelPropertiesToPage(*pVTextLabel);

						//MY_DBG_OUTPUT3("Updating TextLabel %s  (%g, %g)", pVTextLabel->GetRangeString(), dXScale, vY[ii]);
					}
					else
					{
						VTextLabel& vTextLabel = arrTextLabels.GetAt(nUpdateLabelCountPerLayer);
					#ifdef REMOVE_LABEL_FEATURE
						vTextLabel.GetValidObj().Show = FALSE;
					#else
						vTextLabel.GetValidObj().Show = gvcHolder.m_Settings.bShowTextLabels;
					#endif
						vTextLabel.GetValidObj().AttachToPlot(dp);
						vTextLabel.GetValidObj().MarkTo(dXScale, vY[ii]);
						
						gvcHolder.SaveTextLabelPropertiesToPage(vTextLabel);

						//MY_DBG_OUTPUT3("Updating TextLabel %s  (%g, %g)", vTextLabel.GetValidObj().GetRangeString(), dXScale, vY[ii]);
					}
					++nUpdateLabelCount;
					++nUpdateLabelCountPerLayer;
				}

				{///Jasmine 01/15/10 REORDER_CURSOR_VALUE_BY_LAYER_POS	
					VIntersectPoint* pti = new VIntersectPoint;
					
					pti->fpPoint.x = dXScale;
					pti->fpPoint.y = vY[ii];
					///Jasmine 12/08/09 QA81-8980 MORE_CONFIGURATION_FOR_INFO_TABLE	
					
					///Jasmine 01/20/10 SHOW_NEAREST_POINT
					int nIndex = vnIndex[ii];
					double xx = pti->fpPoint.x, yy = pti->fpPoint.y;
					if(nIndex == -1)
						xx = yy = NANUM;
					else if( vbIsInterpolated[ii] )
						dp.GetDataPoint(nIndex, &xx, &yy);
					pti->fpNearPoint.x = xx;
					pti->fpNearPoint.y = yy;
							
					nIndex++;//offset
					pti->index = nIndex;
					///End SHOW_NEAREST_POINT
					pti->plotuid = dp.GetUID(TRUE);
					///End MORE_CONFIGURATION_FOR_INFO_TABLE
					
					///Jasmine 01/15/10 REORDER_CURSOR_VALUE_BY_LAYER_POS
					pti->layerYPos = layPosY;	
					pti->layer = gl.GetIndex();
					pti->plot = dp.GetIndex();
					///End REORDER_CURSOR_VALUE_BY_LAYER_POS
					
					pti->xinfo.plotuid = uidplotx;///Jasmine 01/26/10 OUTPUT_X_COLUMN_NEED_MORE_INFO
					///Jasmine 01/20/10 SHOW_XVALUE_WITH_FORMAT
					pti->xinfo.format = nFormat;	
					pti->xinfo.display = nDisplay;	
					pti->xinfo.custom = strCustom;	
					///End SHOW_XVALUE_WITH_FORMAT
										
					//pti.strPlotName = dp.GetName();
					if(bUpdateNotation)
					{
						///Jasmine 01/18/10 SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
						pti->plotinfo.book = strBook;
						pti->plotinfo.sheet = strSheet;
						pti->plotinfo.colname = strCol;
						pti->plotinfo.collongname = strLongname;
						///End SHOW_MORE_THAN_ONE_PLOT_INFO_AT_A_TIME
						
						///Jasmine 01/26/10 OUTPUT_X_COLUMN_NEED_MORE_INFO
						pti->xinfo.plotinfo.book = strXBook;
						pti->xinfo.plotinfo.sheet = strXSheet;
						pti->xinfo.plotinfo.colname = strXCol;
						pti->xinfo.plotinfo.collongname = strXLongname;
						///End OUTPUT_X_COLUMN_NEED_MORE_INFO
					}
					
					m_arrPoints.Add(*pti);					
				}///End REORDER_CURSOR_VALUE_BY_LAYER_POS
			}
		}

		if ( bUpdateTextLabels )
		{
			// We just simply hide the extra VTextLabels here to avoid frequently creating and/or destroying them.
			for ( int nn = nUpdateLabelCountPerLayer; nn<arrTextLabels.GetSize(); ++nn )
			{
				VTextLabel& vTextLabel = arrTextLabels.GetAt(nUpdateLabelCount);
				vTextLabel.GetValidObj().Show = FALSE;
			}
		}
	}
	if (m_pParentWnd)
		m_pParentWnd->SendMessage(WM_USER_NEW_CURSOR_INTERSECT_POINT, gvcHolder.m_uPageUID, 0);
			
	return true;
}

/*----------------------------------------------------------------------------*/
/* Interface to get instance of GraphVerticalCursorManager
/*----------------------------------------------------------------------------*/

GraphVerticalCursorManager& get_graph_vertical_cursor_manager()
{
	static GraphVerticalCursorManager s_Manager;
	return s_Manager;
}

#endif // __VERTICAL_CURSOR_MANAGER_H__
