/*
 * bltBusy.c --
 *
 *
 * This file has been changed to fit into the tkrat distribution.
 * I have among other things changed the semantics of the release command
 * so that it ignores errors.
 *
 *	maf@dtek.chalmers.se
 *
 *
 *	This module implements busy windows for the BLT toolkit.
 *
 * Copyright 1993-1998 Lucent Technologies, Inc.
 *
 * 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 the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 *
 *	The "busy" command was created by George Howlett.  
 */

#include "tcl.h"
#include "tk.h"

#include <X11/Xutil.h>
#include <X11/Xatom.h>

#include <stdlib.h>
#include "blt.h"

#ifndef CONST84
#   define CONST84
#endif

#define TRUE    1
#define FALSE   0


#ifndef TK_REPARENTED
#define TK_REPARENTED 0
#endif

typedef struct {
    Display *display;		/* Display of busy window */
    Tcl_Interp *interp;		/* Interpreter where "busy" command was 
				 * created. It's used to key the
				 * searches in the window hierarchy. See the
				 * "windows" command. */

    Tk_Window busy;		/* Busy window: Transparent window used 
				 * to block delivery of events to windows
				 * underneath it. */

    Tk_Window parent;		/* Parent window of the busy
				 * window. It may be the reference
				 * window (if the reference is a
				 * toplevel) or a mutual ancestor of
				 * the reference window */

    Tk_Window tkwin;		/* Reference window of the busy window. 
				 * It is used to manage the size and 
				 * position of the busy window. */

    int x, y;			/* Position of the reference window */

    int width, height;		/* Size of the reference window. Retained to 
				 * know if the reference window has been 
				 * reconfigured to a new size. */

    int isBusy;			/* Indicates whether the transparent
				 * window should be displayed. This
				 * can be different from what
				 * Tk_IsMapped says because the a
				 * sibling reference window may be
				 * unmapped, forcing the busy window
				 * to be also hidden. */

    int menuBar;		/* Menu bar flag. */
    Tk_Cursor cursor;		/* Cursor for the busy window. */

} Busy;

#ifdef WIN32
#define DEF_BUSY_CURSOR "wait"
#else 
#define DEF_BUSY_CURSOR "watch"
#endif

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_CURSOR, "-cursor", "busyCursor", "BusyCursor",
	DEF_BUSY_CURSOR, Tk_Offset(Busy, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

static int initialized = 0;	/* If non-zero, indicates to
				 * initialize the hash table */
static Tcl_HashTable busyTable;	/* Hash table of busy window
				 * structures keyed by the address of
				 * the reference Tk window */

static void BusyGeometryProc _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin));
static void BusyCustodyProc _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin));

static Tk_GeomMgr busyMgrInfo =
{
    "busy",			/* Name of geometry manager used by winfo */
    BusyGeometryProc,		/* Procedure to for new geometry requests */
    BusyCustodyProc,		/* Procedure when window is taken away */
};

/* Forward declarations */
static void DestroyBusy _ANSI_ARGS_((char* dataPtr));
static void BusyEventProc _ANSI_ARGS_((ClientData clientData, 
	XEvent *eventPtr));

#ifdef __STDC__
static Tk_EventProc BusyEventProc;
static Tk_EventProc RefWinEventProc;
static Tcl_CmdProc BusyCmd;
#endif


/*
 *----------------------------------------------------------------------
 *
 * BusyEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for events on
 *	the busy window itself.  We're only concerned with destroy
 *	events.
 *
 *	It might be necessary (someday) to watch resize events.  Right
 *	now, I don't think there's any point in it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When a busy window is destroyed, all internal structures
 * 	associated with it released at the next idle point.
 *
 *----------------------------------------------------------------------
 */
static void
BusyEventProc(clientData, eventPtr)
    ClientData clientData;	/* Busy window record */
    XEvent *eventPtr;		/* Event which triggered call to routine */
{
    Busy *busyPtr = (Busy *)clientData;

    if (eventPtr->type == DestroyNotify) {
	busyPtr->busy = NULL;
	Tk_EventuallyFree((ClientData)busyPtr, DestroyBusy);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * BusyCustodyProc --
 *
 *	This procedure is invoked when the busy window has been stolen
 *	by another geometry manager.  The information and memory
 *	associated with the busy window is released. I don't know why
 *	anyone would try to pack a busy window, but this should keep
 *	everything sane, if it is.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The Busy structure is freed at the next idle point.
 *
 * ----------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
BusyCustodyProc(clientData, tkwin)
    ClientData clientData;	/* Information about the slave window */
    Tk_Window tkwin;		/* Not used */
{
    Busy *busyPtr = (Busy *)clientData;

    Tk_DeleteEventHandler(busyPtr->busy, StructureNotifyMask, BusyEventProc,
	(ClientData)busyPtr);
    if (busyPtr->busy != NULL) {
	Tk_UnmapWindow(busyPtr->busy);
	busyPtr->busy = NULL;
    }
    busyPtr->isBusy = FALSE;
    Tk_EventuallyFree((ClientData)busyPtr, DestroyBusy);
}

/*
 * ----------------------------------------------------------------------------
 *
 * BusyGeometryProc --
 *
 *	This procedure is invoked by Tk_GeometryRequest for busy
 *	windows.  Busy windows never request geometry, so it's
 *	unlikely that this routine will ever be called.  The routine
 *	exists simply as a place holder for the GeomProc in the
 *	Geometry Manager structure.
 *
 * Results:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
BusyGeometryProc(clientData, tkwin)
    ClientData clientData;	/* Information about window that got new
				 * preferred geometry.  */
    Tk_Window tkwin;		/* Other Tk-related information about the
			         * window. */
{
    /* Should never get here */
}

/*
 * ------------------------------------------------------------------
 *
 * RefWinEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for the
 *	following events on the reference window.  If the reference and
 *	parent windows are the same, only the first event is
 *	important.
 *
 *	   1) ConfigureNotify  - The reference window has been resized or
 *				 moved.  Move and resize the busy window
 *				 to be the same size and position of the
 *				 reference window.
 *
 *	   2) DestroyNotify    - The reference window was destroyed. Destroy
 *				 the busy window and the free resources
 *				 used.
 *
 *	   3) MapNotify	       - The reference window was (re)shown. Map the
 *				 busy window again.
 *
 *	   4) UnmapNotify      - The reference window was hidden. Unmap the
 *				 busy window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the reference window gets deleted, internal structures get
 *	cleaned up.  When it gets resized, the busy window is resized
 *	accordingly. If it's displayed, the busy window is displayed. And
 *	when it's hidden, the busy window is unmapped.
 *
 * -------------------------------------------------------------------
 */
static void
RefWinEventProc(clientData, eventPtr)
    ClientData clientData;	/* Busy window record */
    register XEvent *eventPtr;	/* Event which triggered call to routine */
{
    register Busy *busyPtr = (Busy *)clientData;

    switch (eventPtr->type) {
    case DestroyNotify:

	/*
	 * Arrange for the busy structure to be removed at a proper time.
	 */

	Tk_EventuallyFree((ClientData)busyPtr, DestroyBusy);
	break;

    case ConfigureNotify:
	if ((busyPtr->width != Tk_Width(busyPtr->tkwin)) ||
	    (busyPtr->height != Tk_Height(busyPtr->tkwin)) ||
	    (busyPtr->x != Tk_X(busyPtr->tkwin)) ||
	    (busyPtr->y != Tk_Y(busyPtr->tkwin))) {
	    int x, y;

	    busyPtr->width = Tk_Width(busyPtr->tkwin);
	    busyPtr->height = Tk_Height(busyPtr->tkwin);
	    busyPtr->x = Tk_X(busyPtr->tkwin);
	    busyPtr->y = Tk_Y(busyPtr->tkwin);

	    x = y = 0;
	    if (busyPtr->parent != busyPtr->tkwin) {
		Tk_Window ancestor;

		for (ancestor = busyPtr->tkwin; ancestor != busyPtr->parent;
		    ancestor = Tk_Parent(ancestor)) {
		    x += Tk_X(ancestor) + Tk_Changes(ancestor)->border_width;
		    y += Tk_Y(ancestor) + Tk_Changes(ancestor)->border_width;
		}
	    }
#ifdef DEBUG
	    PurifyPrintf("2 busyPtr->width=%d, busyPtr->height=%d\n", busyPtr->width, busyPtr->height);
#endif
	    if (busyPtr->busy != NULL) {
		Tk_MoveResizeWindow(busyPtr->busy, x, y, busyPtr->width,
		    busyPtr->height);
	    }
	}
	break;

    case MapNotify:
	if ((busyPtr->parent != busyPtr->tkwin) && (busyPtr->isBusy)) {
	    Tk_MapWindow(busyPtr->busy);
	}
	break;

    case UnmapNotify:
	if (busyPtr->parent != busyPtr->tkwin) {
	    Tk_UnmapWindow(busyPtr->busy);
	}
	break;
    }
}

/*
 * ------------------------------------------------------------------
 *
 * ConfigureBusy --
 *
 *	This procedure is called from the Tk event dispatcher. It
 * 	releases X resources and memory used by the busy window and
 *	updates the internal hash table.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory and resources are released and the Tk event handler
 *	is removed.
 *
 * -------------------------------------------------------------------
 */
static int
ConfigureBusy(interp, busyPtr, argc, argv)
    Tcl_Interp *interp;
    Busy *busyPtr;
    int argc;
    char **argv;
{
    Tk_Cursor oldCursor;

    oldCursor = busyPtr->cursor;
    if (Tk_ConfigureWidget(interp, busyPtr->tkwin, configSpecs, argc,
			   (CONST84 char**)argv, (char*)busyPtr, 0)
	!= TCL_OK) {
	return TCL_ERROR;
    }
    if (busyPtr->cursor != oldCursor) {
	if (busyPtr->cursor == None) {
	    Tk_UndefineCursor(busyPtr->busy);
	} else {
	    Tk_DefineCursor(busyPtr->busy, busyPtr->cursor);
	}
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------
 *
 * CreateBusy --
 *
 *	Creates a child transparent window that obscures its parent
 *	window thereby effectively blocking device events.  The size
 *	and position of the busy window is exactly that of the reference
 *	window.
 *
 *	We want to create sibling to the window to be blocked.  If the
 *	busy window is a child of the window to be blocked, Enter/Leave
 *	events can sneak through.  Futhermore under WIN32, messages of
 *	transparent windows are sent directly to the parent.  The only
 *	exception to this are toplevels, since we can't make a sibling.
 *	Fortunately, toplevel windows rarely receive events that need
 *	blocking.
 *
 * Results:
 *	Returns a pointer to the new busy window structure.
 *
 * Side effects:
 *	When the busy window is eventually displayed, it will screen
 *	device events (in the area of the reference window) from reaching
 *	its parent window and its children.  User feed back can be
 *	achieved by changing the cursor.
 *
 * -------------------------------------------------------------------
 */
static Busy *
CreateBusy(interp, tkwin)
    Tcl_Interp *interp;		/* Interpreter to report error to */
    Tk_Window tkwin;		/* Window hosting the busy window */
{
    Busy *busyPtr;
    int length;
    char *fmt, *name;
    Tk_Window busy;
    Window parentWin;
    Tk_Window parent;
    Tk_FakeWin *winPtr;
    int x, y;

    busyPtr = (Busy *)calloc(1, sizeof(Busy));
    x = y = 0;
    length = strlen(Tk_Name(tkwin));
    name = (char *)ckalloc(length + 6);
    if (Tk_IsTopLevel(tkwin)) {
	fmt = "_Busy";		/* Child */
	parent = tkwin;
    } else {
	Tk_Window ancestor;

	fmt = "%s_Busy";	/* Sibling */
	parent = Tk_Parent(tkwin);
	for (ancestor = tkwin; ancestor != parent;
	    ancestor = Tk_Parent(ancestor)) {
	    x += Tk_X(ancestor) + Tk_Changes(ancestor)->border_width;
	    y += Tk_Y(ancestor) + Tk_Changes(ancestor)->border_width;
	    if (Tk_IsTopLevel(ancestor)) {
		break;
	    }
	}
    }
    sprintf(name, fmt, Tk_Name(tkwin));
    busy = Tk_CreateWindow(interp, parent, name, (char *)NULL);
    ckfree((char *)name);

    if (busy == NULL) {
	return NULL;
    }
    Tk_MakeWindowExist(tkwin);
    busyPtr->display = Tk_Display(tkwin);
    busyPtr->tkwin = tkwin;
    busyPtr->parent = parent;
    busyPtr->interp = interp;
    busyPtr->width = Tk_Width(tkwin);
    busyPtr->height = Tk_Height(tkwin);
    busyPtr->x = Tk_X(tkwin);
    busyPtr->y = Tk_Y(tkwin);
    busyPtr->cursor = None;
    busyPtr->busy = busy;
    Tk_SetClass(busy, "Busy");
#if (TK_MAJOR_VERSION >= 8)
    Blt_SetWindowInstanceData(busy, (ClientData)busyPtr);
#endif
    winPtr = (Tk_FakeWin *)tkwin;
    if (winPtr->flags & TK_REPARENTED) {
	/* 
	 * This works around a bug in the implementation of menubars
	 * for non-MacIntosh window systems (Win32 and X11).  Tk
	 * doesn't reset the pointers to the parent window when the
	 * menu is reparented (winPtr->parentPtr points to the
	 * wrong window). We get around this by determining the parent
	 * via the native API calls. 
	 */
#ifdef WIN32
	{
	    HWND hWnd;
	    RECT region;

	    hWnd = GetParent(Tk_GetHWND(Tk_WindowId(tkwin)));
	    parentWin = (Window)hWnd;
	    if (GetWindowRect(hWnd, &region)) {
		busyPtr->width = region.right - region.left;
		busyPtr->height = region.bottom - region.top;
#ifdef WINDEBUG
		PurifyPrintf("0. busyPtr->width=%d, busyPtr->height=%d\n", 
	busyPtr->width, busyPtr->height);
#endif
	    }
	}
#else
	parentWin = Blt_GetParent(Tk_Display(tkwin), Tk_WindowId(tkwin));
#endif
    } else {
	parentWin = Tk_WindowId(parent);
#ifdef WIN32
	parentWin = (Window)Tk_GetHWND(parentWin);
#endif
    }

    Blt_MakeTransparentWindowExist(busy, parentWin);

#ifdef WINDEBUG
    PurifyPrintf("1. busyPtr->width=%d, busyPtr->height=%d\n", 
	busyPtr->width, busyPtr->height);
#endif
    Tk_MoveResizeWindow(busy, x, y, busyPtr->width, busyPtr->height);
    Tk_RestackWindow(busy, Above, (Tk_Window)NULL);

    /*
     * Only worry if the busy window is destroyed.
     */
    Tk_CreateEventHandler(busy, StructureNotifyMask, BusyEventProc, 
	(ClientData)busyPtr);

    /*
     * Indicate that the busy window's geometry is being managed.
     * This will also notify us if the busy window is ever packed.
     */
    Tk_ManageGeometry(busy, &busyMgrInfo, (ClientData)busyPtr);

    if (busyPtr->cursor != None) {
	Tk_DefineCursor(busy, busyPtr->cursor);
    }

    /* Track the reference window to see if it is resized or destroyed.  */
    Tk_CreateEventHandler(tkwin, StructureNotifyMask, RefWinEventProc,
	(ClientData)busyPtr);
    return (busyPtr);
}

/*
 * ------------------------------------------------------------------
 *
 * DestroyBusy --
 *
 *	This procedure is called from the Tk event dispatcher. It
 *	releases X resources and memory used by the busy window and
 *	updates the internal hash table.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory and resources are released and the Tk event handler
 *	is removed.
 *
 * -------------------------------------------------------------------
 */
static void
DestroyBusy(char *dataPtr)
{
    Busy *busyPtr = (Busy *)dataPtr;
    Tcl_HashEntry *hPtr;

    Tk_FreeOptions(configSpecs, (char *)busyPtr, busyPtr->display, 0);
    hPtr = Tcl_FindHashEntry(&busyTable, (char *)busyPtr->tkwin);
    if (hPtr != NULL) {
	Tcl_DeleteHashEntry(hPtr);
    }
    Tk_DeleteEventHandler(busyPtr->tkwin, StructureNotifyMask,
	RefWinEventProc, (ClientData)busyPtr);
    if (busyPtr->busy != NULL) {
	Tk_DeleteEventHandler(busyPtr->busy, StructureNotifyMask,
	    BusyEventProc, (ClientData)busyPtr);
	Tk_ManageGeometry(busyPtr->busy, (Tk_GeomMgr *) NULL,
	    (ClientData)busyPtr);
	Tk_DestroyWindow(busyPtr->busy);
    }
    free((char *)busyPtr);
}

/*
 * ------------------------------------------------------------------
 *
 * GetBusy --
 *
 *	Returns the busy window structure associated with the reference
 *	window, keyed by its path name.  The clientData argument is
 *	the main window of the interpreter, used to search for the
 *	reference window in its own window hierarchy.
 *
 * Results:
 *	If path name represents a reference window with a busy window, a
 *	pointer to the busy window structure is returned. Otherwise,
 *	NULL is returned and an error message is left in
 *	interp->result.
 *
 * -------------------------------------------------------------------
 */
static int
GetBusy(clientData, interp, pathName, busyPtrPtr)
    ClientData clientData;	/* Window used to reference search  */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    char *pathName;		/* Path name of parent window */
    Busy **busyPtrPtr;		/* Will contain address of busy window if
				 * found. */
{
    Tcl_HashEntry *hPtr;
    Tk_Window tkwin;

    tkwin = Tk_NameToWindow(interp, pathName, Tk_MainWindow(interp));
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    hPtr = Tcl_FindHashEntry(&busyTable, (char *)tkwin);
    if (hPtr == NULL) {
	Tcl_AppendResult(interp, "can't find busy window \"", pathName, "\"",
	    (char *)NULL);
	return TCL_ERROR;
    }
    *busyPtrPtr = ((Busy *)Tcl_GetHashValue(hPtr));
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------
 *
 * HoldBusy --
 *
 *	Creates (if necessary) and maps a busy window, thereby
 *	preventing device events from being be received by the parent
 *	window and its children.
 *
 * Results:
 *	Returns a standard TCL result. If path name represents a busy
 *	window, it is unmapped and TCL_OK is returned. Otherwise,
 *	TCL_ERROR is returned and an error message is left in
 *	interp->result.
 *
 * Side effects:
 *	The busy window is created and displayed, blocking events from
 *	the parent window and its children.
 *
 * -------------------------------------------------------------------
 */
static int
HoldBusy(clientData, interp, argc, argv)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;		/* Window name and option pairs */
{
    Tk_Window tkwin;
    Tcl_HashEntry *hPtr;
    Busy *busyPtr;
    int isNew;
    int result;

    tkwin = Tk_NameToWindow(interp, argv[0], Tk_MainWindow(interp));
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    hPtr = Tcl_CreateHashEntry(&busyTable, (char *)tkwin, &isNew);
    if (isNew) {
	busyPtr = (Busy *)CreateBusy(interp, tkwin);
	if (busyPtr == NULL) {
	    return TCL_ERROR;
	}
	Tcl_SetHashValue(hPtr, (char *)busyPtr);
    } else {
	busyPtr = (Busy *)Tcl_GetHashValue(hPtr);

	/*
	 * Raise and re-map the busy window whenever hold is reasserted.
	 */

	Tk_RestackWindow(busyPtr->busy, Above, (Tk_Window)NULL);
    }

    /*
     * Don't map the busy window unless the reference window is also displayed
     */
    if (Tk_IsMapped(busyPtr->tkwin)) {
	Tk_MapWindow(busyPtr->busy);
    } else {
	Tk_UnmapWindow(busyPtr->busy);
    }
    busyPtr->isBusy = TRUE;
    Tk_Preserve((ClientData)busyPtr);
    result = ConfigureBusy(interp, busyPtr, argc - 1, argv + 1);
    Tk_Release((ClientData)busyPtr);
#ifdef WIN32
    { 
	POINT point;
	/* 
	 * Under Win32, cursors aren't associated with windows.  Tk
	 * fakes this by watching Motion events on its windows.  So Tk
	 * will automatically change the cursor when the pointer
	 * enters the Busy window.  But Windows doesn't immediately
	 * change the cursor; it waits for the cursor position to
	 * change or a system call.  We need to change the cursor
	 * before the application starts processing, so set the cursor
	 * position redundantly back to the current position.  
	 */
	GetCursorPos(&point);
	SetCursorPos(point.x, point.y);
    }
#endif
    return result;
}

/*
 * ------------------------------------------------------------------
 *
 * StatusOp --
 *
 *	Returns the status of the busy window; whether it's blocking
 *	events or not.
 *
 * Results:
 *	Returns a standard TCL result. If path name represents a busy
 *	window, the status is returned via interp->result and TCL_OK
 *	is returned. Otherwise, TCL_ERROR is returned and an error
 *	message is left in interp->result.
 *
 * -------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StatusOp(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window of interpreter */
    Tcl_Interp *interp;		/* Interpreter to report error to */
    int argc;			/* not used */
    char **argv;
{
    Busy *busyPtr;

    if (GetBusy(clientData, interp, argv[2], &busyPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    Tk_Preserve((ClientData)busyPtr);
    Tcl_SetResult(interp, busyPtr->isBusy ? "1" : "0", TCL_STATIC);
    Tk_Release((ClientData)busyPtr);
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------
 *
 * ForgetOp --
 *
 *	Destroys the busy window associated with the reference window and
 *	arranges for internal resources to the released when they're
 *	not being used anymore.
 *
 * Results:
 *	Returns a standard TCL result. If path name represents a busy
 *	window, it is destroyed and TCL_OK is returned. Otherwise,
 *	TCL_ERROR is returned and an error message is left in
 *	interp->result.
 *
 * Side effects:
 *	The busy window is removed.  Other related memory and resources
 *	are eventually released by the Tk dispatcher.
 *
 * -------------------------------------------------------------------
 */
static int
ForgetOp(clientData, interp, argc, argv)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;
{
    Busy *busyPtr;
    register int i;
    Tcl_HashEntry *hPtr;
    Tk_Window tkwin;
    Tk_Window mainWindow;

    mainWindow = Tk_MainWindow(interp);
    for (i = 2; i < argc; i++) {
	tkwin = Tk_NameToWindow(interp, argv[i], mainWindow);
	if (tkwin == NULL) {
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&busyTable, (char *)tkwin);
	if (hPtr == NULL) {
	    Tcl_AppendResult(interp, "can't find busy window \"", argv[i],
		"\"", (char *)NULL);
	    return TCL_ERROR;
	}
	busyPtr = (Busy *)Tcl_GetHashValue(hPtr);

	/* Unmap the window even though it will be soon destroyed */
	if (busyPtr->busy != NULL) {
	    Tk_UnmapWindow(busyPtr->busy);
	}
	busyPtr->isBusy = FALSE;

	Tk_EventuallyFree((ClientData)busyPtr, DestroyBusy);
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------
 *
 * ReleaseOp --
 *
 *	Unmaps the busy window, thereby permitting device events
 *	to be received by the parent window and its children.
 *
 * Results:
 *	Returns a standard TCL result. If path name represents a busy
 *	window, it is unmapped and TCL_OK is returned. Otherwise,
 *	TCL_ERROR is returned and an error message is left in
 *	interp->result.
 *
 * Side effects:
 *	The busy window is hidden, allowing the parent window and
 *	its children to receive events again.
 *
 * -------------------------------------------------------------------
 */
static int
ReleaseOp(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window of the interpreter */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;
{
    Busy *busyPtr;
    int i;

    for (i = 2; i < argc; i++) {
	if (GetBusy(clientData, interp, argv[i], &busyPtr) != TCL_OK) {
	    continue;
	}
	Tk_Preserve((ClientData)busyPtr);
	if (busyPtr->busy != NULL) {
	    Tk_UnmapWindow(busyPtr->busy);
	}
	busyPtr->isBusy = FALSE;
	Tk_Release((ClientData)busyPtr);
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------
 *
 * WindowsOp --
 *
 *	Reports the names of all widgets with busy windows attached to
 *	them, matching a given pattern.  If no pattern is given, all
 *	busy widgets are listed.
 *
 * Results:
 *	Returns a TCL list of the names of the widget with busy windows
 *	attached to them, regardless if the widget is currently busy
 *	or not.
 *
 * -------------------------------------------------------------------
 */
static int
WindowsOp(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window of the interpreter */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Busy *busyPtr;

    for (hPtr = Tcl_FirstHashEntry(&busyTable, &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	busyPtr = (Busy *)Tcl_GetHashValue(hPtr);
	if (busyPtr->interp == interp) {
	    if ((argc != 3) ||
		(Tcl_StringMatch(Tk_PathName(busyPtr->tkwin), argv[2]))) {
		Tcl_AppendElement(interp, Tk_PathName(busyPtr->tkwin));
	    }
	}
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------
 *
 * IsBusyOp --
 *
 *	Reports the names of all widgets with busy windows attached to
 *	them, matching a given pattern.  If no pattern is given, all
 *	busy widgets are listed.
 *
 * Results:
 *	Returns a TCL list of the names of the widget with busy windows
 *	attached to them, regardless if the widget is currently busy
 *	or not.
 *
 * -------------------------------------------------------------------
 */
static int
IsBusyOp(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window of the interpreter */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Busy *busyPtr;

    for (hPtr = Tcl_FirstHashEntry(&busyTable, &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	busyPtr = (Busy *)Tcl_GetHashValue(hPtr);
	if (busyPtr->interp == interp) {
	    if ((busyPtr->isBusy) && ((argc != 3) ||
		    (Tcl_StringMatch(Tk_PathName(busyPtr->tkwin), argv[2])))) {
		Tcl_AppendElement(interp, Tk_PathName(busyPtr->tkwin));
	    }
	}
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------
 *
 * HoldOp --
 *
 *	Creates (if necessary) and maps a busy window, thereby
 *	preventing device events from being be received by the parent
 *      window and its children. The argument vector may contain
 *	option-value pairs of configuration options to be set.
 *
 * Results:
 *	Returns a standard TCL result.
 *
 * Side effects:
 *	The busy window is created and displayed, blocking events from the
 *	parent window and its children.
 *
 * -------------------------------------------------------------------
 */
static int
HoldOp(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window of the interpreter */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;		/* Window name and option pairs */
{
    register int i, count;

    if ((argv[1][0] == 'h') && (strcmp(argv[1], "hold") == 0)) {	
	argc--, argv++;		/* Command used "hold" keyword */
    }
    for (i = 1; i < argc; i++) {
	/*
	 * Find the end of the option-value pairs for this window.
	 */
	for (count = i + 1; count < argc; count += 2) {
	    if (argv[count][0] != '-') {
		break;
	    }
	}
	if (count > argc) {
	    count = argc;
	}
	if (HoldBusy(clientData, interp, count - i, argv + i) != TCL_OK) {
	    return TCL_ERROR;
	}
	i = count;
    }
    return TCL_OK;
}

/* ARGSUSED*/
static int
CgetOp(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window of the interpreter */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;		/* Widget pathname and option switch */
{
    Busy *busyPtr;
    int result;

    if (GetBusy(clientData, interp, argv[2], &busyPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    Tk_Preserve((ClientData)busyPtr);
    result = Tk_ConfigureValue(interp, busyPtr->tkwin, configSpecs,
	(char *)busyPtr, argv[3], 0);
    Tk_Release((ClientData)busyPtr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 *	This procedure is called to process an argv/argc list in order
 *	to configure (or reconfigure) a busy window.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information get set for busyPtr; old resources
 *	get freed, if there were any.  The busy window destroyed and
 *	recreated in a new parent window.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window of the interpreter */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;
    char **argv;		/* Reference window path name and options */
{
    Busy *busyPtr;
    int result;

    if (GetBusy(clientData, interp, argv[2], &busyPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    Tk_Preserve((ClientData)busyPtr);
    if (argc == 3) {
	result = Tk_ConfigureInfo(interp, busyPtr->tkwin, configSpecs,
	    (char *)busyPtr, (char *)NULL, 0);
    } else if (argc == 4) {
	result = Tk_ConfigureInfo(interp, busyPtr->tkwin, configSpecs,
	    (char *)busyPtr, argv[3], 0);
    } else {
	result = ConfigureBusy(interp, busyPtr, argc - 3, argv + 3);
    }
    Tk_Release((ClientData)busyPtr);
    return result;
}

/*
 *--------------------------------------------------------------
 *
 * Busy Sub-command specification:
 *
 *	- Name of the sub-command.
 *	- Minimum number of characters needed to unambiguously
 *        recognize the sub-command.
 *	- Pointer to the function to be called for the sub-command.
 *	- Minimum number of arguments accepted.
 *	- Maximum number of arguments accepted.
 *	- String to be displayed for usage (arguments only).
 *
 *--------------------------------------------------------------
 *
static Blt_OpSpec busyOps[] =
{
    {"cget", 2, (Blt_Operation)CgetOp, 4, 4, "window option",},
    {"configure", 2, (Blt_Operation)ConfigureOp, 3, 0,
	"window ?options?...",},
    {"forget", 1, (Blt_Operation)ForgetOp, 2, 0, "?window?...",},
    {"hold", 3, (Blt_Operation)HoldOp, 3, 0,
	"window ?options?... ?window options?...",},
    {"isbusy", 1, (Blt_Operation)IsBusyOp, 2, 3, "?pattern?",},
    {"release", 1, (Blt_Operation)ReleaseOp, 2, 0, "?window?...",},
    {"status", 1, (Blt_Operation)StatusOp, 3, 3, "window",},
    {"windows", 1, (Blt_Operation)WindowsOp, 2, 3, "?pattern?",},
};
static int numBusyOps = sizeof(busyOps) / sizeof(Blt_OpSpec);*/

/*
 *----------------------------------------------------------------------
 *
 * BusyCmd --
 *
 *	This procedure is invoked to process the "busy" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */
static int
BusyCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window of the interpreter */
    Tcl_Interp *interp;		/* Interpreter associated with command */
    int argc;
    CONST84 char **argv;
{
    int result;

    if (!initialized) {
	Tcl_InitHashTable(&busyTable, TCL_ONE_WORD_KEYS);
	initialized = 1;
    }
    if ((argc > 1) && (argv[1][0] == '.')) {
	return (HoldOp(clientData, interp, argc, argv));
    }
    if (!strcmp(argv[1], "cget") && argc == 4) {
	result = CgetOp(clientData, interp, argc, argv);
    } else if (!strcmp(argv[1], "configure") && argc >= 3) {
	result = ConfigureOp(clientData, interp, argc, argv);
    } else if (!strcmp(argv[1], "forget") && argc >= 2) {
	result = ForgetOp(clientData, interp, argc, argv);
    } else if (!strcmp(argv[1], "hold") && argc >= 3) {
	result = HoldOp(clientData, interp, argc, argv);
    } else if (!strcmp(argv[1], "isbusy") && argc >= 2 && argc <= 3) {
	result = IsBusyOp(clientData, interp, argc, argv);
    } else if (!strcmp(argv[1], "release") && argc >= 2) {
	result = ReleaseOp(clientData, interp, argc, argv);
    } else if (!strcmp(argv[1], "status") && argc == 3) {
	result = StatusOp(clientData, interp, argc, argv);
    } else if (!strcmp(argv[1], "windows") && argc >= 2 && argc <= 3) {
	result = WindowsOp(clientData, interp, argc, argv);
    } else {
	return TCL_ERROR;
    }
    return result;
}

int
Blt_busy_Init(Tcl_Interp *interp)
{
    Tcl_CreateCommand(interp, "blt_busy", BusyCmd, NULL, NULL);
    return Tcl_PkgProvide(interp, "blt_busy", BUSYLIB_VERSION);
}


syntax highlighted by Code2HTML, v. 0.9.1