archie/tk3.6/tkFocus.c

502 lines
14 KiB
C
Raw Normal View History

2024-05-27 16:13:40 +02:00
/*
* 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;
}
}