/* 
 * sxDispatch.c --
 *
 *	This file implements the sx dispatcher, which processes
 *	events and calls clients that have expressed an interest
 *	in the events.
 *
 * Copyright (C) 1986 Regents of the University of California
 * 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.  The University of California makes no
 * representations about the suitability of this software for
 * any purpose.  It is provided "as is" without express or
 * implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /sprite/src/lib/sx/RCS/sxDispatch.c,v 1.6 89/05/12 09:33:41 ouster Exp $ SPRITE (Berkeley)";
#endif not lint

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "sx.h"

/*
 * Library imports:
 */

extern char *malloc();
extern void panic();

/*
 * The following data structure is used to keep track of the handlers
 * declared by calls to Sx_HandlerCreate.  One such structure is allocated
 * for each call to Sx_HandlerCreate.  In order to keep users from having
 * to see the guts of a handler, it's declared here as a different type
 * and cast into and out of the user's type as needed.
 */

typedef struct Handler {
    Display *display;		/* Display containing window. */
    Window w;			/* Window in which event must occur. */
    unsigned long mask;		/* Mask of events in w that are desired. */
    void (*proc)();		/* Procedure to call for each such event. */
    ClientData clientData;	/* Additional stuff to pass to proc. */
    struct Handler *nextPtr;	/* Next handler in list of those associated
				 * with this window, or NULL for end of
				 * list. */
} Handler;

/*
 * One of the following structures exists for each window managed in
 * any way by this module.
 */

typedef struct WindowStuff {
    Window w;				/* X's id for window. */

    /*
     * Handlers declared for the window.
     */

    unsigned long selectMask;		/* Mask of events for which X
					 * notification has been requested. */
    Handler *firstHandler;		/* First in list of handlers
					 * associated with this window. */

    /*
     * If the window is a top-level application window with focussing
     * enabled for subwindows, the following information is relevant.
     */

    int childCount;			/* Count of number of windows sharing
					 * focus off this window (i.e. # of
					 * distinct w's passed to
					 * Sx_EnableFocus with this as
					 * applWindow). */
    struct WindowStuff *curFocusPtr;	/* Window that currently has the focus,
					 * or NULL, there is no focus window.
					 * right now. */
    Sx_EventHandler crossingHandler;	/* Handler for enter/leave events. */
    int mouseInWindow;			/* Non-zero means mouse is currently
					 * in this window. */

    /*
     * The information below is relevant for windows that can receive
     * the focus.  Nothing in this section is valid if applPtr == NULL.
     */

    struct WindowStuff *applPtr;	/* Points to info for overall
					 * application (or NULL if this window
					 * was never passed as "w" to
					 * Sx_EnableFocus. */
    void (*proc)();			/* Procedure to call when w gets or
					 * or loses the focus. */
    ClientData clientData;		/* Additional argument to pass to
					 * proc. */
} WindowStuff;

/*
 * To make it easy to locate information associated with a particular
 * window, an XContext is used to keep track of all the WindowStuff
 * structures:
 */

static XContext dispatchContext;
static int initialized = 0;

/*
 * There's a potential problem if a handler is deleted while it's
 * current (i.e. it's procedure is executing), since Sx_HandleEvent
 * will need to read the handle's "nextPtr" field when the procedure
 * returns.  To handle this problem, structures of the type below
 * indicate the next handler to be processed for any (recursively
 * nested) dispatches in progress.  The nextHandler fields get updated
 * if the handlers pointed to are deleted.
 */

typedef struct InProgress {
    Handler *nextHandler;	/* Next handler in somebody's search. */
    struct InProgress *nextPtr;	/* Next higher recursive search. */
} InProgress;

static InProgress *pendingPtr = NULL;
				/* Topmost search in progress, or NULL if
				 * none. */

/*
 * Array of event masks corresponding to each X event:
 */

static unsigned long eventMasks[] = {
    0,
    0,
    KeyPressMask,			/* KeyPress */
    KeyPressMask,			/* KeyRelease */
    ButtonPressMask,			/* ButtonPress */
    ButtonReleaseMask,			/* ButtonRelease */
    PointerMotionMask|PointerMotionHintMask|ButtonMotionMask
	    |Button1MotionMask|Button2MotionMask|Button3MotionMask
	    |Button4MotionMask|Button5MotionMask,
					/* MotionNotify */
    EnterWindowMask,			/* EnterNotify */
    LeaveWindowMask,			/* LeaveNotify */
    FocusChangeMask,			/* FocusIn */
    FocusChangeMask,			/* FocusOut */
    KeymapStateMask,			/* KeymapNotify */
    ExposureMask,			/* Expose */
    ExposureMask,			/* GraphicsExpose */
    ExposureMask,			/* NoExpose */
    VisibilityChangeMask,		/* VisibilityNotify */
    SubstructureNotifyMask,		/* CreateNotify */
    StructureNotifyMask,		/* DestroyNotify */
    StructureNotifyMask,		/* UnmapNotify */
    StructureNotifyMask,		/* MapNotify */
    SubstructureRedirectMask,		/* MapRequest */
    StructureNotifyMask,		/* ReparentNotify */
    StructureNotifyMask,		/* ConfigureNotify */
    SubstructureRedirectMask,		/* ConfigureRequest */
    StructureNotifyMask,		/* GravityNotify */
    ResizeRedirectMask,			/* ResizeRequest */
    StructureNotifyMask,		/* CirculateNotify */
    SubstructureRedirectMask,		/* CirculateRequest */
    PropertyChangeMask,			/* PropertyNotify */
    0,					/* SelectionClear */
    0,					/* SelectionRequest */
    0,					/* SelectionNotify */
    ColormapChangeMask,			/* ColormapNotify */
    0,					/* ClientMessage */
    0,					/* Mapping Notify */
};

/*
 * Forward declarations for procedures defined later in this file:
 */

static void		FocusCrossingProc();
static WindowStuff *	GetWindowStuff();

/*
 *----------------------------------------------------------------------
 *
 * Sx_HandlerCreate --
 *
 *	This procedure sets up a handler for a particular class
 *	of events on a particular window.
 *
 * Results:
 *	The return value is a handler, which can be passed to
 *	Sx_HandlerDelete to delete the handler (the handler should
 *	be deleted before deleting w).
 *
 * Side effects:
 *	From now on, whenever an event of one of the types given
 *	by mask occurs for w and is processed by Sx_HandleEvent,
 *	proc will be called as shown below:
 *
 *	void
 *	proc(clientData, eventPtr)
 *	    ClientData clientData;
 *	    XEvent *eventPtr;
 *	{
 *	}
 *
 *	In the call to proc, clientData is the same value passed to
 *	Sx_HandlerCreate and eventPtr refers to X'es description of
 *	the event.  If multiple handlers have been specified for a
 *	given event, then they are invoked in the order they were set
 *	up.  Warning:  clients requesting one kind of PointerMotion
 *	event may receive other kinds of PointerMotion events as well.
 *
 *	Another side effect of this procedure is that a call is made
 *	to XSelectInput to change the set of events selected for this
 *	window.
 *
 *----------------------------------------------------------------------
 */

Sx_EventHandler
Sx_HandlerCreate(display, w, mask, proc, clientData)
    Display *display;		/* Display for window. */
    Window w;			/* Window for which event was generated. */
    unsigned long mask;		/* Events for which proc should be called. */
    void (*proc)();		/* Procedure to call for each selected event */
    ClientData clientData;	/* Arbitrary data to pass to proc. */
{
    Handler *handlerPtr;
    register Handler *h2Ptr;
    unsigned long windowMask;
    WindowStuff *wsPtr;

    if (!initialized) {
	dispatchContext = XUniqueContext();
	initialized = 1;
    }
    wsPtr = GetWindowStuff(display, w);

    /*
     * Add a new handler to the end of the list of handlers for the
     * window.  While skimming through the list, compute the overall
     * event mask for the window, so we can pass this new value to
     * the X system.
     */

    handlerPtr = (Handler *) malloc(sizeof(Handler));
    handlerPtr->display = display;
    handlerPtr->w = w;
    handlerPtr->mask = mask;
    handlerPtr->proc = proc;
    handlerPtr->clientData = clientData;
    handlerPtr->nextPtr = NULL;
    windowMask = mask | StructureNotifyMask;
    if (wsPtr->firstHandler == NULL) {
	wsPtr->firstHandler = handlerPtr;
    } else {
	for (h2Ptr = wsPtr->firstHandler; ; h2Ptr = h2Ptr->nextPtr) {
	    windowMask |= h2Ptr->mask;
	    if (h2Ptr->nextPtr == NULL) {
		h2Ptr->nextPtr = handlerPtr;
		break;
	    }
	}
    }

    /*
     * Make sure that X knows about all the events of interest in this
     * window, if the event mask has changed and if there are any events
     * requested.  Note:  keystroke event masks get modified to reflect
     * focussing.
     */

    if (wsPtr->applPtr != NULL) {
	windowMask &= ~KeyPressMask;
    }
    if (wsPtr->childCount > 0) {
	windowMask |= KeyPressMask;
    }
    if (windowMask ^ wsPtr->selectMask) {
	XSelectInput(display, w, windowMask);
    }
    wsPtr->selectMask = windowMask;

    return (Sx_EventHandler) handlerPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_HandlerDelete --
 *
 *	Remove a handler.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	After this call, the handler will never be invoked again (unless
 *	it is recreated with a call to Sx_HandlerCreate).
 *
 *----------------------------------------------------------------------
 */

void
Sx_HandlerDelete(handlerToken)
    Sx_EventHandler handlerToken;	/* Token for handler to delete;  must
					 * have been returned by previous call
					 * to Sx_HandlerCreate. */
{
    register Handler *handlerPtr = (Handler *) handlerToken;
    WindowStuff *wsPtr;
    InProgress *ipPtr;
    register Handler *prevPtr;
    unsigned long mask;

    /*
     * Remove the handler from the chain associated with its
     * window, and compute the new mask of events desired from
     * the window.
     */

    if (!initialized || (XFindContext(handlerPtr->display, handlerPtr->w,
	    dispatchContext, (caddr_t *) &wsPtr) != 0)) {
	return;
    }
    mask = StructureNotifyMask;
    if (wsPtr->firstHandler == handlerPtr) {
	wsPtr->firstHandler = handlerPtr->nextPtr;
    } else for (prevPtr = wsPtr->firstHandler; prevPtr != NULL;
	    prevPtr = prevPtr->nextPtr) {
	if (prevPtr->nextPtr == handlerPtr) {
	    prevPtr->nextPtr = handlerPtr->nextPtr;
	}
	mask |= prevPtr->mask;
    }

    /*
     * If Sx_HandleEvent is about to process this handler, tell it to
     * process the next one instead.
     */

    for (ipPtr = pendingPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) {
	if (ipPtr->nextHandler == handlerPtr) {
	    ipPtr->nextHandler = handlerPtr->nextPtr;
	}
    }

    /*
     * Free the handler and tell X about the events we're now interested in.
     * Modify the event mask to reflect redirection of keystrokes because
     * of focussing.
     */

    if (wsPtr->applPtr != NULL) {
	mask &= ~KeyPressMask;
    }
    if (wsPtr->childCount > 0) {
	mask |= KeyPressMask;
    }
    if (mask ^ wsPtr->selectMask) {
	XSelectInput(handlerPtr->display, handlerPtr->w, mask);
    }
    wsPtr->selectMask = mask;
    free((char *) handlerPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_HandleEvent --
 *
 *	This procedure processes an event, calling all the handlers
 *	that have been declared for that event.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The only side effects are those taken by the handlers.
 *
 *----------------------------------------------------------------------
 */

void
Sx_HandleEvent(eventPtr)
    register XEvent *eventPtr;	/* Event to be parceled off to handler(s). */
{
    register Handler *handlerPtr;
    WindowStuff *wsPtr;
    register unsigned long mask;
    InProgress ip, *ipPtr;

    /*
     * Locate information for the event's window.
     */

    if (!initialized) {
	return;
    }

    /*
     * For some events, have to figure out whether they are StructureNotify
     * or SubstructureNotify.
     */

    mask = eventMasks[eventPtr->xany.type];
    if (mask == StructureNotifyMask) {
	if (eventPtr->xmap.event != eventPtr->xmap.window) {
	    mask = SubstructureNotifyMask;
	}
    }
    if (XFindContext(eventPtr->xany.display, eventPtr->xany.window,
	    dispatchContext, (caddr_t *) &wsPtr) != 0) {
	return;
    }

    /*
     * If this is a keystroke event and this window is a focus source,
     * redirect the event to the correct focus destination.
     */

    if ((mask & (KeyPressMask)) && (wsPtr->childCount > 0)) {
	if (wsPtr->curFocusPtr == 0) {
	    return;
	}
	wsPtr = wsPtr->curFocusPtr;
    }

    /*
     * There's a potential interaction here with Sx_HandlerDelete.
     * Read the documentation for pendingPtr.
     */

    ip.nextPtr = pendingPtr;
    pendingPtr = &ip;
    for (handlerPtr = wsPtr->firstHandler; handlerPtr != NULL;
	    handlerPtr = ip.nextHandler) {
	ip.nextHandler = handlerPtr->nextPtr;
	if ((handlerPtr->mask & mask) != 0) {
	    (*(handlerPtr->proc))(handlerPtr->clientData, eventPtr);
	}
    }
    pendingPtr = ip.nextPtr;

    /*
     * If this is a deletion event, then free up all of our information
     * about the window.
     */

    if ((eventPtr->xany.type == DestroyNotify)
	    && (eventPtr->xdestroywindow.event
	    == eventPtr->xdestroywindow.window)) {
	register WindowStuff *applPtr;

	for (handlerPtr = wsPtr->firstHandler; handlerPtr != NULL;
		handlerPtr = handlerPtr->nextPtr) {
	    for (ipPtr = pendingPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) {
		if (ipPtr->nextHandler == handlerPtr) {
		    ipPtr->nextHandler = handlerPtr->nextPtr;
		}
	    }
	    free((char *) handlerPtr);
	}
	applPtr = wsPtr->applPtr;
	if (applPtr != 0) {
	    applPtr->childCount--;
	    if (applPtr->curFocusPtr == wsPtr) {
		applPtr->curFocusPtr = NULL;
	    }
	    if (applPtr->childCount == 0) {
		Sx_HandlerDelete(applPtr->crossingHandler);
	    }
	}
	XDeleteContext(eventPtr->xany.display, eventPtr->xany.window,
		dispatchContext);
	free((char *) wsPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_EnableFocus --
 *
 *	This procedure sets things up to allow focussing on a particular
 *	window.  Focussing is a mechanism that allows keystroke events to
 *	be dispatched between a number of competing windows, typically
 *	subwindows of an application.  Whenever the mouse is anywhere in
 *	the application's outermost window or any of its children,  one of
 *	the children is selected as focus window.  All keystroke events
 *	anywhere within the application are redispatched as if they
 *	occurred in the focus window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	W is entered into the focus group associated with "applWindow" on
 *	"display".  This permits Sx_Focus to be called later on to make w
 *	the focus window for the group.  Proc will be called later when
 *	w gets or loses the focus.  It should have the following structure:
 *
 *	void
 *	proc(clientData, gotFocus)
 *	    ClientData clientData;
 *	    int gotFocus;
 *	{
 *	}
 *
 *	The clientData argument will be the same as the clientData argument
 *	to this procedure.  GotFocus is 1 if w is getting the focus (meaning
 *	that Sx_Focus has been called specifying w and the mouse is in
 *	applWindow) or 0 if w is losing the focus (meaning the above
 *	condition has ceased to be true because the mouse left the window
 *	or Sx_Focus was called with a different w).
 *
 *----------------------------------------------------------------------
 */

void
Sx_EnableFocus(display, applWindow, w, proc, clientData)
    Display *display;		/* Display containing applWindow and w. */
    Window applWindow;		/* Outermost window of application;  keystrokes
				 * in this window get redispatched to focus
				 * window. */
    Window w;			/* Window onto which focussing is to be
				 * allowed (this argument may later be
				 * passed to Sx_Focus). */
    void (*proc)();		/* Procedure to call when w gets or loses
				 * the focus. */
    ClientData clientData;	/* Arbitrary value to pass to proc. */
{
    register WindowStuff *applPtr;
    register WindowStuff *wsPtr;

    applPtr = GetWindowStuff(display, applWindow);
    wsPtr = GetWindowStuff(display, w);

    /*
     * If this is the first w for applPtr, then create a handler to keep
     * track of whether the mouse is in applWindow, and make sure the
     * applWindow receives KeyPress events.
     */

    if (applPtr->childCount == 0) {
	applPtr->crossingHandler = Sx_HandlerCreate(display, applWindow,
		EnterWindowMask|LeaveWindowMask, FocusCrossingProc,
		(ClientData) applPtr);
	applPtr->mouseInWindow = 0;
	if (!(applPtr->selectMask & KeyPressMask)) {
	    applPtr->selectMask |= KeyPressMask;
	    XSelectInput(display, applPtr->w, applPtr->selectMask);
	}
    }
    applPtr->childCount++;

    /*
     * Create a new FocusWindow structure and link it into the group.
     */

    wsPtr->applPtr = applPtr;
    wsPtr->proc = proc;
    wsPtr->clientData = clientData;
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_Focus --
 *
 *	Change the focus.  The display and w arguments should be the same
 *	as those passed to some previous call to Sx_EnableFocus.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window "w" will be the focus window for its application
 *	whenever the mouse is in the application's window.  The old focus
 *	window's outProc and/or the new focus window's inProc may be
 *	called (see Sx_EnableFocus above for details).
 *
 *----------------------------------------------------------------------
 */

void
Sx_Focus(display, w)
    Display *display;		/* Display containing applWindow and w. */
    Window w;			/* Window to get focus. */
{
    WindowStuff *applPtr, *wsPtr;

    if (XFindContext(display, w, dispatchContext, (caddr_t *) &wsPtr) != 0) {
	panic("Sx_Focus called with unknown \"w\".\n");
    }
    applPtr = wsPtr->applPtr;
    if (applPtr == NULL) {
	panic("Sx_Focus called with unregistered \"w\".\n");
    }
    if (applPtr->curFocusPtr == wsPtr) {
	return;
    }

    /*
     * Notify the old focus window and new focus window, if the mouse
     * is in applPtr's window.
     */

    if (applPtr->mouseInWindow) {
	if (applPtr->curFocusPtr != NULL) {
	    (*applPtr->curFocusPtr->proc)(applPtr->curFocusPtr->clientData, 0);
	}
	(*wsPtr->proc)(wsPtr->clientData, 1);
    }

    applPtr->curFocusPtr = wsPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * GetWindowStuff --
 *
 *	Locate our information for a window, and make a new record if
 *	none exists yet.
 *
 * Results:
 *	The return value is a pointer to the information related to w.
 *
 * Side effects:
 *	Memory may be allocated.
 *
 *----------------------------------------------------------------------
 */

static WindowStuff *
GetWindowStuff(display, w)
    Display *display;		/* Display holding w. */
    Window w;			/* Window whose WindowStuff is wanted. */
{
    WindowStuff *wsPtr;
    if (XFindContext(display, w, dispatchContext, (caddr_t *) &wsPtr) != 0) {
	wsPtr = (WindowStuff *) malloc(sizeof(WindowStuff));
	wsPtr->w = w;
	wsPtr->selectMask = 0;
	wsPtr->firstHandler = NULL;
	wsPtr->childCount = 0;
	wsPtr->curFocusPtr = NULL;
	wsPtr->crossingHandler = 0;
	wsPtr->mouseInWindow = 0;
	wsPtr->applPtr = NULL;
	XSaveContext(display, w, dispatchContext, (caddr_t) wsPtr);
    }
    return wsPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * FocusCrossingProc --
 *
 *	This procedure is invoked when the mouse moves into or out of
 *	a top-level application window used for focussing.  It notifies
 *	clients that they got or lost the focus.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Focus clients get notified.
 *
 *----------------------------------------------------------------------
 */

static void
FocusCrossingProc(applPtr, eventPtr)
    register WindowStuff *applPtr;	/* Information about window just
					 * entered or exited. */
    XEvent *eventPtr;
{
    if (eventPtr->xcrossing.detail == NotifyInferior) {
	return;
    }
    if (eventPtr->type == EnterNotify) {
	applPtr->mouseInWindow = 1;
	if (applPtr->curFocusPtr != NULL) {
	    (*applPtr->curFocusPtr->proc)(
		    applPtr->curFocusPtr->clientData, 1);
	}
    } else {
	applPtr->mouseInWindow = 0;
	if (applPtr->curFocusPtr != NULL) {
	    (*applPtr->curFocusPtr->proc)(
		    applPtr->curFocusPtr->clientData, 0);
	}
    }
}
