/* textinput.c - draw an editable text widget
   Copyright (C) 1996-2000 Paul Sheer

   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., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
 */


#include <config.h>
#include <stdio.h>
#include <my_string.h>
#include <stdlib.h>
#include <stdarg.h>

#include <X11/Intrinsic.h>
#include <X11/Xatom.h>
#include "lkeysym.h"

#include "stringtools.h"
#include "app_glob.c"

#include "coolwidget.h"
#include "coollocal.h"

#include "edit.h"
#include "editcmddef.h"
#include "mousemark.h"

#include "mad.h"


int eh_textinput (CWidget * w, XEvent * xevent, CEvent * cwevent);
void input_mouse_mark (CWidget * w, XEvent * event, CEvent * ce);

extern struct look *look;


/* {{{  history stuff: draws a history of inputs a widget of the same ident */


#define MAX_HIST_LEN 64
#define MAX_HIST_WIDGETS 128

struct textinput_history {
    char ident[32];
    int text_len;	/* length of a newline separate list of all 'input' strings */
    int last;
    char *input[MAX_HIST_LEN];
};

static struct textinput_history *history_widgets[MAX_HIST_WIDGETS] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
static int last = 0;

static void add_to_history (struct textinput_history *h, const char *text, int allow_blank_lines)
{
    int i, j;
    char *t, *p;
    if (!text)
	return;
    if (!*text && !allow_blank_lines)
	return;
    t = (char *) strdup (text);
    if ((p = strchr (t, '\n')))
	*p = '\0';		/* no newlines allowed in the history */
    if (h->last)
	for (i = h->last - 1; i >= 0; i--)
	    if (!strcmp (h->input[i], text)) {	/* avoid adding duplicates */
		text = h->input[i];
		if (i < h->last - 1)
		    for (j = i; j < h->last - 1; j++)	/* shift all entries up one place */
			h->input[j] = h->input[j + 1];
		h->input[h->last - 1] = (char *) text;	/* move new entry to top */
		free (t);
		return;
	    }
    h->input[h->last++] = t;
    if (h->last == MAX_HIST_LEN) {
	h->text_len -= strlen (h->input[0]) + 1;
	free (h->input[0]);
	memmove (h->input, h->input + 1, (MAX_HIST_LEN - 1) * sizeof (char *));		/* shift up to make space */
	h->last--;
    }
    h->text_len += strlen (text) + 1;	/* maintain total list length */
}

static void add_to_widget_history (const char *ident, const char *text)
{
    int i;
    int allow_blank_lines = 0;
    allow_blank_lines = (strchr (ident, '+') != 0);
    for (i = 0; i < MAX_HIST_WIDGETS; i++) {
	if (!history_widgets[i])
	    break;
	if (!strcmp (history_widgets[i]->ident, ident)) {
	    add_to_history (history_widgets[i], text, allow_blank_lines);
	    return;
	}
    }
/* create a new history */
    history_widgets[last] = CMalloc (sizeof (struct textinput_history));
    memset (history_widgets[last], 0, sizeof (struct textinput_history));
    strcpy (history_widgets[last]->ident, ident);
    add_to_history (history_widgets[last], text, allow_blank_lines);
    last++;

    if (last == MAX_HIST_WIDGETS) {	/* shift up one */
	for (i=0;i<history_widgets[0]->last;i++) {
	    if (!history_widgets[0]->input[i])
		break;
	    free (history_widgets[0]->input[i]);
	}
	free (history_widgets[0]);
	memmove (history_widgets, history_widgets + 1, (MAX_HIST_WIDGETS - 1) * sizeof (struct textinput_history *));
	last--;
    }
}

void CAddToTextInputHistory (const char *ident, const char *text)
{
    add_to_widget_history (ident, text);
}

/*
   Returns a newline separate list of all 'input' in the history.
   Result must be free'd .
 */
static char *get_history_list (const char *ident, int reverse, int *num_lines)
{
    char *s, *r;
    int i, j;
    for (i = 0; i < MAX_HIST_WIDGETS; i++) {
	if (!history_widgets[i])
	    break;
	if (!strcmp (history_widgets[i]->ident, ident)) {
	    r = s = CMalloc (history_widgets[i]->text_len);
	    if (!(*num_lines = history_widgets[i]->last))
		break;
	    if (reverse) {
		for (j = 0; j < history_widgets[i]->last; j++) {
		    strcpy (s, history_widgets[i]->input[j]);
		    s += strlen (s);
		    *s++ = '\n';
		}
	    } else {
		for (j = history_widgets[i]->last - 1; j >= 0; j--) {	/* most recent at top */
		    strcpy (s, history_widgets[i]->input[j]);
		    s += strlen (s);
		    *s++ = '\n';
		}
	    }
	    *(--s) = 0;
	    return r;
	}
    }
    *num_lines = 1;
    return (char *) strdup ("");
}

/* result must not be free'd */
/* returns the last text inputted in the input widget named ident */
char *CLastInput (const char *ident)
{
    int i;
    for (i = 0; i < MAX_HIST_WIDGETS; i++) {
	if (!history_widgets[i])
	    break;
	if (!strcmp (history_widgets[i]->ident, ident)) {
	    if (!history_widgets[i]->last)
		return "";
	    return history_widgets[i]->input[history_widgets[i]->last - 1];
	}
    }
    return "";
}

#define HISTORY_LINES 10

static int clip_lines (int lines, int num_lines)
{
    if (lines > num_lines)
	lines = num_lines;
    if (lines > HISTORY_LINES)
	lines = HISTORY_LINES;
    if (lines < 1)
	lines = 1;
    return lines;
}

/* gets a list of all input histories and all widget for use in saving
   to an options file (if you want to save the state of an application, 
   for example. result must be free'd */
char *get_all_lists (void)
{
    char *s, *r;
    int i, j;
    int tot_len;

    tot_len = 0;

/* calc length to alloc */
    for (i = 0; i < MAX_HIST_WIDGETS; i++) {
	if (!history_widgets[i])
	    break;
	tot_len += strlen (history_widgets[i]->ident) + 1;
	tot_len += history_widgets[i]->text_len + history_widgets[i]->last;
    }

    r = s = CMalloc (tot_len + 1);

    for (i = 0; i < MAX_HIST_WIDGETS; i++) {
	if (!history_widgets[i])
	    break;
	strcpy (s, history_widgets[i]->ident);
	s += strlen (s);
	*s++ = '\n';
	for (j = 0; j < history_widgets[i]->last; j++) {
	    *s++ = '\t';
	    strcpy (s, history_widgets[i]->input[j]);
	    s += strlen (s);
	    *s++ = '\n';
	}
    }
    *s = 0;
    return r;
}

void free_all_lists (void)
{
    int i, j;
    for (i = 0; i < MAX_HIST_WIDGETS; i++) {
	if (!history_widgets[i])
	    break;
	for (j = 0; j < history_widgets[i]->last; j++) {
	    if (!history_widgets[i]->input[j])
		break;
	    free (history_widgets[i]->input[j]);
	    history_widgets[i]->input[j] = 0;
	}
	free (history_widgets[i]);
	history_widgets[i] = 0;
    }
}

void put_all_lists (char *list)
{
    char *p;
    char ident[33];
    char input[1024];

    ident[32] = 0;
    input[1023] = 0;

    if (!list)
	return;
    while (*list) {
	p = strchr (list, '\n');
	if (!p)
	    return;
	*p++ = 0;
	strncpy (ident, list, 32);
	list = p;
	while (*list == '\t') {
	    list++;
	    p = strchr (list, '\n');
	    if (!p)
		return;
	    *p++ = 0;
	    strncpy (input, list, 1023);
	    list = p;
	    add_to_widget_history (ident, input);
	}
    }
}

static char *draw_text_input_history (CWidget * text_input)
{
    char *p, *r;
    int num_lines;
    CWidget *w;
    int x, y;
    int columns, lines;

    if (text_input->options & TEXTINPUT_PASSWORD)	/* password lines, not allowed a history! */
	return 0;

    x = text_input->x;
    CPushFont ("editor", 0);
    columns = (text_input->width - WIDGET_SPACING * 3 - 4 - 6 - 20) / FONT_MEAN_WIDTH;
    w = CWidgetOfWindow (text_input->parentid);

    if (!w) {
    	CPopFont ();
	return 0;
    }
    if (text_input->y > w->height / 2) {
	p = get_history_list (text_input->ident, 1, &num_lines);
	lines = (text_input->y - 2 - WIDGET_SPACING * 2 - 4 - 6) / FONT_PIX_PER_LINE;
	lines = clip_lines (lines, num_lines);
	y = text_input->y - lines * FONT_PIX_PER_LINE - WIDGET_SPACING * 2 - 4 - 6;
	r = CTrivialSelectionDialog (w->winid, x, y, columns, lines, p, max (0, num_lines - lines), num_lines - 1);
    } else {
	p = get_history_list (text_input->ident, 0, &num_lines);
	lines = (w->height - text_input->height - text_input->y - 2 - WIDGET_SPACING * 2 - 4 - 6) / FONT_PIX_PER_LINE;
	lines = clip_lines (lines, num_lines);
	y = text_input->y + text_input->height;
	r = CTrivialSelectionDialog (w->winid, x, y, columns, lines, p, 0, 0);
    }
    free (p);
    CPopFont ();
    return r;
}

static char *draw_selection_history (CWidget * text_input)
{
    CWidget *w;
    char *p, *r;
    int x, y;
    long len;
    int columns, lines;
    x = text_input->x;
    CPushFont ("editor", 0);
    columns = (text_input->width - WIDGET_SPACING * 3 - 4 - 6 - 20) / FONT_MEAN_WIDTH;
    w = CWidgetOfWindow (text_input->parentid);
    if (!w) {
	CPopFont ();
	return 0;
    }
    if (text_input->y > w->height / 2) {
	lines = (text_input->y - 2 - WIDGET_SPACING * 2 - 4 - 6) / FONT_PIX_PER_LINE;
	y = text_input->y - lines * FONT_PIX_PER_LINE - WIDGET_SPACING * 2 - 4 - 6;
    } else {
	lines =
	    (w->height - text_input->height - text_input->y - 2 - WIDGET_SPACING * 2 - 4 -
	     6) / FONT_PIX_PER_LINE;
	y = text_input->y + text_input->height;
    }
    p = edit_get_text_from_selection_history (w->winid, x, y, columns, lines, &len);
    if (p) {
	r = (char *) CMalloc (len + 1);
	strncpy (r, p, len + 1);
    } else {
	r = NULL;
    }
    CPopFont ();
    return r;
}

static char *draw_selection_completion (CWidget * text_input)
{
    CWidget *w;
    char *r;
    int x, y;
    int columns, lines;
    x = text_input->x;
    CPushFont ("editor", 0);
    columns = (text_input->width - WIDGET_SPACING * 3 - 4 - 6 - 20) / FONT_MEAN_WIDTH;
    w = CWidgetOfWindow (text_input->parentid);
    if (!w) {
	CPopFont ();
	return 0;
    }
    if (text_input->y > w->height / 2) {
	lines = (text_input->y - 2 - WIDGET_SPACING * 2 - 4 - 6) / FONT_PIX_PER_LINE;
	y = text_input->y - lines * FONT_PIX_PER_LINE - WIDGET_SPACING * 2 - 4 - 6;
    } else {
	lines =
	    (w->height - text_input->height - text_input->y - 2 - WIDGET_SPACING * 2 - 4 -
	     6) / FONT_PIX_PER_LINE;
	y = text_input->y + text_input->height;
    }
    r = user_file_list_complete (w->winid, x, y, columns, lines, text_input->text);
    CPopFont ();
    return r;
}

#if 0
wchar_t *mbstowcs_dup (unsigned char *s);
int wchar_t_strlen (wchar_t * s);

int wchar_t_columns (char *t, int x)
{
    wchar_t *s;
    t = (char *) strdup ((char *) t);
    t[x] = '\0';
    s = mbstowcs_dup ((unsigned char *) t);
    x = wchar_t_strlen (s);
    free (t);
    free (s);
    return x;
}
#endif

void render_passwordinput (CWidget * wdt)
{
    int wc, k, l, w = wdt->width, h = wdt->height;
    Window win;
    char *password;

    CPushFont ("editor", 0);

    win = wdt->winid;

    CSetBackgroundColor (COLOR_WHITE);
    CSetColor (COLOR_BLACK);
    password = (char *) strdup (wdt->text);
    memset (password, '*', strlen (wdt->text));
    CImageString (win, FONT_OFFSET_X + 3 + TEXTINPUT_RELIEF,
		      FONT_OFFSET_Y + 3 + TEXTINPUT_RELIEF,
		      password);
    CSetColor (COLOR_WHITE);
    l = CImageStringWidth (password);
    k = min (l, w - 6);
    memset (password, 0, strlen (password));
    free (password);
    CRectangle (win, 3, 3, k, option_text_line_spacing + 1);
    CLine (win, 3, 4, 3, h - 5);
    CLine (win, 3, h - 4, k + 3, h - 4);
    CRectangle (win, k + 3, 3, w - 6 - k, h - 6);
    (*look->render_passwordinput_tidbits) (wdt, win == CGetFocus ());
    wc = 3 + TEXTINPUT_RELIEF + 1 + CImageTextWidth (password, wdt->cursor);
    set_cursor_position (win, wc, 5, 0, h - 5, CURSOR_TYPE_TEXTINPUT, 0, 0, 0, 0);
    CPopFont ();
    return;
}

void render_textinput (CWidget * wdt)
{
    int wc, isfocussed = 0;
    int f, k, l;
    int x, m1, m2;
    int w = wdt->width, h = wdt->height;
    Window win;
    char *s;

    if (wdt->options & TEXTINPUT_PASSWORD) {
	render_passwordinput (wdt);
	return;
    }
    
    CPushFont ("editor", 0);
    win = wdt->winid;
    isfocussed = (win == CGetFocus ());

/*This is a little untidy, but it will account for uneven font widths
   without having to think to hard */

    do {
	f = 0;
/*wc is the position of the cursor from the left of the input window */
	wc = 3 + TEXTINPUT_RELIEF + 1 +
	    CImageTextWidth (wdt->text + wdt->firstcolumn, wdt->cursor - wdt->firstcolumn);

	/*now lets make sure the cursor is well within the view */

/*except for when the cursor is at the end of the line */
	if (wdt->cursor == strlen (wdt->text)) {
	    if (wc > w - 3 - h) {
		wdt->firstcolumn++;
		f = 1;
	    }
	} else if (wc > max (w - FONT_MEAN_WIDTH - h, w * 3 / 4 - h)) {
	    wdt->firstcolumn++;
	    f = 1;
	}
	if (wc < min (FONT_MEAN_WIDTH, w / 4)) {
	    wdt->firstcolumn--;
	    f = 1;
	    /*Unless of course we are at the beginning of the string */
	    if (wdt->firstcolumn <= 0) {
		wdt->firstcolumn = 0;
		f = 0;
	    }
	}
    } while (f);		/*recalculate if firstcolumn has changed */

    s = wdt->text + wdt->firstcolumn;
    l = strlen (s);
    CSetColor (COLOR_WHITE);
    k = min (CImageTextWidth (s, l), w - h - 6);
    CRectangle (win, 3, 3, k, option_text_line_spacing + 1);
    CLine (win, 3, 4, 3, h - 5);
    CLine (win, 3, h - 4, k + 3, h - 4);
    CRectangle (win, k + 3, 3, w - h - 6 - k, h - 6);
/* now draw the visible part of the string */
    wdt->mark1 = min (wdt->mark1, l + wdt->firstcolumn);
    wdt->mark2 = min (wdt->mark2, l + wdt->firstcolumn);
    m1 = min (wdt->mark1, wdt->mark2);
    m2 = max (wdt->mark1, wdt->mark2);
    x = 0;
    if (m1 > wdt->firstcolumn) {
	CSetBackgroundColor (COLOR_WHITE);
	CSetColor (COLOR_BLACK);
	CImageText (win,
			  FONT_OFFSET_X + 3 + TEXTINPUT_RELIEF,
			  FONT_OFFSET_Y + 3 + TEXTINPUT_RELIEF,
			  s, m1 - wdt->firstcolumn);
	x += CImageTextWidth (s, m1 - wdt->firstcolumn);
	s += m1 - wdt->firstcolumn;
    }
    if (x < w - h && m2 > wdt->firstcolumn) {
	m1 = max (wdt->firstcolumn, m1);
	CSetBackgroundColor (COLOR_BLACK);
	CSetColor (COLOR_WHITE);
	CImageText (win,
			  FONT_OFFSET_X + 3 + TEXTINPUT_RELIEF + x,
			  FONT_OFFSET_Y + 3 + TEXTINPUT_RELIEF,
			  s, m2 - m1);
	x += CImageTextWidth (s, m2 - m1);
	s += m2 - m1;
    }
    if (x < w - h) {
	CSetBackgroundColor (COLOR_WHITE);
	CSetColor (COLOR_BLACK);
	CImageString (win,
			  FONT_OFFSET_X + 3 + TEXTINPUT_RELIEF + x,
			  FONT_OFFSET_Y + 3 + TEXTINPUT_RELIEF,
			  s);
    }

    (*look->render_textinput_tidbits) (wdt, isfocussed);

    set_cursor_position (win, wc, 5, 0, h - 5, CURSOR_TYPE_TEXTINPUT, 0, 0, 0, 0);
    CPopFont();
    return;
}


void text_input_destroy (CWidget * w)
{
    CAddToTextInputHistory (w->ident, w->text);
}

static void xy (int x, int y, int *x_return, int *y_return)
{
    *x_return = x - (3 + TEXTINPUT_RELIEF + 1);
    *y_return = 0;
}

static long cp (CWidget * wdt, int x, int y)
{
    int i;
    i = wdt->firstcolumn;
    for (;;) {
	int a;
	a = CImageTextWidth (wdt->text + wdt->firstcolumn, i - wdt->firstcolumn);
	if (a > x) {
	    if (i > 0)
		return i - 1;
	    return 0;
	}
	if (!wdt->text[i])
	    break;
	i++;
    }
    return strlen (wdt->text);
}

/* return 1 if not marked */
static int marks (CWidget * w, long *start, long *end)
{
    if (w->mark1 == w->mark2)
	return 1;
    *start = min (w->mark1, w->mark2);
    *end = max (w->mark1, w->mark2);
    return 0;
}

extern int range (CWidget * w, long start, long end, int click);

static void move_mark (CWidget * w)
{
    w->mark2 = w->mark1 = w->cursor;
}

static void fin_mark (CWidget * w)
{
    w->mark2 = w->mark1 = -1;
}

static void release_mark (CWidget * w, XEvent * event)
{
    w->mark2 = w->cursor;
    if (w->mark2 != w->mark1 && event)
	XSetSelectionOwner (CDisplay, XA_PRIMARY, w->winid, event->xbutton.time);
}

static char *get_block (CWidget * w, long start_mark, long end_mark, int *type, int *l)
{
    char *t;
    if (w->options & TEXTINPUT_PASSWORD) {
	*type = DndText;
	*l = 0;
	return (char *) strdup ("");
    }
    *l = abs (w->mark2 - w->mark1);
    t = CMalloc (*l + 1);
    memcpy (t, w->text + min (w->mark1, w->mark2), *l);
    t[*l] = 0;
    if (*type == DndFile || *type == DndFiles) {
	char *s;
	int i;
	s = CDndFileList (t, l, &i);
	free (t);
	t = s;
    }
    return t;
}

static void move (CWidget * w, long click, int row)
{
    w->cursor = click;
    if (w->mark2 == -1)
	w->mark1 = click;
    w->mark2 = click;
}

static void motion (CWidget * w, long click)
{
    w->mark2 = click;
}

void input_insert (CWidget * w, int c);
char *filename_from_url (char *data, int size, int i);

static int insert_drop (CWidget * w, Window from, unsigned char *data, int size, int xs, int ys, Atom type, Atom action)
{
    int cursor;
    char *f;
    int x, y, i;
    if (xs < 0 || ys < 0 || xs >= w->width || ys >= w->height)
	return 1;
    xy (xs, ys, &x, &y);
    f = filename_from_url ((char *) data, size, 0);
    data = (unsigned char *) f;
    cursor = w->cursor = cp (w, x, y);
    if (type == XInternAtom (CDisplay, "url/url", False) || \
	type == XInternAtom (CDisplay, "text/uri-list", False))
	if (!strncmp ((char *) data, "file:/", 6))
	    data += 5;
    for (i = 0; i < size && data[i] != '\n' && data[i]; i++)
	input_insert (w, data[i] < ' ' ? ' ' : data[i]);
    if (cursor > strlen (w->text))
	cursor = strlen (w->text);
    w->cursor = cursor;
    free (f);
    return 0;
}

static char *mime_majors[3] =
{"text", 0};

static struct mouse_funcs input_mouse_funcs =
{
    0,
    (void (*)(int, int, int *, int *)) xy,
    (long (*)(void *, int, int)) cp,
    (int (*)(void *, long *, long *)) marks,
    (int (*)(void *, long, long, long)) range,
    (void (*)(void *)) fin_mark,
    (void (*)(void *)) move_mark,
    (void (*)(void *, XEvent *)) release_mark,
    (char *(*)(void *, long, long, int *, int *)) get_block,
    (void (*)(void *, long, int)) move,
    (void (*)(void *, long)) motion,
    0,
    0,
    (int (*)(void *, Window, unsigned char *, int, int, int, Atom, Atom)) insert_drop,
    0,
    DndText,
    mime_majors
};

/*
   This will reallocate a previous draw of the same identifier.
   so you can draw the same widget over and over without flicker
 */
CWidget *CDrawTextInput (const char *identifier, Window parent, int x, int y,
		     int width, int height, int maxlen, const char *text)
{
    CWidget *wdt;

    if (text == TEXTINPUT_LAST_INPUT)
	text = CLastInput (identifier);

    CPushFont ("editor", 0);
    if (!(wdt = CIdent (identifier))) {
	int w, h;
	if (width == AUTO_WIDTH || height == AUTO_HEIGHT)
	    CTextSize (&w, &h, text);
	if (width == AUTO_WIDTH)
	    width = w + 6 + TEXTINPUT_RELIEF * 2;
	if (height == AUTO_HEIGHT)
	    height = FONT_PIX_PER_LINE + 6 + TEXTINPUT_RELIEF * 2;

	set_hint_pos (x + width + WIDGET_SPACING, y + height + WIDGET_SPACING);

	wdt = CSetupWidget (identifier, parent, x, y,
	 width, height, C_TEXTINPUT_WIDGET, INPUT_KEY, COLOR_FLAT, 1);

/* For the text input widget we need enough memory allocated to the label
   for it to grow to maxlen, so reallocate it */

	wdt->text = CMalloc (maxlen + 16);
	strcpy (wdt->text, text);
	wdt->cursor = strlen (text);
	wdt->firstcolumn = 0;
	wdt->textlength = maxlen;
	wdt->destroy = text_input_destroy;
	wdt->options |= WIDGET_TAKES_SELECTION;
	wdt->funcs = mouse_funcs_new (wdt, &input_mouse_funcs);

	xdnd_set_dnd_aware (CDndClass, wdt->winid, 0);
	xdnd_set_type_list (CDndClass, wdt->winid, xdnd_typelist_send[DndText]);
    } else {			/*redraw the thing so it doesn't flicker if its redrawn in the same place.
				   Also, this doesn't need an undraw */
	CSetWidgetSize (identifier, width, height);
	wdt->x = x;
	wdt->y = y;
	XMoveWindow (CDisplay, wdt->winid, x, y);
	free (wdt->text);
	wdt->text = CMalloc (maxlen + 16);
	strcpy (wdt->text, text);
	wdt->cursor = strlen (text);
	wdt->firstcolumn = 0;
	wdt->textlength = maxlen;
	wdt->keypressed = 0;
	render_textinput (wdt);
    }
    CPopFont();

    return wdt;
}

void paste_prop (void *data, void (*insert) (void *, int), Window win, unsigned prop, int delete);

void input_insert (CWidget * w, int c)
{
    if (strlen ((char *) w->text) < w->textlength) {
	if (!w->keypressed) {
	    w->keypressed = 1;
	    w->cursor = 0;
	    w->text[0] = '\0';
	}
	memmove ((char *) w->text + w->cursor + 1, w->text + w->cursor, strlen ((char *) w->text) - w->cursor + 1);
	w->text[w->cursor] = c;
	w->cursor++;
    }
}

static void xy (int x, int y, int *x_return, int *y_return);
static long cp (CWidget * wdt, int x, int y);
void text_get_selection (CWidget * w);
void selection_send (XSelectionRequestEvent * rq);

int eh_textinput (CWidget * w, XEvent * xevent, CEvent * cwevent)
{
    int handled = 0, save_options;
    int cursor;
    char *u;

    switch (xevent->type) {
    case FocusIn:
    case FocusOut:
	render_textinput (w);
	break;
    case SelectionRequest:
	text_get_selection (w);
	selection_send (&(xevent->xselectionrequest));
	render_textinput (w);
	return 1;
    case SelectionNotify:
	cursor = w->keypressed ? w->cursor : 0;
	paste_prop ((void *) w, (void (*)(void *, int)) input_insert,
	xevent->xselection.requestor, xevent->xselection.property, True);
	w->mark1 = w->mark2 = 0;
	w->cursor = cursor;
	render_textinput (w);
	break;
    case ButtonPress:
	resolve_button (xevent, cwevent);
	if (!(w->options & TEXTINPUT_PASSWORD)) {
	    if (xevent->xbutton.x >= w->width - w->height) {
		char *p;
		w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
		w->options |= BUTTON_PRESSED;
		p = draw_text_input_history (w);
		if (p) {
		    strncpy (w->text, p, w->textlength);
		    w->keypressed = 1;
		    w->cursor = strlen (w->text);
		    w->firstcolumn = 0;
		}
	    } else {
		input_mouse_mark (w, xevent, cwevent);
		w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
	    }
	}
	render_textinput (w);
	CFocus (w);
    case ButtonRelease:
	if (!(w->options & TEXTINPUT_PASSWORD))
	    input_mouse_mark (w, xevent, cwevent);
	render_textinput (w);
	break;
    case Expose:
	if (xevent->xexpose.count)
	    return 0;
    case EnterNotify:
	w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
	if (xevent->xbutton.x >= w->width - w->height)
	    w->options |= BUTTON_HIGHLIGHT;
	render_textinput (w);
	break;
    case MotionNotify:
	save_options = w->options;
	w->options &= ~(BUTTON_PRESSED | BUTTON_HIGHLIGHT);
	if (xevent->xmotion.x >= w->width - w->height) {
	    w->options |= BUTTON_HIGHLIGHT;
	    if (save_options != w->options)
		render_textinput (w);
	    return 0;
	} else {
	    if (!xevent->xmotion.state) {
		if (save_options != w->options)
		    render_textinput (w);
		return 0;
	    }
	}
	if (!(w->options & TEXTINPUT_PASSWORD))
	    input_mouse_mark (w, xevent, cwevent);
	render_textinput (w);
	break;
    case LeaveNotify:
	w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
	render_textinput (w);
	break;
    case KeyPress:
	cwevent->ident = w->ident;
	cwevent->state = xevent->xkey.state;
	cursor = w->cursor;
	if (cwevent->insert > 0) {
            if (!((w->options & TEXTINPUT_NUMBERS) && !(cwevent->insert >= '0' && cwevent->insert <= '9')))
	        input_insert (w, cwevent->insert);
	    handled = 1;
	} else {
	    unsigned char *intext;
	    intext = (unsigned char *) w->text;
	    switch (cwevent->command) {
	    case CK_Complete:
		u = draw_selection_completion (w);
		if (u) {
		    w->cursor = 0;
		    w->text[0] = '\0';
		    while (*u)
			input_insert (w, *u++);
		}
		handled = 1;
		break;
	    case CK_Selection_History:
		u = draw_selection_history (w);
		if (u)
		    while (*u)
			input_insert (w, *u++);
		handled = 1;
		break;
	    case CK_Insert_Unicode:
		u = (char *) CGetUnichar (CRoot, "Unicode characters");
		if (u)
		    while (*u)
			input_insert (w, *u++);
		handled = 1;
		break;
	    case CK_XPaste:
		if (!XGetSelectionOwner (CDisplay, XA_PRIMARY)) {
		    cursor = w->cursor;
		    paste_prop ((void *) w, (void (*)(void *, int)) input_insert,
				CRoot, XA_CUT_BUFFER0, False);
		    w->cursor = cursor;
		} else {
		    XConvertSelection (CDisplay, XA_PRIMARY, XA_STRING,
			   XInternAtom (CDisplay, "VT_SELECTION", False),
				       w->winid, CurrentTime);
		    return 0;
		}
		handled = 1;
		break;
	    case CK_BackSpace:
		if (w->mark1 != w->mark2) {
		    memmove (intext + min (w->mark1, w->mark2), intext + max (w->mark1, w->mark2), strlen ((char *) intext + max (w->mark1, w->mark2)) + 1);
		    w->cursor = min (w->mark1, w->mark2);
		} else if (w->cursor > 0) {
		    memmove ((char *) intext + w->cursor - 1, intext + w->cursor, strlen ((char *) intext) - w->cursor + 1);
		    w->cursor--;
		}
		handled = 1;
		break;
	    case CK_Left:
		if (w->cursor > 0)
		    w->cursor--;
		handled = 1;
		break;
	    case CK_Down:
	    case CK_Up:
	    case CK_Down_Highlight:
	    case CK_Up_Highlight:
		if (cwevent->state & ShiftMask) {
		    char *p;
		    w->options |= BUTTON_PRESSED;
		    p = draw_text_input_history (w);
		    if (p) {
			strncpy (w->text, p, w->textlength);
			w->keypressed = 1;
			w->cursor = strlen (w->text);
			w->firstcolumn = 0;
		    }
		    w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
		    handled = 1;
		}
		break;
	    case CK_Right:
		if (w->cursor < strlen ((char *) intext))
		    w->cursor++;
		handled = 1;
		break;
	    case CK_Delete:
		if (w->mark1 != w->mark2) {
		    memmove (intext + min (w->mark1, w->mark2), intext + max (w->mark1, w->mark2), strlen ((char *) intext + max (w->mark1, w->mark2)) + 1);
		    w->cursor = min (w->mark1, w->mark2);
		} else if (w->cursor < strlen ((char *) intext))
		    memmove (intext + w->cursor, intext + w->cursor + 1, strlen ((char *) intext) - w->cursor + 1);
		handled = 1;
		break;
	    case CK_Home:
		w->cursor = 0;
		handled = 1;
		break;
	    case CK_End:
		w->cursor = strlen ((char *) intext);
		handled = 1;
		break;
	    }
	    w->keypressed |= handled;
	}
	if (handled) {
	    w->mark1 = w->mark2 = 0;
	    render_textinput (w);
	}
	cwevent->text = w->text;
    }

    return handled;
}

void input_mouse_mark (CWidget * w, XEvent * event, CEvent * ce)
{
    CPushFont ("editor", 0);
    mouse_mark (event, ce->double_click, w->funcs);
    CPopFont ();
}








