home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / lang / tcl / tkstep0.3b3 / tkstep0 / tkstep / tkMenu.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-07-08  |  84.0 KB  |  2,736 lines

  1. /* 
  2.  * tkMenu.c --
  3.  *
  4.  *    This module implements menus for the Tk toolkit.  The menus
  5.  *    support normal button entries, plus check buttons, radio
  6.  *    buttons, iconic forms of all of the above, and separator
  7.  *    entries.
  8.  *
  9.  * Copyright (c) 1990-1994 The Regents of the University of California.
  10.  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
  11.  *
  12.  * See the file "license.terms" for information on usage and redistribution
  13.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  14.  *
  15.  * SCCS: @(#) tkMenu.c 1.102 96/03/26 16:07:08
  16.  */
  17. /*
  18.  * TkSTEP modifications Copyright
  19.  * by Alfredo K. Kojima 
  20.  */ 
  21.  
  22. #include "tkPort.h"
  23. #include "default.h"
  24. #include "tkInt.h"
  25.  
  26. /*
  27.  * One of the following data structures is kept for each entry of each
  28.  * menu managed by this file:
  29.  */
  30.  
  31. typedef struct MenuEntry {
  32.     int type;            /* Type of menu entry;  see below for
  33.                  * valid types. */
  34.     struct Menu *menuPtr;    /* Menu with which this entry is associated. */
  35.     char *label;        /* Main text label displayed in entry (NULL
  36.                  * if no label).  Malloc'ed. */
  37.     int labelLength;        /* Number of non-NULL characters in label. */
  38.     int underline;        /* Index of character to underline. */
  39.     Pixmap bitmap;        /* Bitmap to display in menu entry, or None.
  40.                  * If not None then label is ignored. */
  41.     char *imageString;        /* Name of image to display (malloc'ed), or
  42.                  * NULL.  If non-NULL, bitmap, text, and
  43.                  * textVarName are ignored. */
  44.     Tk_Image image;        /* Image to display in menu entry, or NULL if
  45.                  * none. */
  46.     char *selectImageString;    /* Name of image to display when selected
  47.                  * (malloc'ed), or NULL. */
  48.     Tk_Image selectImage;    /* Image to display in entry when selected,
  49.                  * or NULL if none.  Ignored if image is
  50.                  * NULL. */
  51.     char *accel;        /* Accelerator string displayed at right
  52.                  * of menu entry.  NULL means no such
  53.                  * accelerator.  Malloc'ed. */
  54.     int accelLength;        /* Number of non-NULL characters in
  55.                  * accelerator. */
  56.  
  57.     /*
  58.      * Information related to displaying entry:
  59.      */
  60.  
  61.     Tk_Uid state;        /* State of button for display purposes:
  62.                  * normal, active, or disabled. */
  63.     int height;            /* Number of pixels occupied by entry in
  64.                  * vertical dimension, including raised
  65.                  * border drawn around entry when active. */
  66.     int y;            /* Y-coordinate of topmost pixel in entry. */
  67.     int indicatorOn;        /* True means draw indicator, false means
  68.                  * don't draw it. */
  69.     int indicatorDiameter;    /* Size of indicator display, in pixels. */
  70.     Tk_3DBorder border;        /* Structure used to draw background for
  71.                  * entry.  NULL means use overall border
  72.                  * for menu. */
  73.     XColor *fg;            /* Foreground color to use for entry.  NULL
  74.                  * means use foreground color from menu. */
  75.     Tk_3DBorder activeBorder;    /* Used to draw background and border when
  76.                  * element is active.  NULL means use
  77.                  * activeBorder from menu. */
  78.     XColor *activeFg;        /* Foreground color to use when entry is
  79.                  * active.  NULL means use active foreground
  80.                  * from menu. */
  81.     XFontStruct *fontPtr;    /* Text font for menu entries.  NULL means
  82.                  * use overall font for menu. */
  83.     GC textGC;            /* GC for drawing text in entry.  NULL means
  84.                  * use overall textGC for menu. */
  85.     GC activeGC;        /* GC for drawing text in entry when active.
  86.                  * NULL means use overall activeGC for
  87.                  * menu. */
  88.     GC disabledGC;        /* Used to produce disabled effect for entry.
  89.                  * NULL means use overall disabledGC from
  90.                  * menu structure.  See comments for
  91.                  * disabledFg in menu structure for more
  92.                  * information. */
  93.     XColor *indicatorFg;    /* Color for indicators in radio and check
  94.                  * button entries.  NULL means use indicatorFg
  95.                  * GC from menu. */
  96.     GC indicatorGC;        /* For drawing indicators.  None means use
  97.                  * GC from menu. */
  98.  
  99.     /*
  100.      * Information used to implement this entry's action:
  101.      */
  102.  
  103.     char *command;        /* Command to invoke when entry is invoked.
  104.                  * Malloc'ed. */
  105.     char *name;            /* Name of variable (for check buttons and
  106.                  * radio buttons) or menu (for cascade
  107.                  * entries).  Malloc'ed.*/
  108.     char *onValue;        /* Value to store in variable when selected
  109.                  * (only for radio and check buttons).
  110.                  * Malloc'ed. */
  111.     char *offValue;        /* Value to store in variable when not
  112.                  * selected (only for check buttons).
  113.                  * Malloc'ed. */
  114.  
  115.     /*
  116.      * Miscellaneous information:
  117.      */
  118.  
  119.     int flags;            /* Various flags.  See below for definitions. */
  120. } MenuEntry;
  121.  
  122. /*
  123.  * Flag values defined for menu entries:
  124.  *
  125.  * ENTRY_SELECTED:        Non-zero means this is a radio or check
  126.  *                button and that it should be drawn in
  127.  *                the "selected" state.
  128.  * ENTRY_NEEDS_REDISPLAY:    Non-zero means the entry should be redisplayed.
  129.  */
  130.  
  131. #define ENTRY_SELECTED        1
  132. #define ENTRY_NEEDS_REDISPLAY    4
  133.  
  134. /*
  135.  * Types defined for MenuEntries:
  136.  */
  137.  
  138. #define COMMAND_ENTRY        0
  139. #define SEPARATOR_ENTRY        1
  140. #define CHECK_BUTTON_ENTRY    2
  141. #define RADIO_BUTTON_ENTRY    3
  142. #define CASCADE_ENTRY        4
  143. #define TEAROFF_ENTRY        5
  144.  
  145. /*
  146.  * Mask bits for above types:
  147.  */
  148.  
  149. #define COMMAND_MASK        TK_CONFIG_USER_BIT
  150. #define SEPARATOR_MASK        (TK_CONFIG_USER_BIT << 1)
  151. #define CHECK_BUTTON_MASK    (TK_CONFIG_USER_BIT << 2)
  152. #define RADIO_BUTTON_MASK    (TK_CONFIG_USER_BIT << 3)
  153. #define CASCADE_MASK        (TK_CONFIG_USER_BIT << 4)
  154. #define TEAROFF_MASK        (TK_CONFIG_USER_BIT << 5)
  155. #define ALL_MASK        (COMMAND_MASK | SEPARATOR_MASK \
  156.     | CHECK_BUTTON_MASK | RADIO_BUTTON_MASK | CASCADE_MASK | TEAROFF_MASK)
  157.  
  158. /*
  159.  * Configuration specs for individual menu entries:
  160.  */
  161.  
  162. static Tk_ConfigSpec entryConfigSpecs[] = {
  163.     {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
  164.     DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(MenuEntry, activeBorder),
  165.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  166.     |TK_CONFIG_NULL_OK},
  167.     {TK_CONFIG_COLOR, "-activeforeground", (char *) NULL, (char *) NULL,
  168.     DEF_MENU_ENTRY_ACTIVE_FG, Tk_Offset(MenuEntry, activeFg),
  169.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  170.     |TK_CONFIG_NULL_OK},
  171.     {TK_CONFIG_STRING, "-accelerator", (char *) NULL, (char *) NULL,
  172.     DEF_MENU_ENTRY_ACCELERATOR, Tk_Offset(MenuEntry, accel),
  173.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  174.     |TK_CONFIG_NULL_OK},
  175.     {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
  176.     DEF_MENU_ENTRY_BG, Tk_Offset(MenuEntry, border),
  177.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  178.     |SEPARATOR_MASK|TEAROFF_MASK|TK_CONFIG_NULL_OK},
  179.     {TK_CONFIG_BITMAP, "-bitmap", (char *) NULL, (char *) NULL,
  180.     DEF_MENU_ENTRY_BITMAP, Tk_Offset(MenuEntry, bitmap),
  181.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  182.     |TK_CONFIG_NULL_OK},
  183.     {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL,
  184.     DEF_MENU_ENTRY_COMMAND, Tk_Offset(MenuEntry, command),
  185.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  186.     |TK_CONFIG_NULL_OK},
  187.     {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
  188.     DEF_MENU_ENTRY_FONT, Tk_Offset(MenuEntry, fontPtr),
  189.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  190.     |TK_CONFIG_NULL_OK},
  191.     {TK_CONFIG_COLOR, "-foreground", (char *) NULL, (char *) NULL,
  192.     DEF_MENU_ENTRY_FG, Tk_Offset(MenuEntry, fg),
  193.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  194.     |TK_CONFIG_NULL_OK},
  195.     {TK_CONFIG_STRING, "-image", (char *) NULL, (char *) NULL,
  196.     DEF_MENU_ENTRY_IMAGE, Tk_Offset(MenuEntry, imageString),
  197.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  198.     |TK_CONFIG_NULL_OK},
  199.     {TK_CONFIG_BOOLEAN, "-indicatoron", (char *) NULL, (char *) NULL,
  200.     DEF_MENU_ENTRY_INDICATOR, Tk_Offset(MenuEntry, indicatorOn),
  201.     CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_DONT_SET_DEFAULT},
  202.     {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL,
  203.     DEF_MENU_ENTRY_LABEL, Tk_Offset(MenuEntry, label),
  204.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
  205.     {TK_CONFIG_STRING, "-menu", (char *) NULL, (char *) NULL,
  206.     DEF_MENU_ENTRY_MENU, Tk_Offset(MenuEntry, name),
  207.     CASCADE_MASK|TK_CONFIG_NULL_OK},
  208.     {TK_CONFIG_STRING, "-offvalue", (char *) NULL, (char *) NULL,
  209.     DEF_MENU_ENTRY_OFF_VALUE, Tk_Offset(MenuEntry, offValue),
  210.     CHECK_BUTTON_MASK},
  211.     {TK_CONFIG_STRING, "-onvalue", (char *) NULL, (char *) NULL,
  212.     DEF_MENU_ENTRY_ON_VALUE, Tk_Offset(MenuEntry, onValue),
  213.     CHECK_BUTTON_MASK},
  214.     {TK_CONFIG_COLOR, "-selectcolor", (char *) NULL, (char *) NULL,
  215.     DEF_MENU_ENTRY_SELECT, Tk_Offset(MenuEntry, indicatorFg),
  216.     CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
  217.     {TK_CONFIG_STRING, "-selectimage", (char *) NULL, (char *) NULL,
  218.     DEF_MENU_ENTRY_SELECT_IMAGE, Tk_Offset(MenuEntry, selectImageString),
  219.     CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
  220.     {TK_CONFIG_UID, "-state", (char *) NULL, (char *) NULL,
  221.     DEF_MENU_ENTRY_STATE, Tk_Offset(MenuEntry, state),
  222.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  223.     |TEAROFF_MASK|TK_CONFIG_DONT_SET_DEFAULT},
  224.     {TK_CONFIG_STRING, "-value", (char *) NULL, (char *) NULL,
  225.     DEF_MENU_ENTRY_VALUE, Tk_Offset(MenuEntry, onValue),
  226.     RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
  227.     {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
  228.     DEF_MENU_ENTRY_CHECK_VARIABLE, Tk_Offset(MenuEntry, name),
  229.     CHECK_BUTTON_MASK|TK_CONFIG_NULL_OK},
  230.     {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
  231.     DEF_MENU_ENTRY_RADIO_VARIABLE, Tk_Offset(MenuEntry, name),
  232.     RADIO_BUTTON_MASK},
  233.     {TK_CONFIG_INT, "-underline", (char *) NULL, (char *) NULL,
  234.     DEF_MENU_ENTRY_UNDERLINE, Tk_Offset(MenuEntry, underline),
  235.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  236.     |TK_CONFIG_DONT_SET_DEFAULT},
  237.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  238.     (char *) NULL, 0, 0}
  239. };
  240.  
  241. /*
  242.  * A data structure of the following type is kept for each
  243.  * menu managed by this file:
  244.  */
  245.  
  246. typedef struct Menu {
  247.     Tk_Window tkwin;        /* Window that embodies the pane.  NULL
  248.                  * means that the window has been destroyed
  249.                  * but the data structures haven't yet been
  250.                  * cleaned up.*/
  251.     Display *display;        /* Display containing widget.  Needed, among
  252.                  * other things, so that resources can be
  253.                  * freed up even after tkwin has gone away. */
  254.     Tcl_Interp *interp;        /* Interpreter associated with menu. */
  255.     Tcl_Command widgetCmd;    /* Token for menu's widget command. */
  256.     MenuEntry **entries;    /* Array of pointers to all the entries
  257.                  * in the menu.  NULL means no entries. */
  258.     int numEntries;        /* Number of elements in entries. */
  259.     int active;            /* Index of active entry.  -1 means
  260.                  * nothing active. */
  261.  
  262.     /*
  263.      * Information used when displaying widget:
  264.      */
  265.  
  266.     Tk_3DBorder border;        /* Structure used to draw 3-D
  267.                  * border and background for menu. */
  268.     int borderWidth;        /* Width of border around whole menu. */
  269.     Tk_3DBorder activeBorder;    /* Used to draw background and border for
  270.                  * active element (if any). */
  271.     int activeBorderWidth;    /* Width of border around active element. */
  272.     int relief;            /* 3-d effect: TK_RELIEF_RAISED, etc. */
  273.     XFontStruct *fontPtr;    /* Text font for menu entries. */
  274.     XColor *fg;            /* Foreground color for entries. */
  275.     GC textGC;            /* GC for drawing text and other features
  276.                  * of menu entries. */
  277.     XColor *disabledFg;        /* Foreground color when disabled.  NULL
  278.                  * means use normalFg with a 50% stipple
  279.                  * instead. */
  280.     Pixmap gray;        /* Bitmap for drawing disabled entries in
  281.                  * a stippled fashion.  None means not
  282.                  * allocated yet. */
  283.     GC disabledGC;        /* Used to produce disabled effect.  If
  284.                  * disabledFg isn't NULL, this GC is used to
  285.                  * draw text and icons for disabled entries.
  286.                  * Otherwise text and icons are drawn with
  287.                  * normalGC and this GC is used to stipple
  288.                  * background across them. */
  289.     XColor *activeFg;        /* Foreground color for active entry. */
  290.     GC activeGC;        /* GC for drawing active entry. */
  291.     XColor *indicatorFg;    /* Color for indicators in radio and check
  292.                  * button entries. */
  293.     GC indicatorGC;        /* For drawing indicators. */
  294.     int indicatorSpace;        /* Number of pixels to allow for displaying
  295.                  * indicators in menu entries (includes extra
  296.                  * space around indicator). */
  297.     int labelWidth;        /* Number of pixels to allow for displaying
  298.                  * labels in menu entries. */
  299.  
  300.     /*
  301.      * Miscellaneous information:
  302.      */
  303.  
  304.     int tearOff;        /* 1 means this is a tear-off menu, so the
  305.                  * first entry always shows a dashed stripe
  306.                  * for tearing off. */
  307.     char *tearOffCommand;    /* If non-NULL, points to a command to
  308.                  * run whenever the menu is torn-off. */
  309.     int transient;        /* 1 means menu is only posted briefly as
  310.                  * a popup or pulldown or cascade.  0 means
  311.                  * menu is always visible, e.g. as a torn-off
  312.                  * menu.  Determines whether save_under and
  313.                  * override_redirect should be set. */
  314.     Tk_Cursor cursor;        /* Current cursor for window, or None. */
  315.     char *takeFocus;        /* Value of -takefocus option;  not used in
  316.                  * the C code, but used by keyboard traversal
  317.                  * scripts.  Malloc'ed, but may be NULL. */
  318.     char *postCommand;        /* Command to execute just before posting
  319.                  * this menu, or NULL.  Malloc-ed. */
  320.     MenuEntry *postedCascade;    /* Points to menu entry for cascaded
  321.                  * submenu that is currently posted, or
  322.                  * NULL if no submenu posted. */
  323.     int flags;            /* Various flags;  see below for
  324.                  * definitions. */
  325. } Menu;
  326.  
  327. /*
  328.  * Flag bits for menus:
  329.  *
  330.  * REDRAW_PENDING:        Non-zero means a DoWhenIdle handler
  331.  *                has already been queued to redraw
  332.  *                this window.
  333.  * RESIZE_PENDING:        Non-zero means a call to ComputeMenuGeometry
  334.  *                has already been scheduled.
  335.  */
  336.  
  337. #define REDRAW_PENDING        1
  338. #define RESIZE_PENDING        2
  339.  
  340. /*
  341.  * Configuration specs valid for the menu as a whole:
  342.  */
  343.  
  344. static Tk_ConfigSpec configSpecs[] = {
  345.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  346.     DEF_MENU_ACTIVE_BG_COLOR, Tk_Offset(Menu, activeBorder),
  347.     TK_CONFIG_COLOR_ONLY},
  348.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  349.     DEF_MENU_ACTIVE_BG_MONO, Tk_Offset(Menu, activeBorder),
  350.     TK_CONFIG_MONO_ONLY},
  351.     {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth",
  352.     DEF_MENU_ACTIVE_BORDER_WIDTH, Tk_Offset(Menu, activeBorderWidth), 0},
  353.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  354.     DEF_MENU_ACTIVE_FG_COLOR, Tk_Offset(Menu, activeFg),
  355.     TK_CONFIG_COLOR_ONLY},
  356.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  357.     DEF_MENU_ACTIVE_FG_MONO, Tk_Offset(Menu, activeFg),
  358.     TK_CONFIG_MONO_ONLY},
  359.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  360.     DEF_MENU_BG_COLOR, Tk_Offset(Menu, border), TK_CONFIG_COLOR_ONLY},
  361.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  362.     DEF_MENU_BG_MONO, Tk_Offset(Menu, border), TK_CONFIG_MONO_ONLY},
  363.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  364.     (char *) NULL, 0, 0},
  365.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  366.     (char *) NULL, 0, 0},
  367.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  368.     DEF_MENU_BORDER_WIDTH, Tk_Offset(Menu, borderWidth), 0},
  369.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  370.     DEF_MENU_CURSOR, Tk_Offset(Menu, cursor), TK_CONFIG_NULL_OK},
  371.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  372.     "DisabledForeground", DEF_MENU_DISABLED_FG_COLOR,
  373.     Tk_Offset(Menu, disabledFg), TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
  374.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  375.     "DisabledForeground", DEF_MENU_DISABLED_FG_MONO,
  376.     Tk_Offset(Menu, disabledFg), TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
  377.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  378.     (char *) NULL, 0, 0},
  379.     {TK_CONFIG_FONT, "-font", "font", "Font",
  380.     DEF_MENU_FONT, Tk_Offset(Menu, fontPtr), 0},
  381.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  382.     DEF_MENU_FG, Tk_Offset(Menu, fg), 0},
  383.     {TK_CONFIG_STRING, "-postcommand", "postCommand", "Command",
  384.     DEF_MENU_POST_COMMAND, Tk_Offset(Menu, postCommand), TK_CONFIG_NULL_OK},
  385.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  386.     DEF_MENU_RELIEF, Tk_Offset(Menu, relief), 0},
  387.     {TK_CONFIG_COLOR, "-selectcolor", "selectColor", "Background",
  388.     DEF_MENU_SELECT_COLOR, Tk_Offset(Menu, indicatorFg),
  389.     TK_CONFIG_COLOR_ONLY},
  390.     {TK_CONFIG_COLOR, "-selectcolor", "selectColor", "Background",
  391.     DEF_MENU_SELECT_MONO, Tk_Offset(Menu, indicatorFg),
  392.     TK_CONFIG_MONO_ONLY},
  393.     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
  394.     DEF_MENU_TAKE_FOCUS, Tk_Offset(Menu, takeFocus), TK_CONFIG_NULL_OK},
  395.     {TK_CONFIG_BOOLEAN, "-tearoff", "tearOff", "TearOff",
  396.     DEF_MENU_TEAROFF, Tk_Offset(Menu, tearOff), 0},
  397.     {TK_CONFIG_STRING, "-tearoffcommand", "tearOffCommand", "TearOffCommand",
  398.     DEF_MENU_TEAROFF_CMD, Tk_Offset(Menu, tearOffCommand),
  399.     TK_CONFIG_NULL_OK},
  400.     {TK_CONFIG_BOOLEAN, "-transient", "transient", "Transient",
  401.     DEF_MENU_TRANSIENT, Tk_Offset(Menu, transient), 0},
  402.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  403.     (char *) NULL, 0, 0}
  404. };
  405.  
  406. /*
  407.  * Various geometry definitions:
  408.  */
  409.  
  410. #define CASCADE_ARROW_HEIGHT 8
  411. #define CASCADE_ARROW_WIDTH 7
  412. #define DECORATION_BORDER_WIDTH 2
  413. #define MARGIN_WIDTH 2
  414.  
  415. /*
  416.  * Forward declarations for procedures defined later in this file:
  417.  */
  418.  
  419. static int        ActivateMenuEntry _ANSI_ARGS_((Menu *menuPtr,
  420.                 int index));
  421. static void        ComputeMenuGeometry _ANSI_ARGS_((
  422.                 ClientData clientData));
  423. static int        ConfigureMenu _ANSI_ARGS_((Tcl_Interp *interp,
  424.                 Menu *menuPtr, int argc, char **argv,
  425.                 int flags));
  426. static int        ConfigureMenuEntry _ANSI_ARGS_((Tcl_Interp *interp,
  427.                 Menu *menuPtr, MenuEntry *mePtr, int index,
  428.                 int argc, char **argv, int flags));
  429. static void        DestroyMenu _ANSI_ARGS_((char *memPtr));
  430. static void        DestroyMenuEntry _ANSI_ARGS_((char *memPtr));
  431. static void        DisplayMenu _ANSI_ARGS_((ClientData clientData));
  432. static void        EventuallyRedrawMenu _ANSI_ARGS_((Menu *menuPtr,
  433.                 MenuEntry *mePtr));
  434. static int        GetMenuIndex _ANSI_ARGS_((Tcl_Interp *interp,
  435.                 Menu *menuPtr, char *string, int lastOK,
  436.                 int *indexPtr));
  437. static int        MenuAddOrInsert _ANSI_ARGS_((Tcl_Interp *interp,
  438.                 Menu *menuPtr, char *indexString, int argc,
  439.                 char **argv));
  440. static void        MenuCmdDeletedProc _ANSI_ARGS_((
  441.                 ClientData clientData));
  442. static void        MenuEventProc _ANSI_ARGS_((ClientData clientData,
  443.                 XEvent *eventPtr));
  444. static void        MenuImageProc _ANSI_ARGS_((ClientData clientData,
  445.                 int x, int y, int width, int height, int imgWidth,
  446.                 int imgHeight));
  447. static MenuEntry *    MenuNewEntry _ANSI_ARGS_((Menu *menuPtr, int index,
  448.                 int type));
  449. static void        MenuSelectImageProc _ANSI_ARGS_((ClientData clientData,
  450.                 int x, int y, int width, int height, int imgWidth,
  451.                 int imgHeight));
  452. static char *        MenuVarProc _ANSI_ARGS_((ClientData clientData,
  453.                 Tcl_Interp *interp, char *name1, char *name2,
  454.                 int flags));
  455. static int        MenuWidgetCmd _ANSI_ARGS_((ClientData clientData,
  456.                 Tcl_Interp *interp, int argc, char **argv));
  457. static int        PostSubmenu _ANSI_ARGS_((Tcl_Interp *interp,
  458.                 Menu *menuPtr, MenuEntry *mePtr));
  459.  
  460. /*
  461.  *--------------------------------------------------------------
  462.  *
  463.  * Tk_MenuCmd --
  464.  *
  465.  *    This procedure is invoked to process the "menu" Tcl
  466.  *    command.  See the user documentation for details on
  467.  *    what it does.
  468.  *
  469.  * Results:
  470.  *    A standard Tcl result.
  471.  *
  472.  * Side effects:
  473.  *    See the user documentation.
  474.  *
  475.  *--------------------------------------------------------------
  476.  */
  477.  
  478. int
  479. Tk_MenuCmd(clientData, interp, argc, argv)
  480.     ClientData clientData;    /* Main window associated with
  481.                  * interpreter. */
  482.     Tcl_Interp *interp;        /* Current interpreter. */
  483.     int argc;            /* Number of arguments. */
  484.     char **argv;        /* Argument strings. */
  485. {
  486.     Tk_Window tkwin = (Tk_Window) clientData;
  487.     Tk_Window new;
  488.     register Menu *menuPtr;
  489.  
  490.     if (argc < 2) {
  491.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  492.         argv[0], " pathName ?options?\"", (char *) NULL);
  493.     return TCL_ERROR;
  494.     }
  495.  
  496.     /*
  497.      * Create the new window.  Set override-redirect so the window
  498.      * manager won't add a border or argue about placement, and set
  499.      * save-under so that the window can pop up and down without a
  500.      * lot of re-drawing.
  501.      */
  502.  
  503.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
  504.     if (new == NULL) {
  505.     return TCL_ERROR;
  506.     }
  507.  
  508.     /*
  509.      * Initialize the data structure for the menu.
  510.      */
  511.  
  512.     menuPtr = (Menu *) ckalloc(sizeof(Menu));
  513.     menuPtr->tkwin = new;
  514.     menuPtr->display = Tk_Display(new);
  515.     menuPtr->interp = interp;
  516.     menuPtr->widgetCmd = Tcl_CreateCommand(interp,
  517.         Tk_PathName(menuPtr->tkwin), MenuWidgetCmd,
  518.         (ClientData) menuPtr, MenuCmdDeletedProc);
  519.     menuPtr->entries = NULL;
  520.     menuPtr->numEntries = 0;
  521.     menuPtr->active = -1;
  522.     menuPtr->border = NULL;
  523.     menuPtr->borderWidth = 0;
  524.     menuPtr->relief = TK_RELIEF_FLAT;
  525.     menuPtr->activeBorder = NULL;
  526.     menuPtr->activeBorderWidth = 0;
  527.     menuPtr->fontPtr = NULL;
  528.     menuPtr->fg = NULL;
  529.     menuPtr->textGC = None;
  530.     menuPtr->disabledFg = NULL;
  531.     menuPtr->gray = None;
  532.     menuPtr->disabledGC = None;
  533.     menuPtr->activeFg = NULL;
  534.     menuPtr->activeGC = None;
  535.     menuPtr->indicatorFg = NULL;
  536.     menuPtr->indicatorGC = None;
  537.     menuPtr->indicatorSpace = 0;
  538.     menuPtr->labelWidth = 0;
  539.     menuPtr->tearOff = 1;
  540.     menuPtr->tearOffCommand = NULL;
  541.     menuPtr->cursor = None;
  542.     menuPtr->takeFocus = NULL;
  543.     menuPtr->postCommand = NULL;
  544.     menuPtr->postedCascade = NULL;
  545.     menuPtr->flags = 0;
  546.  
  547.     Tk_SetClass(new, "Menu");
  548.     Tk_CreateEventHandler(menuPtr->tkwin, ExposureMask|StructureNotifyMask,
  549.         MenuEventProc, (ClientData) menuPtr);
  550.     if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
  551.     goto error;
  552.     }
  553.  
  554.     interp->result = Tk_PathName(menuPtr->tkwin);
  555.     return TCL_OK;
  556.  
  557.     error:
  558.     Tk_DestroyWindow(menuPtr->tkwin);
  559.     return TCL_ERROR;
  560. }
  561.  
  562. /*
  563.  *--------------------------------------------------------------
  564.  *
  565.  * MenuWidgetCmd --
  566.  *
  567.  *    This procedure is invoked to process the Tcl command
  568.  *    that corresponds to a widget managed by this module.
  569.  *    See the user documentation for details on what it does.
  570.  *
  571.  * Results:
  572.  *    A standard Tcl result.
  573.  *
  574.  * Side effects:
  575.  *    See the user documentation.
  576.  *
  577.  *--------------------------------------------------------------
  578.  */
  579.  
  580. static int
  581. MenuWidgetCmd(clientData, interp, argc, argv)
  582.     ClientData clientData;    /* Information about menu widget. */
  583.     Tcl_Interp *interp;        /* Current interpreter. */
  584.     int argc;            /* Number of arguments. */
  585.     char **argv;        /* Argument strings. */
  586. {
  587.     register Menu *menuPtr = (Menu *) clientData;
  588.     register MenuEntry *mePtr;
  589.     int result = TCL_OK;
  590.     size_t length;
  591.     int c;
  592.  
  593.     if (argc < 2) {
  594.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  595.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  596.     return TCL_ERROR;
  597.     }
  598.     Tcl_Preserve((ClientData) menuPtr);
  599.     c = argv[1][0];
  600.     length = strlen(argv[1]);
  601.     if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
  602.         && (length >= 2)) {
  603.     int index;
  604.  
  605.     if (argc != 3) {
  606.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  607.             argv[0], " activate index\"", (char *) NULL);
  608.         goto error;
  609.     }
  610.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  611.         goto error;
  612.     }
  613.     if (menuPtr->active == index) {
  614.         goto done;
  615.     }
  616.     if (index >= 0) {
  617.         if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY)
  618.             || (menuPtr->entries[index]->state == tkDisabledUid)) {
  619.         index = -1;
  620.         }
  621.     }
  622.     result = ActivateMenuEntry(menuPtr, index);
  623.     } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
  624.         && (length >= 2)) {
  625.     if (argc < 3) {
  626.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  627.             argv[0], " add type ?options?\"", (char *) NULL);
  628.         goto error;
  629.     }
  630.     if (MenuAddOrInsert(interp, menuPtr, (char *) NULL,
  631.         argc-2, argv+2) != TCL_OK) {
  632.         goto error;
  633.     }
  634.     } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
  635.         && (length >= 2)) {
  636.     if (argc != 3) {
  637.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  638.             argv[0], " cget option\"",
  639.             (char *) NULL);
  640.         goto error;
  641.     }
  642.     result = Tk_ConfigureValue(interp, menuPtr->tkwin, configSpecs,
  643.         (char *) menuPtr, argv[2], 0);
  644.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
  645.         && (length >= 2)) {
  646.     if (argc == 2) {
  647.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
  648.             (char *) menuPtr, (char *) NULL, 0);
  649.     } else if (argc == 3) {
  650.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
  651.             (char *) menuPtr, argv[2], 0);
  652.     } else {
  653.         result = ConfigureMenu(interp, menuPtr, argc-2, argv+2,
  654.             TK_CONFIG_ARGV_ONLY);
  655.     }
  656.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) {
  657.     int first, last, i, numDeleted;
  658.  
  659.     if ((argc != 3) && (argc != 4)) {
  660.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  661.             argv[0], " delete first ?last?\"", (char *) NULL);
  662.         goto error;
  663.     }
  664.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &first) != TCL_OK) {
  665.         goto error;
  666.     }
  667.     if (argc == 3) {
  668.         last = first;
  669.     } else {
  670.         if (GetMenuIndex(interp, menuPtr, argv[3], 0, &last) != TCL_OK) {
  671.             goto error;
  672.         }
  673.     }
  674.     if (menuPtr->tearOff && (first == 0)) {
  675.         /*
  676.          * Sorry, can't delete the tearoff entry;  must reconfigure
  677.          * the menu.
  678.          */
  679.         first = 1;
  680.     }
  681.     if ((first < 0) || (last < first)) {
  682.         goto done;
  683.     }
  684.     numDeleted = last + 1 - first;
  685.     for (i = first; i <= last; i++) {
  686.         Tcl_EventuallyFree((ClientData) menuPtr->entries[i],
  687.                   DestroyMenuEntry);
  688.     }
  689.     for (i = last+1; i < menuPtr->numEntries; i++) {
  690.         menuPtr->entries[i-numDeleted] = menuPtr->entries[i];
  691.     }
  692.     menuPtr->numEntries -= numDeleted;
  693.     if ((menuPtr->active >= first) && (menuPtr->active <= last)) {
  694.         menuPtr->active = -1;
  695.     } else if (menuPtr->active > last) {
  696.         menuPtr->active -= numDeleted;
  697.     }
  698.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  699.         menuPtr->flags |= RESIZE_PENDING;
  700.         Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  701.     }
  702.     } else if ((c == 'e') && (length >= 7)
  703.         && (strncmp(argv[1], "entrycget", length) == 0)) {
  704.     int index;
  705.  
  706.     if (argc != 4) {
  707.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  708.             argv[0], " entrycget index option\"",
  709.             (char *) NULL);
  710.         goto error;
  711.     }
  712.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  713.         goto error;
  714.     }
  715.     if (index < 0) {
  716.         goto done;
  717.     }
  718.     mePtr = menuPtr->entries[index];
  719.     Tcl_Preserve((ClientData) mePtr);
  720.     result = Tk_ConfigureValue(interp, menuPtr->tkwin, entryConfigSpecs,
  721.         (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
  722.     Tcl_Release((ClientData) mePtr);
  723.     } else if ((c == 'e') && (length >= 7)
  724.         && (strncmp(argv[1], "entryconfigure", length) == 0)) {
  725.     int index;
  726.  
  727.     if (argc < 3) {
  728.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  729.             argv[0], " entryconfigure index ?option value ...?\"",
  730.             (char *) NULL);
  731.         goto error;
  732.     }
  733.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  734.         goto error;
  735.     }
  736.     if (index < 0) {
  737.         goto done;
  738.     }
  739.     mePtr = menuPtr->entries[index];
  740.     Tcl_Preserve((ClientData) mePtr);
  741.     if (argc == 3) {
  742.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
  743.             (char *) mePtr, (char *) NULL,
  744.             COMMAND_MASK << mePtr->type);
  745.     } else if (argc == 4) {
  746.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
  747.             (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
  748.     } else {
  749.         result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3,
  750.             argv+3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
  751.     }
  752.     Tcl_Release((ClientData) mePtr);
  753.     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
  754.         && (length >= 3)) {
  755.     int index;
  756.  
  757.     if (argc != 3) {
  758.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  759.             argv[0], " index string\"", (char *) NULL);
  760.         goto error;
  761.     }
  762.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  763.         goto error;
  764.     }
  765.     if (index < 0) {
  766.         interp->result = "none";
  767.     } else {
  768.         sprintf(interp->result, "%d", index);
  769.     }
  770.     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
  771.         && (length >= 3)) {
  772.     if (argc < 4) {
  773.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  774.             argv[0], " insert index type ?options?\"", (char *) NULL);
  775.         goto error;
  776.     }
  777.     if (MenuAddOrInsert(interp, menuPtr, argv[2],
  778.         argc-3, argv+3) != TCL_OK) {
  779.         goto error;
  780.     }
  781.     } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
  782.         && (length >= 3)) {
  783.     int index;
  784.  
  785.     if (argc != 3) {
  786.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  787.             argv[0], " invoke index\"", (char *) NULL);
  788.         goto error;
  789.     }
  790.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  791.         goto error;
  792.     }
  793.     if (index < 0) {
  794.         goto done;
  795.     }
  796.     mePtr = menuPtr->entries[index];
  797.     if (mePtr->state == tkDisabledUid) {
  798.         goto done;
  799.     }
  800.     Tcl_Preserve((ClientData) mePtr);
  801.     if (mePtr->type == CHECK_BUTTON_ENTRY) {
  802.         if (mePtr->flags & ENTRY_SELECTED) {
  803.         if (Tcl_SetVar(interp, mePtr->name, mePtr->offValue,
  804.             TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
  805.             result = TCL_ERROR;
  806.         }
  807.         } else {
  808.         if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
  809.             TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
  810.             result = TCL_ERROR;
  811.         }
  812.         }
  813.     } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  814.         if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
  815.             TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
  816.         result = TCL_ERROR;
  817.         }
  818.     }
  819.     if ((result == TCL_OK) && (mePtr->command != NULL)) {
  820.         result = TkCopyAndGlobalEval(interp, mePtr->command);
  821.     }
  822.     if ((result == TCL_OK) && (mePtr->type == CASCADE_ENTRY)) {
  823.         result = PostSubmenu(menuPtr->interp, menuPtr, mePtr);
  824.     }
  825.     Tcl_Release((ClientData) mePtr);
  826.     } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)
  827.         && (length == 4)) {
  828.     int x, y, tmp, vRootX, vRootY;
  829.     int vRootWidth, vRootHeight;
  830.  
  831.     if (argc != 4) {
  832.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  833.             argv[0], " post x y\"", (char *) NULL);
  834.         goto error;
  835.     }
  836.     if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
  837.         || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
  838.         goto error;
  839.     }
  840.  
  841.     /*
  842.      * De-activate any active element.
  843.      */
  844.  
  845.     ActivateMenuEntry(menuPtr, -1);
  846.  
  847.     /*
  848.      * If there is a command for the menu, execute it.  This
  849.      * may change the size of the menu, so be sure to recompute
  850.      * the menu's geometry if needed.
  851.      */
  852.  
  853.     if (menuPtr->postCommand != NULL) {
  854.         result = TkCopyAndGlobalEval(menuPtr->interp,
  855.             menuPtr->postCommand);
  856.         if (result != TCL_OK) {
  857.         return result;
  858.         }
  859.         if (menuPtr->flags & RESIZE_PENDING) {
  860.         Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
  861.         ComputeMenuGeometry((ClientData) menuPtr);
  862.         }
  863.     }
  864.  
  865.     /*
  866.      * Adjust the position of the menu if necessary to keep it
  867.      * visible on the screen.  There are two special tricks to
  868.      * make this work right:
  869.      *
  870.      * 1. If a virtual root window manager is being used then
  871.      *    the coordinates are in the virtual root window of
  872.      *    menuPtr's parent;  since the menu uses override-redirect
  873.      *    mode it will be in the *real* root window for the screen,
  874.      *    so we have to map the coordinates from the virtual root
  875.      *    (if any) to the real root.  Can't get the virtual root
  876.      *    from the menu itself (it will never be seen by the wm)
  877.      *    so use its parent instead (it would be better to have an
  878.      *    an option that names a window to use for this...).
  879.      * 2. The menu may not have been mapped yet, so its current size
  880.      *    might be the default 1x1.  To compute how much space it
  881.      *    needs, use its requested size, not its actual size.
  882.      */
  883.  
  884.     Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY,
  885.         &vRootWidth, &vRootHeight);
  886.     x += vRootX;
  887.     y += vRootY;
  888.     tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin))
  889.         - Tk_ReqWidth(menuPtr->tkwin);
  890.     if (x > tmp) {
  891.         x = tmp;
  892.     }
  893.     if (x < 0) {
  894.         x = 0;
  895.     }
  896.     tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin))
  897.         - Tk_ReqHeight(menuPtr->tkwin);
  898.     if (y > tmp) {
  899.         y = tmp;
  900.     }
  901.     if (y < 0) {
  902.         y = 0;
  903.     }
  904.     if ((x != Tk_X(menuPtr->tkwin)) || (y != Tk_Y(menuPtr->tkwin))) {
  905.         Tk_MoveToplevelWindow(menuPtr->tkwin, x, y);
  906.     }
  907.     if (!Tk_IsMapped(menuPtr->tkwin)) {
  908.         Tk_MapWindow(menuPtr->tkwin);
  909.     }
  910.     XRaiseWindow(menuPtr->display, Tk_WindowId(menuPtr->tkwin));
  911.     } else if ((c == 'p') && (strncmp(argv[1], "postcascade", length) == 0)
  912.         && (length > 4)) {
  913.     int index;
  914.     if (argc != 3) {
  915.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  916.             argv[0], " postcascade index\"", (char *) NULL);
  917.         goto error;
  918.     }
  919.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  920.         goto error;
  921.     }
  922.     if ((index < 0) || (menuPtr->entries[index]->type != CASCADE_ENTRY)) {
  923.         result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
  924.     } else {
  925.         result = PostSubmenu(interp, menuPtr, menuPtr->entries[index]);
  926.     }
  927.     } else if ((c == 't') && (strncmp(argv[1], "type", length) == 0)) {
  928.     int index;
  929.     if (argc != 3) {
  930.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  931.             argv[0], " type index\"", (char *) NULL);
  932.         goto error;
  933.     }
  934.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  935.         goto error;
  936.     }
  937.     if (index < 0) {
  938.         goto done;
  939.     }
  940.     mePtr = menuPtr->entries[index];
  941.     switch (mePtr->type) {
  942.         case COMMAND_ENTRY:
  943.         interp->result = "command";
  944.         break;
  945.         case SEPARATOR_ENTRY:
  946.         interp->result = "separator";
  947.         break;
  948.         case CHECK_BUTTON_ENTRY:
  949.         interp->result = "checkbutton";
  950.         break;
  951.         case RADIO_BUTTON_ENTRY:
  952.         interp->result = "radiobutton";
  953.         break;
  954.         case CASCADE_ENTRY:
  955.         interp->result = "cascade";
  956.         break;
  957.         case TEAROFF_ENTRY:
  958.         interp->result = "tearoff";
  959.         break;
  960.     }
  961.     } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
  962.     if (argc != 2) {
  963.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  964.             argv[0], " unpost\"", (char *) NULL);
  965.         goto error;
  966.     }
  967.     Tk_UnmapWindow(menuPtr->tkwin);
  968.     if (result == TCL_OK) {
  969.         result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
  970.     }
  971.     } else if ((c == 'y') && (strncmp(argv[1], "yposition", length) == 0)) {
  972.     int index;
  973.  
  974.     if (argc != 3) {
  975.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  976.             argv[0], " yposition index\"", (char *) NULL);
  977.         goto error;
  978.     }
  979.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  980.         goto error;
  981.     }
  982.     if (index < 0) {
  983.         interp->result = "0";
  984.     } else {
  985.         sprintf(interp->result, "%d", menuPtr->entries[index]->y);
  986.     }
  987.     } else {
  988.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  989.         "\": must be activate, add, cget, configure, delete, ",
  990.         "entrycget, entryconfigure, index, insert, invoke, ",
  991.         "post, postcascade, type, unpost, or yposition",
  992.         (char *) NULL);
  993.     goto error;
  994.     }
  995.     done:
  996.     Tcl_Release((ClientData) menuPtr);
  997.     return result;
  998.  
  999.     error:
  1000.     Tcl_Release((ClientData) menuPtr);
  1001.     return TCL_ERROR;
  1002. }
  1003.  
  1004. /*
  1005.  *----------------------------------------------------------------------
  1006.  *
  1007.  * DestroyMenu --
  1008.  *
  1009.  *    This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
  1010.  *    to clean up the internal structure of a menu at a safe time
  1011.  *    (when no-one is using it anymore).
  1012.  *
  1013.  * Results:
  1014.  *    None.
  1015.  *
  1016.  * Side effects:
  1017.  *    Everything associated with the menu is freed up.
  1018.  *
  1019.  *----------------------------------------------------------------------
  1020.  */
  1021.  
  1022. static void
  1023. DestroyMenu(memPtr)
  1024.     char *memPtr;    /* Info about menu widget. */
  1025. {
  1026.     register Menu *menuPtr = (Menu *) memPtr;
  1027.     int i;
  1028.  
  1029.     /*
  1030.      * Free up all the stuff that requires special handling, then
  1031.      * let Tk_FreeOptions handle all the standard option-related
  1032.      * stuff.
  1033.      */
  1034.  
  1035.     for (i = 0; i < menuPtr->numEntries; i++) {
  1036.     DestroyMenuEntry((char *) menuPtr->entries[i]);
  1037.     }
  1038.     if (menuPtr->entries != NULL) {
  1039.     ckfree((char *) menuPtr->entries);
  1040.     }
  1041.     if (menuPtr->textGC != None) {
  1042.     Tk_FreeGC(menuPtr->display, menuPtr->textGC);
  1043.     }
  1044.     if (menuPtr->gray != None) {
  1045.     Tk_FreeBitmap(menuPtr->display, menuPtr->gray);
  1046.     }
  1047.     if (menuPtr->disabledGC != None) {
  1048.     Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
  1049.     }
  1050.     if (menuPtr->activeGC != None) {
  1051.     Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
  1052.     }
  1053.     if (menuPtr->indicatorGC != None) {
  1054.     Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC);
  1055.     }
  1056.     Tk_FreeOptions(configSpecs, (char *) menuPtr, menuPtr->display, 0);
  1057.     ckfree((char *) menuPtr);
  1058. }
  1059.  
  1060. /*
  1061.  *----------------------------------------------------------------------
  1062.  *
  1063.  * DestroyMenuEntry --
  1064.  *
  1065.  *    This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
  1066.  *    to clean up the internal structure of a menu entry at a safe time
  1067.  *    (when no-one is using it anymore).
  1068.  *
  1069.  * Results:
  1070.  *    None.
  1071.  *
  1072.  * Side effects:
  1073.  *    Everything associated with the menu entry is freed up.
  1074.  *
  1075.  *----------------------------------------------------------------------
  1076.  */
  1077.  
  1078. static void
  1079. DestroyMenuEntry(memPtr)
  1080.     char *memPtr;        /* Pointer to entry to be freed. */
  1081. {
  1082.     register MenuEntry *mePtr = (MenuEntry *) memPtr;
  1083.     Menu *menuPtr = mePtr->menuPtr;
  1084.  
  1085.     /*
  1086.      * Free up all the stuff that requires special handling, then
  1087.      * let Tk_FreeOptions handle all the standard option-related
  1088.      * stuff.
  1089.      */
  1090.  
  1091.     if (menuPtr->postedCascade == mePtr) {
  1092.     /*
  1093.      * Ignore errors while unposting the menu, since it's possible
  1094.      * that the menu has already been deleted and the unpost will
  1095.      * generate an error.
  1096.      */
  1097.  
  1098.     PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
  1099.     }
  1100.     if (mePtr->image != NULL) {
  1101.     Tk_FreeImage(mePtr->image);
  1102.     }
  1103.     if (mePtr->textGC != None) {
  1104.     Tk_FreeGC(menuPtr->display, mePtr->textGC);
  1105.     }
  1106.     if (mePtr->activeGC != None) {
  1107.     Tk_FreeGC(menuPtr->display, mePtr->activeGC);
  1108.     }
  1109.     if (mePtr->disabledGC != None) {
  1110.     Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
  1111.     }
  1112.     if (mePtr->indicatorGC != None) {
  1113.     Tk_FreeGC(menuPtr->display, mePtr->indicatorGC);
  1114.     }
  1115.     if (mePtr->name != NULL) {
  1116.     Tcl_UntraceVar(menuPtr->interp, mePtr->name,
  1117.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1118.         MenuVarProc, (ClientData) mePtr);
  1119.     }
  1120.     Tk_FreeOptions(entryConfigSpecs, (char *) mePtr, menuPtr->display, 
  1121.         (COMMAND_MASK << mePtr->type));
  1122.     ckfree((char *) mePtr);
  1123. }
  1124.  
  1125. /*
  1126.  *----------------------------------------------------------------------
  1127.  *
  1128.  * ConfigureMenu --
  1129.  *
  1130.  *    This procedure is called to process an argv/argc list, plus
  1131.  *    the Tk option database, in order to configure (or
  1132.  *    reconfigure) a menu widget.
  1133.  *
  1134.  * Results:
  1135.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  1136.  *    returned, then interp->result contains an error message.
  1137.  *
  1138.  * Side effects:
  1139.  *    Configuration information, such as colors, font, etc. get set
  1140.  *    for menuPtr;  old resources get freed, if there were any.
  1141.  *
  1142.  *----------------------------------------------------------------------
  1143.  */
  1144.  
  1145. static int
  1146. ConfigureMenu(interp, menuPtr, argc, argv, flags)
  1147.     Tcl_Interp *interp;        /* Used for error reporting. */
  1148.     register Menu *menuPtr;    /* Information about widget;  may or may
  1149.                  * not already have values for some fields. */
  1150.     int argc;            /* Number of valid entries in argv. */
  1151.     char **argv;        /* Arguments. */
  1152.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  1153. {
  1154.     XGCValues gcValues;
  1155.     GC newGC;
  1156.     unsigned long mask;
  1157.     int i;
  1158.     XSetWindowAttributes atts;
  1159.  
  1160.     if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs,
  1161.         argc, argv, (char *) menuPtr, flags) != TCL_OK) {
  1162.     return TCL_ERROR;
  1163.     }
  1164.  
  1165.     /*
  1166.      * A few options need special processing, such as setting the
  1167.      * background from a 3-D border, or filling in complicated
  1168.      * defaults that couldn't be specified to Tk_ConfigureWidget.
  1169.      */
  1170.  
  1171.     Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);
  1172.  
  1173.     gcValues.font = menuPtr->fontPtr->fid;
  1174.     gcValues.foreground = menuPtr->fg->pixel;
  1175.     gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
  1176.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  1177.         &gcValues);
  1178.     if (menuPtr->textGC != None) {
  1179.     Tk_FreeGC(menuPtr->display, menuPtr->textGC);
  1180.     }
  1181.     menuPtr->textGC = newGC;
  1182.  
  1183.     if (menuPtr->disabledFg != NULL) {
  1184.     gcValues.foreground = menuPtr->disabledFg->pixel;
  1185.     mask = GCForeground|GCBackground|GCFont;
  1186.     } else {
  1187.     gcValues.foreground = gcValues.background;
  1188.     if (menuPtr->gray == None) {
  1189.         menuPtr->gray = Tk_GetBitmap(interp, menuPtr->tkwin,
  1190.             Tk_GetUid("gray50"));
  1191.         if (menuPtr->gray == None) {
  1192.         return TCL_ERROR;
  1193.         }
  1194.     }
  1195.     gcValues.fill_style = FillStippled;
  1196.     gcValues.stipple = menuPtr->gray;
  1197.     mask = GCForeground|GCFillStyle|GCStipple;
  1198.     }
  1199.     newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
  1200.     if (menuPtr->disabledGC != None) {
  1201.     Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
  1202.     }
  1203.     menuPtr->disabledGC = newGC;
  1204.  
  1205.     gcValues.font = menuPtr->fontPtr->fid;
  1206.     gcValues.foreground = menuPtr->activeFg->pixel;
  1207.     gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
  1208.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  1209.         &gcValues);
  1210.     if (menuPtr->activeGC != None) {
  1211.     Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
  1212.     }
  1213.     menuPtr->activeGC = newGC;
  1214.  
  1215.     gcValues.foreground = menuPtr->indicatorFg->pixel;
  1216.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCFont, &gcValues);
  1217.     if (menuPtr->indicatorGC != None) {
  1218.     Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC);
  1219.     }
  1220.     menuPtr->indicatorGC = newGC;
  1221.  
  1222.     if (menuPtr->transient) {
  1223.     atts.override_redirect = True;
  1224.     atts.save_under = True;
  1225.     } else {
  1226.     atts.override_redirect = False;
  1227.     atts.save_under = False;
  1228.     }
  1229.     if ((atts.override_redirect
  1230.         != Tk_Attributes(menuPtr->tkwin)->override_redirect)
  1231.         || (atts.save_under
  1232.         != Tk_Attributes(menuPtr->tkwin)->save_under)) {
  1233.     Tk_ChangeWindowAttributes(menuPtr->tkwin,
  1234.         CWOverrideRedirect|CWSaveUnder, &atts);
  1235.     }
  1236.  
  1237.     /*
  1238.      * After reconfiguring a menu, we need to reconfigure all of the
  1239.      * entries in the menu, since some of the things in the children
  1240.      * (such as graphics contexts) may have to change to reflect changes
  1241.      * in the parent.
  1242.      */
  1243.  
  1244.     for (i = 0; i < menuPtr->numEntries; i++) {
  1245.     MenuEntry *mePtr;
  1246.  
  1247.     mePtr = menuPtr->entries[i];
  1248.     ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
  1249.         TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
  1250.     }
  1251.  
  1252.     /*
  1253.      * Depending on the -tearOff option, make sure that there is or
  1254.      * isn't an initial tear-off entry at the beginning of the menu.
  1255.      */
  1256.  
  1257.     if (menuPtr->tearOff) {
  1258.     if ((menuPtr->numEntries == 0)
  1259.         || (menuPtr->entries[0]->type != TEAROFF_ENTRY)) {
  1260.         MenuNewEntry(menuPtr, 0, TEAROFF_ENTRY);
  1261.     }
  1262.     } else if ((menuPtr->numEntries > 0)
  1263.         && (menuPtr->entries[0]->type == TEAROFF_ENTRY)) {
  1264.     Tcl_EventuallyFree((ClientData) menuPtr->entries[0],
  1265.               DestroyMenuEntry);
  1266.     for (i = 1; i < menuPtr->numEntries;  i++) {
  1267.         menuPtr->entries[i-1] = menuPtr->entries[i];
  1268.     }
  1269.     menuPtr->numEntries--;
  1270.     }
  1271.  
  1272.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  1273.     menuPtr->flags |= RESIZE_PENDING;
  1274.     Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  1275.     }
  1276.  
  1277.     return TCL_OK;
  1278. }
  1279.  
  1280. /*
  1281.  *----------------------------------------------------------------------
  1282.  *
  1283.  * ConfigureMenuEntry --
  1284.  *
  1285.  *    This procedure is called to process an argv/argc list, plus
  1286.  *    the Tk option database, in order to configure (or
  1287.  *    reconfigure) one entry in a menu.
  1288.  *
  1289.  * Results:
  1290.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  1291.  *    returned, then interp->result contains an error message.
  1292.  *
  1293.  * Side effects:
  1294.  *    Configuration information such as label and accelerator get
  1295.  *    set for mePtr;  old resources get freed, if there were any.
  1296.  *
  1297.  *----------------------------------------------------------------------
  1298.  */
  1299.  
  1300. static int
  1301. ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags)
  1302.     Tcl_Interp *interp;            /* Used for error reporting. */
  1303.     Menu *menuPtr;            /* Information about whole menu. */
  1304.     register MenuEntry *mePtr;        /* Information about menu entry;  may
  1305.                      * or may not already have values for
  1306.                      * some fields. */
  1307.     int index;                /* Index of mePtr within menuPtr's
  1308.                      * entries. */
  1309.     int argc;                /* Number of valid entries in argv. */
  1310.     char **argv;            /* Arguments. */
  1311.     int flags;                /* Additional flags to pass to
  1312.                      * Tk_ConfigureWidget. */
  1313. {
  1314.     XGCValues gcValues;
  1315.     GC newGC, newActiveGC, newDisabledGC;
  1316.     unsigned long mask;
  1317.     Tk_Image image;
  1318.  
  1319.     /*
  1320.      * If this entry is a cascade and the cascade is posted, then unpost
  1321.      * it before reconfiguring the entry (otherwise the reconfigure might
  1322.      * change the name of the cascaded entry, leaving a posted menu
  1323.      * high and dry).
  1324.      */
  1325.  
  1326.     if (menuPtr->postedCascade == mePtr) {
  1327.     if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
  1328.         != TCL_OK) {
  1329.         Tcl_BackgroundError(menuPtr->interp);
  1330.     }
  1331.     }
  1332.  
  1333.     /*
  1334.      * If this entry is a check button or radio button, then remove
  1335.      * its old trace procedure.
  1336.      */
  1337.  
  1338.     if ((mePtr->name != NULL) &&
  1339.         ((mePtr->type == CHECK_BUTTON_ENTRY)
  1340.         || (mePtr->type == RADIO_BUTTON_ENTRY))) {
  1341.     Tcl_UntraceVar(menuPtr->interp, mePtr->name,
  1342.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1343.         MenuVarProc, (ClientData) mePtr);
  1344.     }
  1345.  
  1346.     if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
  1347.         argc, argv, (char *) mePtr,
  1348.         flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
  1349.     return TCL_ERROR;
  1350.     }
  1351.  
  1352.     /*
  1353.      * The code below handles special configuration stuff not taken
  1354.      * care of by Tk_ConfigureWidget, such as special processing for
  1355.      * defaults, sizing strings, graphics contexts, etc.
  1356.      */
  1357.  
  1358.     if (mePtr->label == NULL) {
  1359.     mePtr->labelLength = 0;
  1360.     } else {
  1361.     mePtr->labelLength = strlen(mePtr->label);
  1362.     }
  1363.     if (mePtr->accel == NULL) {
  1364.     mePtr->accelLength = 0;
  1365.     } else {
  1366.     mePtr->accelLength = strlen(mePtr->accel);
  1367.     }
  1368.  
  1369.     if (mePtr->state == tkActiveUid) {
  1370.     if (index != menuPtr->active) {
  1371.         ActivateMenuEntry(menuPtr, index);
  1372.     }
  1373.     } else {
  1374.     if (index == menuPtr->active) {
  1375.         ActivateMenuEntry(menuPtr, -1);
  1376.     }
  1377.     if ((mePtr->state != tkNormalUid) && (mePtr->state != tkDisabledUid)) {
  1378.         Tcl_AppendResult(interp, "bad state value \"", mePtr->state,
  1379.             "\": must be normal, active, or disabled", (char *) NULL);
  1380.         mePtr->state = tkNormalUid;
  1381.         return TCL_ERROR;
  1382.     }
  1383.     }
  1384.  
  1385.     if ((mePtr->fontPtr != NULL) || (mePtr->border != NULL)
  1386.         || (mePtr->fg != NULL) || (mePtr->activeBorder != NULL)
  1387.         || (mePtr->activeFg != NULL)) {
  1388.     gcValues.foreground = (mePtr->fg != NULL) ? mePtr->fg->pixel
  1389.         : menuPtr->fg->pixel;
  1390.     gcValues.background = Tk_3DBorderColor(
  1391.         (mePtr->border != NULL) ? mePtr->border : menuPtr->border)
  1392.         ->pixel;
  1393.     gcValues.font = (mePtr->fontPtr != NULL) ? mePtr->fontPtr->fid
  1394.         : menuPtr->fontPtr->fid;
  1395.  
  1396.     /*
  1397.      * Note: disable GraphicsExpose events;  we know there won't be
  1398.      * obscured areas when copying from an off-screen pixmap to the
  1399.      * screen and this gets rid of unnecessary events.
  1400.      */
  1401.  
  1402.     gcValues.graphics_exposures = False;
  1403.     newGC = Tk_GetGC(menuPtr->tkwin,
  1404.         GCForeground|GCBackground|GCFont|GCGraphicsExposures,
  1405.         &gcValues);
  1406.  
  1407.     if (menuPtr->disabledFg != NULL) {
  1408.         gcValues.foreground = menuPtr->disabledFg->pixel;
  1409.         mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures;
  1410.     } else {
  1411.         gcValues.foreground = gcValues.background;
  1412.         gcValues.fill_style = FillStippled;
  1413.         gcValues.stipple = menuPtr->gray;
  1414.         mask = GCForeground|GCFillStyle|GCStipple;
  1415.     }
  1416.     newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
  1417.  
  1418.     gcValues.foreground = (mePtr->activeFg != NULL)
  1419.         ? mePtr->activeFg->pixel : menuPtr->activeFg->pixel;
  1420.     gcValues.background = Tk_3DBorderColor(
  1421.         (mePtr->activeBorder != NULL) ? mePtr->activeBorder
  1422.         : menuPtr->activeBorder)->pixel;
  1423.     newActiveGC = Tk_GetGC(menuPtr->tkwin,
  1424.         GCForeground|GCBackground|GCFont|GCGraphicsExposures,
  1425.         &gcValues);
  1426.     } else {
  1427.     newGC = None;
  1428.     newActiveGC = None;
  1429.     newDisabledGC = None;
  1430.     }
  1431.     if (mePtr->textGC != None) {
  1432.         Tk_FreeGC(menuPtr->display, mePtr->textGC);
  1433.     }
  1434.     mePtr->textGC = newGC;
  1435.     if (mePtr->activeGC != None) {
  1436.         Tk_FreeGC(menuPtr->display, mePtr->activeGC);
  1437.     }
  1438.     mePtr->activeGC = newActiveGC;
  1439.     if (mePtr->disabledGC != None) {
  1440.         Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
  1441.     }
  1442.     mePtr->disabledGC = newDisabledGC;
  1443.     if (mePtr->indicatorFg != NULL) {
  1444.     gcValues.foreground = mePtr->indicatorFg->pixel;
  1445.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground, &gcValues);
  1446.     } else {
  1447.     newGC = None;
  1448.     }
  1449.     if (mePtr->indicatorGC != None) {
  1450.     Tk_FreeGC(menuPtr->display, mePtr->indicatorGC);
  1451.     }
  1452.     mePtr->indicatorGC = newGC;
  1453.  
  1454.     if ((mePtr->type == CHECK_BUTTON_ENTRY)
  1455.         || (mePtr->type == RADIO_BUTTON_ENTRY)) {
  1456.     char *value;
  1457.  
  1458.     if (mePtr->name == NULL) {
  1459.         mePtr->name = (char *) ckalloc((unsigned) (mePtr->labelLength + 1));
  1460.         strcpy(mePtr->name, (mePtr->label == NULL) ? "" : mePtr->label);
  1461.     }
  1462.     if (mePtr->onValue == NULL) {
  1463.         mePtr->onValue = (char *) ckalloc((unsigned)
  1464.             (mePtr->labelLength + 1));
  1465.         strcpy(mePtr->onValue, (mePtr->label == NULL) ? "" : mePtr->label);
  1466.     }
  1467.  
  1468.     /*
  1469.      * Select the entry if the associated variable has the
  1470.      * appropriate value, initialize the variable if it doesn't
  1471.      * exist, then set a trace on the variable to monitor future
  1472.      * changes to its value.
  1473.      */
  1474.  
  1475.     value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
  1476.     mePtr->flags &= ~ENTRY_SELECTED;
  1477.     if (value != NULL) {
  1478.         if (strcmp(value, mePtr->onValue) == 0) {
  1479.         mePtr->flags |= ENTRY_SELECTED;
  1480.         }
  1481.     } else {
  1482.         Tcl_SetVar(interp, mePtr->name,
  1483.             (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "",
  1484.             TCL_GLOBAL_ONLY);
  1485.     }
  1486.     Tcl_TraceVar(interp, mePtr->name,
  1487.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1488.         MenuVarProc, (ClientData) mePtr);
  1489.     }
  1490.  
  1491.     /*
  1492.      * Get the images for the entry, if there are any.  Allocate the
  1493.      * new images before freeing the old ones, so that the reference
  1494.      * counts don't go to zero and cause image data to be discarded.
  1495.      */
  1496.  
  1497.     if (mePtr->imageString != NULL) {
  1498.     image = Tk_GetImage(interp, menuPtr->tkwin, mePtr->imageString,
  1499.         MenuImageProc, (ClientData) mePtr);
  1500.     if (image == NULL) {
  1501.         return TCL_ERROR;
  1502.     }
  1503.     } else {
  1504.     image = NULL;
  1505.     }
  1506.     if (mePtr->image != NULL) {
  1507.     Tk_FreeImage(mePtr->image);
  1508.     }
  1509.     mePtr->image = image;
  1510.     if (mePtr->selectImageString != NULL) {
  1511.     image = Tk_GetImage(interp, menuPtr->tkwin, mePtr->selectImageString,
  1512.         MenuSelectImageProc, (ClientData) mePtr);
  1513.     if (image == NULL) {
  1514.         return TCL_ERROR;
  1515.     }
  1516.     } else {
  1517.     image = NULL;
  1518.     }
  1519.     if (mePtr->selectImage != NULL) {
  1520.     Tk_FreeImage(mePtr->selectImage);
  1521.     }
  1522.     mePtr->selectImage = image;
  1523.  
  1524.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  1525.     menuPtr->flags |= RESIZE_PENDING;
  1526.     Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  1527.     }
  1528.     return TCL_OK;
  1529. }
  1530.  
  1531. /*
  1532.  *--------------------------------------------------------------
  1533.  *
  1534.  * ComputeMenuGeometry --
  1535.  *
  1536.  *    This procedure is invoked to recompute the size and
  1537.  *    layout of a menu.  It is called as a when-idle handler so
  1538.  *    that it only gets done once, even if a group of changes is
  1539.  *    made to the menu.
  1540.  *
  1541.  * Results:
  1542.  *    None.
  1543.  *
  1544.  * Side effects:
  1545.  *    Fields of menu entries are changed to reflect their
  1546.  *    current positions, and the size of the menu window
  1547.  *    itself may be changed.
  1548.  *
  1549.  *--------------------------------------------------------------
  1550.  */
  1551.  
  1552. static void
  1553. ComputeMenuGeometry(clientData)
  1554.     ClientData clientData;        /* Structure describing menu. */
  1555. {
  1556.     Menu *menuPtr = (Menu *) clientData;
  1557.     register MenuEntry *mePtr;
  1558.     XFontStruct *fontPtr;
  1559.     int maxLabelWidth, maxIndicatorWidth, maxAccelWidth;
  1560.     int width, height, indicatorSpace;
  1561.     int i, y;
  1562.     int imageWidth, imageHeight;
  1563.  
  1564.     if (menuPtr->tkwin == NULL) {
  1565.     return;
  1566.     }
  1567.  
  1568.     maxLabelWidth = maxIndicatorWidth = maxAccelWidth = 0;
  1569.     y = menuPtr->borderWidth;
  1570.  
  1571.     for (i = 0; i < menuPtr->numEntries; i++) {
  1572.     mePtr = menuPtr->entries[i];
  1573.     indicatorSpace = 0;
  1574.     fontPtr = mePtr->fontPtr;
  1575.     if (fontPtr == NULL) {
  1576.         fontPtr = menuPtr->fontPtr;
  1577.     }
  1578.  
  1579.     /*
  1580.      * For each entry, compute the height required by that
  1581.      * particular entry, plus three widths:  the width of the
  1582.      * label, the width to allow for an indicator to be displayed
  1583.      * to the left of the label (if any), and the width of the
  1584.      * accelerator to be displayed to the right of the label
  1585.      * (if any).  These sizes depend, of course, on the type
  1586.      * of the entry.
  1587.      */
  1588.  
  1589.     if (mePtr->image != NULL) {
  1590.         Tk_SizeOfImage(mePtr->image, &imageWidth, &imageHeight);
  1591.  
  1592.         imageOrBitmap:
  1593.         mePtr->height = imageHeight;
  1594.         width = imageWidth;
  1595.         if (mePtr->indicatorOn) {
  1596.         if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1597.             indicatorSpace = (14*mePtr->height)/10;
  1598.             mePtr->indicatorDiameter = (65*mePtr->height)/100;
  1599.         } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1600.             indicatorSpace = (14*mePtr->height)/10;
  1601.             mePtr->indicatorDiameter = (75*mePtr->height)/100;
  1602.         }
  1603.         }
  1604.     } else if (mePtr->bitmap != None) {
  1605.         Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap,
  1606.             &imageWidth, &imageHeight);
  1607.         goto imageOrBitmap;
  1608.     } else {
  1609.         mePtr->height = fontPtr->ascent + fontPtr->descent;
  1610.         if (mePtr->label != NULL) {
  1611.         (void) TkMeasureChars(fontPtr, mePtr->label,
  1612.             mePtr->labelLength, 0, (int) 100000, 0,
  1613.             TK_NEWLINES_NOT_SPECIAL, &width);
  1614.         } else {
  1615.         width = 0;
  1616.         }
  1617.         if (mePtr->indicatorOn) {
  1618.         if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1619.             indicatorSpace = mePtr->height;
  1620.             mePtr->indicatorDiameter = (80*mePtr->height)/100;
  1621.         } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1622.             indicatorSpace = mePtr->height;
  1623.             mePtr->indicatorDiameter = mePtr->height;
  1624.         }
  1625.         }
  1626.     }
  1627.     mePtr->height += 2*menuPtr->activeBorderWidth + 2;
  1628.     if (width > maxLabelWidth) {
  1629.         maxLabelWidth = width;
  1630.     }
  1631.     if (mePtr->type == CASCADE_ENTRY) {
  1632.         width = 2*CASCADE_ARROW_WIDTH;
  1633.     } else if (mePtr->accel != NULL) {
  1634.         (void) TkMeasureChars(fontPtr, mePtr->accel, mePtr->accelLength,
  1635.             0, (int) 100000, 0, TK_NEWLINES_NOT_SPECIAL, &width);
  1636.     } else {
  1637.         width = 0;
  1638.     }
  1639.     if (width > maxAccelWidth) {
  1640.         maxAccelWidth = width;
  1641.     }
  1642.     if (mePtr->type == SEPARATOR_ENTRY) {
  1643.         mePtr->height = 0; /*8;*/
  1644.     }
  1645.     if (mePtr->type == TEAROFF_ENTRY) {
  1646.         mePtr->height = 12;
  1647.     }
  1648.     if (indicatorSpace > maxIndicatorWidth) {
  1649.         maxIndicatorWidth = indicatorSpace;
  1650.     }
  1651.     mePtr->y = y;
  1652.     y += mePtr->height;
  1653.     }
  1654.  
  1655.     /*
  1656.      * Got all the sizes.  Update fields in the menu structure, then
  1657.      * resize the window if necessary.  Leave margins on either side
  1658.      * of the indicator (or just one margin if there is no indicator).
  1659.      * Leave another margin on the right side of the label, plus yet
  1660.      * another margin to the right of the accelerator (if there is one).
  1661.      */
  1662.  
  1663.     menuPtr->indicatorSpace = maxIndicatorWidth + MARGIN_WIDTH;
  1664.     if (maxIndicatorWidth != 0) {
  1665.     menuPtr->indicatorSpace += MARGIN_WIDTH;
  1666.     }
  1667.     menuPtr->labelWidth = maxLabelWidth + MARGIN_WIDTH;
  1668.     width = menuPtr->indicatorSpace + menuPtr->labelWidth + maxAccelWidth
  1669.         + 2*menuPtr->borderWidth + 2*menuPtr->activeBorderWidth;
  1670.     if (maxAccelWidth != 0) {
  1671.     width += MARGIN_WIDTH;
  1672.     }
  1673.     height = y + menuPtr->borderWidth;
  1674.  
  1675.     /*
  1676.      * The X server doesn't like zero dimensions, so round up to at least
  1677.      * 1 (a zero-sized menu should never really occur, anyway).
  1678.      */
  1679.  
  1680.     if (width <= 0) {
  1681.     width = 1;
  1682.     }
  1683.     if (height <= 0) {
  1684.     height = 1;
  1685.     }
  1686.     width+=2;
  1687.     if ((width != Tk_ReqWidth(menuPtr->tkwin)) ||
  1688.         (height != Tk_ReqHeight(menuPtr->tkwin))) {
  1689.     Tk_GeometryRequest(menuPtr->tkwin, width, height);
  1690.     } else {
  1691.     /*
  1692.      * Must always force a redisplay here if the window is mapped
  1693.      * (even if the size didn't change, something else might have
  1694.      * changed in the menu, such as a label or accelerator).  The
  1695.      * resize will force a redisplay above.
  1696.      */
  1697.  
  1698.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  1699.     }
  1700.  
  1701.     menuPtr->flags &= ~RESIZE_PENDING;
  1702. }
  1703.  
  1704. /*
  1705.  *----------------------------------------------------------------------
  1706.  *
  1707.  * DisplayMenu --
  1708.  *
  1709.  *    This procedure is invoked to display a menu widget.
  1710.  *
  1711.  * Results:
  1712.  *    None.
  1713.  *
  1714.  * Side effects:
  1715.  *    Commands are output to X to display the menu in its
  1716.  *    current mode.
  1717.  *
  1718.  *----------------------------------------------------------------------
  1719.  */
  1720.  
  1721. static void
  1722. DisplayMenu(clientData)
  1723.     ClientData clientData;    /* Information about widget. */
  1724. {
  1725.     register Menu *menuPtr = (Menu *) clientData;
  1726.     register MenuEntry *mePtr;
  1727.     register Tk_Window tkwin = menuPtr->tkwin;
  1728.     Tk_3DBorder bgBorder, activeBorder;
  1729.     XFontStruct *fontPtr;
  1730.     int index, baseline, strictMotif, leftEdge, y,  height;
  1731.     GC gc;
  1732.  
  1733.     menuPtr->flags &= ~REDRAW_PENDING;
  1734.     if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
  1735.     return;
  1736.     }
  1737.  
  1738.     /* 
  1739.      * Draw Window border before
  1740.      */
  1741.     if (menuPtr->transient) {    
  1742.     Tk_Draw3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
  1743.         menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
  1744.         menuPtr->borderWidth, menuPtr->relief);
  1745.     } else {
  1746.     XDrawLine(menuPtr->display, Tk_WindowId(tkwin), 
  1747.             Tk_3DBorderGC(tkwin, menuPtr->border, TK_3D_LIGHT_GC),
  1748.             0,0, Tk_Width(tkwin)-1, 0);
  1749.     XDrawLine(menuPtr->display, Tk_WindowId(tkwin), 
  1750.             Tk_3DBorderGC(tkwin, menuPtr->border, TK_3D_LIGHT_GC),
  1751.             0, 0, 0, Tk_Height(tkwin));
  1752.     XDrawLine(menuPtr->display, Tk_WindowId(tkwin), 
  1753.             Tk_3DBorderGC(tkwin, menuPtr->border, TK_3D_DARK_GC),
  1754.             Tk_Width(tkwin)-1, 0, Tk_Width(tkwin)-1, Tk_Height(tkwin));
  1755.     XDrawLine(menuPtr->display, Tk_WindowId(tkwin),
  1756.               Tk_3DBorderGC(tkwin, menuPtr->border, TK_3D_DARK_GC),
  1757.           0, Tk_Height(tkwin), Tk_Width(tkwin)-1, Tk_Height(tkwin));
  1758.     }    
  1759.     /*
  1760.      * Loop through all of the entries, drawing them one at a time.
  1761.      */
  1762.  
  1763.     strictMotif = Tk_StrictMotif(menuPtr->tkwin);
  1764.     leftEdge = menuPtr->borderWidth + menuPtr->indicatorSpace
  1765.         + menuPtr->activeBorderWidth;
  1766.     for (index = 0; index < menuPtr->numEntries; index++) {
  1767.     mePtr = menuPtr->entries[index];
  1768.     if (mePtr->type==SEPARATOR_ENTRY) {
  1769.         continue;
  1770.     }    
  1771.     /* 
  1772.      * Draw top and bottom part of entry bevel 
  1773.      */
  1774.     if (index!=0) {
  1775.         XDrawLine(menuPtr->display, Tk_WindowId(menuPtr->tkwin),
  1776.         Tk_3DBorderGC(menuPtr->tkwin, menuPtr->border,
  1777.         TK_3D_LIGHT_GC), 0, mePtr->y, Tk_Width(tkwin)-2, mePtr->y);
  1778.     }
  1779.     if (index<menuPtr->numEntries-1) {        
  1780.         XDrawLine(menuPtr->display, Tk_WindowId(menuPtr->tkwin),
  1781.               Tk_3DBorderGC(menuPtr->tkwin, menuPtr->border,
  1782.         TK_3D_DARK_GC), 0, mePtr->y+mePtr->height-2, Tk_Width(tkwin)-1,
  1783.         mePtr->y+mePtr->height-2);
  1784.         XDrawLine(menuPtr->display, Tk_WindowId(menuPtr->tkwin),
  1785.         Tk_3DBorderGC(menuPtr->tkwin, menuPtr->border,
  1786.         TK_3D_DARK2_GC), 0, mePtr->y+mePtr->height-1,Tk_Width(tkwin),
  1787.         mePtr->y+mePtr->height-1);
  1788.     }
  1789.     
  1790.     if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {        
  1791.         continue;
  1792.     }
  1793.     mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
  1794.     /*
  1795.      * Background.
  1796.      */
  1797.  
  1798.     bgBorder = mePtr->border;
  1799.     if (bgBorder == NULL) {
  1800.         bgBorder = menuPtr->border;
  1801.     }
  1802.     activeBorder = mePtr->activeBorder;
  1803.     if (activeBorder == NULL) {
  1804.         activeBorder = menuPtr->activeBorder;
  1805.     }
  1806.     
  1807.     if (mePtr->state == tkActiveUid) {
  1808.         XFillRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1809.             Tk_3DBorderGC(tkwin,activeBorder,TK_3D_FLAT_GC), 
  1810.             menuPtr->borderWidth-1, mePtr->y+1,
  1811.             Tk_Width(tkwin)-2*menuPtr->borderWidth+1, mePtr->height-3);
  1812.             
  1813.     } else {
  1814.         XFillRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1815.             Tk_3DBorderGC(tkwin,bgBorder,TK_3D_FLAT_GC), 
  1816.             menuPtr->borderWidth-1, mePtr->y+1,
  1817.             Tk_Width(tkwin)-2*menuPtr->borderWidth+1, mePtr->height-3);
  1818.     }
  1819.  
  1820.     /*
  1821.      * Choose the gc for drawing the foreground part of the entry.
  1822.      */
  1823.  
  1824.     if ((mePtr->state == tkActiveUid) && !strictMotif) {
  1825.         gc = mePtr->activeGC;
  1826.         if (gc == NULL) {
  1827.         gc = menuPtr->activeGC;
  1828.         }
  1829.     } else {
  1830.         if ((mePtr->state == tkDisabledUid)
  1831.             && (menuPtr->disabledFg != NULL)) {
  1832.         gc = mePtr->disabledGC;
  1833.         if (gc == NULL) {
  1834.             gc = menuPtr->disabledGC;
  1835.         }
  1836.         } else {
  1837.         gc = mePtr->textGC;
  1838.         if (gc == NULL) {
  1839.             gc = menuPtr->textGC;
  1840.         }
  1841.         }
  1842.     }
  1843.  
  1844.     
  1845.     /*
  1846.      * Draw label or bitmap or image for entry.
  1847.      */
  1848.  
  1849.     fontPtr = mePtr->fontPtr;
  1850.     if (fontPtr == NULL) {
  1851.         fontPtr = menuPtr->fontPtr;
  1852.     }
  1853.     baseline = mePtr->y + (mePtr->height + fontPtr->ascent
  1854.         - fontPtr->descent)/2;
  1855.     if (mePtr->image != NULL) {
  1856.         int width, height;
  1857.  
  1858.         Tk_SizeOfImage(mePtr->image, &width, &height);
  1859.         if ((mePtr->selectImage != NULL)
  1860.             && (mePtr->flags & ENTRY_SELECTED)) {
  1861.         Tk_RedrawImage(mePtr->selectImage, 0, 0, width, height,
  1862.             Tk_WindowId(tkwin), leftEdge,
  1863.             (int) (mePtr->y + (mePtr->height - height)/2));
  1864.         } else {
  1865.         Tk_RedrawImage(mePtr->image, 0, 0, width, height,
  1866.             Tk_WindowId(tkwin), leftEdge,
  1867.             (int) (mePtr->y + (mePtr->height - height)/2));
  1868.         }
  1869.     } else if (mePtr->bitmap != None) {
  1870.         int width, height;
  1871.  
  1872.         Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap, &width, &height);
  1873.         XCopyPlane(menuPtr->display, mePtr->bitmap, Tk_WindowId(tkwin),
  1874.             gc, 0, 0, (unsigned) width, (unsigned) height, leftEdge,
  1875.             (int) (mePtr->y + (mePtr->height - height)/2), 1);
  1876.     } else {
  1877.         baseline = mePtr->y + (mePtr->height + fontPtr->ascent
  1878.             - fontPtr->descent)/2;
  1879.         if (mePtr->label != NULL) {
  1880.         TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1881.             fontPtr, mePtr->label, mePtr->labelLength,
  1882.             leftEdge, baseline, leftEdge,
  1883.             TK_NEWLINES_NOT_SPECIAL);
  1884.         if (mePtr->underline >= 0) {
  1885.             TkUnderlineChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1886.                 fontPtr, mePtr->label, leftEdge, baseline,
  1887.                 leftEdge, TK_NEWLINES_NOT_SPECIAL,
  1888.                 mePtr->underline, mePtr->underline);
  1889.         }
  1890.         }
  1891.     }
  1892.  
  1893.     /*
  1894.      * Draw accelerator or cascade arrow.
  1895.      */
  1896.  
  1897.     if (mePtr->type == CASCADE_ENTRY) {
  1898.         int x,u;
  1899.  
  1900.         x = Tk_Width(tkwin) - menuPtr->borderWidth 
  1901.             - menuPtr->activeBorderWidth - MARGIN_WIDTH
  1902.             - CASCADE_ARROW_WIDTH;
  1903.         y = mePtr->y + (mePtr->height - CASCADE_ARROW_HEIGHT)/2;
  1904.           XDrawLine(menuPtr->display, Tk_WindowId(menuPtr->tkwin),
  1905.             Tk_3DBorderGC(menuPtr->tkwin, menuPtr->border, 
  1906.             TK_3D_DARK2_GC), x, y, x, y+CASCADE_ARROW_HEIGHT);
  1907.         XDrawLine(menuPtr->display, Tk_WindowId(menuPtr->tkwin),
  1908.             Tk_3DBorderGC(menuPtr->tkwin, menuPtr->border, 
  1909.             TK_3D_DARK_GC), x, y, x+CASCADE_ARROW_WIDTH, 
  1910.             y+CASCADE_ARROW_HEIGHT/2);
  1911.         XDrawLine(menuPtr->display, Tk_WindowId(menuPtr->tkwin),
  1912.             Tk_3DBorderGC(menuPtr->tkwin, menuPtr->border, 
  1913.             TK_3D_LIGHT_GC), x, y+CASCADE_ARROW_HEIGHT, 
  1914.             x+CASCADE_ARROW_WIDTH, y+CASCADE_ARROW_HEIGHT/2);
  1915.     } else if (mePtr->accel != NULL) {
  1916.         TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1917.             fontPtr, mePtr->accel, mePtr->accelLength,
  1918.             leftEdge + menuPtr->labelWidth, baseline,
  1919.             leftEdge + menuPtr->labelWidth, TK_NEWLINES_NOT_SPECIAL);
  1920.     }
  1921.  
  1922.     /*
  1923.      * Draw check-button indicator.
  1924.      */
  1925.  
  1926.     gc = mePtr->indicatorGC;
  1927.     if (gc == None) {
  1928.         gc = menuPtr->indicatorGC;
  1929.     }
  1930.     if ((mePtr->type == CHECK_BUTTON_ENTRY) && mePtr->indicatorOn) {
  1931.         int dim, x, y;
  1932.  
  1933.         dim = mePtr->indicatorDiameter;
  1934.         x = menuPtr->borderWidth + menuPtr->activeBorderWidth
  1935.             + (menuPtr->indicatorSpace - dim)/2;
  1936.         y = mePtr->y + (mePtr->height - dim)/2;
  1937.         Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
  1938.             menuPtr->border, x, y, dim, dim,
  1939.             DECORATION_BORDER_WIDTH, TK_RELIEF_RAISED);
  1940.         x += DECORATION_BORDER_WIDTH;
  1941.         y += DECORATION_BORDER_WIDTH;
  1942.         dim -= 2*DECORATION_BORDER_WIDTH;
  1943.         if ((dim > 0) && (mePtr->flags & ENTRY_SELECTED)) {
  1944.         Tk_DrawCheckMark(menuPtr->display, tkwin, Tk_WindowId(tkwin),
  1945.             x, y-3, menuPtr->border);
  1946.         }
  1947.     }
  1948.  
  1949.     /*
  1950.      * Draw radio-button indicator.
  1951.      */
  1952.  
  1953.     if ((mePtr->type == RADIO_BUTTON_ENTRY) && mePtr->indicatorOn) {
  1954.         int radius;
  1955.         int x, y;
  1956.  
  1957.         radius = mePtr->indicatorDiameter/2-1;
  1958.         x = menuPtr->borderWidth + menuPtr->activeBorderWidth
  1959.             + (menuPtr->indicatorSpace - mePtr->indicatorDiameter)/2;
  1960.         y = mePtr->y + (mePtr->height-mePtr->indicatorDiameter)/2;
  1961.         Tk_Draw3DCircle(menuPtr->display, tkwin, Tk_WindowId(tkwin),
  1962.         x, y, menuPtr->borderWidth, radius, TK_RELIEF_SUNKEN,
  1963.         menuPtr->border);
  1964.         if (mePtr->flags & ENTRY_SELECTED) {
  1965.         gc = Tk_3DBorderGC(tkwin, menuPtr->border, TK_3D_LIGHT_GC);
  1966.         XFillArc(menuPtr->display, Tk_WindowId(tkwin), gc, 
  1967.              x+menuPtr->borderWidth, y+menuPtr->borderWidth, 
  1968.              radius*2-menuPtr->borderWidth-1,
  1969.              radius*2-menuPtr->borderWidth-1,0,360*64);
  1970.         } else {
  1971.         gc = Tk_3DBorderGC(tkwin, menuPtr->border, TK_3D_FLAT_GC);
  1972.         XFillArc(menuPtr->display, Tk_WindowId(tkwin), gc, 
  1973.              x+menuPtr->borderWidth, y+menuPtr->borderWidth, 
  1974.              radius*2-menuPtr->borderWidth-1,
  1975.              radius*2-menuPtr->borderWidth-1,0,360*64);
  1976.         }        
  1977.     }
  1978.  
  1979.     /*
  1980.      * Draw tear-off line.
  1981.      */
  1982.  
  1983.     if (mePtr->type == TEAROFF_ENTRY) {
  1984.         XPoint points[2];
  1985.         int margin, width, maxX;
  1986.  
  1987.         margin = (fontPtr->ascent + fontPtr->descent)/2;
  1988.         points[0].x = 0;
  1989.         points[0].y = mePtr->y + mePtr->height/2-1;
  1990.         points[1].y = points[0].y;
  1991.         width = 6;
  1992.         maxX  = Tk_Width(tkwin) - 1;
  1993.  
  1994.         while (points[0].x < maxX) {
  1995.         points[1].x = points[0].x + width;
  1996.         if (points[1].x > maxX) {
  1997.             points[1].x = maxX;
  1998.         }
  1999.         Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
  2000.             menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
  2001.         points[0].x += 2*width;
  2002.         }
  2003.     }
  2004.  
  2005.     /*
  2006.      * If the entry is disabled with a stipple rather than a special
  2007.      * foreground color, generate the stippled effect.
  2008.      */
  2009.  
  2010.     if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg == NULL)) {
  2011.         XFillRectangle(menuPtr->display, Tk_WindowId(tkwin),
  2012.             menuPtr->disabledGC, menuPtr->borderWidth,
  2013.             mePtr->y,
  2014.             (unsigned) (Tk_Width(tkwin) - 2*menuPtr->borderWidth),
  2015.             (unsigned) mePtr->height);
  2016.     }
  2017.     }
  2018.  
  2019.     /*
  2020.      * If there is extra space after the last entry in the menu,
  2021.      * clear it.
  2022.      */
  2023.  
  2024.     if (menuPtr->numEntries >= 1) {
  2025.     mePtr = menuPtr->entries[menuPtr->numEntries-1];
  2026.     y = mePtr->y + mePtr->height;
  2027.     } else {
  2028.     y = menuPtr->borderWidth;
  2029.     }
  2030.     height = Tk_Height(tkwin) - menuPtr->borderWidth - y;
  2031.     if (height > 0) {
  2032.     Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin),
  2033.         menuPtr->border, menuPtr->borderWidth, y, 
  2034.         Tk_Width(tkwin) - 2*menuPtr->borderWidth,
  2035.         height, 0, TK_RELIEF_FLAT);
  2036.     }
  2037.  
  2038.  
  2039. }
  2040.  
  2041. /*
  2042.  *--------------------------------------------------------------
  2043.  *
  2044.  * GetMenuIndex --
  2045.  *
  2046.  *    Parse a textual index into a menu and return the numerical
  2047.  *    index of the indicated entry.
  2048.  *
  2049.  * Results:
  2050.  *    A standard Tcl result.  If all went well, then *indexPtr is
  2051.  *    filled in with the entry index corresponding to string
  2052.  *    (ranges from -1 to the number of entries in the menu minus
  2053.  *    one).  Otherwise an error message is left in interp->result.
  2054.  *
  2055.  * Side effects:
  2056.  *    None.
  2057.  *
  2058.  *--------------------------------------------------------------
  2059.  */
  2060.  
  2061. static int
  2062. GetMenuIndex(interp, menuPtr, string, lastOK, indexPtr)
  2063.     Tcl_Interp *interp;        /* For error messages. */
  2064.     Menu *menuPtr;        /* Menu for which the index is being
  2065.                  * specified. */
  2066.     char *string;        /* Specification of an entry in menu.  See
  2067.                  * manual entry for valid .*/
  2068.     int lastOK;            /* Non-zero means its OK to return index
  2069.                  * just *after* last entry. */
  2070.     int *indexPtr;        /* Where to store converted relief. */
  2071. {
  2072.     int i, y;
  2073.  
  2074.     if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
  2075.     *indexPtr = menuPtr->active;
  2076.     return TCL_OK;
  2077.     }
  2078.  
  2079.     if (((string[0] == 'l') && (strcmp(string, "last") == 0))
  2080.         || ((string[0] == 'e') && (strcmp(string, "end") == 0))) {
  2081.     *indexPtr = menuPtr->numEntries - ((lastOK) ? 0 : 1);
  2082.     return TCL_OK;
  2083.     }
  2084.  
  2085.     if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
  2086.     *indexPtr = -1;
  2087.     return TCL_OK;
  2088.     }
  2089.  
  2090.     if (string[0] == '@') {
  2091.     if (Tcl_GetInt(interp, string+1,  &y) == TCL_OK) {
  2092.         for (i = 0; i < menuPtr->numEntries; i++) {
  2093.         MenuEntry *mePtr = menuPtr->entries[i];
  2094.  
  2095.         if (y < (mePtr->y + mePtr->height)) {
  2096.             break;
  2097.         }
  2098.         }
  2099.         if (i >= menuPtr->numEntries) {
  2100.         i = menuPtr->numEntries-1;
  2101.         }
  2102.         *indexPtr = i;
  2103.         return TCL_OK;
  2104.     } else {
  2105.         Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  2106.     }
  2107.     }
  2108.  
  2109.     if (isdigit(UCHAR(string[0]))) {
  2110.     if (Tcl_GetInt(interp, string,  &i) == TCL_OK) {
  2111.         if (i >= menuPtr->numEntries) {
  2112.         if (lastOK) {
  2113.             i = menuPtr->numEntries;
  2114.         } else {
  2115.             i = menuPtr->numEntries-1;
  2116.         }
  2117.         } else if (i < 0) {
  2118.         i = -1;
  2119.         }
  2120.         *indexPtr = i;
  2121.         return TCL_OK;
  2122.     }
  2123.     Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  2124.     }
  2125.  
  2126.     for (i = 0; i < menuPtr->numEntries; i++) {
  2127.     char *label;
  2128.  
  2129.     label = menuPtr->entries[i]->label;
  2130.     if ((label != NULL)
  2131.         && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
  2132.         *indexPtr = i;
  2133.         return TCL_OK;
  2134.     }
  2135.     }
  2136.  
  2137.     Tcl_AppendResult(interp, "bad menu entry index \"",
  2138.         string, "\"", (char *) NULL);
  2139.     return TCL_ERROR;
  2140. }
  2141.  
  2142. /*
  2143.  *--------------------------------------------------------------
  2144.  *
  2145.  * MenuEventProc --
  2146.  *
  2147.  *    This procedure is invoked by the Tk dispatcher for various
  2148.  *    events on menus.
  2149.  *
  2150.  * Results:
  2151.  *    None.
  2152.  *
  2153.  * Side effects:
  2154.  *    When the window gets deleted, internal structures get
  2155.  *    cleaned up.  When it gets exposed, it is redisplayed.
  2156.  *
  2157.  *--------------------------------------------------------------
  2158.  */
  2159.  
  2160. static void
  2161. MenuEventProc(clientData, eventPtr)
  2162.     ClientData clientData;    /* Information about window. */
  2163.     XEvent *eventPtr;        /* Information about event. */
  2164. {
  2165.     Menu *menuPtr = (Menu *) clientData;
  2166.     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
  2167.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2168.     } else if (eventPtr->type == ConfigureNotify) {
  2169.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2170.     } else if (eventPtr->type == DestroyNotify) {
  2171.     if (menuPtr->tkwin != NULL) {
  2172.         menuPtr->tkwin = NULL;
  2173.         Tcl_DeleteCommand(menuPtr->interp,
  2174.             Tcl_GetCommandName(menuPtr->interp, menuPtr->widgetCmd));
  2175.     }
  2176.     if (menuPtr->flags & REDRAW_PENDING) {
  2177.         Tcl_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
  2178.     }
  2179.     if (menuPtr->flags & RESIZE_PENDING) {
  2180.         Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
  2181.     }
  2182.     Tcl_EventuallyFree((ClientData) menuPtr, DestroyMenu);
  2183.     }
  2184. }
  2185.  
  2186. /*
  2187.  *----------------------------------------------------------------------
  2188.  *
  2189.  * MenuCmdDeletedProc --
  2190.  *
  2191.  *    This procedure is invoked when a widget command is deleted.  If
  2192.  *    the widget isn't already in the process of being destroyed,
  2193.  *    this command destroys it.
  2194.  *
  2195.  * Results:
  2196.  *    None.
  2197.  *
  2198.  * Side effects:
  2199.  *    The widget is destroyed.
  2200.  *
  2201.  *----------------------------------------------------------------------
  2202.  */
  2203.  
  2204. static void
  2205. MenuCmdDeletedProc(clientData)
  2206.     ClientData clientData;    /* Pointer to widget record for widget. */
  2207. {
  2208.     Menu *menuPtr = (Menu *) clientData;
  2209.     Tk_Window tkwin = menuPtr->tkwin;
  2210.  
  2211.     /*
  2212.      * This procedure could be invoked either because the window was
  2213.      * destroyed and the command was then deleted (in which case tkwin
  2214.      * is NULL) or because the command was deleted, and then this procedure
  2215.      * destroys the widget.
  2216.      */
  2217.  
  2218.     if (tkwin != NULL) {
  2219.     menuPtr->tkwin = NULL;
  2220.     Tk_DestroyWindow(tkwin);
  2221.     }
  2222. }
  2223.  
  2224. /*
  2225.  *----------------------------------------------------------------------
  2226.  *
  2227.  * MenuNewEntry --
  2228.  *
  2229.  *    This procedure allocates and initializes a new menu entry.
  2230.  *
  2231.  * Results:
  2232.  *    The return value is a pointer to a new menu entry structure,
  2233.  *    which has been malloc-ed, initialized, and entered into the
  2234.  *    entry array for the  menu.
  2235.  *
  2236.  * Side effects:
  2237.  *    Storage gets allocated.
  2238.  *
  2239.  *----------------------------------------------------------------------
  2240.  */
  2241.  
  2242. static MenuEntry *
  2243. MenuNewEntry(menuPtr, index, type)
  2244.     Menu *menuPtr;        /* Menu that will hold the new entry. */
  2245.     int index;            /* Where in the menu the new entry is to
  2246.                  * go. */
  2247.     int type;            /* The type of the new entry. */
  2248. {
  2249.     MenuEntry *mePtr;
  2250.     MenuEntry **newEntries;
  2251.     int i;
  2252.  
  2253.     /*
  2254.      * Create a new array of entries with an empty slot for the
  2255.      * new entry.
  2256.      */
  2257.  
  2258.     newEntries = (MenuEntry **) ckalloc((unsigned)
  2259.         ((menuPtr->numEntries+1)*sizeof(MenuEntry *)));
  2260.     for (i = 0; i < index; i++) {
  2261.     newEntries[i] = menuPtr->entries[i];
  2262.     }
  2263.     for (  ; i < menuPtr->numEntries; i++) {
  2264.     newEntries[i+1] = menuPtr->entries[i];
  2265.     }
  2266.     if (menuPtr->numEntries != 0) {
  2267.     ckfree((char *) menuPtr->entries);
  2268.     }
  2269.     menuPtr->entries = newEntries;
  2270.     menuPtr->numEntries++;
  2271.     menuPtr->entries[index] = mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry));
  2272.     mePtr->type = type;
  2273.     mePtr->menuPtr = menuPtr;
  2274.     mePtr->label = NULL;
  2275.     mePtr->labelLength = 0;
  2276.     mePtr->underline = -1;
  2277.     mePtr->bitmap = None;
  2278.     mePtr->imageString = NULL;
  2279.     mePtr->image = NULL;
  2280.     mePtr->selectImageString  = NULL;
  2281.     mePtr->selectImage = NULL;
  2282.     mePtr->accel = NULL;
  2283.     mePtr->accelLength = 0;
  2284.     mePtr->state = tkNormalUid;
  2285.     mePtr->height = 0;
  2286.     mePtr->y = 0;
  2287.     mePtr->indicatorDiameter = 0;
  2288.     mePtr->border = NULL;
  2289.     mePtr->fg = NULL;
  2290.     mePtr->activeBorder = NULL;
  2291.     mePtr->activeFg = NULL;
  2292.     mePtr->fontPtr = NULL;
  2293.     mePtr->textGC = None;
  2294.     mePtr->activeGC = None;
  2295.     mePtr->disabledGC = None;
  2296.     mePtr->indicatorOn = 1;
  2297.     mePtr->indicatorFg = NULL;
  2298.     mePtr->indicatorGC = None;
  2299.     mePtr->command = NULL;
  2300.     mePtr->name = NULL;
  2301.     mePtr->onValue = NULL;
  2302.     mePtr->offValue = NULL;
  2303.     mePtr->flags = 0;
  2304.     return mePtr;
  2305. }
  2306.  
  2307. /*
  2308.  *----------------------------------------------------------------------
  2309.  *
  2310.  * MenuAddOrInsert --
  2311.  *
  2312.  *    This procedure does all of the work of the "add" and "insert"
  2313.  *    widget commands, allowing the code for these to be shared.
  2314.  *
  2315.  * Results:
  2316.  *    A standard Tcl return value.
  2317.  *
  2318.  * Side effects:
  2319.  *    A new menu entry is created in menuPtr.
  2320.  *
  2321.  *----------------------------------------------------------------------
  2322.  */
  2323.  
  2324. static int
  2325. MenuAddOrInsert(interp, menuPtr, indexString, argc, argv)
  2326.     Tcl_Interp *interp;            /* Used for error reporting. */
  2327.     Menu *menuPtr;            /* Widget in which to create new
  2328.                      * entry. */
  2329.     char *indexString;            /* String describing index at which
  2330.                      * to insert.  NULL means insert at
  2331.                      * end. */
  2332.     int argc;                /* Number of elements in argv. */
  2333.     char **argv;            /* Arguments to command:  first arg
  2334.                      * is type of entry, others are
  2335.                      * config options. */
  2336. {
  2337.     int c, type, i, index;
  2338.     size_t length;
  2339.     MenuEntry *mePtr;
  2340.  
  2341.     if (indexString != NULL) {
  2342.     if (GetMenuIndex(interp, menuPtr, indexString, 1, &index) != TCL_OK) {
  2343.         return TCL_ERROR;
  2344.     }
  2345.     } else {
  2346.     index = menuPtr->numEntries;
  2347.     }
  2348.     if (index < 0) {
  2349.     Tcl_AppendResult(interp, "bad index \"", indexString, "\"",
  2350.          (char *) NULL);
  2351.     return TCL_ERROR;
  2352.     }
  2353.     if (menuPtr->tearOff && (index == 0)) {
  2354.     index = 1;
  2355.     }
  2356.  
  2357.     /*
  2358.      * Figure out the type of the new entry.
  2359.      */
  2360.  
  2361.     c = argv[0][0];
  2362.     length = strlen(argv[0]);
  2363.     if ((c == 'c') && (strncmp(argv[0], "cascade", length) == 0)
  2364.         && (length >= 2)) {
  2365.     type = CASCADE_ENTRY;
  2366.     } else if ((c == 'c') && (strncmp(argv[0], "checkbutton", length) == 0)
  2367.         && (length >= 2)) {
  2368.     type = CHECK_BUTTON_ENTRY;
  2369.     } else if ((c == 'c') && (strncmp(argv[0], "command", length) == 0)
  2370.         && (length >= 2)) {
  2371.     type = COMMAND_ENTRY;
  2372.     } else if ((c == 'r')
  2373.         && (strncmp(argv[0], "radiobutton", length) == 0)) {
  2374.     type = RADIO_BUTTON_ENTRY;
  2375.     } else if ((c == 's')
  2376.         && (strncmp(argv[0], "separator", length) == 0)) {
  2377.     type = SEPARATOR_ENTRY;
  2378.     } else {
  2379.     Tcl_AppendResult(interp, "bad menu entry type \"",
  2380.         argv[0], "\": must be cascade, checkbutton, ",
  2381.         "command, radiobutton, or separator", (char *) NULL);
  2382.     return TCL_ERROR;
  2383.     }
  2384.     mePtr = MenuNewEntry(menuPtr, index, type);
  2385.     if (ConfigureMenuEntry(interp, menuPtr, mePtr, index,
  2386.         argc-1, argv+1, 0) != TCL_OK) {
  2387.     DestroyMenuEntry((ClientData) mePtr);
  2388.     for (i = index+1; i < menuPtr->numEntries; i++) {
  2389.         menuPtr->entries[i-1] = menuPtr->entries[i];
  2390.     }
  2391.     menuPtr->numEntries--;
  2392.     return TCL_ERROR;
  2393.     }
  2394.     return TCL_OK;
  2395. }
  2396.  
  2397. /*
  2398.  *--------------------------------------------------------------
  2399.  *
  2400.  * MenuVarProc --
  2401.  *
  2402.  *    This procedure is invoked when someone changes the
  2403.  *    state variable associated with a radiobutton or checkbutton
  2404.  *    menu entry.  The entry's selected state is set to match
  2405.  *    the value of the variable.
  2406.  *
  2407.  * Results:
  2408.  *    NULL is always returned.
  2409.  *
  2410.  * Side effects:
  2411.  *    The menu entry may become selected or deselected.
  2412.  *
  2413.  *--------------------------------------------------------------
  2414.  */
  2415.  
  2416.     /* ARGSUSED */
  2417. static char *
  2418. MenuVarProc(clientData, interp, name1, name2, flags)
  2419.     ClientData clientData;    /* Information about menu entry. */
  2420.     Tcl_Interp *interp;        /* Interpreter containing variable. */
  2421.     char *name1;        /* First part of variable's name. */
  2422.     char *name2;        /* Second part of variable's name. */
  2423.     int flags;            /* Describes what just happened. */
  2424. {
  2425.     MenuEntry *mePtr = (MenuEntry *) clientData;
  2426.     Menu *menuPtr;
  2427.     char *value;
  2428.  
  2429.     menuPtr = mePtr->menuPtr;
  2430.  
  2431.     /*
  2432.      * If the variable is being unset, then re-establish the
  2433.      * trace unless the whole interpreter is going away.
  2434.      */
  2435.  
  2436.     if (flags & TCL_TRACE_UNSETS) {
  2437.     mePtr->flags &= ~ENTRY_SELECTED;
  2438.     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
  2439.         Tcl_TraceVar(interp, mePtr->name,
  2440.             TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  2441.             MenuVarProc, clientData);
  2442.     }
  2443.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2444.     return (char *) NULL;
  2445.     }
  2446.  
  2447.     /*
  2448.      * Use the value of the variable to update the selected status of
  2449.      * the menu entry.
  2450.      */
  2451.  
  2452.     value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
  2453.     if (value == NULL) {
  2454.     value = "";
  2455.     }
  2456.     if (strcmp(value, mePtr->onValue) == 0) {
  2457.     if (mePtr->flags & ENTRY_SELECTED) {
  2458.         return (char *) NULL;
  2459.     }
  2460.     mePtr->flags |= ENTRY_SELECTED;
  2461.     } else if (mePtr->flags & ENTRY_SELECTED) {
  2462.     mePtr->flags &= ~ENTRY_SELECTED;
  2463.     } else {
  2464.     return (char *) NULL;
  2465.     }
  2466.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2467.     return (char *) NULL;
  2468. }
  2469.  
  2470. /*
  2471.  *----------------------------------------------------------------------
  2472.  *
  2473.  * EventuallyRedrawMenu --
  2474.  *
  2475.  *    Arrange for an entry of a menu, or the whole menu, to be
  2476.  *    redisplayed at some point in the future.
  2477.  *
  2478.  * Results:
  2479.  *    None.
  2480.  *
  2481.  * Side effects:
  2482.  *    A when-idle hander is scheduled to do the redisplay, if there
  2483.  *    isn't one already scheduled.
  2484.  *
  2485.  *----------------------------------------------------------------------
  2486.  */
  2487.  
  2488. static void
  2489. EventuallyRedrawMenu(menuPtr, mePtr)
  2490.     register Menu *menuPtr;    /* Information about menu to redraw. */
  2491.     register MenuEntry *mePtr;    /* Entry to redraw.  NULL means redraw
  2492.                  * all the entries in the menu. */
  2493. {
  2494.     int i;
  2495.     if (menuPtr->tkwin == NULL) {
  2496.     return;
  2497.     }
  2498.     if (mePtr != NULL) {
  2499.     mePtr->flags |= ENTRY_NEEDS_REDISPLAY;
  2500.     } else {
  2501.     for (i = 0; i < menuPtr->numEntries; i++) {
  2502.         menuPtr->entries[i]->flags |= ENTRY_NEEDS_REDISPLAY;
  2503.     }
  2504.     }
  2505.     if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
  2506.         || (menuPtr->flags & REDRAW_PENDING)) {
  2507.     return;
  2508.     }
  2509.     Tcl_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
  2510.     menuPtr->flags |= REDRAW_PENDING;
  2511. }
  2512.  
  2513. /*
  2514.  *--------------------------------------------------------------
  2515.  *
  2516.  * PostSubmenu --
  2517.  *
  2518.  *    This procedure arranges for a particular submenu (i.e. the
  2519.  *    menu corresponding to a given cascade entry) to be
  2520.  *    posted.
  2521.  *
  2522.  * Results:
  2523.  *    A standard Tcl return result.  Errors may occur in the
  2524.  *    Tcl commands generated to post and unpost submenus.
  2525.  *
  2526.  * Side effects:
  2527.  *    If there is already a submenu posted, it is unposted.
  2528.  *    The new submenu is then posted.
  2529.  *
  2530.  *--------------------------------------------------------------
  2531.  */
  2532.  
  2533. static int
  2534. PostSubmenu(interp, menuPtr, mePtr)
  2535.     Tcl_Interp *interp;        /* Used for invoking sub-commands and
  2536.                  * reporting errors. */
  2537.     register Menu *menuPtr;    /* Information about menu as a whole. */
  2538.     register MenuEntry *mePtr;    /* Info about submenu that is to be
  2539.                  * posted.  NULL means make sure that
  2540.                  * no submenu is posted. */
  2541. {
  2542.     char string[30];
  2543.     int result, x, y;
  2544.     Tk_Window tkwin;
  2545.  
  2546.     if (mePtr == menuPtr->postedCascade) {
  2547.     return TCL_OK;
  2548.     }
  2549.  
  2550.     if (menuPtr->postedCascade != NULL) {
  2551.     /*
  2552.      * Note: when unposting a submenu, we have to redraw the entire
  2553.      * parent menu.  This is because of a combination of the following
  2554.      * things:
  2555.      * (a) the submenu partially overlaps the parent.
  2556.      * (b) the submenu specifies "save under", which causes the X
  2557.      *     server to make a copy of the information under it when it
  2558.      *     is posted.  When the submenu is unposted, the X server
  2559.      *     copies this data back and doesn't generate any Expose
  2560.      *     events for the parent.
  2561.      * (c) the parent may have redisplayed itself after the submenu
  2562.      *     was posted, in which case the saved information is no
  2563.      *     longer correct.
  2564.      * The simplest solution is just force a complete redisplay of
  2565.      * the parent.
  2566.      */
  2567.  
  2568.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2569.     result = Tcl_VarEval(interp, menuPtr->postedCascade->name,
  2570.         " unpost", (char *) NULL);
  2571.     menuPtr->postedCascade = NULL;
  2572.     if (result != TCL_OK) {
  2573.         return result;
  2574.     }
  2575.     }
  2576.  
  2577.     if ((mePtr != NULL) && (mePtr->name != NULL)
  2578.         && Tk_IsMapped(menuPtr->tkwin)) {
  2579.     /*
  2580.      * Make sure that the cascaded submenu is a child of the
  2581.      * parent menu.
  2582.      */
  2583.  
  2584.     tkwin = Tk_NameToWindow(interp, mePtr->name, menuPtr->tkwin);
  2585.     if (tkwin == NULL) {
  2586.         return TCL_ERROR;
  2587.     }
  2588.     if (Tk_Parent(tkwin) != menuPtr->tkwin) {
  2589.         Tcl_AppendResult(interp, "cascaded sub-menu ",
  2590.             Tk_PathName(tkwin), " must be a child of ",
  2591.             Tk_PathName(menuPtr->tkwin), (char *) NULL);
  2592.         return TCL_ERROR;
  2593.     }
  2594.  
  2595.     /*
  2596.      * Position the cascade with its upper left corner slightly
  2597.      * below and to the left of the upper right corner of the
  2598.      * menu entry (this is an attempt to match Motif behavior).
  2599.      */
  2600.     Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
  2601.     x += Tk_Width(menuPtr->tkwin) - menuPtr->borderWidth
  2602.         - menuPtr->activeBorderWidth - 2;
  2603.     y += mePtr->y + menuPtr->activeBorderWidth + 2;
  2604.     sprintf(string, "%d %d", x, y);
  2605.     result = Tcl_VarEval(interp, mePtr->name, " post ", string,
  2606.         (char *) NULL);
  2607.     if (result != TCL_OK) {
  2608.         return result;
  2609.     }
  2610.     menuPtr->postedCascade = mePtr;
  2611.     }
  2612.     return TCL_OK;
  2613. }
  2614.  
  2615. /*
  2616.  *----------------------------------------------------------------------
  2617.  *
  2618.  * ActivateMenuEntry --
  2619.  *
  2620.  *    This procedure is invoked to make a particular menu entry
  2621.  *    the active one, deactivating any other entry that might
  2622.  *    currently be active.
  2623.  *
  2624.  * Results:
  2625.  *    The return value is a standard Tcl result (errors can occur
  2626.  *    while posting and unposting submenus).
  2627.  *
  2628.  * Side effects:
  2629.  *    Menu entries get redisplayed, and the active entry changes.
  2630.  *    Submenus may get posted and unposted.
  2631.  *
  2632.  *----------------------------------------------------------------------
  2633.  */
  2634.  
  2635. static int
  2636. ActivateMenuEntry(menuPtr, index)
  2637.     register Menu *menuPtr;        /* Menu in which to activate. */
  2638.     int index;                /* Index of entry to activate, or
  2639.                      * -1 to deactivate all entries. */
  2640. {
  2641.     register MenuEntry *mePtr;
  2642.     int result = TCL_OK;
  2643.  
  2644.     if (menuPtr->active >= 0) {
  2645.     mePtr = menuPtr->entries[menuPtr->active];
  2646.  
  2647.     /*
  2648.      * Don't change the state unless it's currently active (state
  2649.      * might already have been changed to disabled).
  2650.      */
  2651.  
  2652.     if (mePtr->state == tkActiveUid) {
  2653.         mePtr->state = tkNormalUid;
  2654.     }
  2655.     EventuallyRedrawMenu(menuPtr, menuPtr->entries[menuPtr->active]);
  2656.     }
  2657.     menuPtr->active = index;
  2658.     if (index >= 0) {
  2659.     mePtr = menuPtr->entries[index];
  2660.     mePtr->state = tkActiveUid;
  2661.     EventuallyRedrawMenu(menuPtr, mePtr);
  2662.     }
  2663.     return result;
  2664. }
  2665.  
  2666. /*
  2667.  *----------------------------------------------------------------------
  2668.  *
  2669.  * MenuImageProc --
  2670.  *
  2671.  *    This procedure is invoked by the image code whenever the manager
  2672.  *    for an image does something that affects the size of contents
  2673.  *    of an image displayed in a menu entry.
  2674.  *
  2675.  * Results:
  2676.  *    None.
  2677.  *
  2678.  * Side effects:
  2679.  *    Arranges for the menu to get redisplayed.
  2680.  *
  2681.  *----------------------------------------------------------------------
  2682.  */
  2683.  
  2684. static void
  2685. MenuImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
  2686.     ClientData clientData;        /* Pointer to widget record. */
  2687.     int x, y;                /* Upper left pixel (within image)
  2688.                          * that must be redisplayed. */
  2689.     int width, height;        /* Dimensions of area to redisplay
  2690.                      * (may be <= 0). */
  2691.     int imgWidth, imgHeight;        /* New dimensions of image. */
  2692. {
  2693.     register Menu *menuPtr = ((MenuEntry *) clientData)->menuPtr;
  2694.  
  2695.     if ((menuPtr->tkwin != NULL) && !(menuPtr->flags & RESIZE_PENDING)) {
  2696.     menuPtr->flags |= RESIZE_PENDING;
  2697.     Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  2698.     }
  2699. }
  2700.  
  2701. /*
  2702.  *----------------------------------------------------------------------
  2703.  *
  2704.  * MenuSelectImageProc --
  2705.  *
  2706.  *    This procedure is invoked by the image code whenever the manager
  2707.  *    for an image does something that affects the size of contents
  2708.  *    of an image displayed in a menu entry when it is selected.
  2709.  *
  2710.  * Results:
  2711.  *    None.
  2712.  *
  2713.  * Side effects:
  2714.  *    Arranges for the menu to get redisplayed.
  2715.  *
  2716.  *----------------------------------------------------------------------
  2717.  */
  2718.  
  2719. static void
  2720. MenuSelectImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
  2721.     ClientData clientData;        /* Pointer to widget record. */
  2722.     int x, y;                /* Upper left pixel (within image)
  2723.                      * that must be redisplayed. */
  2724.     int width, height;            /* Dimensions of area to redisplay
  2725.                      * (may be <= 0). */
  2726.     int imgWidth, imgHeight;        /* New dimensions of image. */
  2727. {
  2728.     register MenuEntry *mePtr = (MenuEntry *) clientData;
  2729.  
  2730.     if ((mePtr->flags & ENTRY_SELECTED)
  2731.         && !(mePtr->menuPtr->flags & REDRAW_PENDING)) {
  2732.     mePtr->menuPtr->flags |= REDRAW_PENDING;
  2733.     Tcl_DoWhenIdle(DisplayMenu, (ClientData) mePtr->menuPtr);
  2734.     }
  2735. }
  2736.