///////////////////////////////////////////////////////////////////////////////
// ReportCtrl.cpp
//
// CReportCtrl, a CListCtrl derived class that is specialized on "Report View"
// style. 
//
// Features:
//
// 1, Item sorting by clicking on column header.
// 2, Sub-item text edit.
// 3, Item repositioning.
// 4, Customizing checkbox styles, including "single" and "disabled".
// 5, Sending a message to parent window when user clicks on a checkbox.
// 6, Convenient item insertion, deletion, moving, and sub-item text changing.
// 7, Sub-item images and color
// 8, And much more... 
//
// This code may be used in compiled form in any way you desire. This file may be
// redistributed unmodified by any means PROVIDING it is not sold for profit without
// the authors written consent, and providing that this notice and the authors name 
// is included. If the source code in  this file is used in any commercial application 
// then acknowledgement must be made to the author of this file .
//
// This file is provided "as is" with no expressed or implied warranty.
//
// Written by Bin Liu (abinn32@yahoo.com)
//
// History
//
// Nov. 26, 2003 - Initial release.
// Dec. 03, 2003 - Fixed a bug in "EndEdit" where item text were not preperly committed.
//               - Completed the implementation of the "Sort-Separator" feature.
// Jan. 01, 2004 - Fixed a bug in "SetItemData".
//               - Added message "WM_EDIT_COMMITTED" which is sent to the parent window
//                 when an item text editing is committed.
//               - Fixed a bug in "SetItemText"(double type).
//               - Fixed a bug where item sorting does not work properly when there
//                 are multiple CReportCtrl objects on same window.
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ReportCtrl.h"
#include <afxtempl.h>
#include "resource.h"

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

// Below styles MUST be present in a report ctrl
#define MUST_STYLE			(WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | LVS_REPORT)
#define MUST_EX_STYLE		(LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES)

// Below styles MUST NOT be present in a report ctrl
#define MUST_NOT_STYLE		(LVS_EDITLABELS | LVS_ICON | LVS_SMALLICON | LVS_LIST | LVS_NOSCROLL)
#define MUST_NOT_EX_STYLE	(0)

struct ROWINFO
{
	DWORD dwData;
	DWORD dwStates;
	CArray<int, int> aImages;
	CStringArray aTexts;
};

//////////////////////////////////////////////////////////////////////////
// CItemData class is used for store extra information
//////////////////////////////////////////////////////////////////////////
class CItemData
{
public:
	CItemData() { dwData = 0; }
	void InsertColumn(int nColumn);
	void DeleteColumn(int nColumn);
	DWORD dwData; // The actual 32-bit user data stores here
	CArray<COLORREF, COLORREF> aTextColors; // Sub item text colors
	CArray<COLORREF, COLORREF> aBkColors; // Sub item backgroud colors
};

void CItemData::InsertColumn(int nColumn)
{
	aTextColors.InsertAt(nColumn, ::GetSysColor(COLOR_WINDOWTEXT));
	aBkColors.InsertAt(nColumn, ::GetSysColor(COLOR_WINDOW));
}

void CItemData::DeleteColumn(int nColumn)
{
	aTextColors.RemoveAt(nColumn);
	aBkColors.RemoveAt(nColumn);
}

///////////////////////////////////////////////////////////////////////
// A set of functions used for item text format determining
///////////////////////////////////////////////////////////////////////
namespace _ITEM_COMPARE_FUNCS
{
	BOOL _IsDecNumber(const CString& str, double& f);
	int _DecNumberCompare(double f1, double f2);
	BOOL _IsHexNumber(const CString& str, DWORD& dw);
	int _HexNumberCompare(DWORD dw1, DWORD dw2);
	BOOL _IsDate(const CString& str, COleDateTime& date);
	int _DateCompare(const COleDateTime& date1, const COleDateTime& date2);
};

BOOL _ITEM_COMPARE_FUNCS::_IsDecNumber(const CString& str, double& f)
{
	if (str.IsEmpty())
		return FALSE;

	LPTSTR p;
	f = _tcstod(str, &p);
	return (*p == _T('\0') || (*p == _T('%') && p[1] == _T('\0')));
}

int _ITEM_COMPARE_FUNCS::_DecNumberCompare(double f1, double f2)
{
	if(f1 < f2)
		return -1;
	
	if(f1 > f2)
		return 1;

	return 0;
}

BOOL _ITEM_COMPARE_FUNCS::_IsHexNumber(const CString& str, DWORD& dw)
{
	if (str.IsEmpty())
		return FALSE;

	LPTSTR p;
	dw = _tcstoul(str, &p, 16);
	return *p == _T('\0');
}

int _ITEM_COMPARE_FUNCS::_HexNumberCompare(DWORD dw1, DWORD dw2)
{
	if (dw1 > dw2)
		return 1;

	if (dw1 < dw2)
		return -1;

	return 0;
}

BOOL _ITEM_COMPARE_FUNCS::_IsDate(const CString& str, COleDateTime& date)
{
	return date.ParseDateTime(str);
}

int _ITEM_COMPARE_FUNCS::_DateCompare(const COleDateTime& date1, const COleDateTime& date2)
{
	if (date1 < date2)
		return -1;

	if (date1 > date2)
		return 1;

	return 0;
}

/////////////////////////////////////////////////////////////////////////////
// CReportCtrl Implementation
/////////////////////////////////////////////////////////////////////////////
CReportCtrl::CReportCtrl(): 
	m_pWndEdit(NULL)
{
	m_pWndEdit = new CEdit;
	VERIFY(m_pWndEdit != NULL);
	m_pszSeparator = NULL;
	m_bAllowEdit = FALSE;
	m_ptEditting.x = -1;
	m_ptEditting.y = -1;
	m_nChkStyle = RC_CHKBOX_NORMAL;
	m_dwPrevEditFmt = ES_LEFT;
	m_nSortCol = -1;
	m_bSortAscending = TRUE;
}

CReportCtrl::~CReportCtrl()
{	
	if (m_pszSeparator != NULL)
		delete [] m_pszSeparator;

	if (m_pWndEdit != NULL)
		delete m_pWndEdit;
}


BEGIN_MESSAGE_MAP(CReportCtrl, CListCtrl)
	//{{AFX_MSG_MAP(CReportCtrl)
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnclick)
	ON_WM_LBUTTONDOWN()
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_MBUTTONDOWN()
	ON_WM_MBUTTONDBLCLK()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONDBLCLK()
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CReportCtrl message handlers
void CReportCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) 
{
	LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)pNMHDR;

	if (lplvcd->nmcd.dwDrawStage == CDDS_PREPAINT)
	{
		*pResult = CDRF_NOTIFYITEMDRAW;
	}
	else if (lplvcd->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)
	{
		*pResult = CDRF_NOTIFYSUBITEMDRAW;
	}
	else if (lplvcd->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM))
	{
		int serial = GetItemData(lplvcd->nmcd.dwItemSpec);
		ItemColorMap::iterator it = m_itemColor.find(serial);
		if (it != m_itemColor.end())
		{
			lplvcd->clrText = it->second;
		}
		*pResult = CDRF_DODEFAULT;
	}
}

void CReportCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	// TODO: Add your control notification handler code here
	const int COL = pNMListView->iSubItem;
//	SortItems(COL, COL == m_nSortCol ? !m_bSortAscending : TRUE);
	*pResult = 0;
}

void CReportCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	_MouseClkMonitor(WM_LBUTTONDOWN, nFlags, point, TRUE);
}

void CReportCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) 
{	
	_MouseClkMonitor(WM_LBUTTONDBLCLK, nFlags, point, TRUE);
}

void CReportCtrl::OnMButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	_MouseClkMonitor(WM_MBUTTONDOWN, nFlags, point, FALSE);
}

void CReportCtrl::OnMButtonDblClk(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	_MouseClkMonitor(WM_MBUTTONDBLCLK, nFlags, point, FALSE);
}

void CReportCtrl::OnRButtonDown(UINT nFlags, CPoint point) 
{
	_MouseClkMonitor(WM_RBUTTONDOWN, nFlags, point, FALSE);
}

void CReportCtrl::OnRButtonDblClk(UINT nFlags, CPoint point) 
{
	_MouseClkMonitor(WM_RBUTTONDBLCLK, nFlags, point, FALSE);
}

int CReportCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	lpCreateStruct->style &= ~MUST_NOT_STYLE;
	lpCreateStruct->style |= MUST_STYLE;
	if (CListCtrl::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	// TODO: Add your specialized creation code here
	SetExtendedStyle(GetExtendedStyle());
	ASSERT(GetHeaderCtrl() != NULL);
	return 0;
}

void CReportCtrl::OnDestroy() 
{
	DeleteAllItems();
	m_pWndEdit->DestroyWindow();
	m_imgList.DeleteImageList();
	m_headerImgList.DeleteImageList();
	CListCtrl::OnDestroy();
	// TODO: Add your message handler code here	
}

BOOL CReportCtrl::_IsValidIndex(int nIndex) const
{
	return nIndex >= 0 && nIndex < CListCtrl::GetItemCount();
}

// the heading text is in the format of "text,width,format;text,width,format;..."
BOOL CReportCtrl::SetColumnHeader(const CString& strHeadings)
{
	DeleteAllItems();
	DeleteAllColumns();
	EndEdit(TRUE);
	BOOL bInserted = FALSE;
	CStringArray aLong, aShort;
	_StringSplit(strHeadings, aLong, _T(';'));
	for (int i = 0; i < aLong.GetSize(); i++)
	{
		_StringSplit(aLong[i], aShort, _T(','));
		if (aShort.GetSize() > 0)
		{
			const int WIDTH = aShort.GetSize() > 1 ? _ttoi(aShort[1]) : 100;
			int nFormat = aShort.GetSize() > 2 ? _ttoi(aShort[2]) : 0;

			if (nFormat == 1)
				nFormat = LVCFMT_CENTER;
			else if (nFormat == 2)
				nFormat = LVCFMT_RIGHT;
			else
				nFormat = LVCFMT_LEFT;

			bInserted |= (InsertColumn(GetColumnCount(), aShort[0], nFormat, WIDTH) >= 0);
		}
	}
	return bInserted;
}

int CReportCtrl::GetIdByData(int data)
{
	int nCount = GetItemCount();

	for (int i = 0 ; i < nCount ; i++)
	{
		DWORD dwData = GetItemData(i);
		if (dwData == data)
		{
			return i;
		}
	}
	return 0xffffffff;
}

int CReportCtrl::InsertItem(int nIndex, LPCTSTR lpText)
{	
	EndEdit(TRUE);
	_UnsetSortedColumn();
	const int IDX = CListCtrl::InsertItem(nIndex, lpText);
	if (IDX >= 0)
		_AllocItemMemory(IDX);
	return IDX;
}

BOOL CReportCtrl::DeleteItem(int nItem, BOOL bSelectNextItem)
{
	EndEdit(m_ptEditting.x != nItem);
	
	if (bSelectNextItem)
		SetItemStates(nItem + 1, RC_ITEM_SELECTED);

	_FreeItemMemory(nItem);
	return CListCtrl::DeleteItem(nItem);
}

int CReportCtrl::DeleteAllItems(DWORD dwStates)
{
	EndEdit(FALSE);
	int nItemCount = CListCtrl::GetItemCount();
	if (dwStates & RC_ITEM_ALL)
	{
		LockWindowUpdate();
		for (int i = 0; i < nItemCount; i++)
			_FreeItemMemory(i);
		CListCtrl::DeleteAllItems();
		UnlockWindowUpdate();
		return nItemCount;		
	}
	
	int nDelCount = 0;
	LockWindowUpdate();

	for (int i = 0; i < nItemCount; i++)
	{
		if (ExamItemStates(i, dwStates))
		{
			DeleteItem(i--);
			nItemCount--;
			nDelCount++;
		}
	}	
	UnlockWindowUpdate();
	return nDelCount;
}

void CReportCtrl::SortItems(int nColumn, BOOL bAscending)
{
	EndEdit(TRUE);
	if (nColumn < 0 || nColumn >= GetColumnCount() || !IsSortable())
		return;	

	// do the sorting	
	m_nSortCol = nColumn;
	m_bSortAscending = bAscending;
	
	BOOL bEnd = FALSE;
	int nSep1 = -1;
	int nSep2 = _FindSeparator(-1, nColumn);
	do
	{
		if (nSep2 < 0)
		{
			nSep2 = GetItemCount();
			bEnd = TRUE;
		}
		_PartialSort(nSep1 + 1, nSep2 - 1);
		nSep1 = _FindSeparator(nSep2 - 1, nColumn);
		nSep2 = _FindSeparator(nSep1, nColumn);
	} while (!bEnd && nSep1 >= 0);

	GetParent()->SendMessage(WM_ITEM_SORTED, (WPARAM)m_nSortCol, (LPARAM)m_bSortAscending);
}

BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, LPCTSTR lpText)
{
	EndEdit(TRUE);
	_UnsetSortedColumn();
	return CListCtrl::SetItemText(nItem, nSubItem, lpText);
}

int CReportCtrl::GetColumnCount() const
{
	return GetHeaderCtrl()->GetItemCount();
}

int CReportCtrl::InsertItem(const LVITEM *pItem)
{
	EndEdit(TRUE);
	_UnsetSortedColumn();
	const int IDX = CListCtrl::InsertItem(pItem);
	if (IDX >= 0)
		_AllocItemMemory(IDX);
	return IDX;
}

int CReportCtrl::InsertItem(int nItem, LPCTSTR lpItem, int nImage)
{	
	EndEdit(TRUE);
	_UnsetSortedColumn();
	const int IDX = CListCtrl::InsertItem(nItem, lpItem, nImage);
	if (IDX >= 0)
		_AllocItemMemory(IDX);
	return IDX;
}

int CReportCtrl::InsertItem(UINT nMask, int nItem, LPCTSTR lpItem, UINT nState, UINT nStateMask, int nImage, LPARAM lParam)
{
	EndEdit(TRUE);
	_UnsetSortedColumn();
	const int IDX = CListCtrl::InsertItem(nMask, nItem, lpItem, nState, nStateMask, nImage, lParam);
	if (IDX >= 0)
		_AllocItemMemory(IDX);
	return IDX;
}

void CReportCtrl::SetGridLines(BOOL bSet)
{
	DWORD dwStype = GetExtendedStyle();
	if (bSet)
		dwStype |= LVS_EX_GRIDLINES;
	else
		dwStype &= ~LVS_EX_GRIDLINES;

	SetExtendedStyle(dwStype);	
}

int CReportCtrl::InsertColumn(int nCol, const LVCOLUMN *pColumn)
{
	EndEdit(TRUE);
	const int IDX = CListCtrl::InsertColumn(nCol, pColumn);
	if (IDX >= 0)
		_UpdateColumn(IDX, TRUE);
	return IDX;
}

int CReportCtrl::InsertColumn(int nCol, LPCTSTR lpColumnHeading, int nFormat, int nWidth, int nSubItem)
{
	EndEdit(TRUE);
	const int IDX = CListCtrl::InsertColumn(nCol, lpColumnHeading, nFormat, nWidth, nSubItem);
	if (IDX >= 0)
		_UpdateColumn(IDX, TRUE);
	return IDX;
}

BOOL CReportCtrl::DeleteColumn(int nCol)
{
	EndEdit(TRUE);
	const BOOL RES = CListCtrl::DeleteColumn(nCol);
	if (RES)
		_UpdateColumn(nCol, FALSE);
	return RES;
}

void CReportCtrl::SetCheckboxeStyle(int nStyle)
{
	m_nChkStyle = nStyle;
	EndEdit(TRUE);
	DWORD dwStype = GetExtendedStyle();
	if (nStyle == 0)
	{
		dwStype &= ~LVS_EX_CHECKBOXES;		
	}
	else
	{
		dwStype |= LVS_EX_CHECKBOXES;
		_EnsureSingleCheck(-1);
	}

	SetExtendedStyle(dwStype);
}

int CReportCtrl::MoveTo(int nItem, int nNewPosition)
{
	if (!_IsValidIndex(nItem))
		return -1;

	EndEdit(TRUE);
	const int ITEMS = CListCtrl::GetItemCount();
	nNewPosition = max(0, nNewPosition);
	nNewPosition  = min(ITEMS - 1, nNewPosition);

	if (nItem == nNewPosition)
		return nNewPosition;

	_UnsetSortedColumn();

	// Backup all states and attributes
	const int COLS = GetColumnCount();
	const DWORD STATES = GetItemStates(nItem);
	const DWORD DATA = CListCtrl::GetItemData(nItem);
	CArray<int, int> aImages;
	CStringArray aTexts;
	aImages.SetSize(COLS);
	aTexts.SetSize(COLS);
	for (int i = 0; i < COLS; i++)
	{
		aImages[i] = GetItemImage(nItem, i);
		aTexts[i] = GetItemText(nItem, i);
	}	

	// Delete the item
	CListCtrl::DeleteItem(nItem);

	// Insert a new item to the new position
	const int IDX = CListCtrl::InsertItem(nNewPosition, _T(""));

	// Restore all states & attributes to the newly inserted item
	for (int j = 0; j < COLS; j++)
	{
		CListCtrl::SetItemText(IDX, j, aTexts[j]);
		SetItemImage(IDX, j, aImages[j]);
	}
	CListCtrl::SetItemData(IDX, DATA);
	SetItemStates(IDX, STATES);
	return IDX;
}

int CReportCtrl::MoveUp(int nItem, int nCount)
{	
	return MoveTo(nItem, nItem - nCount);
}

BOOL CReportCtrl::MoveDown(int nItem, int nCount)
{
	return MoveTo(nItem, nItem + nCount);
}

BOOL CReportCtrl::SwapItems(int nItem1, int nItem2)
{
	EndEdit(TRUE);
	if (!_IsValidIndex(nItem1) || !_IsValidIndex(nItem2))
		return FALSE;

	if (nItem1 == nItem2)
		return TRUE;

	_UnsetSortedColumn();

	// record previous states first
	const DWORD STATES1 = GetItemStates(nItem1);
	const DWORD STATES2 = GetItemStates(nItem2);
	const DWORD DATA1 = CListCtrl::GetItemData(nItem1);
	const DWORD DATA2 = CListCtrl::GetItemData(nItem2);

	// swap item texts and images
	for (int i = 0; i < GetColumnCount(); i++)
	{
		CString str = GetItemText(nItem1, i);
		CListCtrl::SetItemText(nItem1, i, GetItemText(nItem2, i));
		CListCtrl::SetItemText(nItem2, i, str);
		UINT nImg = GetItemImage(nItem1, i);
		SetItemImage(nItem1, i, GetItemImage(nItem2, i));
		SetItemImage(nItem2, i, nImg);
	}
	
	// swap item data
	CListCtrl::SetItemData(nItem1, DATA2);
	CListCtrl::SetItemData(nItem2, DATA1);

	// restore states
	SetItemStates(nItem1, STATES2);
	SetItemStates(nItem2, STATES1);

	return TRUE;
}

int CReportCtrl::MoveToTop(int nItem)
{
	return MoveTo(nItem, -1);
}

int CReportCtrl::MoveToBottom(int nItem)
{
	return MoveTo(nItem, INT_MAX);
}

BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, TCHAR val)
{
	return SetItemText(nItem, nSubItem, CString(val));
}

BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, INT val)
{	
	return SetItemText(nItem, nSubItem, (LONG)val);
}

BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, UINT val)
{
	return SetItemText(nItem, nSubItem, (ULONG)val);
}

BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, DOUBLE val, int nPrecision)
{
	CString sText, sFmt;
	
	if (nPrecision >= 0)
	{
		sFmt.Format(_T("%%.%df"), nPrecision);
		sText.Format(sFmt, val);
	}
	else
	{
		sText.Format(_T("%f"), val);
		sText.TrimRight(_T('0'));
		sText.TrimRight(_T('.'));
	}
	
	return SetItemText(nItem, nSubItem, sText);
}

BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, const COleDateTime &dateTime, DWORD dwFlags)
{
	return SetItemText(nItem, nSubItem, dateTime.GetStatus() == COleDateTime::valid ? dateTime.Format(dwFlags) : _T(""));
}

BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, LONG val)
{
	CString str;
	str.Format(_T("%d"), val);
	return SetItemText(nItem, nSubItem, str);
}

BOOL CReportCtrl::SetItemText(int nItem, int nSubItem, ULONG val)
{
	CString str;
	str.Format(_T("%u"), val);
	return SetItemText(nItem, nSubItem, str);
}

BOOL CReportCtrl::DeleteAllColumns()
{
	while (GetColumnCount() > 0)
		DeleteColumn(0);		
	return TRUE;
}

LPCTSTR CReportCtrl::GetSortSeparator() const
{
	return m_pszSeparator;
}

void CReportCtrl::SetSortSeparator(LPCTSTR lpSortSeparator)
{
	if (m_pszSeparator != NULL)
	{
		delete [] m_pszSeparator;
		m_pszSeparator = NULL;
	}

	if (lpSortSeparator != NULL)
		m_pszSeparator = _tcsdup(lpSortSeparator);
}

int CReportCtrl::InsertItemEx(int nItem, LPCTSTR lpText, ...)
{
	EndEdit(TRUE);
	_UnsetSortedColumn();
	const int IDX = CListCtrl::InsertItem(nItem, lpText);
	if (IDX < 0)
		return -1;

 	va_list list;
	va_start(list, lpText);

	for(int iColumn = 1; iColumn < GetColumnCount(); iColumn++)
	{
		LPCTSTR lp = va_arg(list, LPCTSTR);
		if (lp != NULL)
		{
			CListCtrl::SetItemText(IDX, iColumn, lp);
		}
		else
		{
			break;
		}
	}

	va_end(list);

	if (IDX >= 0)
		_AllocItemMemory(IDX);

	return IDX;
}

BOOL CReportCtrl::_IsEditVisible() const
{
	return m_bAllowEdit
		&& m_ptEditting.x >= 0 && m_ptEditting.x < CListCtrl::GetItemCount()
		&& m_ptEditting.y >= 0 && m_ptEditting.y < GetColumnCount();
}

void CReportCtrl::SetEditable(BOOL bSet)
{	
	if (!bSet)
		EndEdit(TRUE);
	m_bAllowEdit = bSet;
}

BOOL CReportCtrl::IsEditable() const
{
	return m_bAllowEdit;
}

BOOL CReportCtrl::_ItemCheckMonitor(int nIndex, BOOL bBefore, BOOL bAfter, UINT nMsg)
{
	if (!_IsValidIndex(nIndex)
		|| m_nChkStyle == RC_CHKBOX_NONE
		|| (bBefore && bAfter)
		|| (!bBefore && !bAfter))
	{
		return FALSE;
	}

	if (m_nChkStyle == RC_CHKBOX_SINGLE)
	{
		// Only one item can be checked
		if (!bBefore && bAfter)
		{
			SetAllItemStates(RC_ITEM_CHECKED, RC_ITEM_UNCHECKED); // uncheck all
			SetItemStates(nIndex, RC_ITEM_CHECKED);
		}		
	}
	else if (m_nChkStyle == RC_CHKBOX_DISABLED)
	{
		// Cannot change the item checked/unchecked states by mouse clicks
		SetItemStates(nIndex, bBefore ? RC_ITEM_CHECKED : RC_ITEM_UNCHECKED);
		return TRUE;
	}	

	SetAllItemStates(RC_ITEM_SELECTED, RC_ITEM_UNSELECTED); // unselect all
	SetItemStates(nIndex, RC_ITEM_SELECTED | RC_ITEM_FOCUSED);
	GetParent()->SendMessage(WM_ON_CHKBOX, (WPARAM)nIndex, (LPARAM)nMsg);
	return TRUE;
}

int CReportCtrl::GetCheckboxStyle() const
{
	return m_nChkStyle;
}

void CReportCtrl::_StringSplit(const CString &str, CStringArray &arr, TCHAR chDelimitior)
{
	int nStart = 0, nEnd = 0;
	arr.RemoveAll();

	while (nEnd < str.GetLength())
	{
		// determine the paragraph ("xxx,xxx,xxx;")
		nEnd = str.Find(chDelimitior, nStart);
		if( nEnd == -1 )
		{
			// reached the end of string
			nEnd = str.GetLength();
		}

		CString s = str.Mid(nStart, nEnd - nStart);
		if (!s.IsEmpty())
			arr.Add(s);

		nStart = nEnd + 1;
	}
}

BOOL CReportCtrl::SetSortable(BOOL bSet)
{
	if (!HasColumnHeader())
		return FALSE;

	LONG lStyle = ::GetWindowLong(GetHeaderCtrl()->GetSafeHwnd(), GWL_STYLE);
	if (bSet)
	{
		lStyle |= HDS_BUTTONS;
	}
	else
	{
		lStyle &= ~HDS_BUTTONS;
		_UnsetSortedColumn();
	}
	::SetWindowLong(GetHeaderCtrl()->GetSafeHwnd(), GWL_STYLE, lStyle);
	CListCtrl::GetHeaderCtrl()->RedrawWindow();
	return TRUE;
}

DWORD CReportCtrl::SetExtendedStyle(DWORD dwNewStyle)
{
	dwNewStyle &= ~MUST_NOT_EX_STYLE;
	dwNewStyle |= MUST_EX_STYLE;
	return CListCtrl::SetExtendedStyle(dwNewStyle);
}

BOOL CReportCtrl::PreCreateWindow(CREATESTRUCT& cs) 
{
	// TODO: Add your specialized code here and/or call the base class
	cs.style &= ~MUST_NOT_STYLE;
	cs.style |= MUST_STYLE;
	return CListCtrl::PreCreateWindow(cs);
}

BOOL CReportCtrl::PreTranslateMessage(MSG* pMsg) 
{
	// TODO: Add your specialized code here and/or call the base class
	if (pMsg->message == WM_KEYDOWN)
	{
		if (!_IsEditVisible())
		{
			// disable user from check/uncheck the checkboxes using space key
			// things get nasty if the user is HOLDING the space bar down
			if (pMsg->wParam == VK_SPACE)
				return TRUE;
		}
		else
		{
			POINT pt = m_ptEditting;
			switch (pMsg->wParam)
			{
			case VK_ESCAPE: // Cancel edit
				EndEdit(FALSE);
				return TRUE;

			case VK_RETURN: // Commit edit
				EndEdit(TRUE);
				return TRUE;

			case VK_TAB: // switch edit sub items
				if (pt.y == GetColumnCount() - 1)
					pt.y = 0;
				else
					pt.y++;
				EndEdit(TRUE);
				StartEdit(pt.x, pt.y);
				return TRUE;

			case VK_UP: // edit upper item
				if (pt.x > 0)
				{
					pt.x--;
					EndEdit(TRUE);
					StartEdit(pt.x, pt.y);
					EnsureVisible(pt.x, FALSE);
					return TRUE;
				}
				break;
				
			case VK_DOWN: // edit lower item
				if (pt.x < CListCtrl::GetItemCount() - 1)
				{
					pt.x++;
					EndEdit(TRUE);
					StartEdit(pt.x, pt.y);
					EnsureVisible(pt.x, FALSE);
					return TRUE;
				}
				break;			

			default:
				break;				
			}
		}
	}

	return CListCtrl::PreTranslateMessage(pMsg);
}

CEdit* CReportCtrl::GetEditControl()
{
	return m_pWndEdit;
}

BOOL CReportCtrl::Create(CWnd* pParentWnd, UINT nID, LPCRECT lpRect, DWORD dwStyle) 
{
	// TODO: Add your specialized code here and/or call the base class
	ASSERT(pParentWnd != NULL);

	dwStyle &= ~MUST_NOT_STYLE;
	dwStyle |= MUST_STYLE;

	CRect rect;
	if (lpRect == NULL)
		pParentWnd->GetClientRect(&rect);
	else
		rect = *lpRect;

	return CListCtrl::Create(dwStyle, rect, pParentWnd, nID);
}

void CReportCtrl::PreSubclassWindow() 
{
	// TODO: Add your specialized code here and/or call the base class
	LONG lStyle = ::GetWindowLong(GetSafeHwnd(), GWL_STYLE);
	lStyle &= ~MUST_NOT_STYLE;
	lStyle |= MUST_STYLE;
	::SetWindowLong(GetSafeHwnd(), GWL_STYLE, lStyle);
	SetExtendedStyle(0);
	ASSERT(GetHeaderCtrl() != NULL);
	CListCtrl::PreSubclassWindow();
}

BOOL CReportCtrl::IsSortable() const
{
	if (!HasColumnHeader())
		return FALSE;

	LONG lStyle = ::GetWindowLong(GetHeaderCtrl()->GetSafeHwnd(), GWL_STYLE);
	return (lStyle & HDS_BUTTONS) != 0;
}

BOOL CReportCtrl::HasColumnHeader() const
{
	return (GetStyle() & LVS_NOCOLUMNHEADER) == 0;
}

void CReportCtrl::ResizeToFitParent()
{
	// resize the list ctrl to fit parent client area
	if (GetSafeHwnd() != NULL)
	{
		CRect rect;
		GetParent()->GetClientRect(&rect);
		MoveWindow(&rect);
	}
}

BOOL CReportCtrl::ModifyStyleEx(DWORD dwRemove, DWORD dwAdd, UINT nFlags)
{
	dwRemove &= ~MUST_EX_STYLE;
	dwRemove |= MUST_NOT_EX_STYLE;
	dwAdd &= ~MUST_NOT_EX_STYLE;
	dwAdd |= MUST_EX_STYLE;
	return CListCtrl::ModifyStyleEx(dwRemove, dwAdd, nFlags);
}

BOOL CReportCtrl::ModifyStyle(DWORD dwRemove, DWORD dwAdd, UINT nFlags)
{
	dwRemove &= ~MUST_STYLE;
	dwRemove |= MUST_NOT_STYLE;
	dwAdd &= ~MUST_NOT_STYLE;
	dwAdd |= MUST_STYLE;
	return CListCtrl::ModifyStyle(dwRemove, dwAdd, nFlags);
}

void CReportCtrl::_MouseClkMonitor(UINT nMsg, UINT nFlags, CPoint point, BOOL bTriggerEdit)
{
	LVHITTESTINFO hti;
	hti.pt = point;
	const int IDX = SubItemHitTest(&hti);
	const BOOL BEFORE = CListCtrl::GetCheck(IDX) > 0;
	const BOOL WAS_EDIT = _IsEditVisible();
	EndEdit(TRUE);
	const BOOL WASACTIVE = bTriggerEdit ? ExamItemStates(IDX, RC_ITEM_FOCUSED | RC_ITEM_SELECTED) : FALSE;

	switch (nMsg)
	{
	case WM_LBUTTONDOWN:
		CListCtrl::OnLButtonDown(nFlags, point);
		break;

	case WM_LBUTTONDBLCLK:
		CListCtrl::OnLButtonDblClk(nFlags, point);
		break;

	case WM_MBUTTONDOWN:
		CListCtrl::OnMButtonDown(nFlags, point);
		break;

	case WM_MBUTTONDBLCLK:
		CListCtrl::OnMButtonDblClk(nFlags, point);
		break;

	case WM_RBUTTONDOWN:
		CListCtrl::OnRButtonDown(nFlags, point);
		break;

	case WM_RBUTTONDBLCLK:
		CListCtrl::OnRButtonDblClk(nFlags, point);
		break;

	default:
		break;		
	}
	
	const BOOL STATSCHANGED = _ItemCheckMonitor(IDX, BEFORE, CListCtrl::GetCheck(IDX) > 0, nMsg);

	if (bTriggerEdit && m_bAllowEdit && !STATSCHANGED && !WAS_EDIT && WASACTIVE)
		StartEdit(IDX, hti.iSubItem);
}

BOOL CReportCtrl::SetItemImage(int nItem, int nSubItem, int nImageIndex)
{
	return CListCtrl::SetItem(nItem, nSubItem, LVIF_IMAGE, NULL, nImageIndex, 0, 0, 0);
}

int CReportCtrl::GetItemImage(int nItem, int nSubItem) const
{
	LVITEM lvi;
	lvi.iItem = nItem;
	lvi.iSubItem = nSubItem;
	lvi.mask = LVIF_IMAGE;
	return CListCtrl::GetItem(&lvi) ? lvi.iImage : -1;
}

CImageList* CReportCtrl::SetImageList(CImageList *pImageList)
{
	return CListCtrl::SetImageList(pImageList, LVSIL_SMALL);
}

CImageList* CReportCtrl::GetImageList() const
{
	return CListCtrl::GetImageList(LVSIL_SMALL);
}

CImageList* CReportCtrl::SetImageList(UINT nBitmapID, COLORREF crMask)
{
	m_imgList.DeleteImageList();
	m_imgList.Create(nBitmapID, 16, 4, crMask);	
	return CListCtrl::SetImageList(&m_imgList, LVSIL_SMALL);
}

BOOL CReportCtrl::StartEdit(int nItem, int nSubItem)
{
	// Get the grid width and height
	if (!m_bAllowEdit || !_IsValidIndex(nItem) || nSubItem < 0 || nSubItem >= GetColumnCount())
		return FALSE;
	
	if (m_ptEditting.x == nItem && m_ptEditting.y == nSubItem)
		return TRUE;

	EndEdit(TRUE);
	m_ptEditting.x = nItem;
	m_ptEditting.y = nSubItem;
	SetAllItemStates(RC_ITEM_SELECTED, RC_ITEM_UNSELECTED); // unselect all
	SetItemStates(m_ptEditting.x, RC_ITEM_SELECTED | RC_ITEM_FOCUSED);

	// determine editbox font and alignment
	const DWORD FMT = _GetHeaderTextFormat(nSubItem);
	if (FMT != m_dwPrevEditFmt)
	{
		m_dwPrevEditFmt = FMT;

		// Funny thing:
		// Changing CEdit style among ES_LEFT, ES_CENTER, ES_RIGHT at runtime works
		// sometimes and fails other times. It just cannot guarantee to be succeed.
		// So I decided to destroy and recreate the CEdit every time when the text
		// format changes.
		if (m_pWndEdit->GetSafeHwnd() != NULL)
			m_pWndEdit->DestroyWindow();

		if (!m_pWndEdit->Create(ES_AUTOHSCROLL | ES_NOHIDESEL | WS_CHILD | WS_BORDER | FMT, CRect(0, 0, 1, 1), this, 0))
			return FALSE;
	}
	else
	{
		if (m_pWndEdit->GetSafeHwnd() == NULL
			&& !m_pWndEdit->Create(ES_AUTOHSCROLL | ES_NOHIDESEL | WS_CHILD | WS_BORDER | FMT, CRect(0, 0, 1, 1), this, 0))
		{
			return FALSE;
		}
	}

	m_pWndEdit->SetFont(GetFont());

	CRect rcEdit;
	ListView_GetSubItemRect(GetSafeHwnd(), m_ptEditting.x, m_ptEditting.y, LVIR_LABEL, &rcEdit);
	if (m_ptEditting.y > 0 && GetImageList() != NULL && GetItemImage(m_ptEditting.x, m_ptEditting.y) >= 0)
		rcEdit.DeflateRect(16, 0, 0, 0);

	// Move the editbox to that grid, obtain text from the grid, display the
	// editbox, and, finally, highlights all text in the editbox and set the
	// windows focus to the editbox.
	m_pWndEdit->MoveWindow(&rcEdit);
	m_pWndEdit->SetWindowText(GetItemText(m_ptEditting.x, m_ptEditting.y));
	m_pWndEdit->ShowWindow(SW_SHOW);
	m_pWndEdit->SetSel(0, -1);
	m_pWndEdit->SetFocus();
	return TRUE;
}

BOOL CReportCtrl::EndEdit(BOOL bCommit)
{
	if (!_IsEditVisible())
		return FALSE;

	CString str;
	m_pWndEdit->GetWindowText(str);
	BOOL bChanged = bCommit && str.Compare(GetItemText(m_ptEditting.x, m_ptEditting.y)) != 0;
	if (bChanged)
	{
		// update the list item
		CListCtrl::SetItemText(m_ptEditting.x, m_ptEditting.y, str);
		_UnsetSortedColumn();
		GetParent()->SendMessage(WM_EDIT_COMMITTED, (WPARAM)m_ptEditting.x, (LPARAM)m_ptEditting.y);
	}
	m_pWndEdit->ShowWindow(SW_HIDE);
	m_ptEditting.x = -1;
	m_ptEditting.y = -1;
	return bChanged;
}

BOOL CReportCtrl::SetHeaderImage(int nColumn, int nImageIndex, BOOL bLeftSide)
{
	if (GetHeaderCtrl()->GetImageList() == NULL)
		CListCtrl::GetHeaderCtrl()->SetImageList(GetImageList());
	
	HDITEM hi;
	::memset(&hi, 0, sizeof(HDITEM));
	hi.mask = HDI_FORMAT;
	if (!GetHeaderCtrl()->GetItem(nColumn, &hi))
		return FALSE;
	
	hi.mask |= HDI_IMAGE;		
	hi.fmt |= HDF_IMAGE;

	if (!bLeftSide)
		hi.fmt |= HDF_BITMAP_ON_RIGHT; // draw the image on right side of text

	hi.iImage = nImageIndex;
	return CListCtrl::GetHeaderCtrl()->SetItem(nColumn, &hi);
}

int CReportCtrl::GetSortedColumn() const
{
	return m_nSortCol;
}

BOOL CReportCtrl::IsSortAscending() const
{
	return m_bSortAscending;
}

void CReportCtrl::_UnsetSortedColumn()
{
	m_nSortCol = -1;
	m_bSortAscending = TRUE;
}

DWORD CReportCtrl::_GetHeaderTextFormat(int nColumn) const
{
	if (!HasColumnHeader())
		return ES_LEFT;

	HDITEM hd;
	hd.mask = HDI_FORMAT;
	if (!GetHeaderCtrl()->GetItem(nColumn, &hd))
		return ES_LEFT;

	if (hd.fmt & HDF_CENTER)
	{	
		return ES_CENTER;
	}
	else if (hd.fmt & HDF_RIGHT)
	{
		return ES_RIGHT;
	}
	else
	{
		return ES_LEFT;
	}
}

int CReportCtrl::GetHeaderImage(int nColumn) const
{
	HDITEM hi;
	::memset(&hi, 0, sizeof(HDITEM));
	hi.mask = HDI_IMAGE;
	return !GetHeaderCtrl()->GetItem(nColumn, &hi) ? hi.iImage : -1;
}

CImageList* CReportCtrl::SetHeaderImageList(CImageList *pImageList)
{
	return CListCtrl::GetHeaderCtrl()->SetImageList(pImageList);
}

CImageList* CReportCtrl::SetHeaderImageList(UINT nBitmapID, COLORREF crMask)
{
	m_headerImgList.Create(nBitmapID, 16, 4, crMask);
	return SetHeaderImageList(&m_headerImgList);
}

BOOL CReportCtrl::ExamItemStates(int nItem, DWORD dwStates) const
{
	if (dwStates == RC_ITEM_NONE)
		return FALSE;

	if (dwStates & RC_ITEM_ALL)
		return TRUE;

	return (GetItemStates(nItem) & dwStates) == dwStates;
}

int CReportCtrl::GetItemCount(DWORD dwStates) const
{
	const int ITEMS = CListCtrl::GetItemCount();
	if (dwStates == RC_ITEM_ALL)
		return ITEMS;

	int nCount = 0;
	for (int i = 0; i <ITEMS; i++)
	{
		if (ExamItemStates(i, dwStates))
			nCount++;
	}
	return nCount;
}

int CReportCtrl::GetFirstItem(DWORD dwStates, int nStartAfter) const
{
	const int ITEMS = CListCtrl::GetItemCount();
	nStartAfter = max(-1, nStartAfter);
	for (int i = nStartAfter + 1; i <ITEMS; i++)
	{
		if (ExamItemStates(i, dwStates))
			return i;
	}
	return -1;
}

int CReportCtrl::GetLastItem(DWORD dwStates, int nStartBefore) const
{
	const int ITEMS = CListCtrl::GetItemCount();
	nStartBefore = nStartBefore < 0 ? ITEMS - 1 : min(ITEMS, nStartBefore);
	for (int i = nStartBefore - 1; i >= 0; i--)
	{
		if (ExamItemStates(i, dwStates))
			return i;
	}
	return -1;
}

void CReportCtrl::InvertItems(int nType)
{
	const int ITEMS = CListCtrl::GetItemCount();
	if (nType == RC_INVERT_SELECTION)
	{
		for (int i = 0; i < ITEMS; i++)
			SetItemStates(i, ExamItemStates(i, RC_ITEM_SELECTED) ? RC_ITEM_UNSELECTED : RC_ITEM_SELECTED);
	}
	else if (nType == RC_INVERT_CHECKMARK)
	{
		for (int i = 0; i < ITEMS; i++)
			SetItemStates(i, ExamItemStates(i, RC_ITEM_CHECKED) ? RC_ITEM_UNCHECKED : RC_ITEM_CHECKED);
	}
}

DWORD CReportCtrl::GetItemStates(int nItem) const
{
	DWORD dwStates = 0;

	if (CListCtrl::GetItemState(nItem, LVIS_SELECTED))
		dwStates |= RC_ITEM_SELECTED;
	else
		dwStates |= RC_ITEM_UNSELECTED;

	if (CListCtrl::GetCheck(nItem) > 0)
		dwStates |= RC_ITEM_CHECKED;
	else
		dwStates |= RC_ITEM_UNCHECKED;

	if (CListCtrl::GetItemState(nItem, LVIS_FOCUSED))
		dwStates |= RC_ITEM_FOCUSED;
	else
		dwStates |= RC_ITEM_UNFOCUSED;

	return dwStates;
}

BOOL CReportCtrl::SetItemStates(int nItem, DWORD dwNewStates)
{
	BOOL bResult = _SetItemStatesNoVerify(nItem, dwNewStates);
	if (dwNewStates & RC_ITEM_CHECKED)
		_EnsureSingleCheck(nItem);
	return bResult;
}

int CReportCtrl::SetAllItemStates(DWORD dwOldStates, DWORD dwNewStates)
{
	if (dwNewStates == RC_ITEM_NONE || dwOldStates == RC_ITEM_NONE)
		return 0;

	int nCount = 0;
	const int ITEMS = CListCtrl::GetItemCount();
	for (int i = 0; i < ITEMS; i++)
	{
		if (dwOldStates == RC_ITEM_ALL || ExamItemStates(i, dwOldStates))
		{
			SetItemStates(i, dwNewStates);
			nCount++;
		}
	}

	if (dwNewStates & RC_ITEM_CHECKED)
		_EnsureSingleCheck(-1);

	return nCount;
}

void CReportCtrl::_EnsureSingleCheck(int nItem)
{
	if (m_nChkStyle == RC_CHKBOX_SINGLE)
	{
		const int FIRST = nItem < 0 ? GetFirstItem(RC_ITEM_CHECKED) : nItem;
		if (FIRST >= 0)
		{
			const int ITEMS = CListCtrl::GetItemCount();
			for (int i = FIRST + 1; i < ITEMS; i++)
			{
				if (CListCtrl::GetCheck(i))
					CListCtrl::SetCheck(i, FALSE);
			}
		}		
	}
}

BOOL CReportCtrl::_SetItemStatesNoVerify(int nItem, DWORD dwNewStates)
{
	if (dwNewStates == RC_ITEM_NONE)
		return FALSE;
	
	BOOL bResult = TRUE;
	
	if (dwNewStates & RC_ITEM_SELECTED)
		bResult &= CListCtrl::SetItemState(nItem, LVIS_SELECTED, LVIS_SELECTED);

	if (dwNewStates & RC_ITEM_UNSELECTED)
		bResult &= CListCtrl::SetItemState(nItem, 0, LVIS_SELECTED);

	if (dwNewStates & RC_ITEM_CHECKED)
		bResult &= CListCtrl::SetCheck(nItem, TRUE);

	if (dwNewStates & RC_ITEM_UNCHECKED)
		bResult &= CListCtrl::SetCheck(nItem, FALSE);

	if (dwNewStates & RC_ITEM_FOCUSED)
		bResult &= CListCtrl::SetItemState(nItem, LVIS_FOCUSED, LVIS_FOCUSED);

	if (dwNewStates & RC_ITEM_UNFOCUSED)
		bResult &= CListCtrl::SetItemState(nItem, 0, LVIS_FOCUSED);

	return bResult;
}

BOOL CReportCtrl::SetCheck(int nItem, BOOL bCheck)
{
	const BOOL RES = CListCtrl::SetCheck(nItem, bCheck);
	if (RES && bCheck)
		_EnsureSingleCheck(nItem);
	return RES;
}

CString CReportCtrl::GetHeaderText(int nColumn) const
{
	if (!HasColumnHeader())
		return _T("");

	HDITEM hd;
	TCHAR  szBuffer[256] = _T("");
	hd.mask = HDI_TEXT;
	hd.pszText = szBuffer;
	hd.cchTextMax = 255;
	return GetHeaderCtrl()->GetItem(nColumn, &hd) ? hd.pszText : _T("");
}

BOOL CReportCtrl::SetHeaderText(int nColumn, LPCTSTR lpText)
{
	if (!HasColumnHeader())
		return FALSE;

	LPTSTR psz = NULL;
	if (lpText == NULL)
	{
		psz = new TCHAR[1];
		*psz = _T('\0');
	}
	else
	{
		psz = _tcsdup(lpText);
	}
	HDITEM hd;
	hd.mask = HDI_TEXT;
	hd.pszText = psz;
	hd.cchTextMax = _tcslen(psz);
	const BOOL RES = CListCtrl::GetHeaderCtrl()->SetItem(nColumn, &hd);
	delete [] psz;
	return RES;
}

const CHeaderCtrl* CReportCtrl::GetHeaderCtrl() const
{
	// Yes, CListCtrl already provides "GetHeaderCtrl", but not const.	
	// I desperately need a "const" version of "GetHeaderCtrl" because that's the
	// only way to make "GetColumnCount" const, which, in turn, is being called by
	// A LOT of const member functions. So if "GetHeaderCtrl" is not const, there will
	// be almost no const member function at all in this class. Terrible.
	return (const CHeaderCtrl*)(CWnd::FromHandle(ListView_GetHeader(GetSafeHwnd())));
}

void CReportCtrl::_FreeItemMemory(int nItem)
{
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
	if (p != NULL)
	{
		CListCtrl::SetItemData(nItem, 0);
		delete p;
	}
}

void CReportCtrl::_AllocItemMemory(int nItem)
{
	ASSERT(_IsValidIndex(nItem));
	const int COLS = GetColumnCount();
	ASSERT(COLS > 0);
	CItemData* pData = new CItemData;	
	pData->dwData = CListCtrl::GetItemData(nItem);
	pData->aTextColors.SetSize(COLS);
	pData->aBkColors.SetSize(COLS);
	for (int i = 0; i < COLS; i++)
	{
		pData->aTextColors[i] = ::GetSysColor(COLOR_WINDOWTEXT);
		pData->aBkColors[i] = ::GetSysColor(COLOR_WINDOW);
	}
	CListCtrl::SetItemData(nItem, (DWORD)pData);
}

void CReportCtrl::_UpdateColumn(int nColumn, BOOL bInsert)
{
	const int ITEMS = GetItemCount();
	for (int i = 0; i < ITEMS; i++)
	{
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(i));
		ASSERT(p != NULL);
		if (bInsert)
			p->InsertColumn(nColumn);
		else
			p->DeleteColumn(nColumn);
	}
}

BOOL CReportCtrl::SetItemData(int nItem, DWORD dwData)
{
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
	if (p == NULL)
		return FALSE;
	p->dwData = dwData;
	return CListCtrl::SetItemData(nItem, (DWORD)p);
}

DWORD CReportCtrl::GetItemData(int nItem) const
{
	ASSERT(::IsWindow(m_hWnd));
	LVITEM lvi;
	memset(&lvi, 0, sizeof(LVITEM));
	lvi.iItem = nItem;
	lvi.mask = LVIF_PARAM;
	::SendMessage(m_hWnd, LVM_GETITEM, 0, (LPARAM)&lvi);
	CItemData* p = (CItemData*)lvi.lParam;
	return p == NULL ? 0 : p->dwData;
}

BOOL CReportCtrl::GetItem(LVITEM *pItem) const
{
	const BOOL RES = CListCtrl::GetItem(pItem);
	if (RES && (pItem->mask & LVIF_PARAM) != 0)
	{
		CItemData* p = (CItemData*)(pItem->lParam);
		if (p != NULL)
			pItem->lParam = p->dwData;
	}
	return RES;
}

BOOL CReportCtrl::SetItem(const LVITEM *pItem)
{
	if (pItem == NULL)
		return FALSE;
	
	LVITEM li;
	memcpy(&li, pItem, sizeof(LVITEM));
	if ((li.mask & LVIF_PARAM) != 0)
	{
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(li.iItem));
		if (p != NULL)
		{
			p->dwData = li.lParam;
			li.lParam = (LPARAM)p;
		}		
	}
	return CListCtrl::SetItem(&li);
}

BOOL CReportCtrl::SetItem(int nItem, int nSubItem, UINT nMask, LPCTSTR lpszItem, int nImage, UINT nState, UINT nStateMask, LPARAM lParam)
{
	if ((nMask & LVIF_PARAM) != 0)
	{
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
		if (p != NULL)
		{
			p->dwData = lParam;
			lParam = (LPARAM)p;
		}		
	}
	return CListCtrl::SetItem(nItem, nSubItem, nMask, lpszItem, nImage, nState, nStateMask, lParam);
}

void CReportCtrl::SetItemColor(int serial,COLORREF color)
{
	m_itemColor[serial] = color;
}

void CReportCtrl::SetItemTextColor(int nItem, int nSubItem, COLORREF color, BOOL bRedraw)
{
	if (color == COLOR_INVALID)
		color = ::GetSysColor(COLOR_WINDOWTEXT);
	const int ROWS = GetItemCount();
	const int COLS = GetColumnCount();
	BOOL bRowValid = nItem >= 0 && nItem < ROWS;
	BOOL bColValid = nSubItem >= 0 && nSubItem < COLS;

	if (bRowValid && bColValid)
	{
		// apply to individual grid
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
		ASSERT(p != NULL);
		p->aTextColors[nSubItem] = color;
	}
	else if (bRowValid && !bColValid)
	{
		// apply to whole row for the existing item
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
		ASSERT(p != NULL);
		for (int i = 0; i < COLS; i++)
			p->aTextColors[i] = color;
	}
	else if (!bRowValid && bColValid)
	{
		// apply to whole column for all existing items
		for (int i = 0; i < ROWS; i++)
		{
			CItemData* p = (CItemData*)(CListCtrl::GetItemData(i));
			ASSERT(p != NULL);
			p->aTextColors[nSubItem] = color;
		}
	}
	else
	{
		// apply to whole table for all existing items
		for (int i = 0; i < ROWS; i++)
		{
			CItemData* p = (CItemData*)(CListCtrl::GetItemData(i));
			ASSERT(p != NULL);
			for (int j = 0; j < COLS; j++)
				p->aTextColors[j] = color;
		}
	}

	if (bRedraw)
		RedrawWindow();
}

void CReportCtrl::SetItemBkColor(int nItem, int nSubItem, COLORREF color, BOOL bRedraw)
{
	if (color == COLOR_INVALID)
		color = ::GetSysColor(COLOR_WINDOW);
	const int ROWS = GetItemCount();
	const int COLS = GetColumnCount();
	BOOL bRowValid = nItem >= 0 && nItem < ROWS;
	BOOL bColValid = nSubItem >= 0 && nSubItem < COLS;

	if (bRowValid && bColValid)
	{
		// apply to individual grid
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
		ASSERT(p != NULL);
		p->aBkColors[nSubItem] = color;
	}
	else if (bRowValid && !bColValid)
	{
		// apply to whole row for the existing item
		CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
		ASSERT(p != NULL);
		for (int i = 0; i < COLS; i++)
			p->aBkColors[i] = color;
	}
	else if (!bRowValid && bColValid)
	{
		// apply to whole column for all existing items
		for (int i = 0; i < ROWS; i++)
		{
			CItemData* p = (CItemData*)(CListCtrl::GetItemData(i));
			ASSERT(p != NULL);
			p->aBkColors[nSubItem] = color;
		}
	}
	else
	{
		// apply to whole table for all existing items
		for (int i = 0; i < ROWS; i++)
		{
			CItemData* p = (CItemData*)(CListCtrl::GetItemData(i));
			ASSERT(p != NULL);
			for (int j = 0; j < COLS; j++)
				p->aBkColors[j] = color;
		}
	}

	if (bRedraw)
		RedrawWindow();
}

COLORREF CReportCtrl::GetItemTextColor(int nItem, int nSubItem) const
{
	if (!_IsValidIndex(nItem) || nSubItem < 0 || nSubItem >= GetColumnCount())
		return COLOR_INVALID;
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
	ASSERT(p != NULL);
	return p->aTextColors[nSubItem];
}

COLORREF CReportCtrl::GetItemBkColor(int nItem, int nSubItem) const
{
	if (!_IsValidIndex(nItem) || nSubItem < 0 || nSubItem >= GetColumnCount())
		return COLOR_INVALID;
	CItemData* p = (CItemData*)(CListCtrl::GetItemData(nItem));
	ASSERT(p != NULL);
	return p->aBkColors[nSubItem];
}

BOOL CReportCtrl::_PartialSort(int nStart, int nEnd)
{
	if (nStart >= nEnd || !_IsValidIndex(nStart) || !_IsValidIndex(nEnd))
		return FALSE;

	const int COUNT = nEnd - nStart + 1;
	int i = 0;
	int* aIndices = new int[COUNT];
	for (i = 0; i < COUNT; i++)
		aIndices[i] = nStart + i;
	
	_QuickSortRecursive(aIndices, 0, COUNT - 1); 

	// rearrange items
	const int COLS = GetColumnCount();
	ROWINFO* aRows = new ROWINFO[COUNT];
	for (i = 0; i < COUNT; i++)
	{
		int n = aIndices[i];
		aRows[i].dwStates = GetItemStates(aIndices[i]);
		aRows[i].dwData = CListCtrl::GetItemData(aIndices[i]);
		aRows[i].aImages.SetSize(COLS);
		aRows[i].aTexts.SetSize(COLS);
		for (int j = 0; j < COLS; j++)
		{
			aRows[i].aImages[j] = GetItemImage(aIndices[i], j);
			aRows[i].aTexts[j] = GetItemText(aIndices[i], j);
		}
	}

	for (i = 0; i < COUNT; i++)
	{
		SetItemStates(nStart + i, aRows[i].dwStates);
		CListCtrl::SetItemData(nStart + i, aRows[i].dwData);
		for (int j = 0; j < COLS; j++)
		{
			SetItemImage(nStart + i, j, aRows[i].aImages[j]);			
			CListCtrl::SetItemText(nStart + i, j, aRows[i].aTexts[j]);
		}
	}
	delete [] aRows;
	delete [] aIndices;
	return TRUE;
}

int CReportCtrl::_CompareItems(int nItem1, int nItem2)
{
	const CString s1 = GetItemText(nItem1, m_nSortCol);
	const CString s2 = GetItemText(nItem2, m_nSortCol);

	DWORD dw1, dw2;
	if(_ITEM_COMPARE_FUNCS::_IsHexNumber(s1, dw1) && _ITEM_COMPARE_FUNCS::_IsHexNumber(s2, dw2))
		return _ITEM_COMPARE_FUNCS::_HexNumberCompare(dw1, dw2);

	double f1, f2;
	if(_ITEM_COMPARE_FUNCS::_IsDecNumber(s1, f1) && _ITEM_COMPARE_FUNCS::_IsDecNumber(s2, f2))
		return _ITEM_COMPARE_FUNCS::_DecNumberCompare(f1, f2);

	COleDateTime date1, date2;
	if(_ITEM_COMPARE_FUNCS::_IsDate(s1, date1) && _ITEM_COMPARE_FUNCS::_IsDate(s2, date2))
		return _ITEM_COMPARE_FUNCS::_DateCompare(date1, date2);
	
	// plain text.
	return s1.Compare(s2);
}

int CReportCtrl::_FindSeparator(int nStartAfter, int nColumn) const
{
	if (m_pszSeparator == NULL)
		return -1;

	const int ITEMS = GetItemCount();
	for (int i = nStartAfter + 1; i < ITEMS; i++)
	{
		if (GetItemText(i, nColumn) == m_pszSeparator)
			return i;
	}
	return -1;
}

void CReportCtrl::_QuickSortRecursive(int* pArr, int nLow, int nHigh)
{
	int i = nHigh, j = nLow;
	int n = pArr[(nLow + nHigh) / 2];
	do
	{
		if (m_bSortAscending)
		{
			while (_CompareItems(pArr[j], n) < 0)
				j++;

			while (_CompareItems(pArr[i], n) > 0)
				i--;
		}
		else
		{
			while (_CompareItems(pArr[j], n) > 0)
				j++;

			while (_CompareItems(pArr[i], n) < 0)
				i--;
		}

		if ( i >= j )
		{
			if ( i != j )
			{
				int nTemp = pArr[i];
				pArr[i] = pArr[j];
				pArr[j] = nTemp;
			}

			i--;
			j++;
		}
		
	} while (j <= i);

	if (nLow < i)
		_QuickSortRecursive(pArr,nLow,i);

	if (j < nHigh)
		_QuickSortRecursive(pArr,j,nHigh);
}
