#include "WINGsP.h"
#include "wconfig.h"

#include <X11/keysym.h>
#include <X11/Xatom.h>

#include <ctype.h>

#define CURSOR_BLINK_ON_DELAY	600
#define CURSOR_BLINK_OFF_DELAY	300



char *WMTextDidChangeNotification = "WMTextDidChangeNotification";
char *WMTextDidBeginEditingNotification = "WMTextDidBeginEditingNotification";
char *WMTextDidEndEditingNotification = "WMTextDidEndEditingNotification";


typedef struct W_TextField {
    W_Class widgetClass;
    W_View *view;

#if 0
    struct W_TextField *nextField;     /* next textfield in the chain */
    struct W_TextField *prevField;
#endif

    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 usableWidth;
    short offsetWidth;		       /* offset of text from border */

    WMRange selection;

    WMFont *font;

    WMTextFieldDelegate *delegate;

#if 0
    WMHandlerID	timerID;	       /* for cursor blinking */
#endif
    struct {
        WMAlignment alignment:2;

        unsigned int bordered:1;

        unsigned int beveled:1;

        unsigned int enabled:1;

        unsigned int focused:1;

        unsigned int cursorOn:1;

        unsigned int secure:1;	       /* password entry style */

        unsigned int pointerGrabbed:1;

        unsigned int ownsSelection:1;

        unsigned int waitingSelection:1; /* requested selection, but
        * didnt get yet */

       
        unsigned int notIllegalMovement:1;
    } flags;
} TextField;


#define NOTIFY(T,C,N,A)	{ WMNotification *notif = WMCreateNotification(N,T,A);\
    if ((T)->delegate && (T)->delegate->C)\
    (*(T)->delegate->C)((T)->delegate,notif);\
    WMPostNotification(notif);\
    WMReleaseNotification(notif);}


#define MIN_TEXT_BUFFER		2
#define TEXT_BUFFER_INCR	8


#define WM_EMACSKEYMASK   ControlMask

#define WM_EMACSKEY_LEFT  XK_b
#define WM_EMACSKEY_RIGHT XK_f
#define WM_EMACSKEY_HOME  XK_a
#define WM_EMACSKEY_END   XK_e
#define WM_EMACSKEY_BS    XK_h
#define WM_EMACSKEY_DEL   XK_d



#define DEFAULT_WIDTH		60
#define DEFAULT_HEIGHT		20
#define DEFAULT_BORDERED	True
#define DEFAULT_ALIGNMENT	WALeft



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

static void handleEvents(XEvent *event, void *data);
static void handleTextFieldActionEvents(XEvent *event, void *data);
static void didResizeTextField();

struct W_ViewDelegate _TextFieldViewDelegate = {
    NULL,
    NULL,
    didResizeTextField,
    NULL,
    NULL
};



static void lostSelection(WMView *view, Atom selection, void *cdata);

static WMData *requestHandler(WMView *view, Atom selection, Atom target,
                              void *cdata, Atom *type);


static WMSelectionProcs selectionHandler = {
    requestHandler,
    lostSelection,
    NULL
};


#define TEXT_WIDTH(tPtr, start)	(WMWidthOfString((tPtr)->font, \
    &((tPtr)->text[(start)]), (tPtr)->textLen - (start) + 1))

#define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
    &((tPtr)->text[(start)]), (end) - (start) + 1))


static void
normalizeRange(TextField *tPtr, WMRange *range)
{
    if (range->position < 0 && range->count < 0)
        range->count = 0;

    if (range->count == 0) {
        /*range->position = 0; why is this?*/
        return;
    }

    /* (1,-2) ~> (0,1) ; (1,-1) ~> (0,1) ; (2,-1) ~> (1,1) */
    if (range->count < 0) { /* && range->position >= 0 */
        if (range->position + range->count < 0) {
            range->count = range->position;
            range->position = 0;
        } else {
            range->count = -range->count;
            range->position -= range->count;
        }
        /* (-2,1) ~> (0,0) ; (-1,1) ~> (0,0) ; (-1,2) ~> (0,1) */
    } else if (range->position < 0) { /* && range->count > 0 */
        if (range->position + range->count < 0) {
            range->position = range->count = 0;
        } else {
            range->count += range->position;
            range->position = 0;
        }
    }

    if (range->position + range->count > tPtr->textLen)
        range->count = tPtr->textLen - range->position;
}

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 int
incrToFit(TextField *tPtr)
{
    int vp = tPtr->viewPosition;

    while (TEXT_WIDTH(tPtr, tPtr->viewPosition) > tPtr->usableWidth) {
        tPtr->viewPosition++;
    }
    return vp!=tPtr->viewPosition;
}


static int
incrToFit2(TextField *tPtr)
{
    int vp = tPtr->viewPosition;
    while (TEXT_WIDTH2(tPtr, tPtr->viewPosition, tPtr->cursorPosition)
           >= tPtr->usableWidth)
        tPtr->viewPosition++;


    return vp!=tPtr->viewPosition;
}


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

#undef TEXT_WIDTH
#undef TEXT_WIDTH2



static WMData*
requestHandler(WMView *view, Atom selection, Atom target, void *cdata,
               Atom *type)
{
    TextField *tPtr = view->self;
    int count;
    Display *dpy = tPtr->view->screen->display;
    Atom _TARGETS;
    Atom TEXT = XInternAtom(dpy, "TEXT", False);
    Atom COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", False);
    WMData *data;

    count = tPtr->selection.count < 0
        ? tPtr->selection.position + tPtr->selection.count
        : tPtr->selection.position;

    if (target == XA_STRING || target == TEXT || target == COMPOUND_TEXT) {

        data = WMCreateDataWithBytes(&(tPtr->text[count]),
                                     abs(tPtr->selection.count));
        WMSetDataFormat(data, 8);
        *type = target;

        return data;
    }

    _TARGETS = XInternAtom(dpy, "TARGETS", False);
    if (target == _TARGETS) {
        Atom *ptr;

        ptr = wmalloc(4 * sizeof(Atom));
        ptr[0] = _TARGETS;
        ptr[1] = XA_STRING;
        ptr[2] = TEXT;
        ptr[3] = COMPOUND_TEXT;

        data = WMCreateDataWithBytes(ptr, 4*4);
        WMSetDataFormat(data, 32);

        *type = target;
        return data;
    }

    return NULL;

}


static void
lostSelection(WMView *view, Atom selection, void *cdata)
{
    TextField *tPtr = (WMTextField*)view->self;

    if (tPtr->flags.ownsSelection) {
        WMDeleteSelectionHandler(view, selection, CurrentTime);
        tPtr->flags.ownsSelection = 0;
    }
    if (tPtr->selection.count != 0) {
        tPtr->selection.count = 0;
        paintTextField(tPtr);
    }
}


static void
selectionNotification(void *observerData, WMNotification *notification)
{
    WMView *observerView = (WMView*)observerData;
    WMView *newOwnerView = (WMView*)WMGetNotificationClientData(notification);

    if (observerView != newOwnerView) {
        /*
         //if (tPtr->flags.ownsSelection)
         //    WMDeleteSelectionHandler(observerView, XA_PRIMARY, CurrentTime);
         */
        lostSelection(observerView, XA_PRIMARY, NULL);
    }
}


static void
realizeObserver(void *self, WMNotification *not)
{
    W_CreateIC(((TextField*)self)->view);
}


WMTextField*
WMCreateTextField(WMWidget *parent)
{
    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) {
        wfree(tPtr);
        return NULL;
    }
    tPtr->view->self = tPtr;

    tPtr->view->delegate = &_TextFieldViewDelegate;

    tPtr->view->attribFlags |= CWCursor;
    tPtr->view->attribs.cursor = tPtr->view->screen->textCursor;

    W_SetViewBackgroundColor(tPtr->view, tPtr->view->screen->white);

    tPtr->text = wmalloc(MIN_TEXT_BUFFER);
    tPtr->text[0] = 0;
    tPtr->textLen = 0;
    tPtr->bufferSize = MIN_TEXT_BUFFER;

    tPtr->flags.enabled = 1;

    WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
                         |FocusChangeMask, handleEvents, tPtr);

    tPtr->font = WMRetainFont(tPtr->view->screen->normalFont);

    tPtr->flags.bordered = DEFAULT_BORDERED;
    tPtr->flags.beveled = True;
    tPtr->flags.alignment = DEFAULT_ALIGNMENT;
    tPtr->offsetWidth =
        WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);

    W_ResizeView(tPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);

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

    WMAddNotificationObserver(selectionNotification, tPtr->view,
                              WMSelectionOwnerDidChangeNotification,
                              (void*)XA_PRIMARY);

    WMAddNotificationObserver(realizeObserver, tPtr,
                              WMViewRealizedNotification, tPtr->view);

    tPtr->flags.cursorOn = 1;

    return tPtr;
}


void
WMSetTextFieldDelegate(WMTextField *tPtr, WMTextFieldDelegate *delegate)
{
    CHECK_CLASS(tPtr, WC_TextField);

    tPtr->delegate = delegate;
}


WMTextFieldDelegate*
WMGetTextFieldDelegate(WMTextField *tPtr)
{
    CHECK_CLASS(tPtr, WC_TextField);

    return tPtr->delegate;
}


void
WMInsertTextFieldText(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 = wrealloc(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
WMDeleteTextFieldRange(WMTextField *tPtr, WMRange range)
{
    CHECK_CLASS(tPtr, WC_TextField);

    normalizeRange(tPtr, &range);

    if (!range.count)
        return;

    memmv(&(tPtr->text[range.position]), &(tPtr->text[range.position+range.count]),
          tPtr->textLen - (range.position+range.count) + 1);

    tPtr->textLen -= range.count;

    /* try to keep cursorPosition at the same place */
    tPtr->viewPosition -= range.count;
    if (tPtr->viewPosition < 0)
        tPtr->viewPosition = 0;
    tPtr->cursorPosition = range.position;

    decrToFit(tPtr);

    paintTextField(tPtr);
}



char*
WMGetTextFieldText(WMTextField *tPtr)
{
    CHECK_CLASS(tPtr, WC_TextField);

    return wstrdup(tPtr->text);
}


void
WMSetTextFieldText(WMTextField *tPtr, char *text)
{
    CHECK_CLASS(tPtr, WC_TextField);

    if ((text && strcmp(tPtr->text, text) == 0) ||
        (!text && tPtr->textLen == 0))
        return;

    if (text==NULL) {
        tPtr->text[0] = 0;
        tPtr->textLen = 0;
    } else {
        tPtr->textLen = strlen(text);

        if (tPtr->textLen >= tPtr->bufferSize) {
            tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
            tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
        }
        strcpy(tPtr->text, text);
    }

    tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;
    tPtr->viewPosition = 0;
    tPtr->selection.count = 0;

    if (tPtr->view->flags.realized)
        paintTextField(tPtr);
}


void
WMSetTextFieldAlignment(WMTextField *tPtr, WMAlignment alignment)
{
    CHECK_CLASS(tPtr, WC_TextField);

    tPtr->flags.alignment = alignment;

    if (alignment!=WALeft) {
        wwarning("only left alignment is supported in textfields");
        return;
    }

    if (tPtr->view->flags.realized) {
        paintTextField(tPtr);
    }
}


void
WMSetTextFieldBordered(WMTextField *tPtr, Bool bordered)
{
    CHECK_CLASS(tPtr, WC_TextField);

    tPtr->flags.bordered = bordered;

    if (tPtr->view->flags.realized) {
        paintTextField(tPtr);
    }
}


void
WMSetTextFieldBeveled(WMTextField *tPtr, Bool flag)
{
    CHECK_CLASS(tPtr, WC_TextField);

    tPtr->flags.beveled = ((flag==0) ? 0 : 1);

    if (tPtr->view->flags.realized) {
        paintTextField(tPtr);
    }
}



void
WMSetTextFieldSecure(WMTextField *tPtr, Bool flag)
{
    CHECK_CLASS(tPtr, WC_TextField);

    tPtr->flags.secure = ((flag==0) ? 0 : 1);

    if (tPtr->view->flags.realized) {
        paintTextField(tPtr);
    }
}


Bool
WMGetTextFieldEditable(WMTextField *tPtr)
{
    CHECK_CLASS(tPtr, WC_TextField);

    return tPtr->flags.enabled;
}


void
WMSetTextFieldEditable(WMTextField *tPtr, Bool flag)
{
    CHECK_CLASS(tPtr, WC_TextField);

    tPtr->flags.enabled = ((flag==0) ? 0 : 1);

    if (tPtr->view->flags.realized) {
        paintTextField(tPtr);
    }
}


void
WMSelectTextFieldRange(WMTextField *tPtr, WMRange range)
{
    CHECK_CLASS(tPtr, WC_TextField);

    if (tPtr->flags.enabled) {
        normalizeRange(tPtr, &range);

        tPtr->selection = range;

        tPtr->cursorPosition = range.position + range.count;

        if (tPtr->view->flags.realized) {
            paintTextField(tPtr);
        }
    }
}


void
WMSetTextFieldCursorPosition(WMTextField *tPtr, unsigned int position)
{
    CHECK_CLASS(tPtr, WC_TextField);

    if (tPtr->flags.enabled) {
        if (position > tPtr->textLen)
            position = tPtr->textLen;

        tPtr->cursorPosition = position;
        if (tPtr->view->flags.realized) {
            paintTextField(tPtr);
        }
    }
}


void
WMSetTextFieldNextTextField(WMTextField *tPtr, WMTextField *next)
{
    CHECK_CLASS(tPtr, WC_TextField);
    if (next == NULL) {
        if (tPtr->view->nextFocusChain)
            tPtr->view->nextFocusChain->prevFocusChain = NULL;
        tPtr->view->nextFocusChain = NULL;
        return;
    }

    CHECK_CLASS(next, WC_TextField);

    if (tPtr->view->nextFocusChain)
        tPtr->view->nextFocusChain->prevFocusChain = NULL;
    if (next->view->prevFocusChain)
        next->view->prevFocusChain->nextFocusChain = NULL;

    tPtr->view->nextFocusChain = next->view;
    next->view->prevFocusChain = tPtr->view;
}


void
WMSetTextFieldPrevTextField(WMTextField *tPtr, WMTextField *prev)
{
    CHECK_CLASS(tPtr, WC_TextField);
    if (prev == NULL) {
        if (tPtr->view->prevFocusChain)
            tPtr->view->prevFocusChain->nextFocusChain = NULL;
        tPtr->view->prevFocusChain = NULL;
        return;
    }

    CHECK_CLASS(prev, WC_TextField);

    if (tPtr->view->prevFocusChain)
        tPtr->view->prevFocusChain->nextFocusChain = NULL;
    if (prev->view->nextFocusChain)
        prev->view->nextFocusChain->prevFocusChain = NULL;

    tPtr->view->prevFocusChain = prev->view;
    prev->view->nextFocusChain = tPtr->view;
}


void
WMSetTextFieldFont(WMTextField *tPtr, WMFont *font)
{
    CHECK_CLASS(tPtr, WC_TextField);

    if (tPtr->font)
        WMReleaseFont(tPtr->font);
    tPtr->font = WMRetainFont(font);

    tPtr->offsetWidth =
        WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);

    if (tPtr->view->flags.realized) {
        paintTextField(tPtr);
    }
}



WMFont*
WMGetTextFieldFont(WMTextField *tPtr)
{
    return tPtr->font;
}


static void
didResizeTextField(W_ViewDelegate *self, WMView *view)
{
    WMTextField *tPtr = (WMTextField*)view->self;

    tPtr->offsetWidth =
        WMAX((tPtr->view->size.height - WMFontHeight(tPtr->font))/2, 1);

    tPtr->usableWidth = tPtr->view->size.width - 2*tPtr->offsetWidth /*+ 2*/;
}


static char*
makeHiddenString(int length)
{
    char *data = wmalloc(length+1);

    memset(data, '*', length);
    data[length] = '\0';

    return data;
}


static void
paintCursor(TextField *tPtr)
{
    int cx;
    WMScreen *screen = tPtr->view->screen;
    int textWidth;
    char *text;

    if (tPtr->flags.secure)
        text = makeHiddenString(strlen(tPtr->text));
    else
        text = tPtr->text;

    cx = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
                         tPtr->cursorPosition-tPtr->viewPosition);

    switch (tPtr->flags.alignment) {
    case WARight:
        textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
        if (textWidth < tPtr->usableWidth)
            cx += tPtr->offsetWidth + tPtr->usableWidth - textWidth + 1;
        else
            cx += tPtr->offsetWidth + 1;
        break;
    case WALeft:
        cx += tPtr->offsetWidth + 1;
        break;
    case WAJustified:
        /* not supported */
    case WACenter:
        textWidth = WMWidthOfString(tPtr->font, text, tPtr->textLen);
        if (textWidth < tPtr->usableWidth)
            cx += tPtr->offsetWidth + (tPtr->usableWidth-textWidth)/2;
        else
            cx += tPtr->offsetWidth;
        break;
    }
    /*
     XDrawRectangle(screen->display, tPtr->view->window, screen->xorGC,
     cx, tPtr->offsetWidth, 1,
     tPtr->view->size.height - 2*tPtr->offsetWidth - 1);
     printf("%d %d\n",cx,tPtr->cursorPosition);
     */

    XDrawLine(screen->display, tPtr->view->window, screen->xorGC,
              cx, tPtr->offsetWidth, cx,
              tPtr->view->size.height - tPtr->offsetWidth - 1);

    W_SetPreeditPositon(tPtr->view, cx, 0);

    if (tPtr->flags.secure) {
        wfree(text);
    }
}



static void
drawRelief(WMView *view, Bool beveled)
{
    WMScreen *scr = view->screen;
    Display *dpy = scr->display;
    GC wgc;
    GC lgc;
    GC dgc;
    int width = view->size.width;
    int height = view->size.height;

    dgc = WMColorGC(scr->darkGray);

    if (!beveled) {
        XDrawRectangle(dpy, view->window, dgc, 0, 0, width-1, height-1);

        return;
    }
    wgc = WMColorGC(scr->white);
    lgc = WMColorGC(scr->gray);

    /* top left */
    XDrawLine(dpy, view->window, dgc, 0, 0, width-1, 0);
    XDrawLine(dpy, view->window, dgc, 0, 1, width-2, 1);

    XDrawLine(dpy, view->window, dgc, 0, 0, 0, height-2);
    XDrawLine(dpy, view->window, dgc, 1, 0, 1, height-3);

    /* bottom right */
    XDrawLine(dpy, view->window, wgc, 0, height-1, width-1, height-1);
    XDrawLine(dpy, view->window, lgc, 1, height-2, width-2, height-2);

    XDrawLine(dpy, view->window, wgc, width-1, 0, width-1, height-1);
    XDrawLine(dpy, view->window, lgc, width-2, 1, width-2, height-3);
}


static void
paintTextField(TextField *tPtr)
{
    W_Screen *screen = tPtr->view->screen;
    W_View *view = tPtr->view;
    W_View viewbuffer;
    int tx, ty, tw, th;
    int rx;
    int bd;
    int totalWidth;
    char *text;
    Pixmap drawbuffer;
    WMColor *color;


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

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

    if (tPtr->flags.secure) {
        text = makeHiddenString(strlen(tPtr->text));
    } else {
        text = tPtr->text;
    }

    totalWidth = tPtr->view->size.width - 2*bd;

    drawbuffer = XCreatePixmap(screen->display, view->window,
                               view->size.width, view->size.height, screen->depth);
    XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
                   0,0, view->size.width,view->size.height);
    /* this is quite dirty */
    viewbuffer.screen = view->screen;
    viewbuffer.size = view->size;
    viewbuffer.window = drawbuffer;


    if (tPtr->textLen > 0) {
        tw = WMWidthOfString(tPtr->font, &(text[tPtr->viewPosition]),
                             tPtr->textLen - tPtr->viewPosition);

        th = WMFontHeight(tPtr->font);

        ty = tPtr->offsetWidth;
        switch (tPtr->flags.alignment) {
        case WALeft:
            tx = tPtr->offsetWidth + 1;
            if (tw < tPtr->usableWidth)
                XFillRectangle(screen->display, drawbuffer,
                               WMColorGC(screen->white),
                               bd+tw,bd, totalWidth-tw,view->size.height-2*bd);
            break;

        case WACenter:
            tx = tPtr->offsetWidth + (tPtr->usableWidth - tw) / 2;
            if (tw < tPtr->usableWidth)
                XClearArea(screen->display, view->window, bd, bd,
                           totalWidth, view->size.height-2*bd, False);
            break;

        default:
        case WARight:
            tx = tPtr->offsetWidth + tPtr->usableWidth - tw - 1;
            if (tw < tPtr->usableWidth)
                XClearArea(screen->display, view->window, bd, bd,
                           totalWidth-tw, view->size.height-2*bd, False);
            break;
        }

        color = tPtr->flags.enabled ? screen->black : screen->darkGray;

        WMDrawImageString(screen, drawbuffer, color, screen->white,
                          tPtr->font, tx, ty, &(text[tPtr->viewPosition]),
                          tPtr->textLen - tPtr->viewPosition);

        if (tPtr->selection.count) {
            int count,count2;

            count = tPtr->selection.count < 0
                ? tPtr->selection.position + tPtr->selection.count
                : tPtr->selection.position;
            count2 = abs(tPtr->selection.count);
            if (count < tPtr->viewPosition) {
                count2 = abs(count2 - abs(tPtr->viewPosition - count));
                count = tPtr->viewPosition;
            }

            rx = tPtr->offsetWidth + 1 + WMWidthOfString(tPtr->font,text,count)
                - WMWidthOfString(tPtr->font,text,tPtr->viewPosition);

            WMDrawImageString(screen, drawbuffer, color, screen->gray,
                              tPtr->font, rx, ty, &(text[count]), count2);
        }
    } else {
        XFillRectangle(screen->display, drawbuffer, WMColorGC(screen->white),
                       bd, bd, totalWidth,view->size.height-2*bd);
    }

    /* draw relief */
    if (tPtr->flags.bordered) {
        drawRelief(&viewbuffer, tPtr->flags.beveled);
    }

    if (tPtr->flags.secure)
        wfree(text);
    XCopyArea(screen->display, drawbuffer, view->window,
              screen->copyGC, 0,0, view->size.width,
              view->size.height,0,0);
    XFreePixmap(screen->display, drawbuffer);

    /* draw cursor */
    if (tPtr->flags.focused && tPtr->flags.enabled && tPtr->flags.cursorOn) {
        paintCursor(tPtr);
    }
}


#if 0
static void
blinkCursor(void *data)
{
    TextField *tPtr = (TextField*)data;

    if (tPtr->flags.cursorOn) {
        tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_OFF_DELAY, blinkCursor,
                                          data);
    } else {
        tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY, blinkCursor,
                                          data);
    }
    paintCursor(tPtr);
    tPtr->flags.cursorOn = !tPtr->flags.cursorOn;
}
#endif


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

    CHECK_CLASS(data, WC_TextField);

    switch (event->type) {
    case FocusIn:
        W_FocusIC(tPtr->view);
        if (W_FocusedViewOfToplevel(W_TopLevelOfView(tPtr->view))!=tPtr->view)
            return;
        tPtr->flags.focused = 1;
#if 0
        if (!tPtr->timerID) {
            tPtr->timerID = WMAddTimerHandler(CURSOR_BLINK_ON_DELAY,
                                              blinkCursor, tPtr);
        }
#endif
        paintTextField(tPtr);

        NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL);

        tPtr->flags.notIllegalMovement = 0;
        break;

    case FocusOut:
        W_UnFocusIC(tPtr->view);
        tPtr->flags.focused = 0;
#if 0
        if (tPtr->timerID)
            WMDeleteTimerHandler(tPtr->timerID);
        tPtr->timerID = NULL;
#endif

        paintTextField(tPtr);
        if (!tPtr->flags.notIllegalMovement) {
            NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification,
                   (void*)WMIllegalTextMovement);
        }
        break;

    case Expose:
        if (event->xexpose.count!=0)
            break;
        paintTextField(tPtr);
        break;

    case DestroyNotify:
        destroyTextField(tPtr);
        break;
    }
}


static void
handleTextFieldKeyPress(TextField *tPtr, XEvent *event)
{
    char buffer[64];
    KeySym ksym;
    char *textEvent = NULL;
    void *data = NULL;
    int count, refresh = 0;
    int control_pressed = 0;
    int cancelSelection = 1;
    Bool shifted, controled, modified;
    Bool relay = True;

    /*printf("(%d,%d) -> ", tPtr->selection.position, tPtr->selection.count);*/
    if (((XKeyEvent *) event)->state & WM_EMACSKEYMASK)
        control_pressed = 1;

    shifted = (event->xkey.state & ShiftMask ? True : False);
    controled = (event->xkey.state & ControlMask ? True : False);
    modified = shifted || controled;

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

    switch (ksym) {
    case XK_Tab:
#ifdef XK_ISO_Left_Tab
    case XK_ISO_Left_Tab:
#endif
        if (!controled) {
            if (shifted) {
                if (tPtr->view->prevFocusChain) {
                    W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
                                         tPtr->view->prevFocusChain);
                    tPtr->flags.notIllegalMovement = 1;
                }
                data = (void*)WMBacktabTextMovement;
            } else {
                if (tPtr->view->nextFocusChain) {
                    W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view),
                                         tPtr->view->nextFocusChain);
                    tPtr->flags.notIllegalMovement = 1;
                }
                data = (void*)WMTabTextMovement;
            }
            textEvent = WMTextDidEndEditingNotification;

            cancelSelection = 0;

            relay = False;
        }
        break;

    case XK_Escape:
        if (!modified) {
            data = (void*)WMEscapeTextMovement;
            textEvent = WMTextDidEndEditingNotification;

            relay = False;
        }
        break;

    case XK_Return:
        if (!modified) {
            data = (void*)WMReturnTextMovement;
            textEvent = WMTextDidEndEditingNotification;

            relay = False;
        }
        break;

    case WM_EMACSKEY_LEFT:
        if (!control_pressed)
            goto normal_key;
        else
            controled = False;

#ifdef XK_KP_Left
    case XK_KP_Left:
#endif
    case XK_Left:
        if (tPtr->cursorPosition > 0) {
            paintCursor(tPtr);
            if (controled) {
                int i = tPtr->cursorPosition - 1;

                while (i > 0 && tPtr->text[i] != ' ') i--;
                while (i > 0 && tPtr->text[i] == ' ') i--;

                tPtr->cursorPosition = (i > 0) ? i + 1 : 0;
            } else
                tPtr->cursorPosition--;

            if (tPtr->cursorPosition < tPtr->viewPosition) {
                tPtr->viewPosition = tPtr->cursorPosition;
                refresh = 1;
            } else
                paintCursor(tPtr);
        }
        if (shifted)
            cancelSelection = 0;

        relay = False;

        break;

    case WM_EMACSKEY_RIGHT:
        if (!control_pressed)
            goto normal_key;
        else
            controled = False;

#ifdef XK_KP_Right
    case XK_KP_Right:
#endif
    case XK_Right:
        if (tPtr->cursorPosition < tPtr->textLen) {
            paintCursor(tPtr);
            if (controled) {
                int i = tPtr->cursorPosition;

                while (tPtr->text[i] && tPtr->text[i] != ' ') i++;
                while (tPtr->text[i] == ' ') i++;

                tPtr->cursorPosition = i;
            } else {
                tPtr->cursorPosition++;
            }
            while (WMWidthOfString(tPtr->font,
                                   &(tPtr->text[tPtr->viewPosition]),
                                   tPtr->cursorPosition-tPtr->viewPosition)
                   > tPtr->usableWidth) {
                tPtr->viewPosition++;
                refresh = 1;
            }
            if (!refresh)
                paintCursor(tPtr);
        }
        if (shifted)
            cancelSelection = 0;

        relay = False;

        break;

    case WM_EMACSKEY_HOME:
        if (!control_pressed)
            goto normal_key;
        else
            controled = False;

#ifdef XK_KP_Home
    case XK_KP_Home:
#endif
    case XK_Home:
        if (!controled) {
            if (tPtr->cursorPosition > 0) {
                paintCursor(tPtr);
                tPtr->cursorPosition = 0;
                if (tPtr->viewPosition > 0) {
                    tPtr->viewPosition = 0;
                    refresh = 1;
                } else
                    paintCursor(tPtr);
            }
            if (shifted)
                cancelSelection = 0;

            relay = False;
        }
        break;

    case WM_EMACSKEY_END:
        if (!control_pressed)
            goto normal_key;
        else
            controled = False;

#ifdef XK_KP_End
    case XK_KP_End:
#endif
    case XK_End:
        if (!controled) {
            if (tPtr->cursorPosition < tPtr->textLen) {
                paintCursor(tPtr);
                tPtr->cursorPosition = tPtr->textLen;
                tPtr->viewPosition = 0;
                while (WMWidthOfString(tPtr->font,
                                       &(tPtr->text[tPtr->viewPosition]),
                                       tPtr->textLen-tPtr->viewPosition)
                       > tPtr->usableWidth) {
                    tPtr->viewPosition++;
                    refresh = 1;
                }
                if (!refresh)
                    paintCursor(tPtr);
            }
            if (shifted)
                cancelSelection = 0;

            relay = False;
        }
        break;

    case WM_EMACSKEY_BS:
        if (!control_pressed)
            goto normal_key;
        else
            modified = False;

    case XK_BackSpace:
        if (!modified) {
            if (tPtr->selection.count) {
                WMDeleteTextFieldRange(tPtr, tPtr->selection);
                data = (void*)WMDeleteTextEvent;
                textEvent = WMTextDidChangeNotification;
            } else if (tPtr->cursorPosition > 0) {
                WMRange range;
                range.position = tPtr->cursorPosition - 1;
                range.count = 1;
                WMDeleteTextFieldRange(tPtr, range);
                data = (void*)WMDeleteTextEvent;
                textEvent = WMTextDidChangeNotification;
            }

            relay = False;
        }
        break;

    case WM_EMACSKEY_DEL:
        if (!control_pressed)
            goto normal_key;
        else
            modified = False;

#ifdef XK_KP_Delete
    case XK_KP_Delete:
#endif
    case XK_Delete:
        if (!modified) {
            if (tPtr->selection.count) {
                WMDeleteTextFieldRange(tPtr, tPtr->selection);
                data = (void*)WMDeleteTextEvent;
                textEvent = WMTextDidChangeNotification;
            } else if (tPtr->cursorPosition < tPtr->textLen) {
                WMRange range;
                range.position = tPtr->cursorPosition;
                range.count = 1;
                WMDeleteTextFieldRange(tPtr, range);
                data = (void*)WMDeleteTextEvent;
                textEvent = WMTextDidChangeNotification;
            }

            relay = False;
        }
        break;

    normal_key:
    default:
        if (!controled) {
            if (count > 0 && !iscntrl(buffer[0])) {
                if (tPtr->selection.count)
                    WMDeleteTextFieldRange(tPtr, tPtr->selection);
                WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition);
                data = (void*)WMInsertTextEvent;
                textEvent = WMTextDidChangeNotification;

                relay = False;
            }
        }
        break;
    }

    if (relay) {
        WMRelayToNextResponder(W_VIEW(tPtr), event);
        return;
    }

    /* Do not allow text selection in secure text fields */
    if (cancelSelection || tPtr->flags.secure) {
        lostSelection(tPtr->view, XA_PRIMARY, NULL);

        if (tPtr->selection.count) {
            tPtr->selection.count = 0;
            refresh = 1;
        }
        tPtr->selection.position = tPtr->cursorPosition;
    } else {
        if (tPtr->selection.count != tPtr->cursorPosition - tPtr->selection.position) {

            tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;

            refresh = 1;
        }
    }

    /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/

    if (textEvent) {
        WMNotification *notif = WMCreateNotification(textEvent, tPtr, data);

        if (tPtr->delegate) {
            if (textEvent==WMTextDidBeginEditingNotification &&
                tPtr->delegate->didBeginEditing)
                (*tPtr->delegate->didBeginEditing)(tPtr->delegate, notif);

            else if (textEvent==WMTextDidEndEditingNotification &&
                     tPtr->delegate->didEndEditing)
                (*tPtr->delegate->didEndEditing)(tPtr->delegate, notif);

            else if (textEvent==WMTextDidChangeNotification &&
                     tPtr->delegate->didChange)
                (*tPtr->delegate->didChange)(tPtr->delegate, notif);
        }

        WMPostNotification(notif);
        WMReleaseNotification(notif);
    }

    if (refresh)
        paintTextField(tPtr);

    /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count);*/
}


static int
pointToCursorPosition(TextField *tPtr, int x)
{
    int a, b, mid;
    int tw;

    if (tPtr->flags.bordered)
        x -= 2;

    if (WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
                        tPtr->textLen - tPtr->viewPosition) < x)
        return tPtr->textLen;

    a = tPtr->viewPosition;
    b = tPtr->textLen;

    while (a < b && b-a>1) {
        mid = (a+b)/2;
        tw = WMWidthOfString(tPtr->font, &(tPtr->text[tPtr->viewPosition]),
                             mid - tPtr->viewPosition);
        if (tw > x)
            b = mid;
        else if (tw < x)
            a = mid;
        else
            return mid;
    }

    return (a+b)/2;
}



static void
pasteText(WMView *view, Atom selection, Atom target, Time timestamp,
          void *cdata, WMData *data)
{
    TextField *tPtr = (TextField*)view->self;
    char *str;

    tPtr->flags.waitingSelection = 0;

    if (data != NULL) {
        str = (char*)WMDataBytes(data);

        WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
        NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
               (void*)WMInsertTextEvent);
    } else {
        int n;

        str = XFetchBuffer(tPtr->view->screen->display, &n, 0);

        if (str != NULL) {
            str[n] = 0;
            WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition);
            XFree(str);
            NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
                   (void*)WMInsertTextEvent);
        }
    }
}


static void
handleTextFieldActionEvents(XEvent *event, void *data)
{
    TextField *tPtr = (TextField*)data;
    static int move = 0;
    static Time lastButtonReleasedEvent = 0;
    static Time lastButtonReleasedEvent2 = 0;
    Display *dpy = event->xany.display;

    CHECK_CLASS(data, WC_TextField);

    switch (event->type) {
    case KeyPress:
        if (tPtr->flags.waitingSelection) {
            return;
        }
        if (tPtr->flags.enabled && tPtr->flags.focused) {
            handleTextFieldKeyPress(tPtr, event);
            XDefineCursor(dpy, W_VIEW(tPtr)->window,
                          W_VIEW(tPtr)->screen->invisibleCursor);
            tPtr->flags.pointerGrabbed = 1;
        }
        break;

    case MotionNotify:

        if (tPtr->flags.pointerGrabbed) {
            tPtr->flags.pointerGrabbed = 0;
            XDefineCursor(dpy, W_VIEW(tPtr)->window,
                          W_VIEW(tPtr)->screen->textCursor);
        }
        if (tPtr->flags.waitingSelection) {
            return;
        }

        if (tPtr->flags.enabled && (event->xmotion.state & Button1Mask)) {

            if (tPtr->viewPosition < tPtr->textLen && event->xmotion.x >
                tPtr->usableWidth) {
                if (WMWidthOfString(tPtr->font,
                                    &(tPtr->text[tPtr->viewPosition]),
                                    tPtr->cursorPosition-tPtr->viewPosition)
                    > tPtr->usableWidth) {
                    tPtr->viewPosition++;
                }
            } else if (tPtr->viewPosition > 0 && event->xmotion.x < 0) {
                paintCursor(tPtr);
                tPtr->viewPosition--;
            }

            tPtr->cursorPosition =
                pointToCursorPosition(tPtr, event->xmotion.x);

            /* Do not allow text selection in secure textfields */
            if (tPtr->flags.secure) {
                tPtr->selection.position = tPtr->cursorPosition;
            }

            tPtr->selection.count = tPtr->cursorPosition - tPtr->selection.position;

            paintCursor(tPtr);
            paintTextField(tPtr);

        }
        break;

    case ButtonPress:
        if (tPtr->flags.pointerGrabbed) {
            tPtr->flags.pointerGrabbed = 0;
            XDefineCursor(dpy, W_VIEW(tPtr)->window,
                          W_VIEW(tPtr)->screen->textCursor);
            break;
        }

        if (tPtr->flags.waitingSelection) {
            break;
        }

        move = 1;
        switch (tPtr->flags.alignment) {
            int textWidth;
        case WARight:
            textWidth = WMWidthOfString(tPtr->font, tPtr->text, tPtr->textLen);
            if (tPtr->flags.enabled && !tPtr->flags.focused) {
                WMSetFocusToWidget(tPtr);
            }
            if (tPtr->flags.focused) {
                tPtr->selection.position = tPtr->cursorPosition;
                tPtr->selection.count = 0;
            }
            if (textWidth < tPtr->usableWidth) {
                tPtr->cursorPosition = pointToCursorPosition(tPtr,
                                                             event->xbutton.x - tPtr->usableWidth
                                                             + textWidth);
            } else tPtr->cursorPosition = pointToCursorPosition(tPtr,
                                                                event->xbutton.x);

            paintTextField(tPtr);
            break;

        case WALeft:
            if (tPtr->flags.enabled && !tPtr->flags.focused) {
                WMSetFocusToWidget(tPtr);
            }
            if (tPtr->flags.focused && event->xbutton.button == Button1) {
                tPtr->cursorPosition = pointToCursorPosition(tPtr,
                                                             event->xbutton.x);
                tPtr->selection.position = tPtr->cursorPosition;
                tPtr->selection.count = 0;
                paintTextField(tPtr);
            }
            if (event->xbutton.button == Button2 && tPtr->flags.enabled) {
                char *text;
                int n;

                if (!WMRequestSelection(tPtr->view, XA_PRIMARY, XA_STRING,
                                        event->xbutton.time,
                                        pasteText, NULL)) {
                    text = XFetchBuffer(tPtr->view->screen->display, &n, 0);

                    if (text) {
                        text[n] = 0;
                        WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition);
                        XFree(text);
                        NOTIFY(tPtr, didChange, WMTextDidChangeNotification,
                               (void*)WMInsertTextEvent);
                    }
                } else {
                    tPtr->flags.waitingSelection = 1;
                }
            }
            break;
        default:
            break;
        }
        break;

    case ButtonRelease:
        if (tPtr->flags.pointerGrabbed) {
            tPtr->flags.pointerGrabbed = 0;
            XDefineCursor(dpy, W_VIEW(tPtr)->window,
                          W_VIEW(tPtr)->screen->textCursor);
        }
        if (tPtr->flags.waitingSelection) {
            break;
        }

        if (!tPtr->flags.secure && tPtr->selection.count!=0) {
            int start, count;
            XRotateBuffers(dpy, 1);

            count = abs(tPtr->selection.count);
            if (tPtr->selection.count < 0)
                start = tPtr->selection.position - count;
            else
                start = tPtr->selection.position;

            XStoreBuffer(dpy, &tPtr->text[start], count, 0);
        }

        move = 0;

        if (!tPtr->flags.secure &&
            event->xbutton.time - lastButtonReleasedEvent
            <= WINGsConfiguration.doubleClickDelay) {

            if (event->xbutton.time - lastButtonReleasedEvent2 <= 2*WINGsConfiguration.doubleClickDelay) {
                tPtr->selection.position = 0;
                tPtr->selection.count = tPtr->textLen;
            } else {
                int pos, cnt;
                char *txt;
                pos = tPtr->selection.position;
                cnt = tPtr->selection.count;
                txt = tPtr->text;
                while(pos >= 0) {
                    if (txt[pos] == ' ' || txt[pos] == '\t') break;
                    pos--;
                }
                pos++;

                while(pos + cnt < tPtr->textLen) {
                    if (txt[pos + cnt] == ' ' || txt[pos + cnt] == '\t')
                        break;
                    cnt++;
                }
                tPtr->selection.position = pos;
                tPtr->selection.count = cnt;
            }
            paintTextField(tPtr);

            if (!tPtr->flags.ownsSelection) {
                tPtr->flags.ownsSelection =
                    WMCreateSelectionHandler(tPtr->view,
                                             XA_PRIMARY,
                                             event->xbutton.time,
                                             &selectionHandler, NULL);
            }
        } else if (!tPtr->flags.secure && tPtr->selection.count!=0 &&
                   !tPtr->flags.ownsSelection) {
            tPtr->flags.ownsSelection =
                WMCreateSelectionHandler(tPtr->view,
                                         XA_PRIMARY,
                                         event->xbutton.time,
                                         &selectionHandler, NULL);
        }

        lastButtonReleasedEvent2 = lastButtonReleasedEvent;
        lastButtonReleasedEvent = event->xbutton.time;

        break;
    }
}


static void
destroyTextField(TextField *tPtr)
{
#if 0
    if (tPtr->timerID)
        WMDeleteTimerHandler(tPtr->timerID);
#endif

    W_DestroyIC(tPtr->view);

    WMReleaseFont(tPtr->font);
    /*// use lostSelection() instead of WMDeleteSelectionHandler here?*/
    WMDeleteSelectionHandler(tPtr->view, XA_PRIMARY, CurrentTime);
    WMRemoveNotificationObserver(tPtr);

    if (tPtr->text)
        wfree(tPtr->text);

    wfree(tPtr);
}




syntax highlighted by Code2HTML, v. 0.9.1