Click here to Skip to main content
15,896,207 members
Articles / Desktop Programming / MFC

An MFC ActiveX control to display trays

Rate me:
Please Sign up or sign in to vote.
4.20/5 (2 votes)
15 Nov 2001CPOL2 min read 84.7K   1.4K   16  
An MFC ActiveX control to display trays and samples, with selection and cherry picking properties
// TrayControlCtl.cpp : Implementation of the CTrayControlCtrl ActiveX Control class.

#include "stdafx.h"
#include "TrayControl.h"
#include "TrayControlCtl.h"
#include "TrayControlPpg.h"
#include "MemDC.h"
#include "ColorNames.h"


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


IMPLEMENT_DYNCREATE(CTrayControlCtrl, COleControl)

// from the colourNames posted on CodeProject by Ales Krajnc see url http://www.codeproject.com/gdi/colornames.asp
// set up a table of colours I like
COLORREF default_colours[] =
		{
		colWhite,
		colBlue,
		colDarkGreen,
		colRed,
		colYellow,
		colSlateGray,
		colCyan,
		colPurple,
		colRoyalBlue,
		colBrown,
		colGreen,
		colGoldenRod,
		colLightSalmon,
		colSlateBlue,
		colDarkKhaki,
		colNavyblue,
		colPowderBlue,
		colDarkSeaGreen,
		colLightGrey,
		colLightPink,
		colSandyBrown
		} ;
/////////////////////////////////////////////////////////////////////////////
// Message map

BEGIN_MESSAGE_MAP(CTrayControlCtrl, COleControl)
	//{{AFX_MSG_MAP(CTrayControlCtrl)
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_MOUSEMOVE()
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_TIMER()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_RBUTTONDOWN()
	//}}AFX_MSG_MAP
	ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// Dispatch map

BEGIN_DISPATCH_MAP(CTrayControlCtrl, COleControl)
	//{{AFX_DISPATCH_MAP(CTrayControlCtrl)
	DISP_PROPERTY_EX(CTrayControlCtrl, "XSize", GetXSize, SetXSize, VT_I4)
	DISP_PROPERTY_EX(CTrayControlCtrl, "YSize", GetYSize, SetYSize, VT_I4)
	DISP_PROPERTY_EX(CTrayControlCtrl, "ShowCellTitles", GetShowCellTitles, SetShowCellTitles, VT_I4)
	DISP_PROPERTY_EX(CTrayControlCtrl, "ShowLabels", GetShowLabels, SetShowLabels, VT_I4)
	DISP_PROPERTY_EX(CTrayControlCtrl, "XLabelsAlpha", GetXLabelsAlpha, SetXLabelsAlpha, VT_I4)
	DISP_PROPERTY_EX(CTrayControlCtrl, "YLabelsAlpha", GetYLabelsAlpha, SetYLabelsAlpha, VT_I4)
	DISP_PROPERTY_EX(CTrayControlCtrl, "TrayOrieintation", GetTrayOrieintation, SetTrayOrieintation, VT_I4)
	DISP_FUNCTION(CTrayControlCtrl, "EnableDoubleBuffering", EnableDoubleBuffering, VT_I4, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "GetSampleName", GetSampleName, VT_BSTR, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "GetSampleColourIndex", GetSampleColourIndex, VT_I4, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "GetColourRGB", GetColourRGB, VT_I4, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "SetColourRGB", SetColourRGB, VT_I4, VTS_I4 VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "SetSampleName", SetSampleName, VT_I4, VTS_I4 VTS_BSTR)
	DISP_FUNCTION(CTrayControlCtrl, "SetSampleColour", SetSampleColour, VT_I4, VTS_I4 VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "SetSampleCellCount", SetSampleCellCount, VT_EMPTY, VTS_I4 VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "GetSampleCellCount", GetSampleCellCount, VT_I4, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "EnableTitleTips", EnableTitleTips, VT_EMPTY, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "EnableCherryPicking", EnableCherryPicking, VT_EMPTY, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "GetCherryCount", GetCherryCount, VT_I4, VTS_NONE)
	DISP_FUNCTION(CTrayControlCtrl, "GetCherryIndex", GetCherryIndex, VT_I4, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "RemoveAll", RemoveAll, VT_EMPTY, VTS_NONE)
	DISP_FUNCTION(CTrayControlCtrl, "SetFlashState", SetFlashState, VT_EMPTY, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "SetFlashIndex", SetFlashIndex, VT_EMPTY, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "SetFlashRate", SetFlashRate, VT_EMPTY, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "SetSelectionState", SetSelectionState, VT_EMPTY, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "GetSampleIndex", GetSampleIndex, VT_I4, VTS_NONE)
	DISP_FUNCTION(CTrayControlCtrl, "AddSample", AddSample, VT_BOOL, VTS_BSTR VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "SetSampleIndex", SetSampleIndex, VT_EMPTY, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "SetShowNextInsertPosition", SetShowNextInsertPosition, VT_EMPTY, VTS_I4)
	DISP_FUNCTION(CTrayControlCtrl, "GetPositionLabel", GetPositionLabel, VT_BSTR, VTS_I4)
	//}}AFX_DISPATCH_MAP
	DISP_FUNCTION_ID(CTrayControlCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()


/////////////////////////////////////////////////////////////////////////////
// Event map

BEGIN_EVENT_MAP(CTrayControlCtrl, COleControl)
	//{{AFX_EVENT_MAP(CTrayControlCtrl)
	EVENT_CUSTOM("ControlClicked", FireControlClicked, VTS_I4)
	EVENT_CUSTOM("CherryPicked", FireCherryPicked, VTS_NONE)
	EVENT_CUSTOM("InsertPointChanged", FireInsertPointChanged, VTS_NONE)
	EVENT_CUSTOM("ControlRightClicked", FireControlRightClicked, VTS_I4)
	//}}AFX_EVENT_MAP
END_EVENT_MAP()


/////////////////////////////////////////////////////////////////////////////
// Property pages

// TODO: Add more property pages as needed.  Remember to increase the count!
BEGIN_PROPPAGEIDS(CTrayControlCtrl, 1)
	PROPPAGEID(CTrayControlPropPage::guid)
END_PROPPAGEIDS(CTrayControlCtrl)


/////////////////////////////////////////////////////////////////////////////
// Initialize class factory and guid

IMPLEMENT_OLECREATE_EX(CTrayControlCtrl, "TRAYCONTROL.TrayControlCtrl.1",
	0x80ab393a, 0x6f9f, 0x11d5, 0xad, 0x35, 0, 0xb0, 0xd0, 0x65, 0x2e, 0x95)


/////////////////////////////////////////////////////////////////////////////
// Type library ID and version

IMPLEMENT_OLETYPELIB(CTrayControlCtrl, _tlid, _wVerMajor, _wVerMinor)


/////////////////////////////////////////////////////////////////////////////
// Interface IDs

const IID BASED_CODE IID_DTrayControl =
		{ 0x80ab3938, 0x6f9f, 0x11d5, { 0xad, 0x35, 0, 0xb0, 0xd0, 0x65, 0x2e, 0x95 } };
const IID BASED_CODE IID_DTrayControlEvents =
		{ 0x80ab3939, 0x6f9f, 0x11d5, { 0xad, 0x35, 0, 0xb0, 0xd0, 0x65, 0x2e, 0x95 } };


/////////////////////////////////////////////////////////////////////////////
// Control type information

static const DWORD BASED_CODE _dwTrayControlOleMisc =
	OLEMISC_ACTIVATEWHENVISIBLE |
	OLEMISC_IGNOREACTIVATEWHENVISIBLE |
	OLEMISC_SETCLIENTSITEFIRST |
	OLEMISC_INSIDEOUT |
	OLEMISC_CANTLINKINSIDE |
	OLEMISC_RECOMPOSEONRESIZE;

IMPLEMENT_OLECTLTYPE(CTrayControlCtrl, IDS_TRAYCONTROL, _dwTrayControlOleMisc)


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl::CTrayControlCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CTrayControlCtrl

BOOL CTrayControlCtrl::CTrayControlCtrlFactory::UpdateRegistry(BOOL bRegister)
{
	// TODO: Verify that your control follows apartment-model threading rules.
	// Refer to MFC TechNote 64 for more information.
	// If your control does not conform to the apartment-model rules, then
	// you must modify the code below, changing the 6th parameter from
	// afxRegApartmentThreading to 0.

	if (bRegister)
		return AfxOleRegisterControlClass(
			AfxGetInstanceHandle(),
			m_clsid,
			m_lpszProgID,
			IDS_TRAYCONTROL,
			IDB_TRAYCONTROL,
			afxRegApartmentThreading,
			_dwTrayControlOleMisc,
			_tlid,
			_wVerMajor,
			_wVerMinor);
	else
		return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
}


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl::CTrayControlCtrl - Constructor

CTrayControlCtrl::CTrayControlCtrl()
{
	InitializeIIDs(&IID_DTrayControl, &IID_DTrayControlEvents);

	// setup properties
	m_xSize = 12 ;
	m_ySize = 8 ;
	m_bHasFocus = false ;
	m_bShowLabels = false ;
	m_bXAxisAlpha = true ;
	m_bYAxisAlpha = false ;
	m_bTitleTipsEnabled = false ;
	m_TitleTipIndex = CPoint(-1, -1) ;
	m_bDoubleBuffering = false ;
	m_bCherryPicking = false ;
	m_bCherryDrag = false ;
	m_CherryIndex = -1 ;
	m_bTracking = false ;
	m_bDrawTracker = false ;
	m_bAutoChooseLabels = true ;

	// setup sample colour infromation
	m_pColourIndexes = new int[m_xSize * m_ySize] ;
	memset(m_pColourIndexes, 0, sizeof(int) * m_xSize * m_ySize) ;
	// setup samples selection information
	m_bSelected = new bool[m_xSize * m_ySize] ;
	memset(m_bSelected, 0, sizeof(bool) * m_xSize * m_ySize) ;
	// setup the ocunt indexes
	m_Count = new int[m_xSize * m_ySize] ;
	memset(m_Count, 0, sizeof(int) * m_xSize * m_ySize) ;
	// setup sample name information
	m_Names = new CString[m_xSize * m_ySize] ;
	// put in 3 example samples for use when placing the control in a resource editor or using the ActiveX text container
	m_Names[0] = "Example 1" ;
	m_Names[1] = "Example 2" ;
	m_Names[2] = "Example 3" ;
	// setup colour table infromation
	m_NumColours = sizeof(default_colours) / sizeof(COLORREF) ;
	m_pColours = new COLORREF[m_NumColours] ;
	memcpy(m_pColours, default_colours, sizeof(COLORREF) * m_NumColours) ;

	m_orientation = RightDown ;
	// example counts for resource editor in VC etc
	m_Count[0] = 3 ;
	m_Count[1] = 2 ;
	m_Count[2] = 1 ;

	// flashing
	m_Timer = 0 ;
	m_bFlashing  = false ;
	m_FlashingIndex = -1 ;
	m_bFlashOn = false ;
	m_FlashRate = 500 ;		// ms
	m_bSelectionState = false ;
	m_SampleIndex = 0 ;
	m_bShowSampleIndex = false ;
}


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl::~CTrayControlCtrl - Destructor

CTrayControlCtrl::~CTrayControlCtrl()
{
	// release any memory used
	delete []m_bSelected ;
	delete []m_Names ;
	delete []m_pColours ;
	delete []m_pColourIndexes ;
	delete []m_Count ;
}


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl::OnDraw - Drawing function

void CTrayControlCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	CRect	rect ;

	// KNOWN PROBLEM : If the activeX control is in a from view and is not completely visible when being drawn,
	// when it receives the focus, we get an invalid size reported to us, so the control thinks its window area is
	// only the visible area of the control. THis only happens when the control gets the focus the first time. I have
	// spent a long time looking for a fix for this, but no joy! Sob Sob!
	GetWindowRect(&rect) ;				// get size of window to know to draw in, I don;t trust the passed rcBounds!
	rect -= rect.TopLeft() ;

	// NOTE : I had by default to set double buffereing to false as the control would not draw in a resource editor
	// it set to true. Not sure why this happens.
	if (m_bDoubleBuffering)				// use memDC for flicker free drawing? (Thanks to Keith Ruls)
		{
		CMemDC		dc(pdc, rcBounds) ;			// save / restoreDC build into constructor  destructor
		DoDraw(&dc, rcBounds, rcInvalid) ;
		}
	else
		DoDraw(pdc, rcBounds, rcInvalid) ;		// not souble buffered
}

void CTrayControlCtrl::DoDraw(CDC *pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	// stage 1 : Calculate the size the cells can be to fit in the given location
	if (m_SampleIndex >= m_xSize * m_ySize)
		{
		// make sure the sample index is in the correct range
		m_SampleIndex = m_xSize * m_ySize - 1 ;
		FireInsertPointChanged() ;
		}

	pdc->SetMapMode(MM_TEXT) ;
	pdc->SetBkMode(TRANSPARENT) ;
	int		x_size ;
	int		y_size ;
	CString	text ;
	CSize	csText ;
	CFont	local_font ;

	// are we shown x,y labels for cell? If we are, count it as another column/row when calculating the cell size
	if (m_bShowLabels)
		{
		// title tips if enabled also use another row
		if (m_bTitleTipsEnabled)
			{
			x_size = rcBounds.Width() / (m_xSize + 1) ;			// 1 column for labels
			y_size = rcBounds.Height() / (m_ySize + 2) ;		// 1 column for labels + 1 column for title tip
			}
		else
			{
			x_size = rcBounds.Width() / (m_xSize + 1) ;			// 1 column for labels
			y_size = rcBounds.Height() / (m_ySize + 1) ;		// 1 column for labels
			}
		}
	else
		{
		// just he cells to be shown, possible title tips as well
		x_size = rcBounds.Width() / m_xSize ;
		if (m_bTitleTipsEnabled)
			y_size = rcBounds.Height() / (m_ySize + 1) ;		// 1 column for labels + 1 column for title tip
		else
			y_size = rcBounds.Height() / m_ySize ;
		}
	m_cell_size = x_size ;										// save the pixel size of the cells

	if (y_size < m_cell_size)									// us ethe samllest x,y size to keep cells square
		m_cell_size = y_size ;

	// make sure that the font text fits the size of the cell
	// we need to set the size of the font being used
	LOGFONT	lf ;
	memset(&lf, 0, sizeof(LOGFONT)) ;
	lf.lfHeight = m_cell_size - 1 ;
	lf.lfWidth = 0 ;
	lf.lfWeight = FW_NORMAL ;
	lf.lfCharSet = DEFAULT_CHARSET ;
	lf.lfClipPrecision = CLIP_CHARACTER_PRECIS ;
	lf.lfEscapement = 0 ;
	lf.lfItalic = FALSE ;
	lf.lfOrientation = 0L ;
	lf.lfOutPrecision = OUT_TT_PRECIS ;
	lf.lfPitchAndFamily = FF_ROMAN ;
	lf.lfQuality = DEFAULT_QUALITY ;
	strcpy(lf.lfFaceName, "Arial") ;							// I use the Arial font, but you don't have to!
	local_font.CreateFontIndirect(&lf) ;
	pdc->SelectObject(&local_font) ;

	// make sure there is space for the border around the tray
	if (m_cell_size * m_xSize + 4 > rcBounds.Width())
		m_cell_size-- ;
	else if (m_cell_size * m_ySize + 4 > rcBounds.Height())
		m_cell_size-- ;

	// generate the bounding rectangle of the control
	m_actual = CRect(0, 0, 0, 0) ;
	CRect	cell_rect ;
	m_actual.right = m_cell_size * m_xSize + 4 - 1 ;
	m_actual.bottom = m_cell_size * m_ySize + 4 - 1 ;
	if (m_bShowLabels)
		{
		if (m_bTitleTipsEnabled)
			m_actual += CSize((rcBounds.Width() - m_actual.Width() + m_cell_size) / 2,(rcBounds.Height() - m_actual.Height()) / 2);
		else
			m_actual += CSize((rcBounds.Width() - m_actual.Width() + m_cell_size) / 2,(rcBounds.Height() - m_actual.Height() + m_cell_size) / 2);
		}
	else
		{
		if (m_bTitleTipsEnabled)
			m_actual += CSize((rcBounds.Width() - m_actual.Width()) / 2,(rcBounds.Height() - m_actual.Height() - m_cell_size) / 2);
		else
			m_actual += CSize((rcBounds.Width() - m_actual.Width()) / 2,(rcBounds.Height() - m_actual.Height()) / 2) ;
		}
	// stage 2 : draw the tray border as a 3D rect
	// prepare the DC for drawing
	CBrush	background_brush ;
	CBrush	*old_brush ;
	CBrush	selected_brush ;

	// if the control is in a selected state, draw everything in reverse colours
	if (m_bSelectionState)
		{
		selected_brush.CreateSolidBrush(GetSysColor(COLOR_3DFACE)) ;
		background_brush.CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT)) ;
		pdc->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT)) ;
		}
	else
		{
		selected_brush.CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT)) ;
		background_brush.CreateSolidBrush(GetSysColor(COLOR_3DFACE)) ;
		pdc->SetTextColor(GetSysColor(COLOR_WINDOWTEXT)) ;
		}
	old_brush = (CBrush*)pdc->SelectObject(&background_brush) ;
	pdc->FillRect(rcBounds, &background_brush);
	// draw focus rectangle if we have the focus
	if (m_bHasFocus)
		pdc->DrawFocusRect(&rcBounds) ;
	pdc->Draw3dRect(&m_actual, GetSysColor(COLOR_3DHILIGHT), GetSysColor(COLOR_3DSHADOW)) ;	// actual tray border drawn here
	// draw the cells
	for (int i = 0 ; i < m_xSize ; i++)
		{
		cell_rect.left = 2 + i * m_cell_size + m_actual.left ;
		cell_rect.right = 2 + i * m_cell_size + m_cell_size - 1 + m_actual.left ;
		if (m_bShowLabels)
			{
			// show the x (column) labels
			text = GetXLabelText(i) ;					// get the column text
			csText = pdc->GetTextExtent(text) ;
			pdc->TextOut(cell_rect.left + (m_cell_size - csText.cx) / 2, m_actual.top - m_cell_size + (m_cell_size - csText.cy) / 2, text) ;
			}
		for (int j = 0 ; j < m_ySize ; j++)
			{
			cell_rect.top = 2 + j * m_cell_size + m_actual.top ;
			cell_rect.bottom = 2 + j * m_cell_size + m_cell_size - 1 + m_actual.top ;
			if (m_bShowLabels && i == 0)
				{
				// only draw the y (row) labels on the first pass if required
				text = GetYLabelText(j) ;				// get the row text
				csText = pdc->GetTextExtent(text) ;
				pdc->TextOut(m_actual.left - m_cell_size + (m_cell_size - csText.cx) / 2, cell_rect.top + (m_cell_size - csText.cy) / 2, text) ;
				}
			int index = GetOrientationIndex(i + j * m_xSize) ;
			if (!m_bFlashing || !m_bFlashOn || index != m_FlashingIndex)
				{
				if (m_bSelected[index])
					{
					//pdc->Draw3dRect(&cell_rect, GetSysColor(COLOR_3DSHADOW), GetSysColor(COLOR_3DHILIGHT)) ;
					// fill the inside of the rect with the selected colour, as its in a selected state
					pdc->FillRect(&cell_rect, &selected_brush) ;
					}
				else
					pdc->Draw3dRect(&cell_rect, GetSysColor(COLOR_3DHILIGHT), GetSysColor(COLOR_3DSHADOW)) ;
				}
			else
				{
				// show the flash state
				CBrush brush ;

				brush.CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT)) ;
				pdc->FillRect(&cell_rect, &brush) ;
				VERIFY(brush.DeleteObject()) ;
				}
			if (m_TitleTipIndex == CPoint(i, j))
				{
				// draw a focus around the cell under the mouse if shown title tips
				CRect	focus(cell_rect) ;
				focus.InflateRect(1, 1, 1, 1) ;
				pdc->DrawFocusRect(focus) ;
				}
			if (m_bShowSampleIndex && m_SampleIndex == index)
				{
				// draw a red square around the next insert position if shown
				CRect	hilight(cell_rect) ;
				CPen	pen(PS_SOLID, 1, colRed) ;
				CPen	*old = pdc->SelectObject(&pen) ;
				hilight.InflateRect(1, 1, 0, 0) ;
				pdc->MoveTo(hilight.TopLeft()) ;
				pdc->LineTo(hilight.right, hilight.top) ;
				pdc->LineTo(hilight.BottomRight()) ;
				pdc->LineTo(hilight.left, hilight.bottom) ;
				pdc->LineTo(hilight.TopLeft()) ;
				pdc->SelectObject(old) ;
				}
			if (m_Names[index] != "")
				{
				// if the name is not blank, show a sample in this position
				CRect	circle(cell_rect) ;
				CBrush	brush(m_pColours[m_pColourIndexes[index]]) ;
				CBrush	*pOld = pdc->SelectObject(&brush) ;
				circle.DeflateRect(1, 1) ;
				pdc->Ellipse(circle) ;
				pdc->SelectObject(pOld) ;
				if (m_Count[index] > 0)
					{
					// show number in cell if != 0
					text.Format("%1d", m_Count[index]) ;
					csText = pdc->GetTextExtent(text) ;
					pdc->TextOut(cell_rect.left + cell_rect.Width() / 2 - csText.cx / 2,
								cell_rect.top + cell_rect.Height() / 2 - csText.cy / 2,
								text) ;
					}
				}
			}
		}

	if (m_bDrawTracker)
		{
		// we only draw the tracker if the user is dragging a cherry pick selection
		CPoint point ;
		GetCursorPos(&point) ;
		ScreenToClient(&point) ;
		CRect	rect(m_TrackerStart, point) ;
		rect.NormalizeRect() ;					// make sure the rect draws correctly
		pdc->DrawFocusRect(&rect) ;
		}
	if (m_bTitleTipsEnabled && m_TitleTipIndex.x >= 0)
		{
		// display the title tip text
		text = "" ;
		if (m_bShowLabels)
			text = GetCellLabel(m_TitleTipIndex) ;
		text += m_Names[GetOrientationIndex(m_TitleTipIndex.x + m_TitleTipIndex.y * m_xSize)] ;
		csText = pdc->GetTextExtent(text) ;
		pdc->TextOut((rcBounds.Width() - csText.cx) / 2, m_actual.bottom, text) ;
		}
	// restore the DC
	pdc->SelectObject(old_brush) ;
	VERIFY(local_font.DeleteObject()) ;
	VERIFY(selected_brush.DeleteObject()) ;
	VERIFY(background_brush.DeleteObject()) ;
}


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl::DoPropExchange - Persistence support

void CTrayControlCtrl::DoPropExchange(CPropExchange* pPX)
{
	ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
	COleControl::DoPropExchange(pPX);

	long x = m_xSize ;
	long y = m_ySize ;

	PX_Long(pPX, _T("XSize"), x, 12);
	PX_Long(pPX, _T("YSize"), y, 8);
	PX_Long(pPX, _T("ShowCellTitles"), m_bTitleTipsEnabled, TRUE);
	PX_Long(pPX, _T("ShowLabels"), m_bShowLabels, TRUE);
	PX_Long(pPX, _T("XLabelsAlpha"), m_bXAxisAlpha, TRUE);
	PX_Long(pPX, _T("YLabelsAlpha"), m_bYAxisAlpha, FALSE);
	PX_Long(pPX, _T("TrayOrieintation"), m_orientation, FALSE);
	SetXSize(x) ;			// done this way to keep things in sync
	SetYSize(y) ;
}


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl::GetControlFlags -
// Flags to customize MFC's implementation of ActiveX controls.
//
// For information on using these flags, please see MFC technical note
// #nnn, "Optimizing an ActiveX Control".
DWORD CTrayControlCtrl::GetControlFlags()
{
	DWORD dwFlags = COleControl::GetControlFlags();


	// The control can receive mouse notifications when inactive.
	// TODO: if you write handlers for WM_SETCURSOR and WM_MOUSEMOVE,
	//		avoid using the m_hWnd member variable without first
	//		checking that its value is non-NULL.
	dwFlags |= pointerInactive;
	return dwFlags;
}


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl::OnResetState - Reset control to default state

void CTrayControlCtrl::OnResetState()
{
	COleControl::OnResetState();  // Resets defaults found in DoPropExchange

	// TODO: Reset any other control state here.
}


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl::AboutBox - Display an "About" box to the user

void CTrayControlCtrl::AboutBox()
{
	CDialog dlgAbout(IDD_ABOUTBOX_TRAYCONTROL);
	dlgAbout.DoModal();
}


/////////////////////////////////////////////////////////////////////////////
// CTrayControlCtrl message handlers

void CTrayControlCtrl::OnSetFocus(CWnd* pOldWnd) 
{
	COleControl::OnSetFocus(pOldWnd);
	
	// we have been given the focus
	m_bHasFocus = true ;
	Invalidate() ;
}

void CTrayControlCtrl::OnKillFocus(CWnd* pNewWnd) 
{
	COleControl::OnKillFocus(pNewWnd);
	
	// we have lost the focus
	m_bHasFocus = false ;	
	Invalidate() ;
}


BOOL CTrayControlCtrl::OnEraseBkgnd(CDC* pDC) 
{
	// return false so the control does not flash when being redrawn etc
	return FALSE ;	
	//return COleControl::OnEraseBkgnd(pDC);
}

long CTrayControlCtrl::GetXSize() 
{
	return m_xSize ;
}

void CTrayControlCtrl::SetXSize(long nNewValue) 
{
	if (nNewValue == m_xSize)
		return ;
	if (nNewValue < 1)
		nNewValue = 1 ;				// stop bad programmers!
	// allocate the new hilight bool buffer
	bool	*pBuffer = new bool[nNewValue * m_ySize] ;
	memset(pBuffer, FALSE, sizeof(bool) * nNewValue * m_ySize) ;
	// copy as much of the old selected data that will fit
	if (m_xSize <= nNewValue)
		memcpy(pBuffer, m_bSelected, m_xSize * m_ySize * sizeof(bool)) ;
	else
		memcpy(pBuffer, m_bSelected, nNewValue * m_ySize * sizeof(bool)) ;
	delete []m_bSelected ;
	m_bSelected = pBuffer ;
	SetModifiedFlag();
	// change the size of the m_Names array as well
	CString	*pNames = new CString[nNewValue * m_ySize] ;
	for (int i = 0 ; i < nNewValue * m_ySize && i < m_xSize * m_ySize ; i++)
		pNames[i] = m_Names[i] ;				// copy across existing names
	delete []m_Names ;
	m_Names = pNames ;
	// update colour indexes
	int	*pColours = new int[nNewValue * m_ySize] ;
	memset(pColours, 0, sizeof(int) * nNewValue * m_ySize) ;
	for (i = 0 ; i < nNewValue * m_ySize && i < m_xSize * m_ySize ; i++)
		pColours[i] = m_pColourIndexes[i] ;
	delete []m_pColourIndexes ;
	m_pColourIndexes = pColours ;
	// update count indexes
	int	*pCount = new int[nNewValue * m_ySize] ;
	memset(pCount, 0, sizeof(int) * nNewValue * m_ySize) ;
	for (i = 0 ; i < nNewValue * m_ySize && i < m_xSize * m_ySize ; i++)
		pCount[i] = m_Count[i] ;
	delete []m_Count ;
	m_Count = pCount ;
	// update the size of the tray
	m_xSize = nNewValue ;
	if (m_bAutoChooseLabels)
		{
		switch (nNewValue)
			{
			default :	// all other types are numeric
			case 12 :	SetXLabelsAlpha(FALSE) ;	// labels are numeric
						break ;
			case 8 :	SetXLabelsAlpha(TRUE) ;		// labels are alpha
						break ;
			}
		}
	if (m_hWnd)							// in case our window does not exist!
		Invalidate() ;
}

long CTrayControlCtrl::GetYSize() 
{
	return m_ySize ;
}

void CTrayControlCtrl::SetYSize(long nNewValue) 
{
	if (nNewValue == m_ySize)
		return ;
	if (nNewValue < 1)
		nNewValue = 1 ;				// stop bad programmers!
	// allocate the new hilight bool buffer
	bool	*pBuffer = new bool[m_xSize * nNewValue] ;
	memset(pBuffer, FALSE, sizeof(bool) * m_xSize * nNewValue) ;
	// copy as much of the old selected data that will fit
	if (m_ySize <= nNewValue)
		memcpy(pBuffer, m_bSelected, m_xSize * m_ySize * sizeof(bool)) ;
	else
		memcpy(pBuffer, m_bSelected, m_xSize * nNewValue * sizeof(bool)) ;
	delete []m_bSelected ;
	m_bSelected = pBuffer ;
	SetModifiedFlag();
	// change the size of the m_Names array as well
	CString	*pNames = new CString[m_xSize * nNewValue] ;
	for (int i = 0 ; i < m_xSize * nNewValue && i < m_xSize * m_ySize ; i++)
		pNames[i] = m_Names[i] ;				// copy across existing names
	delete []m_Names ;
	m_Names = pNames ;
	// update colour indexes
	int	*pColours = new int[m_xSize * nNewValue] ;
	memset(pColours, 0, sizeof(int) * m_xSize * nNewValue) ;
	for (i = 0 ; i < m_xSize * nNewValue && i < m_xSize * m_ySize ; i++)
		pColours[i] = m_pColourIndexes[i] ;
	delete []m_pColourIndexes ;
	m_pColourIndexes = pColours ;
	// update the count indexes
	int	*pCount = new int[m_xSize * nNewValue] ;
	memset(pCount, 0, sizeof(int) * m_xSize * nNewValue) ;
	for (i = 0 ; i < m_xSize * nNewValue && i < m_xSize * m_ySize ; i++)
		pCount[i] = m_Count[i] ;
	delete []m_Count ;
	m_Count = pCount ;

	// update the size of the tray
	m_ySize = nNewValue ;
	if (m_bAutoChooseLabels)
		{
		switch (nNewValue)
			{
			default :	// all other types are numeric
			case 12 :	SetYLabelsAlpha(FALSE) ;	// labels are numeric
						break ;
			case 8 :	SetYLabelsAlpha(TRUE) ;		// labels are alpha
						break ;
			}
		}
	if (m_hWnd)						// in case our window does not exist
		Invalidate() ;
}

long CTrayControlCtrl::GetShowCellTitles() 
{
	return m_bTitleTipsEnabled ;
}

void CTrayControlCtrl::SetShowCellTitles(long nNewValue) 
{
	if (nNewValue == (long)m_bTitleTipsEnabled)
		return ;
	SetModifiedFlag();
	m_bTitleTipsEnabled = nNewValue > 0 ? 1 : 0 ;
	Invalidate() ;
}

long CTrayControlCtrl::GetShowLabels() 
{
	return m_bShowLabels ;
}

void CTrayControlCtrl::SetShowLabels(long nNewValue) 
{
	if (nNewValue == (long)m_bShowLabels)
		return ;
	SetModifiedFlag();
	m_bShowLabels = nNewValue > 0 ? 1 : 0 ;
	Invalidate() ;
}

long CTrayControlCtrl::GetXLabelsAlpha() 
{
	return m_bXAxisAlpha ;
}

void CTrayControlCtrl::SetXLabelsAlpha(long nNewValue) 
{
	if (nNewValue == (long)m_bXAxisAlpha)
		return ;
	SetModifiedFlag();
	m_bXAxisAlpha = nNewValue > 0 ? 1 : 0 ;
	Invalidate() ;
}

long CTrayControlCtrl::GetYLabelsAlpha() 
{
	return m_bYAxisAlpha ;
}

void CTrayControlCtrl::SetYLabelsAlpha(long nNewValue) 
{
	if (nNewValue == (long)m_bYAxisAlpha)
		return ;
	SetModifiedFlag();
	m_bYAxisAlpha = nNewValue > 0 ? 1 : 0 ;
	Invalidate() ;
}

long CTrayControlCtrl::EnableDoubleBuffering(long double_buffer) 
{
	long	old = m_bDoubleBuffering ;

	m_bDoubleBuffering = double_buffer > 0 ? true : false ;
	return old ;
}

BSTR CTrayControlCtrl::GetSampleName(long sample_index) 
{
	CString strResult;
	if (sample_index < 0 || sample_index >= m_xSize * m_ySize)
		strResult = "Error, sample index out of range" ;
	else
		strResult = m_Names[sample_index] ;

	return strResult.AllocSysString();
}

long CTrayControlCtrl::GetSampleColourIndex(long sample_index) 
{
	if (sample_index >= 0 && sample_index < m_xSize * m_ySize)
		{
		// its a valid index, return the value
		return (long)m_pColourIndexes[sample_index] ;
		}
	return 0;			// invalid index passed to us
}

long CTrayControlCtrl::GetColourRGB(long colour_index) 
{
	if (colour_index >= 0 && colour_index < m_NumColours)
		{
		// its a valid index, return the value
		return (long)m_pColours[colour_index] ;
		}
	return (long)m_pColours[0] ;		// invalid index, just return first colour
}

long CTrayControlCtrl::SetColourRGB(long colour_index, long colour_rgb) 
{
	COLORREF	old = 0L ;

	if (colour_index >= 0 && colour_index < m_NumColours)
		{
		old = m_pColours[colour_index] ;
		m_pColours[colour_index] = (COLORREF)colour_rgb ;
		// need to re-draw the control
		Invalidate() ;
		}
	return old;					// return old colour in case they need to know what it was
}

long CTrayControlCtrl::SetSampleName(long sample_index, LPCTSTR sample_name) 
{
	if (sample_index >= 0 && sample_index < m_xSize * m_ySize)
		{
		m_Names[sample_index] = sample_name ;		// take a copy of the name
		Invalidate() ;								// re-draw the control
		return TRUE ;								// success
		}
	return FALSE ;									// failure
}

long CTrayControlCtrl::SetSampleColour(long sample_index, long colour_index) 
{
	int old = 0 ;
	// set the colour index for the given position
	if (sample_index >= 0 && sample_index < m_xSize * m_ySize)
		{
		if (colour_index < 0 || colour_index >= m_NumColours)
			colour_index = 0 ;				// keep in range
		old = m_pColourIndexes[sample_index] ;
		m_pColourIndexes[sample_index] = colour_index ;
		Invalidate() ;								// re-darw to update the control
		}
	return old ;
}


long CTrayControlCtrl::GetTrayOrieintation() 
{
	return m_orientation ;
}

void CTrayControlCtrl::SetTrayOrieintation(long nNewValue) 
{
	SetModifiedFlag();
	if (nNewValue >= 0 && nNewValue < NUM_ORIENTATIONS)
		{
		m_orientation = nNewValue ;
		Invalidate() ;
		}
}

int	CTrayControlCtrl::GetOrientationIndex(int sample_index)
{
	int x = sample_index % m_xSize ;
	int y = sample_index / m_xSize ;
	// convert the sample index supplied to support the orientation the tary is setup to use
	// note : I ended up not using this feature so not sure how robust it is!
	switch (m_orientation)
		{
		case RightDown :
			return sample_index ;				// no change
		case DownRight :
			return x * m_ySize + y ;
		case RightUp :
			return x + (m_ySize - y - 1) * m_xSize ;
		case UpRight :
			return (m_ySize - y - 1) + x * m_ySize ;
		case LeftDown :
			return (m_xSize - x - 1) + y * m_xSize ;
		case DownLeft :
			return y + (m_xSize - x - 1) * m_ySize ;
		case LeftUp :
			return (m_xSize - x - 1) + (m_ySize - y - 1) * m_xSize ;
		case UpLeft :
			return (m_ySize - y - 1) + (m_xSize - x - 1) * m_ySize ;
		}
	return 0 ;					// error
}

void CTrayControlCtrl::SetSampleCellCount(long index, long count) 
{
	// if a cell has a cell count > 0, then a numeric is shown in that cells location
	if (index >= 0 && index < m_xSize * m_ySize)
		{
		m_Count[index] = count ;
		Invalidate() ;
		}
	else
		ASSERT(FALSE) ;
}

long CTrayControlCtrl::GetSampleCellCount(long index) 
{
	if (index >= 0 && index < m_xSize * m_ySize)
		return m_Count[index] ;
	ASSERT(FALSE) ;
	return 0;
}

void CTrayControlCtrl::EnableTitleTips(long enable_tips) 
{
	m_bTitleTipsEnabled = (enable_tips != 0) ;
	Invalidate() ;
}

void CTrayControlCtrl::EnableCherryPicking(long enable_cherry) 
{
	// cherry picking is where a user can select locations with the mouse, or drag loactions.
	m_bCherryPicking = (enable_cherry != 0) ;
}

long CTrayControlCtrl::GetCherryCount() 
{
	// return how many items are selected
	int count = 0 ;
	for (int i = 0 ; i < m_xSize * m_ySize ; i++)
		{
		if (m_bSelected[i])
			count++ ;
		}
	return count;
}

long CTrayControlCtrl::GetCherryIndex(long index) 
{
	// return the index of the item
	for (int i = 0; i < m_xSize * m_ySize ; i++)
		{
		if (m_bSelected[i])
			index-- ;
		if (index < 0)
			break ;
		}
	if (i == m_xSize * m_ySize)
		return -1 ;					// error
	return i ;
}

void CTrayControlCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	int fire_pos = -1 ;

	if (m_bCherryPicking)
		{
		if (GetAsyncKeyState(VK_CONTROL) >= 0)
			{
			// clear all the cherry picking state
			for (int i = 0 ; i < m_xSize * m_ySize ; i++)
				m_bSelected[i] = false ;
			}
		m_CherryPoint = point ;			// point start if dragging a cherry rect
		m_TrackerStart = point ;		// for drawing tracker rectangle
		// cherry picking is enabled
		point -= m_actual.TopLeft() ;
		point -= CSize(1, 1) ;
		if (point.x >= 0 && point.y >= 0)
			{
			point.x /= m_cell_size ;
			point.y /= m_cell_size ;
			if (point.x >= 0 && point.x < m_xSize && point.y >= 0 && point.y < m_ySize)
				{
				m_CherryIndex = GetOrientationIndex(point.x + point.y * m_xSize) ;
				m_bSelected[m_CherryIndex] = !m_bSelected[m_CherryIndex] ;
				m_CherryStart = point ;
				m_CherryEnd = m_CherryStart ;
				fire_pos = m_CherryIndex ;
				}
			}
		Invalidate() ;
		}
	FireControlClicked(fire_pos) ;
	if (fire_pos != -1)
		FireCherryPicked() ;				// cherry picking was performed
	
	COleControl::OnLButtonDown(nFlags, point);
}

void CTrayControlCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
	COleControl::OnMouseMove(nFlags, point);

	if (!::IsWindow(m_hWnd))
		return ;					// you can get a mouse move for your window which is not a valid window!

	bool	inval = false ;

	// check for cherry picking
	if (m_bCherryPicking && m_CherryIndex >= 0)
		{
		// has the mouse moved far enough from the cherry start point to display a tracker rect?
		if ((abs(point.x - m_TrackerStart.x) > 3 || abs(point.y - m_TrackerStart.y) > 3) && !m_bCherryDrag)
			{
			// start a cherry drag operation
			m_bCherryDrag = true ;
			CRect	rect ;
			GetWindowRect(&rect) ;
			//ClientToScreen(&rect) ;
			ClipCursor(rect) ;				// stop the user moving outside of the control with the tracker rect
			m_bDrawTracker = true ;
			inval = true ;
			}
		if (m_bCherryDrag)
			{
			m_bDrawTracker = true ;
			inval = true ;
			CPoint pos(point) ;
			pos -= m_actual.TopLeft() ;
			pos -= CSize(1, 1) ;
			pos.x /= m_cell_size ;
			pos.y /= m_cell_size ;
			// limit the drag target to the  actual cells of the control
			if (pos.x < 0)
				pos.x = 0 ;
			if (pos.x >= m_xSize)
				pos.x = m_xSize - 1 ;
			if (pos.y < 0)
				pos.y = 0 ;
			if (pos.y >= m_ySize)
				pos.y = m_ySize - 1 ;
			if (m_CherryEnd != pos)
				{
				for (int i = (m_CherryEnd.x <= m_CherryStart.x) ? m_CherryEnd.x : m_CherryStart.x ;
						i <= m_CherryStart.x || i <= m_CherryEnd.x ; 
						i++)
					{
					for (int j = (m_CherryEnd.y <= m_CherryStart.y) ? m_CherryEnd.y : m_CherryStart.y ;
							j <= m_CherryStart.y || j <= m_CherryEnd.y ; 
							j++)
						{
						int index = GetOrientationIndex(i + j * m_xSize) ;
						m_bSelected[index] = !m_bSelected[index] ;
						}
					}
				// need to update the selected indexes
				m_CherryEnd = pos ;
				for (i = (m_CherryEnd.x <= m_CherryStart.x) ? m_CherryEnd.x : m_CherryStart.x ;
						i <= m_CherryStart.x || i <= m_CherryEnd.x ; 
						i++)
					{
					for (int j = (m_CherryEnd.y <= m_CherryStart.y) ? m_CherryEnd.y : m_CherryStart.y ;
							j <= m_CherryStart.y || j <= m_CherryEnd.y ; 
							j++)
						{
						int index = GetOrientationIndex(i + j * m_xSize) ;
						m_bSelected[index] = !m_bSelected[index] ;
						}
					}
				FireCherryPicked() ;
				inval = true ;
				}
			}
		}

	// show title of cell under assay
	CString	text ;

	if (m_bTitleTipsEnabled)
		{
		CPoint pos(point) ;
		ClientToScreen(&pos) ;
		if (WindowFromPoint(pos) != this)
			{
			ReleaseCapture() ;
			inval = true ;
			}
		else
			{
			point -= m_actual.TopLeft() ;
			point -= CSize(1, 1) ;
			if (m_cell_size == 0)
				m_cell_size = 1 ;
			if (point.x >= 0 && point.y >= 0)
				{
				point.x /= m_cell_size ;
				point.y /= m_cell_size ;
				if (point.x >= 0 && point.x < m_xSize && point.y >= 0 && point.y < m_ySize)
					{
					if (point != m_TitleTipIndex)
						{
						m_TitleTipIndex = point ;
						inval = true ;
						SetCapture() ;		// so we can remove the title tip if the mouse leaves the control
						}
					}
				else
					{
					if (m_TitleTipIndex.x != -1)
						{
						m_TitleTipIndex = CPoint(-1, -1) ;
						inval = true ;
						ReleaseCapture() ;
						}
					}
				}
			else
				{
				if (m_TitleTipIndex.x != -1)
					{
					m_TitleTipIndex = CPoint(-1, -1) ;
					inval = true ;
					}
				}
			if (!m_bTracking)
				{
				TRACKMOUSEEVENT tme;
				tme.cbSize = sizeof(tme);
				tme.hwndTrack = m_hWnd;
				tme.dwFlags = TME_LEAVE;
				tme.dwHoverTime = 1;
				m_bTracking = (_TrackMouseEvent(&tme) != 0) ;
				}
			}
		}
	if (inval)
		Invalidate() ;
}

void CTrayControlCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	if (m_bCherryDrag)
		{
		// finish the drag operation
		ClipCursor(NULL) ;				// allow mouse access to full screen again
		m_bDrawTracker = false ;
		m_bCherryDrag = false ;
		Invalidate() ;
		FireCherryPicked() ;
		}
	m_CherryIndex = -1 ;
	COleControl::OnLButtonUp(nFlags, point);
}

LRESULT CTrayControlCtrl::OnMouseLeave(WPARAM wParam, LPARAM lParam) 
{
	// make any title tip dissapear
	m_TitleTipIndex = -1 ;
	m_bTracking = false ;
	Invalidate() ;
	return 0 ;
}

void CTrayControlCtrl::RemoveAll() 
{
	for (int i = 0 ; i < m_xSize * m_ySize ; i++)
		{
		m_bSelected[i] = false ;
		m_Count[i] = 0 ;
		m_Names[i] = "" ;
		m_pColourIndexes[i] = 0 ;
		}
	m_SampleIndex = 0 ;
	FireInsertPointChanged() ;
}

CString CTrayControlCtrl::GetXLabelText(int x)
{
	CString	text ;

	if (m_bXAxisAlpha)
		{
		switch (m_orientation)
			{
			case RightDown :
			case RightUp :
			case UpRight :
			case DownRight :
					text.Format("%c", (char)(65 + x)) ;
					break ;
			case LeftDown :
			case LeftUp :
			case UpLeft :
			case DownLeft :
					text.Format("%c", (char)(65 + (m_xSize - x - 1))) ;
					break ;
			}
		}
	else
		{
		switch (m_orientation)
			{
			case RightDown :
			case RightUp :
			case UpRight :
			case DownRight :
					text.Format("%1d", x + 1) ;
					break ;
			case LeftDown :
			case LeftUp :
			case UpLeft :
			case DownLeft :
					text.Format("%1d", m_xSize - x) ;
					break ;
			}
		}
	return text ;
}

CString CTrayControlCtrl::GetYLabelText(int y)
{
	CString	text ;
				
	if (m_bYAxisAlpha)
		{
		switch (m_orientation)
			{
			case RightDown :
			case LeftDown :
			case DownLeft :
			case DownRight :
					text.Format("%c", (char)(65 + y)) ;
					break ;
			case LeftUp :
			case UpLeft :
			case RightUp :
			case UpRight :
					text.Format("%c", (char)(65 + (m_ySize - y - 1))) ;
					break ;
			}
		}
	else
		{
		switch (m_orientation)
			{
			case RightDown :
			case DownLeft :
			case DownRight :
			case LeftDown :
					text.Format("%1d", y + 1) ;
					break ;
			case LeftUp :
			case UpLeft :
			case RightUp :
			case UpRight :
					text.Format("%1d", m_ySize - y) ;
					break ;
			}
		}
	return text ;
}

CString CTrayControlCtrl::GetCellLabel(CPoint pos)
{
	CString	x ;
	CString y ;

	if (m_xSize > 1 && m_ySize > 1)
		x = GetXLabelText(pos.x) ;
	if (m_xSize > 1 && m_ySize > 1)
		y = GetYLabelText(pos.y) ;
	switch (m_bXAxisAlpha + m_bYAxisAlpha * 2)
		{
		case 0 :	// neither is alpha, 2 numbers
					x = x + ":" + y + " " ;
					return x ;
		case 1 :	x += y + " " ;
					return x ;
		case 2 :	y += x + " " ;
					return y ;
		case 3 :	x += y + " " ;
					return x ;
		}
	return "Error" ;
}

void CTrayControlCtrl::SetFlashState(long state) 
{
	bool	new_state = (state != 0) ;
	if (new_state && !m_bFlashing)
		{
		// set the timer going
		m_Timer = SetTimer(1, m_FlashRate, 0) ;
		m_bFlashing = true ;
		}
	else if (!new_state && m_bFlashing)
		{
		KillTimer(m_Timer) ;
		m_Timer = 0 ;
		m_bFlashing = false ;
		}
}

void CTrayControlCtrl::SetFlashIndex(long index) 
{
	m_FlashingIndex = index ;
}

void CTrayControlCtrl::SetFlashRate(long rate_in_ms) 
{
	m_FlashRate = rate_in_ms ;
	if (m_bFlashing)
		{
		// set the new timer speed
		KillTimer(m_Timer) ;
		m_Timer = SetTimer(1, m_FlashRate, 0) ;
		}
}

void CTrayControlCtrl::OnTimer(UINT nIDEvent) 
{
	m_bFlashOn = ! m_bFlashOn ;
	Invalidate() ;
	COleControl::OnTimer(nIDEvent);
}

void CTrayControlCtrl::SetSelectionState(long state) 
{
	m_bSelectionState = (state != 0) ;
	Invalidate() ;
}

long CTrayControlCtrl::GetSampleIndex() 
{
	return m_SampleIndex ;
}

BOOL CTrayControlCtrl::AddSample(LPCTSTR sample_name, long colour_index) 
{
	if (m_SampleIndex >= 0 && m_SampleIndex < m_xSize * m_ySize)
		{
		m_Names[m_SampleIndex] = sample_name ;		// take a copy of the name
		m_pColourIndexes[m_SampleIndex] = colour_index ;
		if (m_SampleIndex < m_xSize * m_ySize - 1)
			{
			m_SampleIndex++ ;
			FireInsertPointChanged() ;
			}
		Invalidate() ;								// re-draw the control
		return TRUE ;								// success
		}
	return FALSE;
}

void CTrayControlCtrl::SetSampleIndex(long sample_index) 
{
	m_SampleIndex = sample_index ;
	if (m_bShowSampleIndex)
		Invalidate() ;
	FireInsertPointChanged() ;
}

void CTrayControlCtrl::SetShowNextInsertPosition(long show) 
{
	m_bShowSampleIndex = (show != 0) ;
	Invalidate() ;
}

void CTrayControlCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	if (m_bShowSampleIndex)
		{
		point -= m_actual.TopLeft() ;
		point -= CSize(1, 1) ;
		if (point.x >= 0 && point.y >= 0)
			{
			point.x /= m_cell_size ;
			point.y /= m_cell_size ;
			if (point.x >= 0 && point.x < m_xSize && point.y >= 0 && point.y < m_ySize)
				{
				m_SampleIndex = GetOrientationIndex(point.x + point.y * m_xSize) ;
				FireInsertPointChanged() ;
				}
			}
		Invalidate() ;
		}
	COleControl::OnLButtonDblClk(nFlags, point);
}

BSTR CTrayControlCtrl::GetPositionLabel(long index) 
{
	CString strResult;
	CPoint	pos = GetPointFromIndex(index) ;
	strResult = GetCellLabel(pos) ;

	return strResult.AllocSysString();
}

CPoint CTrayControlCtrl::GetPointFromIndex(int index)
{
	CPoint	pos(0, 0) ;

	switch (m_orientation)
		{
		case RightDown :
			pos.x = index % m_xSize ;
			pos.y = index / m_xSize ;
			break ;
		case DownRight :
			pos.x = index / m_ySize ;
			pos.y = index % m_ySize ;
			break ;
		case RightUp :
			pos.x = index % m_xSize ;
			pos.y = m_ySize - index / m_xSize - 1 ;
			break ;
		case UpRight :
			pos.x = index / m_ySize ;
			pos.y = m_ySize - index % m_ySize - 1 ;
			break ;
		case LeftDown :
			pos.x = m_xSize - index % m_xSize - 1 ;
			pos.y = index / m_xSize ;
			break ;
		case DownLeft :
			pos.x = m_xSize - index / m_ySize - 1 ;
			pos.y = index % m_ySize ;
			break ;
		case LeftUp :
			pos.x = m_xSize - index % m_xSize - 1 ;
			pos.y = m_ySize - index / m_xSize - 1 ;
			break ;
		case UpLeft :
			pos.x = m_xSize - index / m_ySize - 1 ;
			pos.y = m_ySize - index % m_ySize - 1 ;
			break ;
		}
	return pos ;
}

BOOL CTrayControlCtrl::PreTranslateMessage(MSG* pMsg) 
{
	if (pMsg->message == WM_KEYDOWN)
		{
		// process keyboard messages if required
		if (m_bShowSampleIndex)
			{
			// the red selection square is shown, allow the user to move it with the arrow keys
			switch (pMsg->wParam)
				{
				case VK_LEFT :
					if (m_SampleIndex >= m_ySize)
						{
						m_SampleIndex -= m_ySize ;
						Invalidate() ;
						FireInsertPointChanged() ;
						}
					return TRUE ;				// we used the key press
				case VK_RIGHT :
					if (m_SampleIndex < (m_xSize - 1) * m_ySize)
						{
						m_SampleIndex += m_ySize ;
						Invalidate() ;
						FireInsertPointChanged() ;
						}
					return TRUE ;
				case VK_UP :
					if (m_SampleIndex % m_ySize < m_ySize - 1)
						{
						m_SampleIndex++ ;
						Invalidate() ;
						FireInsertPointChanged() ;
						}
					return TRUE ;
				case VK_DOWN :
					if (m_SampleIndex % m_ySize > 0)
						{
						m_SampleIndex-- ;
						Invalidate() ;
						FireInsertPointChanged() ;
						}
					return TRUE ;
					// all other keys fall through to the default handler
				}
			}
		}
	
	return COleControl::PreTranslateMessage(pMsg);
}

void CTrayControlCtrl::OnRButtonDown(UINT nFlags, CPoint point) 
{
	int	fire_pos = -1 ;
	// cherry picking is enabled
	point -= m_actual.TopLeft() ;
	point -= CSize(1, 1) ;
	if (point.x >= 0 && point.y >= 0)
		{
		point.x /= m_cell_size ;
		point.y /= m_cell_size ;
		if (point.x >= 0 && point.x < m_xSize && point.y >= 0 && point.y < m_ySize)
			fire_pos = GetOrientationIndex(point.x + point.y * m_xSize) ;
		}
	FireControlRightClicked(fire_pos) ;		// let container app know which location was right clicked, -1 if not on an actual cell
	
	COleControl::OnRButtonDown(nFlags, point);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Sirius Analytical Instruments
United Kingdom United Kingdom
A research and development programmer working for a pharmaceutical instrument company for the past 17 years.

I am one of those lucky people who enjoys his work and spends more time than he should either doing work or reseaching new stuff. I can also be found on playing DDO on the Cannith server (Send a tell to "Maetrim" who is my current main)

I am also a keep fit fanatic, doing cross country running and am seriously into [url]http://www.ryushinkan.co.uk/[/url] Karate at this time of my life, training from 4-6 times a week and recently achieved my 1st Dan after 6 years.

Comments and Discussions