// For licensing and usage information, read docs/winui_license.txt
// MASTER
//****************************************************************************

// standard windows headers
#define _WIN32_IE 0x0501
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>

// standard C headers
#include <tchar.h>

// MAME/MAMEUI headers
#include "picker.h"
#include "winui.h"
#include "mui_opts.h"
#include "treeview.h"


// fix warning: cast does not match function type
#if defined(__GNUC__) && defined(ListView_GetHeader)
#undef ListView_GetHeader
#endif
#if defined(__GNUC__) && defined(ListView_GetImageList)
#undef ListView_GetImageList
#undef ListView_GetItemRect
#endif

#ifndef ListView_GetItemRect
#define ListView_GetItemRect(w,i,p,c) \
	(BOOL)SNDMSG((w),LVM_GETITEMRECT,i,((p != NULL)?(((LPRECT)(p))->left=(c),(LPARAM)(LPRECT)(p)):0))
#endif

#ifndef ListView_GetImageList
#define ListView_GetImageList(w,i) (HIMAGELIST)(LRESULT)(int)SendMessage((w),LVM_GETIMAGELIST,(i),0)
#endif // ListView_GetImageList

#ifndef ListView_GetHeader
#define ListView_GetHeader(w) (HWND)(LRESULT)(int)SNDMSG((w),LVM_GETHEADER,0,0)
#endif // ListView_GetHeader

#ifndef HDM_SETIMAGELIST
#define HDM_SETIMAGELIST        (HDM_FIRST + 8)
#endif // HDM_SETIMAGELIST

#ifndef Header_SetImageList
#define Header_SetImageList(h,i) (HIMAGELIST)(LRESULT)(int)SNDMSG((h), HDM_SETIMAGELIST, 0, (LPARAM)i)
#endif // Header_SetImageList

#ifndef HDF_SORTUP
#define HDF_SORTUP 0x400
#endif

#ifndef HDF_SORTDOWN
#define HDF_SORTDOWN 0x200
#endif



struct PickerInfo
{
	const struct PickerCallbacks *pCallbacks;
	WNDPROC pfnParentWndProc;
	int nCurrentViewID;
	int nLastItem;
	int nColumnCount;
	int *pnColumnsShown;
	int *pnColumnsOrder;
	UINT_PTR nTimer;
	const LPCTSTR *ppszColumnNames;
};


static struct PickerInfo *GetPickerInfo(HWND hWnd)
{
	LONG_PTR l = GetWindowLongPtr(hWnd, GWLP_USERDATA);
	return (struct PickerInfo *) l;
}



static LRESULT CallParentWndProc(WNDPROC pfnParentWndProc, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	LRESULT rc;

	if (!pfnParentWndProc)
		pfnParentWndProc = GetPickerInfo(hWnd)->pfnParentWndProc;

	if (IsWindowUnicode(hWnd))
		rc = CallWindowProcW(pfnParentWndProc, hWnd, message, wParam, lParam);
	else
		rc = CallWindowProcA(pfnParentWndProc, hWnd, message, wParam, lParam);
	return rc;
}

static BOOL ListViewOnErase(HWND hWnd, HDC hDC)
{
	MYBITMAPINFO *pbmDesc = GetBackgroundInfo();
	HBITMAP hBackground = GetBackgroundBitmap();
	HPALETTE hPALbg = GetBackgroundPalette();

	// this does not draw the background properly in report view

	RECT rcClient;
	GetClientRect(hWnd, &rcClient);

	HDC htempDC = CreateCompatibleDC(hDC);
	HBITMAP hOldBitmap = (HBITMAP)SelectObject(htempDC, hBackground);

	HRGN rgnBitmap = CreateRectRgnIndirect(&rcClient);
	SelectClipRgn(hDC, rgnBitmap);
	DeleteBitmap(rgnBitmap);

	HPALETTE hPAL = (!hPALbg) ? CreateHalftonePalette(hDC) : hPALbg;

	if (GetDeviceCaps(htempDC, RASTERCAPS) & RC_PALETTE && hPAL != NULL)
	{
		SelectPalette(htempDC, hPAL, false);
		RealizePalette(htempDC);
	}

	// Get x and y offset
	POINT pt = {0,0};
	MapWindowPoints(hWnd, GetTreeView(), &pt, 1);
	POINT ptOrigin;
	GetDCOrgEx(hDC, &ptOrigin);
	ptOrigin.x -= pt.x;
	ptOrigin.y -= pt.y;
	ptOrigin.x = -GetScrollPos(hWnd, SB_HORZ);
	ptOrigin.y = -GetScrollPos(hWnd, SB_VERT);

	if (pbmDesc->bmWidth && pbmDesc->bmHeight)
		for (int i = ptOrigin.x; i < rcClient.right; i += pbmDesc->bmWidth)
			for (int j = ptOrigin.y; j < rcClient.bottom; j += pbmDesc->bmHeight)
				BitBlt(hDC, i, j, pbmDesc->bmWidth, pbmDesc->bmHeight, htempDC, 0, 0, SRCCOPY);

	SelectObject(htempDC, hOldBitmap);
	DeleteDC(htempDC);

	if (!pbmDesc->bmColors)
	{
		DeletePalette(hPAL);
		hPAL = 0;
	}

	return true;
}



static BOOL ListViewNotify(HWND hWnd, LPNMHDR lpNmHdr)
{
	// This code is for using bitmap in the background
	// Invalidate the right side of the control when a column is resized
	if (lpNmHdr->code == HDN_ITEMCHANGINGA || lpNmHdr->code == HDN_ITEMCHANGINGW)
	{
		DWORD dwPos = GetMessagePos();
		POINT pt;
		pt.x = LOWORD(dwPos);
		pt.y = HIWORD(dwPos);

		RECT rcClient;
		GetClientRect(hWnd, &rcClient);
		ScreenToClient(hWnd, &pt);
		rcClient.left = pt.x;
		InvalidateRect(hWnd, &rcClient, false);
	}
	return false;
}



static BOOL ListViewContextMenu(HWND hwndPicker, LPARAM lParam)
{
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hwndPicker);

	// Extract the point out of the lparam
	POINT pt;
	pt.x = GET_X_LPARAM(lParam);
	pt.y = GET_Y_LPARAM(lParam);
	if (pt.x < 0 && pt.y < 0)
		GetCursorPos(&pt);

	// Figure out which header column was clicked, if at all
	int nViewID = Picker_GetViewID(hwndPicker);
	int nColumn = -1;

	if ((nViewID == VIEW_REPORT) || (nViewID == VIEW_GROUPED))
	{
		HWND hwndHeader = ListView_GetHeader(hwndPicker);
		POINT headerPt = pt;
		ScreenToClient(hwndHeader, &headerPt);

		RECT rcCol;
		for (int i = 0; Header_GetItemRect(hwndHeader, i, &rcCol); i++)
		{
			if (PtInRect(&rcCol, headerPt))
			{
				nColumn = i;
				break;
			}
		}
	}

	if (nColumn >= 0)
	{
		// A column header was clicked
		if (pPickerInfo->pCallbacks->pfnOnHeaderContextMenu)
			pPickerInfo->pCallbacks->pfnOnHeaderContextMenu(pt, nColumn);
	}
	else
	{
		// The body was clicked
		if (pPickerInfo->pCallbacks->pfnOnBodyContextMenu)
			pPickerInfo->pCallbacks->pfnOnBodyContextMenu(pt);
	}
	return true;
}



static void Picker_Free(struct PickerInfo *pPickerInfo)
{
	// Free up all resources associated with this picker structure
	if (pPickerInfo->pnColumnsShown)
		free(pPickerInfo->pnColumnsShown);
	if (pPickerInfo->pnColumnsOrder)
		free(pPickerInfo->pnColumnsOrder);
	free(pPickerInfo);
}



static LRESULT CALLBACK ListViewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	struct PickerInfo *pPickerInfo;
	LRESULT rc = 0;
	BOOL bHandled = false;
	WNDPROC pfnParentWndProc;
	HWND hwndHeaderCtrl  = NULL;
	HFONT hHeaderCtrlFont = NULL;

	pPickerInfo = GetPickerInfo(hWnd);
	pfnParentWndProc = pPickerInfo->pfnParentWndProc;

	switch(message)
	{
		case WM_MOUSEMOVE:
			if (MouseHasBeenMoved())
				ShowCursor(true);
			break;

		case WM_ERASEBKGND:
			if (GetBackgroundBitmap())
			{
				rc = ListViewOnErase(hWnd, (HDC) wParam);
				bHandled = true;
			}
			break;

		case WM_NOTIFY:
			bHandled = ListViewNotify(hWnd, (LPNMHDR) lParam);
			break;

		case WM_SETFONT:
			hwndHeaderCtrl = ListView_GetHeader(hWnd);
			if (hwndHeaderCtrl)
				hHeaderCtrlFont = GetWindowFont(hwndHeaderCtrl);
			break;

		case WM_CONTEXTMENU:
			bHandled = ListViewContextMenu(hWnd, lParam);
			break;

		case WM_DESTROY:
			// Received WM_DESTROY; time to clean up
			if (pPickerInfo->pCallbacks->pfnSetViewMode)
				pPickerInfo->pCallbacks->pfnSetViewMode(pPickerInfo->nCurrentViewID);
			Picker_Free(pPickerInfo);
			SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR) pfnParentWndProc);
			SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) NULL);
			break;
	}

	if (!bHandled)
		rc = CallParentWndProc(pfnParentWndProc, hWnd, message, wParam, lParam);

	 // If we received WM_SETFONT, reset header ctrl font back to original font
	if (hwndHeaderCtrl)
		SetWindowFont(hwndHeaderCtrl, hHeaderCtrlFont, true);

	return rc;
}



// Re/initialize the ListControl Columns
static void Picker_InternalResetColumnDisplay(HWND hWnd, BOOL bFirstTime)
{
	LV_COLUMN   lvc;
	int         i = 0;
	int         nColumn = 0;
	//int shown_columns;
	LVCOLUMN col;
	struct PickerInfo *pPickerInfo;
	HRESULT res = 0;
	BOOL b_res = 0;

	pPickerInfo = GetPickerInfo(hWnd);

	int *widths, *order, *shown;
	widths = (int*)malloc(pPickerInfo->nColumnCount * sizeof(*widths));
	order = (int*)malloc(pPickerInfo->nColumnCount * sizeof(*order));
	shown = (int*)malloc(pPickerInfo->nColumnCount * sizeof(*shown));
	if (!widths || !order || !shown)
		goto done;

	memset(widths, 0, pPickerInfo->nColumnCount * sizeof(*widths));
	memset(order, 0, pPickerInfo->nColumnCount * sizeof(*order));
	memset(shown, 0, pPickerInfo->nColumnCount * sizeof(*shown));
	pPickerInfo->pCallbacks->pfnGetColumnWidths(widths);
	pPickerInfo->pCallbacks->pfnGetColumnOrder(order);
	pPickerInfo->pCallbacks->pfnGetColumnShown(shown);

	if (!bFirstTime)
	{
		DWORD style = GetWindowLong(hWnd, GWL_STYLE);

		// switch the list view to LVS_REPORT style so column widths reported correctly
		SetWindowLong(hWnd, GWL_STYLE, (GetWindowLong(hWnd, GWL_STYLE) & ~LVS_TYPEMASK) | LVS_REPORT);

		// Retrieve each of the column widths
		i = 0;
		memset(&col, 0, sizeof(col));
		col.mask = LVCF_WIDTH;
		while(ListView_GetColumn(hWnd, 0, &col))
		{
			nColumn = Picker_GetRealColumnFromViewColumn(hWnd, i++);
			widths[nColumn] = col.cx;
			b_res = ListView_DeleteColumn(hWnd, 0);
		}

		pPickerInfo->pCallbacks->pfnSetColumnWidths(widths);

		// restore old style
		SetWindowLong(hWnd, GWL_STYLE, style);
	}

	nColumn = 0;
	for (i = 0; i < pPickerInfo->nColumnCount; i++)
	{
		if (shown[order[i]])
		{
			lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT;
			lvc.pszText = (LPTSTR) pPickerInfo->ppszColumnNames[order[i]];
			lvc.iSubItem = nColumn;
			lvc.cx = widths[order[i]];
			lvc.fmt = LVCFMT_LEFT;
			if (lvc.pszText[0] > 0) // column name cannot be blank
			{
				res = ListView_InsertColumn(hWnd, nColumn, &lvc);
				pPickerInfo->pnColumnsOrder[nColumn] = order[i];
				//printf("Visible column %d: Logical column %d; Width=%d\n", nColumn, order[i], widths[order[i]]);
				nColumn++;
			}
		}
	}

	//shown_columns = nColumn;

	/* Fill this in so we can still sort on columns NOT shown */
	for (i = 0; i < pPickerInfo->nColumnCount && nColumn < pPickerInfo->nColumnCount; i++)
	{
		if (!shown[order[i]])
		{
			pPickerInfo->pnColumnsOrder[nColumn] = order[i];
			nColumn++;
		}
	}

	if (GetListFontColor() == RGB(255, 255, 255))
		b_res = ListView_SetTextColor(hWnd, RGB(240, 240, 240));
	else
		b_res = ListView_SetTextColor(hWnd, GetListFontColor());

done:
	if (widths)
		free(widths);
	if (order)
		free(order);
	if (shown)
		free(shown);
	res++;
	b_res++;
}



void Picker_ResetColumnDisplay(HWND hWnd)
{
	Picker_InternalResetColumnDisplay(hWnd, false);
}



void Picker_ClearIdle(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;

	pPickerInfo = GetPickerInfo(hwndPicker);
	if (pPickerInfo->nTimer)
	{
		KillTimer(hwndPicker, 0);
		pPickerInfo->nTimer = 0;
	}
}



static void CALLBACK Picker_TimerProc(HWND hwndPicker, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	struct PickerInfo *pPickerInfo;
	DWORD nTickCount = 0;
	DWORD nMaxIdleTicks = 50;

	pPickerInfo = GetPickerInfo(hwndPicker);
	BOOL bContinueIdle = false;
	DWORD nBaseTickCount = GetTickCount();

	// This idle procedure will loop until either idling is over, or until
	// a specified amount of time elapses (in this case, 50ms).  This frees
	// idle callbacks of any responsibility for balancing their workloads; the
	// picker code will
	do
	{
		if (pPickerInfo->pCallbacks->pfnOnIdle)
			bContinueIdle = pPickerInfo->pCallbacks->pfnOnIdle(hwndPicker);
		nTickCount = GetTickCount();
	}
	while(bContinueIdle && ((nTickCount - nBaseTickCount) < nMaxIdleTicks));

	if (!bContinueIdle)
		Picker_ClearIdle(hwndPicker);
}



// Instructs this picker to reset idling; idling will continue until the
// idle function returns false
void Picker_ResetIdle(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;

	pPickerInfo = GetPickerInfo(hwndPicker);

	Picker_ClearIdle(hwndPicker);
	if (pPickerInfo->pCallbacks->pfnOnIdle)
		pPickerInfo->nTimer = SetTimer(hwndPicker, 0, 0, Picker_TimerProc);
}



BOOL Picker_IsIdling(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hwndPicker);
	return pPickerInfo->nTimer != 0;
}



BOOL SetupPicker(HWND hwndPicker, const struct PickerOptions *pOptions)
{
	struct PickerInfo *pPickerInfo;
	int i = 0;
	LONG_PTR l = 0;
	HRESULT res = 0;

	//assert(hwndPicker);

	// Allocate the list view struct
	pPickerInfo = (struct PickerInfo *) malloc(sizeof(struct PickerInfo));
	if (!pPickerInfo)
		return false;

	// And fill it out
	memset(pPickerInfo, 0, sizeof(*pPickerInfo));
	pPickerInfo->pCallbacks = pOptions->pCallbacks;
	pPickerInfo->nColumnCount = pOptions->nColumnCount;
	pPickerInfo->ppszColumnNames = pOptions->ppszColumnNames;
	pPickerInfo->nLastItem = -1;

	if (pPickerInfo->nColumnCount)
	{
		// Allocate space for the column order and columns shown array
		pPickerInfo->pnColumnsOrder = (int*)malloc(pPickerInfo->nColumnCount *
			sizeof(*pPickerInfo->pnColumnsOrder));
		pPickerInfo->pnColumnsShown = (int*)malloc(pPickerInfo->nColumnCount *
			sizeof(*pPickerInfo->pnColumnsShown));
		if (!pPickerInfo->pnColumnsOrder || !pPickerInfo->pnColumnsShown)
			goto error;

		// set up initial values
		for (i = 0; i < pPickerInfo->nColumnCount; i++)
		{
			pPickerInfo->pnColumnsOrder[i] = i;
			pPickerInfo->pnColumnsShown[i] = true;
		}

		if (pPickerInfo->pCallbacks->pfnGetColumnOrder)
			pPickerInfo->pCallbacks->pfnGetColumnOrder(pPickerInfo->pnColumnsOrder);
		if (pPickerInfo->pCallbacks->pfnGetColumnShown)
			pPickerInfo->pCallbacks->pfnGetColumnShown(pPickerInfo->pnColumnsShown);
	}

	// Hook in our wndproc and userdata pointer
	l = GetWindowLongPtr(hwndPicker, GWLP_WNDPROC);
	pPickerInfo->pfnParentWndProc = (WNDPROC) l;
	SetWindowLongPtr(hwndPicker, GWLP_USERDATA, (LONG_PTR) pPickerInfo);
	SetWindowLongPtr(hwndPicker, GWLP_WNDPROC, (LONG_PTR) ListViewWndProc);

	res = ListView_SetExtendedListViewStyle(hwndPicker, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP |
		LVS_EX_UNDERLINEHOT | LVS_EX_UNDERLINECOLD | LVS_EX_LABELTIP);

	Picker_InternalResetColumnDisplay(hwndPicker, true);
	Picker_ResetIdle(hwndPicker);
	res++;
	return true;

error:
	if (pPickerInfo)
		Picker_Free(pPickerInfo);
	return false;
}



int Picker_GetViewID(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hwndPicker);
	return pPickerInfo->nCurrentViewID;
}



void Picker_SetViewID(HWND hwndPicker, int nViewID)
{
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hwndPicker);

	// Change the nCurrentViewID member
	pPickerInfo->nCurrentViewID = nViewID;
	if (pPickerInfo->pCallbacks->pfnSetViewMode)
		pPickerInfo->pCallbacks->pfnSetViewMode(pPickerInfo->nCurrentViewID);

	// Change the ListView flags in accordance
	LONG_PTR nListViewStyle;
	switch(nViewID)
	{
		case VIEW_LARGE_ICONS:
			nListViewStyle = LVS_ICON;
			break;
		case VIEW_SMALL_ICONS:
			nListViewStyle = LVS_SMALLICON;
			break;
		case VIEW_INLIST:
			nListViewStyle = LVS_LIST;
			break;
		case VIEW_GROUPED:
		case VIEW_REPORT:
		default:
			nListViewStyle = LVS_REPORT;
			break;
	}

	DWORD dwStyle = GetWindowLong(hwndPicker, GWL_STYLE);
	if (GetUseXPControl())
	{
		// RS Microsoft must have changed something in the Ownerdraw handling with Version 6 of the Common Controls
		// as on all other OSes it works without this...
		if (nViewID == VIEW_LARGE_ICONS || nViewID == VIEW_SMALL_ICONS)
		{
			// remove Ownerdraw style for Icon views
			dwStyle &= ~LVS_OWNERDRAWFIXED;
			if( nViewID == VIEW_SMALL_ICONS )
			{
				// to properly get them to arrange, otherwise the entries might overlap
				// we have to call SetWindowLong to get it into effect !!
				// It's no use just setting the Style, as it's changed again further down...
				SetWindowLong(hwndPicker, GWL_STYLE, (GetWindowLong(hwndPicker, GWL_STYLE) & ~LVS_TYPEMASK) | LVS_ICON);
			}
		}
		else
		{
			// add again..
			dwStyle |= LVS_OWNERDRAWFIXED;
		}
	}

	dwStyle &= ~LVS_TYPEMASK;
	dwStyle |= nListViewStyle;
	SetWindowLong(hwndPicker, GWL_STYLE, dwStyle);
	RedrawWindow(hwndPicker, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME);
}


static BOOL PickerHitTest(HWND hWnd)
{
	LVHITTESTINFO htInfo;
	memset(&htInfo, 0, sizeof(htInfo));
	RECT rect;
	GetWindowRect(hWnd, &rect);
	DWORD res = GetMessagePos();
	POINTS p = MAKEPOINTS(res);
	htInfo.pt.x = p.x - rect.left;
	htInfo.pt.y = p.y - rect.top;
	HRESULT result = ListView_HitTest(hWnd, &htInfo);
	result++;
	return (! (htInfo.flags & LVHT_NOWHERE));
}


int Picker_GetSelectedItem(HWND hWnd)
{
	int nItem = ListView_GetNextItem(hWnd, -1, LVIS_SELECTED | LVIS_FOCUSED);
	if (nItem < 0)
		return nItem;

	LV_ITEM lvi;
	memset(&lvi, 0, sizeof(lvi));
	lvi.iItem = nItem;
	lvi.mask = LVIF_PARAM;
	BOOL res = ListView_GetItem(hWnd, &lvi);
	res++;
	return lvi.lParam;
}


// This highlights a selected game, and scrolls it into view
void Picker_SetSelectedPick(HWND hWnd, uint32_t nIndex)
{
	// nCount is one more than number of last game
	int nCount = ListView_GetItemCount(hWnd);
	// No games to show
	if (nCount < 1)
		return;
	nCount--;
	if (nCount < nIndex)
		nIndex = nCount;
	// Highlight a game
	ListView_SetItemState(hWnd, nIndex, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
	// Bring the game into view
	(void)ListView_EnsureVisible(hWnd, nIndex, false);
}



void Picker_SetSelectedItem(HWND hWnd, uint32_t nItem)
{
	LV_FINDINFO lvfi;
	lvfi.flags = LVFI_PARAM;
	lvfi.lParam = nItem;
	int i = ListView_FindItem(hWnd, -1, &lvfi);
	if (i == -1)
	{
		POINT p = {0,0};
		lvfi.flags = LVFI_NEARESTXY;
		lvfi.pt = p;
		i = ListView_FindItem(hWnd, -1, &lvfi);
	}
	Picker_SetSelectedPick(hWnd, (i == -1) ? 0 : i);
}



static const TCHAR *Picker_CallGetItemString(HWND hwndPicker, int nItem, int nColumn, TCHAR *pszBuffer, UINT nBufferLength)
{
	// this call wraps the pfnGetItemString callback to properly set up the
	// buffers, and normalize the results
	struct PickerInfo *pPickerInfo;
	const TCHAR *s;

	pPickerInfo = GetPickerInfo(hwndPicker);
	pszBuffer[0] = '\0';
	s = NULL;

	if (pPickerInfo->pCallbacks->pfnGetItemString)
		s = pPickerInfo->pCallbacks->pfnGetItemString(hwndPicker, nItem, nColumn, pszBuffer, nBufferLength);

	if (!s)
		s = pszBuffer;
	return s;
}



// put the arrow on the new sort column
static void Picker_ResetHeaderSortIcon(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hwndPicker);
	HWND hwndHeader = ListView_GetHeader(hwndPicker);
	BOOL res;

	// take arrow off non-current columns
	HD_ITEM hdi;
	hdi.mask = HDI_FORMAT;
	hdi.fmt = HDF_STRING;
	for (int i = 0; i < pPickerInfo->nColumnCount; i++)
		if (i != pPickerInfo->pCallbacks->pfnGetSortColumn())
			res = Header_SetItem(hwndHeader, Picker_GetViewColumnFromRealColumn(hwndPicker, i), &hdi);

	if (GetUseXPControl())
	{
		// use built in sort arrows
		hdi.mask = HDI_FORMAT;
		hdi.fmt = HDF_STRING | (pPickerInfo->pCallbacks->pfnGetSortReverse() ? HDF_SORTDOWN : HDF_SORTUP);
	}
	else
	{
		// put our arrow icon next to the text
		hdi.mask = HDI_FORMAT | HDI_IMAGE;
		hdi.fmt = HDF_STRING | HDF_IMAGE | HDF_BITMAP_ON_RIGHT;
		hdi.iImage = pPickerInfo->pCallbacks->pfnGetSortReverse() ? 1 : 0;
	}

	int nViewColumn = Picker_GetViewColumnFromRealColumn(hwndPicker, pPickerInfo->pCallbacks->pfnGetSortColumn());
	res = Header_SetItem(hwndHeader, nViewColumn, &hdi);
	res++;
}



struct CompareProcParams
{
	HWND hwndPicker;
	struct PickerInfo *pPickerInfo;
	int nSortColumn;
	int nViewMode;
	BOOL bReverse;
};



static void Picker_PopulateCompareProcParams(HWND hwndPicker, struct CompareProcParams *pParams)
{
	struct PickerInfo *pPickerInfo;

	pPickerInfo = GetPickerInfo(hwndPicker);

	// populate the CompareProcParams structure
	memset(pParams, 0, sizeof(*pParams));
	pParams->hwndPicker = hwndPicker;
	pParams->pPickerInfo = pPickerInfo;
	pParams->nViewMode = pPickerInfo->pCallbacks->pfnGetViewMode();
	if (pPickerInfo->pCallbacks->pfnGetSortColumn)
		pParams->nSortColumn = pPickerInfo->pCallbacks->pfnGetSortColumn();
	if (pPickerInfo->pCallbacks->pfnGetSortReverse)
		pParams->bReverse = pPickerInfo->pCallbacks->pfnGetSortReverse();
}



static int CALLBACK Picker_CompareProc(LPARAM index1, LPARAM index2, LPARAM nParamSort)
{
	struct CompareProcParams *pcpp = (struct CompareProcParams *) nParamSort;
	struct PickerInfo *pPickerInfo = pcpp->pPickerInfo;
	BOOL bCallCompare = true;
	int nResult = 0, nParent1 = 0, nParent2 = 0;
	TCHAR szBuffer1[256], szBuffer2[256];
	const TCHAR *s1, *s2;

	if (pcpp->nViewMode == VIEW_GROUPED)
	{
		// do our fancy compare, with clones grouped under parents
		// first thing we need to do is identify both item's parents
		if (pPickerInfo->pCallbacks->pfnFindItemParent)
		{
			nParent1 = pPickerInfo->pCallbacks->pfnFindItemParent(pcpp->hwndPicker, index1);
			nParent2 = pPickerInfo->pCallbacks->pfnFindItemParent(pcpp->hwndPicker, index2);
		}
		else
		{
			nParent1 = nParent2 = -1;
		}

		if ((nParent1 < 0) && (nParent2 < 0))
		{
			// if both entries are both parents, we just do a basic sort
		}
		else if ((nParent1 >= 0) && (nParent2 >= 0))
		{
			// if both entries are children and the parents are different,
			// sort on their parents
			if (nParent1 != nParent2)
			{
				index1 = nParent1;
				index2 = nParent2;
			}
		}
		else
		{
			// one parent, one child
			if (nParent1 >= 0)
			{
				// first one is a child
				if (nParent1 == index2)
				{
					// if this is a child and its parent, put child after
					nResult = 1;
					bCallCompare = false;
				}
				else
				{
					// sort on parent
					index1 = nParent1;
				}
			}
			else
			{
				// second one is a child
				if (nParent2 == index1)
				{
					// if this is a child and its parent, put child after
					nResult = -1;
					bCallCompare = false;
				}
				else
				{
					// sort on parent
					index2 = nParent2;
				}
			}
		}
	}

	if (bCallCompare)
	{
		if (pPickerInfo->pCallbacks->pfnCompare)
		{
			nResult = pPickerInfo->pCallbacks->pfnCompare(pcpp->hwndPicker,
				index1, index2, pcpp->nSortColumn);
		}
		else
		{
			// no default sort proc, just get the string and compare them
			s1 = Picker_CallGetItemString(pcpp->hwndPicker, index1, pcpp->nSortColumn,
				szBuffer1, sizeof(szBuffer1) / sizeof(szBuffer1[0]));
			s2 = Picker_CallGetItemString(pcpp->hwndPicker, index2, pcpp->nSortColumn,
				szBuffer2, sizeof(szBuffer2) / sizeof(szBuffer2[0]));
			nResult = _tcsicmp(s1, s2);
		}

		if (pcpp->bReverse)
			nResult = -nResult;
	}
	return nResult;
}



void Picker_Sort(HWND hwndPicker)
{
	//struct PickerInfo *pPickerInfo;
	struct CompareProcParams params;

	//pPickerInfo = GetPickerInfo(hwndPicker);

	// populate the CompareProcParams structure
	Picker_PopulateCompareProcParams(hwndPicker, &params);

	ListView_SortItems(hwndPicker, Picker_CompareProc, (LPARAM) &params);

	Picker_ResetHeaderSortIcon(hwndPicker);

	LV_FINDINFO lvfi;
	memset(&lvfi, 0, sizeof(lvfi));
	lvfi.flags = LVFI_PARAM;
	lvfi.lParam = Picker_GetSelectedItem(hwndPicker);
	int nItem = ListView_FindItem(hwndPicker, -1, &lvfi);

	ListView_EnsureVisible(hwndPicker, nItem, false);
}



int Picker_InsertItemSorted(HWND hwndPicker, int nParam)
{
	//struct PickerInfo *pPickerInfo;
	int nLow = 0, nMid = 0;
	struct CompareProcParams params;
	int nCompareResult = 0;
	LVITEM lvi;
	//pPickerInfo = GetPickerInfo(hwndPicker);

	int nHigh = ListView_GetItemCount(hwndPicker);

	// populate the CompareProcParams structure
	Picker_PopulateCompareProcParams(hwndPicker, &params);

	while(nLow < nHigh)
	{
		nMid = (nHigh + nLow) / 2;

		memset(&lvi, 0, sizeof(lvi));
		lvi.mask = LVIF_PARAM;
		lvi.iItem = nMid;
		ListView_GetItem(hwndPicker, &lvi);
		nCompareResult = Picker_CompareProc(nParam, lvi.lParam, (LPARAM) &params);

		if (nCompareResult > 0)
			nLow  = nMid + 1;
		else if (nCompareResult < 0)
			nHigh = nMid;
		else
		{
			nLow = nMid;
			break;
		}
	}

	memset(&lvi, 0, sizeof(lvi));
	lvi.mask     = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM;
	lvi.iItem    = nLow;
	lvi.iSubItem = 0;
	lvi.lParam   = nParam;
	lvi.pszText  = LPSTR_TEXTCALLBACK;
	lvi.iImage   = I_IMAGECALLBACK;

	return ListView_InsertItem(hwndPicker, &lvi);
}



int Picker_GetRealColumnFromViewColumn(HWND hWnd, int nViewColumn)
{
	struct PickerInfo *pPickerInfo;
	int nRealColumn = 0;

	pPickerInfo = GetPickerInfo(hWnd);
	if (nViewColumn >= 0 && nViewColumn < pPickerInfo->nColumnCount)
		nRealColumn = pPickerInfo->pnColumnsOrder[nViewColumn];
	return nRealColumn;
}



int Picker_GetViewColumnFromRealColumn(HWND hWnd, int nRealColumn)
{
	struct PickerInfo *pPickerInfo;

	pPickerInfo = GetPickerInfo(hWnd);
	for (int i = 0; i < pPickerInfo->nColumnCount; i++)
		if (pPickerInfo->pnColumnsOrder[i] == nRealColumn)
			return i;

	// major error, shouldn't be possible, but no good way to warn
	return 0;
}



BOOL Picker_HandleNotify(LPNMHDR lpNmHdr)
{
	struct PickerInfo *pPickerInfo;
	HWND hWnd;
	BOOL bResult = false;
	NM_LISTVIEW *pnmv;
	LV_DISPINFO *pDispInfo;
	int nItem = 0, nColumn = 0;
	const TCHAR *s;
	BOOL bReverse = 0;

	hWnd = lpNmHdr->hwndFrom;
	pPickerInfo = GetPickerInfo(hWnd);
	pnmv = (NM_LISTVIEW *) lpNmHdr;

	switch(lpNmHdr->code)
	{
		case NM_RCLICK:
		case NM_CLICK:
		case NM_DBLCLK:
			// don't allow selection of blank spaces in the listview
			if (!PickerHitTest(hWnd))
			{
				// we have no current item selected
				if (pPickerInfo->nLastItem != -1)
				{
					Picker_SetSelectedItem(hWnd, pPickerInfo->nLastItem);
				}
				bResult = true;
			}
			else if ((lpNmHdr->code == NM_DBLCLK) && (pPickerInfo->pCallbacks->pfnDoubleClick))
			{
				// double click!
				pPickerInfo->pCallbacks->pfnDoubleClick();
				bResult = true;
			}
			break;

		case LVN_GETDISPINFO:
			pDispInfo = (LV_DISPINFO *) lpNmHdr;
			nItem = (int) pDispInfo->item.lParam;

			if (pDispInfo->item.mask & LVIF_IMAGE)
			{
				// retrieve item image
				if (pPickerInfo->pCallbacks->pfnGetItemImage)
					pDispInfo->item.iImage = pPickerInfo->pCallbacks->pfnGetItemImage(hWnd, nItem);
				else
					pDispInfo->item.iImage = 0;
				bResult = true;
			}

			if (pDispInfo->item.mask & LVIF_STATE)
			{
				pDispInfo->item.state = 0;
				bResult = true;
			}

			if (pDispInfo->item.mask & LVIF_TEXT)
			{
				// retrieve item text
				nColumn = Picker_GetRealColumnFromViewColumn(hWnd, pDispInfo->item.iSubItem);

				s = Picker_CallGetItemString(hWnd, nItem, nColumn,
					pDispInfo->item.pszText, pDispInfo->item.cchTextMax);

				pDispInfo->item.pszText = (TCHAR *) s;
				bResult = true;
			}
			break;

		case LVN_ITEMCHANGED:
			if ((pnmv->uOldState & LVIS_SELECTED)
				&& !(pnmv->uNewState & LVIS_SELECTED))
			{
				if (pnmv->lParam != -1)
					pPickerInfo->nLastItem = pnmv->lParam;
				if (pPickerInfo->pCallbacks->pfnLeavingItem)
					pPickerInfo->pCallbacks->pfnLeavingItem(hWnd, pnmv->lParam);
			}
			if (!(pnmv->uOldState & LVIS_SELECTED)
				&& (pnmv->uNewState & LVIS_SELECTED))
			{
				if (pPickerInfo->pCallbacks->pfnEnteringItem)
					pPickerInfo->pCallbacks->pfnEnteringItem(hWnd, pnmv->lParam);
			}
			bResult = true;
			break;

		case LVN_COLUMNCLICK:
			// if clicked on the same column we're sorting by, reverse it
			if (pPickerInfo->pCallbacks->pfnGetSortColumn() == Picker_GetRealColumnFromViewColumn(hWnd, pnmv->iSubItem))
				bReverse = !pPickerInfo->pCallbacks->pfnGetSortReverse();
			else
				bReverse = false;
			pPickerInfo->pCallbacks->pfnSetSortReverse(bReverse);
			pPickerInfo->pCallbacks->pfnSetSortColumn(Picker_GetRealColumnFromViewColumn(hWnd, pnmv->iSubItem));
			Picker_Sort(hWnd);
			bResult = true;
			break;

		case LVN_BEGINDRAG:
			if (pPickerInfo->pCallbacks->pfnBeginListViewDrag)
				pPickerInfo->pCallbacks->pfnBeginListViewDrag(pnmv);
			break;
	}
	return bResult;
}



int Picker_GetNumColumns(HWND hWnd)
{
	int  nColumnCount = 0;
	int  *shown;
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hWnd);

	shown = (int*)malloc(pPickerInfo->nColumnCount * sizeof(*shown));
	if (!shown)
		return -1;

	pPickerInfo->pCallbacks->pfnGetColumnShown(shown);
	HWND hwndHeader = ListView_GetHeader(hWnd);

	if ((nColumnCount = Header_GetItemCount(hwndHeader)) < 1)
	{
		nColumnCount = 0;
		for (int i = 0; i < pPickerInfo->nColumnCount ; i++ )
			if (shown[i])
				nColumnCount++;
	}

	free(shown);
	return nColumnCount;
}



/* Add ... to Items in ListView if needed */
static LPCTSTR MakeShortString(HDC hDC, LPCTSTR lpszLong, int nColumnLen, int nOffset)
{
	static const TCHAR szThreeDots[] = TEXT("...");
	static TCHAR szShort[MAX_PATH];
	int nStringLen = lstrlen(lpszLong);
	SIZE size;

	GetTextExtentPoint32(hDC, lpszLong, nStringLen, &size);
	if (nStringLen == 0 || size.cx + nOffset <= nColumnLen)
		return lpszLong;

	lstrcpy(szShort, lpszLong);
	GetTextExtentPoint32(hDC, szThreeDots, std::size(szThreeDots), &size);
	int nAddLen = size.cx;

	for (int i = nStringLen - 1; i > 0; i--)
	{
		szShort[i] = 0;
		GetTextExtentPoint32(hDC, szShort, i, &size);
		if (size.cx + nOffset + nAddLen <= nColumnLen)
			break;
	}

	lstrcat(szShort, szThreeDots);

	return szShort;
}

#define ListView_GetItemRect_Modified(hwnd,i,prc,code) (BOOL)SNDMSG((hwnd),LVM_GETITEMRECT,(WPARAM)(int)(i),(((RECT *)(prc))->left = (code),(LPARAM)(RECT *)(prc)))

void Picker_HandleDrawItem(HWND hWnd, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	struct       PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hWnd);
	int          *order;
	order = (int*)malloc(pPickerInfo->nColumnCount * sizeof(*order));
	if (!order)
		return;

	HDC          hDC = lpDrawItemStruct->hDC;
	RECT         rcItem = lpDrawItemStruct->rcItem;
	UINT         uiFlags = ILD_TRANSPARENT;
	HIMAGELIST   hImageList;
	int          nItem = lpDrawItemStruct->itemID;
	COLORREF     clrTextSave = 0;
	COLORREF     clrBkSave = 0;
	COLORREF     clrImage = GetSysColor(COLOR_WINDOW);
	static TCHAR szBuff[MAX_PATH];
	BOOL         bFocus = (GetFocus() == hWnd);
	LPCTSTR      pszText;
	UINT         nStateImageMask = 0;
	BOOL         bSelected = 0;
	LV_COLUMN    lvc;
	LV_ITEM      lvi;
	RECT         rcAllLabels;
	RECT         rcLabel;
	RECT         rcIcon;
	int          offset = 0;
	SIZE         size;
	int          i = 0, j = 0;
	int          nColumn = 0;
	BOOL         bDrawAsChild = 0;
	int          indent_space = 0;
	BOOL         bColorChild = false;
	BOOL         bParentFound = false;
	int          nParent = 0;
	HBITMAP      hBackground = GetBackgroundBitmap();
	MYBITMAPINFO *pbmDesc = GetBackgroundInfo();
	BOOL         res = 0;

	int nColumnMax = Picker_GetNumColumns(hWnd);

	/* Get the Column Order and save it */
	ListView_GetColumnOrderArray(hWnd, nColumnMax, order);

	/* Disallow moving column 0 */
	if (order[0] != 0)
	{
		for (i = 0; i < nColumnMax; i++)
		{
			if (order[i] == 0)
			{
				order[i] = order[0];
				order[0] = 0;
			}
		}
		ListView_SetColumnOrderArray(hWnd, nColumnMax, order);
	}

	/* Labels are offset by a certain amount */
	/* This offset is related to the width of a space character */
	GetTextExtentPoint32(hDC, TEXT(" "), 1, &size);
	offset = size.cx;

	lvi.mask       = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_PARAM;
	lvi.iItem      = nItem;
	lvi.iSubItem   = order[0];
	lvi.pszText    = szBuff;
	lvi.cchTextMax = sizeof(szBuff) / sizeof(szBuff[0]);
	lvi.stateMask  = 0xFFFF;   /* get all state flags */
	res = ListView_GetItem(hWnd, &lvi);

	bSelected = ((lvi.state & LVIS_DROPHILITED) || ( (lvi.state & LVIS_SELECTED) && ((bFocus) || (GetWindowLong(hWnd, GWL_STYLE) & LVS_SHOWSELALWAYS))));

	/* figure out if we indent and draw grayed */
	if (pPickerInfo->pCallbacks->pfnFindItemParent)
		nParent = pPickerInfo->pCallbacks->pfnFindItemParent(hWnd, lvi.lParam);
	else
		nParent = -1;
	bDrawAsChild = (pPickerInfo->pCallbacks->pfnGetViewMode() == VIEW_GROUPED && (nParent >= 0));

	/* only indent if parent is also in this view */
#if 1	// minimal listview flickering.
	if ((nParent >= 0) && bDrawAsChild)
	{
		if (GetParentFound(lvi.lParam))
			bParentFound = TRUE;
	}
#else
	if ((nParent >= 0) && bDrawAsChild)
	{
		for (i = 0; i < ListView_GetItemCount(hWnd); i++)
		{
			lvi.mask = LVIF_PARAM;
			lvi.iItem = i;
			res = ListView_GetItem(hWnd, &lvi);

			if (lvi.lParam == nParent)
			{
				bParentFound = TRUE;
				break;
			}
		}
	}
#endif

	if (pPickerInfo->pCallbacks->pfnGetOffsetChildren && pPickerInfo->pCallbacks->pfnGetOffsetChildren())
	{
		if (!bParentFound && bDrawAsChild)
		{
			/*Reset it, as no Parent is there*/
			bDrawAsChild = false;
			bColorChild = true;
		}
		else
		{
			nParent = -1;
			bParentFound = false;
		}
	}

	res = ListView_GetItemRect_Modified(hWnd, nItem, &rcAllLabels, LVIR_BOUNDS);
	res = ListView_GetItemRect_Modified(hWnd, nItem, &rcLabel, LVIR_LABEL);

	rcAllLabels.left = rcLabel.left;

	if (hBackground)
	{
		RECT rcClient;
		HRGN rgnBitmap;
		RECT rcTmpBmp = rcItem;
		RECT rcFirstItem;
		HPALETTE hPAL;

		HDC htempDC = CreateCompatibleDC(hDC);

		HBITMAP oldBitmap = (HBITMAP)SelectObject(htempDC, hBackground);

		GetClientRect(hWnd, &rcClient);
		rcTmpBmp.right = rcClient.right;
		/* We also need to check whether it is the last item
           The update region has to be extended to the bottom if it is */
		if (nItem == ListView_GetItemCount(hWnd) - 1)
			rcTmpBmp.bottom = rcClient.bottom;

		rgnBitmap = CreateRectRgnIndirect(&rcTmpBmp);
		SelectClipRgn(hDC, rgnBitmap);
		DeleteBitmap(rgnBitmap);

		hPAL = GetBackgroundPalette();
		if (hPAL == NULL)
			hPAL = CreateHalftonePalette(hDC);

		if (GetDeviceCaps(htempDC, RASTERCAPS) & RC_PALETTE && hPAL != NULL)
		{
			SelectPalette(htempDC, hPAL, false);
			RealizePalette(htempDC);
		}

		res = ListView_GetItemRect_Modified(hWnd, 0, &rcFirstItem, LVIR_BOUNDS);

		for (i = rcFirstItem.left; i < rcClient.right; i += pbmDesc->bmWidth)
			for (j = rcFirstItem.top; j < rcClient.bottom; j +=  pbmDesc->bmHeight)
				BitBlt(hDC, i, j, pbmDesc->bmWidth, pbmDesc->bmHeight, htempDC, 0, 0, SRCCOPY);

		SelectObject(htempDC, oldBitmap);
		DeleteDC(htempDC);

		if (GetBackgroundPalette() == NULL)
		{
			DeletePalette(hPAL);
			hPAL = NULL;
		}
	}

	indent_space = 0;

	if (bDrawAsChild)
	{
		RECT rect;

		res = ListView_GetItemRect_Modified(hWnd, nItem, &rect, LVIR_ICON);

		/* indent width of icon + the space between the icon and text
         * so left of clone icon starts at text of parent
         */
		indent_space = rect.right - rect.left + offset;
	}

	rcAllLabels.left += indent_space;

	if (bSelected)
	{
		HBRUSH hBrush;
		HBRUSH hOldBrush;

		if (bFocus)
		{
			clrTextSave = SetTextColor(hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
			clrBkSave = SetBkColor(hDC, GetSysColor(COLOR_HIGHLIGHT));
			hBrush = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
		}
		else
		{
			clrTextSave = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
			clrBkSave = SetBkColor(hDC, GetSysColor(COLOR_BTNFACE));
			hBrush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
		}

		hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
		FillRect(hDC, &rcAllLabels, hBrush);
		SelectObject(hDC, hOldBrush);
		DeleteBrush(hBrush);
	}
	else
	{
		if (hBackground == NULL)
		{
			HBRUSH hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
			FillRect(hDC, &rcAllLabels, hBrush);
			DeleteBrush(hBrush);
		}

		if (pPickerInfo->pCallbacks->pfnGetOffsetChildren && pPickerInfo->pCallbacks->pfnGetOffsetChildren())
		{
			if (bDrawAsChild || bColorChild)
				clrTextSave = SetTextColor(hDC, GetListCloneColor());
			else
				clrTextSave = SetTextColor(hDC, GetListFontColor());
		}
		else
		{
			if (bDrawAsChild)
				clrTextSave = SetTextColor(hDC, GetListCloneColor());
			else
				clrTextSave = SetTextColor(hDC, GetListFontColor());
		}

		clrBkSave = SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
	}


	if (lvi.state & LVIS_CUT)
	{
		clrImage = GetSysColor(COLOR_WINDOW);
		uiFlags |= ILD_BLEND50;
	}
	else
	if (bSelected)
	{
		if (bFocus)
			clrImage = GetSysColor(COLOR_HIGHLIGHT);
		else
			clrImage = GetSysColor(COLOR_BTNFACE);

		uiFlags |= ILD_BLEND50;
	}

	nStateImageMask = lvi.state & LVIS_STATEIMAGEMASK;

	if (nStateImageMask)
	{
		int nImage = (nStateImageMask >> 12) - 1;
		hImageList = ListView_GetImageList(hWnd, LVSIL_STATE);
		if (hImageList)
			ImageList_Draw(hImageList, nImage, hDC, rcItem.left, rcItem.top, ILD_TRANSPARENT);
	}

	res = ListView_GetItemRect_Modified(hWnd, nItem, &rcIcon, LVIR_ICON);

	rcIcon.left += indent_space;

	res = ListView_GetItemRect_Modified(hWnd, nItem, &rcItem, LVIR_LABEL);

	hImageList = ListView_GetImageList(hWnd, LVSIL_SMALL);
	if (hImageList)
	{
		UINT nOvlImageMask = lvi.state & LVIS_OVERLAYMASK;
		if (rcIcon.left + 16 + indent_space < rcItem.right)
			ImageList_DrawEx(hImageList, lvi.iImage, hDC, rcIcon.left, rcIcon.top, 16, 16, GetSysColor(COLOR_WINDOW), clrImage, uiFlags | nOvlImageMask);
	}

	res = ListView_GetItemRect_Modified(hWnd, nItem, &rcItem, LVIR_LABEL);

	pszText = MakeShortString(hDC, szBuff, rcItem.right - rcItem.left, 2*offset + indent_space);

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

	DrawText(hDC, pszText, -1, &rcLabel, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER);

	for (nColumn = 1; nColumn < nColumnMax; nColumn++)
	{
		int nRetLen;
		UINT nJustify;
		LV_ITEM lvItem;

		lvc.mask = LVCF_FMT | LVCF_WIDTH;
		res = ListView_GetColumn(hWnd, order[nColumn], &lvc);

		lvItem.mask       = LVIF_TEXT;
		lvItem.iItem      = nItem;
		lvItem.iSubItem   = order[nColumn];
		lvItem.pszText    = szBuff;
		lvItem.cchTextMax = sizeof(szBuff) / sizeof(szBuff[0]);

		if (ListView_GetItem(hWnd, &lvItem) == false)
			continue;

		rcItem.left   = rcItem.right;
		rcItem.right += lvc.cx;

		nRetLen = _tcslen(szBuff);
		if (nRetLen == 0)
			continue;

		pszText = MakeShortString(hDC, szBuff, rcItem.right - rcItem.left, 2 * offset);

		nJustify = DT_LEFT;

		if (pszText == szBuff)
		{
			switch (lvc.fmt & LVCFMT_JUSTIFYMASK)
			{
			case LVCFMT_RIGHT:
				nJustify = DT_RIGHT;
				break;

			case LVCFMT_CENTER:
				nJustify = DT_CENTER;
				break;

			default:
				break;
			}
		}

		rcLabel = rcItem;
		rcLabel.left  += offset;
		rcLabel.right -= offset;
		DrawText(hDC, pszText, -1, &rcLabel, nJustify | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER);
	}

	if (lvi.state & LVIS_FOCUSED && bFocus)
		DrawFocusRect(hDC, &rcAllLabels);

	SetTextColor(hDC, clrTextSave);
	SetBkColor(hDC, clrBkSave);
	free(order);
	res++;
}




const struct PickerCallbacks *Picker_GetCallbacks(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hwndPicker);
	return pPickerInfo->pCallbacks;
}



int Picker_GetColumnCount(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hwndPicker);
	return pPickerInfo->nColumnCount;
}



const LPCTSTR *Picker_GetColumnNames(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;
	pPickerInfo = GetPickerInfo(hwndPicker);
	return pPickerInfo->ppszColumnNames;
}



void Picker_SetHeaderImageList(HWND hwndPicker, HIMAGELIST hHeaderImages)
{
	HWND hwndHeader;
	hwndHeader = ListView_GetHeader(hwndPicker);
	SendMessage(hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM) (void *) hHeaderImages);
}



BOOL Picker_SaveColumnWidths(HWND hwndPicker)
{
	struct PickerInfo *pPickerInfo;
	int nColumnMax = 0, i = 0;
	BOOL bSuccess = false;
	BOOL res = 0;

	pPickerInfo = GetPickerInfo(hwndPicker);

	/* allocate space for the column info */
	int *widths, *order, *tmpOrder;
	widths = (int*)malloc(pPickerInfo->nColumnCount * sizeof(*widths));
	order = (int*)malloc(pPickerInfo->nColumnCount * sizeof(*order));
	tmpOrder = (int*)malloc(pPickerInfo->nColumnCount * sizeof(*tmpOrder));
	if (!widths || !order || !tmpOrder)
		goto done;

	/* retrieve the values */
	memset(widths, 0, pPickerInfo->nColumnCount * sizeof(*widths));
	memset(order, 0, pPickerInfo->nColumnCount * sizeof(*order));
	pPickerInfo->pCallbacks->pfnGetColumnWidths(widths);
	pPickerInfo->pCallbacks->pfnGetColumnOrder(order);

	/* switch the list view to LVS_REPORT style so column widths reported correctly */
	SetWindowLong(hwndPicker, GWL_STYLE, (GetWindowLong(hwndPicker, GWL_STYLE) & ~LVS_TYPEMASK) | LVS_REPORT);

	nColumnMax = Picker_GetNumColumns(hwndPicker);

	/* Get the Column Order and save it */
	ListView_GetColumnOrderArray(hwndPicker, nColumnMax, tmpOrder);

	for (i = 0; i < nColumnMax; i++)
	{
		widths[Picker_GetRealColumnFromViewColumn(hwndPicker, i)] = ListView_GetColumnWidth(hwndPicker, i);
		order[i] = Picker_GetRealColumnFromViewColumn(hwndPicker, tmpOrder[i]);
	}

	pPickerInfo->pCallbacks->pfnSetColumnWidths(widths);
	pPickerInfo->pCallbacks->pfnSetColumnOrder(order);
	bSuccess = true;

done:
	if (widths)
		free(widths);
	if (order)
		free(order);
	if (tmpOrder)
		free(tmpOrder);
	res++;
	return bSuccess;
}
