www.gusucode.com > VC++绘制样条曲线的源码实例源码程序 > VC++绘制样条曲线的源码实例源码程序\code\CurveCtrl.cpp

    //Download by http://www.NewXing.com
/*==============================================================================================================================*
// CurveCtrl.cpp											   
// 
// Author:		  Sunjoy Chen
// Email(MSN):    cfd@dl.cn
// Copyright 2004, Sunjoy Chen.
//
// Permission to use, copy, modify, distribute for any purpose is hereby
// granted without fee, provided that the above copyright notice and
// this permission notice included in all derived versions. 
// I will be glad to know you using it, let me know by email, and 
// your bugs report are also welcome. 
// This file is provided "as is" with no expressed or implied warranty.
//
// Special thanks to Chris Maunder(for his CGridCtrl which I had learned much from).
//
// History: 
//     09/09/2004 ~ 09/17/2004  basic function finished
//	   09/27/2004  	            select/deselect curve
//	   11/11/2004               bugs: disable edit if no curve selected;
//	                                  send messages if multi curves selected/deselected.
==============================================================================================================================*/
	   

#include "stdafx.h"
#include "CurveCtrl.h"
#include "MemDC.h"
 
#include <FLOAT.h>
#include <math.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define CURVE_EPSILON       FLT_EPSILON

const int CURVE_NEAR_RANGE	= 4;

/*=======================================================================================*/
// CCurve
CCurve::CCurve()
{
	m_crColor = RGB(0, 0, 255);
	m_iStyle  = PS_SOLID;
	m_nWidth  = 1;	
	m_bSelected = FALSE;	
	m_bVisible = TRUE;	
	m_strName.Empty();
}

CCurve::~CCurve()
{
}

// description  : to judge if there is any key point near given point 
//                or if the given point on the line segment between neighbor key points
// in parameter : point  
// out parameter: iIndex -- index of key point if there is
BOOL CCurve::IsPointNearCurve(const CPoint& point, int& iIndex)
{
	iIndex = -1;
	int nCount = m_ArrPoint.GetSize();
	if (nCount < 1)
		return FALSE;
	
	// m_ArrPoint is already sorted
	for (int iPt = 0; iPt < nCount; iPt++)
	{
		if (point.x < m_ArrPoint[iPt].x)
		{			
			break;
		}
	}

	// to check if point before the first one or after the last one
	if (iPt == 0 || iPt == nCount) 
	{
		if (iPt == nCount)
			iPt--;
		if (Distance(point, m_ArrPoint[iPt]) < CURVE_NEAR_RANGE)
		{
			iIndex = iPt;
			return TRUE;
		}
		else
			return FALSE;		
	}	

	// to check if point near next key point in  m_ArrPoint
	if (Distance(point, m_ArrPoint[iPt]) < CURVE_NEAR_RANGE)
	{
		iIndex = iPt;
		return TRUE;
	}
	// to check if point near previous key point in  m_ArrPoint
	if (Distance(point, m_ArrPoint[iPt - 1]) < CURVE_NEAR_RANGE)
	{
		iIndex = iPt - 1;
		return TRUE;
	}

	// to check if point near the line defined by two key points:m_ArrPoint[iPt] and m_ArrPoint[iPt - 1]
	if (abs(m_ArrPoint[iPt].x - m_ArrPoint[iPt - 1].x) < CURVE_NEAR_RANGE)
	{		
		if ((point.y > m_ArrPoint[iPt].y && point.y < m_ArrPoint[iPt - 1].y)
			|| (point.y < m_ArrPoint[iPt].y && point.y > m_ArrPoint[iPt - 1].y))
		{
			return TRUE;
		}
		else 
			return FALSE;
	}
	else // the line defined by two key points:y = k * x + b
	{
		// k = (y2 - y1) / (x2 - x1)
		float k = float(m_ArrPoint[iPt].y - m_ArrPoint[iPt - 1].y) 
			      / float(m_ArrPoint[iPt].x - m_ArrPoint[iPt - 1].x);
		// b = (x2 * y1 - x1 * y2) / (x2 - x1)
		float b = float(m_ArrPoint[iPt].x * m_ArrPoint[iPt - 1].y - m_ArrPoint[iPt - 1].x * m_ArrPoint[iPt].y) 
			      / float(m_ArrPoint[iPt].x - m_ArrPoint[iPt - 1].x);
		
		float y = k * point.x + b;
		if (abs(int(y - point.y)) < CURVE_NEAR_RANGE)
			return TRUE;
	}		

	return FALSE;
}

// description  : static function, to calculate distance between two points
// in paramenter: pt1, pt2
float CCurve::Distance(const CPoint& pt1, const CPoint& pt2)
{
	return (float)sqrt((pt1.x - pt2.x) * (pt1.x - pt2.x) + (pt1.y - pt2.y) * (pt1.y - pt2.y));
}

/*=======================================================================================*/
// CCurveCtrl

IMPLEMENT_DYNCREATE(CCurveCtrl, CWnd)

// construct 
CCurveCtrl::CCurveCtrl()  
{
    RegisterWindowClass();

    _AFX_THREAD_STATE* pState = AfxGetThreadState();
    if (!pState->m_bNeedTerm && !AfxOleInit())
	{
		AfxMessageBox(_T("OLE initialization failed. Make sure that the OLE libraries are the correct version"));
	}

// init:
	m_fHoriMax = m_fVertMax = -FLT_MAX / 2;
	m_fHoriMin = m_fVertMin = FLT_MAX / 2;

	m_Margin = CRect(80, 50, 70, 40);
	m_crBack = RGB(255, 255, 255);		// white
	m_crGridLine = RGB(192, 192, 192);
	m_iGridLineStyle = PS_DOT;			// as: - - - - - 

	m_bShowCross	 = FALSE;
	m_crAxis = RGB(0, 0, 0);			// black

	m_bEdit   = FALSE;
	m_pCurveEdit = NULL;
	m_iCurPoint  = -1;
	m_strHoriLabel = _T("X");
	m_strVertLabel = _T("Y");

	m_iZoom = 0;
}

// deconstruct 
CCurveCtrl::~CCurveCtrl()
{
	// free memory
	RemoveAll();
}

BEGIN_MESSAGE_MAP(CCurveCtrl, CWnd)
	//{{AFX_MSG_MAP(CCurveCtrl)
	ON_WM_PAINT()
	ON_WM_MOUSEMOVE()
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_RBUTTONDOWN()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCurveCtrl message handlers
 
// description :create this window, use like any other window create control
// in parameter: rect       -- window rect
//               pParentWnd -- pointer of parent window 
//               nID        -- resource ID
//               dwStyle    -- style
BOOL CCurveCtrl::Create(const RECT& rect, CWnd* pParentWnd, UINT nID, DWORD dwStyle)
{
    ASSERT(pParentWnd->GetSafeHwnd());

    if (!CWnd::Create(CURVECTRL_CLASSNAME, NULL, dwStyle, rect, pParentWnd, nID))
        return FALSE;

	//Add ToolTips
	if (!m_Tooltip.Create(this))
		TRACE(_T("Unable to create tip window for CCurveCtrl."));
	else if (!m_Tooltip.AddTool(this, _T("Control ToolTips")))
		TRACE(_T("Unable to add tip for the control window for CCurveCtrl."));
	else
		m_Tooltip.Activate(TRUE);

    return TRUE;
}

BOOL CCurveCtrl::RegisterWindowClass()
{
    WNDCLASS wndcls;
    HINSTANCE hInst = AfxGetInstanceHandle();

    if (!(::GetClassInfo(hInst, CURVECTRL_CLASSNAME, &wndcls)))
    {
        // otherwise we need to register a new class
        wndcls.style            = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
        wndcls.lpfnWndProc      = ::DefWindowProc;
        wndcls.cbClsExtra       = wndcls.cbWndExtra = 0;
        wndcls.hInstance        = hInst;
        wndcls.hIcon            = NULL;
        wndcls.hCursor          = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
        wndcls.hbrBackground    = (HBRUSH) (COLOR_3DFACE + 1);
        wndcls.lpszMenuName     = NULL;
        wndcls.lpszClassName    = CURVECTRL_CLASSNAME;

        if (!AfxRegisterClass(&wndcls))
        {
            AfxThrowResourceException();
            return FALSE;
        }
    }

    return TRUE;
}

void CCurveCtrl::OnPaint() 
{
	CPaintDC dc(this); 
 	CMemDC memdc(&dc); // use CMemDC to avoid flicker

	CRect rect;
	GetClientRect(&rect);

	// background and margin    
	CBrush bkBrush(m_crBack);
	CBrush* pOldBrush = memdc.SelectObject(&bkBrush);	
 	memdc.FillRect(rect, &bkBrush);
	rect.InflateRect(1, 1, 0, 0);
	memdc.DrawEdge(&rect, EDGE_ETCHED, BF_TOPLEFT);
	memdc.SelectObject(pOldBrush);

	m_RectCoord = rect;
	m_RectCoord.DeflateRect(m_Margin);

 	DrawGrid(&memdc, m_RectCoord);

	ReCalcAllPoint();	// ! must do this before DrawCurve(...) 
	DrawCurve(&memdc);

	DrawCross(&memdc);
}

// description : draw grid line
// in parameter: pdc  -- pointer of CDC
//               rect -- paint area
void CCurveCtrl::DrawGrid(CDC *pdc, const CRect& rect)
{
	CPen	penStroke;	// pen for drawing grid line
	CPen	penGrid;
	
	// cells in horizontal and vertical, may be as member viariables of CCurveCtrl
	int nGrid = 10;		
	int nVertGrid = 10; 

	CPen penRect;
	penRect.CreatePen(0, 1, m_crAxis);
	CPen *pOldPentmp = pdc->SelectObject(&penRect);

	CBrush bkBrush(m_crBack);
	CBrush* pOldBrush=pdc->SelectObject(&bkBrush);	
		
	// draw rectangle aera contains all
	{
		CRect rctmp = rect;
		rctmp.DeflateRect(-1, -1, -1, -1);
		pdc->Rectangle(&rctmp);
	}
	pdc->SelectObject(pOldBrush);
 	pdc->SelectObject(pOldPentmp);

	penGrid.CreatePen(0, 1, m_crGridLine);
	CPen *pOldPen=pdc->SelectObject(&penGrid);

	penStroke.CreatePen(m_iGridLineStyle, 1, m_crGridLine);// grid line style
	pdc->SelectObject(&penStroke);
	// to draw grid line 
	{
		for(int i = 1; i < nVertGrid; i++)// in horizontal
		{
			pdc->MoveTo(rect.left, rect.top + i * rect.Height() / nVertGrid);
			pdc->LineTo(rect.right, rect.top + i * rect.Height() / nVertGrid);
		}

		for(int k = 1; k < nGrid; k++)// in vertical
		{
			pdc->MoveTo(rect.left + rect.Width() * k / nGrid, rect.bottom);
			pdc->LineTo(rect.left + rect.Width() * k / nGrid, rect.top);
		}
	}
	
	// draw label in horizontal
	pdc->SetTextColor(m_crAxis);	// the same color as axis' color
	pdc->SetBkMode(TRANSPARENT);
	int nTextHei;
	nTextHei = pdc->GetTextExtent(m_strHoriLabel).cy; // length of horizontal label

	CRect rectLabel;
	rectLabel.left   = m_RectCoord.left;
	rectLabel.right  = m_RectCoord.right;
	rectLabel.top	 = m_RectCoord.bottom + nTextHei + 15;
	rectLabel.bottom = rectLabel.top + nTextHei;
	pdc->DrawText(m_strHoriLabel, &rectLabel, DT_CENTER|DT_SINGLELINE);	

	// draw scale
	if (m_ArrCurve.GetSize())
	{
		CString   strScale;
		float	  fDlt = (m_fHoriEnd - m_fHoriBegin) / nGrid;

		if (fabs(fDlt) > CURVE_EPSILON)
		{
			for (int iScale = 0; iScale <= nGrid; iScale++)
			{
				strScale.Format(_T("%.1f"), int(10*(m_fHoriBegin + fDlt * iScale)) / 10.0f );
				CSize szLabel = pdc->GetTextExtent(strScale);
				if (szLabel.cx * (nGrid + 1) > m_RectCoord.Width() && (iScale % 2) == 1 )
					continue;

				pdc->TextOut(m_RectCoord.left + iScale * m_RectCoord.Width() / nGrid - szLabel.cx / 2, m_RectCoord.bottom + szLabel.cy / 2, strScale);
			}
		}
	}

	// draw label in vertical 
	CFont	*curFont=pdc->GetCurrentFont();
	LOGFONT	curLogFont;
	curFont->GetLogFont(&curLogFont);

	curLogFont.lfEscapement = 900; // rotate 90 degree
	CFont	newFont;
	newFont.CreateFontIndirect(&curLogFont);
	CFont* pOldFont = pdc->SelectObject(&newFont);

	CSize szLabel = pdc->GetTextExtent(m_strVertLabel);
	pdc->TextOut(m_RectCoord.left - 60, m_RectCoord.top + (szLabel.cy + m_RectCoord.Height()) / 2, m_strVertLabel);
	pdc->SelectObject(pOldFont);

	// draw scale
	if (m_ArrCurve.GetSize())
	{
		CString   strScale;
		float	  fDlt = (m_fVertMax - m_fVertMin) / nGrid;

		if (fabs(fDlt) > CURVE_EPSILON)
		{
			for (int iScale = 0; iScale <= nVertGrid; iScale++)
			{
				// use int(...) to avoid "-0.0"
				strScale.Format(_T("%.2f"), int(0.5f + 100 * (m_fVertMin + fDlt * iScale)) / 100.0f ); 
				CSize szLabel = pdc->GetTextExtent(strScale);
				if (szLabel.cy * (nVertGrid + 1) > m_RectCoord.Height() && (iScale % 2 == 1))
					continue;

				pdc->TextOut(m_RectCoord.left - szLabel.cx - 2, m_RectCoord.bottom - m_RectCoord.Height() * iScale/ nVertGrid - szLabel.cy / 2, strScale);
			}
		}
	}
	
	pdc->SelectObject(pOldPen);
}

// decription : connect neighbor points by drawing lines
void CCurveCtrl::DrawCurve(CDC *pdc)
{
	CPen		Pen;
	CBrush		brush;
	CPen*		pOldPen;
	CBrush*		pOldBrush;
	CCurve*		pCurve;

	int		iPoint;
	CRect	rect;
	int		nRadius;
	for (int iCurve = 0; iCurve < m_ArrCurve.GetSize(); iCurve++)
	{		
		pCurve = m_ArrCurve[iCurve];
		if ((!pCurve->IsVisible()) || (pCurve->m_ArrPoint.GetSize() < 1))
			continue;

		// create pen using CCurve's member variables
		Pen.CreatePen(pCurve->m_iStyle, pCurve->m_nWidth, pCurve->m_crColor);
		pOldPen = pdc->SelectObject(&Pen);		
		brush.CreateSolidBrush(pCurve->m_crColor);
		pOldBrush = pdc->SelectObject(&brush);	
		nRadius = pCurve->m_nWidth + 1;

		if ((1 == pCurve->m_ArrPoint.GetSize()) && (pCurve->m_ArrPoint[0] != INVALID_POINT))
		{
			// if there is only one point in m_ArrPoint, draw it 
			rect.top	= pCurve->m_ArrPoint[0].y - nRadius / 2;
			rect.bottom = pCurve->m_ArrPoint[0].y + nRadius / 2;
			rect.left	= pCurve->m_ArrPoint[0].x - nRadius / 2;
			rect.right	= pCurve->m_ArrPoint[0].x + nRadius / 2;

			pdc->Rectangle(rect);			
		}
		else   // draw connected line between neighbor key points in m_ArrPoint
		{
			// flag for whether already begin draw line
			BOOL	bStart = FALSE;		

			// make sure that line get to margin line if there are points outside drawing area 
			for (iPoint = 0; iPoint < pCurve->m_ArrPoint.GetSize(); iPoint++)
			{
					// if point in drawing area(INVALID_POINT)
					if (pCurve->m_ArrPoint[iPoint] == INVALID_POINT)
					{
							if (!bStart)  // if not begin drawing,then INVALID_PINT is foregoing points, don't draw it
							{
								continue;
							}
							else if (iPoint != pCurve->m_ArrPoint.GetUpperBound())  // if already beginning, then INVALID_PINT isback points
							{
								CPoint ptside;

								// using line equation to calculate value , and then point in pixel
								// k = (y2 - y1) / (x2 - x1)
								float k = float(pCurve->m_fArrVertValue[iPoint] - pCurve->m_fArrVertValue[iPoint + 1]) 
										  / float(pCurve->m_fArrHoriValue[iPoint] - pCurve->m_fArrHoriValue[iPoint + 1]);
								//b = (x2 * y1 - x1 * y2) / (x2 - x1)
								float b = float(pCurve->m_fArrHoriValue[iPoint] * pCurve->m_fArrVertValue[iPoint + 1]
												 - pCurve->m_fArrHoriValue[iPoint + 1] * pCurve->m_fArrVertValue[iPoint]) 
										 / float(pCurve->m_fArrHoriValue[iPoint] - pCurve->m_fArrHoriValue[iPoint + 1]);
								float fVside = k * m_fHoriEnd + b;
								
								CalculatePoint(m_fHoriEnd, fVside, ptside);
								pdc->LineTo(ptside);
							}

							break;   // return if connect to margin line
					}// end INVALID_POINT 

					if (!bStart) // begin drawing after first validate point
					{
						bStart = TRUE;

						// if the first validate point is out of drawing area, draw line to margin line
						if (iPoint > 0)
						{
							CPoint   ptside;
							if (fabs(pCurve->m_fArrHoriValue[iPoint] - pCurve->m_fArrHoriValue[iPoint - 1]) > CURVE_EPSILON)
							{
								// k = (y2 - y1) / (x2 - x1)
								float k = float(pCurve->m_fArrVertValue[iPoint] - pCurve->m_fArrVertValue[iPoint - 1]) 
										  / float(pCurve->m_fArrHoriValue[iPoint] - pCurve->m_fArrHoriValue[iPoint - 1]);
								//b = (x2 * y1 - x1 * y2) / (x2 - x1)
								float b = float(pCurve->m_fArrHoriValue[iPoint] * pCurve->m_fArrVertValue[iPoint - 1]
												 - pCurve->m_fArrHoriValue[iPoint - 1] * pCurve->m_fArrVertValue[iPoint]) 
										 / float(pCurve->m_fArrHoriValue[iPoint] - pCurve->m_fArrHoriValue[iPoint - 1]);
								float fVside = k * m_fHoriBegin + b;

								CalculatePoint(m_fHoriBegin, fVside, ptside);
							}
							else
							{
								ptside = CPoint(m_RectCoord.left, pCurve->m_ArrPoint[iPoint-1].y);
							}
							pdc->MoveTo(ptside);
						}
						else
							pdc->MoveTo(pCurve->m_ArrPoint[iPoint]);
					} // end first validate point
				
					// connect line between neighbor points
					pdc->LineTo(pCurve->m_ArrPoint[iPoint]);
					if (pCurve->m_bSelected)
					{
						rect.top	= pCurve->m_ArrPoint[iPoint].y - nRadius;
						rect.bottom = pCurve->m_ArrPoint[iPoint].y + nRadius;
						rect.left	= pCurve->m_ArrPoint[iPoint].x - nRadius;
						rect.right	= pCurve->m_ArrPoint[iPoint].x + nRadius;
						pdc->Ellipse(rect);
					}
			} //end for pCurve->m_ArrPoint 
		}
		pdc->SelectObject(pOldBrush);
		pdc->SelectObject(pOldPen);
		brush.DeleteObject();
		Pen.DeleteObject();	
	} // end for m_ArrCurve 
}

// decription  : add a curve
// in parameter: strName -- curve name, must not empty
//               color   -- curve line color
//               iStyle  -- curve line style
//               nWidth  -- curve line width
// return value: index of the added curve in CCurveCtrl
int	CCurveCtrl::AddCurve(const CString& strName, COLORREF color, int iStyle, int nWidth)
{
	if (strName.IsEmpty())
		return -1;
	for (int iCurve = 0; iCurve < m_ArrCurve.GetSize(); iCurve++)
	{
		if (strName == m_ArrCurve[iCurve]->m_strName)
			return -1;
	}

	CCurve*	pCurve = new CCurve;
	pCurve->m_strName = strName;
	pCurve->m_crColor = color;
	pCurve->m_iStyle = iStyle;
	pCurve->m_nWidth = nWidth;

	// restore zoom
	m_iZoom = 0;
	
	return m_ArrCurve.Add(pCurve);
}

// description  : add data to an exist curve
// in parameter : strName    -- curve name
//                fHoriValue -- horizontal value
//                fVertValue -- vertical value
// return value : TRUE: data added to curve; FALSE: curve is empty or there are no curve named as strName
BOOL CCurveCtrl::AddData(const CString& strName, float fHoriValue, float fVertValue)
{
	if (strName.IsEmpty()) 
		return FALSE;

	CCurve*		pCurve = NULL;
	for (int iCurve = 0; iCurve < m_ArrCurve.GetSize(); iCurve++)
	{
		if (strName == m_ArrCurve[iCurve]->m_strName)
		{
			pCurve = m_ArrCurve[iCurve];
			break;
		}
	}

	// following AddData(...) will check whether pCurve is NULL
	return AddData(pCurve, fHoriValue, fVertValue);
}

// description  : add data to an exist curve
// in parameter : pCurve     -- curve pointer in this CCurveCtrl
//                fHoriValue -- horizontal value
//                fVertValue -- vertical value
// return value : TRUE: data added to curve; FALSE: pointer of curve invalidate
BOOL CCurveCtrl::AddData(CCurve* pCurve, float fHori, float fVert)
{
	if (!pCurve)
		return FALSE;

	// ad data to array, inserted position decided by horizontal value
	ASSERT(pCurve->m_fArrHoriValue.GetSize() == pCurve->m_fArrVertValue.GetSize());
	InsertDataToCurve(pCurve, fHori, fVert);

	// save max and min values in horizontal
	m_fHoriMax = max(m_fHoriMax, fHori);
	m_fHoriMin = min(m_fHoriMin, fHori);

	CalculateVertRange(fVert, TRUE);
	CalculateVertRange(fVert, FALSE);
	m_fHoriBegin = m_fHoriMin;
	m_fHoriEnd   = m_fHoriMax;

	return TRUE;	
}

// decription  : insert data to data array which already sorted by horizontal value
// in parameter: pCurve -- Curve object pointer
//               fHori  -- horizontal value 
//               fVert  -- vertical value
//               point  -- corresponding point in pixel
// return value: index of the added data in data array
int CCurveCtrl::InsertDataToCurve(CCurve* pCurve, float fHori, float fVert, CPoint point)
{
	if (!pCurve)
		return -1;
	ASSERT(pCurve->m_fArrHoriValue.GetSize() == pCurve->m_fArrVertValue.GetSize());
	ASSERT(pCurve->m_fArrHoriValue.GetSize() == pCurve->m_ArrPoint.GetSize());
	
	for (int iIndex = pCurve->m_fArrHoriValue.GetUpperBound(); iIndex >= 0; iIndex--)
	{
		if (pCurve->m_fArrHoriValue[iIndex] < fHori)
		{
			break;
		}		
	}

	if (iIndex == pCurve->m_fArrHoriValue.GetUpperBound())
	{
		pCurve->m_ArrPoint.Add(point);
		pCurve->m_fArrHoriValue.Add(fHori);
		pCurve->m_fArrVertValue.Add(fVert);
	}
	else
	{
		pCurve->m_ArrPoint.InsertAt(iIndex + 1, point);
		pCurve->m_fArrHoriValue.InsertAt(iIndex + 1, fHori);
		pCurve->m_fArrVertValue.InsertAt(iIndex + 1, fVert);
	}
		
	return iIndex + 1;
}

// decription  : add one curve and copy it's data to data array
// in parameter: strName -- curve name
//               ArrHori -- data array of horizontal value
//               ArrVert -- data array of vertical value
// return value: TRUE if success
BOOL CCurveCtrl::AddCurveData(const CString& strName, const CArray< float, float >& ArrHori, const CArray< float, float >& ArrVert)
{
	int iCurve = AddCurve(strName);
	if (iCurve < 0 || (ArrHori.GetSize() != ArrVert.GetSize()))
		return FALSE;		

	// copy data
	m_ArrCurve[iCurve]->m_fArrHoriValue.Copy(ArrHori);
	m_ArrCurve[iCurve]->m_fArrVertValue.Copy(ArrVert);
	// make sure point array has the same size as the data arrays
	m_ArrCurve[iCurve]->m_ArrPoint.SetSize(ArrHori.GetSize()); 

	// remember the max and min value
	int iIndex;
	for (iIndex = 0; iIndex < ArrHori.GetSize(); iIndex++)
	{
		m_fHoriMax = max(m_fHoriMax, ArrHori[iIndex]);
		m_fHoriMin = min(m_fHoriMin, ArrHori[iIndex]);
	}

	// to adjust max and min value in vertical for mouse editing
	float fVMax = -FLT_MAX / 2;
	float fVMin = FLT_MAX / 2;
	for (iIndex = 0; iIndex < ArrVert.GetSize(); iIndex++)
	{
		fVMax = max(fVMax, ArrVert[iIndex]);
		fVMin = min(fVMin, ArrVert[iIndex]);		
	}
		
	SortCurveData(m_ArrCurve[iCurve]);

	CalculateVertRange(fVMax, TRUE);
	CalculateVertRange(fVMin, FALSE);

	m_fHoriBegin = m_fHoriMin;
	m_fHoriEnd   = m_fHoriMax;
	
	return TRUE;
}

// decription  : sort data to make sure all arrays order by horizontal value
void CCurveCtrl::SortCurveData(CCurve* pCurve)
{
	int nCount = pCurve->m_fArrHoriValue.GetSize();
	
	ASSERT(nCount == pCurve->m_fArrVertValue.GetSize());
	ASSERT(nCount == pCurve->m_ArrPoint.GetSize());

	int		iPos;
	float	fTemp;
	CPoint	PtTemp;
	for (int iPre = 0; iPre < nCount - 1; iPre++)
	{
		iPos = iPre;
		for (int iAft = iPre + 1; iAft < nCount; iAft++)
		{
			if (pCurve->m_fArrHoriValue[iPre] > pCurve->m_fArrHoriValue[iAft])
			{
				iPos = iAft;				
			}
		}

		// exchange
		if (iPos != iPre)
		{
			// horizontal value
			fTemp = pCurve->m_fArrHoriValue[iPre];
			pCurve->m_fArrHoriValue[iPre] = pCurve->m_fArrHoriValue[iPos];
			pCurve->m_fArrHoriValue[iPos] = fTemp;

			// vertical value
			fTemp = pCurve->m_fArrVertValue[iPre];
			pCurve->m_fArrVertValue[iPre] = pCurve->m_fArrVertValue[iPos];
			pCurve->m_fArrVertValue[iPos] = fTemp;

			// point
			PtTemp = pCurve->m_ArrPoint[iPre];
			pCurve->m_ArrPoint[iPre] = pCurve->m_ArrPoint[iPos];
			pCurve->m_ArrPoint[iPos] = PtTemp;
		}
	}
}

// decription  : get CCurve object pointer by its index in this CCurveCtrl 
CCurve* CCurveCtrl::GetCurve(int iIndex)
{
	if (iIndex > m_ArrCurve.GetUpperBound() || iIndex < 0)
		return NULL;

	return m_ArrCurve[iIndex];
}

// decription  : get CCurve object pointer by its name
CCurve* CCurveCtrl::GetCurve(const CString& strName)
{
	CCurve*		pCurve = NULL;
	for (int iCurve = 0; iCurve < m_ArrCurve.GetSize(); iCurve++)
	{
		if (strName == m_ArrCurve[iCurve]->m_strName)
		{
			pCurve = m_ArrCurve[iCurve];
			break;			
		}
	}

	return pCurve;
}

// decription  : get index in this CCurveCtrl by CCurve object pointer
int	CCurveCtrl::GetIndex(const CCurve* pCurve)
{
	int iPos = -1;
	for (int iCur = 0; iCur < m_ArrCurve.GetSize(); iCur++)
	{
		if (pCurve == m_ArrCurve[iCur])
		{
			iPos = iCur;
			break;
		}
	}

	return iPos;
}

// decription  : remove one curve by its index
BOOL CCurveCtrl::Remove(int index)
{
	if (index < 0 || index > m_ArrCurve.GetUpperBound())
		return FALSE;

	delete m_ArrCurve[index];
	m_ArrCurve[index] = NULL;
	m_ArrCurve.RemoveAt(index);

	// disable edit mode if no curve selected
	if (GetSelectedCount() < 1)
		m_bEdit = FALSE;

	return TRUE;
}

// decription  : remove one curve by its name
BOOL CCurveCtrl::Remove(const CString& strName)
{
	for (int iCurve = 0; iCurve < m_ArrCurve.GetSize(); iCurve++)
	{
		if (strName == m_ArrCurve[iCurve]->m_strName)
		{
			delete m_ArrCurve[iCurve];
			m_ArrCurve[iCurve] = NULL;
			m_ArrCurve.RemoveAt(iCurve);

			return TRUE;
		}
	}

	return FALSE;
}

// decription  : remove all curve object in this CCurveCtrl
void CCurveCtrl::RemoveAll()
{
	for (int iC = 0; iC < m_ArrCurve.GetSize(); iC++)
	{
		delete m_ArrCurve[iC];
		m_ArrCurve[iC] = NULL;		
	}
	m_ArrCurve.RemoveAll();

	// there are no curve, so disable edit
	m_bEdit = FALSE;
}

// decription  : get curve count in this CCurveCtrl
int	CCurveCtrl::GetCurveCount() 
{
	return m_ArrCurve.GetSize(); 
}

// decription  : get selected curve count
int	CCurveCtrl::GetSelectedCount()
{
	int nSelected = 0;
	
	for (int iCurve = 0; iCurve < m_ArrCurve.GetSize(); iCurve++)
	{
		if (m_ArrCurve[iCurve]->m_bSelected)
			nSelected++;
	}

	return nSelected;
}

// decription   : calculate point in pixel by horizontal value and vertical value
// in parameter : fHori -- horizontal value
//                fVert -- vertical value
// out parameter: point -- point in pixel, INVALID_POINT if out of drawing area
BOOL CCurveCtrl::CalculatePoint(float fHori, float fVert, CPoint& point)
{
	point = INVALID_POINT;

	float fHoriDiff = m_fHoriEnd - m_fHoriBegin;
	float fHoriDlt = fHori - m_fHoriBegin;
	if ( fHoriDlt < 0 || fHoriDiff <= 0 || (m_fHoriEnd - fHori) < 0)
		return FALSE;

	float fVertDiff = m_fVertMax - m_fVertMin;
	float fVertDlt  = fVert - m_fVertMin;
	if ( fVertDlt < 0 || fVertDiff <= 0 )
		return FALSE;

	float fHoriCof = fHoriDlt / fHoriDiff;	
	float fVertCof = fVertDlt / fVertDiff;		
	
	point.x = m_RectCoord.left + int(m_RectCoord.Width() * fHoriCof + 0.5f);
	point.y = m_RectCoord.bottom - int(m_RectCoord.Height() * fVertCof + 0.5f);


	return TRUE;
}

// decription   : calculate horizontal value and vertical value by point in pixel
// in parameter : point -- point in pixel
// out parameter: fHori -- horizontal value
//                fVert -- vertical value
// return value : TRUE if success, and FALSE if point out of rect area in which drawing curve 
BOOL CCurveCtrl::CalculateValue(const CPoint& point, float& fHori, float& fVert)
{
	if (!m_ArrCurve.GetSize())
		return FALSE;
	
	// if point is out of drawing area
	if (m_RectCoord.PtInRect(point))
	{		
		float fHCof = float(point.x - m_RectCoord.left) / float(m_RectCoord.Width());
		fHori = m_fHoriBegin + fHCof * (m_fHoriEnd - m_fHoriBegin);

		float fVCof = float(point.y - m_RectCoord.top) / float(m_RectCoord.Height());
		fVert = m_fVertMax - fVCof * (m_fVertMax - m_fVertMin); 

		return TRUE;
	}
	return FALSE;
}

void CCurveCtrl::ReCalcAllPoint()
{
	
	for (int iCurve = 0; iCurve < m_ArrCurve.GetSize(); iCurve++)
	{
		CCurve*	pCurve = m_ArrCurve[iCurve];
		ASSERT(pCurve->m_fArrHoriValue.GetSize() == pCurve->m_fArrVertValue.GetSize());

		pCurve->m_ArrPoint.SetSize(pCurve->m_fArrHoriValue.GetSize());
		
		for (int iPoint = 0; iPoint < pCurve->m_ArrPoint.GetSize(); iPoint++)
		{
			CalculatePoint(pCurve->m_fArrHoriValue[iPoint], pCurve->m_fArrVertValue[iPoint], pCurve->m_ArrPoint[iPoint]);
		}
	}
}

// decription   : draw cross lines and output string of corresponding values
void CCurveCtrl::DrawCross(CDC *pdc)
{
	CPoint point;
	GetCursorPos(&point);
	ScreenToClient(&point);	

	if(PtInRect(m_RectCoord, point)) 
	{
		// cross lines
		if (/*!m_bEdit &&*/ m_bShowCross)
		{	
			// color: using m_crAxis 
			CPen pentmp(PS_SOLID, 1, m_crAxis);
			CPen *pOldPentmp=pdc->SelectObject(&pentmp);

			pdc->MoveTo(point.x, m_RectCoord.top);
			pdc->LineTo(point.x, m_RectCoord.bottom);
			pdc->MoveTo(m_RectCoord.left, point.y);
			pdc->LineTo(m_RectCoord.right, point.y);

			pdc->SelectObject(pOldPentmp);
		}

		// out put string to top-right outside of the drawing rect
		float		fHori, fVert;
		CString		str;
		CSize		szText;
		if (CalculateValue(point, fHori, fVert))
		{
			str.Format(_T("%.1f, %.1f"), int(10 * fHori) / 10.0f, int(10 * fVert) / 10.0f);
			szText = pdc->GetTextExtent(str);
			pdc->TextOut(m_RectCoord.right - szText.cx, m_RectCoord.top - szText.cy - 2, str);				
		}
	}
}

// decription   : disable base function to avoid flicker
BOOL CCurveCtrl::OnEraseBkgnd(CDC* /*pDC*/) 
{
//	return CWnd::OnEraseBkgnd(pDC);
	return TRUE;	
}

// description : move curves and coordinate scale, data of curves not changed
// in parameter: bLeft -- TRUE: move left; FALSE: move right
void CCurveCtrl::Move(BOOL bLeft)
{
	int nHoriGrid = 10;
	float fPerGird = (m_fHoriEnd - m_fHoriBegin) / nHoriGrid;

	if (bLeft && (m_fHoriBegin + fPerGird < m_fHoriMax))      
	{
		// move left, and make sure there is at least one visible point in drawing area
		m_fHoriBegin += fPerGird;
		m_fHoriEnd   += fPerGird;
	}
	else if (!bLeft && (m_fHoriEnd - fPerGird > m_fHoriMin))  
	{
		// move right
		m_fHoriBegin -= fPerGird;
		m_fHoriEnd   -= fPerGird;
	}
	
	Invalidate();
}

// description : move one page, refer to Move(BOOL bLeft);
void CCurveCtrl::MovePage(BOOL bLeft)
{
	float fPerPage = m_fHoriEnd - m_fHoriBegin;

	if (bLeft && (m_fHoriBegin + fPerPage < m_fHoriMax))     // move left
	{
		m_fHoriBegin += fPerPage;
		m_fHoriEnd   += fPerPage;
	}
	else if (!bLeft && (m_fHoriEnd - fPerPage > m_fHoriMin)) // move right
	{
		m_fHoriBegin -= fPerPage;
		m_fHoriEnd   -= fPerPage;
	}
	Invalidate();
}

// description : shift a curve right, will modify the data of the curve
//               that is, add offset to each element in data array of horizontal value
// in parameter: pCurve -- curve object
//               fLen   -- offset
void CCurveCtrl::CurveRight(CCurve* pCurve, float fLen)
{
	if (pCurve == NULL)
		return;

	for (int iIndex = 0; iIndex < pCurve->m_ArrPoint.GetSize(); iIndex++)
	{
		pCurve->m_fArrHoriValue[iIndex] += fLen;
		// max value 
		m_fHoriMax = max(m_fHoriMax, pCurve->m_fArrHoriValue[iIndex]);
	}
	
	Restore();
}

// description : shift a curve left, will modify the data of the curve
//               that is, subtract offset to each element in data array of horizontal value
// in parameter: pCurve -- curve object
//               fLen   -- offset
void CCurveCtrl::CurveLeft(CCurve* pCurve, float fLen)
{
	if (pCurve == NULL)
		return;

	for (int iIndex = 0; iIndex < pCurve->m_ArrPoint.GetSize(); iIndex++)
	{
		pCurve->m_fArrHoriValue[iIndex] -= fLen;
		// min value
		m_fHoriMin = min(m_fHoriMin, pCurve->m_fArrHoriValue[iIndex]);
	}
	
	Restore();
}

// description : show all points in all visible curves
void CCurveCtrl::Restore()
{
	float fVMin, fVMax;
	m_fHoriMin = FLT_MAX / 2;
	m_fHoriMax = -FLT_MAX / 2;
	m_fVertMin = fVMin = FLT_MAX / 2;
	m_fVertMax = fVMax = -FLT_MAX / 2;

	// max and min value
	for (int iCur = 0; iCur < m_ArrCurve.GetSize(); iCur++)
	{
		for (int iPt = 0; iPt < m_ArrCurve[iCur]->m_fArrHoriValue.GetSize(); iPt++)
		{
			m_fHoriMin = min(m_fHoriMin, m_ArrCurve[iCur]->m_fArrHoriValue[iPt]);
			m_fHoriMax = max(m_fHoriMax, m_ArrCurve[iCur]->m_fArrHoriValue[iPt]);
			
			fVMin = min(fVMin, m_ArrCurve[iCur]->m_fArrVertValue[iPt]);
			fVMax = max(fVMax, m_ArrCurve[iCur]->m_fArrVertValue[iPt]);	
		}
	}

	m_fHoriBegin = m_fHoriMin;
	m_fHoriEnd   = m_fHoriMax;

	// adjust
	CalculateVertRange(fVMin, FALSE);
	CalculateVertRange(fVMax, TRUE);

	// restore zoom
	m_iZoom = 0;

	Invalidate();
}

// description : zoom in or out curves
// in parameter: bIn -- TRUE: out; FALSE: in
BOOL CCurveCtrl::Zoom(BOOL bIn)
{
	bIn ? m_iZoom++ : m_iZoom--;
	if (m_iZoom == 4)
	{
		m_iZoom--;
		return FALSE;
	}
	else if (m_iZoom == -4)
	{
		m_iZoom++;
		return FALSE;
	}

	float fDiff = (m_fHoriMax - m_fHoriMin) / 4.0f;
	
	m_fHoriEnd = m_fHoriMax - m_iZoom * fDiff;
	Invalidate();

	return TRUE;
}

// description : mirror one curve in horizontal
// in parameter: pCurve -- curve object
//               fMid   -- center value 
void CCurveCtrl::MirrorHori(CCurve* pCurve, float fMid)
{
	if (!pCurve)
		return;

	for (int iIndex = 0; iIndex < pCurve->m_fArrHoriValue.GetSize(); iIndex++)
	{
		pCurve->m_fArrHoriValue[iIndex] = 2 * fMid - pCurve->m_fArrHoriValue[iIndex];
	}

	// resort
	SortCurveData(pCurve);	
	
	Restore();
	
	Invalidate();
}

// description : mirror one curve in vertical
// in parameter: pCurve -- curve object
//               fMid   -- center value 
void CCurveCtrl::MirrorVert(CCurve* pCurve, float fMid)
{
	if (!pCurve)
		return;

	for (int iIndex = 0; iIndex < pCurve->m_fArrVertValue.GetSize(); iIndex++)
	{
		pCurve->m_fArrVertValue[iIndex] = 2 * fMid - pCurve->m_fArrVertValue[iIndex];
	}

	// need not resort
	Restore();
	Invalidate();
}

// description : calculate vertical range to show, adjust max and min value to avoid drawing
//               points on margin line
// in parameter: fValue -- vertical value
//               bUp    -- TRUE: max value; FALSE: min value
void CCurveCtrl::CalculateVertRange(float fValue, BOOL bMax)
{
	CString str;
	str.Format(_T("%.0f"), fabs(fValue));
	// get integer digital count
	int nEx   = str.GetLength() - 1;
 	int ntemp = int(pow(10, nEx));

	if (bMax && (fValue + ntemp * 0.1) > m_fVertMax)
	{		
		m_fVertMax = fValue + 0.1f * ntemp;
	}
	else if (!bMax && (fValue - ntemp * 0.1) < m_fVertMin)
	{
		m_fVertMin = fValue - 0.1f * ntemp;
	}	
}

void CCurveCtrl::SetHoriLabel(CString& str)
{
	m_strHoriLabel = str;
}

CString CCurveCtrl::GetHoriLabel()
{
	return m_strHoriLabel;
}

void CCurveCtrl::SetVertLabel(CString& str)
{
	m_strVertLabel = str;
}

CString CCurveCtrl::GetVertLabel()
{
	return m_strVertLabel;
}

void CCurveCtrl::ShowCross(BOOL bShow)
{
	m_bShowCross = bShow;
}

BOOL CCurveCtrl::IsShowCross()
{
	return m_bShowCross;
}

// description : set margin, leave a blank in each direction outside of drawing area 
//               to show title, label, and so on
void CCurveCtrl::SetMargin(const CRect& rect)
{
	m_Margin = rect;
}

CRect CCurveCtrl::GetMargin()
{
	return m_Margin;
}

// enable/disable edit by using mouse key operation
void CCurveCtrl::EnableEdit(BOOL bEdit)
{
	if (GetSelectedCount() > 0)
		m_bEdit = bEdit;
	else
		m_bEdit = FALSE;
}

BOOL CCurveCtrl::CanEditCurve()
{
	return m_bEdit;
}

void CCurveCtrl::SetGridLineStyle(int iStyle)
{
	m_iGridLineStyle = iStyle;
}

int	 CCurveCtrl::GetGridLineStyle()
{
	return m_iGridLineStyle;
}

// description : non-edit mode: select/deselect one or more(by Shift or Ctrl key pressed) curves 
//                              by clicking left button of mouse;
//               edit mode: if parameter "point" is near key point of a curve, select key point and ready to move;
//                          if parameter "point" is not near key point but near to line between two key points, add key point.
//                          note that the edited curve must be selected before editing.
//                
void CCurveCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{	
	// disable tooltip	
	if (m_Tooltip.m_hWnd)
		m_Tooltip.Activate(FALSE);	
	
	int iPt = -1;
	CCurve*	 pCurentCur = NULL;
	
	// has any curve near point
	for (int iIndex = 0; iIndex < m_ArrCurve.GetSize(); iIndex++)
	{
		if (m_ArrCurve[iIndex]->IsPointNearCurve(point, iPt))
		{
			pCurentCur = m_ArrCurve[iIndex];
			break;
		}
	}

	// calculate value by point in pixel
	float fHori = 0.0f, fVert = 0.0f;
	if (pCurentCur)
	{
		if (iPt > -1)
		{
			fHori = pCurentCur->m_fArrHoriValue[iPt];
			fVert = pCurentCur->m_fArrVertValue[iPt];
		}
		else 
		{
			CalculateValue(point, fHori, fVert);
		}
	}
	
	// non-edit mode
	if (!m_bEdit)
	{			
		// multi-select if Ctrl or Shift key are pressed
		if ((nFlags & MK_CONTROL) == MK_CONTROL || (nFlags & MK_SHIFT) == MK_SHIFT)
		{
			if (pCurentCur)
			{				
				// save previous status(selected or not) 
				BOOL bOldSel = pCurentCur->m_bSelected;
				// change status of the curve (deselected or not)
				pCurentCur->m_bSelected = !pCurentCur->m_bSelected;
				
				// send message to owner
				int nMsg = pCurentCur->m_bSelected ? CVN_CURVE_SELECTED : CVN_CURVE_CANCELSELECT;					
				BOOL bKeepOld = SendMessageToParent(nMsg,
													pCurentCur,
													iIndex,	// curve index in this CCurveCtrl
													fHori,
													fVert					
													);
				// restore previous status if owner does not permit to change 
				if (bKeepOld)
				{
					pCurentCur->m_bSelected = bOldSel;
				}
			}
		}
		else // single-select
		{			
			// deselect all curve
			for (int iQ = 0; iQ < m_ArrCurve.GetSize(); iQ++)
			{
				m_ArrCurve[iQ]->m_bSelected = FALSE;
			}

			if (pCurentCur != NULL)
			{		
				// save previous status(selected or not) 
				BOOL bOldSel = pCurentCur->m_bSelected;
				// change status of the curve (deselected or not)
				pCurentCur->m_bSelected = !pCurentCur->m_bSelected;

				// send message to owner
				int nMsg = pCurentCur->m_bSelected ? CVN_CURVE_SELECTED : CVN_CURVE_CANCELSELECT;					
				BOOL bKeepOld = SendMessageToParent(nMsg,
													pCurentCur,
													iIndex,	// curve index in this CCurveCtrl
													fHori,
													fVert					
													);
				// restore previous status if owner does not permit to change 
				if (bKeepOld)
				{
					pCurentCur->m_bSelected = bOldSel;
				}
			}
			else 			// send message to owner if no curve is selected 
			{
				SendMessageToParent(CVN_CURVE_SELECTNONE,
							        pCurentCur,
									-1, // no curve selected
									fHori,
									fVert
									);
				
				// disable edit mode if no curve selected
				m_bEdit = FALSE;
			}
		}
	}
	else if(pCurentCur && pCurentCur->IsSelected())// under edit mode, the edited curve must be selected already
	{
		// if index of key point of the curve is valid, move point when mouse move
		if (iPt >= 0)
		{
			m_iCurPoint  = iPt;
			m_pCurveEdit = pCurentCur;
			SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS));
			SetCapture();

			// save value before moving
			m_fOldHori = pCurentCur->m_fArrHoriValue[iPt];
			m_fOldVert = pCurentCur->m_fArrVertValue[iPt];
		}
		else // if index invalidate, then add key point to the curve
		{			
			// calculate value by point
			float fHori, fVert;
			CalculateValue(point, fHori, fVert);
			
			// insert values to sorted data arrays, and get the index of the new key point
			int iH = InsertDataToCurve(pCurentCur, fHori, fVert, point);

			// to notify owner that new key point is added 
			int bDontAdd = SendMessageToParent(CVN_MVALUE_ADD, 
											pCurentCur, 
											iH, 
											fHori,
											fVert
											);			
			// whether to add the new point is determined by owner
			if (bDontAdd)
			{
				pCurentCur->m_fArrHoriValue.RemoveAt(iH);
				pCurentCur->m_fArrVertValue.RemoveAt(iH);
				pCurentCur->m_ArrPoint.RemoveAt(iH);
			}
			
			m_iCurPoint  = -1;
			m_pCurveEdit = NULL;
		}
	}
	
	Invalidate();
	
	CWnd::OnLButtonDown(nFlags, point);
}

// description : non-edit mode: refresh to draw cross(if shown) and tooltip
//               edit mode: change cursor and move the selected key point
void CCurveCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{	
	// change point if has any key point selected
	if (m_bEdit && m_pCurveEdit && m_iCurPoint >= 0)
	{
		SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS));
		
		float fHori, fVert;
		if (CalculateValue(point, fHori, fVert))
		{
			// change vertical value only
			m_pCurveEdit->m_ArrPoint[m_iCurPoint].y = point.y;
			m_pCurveEdit->m_fArrVertValue[m_iCurPoint] = fVert;		
		}
	}
	else
	{		
		// if there is any key point nearby
		int iPoint = -1;
		CCurve* pCurentCur = NULL;
		for (int iIndex = 0; iIndex < m_ArrCurve.GetSize(); iIndex++)
		{
			if (m_ArrCurve[iIndex]->IsPointNearCurve(point, iPoint))
			{
				pCurentCur = m_ArrCurve[iIndex];
				break;
			}
		}

		// if parameter point is near one key point of a selected curve
		if (pCurentCur && iPoint >= 0 && pCurentCur->IsSelected())
		{
			// change cursor
			if (m_bEdit)
				SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS));
			
			// use tooltip to show the values of key point nearby
			CString		strTip;
			strTip.Format(_T("%s[%d]: %.1f,%.1f"), pCurentCur->GetCurveName(), iPoint, pCurentCur->m_fArrHoriValue[iPoint], pCurentCur->m_fArrVertValue[iPoint]);
			
			if(m_Tooltip.m_hWnd)
			{
				m_Tooltip.Activate(TRUE);			
				m_Tooltip.UpdateTipText(strTip, this);

				MSG msg;
				msg.hwnd= m_hWnd;
				msg.message= WM_MOUSEMOVE;
				msg.wParam= nFlags;
				msg.lParam= MAKELPARAM(LOWORD(point.x), LOWORD(point.y));
				msg.time= 0;
				msg.pt.x= point.x;
				msg.pt.y= point.y;
				
				m_Tooltip.RelayEvent(&msg);
			}
		}
	}
	
	Invalidate();

	CWnd::OnMouseMove(nFlags, point);
}

// description : handle left mouse button up, notify owner if data changed
void CCurveCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if (m_bEdit && m_pCurveEdit && m_iCurPoint >=0)
	{
		BOOL bKeepOld = SendMessageToParent(CVN_MVALUE_CHANG, 
											m_pCurveEdit, 
											m_iCurPoint, 
											m_pCurveEdit->m_fArrHoriValue[m_iCurPoint],
											m_pCurveEdit->m_fArrVertValue[m_iCurPoint]
											);

		if (bKeepOld) // owner does not permit to change data, restore previous values
		{
			m_pCurveEdit->m_fArrHoriValue[m_iCurPoint] = m_fOldHori;
			m_pCurveEdit->m_fArrVertValue[m_iCurPoint] = m_fOldVert;
		}
		else     
		{			
			CalculateVertRange(m_pCurveEdit->m_fArrVertValue[m_iCurPoint], TRUE);
			CalculateVertRange(m_pCurveEdit->m_fArrVertValue[m_iCurPoint], FALSE);
		}

		// cursor style
		SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));

		m_pCurveEdit = NULL;
		m_iCurPoint = -1;
		ReleaseCapture();
		Invalidate();
	}	

	CWnd::OnLButtonUp(nFlags, point);
}

// description : delete key point after right mouse button clicked when enable edit
void CCurveCtrl::OnRButtonDown(UINT nFlags, CPoint point) 
{
	if (m_bEdit)
	{
		// get the nearby curve
		int			iPt = -1;
		CCurve*		pCurentCur = NULL;
		for (int iIndex = 0; iIndex < m_ArrCurve.GetSize(); iIndex++)
		{
			if (m_ArrCurve[iIndex]->IsPointNearCurve(point, iPt))
			{
				pCurentCur = m_ArrCurve[iIndex];
				break;
			}
		}

		// if there is one  and be selected and near point
		if (pCurentCur && pCurentCur->IsSelected() && iPt >= 0)
		{
			int bKeep = SendMessageToParent(CVN_MVALUE_DELETE, 
											pCurentCur, 
											iPt, 
											pCurentCur->m_fArrHoriValue[iPt],
											pCurentCur->m_fArrVertValue[iPt]
											);
			// whether delete the point determined by message handle function
			if (!bKeep)
			{
				pCurentCur->m_ArrPoint.RemoveAt(iPt);
				pCurentCur->m_fArrHoriValue.RemoveAt(iPt);
				pCurentCur->m_fArrVertValue.RemoveAt(iPt);
				
				// remove the curve object if there is no point in it
				if (pCurentCur->m_fArrHoriValue.GetSize() == 0)
				{
					ASSERT(m_ArrCurve[iIndex] == pCurentCur);
					delete pCurentCur;
					m_ArrCurve.RemoveAt(iIndex);
				}
			}
			
			Invalidate();
		}
	}	
	
	CWnd::OnRButtonDown(nFlags, point);
}

// description : notify owner that curve mode or data changed
// in parameter: nMessage -- message id
//               pCurve   -- curve object
//               iIndex   -- index of curve or key point
//               fHori    -- horizontal value
//               fVert    -- vertical value
// return      :  0:permit to change;  else: do not change
LRESULT CCurveCtrl::SendMessageToParent(int nMessage, CCurve* pCurve, int iIndex, float fHori, float fVert) const
{
    if (!IsWindow(m_hWnd))
        return 0;

	// struct to save parameter
    NM_CURVECTRL nmcurve;

	nmcurve.pCurve		 = (void*)pCurve;
	nmcurve.iIndex		 = iIndex;
	nmcurve.fHori		 = fHori;
	nmcurve.fVert		 = fVert;	
    nmcurve.hdr.hwndFrom = m_hWnd;
    nmcurve.hdr.idFrom   = GetDlgCtrlID();
    nmcurve.hdr.code     = nMessage;

	// send message to owner, owner is parent or not
    CWnd *pOwner = GetOwner();
    if (pOwner && IsWindow(pOwner->m_hWnd))
        return pOwner->SendMessage(WM_NOTIFY, nmcurve.hdr.idFrom, (LPARAM)&nmcurve);
    else
        return 0;
}