/* Input field widget implementation. */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "elinks.h"

#include "bfu/button.h"
#include "bfu/dialog.h"
#include "bfu/inpfield.h"
#include "bfu/inphist.h"
#include "bfu/msgbox.h"
#include "bfu/text.h"
#include "config/kbdbind.h"
#include "intl/charsets.h"
#include "intl/libintl.h"
#include "osdep/osdep.h"
#include "session/session.h"
#include "terminal/draw.h"
#include "terminal/kbd.h"
#include "terminal/mouse.h"
#include "terminal/terminal.h"
#include "terminal/window.h"
#include "util/color.h"
#include "util/memlist.h"
#include "util/memory.h"

#define INPUTFIELD_HEIGHT 1

#define INPUTFIELD_FLOATLABEL_PADDING 1

#define INPUTFIELD_FLOAT_SEPARATOR ":"
#define INPUTFIELD_FLOAT_SEPARATOR_LEN 1

void
add_dlg_field_do(struct dialog *dlg, enum widget_type type, char *label,
		 int min, int max, widget_handler_T *handler,
		 int datalen, void *data,
		 struct input_history *history, enum inpfield_flags flags)
{
	ELOG
	struct widget *widget = &dlg->widgets[dlg->number_of_widgets++];

	widget->type    = type;
	widget->text    = label;
	widget->handler = handler;
	widget->datalen = datalen;
	widget->data    = data;

	widget->info.field.history = history;
	widget->info.field.flags   = flags;
	widget->info.field.min     = min;
	widget->info.field.max     = max;
}

widget_handler_status_T
check_number(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	struct widget *widget = widget_data->widget;
	char *end;
	long l;

	errno = 0;
	l = strtol(widget_data->cdata, &end, 10);

	if (errno || !*widget_data->cdata || *end) {
		info_box(dlg_data->win->term, 0,
			 N_("Bad number"), ALIGN_CENTER,
			 N_("Number expected in field"));
		return EVENT_NOT_PROCESSED;
	}

	if (l < widget->info.field.min || l > widget->info.field.max) {
		info_box(dlg_data->win->term, MSGBOX_FREE_TEXT,
			 N_("Bad number"), ALIGN_CENTER,
			 msg_text(dlg_data->win->term,
				  N_("Number should be in the range from %d to %d."),
				  widget->info.field.min, widget->info.field.max));
		return EVENT_NOT_PROCESSED;
	}

	return EVENT_PROCESSED;
}

widget_handler_status_T
check_nonempty(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	unsigned char *p;

	for (p = (unsigned char *)widget_data->cdata; *p; p++)
		if (*p > ' ')
			return EVENT_PROCESSED;

	info_box(dlg_data->win->term, 0,
		 N_("Bad string"), ALIGN_CENTER,
		 N_("Empty string not allowed"));

	return EVENT_NOT_PROCESSED;
}

void
dlg_format_field(struct dialog_data *dlg_data,
		 struct widget_data *widget_data,
		 int x, int *y, int w, int *rw, format_align_T align, int format_only)
{
	ELOG
	struct terminal *term = dlg_data->win->term;
	static int max_label_width;
	static int *prev_y; /* Assert the uniqueness of y */	/* TODO: get rid of this !! --Zas */
	char *label = widget_data->widget->text;
	struct color_pair *text_color = NULL;
	int label_width = 0;
	int float_label = widget_data->widget->info.field.flags & (INPFIELD_FLOAT|INPFIELD_FLOAT2);

	if (label && *label && float_label) {
		label_width = strlen(label);
		if (prev_y == y) {
			int_lower_bound(&max_label_width, label_width);
		} else {
			max_label_width = label_width;
			prev_y = y;
		}

		/* Right align the floating label up against the
		 * input field */
		x += max_label_width - label_width;
		w -= max_label_width - label_width;
	}

	if (label && *label) {
		if (!format_only) text_color = get_bfu_color(term, "dialog.text");

		dlg_format_text_do(dlg_data, label, x, y, w, rw, text_color, ALIGN_LEFT, format_only);
	}

	/* XXX: We want the field and label on the same line if the terminal
	 * width allows it. */
	if (label && *label && float_label) {
		if (widget_data->widget->info.field.flags & INPFIELD_FLOAT) {
			(*y) -= INPUTFIELD_HEIGHT;
			dlg_format_text_do(dlg_data, INPUTFIELD_FLOAT_SEPARATOR,
					   x + label_width, y, w, rw,
					   text_color, ALIGN_LEFT, format_only);
			w -= INPUTFIELD_FLOAT_SEPARATOR_LEN + INPUTFIELD_FLOATLABEL_PADDING;
			x += INPUTFIELD_FLOAT_SEPARATOR_LEN + INPUTFIELD_FLOATLABEL_PADDING;
		}

		/* FIXME: Is 5 chars for input field enough? --jonas */
		if (label_width < w - 5) {
			(*y) -= INPUTFIELD_HEIGHT;
			w -= label_width;
			x += label_width;
		}
	}

	if (rw) int_lower_bound(rw, int_min(w, DIALOG_MIN_WIDTH));

	set_box(&widget_data->box, x, *y, w, INPUTFIELD_HEIGHT);

	(*y) += INPUTFIELD_HEIGHT;
}

static widget_handler_status_T
input_field_cancel(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	void (*fn)(void *) = (void (*)(void *))widget_data->widget->data;
	void *data = dlg_data->dlg->udata2;

	if (fn) fn(data);

	return cancel_dialog(dlg_data, widget_data);
}

static widget_handler_status_T
input_field_ok(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	void (*fn)(void *, char *) = (void (*)(void *, char *))widget_data->widget->data;
	void *data = dlg_data->dlg->udata2;
	char *text = dlg_data->widgets_data->cdata;

	if (check_dialog(dlg_data)) return EVENT_NOT_PROCESSED;

	if (widget_has_history(dlg_data->widgets_data))
		add_to_input_history(dlg_data->dlg->widgets->info.field.history,
				     text, 1);

	if (fn) fn(data, text);

	return cancel_dialog(dlg_data, widget_data);
}

void
input_field(struct terminal *term, struct memory_list *ml, int intl,
	    char *title,
	    char *text,
	    char *okbutton,
	    char *cancelbutton,
	    void *data, struct input_history *history, int l,
	    const char *def, int min, int max,
	    widget_handler_T *check,
	    void (*fn)(void *, char *),
	    void (*cancelfn)(void *))
{
	ELOG
	struct dialog *dlg;
	char *field;

	if (intl) {
		title = _(title, term);
		text = _(text, term);
		okbutton = _(okbutton, term);
		cancelbutton = _(cancelbutton, term);
	}

#define INPUT_WIDGETS_COUNT 3
	dlg = calloc_dialog(INPUT_WIDGETS_COUNT, l);
	if (!dlg) return;

	/* @field is automatically cleared by calloc() */
	field = get_dialog_offset(dlg, INPUT_WIDGETS_COUNT);

	if (def) {
		int defsize = strlen(def) + 1;

		memcpy(field, def, (defsize > l) ? l - 1 : defsize);
	}

	dlg->title = title;
	dlg->layouter = generic_dialog_layouter;
	dlg->layout.fit_datalen = 1;
	dlg->udata2 = data;

	add_dlg_field(dlg, text, min, max, check, l, field, history);

	add_dlg_button(dlg, okbutton, B_ENTER, input_field_ok, (void *)fn);
	add_dlg_button(dlg, cancelbutton, B_ESC, input_field_cancel, (void *)cancelfn);

	add_dlg_end(dlg, INPUT_WIDGETS_COUNT);

	add_to_ml(&ml, (void *) dlg, (void *) NULL);
	do_dialog(term, dlg, ml);
#undef INPUT_WIDGETS_COUNT
}

static void
password_field(struct terminal *term, struct memory_list *ml, int intl,
	    char *title,
	    char *text,
	    char *okbutton,
	    char *cancelbutton,
	    void *data, struct input_history *history, int l,
	    const char *def, int min, int max,
	    widget_handler_T *check,
	    void (*fn)(void *, char *),
	    void (*cancelfn)(void *))
{
	ELOG
	struct dialog *dlg;
	char *field;

	if (intl) {
		title = _(title, term);
		text = _(text, term);
		okbutton = _(okbutton, term);
		cancelbutton = _(cancelbutton, term);
	}

#define INPUT_WIDGETS_COUNT 3
	dlg = calloc_dialog(INPUT_WIDGETS_COUNT, l);
	if (!dlg) return;

	/* @field is automatically cleared by calloc() */
	field = get_dialog_offset(dlg, INPUT_WIDGETS_COUNT);

	if (def) {
		int defsize = strlen(def) + 1;

		memcpy(field, def, (defsize > l) ? l - 1 : defsize);
	}

	dlg->title = title;
	dlg->layouter = generic_dialog_layouter;
	dlg->layout.fit_datalen = 1;
	dlg->udata2 = data;

	add_dlg_field_pass(dlg, text, min, max, check, l, field);

	add_dlg_button(dlg, okbutton, B_ENTER, input_field_ok, (void *)fn);
	add_dlg_button(dlg, cancelbutton, B_ESC, input_field_cancel, (void *)cancelfn);

	add_dlg_end(dlg, INPUT_WIDGETS_COUNT);

	add_to_ml(&ml, (void *) dlg, (void *) NULL);
	do_dialog(term, dlg, ml);
#undef INPUT_WIDGETS_COUNT
}

void
input_dialog(struct terminal *term, struct memory_list *ml,
	     char *title,
	     char *text,
	     void *data, struct input_history *history, int l,
	     const char *def, int min, int max,
	     widget_handler_T *check,
	     void (*fn)(void *, char *),
	     void (*cancelfn)(void *))
{
	ELOG
	/* [gettext_accelerator_context(input_dialog)] */
	input_field(term, ml, 1, title, text, N_("~OK"), N_("~Cancel"),
		    data, history, l,
		    def, min, max,
		    check, fn, cancelfn);
}

void
password_dialog(struct terminal *term, struct memory_list *ml,
	     char *title,
	     char *text,
	     void *data, struct input_history *history, int l,
	     const char *def, int min, int max,
	     widget_handler_T *check,
	     void (*fn)(void *, char *),
	     void (*cancelfn)(void *))
{
	ELOG
	/* [gettext_accelerator_context(input_dialog)] */
	password_field(term, ml, 1, title, text, N_("~OK"), N_("~Cancel"),
		    data, history, l,
		    def, min, max,
		    check, fn, cancelfn);
}

static widget_handler_status_T
display_field_do(struct dialog_data *dlg_data, struct widget_data *widget_data,
		 int hide)
{
	ELOG
	struct terminal *term = dlg_data->win->term;
	struct color_pair *color;
	int sel = is_selected_widget(dlg_data, widget_data);
#ifdef CONFIG_UTF8
	int len = 0, left = 0;
#endif /* CONFIG_UTF8 */

#ifdef CONFIG_UTF8
	if (term->utf8_cp) {
		char *t = widget_data->cdata;
		int p = widget_data->info.field.cpos;

		len = utf8_ptr2cells(t, &t[p]);
		int_bounds(&left, len - widget_data->box.width + 1, len);
		int_lower_bound(&left, 0);
		widget_data->info.field.vpos = utf8_cells2bytes(t, left, NULL);
	} else
#endif /* CONFIG_UTF8 */
	{
		int_bounds(&widget_data->info.field.vpos,
		   widget_data->info.field.cpos - widget_data->box.width + 1,
		   widget_data->info.field.cpos);
		int_lower_bound(&widget_data->info.field.vpos, 0);
	}

	color = get_bfu_color(term, "dialog.field");
	if (color)
		draw_box(term, &widget_data->box, ' ', 0, color);

	color = get_bfu_color(term, "dialog.field-text");
	if (color) {
		char *text = widget_data->cdata + widget_data->info.field.vpos;
		int len, w;

#ifdef CONFIG_UTF8
		if (term->utf8_cp && !hide)
			len = utf8_ptr2cells(text, NULL);
		else if (term->utf8_cp)
			len = utf8_ptr2chars(text, NULL);
		else
#endif /* CONFIG_UTF8 */
			len = strlen(text);
		w = int_min(len, widget_data->box.width);

		if (!hide) {
#ifdef CONFIG_UTF8
			if (term->utf8_cp)
				w = utf8_cells2bytes(text, w, NULL);
#endif /* CONFIG_UTF8 */
			draw_dlg_text(dlg_data, widget_data->box.x, widget_data->box.y,
				  text, w, 0, color);
		} else {
			struct el_box box;

			copy_box(&box, &widget_data->box);
			box.width = w;

			draw_box(term, &box, '*', 0, color);
		}
	}

	if (sel) {
		int x;

#ifdef CONFIG_UTF8
		if (term->utf8_cp)
			x = widget_data->box.x + len - left;
		else
#endif /* CONFIG_UTF8 */
			x = widget_data->box.x + widget_data->info.field.cpos - widget_data->info.field.vpos;

		set_cursor(term, x, widget_data->box.y, 0);
		set_window_ptr(dlg_data->win, widget_data->box.x, widget_data->box.y);
	}

	return EVENT_PROCESSED;
}

static widget_handler_status_T
display_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	return display_field_do(dlg_data, widget_data, 0);
}

static widget_handler_status_T
display_field_pass(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	return display_field_do(dlg_data, widget_data, 1);
}

static widget_handler_status_T
init_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	if (widget_has_history(widget_data)) {
		struct input_history_entry *entry;

		foreach (entry, widget_data->widget->info.field.history->entries) {
			int datalen = strlen(entry->data);
			struct input_history_entry *new_entry;

			/* One byte is reserved in struct input_history_entry. */
			new_entry = (struct input_history_entry *)mem_alloc(sizeof(*new_entry) + datalen);
			if (!new_entry) continue;

			memcpy(new_entry->data, entry->data, datalen + 1);
			add_to_list(widget_data->info.field.history, new_entry);
		}
	}

	widget_data->info.field.cpos = strlen(widget_data->cdata);
	return EVENT_PROCESSED;
}

static int
field_prev_history(struct widget_data *widget_data)
{
	ELOG
	if (widget_has_history(widget_data)
	    && (void *) widget_data->info.field.cur_hist->prev != &widget_data->info.field.history) {
		widget_data->info.field.cur_hist = widget_data->info.field.cur_hist->prev;
		dlg_set_history(widget_data);
		return 1;
	}
	return 0;
}

static int
field_next_history(struct widget_data *widget_data)
{
	ELOG
	if (widget_has_history(widget_data)
	    && (void *) widget_data->info.field.cur_hist != &widget_data->info.field.history) {
		widget_data->info.field.cur_hist = widget_data->info.field.cur_hist->next;
		dlg_set_history(widget_data);
		return 1;
	}
	return 0;
}

static widget_handler_status_T
mouse_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	struct term_event *ev = dlg_data->term_event;

	if (!check_mouse_position(ev, &widget_data->box))
		return EVENT_NOT_PROCESSED;

	/* Handle navigation through history (if any) using up/down mouse wheel */
	switch (get_mouse_button(ev)) {
	case B_WHEEL_UP:
		if (check_mouse_action(ev, B_DOWN)) {
			if (field_prev_history(widget_data)) {
				select_widget(dlg_data, widget_data);
				return EVENT_PROCESSED;
			}
		}
		return EVENT_NOT_PROCESSED;

	case B_WHEEL_DOWN:
		if (check_mouse_action(ev, B_DOWN)) {
			if (field_next_history(widget_data)) {
				select_widget(dlg_data, widget_data);
				return EVENT_PROCESSED;
			}
		}
		return EVENT_NOT_PROCESSED;
	}

	/* Place text cursor at mouse position and focus the widget. */
	widget_data->info.field.cpos = widget_data->info.field.vpos
				     + ev->info.mouse.x - widget_data->box.x;
	int_upper_bound(&widget_data->info.field.cpos, strlen(widget_data->cdata));

	select_widget(dlg_data, widget_data);
	return EVENT_PROCESSED;
}

static widget_handler_status_T
kbd_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	struct window *win = dlg_data->win;
	struct terminal *term = win->term;
	struct term_event *ev = dlg_data->term_event;
	action_id_T action_id;

	action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
	if (action_id != -1
	    && !action_is_anonymous_safe(KEYMAP_EDIT, action_id)
	    && get_cmd_opt_bool("anonymous"))
		return EVENT_NOT_PROCESSED;

	switch (action_id) {
		case ACT_EDIT_UP:
			if (!widget_has_history(widget_data))
				return EVENT_NOT_PROCESSED;

			if (field_prev_history(widget_data)) {
				goto display_field;
			}
			break;

		case ACT_EDIT_DOWN:
			if (!widget_has_history(widget_data))
				return EVENT_NOT_PROCESSED;

			if (field_next_history(widget_data)) {
				goto display_field;
			}
			break;

		case ACT_EDIT_RIGHT:
			if (widget_data->info.field.cpos < strlen(widget_data->cdata)) {
#ifdef CONFIG_UTF8
				if (term->utf8_cp) {
					char *next = widget_data->cdata + widget_data->info.field.cpos;
					char *end = strchr(next, '\0');

					utf8_to_unicode(&next, end);
					widget_data->info.field.cpos = (int)(next - widget_data->cdata);
				} else
#endif /* CONFIG_UTF8 */
				{
					widget_data->info.field.cpos++;
				}
			}
			goto display_field;

		case ACT_EDIT_LEFT:
			if (widget_data->info.field.cpos > 0)
				widget_data->info.field.cpos--;
#ifdef CONFIG_UTF8
			if (widget_data->info.field.cpos && term->utf8_cp) {
				char *t = widget_data->cdata;
				char *t2 = t;
				int p = widget_data->info.field.cpos;
				unsigned char tmp = t[p];

				t[p] = '\0';
				strlen_utf8(&t2);
				t[p] = tmp;
				widget_data->info.field.cpos = (int)(t2 - t);

			}
#endif /* CONFIG_UTF8 */
			goto display_field;

		case ACT_EDIT_HOME:
			widget_data->info.field.cpos = 0;
			goto display_field;

		case ACT_EDIT_END:
			widget_data->info.field.cpos = strlen(widget_data->cdata);
			goto display_field;

		case ACT_EDIT_BACKSPACE:
#ifdef CONFIG_UTF8
			if (widget_data->info.field.cpos && term->utf8_cp) {
				/* XXX: stolen from src/viewer/text/form.c */
				/* FIXME: This isn't nice. We remove last byte
				 *        from UTF-8 character to detect
				 *        character before it. */
				char *text = widget_data->cdata;
				char *end = widget_data->cdata + widget_data->info.field.cpos - 1;
				unicode_val_T data;
				int old = widget_data->info.field.cpos;

				while(1) {
					data = utf8_to_unicode(&text, end);
					if (data == UCS_NO_CHAR)
						break;
				}

				widget_data->info.field.cpos = (int)(text - widget_data->cdata);
				if (old != widget_data->info.field.cpos) {
					int length;

					text = widget_data->cdata;
					length = strlen(text + old) + 1;
					memmove(text + widget_data->info.field.cpos, text + old, length);
				}
				goto display_field;
			}
#endif /* CONFIG_UTF8 */
			if (widget_data->info.field.cpos) {
				memmove(widget_data->cdata + widget_data->info.field.cpos - 1,
					widget_data->cdata + widget_data->info.field.cpos,
					strlen(widget_data->cdata) - widget_data->info.field.cpos + 1);
				widget_data->info.field.cpos--;
			}
			goto display_field;

		case ACT_EDIT_DELETE:
			{
				int cdata_len = strlen(widget_data->cdata);

				if (widget_data->info.field.cpos >= cdata_len) goto display_field;

#ifdef CONFIG_UTF8
				if (term->utf8_cp) {
					char *end = widget_data->cdata + cdata_len;
					char *text = widget_data->cdata + widget_data->info.field.cpos;
					char *old = text;

					utf8_to_unicode(&text, end);
					if (old != text) {
						memmove(old, text,
								(int)(end - text) + 1);
					}
					goto display_field;
				}
#endif /* CONFIG_UTF8 */
				memmove(widget_data->cdata + widget_data->info.field.cpos,
					widget_data->cdata + widget_data->info.field.cpos + 1,
					cdata_len - widget_data->info.field.cpos + 1);
				goto display_field;
			}

		case ACT_EDIT_KILL_TO_BOL:
			memmove(widget_data->cdata,
					widget_data->cdata + widget_data->info.field.cpos,
					strlen(widget_data->cdata + widget_data->info.field.cpos) + 1);
			widget_data->info.field.cpos = 0;
			goto display_field;

		case ACT_EDIT_KILL_TO_EOL:
			widget_data->cdata[widget_data->info.field.cpos] = 0;
			goto display_field;

		case ACT_EDIT_KILL_WORD_BACK:
			{
				int cdata_len = strlen(widget_data->cdata);
				int start = widget_data->info.field.cpos;

				while (start > 0 && isspace((unsigned char)widget_data->cdata[start - 1]))
					--start;
				while (start > 0 && !isspace((unsigned char)widget_data->cdata[start - 1]))
					--start;

				memmove(widget_data->cdata + start,
					widget_data->cdata + widget_data->info.field.cpos,
					cdata_len - widget_data->info.field.cpos + 1);

				widget_data->info.field.cpos = start;

				goto display_field;
			}

		case ACT_EDIT_MOVE_BACKWARD_WORD:
			while (widget_data->info.field.cpos > 0 && isspace((unsigned char)widget_data->cdata[widget_data->info.field.cpos - 1]))
				--widget_data->info.field.cpos;
			while (widget_data->info.field.cpos > 0 && !isspace((unsigned char)widget_data->cdata[widget_data->info.field.cpos - 1]))
				--widget_data->info.field.cpos;

			goto display_field;

		case ACT_EDIT_MOVE_FORWARD_WORD:
			while (isspace((unsigned char)widget_data->cdata[widget_data->info.field.cpos]))
				++widget_data->info.field.cpos;
			while (widget_data->cdata[widget_data->info.field.cpos] && !isspace((unsigned char)widget_data->cdata[widget_data->info.field.cpos]))
				++widget_data->info.field.cpos;
			while (isspace((unsigned char)widget_data->cdata[widget_data->info.field.cpos]))
				++widget_data->info.field.cpos;

			goto display_field;

		case ACT_EDIT_COPY_CLIPBOARD:
			/* Copy to clipboard */
			set_clipboard_text(widget_data->cdata);
			return EVENT_PROCESSED;

		case ACT_EDIT_CUT_CLIPBOARD:
			/* Cut to clipboard */
			set_clipboard_text(widget_data->cdata);
			widget_data->cdata[0] = 0;
			widget_data->info.field.cpos = 0;
			goto display_field;

		case ACT_EDIT_PASTE_CLIPBOARD:
			{
				/* Paste from clipboard */
				char *clipboard = get_clipboard_text();

				if (!clipboard) goto display_field;

				safe_strncpy(widget_data->cdata, clipboard, widget_data->widget->datalen);
				widget_data->info.field.cpos = strlen(widget_data->cdata);
				mem_free(clipboard);
				goto display_field;
			}

		case ACT_EDIT_AUTO_COMPLETE:
			if (!widget_has_history(widget_data))
				return EVENT_NOT_PROCESSED;

			do_tab_compl(dlg_data, &widget_data->info.field.history);
			goto display_field;

		case ACT_EDIT_AUTO_COMPLETE_FILE:
			if (!widget_has_history(widget_data))
				return EVENT_NOT_PROCESSED;

			do_tab_compl_file(dlg_data, &widget_data->info.field.history);
			goto display_field;

		case ACT_EDIT_AUTO_COMPLETE_UNAMBIGUOUS:
			if (!widget_has_history(widget_data))
				return EVENT_NOT_PROCESSED;

			do_tab_compl_unambiguous(dlg_data, &widget_data->info.field.history);
			goto display_field;

		case ACT_EDIT_REDRAW:
			redraw_terminal_cls(term);
			return EVENT_PROCESSED;

		default:
			if (check_kbd_textinput_key(ev)) {
				char *text = widget_data->cdata;
				int textlen = strlen(text);
#ifndef CONFIG_UTF8
				/* Both get_kbd_key(ev) and @text
				 * are in the terminal's charset.  */
				const int inslen = 1;
#else  /* CONFIG_UTF8 */
				const char *ins;
				int inslen;

				/* get_kbd_key(ev) is UCS-4, and @text
				 * is in the terminal's charset.  */
				ins = u2cp_no_nbsp(get_kbd_key(ev),
						   get_terminal_codepage(term));
				inslen = strlen(ins);
#endif /* CONFIG_UTF8 */

				if (textlen >= widget_data->widget->datalen - inslen)
					goto display_field;

				/* Shift to position of the cursor */
				textlen -= widget_data->info.field.cpos;
				text	+= widget_data->info.field.cpos;

				memmove(text + inslen, text, textlen + 1);
#ifdef CONFIG_UTF8
				memcpy(text, ins, inslen);
#else  /* !CONFIG_UTF8 */
				*text = get_kbd_key(ev);
#endif /* !CONFIG_UTF8 */
				widget_data->info.field.cpos += inslen;
				goto display_field;
			}
	}
	return EVENT_NOT_PROCESSED;

display_field:
	display_widget(dlg_data, widget_data);
	redraw_windows(REDRAW_IN_FRONT_OF_WINDOW, dlg_data->win);
	return EVENT_PROCESSED;
}


static widget_handler_status_T
clear_field(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
	ELOG
	widget_data->info.field.cpos = 0;

	if (widget_data->widget->datalen)
		memset(widget_data->cdata, 0, widget_data->widget->datalen);

	return EVENT_PROCESSED;
}

const struct widget_ops field_ops = {
	display_field,
	init_field,
	mouse_field,
	kbd_field,
	NULL,
	clear_field,
};

const struct widget_ops field_pass_ops = {
	display_field_pass,
	init_field,
	mouse_field,
	kbd_field,
	NULL,
	clear_field,
};


/* Input lines */

static void
input_line_layouter(struct dialog_data *dlg_data)
{
	ELOG
	struct input_line *input_line = (struct input_line *)dlg_data->dlg->udata;
	struct session *ses = input_line->ses;
	struct window *win = dlg_data->win;
	int y = win->term->height - 1
		- ses->status.show_status_bar
		- ses->status.show_tabs_bar;

	dlg_format_field(dlg_data, dlg_data->widgets_data, 0,
			 &y, win->term->width, NULL, ALIGN_LEFT, 0);
}

static widget_handler_status_T
input_line_event_handler(struct dialog_data *dlg_data)
{
	ELOG
	struct input_line *input_line = (struct input_line *)dlg_data->dlg->udata;
	input_line_handler_T handler = input_line->handler;
	action_id_T action_id;
	struct widget_data *widget_data = dlg_data->widgets_data;
	struct term_event *ev = dlg_data->term_event;

	/* Noodle time */
	switch (ev->ev) {
	case EVENT_KBD:
		action_id = kbd_action(KEYMAP_EDIT, ev, NULL);

		/* Handle some basic actions such as quiting for empty buffers */
		switch (action_id) {
		case ACT_EDIT_ENTER:
		case ACT_EDIT_NEXT_ITEM:
		case ACT_EDIT_PREVIOUS_ITEM:
			if (widget_has_history(widget_data))
				add_to_input_history(widget_data->widget->info.field.history,
						     input_line->buffer, 1);
			break;

		case ACT_EDIT_BACKSPACE:
			if (!*input_line->buffer)
				goto cancel_input_line;
			break;

		case ACT_EDIT_CANCEL:
			goto cancel_input_line;

		default:
			break;
		}

		/* First let the input field do its business */
		kbd_field(dlg_data, widget_data);
		break;

	case EVENT_MOUSE:
#ifdef CONFIG_MOUSE
		if (ev->info.mouse.y != dlg_data->win->y) {
			delete_window_ev(dlg_data->win, ev);
			return EVENT_PROCESSED;
		}
#endif /* CONFIG_MOUSE */
		return EVENT_NOT_PROCESSED;

	case EVENT_REDRAW:
		/* Try to catch the redraw event initiated by the history
		 * completion and only respond if something was actually
		 * updated. Meaning we have new data in the line buffer that
		 * should be propagated to the line handler. */
		if (!widget_has_history(widget_data)
		    || widget_data->info.field.cpos <= 0
		    || widget_data->info.field.cpos <= strlen(input_line->buffer))
			return EVENT_NOT_PROCESSED;

		/* Fall thru */

	case EVENT_RESIZE:
		action_id = ACT_EDIT_REDRAW;
		break;

	default:
		return EVENT_NOT_PROCESSED;
	}

	update_dialog_data(dlg_data);

send_action_to_handler:
	/* Then pass it on to the specialized handler */
	switch (handler(input_line, action_id)) {
	case INPUT_LINE_CANCEL:
cancel_input_line:
		cancel_dialog(dlg_data, widget_data);
		break;

	case INPUT_LINE_REWIND:
		/* This is stolen kbd_field() handling for ACT_EDIT_BACKSPACE */
		memmove(widget_data->cdata + widget_data->info.field.cpos - 1,
			widget_data->cdata + widget_data->info.field.cpos,
			strlen(widget_data->cdata) - widget_data->info.field.cpos + 1);
		widget_data->info.field.cpos--;

		update_dialog_data(dlg_data);

		/* Set action_id to -2 to signal to the handler that it should
		 * not report errors or take any action except to search. */
		action_id = -2;
		goto send_action_to_handler;

	case INPUT_LINE_PROCEED:
		break;
	}

	/* Hack: We want our caller to perform its redrawing routine,
	 * even if we did process the event here. */
	if (action_id == ACT_EDIT_REDRAW) return EVENT_NOT_PROCESSED;

	/* Completely bypass any further dialog event handling */
	return EVENT_PROCESSED;
}

void
input_field_line(struct session *ses, char *prompt, void *data,
		 struct input_history *history, input_line_handler_T handler)
{
	ELOG
	struct dialog *dlg;
	char *buffer;
	struct input_line *input_line;

	assert(ses);

	dlg = calloc_dialog(INPUT_LINE_WIDGETS, sizeof(*input_line));
	if (!dlg) return;

	input_line = (struct input_line *) get_dialog_offset(dlg, INPUT_LINE_WIDGETS);
	input_line->ses = ses;
	input_line->data = data;
	input_line->handler = handler;
	buffer = input_line->buffer;

	dlg->handle_event = input_line_event_handler;
	dlg->layouter = input_line_layouter;
	dlg->layout.only_widgets = 1;
	dlg->udata = input_line;

	add_dlg_field_float2(dlg, prompt, 0, 0, NULL, INPUT_LINE_BUFFER_SIZE,
			     buffer, history);

	do_dialog(ses->tab->term, dlg, getml(dlg, (void *) NULL));
}
