/*
 *     XaoS, a fast portable realtime fractal zoomer 
 *                  Copyright (C) 1996 by
 *
 *      Jan Hubicka          (hubicka@paru.cas.cz)
 *      Thomas Marsh         (tmarsh@austin.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#undef _EFENCE_
#include <config.h>
#ifdef _plan9_
#include <u.h>
#include <libc.h>
#include <ctype.h>
#else
#include <aconfig.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#ifndef _MAC
#include <sys/stat.h>
#endif
#endif
#include <fconfig.h>
#ifndef _plan9_
#include <assert.h>
#endif
#include <filter.h>
#include <fractal.h>
#include <ui_helper.h>
#include <ui.h>
#include <xmenu.h>
#include <grlib.h>
#include "uiint.h"
#include <xldio.h>
#include <misc-f.h>

#ifdef HAVE_GETTEXT
#include <libintl.h>
#else
#define gettext(STRING) STRING
#endif

struct dialogitem;
struct dialogtype {
    void (*build) (struct dialogitem * item, CONST menudialog * entry);
    int (*key) (struct dialogitem * item, int key);
    void (*mouse) (struct dialogitem * item, int x, int y, int buttons,
		   int flags);
    void (*destroy) (struct dialogitem * item, dialogparam * param);
    void (*draw) (struct dialogitem * item);
    void (*unselect) (struct dialogitem * item);
};
struct dialogitem {
    int y;
    int width, width1, height;
    CONST menudialog *dialog;
    void *data;
    CONST struct dialogtype *type;
};
static struct opendialog {
    int x, y, width, height;
    int half;
    int nitems;
    CONST menudialog *dialog;
    int mousereleased;
    int mousegrab;
    CONST menuitem *item;
    int current;
    struct dialogitem *items;
    struct uih_window *window;
} dialog;

static dialogparam *qparam;
static CONST menuitem *qitem;

int dialogvisible;

int yesnodialogvisible;
static struct yesnodialog {
    int width;
    int questionwidth;
    int mousereleased;
    char *question;
    void (*handler) (int yes);
    int selected;
    int pressed;
    struct uih_window *window;
} yesnodialog;

// These 3 definitions are no longer used:
static CONST char *CONST oktext = "OK";
static CONST char *CONST canceltext = "Cancel";
static CONST char *CONST helptext = "Help";

static int okwidth;
static int cancelwidth;
#define SELECTED(item) ((item-dialog.items)==dialog.current)
static void NEXT(void)
{
    dialog.items[dialog.current].type->unselect(dialog.items +
						dialog.current);
    dialog.current = (dialog.current + 1) % dialog.nitems;
    uih->display = 1;
}

static void PREV(void)
{
    dialog.items[dialog.current].type->unselect(dialog.items +
						dialog.current);
    dialog.current =
	dialog.current ? (dialog.current - 1) : dialog.nitems - 1;
    uih->display = 1;
}

struct okdata {
    int pressed;
    int selected;
};

CONST char *CONST yestext = "Yes";
CONST char *CONST notext = "No";
#define YESNOX ((uih->image->width-yesnodialog.width)/2)
#define YESNOHEIGHT (2*BUTTONHEIGHT+2*BORDERHEIGHT)
#define YESNOY ((uih->image->height-YESNOHEIGHT)/2)
void
ui_drawbutton(CONST char *text, int pressed, int selected, int x1, int x2,
	      int y)
{
    int width = xtextwidth(uih->font, text);
    /*printf("%s %i %i\n",text,pressed,selected); */
    if (uih->palette->type & BITMAPS) {
	uih_drawborder(uih, x1, y, x2 - x1, BUTTONHEIGHT,
		       (pressed != 0
			|| selected != 0) * BORDER_PRESSED | BORDER_LIGHT);
	xprint(uih->image, uih->font, (x1 + x2 - width) / 2 + pressed,
	       y + BORDERHEIGHT + pressed, text, uih->encoding, selected
	       || pressed ? BGCOLOR(uih) : FGCOLOR(uih), BGCOLOR(uih),
	       TEXT_PRESSED);
    } else {
	uih_drawborder(uih, x1, y, x2 - x1, BUTTONHEIGHT,
		       (pressed != 0) * BORDER_PRESSED | BORDER_LIGHT);
	xprint(uih->image, uih->font, (x1 + x2 - width) / 2 + pressed,
	       y + BORDERHEIGHT + pressed, text, uih->encoding,
	       selected ? SELCOLOR(uih) : FGCOLOR(uih), BGCOLOR(uih),
	       /*TEXT_PRESSED */ 0);
    }
}

static void
ui_yesnopos(struct uih_context *c, int *x, int *y, int *w, int *h,
	    void *data)
{
#ifdef _plan9_
#define filevisible 0
#endif
    if (filevisible || helpvisible) {
	*x = *y = *w = *h = 0;
	return;
    }
    *w = yesnodialog.width;
    *h = YESNOHEIGHT;
    *x = YESNOX;
    *y = YESNOY;
}

static void ui_drawyesno(struct uih_context *c, void *data)
{
    xprint(uih->image, uih->font,
	   YESNOX + (yesnodialog.width - yesnodialog.questionwidth) / 2,
	   YESNOY + BORDERHEIGHT, yesnodialog.question, uih->encoding,
	   FGCOLOR(uih), BGCOLOR(uih), 0);
    ui_drawbutton(yestext, yesnodialog.pressed == 0,
		  yesnodialog.selected == 0, YESNOX + BORDERWIDTH + 1,
		  YESNOX + (yesnodialog.width) / 2 - 1,
		  YESNOY + BUTTONHEIGHT + BORDERHEIGHT);
    ui_drawbutton(notext, yesnodialog.pressed == 1,
		  yesnodialog.selected == 1,
		  YESNOX + (yesnodialog.width) / 2 + 1,
		  YESNOX + yesnodialog.width - BORDERWIDTH - 1,
		  YESNOY + BUTTONHEIGHT + BORDERHEIGHT);
}

static void ui_closeyesno(int success)
{
    if (!yesnodialogvisible)
	return;
    free(yesnodialog.question);
    yesnodialog.handler(success);
    yesnodialogvisible = 0;
    uih_removew(uih, yesnodialog.window);
    uih->display = 1;
}

void ui_buildyesno(CONST char *question, void (*handler) (int yes))
{
    if (yesnodialogvisible)
	ui_closeyesno(0);
    yesnodialogvisible = 1;
    yesnodialog.questionwidth = xtextwidth(uih->font, question);
    yesnodialog.question = mystrdup(question);
    yesnodialog.mousereleased = 0;
    yesnodialog.width =
	xtextwidth(uih->font, yestext) + xtextwidth(uih->font,
						    notext) +
	8 * BORDERWIDTH + 2;
    if (yesnodialog.width < yesnodialog.questionwidth)
	yesnodialog.width = yesnodialog.questionwidth;
    yesnodialog.width += 2 * BORDERWIDTH;
    yesnodialog.handler = handler;
    yesnodialog.selected = 0;
    yesnodialog.pressed = -1;
    yesnodialog.window =
	uih_registerw(uih, ui_yesnopos, ui_drawyesno, NULL, DRAWBORDER);
    uih->display = 1;
}

static int ui_keyyesno(int key)
{
    if (!yesnodialogvisible)
	return 0;
    switch (key) {
    case UIKEY_LEFT:
	yesnodialog.selected ^= 1;
	uih->display = 1;
	return 1;
    case UIKEY_UP:
	return 1;
    case UIKEY_RIGHT:
    case UIKEY_TAB:
	yesnodialog.selected ^= 1;
	uih->display = 1;
	return 1;
    case UIKEY_DOWN:
	return 1;
    case 13:
    case '\n':
	ui_closeyesno(!yesnodialog.selected);
	return 1;
    case UIKEY_ESC:
	ui_closeyesno(0);
	return 1;
    }
    return 1;
}

static int ui_mouseyesno(int x, int y, int buttons, int flags)
{
    int mouseat = 0;
    if (!yesnodialogvisible)
	return 0;
    if (!yesnodialog.mousereleased && (flags & MOUSE_RELEASE)) {
	yesnodialog.mousereleased = 1;
	return 1;
    }
    if (!yesnodialog.mousereleased && (flags & MOUSE_DRAG)) {
	return 1;
    }
    yesnodialog.mousereleased = 1;
    if (x < YESNOX || y < YESNOY || x > YESNOX + yesnodialog.width
	|| y > YESNOY + YESNOHEIGHT) {
	if (flags & MOUSE_PRESS) {
	    ui_closeyesno(0);
	} else {
	    if (yesnodialog.pressed != -1)
		uih->display = 1;
	    yesnodialog.pressed = -1;
	}
	return 1;
    }
    if (x > YESNOX + yesnodialog.width / 2)
	mouseat = 1;
    if (flags & MOUSE_DRAG) {
	if (yesnodialog.pressed != mouseat)
	    uih->display = 1;
	if (yesnodialog.selected != mouseat)
	    uih->display = 1;
	yesnodialog.selected = mouseat;
	yesnodialog.pressed = mouseat;
    } else {
	if ((flags & MOUSE_MOVE) && yesnodialog.selected != mouseat)
	    uih->display = 1, yesnodialog.selected = mouseat;
	if (yesnodialog.pressed != -1)
	    uih->display = 1;
	yesnodialog.pressed = -1;
    }
    if (flags & MOUSE_RELEASE) {
	ui_closeyesno(!mouseat);
    }
    return 1;
}

static void ui_buildok(struct dialogitem *item, CONST menudialog * entry)
{
    struct okdata *ok;
    item->height = BUTTONHEIGHT;
    okwidth = xtextwidth(uih->font, gettext("OK"));
    cancelwidth = xtextwidth(uih->font, gettext("Cancel"));
    item->width = okwidth + 2 * BORDERWIDTH + 2;
    item->width1 = cancelwidth + 2 * BORDERWIDTH + 2;
    if (item->width < item->width1)
	item->width = item->width1;
    if (item->width > item->width1)
	item->width1 = item->width;
    item->data = ok = (struct okdata *) malloc(sizeof(struct okdata));
    ok->pressed = -1;
    ok->selected = 0;
}

static void ui_destroyok(struct dialogitem *item, dialogparam * param)
{
    free(item->data);
}

static int ui_keyok(struct dialogitem *item, int key)
{
    struct okdata *ok = (struct okdata *) item->data;
    switch (key) {
    case UIKEY_LEFT:
	if (ok->selected >= 1) {
	    ok->selected--;
	    uih->display = 1;
	    return 1;
	}
    case UIKEY_UP:
	PREV();
	return 1;
    case UIKEY_RIGHT:
    case UIKEY_TAB:
	if (ok->selected < 2) {
	    ok->selected++;
	    uih->display = 1;
	    return 1;
	}
    case UIKEY_DOWN:
	NEXT();
	return 1;
    case 13:
    case '\n':
	if (ok->selected <= 1)
	    ui_closedialog(!ok->selected);
	else
	    ui_help(dialog.item->shortname);
	return 1;
    }
    return 0;
}

static void
ui_mouseok(struct dialogitem *item, int x, int y, int buttons, int flags)
{
    struct okdata *ok = (struct okdata *) item->data;
    int mouseat = 0;
    if (x > dialog.x + dialog.width / 3)
	mouseat = 1;
    if (x > dialog.x + 2 * dialog.width / 3)
	mouseat = 2;
    if (flags & MOUSE_DRAG) {
	if (ok->pressed != mouseat)
	    uih->display = 1;
	if (ok->selected != mouseat)
	    uih->display = 1;
	ok->selected = mouseat;
	ok->pressed = mouseat;
    } else {
	if ((flags & MOUSE_MOVE) && ok->selected != mouseat)
	    uih->display = 1, ok->selected = mouseat;
	if (ok->pressed != -1)
	    uih->display = 1;
	ok->pressed = -1;
    }
    if (flags & MOUSE_RELEASE) {
	if (mouseat < 2)
	    ui_closedialog(!mouseat);
	else
	    ui_help(dialog.item->shortname);
    }
}

static void ui_drawok(struct dialogitem *item)
{
    struct okdata *ok = (struct okdata *) item->data;
    ui_drawbutton(gettext("OK"), ok->pressed == 0, SELECTED(item)
		  && ok->selected == 0, dialog.x + BORDERWIDTH + 1,
		  dialog.x + (dialog.width) / 3 - 1, item->y);
    ui_drawbutton(gettext("Cancel"), ok->pressed == 1, SELECTED(item)
		  && ok->selected == 1, dialog.x + (dialog.width) / 3 + 1,
		  dialog.x + 2 * dialog.width / 3 - BORDERWIDTH, item->y);
    ui_drawbutton(gettext("Help"), ok->pressed == 2, SELECTED(item)
		  && ok->selected == 2, dialog.x + 2 * (dialog.width) / 3,
		  dialog.x + dialog.width - BORDERWIDTH - 1, item->y);
}

static void ui_unselectok(struct dialogitem *item)
{
    struct okdata *ok = (struct okdata *) item->data;
    ok->pressed = -1;
    ok->selected = 0;
    uih->display = 1;
}

CONST static struct dialogtype okdialog = {
    ui_buildok,
    ui_keyok,
    ui_mouseok,
    ui_destroyok,
    ui_drawok,
    ui_unselectok
};

void ui_updatetext(struct ui_textdata *d)
{
    int again = 1;
    int i;
    int wi;
    int len = (int) strlen(d->text);
    if (d->start >= len)
	d->start = 0;
    if (d->cursor > len)
	d->cursor = len;
    if (d->cursor < d->start)
	d->start = d->cursor;
    do {
	wi = 0;
	for (i = 0; d->text[d->start + i]; i++) {
	    if (d->start + i == d->cursor)
		d->cursorpos = wi;
	    wi += xtextcharw(uih->font, d->taxt[d->start + i]);
	    if (wi >= d->width) {
		break;
	    }
	}
	if (d->start + i == d->cursor && wi < d->width)
	    d->cursorpos = wi;
	if (d->start + i < d->cursor)
	    d->start++;
	else
	    again = 0;
    }
    while (again);
    d->ndisplayed = i;
    while (again);
}

struct ui_textdata *ui_opentext(int x, int y, int width, CONST char *def)
{
    struct ui_textdata *d = (struct ui_textdata *) malloc(sizeof(*d));
    char *text;
    int size = 100;
    if ((int) strlen(def) > size)
	size = (int) strlen(def) * 2;
    d->x = x;
    d->y = y;
    d->width = width;
    text = (char *) malloc(size);
    strcpy(text, def);
    d->text = text;
    d->cursor = 0;
    d->cursorpos = 0;
    d->start = 0;
    d->ndisplayed = 0;
    d->clear = 1;
    d->size = size;
    ui_updatetext(d);
    return (d);
}

void ui_drawtext(struct ui_textdata *d, int active)
{
    char *c = (char *) malloc(d->ndisplayed + 2);
    strncpy(c, d->text + d->start, d->ndisplayed);
    c[d->ndisplayed] = 0;
    xprint(uih->image, uih->font, d->x, d->y, c, uih->encoding,
	   (uih->palette->type & BITMAPS) ? BGCOLOR(uih) : ((active
							     && d->clear) ?
							    SELCOLOR(uih) :
							    FGCOLOR(uih)),
	   BGCOLOR(uih),
	   (uih->palette->type & BITMAPS) ? TEXT_PRESSED : 0);
    if (active) {
	xdrawcursor(uih->image, d->x + d->cursorpos, d->y,
		    (uih->palette->
		     type & BITMAPS) ? BGCOLOR(uih) : SELCOLOR(uih),
		    xtextheight(uih->font));
    }
    free(c);
}

void ui_textmouse(struct ui_textdata *d, int x, int y)
{
    if (y > d->y && y < d->y + xtextheight(uih->font) && x > d->x) {
	int w = 0;
	int i;
	int xp = d->x;
	for (i = 0; i < d->ndisplayed + 1 && xp - w / 2 < x; i++) {
	    w = xtextcharw(uih->font, d->text[i + d->start]);
	    xp += w;
	}
	d->cursor = i + d->start - 1;
	if (d->cursor < 0)
	    d->cursor = 0;
	d->clear = 0;
	ui_updatetext(d);
	uih->display = 1;
    }
}

void ui_closetext(struct ui_textdata *d)
{
    free(d->text);
    free(d);
}

int ui_textkey(struct ui_textdata *d, int key)
{
    switch (key) {
    case UIKEY_LEFT:
	if (d->clear)
	    d->clear = 0;
	if (d->cursor)
	    d->cursor--;
	else
	    return 0;
	ui_updatetext(d);
	uih->display = 1;
	return 1;
    case UIKEY_RIGHT:
	if (d->clear)
	    d->clear = 0;
	if (d->cursor < (int) strlen(d->text))
	    d->cursor++;
	else
	    return 0;
	ui_updatetext(d);
	uih->display = 1;
	return 1;
    case UIKEY_HOME:
	if (d->clear)
	    d->clear = 0;
	d->cursor = 0;
	ui_updatetext(d);
	uih->display = 1;
	return 1;
    case UIKEY_END:
	if (d->clear)
	    d->clear = 0;
	d->cursor = (int) strlen(d->text);
	ui_updatetext(d);
	uih->display = 1;
	return 1;
    case UIKEY_BACKSPACE:
	if (d->clear)
	    d->text[0] = 0, d->clear = 0;
	else if (d->cursor) {
	    int len, i;
	    len = (int) strlen(d->text);
	    for (i = d->cursor; i <= len; i++) {
		d->text[i - 1] = d->text[i];
	    }
	    d->cursor--;
	}
	ui_updatetext(d);
	uih->display = 1;
	return 1;
    }
    if (isprint(key)) {
	int i;
	int len;
	if (d->clear)
	    d->text[0] = 0, d->clear = 0;
	if ((len = (int) strlen(d->text)) > d->size - 2) {
	    d->text = (char *) realloc(d->text, d->size * 2);
	}
	for (i = len; i >= d->cursor; i--) {
	    d->text[i + 1] = d->text[i];
	}
	d->text[d->cursor] = key;
	d->cursor++;
	ui_updatetext(d);
	uih->display = 1;
	return 1;
    }
    return 0;
}

static void
ui_buildstring(struct dialogitem *item, CONST menudialog * entry)
{
    item->height = BUTTONHEIGHT;
    item->width = xtextwidth(uih->font, item->dialog->question);
    item->width1 = xtextcharw(uih->font, 'w') * 20;
    item->data = ui_opentext(0, 0, 2043, item->dialog->defstr);
}

static void ui_destroystring(struct dialogitem *item, dialogparam * param)
{
    struct ui_textdata *text = (struct ui_textdata *) item->data;
    param->dstring = mystrdup(text->text);
    ui_closetext(text);
}

static void ui_drawquestion(struct dialogitem *item)
{
    if (uih->palette->type & BITMAPS) {
	if (SELECTED(item))
	    xrectangle(uih->image, dialog.x + BORDERWIDTH, item->y,
		       dialog.half - dialog.x - 2 * BORDERWIDTH,
		       BUTTONHEIGHT, FGCOLOR(uih));
	xprint(uih->image, uih->font, dialog.half - item->width,
	       item->y + BORDERHEIGHT, item->dialog->question,
	       uih->encoding, SELECTED(item) ? BGCOLOR(uih) : FGCOLOR(uih),
	       BGCOLOR(uih), TEXT_PRESSED);
    } else {
	xprint(uih->image, uih->font, dialog.half - item->width,
	       item->y + BORDERHEIGHT, item->dialog->question,
	       uih->encoding,
	       SELECTED(item) ? SELCOLOR(uih) : FGCOLOR(uih), BGCOLOR(uih),
	       0);
    }
}

static void ui_drawstring(struct dialogitem *item)
{
    struct ui_textdata *text = (struct ui_textdata *) item->data;
    if (text->width == 2043)
	text->x = dialog.half + BORDERWIDTH, text->width =
	    dialog.width + dialog.x - dialog.half - 2 * BORDERWIDTH,
	    text->y = item->y + BORDERHEIGHT, ui_updatetext(text);
    uih_drawborder(uih, dialog.half, item->y,
		   dialog.width - dialog.half + dialog.x - BORDERWIDTH,
		   BUTTONHEIGHT, BORDER_PRESSED | BORDER_LIGHT);
    ui_drawtext(text, SELECTED(item));
    ui_drawquestion(item);
}

static int ui_keystring(struct dialogitem *item, int key)
{
    struct ui_textdata *text = (struct ui_textdata *) item->data;
    return (ui_textkey(text, key));
}

static void
ui_mousestring(struct dialogitem *item, int x, int y, int buttons,
	       int flags)
{
    struct ui_textdata *text = (struct ui_textdata *) item->data;
    if (flags & MOUSE_DRAG) {
	ui_textmouse(text, x, y);
    }
}

static void ui_unselectstring(struct dialogitem *item)
{
}

CONST static struct dialogtype stringdialog = {
    ui_buildstring,
    ui_keystring,
    ui_mousestring,
    ui_destroystring,
    ui_drawstring,
    ui_unselectstring
};

static void ui_buildint(struct dialogitem *item, CONST menudialog * entry)
{
    char s[50];
    item->height = BUTTONHEIGHT;
    item->width = xtextwidth(uih->font, item->dialog->question);
    item->width1 = xtextcharw(uih->font, 'w') * 5;
    sprintf(s, "%i", item->dialog->defint);
    item->data = ui_opentext(0, 0, 2043, s);
}

static void ui_destroyint(struct dialogitem *item, dialogparam * param)
{
    struct ui_textdata *text = (struct ui_textdata *) item->data;
    param->dint = (int) atol(text->text);
    ui_closetext(text);
}

CONST static struct dialogtype intdialog = {
    ui_buildint,
    ui_keystring,
    ui_mousestring,
    ui_destroyint,
    ui_drawstring,
    ui_unselectstring
};

static CONST char *ui_getextension(CONST char *ch)
{
    int i = 0;
    while (ch[i]) {
	if (ch[i] == '*')
	    return (ch + i + 1);
	i++;
    }
    return ch + i;
}

number_t ui_getfloat(CONST char *c)
{
#ifdef HAVE_LONG_DOUBLE
    long double param;
#else
    double param;
#endif
#ifdef HAVE_LONG_DOUBLE
#ifndef USE_ATOLD
#ifdef USE_XLDIO
    param = x_strtold(c, NULL);
    if (0)
#else
    if (sscanf(c, "%LG", &param) == 0)
#endif
#else
    param = _atold(c);
    if (0)
#endif
    {
#else
    if (sscanf(c, "%lG", &param) == 0) {
#endif
	return 0;
    }
    return (param);
}

#define BROWSEWIDTH /*(2*BORDERWIDTH+xtextcharw(uih->font,'B'))*/BUTTONHEIGHT
struct ui_filedata {
    struct ui_textdata *text;
    int active;
    int pressed;
};

static struct dialogitem *curritem;
static void filecallback(CONST char *name, int succ)
{
    struct ui_filedata *text = (struct ui_filedata *) curritem->data;
    if (succ) {
	ui_closetext(text->text);
	uih->display = 1;
	text->text = ui_opentext(0, 0, 2043, name);
	dialog.mousereleased = 0;
    }
    if (dialog.nitems == 2)
	ui_closedialog(succ);
}

static void ui_buildfile(struct dialogitem *item, CONST menudialog * entry)
{
    char str[256];
    struct ui_filedata *data =
	(struct ui_filedata *) malloc(sizeof(*data));
    int i = 0;
    item->height = BUTTONHEIGHT;
    item->width = xtextwidth(uih->font, item->dialog->question);
    item->width1 = xtextcharw(uih->font, 'w') * 20;
    while (item->dialog->defstr[i] != '*' && item->dialog->defstr[i] != 0)
	str[i] = item->dialog->defstr[i], i++;
    str[i] = 0;
    item->data = data;
    if (entry->type == DIALOG_OFILE)
	data->text =
	    ui_opentext(0, 0, 2043,
			ui_getfile(str,
				   ui_getextension(item->dialog->defstr)));
    else
	data->text = ui_opentext(0, 0, 2043, item->dialog->defstr);
    data->active = 0;
    data->pressed = 0;
#ifndef _plan9_
    if (dialog.nitems == 2) {
	curritem = item;
	ui_buildfilesel(data->text->text, "", filecallback);
    }
#endif
}

static void ui_destroyfile(struct dialogitem *item, dialogparam * param)
{
    struct ui_filedata *text = (struct ui_filedata *) item->data;
#ifndef _plan9_
    if (filevisible)
	ui_closefilesel(0);
#endif
    param->dpath = mystrdup(text->text->text);
    ui_closetext(text->text);
    free(text);
}

static void ui_drawfile(struct dialogitem *item)
{
    struct ui_filedata *data = (struct ui_filedata *) item->data;
    int wholesize =
	dialog.width + dialog.x - dialog.half - 2 * BORDERWIDTH;
    if (data->text->width == 2043) {
	data->text->x = dialog.half + BORDERWIDTH,
	    data->text->width = wholesize - BROWSEWIDTH - 2 * BORDERWIDTH,
	    data->text->y = item->y + BORDERHEIGHT;
	ui_updatetext(data->text);
    }
    uih_drawborder(uih, dialog.half, item->y, wholesize - BROWSEWIDTH,
		   BUTTONHEIGHT, BORDER_PRESSED | BORDER_LIGHT);
    ui_drawtext(data->text, SELECTED(item) && !data->active);
    xprint(uih->image, uih->font, dialog.half - item->width,
	   item->y + BORDERHEIGHT, item->dialog->question, uih->encoding,
	   SELECTED(item) ? SELCOLOR(uih) : FGCOLOR(uih), BGCOLOR(uih), 0);
    ui_drawquestion(item);
    ui_drawbutton("B", data->pressed && SELECTED(item), SELECTED(item)
		  && data->active,
		  dialog.x + dialog.width - BROWSEWIDTH - BORDERWIDTH,
		  dialog.x + dialog.width - BORDERWIDTH, item->y);
}

static int ui_keyfile(struct dialogitem *item, int key)
{
    struct ui_filedata *text = (struct ui_filedata *) item->data;
    int i = 0;
    if (!text->active)
	i = ui_textkey(text->text, key);
    if (!i) {
	if (key == '\t' || key == UIKEY_RIGHT) {
	    text->active++;
	    if (text->active > 1) {
		text->active = 0;
		return 0;
	    }
	    uih->display = 1;
	    return 1;
	}
	if (key == UIKEY_LEFT) {
	    text->active--;
	    if (text->active < 0) {
		text->active = 1;
		return 0;
	    }
	    return 1;
	}
#ifndef _plan9_
	if ((key == 13 || key == '\n') && text->active) {
	    curritem = item;
	    ui_buildfilesel(text->text->text, "", filecallback);
	    return 1;
	}
#endif
    }
    return (i);
}

static void
ui_mousefile(struct dialogitem *item, int x, int y, int buttons, int flags)
{
    struct ui_filedata *text = (struct ui_filedata *) item->data;
    int i;
    if (flags & MOUSE_MOVE) {
	if (x < dialog.x + dialog.width - BORDERWIDTH - BROWSEWIDTH)
	    i = 0;
	else
	    i = 1;
	if (text->active != i)
	    text->active = i, uih->display = 1;
    }
#ifndef _plan9_
    if ((flags & MOUSE_RELEASE) && text->pressed) {
	text->pressed = 0;
	uih->display = 1;
	curritem = item;
	ui_buildfilesel(text->text->text, "", filecallback);
	return;
    }
#endif
    if (flags & MOUSE_DRAG) {
	if (x < dialog.x + dialog.width - BORDERWIDTH - BROWSEWIDTH) {
	    text->active = 0, ui_textmouse(text->text, x, y);
	    if (text->pressed)
		text->pressed = 0, uih->display = 1;
	} else if (!text->pressed) {
	    text->pressed = 1;
	    uih->display = 1;
	}
    }
}

static void ui_unselectfile(struct dialogitem *item)
{
    struct ui_filedata *text = (struct ui_filedata *) item->data;
    if (text->active)
	text->active = 0, uih->display = 1;
    if (text->pressed)
	text->pressed = 0, uih->display = 1;
}

CONST static struct dialogtype filedialog = {
    ui_buildfile,
    ui_keyfile,
    ui_mousefile,
    ui_destroyfile,
    ui_drawfile,
    ui_unselectfile
};

static void
ui_buildfloat(struct dialogitem *item, CONST menudialog * entry)
{
    char s[50];
    item->height = BUTTONHEIGHT;
    item->width = xtextwidth(uih->font, item->dialog->question);
    item->width1 = xtextcharw(uih->font, 'w') * 10;
    sprintf(s, "%g", (double) item->dialog->deffloat);
    item->data = ui_opentext(0, 0, 2043, s);
}

static void ui_destroyfloat(struct dialogitem *item, dialogparam * param)
{
    struct ui_textdata *text = (struct ui_textdata *) item->data;
    param->number = ui_getfloat(text->text);
    ui_closetext(text);
}

CONST static struct dialogtype floatdialog = {
    ui_buildfloat,
    ui_keystring,
    ui_mousestring,
    ui_destroyfloat,
    ui_drawstring,
    ui_unselectstring
};

struct ui_coorddata {
    struct ui_textdata *text[2];
    int active;
};
static void
ui_buildcoord(struct dialogitem *item, CONST menudialog * entry)
{
    char s[50];
    struct ui_coorddata *data =
	(struct ui_coorddata *) malloc(sizeof(*data));
    item->height = BUTTONHEIGHT;
    item->width = xtextwidth(uih->font, item->dialog->question);
    item->width1 = xtextcharw(uih->font, 'w') * 20;
    item->data = data;
    data->active = 0;
    sprintf(s, "%g", (double) item->dialog->deffloat);
    data->text[0] = ui_opentext(0, 0, 2043, s);
    sprintf(s, "%g", (double) item->dialog->deffloat2);
    data->text[1] = ui_opentext(0, 0, 2043, s);
}

static void ui_destroycoord(struct dialogitem *item, dialogparam * param)
{
    struct ui_coorddata *data = (struct ui_coorddata *) item->data;
    param->dcoord[0] = ui_getfloat(data->text[0]->text);
    param->dcoord[1] = ui_getfloat(data->text[1]->text);
    ui_closetext(data->text[0]);
    ui_closetext(data->text[1]);
    free(data);
}

#define SPACESIZE xtextwidth(uih->font,"+")
#define ENDSIZE xtextwidth(uih->font,"i")
static void ui_drawcoord(struct dialogitem *item)
{
    struct ui_coorddata *data = (struct ui_coorddata *) item->data;
    int wholesize =
	dialog.width + dialog.x - dialog.half - 2 * BORDERWIDTH - ENDSIZE;
    int half = (wholesize - SPACESIZE) / 2;
    if (data->text[0]->width == 2043) {
	data->text[0]->x = dialog.half + BORDERWIDTH,
	    data->text[0]->width = half - BORDERWIDTH,
	    data->text[0]->y = item->y + BORDERHEIGHT;
	data->text[1]->x = dialog.half + half + SPACESIZE + BORDERWIDTH,
	    data->text[1]->width = half - BORDERWIDTH,
	    data->text[1]->y = item->y + BORDERHEIGHT;
	ui_updatetext(data->text[0]);
	ui_updatetext(data->text[1]);
    }
    uih_drawborder(uih, dialog.half, item->y, half, BUTTONHEIGHT,
		   BORDER_PRESSED | BORDER_LIGHT);
    uih_drawborder(uih, dialog.half + half + SPACESIZE, item->y, half,
		   BUTTONHEIGHT, BORDER_PRESSED | BORDER_LIGHT);
    ui_drawtext(data->text[0], SELECTED(item) && !data->active);
    ui_drawtext(data->text[1], SELECTED(item) && data->active);
    xprint(uih->image, uih->font, dialog.half + half,
	   item->y + BORDERHEIGHT, "+", uih->encoding, FGCOLOR(uih),
	   BGCOLOR(uih), 0);
    xprint(uih->image, uih->font,
	   dialog.x + dialog.width - BORDERWIDTH - ENDSIZE,
	   item->y + BORDERHEIGHT, "i", uih->encoding, FGCOLOR(uih),
	   BGCOLOR(uih), 0);
    ui_drawquestion(item);
}

static int ui_keycoord(struct dialogitem *item, int key)
{
    struct ui_coorddata *text = (struct ui_coorddata *) item->data;
    int i = ui_textkey(text->text[text->active], key);
    if (!i) {
	if (key == '\t' || key == UIKEY_RIGHT) {
	    text->active++;
	    if (text->active > 1) {
		text->active = 0;
		return 0;
	    }
	    uih->display = 1;
	    if (key == UIKEY_RIGHT) {
		text->text[1]->cursor = 0;
		ui_updatetext(text->text[1]);
		uih->display = 1;
	    }
	    return 1;
	}
	if (key == UIKEY_LEFT) {
	    text->active--;
	    if (text->active < 0) {
		text->active = 1;
		return 0;
	    }
	    text->text[0]->cursor = (int) strlen(text->text[0]->text);
	    ui_updatetext(text->text[0]);
	    uih->display = 1;
	    return 1;
	}
    }
    return (i);
}

static void
ui_mousecoord(struct dialogitem *item, int x, int y, int buttons,
	      int flags)
{
    struct ui_coorddata *text = (struct ui_coorddata *) item->data;
    int i;
    if (flags & MOUSE_MOVE) {
	if (x < text->text[1]->x)
	    i = 0;
	else
	    i = 1;
	if (text->active != i)
	    text->active = i, uih->display = 1;
    }
    if (flags & MOUSE_DRAG) {
	if (x < text->text[1]->x)
	    text->active = 0, ui_textmouse(text->text[0], x, y);
	else
	    text->active = 1, ui_textmouse(text->text[1], x, y);
    }
}

static void ui_unselectcoord(struct dialogitem *item)
{
    struct ui_coorddata *text = (struct ui_coorddata *) item->data;
    text->active = 0;
}

CONST static struct dialogtype coorddialog = {
    ui_buildcoord,
    ui_keycoord,
    ui_mousecoord,
    ui_destroycoord,
    ui_drawcoord,
    ui_unselectcoord
};

struct ui_choicedata {
    CONST char **texts;
    int selected;
    int n;

    struct uih_window *menu;
    int x, y, width, height;
    int active;
};
static void
ui_choicemenupos(struct uih_context *uih, int *x, int *y, int *width,
		 int *height, void *data)
{
    struct ui_choicedata *choice = (struct ui_choicedata *) data;
    if (filevisible || helpvisible) {
	*x = *y = *width = *height = 0;
	return;
    }
    *x = choice->x;
    *y = choice->y;
    *width = choice->width;
    *height = choice->height;
}

static void ui_drawchoicemenu(uih_context * uih, void *data)
{
    struct ui_choicedata *choice = (struct ui_choicedata *) data;
    int i;
    for (i = 0; i < choice->n; i++) {
	xprint(uih->image, uih->font, choice->x + BORDERWIDTH,
	       choice->y + BORDERHEIGHT + i * xtextheight(uih->font),
	       choice->texts[i], uih->encoding,
	       i == choice->active ? SELCOLOR(uih) : FGCOLOR(uih),
	       BGCOLOR(uih), 0);
    }
}

static void
ui_buildchoicemenu(struct uih_context *uih, struct ui_choicedata *choice,
		   int x, int y, int width)
{
    if (choice->menu != NULL)
	return;
    choice->width = width + 2 * BORDERWIDTH;
    choice->x = x;
    choice->height = xtextheight(uih->font) * choice->n + 2 * BORDERHEIGHT;
    choice->active = choice->selected;
    choice->y = y - choice->active * xtextheight(uih->font);
    dialog.mousegrab = 1;
    if (choice->x + choice->width > uih->image->width)
	choice->x = uih->image->width - choice->width;
    if (choice->y + choice->height > uih->image->height)
	choice->y = uih->image->height - choice->height;
    if (choice->x < 0)
	choice->x = 0;
    if (choice->y < 0)
	choice->y = 0;
    choice->menu =
	uih_registerw(uih, ui_choicemenupos, ui_drawchoicemenu, choice,
		      DRAWBORDER);
    uih->display = 1;
}

static void
ui_closechoicemenu(struct uih_context *uih, struct ui_choicedata *choice)
{
    if (choice->menu == NULL)
	return;
    uih_removew(uih, choice->menu);
    choice->menu = NULL;
    uih->display = 1;
    dialog.mousegrab = 0;
    dialog.mousereleased = 0;
}

static void
ui_buildchoice(struct dialogitem *item, CONST menudialog * entry)
{
    int i;
    struct ui_choicedata *data =
	(struct ui_choicedata *) malloc(sizeof(*data));
    item->height = BUTTONHEIGHT;
    item->width = xtextwidth(uih->font, item->dialog->question);
    item->width1 = 0;
    data->menu = NULL;

    data->texts = (CONST char **) entry->defstr;
    for (i = 0; data->texts[i] != NULL; i++) {
	int w = xtextwidth(uih->font, data->texts[i]);
	if (w > item->width1)
	    item->width1 = w;
    }
    item->width1 += 2 * BORDERWIDTH;
    data->n = i;
    data->selected = entry->defint;
    item->data = data;
}

static void ui_destroychoice(struct dialogitem *item, dialogparam * param)
{
    struct ui_choicedata *data = (struct ui_choicedata *) item->data;
    param->dint = data->selected;
    ui_closechoicemenu(uih, data);
    free(data);
}

static void ui_drawchoice(struct dialogitem *item)
{
    struct ui_choicedata *data = (struct ui_choicedata *) item->data;
    uih_drawborder(uih, dialog.half, item->y, item->width1,
		   BUTTONHEIGHT | BORDER_LIGHT, 0);
    xprint(uih->image, uih->font, dialog.half + BORDERWIDTH,
	   item->y + BORDERHEIGHT, data->texts[data->selected],
	   uih->encoding, SELECTED(item) ? SELCOLOR(uih) : FGCOLOR(uih),
	   BGCOLOR(uih), 0);
    ui_drawquestion(item);
}

static int ui_keychoice(struct dialogitem *item, int key)
{
    struct ui_choicedata *data = (struct ui_choicedata *) item->data;
    if (!data->menu) {
	switch (key) {
	case ' ':
	case '\n':
	case 13:
	case UIKEY_UP:
	case UIKEY_DOWN:
	case UIKEY_RIGHT:
	    ui_buildchoicemenu(uih, data, dialog.half, item->y,
			       item->width1);
	    return (1);
	}
    } else {
	switch (key) {
	case ' ':
	case 13:
	case '\n':
	    data->selected = data->active;
	    ui_closechoicemenu(uih, data);
	    return (1);
	case UIKEY_DOWN:
	    data->active++;
	    data->active %= data->n;
	    uih->display = 1;
	    return (1);
	case UIKEY_UP:
	    data->active--;
	    if (data->active < 0)
		data->active = data->n - 1;
	    uih->display = 1;
	    return (1);
	case UIKEY_ESC:
	    ui_closechoicemenu(uih, data);
	    return (1);
	}
    }
    return 0;
}

static void
ui_mousechoice(struct dialogitem *item, int x, int y, int buttons,
	       int flags)
{
    struct ui_choicedata *data = (struct ui_choicedata *) item->data;
    int in;
    if (data->menu != NULL) {
	in = 0;
	if (x > data->x && y > data->y && x < data->x + data->width
	    && y < data->y + data->height)
	    in = 1;
	if ((flags & MOUSE_PRESS) && !in) {
	    ui_closechoicemenu(uih, data);
	    return;
	}
	if ((flags & MOUSE_MOVE) && in) {
	    in = (y - data->y) / xtextheight(uih->font);
	    if (in < 0)
		in = 0;
	    if (in >= data->n)
		in = data->n - 1;
	    if (data->active != in)
		data->active = in, uih->display = 1;
	}
	if (flags & MOUSE_RELEASE) {
	    data->selected = data->active;
	    ui_closechoicemenu(uih, data);
	    return;
	}
    } else {
	if (flags & MOUSE_PRESS && x > dialog.half)
	    ui_buildchoicemenu(uih, data, dialog.half, item->y,
			       item->width1);
    }
}

static void ui_unselectchoice(struct dialogitem *item)
{
    struct ui_choicedata *data = (struct ui_choicedata *) item->data;
    ui_closechoicemenu(uih, data);
}

CONST static struct dialogtype choicedialog = {
    ui_buildchoice,
    ui_keychoice,
    ui_mousechoice,
    ui_destroychoice,
    ui_drawchoice,
    ui_unselectchoice
};

static void
ui_dialogpos(struct uih_context *c, int *x, int *y, int *width,
	     int *height, void *data)
{
    *x = dialog.x;
    *y = dialog.y;
    if (filevisible || helpvisible) {
	*x = *y = *width = *height = 0;
	return;
    }
    *width = dialog.width;
    *height = dialog.height;
}

static void ui_dialogdraw(struct uih_context *c, void *data)
{
    int n;
    for (n = 0; n < dialog.nitems; n++)
	dialog.items[n].type->draw(dialog.items + n);
}

#define YSKIP 2
void ui_builddialog(CONST menuitem * item)
{
    int n = 2;
    int ypos;
    int width1 = 0;
    if (ui_nogui) {
	printf("dialog \"%s\"\n", item->shortname);
	return;
    }
    if (driver->gui_driver && driver->gui_driver->dialog) {
	driver->gui_driver->dialog(uih, item->shortname);
	return;
    }
    dialogvisible = 1;
    dialog.width = 0;
    dialog.height = 0;
    dialog.dialog = menu_getdialog(uih, item);
    dialog.item = item;
    for (n = 0; dialog.dialog[n].question != NULL; n++);
    n++;
    dialog.nitems = n;
    dialog.items =
	(struct dialogitem *) malloc(sizeof(struct dialogitem) *
				     dialog.nitems);
    dialog.mousereleased = 0;
    dialog.items[dialog.nitems - 1].type = &okdialog;
    for (n = 0; n < dialog.nitems; n++) {
	if (n < dialog.nitems - 1) {
	    switch (dialog.dialog[n].type) {
	    case DIALOG_STRING:
	    case DIALOG_KEYSTRING:
		dialog.items[n].type = &stringdialog;
		break;
	    case DIALOG_INT:
		dialog.items[n].type = &intdialog;
		break;
	    case DIALOG_IFILE:
	    case DIALOG_OFILE:
		dialog.items[n].type = &filedialog;
		break;
	    case DIALOG_FLOAT:
		dialog.items[n].type = &floatdialog;
		break;
	    case DIALOG_COORD:
		dialog.items[n].type = &coorddialog;
		break;
	    case DIALOG_CHOICE:
		dialog.items[n].type = &choicedialog;
		break;
	    default:
		printf("uidialog:unknown type!\n");
		exit(1);
	    }
	}
	dialog.items[n].dialog = dialog.dialog + n;
	dialog.items[n].type->build(dialog.items + n, dialog.dialog + n);
	dialog.height += dialog.items[n].height;
	if (width1 < dialog.items[n].width1)
	    width1 = dialog.items[n].width1;
	if (dialog.width < dialog.items[n].width)
	    dialog.width = dialog.items[n].width;
    }
    dialog.height += YSKIP * (n - 1);
    n = xtextwidth(uih->font, gettext("OK")) + xtextwidth(uih->font,
							  gettext
							  ("Cancel")) +
	xtextwidth(uih->font, gettext("Help")) + 10;
    if (dialog.width < n)
	dialog.width = n;
    dialog.half = dialog.width + 2 * BORDERWIDTH;
    dialog.width += 2 * BORDERWIDTH + width1;
    dialog.height += 2 * BORDERHEIGHT;
    dialog.current = 0;
    dialog.x = (uih->image->width - dialog.width) / 2;
    dialog.half += dialog.x;
    dialog.y = (uih->image->height - dialog.height) / 2;
    ypos = dialog.y + BORDERHEIGHT;
    for (n = 0; n < dialog.nitems; n++) {
	dialog.items[n].y = ypos;
	ypos += dialog.items[n].height + YSKIP;
    }
    dialog.window =
	uih_registerw(uih, ui_dialogpos, ui_dialogdraw, NULL, DRAWBORDER);
    uih->display = 1;
}

static void ui_dialogquestion(int succesfull)
{
    if (succesfull)
	ui_menuactivate(qitem, qparam);
    else
	menu_destroydialog(qitem, qparam, uih);
}

void ui_closedialog(int succesfull)
{
    int n = 2;
    if (dialogvisible) {
	dialogparam *param =
	    (dialogparam *) malloc(sizeof(dialogparam) * (dialog.nitems));
	dialogvisible = 0;
	uih_removew(uih, dialog.window);
	uih->display = 1;
	for (n = 0; n < dialog.nitems; n++) {
	    dialog.items[n].type->destroy(dialog.items + n, param + n);
	}
	free(dialog.items);
	if (succesfull) {
	    for (n = 0; n < dialog.nitems - 1; n++)
		if (dialog.dialog[n].type == DIALOG_OFILE
		    && xio_exist(param[n].dpath)) {
		    qparam = param;
		    qitem = dialog.item;
		    ui_buildyesno("File exist. Overwrite?",
				  ui_dialogquestion);
		    return;
		}
	    ui_menuactivate(dialog.item, param);
	} else
	    menu_destroydialog(dialog.item, param, uih);
    }
}

int ui_dialogmouse(int x, int y, int mousebuttons, int flags)
{
    int i;
    if (ui_mouseyesno(x, y, mousebuttons, flags))
	return 1;
    if (!dialogvisible)
	return 0;
    if (!dialog.mousereleased && (flags & MOUSE_RELEASE)) {
	dialog.mousereleased = 1;
	return 1;
    }
    if (!dialog.mousereleased && (flags & MOUSE_DRAG)) {
	return 1;
    }
    dialog.mousereleased = 1;
    if (dialog.mousegrab) {
	dialog.items[dialog.current].type->mouse(dialog.items +
						 dialog.current, x, y,
						 mousebuttons, flags);
	return 1;
    }
    if (dialog.x > x || dialog.y > y || dialog.x + dialog.width < x
	|| dialog.y + dialog.height < y) {
	if (flags & MOUSE_PRESS) {
	    ui_closedialog(0);
	    return 1;
	}
	dialog.items[dialog.current].type->mouse(dialog.items +
						 dialog.current, x, y, 0,
						 0);
	return 1;
    }
    for (i = dialog.nitems - 1; i >= 0; i--)
	if (dialog.items[i].y < y)
	    break;
    if (i == -1) {
	dialog.items[dialog.current].type->mouse(dialog.items +
						 dialog.current, x, y, 0,
						 0);
	return 1;
    }
    if (((flags & MOUSE_PRESS) || (flags & MOUSE_MOVE))
	&& dialog.current != i) {
	dialog.items[dialog.current].type->unselect(dialog.items +
						    dialog.current);
	dialog.current = i;
	uih->display = 1;
    }
    dialog.items[i].type->mouse(dialog.items + i, x, y, mousebuttons,
				flags);
    return 1;
}

int ui_dialogkeys(int key)
{
    if (ui_keyyesno(key))
	return 1;
    if (!dialogvisible)
	return 0;
    if (key == UIKEY_ESC) {
	ui_closedialog(0);
	return 1;
    }
    if (!dialog.items[dialog.current].type->
	key(dialog.items + dialog.current, key)) {
	switch (key) {
	case 'h':
	    ui_help(dialog.item->shortname);
	    return 1;
	case UIKEY_TAB:
	case UIKEY_DOWN:
	case UIKEY_RIGHT:
	    NEXT();
	    break;
	case UIKEY_UP:
	case UIKEY_LEFT:
	    PREV();
	    break;
	case 13:
	case '\n':
	    ui_closedialog(1);
	}
    }
    return 1;
}
