/* $Id: Textfield.c,v 1.4 1997/05/01 12:05:59 archer Exp $
 *	TEXTFIELD.C - Handle Text fields objects
 */
#include <Widget/TextFieldP.h>
#include <Widget/ShellP.h>
#include <Widget/drawP.h>
#include <Widget/clipP.h>
#include <Widget/fontsP.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <ctype.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif


#ifndef DOUBLE_CLICK
#define DOUBLE_CLICK	500
#endif

/**
 ** Forward declaration
 **/
static void	deselected(X11_widget, XEvent *);


/**
 ** Private variables
 **/
static GC	textfield_gc;
static int	min_x, max_x;


/**
 ** Internal: TEXTFIELD_CLEANUP
 **	Free the allocated memory
 **/
static void textfield_cleanup(X11_widget wdg, XEvent *dummy)
{
/*
 * Free storage
 */
	if (W_TEXTFIELD->storage) {
		free(W_TEXTFIELD->storage);
		W_TEXTFIELD->storage = 0;
	}
/*
 * In case we're clipboard source...
 */
	x11__clipboard_owner(wdg, 0);
}


/**
 ** Internal: TEXTFIELD_REFOCUS
 **	Focus changes
 **/
static void textfield_refocus(X11_widget wdg, XEvent *dummy)
{
	if (wdg->unmapped)
		return;
	if (wdg->has_focus)
		X11_SignalArm(&W_TEXTFIELD->flash);
	else
		X11_SignalCancel(&W_TEXTFIELD->flash);
	W_TEXTFIELD->cursor_off = False;
	if (wdg->id) {
		XClearArea(x11__screen, wdg->id, 0, 0, 0, 0, False);
		(*wdg->on_expose)(wdg, dummy);
	}
}


/**
 ** Internal: TEXT_EXPOSE
 **	Redraw text field
 **/
static void text_expose(x11__textfield *wdg)
{
XFontStruct	*info;
Window		id;
GC		gc;
int		w, x, max;
int		len, off;
char		*s;
/*
 * GC
 */
	if (!textfield_gc)
		textfield_gc = x11__widget_gc(W_WIDGET);
/*
 * We're starting at?
 */
	max = wdg->core.w - 2*wdg->x_start;
	x   = wdg->x_start;
	off = wdg->s_offset;
/*
 * Draw what insertion point?
 */
	info = wdg->font;
	s    = wdg->storage + off;
	XSetFont(x11__screen, textfield_gc, info->fid);
	id = wdg->core.id;
/*
 * No Copy buffer highlighted
 */
	if (wdg->w_select <= 0 || wdg->x_select >= max) {
		len = wdg->current_length - off;
		while (1) {
			w = XTextWidth(info, s, len);
			if (w <= max)
				break;
			len--;
		}
		XSetForeground(x11__screen,textfield_gc, wdg->core.foreground);
		XDrawString(x11__screen, id, textfield_gc,
						x, wdg->y_base, s, len);
	} else {
/*
 * Draw field with a selection inside
 */
		if (wdg->x_select) {
/*
 * The beginning of the field is unselected
 */
			len = wdg->insert_point - off;
			XSetForeground(x11__screen, textfield_gc,
							wdg->core.foreground);
			XDrawString(x11__screen, id, textfield_gc,
						x, wdg->y_base, s, len);
			x   += wdg->x_select;
			max -= wdg->x_select;
			off += len;
			s   += len;
			len  = wdg->replace_size;
			w    = wdg->w_select;
/*
 * The beginning of the selection might not appear
 */
		} else {
			len = wdg->replace_size + (off - wdg->insert_point);
			w   = XTextWidth(info, s, len);
		}
/*
 * This part of the field is selected
 */
		XSetForeground(x11__screen,x11__active_gc,wdg->core.active_bk);
		XSetForeground(x11__screen, textfield_gc, wdg->core.active_fg);
		if (max < w) {
/*
 * Only part of the selection appears
 */
			XFillRectangle(x11__screen, id, x11__active_gc,
					x, x11__y_top, max, x11__y_h);
			while (max <= w) {
				len--;
				w = XTextWidth(info, s, len);
			}
			XDrawString(x11__screen, id, textfield_gc,
					x, wdg->y_base, s, len);
			max = 0;
			len = 0;
		} else {
/*
 * All the selection appears
 */
			XFillRectangle(x11__screen, id, x11__active_gc,
					x, x11__y_top, wdg->w_select, x11__y_h);
			XDrawString(x11__screen, id, textfield_gc,
					x, wdg->y_base, s, len);
			max -= wdg->w_select;
			x   += wdg->w_select;
			off += len;
			s   += len;
			len  = wdg->current_length - off;
		}
/*
 * Is there anything left after the selection?
 */
		if (len > 0 && max > 0) {
			while (1) {
				w = XTextWidth(info, s, len);
				if (w <= max)
					break;
				len--;
			}
			XSetForeground(x11__screen, textfield_gc,
							wdg->core.foreground);
			XDrawString(x11__screen, id, textfield_gc,
					x, wdg->y_base, s, len);
		}
	}
/*
 * Draw cursor?
 */
	x = wdg->x_cursor;
	if (x < 0 || x > max)
		return;
/*
 * No focus, no cursor
 */
	if (wdg->cursor_off || wdg->replace_size)
		return;
/*
 * Which GC is used to draw this?
 */
	if (wdg->core.has_focus)
		gc = textfield_gc;
	else {
		gc = x11__stippled_gc;
		XSetBackground(x11__screen, gc, wdg->core.background);
	}
/*
 * Cursor is visible. Youpee!
 */
	x += wdg->x_start;
	XSetForeground(x11__screen, gc, wdg->core.foreground);
	XDrawLine(x11__screen, id, gc, x-2, x11__y_top, x+2, x11__y_top);
	XDrawLine(x11__screen, id, gc, x, x11__y_top, x, x11__y_bot);
	XDrawLine(x11__screen, id, gc, x-2, x11__y_bot, x+2, x11__y_bot);
}


/**
 ** Internal: SELECTION
 **	Sets the cursor to cover the given field
 **/
static void selection(x11__textfield *wdg, int start, int len, boolean cutpaste)
{
char	*s;
int	w, h;
/*
 * Refresh area
 */
	if (wdg->w_select) {
		if (wdg->x_select < min_x)
			min_x = wdg->x_select;
		w = wdg->x_select + wdg->w_select;
		if (w > max_x)
			max_x = w;
	}
/*
 * Memorize everything
 */
	if (start == -1)
		start = wdg->insert_point;
	else
		wdg->insert_point = start;
	wdg->replace_size = len;
/*
 * Copy (for future pastes?)
 */
	x11__clipboard_owner(W_WIDGET, 0);
	if (len && cutpaste) {
		x11__clipboard_string(wdg->storage + start, len);
		x11__clipboard_owner(W_WIDGET, 0);
	}
/*
 * Where is the selection, now?
 */
	wdg->x_select = 0;
	if (len > 0) {
		start -= wdg->s_offset;
		s = wdg->storage + wdg->s_offset;
		if (start <= 0) {
			len += start;
			if (len <= 0)
				wdg->w_select = 0;
			else
				wdg->w_select = XTextWidth(wdg->font, s, len);
		} else {
			wdg->x_select = XTextWidth(wdg->font, s, start);
			s += start;
			wdg->w_select = XTextWidth(wdg->font, s, len);
		}
	} else
		wdg->w_select = 0;
/*
 * Refresh occurs over this area
 */
	if (wdg->w_select) {
		if (wdg->x_select < min_x)
			min_x = wdg->x_start;
		w = wdg->x_select + wdg->w_select;
		if (w > max_x)
			max_x = w;
	}
/*
 * Redraw done
 */
	if (min_x <= max_x && wdg->core.id) {
		max_x++;
		w = wdg->core.shadow_thickness + wdg->core.focus_thickness +
		    wdg->core.margin_inside;
		h = wdg->core.h - (wdg->core.border2 + 2 * w);
		XClearArea(x11__screen, wdg->core.id,
				wdg->x_start + min_x, w,
				max_x - min_x, h, True);
	}
}


/**
 ** Internal: DESELECTED
 **	The selection disappears
 **/
static void deselected(X11_widget wdg, XEvent *dummy)
{
/*
 * Make sure...
 */
	if (W_TEXTFIELD->replace_size)
		selection(W_TEXTFIELD, -1, 0, False);
}


/**
 ** Internal: RECURSOR
 **	Redraw the cursor position
 **/
static void recursor(int x_cursor)
{
/*
 * Cursor goes -2/+2
 */
	x_cursor -= 2;
	if (x_cursor < min_x)
		min_x = x_cursor;
	x_cursor += 4;
	if (x_cursor > max_x)
		max_x = x_cursor;
}


/**
 ** Internal: ENSURE
 **	The text field should display the given character...
 **/
static void ensure(x11__textfield *wdg, int pos)
{
int	max;
int	w;
int	off;
/*
 * Error checking
 */
	if (pos > wdg->current_length)
		pos = wdg->current_length;
	if (pos < 0)
		pos = 0;
/*
 * Global sizes
 */
	if (wdg->current_length)
		w = XTextWidth(wdg->font, wdg->storage, wdg->current_length);
	else
		w = 0;
	max = wdg->core.w - 2*wdg->x_start;
/*
 * String entirely contained...
 */
	off = wdg->s_offset;
	if (w <= max) {
		if (off) {
			wdg->s_offset = 0;
			off   = 0;
			min_x = 0;
			max_x = 0x7fff;
		}
	} else {
/*
 * String will be partially obscured
 */
		if (pos < off) {
			wdg->s_offset = pos;
			off   = pos;
			min_x = 0;
			max_x = 0x7fff;
		} else {
			while (1) {
				w = XTextWidth(wdg->font, wdg->storage+off,
								pos-off);
				if (w <= max)
					break;
				wdg->s_offset++;
				off++;
				min_x = 0;
				max_x = 0x7fff;
			}
		}
	}
/*
 * Cursor will be...
 */
	wdg->x_cursor = XTextWidth(wdg->font, wdg->storage + off, pos - off);
	recursor(wdg->x_cursor);
}


/**
 ** Internal: GENERIC_EXPOSE
 **	Redraw text field
 **/
static void generic_expose(X11_widget wdg, XEvent *dummy)
{
/*
 * Drawing starts here
 */
	x11__draw_start(wdg);
	if (wdg->has_focus)
		x11__draw_lined(wdg->focus_color, wdg->focus_thickness);
	else
		x11__draw_lined(wdg->parent->background, wdg->focus_thickness);
	if (!x11__is_monochrome) {
		x11__shadows_gc();
		x11__draw_downshadow(wdg->shadow_thickness);
	} else
		x11__draw_nothing(wdg->shadow_thickness);
/*
 * Draw insides
 */
	x11__y_top += wdg->margin_inside;
	x11__y_bot -= wdg->margin_inside;
	x11__y_h   -= 2 * wdg->margin_inside;
	text_expose(W_TEXTFIELD);
}


/**
 ** Internal: CURSOR_FLASHING
 **	Cursor flashes on and off
 **/
static void cursor_flashing(X11_widget wdg, void *data)
{
int	y, h;
/*
 * Nothing to do
 */
	if (!wdg->id || wdg->unmapped)
		return;
	if (W_TEXTFIELD->selecting || W_TEXTFIELD->replace_size)
		return;
/*
 * Toggle cursor
 */
	if (W_TEXTFIELD->cursor_off)
		W_TEXTFIELD->cursor_off = False;
	else
		W_TEXTFIELD->cursor_off = True;
	max_x = 0;
	min_x = 0x7fff;
	recursor(W_TEXTFIELD->x_cursor);
/*
 * Refresh (only the cursor is redrawn)
 */
	max_x++;
	y = wdg->shadow_thickness + wdg->focus_thickness + wdg->margin_inside;
	h = wdg->h - (wdg->border2 + 2 * y);
	XClearArea(x11__screen, wdg->id, W_TEXTFIELD->x_start + min_x, y,
					max_x - min_x, h, False);
/*
 * Fake refresh
 */
	x11__draw_start(wdg);
	x11__draw_nothing(wdg->focus_thickness + wdg->shadow_thickness);
	x11__y_top += wdg->margin_inside;
	x11__y_bot -= wdg->margin_inside;
	x11__y_h   -= 2 * wdg->margin_inside;
	text_expose(W_TEXTFIELD);
}


/**
 ** Internal: TEXTFIELD_DELETE
 **	Delete key
 **/
static void textfield_delete(x11__textfield *t)
{
char	*d, *s;
int	l;
/*
 * Copy there?
 */
	d = t->storage + t->insert_point;
/*
 * Selection delete
 */
	if (t->replace_size != 0) {
		l = t->insert_point + t->replace_size;
		s = t->storage + l;
		l = t->current_length - l;
		t->current_length -= t->replace_size;
		t->replace_size = 0;
	} else {
/*
 * Single character delete
 */
		if (t->insert_point == 0)
			return;
		l = t->current_length - t->insert_point;
		t->insert_point--;
		s = d--;
		t->current_length--;
	}
/*
 * Shift characters left
 */
	bcopy(s, d, l);
	t->storage[t->current_length] = '\0';
	ensure(t, t->insert_point);
	max_x = 0x7fff;
}


/**
 ** Internal: TEXTFIELD_NEW_KEY
 **	Parse key, add to text field
 **/
static void textfield_new_key(x11__textfield *t, char c)
{
char	*s;
int	l;
char	c2;
/*
 * Backspace/Delete
 */
	if (c == '\b' || c == 0x7f) {
		textfield_delete(t);
		return;
	}
	if (c < ' ')
		return;
/*
 * Replace selection
 */
	if (t->replace_size)
		textfield_delete(t);
/*
 * Insert new character
 */
	if (t->current_length == t->max_length)
		return;
	recursor(t->x_cursor);
	l = t->current_length++ - t->insert_point;
	s = t->storage + t->insert_point++;
	while (l > 0) {
		c2   = *s;
		*s++ = c;
		c    = c2;
		l--;
	}
	*s++ = c;
	*s   = '\0';
	ensure(t, t->insert_point);
	max_x = 0x7fff;
}


/**
 ** Internal: TEXTFIELD_PROCESS
 **	Process a new string
 **/
static void textfield_process(x11__textfield *wdg, char *s)
{
/*
 * Nothing to do
 */
	if (!s)
		return;
/*
 * Process
 */
	while (*s)
		textfield_new_key(wdg, *s++);
	selection(wdg, -1, 0, True);
/*
 * Notify modification
 */
	if (wdg->on_modify)
		(*wdg->on_modify)(W_WIDGET, wdg->userdata, wdg->storage);
}


/**
 ** Internal: TEXTFIELD_KEYPRESS
 **	Key to textfield
 **/
static void textfield_keypress(X11_widget wdg, XEvent *key)
{
char	*s;
int	l;
/*
 * Avoid characters while selecting
 */
	if (W_TEXTFIELD->selecting)
		return;
/*
 * Get typed characters
 */
	s = x11__key_translate((XKeyEvent *)key);
	max_x = 0;
	min_x = 0x7FFF;
/*
 * Special keys:
 */
	switch (x11__key_sym) {
/*
 * Left arrow
 */
	    case XK_Left:
		if (W_TEXTFIELD->insert_point) {
			recursor(W_TEXTFIELD->x_cursor);
			ensure(W_TEXTFIELD, --(W_TEXTFIELD->insert_point));
			selection(W_TEXTFIELD, -1, 0, True);
		}
		return;
/*
 * Right arrow
 */
	    case XK_Right:
		if (W_TEXTFIELD->insert_point < W_TEXTFIELD->current_length) {
			recursor(W_TEXTFIELD->x_cursor);
			ensure(W_TEXTFIELD, ++(W_TEXTFIELD->insert_point));
			selection(W_TEXTFIELD, -1, 0, True);
		}
		return;
/*
 * Ctrl-A (moves to beginning)
 */
	    case XK_a:
	    case XK_A:
		if (x11__key_mods & ControlMask) {
			if (W_TEXTFIELD->insert_point) {
				recursor(W_TEXTFIELD->x_cursor);
				ensure(W_TEXTFIELD, 0);
				selection(W_TEXTFIELD, 0, 0, True);
			}
			return;
		}
		break;
/*
 * Ctrl-E (moves to end)
 */
	    case XK_e:
	    case XK_E:
		if (x11__key_mods & ControlMask) {
			if (W_TEXTFIELD->insert_point < W_TEXTFIELD->current_length) {
				recursor(W_TEXTFIELD->x_cursor);
				ensure(W_TEXTFIELD,W_TEXTFIELD->current_length);
				selection(W_TEXTFIELD, -1, 0, True);
			}
			return;
		}
		break;
/*
 * Ctrl-K (clears to end)
 */
	    case XK_k:
	    case XK_K:
		if (x11__key_mods & ControlMask) {
			l = W_TEXTFIELD->current_length -
			    W_TEXTFIELD->insert_point;
			if (l > 0) {
				W_TEXTFIELD->replace_size = l;
				textfield_delete(W_TEXTFIELD);
				selection(W_TEXTFIELD, -1, 0, True);
				if (W_TEXTFIELD->on_modify)
					(*W_TEXTFIELD->on_modify)(wdg,
							W_TEXTFIELD->userdata,
							W_TEXTFIELD->storage);
			}
			return;
		}
		break;
	}
/*
 * Process key(s)
 */
	textfield_process(W_TEXTFIELD, s);
}


/**
 ** Internal: TEXTFIELD_RELEASE
 **	Mouse released, selection done
 **/
static void textfield_release(X11_widget wdg, XEvent *dummy)
{
/*
 * Selected
 */
	if (W_TEXTFIELD->selecting && W_TEXTFIELD->replace_size) {
		min_x = 0x7fff;
		max_x = 0;
		selection(W_TEXTFIELD, -1, W_TEXTFIELD->replace_size, True);
		W_TEXTFIELD->number_of_clicks = 0;
		W_TEXTFIELD->last_click       = 0;
	}
/*
 * No longer selecting, in case...
 */
	W_TEXTFIELD->selecting = False;
}


/**
 ** Internal: WHERE_IS_PIXEL
 **	Returns the offset of the insertion point within the string?
 **/
static int where_is_pixel(x11__textfield *wdg, int x)
{
XFontStruct	*info;
char		*s;
int		l, l2, max;
int		w;
/*
 * Correct X position...
 */
	x -= wdg->x_start;
/*
 * Where am I?
 */
	l    = wdg->s_offset;
	info = wdg->font;
/*
 * Special case: x < left margin
 */
	if (x < 0 && l) {
/*** HACK ***/
		return l;
	}
/*
 * Falls somewhere in the middle...
 */
	max = wdg->current_length - l;
	s   = wdg->storage + l;
	l2  = 0;
	while (max-- > 0) {
		w = XTextWidth(info, s, ++l2);
		if (x < w) {
			l2--;
			break;
		}
	}
	return l + l2;
}


/**
 ** Internal: TEXTFIELD_MOTION
 **	Mouse is in motion
 **/
static void textfield_motion(X11_widget wdg, XEvent *event)
{
int	l, w;
int	x;
/*
 * Currently
 */
	if (!W_TEXTFIELD->selecting)
		return;
	x = event->xmotion.x;
	l = where_is_pixel(W_TEXTFIELD, x);
	min_x = 0x7fff;
	max_x = 0;
/*
 * Selection extends from here to original click?
 */
	if (x < W_TEXTFIELD->x_button) {
		if (l == W_TEXTFIELD->insert_point)
			return;
		w = W_TEXTFIELD->s_button - l;
		if (w < 0)
			w = 0;	/* should never occur */
		recursor(W_TEXTFIELD->x_cursor);
		selection(W_TEXTFIELD, l, w, False);
		return;
	}
/*
 * Selection extends from the original click to here
 */
	w = l - W_TEXTFIELD->s_button;
	if (w < 0)
		w = 0;	/* should never occur */
	if (w == W_TEXTFIELD->replace_size)
		return;
	recursor(W_TEXTFIELD->x_cursor);
	selection(W_TEXTFIELD, W_TEXTFIELD->s_button, w, False);
}


/**
 ** Internal: WORD_SELECT
 **	Select a word (usually, because of a double click)
 **/
static void word_select(x11__textfield *wdg)
{
char	*s;
int	min, max;
int	i;
/*
 * Can't select word outside of string
 */
	max = wdg->current_length;
	i   = wdg->insert_point;
	if (i >= max)
		return;
/*
 * Can't select word outside of word
 */
	s   = wdg->storage;
	if (!isalnum(s[i])) {
		selection(wdg, -1, 1, True);
		return;
	}
/*
 * Find minimum
 */
	for (min = i; min; min--)
		if (!isalnum(s[min-1]))
			break;
/*
 * Find maximum
 */
	while (++i < max)
		if (!isalnum(s[i]))
			break;
	selection(wdg, min, i - min, True);
}


/**
 ** Internal: TEXTFIELD_PRESS
 **	Mouse depressed, get focus
 **/
static void textfield_press(X11_widget wdg, XEvent *event)
{
Time	t;
int	l;
/*
 * Focus gets here?
 */
	if (!wdg->has_focus) {
		X11_ShellFocus(wdg);
		W_TEXTFIELD->selecting = False;
		XClearArea(x11__screen, wdg->id, 0, 0, 0, 0, True);
	}
/*
 * Selecting? Change this to a move event
 */
	if (W_TEXTFIELD->selecting) {
		(*wdg->on_motion_notify)(wdg, event);
		return;
	}
	min_x = 0x7fff;
	max_x = 0;
	recursor(W_TEXTFIELD->x_cursor);
/*
 * Button 2 = Paste?
 */
	if (event->xbutton.button == Button2) {
		textfield_process(W_TEXTFIELD, x11__clipboard_get());
		return;
	}
/*
 * Double-click?
 */
	t = event->xbutton.time;
	if ((t - W_TEXTFIELD->last_click) < W_TEXTFIELD->double_click_time &&
	    W_TEXTFIELD->number_of_clicks++ < 2) {
		W_TEXTFIELD->last_click = t;
		if (W_TEXTFIELD->number_of_clicks == 1)
			word_select(W_TEXTFIELD);
		else
			selection(W_TEXTFIELD, 0, W_TEXTFIELD->current_length,
					True);
		return;
	}
/*
 * New pression
 */
	l = where_is_pixel(W_TEXTFIELD, event->xbutton.x);
	W_TEXTFIELD->x_button         = event->xbutton.x;
	W_TEXTFIELD->last_click       = t;
	W_TEXTFIELD->number_of_clicks = 0;
	W_TEXTFIELD->s_button         = l;
	W_TEXTFIELD->selecting        = True;
	ensure(W_TEXTFIELD, l);
	selection(W_TEXTFIELD, l, 0, True);
}


/**
 ** X11_TEXTFIELD_SET
 **	Set the value stored
 **/
void X11_TextFieldSet(X11_widget wdg, char *newvalue)
{
int	l;
/*
 * Just in case
 */
	if (!newvalue)
		newvalue = "";
/*
 * Handle string length
 */
	l = strlen(newvalue);
	if (l > W_TEXTFIELD->max_length)
		l = W_TEXTFIELD->max_length;
/*
 * Store new value
 */
	memcpy(W_TEXTFIELD->storage, newvalue, l);
	W_TEXTFIELD->storage[l]     = 0;
	W_TEXTFIELD->current_length = l;
/*
 * Refresh whole text field
 */
	min_x = 0;
	max_x = 0x7fff;
	ensure(W_TEXTFIELD, l);
	selection(W_TEXTFIELD, l, 0, True);
}


/**
 ** X11_TEXTFIELD_VALUE
 **	Return the storage value
 **/
char *X11_TextFieldValue(X11_widget wdg)
{
char	*s;
/*
 * Don't forget to put a zero byte at end of string
 */
	s = W_TEXTFIELD->storage;
	s[W_TEXTFIELD->current_length] = '\0';
	return s;
}


/**
 ** X11_TEXTFIELD_CALLBACK
 **	Set a callback for the text field
 **/
void X11_TextFieldCallback(X11_widget wdg, X11_string_call call, void *data)
{
	W_TEXTFIELD->on_modify = call;
	W_TEXTFIELD->userdata  = data;
}


/**
 ** X11_TEXTFIELD_SELECTED
 **	All of the textfield is now selected. Note: This doesn't make the
 **	current selection into a copy buffer!
 **/
void X11_TextFieldSelected(X11_widget wdg)
{
	min_x = 0;
	max_x = 0x7fff;
	ensure(W_TEXTFIELD, W_TEXTFIELD->current_length);
	selection(W_TEXTFIELD, 0, W_TEXTFIELD->current_length, False);
}


/**
 ** X11_TEXTFIELD
 **	Create a Text field window
 **/
X11_widget X11_TextField(X11_widget parent, char *name, int max, int view)
{
x11__textfield	*object;
XFontStruct	*info;
int		m;
int		w;
/*
 * Get resource
 */
	object = allocator(x11__textfield);
	x11__core_setup(&object->core, parent, "TextField", name);
/*
 * Initialize fields
 */
	object->core.event_mask        = ButtonPressMask|ButtonReleaseMask|
				      ButtonMotionMask|ExposureMask;
	object->core.on_button_press   = textfield_press;
	object->core.on_button_release = textfield_release;
	object->core.on_destruction    = textfield_cleanup;
	object->core.on_expose         = generic_expose;
	object->core.on_focus_change   = textfield_refocus;
	object->core.on_key_press      = textfield_keypress;
	object->core.on_motion_notify  = textfield_motion;
/*
 * Depend on the look
 */
	object->core.margin_inside    = 2;
	object->core.focus_thickness  = 2;
	object->core.shadow_thickness = 2;
/*
 * Fetch properties
 */
	x11__resource_basic(&object->core);
	object->font = info = x11__font_resource(x11__font_helvetica12);
/*
 * Specific fields
 */
	if (max <= 0)
		max = 80;
	if (view < 1)
		view = 1;
	if (view > max)
		view = max;
	object->max_length = max++;
	object->storage    = x11__widget_malloc(max);
/*** HACK ***/
/*
 * Geometry is fixed
 */
	m  = (object->core.margin_inside + object->core.shadow_thickness
		+ object->core.focus_thickness);
	object->y_base  = m + info->ascent;
	object->x_start = m;
	m *= 2;
	m += object->core.border2;
	w  = info->min_bounds.width + info->max_bounds.width;
	w *= view;
	w /= 2;
	object->core.w       = m + w;
	object->core.w_min   = m + 5 + info->max_bounds.width;
	object->core.h       =
	object->core.h_min   = info->ascent + info->descent + m;
	object->core.fixed_w = False;
	object->core.fixed_h = True;
/*
 * Finished
 */
	object->core.use_cursor = XCreateFontCursor(x11__screen, XC_xterm);
	object->double_click_time = x11__resource_integer("DoubleClickTime",
								DOUBLE_CLICK);
	object->flash.every.tv_sec  = 0;
	object->flash.every.tv_usec = 300000;
	object->flash.on_timer      = cursor_flashing;
	object->flash.wdg           = OBJECT_P;
	x11__focus_might(OBJECT_P);
/*
 * Ok
 */
	return OBJECT_P;
}
