/* * tkWindow.c (CTk) -- * * CTk window manipulation functions. * * Copyright (c) 1989-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * Copyright (c) 1994-1995 Cleveland Clinic Foundation * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ static char rcsid[] = "@(#) $Id: ctk.shar,v 1.50 1996/01/15 14:47:16 andrewm Exp andrewm $"; #include "tkPort.h" #include "tkInt.h" #include "patchlevel.h" #define HEAD_CHILD(winPtr) ((TkWindow *) &((winPtr)->childList)) #define TOP_CHILD(winPtr) ((winPtr)->childList.priorPtr) #define BOTTOM_CHILD(winPtr) ((winPtr)->childList.nextPtr) /* * Count of number of main windows currently open in this process. */ int tk_NumMainWindows; /* * First in list of all main windows managed by this process. */ TkMainInfo *tkMainWindowList = NULL; /* * List of all displays currently in use. */ TkDisplay *tkDisplayList = NULL; /* * Have statics in this module been initialized? */ static int initialized = 0; /* * The variables below hold several uid's that are used in many places * in the toolkit. */ Tk_Uid tkDisabledUid = NULL; Tk_Uid tkActiveUid = NULL; Tk_Uid tkNormalUid = NULL; /* * The following structure defines all of the commands supported by * CTk, and the C procedures that execute them. */ typedef struct { char *name; /* Name of command. */ int (*cmdProc) _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv)); /* Command procedure. */ } TkCmd; static TkCmd commands[] = { /* * Commands that are part of the intrinsics: */ {"bell", Tk_BellCmd}, {"bind", Tk_BindCmd}, {"bindtags", Tk_BindtagsCmd}, /* {"clipboard", Tk_ClipboardCmd}, */ {"ctk", Ctk_CtkCmd}, {"ctk_event", Ctk_CtkEventCmd}, {"destroy", Tk_DestroyCmd}, {"exit", Tk_ExitCmd}, {"focus", Tk_FocusCmd}, /* {"grab", Tk_GrabCmd}, */ /* {"image", Tk_ImageCmd}, */ {"lower", Tk_LowerCmd}, {"option", Tk_OptionCmd}, {"pack", Tk_PackCmd}, {"place", Tk_PlaceCmd}, {"raise", Tk_RaiseCmd}, /* {"selection", Tk_SelectionCmd}, */ {"tk", Tk_TkCmd}, {"tk_focusNext", Ctk_TkFocusNextCmd}, {"tk_focusPrev", Ctk_TkFocusPrevCmd}, {"tkEntryInsert", Ctk_TkEntryInsertCmd}, {"tkEntrySeeInsert", Ctk_TkEntrySeeInsertCmd}, {"tkwait", Tk_TkwaitCmd}, {"update", Tk_UpdateCmd}, {"winfo", Tk_WinfoCmd}, /* {"wm", Tk_WmCmd}, */ /* * Widget-creation commands. */ {"button", Tk_ButtonCmd}, /* {"canvas", Tk_CanvasCmd}, */ {"checkbutton", Tk_CheckbuttonCmd}, {"entry", Tk_EntryCmd}, {"frame", Tk_FrameCmd}, {"label", Tk_LabelCmd}, {"listbox", Tk_ListboxCmd}, {"menu", Tk_MenuCmd}, {"menubutton", Tk_MenubuttonCmd}, /* {"message", Tk_MessageCmd}, */ {"radiobutton", Tk_RadiobuttonCmd}, /* {"scale", Tk_ScaleCmd}, */ {"scrollbar", Tk_ScrollbarCmd}, {"text", Tk_TextCmd}, {"toplevel", Tk_FrameCmd}, {(char *) NULL, (int (*)()) NULL} }; /* * Forward declarations to procedures defined later in this file: */ static TkDisplay * GetScreen _ANSI_ARGS_((Tcl_Interp *interp, char *screenName)); static TkWindow * CreateRoot _ANSI_ARGS_((Tcl_Interp *interp, TkDisplay *dispPtr)); static TkWindow * NewWindow _ANSI_ARGS_((TkDisplay *dispPtr)); static void DisplayWindow _ANSI_ARGS_((TkWindow *winPtr)); static void UndisplayWindow _ANSI_ARGS_((TkWindow *winPtr)); static void InsertWindow _ANSI_ARGS_((TkWindow *winPtr, TkWindow *sibling)); static void UnlinkWindow _ANSI_ARGS_((TkWindow *winPtr)); static void Unoverlap _ANSI_ARGS_((TkWindow *underPtr, TkWindow *overPtr)); static void UnoverlapHierarchy _ANSI_ARGS_((TkWindow *underPtr, TkWindow * overPtr)); static void ExposeWindow _ANSI_ARGS_((TkWindow *winPtr, CtkRegion *rgn)); static void ComputeClipRect _ANSI_ARGS_((TkWindow *winPtr)); /* *---------------------------------------------------------------------- * * Tk_Init -- * * This procedure is typically invoked by Tcl_AppInit procedures * to perform additional Tk initialization for a Tcl interpreter, * such as sourcing the "ctk.tcl" script. * * Results: * Returns a standard Tcl completion code and sets interp->result * if there is an error. * * Side effects: * Depends on what's in the ctk.tcl script. * *---------------------------------------------------------------------- */ int Tk_Init(interp) Tcl_Interp *interp; /* Interpreter to initialize. */ { #ifdef USE_TCL_STUBS Tk_Window winPtr; #endif static char initCmd[] = "if [file exists $tk_library/ctk.tcl] {\n\ source $tk_library/ctk.tcl\n\ } else {\n\ set msg \"can't find $tk_library/ctk.tcl; perhaps you \"\n\ append msg \"need to\\ninstall CTk or set your CTK_LIBRARY \"\n\ append msg \"environment variable?\"\n\ error $msg\n\ }"; int retval; #ifdef USE_TCL_STUBS winPtr = Tk_CreateMainWindow(interp, NULL, "ctk", "ctk"); if (winPtr == NULL) { return(TCL_ERROR); } #endif retval = Tcl_Eval(interp, initCmd); if (retval != TCL_OK) { return(retval); } #ifdef USE_TCL_STUBS Tcl_SetMainLoop(Tk_MainLoop); #endif return(retval); } /* *---------------------------------------------------------------------- * * GetScreen -- * * Given a string name for a terminal device-plus-type, find the * TkDisplay structure for the display. * * Results: * The return value is a pointer to information about the display, * or NULL if the display couldn't be opened. In this case, an * error message is left in interp->result. * * Side effects: * A new stream is opened to the device if there is no * connection already. A new TkDisplay data structure is also * setup, if necessary. * *---------------------------------------------------------------------- */ static TkDisplay * GetScreen(interp, screenName) Tcl_Interp *interp; /* Place to leave error message. */ char *screenName; /* Name for screen. NULL or empty means * use CTK_DISPLAY environment variable. */ { register TkDisplay *dispPtr; char *p; size_t length; /* * Separate the terminal type from the rest of the display * name. ScreenName is assumed to have the syntax * <device>:<type> with the colon and the type being * optional. */ if (screenName == NULL || screenName[0] == '\0') { screenName = Tcl_GetVar2(interp, "env", "CTK_DISPLAY", TCL_GLOBAL_ONLY); if (screenName == NULL) { /* * For backwards compatibility, check CWISH_DISPLAY - * this feature will eventually be removed. */ screenName = Tcl_GetVar2(interp, "env", "CWISH_DISPLAY", TCL_GLOBAL_ONLY); } if (screenName == NULL) { screenName = "tty"; } } p = strchr(screenName, ':'); if (p == NULL) { length = strlen(screenName); } else { length = p - screenName; } /* * See if we already have a connection to this display. */ for (dispPtr = tkDisplayList; dispPtr != NULL; dispPtr = dispPtr->nextPtr) { if ((strncmp(dispPtr->name, screenName, length) == 0) && (dispPtr->name[length] == '\0')) { return dispPtr; } } /* * Create entry for new display. */ dispPtr = (TkDisplay *) ckalloc(sizeof(TkDisplay)); if (CtkDisplayInit(interp, dispPtr, screenName) != TCL_OK) { return (TkDisplay *) NULL; } dispPtr->numWindows = 0; dispPtr->rootPtr = CreateRoot(interp, dispPtr); if (dispPtr->rootPtr == NULL) { CtkDisplayEnd(dispPtr); return (TkDisplay *) NULL; } dispPtr->focusPtr = dispPtr->rootPtr; dispPtr->cursorPtr = dispPtr->rootPtr; dispPtr->cursorX = 0; dispPtr->cursorY = 0; dispPtr->nextPtr = tkDisplayList; tkDisplayList = dispPtr; return dispPtr; } /* *---------------------------------------------------------------------- * * Tk_CreateMainWindow -- * * Make a new main window. A main window is a special kind of * top-level window used as the outermost window in an * application. * * Results: * The return value is a token for the new window, or NULL if * an error prevented the new window from being created. If * NULL is returned, an error message will be left in * interp->result. * * Side effects: * A new window structure is allocated locally; "interp" is * associated with the window and registered for "send" commands * under "baseName". BaseName may be extended with an instance * number in the form "#2" if necessary to make it globally * unique. Tk-related commands are bound into interp. The main * window becomes a "toplevel" widget and its X window will be * created and mapped as an idle handler. * *---------------------------------------------------------------------- */ Tk_Window Tk_CreateMainWindow(interp, screenName, baseName, className) Tcl_Interp *interp; /* Interpreter to use for error reporting. */ char *screenName; /* "device:term-type" on which to create * window. Empty or NULL string means * use stdin/stdout. */ char *baseName; /* Base name for application; usually of the * form "prog instance". */ char *className; /* Class to use for application (same as class * for main window). */ { int dummy; Tcl_HashEntry *hPtr; register TkMainInfo *mainPtr; register TkWindow *winPtr; register TkDisplay *dispPtr; register TkCmd *cmdPtr; char *libDir; char *argv[1]; if (!initialized) { initialized = 1; tkNormalUid = Tk_GetUid("normal"); tkDisabledUid = Tk_GetUid("disabled"); tkActiveUid = Tk_GetUid("active"); } /* * Create the TkMainInfo structure for this application. */ mainPtr = (TkMainInfo *) ckalloc(sizeof(TkMainInfo)); mainPtr->winPtr = NULL; mainPtr->refCount = 0; mainPtr->interp = interp; Tcl_InitHashTable(&mainPtr->nameTable, TCL_STRING_KEYS); mainPtr->bindingTable = Tk_CreateBindingTable(interp); mainPtr->curDispPtr = NULL; mainPtr->bindingDepth = 0; mainPtr->optionRootPtr = NULL; mainPtr->nextPtr = tkMainWindowList; tkMainWindowList = mainPtr; /* * Create the basic TkWindow structure. * * Temporarily put root window into the application's name table * and set root windows mainPtr to the new main structure, * so that Tk_TopLevelCmd() will use the new main structure for * the window it creates. */ if (screenName == (char *) NULL) { screenName = ""; } dispPtr = GetScreen(interp, screenName); if (dispPtr == NULL) { return (Tk_Window) NULL; } dispPtr->rootPtr->mainPtr = mainPtr; hPtr = Tcl_CreateHashEntry(&mainPtr->nameTable, "", &dummy); Tcl_SetHashValue(hPtr, dispPtr->rootPtr); winPtr = Tk_CreateWindowFromPath(interp, dispPtr->rootPtr, ".", screenName); Tcl_DeleteHashEntry(hPtr); dispPtr->rootPtr->mainPtr = NULL; if (winPtr == NULL) { return (Tk_Window) NULL; } mainPtr->winPtr = winPtr; /* * Bind in Tk's commands. */ for (cmdPtr = commands; cmdPtr->name != NULL; cmdPtr++) { Tcl_CreateCommand(interp, cmdPtr->name, cmdPtr->cmdProc, (ClientData) winPtr, (void (*)()) NULL); } /* * Set variables for the intepreter. */ if (Tcl_GetVar(interp, "tk_library", TCL_GLOBAL_ONLY) == NULL) { /* * A library directory hasn't already been set, so figure out * which one to use. */ libDir = getenv("CTK_LIBRARY"); if (libDir == NULL) { libDir = CTK_LIBRARY; } Tcl_SetVar(interp, "tk_library", libDir, TCL_GLOBAL_ONLY); } Tcl_SetVar(interp, "ctk_patchLevel", CTK_PATCH_LEVEL, TCL_GLOBAL_ONLY); Tcl_SetVar(interp, "tk_version", TK_VERSION, TCL_GLOBAL_ONLY); Tcl_SetVar(interp, "tk_port", "curses", TCL_GLOBAL_ONLY); /* * Make the main window into a toplevel widget, and give it an initial * requested size. */ Tk_SetClass(winPtr, className); argv[0] = NULL; if (TkInitFrame(interp, winPtr, 1, 0, argv) == NULL) { return NULL; } Tk_GeometryRequest(winPtr, 20, 10); CtkSetFocus(winPtr); tk_NumMainWindows++; return winPtr; } /* *---------------------------------------------------------------------- * * Ctk_Unsupported -- * * This procedure is invoked when a Tk feature that is not * supported by CTk is requested. * * Results: * A standard TCL result. * * Side effects: * Sets the interpreter result, if "ctk_unsupported" is defined, * could do anything. * *---------------------------------------------------------------------- */ int Ctk_Unsupported(interp, feature) Tcl_Interp *interp; /* Interpreter in which unsupported * feature has been requested. */ char *feature; /* Description of requested feature. */ { Tcl_CmdInfo info; char *argv[3]; Tcl_ResetResult(interp); if (Tcl_GetCommandInfo(interp, "ctk_unsupported", &info)) { argv[0] = "ctk_unsupported"; argv[1] = feature; argv[2] = NULL; return (*info.proc)(info.clientData, interp, 2, argv); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tk_CreateWindowFromPath -- * * This procedure is similar to Tk_CreateWindow except that * it uses a path name to create the window, rather than a * parent and a child name. * * Results: * The return value is a token for the new window. If an error * occurred in creating the window (e.g. no such display or * screen), then an error message is left in interp->result and * NULL is returned. * * Side effects: * A new window structure is allocated. * *---------------------------------------------------------------------- */ Tk_Window Tk_CreateWindowFromPath(interp, winPtr, pathName, screenName) Tcl_Interp *interp; /* Interpreter to use for error reporting. * Interp->result is assumed to be * initialized by the caller. */ TkWindow *winPtr; /* Token for any window in application * that is to contain new window. */ char *pathName; /* Path name for new window within the * application of tkwin. The parent of * this window must already exist, but * the window itself must not exist. */ char *screenName; /* If NULL, new window will be on same * screen as its parent. If non-NULL, * gives name of screen on which to create * new window; window will be a top-level * window. */ { TkWindow *parentPtr; TkDisplay *dispPtr; Tcl_HashEntry *hPtr; int new; char *name; name = strrchr(pathName, '.'); if (name) { name++; } parentPtr = Ctk_ParentByName(interp, pathName, winPtr); if (parentPtr == (TkWindow *) NULL) { return (TkWindow *) NULL; } if (screenName == NULL) { dispPtr = parentPtr->dispPtr; } else { dispPtr = GetScreen(interp, screenName); } /* * Get entry for new name. */ hPtr = Tcl_CreateHashEntry(&winPtr->mainPtr->nameTable, pathName, &new); if (!new) { Tcl_AppendResult(interp, "window name \"", pathName, "\" already exists", (char *) NULL); return NULL; } /* * Create the window. */ winPtr = NewWindow(dispPtr); if (screenName) { winPtr->parentPtr = dispPtr->rootPtr; winPtr->flags |= TK_TOP_LEVEL; parentPtr->flags |= CTK_HAS_TOPLEVEL_CHILD; } else { winPtr->parentPtr = parentPtr; } winPtr->mainPtr = parentPtr->mainPtr; winPtr->mainPtr->refCount++; InsertWindow(winPtr, HEAD_CHILD(winPtr->parentPtr)); Tcl_SetHashValue(hPtr, winPtr); winPtr->pathName = Tcl_GetHashKey(&winPtr->mainPtr->nameTable, hPtr); winPtr->nameUid = Tk_GetUid(name); return winPtr; } /* *---------------------------------------------------------------------- * Ctk_ParentByName -- * Determine parent of window based on path name. This is necessary * for top level windows because Ctk_Parent() will always return * the root window for them. * * Results: * Returns pointer to new window if successful. Returns * NULL if the parent can't be found, and stores an error * message in interp->result. * * Side Effects: *---------------------------------------------------------------------- */ TkWindow * Ctk_ParentByName(interp, pathName, tkwin) Tcl_Interp *interp; char *pathName; Tk_Window tkwin; { #define FIXED_SPACE 50 char fixedSpace[FIXED_SPACE+1]; char *p; int numChars; Tk_Window parent; /* * Strip the parent's name out of pathName (it's everything up * to the last dot). There are two tricky parts: (a) must * copy the parent's name somewhere else to avoid modifying * the pathName string (for large names, space for the copy * will have to be malloc'ed); (b) must special-case the * situations where the parent is "" or ".". */ p = strrchr(pathName, '.'); if (p == NULL) { Tcl_AppendResult(interp, "bad window path name \"", pathName, "\"", (char *) NULL); return NULL; } numChars = p-pathName; if (numChars > FIXED_SPACE) { p = (char *) ckalloc((unsigned) (numChars+1)); } else { p = fixedSpace; } if (pathName[1] == '\0') { /* * Parent is root: "" */ *p = '\0'; } else if (numChars == 0) { /* * Parent is main: "." */ *p = '.'; p[1] = '\0'; } else { strncpy(p, pathName, numChars); p[numChars] = '\0'; } /* * Find the parent window. */ parent = Tk_NameToWindow(interp, p, tkwin); if (p != fixedSpace) { ckfree(p); } return parent; } /* *---------------------------------------------------------------------- * * Tk_SetClass -- * * This procedure is used to give a window a class. * * Results: * None. * * Side effects: * A new class is stored for tkwin, replacing any existing * class for it. * *---------------------------------------------------------------------- */ void Tk_SetClass(tkwin, className) Tk_Window tkwin; /* Token for window to assign class. */ char *className; /* New class for tkwin. */ { register TkWindow *winPtr = (TkWindow *) tkwin; winPtr->classUid = Tk_GetUid(className); TkOptionClassChanged(winPtr); } /* *---------------------------------------------------------------------- * * Tk_NameToWindow -- * * Given a string name for a window, this procedure * returns the token for the window, if there exists a * window corresponding to the given name. * * Results: * The return result is either a token for the window corresponding * to "name", or else NULL to indicate that there is no such * window. In this case, an error message is left in interp->result. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tk_Window Tk_NameToWindow(interp, pathName, winPtr) Tcl_Interp *interp; /* Where to report errors. */ char *pathName; /* Path name of window. */ TkWindow *winPtr; /* Token for window, name is assumed to * belong to the same main window as winPtr. */ { Tcl_HashEntry *hPtr; hPtr = Tcl_FindHashEntry(&winPtr->mainPtr->nameTable, pathName); if (hPtr == NULL) { Tcl_AppendResult(interp, "bad window path name \"", pathName, "\"", (char *) NULL); return NULL; } return (Tk_Window) Tcl_GetHashValue(hPtr); } /* *---------------------------------------------------------------------- * * CreateRoot -- * * Creates the root window (whole screen, no parent). The window * is mapped and displayed. * * Results: * A new window pointer. * * Side Effects: * Screen is cleared. * *---------------------------------------------------------------------- */ static TkWindow * CreateRoot(interp, dispPtr) Tcl_Interp *interp; TkDisplay *dispPtr; { TkWindow *winPtr = NewWindow(dispPtr); winPtr->mainPtr = NULL; winPtr->parentPtr = NULL; winPtr->nextPtr = NULL; winPtr->priorPtr = NULL; CtkSetRect(&winPtr->rect, 0, 0, Ctk_DisplayWidth(dispPtr), Ctk_DisplayHeight(dispPtr)); CtkCopyRect(&winPtr->maskRect, &winPtr->rect); CtkCopyRect(&winPtr->clipRect, &winPtr->rect); winPtr->clipRgn = CtkCreateRegion(&(winPtr->maskRect)); winPtr->absLeft = 0; winPtr->absTop = 0; winPtr->flags |= TK_MAPPED|CTK_DISPLAYED|TK_TOP_LEVEL; winPtr->classUid = Tk_GetUid("Root"); Ctk_ClearWindow(winPtr); return winPtr; } /* *---------------------------------------------------------------------- * NewWindow -- * Allocate a window structure and initialize contents. * * Results: * Returns pointer to window. * * Side Effects: * *---------------------------------------------------------------------- */ static TkWindow * NewWindow(dispPtr) TkDisplay *dispPtr; { TkWindow *winPtr = (TkWindow *) ckalloc(sizeof(TkWindow)); winPtr->dispPtr = dispPtr; winPtr->pathName = NULL; winPtr->classUid = NULL; winPtr->mainPtr = NULL; winPtr->flags = 0; winPtr->handlerList = NULL; winPtr->numTags = 0; winPtr->optionLevel = -1; winPtr->tagPtr = NULL; winPtr->childList.nextPtr = HEAD_CHILD(winPtr); winPtr->childList.priorPtr = HEAD_CHILD(winPtr); winPtr->borderWidth = 0; winPtr->fillChar = ' '; winPtr->fillStyle = CTK_PLAIN_STYLE; winPtr->clipRgn = NULL; winPtr->reqWidth = 1; winPtr->reqHeight = 1; winPtr->geomMgrPtr = NULL; winPtr->geomData = NULL; dispPtr->numWindows++; return winPtr; } /* *-------------------------------------------------------------- * * Tk_DestroyWindow -- * * Destroy an existing window. After this call, the caller * should never again use the token. * * Results: * None. * * Side effects: * The window is deleted, along with all of its children. * Relevant callback procedures are invoked. * *-------------------------------------------------------------- */ void Tk_DestroyWindow(winPtr) TkWindow *winPtr; { TkWindow *child; TkWindow *nextPtr; Ctk_Event event; if (winPtr->flags & TK_ALREADY_DEAD) { /* * A destroy event binding caused the window to be destroyed * again. Ignore the request. */ return; } winPtr->flags |= TK_ALREADY_DEAD; Ctk_Unmap(winPtr); /* * If this is a main window, remove it from the list of main * windows. This needs to be done now (rather than later with * all the other main window cleanup) to handle situations where * a destroy binding for a window calls "exit". In this case * the child window cleanup isn't complete when exit is called, * so the reference count of its application doesn't go to zero * when exit calls Tk_DestroyWindow on ".", so the main window * doesn't get removed from the list and exit loops infinitely. * Even worse, if "destroy ." is called by the destroy binding * before calling "exit", "exit" will attempt to destroy * mainPtr->winPtr, which no longer exists, and there may be a * core dump. */ if (winPtr->mainPtr->winPtr == winPtr) { if (tkMainWindowList == winPtr->mainPtr) { tkMainWindowList = winPtr->mainPtr->nextPtr; } else { TkMainInfo *prevPtr; for (prevPtr = tkMainWindowList; prevPtr->nextPtr != winPtr->mainPtr; prevPtr = prevPtr->nextPtr) { /* Empty loop body. */ } prevPtr->nextPtr = winPtr->mainPtr->nextPtr; } tk_NumMainWindows--; } /* * Recursively destroy children. */ for (child = BOTTOM_CHILD(winPtr); child != HEAD_CHILD(winPtr); child = nextPtr) { nextPtr = child->nextPtr; Tk_DestroyWindow(child); } if (winPtr->flags & CTK_HAS_TOPLEVEL_CHILD) { /* * This window has toplevel children, which are not stored * in the child list. Check all the children of all root * windows to see if their name is an extension of this * windows name - if so destroy the top level window. */ char *path = Tk_PathName(winPtr); char *childPath; int length = strlen(path); TkWindow *priorPtr; TkDisplay *dispPtr; for (dispPtr = tkDisplayList; dispPtr != NULL; dispPtr = dispPtr->nextPtr) { priorPtr = HEAD_CHILD(dispPtr->rootPtr); child = BOTTOM_CHILD(dispPtr->rootPtr); while (child != HEAD_CHILD(dispPtr->rootPtr)) { childPath = Tk_PathName(child); if (strncmp(childPath, path, length) == 0 && (childPath[length] == '.' || (length == 1 && childPath[1] != '\0'))) { Tk_DestroyWindow(child); } else { priorPtr = child; } child = priorPtr->nextPtr; } } } /* * Generate a Destroy event. * * Note: if the window's pathName is NULL it means that the window * was not successfully initialized in the first place, so we should * not make the window exist or generate the event. */ if (winPtr->pathName != NULL) { event.type = CTK_DESTROY_EVENT; event.window = winPtr; Tk_HandleEvent(&event); } UnlinkWindow(winPtr); TkEventDeadWindow(winPtr); if (winPtr->tagPtr != NULL) { TkFreeBindingTags(winPtr); } TkOptionDeadWindow(winPtr); TkFocusDeadWindow(winPtr); if (winPtr->mainPtr != NULL) { if (winPtr->pathName != NULL) { Tk_DeleteAllBindings(winPtr->mainPtr->bindingTable, (ClientData) winPtr->pathName); Tcl_DeleteHashEntry(Tcl_FindHashEntry(&winPtr->mainPtr->nameTable, winPtr->pathName)); } winPtr->mainPtr->refCount--; if (winPtr->mainPtr->refCount == 0) { register TkCmd *cmdPtr; /* * We just deleted the last window in the application. Delete * the TkMainInfo structure too and replace all of Tk's commands * with dummy commands that return errors (except don't replace * the "exit" command, since it may be needed for the application * to exit). */ for (cmdPtr = commands; cmdPtr->name != NULL; cmdPtr++) { if (cmdPtr->cmdProc != Tk_ExitCmd) { Tcl_CreateCommand(winPtr->mainPtr->interp, cmdPtr->name, TkDeadAppCmd, (ClientData) NULL, (void (*)()) NULL); } } if (winPtr->mainPtr->bindingDepth == 0) { TkDeleteMain(winPtr->mainPtr); } } } if ((--(winPtr->dispPtr->numWindows)) == 1) { TkDisplay *dispPtr = winPtr->dispPtr; CtkDisplayEnd(dispPtr); if (tkDisplayList == dispPtr) { tkDisplayList = dispPtr->nextPtr; } else { TkDisplay *prevDispPtr; for (prevDispPtr = tkDisplayList; prevDispPtr != NULL; prevDispPtr = prevDispPtr->nextPtr) { if (prevDispPtr->nextPtr == dispPtr) { prevDispPtr->nextPtr = dispPtr->nextPtr; break; } } } ckfree((char *) dispPtr->rootPtr); ckfree((char *) dispPtr); } ckfree((char *) winPtr); } /* *------------------------------------------------------------ * TkDeleteMain -- * * Release resources for a TkMainInfo structure. * All windows for this main should already have * been destroyed. The pointer should no be referenced * again. * * Results: * None. * * Side Effects: * None. * *------------------------------------------------------------ */ void TkDeleteMain(mainPtr) TkMainInfo *mainPtr; { Tcl_DeleteHashTable(&mainPtr->nameTable); Tk_DeleteBindingTable(mainPtr->bindingTable); ckfree((char *) mainPtr); } /* *---------------------------------------------------------------------- * * Tk_RestackWindow -- * * Change a window's position in the stacking order. * * Results: * TCL_OK is normally returned. If other is not a descendant * of tkwin's parent then TCL_ERROR is returned and tkwin is * not repositioned. * * Side effects: * Tkwin is repositioned in the stacking order. * *---------------------------------------------------------------------- */ int Tk_RestackWindow(winPtr, aboveBelow, otherPtr) TkWindow *winPtr; int aboveBelow; TkWindow *otherPtr; { int redisplay = 0; if (otherPtr) { /* * Find ancestor of otherPtr (or otherPtr itself) that is a * sibling of winPtr. */ while (otherPtr->parentPtr != winPtr->parentPtr) { otherPtr = otherPtr->parentPtr; if (!otherPtr) { return TCL_ERROR; } } } if (otherPtr == winPtr) { return TCL_OK; } if (CtkIsDisplayed(winPtr)) { UndisplayWindow(winPtr); redisplay = 1; } UnlinkWindow(winPtr); if (aboveBelow == Above) { if (otherPtr) { otherPtr = otherPtr->nextPtr; } else { otherPtr = HEAD_CHILD(winPtr->parentPtr); } } else { if (!otherPtr) { otherPtr = BOTTOM_CHILD(winPtr->parentPtr); } } InsertWindow(winPtr, otherPtr); if (redisplay) { DisplayWindow(winPtr); } return TCL_OK; } /* *------------------------------------------------------------ * Ctk_Map -- * * Position a window within its parent. * * Results: * None. * * Side Effects: * Generates a map event for the window. * If parent is displayed, then the window will be displayed. * *------------------------------------------------------------ */ void Ctk_Map(winPtr, left, top, right, bottom) TkWindow *winPtr; int left; int top; int right; int bottom; { TkWindow *parentPtr = winPtr->parentPtr; Ctk_Event event; /* * Keep top-levels within the bounds of the screen. */ if (winPtr->flags & TK_TOP_LEVEL) { int width = right - left; int height = bottom - top; int screenWidth = Tk_Width(parentPtr); int screenHeight = Tk_Height(parentPtr); if (width > screenWidth) { width = screenWidth; } if (height > screenHeight) { height = screenHeight; } if (left < 0) { left = 0; } else if (left + width > screenWidth) { left = screenWidth - width ; } if (top < 0) { top = 0; } else if (top + height > screenHeight) { top = screenHeight - height; } right = left + width; bottom = top + height; } if ( !Tk_IsMapped(winPtr) || (winPtr->rect.left != left) || (winPtr->rect.top != top) || (winPtr->rect.right != right) || (winPtr->rect.bottom != bottom)) { /* * Window position changed (or window was not mapped * before). Undisplay window, re-position it, and then * display it if parent is displayed. */ if (CtkIsDisplayed(winPtr)) { UndisplayWindow(winPtr); } CtkSetRect(&(winPtr->rect), left, top, right, bottom); winPtr->flags |= TK_MAPPED; if (CtkIsDisplayed(parentPtr)) { DisplayWindow(winPtr); } } event.type = CTK_MAP_EVENT; event.window = winPtr; Tk_HandleEvent(&event); } /* *------------------------------------------------------------ * Ctk_Unmap -- * * Remove positioning for a window. * * Results: * None. * * Side Effects: * If window is displayed, it and all its descendants are * undisplayed. * *------------------------------------------------------------ */ void Ctk_Unmap(winPtr) TkWindow *winPtr; { Ctk_Event event; if (Tk_IsMapped(winPtr)) { /* * Window is mapped, unmap it. */ if (CtkIsDisplayed(winPtr)) { UndisplayWindow(winPtr); } winPtr->flags &= ~TK_MAPPED; event.type = CTK_UNMAP_EVENT; event.window = winPtr; Tk_HandleEvent(&event); } } /* *------------------------------------------------------------ * Ctk_BottomChild -- * Ctk_TopChild -- * Ctk_PriorSibling -- * Ctk_NextSibling -- * Ctk_TopLevel -- * * Get window relative. * * Results: * Pointer to window, or NULL if window has no such relative. * * Side Effects: * None. * *------------------------------------------------------------ */ TkWindow * Ctk_BottomChild(winPtr) TkWindow *winPtr; { TkWindow * child = BOTTOM_CHILD(winPtr); if (child == HEAD_CHILD(winPtr)) { return (TkWindow *) NULL; } else { return child; } } TkWindow * Ctk_TopChild(winPtr) TkWindow *winPtr; { TkWindow * child = TOP_CHILD(winPtr); if (child == HEAD_CHILD(winPtr)) { return (TkWindow *) NULL; } else { return child; } } TkWindow * Ctk_NextSibling(winPtr) TkWindow *winPtr; { TkWindow * sibling = winPtr->nextPtr; if (sibling == HEAD_CHILD(winPtr->parentPtr)) { return (TkWindow *) NULL; } else { return sibling; } } TkWindow * Ctk_PriorSibling(winPtr) TkWindow *winPtr; { TkWindow * sibling = winPtr->priorPtr; if (sibling == HEAD_CHILD(winPtr->parentPtr)) { return (TkWindow *) NULL; } else { return sibling; } } TkWindow * Ctk_TopLevel(winPtr) TkWindow *winPtr; { while (!Tk_IsTopLevel(winPtr)) { winPtr = winPtr->parentPtr; } return winPtr; } /* *------------------------------------------------------------ * Tk_SetInternalBorder -- * * Set window's internal border width. The standard drawing * routines will not draw on the internal border (only * Ctk_DrawBorder() will) and the geometry managers should * not place child windows there. * * Results: * None. * * Side Effects: * The border width is recorded for the window, and a map * event is synthesized so that all geometry managers of all * children are notified to re-layout, if necessary. * *------------------------------------------------------------ */ void Tk_SetInternalBorder(winPtr, width) TkWindow *winPtr; int width; { Ctk_Event event; if (winPtr->borderWidth != width) { winPtr->borderWidth = width; ComputeClipRect(winPtr); event.type = CTK_MAP_EVENT; event.window = winPtr; Tk_HandleEvent(&event); } } /* *-------------------------------------------------------------- * * Ctk_DrawBorder -- * * Draw border for a window in specified style. * * Results: * None. * * Side effects: * Characters are output to the terminal. * *-------------------------------------------------------------- */ void Ctk_DrawBorder(winPtr, style, title) TkWindow *winPtr; Ctk_Style style; char *title; { int borderWidth = winPtr->borderWidth; if (borderWidth > 0) { Ctk_Rect saveClip; /* * Temporarily set clipRect to maskRect so that we can * draw within the border area. */ CtkCopyRect(&saveClip, &winPtr->clipRect); CtkCopyRect(&winPtr->clipRect, &winPtr->maskRect); Ctk_DrawRect(winPtr, 0, 0, Tk_Width(winPtr)-1, Tk_Height(winPtr)-1, style); if (title) { Ctk_DrawString(winPtr, 1, 0, style, title, -1); } CtkCopyRect(&winPtr->clipRect, &saveClip); } } /* *---------------------------------------------------------------------- * * Tk_MainWindow -- * * Returns the main window for an application. * * Results: * If interp has a Tk application associated with it, the main * window for the application is returned. Otherwise NULL is * returned and an error message is left in interp->result. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tk_Window Tk_MainWindow(interp) Tcl_Interp *interp; /* Interpreter that embodies the * application. Used for error * reporting also. */ { TkMainInfo *mainPtr; for (mainPtr = tkMainWindowList; mainPtr != NULL; mainPtr = mainPtr->nextPtr) { if (mainPtr->interp == interp) { return (Tk_Window) mainPtr->winPtr; } } Tcl_SetResult(interp,"this isn't a Tk application",TCL_STATIC); return NULL; } /* *------------------------------------------------------------ * DisplayWindow -- * * Display window and all its mapped descendants. * * Results: * None. * * Side Effects: * *------------------------------------------------------------ */ static void DisplayWindow(winPtr) TkWindow *winPtr; { TkWindow *parentPtr = winPtr->parentPtr; TkWindow *sibling; TkWindow *child; if (CtkIsDisplayed(winPtr)) { panic("Attempt to display already displayed window"); } winPtr->flags |= CTK_DISPLAYED; winPtr->absLeft = parentPtr->absLeft + winPtr->rect.left; winPtr->absTop = parentPtr->absTop + winPtr->rect.top; winPtr->maskRect.top = winPtr->absTop; winPtr->maskRect.left = winPtr->absLeft; winPtr->maskRect.bottom = parentPtr->absTop + winPtr->rect.bottom; winPtr->maskRect.right = parentPtr->absLeft + winPtr->rect.right; CtkIntersectRects(&(winPtr->maskRect), &(parentPtr->clipRect)); ComputeClipRect(winPtr); if (winPtr->flags & TK_TOP_LEVEL) { /* * This is a top level window, compute clipping by siblings. * Start with a clipping region equal to `maskRect', then * remove overlaps with siblings above this window. */ winPtr->clipRgn = CtkCreateRegion(&(winPtr->maskRect)); for (sibling = winPtr->nextPtr; sibling != HEAD_CHILD(parentPtr); sibling = sibling->nextPtr) { if (CtkIsDisplayed(sibling)) { CtkRegionMinusRect(winPtr->clipRgn, &(sibling->maskRect), 0); } } /* * For each sibling below this window (and the root), * subtract the overlap between this window and the sibling * from the sibling's clipping region. */ for (sibling = winPtr->priorPtr; sibling != HEAD_CHILD(parentPtr); sibling = sibling->priorPtr) { if (CtkIsDisplayed(sibling)) { CtkRegionMinusRect(sibling->clipRgn, &(winPtr->maskRect), 0); } } CtkRegionMinusRect(parentPtr->clipRgn, &(winPtr->maskRect), 0); } else { winPtr->clipRgn = parentPtr->clipRgn; } Ctk_ClearWindow(winPtr); ExposeWindow(winPtr, winPtr->clipRgn); for (child = BOTTOM_CHILD(winPtr); child != HEAD_CHILD(winPtr); child = child->nextPtr) { if (Tk_IsMapped(child)) { DisplayWindow(child); } } } /* *------------------------------------------------------------ * ComputeClipRect -- * * Set the clipping rectangle for a window according * to it's position, border-width, and parent's clipping * rectangle. * * Results: * None. * * Side Effects: * Stores new values in winPtr->clipRect. * *------------------------------------------------------------ */ static void ComputeClipRect(winPtr) register TkWindow *winPtr; { register TkWindow *parentPtr = winPtr->parentPtr; winPtr->clipRect.top = winPtr->absTop + winPtr->borderWidth; winPtr->clipRect.left = winPtr->absLeft + winPtr->borderWidth; winPtr->clipRect.bottom = parentPtr->absTop + winPtr->rect.bottom - winPtr->borderWidth; winPtr->clipRect.right = parentPtr->absLeft + winPtr->rect.right - winPtr->borderWidth; CtkIntersectRects(&(winPtr->clipRect), &(parentPtr->clipRect)); } /* *------------------------------------------------------------ * ExposeWindow -- * * Send expose event(s) to window for specified region * * Results: * Pointer to sibling window, or NULL if window * does not have a sibling that is displayed and enabled. * * Side Effects: * None. * *------------------------------------------------------------ */ static void ExposeWindow(winPtr, rgnPtr) TkWindow *winPtr; CtkRegion *rgnPtr; { Ctk_Event event; /* * Compute intersection of rgnPtr and winPtr->maskRect. */ CtkRegionGetRect(rgnPtr, &event.u.expose); CtkIntersectRects(&event.u.expose, &(winPtr->maskRect)); CtkMoveRect(&event.u.expose, -winPtr->absLeft, -winPtr->absTop); event.type = CTK_EXPOSE_EVENT; event.window = winPtr; Tk_HandleEvent(&event); } /* *------------------------------------------------------------ * UndisplayWindow -- * * Stop displaying window and all its descendants. * Window must currently be displayed. * * Results: * None. * * Side Effects: * *------------------------------------------------------------ */ static void UndisplayWindow(winPtr) TkWindow *winPtr; { TkWindow *child; TkWindow *sibling; TkWindow *parentPtr = winPtr->parentPtr; Ctk_Event event; if (!CtkIsDisplayed(winPtr)) { panic("Attempt to undisplay window that isn't displayed"); } /* * Stop displaying the descendants of `winPtr'. */ for (child = BOTTOM_CHILD(winPtr); child != HEAD_CHILD(winPtr); child = child->nextPtr) { if (CtkIsDisplayed(child)) { UndisplayWindow(child); } } winPtr->flags &= ~CTK_DISPLAYED; if (parentPtr == NULL) { CtkDestroyRegion(winPtr->clipRgn); } else if (winPtr->flags & TK_TOP_LEVEL) { /* * This is a top level window, * maintain the clipping regions. */ /* * For each (displayed) sibling below this window * (and the root) add the overlap between the window and the * sibling to the siblings clipping region. */ for (sibling = winPtr->priorPtr; sibling != HEAD_CHILD(parentPtr); sibling = sibling->priorPtr) { if (CtkIsDisplayed(sibling)) { UnoverlapHierarchy(sibling, winPtr); } } Unoverlap(parentPtr, winPtr); CtkDestroyRegion(winPtr->clipRgn); } else if (winPtr->fillStyle != CTK_INVISIBLE_STYLE) { Ctk_FillRect(parentPtr, winPtr->rect.left, winPtr->rect.top, winPtr->rect.right, winPtr->rect.bottom, parentPtr->fillStyle, parentPtr->fillChar); event.type = CTK_EXPOSE_EVENT; event.window = parentPtr; CtkCopyRect(&event.u.expose, &parentPtr->rect); Tk_HandleEvent(&event); } else if (winPtr->borderWidth) { int borderWidth = winPtr->borderWidth; /* * Blank out the border area. * This would be much easier if we could pass a character to * Ctk_DrawRect(). */ Ctk_FillRect(parentPtr, winPtr->rect.left, winPtr->rect.top, winPtr->rect.right, winPtr->rect.top+borderWidth, parentPtr->fillStyle, parentPtr->fillChar); Ctk_FillRect(parentPtr, winPtr->rect.left, winPtr->rect.bottom-borderWidth, winPtr->rect.right, winPtr->rect.bottom, parentPtr->fillStyle, parentPtr->fillChar); Ctk_FillRect(parentPtr, winPtr->rect.left, winPtr->rect.top+borderWidth, winPtr->rect.left+borderWidth, winPtr->rect.bottom-borderWidth, parentPtr->fillStyle, parentPtr->fillChar); Ctk_FillRect(parentPtr, winPtr->rect.right-borderWidth, winPtr->rect.top+borderWidth, winPtr->rect.right, winPtr->rect.bottom-borderWidth, parentPtr->fillStyle, parentPtr->fillChar); } winPtr->clipRgn = NULL; } /* *------------------------------------------------------------ * UnoverlapHierarchy -- * * Restore overlapping region to underlying window tree. * * Results: * None. * * Side Effects: * *------------------------------------------------------------ */ static void UnoverlapHierarchy(underWinPtr, overWinPtr) TkWindow *underWinPtr; TkWindow *overWinPtr; { TkWindow *child; for (child = TOP_CHILD(underWinPtr); child != HEAD_CHILD(underWinPtr); child = child->priorPtr) { if (CtkIsDisplayed(child)) { UnoverlapHierarchy(child, overWinPtr); } } Unoverlap(underWinPtr, overWinPtr); } /* *------------------------------------------------------------ * Unoverlap -- * * Restore overlapping region to underlying window. * * Results: * None. * * Side Effects: * *------------------------------------------------------------ */ static void Unoverlap(underWinPtr, overWinPtr) TkWindow *underWinPtr; TkWindow *overWinPtr; { CtkRegion *overlap; if (underWinPtr->fillStyle != CTK_INVISIBLE_STYLE) { overlap = CtkRegionMinusRect( overWinPtr->clipRgn, &(underWinPtr->maskRect), 1); CtkUnionRegions(underWinPtr->clipRgn, overlap); CtkFillRegion(underWinPtr->dispPtr, overlap, underWinPtr->fillStyle, underWinPtr->fillChar); ExposeWindow(underWinPtr, overlap); CtkDestroyRegion(overlap); } else { if (underWinPtr->borderWidth) { /* * Ok - this is a hack: * Invisible windows can have (visible) borders, so * must send an expose event to the window. Ideally, * I would remove the border area from the overlying * clip region, but that would take a lot of work. * Since I know that the window will not redraw until * idle time, I can send expose now, and let the parent * clear the border area. Later, at idle, the invisible * window will draw the border. */ ExposeWindow(underWinPtr, overWinPtr->clipRgn); } } } /* *------------------------------------------------------------ * InsertWindow -- * * Insert window into list in front of `sibling'. * * Results: * None. * * Side Effects: * *------------------------------------------------------------ */ static void InsertWindow(winPtr, sibling) TkWindow *winPtr; TkWindow *sibling; { winPtr->nextPtr = sibling; winPtr->priorPtr = sibling->priorPtr; sibling->priorPtr->nextPtr = winPtr; sibling->priorPtr = winPtr; } /* *------------------------------------------------------------ * UnlinkWindow -- * * Detachs the window from its parent's list of children. * * Results: * None. * * Side Effects: * *------------------------------------------------------------ */ static void UnlinkWindow(winPtr) TkWindow *winPtr; { winPtr->nextPtr->priorPtr = winPtr->priorPtr; winPtr->priorPtr->nextPtr = winPtr->nextPtr; }