diff options
Diffstat (limited to 'generic/tkMenuDraw.c')
-rw-r--r-- | generic/tkMenuDraw.c | 1018 |
1 files changed, 1018 insertions, 0 deletions
diff --git a/generic/tkMenuDraw.c b/generic/tkMenuDraw.c new file mode 100644 index 0000000..be218a0 --- /dev/null +++ b/generic/tkMenuDraw.c @@ -0,0 +1,1018 @@ +/* + * tkMenuDraw.c -- + * + * This module implements the platform-independent drawing and + * geometry calculations of menu widgets. + * + * Copyright (c) 1996-1997 by Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * SCCS: @(#) tkMenuDraw.c 1.46 97/10/28 14:26:00 + */ + +#include "tkMenu.h" + +/* + * Forward declarations for procedures defined later in this file: + */ + +static void AdjustMenuCoords _ANSI_ARGS_ ((TkMenu *menuPtr, + TkMenuEntry *mePtr, int *xPtr, int *yPtr, + char *string)); +static void ComputeMenuGeometry _ANSI_ARGS_(( + ClientData clientData)); +static void DisplayMenu _ANSI_ARGS_((ClientData clientData)); + +/* + *---------------------------------------------------------------------- + * + * TkMenuInitializeDrawingFields -- + * + * Fills in drawing fields of a new menu. Called when new menu is + * created by Tk_MenuCmd. + * + * Results: + * None. + * + * Side effects: + * menuPtr fields are initialized. + * + *---------------------------------------------------------------------- + */ + +void +TkMenuInitializeDrawingFields(menuPtr) + TkMenu *menuPtr; /* The menu we are initializing. */ +{ + menuPtr->textGC = None; + menuPtr->gray = None; + menuPtr->disabledGC = None; + menuPtr->activeGC = None; + menuPtr->indicatorGC = None; + menuPtr->disabledImageGC = None; + menuPtr->totalWidth = menuPtr->totalHeight = 0; +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuInitializeEntryDrawingFields -- + * + * Fills in drawing fields of a new menu entry. Called when an + * entry is created. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkMenuInitializeEntryDrawingFields(mePtr) + TkMenuEntry *mePtr; /* The menu we are initializing. */ +{ + mePtr->width = 0; + mePtr->height = 0; + mePtr->x = 0; + mePtr->y = 0; + mePtr->indicatorSpace = 0; + mePtr->labelWidth = 0; + mePtr->textGC = None; + mePtr->activeGC = None; + mePtr->disabledGC = None; + mePtr->indicatorGC = None; +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuFreeDrawOptions -- + * + * Frees up any structures allocated for the drawing of a menu. + * Called when menu is deleted. + * + * Results: + * None. + * + * Side effects: + * Storage is released. + * + *---------------------------------------------------------------------- + */ + +void +TkMenuFreeDrawOptions(menuPtr) + TkMenu *menuPtr; +{ + if (menuPtr->textGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->textGC); + } + if (menuPtr->disabledImageGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->disabledImageGC); + } + if (menuPtr->gray != None) { + Tk_FreeBitmap(menuPtr->display, menuPtr->gray); + } + if (menuPtr->disabledGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->disabledGC); + } + if (menuPtr->activeGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->activeGC); + } + if (menuPtr->indicatorGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuEntryFreeDrawOptions -- + * + * Frees up drawing structures for a menu entry. Called when + * menu entry is freed. + * + * RESULTS: + * None. + * + * Side effects: + * Storage is freed. + * + *---------------------------------------------------------------------- + */ + +void +TkMenuEntryFreeDrawOptions(mePtr) + TkMenuEntry *mePtr; +{ + if (mePtr->textGC != None) { + Tk_FreeGC(mePtr->menuPtr->display, mePtr->textGC); + } + if (mePtr->disabledGC != None) { + Tk_FreeGC(mePtr->menuPtr->display, mePtr->disabledGC); + } + if (mePtr->activeGC != None) { + Tk_FreeGC(mePtr->menuPtr->display, mePtr->activeGC); + } + if (mePtr->indicatorGC != None) { + Tk_FreeGC(mePtr->menuPtr->display, mePtr->indicatorGC); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuConfigureDrawOptions -- + * + * Sets the menu's drawing attributes in preparation for drawing + * the menu. + * + * RESULTS: + * None. + * + * Side effects: + * Storage is allocated. + * + *---------------------------------------------------------------------- + */ + +void +TkMenuConfigureDrawOptions(menuPtr) + TkMenu *menuPtr; /* The menu we are configuring. */ +{ + XGCValues gcValues; + GC newGC; + unsigned long mask; + + /* + * A few options need special processing, such as setting the + * background from a 3-D border, or filling in complicated + * defaults that couldn't be specified to Tk_ConfigureWidget. + */ + + Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border); + + gcValues.font = Tk_FontId(menuPtr->tkfont); + gcValues.foreground = menuPtr->fg->pixel; + gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel; + newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont, + &gcValues); + if (menuPtr->textGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->textGC); + } + menuPtr->textGC = newGC; + + gcValues.font = Tk_FontId(menuPtr->tkfont); + gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel; + if (menuPtr->disabledFg != NULL) { + gcValues.foreground = menuPtr->disabledFg->pixel; + mask = GCForeground|GCBackground|GCFont; + } else { + gcValues.foreground = gcValues.background; + mask = GCForeground; + if (menuPtr->gray == None) { + menuPtr->gray = Tk_GetBitmap(menuPtr->interp, menuPtr->tkwin, + Tk_GetUid("gray50")); + } + if (menuPtr->gray != None) { + gcValues.fill_style = FillStippled; + gcValues.stipple = menuPtr->gray; + mask = GCForeground|GCFillStyle|GCStipple; + } + } + newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues); + if (menuPtr->disabledGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->disabledGC); + } + menuPtr->disabledGC = newGC; + + gcValues.foreground = Tk_3DBorderColor(menuPtr->border)->pixel; + if (menuPtr->gray == None) { + menuPtr->gray = Tk_GetBitmap(menuPtr->interp, menuPtr->tkwin, + Tk_GetUid("gray50")); + } + if (menuPtr->gray != None) { + gcValues.fill_style = FillStippled; + gcValues.stipple = menuPtr->gray; + newGC = Tk_GetGC(menuPtr->tkwin, + GCForeground|GCFillStyle|GCStipple, &gcValues); + } + if (menuPtr->disabledImageGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->disabledImageGC); + } + menuPtr->disabledImageGC = newGC; + + gcValues.font = Tk_FontId(menuPtr->tkfont); + gcValues.foreground = menuPtr->activeFg->pixel; + gcValues.background = + Tk_3DBorderColor(menuPtr->activeBorder)->pixel; + newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont, + &gcValues); + if (menuPtr->activeGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->activeGC); + } + menuPtr->activeGC = newGC; + + gcValues.foreground = menuPtr->indicatorFg->pixel; + gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel; + newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont, + &gcValues); + if (menuPtr->indicatorGC != None) { + Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC); + } + menuPtr->indicatorGC = newGC; +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuConfigureEntryDrawOptions -- + * + * Calculates any entry-specific draw options for the given menu + * entry. + * + * Results: + * Returns a standard Tcl error. + * + * Side effects: + * Storage may be allocated. + * + *---------------------------------------------------------------------- + */ + +int +TkMenuConfigureEntryDrawOptions(mePtr, index) + TkMenuEntry *mePtr; + int index; +{ + + XGCValues gcValues; + GC newGC, newActiveGC, newDisabledGC, newIndicatorGC; + unsigned long mask; + Tk_Font tkfont; + TkMenu *menuPtr = mePtr->menuPtr; + + tkfont = (mePtr->tkfont == NULL) ? menuPtr->tkfont : mePtr->tkfont; + + if (mePtr->state == tkActiveUid) { + if (index != menuPtr->active) { + TkActivateMenuEntry(menuPtr, index); + } + } else { + if (index == menuPtr->active) { + TkActivateMenuEntry(menuPtr, -1); + } + if ((mePtr->state != tkNormalUid) + && (mePtr->state != tkDisabledUid)) { + Tcl_AppendResult(menuPtr->interp, "bad state value \"", + mePtr->state, + "\": must be normal, active, or disabled", (char *) NULL); + mePtr->state = tkNormalUid; + return TCL_ERROR; + } + } + + if ((mePtr->tkfont != NULL) + || (mePtr->border != NULL) + || (mePtr->fg != NULL) + || (mePtr->activeBorder != NULL) + || (mePtr->activeFg != NULL) + || (mePtr->indicatorFg != NULL)) { + gcValues.foreground = (mePtr->fg != NULL) + ? mePtr->fg->pixel + : menuPtr->fg->pixel; + gcValues.background = Tk_3DBorderColor( + (mePtr->border != NULL) + ? mePtr->border + : menuPtr->border) + ->pixel; + + gcValues.font = Tk_FontId(tkfont); + + /* + * Note: disable GraphicsExpose events; we know there won't be + * obscured areas when copying from an off-screen pixmap to the + * screen and this gets rid of unnecessary events. + */ + + gcValues.graphics_exposures = False; + newGC = Tk_GetGC(menuPtr->tkwin, + GCForeground|GCBackground|GCFont|GCGraphicsExposures, + &gcValues); + + if (mePtr->indicatorFg != NULL) { + gcValues.foreground = mePtr->indicatorFg->pixel; + } else if (menuPtr->indicatorFg != NULL) { + gcValues.foreground = menuPtr->indicatorFg->pixel; + } + newIndicatorGC = Tk_GetGC(menuPtr->tkwin, + GCForeground|GCBackground|GCGraphicsExposures, + &gcValues); + + if ((menuPtr->disabledFg != NULL) || (mePtr->image != NULL)) { + gcValues.foreground = menuPtr->disabledFg->pixel; + mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures; + } else { + gcValues.foreground = gcValues.background; + gcValues.fill_style = FillStippled; + gcValues.stipple = menuPtr->gray; + mask = GCForeground|GCFillStyle|GCStipple; + } + newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues); + + gcValues.foreground = (mePtr->activeFg != NULL) + ? mePtr->activeFg->pixel + : menuPtr->activeFg->pixel; + gcValues.background = Tk_3DBorderColor( + (mePtr->activeBorder != NULL) + ? mePtr->activeBorder + : menuPtr->activeBorder)->pixel; + newActiveGC = Tk_GetGC(menuPtr->tkwin, + GCForeground|GCBackground|GCFont|GCGraphicsExposures, + &gcValues); + } else { + newGC = None; + newActiveGC = None; + newDisabledGC = None; + newIndicatorGC = None; + } + if (mePtr->textGC != None) { + Tk_FreeGC(menuPtr->display, mePtr->textGC); + } + mePtr->textGC = newGC; + if (mePtr->activeGC != None) { + Tk_FreeGC(menuPtr->display, mePtr->activeGC); + } + mePtr->activeGC = newActiveGC; + if (mePtr->disabledGC != None) { + Tk_FreeGC(menuPtr->display, mePtr->disabledGC); + } + mePtr->disabledGC = newDisabledGC; + if (mePtr->indicatorGC != None) { + Tk_FreeGC(menuPtr->display, mePtr->indicatorGC); + } + mePtr->indicatorGC = newIndicatorGC; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TkEventuallyRecomputeMenu -- + * + * Tells Tcl to redo the geometry because this menu has changed. + * + * Results: + * None. + * + * Side effects: + * Menu geometry is recomputed at idle time, and the menu will be + * redisplayed. + * + *---------------------------------------------------------------------- + */ + +void +TkEventuallyRecomputeMenu(menuPtr) + TkMenu *menuPtr; +{ + if (!(menuPtr->menuFlags & RESIZE_PENDING)) { + menuPtr->menuFlags |= RESIZE_PENDING; + Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkRecomputeMenu -- + * + * Tells Tcl to redo the geometry because this menu has changed. + * Does it now; removes any ComputeMenuGeometries from the idler. + * + * Results: + * None. + * + * Side effects: + * Menu geometry is immediately reconfigured. + * + *---------------------------------------------------------------------- + */ + +void +TkRecomputeMenu(menuPtr) + TkMenu *menuPtr; +{ + if (menuPtr->menuFlags & RESIZE_PENDING) { + Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr); + ComputeMenuGeometry((ClientData) menuPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkEventuallyRedrawMenu -- + * + * Arrange for an entry of a menu, or the whole menu, to be + * redisplayed at some point in the future. + * + * Results: + * None. + * + * Side effects: + * A when-idle hander is scheduled to do the redisplay, if there + * isn't one already scheduled. + * + *---------------------------------------------------------------------- + */ + +void +TkEventuallyRedrawMenu(menuPtr, mePtr) + register TkMenu *menuPtr; /* Information about menu to redraw. */ + register TkMenuEntry *mePtr; /* Entry to redraw. NULL means redraw + * all the entries in the menu. */ +{ + int i; + + if (menuPtr->tkwin == NULL) { + return; + } + if (mePtr != NULL) { + mePtr->entryFlags |= ENTRY_NEEDS_REDISPLAY; + } else { + for (i = 0; i < menuPtr->numEntries; i++) { + menuPtr->entries[i]->entryFlags |= ENTRY_NEEDS_REDISPLAY; + } + } + if (!Tk_IsMapped(menuPtr->tkwin) + || (menuPtr->menuFlags & REDRAW_PENDING)) { + return; + } + Tcl_DoWhenIdle(DisplayMenu, (ClientData) menuPtr); + menuPtr->menuFlags |= REDRAW_PENDING; +} + +/* + *-------------------------------------------------------------- + * + * ComputeMenuGeometry -- + * + * This procedure is invoked to recompute the size and + * layout of a menu. It is called as a when-idle handler so + * that it only gets done once, even if a group of changes is + * made to the menu. + * + * Results: + * None. + * + * Side effects: + * Fields of menu entries are changed to reflect their + * current positions, and the size of the menu window + * itself may be changed. + * + *-------------------------------------------------------------- + */ + +static void +ComputeMenuGeometry(clientData) + ClientData clientData; /* Structure describing menu. */ +{ + TkMenu *menuPtr = (TkMenu *) clientData; + + if (menuPtr->tkwin == NULL) { + return; + } + + if (menuPtr->menuType == MENUBAR) { + TkpComputeMenubarGeometry(menuPtr); + } else { + TkpComputeStandardMenuGeometry(menuPtr); + } + + if ((menuPtr->totalWidth != Tk_ReqWidth(menuPtr->tkwin)) || + (menuPtr->totalHeight != Tk_ReqHeight(menuPtr->tkwin))) { + Tk_GeometryRequest(menuPtr->tkwin, menuPtr->totalWidth, + menuPtr->totalHeight); + } + + /* + * Must always force a redisplay here if the window is mapped + * (even if the size didn't change, something else might have + * changed in the menu, such as a label or accelerator). The + * resize will force a redisplay above. + */ + + TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL); + + menuPtr->menuFlags &= ~RESIZE_PENDING; +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuSelectImageProc -- + * + * This procedure is invoked by the image code whenever the manager + * for an image does something that affects the size of contents + * of an image displayed in a menu entry when it is selected. + * + * Results: + * None. + * + * Side effects: + * Arranges for the menu to get redisplayed. + * + *---------------------------------------------------------------------- + */ + +void +TkMenuSelectImageProc(clientData, x, y, width, height, imgWidth, + imgHeight) + ClientData clientData; /* Pointer to widget record. */ + int x, y; /* Upper left pixel (within image) + * that must be redisplayed. */ + int width, height; /* Dimensions of area to redisplay + * (may be <= 0). */ + int imgWidth, imgHeight; /* New dimensions of image. */ +{ + register TkMenuEntry *mePtr = (TkMenuEntry *) clientData; + + if ((mePtr->entryFlags & ENTRY_SELECTED) + && !(mePtr->menuPtr->menuFlags & + REDRAW_PENDING)) { + mePtr->menuPtr->menuFlags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayMenu, (ClientData) mePtr->menuPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * DisplayMenu -- + * + * This procedure is invoked to display a menu widget. + * + * Results: + * None. + * + * Side effects: + * Commands are output to X to display the menu in its + * current mode. + * + *---------------------------------------------------------------------- + */ + +static void +DisplayMenu(clientData) + ClientData clientData; /* Information about widget. */ +{ + register TkMenu *menuPtr = (TkMenu *) clientData; + register TkMenuEntry *mePtr; + register Tk_Window tkwin = menuPtr->tkwin; + int index, strictMotif; + Tk_Font tkfont = menuPtr->tkfont; + Tk_FontMetrics menuMetrics; + int width; + + menuPtr->menuFlags &= ~REDRAW_PENDING; + if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { + return; + } + + if (menuPtr->menuType == MENUBAR) { + Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), menuPtr->border, + menuPtr->borderWidth, menuPtr->borderWidth, + Tk_Width(tkwin) - 2 * menuPtr->borderWidth, + Tk_Height(tkwin) - 2 * menuPtr->borderWidth, 0, + TK_RELIEF_FLAT); + } + + strictMotif = Tk_StrictMotif(menuPtr->tkwin); + + /* + * See note in ComputeMenuGeometry. We don't want to be doing font metrics + * all of the time. + */ + + Tk_GetFontMetrics(menuPtr->tkfont, &menuMetrics); + + /* + * Loop through all of the entries, drawing them one at a time. + */ + + for (index = 0; index < menuPtr->numEntries; index++) { + mePtr = menuPtr->entries[index]; + if (menuPtr->menuType != MENUBAR) { + if (!(mePtr->entryFlags & ENTRY_NEEDS_REDISPLAY)) { + continue; + } + } + mePtr->entryFlags &= ~ENTRY_NEEDS_REDISPLAY; + + if (menuPtr->menuType == MENUBAR) { + width = mePtr->width; + } else { + if (mePtr->entryFlags & ENTRY_LAST_COLUMN) { + width = Tk_Width(menuPtr->tkwin) - mePtr->x + - menuPtr->activeBorderWidth; + } else { + width = mePtr->width + menuPtr->borderWidth; + } + } + TkpDrawMenuEntry(mePtr, Tk_WindowId(menuPtr->tkwin), tkfont, + &menuMetrics, mePtr->x, mePtr->y, width, + mePtr->height, strictMotif, 1); + if ((index > 0) && (menuPtr->menuType != MENUBAR) + && mePtr->columnBreak) { + mePtr = menuPtr->entries[index - 1]; + Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), menuPtr->border, + mePtr->x, mePtr->y + mePtr->height, + mePtr->width, + Tk_Height(tkwin) - mePtr->y - mePtr->height + - menuPtr->activeBorderWidth, 0, + TK_RELIEF_FLAT); + } + } + + if (menuPtr->menuType != MENUBAR) { + int x, y, height; + + if (menuPtr->numEntries == 0) { + x = y = menuPtr->borderWidth; + width = Tk_Width(tkwin) - 2 * menuPtr->activeBorderWidth; + height = Tk_Height(tkwin) - 2 * menuPtr->activeBorderWidth; + } else { + mePtr = menuPtr->entries[menuPtr->numEntries - 1]; + Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), + menuPtr->border, mePtr->x, mePtr->y + mePtr->height, + mePtr->width, Tk_Height(tkwin) - mePtr->y - mePtr->height + - menuPtr->activeBorderWidth, 0, + TK_RELIEF_FLAT); + x = mePtr->x + mePtr->width; + y = mePtr->y + mePtr->height; + width = Tk_Width(tkwin) - x - menuPtr->activeBorderWidth; + height = Tk_Height(tkwin) - y - menuPtr->activeBorderWidth; + } + Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), menuPtr->border, x, y, + width, height, 0, TK_RELIEF_FLAT); + } + + Tk_Draw3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin), + menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), + menuPtr->borderWidth, menuPtr->relief); +} + +/* + *-------------------------------------------------------------- + * + * TkMenuEventProc -- + * + * This procedure is invoked by the Tk dispatcher for various + * events on menus. + * + * Results: + * None. + * + * Side effects: + * When the window gets deleted, internal structures get + * cleaned up. When it gets exposed, it is redisplayed. + * + *-------------------------------------------------------------- + */ + +void +TkMenuEventProc(clientData, eventPtr) + ClientData clientData; /* Information about window. */ + XEvent *eventPtr; /* Information about event. */ +{ + TkMenu *menuPtr = (TkMenu *) clientData; + + if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) { + TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL); + } else if (eventPtr->type == ConfigureNotify) { + TkEventuallyRecomputeMenu(menuPtr); + TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL); + } else if (eventPtr->type == ActivateNotify) { + if (menuPtr->menuType == TEAROFF_MENU) { + TkpSetMainMenubar(menuPtr->interp, menuPtr->tkwin, NULL); + } + } else if (eventPtr->type == DestroyNotify) { + if (menuPtr->tkwin != NULL) { + menuPtr->tkwin = NULL; + Tcl_DeleteCommandFromToken(menuPtr->interp, menuPtr->widgetCmd); + } + if (menuPtr->menuFlags & REDRAW_PENDING) { + Tcl_CancelIdleCall(DisplayMenu, (ClientData) menuPtr); + } + if (menuPtr->menuFlags & RESIZE_PENDING) { + Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr); + } + TkDestroyMenu(menuPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkMenuImageProc -- + * + * This procedure is invoked by the image code whenever the manager + * for an image does something that affects the size of contents + * of an image displayed in a menu entry. + * + * Results: + * None. + * + * Side effects: + * Arranges for the menu to get redisplayed. + * + *---------------------------------------------------------------------- + */ + +void +TkMenuImageProc(clientData, x, y, width, height, imgWidth, + imgHeight) + ClientData clientData; /* Pointer to widget record. */ + int x, y; /* Upper left pixel (within image) + * that must be redisplayed. */ + int width, height; /* Dimensions of area to redisplay + * (may be <= 0). */ + int imgWidth, imgHeight; /* New dimensions of image. */ +{ + register TkMenu *menuPtr = ((TkMenuEntry *)clientData)->menuPtr; + + if ((menuPtr->tkwin != NULL) && !(menuPtr->menuFlags + & RESIZE_PENDING)) { + menuPtr->menuFlags |= RESIZE_PENDING; + Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkPostTearoffMenu -- + * + * Posts a menu on the screen. Used to post tearoff menus. On Unix, + * all menus are posted this way. Adjusts the menu's position + * so that it fits on the screen, and maps and raises the menu. + * + * Results: + * Returns a standard Tcl Error. + * + * Side effects: + * The menu is posted. + * + *---------------------------------------------------------------------- + */ + +int +TkPostTearoffMenu(interp, menuPtr, x, y) + Tcl_Interp *interp; /* The interpreter of the menu */ + TkMenu *menuPtr; /* The menu we are posting */ + int x; /* The root X coordinate where we + * are posting */ + int y; /* The root Y coordinate where we + * are posting */ +{ + int vRootX, vRootY, vRootWidth, vRootHeight; + int tmp, result; + + TkActivateMenuEntry(menuPtr, -1); + TkRecomputeMenu(menuPtr); + result = TkPostCommand(menuPtr); + if (result != TCL_OK) { + return result; + } + + /* + * The post commands could have deleted the menu, which means + * we are dead and should go away. + */ + + if (menuPtr->tkwin == NULL) { + return TCL_OK; + } + + /* + * Adjust the position of the menu if necessary to keep it + * visible on the screen. There are two special tricks to + * make this work right: + * + * 1. If a virtual root window manager is being used then + * the coordinates are in the virtual root window of + * menuPtr's parent; since the menu uses override-redirect + * mode it will be in the *real* root window for the screen, + * so we have to map the coordinates from the virtual root + * (if any) to the real root. Can't get the virtual root + * from the menu itself (it will never be seen by the wm) + * so use its parent instead (it would be better to have an + * an option that names a window to use for this...). + * 2. The menu may not have been mapped yet, so its current size + * might be the default 1x1. To compute how much space it + * needs, use its requested size, not its actual size. + * + * Note that this code assumes square screen regions and all + * positive coordinates. This does not work on a Mac with + * multiple monitors. But then again, Tk has other problems + * with this. + */ + + Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY, + &vRootWidth, &vRootHeight); + x += vRootX; + y += vRootY; + tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin)) + - Tk_ReqWidth(menuPtr->tkwin); + if (x > tmp) { + x = tmp; + } + if (x < 0) { + x = 0; + } + tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin)) + - Tk_ReqHeight(menuPtr->tkwin); + if (y > tmp) { + y = tmp; + } + if (y < 0) { + y = 0; + } + Tk_MoveToplevelWindow(menuPtr->tkwin, x, y); + if (!Tk_IsMapped(menuPtr->tkwin)) { + Tk_MapWindow(menuPtr->tkwin); + } + TkWmRestackToplevel((TkWindow *) menuPtr->tkwin, Above, NULL); + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * TkPostSubmenu -- + * + * This procedure arranges for a particular submenu (i.e. the + * menu corresponding to a given cascade entry) to be + * posted. + * + * Results: + * A standard Tcl return result. Errors may occur in the + * Tcl commands generated to post and unpost submenus. + * + * Side effects: + * If there is already a submenu posted, it is unposted. + * The new submenu is then posted. + * + *-------------------------------------------------------------- + */ + +int +TkPostSubmenu(interp, menuPtr, mePtr) + Tcl_Interp *interp; /* Used for invoking sub-commands and + * reporting errors. */ + register TkMenu *menuPtr; /* Information about menu as a whole. */ + register TkMenuEntry *mePtr; /* Info about submenu that is to be + * posted. NULL means make sure that + * no submenu is posted. */ +{ + char string[30]; + int result, x, y; + + if (mePtr == menuPtr->postedCascade) { + return TCL_OK; + } + + if (menuPtr->postedCascade != NULL) { + + /* + * Note: when unposting a submenu, we have to redraw the entire + * parent menu. This is because of a combination of the following + * things: + * (a) the submenu partially overlaps the parent. + * (b) the submenu specifies "save under", which causes the X + * server to make a copy of the information under it when it + * is posted. When the submenu is unposted, the X server + * copies this data back and doesn't generate any Expose + * events for the parent. + * (c) the parent may have redisplayed itself after the submenu + * was posted, in which case the saved information is no + * longer correct. + * The simplest solution is just force a complete redisplay of + * the parent. + */ + + TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL); + result = Tcl_VarEval(interp, menuPtr->postedCascade->name, + " unpost", (char *) NULL); + menuPtr->postedCascade = NULL; + if (result != TCL_OK) { + return result; + } + } + + if ((mePtr != NULL) && (mePtr->name != NULL) + && Tk_IsMapped(menuPtr->tkwin)) { + + /* + * Position the cascade with its upper left corner slightly + * below and to the left of the upper right corner of the + * menu entry (this is an attempt to match Motif behavior). + * + * The menu has to redrawn so that the entry can change relief. + */ + + Tk_GetRootCoords(menuPtr->tkwin, &x, &y); + AdjustMenuCoords(menuPtr, mePtr, &x, &y, string); + result = Tcl_VarEval(interp, mePtr->name, " post ", string, + (char *) NULL); + if (result != TCL_OK) { + return result; + } + menuPtr->postedCascade = mePtr; + TkEventuallyRedrawMenu(menuPtr, mePtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * AdjustMenuCoords -- + * + * Adjusts the given coordinates down and the left to give a Motif + * look. + * + * Results: + * None. + * + * Side effects: + * The menu is eventually redrawn if necessary. + * + *---------------------------------------------------------------------- + */ + +static void +AdjustMenuCoords(menuPtr, mePtr, xPtr, yPtr, string) + TkMenu *menuPtr; + TkMenuEntry *mePtr; + int *xPtr; + int *yPtr; + char *string; +{ + if (menuPtr->menuType == MENUBAR) { + *xPtr += mePtr->x; + *yPtr += mePtr->y + mePtr->height; + } else { + *xPtr += Tk_Width(menuPtr->tkwin) - menuPtr->borderWidth + - menuPtr->activeBorderWidth - 2; + *yPtr += mePtr->y + + menuPtr->activeBorderWidth + 2; + } + sprintf(string, "%d %d", *xPtr, *yPtr); +} |