/**********************************************************************

	Copyright (C) 2003-2004
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomoki Sekiyama <sekiyama@yahoo.co.jp>

	This program is free software; you can redistribute it
	and/or modify it under the terms of the GLOBALBASE
	Library General Public License (G-LGPL) as published by

	http://www.globalbase.org/

	This program is distributed in the hope that it will be
	useful, but WITHOUT ANY WARRANTY; without even the
	implied warranty of MERCHANTABILITY or FITNESS FOR A
	PARTICULAR PURPOSE.

**********************************************************************/


#include <stdio.h>
#include <stdlib.h>

#include "TreeListView.h"
#include "XPStyle.h"

extern "C" void er_panic(char*);

WNDPROC TreeListView::orgListViewProc;
WNDPROC TreeListView::orgEditProc;

TreeListView::TreeListView(RECT *r, HWND parent, int id,
						   int n, LPTSTR *titles, short *widths, char *t)
{
	user_data = NULL;
	acw_mode = false;
	doing_acw = false;

	HINSTANCE instance = (HINSTANCE)GetWindowLong(parent, GWL_HINSTANCE);
	hList = CreateWindowEx(0,
				WC_LISTVIEW, "",
				WS_CHILD | WS_VISIBLE | LVS_REPORT | 
				LVS_OWNERDRAWFIXED | LVS_NOSORTHEADER,
				r->left, r->top, r->right-r->left,r->bottom-r->top,
				parent,
				(HMENU)id,
				instance,
				NULL);
	SetWindowLong(hList, GWL_USERDATA, (DWORD)this);
	if ( hList == NULL )
		er_panic("TreeListView");
	orgListViewProc = (WNDPROC)SetWindowLong(hList, GWL_WNDPROC, (LONG)ListViewProc);
	hImage = ImageList_Create(16,16,ILC_COLOR8 | ILC_MASK,2,0);

	ImageList_AddIcon(hImage,LoadIcon(instance, MAKEINTRESOURCE(IDI_TREE_MINUS)));
	ImageList_AddIcon(hImage,LoadIcon(instance, MAKEINTRESOURCE(IDI_TREE_PLUS)));
	ListView_SetImageList(hList,hImage,LVSIL_SMALL);

    LVCOLUMN	lvcol;
	lvcol.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
	lvcol.fmt = LVCFMT_LEFT;
	types = new char[n];
	this->widths = new short[n];
	for ( int i = 0 ; i < n ; i++ ) {
		lvcol.cx = this->widths[i] = widths[i] + (i ? 0 : 48);
		lvcol.pszText = titles[i];
		lvcol.iSubItem = i;
		ListView_InsertColumn(hList, i, &lvcol);
		types[i] = t[i];
	}
	n_cols = n;

	edited_index = edited_col = -1;
	hEdit = CreateWindowEx(0,
				"Edit","",
				WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,
				10, 32, 50, 16,
				hList,
				NULL,
				instance,
				NULL);
	orgEditProc = (WNDPROC)SetWindowLong(hEdit, GWL_WNDPROC, (LONG)EditProc);
	SetWindowLong(hEdit, GWL_USERDATA, (DWORD)this);
	SendMessage(hEdit, WM_SETFONT, SendMessage(hList, WM_GETFONT, 0, 0), 1);

	COLORREF Color = ListView_GetTextBkColor(hList);
	if ( Color & 0xff000000 )
		Color = GetSysColor(COLOR_WINDOW);
	hBrushBg = CreateSolidBrush (Color);
	hBrushHi = CreateSolidBrush (GetSysColor(COLOR_HIGHLIGHT));

	timer_index = timer_col = -1;
}

TreeListView::~TreeListView()
{
	delete[] types;
	delete[] widths;

	StopEditTimer();
	DestroyWindow(hEdit);
	if ( hList )
		DestroyWindow(hList);
	DeleteObject(hBrushBg);
	DeleteObject(hBrushHi);
}


int CALLBACK
TreeListView::ListViewProc(HWND hWnd, unsigned message,
				  WPARAM wParam, LPARAM lParam)
{
	RECT			rcClient,rcClip;
	PAINTSTRUCT		ps;
	HDC				hdc;
	LVHITTESTINFO	hittest;
	LVITEM			item;
	RECT			rc;
	NMHEADER *		nmh;
	DWORD			fwKeys;
	int				index;
	short			x, y;
	TreeListView *	tlview = ((TreeListView*)GetWindowLong(hWnd,GWL_USERDATA));

	switch (message) {
	case WM_CTLCOLORSTATIC:
		item.mask = LVIF_STATE;
		item.iItem = tlview->FindItem(GetWindowLong((HWND)lParam, GWL_ID));
		item.iSubItem = 0;
		item.stateMask = LVIS_SELECTED;
		ListView_GetItem(hWnd, &item);
		if ( item.state & LVIS_SELECTED )
			return (BOOL)tlview->hBrushHi;
		return (BOOL)tlview->hBrushBg;
	case WM_PAINT:
		hdc = BeginPaint (hWnd, &ps);
		GetClientRect(hWnd,&rcClient);
		GetClipBox(hdc,&rcClip);
		rcClip.right=rcClient.right;
		tlview->MoveCheckBox();
		EndPaint (hWnd, &ps);
		InvalidateRect(hWnd,&rcClip,FALSE);
		TreeListViewNotif notif;
		notif.message = TLVN_REDRAW;
		notif.id = 0;
		notif.col = -1;
		tlview->notifFunc(tlview, &notif);
		break;
	case WM_NOTIFY:
		nmh = (NMHEADER*)lParam;
		if ( tlview->acw_mode && nmh->hdr.hwndFrom == ListView_GetHeader(hWnd) )
			if ( nmh->hdr.code == HDN_ITEMCHANGINGA || nmh->hdr.code == HDN_ITEMCHANGINGW )
				return tlview->AdjustColumnWidth(nmh->iItem, nmh->pitem);
		break;
	case WM_COMMAND:
		if ( lParam ) {
			HWND hBtn = (HWND)lParam;
			TreeListViewData data;
			data.id  = GetWindowLong(hBtn, GWL_ID);
			if ( data.id == 0 )
				break;
			data.col  = GetWindowLong(hBtn, GWL_USERDATA);
			data.type = 'b';
			tlview->dataFunc(tlview, &data, false);
			if ( data.editable ) {
				bool checked = 
					(SendMessage(hBtn, BM_GETCHECK, 0, 0) & BST_CHECKED) == BST_CHECKED;
				if ( (data.data.flag && !checked) || (!data.data.flag && checked) ) {
					data.data.flag = checked;
					tlview->dataFunc(tlview, &data, true);
				}
			}
		}
		break;
	case WM_KEYDOWN:
	case WM_KILLFOCUS:
		tlview->StopEditTimer();
		break;
	case WM_SIZE:
		if ( tlview->acw_mode )
			tlview->AdjustColumnWidth();
		break;
	case WM_LBUTTONDOWN:
	case WM_LBUTTONDBLCLK:
		tlview->StopEditTimer();

		fwKeys = wParam;
		hittest.pt.x = x = LOWORD(lParam);
		hittest.pt.y = y = HIWORD(lParam);
		index = ListView_SubItemHitTest(hWnd, &hittest);
		if ( index < 0 )
			break;
		ListView_GetItemRect(hWnd, index, &rc, LVIR_ICON);
		if ( rc.left+3 <= x && rc.left+12 > x && rc.top+3 <= y && rc.top+12 > y ) {
			item.iItem = index;
			item.iSubItem = 0;
			item.mask = LVIF_IMAGE;
			ListView_GetItem(hWnd, &item);
			if ( item.iImage == 0 )	{// plus icon
				tlview->ExpandItem(index);
				return 0;
			}
			else if ( item.iImage == 1 ) {// minus icon
				tlview->CollapseItem(index);
				return 0;
			}
		}
		if ( message == WM_LBUTTONDOWN && tlview->types[hittest.iSubItem] == 's' ) {
			item.mask = LVIF_PARAM | LVIF_STATE;
			item.iItem = index;
			item.iSubItem = 0;
			item.stateMask = LVIS_SELECTED;
			ListView_GetItem(hWnd, &item);
			if ( !(item.state & LVIS_SELECTED) )
				break;
			TreeListViewData data;
			data.id = item.lParam;
			data.col = hittest.iSubItem;
			data.type = 's';
			tlview->dataFunc(tlview, &data, false);
			if ( ! data.editable )
				break;
			tlview->StartEditTimer(index, hittest.iSubItem);
		}
		break;
	}

	return CallWindowProc(orgListViewProc, hWnd, message, wParam, lParam);
}

static HWND
CreateCheckBox(HWND parent, bool editable, bool state, int id, int col)
{
	HWND ret = CreateWindow("BUTTON", "",
		WS_CHILD | (editable ? BS_AUTOCHECKBOX : BS_CHECKBOX),
		0, 0, 0, 0, parent, (HMENU)id,
		(HINSTANCE)GetWindowLong(parent, GWL_HINSTANCE), NULL);
	SetWindowLong(ret, GWL_USERDATA, col);
	if ( state )
		SendMessage(ret, BM_SETCHECK, BST_CHECKED, 0);
	return ret;
}

void
TreeListView::Insert(int id, int next, int parent)
{
	FinishEdit();

	TreeListViewData data;
	LVITEM item;
	TreeListViewNodeInfo *info = new TreeListViewNodeInfo;
	int prev;
	int cnt = ListView_GetItemCount(hList);
	int indent = 0;

	// convert id to index
	if ( next )
		next = FindItem(next);
	else
		next = -1;

	if ( parent )
		parent = FindItem(parent);
	else
		parent = -1;

	// search where to insert
	if ( parent >= 0 ) {
		item.iItem = prev = parent;
		item.iSubItem = 0;
		item.mask = LVIF_INDENT;
		ListView_GetItem(hList, &item);
		indent = item.iIndent;

		item.mask = LVIF_IMAGE;
		item.iImage = 1;	// minus icon
		ListView_SetItem(hList, &item);
		
		item.mask = LVIF_INDENT;
		prev++;
		for ( ; prev < cnt ; prev++ ) {
			if ( prev == next )
				break;
			item.iItem = prev;
			ListView_GetItem(hList, &item);
			if ( item.iIndent <= indent )
				break;
		}
		next = prev;
		prev--;
		indent++;
	}
	else if ( next >= 0 ) {
		prev = next-1;
		item.iItem = next;
		item.mask = LVIF_INDENT;
		ListView_GetItem(hList, &item);
		indent = item.iIndent;
	}
	else
		prev = cnt-1;

	// get data
	info->check = new HWND[n_cols];
	data.col = 0;
	data.id = id;
	data.type = types[0];
	dataFunc(this, &data, false);

	// insert item
	item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_INDENT | LVIF_PARAM;
	item.iItem = prev+1;
	item.iSubItem = 0;
	item.lParam = id;

	if ( types[0] == 's' )
		item.pszText = data.data.str;
	else
		item.pszText = "";
	item.iImage = data.child ? 0 : -1;	// plus icon or no icon
	item.iIndent = indent;
	item.iItem = ListView_InsertItem(hList, &item);

	if ( types[0] == 'b' )
		info->check[0] = CreateCheckBox(hList, data.editable, data.data.flag, id, 0);
	else
		info->check[0] = NULL;

	// set sub item
	item.mask = LVIF_TEXT;
	for ( int i = 1 ; i < n_cols ; i++ ) {
		data.col = i;
		data.type = types[i];
		dataFunc(this, &data, false);
			
		item.iSubItem = i;
		if ( types[i] == 's' )
			item.pszText = data.data.str;
		else
			item.pszText = "";
		ListView_SetItem(hList, &item);

		if ( types[i] == 'b' )
			info->check[i] = CreateCheckBox(hList, data.editable, data.data.flag, id, i);
		else
			info->check[i] = NULL;
	}
	data.col = -1;
	data.info = info;
	dataFunc(this, &data, true);
}

void
TreeListView::Remove(int id)
{
	FinishEdit();

	int index = FindItem(id), indent;
	LVITEM item;
	if ( id < 0 ) {
		printf("Warning : item %d not found\n", id);
		return;
	}
	item.mask = LVIF_INDENT | LVIF_PARAM;
	item.iItem = index;
	ListView_GetItem(hList, &item);

	indent = item.iIndent;

	RemoveItem(&item);

	while ( 1 ) {
		if ( index >= ListView_GetItemCount(hList) )
			break;
		ListView_GetItem(hList, &item);
		if ( indent < item.iIndent )
			RemoveItem(&item);
		else
			break;
	}
}

// item must contain iItem, iSubItem and lParam
void
TreeListView::RemoveItem(LVITEM *item)
{
	TreeListViewData data;
	data.id = item->lParam;
	data.col = -1;
	dataFunc(this, &data, false);
	for ( int i = 0 ; i < n_cols ; i++ )
		if ( data.info->check[i] )
			DestroyWindow(data.info->check[i]);
	delete[] data.info->check;
	delete data.info;
	ListView_DeleteItem(hList, item->iItem);
}

void
TreeListView::Set(int id)
{
	FinishEdit();

	int index = FindItem(id);
	if ( index < 0 ) {
		printf("Warning : item %d not found...\n", id);
		return;
	}

	LVITEM item;
	item.iItem = index;
	item.mask = LVIF_TEXT;

	TreeListViewData data;
	data.id = id;
	for ( int i = 0 ; i < n_cols ; i++ ) {
		data.col = i;
		data.type = types[i];
		dataFunc(this, &data, false);
		if ( types[i] == 's' ) {
			item.pszText = data.data.str;
			item.iSubItem = i;
			ListView_SetItem(hList, &item);
		}
		else if ( types[i] == 'b' ) {
			SetWindowLong(data.info->check[i], GWL_STYLE,
				WS_CHILD | (data.editable ? BS_AUTOCHECKBOX : BS_CHECKBOX));
			SendMessage(data.info->check[i], BM_SETCHECK,
				data.data.flag ? BST_CHECKED : BST_UNCHECKED, 0);
		}
	}
	ListView_RedrawItems(hList, index, index);
}

int
TreeListView::FindItem(int id)
{
	LVFINDINFO findinfo;
	findinfo.flags = LVFI_PARAM;
	findinfo.lParam = id;
	return ListView_FindItem(hList, -1, &findinfo);
}

int
TreeListView::GetID(int index)
{
	LVITEM item;
	item.mask = LVIF_PARAM;
	item.iItem = index;
	item.iSubItem = 0;
	if ( ListView_GetItem(hList, &item) < 0 )
		return 0;
	return item.lParam;
}

int
TreeListView::SelectedItemStart()
{
	selected_item_cur = -1;
	return ListView_GetSelectedCount(hList);
}

int
TreeListView::SelectedItemIterate()
{
	selected_item_cur = ListView_GetNextItem(hList,
		selected_item_cur, LVNI_SELECTED);
	return GetID(selected_item_cur);
}


static void
DrawListItemText(HDC hdc,char *Text,RECT *rc,int fmt)
{
	DRAWTEXTPARAMS	DrawTextExParam;
	int		Justify;

	DrawTextExParam.cbSize = sizeof(DrawTextExParam);
	DrawTextExParam.iLeftMargin = 2;
	DrawTextExParam.iRightMargin = 0;
	DrawTextExParam.iTabLength = 0;
	DrawTextExParam.uiLengthDrawn = 0;

	if((fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
		Justify = DT_LEFT;
	else if((fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT) {
		Justify = DT_RIGHT;
		DrawTextExParam.iRightMargin = 6;
	}
	else
		Justify = DT_CENTER;

	SetBkMode(hdc, TRANSPARENT);
	DrawTextEx(hdc,Text, -1,rc,
		Justify | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS,
		&DrawTextExParam);
}

static void
DrawListItemGraph(HDC hdc, RECT *rc, int percent)
{
	rc->left += 2;
	rc->right -= 2;
	rc->top += 2;
	rc->bottom -= 2;
	if ( rc->right - rc->left < 2 )
		return;
	DrawFrameControl(hdc, rc, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
	if ( percent <= 0 )
		return;
	if ( percent > 100 )
		percent = 100;
	rc->left += 1;
	rc->right -= 1;
	rc->top += 1;
	rc->bottom -= 1;
	rc->right = rc->left*(100-percent)/100 + rc->right*percent/100;
	DrawFrameControl(hdc, rc, DFC_BUTTON, DFCS_BUTTONPUSH);
}

void
TreeListView::DrawItems(LPDRAWITEMSTRUCT lpDraw)
{
	HWND		hList;
	HDC			hdc;
	RECT		rc,rcAll,rcClient;
	HBRUSH		hBrush;
	COLORREF	Color;
	char		Text[256];
	int			SubItem;
	LVCOLUMN	LvColumn;
	LVITEM		LvItem;
	HIMAGELIST	hImage;
	TreeListViewData data;

	hList = lpDraw->hwndItem;
	hdc = lpDraw->hDC;

	SaveDC(hdc);
	SetBkMode(hdc,OPAQUE);

	// ODS_SELECTED -> Highlight
	if (lpDraw->itemState & ODS_SELECTED) {
		hBrush = CreateSolidBrush (GetSysColor(COLOR_HIGHLIGHT));
		SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
		SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
	}else{
		Color = ListView_GetTextBkColor(hList);
		if(Color & 0xff000000) Color = GetSysColor(COLOR_WINDOW);
		hBrush = CreateSolidBrush (Color);
	}

	// Get Column Format
	ZeroMemory(&LvColumn,sizeof(LvColumn));
	LvColumn.mask = LVCF_FMT;
	ListView_GetColumn(hList,0,&LvColumn);

	// Draw Background
	ListView_GetItemRect(hList,lpDraw->itemID,&rcAll,LVIR_BOUNDS);
	ListView_GetItemRect(hList,lpDraw->itemID,&rc,LVIR_LABEL);

	GetClientRect(hList,&rcClient);
	if(rcAll.right<rcClient.right) rcAll.right = rcClient.right;
	FillRect(hdc,&rcAll,hBrush);


	// Get Item
	ZeroMemory(&LvItem,sizeof(LvItem));
	LvItem.iItem = lpDraw->itemID;
	LvItem.iSubItem = 0;
	LvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
	LvItem.pszText = Text;
	LvItem.cchTextMax = sizeof(Text);
	ListView_GetItem(hList,&LvItem);

	data.id = LvItem.lParam;

	// Draw Image
	hImage = ListView_GetImageList(hList,LVSIL_SMALL);
	if(hImage){
		ListView_GetItemRect(hList,lpDraw->itemID,&rc,LVIR_ICON);
		if ( LvItem.iImage >= 0 )
			ImageList_Draw(hImage,LvItem.iImage,hdc,
				rc.left,rc.top,
				// is image focused?
				((lpDraw->itemState & ODS_SELECTED)? ILD_FOCUS:ILD_NORMAL) |ILD_TRANSPARENT);
	}

	// Draw Item
	ListView_GetItemRect(hList,lpDraw->itemID,&rc,LVIR_LABEL);
	if ( types[0] == 's' ) {
		DrawListItemText(hdc,Text,&rc,LvColumn.fmt);
	}
	else if ( types[0] == 'p' ) {
		data.col = 0;
		data.type = 'p';
		dataFunc(this, &data, false);
		DrawListItemGraph(hdc, &rc, data.data.perc);
	}

	// Draw Subitems
	for( SubItem = 1 ; SubItem < n_cols ; SubItem++ ){
		ListView_GetSubItemRect(hList,lpDraw->itemID,SubItem,LVIR_BOUNDS,&rc);
		FillRect(hdc,&rc,hBrush);
		ListView_GetColumn(hList,SubItem,&LvColumn);
		ListView_GetSubItemRect(hList,lpDraw->itemID,SubItem,LVIR_LABEL,&rc);
		if ( types[SubItem] == 's' ) {
			ListView_GetItemText(hList,lpDraw->itemID,SubItem,Text,sizeof(Text));
			DrawListItemText(hdc,Text,&rc,LvColumn.fmt);
		}
		else if ( types[SubItem] == 'p' ) {
			data.col = SubItem;
			data.type = 'p';
			dataFunc(this, &data, false);
			DrawListItemGraph(hdc, &rc, data.data.perc);
		}
	}

	if (lpDraw->itemState & ODS_FOCUS){
		DrawFocusRect(hdc,&rcAll);
	}

	// Invalidate Checkbox & Editbox
	data.col = -1;
	dataFunc(this, &data, false);
	if ( data.info ) {
		for( SubItem = 0 ; SubItem < n_cols ; SubItem++ ) {
			if ( data.info->check[SubItem] ) {
				RECT rcChk;
				GetClientRect(data.info->check[SubItem], &rcChk);
				InvalidateRect(data.info->check[SubItem], &rcChk, FALSE);
			}
		}
	}
	if ( lpDraw->itemID == (unsigned)edited_index ) {
		RECT rcEdit;
		GetClientRect(hEdit, &rcEdit);
		InvalidateRect(hEdit, &rcEdit, FALSE);
	}

	DeleteObject(hBrush);
	RestoreDC(hdc,-1);
}

void
TreeListView::MoveCheckBox()
{
	int i, j, n;
	RECT rc;
	HWND hCheck;

	n = ListView_GetItemCount(hList);
	for ( i = 0 ; i < n ; i++ ) {
		LVITEM item;
		item.iItem = i;
		item.iSubItem = 0;
		item.mask = LVIF_PARAM;
		ListView_GetItem(hList, &item);

		TreeListViewData data;
		data.id = item.lParam;
		data.col = -1;
		dataFunc(this, &data, false);
		if ( data.info == NULL )
			continue;

		for ( j = 0 ; j < n_cols ; j++ ) {
			hCheck = data.info->check[j];
			if ( hCheck == 0 )
				continue;
			ListView_GetSubItemRect(hList, i, j, LVIR_LABEL, &rc);
			if ( rc.top < 16 )
				ShowWindow(hCheck, SW_HIDE);
			else {
				short shift = j ? (rc.right-rc.left-16)/2 : 0;
				if ( shift < 0 )
					shift = 0;
				int w = (shift || rc.right-rc.left > 15) ? 15 : rc.right-rc.left;
				MoveWindow(hCheck, rc.left+shift, rc.top+1, w, 15, FALSE);
				ShowWindow(hCheck, SW_SHOW);
			}
		}
	}

	if ( edited_index >= 0 ) {
		RECT r;
		ListView_GetSubItemRect(hList, edited_index, edited_col, LVIR_LABEL, &r);
		MoveWindow(hEdit, r.left, r.top, r.right-r.left, r.bottom-r.top, FALSE);
		ShowWindow(hEdit, SW_SHOW);
	}
}

void
TreeListView::SetColumnWidth(int col, short w)
{
	LVCOLUMN column;
	column.mask = LVCF_WIDTH;
	column.cx = w;
	ListView_SetColumn(hList, col, &column);
}

short
TreeListView::GetColumnWidth(int col)
{
	LVCOLUMN column;
	column.mask = LVCF_WIDTH;
	ListView_GetColumn(hList, col, &column);
	return column.cx;
}

bool
TreeListView::AdjustColumnWidth(int col, HDITEM *item)
{
	if ( doing_acw )
		return false;
	doing_acw = true;

	if ( item == NULL || !(item->mask & HDI_WIDTH) )
		col = -1;
	
	bool ret = false;
	int i, r;
	short w = 0;
	RECT rc;
	GetClientRect(hList, &rc);
	for ( i = 0 ; i < n_cols ; i++ )
		w += (i == col) ? item->cxy : GetColumnWidth(i);
	r = w - (rc.right-rc.left);

	// enlarge column smaller than width set on create to fill window
	for ( i = 0 ; i < n_cols && r < 0 ; i++ ) {
		w = (i == col) ? item->cxy : GetColumnWidth(i);
		if ( w < widths[i] ) {
			r += widths[i] - w;
			w = widths[i];
			if ( col == i ) {
				ret = true;
				if ( w == widths[i] )
					goto end;
			}
			SetColumnWidth(i, w);
		}
	}

	// ensmall column out of window
	for ( i = n_cols - 1 ; i >= 0 && r > 0 ; i-- ) {
		w = (i == col) ? item->cxy : GetColumnWidth(i);
		if ( w < r ) {
			r -= w;
			w = 0;
		}
		else {
			w -= r;
			r = 0;
		}
		SetColumnWidth(i, w);
		if ( i == col )
			ret = true;
	}
end:
	doing_acw = false;
	return ret;
}

void
TreeListView::Expand(int id)
{
	FinishEdit();

	TreeListViewNotif notif;
	notif.id = id;
	notif.col = -1;
	notif.message = TLVN_EXPAND;
	notifFunc(this, &notif);
}

void
TreeListView::Collapse(int id)
{
	CollapseItem(FindItem(id));
}

void
TreeListView::ExpandItem(int index)
{
	LVITEM item;
	item.iItem = index;
	item.iSubItem = 0;
	item.mask = LVIF_PARAM;
	ListView_GetItem(hList, &item);
	Expand(item.lParam);
}

void
TreeListView::CollapseItem(int index)
{
	FinishEdit();

	LVITEM item;
	item.iItem = index;
	item.iSubItem = 0;
	item.mask = LVIF_IMAGE;
	item.iImage = 0;	// plus icon
	ListView_SetItem(hList, &item);

	item.mask = LVIF_INDENT | LVIF_PARAM;
	ListView_GetItem(hList, &item);
	int indent = item.iIndent;
	int n = ListView_GetItemCount(hList);
	item.iItem = index+1;
	for ( int i = index+1 ; i < n ; i ++ ) {
		ListView_GetItem(hList, &item);
		if ( item.iIndent <= indent )
			break;
		RemoveItem(&item);
	}
}

bool
TreeListView::StartEditItem(int index, int col)
{
	StopEditTimer();
	int n = ListView_GetItemCount(hList);
	if ( index < 0 || index >= n || col < 0 || col >= n_cols || types[col] != 's' )
		return false;

	static TCHAR text[256];
	LVITEM item;
	item.mask = LVIF_TEXT;
	item.iItem = index;
	item.iSubItem = col;
	item.pszText = text;
	item.cchTextMax = sizeof(text);
	ListView_GetItem(hList, &item);
	SetWindowText(hEdit, text);

	RECT r;
	ListView_GetSubItemRect(hList, index, col, LVIR_LABEL, &r);
	MoveWindow(hEdit, r.left, r.top, r.right-r.left, r.bottom-r.top, FALSE);
	ShowWindow(hEdit, SW_SHOW);
	SetFocus(hEdit);
	SendMessage(hEdit, EM_SETSEL, 0, -1);

	edited_index = index;
	edited_col = col;
	return true;
}

void
TreeListView::FinishEdit()
{
	if ( edited_index < 0 )
		return;

	TreeListViewData data;
	static TCHAR text[256];
	GetWindowText(hEdit, text, sizeof(text));

	LVITEM item;
	item.mask = LVIF_TEXT;
	item.iItem = edited_index;
	item.iSubItem = edited_col;
	item.pszText = text;
	item.cchTextMax = sizeof(text);
	ListView_SetItem(hList, &item);

	item.mask = LVIF_PARAM;
	ListView_GetItem(hList, &item);
	
	data.id = item.lParam;
	data.col = -1;
	dataFunc(this, &data, false);

	data.col = item.iSubItem;
	data.data.str = text;
	data.type = 's';
	dataFunc(this, &data, true);

	EndEdit();
}

void
TreeListView::EndEdit()
{
	StopEditTimer();
	if ( edited_index < 0 )
		return;

	ShowWindow(hEdit, SW_HIDE);
	edited_index = edited_col = -1;
}

int CALLBACK
TreeListView::EditProc(HWND hWnd, unsigned message,
					WPARAM wParam, LPARAM lParam)
{
	TreeListView *tlview;

	switch ( message ) {
	case WM_CHAR:
		if ( wParam != VK_ESCAPE && wParam != VK_RETURN )
			break;
		// else go into WM_KILLFOCUS
	case WM_KILLFOCUS:
		tlview = (TreeListView*)GetWindowLong(hWnd, GWL_USERDATA);
		tlview->FinishEdit();
		break;
	}

	return CallWindowProc(orgEditProc, hWnd, message, wParam, lParam);
}

void
TreeListView::StartEditTimer(int index, int col)
{
	timer_index = index;
	timer_col = col;
	SetTimer(hList, 'edit', 500, EditTimerProc);
}

void
TreeListView::StopEditTimer()
{
	KillTimer(hList, 'edit');
	timer_index = timer_col = -1;
}

void CALLBACK
TreeListView::EditTimerProc(HWND hwnd, UINT msg, UINT idEvent, DWORD dwTime)
{
	if ( idEvent != 'edit' )
		return;
	TreeListView *tlview = (TreeListView*)GetWindowLong(hwnd, GWL_USERDATA);
	if 	( tlview->timer_index < 0 )
		return;

	int index = tlview->timer_index, col = tlview->timer_col;
	tlview->StopEditTimer();

	LVITEM item;
	item.mask = LVIF_STATE;
	item.stateMask = LVIS_SELECTED;
	item.iItem = index;
	item.iSubItem = 0;
	ListView_GetItem(tlview->hList, &item);
	if ( !(item.state & LVIS_SELECTED) )
		return;
	tlview->StartEditItem(index, col);
}
