



#include "WINGsP.h"

#include <X11/keysym.h>

#include <ctype.h>

typedef struct W_TextField {
    W_Class widgetClass;
    W_View *view;

    struct W_TextField *nextField;
    
    char *text;
    int textLen;		       /* size of text */
    int bufferSize;		       /* memory allocated for text */
    
    int viewPosition;		       /* position of text being shown */

    int cursorPosition;		       /* position of the insertion cursor */
    
    short offsetWidth;		       /* offset of text from border */
    
    struct {
	unsigned int bordered:1;
	
	unsigned int alignment:2;

	unsigned int configured:1;
	
	unsigned int focused:1;
    } flags;
} TextField;


#define MIN_TEXT_BUFFER		2
#define TEXT_BUFFER_INCR	8

static WMArgument textFieldArguments[] = {
    {WARG_WIDTH,	(WMValue)60},
    {WARG_HEIGHT,	(WMValue)20},
    {WARG_BORDERED,	(WMValue)1},
    {WARG_ALIGNMENT,	(WMValue)WALeft},
};


static void destroyTextField(TextField *tPtr);
static void paintTextField(TextField *tPtr);

static int configureTextFieldArg(TextField *tPtr, WMArgument *arg);
static void handleEvents(XEvent *event, void *data);
static void handleTextFieldActionEvents(XEvent *event, void *data);
static void handleTextFieldKeyPress(XEvent *event, void *data);



#define TEXT_WIDTH(tPtr, start)	(XTextWidth((tPtr)->view->screen->normalFont->font, \
				   &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1) \
					+ 2*(tPtr)->offsetWidth)

#define TEXT_WIDTH2(tPtr, start, end) (XTextWidth((tPtr)->view->screen->normalFont->font, \
				   &((tPtr)->text[(start)]), (end) - (start) + 1) \
					+ 2*(tPtr)->offsetWidth)


static void
memmv(char *dest, char *src, int size)
{
    int i;
    
    if (dest > src) {
	for (i=size-1; i>=0; i--) {
	    dest[i] = src[i];
	}
    } else if (dest < src) {
	for (i=0; i<size; i++) {
	    dest[i] = src[i];
	}
    }
}



static void
incrToFit(TextField *tPtr)
{
    while (TEXT_WIDTH(tPtr, tPtr->viewPosition) >= tPtr->view->size.width)
	tPtr->viewPosition++;
}

static void
incrToFit2(TextField *tPtr)
{
    while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition) 
	   >= tPtr->view->size.width)
	tPtr->viewPosition++;
}


static void
decrToFit(TextField *tPtr)
{
    while (TEXT_WIDTH(tPtr, tPtr->viewPosition-1) < tPtr->view->size.width
	   && tPtr->viewPosition>0)
	tPtr->viewPosition--;
}

#undef TEXT_WIDTH
#undef TEXT_WIDTH2


WMTextField*
WMCreateTextField(WMWidget *parent, WMArgument *argv, int argc)
{
    TextField *tPtr;

    
    tPtr = wmalloc(sizeof(TextField));
    memset(tPtr, 0, sizeof(TextField));

    tPtr->widgetClass = WC_TextField;
    
    tPtr->view = W_CreateView(W_VIEW(parent));
    if (!tPtr->view) {
	free(tPtr);
	return NULL;
    }
    tPtr->view->attribFlags |= CWBackPixel;
    tPtr->view->attribs.background_pixel = tPtr->view->screen->whitePixel;

    tPtr->text = malloc(MIN_TEXT_BUFFER);
    if (!tPtr->text)
	goto byebye;
    tPtr->text[0] = 0;

    tPtr->textLen = 0;
    tPtr->bufferSize = MIN_TEXT_BUFFER;
    
    WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask,
			 handleEvents, tPtr);

    if (!WMConfigureTextField(tPtr, argv, argc)) {
   byebye:
	W_DestroyView(tPtr->view);
	return NULL;
    }

    WMCreateEventHandler(tPtr->view, EnterWindowMask|LeaveWindowMask
			 |ButtonPressMask,
			 handleTextFieldActionEvents, tPtr);
    
    WMCreateEventHandler(tPtr->view, KeyPressMask, handleTextFieldKeyPress,
			 tPtr);

    W_SetFocusToWidget(tPtr);

    
    
    
    tPtr->flags.focused = 1;
    
    return tPtr;
}


void
WMInsertTextInTextField(WMTextField *tPtr, char *text, int position)
{
    int len;

 
    CHECK_CLASS(tPtr, WC_TextField);
    
    if (!text)
	return;
    
    len = strlen(text);

    /* check if buffer will hold the text */
    if (len + tPtr->textLen >= tPtr->bufferSize) {
	tPtr->bufferSize = tPtr->textLen + len + TEXT_BUFFER_INCR;
	tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
    }
    
    if (position < 0 || position >= tPtr->textLen) {
	/* append the text at the end */
	strcat(tPtr->text, text);
	
	incrToFit(tPtr);

	tPtr->textLen += len;
	tPtr->cursorPosition += len;
    } else {
	/* insert text at position */ 
	memmv(&(tPtr->text[position+len]), &(tPtr->text[position]),
                tPtr->textLen-position+1);
	
	memcpy(&(tPtr->text[position]), text, len);
	
	tPtr->textLen += len;
	if (position >= tPtr->cursorPosition) {
	    tPtr->cursorPosition += len;
	    incrToFit2(tPtr);
	} else {
	    incrToFit(tPtr);
	}
    }
    
    paintTextField(tPtr);
}


void
WMDeleteRangeFromTextField(WMTextField *tPtr, int position, int count)
{    
    CHECK_CLASS(tPtr, WC_TextField);

    if (position >= tPtr->textLen)
	return;
    
    if (count < 1) {
	if (position < 0)
	    position = 0;
	tPtr->text[position] = 0;
	tPtr->textLen = position;
	
	tPtr->cursorPosition = 0;
	tPtr->viewPosition = 0;
    } else {
	if (position + count > tPtr->textLen)
	    count = tPtr->textLen - position;
	memmv(&(tPtr->text[position]), &(tPtr->text[position+count]),
                tPtr->textLen - (position+count) + 1);	
	tPtr->textLen -= count;
	
	if (tPtr->cursorPosition > position)
	    tPtr->cursorPosition -= count;

	decrToFit(tPtr);
    }
        
    paintTextField(tPtr);
}



char*
WMGetTextFromTextField(WMTextField *tPtr)
{
    CHECK_CLASS(tPtr, WC_TextField);
        
    return strdup(tPtr->text);
}



static int
configureTextFieldArg(TextField *tPtr, WMArgument *arg)
{    
    switch (arg->argument) {	
     case WARG_TEXT:
	if (arg->value==NULL) {
	    tPtr->text[0] = 0;
	} else {
	    tPtr->textLen = strlen((char*)arg->value);
	
	    if (tPtr->textLen >= tPtr->bufferSize) {
		tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
		tPtr->text = realloc(tPtr->text, tPtr->bufferSize);
	    }
	    strcpy(tPtr->text, (char*)arg->value);
	}
	break;

     case WARG_ALIGNMENT:
	tPtr->flags.alignment = (long)arg->value;
	break;

     case WARG_BORDERED:
	tPtr->flags.bordered = ((long)arg->value!=0);
	break;
			
     default:
	if (!W_ConfigureViewArg(tPtr->view, arg)) {
	    wWarning("bad argument value %i in configureTextField",  
		     arg->argument);
	    return False;
	}
    }
    return True;
}



int
WMConfigureTextField(TextField *tPtr, WMArgument *argv, int argc)
{
    int i;
    
    CHECK_CLASS(tPtr, WC_TextField);
    
    if (!tPtr->flags.configured) {
	for (i=0; i<sizeof(textFieldArguments)/sizeof(WMArgument); i++) {
	    if (!configureTextFieldArg(tPtr, &(textFieldArguments[i])))
		return False;
	}	
	tPtr->flags.configured = 1;
    }

    for (i=0; i<argc; i++) {
	if (!configureTextFieldArg(tPtr, &(argv[i])))
	    return False;
    }


    if (!tPtr->flags.bordered) {
	tPtr->offsetWidth = (tPtr->view->size.height 
			     - tPtr->view->screen->normalFont->height)/2;
    } else {
	tPtr->offsetWidth = 2 + (tPtr->view->size.height - 4
				 - tPtr->view->screen->normalFont->height)/2;
    }
    
    
    if (tPtr->view->flags.realized) {
	paintTextField(tPtr);
    }
        
    return True;
}



static void
paintTextField(TextField *tPtr)
{
    W_Screen *screen = tPtr->view->screen;
    W_View *view = tPtr->view;
    int tx, ty, tw, th;
    int bd;

    
    if (!view->flags.realized)
	return;

    if (!tPtr->flags.bordered) {
	bd = 0;
    } else {
	bd = 2;
    }

    if (tPtr->textLen > 0) {
    	tw = XTextWidth(screen->normalFont->font, 
			&(tPtr->text[tPtr->viewPosition]),
			tPtr->textLen - tPtr->viewPosition);
    
	th = screen->normalFont->height;

	XClearArea(screen->display, view->window, bd, bd,
		   view->size.width-2*bd, view->size.height-2*bd, False);
		
	ty = screen->normalFont->y + tPtr->offsetWidth;
	switch (tPtr->flags.alignment) {
	 case WALeft:
	    tx = tPtr->offsetWidth;
	    break;
	
	 case WACenter:
	    tx = tPtr->offsetWidth 
		+ (view->size.width - 2*tPtr->offsetWidth - tw) / 2;
	    break;

	 default:
	 case WARight:
	    tx = view->size.width - tw - tPtr->offsetWidth;
	    break;
	}

	XDrawString(screen->display, view->window, screen->normalFontGC, 
		    tx, ty, &(tPtr->text[tPtr->viewPosition]), 
		    tPtr->textLen - tPtr->viewPosition);
    } else {
	XClearArea(screen->display, view->window, tPtr->offsetWidth, 
		   tPtr->offsetWidth, view->size.width - 2*tPtr->offsetWidth,
		   view->size.height - 2*tPtr->offsetWidth, False);
    }

    /* draw cursor */
    if (tPtr->flags.focused) {
	int cx;
	
	cx = XTextWidth(screen->normalFont->font, 
			&(tPtr->text[tPtr->viewPosition]),
			tPtr->cursorPosition-tPtr->viewPosition);

	XDrawRectangle(screen->display, view->window, screen->blackGC, 
		       tPtr->offsetWidth+cx, tPtr->offsetWidth, 1, 
		       view->size.height - 2*tPtr->offsetWidth - 1);
    }
    
    /* draw relief */
    if (tPtr->flags.bordered)
	W_DrawRelief(screen, view->window, 0, 0, view->size.width, 
		     view->size.height, WRSunken);
}



static void
handleEvents(XEvent *event, void *data)
{
    TextField *tPtr = (TextField*)data;

    CHECK_CLASS(data, WC_TextField);


    switch (event->type) {
     case Expose:
	if (event->xexpose.count!=0)
	    break;
	paintTextField(tPtr);
	break;
	
     case DestroyNotify:
	destroyTextField(tPtr);
	break;
    }
}


static void
handleTextFieldKeyPress(XEvent *event, void *data)
{
    TextField *tPtr = (TextField*)data;
    char buffer[64];
    KeySym ksym;
    int count, refresh = 0;

    CHECK_CLASS(data, WC_TextField);

    count = XLookupString(&event->xkey, buffer, 63, &ksym, NULL);
    buffer[count] = '\0';

    switch (ksym) {
     case XK_Left:
	if (tPtr->cursorPosition > 0) {
	    tPtr->cursorPosition--;
	    if (tPtr->cursorPosition < tPtr->viewPosition)
		tPtr->viewPosition = tPtr->cursorPosition;
	    refresh = 1;
	}
	break;
	
     case XK_Right:
	if (tPtr->cursorPosition < tPtr->textLen) {
	    tPtr->cursorPosition++;
	    incrToFit2(tPtr);
	    refresh = 1;
	}
	break;
	
     case XK_Home:
	if (tPtr->cursorPosition > 0) {
	    tPtr->cursorPosition = 0;
	    tPtr->viewPosition = 0;
	    refresh = 1;
	}
	break;
	
     case XK_End:
	if (tPtr->cursorPosition < tPtr->textLen) {
	    tPtr->cursorPosition = tPtr->textLen;
	    incrToFit(tPtr);
	    refresh = 1;
	}
	break;
	
     case XK_BackSpace:
	if (tPtr->cursorPosition > 0) {
	    WMDeleteRangeFromTextField(tPtr, tPtr->cursorPosition-1, 1);
	}
	break;
	
     case XK_Delete:
	if (tPtr->cursorPosition < tPtr->textLen) {
	    WMDeleteRangeFromTextField(tPtr, tPtr->cursorPosition, 1);
	}
	break;
	
     default:
	if (count > 0 && !iscntrl(buffer[0])) {
	    WMInsertTextInTextField(tPtr, buffer, tPtr->cursorPosition);
	}
    }
    if (refresh) {
	paintTextField(tPtr);
    }
}


static void
handleTextFieldActionEvents(XEvent *event, void *data)
{
    CHECK_CLASS(data, WC_TextField);

    switch (event->type) {
     case ButtonPress:
	W_SetFocusToWidget(data);
	break;
	
     case ButtonRelease:
	break;
	
	break;
    }
}


static void
destroyTextField(TextField *tPtr)
{
    CHECK_CLASS(tPtr, WC_TextField);
    
    if (tPtr->text)
	free(tPtr->text);

    free(tPtr);
}
