/*------------------------------------------------------------------------------*
 * File Name: PickPeak.cpp	 													*
 * Creation: 																	*
 * Purpose: OriginC Source C file												*
 * Copyright (c) ABCD Corp.	2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010		*
 * All Rights Reserved															*
 * 																				*
 * Modification Log:															*
 *	Folger 03/18/10 QA81-15209 IMPROVE_PA_FIND_PEAKS_FOR_NONE_BASELINE			*
 *------------------------------------------------------------------------------*/
 
#include <Origin.h>
#include "DialogEx.h"
#define		BASE_WINDOW		ResizeDialog
#define		DYNA_TAB_ID		IDC_PICK_PEAK_TAB
#include "dynadlg.h"
#include "PickPeak.h"

#include "curve_utils.h"
#include "grobj_utils.h"
#include "GraphObjTools.h"
#include "graph_utils.h"

bool OpenPickPeakPreferDlg(TreeNode& trSettings, bool& bBaselineChanged)
{
	GETN_FIRST_SUBNODES_AS_TABS(trSettings);
	PickPeakPreferDlg dlg(trSettings);
	if( IDOK == dlg.DoModalEx() )
	{
		Tree trTmpBaseline;
		trTmpBaseline.Replace(trSettings.baseline);

		trSettings = dlg.GetSettings();
		bBaselineChanged = _check_if_baseline_setting_changed(trTmpBaseline, trSettings.baseline);

		return true;
	}

	return false;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// class PickPeakPreferDlg /////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////

#define MIN_TAB_WIDTH		450
#define MIN_TAB_HEIGHT		300

#define STR_PICK_PEAK_PREFER_DLG		"Pick Peaks Prefreences"

class PickPeakPreferDlg : public DynaDlg
{
public:
	PickPeakPreferDlg(TreeNode& trSettings) : DynaDlg(IDD_PICK_PEAK_PREFERENCE, trSettings, "ODlg8")
	{
	}

	int		DoModalEx(HWND hWndParent = NULL)
	{
		InitMsgMap();
		int nRet = ResizeDialog::DoModal(hWndParent);
		return nRet;
	}
	
	TreeNode GetSettings()
	{
		return GetTree();
	}

protected:

EVENTS_BEGIN
	ON_INIT(OnInitDialog)
	ON_DESTROY(OnDestroy)
	ON_SIZE(OnDlgResize)
	ON_USER_MSG(WM_USER_DYNACTRL_EXPAND_COLLAPSE, OnAfterExpandCollapse)
	ON_GETNDLG_MSGS(IDC_PICK_PEAK_DYNA)
	
EVENTS_END

	BOOL OnInitDialog()
	{
		ResizeDialog::OnInitDialog();
		InitDynaControl(IDC_PICK_PEAK_DYNA);
	
		InitTabControl();
		UpdateDynaControl(true, GETNEVENT_ON_INIT, true, 0, true, GetDisplaySubnodeInitialIndex());

		return false;
	}
	
	BOOL OnDestroy()
	{
		SaveSetting("CurrentSelTab", m_tab.GetCurSel(), STR_PICK_PEAK_PREFER_DLG);
		return ResizeDialog::OnDestroy();
	}

	BOOL OnDlgResize(int nType, int cx, int cy)
	{
		resizeDlgToFit(cx, cy);
		return TRUE;
	}

	//virtual
	int GetDisplaySubnodeInitialIndex()
	{
		return LoadSetting("CurrentSelTab", 0, STR_PICK_PEAK_PREFER_DLG);
	}
	
private:
	//virtual
	void  resizeDlgToFit(int cx = 0, int cy = 0)
	{
		SIZE	szDynaCtrl;
		TREE_CTRL_VAR.GetOptimalSize(szDynaCtrl);
	
		RECT rrTab, rrDyna, rrBottomBtn;
		rrDyna.right = szDynaCtrl.cx;
		rrDyna.bottom = szDynaCtrl.cy;
		DYNA_TAB_CNTRL.AdjustRect(TRUE, &rrDyna);
	
		SIZE	szTab;
		szTab.cx = max(RECT_WIDTH(rrDyna), MIN_TAB_WIDTH);
		szTab.cy = max(RECT_HEIGHT(rrDyna), MIN_TAB_HEIGHT);
	
		int nGap = GetControlGap();
	
		rrTab.left = rrTab.top = nGap;
		rrTab.right = rrTab.left + szTab.cx;
		rrTab.bottom = rrTab.top + szTab.cy;
		MoveControl(DYNA_TAB_CNTRL, rrTab);
	
		rrDyna = rrTab;
		DYNA_TAB_CNTRL.AdjustRect(FALSE, &rrDyna);
		MoveControl(TREE_CTRL_VAR, rrDyna);
	
		RECT rrCancle;
		Control ctrlCancle;
		GetControlClientRect(IDCANCEL, rrCancle, &ctrlCancle);
		Control ctrlOK = GetItem(IDOK);
	
		int nBtnWidth = RECT_WIDTH(rrCancle);
		int nBtnHeight = RECT_HEIGHT(rrCancle);
	
		rrBottomBtn.right = rrTab.right;
		rrBottomBtn.left = rrBottomBtn.right - nBtnWidth;
		rrBottomBtn.top = rrTab.bottom + nGap;
		rrBottomBtn.bottom = rrBottomBtn.top + nBtnHeight;
		MoveControl(ctrlCancle, rrBottomBtn);
		
		rrBottomBtn.right = rrBottomBtn.left - nGap;
		rrBottomBtn.left = rrBottomBtn.right - nBtnWidth;
		MoveControl(ctrlOK, rrBottomBtn);
	
		// resize dlg
		int nClientWidth = szTab.cx + 2 * nGap;
		int nClientHeight = szTab.cy + nBtnHeight + 3 * nGap;
	
		RECT rrClient;
		GetClientRect(&rrClient);
		rrClient.right = rrClient.left + nClientWidth;
		rrClient.bottom = rrClient.top + nClientHeight;
		ClientToScreen(&rrClient);
	
		RECT rtDlg;
		GetWindowRect(&rtDlg);
		rtDlg.right = rrClient.right;
		rtDlg.bottom = rrClient.bottom;
		MoveWindow(&rtDlg, true);
	
		InvalidateRect(GetSafeHwnd(), NULL, TRUE);
	}
private:
};


///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// class PickPeakTool //////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////

#define XFNAME					"addtool_pickpeaks"
#define	TOOLNAME				_L("Gadget Pick Peak")
#define	TOOL_PREFERENCES_TITLE	_L("Pick Peak Preferences")

static	int*	_get_xf_treenode_evt_handle_state()
{
	static int nUpdated = 0;
	return &nUpdated;
}

enum
{
	PICK_PEAK_REPORT_COL_DATASET,

	PICK_PEAK_REPORT_COL_PEAK_X,
	PICK_PEAK_REPORT_COL_PEAK_Y,
	PICK_PEAK_REPORT_COL_HEIGHT,			// height from baseline
	PICK_PEAK_REPORT_COL_BASELINE,			// put the baseline data to a note and insert it to this column
	
	PICK_PEAK_REPORT_TOTAL_COL,
};

enum
{
	//PICK_PEAK_CMD_OUTPUT_BASELINE_DATA			= GOT_CMD_CUSTOM,
	PICK_PEAK_CMD_SUBTRACT_BASELINE					= GOT_CMD_CUSTOM,
};

enum {
	PICK_PEAK_TOOL_OBJ_FIRST = GOT_OBJ_TOTAL,
	PICK_PEAK_TOOL_OBJ_BASELINE,
};

#define STR_DATASETOBJ_PICK_PEAK			"PickPeakResult"

class PickPeakTool : public GraphObjCurveTool
{
public:
	PickPeakTool(LPCSTR lpcszMainObjName = NULL) : GraphObjCurveTool(lpcszMainObjName)
	{
		m_vnObjIDs.Add(PICK_PEAK_TOOL_OBJ_BASELINE);
		m_vsObjNames.Add("BaselineCurve");

		GraphLayer gl = Project.ActiveLayer();
		if( gl )
		{
			GraphPage gp = gl.GetPage();
			m_strPreviewDataWksName = "Tmp" + gp.GetName() + gl.GetIndex();
		}
		else
		{
			m_strPreviewDataWksName = "TmpPPrev"
		}
		
		m_strPreviewDataWksLongName = "Pick Peak Data";
	}
	
	bool ApplySettings(TreeNode& trGUI = NULL)
	{
		if( trGUI )
			SetGUITree(trGUI);

		UpdateROIGUI();
		updatePeakLabels(m_wks, true);
		
		return true;
	}

protected:
	//virtual 
	BOOL OnMove()
	{
		if(!OnPreMove()) return false;

		if(!GetData())
		{
			ConnectToTempPreviewDataAndGraph();
			if( m_wks )
				m_wks.SetSize(0, -1);
			if( m_gl )
			{
				GraphPage gp = m_gl.GetPage();
				gp.Refresh();
			}
			if( m_goBaseline ) 
				m_goBaseline.Show(false);

			return true; // no data, nothing to do, not an error
		}
		UpdateTopLabel("No Message", false);

		if( CheckPreviewDataWks() > 0 )
		{
			m_wks.SetSize(-1, 2);
			m_wks.Columns(1).SetComments("Peak for Preview");
		}

		vector vxPeaks, vyPeaks;
		if( findAllPlotsPeaks(vxPeaks, vyPeaks) )
		{
			Dataset dsX(m_wks, 0);
			dsX = vxPeaks;

			Dataset dsY(m_wks, 1);
			dsY = vyPeaks;
		}
		updateBaselinePrev();
		updatePeakLabels(m_wks, true);

		//if ( QUERY_ROI_TOOL_OPTION(ROI_OPTION_ON_MOVE_EVENT) )		expand to full range should also update the gui
		{
			Tree trGUI;
			if( GetGUITree(trGUI) )
			{
				Tree tr;
				tr = m_go.GetFormat(FPB_DATA, FOB_SPECIAL, true, true);
				
				vector vx;
				vx = tr.Root.Data.X.dVals;
				if( vx.GetSize() > 2 )
				{
					trGUI.roi.XScale.leftx.strVal = get_value_by_format(m_dp, vx[0]);
					trGUI.roi.XScale.rightx.strVal = get_value_by_format(m_dp, vx[1]);
					SetGUITree(trGUI);
				}
				else
				{
					ASSERT(false);
				}
			}
		}

		return GraphObjCurveTool::OnMove();
	}

	//virtual
	bool 	DoOutput(bool bUpdateLastOutput)
	{
		vector vx, vy;
		if(!getDataRange(vx, vy))
			return false;

		//if( bUpdateLastOutput )			// update peaks points and baseline
		//{
		//	OutputBaselineData();
		//}
		OutputPeaksValues(bUpdateLastOutput);

		return true;
	}

	bool	OutputPeaksValues(bool bReplace)
	{
		if( !findAllPlotsPeaks(NULL, NULL, true, bReplace) )
			return false;

		return true;
	}

	/*
	bool	OutputBaselineData()
	{
		Worksheet wksReport;
		if( !PrepareReportWks(wksReport) )
		{
			error_report("Invalid Report Worksheet!");
			return false;
		}
		GetData();
		vector vxb, vyb;
		bool bBaselineShared;
		CreateBaseline(vxb, vyb, bBaselineShared, m_dp);

		Dataset dsX(wksReport, PICK_PEAK_REPORT_COL_BASELINE_X);
		dsX = vxb;

		Dataset dsY(wksReport, PICK_PEAK_REPORT_COL_BASELINE_Y);
		dsY = vyb;

		return true;
	}
	*/

	bool	CreateBaseline(vector& vxb, vector& vyb, bool& bGlobalGL, const DataPlot& dpSrc, BaselineInfo* pBLInfo)
	{
		Tree trGUI;
		if ( !GetGUITree(trGUI) )
			return FALSE;
		GetData();
		
		double dROIFrom, dROITo;
		if( !GetMainObjectXPosition(dROIFrom, dROITo) )
			return false;

		if( !_create_baseline(trGUI.baseline, vxb, vyb, bGlobalGL, dROIFrom, dROITo, m_gl.X.From, m_gl.X.To, GetMultiPlotsState(), dpSrc, pBLInfo) )
		{
			vxb.SetSize(0);
			vyb.SetSize(0);
			return false;
		}
		
		return true;
	}

	bool	SubtractBaseline()
	{
		bool	bSubtracted = false;
		if( GetMultiPlotsState() )
		{
			m_bGlobalBL = false;
			int nSubtractCount = 0;
			foreach(DataPlot dp in m_gl.DataPlots)
			{
				if( isPlotfiltered(dp) )
					continue;

				_peak_gadget_baseline_subtracted_status(dp, bSubtracted);
				if( bSubtracted )
					continue;

				if( !m_bGlobalBL )
					prepareBaseline(dp, NULL);

				if( SubtrctBaseline(dp, m_vxBaseline, m_vyBaseline) )
				{
					nSubtractCount++;
					bSubtracted = true;
					_peak_gadget_baseline_subtracted_status(dp, bSubtracted, false);
				}
			}

			if( nSubtractCount < 1 )
				return false;
		}
		else
		{
			if(!GetData())
			{
				error_report("GetData() form Grobj error!");
				return false;
			}
			ASSERT(m_dp);

			_peak_gadget_baseline_subtracted_status(m_dp, bSubtracted);
			if( bSubtracted )
				return false;

			prepareBaseline(m_dp, NULL);
			if( SubtrctBaseline(m_dp, m_vxBaseline, m_vyBaseline) )
			{
				bSubtracted = true;
				_peak_gadget_baseline_subtracted_status(m_dp, bSubtracted, false);
			}
		}
		return true;
	}

	bool	SubtrctBaseline(DataPlot& dp, const vector& vxb, const vector& vyb)
	{
		ASSERT( dp );

		XYRange dr;
		if( !dp.GetDataRange(dr) || !dr )
			return false;

		vector vx, vy;
		if( !dr.GetData(vy, vx) )
			return false;
		subtract_baseline(vx, vy, vxb, vyb);

		return dr.SetData(&vy, NULL);
	}
	
	//virtual
	bool	IsPlotForROI(const DataPlot& dp)
	{
		return is_plot_for_ROI(dp, m_go, STR_DATASETOBJ_PICK_PEAK);
	}

	//virtual
	int CreateAttachments()
	{
		int nRet = GraphObjCurveTool::CreateAttachments();
		if ( 0 != nRet )
			return nRet;

		roi_tool_option_access(ROI_OPTION_RESET_LABEL_POS, ROI_OP_SET);
		GraphObject go;
		if ( 0 > CreateAttachment(go, PICK_PEAK_TOOL_OBJ_BASELINE, CTP_FREE_1, GROBJ_TN_POLYLINE) )
			return -1;
		set_go_selectable(go, false); //forbit moving polyline object.
		set_go_color(go, SYSCOLOR_RED);

		return 0;
	}

	//virtual
	int	GetAttachments()
	{
		int nRet = GraphObjCurveTool::GetAttachments();
		if ( 0 != nRet )
			return nRet;
		
		return GetAttachment(m_goBaseline, PICK_PEAK_TOOL_OBJ_BASELINE);
	}
	
	//virtual
	bool	SetMultiPlotsState(bool bOn = true)
	{
		if( !IsSupportMultiPlots() )
			return false;

		if( bOn != GetMultiPlotsState() )
		{
			// dirty code, show/hide refer data
			Tree	trGUI;
			GetGUITree(trGUI);
			baseline_creation_multi_sel_attrib_access(trGUI.baseline, false, bOn);
			SetGUITree(trGUI);

			GraphObjCurveTool::SetMultiPlotsState(bOn);
		}
		return true;
	}

	//virtual
	void	UpdateGUIOnThemeChange(TreeNode& trGUI)
	{
		baseline_creation_multi_sel_attrib_access(trGUI.baseline, false, GetMultiPlotsState());

		check_update_roi_x_position(trGUI.roi.XScale);

		GraphObjCurveTool::UpdateGUIOnThemeChange(trGUI);
	}

	//virtual
	BOOL	IsExpandable()
	{
		Tree	trGUI;
		GetGUITree(trGUI);
		TreeNode	trFixed = trGUI.roi.XScale.fixscale;
		return !(trFixed && trFixed.nVal);
	}

	//virtual
	bool CheckGetMainObjectPositionFromGUITree(const TreeNode& trGUI, double& x0, double& x1, bool& bFixWidth, double& y0, double& y1)
	{
		TreeNode trXScale = trGUI.roi.XScale;
		bFixWidth = trXScale && trXScale.fixscale && trXScale.fixscale.nVal;

		TreeNode trXFrom = trXScale.leftx;
		TreeNode trXTo = trXScale.rightx;

		if( !trXFrom || !trXTo )
		{
			ASSERT(false);
			return false;
		}
		
		x0 = get_value_by_format(m_dp, trXFrom.strVal);
		x1 = get_value_by_format(m_dp, trXTo.strVal);

		return true;
	}

	//virtual
	TreeNode	GetFillColorNode(const TreeNode& trGUI)
	{
		TreeNode trRectColor;
		if( trGUI.roi )
			trRectColor = trGUI.roi.rectcolor;
		return trRectColor;
	}

	//virtual
	TreeNode	GetToolNameNode(const TreeNode& trGUI)
	{
		TreeNode trToolName;
		if( trGUI.roi )
			trToolName = trGUI.roi.toolname;
		return trToolName;
	}

	//virtual
	int	GetPointsLimit(){ return 3; }
	
	//virtual
	//bool	IsSupportMultiPlots() { return true; }

	//virtual
	bool	DoEdit(HWND hWndParent = NULL)
	{
		Tree trGUI;
		if(!GetGUITree(trGUI))
			return false;

		// for safe, may come from newer build
		if( !IsSupportMultiPlots() )
			baseline_creation_multi_sel_attrib_access(trGUI.baseline, false, false);

		bool bBaselineChanged = false;
		if( OpenPickPeakPreferDlg(trGUI, bBaselineChanged) )
		{
			if( bBaselineChanged )
			{
				bool	bSubtracted = false;
				foreach(DataPlot dp in m_gl.DataPlots)				// clear subtract baseline status
					_peak_gadget_baseline_subtracted_status(dp, bSubtracted, false);
			}
			return ApplySettings(trGUI);
		}
		return false;
	}
	
	virtual	bool	UpdateROIGUI()
	{
		bool bXPosChanged = true;
		Tree trGUI;
		if( GetGUITree(trGUI) )
		{
			TreeNode trXScale = trGUI.roi.XScale;

			double x0 = get_value_by_format(m_dp, trXScale.leftx.strVal);
			double x1 = get_value_by_format(m_dp, trXScale.rightx.strVal);
			CheckSetMainObjectXPosition(x0, x1);

			bool bDisable = trXScale.fixscale.nVal;
			disable_go_move(m_go, true, true, false, !bDisable);
		}

		return GraphObjCurveTool::UpdateROIGUI();
	}

	//virtual
	BOOL OnDestroy()
	{
		ConnectToTempPreviewDataAndGraph();
		if( m_gl )
		{
			if( m_wks )
			{
				XYRange dr;
				dr.Add(m_wks, 0, "X");
				dr.Add(m_wks, 1, "Y");
	
				_update_peaks_labels(m_gl, dr, false, false);
			}
			m_gl.LT_execute("legend -s");

			bool	bSubtracted = false;
			foreach(DataPlot dp in m_gl.DataPlots)				// clear subtract baseline status
				_peak_gadget_baseline_subtracted_status(dp, bSubtracted, false);
		}

		// the base class may leave the wks if there is a tool on another layer
		if( m_wks )
			m_wks.Destroy();

		return GraphObjCurveTool::OnDestroy();
	}

	//virtual
	bool AddUpdateLastOutputSubmenu(Menu& pm, uint& nPosition)
	{
		DWORD dwFlags = MF_STRING;
		DataPlot dpSource;
		Worksheet wksReport;
		if( !GetSelectedPlot(dpSource) || !GetReportWorksheet(wksReport) )
		{
			dwFlags |= MF_GRAYED;
		}
		pm.Add(_L("Update Output"), GOT_BTN_UPDATE_LAST_OUTPUT, dwFlags);
		nPosition++;
		return true;
	}

	//virtual
	bool	UpdateContextMenu(Menu& pm, uint& nPosition)
	{
		for(int nPos = 0; nPos < nPosition; nPos++)
		{
			if( GOT_BTN_OUTPUT == pm.GetMenuItemID(nPos) )
				break;
			nPos++;
		}
		if( nPos >= nPosition )
			return false;
		nPos++;				// insert after

		pm.ModifyMenu(GOT_BTN_OUTPUT, MF_STRING, GOT_BTN_OUTPUT, _L("Output Peak Values"));

		bool		bHasBaseline = hasBaseline();
		int nFlags = MF_STRING | MF_BYPOSITION;
		if( !bHasBaseline )
			nFlags |= MF_GRAYED;
		pm.InsertMenu(nPos, nFlags, PICK_PEAK_CMD_SUBTRACT_BASELINE, _L("Subtract Baseline"));		nPosition++;
		//pm.InsertMenu(nPos, nFlags, PICK_PEAK_CMD_OUTPUT_BASELINE_DATA, _L("Output Baseline Data"));			nPosition++;

		return true;
	}

	//virtual
	bool OnCmdCustom(int nCmdID)
	{
		switch(nCmdID)
		{
		//case PICK_PEAK_CMD_OUTPUT_BASELINE_DATA:
		//	return OutputBaselineData();
		//	break;

		case PICK_PEAK_CMD_SUBTRACT_BASELINE:
			return SubtractBaseline();
			break;
			
		default:
			return false;
		}
		
		return true;
	}

	//virtual
	BOOL	GetReportWorksheet(Worksheet& wksReport)
	{
		string strReportSheet = GetReportSheet();
		if( strReportSheet.IsEmpty() )
			return false;
		wksReport.Attach(strReportSheet);

		return wksReport.IsValid();
	}
	
	BOOL	PrepareReportWks(Worksheet& wksReport, const DataPlot& dpSrc = NULL)
	{
		if( dpSrc )
			dpSrc.SetActive();			// active this to parse the report sheet name

		string strReportSheet = GetReportSheet();
		if( strReportSheet.IsEmpty() )
			return false;

		if( !attach_or_create_sheet(wksReport, strReportSheet) || !wksReport.IsValid() )
			return false;

		if( wksReport.GetNumCols() < PICK_PEAK_REPORT_TOTAL_COL )
		{
			wksReport.SetSize(0, PICK_PEAK_REPORT_TOTAL_COL);
			wksReport.SetColDesignations("XXYYY");
	
			Column colDataset(wksReport, PICK_PEAK_REPORT_COL_DATASET);
			colDataset.SetType(OKDATAOBJ_DESIGNATION_NONE);
			colDataset.SetLongName("Dataset");

			Column colPeakX(wksReport, PICK_PEAK_REPORT_COL_PEAK_X);
			colPeakX.SetLongName("Peak X");
	
			Column colPeakY(wksReport, PICK_PEAK_REPORT_COL_PEAK_Y);
			colPeakY.SetLongName("Peak Y");
			
			Column colHeight(wksReport, PICK_PEAK_REPORT_COL_HEIGHT);
			colHeight.SetLongName("Height");
			colHeight.SetComments("Peak Height from Baseline");

			Column colBaseline(wksReport, PICK_PEAK_REPORT_COL_BASELINE);
			colBaseline.SetType(OKDATAOBJ_DESIGNATION_NONE);
			colBaseline.SetLongName("Baseline");
		}

		return true;
	}
	
	string	GetReportSheet(const TreeNode& trGUI = NULL, const DataPlot& dpSrc = NULL)
	{
		string strSheet;
		if( trGUI )
		{
			strSheet = trGUI.findpeak.result.output.strVal;
		}
		else
		{
			Tree tr;
			if ( GetGUITree(tr) )
				strSheet = tr.findpeak.result.output.strVal;
		}
		okutil_arg_copy(&strSheet);

		return strSheet;
	}

protected:

	BOOL	InvalidateResults()
	{
		return OnMove();
	}
	
	//virtaul
	string GetSignature()
	{
		return "xf_" XFNAME;
	}

	//virtual
	string	GetXFName(){ return XFNAME; }
	
	//virtual
	int*	GetXFEventHandlersState() { return _get_xf_treenode_evt_handle_state(); }

	//virtual
	string	GetToolName(){ return TOOLNAME; }

	//virtual
	string	GetPreferenceTitle(){ return TOOL_PREFERENCES_TITLE; }

private:

	bool getDataRange(vector& vx = NULL, vector &vy = NULL)
	{
		if(!GetData())
		{
			error_report("GetData() form Grobj error!");
			return false;
		}

		if(!m_dp.GetDataRange(m_dr, m_i1, m_i2))
		{
			error_report("GetDataRange from dataplot error!");
			return false;
		}

		if(vx != NULL && vy != NULL)
		{
			if ( m_dp.GetDataPoints(m_i1, m_i2, vx, vy) <= 0 )
			{
				error_report("GetDataPoints from DataPlot Error!");
				return false;
			}
		}

		return true;
	}
	
	bool	updatePeakLabels(Worksheet& wks, bool bPreview)
	{
		if( !wks )
			return false;
		XYRange dr;
		int nXCol, nYCol;
		if( bPreview )
		{
			nXCol = 0;
			nYCol = 1;
		}
		else
		{
			nXCol = PICK_PEAK_REPORT_COL_PEAK_X;
			nYCol = PICK_PEAK_REPORT_COL_PEAK_Y;
		}
		dr.Add(wks, nXCol, "X");
		dr.Add(wks, nYCol, "Y");
		if( !dr )
			return false;

		tag_columns_in_data_range(dr, STR_DATASETOBJ_PICK_PEAK);		// will change GraphObjCurveTool::AddPlots() and set our own tag
		bool bShowLabel, bRotateLabel;
		int nLabelXYType, nLabelColor;
		getLabelsOption(bShowLabel, nLabelXYType, nLabelColor, bRotateLabel);
		_update_peaks_labels(m_gl, dr, true, bShowLabel, nLabelXYType, bRotateLabel, nLabelColor);
		return true;
	}

	bool	getLabelsOption(bool& bShowLabel, int& nLabelXYType, int& nLabelColor, bool& bRotateLabel)
	{
		Tree trGUI;
		if( !GetGUITree(trGUI) )
			return false;

		TreeNode trLabels = tree_get_node_by_tagname(trGUI, "labels", true);
		bShowLabel = trLabels.sl.nVal;
		switch(trLabels.lc.nVal)
		{
		case PEAK_CENTER_LABEL_X:
			nLabelXYType = LABEL_X_VAL_GUI;
			break;

		case PEAK_CENTER_LABEL_Y:
			nLabelXYType = LABEL_Y_VAL_GUI;
			break;

		//case PEAK_CENTER_LABEL_INDICES:
		//	nLabelXYType = LABEL_ROW_NUM_GUI;
		//	break;

		case PEAK_CENTER_LABEL_XY:
			nLabelXYType = LABEL_XY_VAL_GUI;
			break;

		default:
			nLabelXYType = LABEL_X_VAL_GUI;
		}

		nLabelColor = trLabels.labelcolor.nVal;
		bRotateLabel = trLabels.lr.nVal;
		return true;
	}

	bool	getPeakFilterOption(int& nFilter, double& dValue, const TreeNode& trFilter, const vector& vxSrc, const vector& vySrc)
	{
		nFilter = trFilter.method.nVal;
		dValue = NANUM;

		switch(nFilter)
		{
		case PEAK_FILTER_HEIGHT:
			{
				TreeNode trValue = trFilter.val_height;
				if( 1 == octree_get_auto_support(&trValue) )
					dValue = 20;
				else
					dValue = trValue.dVal;

				dValue *= getTotalHeight(vySrc) / 100;
			}
			break;
		
		case PEAK_FILTER_NUM:
			{
				TreeNode trValue = trFilter.val_num;
				if( 1 == octree_get_auto_support(&trValue) )
				{
					double dAutoHeightThres;
					double dDefaultHeightPer = 0.20;	
					dAutoHeightThres = getTotalHeight(vySrc) * dDefaultHeightPer;

					int iPeakNum = 1;
					compute_peaks_number_by_filter_height(vxSrc, vySrc, dAutoHeightThres, iPeakNum);
					dValue = iPeakNum;
				}
				else
				{
					dValue = trValue.dVal;
				}
			}
			break;
			
		default:
			return true;
		}

		return true;
	}
	
	bool	getWinSearchHeightWidth(double& dHeight, double& dWidth, int nOption, TreeNode& trHeight, TreeNode& trWidth, const vector& vxSrc, const vector& vySrc)
	{
		if( 1 == octree_get_auto_support(&trHeight) )
		{
			dHeight = 20;
			if ( SIZE_OPTION_RAW_SIZE == nOption ) //raw size
			{
				dHeight *= getTotalHeight(vySrc) / 100;
			}
		}
		else
		{
			dHeight = trHeight.dVal;
		}

		if( 1 == octree_get_auto_support(&trWidth) )
		{
			dWidth = 50;
			if ( SIZE_OPTION_RAW_SIZE == nOption ) //raw size
			{
				double dMin, dMax, dTotalWidth;
				vxSrc.GetMinMax(dMin, dMax);
				dTotalWidth = abs(dMax - dMin);

				dWidth *= dTotalWidth/100;
			}
		}
		else
		{
			dWidth = trWidth.dVal;
		}
		return true;
	}
	
	bool	hasBaseline()
	{
		Tree trGUI;
		if( !GetGUITree(trGUI) )
			return false;
		return PEAK_BASELINE_MODE_NONE != trGUI.baseline.mode.nVal;
	}

	bool	prepareBaseline(const DataPlot& dpSrc, BaselineInfo* pBLInfo)
	{
		CreateBaseline(m_vxBaseline, m_vyBaseline, m_bGlobalBL, dpSrc, pBLInfo);
		return true;
	}

	bool	updateBaselinePrev()
	{
		bool bMultiBaseline = GetMultiPlotsState() && !m_bGlobalBL;

		vector vx, vy;
		if( !bMultiBaseline && intersectBaseline(vx, vy) )
		{
			m_goBaseline.Show(true);
			m_goBaseline.SetPoints(vx, vy);
		}
		else
			m_goBaseline.Show(false);

		m_gp.Refresh();

		return true;
	}

	bool intersectBaseline(vector& vx, vector& vy)
	{
		if( m_vxBaseline.GetSize() < 1 || m_vxBaseline.GetSize() != m_vyBaseline.GetSize() )
			return false;

		double x0, x1;
		if( !GetMainObjectXPosition(x0, x1) )
			return false;

		double dMin, dMax;
		if( !findDataMinMax(dMin, dMax, true) )
			return false;

		if( dMin < x0 )
			dMin = x0;
		if( dMax > x1 )
			dMax = x1;
		if( dMax < dMin )
			return false;

		vx = m_vxBaseline;
		vx.Replace(dMin, dMin, MATREPL_TEST_LESSTHAN);
		vx.Replace(dMax, dMax, MATREPL_TEST_GREATER);
		vx.Sort();
		for(int ii = vx.GetSize()-1; ii > 0; ii--)
		{
			if( vx[ii] == vx[ii-1] )
				vx.RemoveAt(ii);
		}
		if( vx.GetSize() < 2 )
			return false;

		vy.SetSize(vx.GetSize());
		return OE_NOERROR == ocmath_interpolate(vx, vy, vx.GetSize(), m_vxBaseline, m_vyBaseline, m_vxBaseline.GetSize());
	}
	
	bool findDataMinMax(double& dMin, double& dMax, bool bXData = true)
	{
		dMin = dMax = NANUM;
		if( GetMultiPlotsState() )
		{
			if( !m_gl )
				return false;

			double dDataMin, dDataMax;
			foreach(DataPlot dp in m_gl.DataPlots)
			{
				if ( isPlotfiltered(dp) )
					continue;

				if( findPlotMinMax(dp, dDataMin, dDataMax, bXData) )
				{
					if( dMin == NANUM || dMin > dDataMin )
						dMin = dDataMin;
					if( dMax == NANUM || dMax < dDataMax )
						dMax = dDataMax;
				}
			}
		}
		else
			return m_dp.IsValid() && findPlotMinMax(m_dp, dMin, dMax, bXData);

		return true;
	}
	
	bool findPlotMinMax(const DataPlot& dp, double& dMin, double& dMax, bool bXData = true)
	{
		ASSERT( dp );

		XYRange xy;
		if( !dp.GetDataRange(xy) )
			return false;

		Column col;
		if( bXData )
			xy.GetXColumn(col);
		else
			xy.GetYColumn(col);
		if( !col )
			return false;

		Dataset ds(col);
		return ds.IsValid() && ds.GetMinMax(dMin, dMax) > 0;			// not all missing values
	}
	
	bool isPlotfiltered(const DataPlot& dp)
	{
		ASSERT(dp.IsValid());

		XYRange xy;
		Column colY;

		return !dp.GetDataRange(xy) || !xy.GetYColumn(colY) || is_dataobj_tagged(colY, STR_DATASETOBJ_PICK_PEAK);
	}
	
	bool	outputPeaks(	Worksheet& wks, const vector& vxPeaks, const vector& vyPeaks, const vector& vHeights, LPCSTR lpcszDataset,
							const BaselineInfo* pBLInfo, const vector& vxBaseline = NULL, const vector& vyBaseline = NULL)
	{
		ASSERT(wks.IsValid() && wks.GetNumCols() >= PICK_PEAK_REPORT_TOTAL_COL);

		int nMinSize = -1, nMaxSize = -1;
		for(int nCol = 0; nCol < PICK_PEAK_REPORT_TOTAL_COL; nCol++)
		{
			Column col(wks, nCol);
			int nSize = col.GetNumRows();
			if( nMinSize < 0 || nMinSize > nSize )
				nMinSize = nSize;
			if( nMaxSize < 0 || nMaxSize < nSize )
				nMaxSize = nSize;
		}

		if( nMinSize != nMaxSize )
		{
			for(nCol = 0; nCol < PICK_PEAK_REPORT_TOTAL_COL; nCol++)
			{
				Column col(wks, nCol);
				int nSize = col.SetNumRows(nMaxSize);
			}
		}

		Dataset dsDs(wks, PICK_PEAK_REPORT_COL_DATASET);
		Dataset dsX(wks, PICK_PEAK_REPORT_COL_PEAK_X);
		Dataset dsY(wks, PICK_PEAK_REPORT_COL_PEAK_Y);
		Dataset dsHeight(wks, PICK_PEAK_REPORT_COL_HEIGHT);
		Column colBaseline(wks, PICK_PEAK_REPORT_COL_BASELINE);

		ASSERT(dsDs.GetSize() == dsX.GetSize() && dsX.GetSize() == dsY.GetSize() && dsX.GetSize() == dsHeight.GetSize() );
		int nRowToInsert = dsX.GetSize();

		vector<string> vs;
		dsDs.GetStringArray(vs);
		vs.InsertAt(vs.GetSize(), lpcszDataset, vxPeaks.GetSize());
		dsDs.PutStringArray(vs);

		dsX.Append(vxPeaks);
		dsY.Append(vyPeaks);
		
		dsHeight.Append(vHeights);

		colBaseline.SetNumRows(vs.GetSize());
		ASSERT( nRowToInsert < colBaseline.GetNumRows() );

		if( pBLInfo )
		{
			Note note;
			note.Create(CREATE_HIDDEN);
			if( note.IsValid() )
			{
				string strBaseline;
				_baseline_info_2_text(strBaseline, *pBLInfo, vxBaseline, vyBaseline);
				note.Text = strBaseline;

				note.SetName("Baseline", OCD_ENUM_NEXT);
				wks.EmbedNote(nRowToInsert, PICK_PEAK_REPORT_COL_BASELINE, note);
			}
			else
			{
				ASSERT(0);
			}
		}

		return true;
	}

	bool	findAllPlotsPeaks(vector& vxPeaks, vector& vyPeaks, bool bOutput = false, bool bReplace = false)
	{
		if( !(bOutput || (vxPeaks && vyPeaks)) )
		{
			ASSERT(0);
			return false;
		}

		vector vxOneDataPeaks, vyOneDataPeaks;
		BaselineInfo blInfo, *pBLInfo = NULL;
		if( bOutput )
			pBLInfo = &blInfo;
		Worksheet wksReport;

		if( GetMultiPlotsState() )
		{
			m_bGlobalBL = false;			// reset this to update baseline

			int nPlots = m_gl.DataPlots.Count();
			for(int ii = 0; ii < nPlots; ii++)			// will add plots in the loop
			{
				DataPlot dp = m_gl.DataPlots(ii);
				ASSERT( dp.IsValid() );
				if ( isPlotfiltered(dp) )
					continue;

				if( !m_bGlobalBL )
					prepareBaseline(dp, pBLInfo);

				if( bOutput )
				{
					Worksheet wks;
					if( !PrepareReportWks(wks, dp) )
						return false;

					if( wksReport.IsValid() && is_same_layer(wksReport, wks) )
					{
						bReplace = false;
					}
					wksReport = wks;
					if( bReplace )
					{
						for(int nRow = 0; nRow < wksReport.GetNumRows(); nRow++)
							wksReport.RemoveEmbedding(nRow, PICK_PEAK_REPORT_COL_BASELINE, FALSE, FALSE);
						wksReport.SetSize(0, -1, WSS_CLEAR_DATA);
					}
				}

				findOnePlotPeaks(dp, vxPeaks, vyPeaks, wksReport, pBLInfo, true);
			}
		}
		else
		{
			m_bGlobalBL = true;

			if(!GetData())
				return error_report("GetData() form Grobj error!");
			ASSERT(m_dp);

			prepareBaseline(m_dp, pBLInfo);
			if( bOutput )
			{
				if( !PrepareReportWks(wksReport, m_dp) )
					return false;
				if( bReplace )
				{
					for(int nRow = 0; nRow < wksReport.GetNumRows(); nRow++)
						wksReport.RemoveEmbedding(nRow, PICK_PEAK_REPORT_COL_BASELINE, FALSE, FALSE);
					
					wksReport.SetSize(0, -1, WSS_CLEAR_DATA);
					
					
				}
			}

			return findOnePlotPeaks(m_dp, vxPeaks, vyPeaks, wksReport, pBLInfo, true);
		}
		return true;
	}

	bool	findOnePlotPeaks(const DataPlot& dp, vector& vxPeaks, vector& vyPeaks, Worksheet& wksReport, const BaselineInfo* pBLInfo, bool bUpdateCurve = false)
	{
		ASSERT(dp.IsValid());

		vector vxOneDataPeaks, vyOneDataPeaks, vOneDataHeights;
		if( !findOnePlotPeaks(dp, vxOneDataPeaks, vyOneDataPeaks, vOneDataHeights) )
			return false;

		ASSERT(vxOneDataPeaks.GetSize() == vyOneDataPeaks.GetSize());

		if( vxOneDataPeaks.GetSize() < 1 )
			return true;

		if( vxPeaks && vyPeaks )
		{
			vxPeaks.Append(vxOneDataPeaks);
			vyPeaks.Append(vyOneDataPeaks);
		}

		if( wksReport.IsValid() )
		{
			string strDataset = getRangeString(dp);
			outputPeaks(wksReport, vxOneDataPeaks, vyOneDataPeaks, vOneDataHeights, strDataset, pBLInfo, m_vxBaseline, m_vyBaseline);

			if( bUpdateCurve )
				updatePeakLabels(wksReport, false);
		}

		return true;
	}
	
	bool	findOnePlotPeaks(const DataPlot& dp, vector& vxPeaks, vector& vyPeaks, vector& vHeight)		// height from baseline
	{
		int i1, i2;
		if( !getData(dp, i1, i2) )
			return false;

		XYRange xySrc;
		if( !dp.GetDataRange(xySrc, i1, i2) )
			return false;

		vector vxSrc, vySrc;
		if ( dp.GetDataPoints(i1, i2, vxSrc, vySrc) <= 0 )
			return false;

		if( vxSrc.GetSize() < GetPointsLimit() )
			return false;

		bool bSubtracted;
		vector vxSrcTmp, vySrcTmp;
		_peak_gadget_baseline_subtracted_status(dp, bSubtracted);
		if( !bSubtracted && m_vxBaseline.GetSize() > 1 )
		{
			vxSrcTmp = vxSrc;
			vySrcTmp = vySrc;
			subtract_baseline(vxSrc, vySrc, m_vxBaseline, m_vyBaseline);
		}

		Tree trGUI;
		if( !GetGUITree(trGUI) )
			return false;

		TreeNode trFindPeak = trGUI.findpeak;
		int			nDirection = trFindPeak.dir.nVal + 1;			//

		TreeNode trSettings = trFindPeak.settings;
		int			nMethod = trSettings.method.nVal;
		int			nPts = trSettings.npts.nVal;
		int 		nOption = trSettings.option.nVal;
		//double 		dHeight = trSettings.height.dVal;
		//double 		dWidth = trSettings.width.dVal;
		double dHeight, dWidth;
		TreeNode trHeight = trSettings.height;
		TreeNode trWidth = trSettings.width;
		getWinSearchHeightWidth(dHeight, dWidth, nOption, trHeight, trWidth, vxSrc, vySrc);
		int nSmoothMethod;
		if( nMethod == PEAK_FINDING_METHOD_FIRST || nMethod == PEAK_FINDING_METHOD_RESIDUE)
			nSmoothMethod = trSettings.deriv_smooth.first.nVal;
		else if(nMethod == PEAK_FINDING_METHOD_SECOND)
			nSmoothMethod =  trSettings.deriv_smooth.second.nVal;
		
		int nPtsSmooth = 0;

		int 		nDerivPts = trSettings.deriv_smooth.deriv_npts.nVal;
		double 		dFFTCutOff = trSettings.deriv_smooth.fft_cutoff.dVal;
		double		dDerivPoly = trSettings.deriv_smooth.deriv_poly.nVal;

		int nFilter;
		double dValue;
		getPeakFilterOption(nFilter, dValue, trFindPeak.filter, vxSrc, vySrc);

		int nSize = vySrc.GetSize();
		vector<int> vPeaksInd(nSize);

		///------ Folger 03/18/10 QA81-15209 IMPROVE_PA_FIND_PEAKS_FOR_NONE_BASELINE
		//int nRet = find_peaks(vxSrc, vySrc, vxPeaks, vyPeaks, vPeaksInd, nMethod, dWidth, dHeight, nPtsSmooth, nDirection, nPts, nOption, nFilter-1, &dValue,
						//nSmoothMethod, NULL, nDerivPts, dFFTCutOff, dDerivPoly);
		int		nRet = hasBaseline() ?
						find_peaks(vxSrc, vySrc, vxPeaks, vyPeaks, vPeaksInd, nMethod, dWidth, dHeight, nPtsSmooth, nDirection, nPts, nOption, nFilter-1, &dValue,
							nSmoothMethod, NULL, nDerivPts, dFFTCutOff, dDerivPoly)
						:
						find_peaks_without_baseline(vxSrc, vySrc, vxPeaks, vyPeaks, vPeaksInd, nMethod, dWidth, dHeight, nPtsSmooth, nDirection, nPts, nOption, nFilter-1, &dValue,
						nSmoothMethod, NULL, nDerivPts, dFFTCutOff, dDerivPoly);
		///------ End IMPROVE_PA_FIND_PEAKS_FOR_NONE_BASELINE

		if ( nRet != OE_NOERROR && nRet !=  OE_POINT_OUTSIDE_RECT )
		{
			vPeaksInd.SetSize(0);
			vxPeaks.SetSize(0);
			vyPeaks.SetSize(0);
		}
		vHeight = vyPeaks;		///Kyle 09/14/2010 ORG-998-S1 PICK_PEAKS_OUTPUT_PEAK_HEIGHT_FROM_BASELINE

		//match peaks' position to preview graph no matter subtracted or not
		if( vxSrcTmp.GetSize() > 0 )
		{
			ASSERT(vxSrcTmp.GetSize() == vySrcTmp.GetSize());
			match_peaks_centers_by_index(vxSrcTmp, vySrcTmp, vxPeaks.GetSize(), vPeaksInd, vxPeaks, vyPeaks);
		}
		else
		{
			match_peaks_centers_by_index(vxSrc, vySrc, vxPeaks.GetSize(), vPeaksInd, vxPeaks, vyPeaks);
		}

		vector<uint> vn;
		vxPeaks.Sort(SORT_ASCENDING, true, vn);
		vyPeaks.Reorder(vn);
		vHeight.Reorder(vn);	///Kyle 09/14/2010 ORG-998-S1 PICK_PEAKS_OUTPUT_PEAK_HEIGHT_FROM_BASELINE

		return true;
	}
	
	string	getRangeString(const DataPlot& dp)
	{
		//string str;
		//dp.GetLegend(str);
		//return str;

		XYRange xy;
		if( dp.GetDataRange(xy) && xy.IsValid() )
			return xy.GetDescription(GETLC_NO_DESIGNATIONS | GETLC_NO_ROWS);
		return "";
	}
	
	bool getData(const DataPlot& dp, int& i1, int& i2)
	{
		vector<int> vI1, vI2;
		if( !is_plot_for_ROI(dp, m_go, STR_DATASETOBJ_PICK_PEAK, vI1, vI2) )
			return false;
		if( vI1.GetSize() < 1 || vI2.GetSize() < 1 )		// no data
			return false;

		double dMin, dMax;
		vI1.GetMinMax(dMin, dMax);		i1 = dMin;
		vI2.GetMinMax(dMin, dMax);		i2 = dMax;

		return true;
	}

	double		getTotalHeight(const vector& vy)
	{
		double dMin, dMax, dTotalHeight;
		vy.GetMinMax(dMin, dMax);
		return hasBaseline() ? max(abs(dMax), abs(dMin)) : dMax - dMin;
	}

private:
	DataRange				m_dr;

	bool					m_bGlobalBL;
	vector					m_vxBaseline;
	vector					m_vyBaseline;
	PolylineGraphObject		m_goBaseline;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////// global functions //////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////

#define STR_REF_DATA_MULTI_SEL_ATTRIB			"RefMultiSel"
bool baseline_creation_multi_sel_attrib_access(TreeNode& trBaseline, bool bGet, bool bMultiSel)		// true, false
{
	if( !trBaseline )
	{
		ASSERT(0);
		return false;
	}
	TreeNode trCreation = trBaseline.creation;
	if(trCreation)
	{
		int nMultiSel;
		if( bGet )
		{
			if( trCreation.GetAttribute(STR_REF_DATA_MULTI_SEL_ATTRIB, nMultiSel) && nMultiSel )
				return true;
		}
		else
		{
			if( bMultiSel )
			{
				nMultiSel = 1;
				trCreation.SetAttribute(STR_REF_DATA_MULTI_SEL_ATTRIB, nMultiSel);
			}
			else
			{
				trCreation.RemoveAttribute(STR_REF_DATA_MULTI_SEL_ATTRIB);
			}
			_show_hide_baseline_creation_refer_data(trBaseline);
			return true;
		}
	}
	return false;
}

bool check_update_roi_x_position(TreeNode& trXScale)
{
	if( trXScale.fixscale.nVal )	// Fixed checkbox default is unchecked, if it is fixed load from theme, should use x position settings in theme.
		return false;

	GraphLayer gl = Project.ActiveLayer();
	if( !gl )
		return false;

	string strLeft, strRight, strRect;

	get_ROI_main_obj_name(gl, XFNAME, strRect);
	if( check_get_roi_x_position(gl, strLeft, strRight, strRect) )
	{
		trXScale.leftx.strVal = strLeft;
		trXScale.rightx.strVal = strRight;

		return true;
	}

	return false;
}

void construct_pick_peak_gui(TreeNode& trGUI)
{
	if( !trGUI )
		return;
	
	trGUI.SetAttribute(STR_ATTRIB_BRANCH, GETNBRANCH_OPEN);

	TreeNode trBaseline = trGUI.AddNode("baseline");
	trBaseline.SetAttribute(STR_LABEL_ATTRIB, _L("Baseline"));
	_construct_peak_gadget_baseline_gui(trBaseline);
	
	TreeNode trFindPeak = trGUI.AddNode("findpeak");
	trFindPeak.SetAttribute(STR_LABEL_ATTRIB, _L("Find Peak"));
	_construct_peak_gadget_find_peak_gui(trFindPeak);

	TreeNode trROI = trGUI.AddNode("roi");
	trROI.SetAttribute(STR_LABEL_ATTRIB, _L("ROI Box"));
	_construct_peak_gadget_roi_gui(trROI);
}

int open_pick_peak_tool(TreeNode& trGUI)
{
	GraphLayer gl = Project.ActiveLayer();
	if(!gl)
		return XFERR_NO_ACTIVE_GRAPHPAGE;

	int nErr = 0;
	string strMainObjName;
	if ( get_ROI_main_obj_name(gl, XFNAME, strMainObjName) ) //attach to existing tool
	{
		PickPeakTool qtool(strMainObjName);
		Tree tr;
		qtool.GetTree(tr);

		tr.GUI.Replace(trGUI.Clone(), true, true, true); //need to keep version attribute for checking.
		qtool.UpdateThemeFileInGUITree(tr.GUI);

		qtool.SetTree(tr);
		qtool.ApplySettings();

		graphobjtool_events(qtool, strMainObjName, OE_MOVE);
	}
	else
	{
		PickPeakTool itool;
		nErr = itool.Create(XFNAME, 0, trGUI);
	}

	return nErr;
}

void addtool_pickpeaks_events(string strGrName, int nEvent, int nMsg = 0)
{
	PickPeakTool itool;
	graphobjtool_events(itool, strGrName, nEvent, nMsg);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////// static functions //////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////

#define GETN_INPUT_RANGE_OPTION(nInputOptions)		\
	GETN_CURRENT_SUBNODE.SetAttribute(STR_XF_VAR_IO_ATTRIB, IO_INPUT);		\
	GETN_CURRENT_SUBNODE.SetAttribute(STR_INTERACTIVE_CONTROL_OPTIONS_ATTRIB, nInputOptions);	\
	GETN_CURRENT_SUBNODE.SetAttribute(STR_ACTION_FILTER_TAGS, STR_DATASETOBJ_PICK_PEAK);

static void _set_xy_range_subnode_id(TreeNode& trInput, int nBranchID, int nRange1ID, int nXID, int nYID, int nEdID)
{
	trInput.DataID = nBranchID;

	TreeNode trRange1 = trInput.FirstNode;
	trRange1.DataID = nRange1ID;

	trRange1.X.DataID = nXID;
	trRange1.Y.DataID = nYID;
	trRange1.ED.DataID = nEdID;
}

static void _construct_peak_gadget_baseline_gui(TreeNode& tr)
{
	if( !tr )
		return;
	
	int nInputOptions = ICOPT_RESTRICT_TO_ONE_DATA | ICOPT_RESTRICT_TO_Y_COLUMNS;

	GETN_USE(tr)
	GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_BASELINE)		//GETN_OPTION_BRANCH(GETNBRANCH_OPEN)

	GETN_LIST(mode, "Mode", 0, _L("Min/Max|Use Existing Dataset|Constant|Auto"))
	GETN_ID(PEAK_PICK_VAR_ID_BASELINE_MODE)		GETN_OPTION_EVENT_EX(_on_update_gui_baseline_mode)

	GETN_XYRANGE(dataset, _L("Dataset"), 1, STR_OPTIONAL)
	GETN_INPUT_RANGE_OPTION(nInputOptions)				GETN_CURRENT_SUBNODE.Show = false;
	_set_xy_range_subnode_id(GETN_CURRENT_SUBNODE, PEAK_PICK_BRANCH_ID_EXISTING_DATASET, PEAK_PICK_BRANCH_ID_DATASET_RANGE1, PEAK_PICK_VAR_ID_DATASET_X, PEAK_PICK_VAR_ID_DATASET_Y, PEAK_PICK_VAR_ID_DATASET_ED);

	GETN_LIST(constant, _L("Constant"), 0, _L("Minimum|Maximum|Mean|Median|Custom"))
	GETN_ID(PEAK_PICK_VAR_ID_BASELINE_CONST)	GETN_OPTION_EVENT_EX(_on_update_gui_baseline_const)		GETN_CURRENT_SUBNODE.Show = false;

	GETN_LIST(creation, _L("Baseline Creation"), PEAK_BASELINE_CREATION_GLOBAL, _L("Use a Global Baseline for All Plots|Use its Own Baseline for Each Plot"))
	GETN_ID(PEAK_PICK_VAR_ID_BASELINE_CREATION)			GETN_OPTION_EVENT_EX(_on_update_gui_baseline_creation)		GETN_CURRENT_SUBNODE.Show = false;

	GETN_XYRANGE(refer, _L("Reference Data"), 1, STR_OPTIONAL)
	GETN_INPUT_RANGE_OPTION(nInputOptions)				GETN_CURRENT_SUBNODE.Show = false;
	_set_xy_range_subnode_id(GETN_CURRENT_SUBNODE, PEAK_PICK_BRANCH_ID_REFERENCE_DATA, PEAK_PICK_BRANCH_ID_DATA_RANGE1, PEAK_PICK_VAR_ID_DATA_X, PEAK_PICK_VAR_ID_DATA_Y, PEAK_PICK_VAR_ID_DATA_ED);

	GETN_NUM(ybase, _L("Y ="), 0)
	GETN_ID(PEAK_PICK_VAR_ID_Y_BASE)		GETN_CURRENT_SUBNODE.Show = false;

	GETN_NUM(npoints, _L("Number of Points to Find"), 8)
	GETN_ID(PEAK_PICK_VAR_ID_BASELINE_N_POINTS)		GETN_CURRENT_SUBNODE.Show = false;
	GETN_CHECK(snap, _L("Snap to Spectrum"), 0)
	GETN_ID(PEAK_PICK_VAR_ID_BASELINE_SNAP)			GETN_CURRENT_SUBNODE.Show = false;
	GETN_LIST(connect, _L("Connect Method"), 0, _L("Line|Spline|BSpline"))
	GETN_ID(PEAK_PICK_VAR_ID_BASELINE_CONNECT)		GETN_CURRENT_SUBNODE.Show = false;
}

static void _construct_peak_gadget_find_peak_gui(TreeNode& tr)
{
	if( !tr )
		return;

	GETN_USE(tr)
	GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_FIND_PEAK)		//GETN_OPTION_BRANCH(GETNBRANCH_OPEN)

	GETN_LIST(dir, _L("Direction"), PEAK_DIRECTION_BOTH, _L("Positive|Negative|Both"))				GETN_ID(PEAK_PICK_VAR_ID_DIRECTION)

	GETN_BEGIN_BRANCH(settings, _L("Peak Finding Settings"))	GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_PEAK_FINDING_SETTINGS)  GETN_OPTION_BRANCH(GETNBRANCH_OPEN)
		GETN_LIST(method, _L("Method"), 0, _L("Local Maximum|Window Search|1st Derivative|2nd Derivative (Search Hidden Peaks)|Residuals (Search Hidden Peaks)"))
		GETN_ID(PEAK_PICK_VAR_ID_METHOD) GETN_OPTION_EVENT_EX(_on_update_gui_peak_finding_method)

		GETN_NUM(npts, _L("Local Points"), 2)
		GETN_ID(PEAK_PICK_VAR_ID_LOCAL_POINTS)

		GETN_LIST(option, _L("Size Option"), SIZE_OPTION_PERCENT_OF_RAW_DATA, _L("Raw Size|Percent of Raw Data"))
		GETN_ID(PEAK_PICK_VAR_ID_SIZE_OPTION)		GETN_OPTION_EVENT_EX(_on_update_gui_size_option)					GETN_CURRENT_SUBNODE.Show = false;
		GETN_NUM(height, _L("Height"), 20)
		GETN_ID(PEAK_PICK_VAR_ID_HEIGHT)			GETN_ADD_AUTO(1)	GETN_OPTION_EVENT_EX(_on_update_gui_height)		GETN_CURRENT_SUBNODE.Show = false;
		GETN_NUM(width, _L("Width"), 50)
		GETN_ID(PEAK_PICK_VAR_ID_WIDTH)				GETN_ADD_AUTO(1)	GETN_OPTION_EVENT_EX(_on_update_gui_width)		GETN_CURRENT_SUBNODE.Show = false;
		
		GETN_BEGIN_BRANCH(deriv_smooth, _L("Smooth Derivative"))
		GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_DERIV_SMOOTH)  GETN_OPTION_BRANCH(GETNBRANCH_OPEN)							GETN_CURRENT_NODE.Show = false;
			GETN_LIST(first,_L("Method"), 0, _L("None|Savitzky-Golay")) 																GETN_ID(PEAK_PICK_VAR_ID_FIRST)		GETN_OPTION_EVENT_EX(_on_update_gui_deriv_smooth)
			GETN_LIST(second,_L("Method"), 0, _L("None|FFT Filter|Adjacent-Averaging|Savitzky-Golay|Quadratic Savitzky-Golay"))		    GETN_ID(PEAK_PICK_VAR_ID_SECOND)	GETN_OPTION_EVENT_EX(_on_update_gui_deriv_smooth)
			GETN_NUM(fft_cutoff, _L("Cut Off Frequency"), 0.2) 																			GETN_ID(PEAK_PICK_VAR_ID_FFT_CUTOFF)
			GETN_NUM(deriv_poly, _L("Polynomial Order"), 2) 																			GETN_ID(PEAK_PICK_VAR_ID_DERIV_POLY)
			GETN_NUM(deriv_npts, _L("Points of Window"), 20) 																			GETN_ID(PEAK_PICK_VAR_ID_DERIV_NPTS)
		GETN_END_BRANCH(deriv_smooth)
	GETN_END_BRANCH(settings)

	GETN_BEGIN_BRANCH(filter, _L("Peak Filtering"))	GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_PEAK_FILTERING)  GETN_OPTION_BRANCH(GETNBRANCH_OPEN)
		GETN_LIST(method, _L("Method"), PEAK_FILTER_HEIGHT, _L("None|By Height|By Number")) 		GETN_ID(PEAK_PICK_VAR_ID_FILTER)		GETN_OPTION_EVENT_EX(_on_update_gui_filter_params_status)
		GETN_NUM(val_height, _L("Threshold Height(%)"), 20)		GETN_ADD_AUTO(1)
		GETN_ID(PEAK_PICK_VAR_ID_VAL_HEIGHT)	GETN_OPTION_EVENT_EX(_on_update_gui_filter_value)
		GETN_NUM(val_num, _L("Number of Peaks"), NANUM)			GETN_ADD_AUTO(1)		GETN_CURRENT_SUBNODE.Show = false;
		GETN_ID(PEAK_PICK_VAR_ID_VAL_NUM)		GETN_OPTION_EVENT_EX(_on_update_gui_filter_value)
	GETN_END_BRANCH(filter)

	GETN_BEGIN_BRANCH(labels, _L("Peak Labels"))	GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_PEAK_LABELS)  GETN_OPTION_BRANCH(GETNBRANCH_OPEN)
		GETN_CHECK(sl, _L("Show Center Label"), 1)													GETN_ID(PEAK_PICK_VAR_ID_SHOW_LABEL)	GETN_OPTION_EVENT_EX(_on_update_gui_show_label)
		GETN_LIST(lc, _L("Center Label"), 0, _L("X of Peaks|Y of Peaks|(X,Y) of Peaks"))			GETN_ID(PEAK_PICK_VAR_ID_CENTER_LABEL)
		GETN_CHECK(lr, _L("Rotate Center Label"), 1)												GETN_ID(PEAK_PICK_VAR_ID_ROLATE_LABEL)
		GETN_COLOR(labelcolor, _L("Label Color"), 0)	GETN_COLOR_CHOICE_OPTIONS(COLORLIST_CUSTOM | COLORLIST_SINGLE) GETN_ID(PEAK_PICK_VAR_ID_LABEL_COLOR)
	GETN_END_BRANCH(labels)

	GETN_BEGIN_BRANCH(result, _L("Result"))		GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_RESULT)  GETN_OPTION_BRANCH(GETNBRANCH_OPEN)
		GETN_STR(output, _L("Output Result to"), "[QkPeak]%C_Result")						GETN_ID(PEAK_PICK_VAR_ID_OUTPUT)
	GETN_END_BRANCH(result)

	string strMapping;
	vector<string> vsList;
	TreeNode trDerivSmooth = tr.settings.deriv_smooth;

	vector<int> vnID1 = {DERIV_SMOOTH_NONE,	DERIV_SMOOTH_SG};
	convert_int_vector_to_string_vector(vnID1, vsList);
	strMapping.SetTokens(vsList, '|');
	trDerivSmooth.first.SetAttribute(STR_INTMAP_ATTRIB, strMapping);

	vector<int> vnID2 = {DERIV_SMOOTH_NONE, DERIV_SMOOTH_FFT, DERIV_SMOOTH_ADJ, DERIV_SMOOTH_SG, DERIV_SMOOTH_QSG};
	convert_int_vector_to_string_vector(vnID2, vsList);
	strMapping.SetTokens(vsList, '|');
	trDerivSmooth.second.SetAttribute(STR_INTMAP_ATTRIB, strMapping);
}

static void _construct_peak_gadget_roi_gui(TreeNode& tr)
{
	if( !tr )
		return;

	GETN_USE(tr)
	GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_ROI_BOX)		//GETN_OPTION_BRANCH(GETNBRANCH_OPEN)
	
	GETN_BEGIN_BRANCH(XScale, _L("X Scale"))		GETN_ID_BRANCH(PEAK_PICK_BRANCH_ID_XSCALE)  GETN_OPTION_BRANCH(GETNBRANCH_OPEN)
		GETN_NUM(leftx, _L("From"), 0)									GETN_ID(PEAK_PICK_VAR_ID_LEFT_X)
		GETN_NUM(rightx, _L("To"), 10)									GETN_ID(PEAK_PICK_VAR_ID_RIGHT_X)
		GETN_CHECK(fixscale, _L("Fixed(Prevent moving by ROI)"), 0)		GETN_ID(PEAK_PICK_VAR_ID_FIXSCALE)
	GETN_END_BRANCH(XScale)

	GETN_STR(toolname, STR_TOOLNAME, TOOLNAME)	GETN_ID(PEAK_PICK_VAR_ID_TOOL_NAME)	GETN_CONTROL_OPTION_BOX(0)
	GETN_COLOR(rectcolor, STR_FILL_COLOR, 20)	GETN_COLOR_CHOICE_OPTIONS(COLORLIST_CUSTOM | COLORLIST_SINGLE) GETN_ID(PEAK_PICK_VAR_ID_RECT_COLOR)
}

static void _get_deriv_status(TreeNode& trSettings, bool& bHasBasicDerivSmoothMethods, bool& bHas2ndDerivSmoothMethods, 
								bool& bUseDerivSmooth, bool& bUseSG, bool& bUseADJ, bool& bUseFFT)
{
	TreeNode trDerivSmooth 		= trSettings.deriv_smooth;
	bHasBasicDerivSmoothMethods = (trSettings.method.nVal == PEAK_FINDING_METHOD_FIRST || trSettings.method.nVal == PEAK_FINDING_METHOD_RESIDUE);
	bHas2ndDerivSmoothMethods 	= (trSettings.method.nVal == PEAK_FINDING_METHOD_SECOND);
	bUseDerivSmooth 			= (bHasBasicDerivSmoothMethods || bHas2ndDerivSmoothMethods) ;
	bUseSG						= (bHasBasicDerivSmoothMethods && (trDerivSmooth.first.nVal == DERIV_SMOOTH_SG))
									|| (bHas2ndDerivSmoothMethods && ((trDerivSmooth.second.nVal == DERIV_SMOOTH_SG) 
									|| (trDerivSmooth.second.nVal == DERIV_SMOOTH_QSG)));
	bUseADJ 					= (bHas2ndDerivSmoothMethods && (trDerivSmooth.second.nVal == DERIV_SMOOTH_ADJ));
	bUseFFT 					= (bHas2ndDerivSmoothMethods && (trDerivSmooth.second.nVal == DERIV_SMOOTH_FFT));
}

static void _update_gui_deriv_smooth(TreeNode& trSettings)
{
	bool bHasBasicDerivSmoothMethods,  bHas2ndDerivSmoothMethods, bUseDerivSmooth, bUseSG, bUseADJ, bUseFFT;
	_get_deriv_status(trSettings, bHasBasicDerivSmoothMethods, bHas2ndDerivSmoothMethods, bUseDerivSmooth,  bUseSG, bUseADJ, bUseFFT);
	TreeNode trDerivSmooth = trSettings.deriv_smooth;
	trDerivSmooth.first.Show = bHasBasicDerivSmoothMethods;
	trDerivSmooth.second.Show = bHas2ndDerivSmoothMethods;	
	trDerivSmooth.fft_cutoff.Show = bUseFFT;
	trDerivSmooth.deriv_npts.Show = (bUseADJ || bUseSG);
	trDerivSmooth.deriv_poly.Show = bUseSG;
}

static bool _show_hide_baseline_creation_refer_data(TreeNode trBaseline)
{
	if( !trBaseline )
		return false;

	if( baseline_creation_multi_sel_attrib_access(trBaseline) )
	{
		bool bConstant = PEAK_BASELINE_MODE_CONSTANT==trBaseline.mode.nVal;
		bool bAuto = PEAK_BASELINE_MODE_AUTO==trBaseline.mode.nVal;
		bool bCreation = (bConstant && PEAK_BASELINE_CONST_CUSTOM!=trBaseline.constant.nVal) || bAuto;

		trBaseline.creation.Show = bCreation;
		trBaseline.refer.Show = bCreation && PEAK_BASELINE_CREATION_GLOBAL==trBaseline.creation.nVal;
	}
	else
	{
		trBaseline.creation.Show = false;
		trBaseline.refer.Show = false;
	}

	return true;
}

static bool _on_update_gui_baseline_mode(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trBaseline = tree_get_node_by_tagname(tr, "baseline", true);
	bool bNone = PEAK_BASELINE_MODE_NONE==trBaseline.mode.nVal;
	
	trBaseline.dataset.Show = PEAK_BASELINE_MODE_USE_EXISTING_DATASET==trBaseline.mode.nVal;
	
	bool bConstant = PEAK_BASELINE_MODE_CONSTANT==trBaseline.mode.nVal;
	trBaseline.constant.Show = bConstant;
	trBaseline.ybase.Show = bConstant && PEAK_BASELINE_CONST_CUSTOM==trBaseline.constant.nVal;

	bool bAuto = PEAK_BASELINE_MODE_AUTO==trBaseline.mode.nVal;
	trBaseline.snap.Show =
	trBaseline.npoints.Show =
	trBaseline.connect.Show = bAuto;

	_show_hide_baseline_creation_refer_data(trBaseline);

	return true;
}

static bool _on_update_gui_baseline_const(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trBaseline = tree_get_node_by_tagname(tr, "baseline", true);
	_show_hide_baseline_creation_refer_data(trBaseline);
	trBaseline.ybase.Show = PEAK_BASELINE_MODE_CONSTANT==trBaseline.mode.nVal && PEAK_BASELINE_CONST_CUSTOM==trBaseline.constant.nVal;

	return true;
}

static bool _on_update_gui_baseline_creation(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trBaseline = tree_get_node_by_tagname(tr, "baseline", true);
	_show_hide_baseline_creation_refer_data(trBaseline);

	return true;
}

static bool _on_update_gui_peak_finding_method(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trSettings = tree_get_node_by_tagname(tr, "settings", true);
	if( !trSettings )
		return false;

	bool bShowPts = PEAK_FINDING_METHOD_MAX == trSettings.method.nVal;
	trSettings.npts.Show = bShowPts;

	bool bShowWinParams = PEAK_FINDING_METHOD_WIN == trSettings.method.nVal;
	trSettings.option.Show = bShowWinParams;
	trSettings.height.Show = bShowWinParams;
	trSettings.width.Show = bShowWinParams;

	bool bShowDerivParams = !(bShowPts || bShowWinParams);
	trSettings.deriv_smooth.Show = bShowDerivParams;
	if( bShowDerivParams )
		_update_gui_deriv_smooth(trSettings);

	return true;
}

static void _update_size_option_height_width(int nSzOption, TreeNode& trHeight = NULL, TreeNode& trWidth = NULL)
{
	if( trHeight && 1==octree_get_auto_support(&trHeight) )
	{
		trHeight.dVal = SIZE_OPTION_RAW_SIZE == nSzOption ? NANUM : 20;
	}
	if( trWidth && 1==octree_get_auto_support(&trWidth) )
	{
		trWidth.dVal = SIZE_OPTION_RAW_SIZE == nSzOption ? NANUM : 50;
	}
}

static bool _on_update_gui_size_option(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trOption = tree_get_node_by_tagname(tr, "option", true);
	if( !trOption )
		return false;

	TreeNode trHeight = tree_get_node_by_tagname(tr, "height", true);
	TreeNode trWidth = tree_get_node_by_tagname(tr, "width", true);
	_update_size_option_height_width(trOption.nVal, trHeight, trWidth);

	return true;
}

static bool _on_update_gui_height(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trOption = tree_get_node_by_tagname(tr, "option", true);
	TreeNode trHeight = tree_get_node_by_tagname(tr, "height", true);
	_update_size_option_height_width(trOption.nVal, trHeight);

	return true;
}

static bool _on_update_gui_width(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trOption = tree_get_node_by_tagname(tr, "option", true);
	TreeNode trWidth = tree_get_node_by_tagname(tr, "width", true);
	_update_size_option_height_width(trOption.nVal, NULL, trWidth);

	return true;
}

static bool _on_update_gui_deriv_smooth(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trSettings = tree_get_node_by_tagname(tr, "settings", true);
	
	bool bShowPts = PEAK_FINDING_METHOD_MAX == trSettings.method.nVal;
	bool bShowWinParams = PEAK_FINDING_METHOD_WIN == trSettings.method.nVal;
	bool bShowDerivParams = !(bShowPts || bShowWinParams);
	trSettings.deriv_smooth.Show = bShowDerivParams;
	if( bShowDerivParams )
		_update_gui_deriv_smooth(trSettings);
	
	return true;
}

static bool _on_update_gui_filter_params_status(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trFilter = tree_get_node_by_tagname(tr, "filter", true);
	
	trFilter.val_height.Show = PEAK_FILTER_HEIGHT == trFilter.method.nVal;
	trFilter.val_num.Show = PEAK_FILTER_NUM == trFilter.method.nVal;

	return true;
}

static bool _on_update_gui_filter_value(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	if( 1 == octree_get_auto_support(&trNode) )
	{
		if ( trNode.tagName.Compare("val_height") == 0 )
		{
			trNode.dVal = 20;
		}
		else if ( trNode.tagName.Compare("val_num") == 0 )
		{
			trNode.dVal = NANUM;
		}
		else
		{
			ASSERT(FALSE);
		}
		
		return true;
	}

	return false;
}

static bool _on_update_gui_show_label(TreeNode& tr, int nRow, int nCol, TreeNode& trNode, DWORD dwCntrl, int nType, WndContainer& theDlg)
{
	TreeNode trShowLabel = tree_get_node_by_tagname(tr, "sl", true);
	TreeNode trCenterLabel = tree_get_node_by_tagname(tr, "lc", true);
	TreeNode trRotateLabel = tree_get_node_by_tagname(tr, "lr", true);
	TreeNode trLabelColor = tree_get_node_by_tagname(tr, "labelcolor", true);

	trCenterLabel.Show = trShowLabel.nVal;
	trRotateLabel.Show = trShowLabel.nVal;
	trLabelColor.Show = trShowLabel.nVal;

	return true;
}

static bool	_peak_gadget_baseline_subtracted_status(DataPlot& dp, bool& bSubtracted, bool bGet = true)
{
#define STR_PICK_PEAK_BASELINE_SUBTRACTED_BINARY_NAME		"BLSubtracted"

	if( !dp )
		return false;
	Tree tr;
	if( bGet )
	{
		bSubtracted = false;
		if( dp.GetBinaryStorage(STR_PICK_PEAK_BASELINE_SUBTRACTED_BINARY_NAME, tr) && tr.Subtracted )
			bSubtracted = tr.Subtracted.nVal;
	}
	else
	{
		if( bSubtracted )
		{
			tr.Subtracted.nVal = bSubtracted;
			dp.PutBinaryStorage(STR_PICK_PEAK_BASELINE_SUBTRACTED_BINARY_NAME, tr);
		}
		else
		{
			dp.SetMemory(STR_PICK_PEAK_BASELINE_SUBTRACTED_BINARY_NAME, NULL);
		}
	}

	return true;
}

static bool _check_if_baseline_setting_changed(const TreeNode& trBaseline1, const TreeNode& trBaseline2)
{
	if( !(trBaseline1 && trBaseline2) )
	{
		ASSERT(false);
		return false;
	}
	ASSERT( baseline_creation_multi_sel_attrib_access(trBaseline1) == baseline_creation_multi_sel_attrib_access(trBaseline2) );
	
	if( trBaseline1.mode.nVal != trBaseline2.mode.nVal )
		return true;

	bool bDiff = false;
	switch( trBaseline1.mode.nVal )
	{
	case PEAK_BASELINE_MODE_USE_EXISTING_DATASET:
		bDiff = _is_xy_range_diff(trBaseline1.dataset, trBaseline2.dataset, true, true);
		break;

	case PEAK_BASELINE_MODE_CONSTANT:
		bDiff = (trBaseline1.constant.nVal != trBaseline2.constant.nVal );
		if( !bDiff )
		{
			if( PEAK_BASELINE_CONST_CUSTOM == trBaseline1.constant.nVal )
			{
				bDiff = !is_equal(trBaseline1.ybase.dVal, trBaseline2.ybase.dVal);
			}
			else if( baseline_creation_multi_sel_attrib_access(trBaseline1) )
			{
				bDiff = (trBaseline1.creation.nVal!=trBaseline2.creation.nVal) || (PEAK_BASELINE_CREATION_GLOBAL==trBaseline1.creation.nVal && _is_xy_range_diff(trBaseline1.refer, trBaseline2.refer, true));
			}
		}
		break;
		
	case PEAK_BASELINE_MODE_AUTO:
		bDiff = (trBaseline1.snap.nVal != trBaseline2.snap.nVal || trBaseline1.npoints.nVal != trBaseline2.npoints.nVal || trBaseline1.connect.nVal != trBaseline2.connect.nVal);
		if( !bDiff && baseline_creation_multi_sel_attrib_access(trBaseline1) )
		{
			bDiff = (trBaseline1.creation.nVal!=trBaseline2.creation.nVal) || (PEAK_BASELINE_CREATION_GLOBAL==trBaseline1.creation.nVal && _is_xy_range_diff(trBaseline1.refer, trBaseline2.refer, true, true));
		}
		break;
	}

	return bDiff;
}

static bool _is_xy_range_diff(const TreeNode& trRng1, const TreeNode& trRng2, bool bCompareY = false, bool bCompareX = false)
{
	XYRange xy1, xy2;
	bool bValid1 = xy_range_from_GetN_data_node(trRng1, xy1);
	bool bValid2 = xy_range_from_GetN_data_node(trRng2, xy2);
	if( bValid1 != bValid2 )
		return true;
	if( !bValid1 )
		return false;

	DWORD wResults;
	if( bCompareX )
	{
		if( !(xy1.IsIntersect(xy2, "X", "X", &wResults) && RNGINTSCT_FULL==wResults) )
			return true;
	}
	if( bCompareY )
	{
		if( !(xy1.IsIntersect(xy2, "Y", "Y", &wResults) && RNGINTSCT_FULL==wResults) )
			return true;
	}

	return false;
}

static void _init_baseline_info(BaselineInfo& blInfo)
{
	blInfo.m_nMode = PEAK_BASELINE_MODE_NONE;
	blInfo.m_strRefer = "";
}

static bool _intersect_baseline_refer_data(vector& vxRefer, vector& vyRefer, double dROIFrom, double dROITo, double dLayFrom, double dLayTo)
{
	if( vxRefer.GetSize() < 1 || vxRefer.GetSize() != vyRefer.GetSize() )
		return false;

	if( dROIFrom < dLayFrom )
		dROIFrom = dLayFrom;
	if( dROITo > dLayTo )
		dROITo = dLayTo;

	vector<uint> vIndex;

	vxRefer.Find(MATREPL_TEST_LESSTHAN, dROIFrom, vIndex);
	if( vIndex.GetSize() > 0 )
	{
		vector<int> vec;
		vec = vIndex;

		vxRefer.RemoveAt(vec);
		vyRefer.RemoveAt(vec);
	}

	vxRefer.Find(MATREPL_TEST_GREATER, dROITo, vIndex);
	if( vIndex.GetSize() > 0 )
	{
		vector<int> vec;
		vec = vIndex;

		vxRefer.RemoveAt(vec);
		vyRefer.RemoveAt(vec);
	}
	ASSERT( vxRefer.GetSize() == vyRefer.GetSize() );

	return vxRefer.GetSize() > 0;
}

static void _swap(double& d1, double& d2)
{
	double dT = d1;
	d1 = d2;
	d2 = dT;
}

static bool	_create_baseline(	const TreeNode& trBaseline, vector& vxb, vector& vyb, bool& bGlobalGL, double dROIFrom, double dROITo,
								double dLayFrom, double dLayTo, bool bMultiSel = true, const DataPlot& dpSrc = NULL,
								BaselineInfo* pBLInfo = NULL)
{
	vxb.SetSize(0);
	vyb.SetSize(0);

	if( !trBaseline )
		return false;
	if( !bMultiSel && !dpSrc)
		return false;

	XYRange xySrc;
	if( dpSrc )
		dpSrc.GetDataRange(xySrc);

	if( dLayFrom > dLayTo )
		_swap(dLayFrom, dLayTo);
	if( dROIFrom > dROITo )
		_swap(dROIFrom, dROITo);

	bool bLocalBaseline = true;			// for now always use local baseline for constant and auto mode

	bGlobalGL = bMultiSel;

	if( pBLInfo )
	{
		_init_baseline_info(*pBLInfo);
		pBLInfo->m_nMode = trBaseline.mode.nVal;
	}

	switch(trBaseline.mode.nVal)
	{
	case PEAK_BASELINE_MODE_NONE:
		{
			// do nothing
		}
		break;

	case PEAK_BASELINE_MODE_USE_EXISTING_DATASET:
		{
			XYRange xyExisting;
			if( !xy_range_from_GetN_data_node(trBaseline.dataset, xyExisting) )
				return false;
			if( !xyExisting.GetData(vyb, vxb) )
				return false;

			if( pBLInfo )
				pBLInfo->m_strRefer = xyExisting.GetDescription(GETLC_NO_DESIGNATIONS | GETLC_NO_ROWS);
		}
		break;

	case PEAK_BASELINE_MODE_CONSTANT:
		{
			if( pBLInfo )
				pBLInfo->m_nConstant = trBaseline.constant.nVal;

			double dXFrom, dXTo;
			double dYBase = NANUM;

			if( PEAK_BASELINE_CONST_CUSTOM == trBaseline.constant.nVal )
			{
				dYBase = trBaseline.ybase.dVal;
				if( pBLInfo )
					pBLInfo->m_dCustom = dYBase;
				dXFrom = max(dLayFrom, dROIFrom);
				dXTo = min(dLayTo, dROITo);
			}
			else
			{
				vector vxRefer, vyRefer;
				if( bMultiSel && PEAK_BASELINE_CREATION_GLOBAL==trBaseline.creation.nVal )
				{
					bLocalBaseline = false;			// for now always use local baseline for constant and auto mode

					XYRange xyRefer;
					if( !xy_range_from_GetN_data_node(trBaseline.refer, xyRefer) || !xyRefer.GetData(vyRefer, vxRefer) )
						return false;
					
					if( pBLInfo )
						pBLInfo->m_strRefer = xyRefer.GetDescription(GETLC_NO_DESIGNATIONS | GETLC_NO_ROWS);
				}
				else
				{
					bGlobalGL = false;
					if( !xySrc || !xySrc.GetData(vyRefer, vxRefer) )
						return false;
				}

				if( bLocalBaseline )
				{
					if( !_intersect_baseline_refer_data(vxRefer, vyRefer, dROIFrom, dROITo, dLayFrom, dLayTo) )
						return false;
					dXFrom = max(dLayFrom, dROIFrom);
					dXTo = min(dLayTo, dROITo);
				}
				else
				{
					dXFrom = dLayFrom;
					dXTo = dLayTo, dROITo;
				}

				double dMin, dMax, dSum, dMedian;
				switch(trBaseline.constant.nVal)
				{
				case PEAK_BASELINE_CONST_MIN:
				case PEAK_BASELINE_CONST_MAX:
					vyRefer.GetMinMax(dMin, dMax);
					dYBase = PEAK_BASELINE_CONST_MIN == trBaseline.constant.nVal ? dMin : dMax;
					break;

				case PEAK_BASELINE_CONST_MEAN:
					if( 0 != vyRefer.Sum(dSum) )
						return false;
					dYBase = dSum / vyRefer.GetSize();
					break;

				case PEAK_BASELINE_CONST_MEDIAN:
					if( OE_NOERROR != ocmath_get_median_in_vector(vxRefer.GetSize(), vyRefer, &dMedian) )
						return false;
					dYBase = dMedian;
					break;
				}
			}

			vyb.SetSize(2);
			vyb = dYBase;

			vxb.SetSize(2);
			vxb[0] = dXFrom;
			vxb[1] = dXTo;
		}
		break;

	case PEAK_BASELINE_MODE_AUTO:
		{
			vector vxRefer, vyRefer;
			vector vxAnchor, vyAnchor;
			if( bMultiSel && PEAK_BASELINE_CREATION_GLOBAL==trBaseline.creation.nVal )
			{
				bLocalBaseline = false;

				XYRange xyRefer;
				if( !(xy_range_from_GetN_data_node(trBaseline.refer, xyRefer) &&xyRefer.GetData(vyRefer, vxRefer)) )
					return false;

				if( pBLInfo )
					pBLInfo->m_strRefer = xyRefer.GetDescription(GETLC_NO_DESIGNATIONS | GETLC_NO_ROWS);
			}
			else
			{
				bGlobalGL = false;
				if( !xySrc || !xySrc.GetData(vyRefer, vxRefer) )
					return false;
			}

			if( bLocalBaseline )
			{
				if( !_intersect_baseline_refer_data(vxRefer, vyRefer, dROIFrom, dROITo, dLayFrom, dLayTo) )
					return false;
			}

			if( !auto_find_baseline_anchor_points(vxRefer, vyRefer, vxAnchor, vyAnchor, trBaseline.npoints.nVal) )
				return false;
			if( trBaseline.snap.nVal )
				snap_points_to_source_data(vxRefer, vyRefer, vxAnchor, vyAnchor);
			int nRet = interpolate_baseline_base_on_data(vxAnchor, vyAnchor, vxRefer, vyRefer, vxRefer.GetSize(), trBaseline.connect.nVal);
			if(OE_NOERROR != nRet)
				return false;

			vxb = vxAnchor;
			vyb = vyAnchor;

			if( pBLInfo )
			{
				pBLInfo->m_nAnchor = trBaseline.npoints.nVal;
				pBLInfo->m_bSnap = trBaseline.snap.nVal;
				pBLInfo->m_nConnect = trBaseline.connect.nVal;
			}
		}
		break;

	default:
		return false;
	}

	return true;
}

static bool _baseline_info_2_text(string& strText, const BaselineInfo& blInfo, const vector& vxBaseline, const vector& vyBaseline)
{
	strText.Empty();

	bool bAppendData = true;
	switch( blInfo.m_nMode )
	{
	case PEAK_BASELINE_MODE_NONE:
		strText = "Mode: Min/Max";
		bAppendData = false;
		break;

	case PEAK_BASELINE_MODE_USE_EXISTING_DATASET:
		strText = "Mode: Use Existing Dataset\r\n";
		strText += "Reference Dataset: " + blInfo.m_strRefer;
		break;
		
	case PEAK_BASELINE_MODE_CONSTANT:
		{
			strText = "Mode: Const\r\n";

			bool bUseRefer = true;
			switch( blInfo.m_nConstant )
			{
			case PEAK_BASELINE_CONST_MIN:
				strText += "Constant: Min";
				break;

			case PEAK_BASELINE_CONST_MAX:
				strText += "Constant: Max";
				break;

			case PEAK_BASELINE_CONST_MEAN:
				strText += "Constant: Mean";
				break;

			case PEAK_BASELINE_CONST_MEDIAN:
				strText += "Constant: Medin";
				break;
				
			case PEAK_BASELINE_CONST_CUSTOM:
				strText += "Constant: Custom\r\n";
				strText += "Base: " + ftoa(blInfo.m_dCustom);
				bUseRefer = false;
				break;
				
			default:
				ASSERT(0);
				return false;
			}

			if( bUseRefer && !blInfo.m_strRefer.IsEmpty() )
				strText += "\r\nReference Data: " + blInfo.m_strRefer;
		}
		break;
		

	case PEAK_BASELINE_MODE_AUTO:
		{
			strText = "Mode: Auto\r\n";

			if( !blInfo.m_strRefer.IsEmpty() )
				strText += "Reference Data: " + blInfo.m_strRefer + "\r\n";

			strText += "Number of Anchor Points: " + (string)blInfo.m_nAnchor + "\r\n";
			strText += "Snap to Data: ";
			if( blInfo.m_bSnap )
				strText += "True\r\n";
			else
				strText += "False\r\n";

			strText += "Connect Type: ";
			switch( blInfo.m_nConnect )
			{
			case PEAK_BASELINE_CONNECT_METHOD_LINE:
				strText += "Line\r\n";
				break;
				
			case PEAK_BASELINE_CONNECT_METHOD_SPLINE:
				strText += "Spline\r\n";
				break;
				
			case PEAK_BASELINE_CONNECT_METHOD_BSPLINE:
				strText += "B Spline\r\n";
				break;
				
			default:
				ASSERT(0);
				return false;
			}
		}
		break;

	default:
		ASSERT(0);
		return false;
	}

	if( bAppendData )
	{
		if( !(vxBaseline && vyBaseline) )
			return false;

		strText += "\r\n\r\nX\tY\r\n";
		for(int ii = 0; ii < vxBaseline.GetSize(); ii++)
		{
			string strRow = ftoa(vxBaseline[ii]) + "\t" + ftoa(vyBaseline[ii]);
			strText += strRow + "\r\n";
		}
	}

	return true;
}

static bool	_update_peaks_labels(GraphLayer gl, XYRange xyPeaksCenter,  bool bShowCenter, bool bShowLabel, int nLabelXYType = LABEL_Y_VAL_GUI, bool bRotateLabel = false, int nLabelColor = -1)
{
	if( !(gl && xyPeaksCenter) )
		return false;

	DataPlot dp;
	get_plot_from_xyr_in_gl(gl, xyPeaksCenter, dp, IDM_PLOT_SCATTER);

	if( bShowCenter || bShowLabel )
	{
		if( !dp.IsValid() )
		{
			if( !(plot_xyr_to_graph(xyPeaksCenter, gl, IDM_PLOT_SCATTER, SYSCOLOR_RED, dp) && dp.IsValid()) )
				return false;
			set_dataplot_format(dp, 16, 15);
		}

		return _set_peak_center_format(dp, bShowCenter, bShowLabel, nLabelXYType, bRotateLabel, nLabelColor);
	}
	else if( dp.IsValid() )
	{
		legend_delete_plot(gl, dp.GetIndex());
		dp.Destroy();
	}

	return true;
}

static bool	_set_peak_center_format(DataPlot& dp, bool bShowCenter, bool bShowLabel, int nLabelXYType = LABEL_Y_VAL_GUI, bool bRotateLabel = false, int nLabelColor = -1)
{
	if( !dp.IsValid() )
		return false;

	Tree trFormat;
	trFormat.Root.Symbol.Shape.nVal = 16;
	trFormat.Root.Symbol.Size.dVal = bShowCenter ? 15 : 0;

	TreeNode trEnable = trFormat.Root.Label.AddNode("Enable");
	trEnable.nVal = bShowLabel;
	if( bShowLabel )
	{
		trFormat.Root.Label.Form.nVal = nLabelXYType;
		trFormat.Root.Label.Rotate.dVal = bRotateLabel ? 90 : 0;
		trFormat.Root.Label.Color.nVal = nLabelColor;
		trFormat.Root.Label.PosScatter.nVal = 3;			// above
	}

	return 0==dp.UpdateThemeIDs(trFormat.Root) && dp.ApplyFormat(trFormat, true, true);
}
