text: /*
   * 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.
   *
   * RCS: @(#) $Id$
   */
  
  #include "tkInt.h"
  #include "tkMenu.h"
  
  /*
   * Forward declarations for functions defined later in this file:
   */
  
  static void		AdjustMenuCoords(TkMenu *menuPtr, TkMenuEntry *mePtr,
  			    int *xPtr, int *yPtr);
  static void		ComputeMenuGeometry(ClientData clientData);
  static void		DisplayMenu(ClientData clientData);
  
  /*
   *----------------------------------------------------------------------
   *
   * TkMenuInitializeDrawingFields --
   *
   *	Fills in drawing fields of a new menu. Called when new menu is created
   *	by MenuCmd.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	menuPtr fields are initialized.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TkMenuInitializeDrawingFields(
      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(
      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(
      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(
      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(
      TkMenu *menuPtr)		/* The menu we are configuring. */
  {
      XGCValues gcValues;
      GC newGC;
      unsigned long mask;
      Tk_3DBorder border, activeBorder;
      Tk_Font tkfont;
      XColor *fg, *activeFg, *indicatorFg;
  
      /*
       * 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.
       */
  
      border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr);
      Tk_SetBackgroundFromBorder(menuPtr->tkwin, border);
  
      tkfont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
      gcValues.font = Tk_FontId(tkfont);
      fg = Tk_GetColorFromObj(menuPtr->tkwin, menuPtr->fgPtr);
      gcValues.foreground = fg->pixel;
      gcValues.background = Tk_3DBorderColor(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(tkfont);
      gcValues.background = Tk_3DBorderColor(border)->pixel;
      if (menuPtr->disabledFgPtr != NULL) {
  	XColor *disabledFg;
  
  	disabledFg = Tk_GetColorFromObj(menuPtr->tkwin,
  		menuPtr->disabledFgPtr);
  	gcValues.foreground = 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,
  		    "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(border)->pixel;
      if (menuPtr->gray == None) {
  	menuPtr->gray = Tk_GetBitmap(menuPtr->interp, menuPtr->tkwin,
  		"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(tkfont);
      activeFg = Tk_GetColorFromObj(menuPtr->tkwin, menuPtr->activeFgPtr);
      gcValues.foreground = activeFg->pixel;
      activeBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin,
  	    menuPtr->activeBorderPtr);
      gcValues.background = Tk_3DBorderColor(activeBorder)->pixel;
      newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  	    &gcValues);
      if (menuPtr->activeGC != None) {
  	Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
      }
      menuPtr->activeGC = newGC;
  
      indicatorFg = Tk_GetColorFromObj(menuPtr->tkwin,
  	    menuPtr->indicatorFgPtr);
      gcValues.foreground = indicatorFg->pixel;
      gcValues.background = Tk_3DBorderColor(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(
      TkMenuEntry *mePtr,
      int index)
  {
      XGCValues gcValues;
      GC newGC, newActiveGC, newDisabledGC, newIndicatorGC;
      unsigned long mask;
      Tk_Font tkfont;
      TkMenu *menuPtr = mePtr->menuPtr;
  
      tkfont = Tk_GetFontFromObj(menuPtr->tkwin,
  	    (mePtr->fontPtr != NULL) ? mePtr->fontPtr : menuPtr->fontPtr);
  
      if (mePtr->state == ENTRY_ACTIVE) {
  	if (index != menuPtr->active) {
  	    TkActivateMenuEntry(menuPtr, index);
  	}
      } else {
  	if (index == menuPtr->active) {
  	    TkActivateMenuEntry(menuPtr, -1);
  	}
      }
  
      if ((mePtr->fontPtr != NULL)
  	    || (mePtr->borderPtr != NULL)
  	    || (mePtr->fgPtr != NULL)
  	    || (mePtr->activeBorderPtr != NULL)
  	    || (mePtr->activeFgPtr != NULL)
  	    || (mePtr->indicatorFgPtr != NULL)) {
  	XColor *fg, *indicatorFg, *activeFg;
  	Tk_3DBorder border, activeBorder;
  
  	fg = Tk_GetColorFromObj(menuPtr->tkwin, (mePtr->fgPtr != NULL)
  		? mePtr->fgPtr : menuPtr->fgPtr);
  	gcValues.foreground = fg->pixel;
  	border = Tk_Get3DBorderFromObj(menuPtr->tkwin,
  		(mePtr->borderPtr != NULL) ? mePtr->borderPtr
  		: menuPtr->borderPtr);
  	gcValues.background = Tk_3DBorderColor(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);
  
  	indicatorFg = Tk_GetColorFromObj(menuPtr->tkwin,
  		(mePtr->indicatorFgPtr != NULL) ? mePtr->indicatorFgPtr
  		: menuPtr->indicatorFgPtr);
  	gcValues.foreground = indicatorFg->pixel;
  	newIndicatorGC = Tk_GetGC(menuPtr->tkwin,
  		GCForeground|GCBackground|GCGraphicsExposures,
  		&gcValues);
  
  	if ((menuPtr->disabledFgPtr != NULL) || (mePtr->image != NULL)) {
  	    XColor *disabledFg;
  
  	    disabledFg = Tk_GetColorFromObj(menuPtr->tkwin,
  		    menuPtr->disabledFgPtr);
  	    gcValues.foreground = 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);
  
  	activeFg = Tk_GetColorFromObj(menuPtr->tkwin,
  		(mePtr->activeFgPtr != NULL) ? mePtr->activeFgPtr
  		: menuPtr->activeFgPtr);
  	activeBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin,
  		(mePtr->activeBorderPtr != NULL) ? mePtr->activeBorderPtr
  		: menuPtr->activeBorderPtr);
  
  	gcValues.foreground = activeFg->pixel;
  	gcValues.background = Tk_3DBorderColor(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(
      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(
      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(
      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 function 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)	/* 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, NULL);
  
      menuPtr->menuFlags &= ~RESIZE_PENDING;
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * TkMenuSelectImageProc --
   *
   *	This function 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 clientData,	/* Pointer to widget record. */
      int x, int y,		/* Upper left pixel (within image) that must
  				 * be redisplayed. */
      int width, int height,	/* Dimensions of area to redisplay (may be
  				 * <=0). */
      int imgWidth, int 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 function 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)	/* Information about widget. */
  {
      register TkMenu *menuPtr = (TkMenu *) clientData;
      register TkMenuEntry *mePtr;
      register Tk_Window tkwin = menuPtr->tkwin;
      int index, strictMotif;
      Tk_Font tkfont;
      Tk_FontMetrics menuMetrics;
      int width;
      int borderWidth;
      Tk_3DBorder border;
      int activeBorderWidth;
      int relief;
  
  
      menuPtr->menuFlags &= ~REDRAW_PENDING;
      if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
  	return;
      }
  
      Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr,
  	    &borderWidth);
      border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr);
      Tk_GetPixelsFromObj(NULL, menuPtr->tkwin,
  	    menuPtr->activeBorderWidthPtr, &activeBorderWidth);
  
      if (menuPtr->menuType == MENUBAR) {
  	Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), border, borderWidth,
  		borderWidth, Tk_Width(tkwin) - 2 * borderWidth,
  		Tk_Height(tkwin) - 2 * 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.
       */
  
      tkfont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr);
      Tk_GetFontMetrics(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
  			- activeBorderWidth;
  	    } else {
  		width = mePtr->width + 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), border,
  		mePtr->x, mePtr->y + mePtr->height,
  		mePtr->width,
  		Tk_Height(tkwin) - mePtr->y - mePtr->height -
  		activeBorderWidth, 0,
  		TK_RELIEF_FLAT);
  	}
      }
  
      if (menuPtr->menuType != MENUBAR) {
  	int x, y, height;
  
  	if (menuPtr->numEntries == 0) {
  	    x = y = borderWidth;
  	    width = Tk_Width(tkwin) - 2 * activeBorderWidth;
  	    height = Tk_Height(tkwin) - 2 * activeBorderWidth;
  	} else {
  	    mePtr = menuPtr->entries[menuPtr->numEntries - 1];
  	    Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin),
  		border, mePtr->x, mePtr->y + mePtr->height, mePtr->width,
  		Tk_Height(tkwin) - mePtr->y - mePtr->height
  		- activeBorderWidth, 0,
  		TK_RELIEF_FLAT);
  	    x = mePtr->x + mePtr->width;
  	    y = mePtr->y + mePtr->height;
  	    width = Tk_Width(tkwin) - x - activeBorderWidth;
  	    height = Tk_Height(tkwin) - y - activeBorderWidth;
  	}
  	Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), border, x, y,
  		width, height, 0, TK_RELIEF_FLAT);
      }
  
      Tk_GetReliefFromObj(NULL, menuPtr->reliefPtr, &relief);
      Tk_Draw3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
  	    border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), borderWidth,
  	    relief);
  }
  
  /*
   *--------------------------------------------------------------
   *
   * TkMenuEventProc --
   *
   *	This function 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 clientData,	/* Information about window. */
      XEvent *eventPtr)		/* Information about event. */
  {
      TkMenu *menuPtr = (TkMenu *) clientData;
  
      if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
  	TkEventuallyRedrawMenu(menuPtr, NULL);
      } else if (eventPtr->type == ConfigureNotify) {
  	TkEventuallyRecomputeMenu(menuPtr);
  	TkEventuallyRedrawMenu(menuPtr, 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) {
  	    if (!(menuPtr->menuFlags & MENU_DELETION_PENDING)) {
  		TkDestroyMenu(menuPtr);
  	    }
  	    menuPtr->tkwin = NULL;
  	}
  	if (menuPtr->menuFlags & MENU_WIN_DESTRUCTION_PENDING) {
  	    return;
  	}
  	menuPtr->menuFlags |= MENU_WIN_DESTRUCTION_PENDING;
  	if (menuPtr->widgetCmd != NULL) {
  	    Tcl_DeleteCommandFromToken(menuPtr->interp, menuPtr->widgetCmd);
  	    menuPtr->widgetCmd = NULL;
  	}
  	if (menuPtr->menuFlags & REDRAW_PENDING) {
  	    Tcl_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
  	    menuPtr->menuFlags &= ~REDRAW_PENDING;
  	}
  	if (menuPtr->menuFlags & RESIZE_PENDING) {
  	    Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
  	    menuPtr->menuFlags &= ~RESIZE_PENDING;
  	}
  	Tcl_EventuallyFree((ClientData) menuPtr, TCL_DYNAMIC);
      }
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * TkMenuImageProc --
   *
   *	This function 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 clientData,	/* Pointer to widget record. */
      int x, int y,		/* Upper left pixel (within image) that must
  				 * be redisplayed. */
      int width, int height,	/* Dimensions of area to redisplay (may be
  				 * <=0). */
      int imgWidth, int 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(
      Tcl_Interp *interp,		/* The interpreter of the menu */
      TkMenu *menuPtr,		/* The menu we are posting */
      int x, int y)		/* The root X,Y coordinates 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 function 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(
      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. */
  {
      int result, x, y;
      Tcl_Obj *subary[4];
  
      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.
  	 */
  
  	subary[0] = menuPtr->postedCascade->namePtr;
  	subary[1] = Tcl_NewStringObj("unpost", -1);
  	Tcl_IncrRefCount(subary[1]);
  	TkEventuallyRedrawMenu(menuPtr, NULL);
  	result = Tcl_EvalObjv(interp, 2, subary, 0);
  	Tcl_DecrRefCount(subary[1]);
  	menuPtr->postedCascade = NULL;
  	if (result != TCL_OK) {
  	    return result;
  	}
      }
  
      if ((mePtr != NULL) && (mePtr->namePtr != 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.
  	 *
  	 * Set postedCascade early to ensure tear-off submenus work on
  	 * Windows. [Bug 873613]
  	 */
  
  	Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
  	AdjustMenuCoords(menuPtr, mePtr, &x, &y);
  
  	menuPtr->postedCascade = mePtr;
  	subary[0] = mePtr->namePtr;
  	subary[1] = Tcl_NewStringObj("post", -1);
  	subary[2] = Tcl_NewIntObj(x);
  	subary[3] = Tcl_NewIntObj(y);
  	Tcl_IncrRefCount(subary[1]);
  	Tcl_IncrRefCount(subary[2]);
  	Tcl_IncrRefCount(subary[3]);
  	result = Tcl_EvalObjv(interp, 4, subary, 0);
  	Tcl_DecrRefCount(subary[1]);
  	Tcl_DecrRefCount(subary[2]);
  	Tcl_DecrRefCount(subary[3]);
  	if (result != TCL_OK) {
  	    menuPtr->postedCascade = NULL;
  	    return result;
  	}
  	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(
      TkMenu *menuPtr,
      TkMenuEntry *mePtr,
      int *xPtr,
      int *yPtr)
  {
      if (menuPtr->menuType == MENUBAR) {
  	*xPtr += mePtr->x;
  	*yPtr += mePtr->y + mePtr->height;
      } else {
  	int borderWidth, activeBorderWidth;
  
  	Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr,
  		&borderWidth);
  	Tk_GetPixelsFromObj(NULL, menuPtr->tkwin,
  		menuPtr->activeBorderWidthPtr, &activeBorderWidth);
  	*xPtr += Tk_Width(menuPtr->tkwin) - borderWidth	- activeBorderWidth
  		- 2;
  	*yPtr += mePtr->y + activeBorderWidth + 2;
      }
  }
  
  /*
   * Local Variables:
   * mode: c
   * c-basic-offset: 4
   * fill-column: 78
   * End:
   */
  