/*
 * 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.
*/

/* Copyright (C) 2004-8  The R Foundation

   Changes for R:

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

   Copyright (C) 2022  The R Core Team

   use RichEdit20W to work in UTF-8
   allow to leave a dropfield (combo box) with TAB key
 */

#include "internal.h"
#include "ga.h"
#include <richedit.h>

#define SHADOW_WIDTH 1

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

#define hasfocus(obj) ((obj)->state & GA_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)
{
    HWND hwnd;
    hwnd = obj->handle;
#ifdef _WIN64
    obj->winproc = (WNDPROC) GetWindowLongPtr(hwnd, GWLP_WNDPROC);
    SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) app_control_proc);
#else
    obj->winproc = (WNDPROC) GetWindowLong(hwnd, GWL_WNDPROC);
    SetWindowLongPtr(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(const 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(const char *kind, const char *text,
			  unsigned long style, rect r, actionfn fn)
{
    HWND hwnd;
    object obj;

    ensure_window();
    r = rcanon(r);

    if(localeCP > 0 && (localeCP != GetACP())) {
	/* This seems not actually to work */
	wchar_t wkind[100], wc[1000];
	mbstowcs(wkind, kind, 100);
	mbstowcs(wc, text, 1000);
	hwnd = CreateWindowW(wkind, wc,
			     (WS_CHILD | WS_VISIBLE) | style,
			     r.x, r.y, r.width, r.height,
			     current_window->handle,
			     (HMENU) child_id, this_instance, NULL);
    } else
	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 = (GA_Visible | GA_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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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

void undotext(control obj)  /* Why was this previously commented out? CJ */
{
    if (! obj)
	return;
    if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
	return;
    sendmessage(obj->handle, EM_UNDO, 0, 0L);
}

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, const char *text)
{
    if (! obj)
	return;
    if ((obj->kind != FieldObject) && (obj->kind != TextboxObject))
	return;
    text = to_dos_string(text);
    sendmessage(obj->handle, EM_REPLACESEL, 0, (intptr_t) 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 = (DWORD) sendmessage(obj->handle, EM_GETSEL, 0, 0);
    if (start) *start = LOWORD(sel);
    if (end) *end = HIWORD(sel);
}


field newfield(const 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(const 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(const 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(const 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(const 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 newrichtextarea(const char *text, rect r)
{
    textbox obj;
    if (!LoadLibrary("riched20.dll")) /* RichEdit version 2.0, not included in Win95 */
	LoadLibrary("riched32.dll");  /* RichEdit version 1.0 */

    /* RichEdit20A uses the non-UTF-8 ANSI encoding even when Rgui uses UTF-8
       as ACP via its manifest. Only Unicode strings may be sent to RichEdit20W
       window via messages, e.g. EM_FINDTEXTTEXW. */
    obj = newchildwin("RichEdit20W", NULL,
		      WS_HSCROLL | ES_AUTOHSCROLL |
		      WS_VSCROLL | ES_AUTOVSCROLL |
		      ES_LEFT | ES_MULTILINE | ES_NOHIDESEL,
		      r, NULL);
    if (obj) {
	sendmessage(obj->handle, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
	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(const char *list[], rect r, scrollfn fn, actionfn dble)
{
    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;
    obj->dble = dble;

    changelistbox(obj, list);

    return obj;
}

listbox newmultilist(const char *list[], rect r, scrollfn fn, actionfn dble)
{
    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;
    obj->dble = dble;

    changelistbox(obj, list);

    return obj;
}

listbox newdroplist(const 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_AUTOHSCROLL |
		      //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(const 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_AUTOHSCROLL |
		      // CBS_DISABLENOSCROLL |
		      WS_BORDER |
		      WS_VSCROLL | WS_HSCROLL,
		      r, NULL);
    if (! obj)
	return obj;

    /* subclass the edit control to handle TAB */
    HANDLE hwndCombo = obj->handle;
    COMBOBOXINFO cbInfo;
    cbInfo.cbSize = sizeof(COMBOBOXINFO);
    BOOL ret = GetComboBoxInfo(hwndCombo, &cbInfo);
    if (ret) {
	HANDLE hwndEdit = cbInfo.hwndItem; /* the edit control */
	if (GetParent(hwndEdit) == hwndCombo) {
#ifdef _WIN64
	    obj->edit_winproc = (WNDPROC) GetWindowLongPtr(hwndEdit,
	                                                   GWLP_WNDPROC);
	    SetWindowLongPtr(hwndEdit, GWLP_WNDPROC,
	                     (LONG_PTR) edit_control_proc);
#else
	    obj->edit_winproc = (WNDPROC) GetWindowLong(hwndEdit,
	                                                GWL_WNDPROC);
	    SetWindowLongPtr(hwndEdit, GWL_WNDPROC,
	                     (LONG) edit_control_proc);
#endif
	}
    }

    obj->kind = DropfieldObject;
    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 = (INT) 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 == (INT) sendmessage(obj->handle, LB_GETCURSEL, 0, 0L));
    case MultilistObject:
	return (INT) sendmessage(obj->handle, LB_GETSEL, index, 0L);
    case DroplistObject:
    case DropfieldObject:
	return (index == (INT) 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 (INT) sendmessage(obj->handle, LB_GETCURSEL, 0, 0L);
    case MultilistObject:
	count = (INT) 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 (INT) sendmessage(obj->handle, CB_GETCURSEL, 0, 0L);
    default:
	return -1;
    }
}

void changelistbox(listbox obj, const 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 & GA_Enabled)))
	return;

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

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

    case ListboxObject:
	if (message == LBN_DBLCLK) {
	    if(obj->dble) obj->dble(obj);
	    return;
	}
	/* Ignore all but selection-change events. */
	if (message != LBN_SELCHANGE) return;

	index = (INT) sendmessage(hwnd, LB_GETCURSEL, 0, 0L);
	obj->value = index;
	break;

    case MultilistObject:
	if (message == LBN_DBLCLK) {
	    if(obj->dble) obj->dble(obj);
	    return;
	}
	/* Ignore all but selection-change events. */
	if (message != LBN_SELCHANGE)
	    return;
	index = (INT) sendmessage(hwnd, LB_GETCARETINDEX, 0, 0L);
	/* We do want to see de-selection events too
	   if (! (INT) 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 = (INT) sendmessage(hwnd, CB_GETCURSEL, 0, 0L);
	obj->value = index;
	break;

    case FieldObject:
    case TextboxObject:
	if (message == EN_MAXTEXT) {
	    /* increase the character limit in the editor by 50%
	       if the limit is reached, but this should not
	       happen */
	    setlimittext(obj, 1.5 * getlimittext(obj));
	}
	/* Ignore everything else but killfocus. */
	else 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 = (GA_Visible | GA_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);
}
