diff options
author | dkf <donal.k.fellows@manchester.ac.uk> | 2008-10-18 14:22:20 (GMT) |
---|---|---|
committer | dkf <donal.k.fellows@manchester.ac.uk> | 2008-10-18 14:22:20 (GMT) |
commit | 678e5a8acf06358ad722dff065fb80fcd06e7d15 (patch) | |
tree | 5859725f08cf8f5c220d3fd04da0f8fe6f0d3103 /generic/tkBusy.c | |
parent | ce82534d5c94d852ec68426e7cfd45c29f72e5c9 (diff) | |
download | tk-678e5a8acf06358ad722dff065fb80fcd06e7d15.zip tk-678e5a8acf06358ad722dff065fb80fcd06e7d15.tar.gz tk-678e5a8acf06358ad722dff065fb80fcd06e7d15.tar.bz2 |
Implementation of the [tk busy] command on non-OSX.
Adapted from [Patch 1997907]
Diffstat (limited to 'generic/tkBusy.c')
-rw-r--r-- | generic/tkBusy.c | 1222 |
1 files changed, 1222 insertions, 0 deletions
diff --git a/generic/tkBusy.c b/generic/tkBusy.c new file mode 100644 index 0000000..e8343e3 --- /dev/null +++ b/generic/tkBusy.c @@ -0,0 +1,1222 @@ +/* + * tkBusy.c -- + * + * This file provides functions that implement busy for Tk. + * + * Copyright 1993-1998 Lucent Technologies, Inc. + * + * The "busy" command was created by George Howlett. Adapted for + * integration into Tk by Jos Decoster and Donal K. Fellows. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id: tkBusy.c,v 1.1 2008/10/18 14:22:22 dkf Exp $ + */ + +#include "tkInt.h" + +#if !(defined(__WIN32__) || defined(MAC_OSX_TK)) +#include "tkUnixInt.h" +#endif + +#ifdef WIN32 +#include <windows.h> +extern HINSTANCE Tk_GetHINSTANCE(void); +extern Window Tk_AttachHWND(Tk_Window tkwin, HWND hwnd); +extern HWND Tk_GetHWND(Window window); + +#define DEF_BUSY_CURSOR "wait" +#else +#define DEF_BUSY_CURSOR "watch" +#endif + +/* + * Types used in this file. + */ + +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 tkBusy; /* Busy window: Transparent window used to + * block delivery of events to windows + * underneath it. */ + Tk_Window tkParent; /* 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 tkRef; /* 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 menuBar; /* Menu bar flag. */ + Tk_Cursor cursor; /* Cursor for the busy window. */ + Tcl_HashEntry *hashPtr; /* Used the delete the busy window entry out + * of the global hash table. */ + Tcl_HashTable *tablePtr; + Tk_OptionTable optionTable; +} Busy; + +/* + * Things about the busy system that may be configured. + */ + +static Tk_OptionSpec busyOptionSpecs[] = { + {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", + DEF_BUSY_CURSOR, -1, Tk_Offset(Busy, cursor), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0} +}; + +/* + * Forward declarations of functions defined in this file. + */ + +static void DestroyBusy(char *dataPtr); +static void BusyEventProc(ClientData clientData, + XEvent *eventPtr); +static void BusyGeometryProc(ClientData clientData, + Tk_Window tkwin); +static void BusyCustodyProc(ClientData clientData, + Tk_Window tkwin); + +/* + * The "busy" geometry manager definition. + */ + +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 */ +}; + +/* + * Helper functions, need to check if a Tcl/Tk alternative already exists. + */ + +static inline Tk_Window +FirstChild( + Tk_Window parent) +{ + struct TkWindow *parentPtr = (struct TkWindow *) parent; + + return (Tk_Window) parentPtr->childList; +} + +static inline Tk_Window +NextChild( + Tk_Window tkwin) +{ + struct TkWindow *winPtr = (struct TkWindow *) tkwin; + + if (winPtr == NULL) { + return NULL; + } + return (Tk_Window) winPtr->nextPtr; +} + +static inline void +SetWindowInstanceData( + Tk_Window tkwin, + ClientData instanceData) +{ + struct TkWindow *winPtr = (struct TkWindow *) tkwin; + + winPtr->instanceData = instanceData; +} + +/* + *---------------------------------------------------------------------- + * + * TkpShowBusyWindow, TkpHideBusyWindow, TkpMakeTransparentWindowExist, + * TkpCreateBusy -- + * + * Portability layer. Holds platform-specific gunk for the [tk busy] + * command, including a wholly dummy implementation for OSX/Aqua. The + * individual functions do the following: + * + * TkpShowBusyWindow -- + * Make the busy window appear. + * + * TkpHideBusyWindow -- + * Make the busy window go away. + * + * TkpMakeTransparentWindowExist -- + * Actually make a transparent window. + * + * TkpCreateBusy -- + * Creates the platform-specific part of a busy window structure. + * + *---------------------------------------------------------------------- + */ + +#ifdef WIN32 /* Windows */ + +void +TkpShowBusyWindow( + Busy *busyPtr) +{ + HWND hWnd; + POINT point; + Display *display; + Window window; + + if (busyPtr->tkBusy != NULL) { + Tk_MapWindow(busyPtr->tkBusy); + window = Tk_WindowId(busyPtr->tkBusy); + display = Tk_Display(busyPtr->tkBusy); + hWnd = Tk_GetHWND(window); + display->request++; + SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } + + /* + * 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 does + * not 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); +} + +void +TkpHideBusyWindow( + Busy *busyPtr) +{ + POINT point; + + if (busyPtr->tkBusy != NULL) { + Tk_UnmapWindow(busyPtr->tkBusy); + } + + /* + * 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 does + * not 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); +} + +void +TkpMakeTransparentWindowExist( + Tk_Window tkwin, /* Token for window. */ + Window parent) /* Parent window. */ +{ + TkWindow *winPtr = (TkWindow *) tkwin; + HWND hParent; + HWND hWnd; + int style; + DWORD exStyle; + + hParent = (HWND) parent; + style = WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + exStyle = WS_EX_TRANSPARENT | WS_EX_TOPMOST; +#define TK_WIN_CHILD_CLASS_NAME "TkChild" + hWnd = CreateWindowEx(exStyle, TK_WIN_CHILD_CLASS_NAME, NULL, style, + Tk_X(tkwin), Tk_Y(tkwin), Tk_Width(tkwin), Tk_Height(tkwin), + hParent, NULL, Tk_GetHINSTANCE(), NULL); + winPtr->window = Tk_AttachHWND(tkwin, hWnd); +} + +void +TkpCreateBusy( + Tk_FakeWin *winPtr, + Tk_Window tkRef, + Window* parentPtr, + Tk_Window tkParent, + Busy *busyPtr) +{ + 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. + */ + + HWND hWnd; + RECT rect; + + hWnd = GetParent(Tk_GetHWND(Tk_WindowId(tkRef))); + if (GetWindowRect(hWnd, &rect)) { + busyPtr->width = rect.right - rect.left; + busyPtr->height = rect.bottom - rect.top; + } + } else { + *parentPtr = Tk_WindowId(tkParent); + *parentPtr = (Window) Tk_GetHWND(*parentPtr); + } +} + +#elif defined(MAC_OSX_TK) /* Aqua */ + +void +TkpShowBusyWindow( + Busy *busyPtr) +{ +} + +void +TkpHideBusyWindow( + Busy *busyPtr) +{ +} + +void +TkpMakeTransparentWindowExist( + Tk_Window tkwin, /* Token for window. */ + Window parent) /* Parent window. */ +{ +} + +void +TkpCreateBusy( + Tk_FakeWin *winPtr, + Tk_Window tkRef, + Window* parentPtr, + Tk_Window tkParent, + Busy *busyPtr) +{ +} + +#else /* UNIX/X11 */ + +void +TkpShowBusyWindow( + Busy *busyPtr) +{ + if (busyPtr->tkBusy != NULL) { + Tk_MapWindow(busyPtr->tkBusy); + + /* + * Always raise the busy window just in case new sibling windows have + * been created in the meantime. Can't use Tk_RestackWindow because it + * doesn't work under Win32. + */ + + XRaiseWindow(Tk_Display(busyPtr->tkBusy), + Tk_WindowId(busyPtr->tkBusy)); + } +} + +void +TkpHideBusyWindow( + Busy *busyPtr) +{ + if (busyPtr->tkBusy != NULL) { + Tk_UnmapWindow(busyPtr->tkBusy); + } +} + +void +TkpMakeTransparentWindowExist( + Tk_Window tkwin, /* Token for window. */ + Window parent) /* Parent window. */ +{ + TkWindow *winPtr = (TkWindow *) tkwin; + long int mask = CWDontPropagate | CWEventMask; + + /* + * Ignore the important events while the window is mapped. + */ + +#define USER_EVENTS \ + (EnterWindowMask | LeaveWindowMask | KeyPressMask | KeyReleaseMask | \ + ButtonPressMask | ButtonReleaseMask | PointerMotionMask) +#define PROP_EVENTS \ + (KeyPressMask | KeyReleaseMask | ButtonPressMask | \ + ButtonReleaseMask | PointerMotionMask) + + winPtr->atts.do_not_propagate_mask = PROP_EVENTS; + winPtr->atts.event_mask = USER_EVENTS; + winPtr->changes.border_width = 0; + winPtr->depth = 0; + + winPtr->window = XCreateWindow(winPtr->display, parent, + winPtr->changes.x, winPtr->changes.y, + (unsigned) winPtr->changes.width, /* width */ + (unsigned) winPtr->changes.height, /* height */ + (unsigned) winPtr->changes.border_width, /* border_width */ + winPtr->depth, InputOnly, winPtr->visual, mask, &winPtr->atts); +} + +static Window +GetParent( + Display *display, + Window window) +{ + Window root, parent; + Window *dummy; + unsigned int count; + + if (XQueryTree(display, window, &root, &parent, &dummy, &count) > 0) { + XFree(dummy); + return parent; + } + return None; +} + +void +TkpCreateBusy( + Tk_FakeWin *winPtr, + Tk_Window tkRef, + Window* parentPtr, + Tk_Window tkParent, + Busy *busyPtr) +{ + 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. + */ + + *parentPtr = GetParent(Tk_Display(tkRef), Tk_WindowId(tkRef)); + } else { + *parentPtr = Tk_WindowId(tkParent); + } +} +#endif + +/* + *---------------------------------------------------------------------- + * + * 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 clientData, /* Information about the busy window. */ + Tk_Window tkwin) /* Not used. */ +{ + Busy *busyPtr = clientData; + + Tk_DeleteEventHandler(busyPtr->tkBusy, StructureNotifyMask, BusyEventProc, + busyPtr); + TkpHideBusyWindow(busyPtr); + busyPtr->tkBusy = NULL; + Tcl_EventuallyFree(busyPtr, DestroyBusy); +} + +/* + *---------------------------------------------------------------------- + * + * BusyGeometryProc -- + * + * This procedure is invoked by Tk_GeometryRequest for busy windows. + * Busy windows never request geometry, so it's unlikely that this + * function will ever be called;it exists simply as a place holder for + * the GeomProc in the Geometry Manager structure. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +/* ARGSUSED */ +static void +BusyGeometryProc( + ClientData clientData, /* Information about window that got new + * preferred geometry. */ + Tk_Window tkwin) /* Other Tk-related information about the + * window. */ +{ + /* Should never get here */ +} + +/* + *---------------------------------------------------------------------- + * + * DoConfigureNotify -- + * + * Generate a ConfigureNotify event describing the current configuration + * of a window. + * + * Results: + * None. + * + * Side effects: + * An event is generated and processed by Tk_HandleEvent. + * + *---------------------------------------------------------------------- + */ + +static void +DoConfigureNotify( + Tk_FakeWin *winPtr) /* Window whose configuration was just + * changed. */ +{ + XEvent event; + + event.type = ConfigureNotify; + event.xconfigure.serial = LastKnownRequestProcessed(winPtr->display); + event.xconfigure.send_event = False; + event.xconfigure.display = winPtr->display; + event.xconfigure.event = winPtr->window; + event.xconfigure.window = winPtr->window; + event.xconfigure.x = winPtr->changes.x; + event.xconfigure.y = winPtr->changes.y; + event.xconfigure.width = winPtr->changes.width; + event.xconfigure.height = winPtr->changes.height; + event.xconfigure.border_width = winPtr->changes.border_width; + if (winPtr->changes.stack_mode == Above) { + event.xconfigure.above = winPtr->changes.sibling; + } else { + event.xconfigure.above = None; + } + event.xconfigure.override_redirect = winPtr->atts.override_redirect; + Tk_HandleEvent(&event); +} + +/* + *---------------------------------------------------------------------- + * + * 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 clientData, /* Busy window record */ + register XEvent *eventPtr) /* Event which triggered call to routine */ +{ + register Busy *busyPtr = clientData; + + switch (eventPtr->type) { + case ReparentNotify: + case DestroyNotify: + /* + * Arrange for the busy structure to be removed at a proper time. + */ + + Tcl_EventuallyFree(busyPtr, DestroyBusy); + break; + + case ConfigureNotify: + if ((busyPtr->width != Tk_Width(busyPtr->tkRef)) || + (busyPtr->height != Tk_Height(busyPtr->tkRef)) || + (busyPtr->x != Tk_X(busyPtr->tkRef)) || + (busyPtr->y != Tk_Y(busyPtr->tkRef))) { + int x, y; + + busyPtr->width = Tk_Width(busyPtr->tkRef); + busyPtr->height = Tk_Height(busyPtr->tkRef); + busyPtr->x = Tk_X(busyPtr->tkRef); + busyPtr->y = Tk_Y(busyPtr->tkRef); + + x = y = 0; + + if (busyPtr->tkParent != busyPtr->tkRef) { + Tk_Window tkwin; + + for (tkwin = busyPtr->tkRef; (tkwin != NULL) && + (!Tk_IsTopLevel(tkwin)); tkwin = Tk_Parent(tkwin)) { + if (tkwin == busyPtr->tkParent) { + break; + } + x += Tk_X(tkwin) + Tk_Changes(tkwin)->border_width; + y += Tk_Y(tkwin) + Tk_Changes(tkwin)->border_width; + } + } + if (busyPtr->tkBusy != NULL) { + Tk_MoveResizeWindow(busyPtr->tkBusy, x, y, busyPtr->width, + busyPtr->height); + TkpShowBusyWindow(busyPtr); + } + } + break; + + case MapNotify: + if (busyPtr->tkParent != busyPtr->tkRef) { + TkpShowBusyWindow(busyPtr); + } + break; + + case UnmapNotify: + if (busyPtr->tkParent != busyPtr->tkRef) { + TkpHideBusyWindow(busyPtr); + } + break; + } +} + +/* + *---------------------------------------------------------------------- + * + * 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 *data) /* Busy window structure record */ +{ + register Busy *busyPtr = (Busy *) data; + + if (busyPtr->hashPtr != NULL) { + Tcl_DeleteHashEntry(busyPtr->hashPtr); + } + Tk_DeleteEventHandler(busyPtr->tkRef, StructureNotifyMask, + RefWinEventProc, busyPtr); + + if (busyPtr->tkBusy != NULL) { + Tk_FreeConfigOptions(data, busyPtr->optionTable, busyPtr->tkBusy); + Tk_DeleteEventHandler(busyPtr->tkBusy, StructureNotifyMask, + BusyEventProc, busyPtr); + Tk_ManageGeometry(busyPtr->tkBusy, NULL, busyPtr); + Tk_DestroyWindow(busyPtr->tkBusy); + } + ckfree(data); +} + +/* + *---------------------------------------------------------------------- + * + * 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 clientData, /* Busy window record */ + XEvent *eventPtr) /* Event which triggered call to routine */ +{ + Busy *busyPtr = clientData; + + if (eventPtr->type == DestroyNotify) { + busyPtr->tkBusy = NULL; + Tcl_EventuallyFree(busyPtr, DestroyBusy); + } +} + +/* + *---------------------------------------------------------------------- + * + * MakeTransparentWindowExist -- + * + * Similar to Tk_MakeWindowExist but instead creates a transparent window + * to block for user events from sibling windows. + * + * Differences from Tk_MakeWindowExist. + * + * 1. This is always a "busy" window. There's never a platform-specific + * class procedure to execute instead. + * 2. The window is transparent and never will contain children, so + * colormap information is irrelevant. + * + * Results: + * None. + * + * Side effects: + * When the procedure returns, the internal window associated with tkwin + * is guaranteed to exist. This may require the window's ancestors to be + * created too. + * + *---------------------------------------------------------------------- + */ + +static void +MakeTransparentWindowExist( + Tk_Window tkwin, /* Token for window. */ + Window parent) /* Parent window. */ +{ + TkWindow *winPtr = (TkWindow *) tkwin; + Tcl_HashEntry *hPtr; + int notUsed; + TkDisplay *dispPtr; + + if (winPtr->window != None) { + return; /* Window already exists. */ + } + + /* + * Create a transparent window and put it on top. + */ + + TkpMakeTransparentWindowExist(tkwin, parent); + + dispPtr = winPtr->dispPtr; + hPtr = Tcl_CreateHashEntry(&dispPtr->winTable, (char *) winPtr->window, + ¬Used); + Tcl_SetHashValue(hPtr, winPtr); + winPtr->dirtyAtts = 0; + winPtr->dirtyChanges = 0; + + if (!(winPtr->flags & TK_TOP_HIERARCHY)) { + TkWindow *winPtr2; + + /* + * If any siblings higher up in the stacking order have already been + * created then move this window to its rightful position in the + * stacking order. + * + * NOTE: this code ignores any changes anyone might have made to the + * sibling and stack_mode field of the window's attributes, so it + * really isn't safe for these to be manipulated except by calling + * Tk_RestackWindow. + */ + + for (winPtr2 = winPtr->nextPtr; winPtr2 != NULL; + winPtr2 = winPtr2->nextPtr) { + if ((winPtr2->window != None) && + !(winPtr2->flags & (TK_TOP_HIERARCHY|TK_REPARENTED))) { + XWindowChanges changes; + + changes.sibling = winPtr2->window; + changes.stack_mode = Below; + XConfigureWindow(winPtr->display, winPtr->window, + CWSibling | CWStackMode, &changes); + break; + } + } + } + + /* + * Issue a ConfigureNotify event if there were deferred configuration + * changes (but skip it if the window is being deleted; the + * ConfigureNotify event could cause problems if we're being called from + * Tk_DestroyWindow under some conditions). + */ + + if ((winPtr->flags & TK_NEED_CONFIG_NOTIFY) + && !(winPtr->flags & TK_ALREADY_DEAD)) { + winPtr->flags &= ~TK_NEED_CONFIG_NOTIFY; + DoConfigureNotify((Tk_FakeWin *) tkwin); + } +} + +/* + *---------------------------------------------------------------------- + * + * 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( + Tcl_Interp *interp, /* Interpreter to report error to */ + Tk_Window tkRef) /* Window hosting the busy window */ +{ + Busy *busyPtr; + int length, x, y; + char *fmt, *name; + Tk_Window tkBusy, tkChild, tkParent; + Window parent; + Tk_FakeWin *winPtr; + + busyPtr = (Busy *) ckalloc(sizeof(Busy)); + x = y = 0; + length = strlen(Tk_Name(tkRef)); + name = ckalloc(length + 6); + if (Tk_IsTopLevel(tkRef)) { + fmt = "_Busy"; /* Child */ + tkParent = tkRef; + } else { + Tk_Window tkwin; + + fmt = "%s_Busy"; /* Sibling */ + tkParent = Tk_Parent(tkRef); + for (tkwin = tkRef; (tkwin != NULL) && !Tk_IsTopLevel(tkwin); + tkwin = Tk_Parent(tkwin)) { + if (tkwin == tkParent) { + break; + } + x += Tk_X(tkwin) + Tk_Changes(tkwin)->border_width; + y += Tk_Y(tkwin) + Tk_Changes(tkwin)->border_width; + } + } + for (tkChild = FirstChild(tkParent); tkChild != NULL; + tkChild = NextChild(tkChild)) { + Tk_MakeWindowExist(tkChild); + } + sprintf(name, fmt, Tk_Name(tkRef)); + tkBusy = Tk_CreateWindow(interp, tkParent, name, NULL); + ckfree(name); + + if (tkBusy == NULL) { + return NULL; + } + Tk_MakeWindowExist(tkRef); + busyPtr->display = Tk_Display(tkRef); + busyPtr->interp = interp; + busyPtr->tkRef = tkRef; + busyPtr->tkParent = tkParent; + busyPtr->tkBusy = tkBusy; + busyPtr->width = Tk_Width(tkRef); + busyPtr->height = Tk_Height(tkRef); + busyPtr->x = Tk_X(tkRef); + busyPtr->y = Tk_Y(tkRef); + busyPtr->cursor = None; + Tk_SetClass(tkBusy, "Busy"); + busyPtr->optionTable = Tk_CreateOptionTable(interp, busyOptionSpecs); + if (Tk_InitOptions(interp, (char *) busyPtr, busyPtr->optionTable, + tkBusy) != TCL_OK) { + Tk_DestroyWindow(tkBusy); + return NULL; + } + SetWindowInstanceData(tkBusy, (ClientData)busyPtr); + winPtr = (Tk_FakeWin *) tkRef; + + TkpCreateBusy(winPtr, tkRef, &parent, tkParent, busyPtr); + + MakeTransparentWindowExist(tkBusy, parent); + + Tk_MoveResizeWindow(tkBusy, x, y, busyPtr->width, busyPtr->height); + + /* + * Only worry if the busy window is destroyed. + */ + + Tk_CreateEventHandler(tkBusy, StructureNotifyMask, BusyEventProc, + 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(tkBusy, &busyMgrInfo, busyPtr); +#ifndef MAC_OSX_TK + if (busyPtr->cursor != None) { + Tk_DefineCursor(tkBusy, busyPtr->cursor); + } +#endif + + /* + * Track the reference window to see if it is resized or destroyed. + */ + + Tk_CreateEventHandler(tkRef, StructureNotifyMask, RefWinEventProc, + busyPtr); + return busyPtr; +} + +/* + *---------------------------------------------------------------------- + * + * 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( + Tcl_Interp *interp, + Busy *busyPtr, + int objc, + Tcl_Obj *const objv[]) +{ +#ifndef MAC_OSX_TK + Tk_Cursor oldCursor = busyPtr->cursor; + + if (Tk_SetOptions(interp, (char *) busyPtr, busyPtr->optionTable, objc, + objv, busyPtr->tkBusy, NULL, NULL) != TCL_OK) { + return TCL_ERROR; + } + if (busyPtr->cursor != oldCursor) { + if (busyPtr->cursor == None) { + Tk_UndefineCursor(busyPtr->tkBusy); + } else { + Tk_DefineCursor(busyPtr->tkBusy, busyPtr->cursor); + } + } +#endif + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * 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. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetBusy( + Tcl_HashTable *busyTablePtr,/* busy hash table */ + Tcl_Interp *interp, /* Interpreter to report errors to */ + Tcl_Obj *const windowObj, /* Path name of parent window */ + Busy **busyPtrPtr) /* Will contain address of busy window if + * found. */ +{ + Tcl_HashEntry *hPtr; + Tk_Window tkwin; + + if (TkGetWindowFromObj(interp, Tk_MainWindow(interp), windowObj, + &tkwin) != TCL_OK) { + return TCL_ERROR; + } + hPtr = Tcl_FindHashEntry(busyTablePtr, (char *) tkwin); + if (hPtr == NULL) { + Tcl_AppendResult(interp, "can't find busy window \"", + Tcl_GetString(windowObj), "\"", NULL); + return TCL_ERROR; + } + *busyPtrPtr = 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( + Tcl_HashTable *busyTablePtr,/* Busy hash table. */ + Tcl_Interp *interp, /* Interpreter to report errors to. */ + Tcl_Obj *const windowObj, /* Window name. */ + int configObjc, /* Option pairs. */ + Tcl_Obj *const configObjv[]) +{ + Tk_Window tkwin; + Tcl_HashEntry *hPtr; + Busy *busyPtr; + int isNew, result; + + if (TkGetWindowFromObj(interp, Tk_MainWindow(interp), windowObj, + &tkwin) != TCL_OK) { + return TCL_ERROR; + } + hPtr = Tcl_CreateHashEntry(busyTablePtr, (char *) tkwin, &isNew); + if (isNew) { + busyPtr = (Busy *) CreateBusy(interp, tkwin); + if (busyPtr == NULL) { + return TCL_ERROR; + } + Tcl_SetHashValue(hPtr, busyPtr); + busyPtr->hashPtr = hPtr; + } else { + busyPtr = Tcl_GetHashValue(hPtr); + } + + busyPtr->tablePtr = busyTablePtr; + result = ConfigureBusy(interp, busyPtr, configObjc, configObjv); + + /* + * Don't map the busy window unless the reference window is also currently + * displayed. + */ + + if (Tk_IsMapped(busyPtr->tkRef)) { + TkpShowBusyWindow(busyPtr); + } else { + TkpHideBusyWindow(busyPtr); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * Tk_BusyObjCmd -- + * + * This function is invoked to process the "tk busy" 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_BusyObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tk_Window tkwin = clientData; + Tcl_HashTable *busyTablePtr = &((TkWindow *) tkwin)->mainPtr->busyTable; + Busy *busyPtr; + Tcl_Obj *objPtr; + int index, result = TCL_OK; + static const char *optionStrings[] = { + "cget", "configure", "current", "forget", "hold", "status", NULL + }; + enum options { + BUSY_CGET, BUSY_CONFIGURE, BUSY_CURRENT, BUSY_FORGET, BUSY_HOLD, + BUSY_STATUS + }; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "options ?arg arg ...?"); + return TCL_ERROR; + } + + /* + * [tk busy <window>] command shortcut. + */ + + if (Tcl_GetString(objv[2])[0] == '.') { + if (objc%2 != 1) { + Tcl_WrongNumArgs(interp, 2, objv, "window ?option value ...?"); + return TCL_ERROR; + } + return HoldBusy(busyTablePtr, interp, objv[2], objc-3, objv+3); + } + + if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum options) index) { + case BUSY_CGET: + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "window option"); + return TCL_ERROR; + } + if (GetBusy(busyTablePtr, interp, objv[3], &busyPtr) != TCL_OK) { + return TCL_ERROR; + } + Tcl_Preserve(busyPtr); + objPtr = Tk_GetOptionValue(interp, (char *) busyPtr, + busyPtr->optionTable, objv[4], busyPtr->tkBusy); + if (objPtr == NULL) { + result = TCL_ERROR; + } else { + Tcl_SetObjResult(interp, objPtr); + } + Tcl_Release(busyPtr); + return result; + + case BUSY_CONFIGURE: + if (objc < 4) { + Tcl_WrongNumArgs(interp, 3, objv, "window ?option? ?value ...?"); + return TCL_ERROR; + } + if (GetBusy(busyTablePtr, interp, objv[3], &busyPtr) != TCL_OK) { + return TCL_ERROR; + } + Tcl_Preserve(busyPtr); + if (objc <= 5) { + objPtr = Tk_GetOptionInfo(interp, (char *) busyPtr, + busyPtr->optionTable, (objc == 5) ? objv[4] : NULL, + busyPtr->tkBusy); + if (objPtr == NULL) { + result = TCL_ERROR; + } else { + Tcl_SetObjResult(interp, objPtr); + } + } else { + result = ConfigureBusy(interp, busyPtr, objc-4, objv+4); + } + Tcl_Release(busyPtr); + return TCL_OK; + + case BUSY_CURRENT: { + Tcl_HashEntry *hPtr; + Tcl_HashSearch cursor; + const char *pattern = (objc == 4 ? Tcl_GetString(objv[3]) : NULL); + + objPtr = Tcl_NewObj(); + for (hPtr = Tcl_FirstHashEntry(busyTablePtr, &cursor); hPtr != NULL; + hPtr = Tcl_NextHashEntry(&cursor)) { + busyPtr = Tcl_GetHashValue(hPtr); + if (pattern == NULL || + Tcl_StringMatch(Tk_PathName(busyPtr->tkRef), pattern)) { + Tcl_ListObjAppendElement(interp, objPtr, + TkNewWindowObj(busyPtr->tkRef)); + } + } + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; + } + + case BUSY_FORGET: + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "window"); + return TCL_ERROR; + } + if (GetBusy(busyTablePtr, interp, objv[3], &busyPtr) != TCL_OK) { + return TCL_ERROR; + } + TkpHideBusyWindow(busyPtr); + Tcl_EventuallyFree(busyPtr, DestroyBusy); + return TCL_OK; + + case BUSY_HOLD: + if (objc < 4 || objc%2 == 1) { + Tcl_WrongNumArgs(interp, 3, objv, "window ?option value ...?"); + return TCL_ERROR; + } + return HoldBusy(busyTablePtr, interp, objv[3], objc-4, objv+4); + + case BUSY_STATUS: + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "window"); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewBooleanObj( + GetBusy(busyTablePtr, interp, objv[3], &busyPtr) == TCL_OK)); + return TCL_OK; + } + + Tcl_Panic("unhandled option: %d", index); + return TCL_ERROR; /* Unreachable */ +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |