/* +-------------------------------------------------------------------+ */
/* | Copyright 1992, 1993, David Koblas (koblas@netcom.com)            | */
/* |                                                                   | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.  There is no           | */
/* | representations about the suitability of this software for        | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.                                              | */
/* |                                                                   | */
/* +-------------------------------------------------------------------+ */

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include "xpaint.h"
#include "Paint.h"
#include "misc.h"

static Atom	targetAtom, selectionAtom;

#define	DELAY	500

typedef struct {
	XtIntervalId	id;
	Boolean		typing;
	Boolean		state;
	int		startX, startY, curX, curY, height;
	XFontStruct	*fi;
	int		insertX, insertY, insertSX, insertSY, insertH;
	int		zoom;
	Widget		w;
	char		*sptr;
	int		maxStrLen;
	char		minPos;
	char		*str;
	Drawable	drawable;
	GC		gc, gcx;
} LocalInfo;

static void	addString(Widget,LocalInfo*,int,char*);

static void	cursor(LocalInfo *l, Boolean flag)
{
	if (l->w == None)
		return;
	if (flag) {
		XDrawLine(XtDisplay(l->w), XtWindow(l->w), l->gcx,
				l->insertX, l->insertY, l->insertX, l->insertY + l->insertH);
		l->state = !l->state;
	} else if (l->state) {
		XDrawLine(XtDisplay(l->w), XtWindow(l->w), l->gcx,
				l->insertX, l->insertY, l->insertX, l->insertY + l->insertH);
		l->state = False;
	}
}

static void	flash(LocalInfo *l)
{
	cursor(l, True);
	l->id = XtAppAddTimeOut(XtWidgetToApplicationContext(l->w), 
				DELAY, (XtTimerCallbackProc)flash, (XtPointer)l);
}

static void	gotSelection(Widget w, LocalInfo *l, Atom *selection, Atom *type,
				XtPointer value, unsigned long *len, int *format)
{
	if (!l->typing)
		return;

	if (len == 0 || value == NULL)
		return;

	switch (*type) {
	case XA_STRING:
		addString(w, l, *len, (char*)value);
		break;
	}
	XtFree((XtPointer)value);
}

static void	press(Widget w, LocalInfo *l, XButtonEvent *event, OpInfo *info) 
{
	if (event->button == Button1) {
		cursor(l, False);

		XtVaGetValues(w, XtNfont, &l->fi, NULL);
		if (l->fi == NULL)
			return;

		l->w      = w;
		l->height = l->fi->ascent + l->fi->descent;
		l->startX = l->curX = info->x;
		l->startY = l->curY = info->y;
		l->curY   = l->curY - l->height / 2;
		l->typing = True;

		if (info->surface == opWindow) {
			l->insertH  = l->height * info->zoom;
			l->insertY  = event->y - l->insertH / 2;
			l->insertX  = event->x;
			l->insertSX = event->x;
		}

		l->sptr = l->str;

		if (l->id == (XtIntervalId)NULL)
			flash(l);

		l->zoom   = info->zoom;
		l->gc     = info->first_gc;
		l->minPos = 0;

		UndoStartPoint(w, info, l->curX, l->curY);
		if (info->surface == opPixmap)
			l->drawable = info->drawable;
	} else if (event->button == Button2) {
		XtGetSelectionValue(w, selectionAtom, targetAtom,
			(XtSelectionCallbackProc)gotSelection, (XtPointer)l, event->time);
	}
}

static void	key(Widget w, LocalInfo *l, XKeyEvent *event, OpInfo *info) 
{
	char		buf[21];
	KeySym		keysym;
	int		len;

	if (l->w == None)
		return;

	if ((len = XLookupString(event, buf, sizeof(buf) - 1, &keysym, NULL)) == 0)
		return;

	l->zoom     = info->zoom;
	l->gc       = info->first_gc;
	l->drawable = info->drawable;
	addString(w, l, len, buf);
}

static void	addString(Widget w, LocalInfo *l, int len, char *buf)
{
	int		i, width;
	XRectangle      rect;

	if (len != 0)
		cursor(l, False);
	for (i = 0; i < len; i++) {
		if (l->sptr == l->str + l->maxStrLen - 5) {
			int	delta = l->sptr - l->str;
			l->maxStrLen += 128;
			l->str = (char*)XtRealloc((XtPointer)l->str, l->maxStrLen);
			l->sptr = l->str + delta;
		}

		if (buf[i] == '\n' || buf[i] == '\r') {
			l->curX     = l->startX;
			l->curY    += l->height + 2;
			l->insertX  = l->insertSX;
			l->insertY += l->insertH + 2 * l->zoom;

			*l->sptr++ = '\n';
		} else if (buf[i] == 0x08 || buf[i] == 0x7f) {
			if (l->sptr - l->str > l->minPos) {
				l->sptr--;
				if (*l->sptr == '\n') {
					char	*cp;
					*l->sptr = '\0';
					if ((cp = strrchr(l->str, '\n')) == NULL)
						cp = l->str;
					else
						cp++;
					width = XTextWidth(l->fi, cp, strlen(cp));
					l->curX = l->startX + width;
					l->curY    -= l->height + 2;
					l->insertX = (l->startX + width) * l->zoom;
					l->insertY -= l->insertH + 2 * l->zoom;
				} else {
					width = XTextWidth(l->fi, l->sptr, 1);
					l->curX -= width;
					l->insertX -= width * l->zoom;

					/*
					**  Now undraw the character.
					*/
					XYtoRECT(l->curX, l->curY, l->curX + width, l->curY + l->height, &rect);
					PwUpdateFromLast(w, &rect);
				}
			}
		} else {
			if (l->zoom == 1)
				XDrawString(XtDisplay(w), XtWindow(w), l->gc, 
					l->curX, l->curY + l->fi->ascent, &buf[i], 1);
			XDrawString(XtDisplay(w), l->drawable, l->gc, 
					l->curX, l->curY + l->fi->ascent, &buf[i], 1);
			width = XTextWidth(l->fi, &buf[i], 1);

			XYtoRECT(l->curX, l->curY, l->curX + width, l->curY + l->height, &rect);
			PwUpdate(w, &rect, False);

			l->curX    += width;
			l->insertX += width * l->zoom;

			UndoGrow(w, l->curX, l->curY + l->height);
			UndoGrow(w, l->curX, l->curY + l->height);

			*l->sptr++ = buf[i];
		}
	}
}

/*
**  Those public functions
*/
void *FontAdd(Widget w)
{
	static int	inited = False;
	LocalInfo	*l = (LocalInfo*)XtMalloc(sizeof(LocalInfo));

	if (!inited) {
		selectionAtom = XA_PRIMARY;
		targetAtom    = XA_STRING;
	}

	l->id = (XtIntervalId)NULL;
	l->w  = None;
	l->typing = False;
	l->state = False;

	l->maxStrLen = 128;
	l->str = (char*)XtMalloc(l->maxStrLen);
	l->gcx = GetGCX(w);

	OpAddEventHandler(w, opPixmap|opWindow, ButtonPressMask, FALSE, (OpEventProc)press, l);
	OpAddEventHandler(w, opPixmap, KeyPressMask, FALSE, (OpEventProc)key, l);

	SetIBeamCursor(w);

	return l;
}
void FontRemove(Widget w, LocalInfo *l)
{
	OpRemoveEventHandler(w, opPixmap|opWindow, ButtonPressMask, FALSE, (OpEventProc)press, l);
	OpRemoveEventHandler(w, opPixmap, KeyPressMask, FALSE, (OpEventProc)key, l);

	if (l->id != (XtIntervalId)NULL)
		XtRemoveTimeOut(l->id);

	cursor(l, False);

	XtFree((XtPointer)l->str);
	XtFree((XtPointer)l);
}
void FontChanged(Widget w)
{
	LocalInfo	*l;

	if ((int)CurrentOp[0] != (int)FontAdd)
		return;

	l = (LocalInfo*)GraphicGetData(w);
	
	XtVaGetValues(w, XtNfont, &l->fi, NULL);
	if (l->fi == NULL)
		return;

	cursor(l, False);
	l->height = l->fi->ascent + l->fi->descent;

	l->minPos = l->sptr - l->str;
	l->insertH  = l->height * l->zoom;
}
