BOOL lastmouse;
	intptr_t lastmouseRow;
	intptr_t lastmouseColumn;
	BOOL mouseDown;			// TRUE if over a checkbox; the next two decide which ones
	intptr_t mouseDownRow;
	intptr_t mouseDownColumn;

static RECT checkboxRect(struct table *t, intptr_t row, intptr_t column, LONG rowHeight)
{
	RECT r;
	HDITEMW item;
	intptr_t i;

	// TODO count dividers
	for (i = 0; i < column; i++) {
		ZeroMemory(&item, sizeof (HDITEMW));
		item.mask = HDI_WIDTH;
		if (SendMessageW(t->header, HDM_GETITEM, (WPARAM) i, (LPARAM) (&item)) == FALSE)
			abort();
		r.left += item.cxy;
	}
	// TODO double-check to see if this takes any parameters
	r.left += SendMessageW(t->header, HDM_GETBITMAPMARGIN, 0, 0);
	r.right = r.left + t->checkboxWidth;
	// TODO vertical center
	r.top = row * rowHeight;
	r.bottom = r.top + t->checkboxHeight;
	return r;
}

// TODO clean up variables
static BOOL lParamInCheckbox(struct table *t, LPARAM lParam, intptr_t *row, intptr_t *column)
{
	int x, y;
	LONG h;
	intptr_t col;
	RECT r;
	POINT pt;

	x = GET_X_LPARAM(lParam);
	y = GET_Y_LPARAM(lParam);
	h = rowHeight(t);
	y += t->firstVisible * h;
	y -= t->headerHeight;
	pt.y = y;		// save actual y coordinate now
	y /= h;		// turn it into a row count
	if (y >= t->count)
		return FALSE;
	col = hitTestColumn(t, x);
	if (col == -1)
		return FALSE;
	if (t->columnTypes[col] != tableColumnCheckbox)
		return FALSE;
	r = checkboxRect(t, y, col, h);
	pt.x = x;
	if (PtInRect(&r, pt) == 0)
		return FALSE;
	if (row != NULL)
		*row = y;
	if (column != NULL)
		*column = col;
	return TRUE;
}

static void retrack(struct table *t)
{
	TRACKMOUSEEVENT tm;

	ZeroMemory(&tm, sizeof (TRACKMOUSEEVENT));
	tm.cbSize = sizeof (TRACKMOUSEEVENT);
	tm.dwFlags = TME_LEAVE;		// TODO also TME_NONCLIENT?
	tm.hwndTrack = t->hwnd;
	if (_TrackMouseEvent(&tm) == 0)
		abort();
}

static void track(struct table *t, LPARAM lParam)
{
	intptr_t row, column;
	BOOL prev;
	intptr_t prevrow, prevcolumn;

	prev = t->lastmouse;
	prevrow = t->lastmouseRow;
	prevcolumn = t->lastmouseColumn;
	t->lastmouse = lParamInCheckbox(t, lParam, &(t->lastmouseRow), &(t->lastmouseColumn));
	if (prev)
		if (prevrow != row || prevcolumn != column)
			redrawRow(t, prevrow);
	redrawRow(t, t->lastmouseRow);
}

in selectItem
	// TODO only if inside a checkbox
	t->mouseDown = TRUE;
	t->mouseDownRow = t->selected;
	t->mouseDownColumn = t->focusedColumn;

in drawItem
			// TODO replace all this
			rsel.left = headeritem.left + xoff;
			rsel.top = y;
			rsel.right = rsel.left + t->checkboxWidth;
			rsel.bottom = rsel.top + t->checkboxHeight;
			{ COLORREF c;

			c = RGB(255, 0, 0);
			if (t->mouseDown) {
				if (i == t->mouseDownRow && j == t->mouseDownColumn)
					c = RGB(0, 0, 255);
			} else if (t->lastmouse) {
				if (i == t->lastmouseRow && j == t->lastmouseColumn)
					c = RGB(0, 255, 0);
			}
			if (SetDCBrushColor(dc, c) == CLR_INVALID)
				abort();
			}
			if (FillRect(dc, &rsel, GetStockObject(DC_BRUSH)) == 0)
				abort();
			break;

	case WM_LBUTTONDOWN:
		selectItem(t, wParam, lParam);
		return 0;
	case WM_LBUTTONUP:
		// TODO toggle checkbox
		if (t->mouseDown) {
			t->mouseDown = FALSE;
			redrawRow(t, t->mouseDownRow);
		}
		return 0;
	// TODO other mouse buttons?
	case WM_MOUSEMOVE:
		track(t, lParam);
		return 0;
	case WM_MOUSELEAVE:
		t->lastmouse = FALSE;
		retrack(t);
		// TODO redraw row mouse is currently over
		// TODO split into its own function
		if (t->mouseDown) {
			t->mouseDown = FALSE;
			redrawRow(t, t->mouseDownRow);
		}
		return 0;