/*
 * GraphApp - Cross-Platform Graphics Programming Library.
 *
 * File: buttons.c -- creating buttons, scrollbars, etc.
 * Platform: Windows  Version: 2.41  Date: 1998/06/06
 *
 * Version: 1.00  Changes: Original version by Lachlan Patrick.
 * Version: 1.60  Changes: Minor changes.
 * Version: 2.00  Changes: New object class system.
 * Version: 2.10  Changes: Radiogroups fully implemented.
 * Version: 2.20  Changes: Added non-native buttons.
 * Version: 2.21  Changes: 32-bit fix by Wim Rijnders.
 * Version: 2.22  Changes: Added newtextarea function.
 * Version: 2.23  Changes: Added newpassword function.
 * Version: 2.30  Changes: Added imagebuttons.
 * Version: 2.35  Changes: Added newpicture function.
 * Version: 2.40  Changes: Added new list controls.
 * Version: 2.41  Changes: Updated imagebuttons.
 * Version: 2.42  Changes: Fixed drop-down list height.
 */

/* Copyright (C) 1993-1998 Lachlan Patrick

   This file is part of GraphApp, a cross-platform C graphics library.

   GraphApp is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License.
   GraphApp is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY.

   See the file COPYLIB.TXT for details.
*/

/* Changes for R:

   set the system font for labels
   add newscrollbar, field_no_border
   add extended selections, pass on de-selections

 */

#include "internal.h"

#define SHADOW_WIDTH 1

#define isarmed(obj) ((obj)->state & Armed)
#define arm(obj)     ((obj)->state |= Armed)
#define disarm(obj)  ((obj)->state &= ~Armed)

#define hasfocus(obj) ((obj)->state & Focus)
#define setfocus(obj) SetFocus((obj)->handle)

/*
 *  Ensure there is a current window, create one if there isn't.
 *  If a window is created, it is also shown on the screen.
 */
static void ensure_window(void)
{
	if (! current_window) {
		current_window = simple_window();
		show(current_window);
	}
}

/*
 *  Save the winproc for later and set our own winproc instead.
 */
static void set_new_winproc(object obj)
{
  #if TRUE
	HWND hwnd;
	hwnd = obj->handle;
	obj->winproc = (WNDPROC) GetWindowLong(hwnd, GWL_WNDPROC);
	SetWindowLong(hwnd, GWL_WNDPROC, (LONG) app_control_proc);
  #endif
}

/*
 *  Private object destructor.
 */
static void private_delcontrol(control obj)
{
	ShowWindow(obj->handle, SW_HIDE);
#if USE_NATIVE_CONTROLS
	if (obj->bgbrush)
		DeleteObject(obj->bgbrush);
#endif
	DestroyWindow(obj->handle);
}

/*
 *  Creating controls.
 */
control newcontrol(char *text, rect r)
{
	control obj;

	ensure_window();
	obj = newwindow(text, r, ChildWindow | TrackMouse);
	if (obj) {
		obj->kind = UserObject;
		obj->die = private_delcontrol;
	}
	set_new_winproc(obj); /* set custom winproc */
	show(obj);

	return obj;
}

drawing newdrawing(rect r, drawfn fn)
{
	drawing obj = newcontrol(NULL, r);
	if (obj) {
		setredraw(obj, fn);
		show(obj);
		draw(obj);
	}
	return obj;
}

static object newchildwin(char *kind, char *text,
		unsigned long style, rect r, actionfn fn)
{
	HWND hwnd;
	object obj;

	ensure_window();
	r = rcanon(r);

	hwnd = CreateWindow(kind, text,
		(WS_CHILD | WS_VISIBLE) | style,
		r.x, r.y, r.width, r.height,
		current_window->handle,
		(HMENU) child_id, this_instance, NULL);

	obj = new_object(ControlObject, hwnd, current_window);
	if (! obj) {
		DestroyWindow(hwnd);
		return NULL;
	}
	obj->die = private_delcontrol;
	obj->rect = r;
	obj->id = child_id++;
	obj->action = fn;
	obj->state = (Visible | Enabled);
	obj->flags = ChildWindow;
	obj->text = new_string(text);
	set_new_winproc(obj); /* set custom winproc */
	settextfont(obj, SystemFont);
	return obj;
}

/*
 *  The old native version of newlabel.
 */
#if USE_NATIVE_LABELS
label newlabel(char *text, rect r, int alignment)
{
	label obj;
	unsigned long style = SS_LEFT;

	if ((alignment & AlignRight) == AlignRight)
		style = SS_RIGHT;
	if ((alignment & Center) == Center)
		style = SS_CENTER;

	obj = newchildwin("static", text, style, r, NULL);
	obj->kind = LabelObject;
	setbackground(obj, getbackground(parentwindow(obj)));
	return obj;
}
#else
/*
 *  Private label redraw function.
 */
static void draw_label(control c, rect r)
{
	/* Draw the label. */
	if (!isenabled(c))
		setcolour(Grey);
	else
		setcolour(Black);
	setfont(gettextfont(c));
	drawtext(r, getvalue(c), getname(c));
}

/*
 *  Create a new static text label. Now implemented using
 *  GraphApp code instead of native MS-Windows "static"
 *  text. This gives more flexibility, better cross-platform
 *  support and there are no 'look and feel' issues with
 *  labels, so no problems are introduced by doing this.
 */
control newlabel(char *text, rect r, int alignment)
{
	control obj = newcontrol(text, r);
	if (obj) {
		obj->kind = LabelObject;
		setredraw(obj, draw_label);
		setvalue(obj, alignment);
		setbackground(obj, getbackground(parentwindow(obj)));
		settextfont(obj, SystemFont);
		show(obj);
	}
	return obj;
}
#endif

/*
 *  Uncheck any neighbouring radio buttons, up to any bounding
 *  radiogroup objects, or to the start or end of the list of
 *  siblings.
 */
static void uncheck_neighbours(object obj)
{
	object first = obj->parent->child;
	object last  = first->prev;
	object which;

	for (which=obj; ; which=which->prev) {
		if (which->kind == RadiogroupObject)
			break;
		if ((which->kind == RadioObject) && (which != obj))
			uncheck(which);
		if (which == first)
			break;
	}
	for (which=obj; ; which=which->next) {
		if (which->kind == RadiogroupObject)
			break;
		if ((which->kind == RadioObject) && (which != obj))
			uncheck(which);
		if (which == last)
			break;
	}
}

/*
 *  A radiogroup separates radiobuttons from each other.
 */
object newradiogroup(void)
{
	object obj;
	ensure_window();
	obj = new_object(RadiogroupObject, NULL, current_window);
	return obj;
}

/*
 *  Draw a button shadow.
 */
static void draw_shadow(rect r, rgb col1, rgb col2, int size)
{
	rgb hue = current->hue;

	/* Draw top-left button border. */
	setcolour(col1);
	fillrect(rect(r.x,r.y,r.width,size));
	fillrect(rect(r.x,r.y,size,r.height));

	/* Draw bottom-right button border. */
	setcolour(col2);
	while (size > 0) {
		fillrect(rect(r.x+r.width-1,r.y,1,r.height));
		fillrect(rect(r.x,r.y+r.height-1,r.width,1));
		r = insetr(r,1);
		size--;
	}
	setcolour(hue);
}

/*
 *  Various button call-backs.
 */
static void button_mousedown(control c, int buttons, point xy)
{
	arm(c);
	highlight(c);
}

static void button_mousemove(control c, int buttons, point xy)
{
	if (!isarmed(c))
		return;
	if (buttons && ptinr(xy,getrect(c)))
		highlight(c);
	else
		unhighlight(c);
}

static void button_mouseup(control c, int buttons, point xy)
{
	if (!isarmed(c))
		return;
	disarm(c);
	unhighlight(c);
	if (ptinr(xy, getrect(c)))
		activatecontrol(c);
}

static void button_keydown(control c, int ch)
{
	if (ch == ' ') {
		flashcontrol(c);
		activatecontrol(c);
	}
}

/*
 *  Various checkbox call-backs.
 */
static void checkbox_mousedown(control c, int buttons, point xy)
{
	arm(c);
	highlight(c);
}

static void checkbox_mousemove(control c, int buttons, point xy)
{
	if (!isarmed(c))
		return;
	if (buttons && ptinr(xy,getrect(c)))
		highlight(c);
	else
		unhighlight(c);
}

static void checkbox_mouseup(control c, int buttons, point xy)
{
	if (! isenabled(c))
		return;
	disarm(c);
	unhighlight(c);
	if (! ptinr(xy, getrect(c)))
		return;
	if (ischecked(c))
		uncheck(c);
	else
		check(c);
	activatecontrol(c);
}

static void checkbox_keydown(control c, int ch)
{
	if (ch == ' ') {
		if (ischecked(c))
			uncheck(c);
		else
			check(c);
		activatecontrol(c);
	}
}

/*
 *  The imagebutton type:
 */

void setimage(control obj, image img)
{
	/* Change the stored image and draw it. */
	obj->img = img;
	draw(obj);
}

static void draw_image_button(button obj, rect r)
{
	image   img;
	bitmap	store = NULL;
	rect    ir;
	rgb     up, down;
	rgb     old = currentcolour();

	img = obj->img;
	if (has_transparent_pixels(img)) {
		store = newbitmap(r.width, r.height, 0);
		drawto(store);
		setcolour(getbackground(obj));
		fillrect(r);
	}

	if (img) {
		ir = insetr(r,2);
		if (ishighlighted(obj)) /* button is pressed */
			ir.x += 1, ir.y += 1;

		/* Draw the button image. */
		if (ischecked(obj))
			drawdarker(img, ir, getrect(img));
		else if (isenabled(obj))
			drawimage(img, ir, getrect(img));
		else
			drawgreyscale(img, ir, getrect(img));

		if (ishighlighted(obj)) { /* fill the gap */
			ir.x -= 1, ir.y -= 1;
			setcolour(getbackground(obj));
			drawline(topleft(ir),topright(ir));
			drawline(topleft(ir),bottomleft(ir));
		}
	}

	/* Draw button border. */
	setcolour(getforeground(obj));
	setlinewidth(1);
	drawrect(r);

	/* Draw button shadow. */
	up = White, down = Grey;
	if (ishighlighted(obj))
		up = Grey, down = LightGrey;
	draw_shadow(insetr(r,1), up, down, 1);

	if (store != NULL) {
		drawto(obj);
		copyrect(store, pt(0,0), getrect(store));
		del(store);
	}

	setcolour(old);
}


button newimagebutton(image img, rect r, actionfn fn)
{
	button obj;

	obj = newcontrol(NULL, r);
	if (! obj)
		return NULL;

	setredraw(obj, draw_image_button);
	setmousedown(obj, button_mousedown);
	setmousemove(obj, button_mousemove);
	setmousedrag(obj, button_mousemove);
	setmouseup(obj, button_mouseup);
	setkeydown(obj, button_keydown);
	setaction(obj, fn);
	setbackground(obj, LightGrey);
	settextfont(obj, SystemFont);

	setimage(obj, img);

	show(obj);
	return obj;
}

static void draw_picture(control obj, rect r)
{
	image   img;
	bitmap	store = NULL;
	rgb     old = currentcolour();

	img = obj->img;
	if (has_transparent_pixels(img)) {
		store = newbitmap(r.width, r.height, 0);
		drawto(store);
		setcolour(getbackground(obj));
		fillrect(r);
	}

	if (img) {
		/* Draw the button image. */
		if (ischecked(obj))
			drawdarker(img, r, getrect(img));
		else if (isenabled(obj))
			drawimage(img, r, getrect(img));
		else
			drawimage(img, r, getrect(img)); /* never grey */
	}

	if (store != NULL) {
		drawto(obj);
		copyrect(store, pt(0,0), getrect(store));
		del(store);
	}

	setcolour(old);
}

control newpicture(image img, rect r)
{
	control obj;

	obj = newcontrol(NULL, r);
	if (! obj)
		return NULL;

	setredraw(obj, draw_picture);
	setimage(obj, img);
	disable(obj);

	show(obj);
	return obj;
}

button newimagecheckbox(image img, rect r, actionfn fn)
{
	button obj;

	obj = newdrawing(r, draw_image_button);
	if (! obj)
		return NULL;

	setmousedown(obj, checkbox_mousedown);
	setmousemove(obj, checkbox_mousemove);
	setmousedrag(obj, checkbox_mousemove);
	setmouseup(obj, checkbox_mouseup);
	setkeydown(obj, checkbox_keydown);
	setaction(obj, fn);
	setbackground(obj, LightGrey);
	settextfont(obj, SystemFont);

	setimage(obj, img);

	show(obj);
	return obj;
}


#if USE_NATIVE_BUTTONS
button newbutton(char *text, rect r, actionfn fn)
{
	button obj;
	obj = newchildwin("button", text, BS_PUSHBUTTON, r, fn);
	if (obj)
		obj->kind = ButtonObject;
	return obj;
}
#else
static void draw_button(control c, rect r)
{
	rect textrect;
	rgb up, down;
	font f;
	rgb old = currentcolour();

	clear(c);

	/* Draw the button name. */
	if (isenabled(c))
		setcolour(getforeground(c));
	else
		setcolour(Grey);
	f = gettextfont(c);
	setfont(f);
	textrect = r;
	if (ishighlighted(c))
		textrect.x += 1, textrect.y += 1;
	drawtext(textrect, Center|VCenter, getname(c));

	/* Draw button border. */
	setlinewidth(1);
	drawrect(r);
	r = insetr(r,1);

	/* Draw button shadow. */
	up = White, down = Grey;
	if (ishighlighted(c))
		up = Grey, down = LightGrey;
	else if (hasfocus(c)) {
		setcolour(Black);
		drawrect(r);
		r = insetr(r,1);
	}
	draw_shadow(r, up, down, SHADOW_WIDTH);

	setcolour(old);
}

button newbutton(char *text, rect r, actionfn fn)
{
	button obj = newcontrol(text, r);
	if (obj) {
		obj->kind = ButtonObject;
		setredraw(obj, draw_button);
		setmousedown(obj, button_mousedown);
		setmousemove(obj, button_mousemove);
		setmousedrag(obj, button_mousemove);
		setmouseup(obj, button_mouseup);
		setkeydown(obj, button_keydown);
		setaction(obj, fn);
		setbackground(obj, LightGrey);
		settextfont(obj, SystemFont);
		show(obj);
	}
	return obj;
}
#endif

#if USE_NATIVE_TOGGLES
checkbox newcheckbox(char *text, rect r, actionfn fn)
{
	checkbox obj = newchildwin("button", text, BS_CHECKBOX, r, fn);
	if (obj) {
		obj->kind = CheckboxObject;
		setbackground(obj, getbackground(parentwindow(obj)));
	}
	return obj;
}

radiobutton newradiobutton(char *text, rect r, actionfn fn)
{
	radiobutton obj = newchildwin("button", text, BS_RADIOBUTTON, r, fn);
	if (obj) {
		obj->kind = RadioObject;
		setbackground(obj, getbackground(parentwindow(obj)));
	}
	return obj;
}
#else
static void draw_checkmark(rect r)
{
	int len;

	if (r.width < 8) {
		r = insetr(r,1);
		fillrect(r);
		return;
	}

	len = r.width/3;
	if (len < 3)
		len = 3;

	drawline(pt(r.x+1, r.y+r.height-len-2),
		 pt(r.x+len, r.y+r.height-3));
	drawline(pt(r.x+2, r.y+r.height-len-2),
		 pt(r.x+len, r.y+r.height-4));
	drawline(pt(r.x+len, r.y+r.height-3),
		 pt(r.x+r.width-1, r.y+len-2));
	drawline(pt(r.x+len, r.y+r.height-4),
		 pt(r.x+r.width-2, r.y+len-2));
}

static void draw_checkbox(control c, rect r)
{
	int w;
	rect box, textrect;
	char *name;
	int style = (AlignLeft | AlignTop);
	font f;
	rgb old = currentcolour();

	/* Calculate rectangles. */
	f = gettextfont(c);
	setfont(f);
	w = strwidth(f,"W");
	if (w > r.width)  w = r.width;
	if (w > r.height) w = r.height;
	box = rect(r.x,r.y+1,w,w);
	if (w < getheight(f) - getdescent(f))
		box.y += getheight(f) - getdescent(f) - w;
	textrect = rect(r.x+w+w/2,r.y,r.width-(w+w/2),r.height);

	/* Clear check area. */
	setlinewidth(1);
	setcolour(White);
	fillrect(insetr(box,1));

	/* Draw check area */
	if (isenabled(c))
		setcolour(Black);
	else
		setcolour(Grey);
	drawrect(box);

	/* 'Pressed button' effect by black border in box. */
	if (ishighlighted(c))
		drawrect(insetr(box,1));

	/* Put tick in box if checked. */
	if (ischecked(c))
		draw_checkmark(insetr(box,1));

	name = getname(c);
	if (isenabled(c)) {
		/* if (hasfocus(c)) {
			style |= Underline;
			setlinewidth(2);
		} */
		setcolour(getforeground(c));
	}
	drawtext(textrect, style, name);

	setcolour(old);
}

checkbox newcheckbox(char *text, rect r, actionfn fn)
{
	checkbox obj = newcontrol(text, r);
	if (obj) {
		obj->kind = CheckboxObject;
		setredraw(obj, draw_checkbox);
		setmousedown(obj, checkbox_mousedown);
		setmousemove(obj, checkbox_mousemove);
		setmousedrag(obj, checkbox_mousemove);
		setmouseup(obj, checkbox_mouseup);
		setkeydown(obj, checkbox_keydown);
		setaction(obj, fn);
		setbackground(obj, getbackground(parentwindow(obj)));
		settextfont(obj, SystemFont);
		show(obj);
	}
	return obj;
}

static void draw_radio(control c, rect r)
{
	int w;
	rect box, textrect;
	char *name;
	int style = (AlignLeft | AlignTop);
	font f;
	rgb old = currentcolour();

	/* Calculate rectangles. */
	f = gettextfont(c);
	setfont(f);
	w = strwidth(f,"W");
	if (w > r.width)  w = r.width;
	if (w > r.height) w = r.height;
	box = rect(r.x,r.y+1,w,w);
	if (w < getheight(f) - getdescent(f))
		box.y += getheight(f) - getdescent(f) - w;
	textrect = rect(r.x+w+w/2,r.y,r.width-(w+w/2),r.height);

	/* Clear the check area. */
	setlinewidth(1);
	setcolour(White);
	fillellipse(insetr(box,1));

	/* Draw the check area */
	if (isenabled(c))
		setcolour(Black);
	else
		setcolour(Grey);
	drawellipse(box);

	/* Provide 'pressed button' effect by black border. */
	if (ishighlighted(c)) {
		setlinewidth(2);
		drawellipse(box);
		setlinewidth(1);
	}

	/* Put o in circle if checked. */
	if (ischecked(c))
		fillellipse(insetr(box,3));

	name = getname(c);
	if (isenabled(c)) {
		/* if (hasfocus(c)) {
			style |= Underline;
			setlinewidth(2);
		} */
		setcolour(getforeground(c));
	}
	drawtext(textrect, style, name);

	setcolour(old);
}

static void radio_hit(control c)
{
	if (!ischecked(c)) {
		uncheck_neighbours(c);
		check(c);
		activatecontrol(c);
	}
}

static void radio_mouseup(control c, int buttons, point xy)
{
	if (! isarmed(c))
		return;
	disarm(c);
	unhighlight(c);
	if (! ptinr(xy, getrect(c)))
		return;
	radio_hit(c);
}

static void radio_keydown(control c, int ch)
{
	if (ch == ' ')
		radio_hit(c);
}

radiobutton newradiobutton(char *text, rect r, actionfn fn)
{
	radiobutton obj = newcontrol(text, r);
	if (obj) {
		obj->kind = RadioObject;
		setredraw(obj, draw_radio);
		setmousedown(obj, checkbox_mousedown);
		setmousemove(obj, checkbox_mousemove);
		setmousedrag(obj, checkbox_mousemove);
		setmouseup(obj, radio_mouseup);
		setkeydown(obj, radio_keydown);
		setaction(obj, fn);
		setbackground(obj, getbackground(parentwindow(obj)));
		settextfont(obj, SystemFont);
		show(obj);
	}
	return obj;
}
#endif

#if 0
void undotext(control obj)
{
	if (! obj)
		return;
	if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
		return;
	sendmessage(obj->handle, EM_UNDO, 0, 0L);
}
#endif

void cuttext(control obj)
{
	if (! obj)
		return;
	if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
		return;
	sendmessage(obj->handle, WM_CUT, 0, 0L);
}

void copytext(control obj)
{
	if (! obj)
		return;
	if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
		return;
	sendmessage(obj->handle, WM_COPY, 0, 0L);
}

void cleartext(control obj)
{
	if (! obj)
		return;
	if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
		return;
	sendmessage(obj->handle, WM_CLEAR, 0, 0L);
}

void pastetext(control obj)
{
	if (! obj)
		return;
	if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
		return;
	sendmessage(obj->handle, WM_PASTE, 0, 0L);
}

void inserttext(control obj, char *text)
{
	if (! obj)
		return;
	if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
		return;
	text = to_dos_string(text);
	sendmessage(obj->handle, EM_REPLACESEL, 0, (long) text);
	if (text)
		discard(text);
}

void selecttext(control obj, long start, long end)
{
	int left, right;
	long length;

	if (! obj)
		return;
	if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
		return;
	length = GetWindowTextLength(obj->handle);
	left = (start < 0) ? length : start;
	right = (end < 0) ? length : end;
#ifdef WIN32
	sendmessage(obj->handle, EM_SETSEL, left,right);
#else
	sendmessage(obj->handle, EM_SETSEL, 0, MAKELONG(left,right));
#endif
}

void textselection(control obj, long *start, long *end)
{
	unsigned long sel;

	if (! obj)
		return;
	if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
		return;
	sel = sendmessage(obj->handle, EM_GETSEL, 0, 0);
	if (start) *start = LOWORD(sel);
	if (end) *end = HIWORD(sel);
}


field newfield(char *text, rect r)
{
	field obj = newchildwin("edit", NULL,
			WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,
			r, NULL);
	if (obj) {
		obj->kind = FieldObject;
		settext(obj, text);
	}
	return obj;
}


field newfield_no_border(char *text, rect r)
{
	field obj = newchildwin("edit", NULL,
			ES_LEFT | ES_AUTOHSCROLL,
			r, NULL);
	if (obj) {
		obj->kind = FieldObject;
		settext(obj, text);
	}
	return obj;
}

field newpassword(char *text, rect r)
{
	field obj = newchildwin("edit", NULL,
			WS_BORDER | ES_LEFT | ES_AUTOHSCROLL
			| ES_PASSWORD, r, NULL);
	if (obj) {
		obj->kind = FieldObject;
		settextfont(obj, SystemFont);
		settext(obj, text);
	}
	return obj;
}

textbox newtextbox(char *text, rect r)
{
	textbox obj = newchildwin("edit", NULL,
			/* WS_HSCROLL | ES_AUTOHSCROLL | */
			WS_VSCROLL | ES_AUTOVSCROLL |
			WS_BORDER | ES_LEFT |
			ES_MULTILINE,
			r, NULL);
	if (obj) {
		obj->kind = TextboxObject;
		settext(obj, text);
	}
	return obj;
}

textbox newtextarea(char *text, rect r)
{
	textbox obj = newchildwin("edit", NULL,
			WS_HSCROLL | ES_AUTOHSCROLL |
			WS_VSCROLL | ES_AUTOVSCROLL |
			WS_BORDER | ES_LEFT |
			ES_MULTILINE,
			r, NULL);
	if (obj) {
		obj->kind = TextboxObject;
		settext(obj, text);
	}
	return obj;
}

static SCROLLINFO si;

scrollbar newscrollbar(rect r, int max, int pagesize, scrollfn fn)
{
	scrollbar obj;
	HWND hwnd;

	r = rcanon(r);

	obj = newchildwin("scrollbar", NULL,
			(r.width > r.height) ? SBS_HORZ : SBS_VERT,
			r, NULL);
	if (obj) {
		obj->kind = ScrollbarObject;
		obj->hit = fn;
		obj->value = 0;
		obj->max = max;
		obj->size = pagesize;

		hwnd = obj->handle;
		si.cbSize = sizeof(si);
		si.fMask = SIF_ALL;
		si.nMin = 0;
		si.nMax = max + pagesize - 1;
		si.nPage = pagesize;
		si.nPos = 0;
		SetScrollInfo(hwnd, SB_CTL, &si, 1);
	}
	return obj;
}

void changescrollbar(scrollbar obj, int where, int max, int pagesize)
{
	HWND hwnd;

	if (! obj)
		return;
	hwnd = obj->handle;
	obj->max = max;
	obj->size = pagesize;

	si.cbSize = sizeof(si);
	si.fMask = SIF_ALL;
	si.nMin = 0;
	si.nMax = max + pagesize - 1;
	si.nPage = pagesize;
	si.nPos = where;
	SetScrollInfo(hwnd, SB_CTL, &si, 1);
}

listbox newlistbox(char *list[], rect r, scrollfn fn)
{
	listbox obj;

	obj = newchildwin("listbox", NULL,
				LBS_NOTIFY | WS_BORDER |
				WS_VSCROLL | WS_HSCROLL,
				r, NULL);
	if (! obj)
		return obj;
	obj->kind = ListboxObject;
	obj->hit = fn;

	changelistbox(obj, list);

	return obj;
}

listbox newmultilist(char *list[], rect r, scrollfn fn)
{
	listbox obj;

	obj = newchildwin("listbox", NULL,
				LBS_NOTIFY |
				LBS_MULTIPLESEL | LBS_EXTENDEDSEL |
				WS_BORDER |
				WS_VSCROLL | WS_HSCROLL,
				r, NULL);
	if (! obj)
		return obj;
	obj->kind = MultilistObject;
	obj->hit = fn;

	changelistbox(obj, list);

	return obj;
}

listbox newdroplist(char *list[], rect r, scrollfn fn)
{
	listbox obj;
	int h, i;

	initapp(0,0);
	h = getheight(SystemFont);
	r.height = h+h;
	for (i = 0; list && list[i]; i++)
		r.height += h;

	obj = newchildwin("combobox", NULL,
				CBS_DROPDOWNLIST |
				//CBS_DISABLENOSCROLL |
				WS_BORDER |
				WS_VSCROLL | WS_HSCROLL,
				r, NULL);
	if (! obj)
		return obj;
	obj->kind = DroplistObject;
	obj->hit = fn;

	changelistbox(obj, list);

	return obj;
}

listbox newdropfield(char *list[], rect r, scrollfn fn)
{
	listbox obj;
	int h, i;

	initapp(0,0);
	h = getheight(SystemFont);
	r.height = h+h;
	for (i = 0; list && list[i]; i++)
		r.height += h;

	obj = newchildwin("combobox", NULL,
				CBS_DROPDOWN |
				CBS_DISABLENOSCROLL |
				WS_BORDER |
				WS_VSCROLL | WS_HSCROLL,
				r, NULL);
	if (! obj)
		return obj;
	obj->kind = DroplistObject;
	obj->hit = fn;

	changelistbox(obj, list);

	return obj;
}

void setlistitem(listbox obj, int index)
{
	int count;

	if (! obj)
		return;
	if (index < 0)
		index = -1;
	switch (obj->kind)
	{
	case ListboxObject:
	  sendmessage(obj->handle, LB_SETCURSEL, index, 0L);
	  break;
	case MultilistObject:
	  if (index >= 0)
	    sendmessage(obj->handle, LB_SETSEL, TRUE, MAKELPARAM(index, 0));
	  else {
	    count = sendmessage(obj->handle, LB_GETCOUNT, 0, 0L);
	    sendmessage(obj->handle, LB_SELITEMRANGE, FALSE, MAKELPARAM(0,count-1));
	  }
	case DroplistObject:
	case DropfieldObject:
	  sendmessage(obj->handle, CB_SETCURSEL, index, 0L);
	  break;
	default:
	  break;
	}
}

int isselected(listbox obj, int index)
{
	if (! obj)
		return -1;
	switch (obj->kind)
	{
	case ListboxObject:
	  return (index == sendmessage(obj->handle, LB_GETCURSEL, 0, 0L));
	case MultilistObject:
	  return sendmessage(obj->handle, LB_GETSEL, index, 0L);
	case DroplistObject:
	case DropfieldObject:
	  return (index == sendmessage(obj->handle, CB_GETCURSEL, 0, 0L));
	default:
	  return 0;
	}
}

int getlistitem(listbox obj)
{
	int index, count;

	if (! obj)
		return -1;
	switch (obj->kind)
	{
	case ListboxObject:
	  return sendmessage(obj->handle, LB_GETCURSEL, 0, 0L);
	case MultilistObject:
	  count = sendmessage(obj->handle, LB_GETCOUNT, 0, 0L);
	  for (index=0; index < count; index++)
	    if (isselected(obj, index))
	      return index;
	  return -1;
	case DroplistObject:
	case DropfieldObject:
	  return sendmessage(obj->handle, CB_GETCURSEL, 0, 0L);
	default:
	  return -1;
	}
}

void changelistbox(listbox obj, char **list)
{
	int i;
	HWND hwnd;
	UINT reset_msg, add_msg;

	if (! obj)
		return;
	hwnd = obj->handle;

	switch (obj->kind) {
	  case ListboxObject: case MultilistObject:
		reset_msg = LB_RESETCONTENT;
		add_msg = LB_ADDSTRING;
		break;
	  case DroplistObject: case DropfieldObject:
		reset_msg = CB_RESETCONTENT;
		add_msg = CB_ADDSTRING;
		break;
	  default:
		return;
	}

	sendmessage(hwnd, WM_SETREDRAW, FALSE, 0L);
	sendmessage(hwnd, reset_msg, 0, 0L);
	for (i=0; list && list[i]; i++)
	    sendmessage(hwnd, add_msg, 0, (LPSTR) list[i]);
	sendmessage(hwnd, WM_SETREDRAW, TRUE, 0L);
	if (obj->kind == ListboxObject)
		sendmessage(hwnd, LB_SETCURSEL, 0, 0L);
}

/*
 *  Activate a control's action function. We do several things here.
 *  Checking to see what kind of control it is, we handle some
 *  events and discard others. We automatically toggle the state of
 *  checkboxes and radio buttons, and handle listbox changes and
 *  allow text box update events to call the control's action.
 */
PROTECTED
void handle_control(HWND hwnd, UINT message)
{
	object obj;
	int index;

	obj = find_by_handle(hwnd);

	if ((! obj) || (! (obj->state & Enabled)))
		return;

	/* Only let certain events cause activation. */
	switch (obj->kind)
	{
	  case CheckboxObject:
		if (obj->state & Checked)
			uncheck(obj);
		else
			check(obj);
		break;

	  case RadioObject:
		if (!(obj->state & Checked)) {
			uncheck_neighbours(obj);
			check(obj);
		}
		break;

	  case ListboxObject:
		/* Ignore all but selection-change events. */
		if (message != LBN_SELCHANGE)
			return;
		index = sendmessage(hwnd, LB_GETCURSEL, 0, 0L);
		obj->value = index;
		break;

	  case MultilistObject:
		/* Ignore all but selection-change events. */
		if (message != LBN_SELCHANGE)
			return;
		index = sendmessage(hwnd, LB_GETCARETINDEX, 0, 0L);
		/* We do want to see de-selection events too 
		if (! sendmessage(hwnd, LB_GETSEL, index, 0L))
		  return;*/
		obj->value = index;
		break;

	  case DroplistObject:
	  case DropfieldObject:
		/* Ignore all but selection-change events. */
		if (message != CBN_SELCHANGE)
			return;
		index = sendmessage(hwnd, CB_GETCURSEL, 0, 0L);
		obj->value = index;
		break;

	  case FieldObject:
	  case TextboxObject:
		/* For the moment we ignore all but killfocus. */
		if (message != EN_KILLFOCUS)
			return;
		break;

	  default:
		break;
	}
	/* activate the control's callback */
	activatecontrol(obj);
}

#include <commctrl.h>
/* smooth  != 0 gives continuous not segmented bar */
progressbar newprogressbar(rect r, int pbmin, int pbmax, int incr, int smooth)
{
	HWND hwnd;
	progressbar obj;
	int sm;

	ensure_window();
	r = rcanon(r);
	sm = smooth ? PBS_SMOOTH : 0 ;
	hwnd = CreateWindowEx(0, PROGRESS_CLASS, NULL,
		(WS_CHILD | WS_VISIBLE | sm),
		r.x, r.y, r.width, r.height,
		current_window->handle,
		(HMENU) child_id, this_instance, NULL);
	obj = new_object(ControlObject, hwnd, current_window);
	if (! obj) {
		DestroyWindow(hwnd);
		return NULL;
	}
	obj->die = private_delcontrol;
	obj->rect = r;
	obj->id = child_id++;
	obj->action = NULL;
	obj->state = (Visible | Enabled);
	obj->flags = ChildWindow;
	set_new_winproc(obj); /* set custom winproc */
	settextfont(obj, SystemFont);
	obj->kind = ListboxObject;
	SendMessage(hwnd, PBM_SETRANGE32, (WPARAM) pbmin, (LPARAM) pbmax); 
	SendMessage(hwnd, PBM_SETSTEP, (WPARAM) incr, 0);

	return obj;
}

void setprogressbar(progressbar obj, int n)
{
	if (! obj) return;
        SendMessage(obj->handle, PBM_SETPOS, (WPARAM) n, 0); 
}

void stepprogressbar(progressbar obj, int n)
{
	if (! obj) return;
        SendMessage(obj->handle, PBM_STEPIT, 0, 0); 
}

void setprogressbarrange(progressbar obj, int pbmin, int pbmax)
{
	if (! obj) return;
	SendMessage(obj->handle, PBM_SETRANGE32, (WPARAM) pbmin, 
		    (LPARAM) pbmax); 
}
