/*
 *	$Source: /u1/X/DECToolkit/src/RCS/Event.c,v $
 *	$Header: Event.c,v 1.1 86/12/17 09:01:19 swick Exp $
 */

#ifndef lint
static char *rcsid_Event_c = "$Header: Event.c,v 1.1 86/12/17 09:01:19 swick Exp $";
#endif	lint

#ifndef lint
static char *sccsid = "@(#)Event.c	1.10	12/11/86";
#endif lint
/*
 *			  COPYRIGHT 1986
 *		   DIGITAL EQUIPMENT CORPORATION
 *		       MAYNARD, MASSACHUSETTS
 *			ALL RIGHTS RESERVED.
 *
 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 *
 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT RIGHTS,
 * APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN ADDITION TO THAT
 * SET FORTH ABOVE.
 *
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting documentation,
 * and that the name of Digital Equipment Corporation not be used in advertising
 * or publicity pertaining to distribution of the software without specific, 
 * written prior permission.
 */

/* File: Event.c */

#include <X/Xlib.h>
#include "Toolkit.h"

/*
 * General Comment:
 * This file contains much code that compensates for deficiencies
 * under X10.  For example, it synthesizes the ResizeWindow event
 * and mungs with the ordering of ExposeWindow events.  Massive
 * changes to this file (mostly lobotomies) will be required when
 * porting this code to X11.  Performance under X11 should be much
 * faster (and the code should be easier to read!). --mjg
 */


/* Private Definitions */

#define GRABEVENTS	(KeyPressed | KeyReleased | ButtonPressed | \
                         ButtonReleased | MouseMoved)

typedef struct _ProcRec {
    int (*proc)();
    int mask;
    caddr_t data;
    struct _ProcRec *next;
} ProcRec, *ProcPtr;

typedef struct _EventRec {
    Window window;
    int width, height;	/* Size of window, last we knew. */
    int needsexpose;	/* Whether we need to expose this window. */
    struct _EventRec *nextexpose;
    ProcPtr firstproc;
    int totalmask;
    short disableautograb;
} EventRec, *EventPtr;

extern char *Tmalloc();
static TEntryType eventContext;
static int initialized = FALSE;
EventPtr FirstExpose = 0;
EventRec GlobalCtx;

static TEntryType autograbContext;
static int AutoGrab,GrabOn;
static Window GrabWindow;

/* Private Routines */

static int Initialize()
{
    eventContext = UniqueEntryType();
    autograbContext = UniqueEntryType();
    initialized = TRUE;
    GlobalCtx.firstproc = NULL;
    GlobalCtx.window = 0;
    SaveEntry(0, eventContext, &GlobalCtx);
}

static EventPtr EventPtrFromWindow(w)
{
    EventPtr result;
    if (FindEntry(w, eventContext, &result) == ERRNONE)
        return(result);
    return(0);
}

/*
 * Delete the event handler reference in the dispatcher for this window.
 */
static ResetXEventDispatch(w)
Window w;
{
    EventPtr ctx;
    ProcPtr p, p2;

    ctx = EventPtrFromWindow(w);
    if (!ctx)
        return(ERRNOTFOUND);
    if (ctx->needsexpose)
        ctx->needsexpose = 2;	/* Cause it to be freed later. */
    else {
        for (p=ctx->firstproc ; p != NULL ; p2 = p->next, free(p), p = p2)
            /* NULL */;
        free(ctx);
    }
    return(DeleteEntry(w, eventContext));
}

static int CallProc(ctx, event)
EventPtr ctx;
XEvent *event;
{
    EventPtr first;
    ProcPtr p;
    int     type, result;
    int     finalresult = NOENTRY;
    type = event->type;

    /*
     * The following hack gets around the problem of
     * no "ResizeWindow" event under X10.
     */
    if (type == ResizeWindow)		/* HACK %%% */
        type = ExposeWindow;		/* HACK %%% */

    for (first = &GlobalCtx; first != NULL;
         first = (first == ctx) ? NULL : ctx) {
        for (p = first->firstproc; p != NULL; p = p->next) {
            if (type & p->mask ||
                ((type & MouseMoved) &&
                (p->mask & (LeftDownMotion | MiddleDownMotion |
                 RightDownMotion)))) {
                result = p->proc(event, p->data);
                if (finalresult != PROCESSED)
                    finalresult = result;
	    }
	}
    }
    return(finalresult);
}


/* Public routines */

/*
 * Dispatch an event to the appropriate subwindow.
 * Return a value indicating how the event was handled.
 * Note that all of the hair in this procedure is due to 
 * limitations in X10 that will be fixed in X11.
 */
int TDispatchXEvent(event)
XEvent *event;
{
    EventPtr ctx;
    int error;
    int result;

    if (event->type == ButtonPressed) {
        error = FindEntry(event->window, autograbContext, &AutoGrab);
        if (error == ERRNOTFOUND)
            AutoGrab = FALSE;
        GrabWindow = event->window;
    }
    if ((GrabOn || AutoGrab) && ((event->type & GRABEVENTS))) {
        XKeyOrButtonEvent * ev = (XKeyOrButtonEvent *) event;
        ev->window = GrabWindow;
        XInterpretLocator(GrabWindow, &(ev->x), &(ev->y),
                          &(ev->subwindow), ev->location);
    }
    if (event->type == ButtonReleased)
        AutoGrab = FALSE;
    error = FindEntry(event->window, eventContext, &ctx);
    if (error != ERRNONE)
        result = NOENTRY;
    else {
        if (event->type == ExposeWindow && event->subwindow == 0) {
            XExposeWindowEvent *ev = (XExposeWindowEvent *) event;
            if (ctx->width != ev->width || ctx->height != ev->height) {
        	ev->type = ResizeWindow;
                CallProc(ctx, ev);
                ctx->width = ev->width;
                ctx->height = ev->height;
            }
            if (!ctx->needsexpose) {
                ctx->needsexpose = TRUE;
                ctx->nextexpose = FirstExpose;
                FirstExpose = ctx;
            }
            result = PROCESSED;
        }
        else
            result = CallProc(ctx, event);
    }
    if (FirstExpose && QLength() == 0) {
        XExposeWindowEvent ev;
        XSync(0);
        while (FirstExpose && QLength() == 0) {
            ev.type = ExposeWindow;
            ev.window = FirstExpose->window;
            ev.detail = 0;
            ev.width = FirstExpose->width;
            ev.height = FirstExpose->height;
            ev.subwindow = 0;
            ev.x = ev.y = 0;
            if (FirstExpose->needsexpose == 2) {
                ctx = FirstExpose;
                FirstExpose = FirstExpose->nextexpose;
                free(ctx);
            } else {
                CallProc(FirstExpose, &ev);
                FirstExpose->needsexpose = FALSE;
                FirstExpose = FirstExpose->nextexpose;
            }
        }
    }
    return(result);
}

/*
 * Register an event handler "proc" for window "w" with the
 * event dispatcher for "eventmask" events.
 * Note that re-registering the same window and handler with
 * a new event mask OVERWRITES the previous eventmask for that
 * window/handler combination.
 */
int TSetXEventDispatch(w, proc, eventmask, data)
Window w;
int (*proc)();
int eventmask;
caddr_t data;
{
    EventPtr ctx;
    ProcPtr p, p2;
    int totalmask;
    if (initialized == FALSE)
        Initialize();
    ctx = EventPtrFromWindow(w);
    if (ctx == 0) {
        ctx = (EventPtr)Tmalloc(sizeof(EventRec));
        ctx->window = w;
        ctx->width = 0;
        ctx->height = 0;
        ctx->needsexpose = FALSE;
        SaveEntry(w, eventContext, ctx);
        ctx->firstproc = NULL;
        ctx->totalmask = 0;
        ctx->disableautograb = FALSE;
    }
    totalmask = 0;
    p2 = NULL;
    p = ctx->firstproc;
    while (p != NULL && p->proc != proc) {
        totalmask |= p->mask;
        p2 = p;
        p = p->next;
    }
    if (p == NULL) {
        if (eventmask == 0)
            return;
        p = (ProcPtr)Tmalloc(sizeof(ProcRec));
        p->next = ctx->firstproc;
        ctx->firstproc = p;
        p->proc = proc;
    }
    if (eventmask) {
        p->mask = eventmask;
        p->data = data;
    }
    else {
        if (p2)
            p2->next = p->next;
        else
            ctx->firstproc = p->next;
        p2 = p;
        p = p->next;
        free(p2);
    }
    totalmask |= eventmask;
    for (; p != NULL; p = p->next)
        totalmask |= p->mask;
    if (totalmask != ctx->totalmask) {
        ctx->totalmask = totalmask;
        if (ctx->window) {
            if ((totalmask & ButtonPressed) && (totalmask & ButtonReleased) &&
                    (!(ctx->disableautograb)))
                SaveEntry(w, autograbContext, TRUE);
            else
                DeleteEntry(w, autograbContext);
            XSelectInput(w, totalmask);
        }
    }
}

/*
 * Arrange to have the given procedure called whenever ANY event happens
 * that matches the given mask.
 */
TSetNotify(proc, mask, data)
int (*proc)();
int mask;
caddr_t data;
{
    TSetXEventDispatch(0, proc, mask, data);
}

TDisableAutoGrab(w)
Window w;
{
    EventPtr ctx;
    ctx = EventPtrFromWindow(w);
    if (ctx) {
        ctx->disableautograb = TRUE;
        DeleteEntry(w, autograbContext);
    }
}

TEnableAutoGrab(w)
Window w;
{
    EventPtr ctx;
    ctx = EventPtrFromWindow(w);
    if (ctx) {
        ctx->disableautograb = FALSE;
        if ((ctx->totalmask & ButtonPressed) &&
                (ctx->totalmask & ButtonReleased))
            SaveEntry(w, autograbContext, TRUE);
    }
}


TDestroyWindow(window)
Window window;
{
    if (GrabWindow == window)
        AutoGrab = GrabOn = FALSE;
    ResetXEventDispatch(window);
    TClearGeometryRequest(window);
    DeleteEntry(window, autograbContext);
    XDestroyWindow(window);
}


TGrabMouse(w, cursor, mask)
Window w;
Cursor cursor;
int mask;
{
    GrabOn = TRUE;
    GrabWindow = w;
    XGrabMouse(w, cursor, mask);
}


TUngrabMouse()
{
    GrabOn = FALSE;
    XUngrabMouse();
}
