/* 
 * tkFocus.c --
 *
 *	This file contains procedures that manage the input
 *	focus for Tk.
 *
 * Copyright 1990-1992 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.  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: /user6/ouster/wish/RCS/tkFocus.c,v 1.4 93/02/03 10:02:23 ouster Exp $ SPRITE (Berkeley)";
#endif

#include "tkInt.h"
#include "tkConfig.h"

/*
 * The variable below records the address of the last focus event
 * generated by this module.  It's used to indicate to TkFilterFocusEvent
 * that it shouldn't process this event.
 */

static XEvent *generatedEventPtr;

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

static void		ChangeFocusTopLevelPtr _ANSI_ARGS_((TkDisplay *dispPtr,
			    TkWindow *winPtr));
static void		NotifyFocusWindow _ANSI_ARGS_((TkWindow *winPtr,
			    int type, int detail));
static void		SetFocus _ANSI_ARGS_((TkWindow *winPtr,
			    TkWindow *focusPtr));

/*
 *--------------------------------------------------------------
 *
 * Tk_CreateFocusHandler --
 *
 *	Arrange for a procedure to be called whenever the focus
 *	enters or leaves a given window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	After this procedure has been invoked, whenever tkwin gets
 *	or loses the input focus, proc will be called.  It should have
 *	the following structure:
 *
 *	void
 *	proc(clientData, gotFocus)
 *	    ClientData clientData;
 *	    int gotFocus;
 *	{
 *	}
 *
 *	The clientData argument to "proc" will be the same as the
 *	clientData argument to this procedure.  GotFocus will be
 *	1 if tkwin is getting the focus, and 0 if it's losing the
 *	focus.
 *
 *--------------------------------------------------------------
 */

void
Tk_CreateFocusHandler(tkwin, proc, clientData)
    Tk_Window tkwin;		/* Token for window. */
    Tk_FocusProc *proc;		/* Procedure to call when tkwin gets
				 * or loses the input focus. */
    ClientData clientData;	/* Arbitrary value to pass to proc. */
{
    register TkWindow *winPtr = (TkWindow *) tkwin;

    winPtr->focusProc = proc;
    winPtr->focusData = clientData;
}

/*
 *--------------------------------------------------------------
 *
 * Tk_FocusCmd --
 *
 *	This procedure is invoked to process the "focus" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
Tk_FocusCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with
				 * interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    Tk_Window tkwin = (Tk_Window) clientData;
    register TkWindow *winPtr = (TkWindow *) clientData;
    register TkWindow *newPtr;
    char c;
    int length;

    /*
     * If invoked with no arguments, just return the current focus window.
     */

    if (argc == 1) {
	if (winPtr->mainPtr->focusPtr == NULL) {
	    interp->result = "none";
	} else {
	    interp->result = winPtr->mainPtr->focusPtr->pathName;
	}
	return TCL_OK;
    }

    /*
     * If invoked with a single argument beginning with "." then focus
     * on that window.
     */

    if ((argc == 2) && (argv[1][0] == '.')) {
	newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[1], tkwin);
	if (newPtr == NULL) {
	    return TCL_ERROR;
	}
	SetFocus(winPtr, newPtr);
	return TCL_OK;
    }

    length = strlen(argv[1]);
    c = argv[1][0];
    if ((c == 'd') && (strncmp(argv[1], "default", length) == 0)) {
	if (argc > 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " default ?window?\"", (char *) NULL);
	    return TCL_ERROR;
	}
	if (argc == 2) {
	    if (winPtr->mainPtr->focusDefaultPtr == NULL) {
		interp->result = "none";
	    } else {
		interp->result = winPtr->mainPtr->focusDefaultPtr->pathName;
	    }
	    return TCL_OK;
	}
	if ((argv[2][0] == 'n')
		&& (strncmp(argv[2], "none", strlen(argv[2])) == 0)) {
	    newPtr = NULL;
	} else {
	    newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[2], tkwin);
	    if (newPtr == NULL) {
		return TCL_ERROR;
	    }
	}
	winPtr->mainPtr->focusDefaultPtr = newPtr;
    } else if ((c == 'n') && (strncmp(argv[1], "none", length) == 0)) {
	if (argc != 2) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " none\"", (char *) NULL);
	    return TCL_ERROR;
	}
	SetFocus(winPtr, (TkWindow *) NULL);
    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
		"\": must be default or none", (char *) NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * TkFocusFilterEvent --
 *
 *	This procedure is invoked by Tk_HandleEvent when it encounters
 *	a FocusIn, FocusOut, Enter, or Leave event.
 *
 * Results:
 *	A return value of 1 means that Tk_HandleEvent should process
 *	the event normally (i.e. event handlers should be invoked).
 *	A return value of 0 means that this event should be ignored.
 *
 * Side effects:
 *	An additional event may be generated and processed.
 *
 *--------------------------------------------------------------
 */

int
TkFocusFilterEvent(winPtr, eventPtr)
    register TkWindow *winPtr;	/* Window that focus event is directed to. */
    XEvent *eventPtr;		/* FocusIn or FocusOut event. */
{
    if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
	/*
	 * If this event was generated by us then just process it
	 * normally.
	 */
    
	if (eventPtr == generatedEventPtr) {
	    return 1;
	}
    
	/*
	 * Make sure that we're dealing with a top-level window.
	 */
    
	while (!(winPtr->flags & TK_TOP_LEVEL)) {
	    winPtr = winPtr->parentPtr;
	}
    
	/*
	 * This is a "raw" event coming from the server, presumably for a
	 * top-level window.  Notify both the top-level window and the window
	 * that has been assigned the focus by the Tk "focus" command.
	 */
    
	if (eventPtr->type == FocusOut) {
	    ChangeFocusTopLevelPtr(winPtr->dispPtr, (TkWindow *) NULL);
	} else {
	    ChangeFocusTopLevelPtr(winPtr->dispPtr, winPtr);
	}
    
	/*
	 * This particular event should now be ignored, since we just
	 * generated events to notify all of the relevant windows.
	 */
    
	return 0;
    } else {
	/*
	 * This is an Enter or Leave event.  If there's no window manager,
	 * or if the window manager is not moving the focus around (e.g.
	 * if the disgusting "NoTitleFocus" option has been selected in
	 * twm), then we won't get FocusIn or FocusOut events.  Instead,
	 * watch enter and leave events.  If an Enter event arrives for a
	 * top-level window with its focus field set, but we don't have a
	 * record of a FocusIn event, then simulate one.  If a Leave event
	 * arrives and focus was set for the window via an Enter event,
	 * then simulate a FocusOut event.
	 */

	if ((eventPtr->type == EnterNotify) && (winPtr->flags & TK_TOP_LEVEL)
		&& eventPtr->xcrossing.focus
		&& (eventPtr->xcrossing.detail != NotifyInferior)
		&& (winPtr->dispPtr->focusTopLevelPtr != winPtr)) {
	    ChangeFocusTopLevelPtr(winPtr->dispPtr, winPtr);
	    winPtr->dispPtr->focussedOnEnter = 1;
	} else if ((eventPtr->type == LeaveNotify)
		&& (winPtr->dispPtr->focussedOnEnter)
		&& (eventPtr->xcrossing.detail != NotifyInferior)
		&& (winPtr->dispPtr->focusTopLevelPtr == winPtr)) {
	    ChangeFocusTopLevelPtr(winPtr->dispPtr, (TkWindow *) NULL);
	}
	return 1;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ChangeFocusTopLevelPtr --
 *
 *	This procedure is invoked to change the focusTopLevelPtr field
 *	of a display.  It notifies the old focus window, if any, and
 *	the new one.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Windows get notified, and they can do just about anything
 *	as part of the notification.
 *
 *----------------------------------------------------------------------
 */

static void
ChangeFocusTopLevelPtr(dispPtr, winPtr)
    TkDisplay *dispPtr;			/* Display whose focus top-level
					 * changed. */
    TkWindow *winPtr;			/* Top-level window that is to be the
					 * new focus top-level for display.
					 * If NULL, clears the old focus
					 * window without setting a new one. */
{
    TkWindow *focusPtr;

    if (dispPtr->focusTopLevelPtr != NULL) {
	focusPtr = dispPtr->focusTopLevelPtr->mainPtr->focusPtr;
	if (focusPtr != NULL) {
	    NotifyFocusWindow(focusPtr, FocusOut, NotifyAncestor);
	}
	NotifyFocusWindow(dispPtr->focusTopLevelPtr, FocusOut,
		NotifyVirtual);
    }
    dispPtr->focusTopLevelPtr = winPtr;
    if (winPtr != NULL) {
	focusPtr = dispPtr->focusTopLevelPtr->mainPtr->focusPtr;
	NotifyFocusWindow(dispPtr->focusTopLevelPtr, FocusIn,
		NotifyVirtual);
	if (focusPtr != NULL) {
	    NotifyFocusWindow(focusPtr, FocusIn, NotifyAncestor);
	}
    }
    dispPtr->focussedOnEnter = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * SetFocus --
 *
 *	This procedure is invoked to change the focus window for
 *	an application.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Event handlers may be invoked to process the change of
 *	focus.
 *
 *----------------------------------------------------------------------
 */

static void
SetFocus(winPtr, focusPtr)
    TkWindow *winPtr;		/* Window that identifies the application
				 * whose focus is to change. */
    TkWindow *focusPtr;		/* Window that is to be the new focus for
				 * the application.  May be NULL. */
{
    if (winPtr->mainPtr->focusPtr == focusPtr) {
	return;
    }
    if ((winPtr->dispPtr->focusTopLevelPtr != NULL) &&
	    (winPtr->mainPtr == winPtr->dispPtr->focusTopLevelPtr->mainPtr)) {
	if (winPtr->mainPtr->focusPtr != NULL) {
	    NotifyFocusWindow(winPtr->mainPtr->focusPtr, FocusOut,
		    NotifyAncestor);
	}
	winPtr->mainPtr->focusPtr = focusPtr;
	if (focusPtr != NULL) {
	    NotifyFocusWindow(focusPtr, FocusIn, NotifyAncestor);
	}
    } else {
	winPtr->mainPtr->focusPtr = focusPtr;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * NotifyFocusWindow --
 *
 *	This procedure implements the mechanics of notifying a window
 *	that has just gotten or lost the focus.  It generates an
 *	appropriate X event and also uses the (now obsolete) mechanism
 *	of calling a focus procedure.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Depends on the actions associated with the focus event and
 *	procedure callback.
 *
 *----------------------------------------------------------------------
 */

static void
NotifyFocusWindow(winPtr, type, detail)
    TkWindow *winPtr;		/* Window that's getting or losing focus. */
    int type;			/* FocusIn or FocusOut:  tells whether
				 * winPtr is getting or losing the focus. */
    int detail;			/* Detail to use for event:  NotifyAncestor
				 * for the ultimate destination of the focus,
				 * and NotifyVirtual for the top-level window
				 * that actually got the X focus. */
{
    XEvent event;

    Tk_MakeWindowExist((Tk_Window) winPtr);

    /*
     * Generate an event for the focus change and process the event.
     */

    event.type = type;
    event.xfocus.serial = LastKnownRequestProcessed(winPtr->display);
    event.xfocus.send_event = True;
    event.xfocus.display = winPtr->display;
    event.xfocus.window = winPtr->window;
    event.xfocus.mode = NotifyNormal;
    event.xfocus.detail = detail;
    generatedEventPtr = &event;
    Tk_HandleEvent(&event);
    generatedEventPtr = NULL;

    if ((detail == NotifyAncestor) && (winPtr->focusProc != NULL)) {
	(*winPtr->focusProc)(winPtr->focusData, (type == FocusIn));
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkFocusDeadWindow --
 *
 *	This procedure is invoked when it is determined that
 *	a window is dead.  It cleans up focus-related information
 *	about the window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Various things get cleaned up and recycled.
 *
 *----------------------------------------------------------------------
 */

void
TkFocusDeadWindow(winPtr)
    register TkWindow *winPtr;		/* Information about the window
					 * that is being deleted. */
{
    if (winPtr->mainPtr != NULL) {
	if (winPtr->mainPtr->focusDefaultPtr == winPtr) {
	    winPtr->mainPtr->focusDefaultPtr = NULL;
	}
	if (winPtr->mainPtr->focusPtr == winPtr) {
	    SetFocus(winPtr, winPtr->mainPtr->focusDefaultPtr);
	}
    }
    if (winPtr->dispPtr->focusTopLevelPtr == winPtr) {
	winPtr->dispPtr->focusTopLevelPtr = NULL;
    }
}
