////////////////////////////////////////////////////////////////////////////
//
//	Name		:	CSwiListCtrl.cpp
//
//	Purpose		:	Generic ListCtrl with sorting capabilities
//
//	Written		:	chow	12/2000
//  Revision    :   Mohsen Jalali Nov, 2001
//
//	Description	:	Sorts on the ListCtrl's text data or param data
//					WARNING: Do not modify the list ctrl's param data if
//					you plan on using the text data to sort.  Otherwise,
//					unpredicatble behaviour will most likely occur.
//
//	Copyright (c) 2001 Sierra Wireless Inc. All rights reserved
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "SwiListCtrl.h"
#include "ItemEdit.h"
#include "Winuser.h"

#define OBM_DNARROW         32752
#ifdef UNDER_CE
#define	BM_SETIMAGE			WM_USER + 100
#endif

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

#define MAX_ITEM_TEXT	255
#define MIN_COL_SIZE	30

// Local control ID's
enum E_CTRLIDS
{
	LC_BTN_DROPLIST		= 1,
	LC_BTN_FILELIST,
	LC_LISTBOX,
	LC_EDITBOX,
};

/////////////////////////////////////////////////////////////////////////////
// CSwiListCtrl

CSwiListCtrl::CSwiListCtrl(CDialog  *pParentDlg,   //= NULL
                           bool     bUpdateHeader  //= true
                           )
{
	UNREFERENCED_PARAMETER(bUpdateHeader);
	m_pParentDlg			= pParentDlg;
	m_pFnGetItemProp		= NULL;
	m_pFnPopulateEditbox	= NULL;

	m_nItemEdit				= -1;
	m_nSubItemEdit			= -1;
	m_bEditCtrls			= false;
	m_pEdit					= NULL;
}

CSwiListCtrl::~CSwiListCtrl()
{
}

BEGIN_MESSAGE_MAP(CSwiListCtrl, CListCtrl)
	//{{AFX_MSG_MAP(CSwiListCtrl)
	ON_NOTIFY_REFLECT_EX(NM_CLICK, OnItemClick)
	ON_WM_PAINT()
	ON_WM_CREATE()
	ON_WM_VSCROLL()
	ON_WM_HSCROLL()
#ifndef UNDER_CE
	ON_WM_MOUSEWHEEL()
#endif // UNDER_CE
	ON_WM_SIZE()

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSwiListCtrl message handlers


BOOL CSwiListCtrl::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 
{
	return CListCtrl::OnNotify(wParam, lParam, pResult);
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	OnKeydown
//
//   Description:	keypress occured in this listctrl
//
//   Arguments  :	pNMHDR	- pointer to NMHDR structure
//					pResult	- pointer to result to edit
//
//   Returns:   :	none
//
//   Comments   :	Because the keydown event occurs before focus is set,
//					we need to determine the actual item to be focused inorder
//					to display the right message
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::OnKeydown(NMHDR* pNMHDR, LRESULT* pResult) 
{
	/*	Note this following override has been deprecated.  The problem
		with this function is we do not properly account for the SHIFT key
		which would normally in conjuction w/ the cursor can alter the select.
		Instead, the parent of the list control should rely on LVN_ITEMCHANGED
		to determine if a selection change has been made. */

	LV_KEYDOWN* pLVKeyDow	= (LV_KEYDOWN*)pNMHDR;
	int		iSelected		= GetSelectionMark();
	int		iCount			= GetItemCount();
	int		iPerPage		= GetCountPerPage();
	BOOL	bSet			= FALSE;

	switch (pLVKeyDow->wVKey) 
    {
	case VK_DOWN:
		iSelected	= min(iSelected+1, iCount-1);
		bSet		= TRUE;
		break;
	case VK_UP:
		iSelected	= max(0, iSelected-1);
		bSet		= TRUE;
		break;
	case VK_NEXT:
		iSelected	+= iPerPage;
        iSelected	= min(iSelected, iCount-1);
		bSet		= TRUE;
		break;
	case VK_PRIOR:
		iSelected	-= iPerPage;
        iSelected	= max(0, iSelected);
		bSet		= TRUE;
		break;
	case VK_HOME:
		iSelected	= 0;
		bSet		= TRUE;
		break;
	case VK_END:
		iSelected	= iCount - 1;
		bSet		= TRUE;
		break;
	case VK_DELETE:
		iSelected	= iCount - 1;
		bSet		= TRUE;
		break;
	}

	if (bSet)
	{
		SetSelectionMark(iSelected);
		EnsureVisible(iSelected, FALSE);
        if (pLVKeyDow->wVKey == VK_DELETE)
		{
		    GetParent()->PostMessage(GetDlgCtrlID(), VK_DELETE, GetSelectionMark());
		}
        else
		{
		    //GetParent()->PostMessage(GetDlgCtrlID(), NM_CLICK, GetSelectionMark());

			// Interim fix for keyboard usage when used in a view.
			// Proper fix - we should remove this override completely and handle
			// the selection trigger differently.
#ifndef UNDER_CE
			NMITEMACTIVATE nmact;

			memset(&nmact, 0, sizeof(NMITEMACTIVATE));
			nmact.hdr.hwndFrom	= GetSafeHwnd();
			nmact.hdr.idFrom	= GetDlgCtrlID();
			nmact.hdr.code		= NM_CLICK;
			nmact.iItem			= iSelected;
#else
			NMHDR			nmact;
			memset(&nmact, 0, sizeof(NMHDR));
			nmact.hwndFrom	= GetSafeHwnd();
			nmact.idFrom	= GetDlgCtrlID();
			nmact.code		= NM_CLICK;
#endif
			GetParent()->SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM)&nmact);

		}
	}

	*pResult = 0;
}

void CSwiListCtrl::SetGetItemPropFn (PFNGETITEMPROP pFn) 
{
	m_pFnGetItemProp = pFn;
}

void CSwiListCtrl::SetEditBoxFn(PFNPOPULATEEDITBOX pFn)
{
	m_pFnPopulateEditbox = pFn;
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	DrawItem
//
//   Description:	Custom draw of listctrl contents
//
//   Arguments  :	[in]lpDrawItem	-	See MSDN for details
//
//   Returns:   :	None
//
//   Comments   :	To enable use of this function, the listctrl must have
//					the ownerdrawn property turned on.
//					The custom drawing here attempts to highlight an entire
//					row, including any images.
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItem) 
{
	CDC			dc;
	CRect		rcBounds;
	bool		bSelected = false;
	COLORREF	crTextOld;
	COLORREF	crBkOld;
	S_ITEMPROP	itemProp;

	dc.Attach(lpDrawItem->hDC);

	if (lpDrawItem->itemAction	| ODA_SELECT &&
		lpDrawItem->itemState	& ODS_SELECTED)
	{
		bSelected = true;
	}

	// Draw each column item 

	for (int nColumn = 0; nColumn < GetColumnCount(); nColumn++)
	{
		bool bEditable = false;
		bool bReadOnly = false;

		// Attempt to retrieve custom properties
		if (NULL	!= m_pFnGetItemProp &&
			false	!= m_pFnGetItemProp(GetItemData(lpDrawItem->itemID), 
										lpDrawItem->itemID, 
										nColumn, 
										itemProp))
		{
			bEditable = true;
			bReadOnly = itemProp.bReadOnly;
		}

		GetSubItemRectEx(lpDrawItem->itemID, nColumn, LVIR_BOUNDS, rcBounds);

		// Set background fill and text color
		if (bSelected && !bEditable)
		{
			crTextOld	= dc.SetTextColor(bReadOnly ?	GetSysColor(COLOR_GRAYTEXT) :
														GetSysColor(COLOR_HIGHLIGHTTEXT));
			crBkOld		= dc.SetBkColor(GetSysColor(COLOR_HIGHLIGHT));
			dc.FillSolidRect(rcBounds, GetSysColor(COLOR_HIGHLIGHT));
		}
		else
		{
			crTextOld	= dc.SetTextColor(bReadOnly ?	GetSysColor(COLOR_GRAYTEXT) :
														GetSysColor(COLOR_WINDOWTEXT));
			crBkOld		= dc.SetBkColor(GetBkColor());
			dc.FillSolidRect(rcBounds, GetSysColor(COLOR_WINDOW));
		}

		// Draw item
		if (bSelected && bEditable && !bReadOnly)
		{
			DrawEditItem(lpDrawItem->itemID, nColumn, lpDrawItem, &dc);
		}
		else
		{
			DrawSubItem(nColumn, lpDrawItem, &dc);

			if (m_nItemEdit		== (int)lpDrawItem->itemID &&
				m_nSubItemEdit	== nColumn)
			{
				// A previously edit item is not longer selected
				// so we should hie the edit controls
				HideEditCtrls();
			}
		}

		// Restore colors
		dc.SetTextColor(crTextOld);
		dc.SetBkColor(crBkOld);

	}

	// If the item is focused, now draw a focus rect around the entire row
    if (bSelected)
    {
		// Draw the focus rect
		GetItemRect(lpDrawItem->itemID, rcBounds, LVIR_BOUNDS);
		dc.DrawFocusRect(rcBounds);
    }

	dc.Detach();
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	DrawSubItem
//
//   Description:	Custom draw of individual listitem columns
//
//   Arguments  :	[in]nSubItem	- subitem we're drawing
//					[in]lpDrawItem	- LPDRAWITEM for current row
//					[in]pDc			- DC to draw to
//
//   Returns:   :	None
//
//   Comments   :	To enable use of this function, the listctrl must have
//					the ownerdrawn property turned on.
//					The custom drawing here attempts to highlight an entire
//					row, including any images.
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::DrawSubItem(int nSubItem, LPDRAWITEMSTRUCT lpDrawItem, CDC* pDC)
{
	LVITEM		lvi;
	TCHAR		tcBuff[MAX_ITEM_TEXT];
	CImageList* pImageList	= NULL;
	CRect		rcItem;
	CRect		rcLabel;
	CRect		rcIcon;
	IMAGEINFO	imgInfo;

	memset(&lvi,	0, sizeof(LVITEM));
	memset(&tcBuff, 0, MAX_ITEM_TEXT);

	lvi.mask		= LVIF_IMAGE | LVIF_STATE |LVIF_TEXT;
	lvi.iItem		= lpDrawItem->itemID;
	lvi.iSubItem	= nSubItem;
	lvi.stateMask	= 0xFFFF;		// get all state flags
	lvi.iImage		= -1;
	lvi.pszText		= (TCHAR*)&tcBuff;
	lvi.cchTextMax	= MAX_ITEM_TEXT;
	GetItem(&lvi);

	GetSubItemRectEx(lpDrawItem->itemID, nSubItem, LVIR_BOUNDS, rcItem);
	GetItemRect(lpDrawItem->itemID, rcLabel,	LVIR_LABEL);
	GetItemRect(lpDrawItem->itemID, rcIcon,		LVIR_ICON);

	if (-1 != lvi.iImage)
	{
		// Set an indent of 2 pixels
		rcItem.left += 2;

		// Draw state icon
		if (lvi.state & LVIS_STATEIMAGEMASK)
		{
			int nImage = ((lvi.state & LVIS_STATEIMAGEMASK)>>12) - 1;
			pImageList = GetImageList(LVSIL_STATE);
			if (pImageList)
			{
				pImageList->Draw(pDC, nImage,
					CPoint(rcItem.left, rcItem.top), 
					(lpDrawItem->itemState & ODS_SELECTED ? ILD_BLEND25:0) | 
					ILD_TRANSPARENT);
				
				pImageList->GetImageInfo(nImage, &imgInfo);
				rcItem.left += imgInfo.rcImage.right - imgInfo.rcImage.left;
			}
		}

		// Draw normal and overlay icon
		pImageList = GetImageList(LVSIL_SMALL);
		if (pImageList)
		{
			UINT nOvlImageMask = lvi.state & LVIS_OVERLAYMASK;

			pImageList->Draw(pDC, lvi.iImage, 
				CPoint(rcItem.left, rcItem.top),
				(lpDrawItem->itemState & ODS_SELECTED ? ILD_BLEND25:0) | 
				ILD_TRANSPARENT | nOvlImageMask );

			pImageList->GetImageInfo(lvi.iImage, &imgInfo);
			rcItem.left += imgInfo.rcImage.right - imgInfo.rcImage.left;
		}
	}

	// Draw label
	if (0 < _tcslen(lvi.pszText))
	{
		UINT		nJustify = DT_LEFT;
		LVCOLUMN	lvc;

		// Get the text justification *IF* no images were drawn
		memset(&lvc, 0, sizeof(LVCOLUMN));
		lvc.mask = LVCF_FMT;

		if (-1 != lvi.iImage &&
			GetColumn(nSubItem, &lvc))
		{
			switch (LVCFMT_JUSTIFYMASK & lvc.fmt)
			{
			case LVCFMT_RIGHT:
				nJustify = DT_RIGHT;
				break;

			case LVCFMT_CENTER:
				nJustify = DT_CENTER;
				break;

			default:
				break;
			}
		}

		// Labels are offset by a certain amount  
		// This offset is related to the width of a space character
		int	offset	= pDC->GetTextExtent(_T(" "), 1 ).cx*2;

		rcLabel			= rcItem;
		rcLabel.left	+= offset;
		rcLabel.right	-= offset;

#if UNDER_CE
		pDC->DrawText(lvi.pszText, -1, rcLabel,
			nJustify|DT_SINGLELINE|DT_NOPREFIX|DT_VCENTER);
#else
		pDC->DrawText(lvi.pszText, -1, rcLabel,
			nJustify|DT_SINGLELINE|DT_NOPREFIX|DT_VCENTER|DT_END_ELLIPSIS);
#endif
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	OnItemClick
//
//   Description:	Called when an item is selected
//
//   Arguments  :	see MSDN on NM_CLICK
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
BOOL CSwiListCtrl::OnItemClick(NMHDR* pNMHDR, LRESULT *pResult)
{
#ifndef UNDER_CE
	NMITEMACTIVATE* pNMItemActivate = (NMITEMACTIVATE*)pNMHDR;
#else
	NMLISTVIEW* pNMItemActivate = (NMLISTVIEW*)pNMHDR;
#endif
	EditItem(pNMItemActivate->iItem, pNMItemActivate->iSubItem);

	*pResult = 0;

	return FALSE;
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	EditItem
//
//   Description:	Begin editing of the specified item
//
//   Arguments  :	[in]nItem		- item index
//					[in]nSubItem	- subitem index
//
//   Returns:   :	none
//
//   Comments   :	Invoking by enterkey triggers editing of items that would
//					otherwise require a specific mouse click (e.g. droplist)
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::EditItem(int nItem, int nSubItem)
{
	S_ITEMPROP	itemProp;

	if (NULL	== m_pFnGetItemProp		||
		-1		== nItem				||
		true	!=	m_pFnGetItemProp(	GetItemData(nItem), 
										nItem,
										nSubItem,
										itemProp))
	{
		return;
	}

	if (!itemProp.bReadOnly)
	{
		CRect rcItem;

		GetSubItemRectEx(nItem, nSubItem, LVIR_BOUNDS, rcItem);
		EditItemEdit(nItem, nSubItem, itemProp, &rcItem);
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	DrawEditItem
//
//   Description:	Displays an in-place edit item
//
//   Arguments  :	[in]nItem		- item index
//					[in]nSubItem	- subitem index
//
//   Returns:   :	none
//
//   Comments   :	Relies on the callback to determine what type of edit
//					control to use
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::DrawEditItem(int nItem, int nSubItem, LPDRAWITEMSTRUCT lpDrawItem, CDC* pDC)
{
	S_ITEMPROP	itemProp;

	if (NULL == m_pFnGetItemProp ||
		!m_pFnGetItemProp(GetItemData(nItem), nItem, nSubItem, itemProp))
	{
		return;
	}

	// Dont edit here.  We require user to click on subitem.
	DrawSubItem(nSubItem, lpDrawItem, pDC);
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	EditItemEdit
//
//   Description:	Prepares subitem to be edited via edit control
//
//   Arguments  :	[in]nItem		- item index
//					[in]nSubItem	- subitem index
//					[in]itemProp	- item properties
//					[in]pRectItem	- item rect to position editbox to
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::EditItemEdit(int nItem, int nSubItem, S_ITEMPROP& itemProp, LPCRECT pRectItem)
{
	DWORD		dwStyle = WS_CHILD|WS_BORDER|ES_AUTOHSCROLL;
	CString		strText;

	m_nItemEdit		= nItem;
	m_nSubItemEdit	= nSubItem;

	// Display a listbox.  Note, we rely on the listbox killing itself if it loses focus
	m_pEdit = new CItemEdit(_T(""), m_nItemEdit, m_nSubItemEdit);

	if (itemProp.bPassword)
	{
		dwStyle |= ES_PASSWORD;
	}

	if (itemProp.bNumeric)
	{
		dwStyle |= ES_NUMBER;
	}

	m_pEdit->Create(dwStyle, *pRectItem, this, LC_EDITBOX);

	m_pEdit->SetLimitText(itemProp.iMaxLen);

	// We rely on the callback for populating the editbox because
	// if it is a password field, then the listctrl's entry is masked/unusable
	if (NULL != m_pFnPopulateEditbox)
	{
		m_pFnPopulateEditbox(GetItemData(m_nItemEdit), m_pEdit);
	}

	m_pEdit->GetWindowText(strText);
	m_pEdit->SetSel(strText.GetLength(), strText.GetLength());

	// Position control
	m_pEdit->MoveWindow(pRectItem);
	m_pEdit->ShowWindow(SW_SHOW);
	m_pEdit->SetFocus();
	m_pEdit->ResetModified();
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	OnCreate
//
//   Description:	Indicates that a window is being created
//
//   Arguments  :	see MSDN
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
int CSwiListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CListCtrl::OnCreate(lpCreateStruct) == -1)
		return -1;

	InitControls();
	return 0;
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	InitControls
//
//   Description:	Intializes sub-controls for use
//
//   Arguments  :	none
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::InitControls()
{
	CRect rectTemp = CRect(0,0,1,1);
	m_bEditCtrls = true;
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	DeleteAllItems
//
//   Description:	This method deletes all items from the list view control.
//
//   Arguments  :	none
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
BOOL CSwiListCtrl::DeleteAllItems()
{
	HideEditCtrls();

	return CListCtrl::DeleteAllItems();
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	HideEditCtrls
//
//   Description:	Hides all edit controls
//
//   Arguments  :	none
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::HideEditCtrls()
{
	if (!m_bEditCtrls)
	{
		return;
	}

}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	OnVScroll
//
//   Description:	Indicates a click on a vertical scroll bar
//
//   Arguments  :	See MSDN
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	HideEditCtrls();

	if (GetFocus() != this) 
	{
		SetFocus();
	}

	CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	OnHScroll
//
//   Description:	Indicates a click on a horizontal scroll bar
//
//   Arguments  :	See MSDN
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
void CSwiListCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	HideEditCtrls();

	if (GetFocus() != this) 
	{
		SetFocus();
	}

	CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	OnMouseWheel
//
//   Description:	Indicates a mouse-wheel movement
//
//   Arguments  :	See MSDN
//
//   Returns:   :	none
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
BOOL CSwiListCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	UNREFERENCED_PARAMETER(pt);
	UNREFERENCED_PARAMETER(zDelta);
	UNREFERENCED_PARAMETER(nFlags);

	HideEditCtrls();

	if (GetFocus() != this) 
	{
		SetFocus();
	}

#ifdef UNDER_CE
	return TRUE;
#else // UNDER_CE
	return CListCtrl::OnMouseWheel(nFlags, zDelta, pt);
#endif // UNDER_CE
}


BOOL CSwiListCtrl::GetSubItemRectEx(int iItem, int iSubItem, int nArea, CRect &ref)
{
	if (0 == iSubItem)
	{
		// Not a bug, but an odd implementation on MS's part.  GetSubItemRect on
		// the first column will retrieve the entire rect for the item...
		GetSubItemRect(iItem, iSubItem, nArea, ref);

		// So to compensate, we adjust the right border to equal the 2nd column's rect
		if (1 < GetItemCount())
		{
			ref.right = GetColumnOffset(1);
		}

		return true;
	}
	else
	{
		return GetSubItemRect(iItem, iSubItem, nArea, ref);
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	GetColumnCount
//
//   Description:	Returns # of columns in list
//
//   Arguments  :	None
//
//   Returns:   :	# of columns
//
//   Comments   :	None
//
////////////////////////////////////////////////////////////////////////////////
int CSwiListCtrl::GetColumnCount()
{
	CHeaderCtrl* pHeader = GetHeaderCtrl();

	if (pHeader)
	{
		// Should always be true, even if header disabled
		return pHeader->GetItemCount();
	}

	return 0;

}

////////////////////////////////////////////////////////////////////////////////
//
//   Function   :	GetColumnOffset
//
//   Description:	Returns the total x-position offset of a given column
//
//   Arguments  :	Column number
//
//   Returns:   :	offset from the left side of the list
//
//   Comments   :	none
//
////////////////////////////////////////////////////////////////////////////////
int CSwiListCtrl::GetColumnOffset(int iColumn)
{
	int iColumnOffset = 0;

	if (iColumn < 0)
	{
		return 0;
	}
	else if (iColumn >= GetColumnCount())
	{
		// Return width of control
		CRect rect;
		GetClientRect(&rect);
		return rect.Width();
	}

	for (int i = 0; i < iColumn; i++)
	{
		iColumnOffset += GetColumnWidth(i);
	}

	return iColumnOffset;
}
