/* * tkFocus.c -- * * This file contains procedures that manage the input * focus for Tk. * * Copyright (c) 1990-1993 The Regents of the University of California. * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ #ifndef lint static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkFocus.c,v 1.7 93/06/16 17:16:47 ouster Exp $ SPRITE (Berkeley)"; #endif #include "tkInt.h" #include "tkConfig.h" /* * Magic value stored in "send_event" field to mark a FocusIn or FocusOut * event generated by this file. It indicates to TkFilterFocusEvent that * it shouldn't process this event. */ #define GENERATED_EVENT 0x123abcde /* * Forward declarations for procedures defined in this file: */ static void ChangeFocusTopLevelPtr _ANSI_ARGS_((TkDisplay *dispPtr, TkWindow *winPtr, int mode)); static void QueueFocusEvent _ANSI_ARGS_((TkWindow *winPtr, int type, int mode, 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->xfocus.send_event == GENERATED_EVENT) { eventPtr->xfocus.send_event = 1; return 1; } /* * Ignore the focus event if any of the following things is * true: * * 1. The event isn't for a top-level window. * 2. The event has detail NotifyInferior (which means the * focus moved around within the top-level window; it * didn't move between the top-level window and the * outside world. * 3. The event has detail NotifyPointer (I don't really understand * what these events are for, but they don't seem to serve * any useful purpose). */ if (!(winPtr->flags & TK_TOP_LEVEL) || (eventPtr->xfocus.detail == NotifyInferior) || (eventPtr->xfocus.detail == NotifyPointer)) { return 0; } /* * This is a useful event. 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, eventPtr->xfocus.mode); } else { ChangeFocusTopLevelPtr(winPtr->dispPtr, winPtr, eventPtr->xfocus.mode); } /* * 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, eventPtr->xcrossing.mode); 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, eventPtr->xcrossing.mode); } 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, mode) 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. */ int mode; /* Mode to use for generated events: * NotifyNormal, NotifyGrab, or * NotifyUngrab. */ { TkWindow *focusPtr; if (dispPtr->focusTopLevelPtr == winPtr) { /* * The focus is already where it's supposed to be, so there's * nothing else to do. */ return; } if (dispPtr->focusTopLevelPtr != NULL) { focusPtr = dispPtr->focusTopLevelPtr->mainPtr->focusPtr; if (focusPtr != NULL) { QueueFocusEvent(focusPtr, FocusOut, mode, NotifyAncestor); } QueueFocusEvent(dispPtr->focusTopLevelPtr, FocusOut, mode, NotifyVirtual); } if (winPtr != NULL) { focusPtr = winPtr->mainPtr->focusPtr; QueueFocusEvent(winPtr, FocusIn, mode, NotifyVirtual); if (focusPtr != NULL) { QueueFocusEvent(focusPtr, FocusIn, mode, NotifyAncestor); } } dispPtr->focusTopLevelPtr = winPtr; 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) { QueueFocusEvent(winPtr->mainPtr->focusPtr, FocusOut, NotifyNormal, NotifyAncestor); } if (focusPtr != NULL) { QueueFocusEvent(focusPtr, FocusIn, NotifyNormal, NotifyAncestor); } } winPtr->mainPtr->focusPtr = focusPtr; } /* *---------------------------------------------------------------------- * * QueueFocusEvent -- * * This procedure implements the mechanics of notifying a window * that has just gotten or lost the focus. It generates an * appropriate X event, queues it to be process before any other * events from the server (but after any other queued events), * 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 QueueFocusEvent(winPtr, type, mode, 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 mode; /* Mode to use for event: NotifyNormal, * NotifyGrab, or NotifyUngrab. */ 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; if (winPtr->flags & TK_ALREADY_DEAD) { /* * Window is in the process of being destroyed so quit (otherwise * the code below may recreate the window!). */ return; } 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 = GENERATED_EVENT; event.xfocus.display = winPtr->display; event.xfocus.window = winPtr->window; event.xfocus.mode = mode; event.xfocus.detail = detail; TkQueueEvent(winPtr->dispPtr, &event); 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; } }