ASE Home Page Products Download Purchase Support About ASE
ChartDirector Support
Forum HomeForum Home   SearchSearch

Message ListMessage List     Post MessagePost Message

  Break point generated during real-time display
Posted by Ian Hammond on Feb-08-2018 18:27
Attachments:
Hi,

I have an application displaying 2 data channels on a real-time display. After about 9 hours
the application generated a break point in the method CStaticHelper::bmpToHBITMAP
at the line:

memcpy(buffer, data + *(int *)(data + 10), bmp->bmiHeader.biSizeImage);

I have appended the message displayed. I have debugged my code but cannot find anything at this stage and was wondering if there is something I could look-out for or information which might point to why the application breaks.

Any assistance would be appreciated,
OnlineProCrash.JPG

  Re: Break point generated during real-time display
Posted by Peter Kwan on Feb-09-2018 02:15
Hi Ian,

From the error message, it seems the "buffer" is null.

Are your code multi-threaded? In case your code is multi-threaded, please make sure it does not use any MFC control, including the CChartViewer, in a separate thread. It is because all controls can only be used in the thread that creates the control in the first place, which is usually the main thread of the program. If you create another thread, and the control is used in this other thread, there may be conflicts and there may be random crashes.

The following is an example of a multi-threaded realtime chart, and include some brief explanation why one should not access any control in another thread. Note the the second thread never accesses any MFC control (including the CChartViewer control):

http://www.advsofteng.com/tutorials/real_time_chart_multithread/real_time_chart_multithread.html


If your code is not multi-threaded, or you are sure the code that runs in the other thread never uses any control, then we may consider other possibilities.

From the error message, the error occurs in memcpy, and it seems to suggest the destination "buffer" is null.

The "buffer" is allocated in the previous line of the code using the Win32 API CreateDIBSection. The line is:

HBITMAP ret = CreateDIBSection(cdc->m_hDC, bmp, DIB_RGB_COLORS, &buffer, NULL, 0x0);

I suspect the above line cannot allocate the memory, causing the null pointer. Some possibilities are:

(a) The system runs out of memory. It can be because of a memory leak in the application, or it can be due to other applications using up the memory. Please try to monitor the memory usage to see if there is any leak. I may also be useful to shutdown all unnecessary programs, such as closing all the browsers.

(b) The parameters "cdc->m_hDC" and "bmp" may become invalid for some reasons.

Is it possible to compile the code in Debug Mode (or best of all, run them inside visual Studio), so we can try to examine the variables when it crashes? You can use assert statements to verify the variables in debug mode. For example, between the CreateDIBSection and memcpy lines, you can add:

if (buffer == 0)
{
     // Save the bmp variable so we can examine it
     FILE *file = fopen("error_log.txt", "w");
     fwrite (bmp, sizeof(BITMAPINFO), 1, file);
     fclose(file);
}

assert(cdc->m_hDC != NULL);
assert(ret != NULL);
assert(ret != ERROR_INVALID_PARAMETER;)
assert(buffer != 0);

If the variables do not satisfy the assert statement, the program will break immediately, so we can see which variable causes the issue.

Regards
Peter Kwan

  Re: Break point generated during real-time display
Posted by Ian Hammond on Feb-12-2018 22:10
Attachments:
Hi Peter

I have run the application with the changes you recommend. I have attached the error_log.txt file as suggested. Not sure of the significance of the data value but is displays  "BMN(r".

At present the values I have are:
- bmp 0x04e9002e {bmiHeader={biSize=40 biWidth=830 biHeight=346 ...} bmiColors=0x04e90056 {{rgbBlue=0 '
error_log.txt

  Re: Break point generated during real-time display
Posted by Ian Hammond on Feb-13-2018 01:01
Hi Peter,

I have since come across the Microsoft CRT library and have manged to get a DEBUG version of the application to compile and link. This has shown a few potential issues in the application making calls to ChartDirector. The issues seem to be inherent in the calling app so I think it wise to shelve this thread for now. Many thanks for your assistance.

  Re: Break point generated during real-time display
Posted by Peter Kwan on Feb-13-2018 02:36
Hi Ian,

Thanks for your useful information. I have checked the "error_log.txt" which contains the bmp header. I found the header is normal, which seems to suggest the image created by ChartDirector is valid. The first few bytes of the data "BMN(r" also appears valid. Anyway, in this case, we only need to worry about the "bmp" as it is what is used in CreateDIBSection.

In the test code, there are 4 assert statements. Would you mind to clarify which statement is triggered? The statement that is triggered should cause the program to break. Since the error_log.txt is produced only if (buffer == 0), so we know the last assert statement must be triggered. However, it is possible one of the earlier statements was triggered first, in which case we can get more detail on what may be the cause of the problem.

Also, is your program multi-threaded?

We may create a modified CChartViewer to contain include more bug checking to try to determine the problem. Your information can help us to decide where to look for the problem.

Regards
Peter Kwan

  Re: Break point generated during real-time display
Posted by Peter Kwan on Feb-13-2018 02:38
Hi Ian,

I wrote my last response before seeing your latest post. Please ignore it in light of the new information.

Regards
Peter Kwan

  Re: Break point generated during real-time display
Posted by Ian Hammond on Feb-13-2018 19:41
Hi Peter,
Well, it looks as if I spoke to soon.

ASSERT(ret != NULL); Failed here, Pressed continue
ASSERT(buffer != 0); Failed here,

Would not go beyond memcpy.

The values in the Locals window:

this 0x00763484 {m_currentHBITMAP=0xf2051405 {unused=??? }
        m_WCCisV6=0x00000000 m_testMode=false }
bmp 0x038a002e {bmiHeader={biSize=0x00000028 biWidth=0x0000033e
                  biHeight=0x0000015a ...} bmiColors=0x038a0056 {...} }
buffer 0x00000000
cdc 0x0076ef20 {hDC=0x0d0117d8 {unused=??? } attrib=0x0d0117d8 {unused=??? }}
data 0x038a0020 "BMN(r"
dErr 0x00000057
ret 0x00000000
self 0x00763400 {hWnd=0x0024138c {unused=??? }}


I included a GetLastError call after the CreateDIBSection, hence dErr = ERROR_INVALID_PARAMETER.

The application is multithreaded, but the graphics part is run in its own thread and data aquisition in another.

  Re: Break point generated during real-time display
Posted by Ian Hammond on Feb-15-2018 20:49
Hi Peter,

In view of your last but one communication, are you able to supply  a modified form of the CChartViewer module. Many thanks.

  Re: Break point generated during real-time display
Posted by Peter Kwan on Feb-19-2018 02:52
Attachments:
Hi Ian,

Sorry for the late reply. We have finally come up with a modified CChartViewer that contains code to diagnose or even solve the problem.

Based on the information we get so far, the error occurs in:

HBITMAP ret = CreateDIBSection(cdc->m_hDC, bmp, DIB_RGB_COLORS, &buffer, NULL, 0x0);

All parameters are essentially hard coded except the "cdc->m_hDC" and "bmp". For the "bmp", it was saved to an error_log.txt file. We have tried to reconstruct the exact bmp, and it looks normal and works in our case. For the "cdc->m_hDC", it seems this variable is not used for DIB_RGB_COLORS. In our test case, it works even if we use NULL or just a random number (cast to HDC).

The CreateDIBSection is essentially just a memory allocation routine similar to malloc. As the parameters work in our computer, and the error only occurs after around 9 hours, we suspect it may be due to memory fragmentation. It is a condition that there is plenty of memory scattered in the memory space, but there is no continuous block of memory that is large enough for the bmp. This can be because the application allocates and deallocates memory frequently. There can be no memory leakage, but after a long time, the memory space may become fragmented. As the bmp requires one large block of memory, it will be the first to be affected by fragementation issues.

Another possibility is there is a handle leak. The CreateDIBSection needs to return a handle. If there is a handle leak, the system may run out of handles.

When you run the program, you may consider to use the Task Manager to monitor both the memory usage and the handle usage to see if there is any leak.

The modified CChartViewer attached uses a new memory allocation method. In the original version, when a new chart is displayed, it calls CreateDIBSection to allocate memory for the new chart and then deletes the old chart. In the modified case, it tries to reuse the memory if the new chart and old chart happens to be the same size (which is the most common cases). So if the initial memory allocation is successful, it may not need to call CreateDIBSection to allocate memory again, and so it will not contribute or be affected by fragmentation issues.

In case it needs to call CreateDIBSection and fails, it will try to use 5 different methods to call CreateDIBSection, including using 100% hard coded parameters that are known to be valid. This is to determine if it is due to some invalid parameters or there is some resource issues or may be memory corruption at the C runtime level. The result will be logged to the Visual Studio debugger (displayed in the Output tab).

The modified version will also do a thread test. It will break with an assertion if a CChartViewer is called in a thread that is different from the thread that creates the CChartViewer in the first place.

Please kindly let me know what is the test result.

Regards
Peter Kwan
ChartViewer.cpp
///////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Advanced Software	Engineering	Limited
//
// You may use and modify the code in this file	in your	application, provided the code and
// its modifications are used only in conjunction with ChartDirector. Usage	of this	software
// is subjected	to the terms and condition of the ChartDirector	license.
///////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////
// CChartViewer / CViewPortControl Implementation
//
// The CChartViewer	is a subclass of CStatic for displaying	chart images. It extends CStatic 
// to support to support alpha transparency, image maps, clickable hot spots with tool tips, 
// zooming and scrolling and image update rate control. The CViewPortControl is a subclass of 
// CStatic for visualizing and supporting interactive control of the CChartViewer viewport.
//
// To use these controls in a dialog, in the Dialog Editor, drag a Picture control (CStatic
// control) to the form, configure its type as "Bitmap", and give it a unique ID. Then right 
// click on the control and add a variable to represent the control, using CChartViewer or
// CViewPortControl as the variable type.
///////////////////////////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ChartViewer.h"
#include <math.h>
#include <assert.h>

#ifdef _DEBUG
#define	new	DEBUG_NEW
#endif


/////////////////////////////////////////////////////////////////////////////
//
// CChartViewer
//
/////////////////////////////////////////////////////////////////////////////

//
// Build in	mouse cursors for zooming and scrolling	support
//
static HCURSOR getZoomInCursor();
static HCURSOR getZoomOutCursor();
static HCURSOR getNoZoomCursor();
static HCURSOR getNoMove2DCursor();
static HCURSOR getNoMoveHorizCursor();
static HCURSOR getNoMoveVertCursor();

//
// Constants used in m_delayChartUpdate
//
enum { NO_DELAY, NEED_DELAY, NEED_UPDATE };
enum { UPDATE_VIEW_PORT_TIMER = 1, DELAYED_MOUSE_MOVE_TIMER = 2 };


//
// Constructor
//
CChartViewer::CChartViewer()
{
	// current chart and hot spot tester
	m_currentChart = 0;
	m_hotSpotTester	= 0;

	// create the tool tip control
	m_ToolTip.Create(this);
	m_ToolTip.Activate(TRUE);
	m_ToolTip.ModifyStyle(0, TTS_NOPREFIX);
	m_toolTipHasAttached = false;

	// initialize chart	configuration
	m_selectBoxLineColor = RGB(0, 0, 0);
	m_selectBoxLineWidth = 2;
	m_mouseUsage = Chart::MouseUsageDefault;
	m_zoomDirection	= Chart::DirectionHorizontal;
	m_zoomInRatio =	2;
	m_zoomOutRatio = 0.5;
	m_mouseWheelZoomRatio = 1;
	m_scrollDirection =	Chart::DirectionHorizontal;
	m_minDragAmount	= 5;
	m_updateInterval = 20;

	// current state of	the	mouse
	m_isOnPlotArea = false;
	m_isPlotAreaMouseDown =	false;
	m_isDragScrolling =	false;
	m_currentHotSpot = -1;
	m_isClickable =	false;
	m_isMouseTracking = false;
	m_isInMouseMove = false;

	// chart update	rate support
	m_needUpdateChart =	false;
	m_needUpdateImageMap = false;
	m_holdTimerActive =	false;
	m_delayUpdateChart = NO_DELAY;
	m_delayedChart = 0;
	m_lastMouseMove = 0;
	m_hasDelayedMouseMove = false;
	m_delayImageMapUpdate = false;

	// track cursor support
	m_autoHideMsg = 0;
	m_currentMouseX = -1;
	m_currentMouseY = -1;
	m_isInMouseMovePlotArea = false;

	// CViewPortControl support
	m_vpControl = 0;
	m_ReentrantGuard = false;

	// Debug support
	m_threadId = GetCurrentThreadId();

	TRACE("CChartViewer constructed in thread %d.\n", m_threadId);
}

BEGIN_MESSAGE_MAP(CChartViewer,	CStatic)
	//{{AFX_MSG_MAP(CChartViewer)
	ON_WM_MOUSEMOVE()
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
	ON_WM_SETCURSOR()
	ON_WM_DESTROY()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEWHEEL()
	ON_WM_TIMER()
	ON_WM_PAINT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CChartViewer	message	handlers

//
// Free	resources
//
void CChartViewer::OnDestroy() 
{
	setViewPortControl(0);
	displayChart(this, 0);
	delete m_hotSpotTester;
	m_hotSpotTester	= 0;

	TRACE("CChartViewer destroyed.\n");

	CStatic::OnDestroy();
}

//
// MouseMove event handler
//
void CChartViewer::OnMouseMove(UINT	nFlags,	CPoint point) 
{
	verifyThread();

	// Enable mouse tracking to detect mouse leave events
	if (!m_isMouseTracking)
	{
		TRACKMOUSEEVENT e;		
		e.cbSize = sizeof(e);		
		e.dwFlags = TME_LEAVE;		
		e.hwndTrack = this->m_hWnd;
		m_isMouseTracking = (0 != TrackMouseEvent(&e));
	}

	// On Windows, mouse events can by-pass the event queue. If there are too many mouse events,
	// the event queue may not get processed, preventing other controls from updating. If two mouse
	// events are less than 10ms apart, there is a risk of too many mouse events. So we repost the
	// mouse event as a timer event that is queued up normally, allowing the queue to get processed.
	unsigned int timeBetweenMouseMove = GetTickCount() - m_lastMouseMove;
	if ((m_hasDelayedMouseMove && (timeBetweenMouseMove < 250)) || (timeBetweenMouseMove < 10))
	{
		m_delayedMouseMoveFlag = nFlags;
		m_delayedMouseMovePoint = point;
		if (!m_hasDelayedMouseMove)
		{
			m_hasDelayedMouseMove = true;
			SetTimer(DELAYED_MOUSE_MOVE_TIMER, 1, 0);
		}
	}
	else
		commitMouseMove(nFlags, point);
}

//
// The method that actually performs MouseMove event processing
//
void CChartViewer::commitMouseMove(UINT nFlags, CPoint point)
{
	// Cancel the delayed mouse event if any
	if (m_hasDelayedMouseMove)
	{
		KillTimer(DELAYED_MOUSE_MOVE_TIMER);
		m_hasDelayedMouseMove = false;
	}

	// Remember the mouse coordinates for later use
	m_currentMouseX = point.x;
	m_currentMouseY = point.y;

	// The chart can be updated more than once during mouse move. For example, it can update due to
	// drag to scroll, and also due to drawing track cursor. So we delay updating the display until
	// all all events has occured.
	m_delayUpdateChart = NEED_DELAY;
	m_isInMouseMove = true;

	// Check if mouse is dragging on the plot area
	m_isOnPlotArea = m_isPlotAreaMouseDown || inPlotArea(point.x, point.y);
	if (m_isPlotAreaMouseDown)
		OnPlotAreaMouseDrag(nFlags,	point);
	
	// Send CVN_MouseMoveChart
	GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), CVN_MouseMoveChart), 
		(LPARAM)m_hWnd);

	if (inExtendedPlotArea(point.x, point.y))
	{
		// Mouse wheel events are only sent to the control in focus. So if mouse wheel zooming is 
		// enabled, we must get the focus in order to receive mouse wheel events.
		if (m_mouseWheelZoomRatio != 1)
			SetFocus();

		// Mouse is in extended plot area, send CVN_MouseMovePlotArea
		m_isInMouseMovePlotArea = true;
		GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), CVN_MouseMovePlotArea), 
			(LPARAM)m_hWnd);
	}
	else if (m_isInMouseMovePlotArea)
	{
		// Mouse was in extended plot area, but is not in it now, so send CVN_MouseLeavePlotArea
		m_isInMouseMovePlotArea = false;
		GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), CVN_MouseLeavePlotArea), 
			(LPARAM)m_hWnd);
		applyAutoHide(CVN_MouseLeavePlotArea);
	}

	// Can update chart now
	commitUpdateChart();
	m_isInMouseMove = false;

	if (m_delayImageMapUpdate)
	{
		m_delayImageMapUpdate = false;
		if (!m_isPlotAreaMouseDown)
			updateViewPort(false, true);
	}

	//
	// Show	hot	spot tool tips if necessary
	//
	if (!m_toolTipHasAttached)
	{
		m_toolTipHasAttached = true;

		// Connects	the	CChartViewer to	the	CToolTipCtrl control
		m_ToolTip.AddTool(this);
		m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
	}

	if (nFlags != 0)
	{
		// Hide	tool tips if mouse button is pressed.
		m_ToolTip.UpdateTipText(_T(""),	this);
	}
	else
	{
		// Use the ChartDirector ImageMapHandler to	determine if the mouse is over a hot spot
		int	hotSpotNo =	0;
		if (0 != m_hotSpotTester)
			hotSpotNo =	m_hotSpotTester->getHotSpot(point.x, point.y);

		// If the mouse	is in the same hot spot	since the last mouse move event, there is no need
		// to update the tool tip.
		if (hotSpotNo != m_currentHotSpot)
		{
			// Hot spot	has	changed	- update tool tip text
			m_currentHotSpot = hotSpotNo;

			if (hotSpotNo == 0)
			{
				// Mouse is	not	actually on	hanlder	hot	spot - use default tool	tip	text and reset
				// the clickable flag.
				m_ToolTip.UpdateTipText(m_defaultToolTip, this);
				m_isClickable =	false;
			}
			else
			{
				// Mouse is	on a hot spot. In this implementation, we consider the hot spot	as 
				// clickable if	its	href ("path") parameter	is not empty.
				const char *path = m_hotSpotTester->getValue("path");
				m_isClickable =	((0	!= path) &&	(0 != *path));

				// Use the title attribute as the tool tip.	Note that ChartDirector	uses UTF8, 
				// while MFC uses TCHAR, so	we use the utility UTF8toTCHAR for conversion.
				m_ToolTip.UpdateTipText(UTF8toTCHAR(m_hotSpotTester->getValue("title")), this);
			}
		}
	}

	m_lastMouseMove = GetTickCount();
	CStatic::OnMouseMove(nFlags, point);
}

//
// Delayed MouseMove event handler
//
void CChartViewer::OnDelayedMouseMove() 
{
	if (m_hasDelayedMouseMove)
		commitMouseMove(m_delayedMouseMoveFlag, m_delayedMouseMovePoint);
}

//
// MouseWheel handler
//
BOOL CChartViewer::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// Process the mouse wheel only if the mouse is over the plot area
	if (!isMouseOnPlotArea())
		return FALSE;
	else
		return onMouseWheelZoom(getPlotAreaMouseX(), getPlotAreaMouseY(), zDelta);
}

BOOL CChartViewer::onMouseWheelZoom(int x, int y, short zDelta)
{
	// Zoom ratio = 1 means no zooming
	if (m_mouseWheelZoomRatio == 1)
		return FALSE;

	// X and Y zoom ratios
	double rx = 1;
    double ry = 1;
	if (getZoomDirection() != Chart::DirectionVertical)
		rx = (zDelta > 0) ? m_mouseWheelZoomRatio : 1 / m_mouseWheelZoomRatio;
	if (getZoomDirection() != Chart::DirectionHorizontal)
		ry = (zDelta > 0) ? m_mouseWheelZoomRatio : 1 / m_mouseWheelZoomRatio;

	// Do the zooming
	if (zoomAround(x, y, rx, ry))
	{
		updateViewPort(true, false);
		m_delayImageMapUpdate = true;
	}

	return TRUE;
}

//
// MouseLeave event handler
//
LRESULT CChartViewer::OnMouseLeave(WPARAM wParam, LPARAM lParam) 
{
	// Process delayed mouse move, if any
	OnDelayedMouseMove();

	// Mouse tracking is no longer active
	m_isMouseTracking = false;

	if (m_isInMouseMovePlotArea)
	{
		// Mouse was in extended plot area, but is not in it now, so send CVN_MouseLeavePlotArea
		m_isInMouseMovePlotArea = false;
		GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), CVN_MouseLeavePlotArea), 
			(LPARAM)m_hWnd);
		applyAutoHide(CVN_MouseLeavePlotArea);
	}

	// Send CVN_MouseLeaveChart
	GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), CVN_MouseLeaveChart), 
		(LPARAM)m_hWnd);
	applyAutoHide(CVN_MouseLeaveChart);
 
	return TRUE;
}

//
// Intercept WM_SETCURSOR to change	the	mouse cursor.
//
BOOL CChartViewer::OnSetCursor(CWnd* pWnd, UINT	nHitTest, UINT message)	
{
	if (m_isOnPlotArea)
	{
		switch (m_mouseUsage)
		{
		case Chart::MouseUsageZoomIn:
			if (canZoomIn(m_zoomDirection))
				::SetCursor(getZoomInCursor());
			else
				::SetCursor(getNoZoomCursor());
			return TRUE;
		case Chart::MouseUsageZoomOut:
			if (canZoomOut(m_zoomDirection))
				::SetCursor(getZoomOutCursor());
			else
				::SetCursor(getNoZoomCursor());
			return TRUE;
		}
	}

	if (m_isClickable)
	{
		// Hand	cursor = IDC_HAND =	32649
		HCURSOR	h =	AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(32649));
		if (NULL !=	h)
			::SetCursor(h);
		return TRUE;
	}

	return CStatic::OnSetCursor(pWnd, nHitTest,	message);
}

//
// Mouse left button down event.
//
void CChartViewer::OnLButtonDown(UINT nFlags, CPoint point)	
{
	OnDelayedMouseMove();

	if (inPlotArea(point.x,	point.y) &&	(m_mouseUsage != Chart::MouseUsageDefault))
	{
		// Mouse usage is for drag to zoom/scroll. Capture the mouse to	prepare	for	dragging and 
		// save	the	mouse down position	to draw	the	selection rectangle.
		SetCapture();
		m_isPlotAreaMouseDown =	true;
		m_plotAreaMouseDownXPos	= point.x;
		m_plotAreaMouseDownYPos	= point.y;
		startDrag();
	}
	else
		CStatic::OnLButtonDown(nFlags, point);
}

//
// Mouse left button up	event.
//
void CChartViewer::OnLButtonUp(UINT	nFlags,	CPoint point) 
{
	OnDelayedMouseMove();

	if (m_isPlotAreaMouseDown)
	{
		// Release the mouse capture.
		ReleaseCapture();
		m_isPlotAreaMouseDown =	false;
		setRectVisible(false);
		bool hasUpdate = false;

		switch (m_mouseUsage)
		{
		case Chart::MouseUsageZoomIn :
			if (canZoomIn(m_zoomDirection))
			{
				if (isDrag(m_zoomDirection,	point))
					// Zoom	to the drag	selection rectangle.
					hasUpdate =	zoomTo(m_zoomDirection,	
						m_plotAreaMouseDownXPos, m_plotAreaMouseDownYPos, point.x, point.y);
				else
					// User	just click on a	point. Zoom-in around the mouse	cursor position.
					hasUpdate =	zoomAt(m_zoomDirection,	point.x, point.y, m_zoomInRatio);
			}
			break;
		case Chart::MouseUsageZoomOut:
			// Zoom	out	around the mouse cursor	position.
			if (canZoomOut(m_zoomDirection))
				hasUpdate =	zoomAt(m_zoomDirection,	point.x, point.y, m_zoomOutRatio);
			break;
		default	:
			if (m_isDragScrolling)
				// Drag	to scroll. We can update the image map now as scrolling	has	finished.
				updateViewPort(false, true);
			else
				// Is not zooming or scrolling,	so is just a normal	click event.
				GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(),	BN_CLICKED), (LPARAM)m_hWnd);
			break;
		}

		m_isDragScrolling =	false;
		if (hasUpdate)
			// View	port has changed - update it.
			updateViewPort(true, true);
	}
	else
		CStatic::OnLButtonUp(nFlags, point);
}


//
// Custom OnPaint to support alpha transparency
//
void CChartViewer::OnPaint() 
{
	onPaint(this);
}

//
// Chart hold timer.
//
void CChartViewer::OnTimer(UINT_PTR nIDEvent)	
{
	if (nIDEvent == DELAYED_MOUSE_MOVE_TIMER)
	{
		// Is a delayed mouse move event
		OnDelayedMouseMove();
	}
	else
	{	
		// Is a delayed view port update
		m_holdTimerActive =	false;

		// Reset the timer
		KillTimer(nIDEvent);
		
		// If has pending chart	view port update request, handles them now.
		if (m_needUpdateChart || m_needUpdateImageMap)
			updateViewPort(m_needUpdateChart, m_needUpdateImageMap);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CChartViewer	overrides

BOOL CChartViewer::PreTranslateMessage(MSG*	pMsg) 
{
	// Remember	to forward mouse messages to the CToolTipCtrl
	BOOL res = CStatic::PreTranslateMessage(pMsg);
	m_ToolTip.RelayEvent(pMsg);
	return res;
}

/////////////////////////////////////////////////////////////////////////////
// CChartViewer	properties

//
// Set the chart to	the	control
//
void CChartViewer::setChart(BaseChart *c)
{
	verifyThread();

	// In case the user	forgets	to check the "Notify" check	box	in the Dialog editor, we set it
	// ourselves so	the	CChartViewer control can receive mouse events.
	if ((GetStyle()	& SS_NOTIFY) ==	0)
		ModifyStyle(0, SS_NOTIFY);

	m_currentChart = c;
	setImageMap(0);

	if (0 != c)
	{
		commitPendingSyncAxis(c);
		if (m_delayUpdateChart != NO_DELAY)
			c->makeChart();
	}

	updateDisplay();
}

//
// Get back	the	same BaseChart pointer provided	by the previous	setChart call.
//
BaseChart *CChartViewer::getChart()
{
	return m_currentChart;
}

//
// Set the CViewPortControl to be associated with this control
//
void CChartViewer::setViewPortControl(CViewPortControl *vpc)
{
	if (m_ReentrantGuard)
		return;
	m_ReentrantGuard = true;
	
	if (0 != m_vpControl)
		m_vpControl->setViewer(0);
	m_vpControl = vpc;
	if (0 != m_vpControl)
		m_vpControl->setViewer(this);

	m_ReentrantGuard = false;
}


//
// Get the CViewPortControl that is associated with this control
//
CViewPortControl *CChartViewer::getViewPortControl()
{
	return m_vpControl;
}

//
// Set image map used by the chart
//
void CChartViewer::setImageMap(const char *imageMap)
{
	//delete the existing ImageMapHandler
	if (0 != m_hotSpotTester)
		delete m_hotSpotTester;
	m_currentHotSpot = -1;
	m_isClickable =	false;
	
	//create a new ImageMapHandler to represent	the	image map
	if ((0 == imageMap)	|| (0 == *imageMap))
		m_hotSpotTester	= 0;
	else
		m_hotSpotTester	= new ImageMapHandler(imageMap);
}

//
// Get the image map handler for the chart
//
ImageMapHandler	*CChartViewer::getImageMapHandler()
{
	return m_hotSpotTester;
}

//
// Set the default tool	tip	to use
//
void CChartViewer::setDefaultToolTip(LPCTSTR text)
{
	m_defaultToolTip = text;
}

//
// Get the CToolTipCtrl	for	managing tool tips.
//
CToolTipCtrl *CChartViewer::getToolTipCtrl()
{
	return &m_ToolTip;
}

//
// Set the border width	of the selection box
//
void CChartViewer::setSelectionBorderWidth(int width)
{
	m_selectBoxLineWidth = width;
}

//
// Get the border with of the selection	box.
//
int	CChartViewer::getSelectionBorderWidth()
{
	return m_selectBoxLineWidth;
}

//
// Set the border color	of the selection box
//
void CChartViewer::setSelectionBorderColor(COLORREF	c)
{
	m_selectBoxLineColor = c;
	if (m_TopLine.m_hWnd !=	0)
	{
		m_TopLine.SetColor(c);
		m_LeftLine.SetColor(c);
		m_BottomLine.SetColor(c);
		m_RightLine.SetColor(c);
	}
}

//
// Get the border color	of the selection box.
//
COLORREF CChartViewer::getSelectionBorderColor()
{
	return m_selectBoxLineColor;
}

//
// Set the mouse usage mode
//
void CChartViewer::setMouseUsage(int mouseUsage)
{
	m_mouseUsage = mouseUsage;
}

//
// Get the mouse usage mode
//
int	CChartViewer::getMouseUsage()
{
	return m_mouseUsage;
}

//
// Set the zoom	direction
//
void CChartViewer::setZoomDirection(int	direction)
{
	m_zoomDirection	= direction;
}

//
// Get the zoom	direction
//
int	CChartViewer::getZoomDirection()
{
	return m_zoomDirection;
}

//
// Set the scroll direction
//
void CChartViewer::setScrollDirection(int direction)
{
	m_scrollDirection =	direction;
}

//
// Get the scroll direction
//
int	CChartViewer::getScrollDirection()
{
	return m_scrollDirection;
}

//
// Set the zoom-in ratio for mouse click zoom-in
//
void CChartViewer::setZoomInRatio(double ratio)
{
	m_zoomInRatio =	ratio;
}

//
// Get the zoom-in ratio for mouse click zoom-in
//
double CChartViewer::getZoomInRatio()
{
	return m_zoomInRatio;
}

//
// Set the zoom-out	ratio
//
void CChartViewer::setZoomOutRatio(double ratio)
{
	m_zoomOutRatio = ratio;
}

//
// Get the zoom-out	ratio
//
double CChartViewer::getZoomOutRatio()
{
	return m_zoomOutRatio;	
}

//
// Set the mouse wheel zoom ratio 
//
void CChartViewer::setMouseWheelZoomRatio(double ratio)
{
	m_mouseWheelZoomRatio = ratio;
}

//
// Get the mouse wheel zoom ratio 
//
double CChartViewer::getMouseWheelZoomRatio()
{
	return m_mouseWheelZoomRatio;	
}

//
// Set the minimum mouse drag before the dragging is considered	as real. This is to	avoid small	
// mouse vibrations	triggering a mouse drag.
//
void CChartViewer::setMinimumDrag(int offset)
{
	m_minDragAmount	= offset;
}

//
// Get the minimum mouse drag before the dragging is considered	as real.
//
int	CChartViewer::getMinimumDrag()
{
	return m_minDragAmount;
}

//
// Set the minimum interval	between	ViewPortChanged	events.	This is	to avoid the chart being 
// updated too frequently. (Default	is 20ms	between	chart updates.)	Multiple update	events
// arrived during the interval will	be merged into one chart update	and	executed at	the	end
// of the interval.
//
void CChartViewer::setUpdateInterval(int interval)
{
	m_updateInterval = interval;
}

//
// Get the minimum interval	between	ViewPortChanged	events.	
//
int	CChartViewer::getUpdateInterval()
{
	return m_updateInterval;
}

//
// Check if	there is a pending chart update	request. 
//
bool CChartViewer::needUpdateChart()
{
	return m_needUpdateChart;
}

//
// Check if	there is a pending image map update	request. 
//
bool CChartViewer::needUpdateImageMap()
{
	return m_needUpdateImageMap;
}

//
// Get the current mouse x coordinate when used in a mouse move event handler
//
int CChartViewer::getChartMouseX()
{
	int ret = m_currentMouseX;
	if (ret < 0)
		ret = getPlotAreaLeft() + getPlotAreaWidth();
	return ret;
}

//
// Get the current mouse y coordinate when used in a mouse move event handler
//
int CChartViewer::getChartMouseY()
{
	int ret = m_currentMouseY;
	if (ret < 0)
		ret = getPlotAreaTop() + getPlotAreaHeight();
	return ret;
}

//
// Get the current mouse x coordinate bounded to the plot area when used in a mouse event handler
//
int CChartViewer::getPlotAreaMouseX()
{
	int ret = getChartMouseX();
	if (ret < getPlotAreaLeft())
		ret = getPlotAreaLeft();
	if (ret > getPlotAreaLeft() + getPlotAreaWidth())
		ret = getPlotAreaLeft() + getPlotAreaWidth();
	return ret;
}

//
// Get the current mouse y coordinate bounded to the plot area when used in a mouse event handler
//
int CChartViewer::getPlotAreaMouseY()
{
	int ret = getChartMouseY();
	if (ret < getPlotAreaTop())
		ret = getPlotAreaTop();
	if (ret > getPlotAreaTop() + getPlotAreaHeight())
		ret = getPlotAreaTop() + getPlotAreaHeight();
	return ret;
}

//
// Check if mouse is on the extended plot area
//
bool CChartViewer::isMouseOnPlotArea()
{
	if (this->m_isMouseTracking)
		return inExtendedPlotArea(getChartMouseX(), getChartMouseY());
	else
		return false;
}

//
// Check if mouse is dragging to scroll or to select the zoom rectangle
//
bool CChartViewer::isMouseDragging()
{
	return m_isPlotAreaMouseDown;
}

//
// Check if is currently processing a mouse move event
//
bool CChartViewer::isInMouseMoveEvent()
{
	return m_isInMouseMove;
}

/////////////////////////////////////////////////////////////////////////////
// CChartViewer	methods

//
// Update the display
//
void CChartViewer::updateDisplay()
{
	if (m_delayUpdateChart == NO_DELAY)
		commitUpdateChart();
	else
	{
		m_delayUpdateChart = NEED_UPDATE;
		delete m_delayedChart;
		m_delayedChart = (0 != m_currentChart) ? new BaseChart(m_currentChart) : 0;
	}
}

//
// Commit chart to display
//
void CChartViewer::commitUpdateChart()
{
	verifyThread();

	if (m_delayUpdateChart == NEED_DELAY)
	{
		// No actual update occur
		m_delayUpdateChart = NO_DELAY;
		return;
	}

	// Display the chart
	BaseChart *c = (m_delayUpdateChart == NEED_UPDATE) ? m_delayedChart : m_currentChart;
	displayChart(this, c);
	setChartMetrics((0 != c) ? c->getChartMetrics() : 0);

	// Remember to update the selection rectangle
	if ((0 != m_TopLine.m_hWnd) && m_TopLine.IsWindowVisible())
	{
		m_TopLine.Invalidate();
		m_LeftLine.Invalidate();
		m_RightLine.Invalidate();
		m_BottomLine.Invalidate();
		m_TopLine.UpdateWindow();
		m_LeftLine.UpdateWindow();
		m_RightLine.UpdateWindow();
		m_BottomLine.UpdateWindow();
	}

	// Any delayed chart has been committed
	m_delayUpdateChart = NO_DELAY;
	delete m_delayedChart;
	m_delayedChart = 0;
}

//
// Set the message used to remove the dynamic layer
//
void CChartViewer::removeDynamicLayer(int msg)
{
	m_autoHideMsg = msg;
	if (msg == -1)
		applyAutoHide(msg);
}

//
// Attempt to hide the dynamic layer using the specified message
//
void CChartViewer::applyAutoHide(int msg)
{
	if (m_autoHideMsg == msg)
	{
		if (0 != m_currentChart)
			m_currentChart->removeDynamicLayer();
		m_autoHideMsg = 0;

	    updateDisplay();
	}
}

//
// Create the edges	for	the	selection rectangle
//
void CChartViewer::initRect()
{
	m_TopLine.Create(GetParent(), m_selectBoxLineColor);
	m_LeftLine.Create(GetParent(), m_selectBoxLineColor);
	m_BottomLine.Create(GetParent(), m_selectBoxLineColor);
	m_RightLine.Create(GetParent(),	m_selectBoxLineColor);
}

//
// Set selection rectangle position	and	size
//
void CChartViewer::drawRect(int	x, int y, int width, int height)
{
	// Create the edges	of the rectangle if	not	already	created
	if (m_TopLine.m_hWnd ==	0)
		initRect();

	// width < 0 is	interpreted	as the rectangle extends to	the	left or	x.
	// height <0 is	interpreted	as the rectangle extends to	above y.
	if (width <	0)
		x -= (width	= -width);
	if (height < 0)
		y -= (height = -height);

	// Compute the position	of the selection rectangle as relative to the parent window
	RECT rect;
	rect.left =	x;
	rect.top = y;
	rect.right = x + width;
	rect.bottom	= y	+ height;
	MapWindowPoints(m_TopLine.GetParent(), &rect);

	// Put the edges along the sides of	the	rectangle
	m_TopLine.MoveWindow(rect.left,	rect.top, rect.right - rect.left, m_selectBoxLineWidth);
	m_LeftLine.MoveWindow(rect.left, rect.top, m_selectBoxLineWidth, rect.bottom - rect.top);
	m_BottomLine.MoveWindow(rect.left, rect.bottom - m_selectBoxLineWidth +	1, 
		rect.right - rect.left,	m_selectBoxLineWidth);
	m_RightLine.MoveWindow(rect.right -	m_selectBoxLineWidth + 1, rect.top,	
		m_selectBoxLineWidth, rect.bottom -	rect.top);
}

//
// Show/hide selection rectangle
//
void CChartViewer::setRectVisible(bool b)
{
	// Create the edges	of the rectangle if	not	already	created
	if (b && (m_TopLine.m_hWnd == 0)) 
		initRect();

	// Show/hide the edges
	if (m_TopLine.m_hWnd != 0)
	{
		int	state =	b ?	SW_SHOW	: SW_HIDE;
		m_TopLine.ShowWindow(state);
		m_LeftLine.ShowWindow(state);
		m_BottomLine.ShowWindow(state);
		m_RightLine.ShowWindow(state);
	}
}

//
// Determines if the mouse is dragging.
//
bool CChartViewer::isDrag(int direction, CPoint	point)
{
	// We only consider	the	mouse is dragging it is	has	dragged	more than m_minDragAmount. This	is
	// to avoid	small mouse	vibrations triggering a	mouse drag.
	int	spanX =	abs(point.x	- m_plotAreaMouseDownXPos);
	int	spanY =	abs(point.y	- m_plotAreaMouseDownYPos);
	return ((direction != Chart::DirectionVertical)	&& (spanX >= m_minDragAmount)) ||
		((direction	!= Chart::DirectionHorizontal) && (spanY >=	m_minDragAmount));
}

//
// Process mouse dragging over the plot	area
//
void CChartViewer::OnPlotAreaMouseDrag(UINT	/* nFlags */, CPoint point)
{
	switch (m_mouseUsage)
	{
		case Chart::MouseUsageZoomIn :
		{
			//
			// Mouse is	used for zoom in. Draw the selection rectangle if necessary.
			//

			bool isDragZoom	= canZoomIn(m_zoomDirection) &&	isDrag(m_zoomDirection,	point);
			if (isDragZoom)
			{
				int	spanX =	m_plotAreaMouseDownXPos	- point.x;
				int	spanY =	m_plotAreaMouseDownYPos	- point.y;

				switch (m_zoomDirection)
				{
				case Chart::DirectionHorizontal:
					drawRect(point.x, getPlotAreaTop(),	spanX, getPlotAreaHeight());
					break;
				case Chart::DirectionVertical:
					drawRect(getPlotAreaLeft(),	point.y, getPlotAreaWidth(), spanY);
					break;
				default:
					drawRect(point.x, point.y, spanX, spanY);
					break;
				}
			}
			setRectVisible(isDragZoom);
			break;
		}
		case Chart::MouseUsageScroll :
		{
			//
			// Mouse is	used for drag scrolling. Scroll	and	update the view	port.
			//

			if (m_isDragScrolling || isDrag(m_scrollDirection, point))
			{
				m_isDragScrolling =	true;
				switch (m_scrollDirection)
				{
				case Chart::DirectionHorizontal:
					::SetCursor(getNoMoveHorizCursor());
					break;
				case Chart::DirectionVertical:
					::SetCursor(getNoMoveVertCursor());
					break;
				default	:
					::SetCursor(getNoMove2DCursor());
					break;
				}
								
				if (dragTo(m_scrollDirection, 
					point.x	- m_plotAreaMouseDownXPos, point.y - m_plotAreaMouseDownYPos))
					updateViewPort(true, false);
			}
		}
	}
}

//
// Trigger the ViewPortChanged event
//
void CChartViewer::updateViewPort(bool needUpdateChart,	bool needUpdateImageMap)
{
	// Merge the current update	requests with any pending requests.
	m_needUpdateChart =	m_needUpdateChart || needUpdateChart;
	m_needUpdateImageMap = needUpdateImageMap;

	// Hold	timer has not expired, so do not update	chart immediately. Keep	the	requests pending.
	if (m_holdTimerActive)
		return;

	// The chart can be updated more than once during mouse move. For example, it can update due to
	// drag to scroll, and also due to drawing track cursor. So we delay updating the display until
	// all all updates has occured.
	int hasDelayUpdate = (m_delayUpdateChart != NO_DELAY);
	if (!hasDelayUpdate)
		m_delayUpdateChart = NEED_DELAY;
	
	// Can trigger the ViewPortChanged event.
	validateViewPort();
	GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(),	CVN_ViewPortChanged), 
		(LPARAM)m_hWnd);
	
	if (m_needUpdateChart && (0 != m_vpControl))
		m_vpControl->updateDisplay();

	// Can update chart now
	if (!hasDelayUpdate)
		commitUpdateChart();

	// Clear any pending updates.
	m_needUpdateChart =	false;
	m_needUpdateImageMap = false;

	// Set hold	timer to prevent multiple chart	updates	within a short period.
	if (m_updateInterval > 0)
	{
		m_holdTimerActive = true;
		SetTimer(UPDATE_VIEW_PORT_TIMER, m_updateInterval, 0);
	}
}

inline void CChartViewer::verifyThread()
{
	assert(GetCurrentThreadId() == m_threadId);
}

/////////////////////////////////////////////////////////////////////////////
// CRectCtrl
//
// A rectangle with	a background color.	Use	as thick edges for the selection
// rectangle.
//

//
// Create control with a given color
//
BOOL CRectCtrl::Create(CWnd	*pParentWnd, COLORREF c)
{
	SetColor(c);

	RECT r;
	r.left = r.top = r.right = r.bottom	= 0;
	return CStatic::Create(0, WS_CHILD,	r, pParentWnd);
}

BEGIN_MESSAGE_MAP(CRectCtrl, CStatic)
	//{{AFX_MSG_MAP(CRectCtrl)
	ON_WM_CTLCOLOR_REFLECT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CRectCtrl message handlers

HBRUSH CRectCtrl::CtlColor(CDC*	/* pDC */, UINT	/* nCtlColor */) 
{
	return (HBRUSH)m_Color.m_hObject;
}

/////////////////////////////////////////////////////////////////////////////
// CRectCtrl proporties

//
// Set the background color
//
void CRectCtrl::SetColor(COLORREF c)
{
	m_Color.CreateSolidBrush(c);
}

/////////////////////////////////////////////////////////////////////////////
// Build in	mouse cursors for zooming and scrolling	support
//

static const unsigned int zoomInCursorA[] = 
{ 
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xff3ff8ff,
0xff0fe0ff,
0xff07c0ff,
0xff07c0ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff07c0ff,
0xff07c0ff,
0xff01e0ff,
0xff30f8ff,
0x7ff0ffff,
0x3ff8ffff,
0x1ffcffff,
0x0ffeffff,
0x0fffffff,
0x9fffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff
};

static const unsigned int zoomInCursorB[] = 
{
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00c00700,
0x00f01f00,
0x00f01e00,
0x00f83e00,
0x00f83e00,
0x00183000,
0x00f83e00,
0x00f83e00,
0x00f01e00,
0x00f01f00,
0x00c00700,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000
};

static const unsigned int zoomOutCursorA[] =	
{ 
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xff3ff8ff,
0xff0fe0ff,
0xff07c0ff,
0xff07c0ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff07c0ff,
0xff07c0ff,
0xff01e0ff,
0xff30f8ff,
0x7ff0ffff,
0x3ff8ffff,
0x1ffcffff,
0x0ffeffff,
0x0fffffff,
0x9fffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff
};

static const unsigned int zoomOutCursorB[] =	
{
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00c00700,
0x00f01f00,
0x00f01f00,
0x00f83f00,
0x00f83f00,
0x00183000,
0x00f83f00,
0x00f83f00,
0x00f01f00,
0x00f01f00,
0x00c00700,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000
};

static const unsigned int noZoomCursorA[] = 
{ 
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xff3ff8ff,
0xff0fe0ff,
0xff07c0ff,
0xff07c0ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff0380ff,
0xff07c0ff,
0xff07c0ff,
0xff01e0ff,
0xff30f8ff,
0x7ff0ffff,
0x3ff8ffff,
0x1ffcffff,
0x0ffeffff,
0x0fffffff,
0x9fffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff
};

static const unsigned int noZoomCursorB[] = 
{
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00c00700,
0x00f01f00,
0x00f01f00,
0x00f83f00,
0x00f83f00,
0x00f83f00,
0x00f83f00,
0x00f83f00,
0x00f01f00,
0x00f01f00,
0x00c00700,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000
};

static const unsigned int noMove2DCursorA[] =
{
0xffffffff,
0xffffffff,
0xffffffff,
0xff7ffeff,
0xff3ffcff,
0xff1ff8ff,
0xff0ff0ff,
0xff07e0ff,
0xff03c0ff,
0xff03c0ff,
0xfffc3fff,
0x7ffc3ffe,
0x3ffc3ffc,
0x1f7c3ef8,
0x0f3c3cf0,
0x071c38e0,
0x071c38e0,
0x0f3c3cf0,
0x1f7c3ef8,
0x3ffc3ffc,
0x7ffc3ffe,
0xfffc3fff,
0xff03c0ff,
0xff03c0ff,
0xff07e0ff,
0xff0ff0ff,
0xff1ff8ff,
0xff3ffcff,
0xff7ffeff,
0xffffffff,
0xffffffff,
0xffffffff
};

static const unsigned int noMove2DCursorB[] =
{
0x00000000,
0x00000000,
0x00000000,
0x00800100,
0x00400200,
0x00200400,
0x00100800,
0x00081000,
0x00042000,
0x00fc3f00,
0x0003c000,
0x80024001,
0x40024002,
0x20824104,
0x10424208,
0x08224410,
0x08224410,
0x10424208,
0x20824104,
0x40024002,
0x80024001,
0x0003c000,
0x00fc3f00,
0x00042000,
0x00081000,
0x00100800,
0x00200400,
0x00400200,
0x00800100,
0x00000000,
0x00000000,
0x00000000
};

static const unsigned int noMoveHorizCursorA[]	=
{
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xfffc3fff,
0x7ffc3ffe,
0x3ffc3ffc,
0x1f7c3ef8,
0x0f3c3cf0,
0x071c38e0,
0x071c38e0,
0x0f3c3cf0,
0x1f7c3ef8,
0x3ffc3ffc,
0x7ffc3ffe,
0xfffc3fff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff,
0xffffffff
};

static const unsigned int noMoveHorizCursorB[]	=
{
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x0003c000,
0x80024001,
0x40024002,
0x20824104,
0x10424208,
0x08224410,
0x08224410,
0x10424208,
0x20824104,
0x40024002,
0x80024001,
0x0003c000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000
};

static const unsigned int noMoveVertCursorA[] =
{
0xffffffff,
0xffffffff,
0xffffffff,
0xff7ffeff,
0xff3ffcff,
0xff1ff8ff,
0xff0ff0ff,
0xff07e0ff,
0xff03c0ff,
0xff03c0ff,
0xffffffff,
0xffffffff,
0xffffffff,
0xff7ffeff,
0xff3ffcff,
0xff1ff8ff,
0xff1ff8ff,
0xff3ffcff,
0xff7ffeff,
0xffffffff,
0xffffffff,
0xffffffff,
0xff03c0ff,
0xff03c0ff,
0xff07e0ff,
0xff0ff0ff,
0xff1ff8ff,
0xff3ffcff,
0xff7ffeff,
0xffffffff,
0xffffffff,
0xffffffff
};

static const unsigned int noMoveVertCursorB[] =
{
0x00000000,
0x00000000,
0x00000000,
0x00800100,
0x00400200,
0x00200400,
0x00100800,
0x00081000,
0x00042000,
0x00fc3f00,
0x00000000,
0x00000000,
0x00000000,
0x00800100,
0x00400200,
0x00200400,
0x00200400,
0x00400200,
0x00800100,
0x00000000,
0x00000000,
0x00000000,
0x00fc3f00,
0x00042000,
0x00081000,
0x00100800,
0x00200400,
0x00400200,
0x00800100,
0x00000000,
0x00000000,
0x00000000
};

static HCURSOR hZoomInCursor = 0;
static HCURSOR hZoomOutCursor =	0;
static HCURSOR hNoZoomCursor = 0;
static HCURSOR hNoMove2DCursor = 0;
static HCURSOR hNoMoveHorizCursor =	0;
static HCURSOR hNoMoveVertCursor = 0;

class FreeCursors
{
public:
	~FreeCursors()
	{
		if (0 != hZoomInCursor)
			DestroyCursor(hZoomInCursor);
		if (0 != hZoomOutCursor)
			DestroyCursor(hZoomOutCursor);
		if (0 != hNoZoomCursor)
			DestroyCursor(hNoZoomCursor);
		if (0 != hNoMove2DCursor)
			DestroyCursor(hNoMove2DCursor);
		if (0 != hNoMoveHorizCursor)
			DestroyCursor(hNoMoveHorizCursor);
		if (0 != hNoMoveVertCursor)
			DestroyCursor(hNoMoveVertCursor);
	}
} dummyFreeCursorObj;

static HCURSOR getZoomInCursor()
{
	if (0 == hZoomInCursor)
	{
		hZoomInCursor =	CreateCursor(AfxGetApp()->m_hInstance, 15, 15, 32, 32, 
			zoomInCursorA, zoomInCursorB);
	}
	return hZoomInCursor;
}

static HCURSOR getZoomOutCursor()
{
	if (0 == hZoomOutCursor)
	{
		hZoomOutCursor = CreateCursor(AfxGetApp()->m_hInstance,	15,	15,	32,	32,	
			zoomOutCursorA,	zoomOutCursorB);
	}
	return hZoomOutCursor;
}

static HCURSOR getNoZoomCursor()
{
	if (0 == hNoZoomCursor)
	{
		hNoZoomCursor =	CreateCursor(AfxGetApp()->m_hInstance, 15, 15, 32, 32, 
			noZoomCursorA, noZoomCursorB);
	}
	return hNoZoomCursor;
}

static HCURSOR getNoMove2DCursor()
{
	if (0 == hNoMove2DCursor)
	{
		hNoMove2DCursor	= CreateCursor(AfxGetApp()->m_hInstance, 15, 15, 32, 32, 
			noMove2DCursorA, noMove2DCursorB);
	}
	return hNoMove2DCursor;
}

static HCURSOR getNoMoveHorizCursor()
{
	if (0 == hNoMoveHorizCursor)
	{
		hNoMoveHorizCursor = CreateCursor(AfxGetApp()->m_hInstance,	15,	15,	32,	32,	
			noMoveHorizCursorA,	noMoveHorizCursorB);
	}
	return hNoMoveHorizCursor;
}

static HCURSOR getNoMoveVertCursor()
{
	if (0 == hNoMoveVertCursor)
	{
		hNoMoveVertCursor =	CreateCursor(AfxGetApp()->m_hInstance, 15, 15, 32, 32, 
			noMoveVertCursorA, noMoveVertCursorB);
	}
	return hNoMoveVertCursor;
}


/////////////////////////////////////////////////////////////////////////////
//
// CStaticHelper
//
/////////////////////////////////////////////////////////////////////////////

CStaticHelper::CStaticHelper() : m_currentBuffer(0), m_WCCisV6(0), m_testMode(false), m_hasLogSample(false)
{
}

void CStaticHelper::setHBITMAP(CStatic *self, const char *data)
{
	HBITMAP newBitMap = NULL;

	if (0 != data)
	{
		const BITMAPINFO *bmp = (const BITMAPINFO *)(data + 14);
		if ((0 != m_currentBuffer) && (memcmp(&m_currentBITMAPINFO, bmp, sizeof(BITMAPINFO)) == 0))
		{
			memcpy(m_currentBuffer, data + *(int *)(data + 10), bmp->bmiHeader.biSizeImage);
			self->Invalidate();
			return;
		}
		
		CDC	*cdc = self->GetDC();
		newBitMap = CreateDIBSection(cdc->GetSafeHdc(), bmp, DIB_RGB_COLORS, &m_currentBuffer, NULL, 0x0);
		
		if (NULL == newBitMap)
		{
			int errCode = GetLastError();
			TRACE("CreateDIBSection failed - error code = %d.\n", errCode);

			// Try second method
			newBitMap = CreateDIBSection(NULL, bmp, DIB_RGB_COLORS, &m_currentBuffer, NULL, 0x0);
			if (NULL != newBitMap)
				TRACE("Recovered using NULL as hDC.\n");
			else
			{
				int errCode2 = GetLastError();
				TRACE("Use NULL as hDC also fails - error code = %d.\n", errCode2);

				// Try third method
				BITMAPINFO bmpinfo;
				memset(&bmpinfo, 0, sizeof(BITMAPINFO));
				bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
				bmpinfo.bmiHeader.biWidth = bmp->bmiHeader.biWidth;
				bmpinfo.bmiHeader.biHeight = bmp->bmiHeader.biHeight;
				bmpinfo.bmiHeader.biPlanes = 1;
				bmpinfo.bmiHeader.biBitCount = 24;
				bmpinfo.bmiHeader.biCompression = BI_RGB;

				newBitMap = CreateDIBSection(cdc->GetSafeHdc(), &bmpinfo, DIB_RGB_COLORS, &m_currentBuffer, NULL, 0x0);
				if (NULL != newBitMap)
				{
					TRACE("Recovered using alternative BITMAPINFO.");
					if (!m_hasLogSample)
					{
						m_hasLogSample = true;

						FILE *f = fopen("error_bmpinfo.bin", "wb");
						fwrite(bmp, sizeof(BITMAPINFO), 1, f);
						fwrite(&bmpinfo, sizeof(BITMAPINFO), 1, f);
						fclose(f);

						TRACE("Created log of original and alternative BITMAPINFO in 'error_bmpinfo.bin'.\n");
					}
				}
				else
				{
					int errCode3 = GetLastError();
					TRACE("Use alternative BITMAPINFO(width=%d, height=%d) also fails - error code = %d.\n", bmpinfo.bmiHeader.biWidth,
						bmpinfo.bmiHeader.biHeight, errCode3);

					//
					// There is no other method for recovery, but we still do some more test to obtain more information.
					//

					bmpinfo.bmiHeader.biWidth = 1000;
					bmpinfo.bmiHeader.biHeight = 750;
					newBitMap = CreateDIBSection(cdc->GetSafeHdc(), &bmpinfo, DIB_RGB_COLORS, &m_currentBuffer, NULL, 0x0);
					if (NULL != newBitMap)
						TRACE("Verified can use hard coded bmp.\n");
					else
					{
						int errCode4 = GetLastError();
						TRACE("Using hard coded bmp also fails - error code = %d.\n", errCode4);
						
						newBitMap = CreateDIBSection(NULL, &bmpinfo, DIB_RGB_COLORS, &m_currentBuffer, NULL, 0x0);
						if (NULL != newBitMap)
							TRACE("Using verified can use NULL hDC with hard coded bmp.\n");
						else
						{
							int errCode5 = GetLastError();
							TRACE("Using NULL hDC with hard coded bmp also fails - error code = %d.\n", errCode5);
						}
					}

					// Cannot be recovered, so die anyway					
					assert(false);
				}
			}
		}

		memcpy(m_currentBuffer, data + *(int *)(data + 10), bmp->bmiHeader.biSizeImage);
		memcpy(&m_currentBITMAPINFO, bmp, sizeof(BITMAPINFO));
		self->ReleaseDC(cdc);	
	}
	else
		m_currentBuffer = 0;

	HBITMAP oldHBITMAP = self->SetBitmap(newBitMap);
	if (0 != oldHBITMAP)
		DeleteObject(oldHBITMAP);
	if ((0 != newBitMap) && (newBitMap != self->GetBitmap()))
	{
		m_WCCisV6 = 1;
		DeleteObject(newBitMap);
		m_currentBuffer = 0;
	}
}

void CStaticHelper::onPaint(CStatic *self)
{
	CPaintDC dc(self);
	if (m_testMode)
		return;

	CDC dcMem;
	if (!dcMem.CreateCompatibleDC(&dc))
		return;

	HBITMAP h = self->GetBitmap();
	if (h == NULL)
		return;

	CBitmap cbmp;
	if (!cbmp.Attach(h))
		return;

	BITMAP bmp;
	cbmp.GetBitmap(&bmp);

	CBitmap* oldCbmp = dcMem.SelectObject(&cbmp);

	if (bmp.bmBitsPixel == 32)
	{
		BLENDFUNCTION bfn = { 0 };
		bfn.BlendOp = AC_SRC_OVER;
		bfn.BlendFlags = 0;
		bfn.SourceConstantAlpha = 255;
		bfn.AlphaFormat = AC_SRC_ALPHA;

		AlphaBlend(dc.GetSafeHdc(), 0, 0, bmp.bmWidth, bmp.bmHeight, dcMem.GetSafeHdc(),
			0, 0, bmp.bmWidth, bmp.bmHeight, bfn);
	}
	else
		dc.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &dcMem, 0, 0, SRCCOPY);

	dcMem.SelectObject(oldCbmp);
	cbmp.Detach();
}

void CStaticHelper::displayChart(CStatic *self, BaseChart *c)
{
    if (0 == c)
    {
        setHBITMAP(self, 0);
        return;
    }

    c->setTransparentColor(-2);
	c->setOutputOptions((m_WCCisV6 >= 0) ? "alpha" : "alpha=3");
	MemBlock m = c->makeChart(Chart::BMP);

	if ((m_WCCisV6 == 0) && (((const BITMAPINFO *)(m.data + 14))->bmiHeader.biBitCount == 32))
	{
        m_WCCisV6 = -1;

	    m_testMode = true;
		PieChart testC(1, 1, 0x7fffffff);
        displayChart(self, &testC);
        displayChart(self, 0);
		m_testMode = false;

        if (m_WCCisV6 <= 0)
		{
			displayChart(self, c);
            return;
		}
    }

	setHBITMAP(self, m.data);
}


/////////////////////////////////////////////////////////////////////////////
//
// CViewPortControl
//
/////////////////////////////////////////////////////////////////////////////

//
// Constructor
//
CViewPortControl::CViewPortControl()
{
	m_ChartViewer = 0;
	m_Chart = 0;
	m_ReentrantGuard = false;

	m_mouseDownX = 0;
	m_mouseDownY = 0;
}

BEGIN_MESSAGE_MAP(CViewPortControl,	CStatic)
	//{{AFX_MSG_MAP(CViewPortControl)
	ON_WM_MOUSEMOVE()
	ON_WM_MOUSEWHEEL()
	ON_WM_SETCURSOR()
	ON_WM_DESTROY()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_PAINT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//
// Free	resources
//
void CViewPortControl::OnDestroy() 
{
	setViewer(0);
	displayChart(this, 0);
	
	CStatic::OnDestroy();
}

//
// Set the CChartViewer to be associated with this control
//
void CViewPortControl::setViewer(CChartViewer *viewer)
{
	if (m_ReentrantGuard)
		return;
	m_ReentrantGuard = true;

	if (0 != m_ChartViewer)
		m_ChartViewer->setViewPortControl(0);
	m_ChartViewer = viewer;
	setViewPortManager(viewer);
	if (0 != m_ChartViewer)
		m_ChartViewer->setViewPortControl(this);

	m_ReentrantGuard = false;
	
	updateDisplay();
}

//
// Get back	the	same CChartViewer pointer provided	by the previous	setViewer call.
//
CChartViewer *CViewPortControl::getViewer()
{
	return m_ChartViewer;
}

//
// Set the chart to	be displayed in the	control
//
void CViewPortControl::setChart(BaseChart *c)
{
	// In case the user	forgets	to check the "Notify" check	box	in the Dialog editor, we set it
	// ourselves so	the	CChartViewer control can receive mouse events.
	if ((GetStyle()	& SS_NOTIFY) ==	0)
		ModifyStyle(0, SS_NOTIFY);

	m_Chart = c;
	ViewPortControlBase::setChart(c);
	updateDisplay();
}

//
// Get back	the	same BaseChart pointer provided	by the previous	setChart call.
//
BaseChart *CViewPortControl::getChart()
{
	return m_Chart;
}

//
// Convert from mouse x-coordinate to image x-coordinate
//
double CViewPortControl::toImageX(int x)
{
	// In this version, no conversion is done. It is assumed the control does not stretch or shrink
	// the image and does not provide any additional margin to offset the image.
	return x;
}

//
// Convert from mouse x-coordinate to image x-coordinate
//
double CViewPortControl::toImageY(int y)
{
	// In this version, no conversion is done. It is assumed the control does not stretch or shrink
	// the image and does not provide any additional margin to offset the image.
	return y;
}

//
// Display the chart
//
void CViewPortControl::paintDisplay()
{
	displayChart(this, getChart()); 
}

//
// Custom OnPaint to support alpha transparency
//
void CViewPortControl::OnPaint() 
{
	onPaint(this);
}

//
// Mouse left button down event.
//
void CViewPortControl::OnLButtonDown(UINT nFlags, CPoint point)
{
	CStatic::OnLButtonDown(nFlags, point);

	// Enable mouse capture for drag support
	SetCapture();

	// Remember current mouse position
	m_mouseDownX = point.x;
	m_mouseDownY = point.y;

	// Get the CChartViewer zoom/scroll state to determine which type of mouse action is allowed
	syncState();

	// Handle the mouse down event
	handleMouseDown(toImageX(point.x), toImageY(point.y));

	// Update the chart viewer if the viewport has changed
	updateChartViewerIfNecessary();
}

//
// MouseMove event handler
//
void CViewPortControl::OnMouseMove(UINT nFlags, CPoint point)
{
	// Get the CChartViewer zoom/scroll state to determine which type of mouse action is allowed
	syncState();

	// Handle the mouse move event
	handleMouseMove(toImageX(point.x), toImageY(point.y), isDrag(point));

	// Update the chart viewer if the viewport has changed
	updateChartViewerIfNecessary();

	// Mouse wheel events are only sent to the control in focus. So if mouse wheel zooming is 
	// enabled, we must get the focus in order to receive mouse wheel events.
	if ((0 != m_ChartViewer) && (m_ChartViewer->getMouseWheelZoomRatio() != 1)
		&& isOnPlotArea(toImageX(point.x), toImageY(point.y)))
		SetFocus();

	// Update the mouse cursor
	HCURSOR cursor = getHCursor(getCursor());
	if (cursor != NULL)
		::SetCursor(cursor);

	// Update the display
	if (needUpdateDisplay())
		paintDisplay();
}

//
// Mouse left button up event.
//
void CViewPortControl::OnLButtonUp(UINT nFlags, CPoint point)
{
	CStatic::OnLButtonUp(nFlags, point);
	ReleaseCapture();

	// Get the CChartViewer zoom/scroll state to determine which type of mouse action is allowed
	syncState();

	// Handle the mouse up event
	handleMouseUp(toImageX(point.x), toImageY(point.y));

	// Update the chart viewer if the viewport has changed
	updateChartViewerIfNecessary();
}

//
// MouseWheel handler
//
BOOL CViewPortControl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// Process the mouse wheel only if the mouse is over the plot area
	ScreenToClient(&pt);
	if ((0 == m_ChartViewer) || (!isOnPlotArea(toImageX(pt.x), toImageY(pt.y))))
		return FALSE;

	// Ask the CChartViewer to zoom around the center of the chart
	int x = m_ChartViewer->getPlotAreaLeft() + m_ChartViewer->getPlotAreaWidth() / 2;
	int y = m_ChartViewer->getPlotAreaTop() + m_ChartViewer->getPlotAreaHeight() / 2;
	return m_ChartViewer->onMouseWheelZoom(x, y, zDelta);
}

//
// Get the CChartViewer zoom/scroll state
//
void CViewPortControl::syncState()
{
	CChartViewer *viewer = getViewer();
	if (0 != viewer)
		setZoomScrollDirection(viewer->getZoomDirection(), viewer->getScrollDirection());
}

//
// Determines if the mouse is dragging.
//
bool CViewPortControl::isDrag(CPoint point)
{
	CChartViewer *viewer = getViewer();
	if (0 == viewer)
		return false;

	int minimumDrag = viewer->getMinimumDrag();
	int moveX = abs(m_mouseDownX - point.x);
	int moveY = abs(m_mouseDownY - point.y);
	return (moveX >= minimumDrag) || (moveY >= minimumDrag);
}

//
// Update the display
//
void CViewPortControl::updateDisplay()
{
	paintViewPort();
	paintDisplay();
}

//
// Update the CChartViewer if the viewport has changed
//
void CViewPortControl::updateChartViewerIfNecessary()
{
	CChartViewer *viewer = getViewer();
	if (0 == viewer)
		return;
	
	if (needUpdateChart() || needUpdateImageMap())
		viewer->updateViewPort(needUpdateChart(), needUpdateImageMap());
}

//
// The the mouse cursor
//
HCURSOR CViewPortControl::getHCursor(int position)
{
	switch (position)
	{
	case Chart::Left:
	case Chart::Right:
		//IDC_SIZEWE = 32644
		return AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(32644));
	case Chart::Top:
	case Chart::Bottom:
		//IDC_SIZENS = 32645
		return AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(32645));
	case Chart::TopLeft:
	case Chart::BottomRight:
		//IDC_SIZENWSE = 32642
		return AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(32642));
	case Chart::TopRight:
	case Chart::BottomLeft:
		//IDC_SIZENESW = 32643
		return AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(32643));
	}

	//IDC_ARROW = 32512
	return AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(32512));
}

//
// Intercept WM_SETCURSOR to change	the	mouse cursor.
//
BOOL CViewPortControl::OnSetCursor(CWnd* pWnd, UINT	nHitTest, UINT message)	
{
	HCURSOR cursor = getHCursor(getCursor());
	if (cursor != NULL)
	{
		::SetCursor(cursor);
		return TRUE;
	}

	return CStatic::OnSetCursor(pWnd, nHitTest,	message);
}
ChartViewer.h
///////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Advanced Software Engineering Limited
//
// You may use and modify the code in this file in your application, provided the code and
// its modifications are used only in conjunction with ChartDirector. Usage of this software
// is subjected to the terms and condition of the ChartDirector license.
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "chartdir.h"
#include <afxmt.h>

//
// Constants
//
#ifdef CD_NAMESPACE
namespace CD_NAMESPACE
{
#endif

namespace Chart
{
	//
	// Mouse usage mode constants
	//
	enum 
	{ 
		MouseUsageDefault = 0,
		MouseUsageScroll = 1,
		MouseUsageZoomIn = 3,
		MouseUsageZoomOut = 4,
	};
}

#ifdef CD_NAMESPACE
}
#endif

//
// Forward declarations
//
class CViewPortControl;

//
// Utility to convert from UTF8 string to MFC TCHAR string.
//
class UTF8toTCHAR
{
public :
	UTF8toTCHAR(const char *utf8_string) : t_string(0), needFree(false)
	{
		if (0 == utf8_string)
			t_string = 0;
		else if (0 == *utf8_string)
			t_string = _T("");
		else if ((sizeof(TCHAR) == sizeof(char)) && isPureAscii(utf8_string))
			// No conversion needed for pure ASCII text
			t_string = (TCHAR *)utf8_string;
		else
		{
			// Either TCHAR = Unicode (2 bytes), or utf8_string contains non-ASCII characters.
			// Needs conversion
			needFree = true;

			// Convert to Unicode (2 bytes)
			int string_len = (int)strlen(utf8_string);
			wchar_t *buffer = new wchar_t[string_len + 1];
			MultiByteToWideChar(CP_UTF8, 0, utf8_string, -1, buffer, string_len + 1);
			buffer[string_len] = 0;
					
#ifdef _UNICODE
			t_string = buffer;
#else
			// TCHAR is MBCS - need to convert back to MBCS
			t_string = new char[string_len * 2 + 2];
			WideCharToMultiByte(CP_ACP, 0, buffer, -1, t_string, string_len * 2 + 1, 0, 0);
			t_string[string_len * 2 + 1] = 0;
			delete[] buffer;
#endif
		}

	}

	operator const TCHAR*()
	{
		return t_string;
	}

	~UTF8toTCHAR()
	{
		if (needFree)
			delete[] t_string;
	}

private :
	TCHAR *t_string;
	bool needFree;

	//
	// helper utility to test if a string contains only ASCII characters
	//
	bool isPureAscii(const char *s)
	{
		while (*s != 0) { if (*(s++) & 0x80) return false; }
		return true;
	}

	//disable assignment
	UTF8toTCHAR(const UTF8toTCHAR &rhs);
	UTF8toTCHAR &operator=(const UTF8toTCHAR &rhs);
};

//
// Utility to convert from MFC TCHAR string to UTF8 string
//
class TCHARtoUTF8
{
public :
	TCHARtoUTF8(const TCHAR *t_string) : utf8_string(0), needFree(false)
	{
		if (0 == t_string)
			utf8_string = 0;
		else if (0 == *t_string)
			utf8_string = "";
		else if ((sizeof(TCHAR) == sizeof(char)) && isPureAscii((char *)t_string))
			// No conversion needed for pure ASCII text
			utf8_string = (char *)t_string;
		else
		{
			// TCHAR is non-ASCII. Needs conversion.
	
			needFree = true;
			int string_len = (int)_tcslen(t_string);

#ifndef _UNICODE
			// Convert to Unicode if not already in unicode.
			wchar_t *w_string = new wchar_t[string_len + 1];
			MultiByteToWideChar(CP_ACP, 0, t_string, -1, w_string, string_len + 1);
			w_string[string_len] = 0;
#else
			wchar_t *w_string = (wchar_t*)t_string;
#endif

			// Convert from Unicode (2 bytes) to UTF8
			utf8_string = new char[string_len * 3 + 1];
			WideCharToMultiByte(CP_UTF8, 0, w_string, -1, utf8_string, string_len * 3 + 1, 0, 0);
			utf8_string[string_len * 3] = 0;
					
			if (w_string != (wchar_t *)t_string)
				delete[] w_string;
		}

	}

	operator const char*()
	{
		return utf8_string;
	}

	~TCHARtoUTF8()
	{
		if (needFree)
			delete[] utf8_string;
	}

private :
	char *utf8_string;
	bool needFree;

	//
	// helper utility to test if a string contains only ASCII characters
	//
	bool isPureAscii(const char *s)
	{
		while (*s != 0) { if (*(s++) & 0x80) return false; }
		return true;
	}
	
	//disable assignment
	TCHARtoUTF8(const TCHARtoUTF8 &rhs);
	TCHARtoUTF8 &operator=(const TCHARtoUTF8 &rhs);
};

/////////////////////////////////////////////////////////////////////////////
// CRectCtrl window

//
// A rectangle with a background color. Use as thick edges for the selection
// rectangle.
//

class CRectCtrl : public CStatic
{
public:
// Public interface
	BOOL Create(CWnd* pParentWnd, COLORREF c);
	void SetColor(COLORREF c);

// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CRectCtrl)
	//}}AFX_VIRTUAL

protected:
// Generated message map functions
	//{{AFX_MSG(CRectCtrl)
	afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor);
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()

private :
	CBrush m_Color;
};


/////////////////////////////////////////////////////////////////////////////
// CStaticHelper utiltiies

class CStaticHelper
{
private:
	BITMAPINFO m_currentBITMAPINFO;
	void *m_currentBuffer;
	int m_WCCisV6;
	bool m_testMode;
	bool m_hasLogSample;

	void setHBITMAP(CStatic *self, const char *data);

public:
	CStaticHelper();
	
	void onPaint(CStatic *self);
	void displayChart(CStatic *self, BaseChart *c);
};


/////////////////////////////////////////////////////////////////////////////
// CChartViewer window

//
// Event message ID
//
#define CVN_ViewPortChanged	1000			// View port has changed
#define CVN_MouseMoveChart 1001				// Mouse moves over the chart
#define CVN_MouseLeaveChart 1002			// Mouse leaves the chart
#define CVN_MouseMovePlotArea 1003			// Mouse moves over the extended plot area
#define CVN_MouseLeavePlotArea 1004			// Mouse leaves the extended plot area


class CChartViewer : public CStatic, public ViewPortManager, private CStaticHelper
{
public:
	CChartViewer();

	//
	// CChartViewer properties
	//

	virtual void setChart(BaseChart *c);
	virtual BaseChart *getChart();

	virtual void setViewPortControl(CViewPortControl *vpc);
	virtual CViewPortControl *getViewPortControl();

	virtual void setImageMap(const char *imageMap);
	virtual ImageMapHandler *getImageMapHandler();

	virtual void setDefaultToolTip(LPCTSTR text);
	virtual CToolTipCtrl *getToolTipCtrl();

	virtual void setSelectionBorderWidth(int width);
	virtual int getSelectionBorderWidth();

	virtual void setSelectionBorderColor(COLORREF c);
	virtual COLORREF getSelectionBorderColor();
	
	virtual void setMouseUsage(int mouseUsage);
	virtual int getMouseUsage();

	virtual void setZoomDirection(int direction);
	virtual int getZoomDirection();
	
	virtual void setScrollDirection(int direction);
	virtual int getScrollDirection();

	virtual void setZoomInRatio(double ratio);
	virtual double getZoomInRatio();

	virtual void setZoomOutRatio(double ratio);
	virtual double getZoomOutRatio();

	virtual void setMouseWheelZoomRatio(double ratio);
	virtual double getMouseWheelZoomRatio();

	virtual void setMinimumDrag(int offset);
	virtual int getMinimumDrag();

	virtual void setUpdateInterval(int interval);
	virtual int getUpdateInterval();

	virtual bool needUpdateChart();
	virtual bool needUpdateImageMap();

	virtual bool isMouseOnPlotArea();
	virtual bool isInMouseMoveEvent();
	virtual bool isMouseDragging();

	//
	// CChartViewer methods
	//

	// Trigger the ViewPortChanged event
	virtual void updateViewPort(bool needUpdateChart, bool needUpdateImageMap);
	
	// Request CChartViewer to redisplay the chart
	virtual void updateDisplay();

	// Handles mouse wheel zooming
	virtual BOOL onMouseWheelZoom(int x, int y, short zDelta);

	// Set the message used to remove the dynamic layer
	virtual void removeDynamicLayer(int msg);
	
	// Get the mouse coordinates
	virtual int getChartMouseX();
	virtual int getChartMouseY();

	// Get the mouse coordinates bounded to the plot area
	virtual int getPlotAreaMouseX();
	virtual int getPlotAreaMouseY();

	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CChartViewer)
	public:
	virtual BOOL PreTranslateMessage(MSG* pMsg);
	//}}AFX_VIRTUAL

protected:

	// Generated message map functions
	afx_msg void OnPaint();
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnDelayedMouseMove();
	afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
	afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
	afx_msg void OnDestroy();
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	DECLARE_MESSAGE_MAP()

private:

	//
	// CChartViewer configurable properties
	//

	BaseChart *m_currentChart;			// Current BaseChart object
	ImageMapHandler *m_hotSpotTester;	// ImageMapHander representing the image map
	CString m_defaultToolTip;			// Default tool tip text
	CToolTipCtrl m_ToolTip;				// CToolTipCtrl for managing tool tips
	bool m_toolTipHasAttached;			// CToolTipCtrl has attached to CChartViewer
	COLORREF m_selectBoxLineColor;		// Selectiom box border color
	int m_selectBoxLineWidth;			// Selectiom box border width
	int m_mouseUsage;					// Mouse usage mode
	int m_zoomDirection;				// Zoom direction
	int m_scrollDirection;				// Scroll direction
	double m_zoomInRatio;				// Click zoom in ratio
	double m_zoomOutRatio;				// Click zoom out ratio
	double m_mouseWheelZoomRatio;		// Mouse wheel zoom ratio
	int m_minDragAmount;				// Minimum drag amount
	int m_updateInterval;				// Minimum interval between chart updates
	bool m_needUpdateChart;				// Has pending chart update request
	bool m_needUpdateImageMap;			// Has pending image map udpate request

	//
	// Keep track of mouse states
	//

	int m_currentHotSpot;				// The hot spot under the mouse cursor.
	bool m_isClickable;					// Mouse is over a clickable hot spot.
	bool m_isOnPlotArea;				// Mouse is over the plot area.
	bool m_isPlotAreaMouseDown;			// Mouse left button is down in the plot area.
	bool m_isDragScrolling;				// Is drag scrolling the chart.
	bool m_isMouseTracking;				// Is tracking mouse leave event.
    bool m_isInMouseMove;				// Is in mouse moeve event handler

	//
	// Dragging support
	//

	int m_plotAreaMouseDownXPos;		// The starting x coordinate of the mouse drag.
	int m_plotAreaMouseDownYPos;		// The starting y coordinate of the mouse drag.
	bool isDrag(int direction, CPoint point);				// Check if mouse is dragging
	void OnPlotAreaMouseDrag(UINT nFlags, CPoint point);	// Process mouse dragging

	//
	// Selection rectangle
	//

	CRectCtrl m_LeftLine;				// Left edge of selection rectangle
	CRectCtrl m_RightLine;				// Right edge of selection rectangle
	CRectCtrl m_TopLine;				// Top edge of selection rectangle
	CRectCtrl m_BottomLine;				// Bottom edge of selection rectangle
	
	void initRect();					// Initialize selection rectangle edges
	void drawRect(int x, int y, int width, int height);		// Draw selection rectangle
	void setRectVisible(bool b);		// Show/hide selection rectangle

	//
	// Chart update rate control
	//

	bool m_holdTimerActive;				// Delay chart update to limit update frequency
	
	int m_delayUpdateChart;				// Delay chart update until the mouse event is completed
	BaseChart *m_delayedChart;			// The chart to be used for delayed update.
	void commitUpdateChart();			// Commit updating the chart.

	unsigned int m_lastMouseMove;		// The timestamp of the last mouse move event.
	bool m_hasDelayedMouseMove;			// Delay the mouse move event to allow other updates
	UINT m_delayedMouseMoveFlag;		// The mouse key flags of the delayed mouse move event.
	CPoint m_delayedMouseMovePoint;		// The mouse coordinates of the delayed mouse move event.
	void commitMouseMove(UINT nFlags, CPoint point);    // Raise the delayed mouse move event.

	bool m_delayImageMapUpdate;			// Delay image map update until mouse moves on plot area

	//
	// Track Cursor support
	//

	int m_currentMouseX;				// Get the mouse x-pixel coordinate
	int m_currentMouseY;				// Get the mouse y-pixel coordinate
	int m_isInMouseMovePlotArea;		// flag to indicate if is in a mouse move plot area event.

	//
	// Dynamic Layer support
	//

	int m_autoHideMsg;					// The message that will trigger removing the dynamic layer.
	void applyAutoHide(int msg);		// Attempt to remove the dynamic layer with the given message.

	//
	// CViewPortControl support
	//
	
	CViewPortControl *m_vpControl;		// Associated CViewPortControl      
	bool m_ReentrantGuard;				// Prevents infinite calling loops

	//
	// Debugging Support
	//
	DWORD m_threadId;
	inline void verifyThread();
};

/////////////////////////////////////////////////////////////////////////////
// CViewPortControl window

class CViewPortControl : public CStatic, public ViewPortControlBase, private CStaticHelper
{
public:
	CViewPortControl();

	// Set the chart to be displayed in the control
	virtual void setChart(BaseChart *c);
	virtual BaseChart *getChart();
	
	// Associated CChartViewer
	virtual void setViewer(CChartViewer *viewer);
	virtual CChartViewer *getViewer();

	// Request the CViewPortControl to update its contents 
	virtual void updateDisplay();

protected:

	// Generated message map functions
	afx_msg void OnDestroy();
	afx_msg void OnPaint();
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
	DECLARE_MESSAGE_MAP()

private:

	CChartViewer *m_ChartViewer;		// Associated CChartViewer
	BaseChart *m_Chart;					// BaseChart object displayed in the control
	bool m_ReentrantGuard;				// Prevents infinite calling loops

	int m_mouseDownX;					// Current mouse x coordinates
	int m_mouseDownY;					// Current mouse y coordinates
	bool isDrag(CPoint point);			// Check if mouse is dragging

	HCURSOR getHCursor(int position);	// Get the mouse cursor
	void paintDisplay();				// Paint the display
	void syncState();					// Synchronize the CViewPortControl with CChartViewer
	void updateChartViewerIfNecessary();	// Update CChartViewer if viewport has changed

	double toImageX(int x);				// Convert from mouse to image x-coordinate
	double toImageY(int y);				// Convert from mouse to image y-coordinate
};

  Re: Break point generated during real-time display
Posted by Peter Kwan on Mar-11-2018 09:45
For anyone finding this thread, I would write down how this problem is solved. The problem turns out to be due to a "resource leak" in other parts of the application.

You can configure the Task Manager to display "Handles", "User Objs" and "GDI Objs" usage for the application. In this particular case, the GDI Objs count increases indefinitely. Once it reaches 10000 or a certain limit, the application cannot create any more GDI Objs, and the display will freeze or the program will crash. Fixing the leak solves the problem.