home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / tcl / tk3.3b1 / tkMenu.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-16  |  63.8 KB  |  2,093 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-1993 The Regents of the University of California.
  10.  * All rights reserved.
  11.  *
  12.  * Permission is hereby granted, without written agreement and without
  13.  * license or royalty fees, to use, copy, modify, and distribute this
  14.  * software and its documentation for any purpose, provided that the
  15.  * above copyright notice and the following two paragraphs appear in
  16.  * all copies of this software.
  17.  * 
  18.  * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
  19.  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  20.  * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
  21.  * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22.  *
  23.  * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  24.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  25.  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
  26.  * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
  27.  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  28.  */
  29.  
  30. #ifndef lint
  31. static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkMenu.c,v 1.52 93/06/16 17:16:03 ouster Exp $ SPRITE (Berkeley)";
  32. #endif
  33.  
  34. #include "tkConfig.h"
  35. #include "default.h"
  36. #include "tkInt.h"
  37.  
  38. /*
  39.  * One of the following data structures is kept for each entry of each
  40.  * menu managed by this file:
  41.  */
  42.  
  43. typedef struct MenuEntry {
  44.     int type;            /* Type of menu entry;  see below for
  45.                  * valid types. */
  46.     struct Menu *menuPtr;    /* Menu with which this entry is associated. */
  47.     char *label;        /* Main text label displayed in entry (NULL
  48.                  * if no label).  Malloc'ed. */
  49.     int labelLength;        /* Number of non-NULL characters in label. */
  50.     int underline;        /* Index of character to underline. */
  51.     Pixmap bitmap;        /* Bitmap to display in menu entry, or None.
  52.                  * If not None then label is ignored. */
  53.     char *accel;        /* Accelerator string displayed at right
  54.                  * of menu entry.  NULL means no such
  55.                  * accelerator.  Malloc'ed. */
  56.     int accelLength;        /* Number of non-NULL characters in
  57.                  * accelerator. */
  58.  
  59.     /*
  60.      * Information related to displaying entry:
  61.      */
  62.  
  63.     Tk_Uid state;        /* State of button for display purposes:
  64.                  * normal, active, or disabled. */
  65.     int height;            /* Number of pixels occupied by entry in
  66.                  * vertical dimension. */
  67.     int y;            /* Y-coordinate of topmost pixel in entry. */
  68.     int selectorDiameter;    /* Size of selector display, in pixels. */
  69.     Tk_3DBorder border;        /* Structure used to draw background for
  70.                  * entry.  NULL means use overall border
  71.                  * for menu. */
  72.     Tk_3DBorder activeBorder;    /* Used to draw background and border when
  73.                  * element is active.  NULL means use
  74.                  * activeBorder from menu. */
  75.     XFontStruct *fontPtr;    /* Text font for menu entries.  NULL means
  76.                  * use overall font for menu. */
  77.     GC textGC;            /* GC for drawing text in entry.  NULL means
  78.                  * use overall textGC for menu. */
  79.     GC activeGC;        /* GC for drawing text in entry when active.
  80.                  * NULL means use overall activeGC for
  81.                  * menu. */
  82.     GC disabledGC;        /* Used to produce disabled effect for entry.
  83.                  * NULL means use overall disabledGC from
  84.                  * menu structure.  See comments for
  85.                  * disabledFg in menu structure for more
  86.                  * information. */
  87.  
  88.     /*
  89.      * Information used to implement this entry's action:
  90.      */
  91.  
  92.     char *command;        /* Command to invoke when entry is invoked.
  93.                  * Malloc'ed. */
  94.     char *name;            /* Name of variable (for check buttons and
  95.                  * radio buttons) or menu (for cascade
  96.                  * entries).  Malloc'ed.*/
  97.     char *onValue;        /* Value to store in variable when selected
  98.                  * (only for radio and check buttons).
  99.                  * Malloc'ed. */
  100.     char *offValue;        /* Value to store in variable when not
  101.                  * selected (only for check buttons).
  102.                  * Malloc'ed. */
  103.  
  104.     /*
  105.      * Miscellaneous information:
  106.      */
  107.  
  108.     int flags;            /* Various flags.  See below for definitions. */
  109. } MenuEntry;
  110.  
  111. /*
  112.  * Flag values defined for menu entries:
  113.  *
  114.  * ENTRY_SELECTED:        Non-zero means this is a radio or check
  115.  *                button and that it should be drawn in
  116.  *                the "selected" state.
  117.  * ENTRY_NEEDS_REDISPLAY:    Non-zero means the entry should be redisplayed.
  118.  */
  119.  
  120. #define ENTRY_SELECTED        1
  121. #define ENTRY_NEEDS_REDISPLAY    4
  122.  
  123. /*
  124.  * Types defined for MenuEntries:
  125.  */
  126.  
  127. #define COMMAND_ENTRY        0
  128. #define SEPARATOR_ENTRY        1
  129. #define CHECK_BUTTON_ENTRY    2
  130. #define RADIO_BUTTON_ENTRY    3
  131. #define CASCADE_ENTRY        4
  132.  
  133. /*
  134.  * Mask bits for above types:
  135.  */
  136.  
  137. #define COMMAND_MASK        TK_CONFIG_USER_BIT
  138. #define SEPARATOR_MASK        (TK_CONFIG_USER_BIT << 1)
  139. #define CHECK_BUTTON_MASK    (TK_CONFIG_USER_BIT << 2)
  140. #define RADIO_BUTTON_MASK    (TK_CONFIG_USER_BIT << 3)
  141. #define CASCADE_MASK        (TK_CONFIG_USER_BIT << 4)
  142. #define ALL_MASK        (COMMAND_MASK | SEPARATOR_MASK \
  143.     | CHECK_BUTTON_MASK | RADIO_BUTTON_MASK | CASCADE_MASK)
  144.  
  145. /*
  146.  * Configuration specs for individual menu entries:
  147.  */
  148.  
  149. static Tk_ConfigSpec entryConfigSpecs[] = {
  150.     {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
  151.     DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(MenuEntry, activeBorder),
  152.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  153.     |TK_CONFIG_NULL_OK},
  154.     {TK_CONFIG_STRING, "-accelerator", (char *) NULL, (char *) NULL,
  155.     DEF_MENU_ENTRY_ACCELERATOR, Tk_Offset(MenuEntry, accel),
  156.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  157.     |TK_CONFIG_NULL_OK},
  158.     {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
  159.     DEF_MENU_ENTRY_BG, Tk_Offset(MenuEntry, border),
  160.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  161.     |TK_CONFIG_NULL_OK},
  162.     {TK_CONFIG_BITMAP, "-bitmap", (char *) NULL, (char *) NULL,
  163.     DEF_MENU_ENTRY_BITMAP, Tk_Offset(MenuEntry, bitmap),
  164.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  165.     |TK_CONFIG_NULL_OK},
  166.     {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL,
  167.     DEF_MENU_ENTRY_COMMAND, Tk_Offset(MenuEntry, command),
  168.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  169.     |TK_CONFIG_NULL_OK},
  170.     {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
  171.     DEF_MENU_ENTRY_FONT, Tk_Offset(MenuEntry, fontPtr),
  172.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  173.     |TK_CONFIG_NULL_OK},
  174.     {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL,
  175.     DEF_MENU_ENTRY_LABEL, Tk_Offset(MenuEntry, label),
  176.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
  177.     {TK_CONFIG_STRING, "-menu", (char *) NULL, (char *) NULL,
  178.     DEF_MENU_ENTRY_MENU, Tk_Offset(MenuEntry, name),
  179.     CASCADE_MASK|TK_CONFIG_NULL_OK},
  180.     {TK_CONFIG_STRING, "-offvalue", (char *) NULL, (char *) NULL,
  181.     DEF_MENU_ENTRY_OFF_VALUE, Tk_Offset(MenuEntry, offValue),
  182.     CHECK_BUTTON_MASK},
  183.     {TK_CONFIG_UID, "-state", (char *) NULL, (char *) NULL,
  184.     DEF_MENU_ENTRY_STATE, Tk_Offset(MenuEntry, state),
  185.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  186.     |TK_CONFIG_DONT_SET_DEFAULT},
  187.     {TK_CONFIG_STRING, "-onvalue", (char *) NULL, (char *) NULL,
  188.     DEF_MENU_ENTRY_ON_VALUE, Tk_Offset(MenuEntry, onValue),
  189.     CHECK_BUTTON_MASK},
  190.     {TK_CONFIG_STRING, "-value", (char *) NULL, (char *) NULL,
  191.     DEF_MENU_ENTRY_VALUE, Tk_Offset(MenuEntry, onValue),
  192.     RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
  193.     {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
  194.     DEF_MENU_ENTRY_CHECK_VARIABLE, Tk_Offset(MenuEntry, name),
  195.     CHECK_BUTTON_MASK|TK_CONFIG_NULL_OK},
  196.     {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
  197.     DEF_MENU_ENTRY_RADIO_VARIABLE, Tk_Offset(MenuEntry, name),
  198.     RADIO_BUTTON_MASK},
  199.     {TK_CONFIG_INT, "-underline", (char *) NULL, (char *) NULL,
  200.     DEF_MENU_ENTRY_UNDERLINE, Tk_Offset(MenuEntry, underline),
  201.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  202.     |TK_CONFIG_DONT_SET_DEFAULT},
  203.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  204.     (char *) NULL, 0, 0}
  205. };
  206.  
  207. /*
  208.  * A data structure of the following type is kept for each
  209.  * menu managed by this file:
  210.  */
  211.  
  212. typedef struct Menu {
  213.     Tk_Window tkwin;        /* Window that embodies the pane.  NULL
  214.                  * means that the window has been destroyed
  215.                  * but the data structures haven't yet been
  216.                  * cleaned up.*/
  217.     Display *display;        /* Display containing widget.  Needed, among
  218.                  * other things, so that resources can be
  219.                  * freed up even after tkwin has gone away. */
  220.     Tcl_Interp *interp;        /* Interpreter associated with menu. */
  221.     MenuEntry **entries;    /* Array of pointers to all the entries
  222.                  * in the menu.  NULL means no entries. */
  223.     int numEntries;        /* Number of elements in entries. */
  224.     int active;            /* Index of active entry.  -1 means
  225.                  * nothing active. */
  226.  
  227.     /*
  228.      * Information used when displaying widget:
  229.      */
  230.  
  231.     Tk_3DBorder border;        /* Structure used to draw 3-D
  232.                  * border and background for menu. */
  233.     int borderWidth;        /* Width of border around whole menu. */
  234.     Tk_3DBorder activeBorder;    /* Used to draw background and border for
  235.                  * active element (if any). */
  236.     int activeBorderWidth;    /* Width of border around active element. */
  237.     XFontStruct *fontPtr;    /* Text font for menu entries. */
  238.     XColor *fg;            /* Foreground color for entries. */
  239.     GC textGC;            /* GC for drawing text and other features
  240.                  * of menu entries. */
  241.     XColor *disabledFg;        /* Foreground color when disabled.  NULL
  242.                  * means use normalFg with a 50% stipple
  243.                  * instead. */
  244.     Pixmap gray;        /* Bitmap for drawing disabled entries in
  245.                  * a stippled fashion.  None means not
  246.                  * allocated yet. */
  247.     GC disabledGC;        /* Used to produce disabled effect.  If
  248.                  * disabledFg isn't NULL, this GC is used to
  249.                  * draw text and icons for disabled entries.
  250.                  * Otherwise text and icons are drawn with
  251.                  * normalGC and this GC is used to stipple
  252.                  * background across them. */
  253.     XColor *activeFg;        /* Foreground color for active entry. */
  254.     GC activeGC;        /* GC for drawing active entry. */
  255.     XColor *selectorFg;        /* Color for selectors in radio and check
  256.                  * button entries. */
  257.     GC selectorGC;        /* For drawing selectors. */
  258.     int selectorSpace;        /* Number of pixels to allow for displaying
  259.                  * selectors in menu entries (includes extra
  260.                  * space around selector). */
  261.     int labelWidth;        /* Number of pixels to allow for displaying
  262.                  * labels in menu entries. */
  263.  
  264.     /*
  265.      * Miscellaneous information:
  266.      */
  267.  
  268.     Cursor cursor;        /* Current cursor for window, or None. */
  269.     char *postCommand;        /* Command to execute just before posting
  270.                  * this menu, or NULL.  Malloc-ed. */
  271.     MenuEntry *postedCascade;    /* Points to menu entry for cascaded
  272.                  * submenu that is currently posted, or
  273.                  * NULL if no submenu posted. */
  274.     int flags;            /* Various flags;  see below for
  275.                  * definitions. */
  276. } Menu;
  277.  
  278. /*
  279.  * Flag bits for menus:
  280.  *
  281.  * REDRAW_PENDING:        Non-zero means a DoWhenIdle handler
  282.  *                has already been queued to redraw
  283.  *                this window.
  284.  * RESIZE_PENDING:        Non-zero means a call to ComputeMenuGeometry
  285.  *                has already been scheduled.
  286.  */
  287.  
  288. #define REDRAW_PENDING        1
  289. #define RESIZE_PENDING        2
  290.  
  291. /*
  292.  * Configuration specs valid for the menu as a whole:
  293.  */
  294.  
  295. static Tk_ConfigSpec configSpecs[] = {
  296.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  297.     DEF_MENU_ACTIVE_BG_COLOR, Tk_Offset(Menu, activeBorder),
  298.     TK_CONFIG_COLOR_ONLY},
  299.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  300.     DEF_MENU_ACTIVE_BG_MONO, Tk_Offset(Menu, activeBorder),
  301.     TK_CONFIG_MONO_ONLY},
  302.     {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth",
  303.     DEF_MENU_ACTIVE_BORDER_WIDTH, Tk_Offset(Menu, activeBorderWidth), 0},
  304.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  305.     DEF_MENU_ACTIVE_FG_COLOR, Tk_Offset(Menu, activeFg),
  306.     TK_CONFIG_COLOR_ONLY},
  307.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  308.     DEF_MENU_ACTIVE_FG_MONO, Tk_Offset(Menu, activeFg),
  309.     TK_CONFIG_MONO_ONLY},
  310.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  311.     DEF_MENU_BG_COLOR, Tk_Offset(Menu, border), TK_CONFIG_COLOR_ONLY},
  312.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  313.     DEF_MENU_BG_MONO, Tk_Offset(Menu, border), TK_CONFIG_MONO_ONLY},
  314.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  315.     (char *) NULL, 0, 0},
  316.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  317.     (char *) NULL, 0, 0},
  318.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  319.     DEF_MENU_BORDER_WIDTH, Tk_Offset(Menu, borderWidth), 0},
  320.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  321.     DEF_MENU_CURSOR, Tk_Offset(Menu, cursor), TK_CONFIG_NULL_OK},
  322.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  323.     "DisabledForeground", DEF_MENU_DISABLED_FG_COLOR,
  324.     Tk_Offset(Menu, disabledFg), TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
  325.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  326.     "DisabledForeground", DEF_MENU_DISABLED_FG_MONO,
  327.     Tk_Offset(Menu, disabledFg), TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
  328.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  329.     (char *) NULL, 0, 0},
  330.     {TK_CONFIG_FONT, "-font", "font", "Font",
  331.     DEF_MENU_FONT, Tk_Offset(Menu, fontPtr), 0},
  332.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  333.     DEF_MENU_FG, Tk_Offset(Menu, fg), 0},
  334.     {TK_CONFIG_STRING, "-postcommand", "postCommand", "Command",
  335.     DEF_MENU_POST_COMMAND, Tk_Offset(Menu, postCommand), TK_CONFIG_NULL_OK},
  336.     {TK_CONFIG_COLOR, "-selector", "selector", "Foreground",
  337.     DEF_MENU_SELECTOR_COLOR, Tk_Offset(Menu, selectorFg),
  338.     TK_CONFIG_COLOR_ONLY},
  339.     {TK_CONFIG_COLOR, "-selector", "selector", "Foreground",
  340.     DEF_MENU_SELECTOR_MONO, Tk_Offset(Menu, selectorFg),
  341.     TK_CONFIG_MONO_ONLY},
  342.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  343.     (char *) NULL, 0, 0}
  344. };
  345.  
  346. /*
  347.  * Various geometry definitions:
  348.  */
  349.  
  350. #define CASCADE_ARROW_HEIGHT 10
  351. #define CASCADE_ARROW_WIDTH 8
  352. #define MARGIN_WIDTH 2
  353.  
  354. /*
  355.  * Forward declarations for procedures defined later in this file:
  356.  */
  357.  
  358. static int        ActivateMenuEntry _ANSI_ARGS_((Menu *menuPtr,
  359.                 int index));
  360. static void        ComputeMenuGeometry _ANSI_ARGS_((
  361.                 ClientData clientData));
  362. static int        ConfigureMenu _ANSI_ARGS_((Tcl_Interp *interp,
  363.                 Menu *menuPtr, int argc, char **argv,
  364.                 int flags));
  365. static int        ConfigureMenuEntry _ANSI_ARGS_((Tcl_Interp *interp,
  366.                 Menu *menuPtr, MenuEntry *mePtr, int index,
  367.                 int argc, char **argv, int flags));
  368. static void        DestroyMenu _ANSI_ARGS_((ClientData clientData));
  369. static void        DestroyMenuEntry _ANSI_ARGS_((ClientData clientData));
  370. static void        DisplayMenu _ANSI_ARGS_((ClientData clientData));
  371. static void        EventuallyRedrawMenu _ANSI_ARGS_((Menu *menuPtr,
  372.                 MenuEntry *mePtr));
  373. static int        GetMenuIndex _ANSI_ARGS_((Tcl_Interp *interp,
  374.                 Menu *menuPtr, char *string, int *indexPtr));
  375. static void        MenuEventProc _ANSI_ARGS_((ClientData clientData,
  376.                 XEvent *eventPtr));
  377. static char *        MenuVarProc _ANSI_ARGS_((ClientData clientData,
  378.                 Tcl_Interp *interp, char *name1, char *name2,
  379.                 int flags));
  380. static int        MenuWidgetCmd _ANSI_ARGS_((ClientData clientData,
  381.                 Tcl_Interp *interp, int argc, char **argv));
  382. static int        PostSubmenu _ANSI_ARGS_((Tcl_Interp *interp,
  383.                 Menu *menuPtr, MenuEntry *mePtr));
  384.  
  385. /*
  386.  *--------------------------------------------------------------
  387.  *
  388.  * Tk_MenuCmd --
  389.  *
  390.  *    This procedure is invoked to process the "menu" Tcl
  391.  *    command.  See the user documentation for details on
  392.  *    what it does.
  393.  *
  394.  * Results:
  395.  *    A standard Tcl result.
  396.  *
  397.  * Side effects:
  398.  *    See the user documentation.
  399.  *
  400.  *--------------------------------------------------------------
  401.  */
  402.  
  403. int
  404. Tk_MenuCmd(clientData, interp, argc, argv)
  405.     ClientData clientData;    /* Main window associated with
  406.                  * interpreter. */
  407.     Tcl_Interp *interp;        /* Current interpreter. */
  408.     int argc;            /* Number of arguments. */
  409.     char **argv;        /* Argument strings. */
  410. {
  411.     Tk_Window tkwin = (Tk_Window) clientData;
  412.     Tk_Window new;
  413.     register Menu *menuPtr;
  414.     XSetWindowAttributes atts;
  415.  
  416.     if (argc < 2) {
  417.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  418.         argv[0], " pathName ?options?\"", (char *) NULL);
  419.     return TCL_ERROR;
  420.     }
  421.  
  422.     /*
  423.      * Create the new window.  Set override-redirect so the window
  424.      * manager won't add a border or argue about placement, and set
  425.      * save-under so that the window can pop up and down without a
  426.      * lot of re-drawing.
  427.      */
  428.  
  429.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
  430.     if (new == NULL) {
  431.     return TCL_ERROR;
  432.     }
  433.     atts.override_redirect = True;
  434.     atts.save_under = True;
  435.     Tk_ChangeWindowAttributes(new, CWOverrideRedirect|CWSaveUnder, &atts);
  436.  
  437.     /*
  438.      * Initialize the data structure for the menu.
  439.      */
  440.  
  441.     menuPtr = (Menu *) ckalloc(sizeof(Menu));
  442.     menuPtr->tkwin = new;
  443.     menuPtr->display = Tk_Display(new);
  444.     menuPtr->interp = interp;
  445.     menuPtr->entries = NULL;
  446.     menuPtr->numEntries = 0;
  447.     menuPtr->active = -1;
  448.     menuPtr->textGC = None;
  449.     menuPtr->gray = None;
  450.     menuPtr->disabledGC = None;
  451.     menuPtr->activeGC = None;
  452.     menuPtr->selectorGC = None;
  453.     menuPtr->postedCascade = NULL;
  454.     menuPtr->flags = 0;
  455.  
  456.     Tk_SetClass(new, "Menu");
  457.     Tk_CreateEventHandler(menuPtr->tkwin, ExposureMask|StructureNotifyMask,
  458.         MenuEventProc, (ClientData) menuPtr);
  459.     Tcl_CreateCommand(interp, Tk_PathName(menuPtr->tkwin), MenuWidgetCmd,
  460.         (ClientData) menuPtr, (void (*)()) NULL);
  461.     if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
  462.     goto error;
  463.     }
  464.  
  465.     interp->result = Tk_PathName(menuPtr->tkwin);
  466.     return TCL_OK;
  467.  
  468.     error:
  469.     Tk_DestroyWindow(menuPtr->tkwin);
  470.     return TCL_ERROR;
  471. }
  472.  
  473. /*
  474.  *--------------------------------------------------------------
  475.  *
  476.  * MenuWidgetCmd --
  477.  *
  478.  *    This procedure is invoked to process the Tcl command
  479.  *    that corresponds to a widget managed by this module.
  480.  *    See the user documentation for details on what it does.
  481.  *
  482.  * Results:
  483.  *    A standard Tcl result.
  484.  *
  485.  * Side effects:
  486.  *    See the user documentation.
  487.  *
  488.  *--------------------------------------------------------------
  489.  */
  490.  
  491. static int
  492. MenuWidgetCmd(clientData, interp, argc, argv)
  493.     ClientData clientData;    /* Information about menu widget. */
  494.     Tcl_Interp *interp;        /* Current interpreter. */
  495.     int argc;            /* Number of arguments. */
  496.     char **argv;        /* Argument strings. */
  497. {
  498.     register Menu *menuPtr = (Menu *) clientData;
  499.     register MenuEntry *mePtr;
  500.     int result = TCL_OK;
  501.     int length, type;
  502.     char c;
  503.  
  504.     if (argc < 2) {
  505.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  506.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  507.     return TCL_ERROR;
  508.     }
  509.     Tk_Preserve((ClientData) menuPtr);
  510.     c = argv[1][0];
  511.     length = strlen(argv[1]);
  512.     if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
  513.         && (length >= 2)) {
  514.     int index;
  515.  
  516.     if (argc != 3) {
  517.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  518.             argv[0], " activate index\"", (char *) NULL);
  519.         goto error;
  520.     }
  521.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  522.         goto error;
  523.     }
  524.     if (menuPtr->active == index) {
  525.         goto done;
  526.     }
  527.     if (index >= 0) {
  528.         if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY)
  529.             || (menuPtr->entries[index]->state == tkDisabledUid)) {
  530.         index = -1;
  531.         }
  532.     }
  533.     result = ActivateMenuEntry(menuPtr, index);
  534.     } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
  535.         && (length >= 2)) {
  536.     MenuEntry **newEntries;
  537.  
  538.     if (argc < 3) {
  539.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  540.             argv[0], " add type ?options?\"", (char *) NULL);
  541.         goto error;
  542.     }
  543.  
  544.     /*
  545.      * Figure out the type of the new entry.
  546.      */
  547.  
  548.     c = argv[2][0];
  549.     length = strlen(argv[2]);
  550.     if ((c == 'c') && (strncmp(argv[2], "cascade", length) == 0)
  551.         && (length >= 2)) {
  552.         type = CASCADE_ENTRY;
  553.     } else if ((c == 'c') && (strncmp(argv[2], "checkbutton", length) == 0)
  554.         && (length >= 2)) {
  555.         type = CHECK_BUTTON_ENTRY;
  556.     } else if ((c == 'c') && (strncmp(argv[2], "command", length) == 0)
  557.         && (length >= 2)) {
  558.         type = COMMAND_ENTRY;
  559.     } else if ((c == 'r')
  560.         && (strncmp(argv[2], "radiobutton", length) == 0)) {
  561.         type = RADIO_BUTTON_ENTRY;
  562.     } else if ((c == 's')
  563.         && (strncmp(argv[2], "separator", length) == 0)) {
  564.         type = SEPARATOR_ENTRY;
  565.     } else {
  566.         Tcl_AppendResult(interp, "bad menu entry type \"",
  567.             argv[2], "\":  must be cascade, checkbutton, ",
  568.             "command, radiobutton, or separator", (char *) NULL);
  569.         goto error;
  570.     }
  571.  
  572.     /*
  573.      * Add a new entry to the end of the menu's array of entries,
  574.      * and process options for it.
  575.      */
  576.  
  577.     mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry));
  578.     newEntries = (MenuEntry **) ckalloc((unsigned)
  579.         ((menuPtr->numEntries+1)*sizeof(MenuEntry *)));
  580.     if (menuPtr->numEntries != 0) {
  581.         memcpy((VOID *) newEntries, (VOID *) menuPtr->entries,
  582.             menuPtr->numEntries*sizeof(MenuEntry *));
  583.         ckfree((char *) menuPtr->entries);
  584.     }
  585.     menuPtr->entries = newEntries;
  586.     menuPtr->entries[menuPtr->numEntries] = mePtr;
  587.     menuPtr->numEntries++;
  588.     mePtr->type = type;
  589.     mePtr->menuPtr = menuPtr;
  590.     mePtr->underline = -1;
  591.     mePtr->state = tkNormalUid;
  592.     mePtr->textGC = None;
  593.     mePtr->activeGC = None;
  594.     mePtr->disabledGC = None;
  595.     mePtr->name = NULL;
  596.     mePtr->flags = 0;
  597.     if (ConfigureMenuEntry(interp, menuPtr, mePtr, menuPtr->numEntries-1,
  598.         argc-3, argv+3, 0) != TCL_OK) {
  599.         DestroyMenuEntry((ClientData) mePtr);
  600.         menuPtr->numEntries--;
  601.         goto error;
  602.     }
  603.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
  604.     if (argc == 2) {
  605.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
  606.             (char *) menuPtr, (char *) NULL, 0);
  607.     } else if (argc == 3) {
  608.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
  609.             (char *) menuPtr, argv[2], 0);
  610.     } else {
  611.         result = ConfigureMenu(interp, menuPtr, argc-2, argv+2,
  612.             TK_CONFIG_ARGV_ONLY);
  613.     }
  614.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
  615.         && (length >= 2)) {
  616.     int first, last, i, numDeleted;
  617.  
  618.     if ((argc != 3) && (argc != 4)) {
  619.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  620.             argv[0], " delete first ?last?\"", (char *) NULL);
  621.         goto error;
  622.     }
  623.     if (GetMenuIndex(interp, menuPtr, argv[2], &first) != TCL_OK) {
  624.         goto error;
  625.     }
  626.     if (argc == 3) {
  627.         last = first;
  628.     } else {
  629.         if (GetMenuIndex(interp, menuPtr, argv[3], &last) != TCL_OK) {
  630.             goto error;
  631.         }
  632.     }
  633.     if ((first < 0) || (last < first)) {
  634.         goto done;
  635.     }
  636.     numDeleted = last + 1 - first;
  637.     for (i = first; i <= last; i++) {
  638.         Tk_EventuallyFree((ClientData) menuPtr->entries[i],
  639.                   DestroyMenuEntry);
  640.     }
  641.     for (i = last+1; i < menuPtr->numEntries; i++) {
  642.         menuPtr->entries[i-numDeleted] = menuPtr->entries[i];
  643.     }
  644.     menuPtr->numEntries -= numDeleted;
  645.     if ((menuPtr->active >= first) && (menuPtr->active <= last)) {
  646.         menuPtr->active = -1;
  647.     } else if (menuPtr->active > last) {
  648.         menuPtr->active -= numDeleted;
  649.     }
  650.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  651.         menuPtr->flags |= RESIZE_PENDING;
  652.         Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  653.     }
  654.     } else if ((c == 'd') && (strncmp(argv[1], "disable", length) == 0)
  655.         && (length >= 2)) {
  656.     int index;
  657.  
  658.     if (argc != 3) {
  659.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  660.             argv[0], " disable index\"", (char *) NULL);
  661.         goto error;
  662.     }
  663.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  664.         goto error;
  665.     }
  666.     if (index < 0) {
  667.         goto done;
  668.     }
  669.     menuPtr->entries[index]->state = tkDisabledUid;
  670.     if (menuPtr->active == index) {
  671.         menuPtr->active = -1;
  672.     }
  673.     EventuallyRedrawMenu(menuPtr, menuPtr->entries[index]);
  674.     } else if ((c == 'e') && (length >= 3)
  675.         && (strncmp(argv[1], "enable", length) == 0)) {
  676.     int index;
  677.  
  678.     if (argc != 3) {
  679.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  680.             argv[0], " enable index\"", (char *) NULL);
  681.         goto error;
  682.     }
  683.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  684.         goto error;
  685.     }
  686.     if (index < 0) {
  687.         goto done;
  688.     }
  689.     menuPtr->entries[index]->state = tkNormalUid;
  690.     EventuallyRedrawMenu(menuPtr, menuPtr->entries[index]);
  691.     } else if ((c == 'e') && (length >= 3)
  692.         && (strncmp(argv[1], "entryconfigure", length) == 0)) {
  693.     int index;
  694.  
  695.     if (argc < 3) {
  696.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  697.             argv[0], " entryconfigure index ?option value ...?\"",
  698.             (char *) NULL);
  699.         goto error;
  700.     }
  701.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  702.         goto error;
  703.     }
  704.     if (index < 0) {
  705.         goto done;
  706.     }
  707.     mePtr = menuPtr->entries[index];
  708.     Tk_Preserve((ClientData) mePtr);
  709.     if (argc == 3) {
  710.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
  711.             (char *) mePtr, (char *) NULL,
  712.             COMMAND_MASK << mePtr->type);
  713.     } else if (argc == 4) {
  714.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
  715.             (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
  716.     } else {
  717.         result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3,
  718.             argv+3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
  719.     }
  720.     Tk_Release((ClientData) mePtr);
  721.     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
  722.         && (length >= 3)) {
  723.     int index;
  724.  
  725.     if (argc != 3) {
  726.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  727.             argv[0], " index string\"", (char *) NULL);
  728.         goto error;
  729.     }
  730.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  731.         goto error;
  732.     }
  733.     if (index < 0) {
  734.         interp->result = "none";
  735.     } else {
  736.         sprintf(interp->result, "%d", index);
  737.     }
  738.     } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
  739.         && (length >= 3)) {
  740.     int index;
  741.  
  742.     if (argc != 3) {
  743.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  744.             argv[0], " invoke index\"", (char *) NULL);
  745.         goto error;
  746.     }
  747.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  748.         goto error;
  749.     }
  750.     if (index < 0) {
  751.         goto done;
  752.     }
  753.     mePtr = menuPtr->entries[index];
  754.     if (mePtr->state == tkDisabledUid) {
  755.         goto done;
  756.     }
  757.     Tk_Preserve((ClientData) mePtr);
  758.     if (mePtr->type == CHECK_BUTTON_ENTRY) {
  759.         if (mePtr->flags & ENTRY_SELECTED) {
  760.         Tcl_SetVar(interp, mePtr->name, mePtr->offValue,
  761.             TCL_GLOBAL_ONLY);
  762.         } else {
  763.         Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
  764.             TCL_GLOBAL_ONLY);
  765.         }
  766.     } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  767.         Tcl_SetVar(interp, mePtr->name, mePtr->onValue, TCL_GLOBAL_ONLY);
  768.     }
  769.     if ((mePtr->command != NULL) && (mePtr->type != CASCADE_ENTRY)) {
  770.         result = TkCopyAndGlobalEval(interp, mePtr->command);
  771.     }
  772.     Tk_Release((ClientData) mePtr);
  773.     } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)) {
  774.     int x, y, tmp, vRootX, vRootY;
  775.     unsigned int vRootWidth, vRootHeight;
  776.  
  777.     if (argc != 4) {
  778.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  779.             argv[0], " post x y\"", (char *) NULL);
  780.         goto error;
  781.     }
  782.     if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
  783.         || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
  784.         goto error;
  785.     }
  786.  
  787.     /*
  788.      * If there is a command for the menu, execute it.
  789.      */
  790.  
  791.     if (menuPtr->postCommand != NULL) {
  792.         result = TkCopyAndGlobalEval(menuPtr->interp,
  793.             menuPtr->postCommand);
  794.         if (result != TCL_OK) {
  795.         return result;
  796.         }
  797.     }
  798.  
  799.     /*
  800.      * Adjust the position of the menu if necessary to keep it
  801.      * visible on the screen.  There are two special tricks to
  802.      * make this work right:
  803.      *
  804.      * 1. If a virtual root window manager is being used then
  805.      *    the coordinates are in the virtual root window of
  806.      *    menuPtr's parent;  since the menu uses override-redirect
  807.      *    mode it will be in the *real* root window for the screen,
  808.      *    so we have to map the coordinates from the virtual root
  809.      *    (if any) to the real root.  Can't get the virtual root
  810.      *    from the menu itself (it will never be seen by the wm)
  811.      *    so use its parent instead (it would be better to have an
  812.      *    an option that names a window to use for this...).
  813.      * 2. The menu may not have been mapped yet, so its current size
  814.      *    might be the default 1x1.  To compute how much space it
  815.      *    needs, use its requested size, not its actual size.
  816.      */
  817.  
  818.     Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY,
  819.         &vRootWidth, &vRootHeight);
  820.     x += vRootX;
  821.     y += vRootY;
  822.     tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin))
  823.         - Tk_ReqWidth(menuPtr->tkwin);
  824.     if (x > tmp) {
  825.         x = tmp;
  826.     }
  827.     if (x < 0) {
  828.         x = 0;
  829.     }
  830.     tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin))
  831.         - Tk_ReqHeight(menuPtr->tkwin);
  832.     if (y > tmp) {
  833.         y = tmp;
  834.     }
  835.     if (y < 0) {
  836.         y = 0;
  837.     }
  838.     if ((x != Tk_X(menuPtr->tkwin)) || (y != Tk_Y(menuPtr->tkwin))) {
  839.         Tk_MoveToplevelWindow(menuPtr->tkwin, x, y);
  840.     }
  841.     if (!Tk_IsMapped(menuPtr->tkwin)) {
  842.         Tk_MapWindow(menuPtr->tkwin);
  843.     }
  844.     XRaiseWindow(menuPtr->display, Tk_WindowId(menuPtr->tkwin));
  845.     } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
  846.     if (argc != 2) {
  847.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  848.             argv[0], " unpost\"", (char *) NULL);
  849.         goto error;
  850.     }
  851.     Tk_UnmapWindow(menuPtr->tkwin);
  852.     if (result == TCL_OK) {
  853.         result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
  854.     }
  855.     } else if ((c == 'y') && (strncmp(argv[1], "yposition", length) == 0)) {
  856.     int index;
  857.  
  858.     if (argc != 3) {
  859.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  860.             argv[0], " yposition index\"", (char *) NULL);
  861.         goto error;
  862.     }
  863.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  864.         goto error;
  865.     }
  866.     if (index < 0) {
  867.         interp->result = "0";
  868.     } else {
  869.         sprintf(interp->result, "%d", menuPtr->entries[index]->y);
  870.     }
  871.     } else {
  872.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  873.         "\": must be activate, add, configure, delete, disable, ",
  874.         "enable, entryconfigure, index, invoke, post, ",
  875.         "unpost, or yposition", (char *) NULL);
  876.     goto error;
  877.     }
  878.     done:
  879.     Tk_Release((ClientData) menuPtr);
  880.     return result;
  881.  
  882.     error:
  883.     Tk_Release((ClientData) menuPtr);
  884.     return TCL_ERROR;
  885. }
  886.  
  887. /*
  888.  *----------------------------------------------------------------------
  889.  *
  890.  * DestroyMenu --
  891.  *
  892.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  893.  *    to clean up the internal structure of a menu at a safe time
  894.  *    (when no-one is using it anymore).
  895.  *
  896.  * Results:
  897.  *    None.
  898.  *
  899.  * Side effects:
  900.  *    Everything associated with the menu is freed up.
  901.  *
  902.  *----------------------------------------------------------------------
  903.  */
  904.  
  905. static void
  906. DestroyMenu(clientData)
  907.     ClientData clientData;    /* Info about menu widget. */
  908. {
  909.     register Menu *menuPtr = (Menu *) clientData;
  910.     int i;
  911.  
  912.     /*
  913.      * Free up all the stuff that requires special handling, then
  914.      * let Tk_FreeOptions handle all the standard option-related
  915.      * stuff.
  916.      */
  917.  
  918.     for (i = 0; i < menuPtr->numEntries; i++) {
  919.     DestroyMenuEntry((ClientData) menuPtr->entries[i]);
  920.     }
  921.     if (menuPtr->entries != NULL) {
  922.     ckfree((char *) menuPtr->entries);
  923.     }
  924.     if (menuPtr->textGC != None) {
  925.     Tk_FreeGC(menuPtr->display, menuPtr->textGC);
  926.     }
  927.     if (menuPtr->gray != None) {
  928.     Tk_FreeBitmap(menuPtr->display, menuPtr->gray);
  929.     }
  930.     if (menuPtr->disabledGC != None) {
  931.     Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
  932.     }
  933.     if (menuPtr->activeGC != None) {
  934.     Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
  935.     }
  936.     if (menuPtr->selectorGC != None) {
  937.     Tk_FreeGC(menuPtr->display, menuPtr->selectorGC);
  938.     }
  939.     Tk_FreeOptions(configSpecs, (char *) menuPtr, menuPtr->display, 0);
  940.     ckfree((char *) menuPtr);
  941. }
  942.  
  943. /*
  944.  *----------------------------------------------------------------------
  945.  *
  946.  * DestroyMenuEntry --
  947.  *
  948.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  949.  *    to clean up the internal structure of a menu entry at a safe time
  950.  *    (when no-one is using it anymore).
  951.  *
  952.  * Results:
  953.  *    None.
  954.  *
  955.  * Side effects:
  956.  *    Everything associated with the menu entry is freed up.
  957.  *
  958.  *----------------------------------------------------------------------
  959.  */
  960.  
  961. static void
  962. DestroyMenuEntry(clientData)
  963.     ClientData clientData;        /* Pointer to entry to be freed. */
  964. {
  965.     register MenuEntry *mePtr = (MenuEntry *) clientData;
  966.     Menu *menuPtr = mePtr->menuPtr;
  967.  
  968.     /*
  969.      * Free up all the stuff that requires special handling, then
  970.      * let Tk_FreeOptions handle all the standard option-related
  971.      * stuff.
  972.      */
  973.  
  974.     if (mePtr->name != NULL) {
  975.     Tcl_UntraceVar(menuPtr->interp, mePtr->name,
  976.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  977.         MenuVarProc, (ClientData) mePtr);
  978.     }
  979.     if (menuPtr->postedCascade == mePtr) {
  980.     /*
  981.      * Ignore errors while unposting the menu, since it's possible
  982.      * that the menu has already been deleted and the unpost will
  983.      * generate an error.
  984.      */
  985.  
  986.     PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
  987.     }
  988.     if (mePtr->textGC != NULL) {
  989.     Tk_FreeGC(menuPtr->display, mePtr->textGC);
  990.     }
  991.     if (mePtr->activeGC != NULL) {
  992.     Tk_FreeGC(menuPtr->display, mePtr->activeGC);
  993.     }
  994.     if (mePtr->disabledGC != NULL) {
  995.     Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
  996.     }
  997.     Tk_FreeOptions(configSpecs, (char *) mePtr, menuPtr->display, 
  998.         (COMMAND_MASK << mePtr->type));
  999.     ckfree((char *) mePtr);
  1000. }
  1001.  
  1002. /*
  1003.  *----------------------------------------------------------------------
  1004.  *
  1005.  * ConfigureMenu --
  1006.  *
  1007.  *    This procedure is called to process an argv/argc list, plus
  1008.  *    the Tk option database, in order to configure (or
  1009.  *    reconfigure) a menu widget.
  1010.  *
  1011.  * Results:
  1012.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  1013.  *    returned, then interp->result contains an error message.
  1014.  *
  1015.  * Side effects:
  1016.  *    Configuration information, such as colors, font, etc. get set
  1017.  *    for menuPtr;  old resources get freed, if there were any.
  1018.  *
  1019.  *----------------------------------------------------------------------
  1020.  */
  1021.  
  1022. static int
  1023. ConfigureMenu(interp, menuPtr, argc, argv, flags)
  1024.     Tcl_Interp *interp;        /* Used for error reporting. */
  1025.     register Menu *menuPtr;    /* Information about widget;  may or may
  1026.                  * not already have values for some fields. */
  1027.     int argc;            /* Number of valid entries in argv. */
  1028.     char **argv;        /* Arguments. */
  1029.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  1030. {
  1031.     XGCValues gcValues;
  1032.     GC newGC;
  1033.     unsigned long mask;
  1034.     int i;
  1035.  
  1036.     if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs,
  1037.         argc, argv, (char *) menuPtr, flags) != TCL_OK) {
  1038.     return TCL_ERROR;
  1039.     }
  1040.  
  1041.     /*
  1042.      * A few options need special processing, such as setting the
  1043.      * background from a 3-D border, or filling in complicated
  1044.      * defaults that couldn't be specified to Tk_ConfigureWidget.
  1045.      */
  1046.  
  1047.     Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);
  1048.  
  1049.     gcValues.font = menuPtr->fontPtr->fid;
  1050.     gcValues.foreground = menuPtr->fg->pixel;
  1051.     gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
  1052.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  1053.         &gcValues);
  1054.     if (menuPtr->textGC != None) {
  1055.     Tk_FreeGC(menuPtr->display, menuPtr->textGC);
  1056.     }
  1057.     menuPtr->textGC = newGC;
  1058.  
  1059.     if (menuPtr->disabledFg != NULL) {
  1060.     gcValues.foreground = menuPtr->disabledFg->pixel;
  1061.     mask = GCForeground|GCBackground|GCFont;
  1062.     } else {
  1063.     gcValues.foreground = gcValues.background;
  1064.     if (menuPtr->gray == None) {
  1065.         menuPtr->gray = Tk_GetBitmap(interp, menuPtr->tkwin,
  1066.             Tk_GetUid("gray50"));
  1067.         if (menuPtr->gray == None) {
  1068.         return TCL_ERROR;
  1069.         }
  1070.     }
  1071.     gcValues.fill_style = FillStippled;
  1072.     gcValues.stipple = menuPtr->gray;
  1073.     mask = GCForeground|GCFillStyle|GCStipple;
  1074.     }
  1075.     newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
  1076.     if (menuPtr->disabledGC != None) {
  1077.     Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
  1078.     }
  1079.     menuPtr->disabledGC = newGC;
  1080.  
  1081.     gcValues.font = menuPtr->fontPtr->fid;
  1082.     gcValues.foreground = menuPtr->activeFg->pixel;
  1083.     gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
  1084.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  1085.         &gcValues);
  1086.     if (menuPtr->activeGC != None) {
  1087.     Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
  1088.     }
  1089.     menuPtr->activeGC = newGC;
  1090.  
  1091.     gcValues.foreground = menuPtr->selectorFg->pixel;
  1092.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCFont, &gcValues);
  1093.     if (menuPtr->selectorGC != None) {
  1094.     Tk_FreeGC(menuPtr->display, menuPtr->selectorGC);
  1095.     }
  1096.     menuPtr->selectorGC = newGC;
  1097.  
  1098.     /*
  1099.      * After reconfiguring a menu, we need to reconfigure all of the
  1100.      * entries in the menu, since some of the things in the children
  1101.      * (such as graphics contexts) may have to change to reflect changes
  1102.      * in the parent.
  1103.      */
  1104.  
  1105.     for (i = 0; i < menuPtr->numEntries; i++) {
  1106.     MenuEntry *mePtr;
  1107.  
  1108.     mePtr = menuPtr->entries[i];
  1109.     ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
  1110.         TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
  1111.     }
  1112.  
  1113.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  1114.     menuPtr->flags |= RESIZE_PENDING;
  1115.     Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  1116.     }
  1117.  
  1118.     return TCL_OK;
  1119. }
  1120.  
  1121. /*
  1122.  *----------------------------------------------------------------------
  1123.  *
  1124.  * ConfigureMenuEntry --
  1125.  *
  1126.  *    This procedure is called to process an argv/argc list, plus
  1127.  *    the Tk option database, in order to configure (or
  1128.  *    reconfigure) one entry in a menu.
  1129.  *
  1130.  * Results:
  1131.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  1132.  *    returned, then interp->result contains an error message.
  1133.  *
  1134.  * Side effects:
  1135.  *    Configuration information such as label and accelerator get
  1136.  *    set for mePtr;  old resources get freed, if there were any.
  1137.  *
  1138.  *----------------------------------------------------------------------
  1139.  */
  1140.  
  1141. static int
  1142. ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags)
  1143.     Tcl_Interp *interp;            /* Used for error reporting. */
  1144.     Menu *menuPtr;            /* Information about whole menu. */
  1145.     register MenuEntry *mePtr;        /* Information about menu entry;  may
  1146.                      * or may not already have values for
  1147.                      * some fields. */
  1148.     int index;                /* Index of mePtr within menuPtr's
  1149.                      * entries. */
  1150.     int argc;                /* Number of valid entries in argv. */
  1151.     char **argv;            /* Arguments. */
  1152.     int flags;                /* Additional flags to pass to
  1153.                      * Tk_ConfigureWidget. */
  1154. {
  1155.     XGCValues gcValues;
  1156.     GC newGC, newActiveGC, newDisabledGC;
  1157.     unsigned long mask;
  1158.  
  1159.     /*
  1160.      * If this entry is a cascade and the cascade is posted, then unpost
  1161.      * it before reconfiguring the entry (otherwise the reconfigure might
  1162.      * change the name of the cascaded entry, leaving a posted menu
  1163.      * high and dry).
  1164.      */
  1165.  
  1166.     if (menuPtr->postedCascade == mePtr) {
  1167.     if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
  1168.         != TCL_OK) {
  1169.         Tk_BackgroundError(menuPtr->interp);
  1170.     }
  1171.     }
  1172.  
  1173.     /*
  1174.      * If this entry is a check button or radio button, then remove
  1175.      * its old trace procedure.
  1176.      */
  1177.  
  1178.     if ((mePtr->name != NULL) &&
  1179.         ((mePtr->type == CHECK_BUTTON_ENTRY)
  1180.         || (mePtr->type == RADIO_BUTTON_ENTRY))) {
  1181.     Tcl_UntraceVar(menuPtr->interp, mePtr->name,
  1182.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1183.         MenuVarProc, (ClientData) mePtr);
  1184.     }
  1185.  
  1186.     if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
  1187.         argc, argv, (char *) mePtr,
  1188.         flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
  1189.     return TCL_ERROR;
  1190.     }
  1191.  
  1192.     /*
  1193.      * The code below handles special configuration stuff not taken
  1194.      * care of by Tk_ConfigureWidget, such as special processing for
  1195.      * defaults, sizing strings, graphics contexts, etc.
  1196.      */
  1197.  
  1198.     if (mePtr->label == NULL) {
  1199.     mePtr->labelLength = 0;
  1200.     } else {
  1201.     mePtr->labelLength = strlen(mePtr->label);
  1202.     }
  1203.     if (mePtr->accel == NULL) {
  1204.     mePtr->accelLength = 0;
  1205.     } else {
  1206.     mePtr->accelLength = strlen(mePtr->accel);
  1207.     }
  1208.  
  1209.     if (mePtr->state == tkActiveUid) {
  1210.     if (index != menuPtr->active) {
  1211.         ActivateMenuEntry(menuPtr, index);
  1212.     }
  1213.     } else {
  1214.     if (index == menuPtr->active) {
  1215.         ActivateMenuEntry(menuPtr, -1);
  1216.     }
  1217.     if ((mePtr->state != tkNormalUid) && (mePtr->state != tkDisabledUid)) {
  1218.         Tcl_AppendResult(interp, "bad state value \"", mePtr->state,
  1219.             "\":  must be normal, active, or disabled", (char *) NULL);
  1220.         mePtr->state = tkNormalUid;
  1221.         return TCL_ERROR;
  1222.     }
  1223.     }
  1224.  
  1225.     if (mePtr->fontPtr != NULL) {
  1226.     gcValues.foreground = menuPtr->fg->pixel;
  1227.     gcValues.background = Tk_3DBorderColor(
  1228.         (mePtr->border != NULL) ? mePtr->border : menuPtr->border)
  1229.         ->pixel;
  1230.     gcValues.font = mePtr->fontPtr->fid;
  1231.  
  1232.     /*
  1233.      * Note: disable GraphicsExpose events;  we know there won't be
  1234.      * obscured areas when copying from an off-screen pixmap to the
  1235.      * screen and this gets rid of unnecessary events.
  1236.      */
  1237.  
  1238.     gcValues.graphics_exposures = False;
  1239.     newGC = Tk_GetGC(menuPtr->tkwin,
  1240.         GCForeground|GCBackground|GCFont|GCGraphicsExposures,
  1241.         &gcValues);
  1242.  
  1243.     if (menuPtr->disabledFg != NULL) {
  1244.         gcValues.foreground = menuPtr->disabledFg->pixel;
  1245.         mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures;
  1246.     } else {
  1247.         gcValues.foreground = gcValues.background;
  1248.         gcValues.fill_style = FillStippled;
  1249.         gcValues.stipple = menuPtr->gray;
  1250.         mask = GCForeground|GCFillStyle|GCStipple;
  1251.     }
  1252.     newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
  1253.  
  1254.     gcValues.foreground = menuPtr->activeFg->pixel;
  1255.     gcValues.background = Tk_3DBorderColor(
  1256.         (mePtr->activeBorder != NULL) ? mePtr->activeBorder
  1257.         : menuPtr->activeBorder)->pixel;
  1258.     newActiveGC = Tk_GetGC(menuPtr->tkwin,
  1259.         GCForeground|GCBackground|GCFont|GCGraphicsExposures,
  1260.         &gcValues);
  1261.     } else {
  1262.     newGC = NULL;
  1263.     newActiveGC = NULL;
  1264.     newDisabledGC = NULL;
  1265.     }
  1266.     if (mePtr->textGC != NULL) {
  1267.         Tk_FreeGC(menuPtr->display, mePtr->textGC);
  1268.     }
  1269.     mePtr->textGC = newGC;
  1270.     if (mePtr->activeGC != NULL) {
  1271.         Tk_FreeGC(menuPtr->display, mePtr->activeGC);
  1272.     }
  1273.     mePtr->activeGC = newActiveGC;
  1274.     if (mePtr->disabledGC != NULL) {
  1275.         Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
  1276.     }
  1277.     mePtr->disabledGC = newDisabledGC;
  1278.  
  1279.     if ((mePtr->type == CHECK_BUTTON_ENTRY)
  1280.         || (mePtr->type == RADIO_BUTTON_ENTRY)) {
  1281.     char *value;
  1282.  
  1283.     if (mePtr->name == NULL) {
  1284.         mePtr->name = ckalloc((unsigned) (strlen(mePtr->label) + 1));
  1285.         strcpy(mePtr->name, mePtr->label);
  1286.     }
  1287.     if (mePtr->onValue == NULL) {
  1288.         mePtr->onValue = ckalloc((unsigned) (strlen(mePtr->label) + 1));
  1289.         strcpy(mePtr->onValue, mePtr->label);
  1290.     }
  1291.  
  1292.     /*
  1293.      * Select the entry if the associated variable has the
  1294.      * appropriate value, initialize the variable if it doesn't
  1295.      * exist, then set a trace on the variable to monitor future
  1296.      * changes to its value.
  1297.      */
  1298.  
  1299.     value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
  1300.     mePtr->flags &= ENTRY_SELECTED;
  1301.     if (value != NULL) {
  1302.         if (strcmp(value, mePtr->onValue) == 0) {
  1303.         mePtr->flags |= ENTRY_SELECTED;
  1304.         }
  1305.     } else {
  1306.         Tcl_SetVar(interp, mePtr->name,
  1307.             (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "",
  1308.             TCL_GLOBAL_ONLY);
  1309.     }
  1310.     Tcl_TraceVar(interp, mePtr->name,
  1311.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1312.         MenuVarProc, (ClientData) mePtr);
  1313.     }
  1314.  
  1315.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  1316.     menuPtr->flags |= RESIZE_PENDING;
  1317.     Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  1318.     }
  1319.     return TCL_OK;
  1320. }
  1321.  
  1322. /*
  1323.  *--------------------------------------------------------------
  1324.  *
  1325.  * ComputeMenuGeometry --
  1326.  *
  1327.  *    This procedure is invoked to recompute the size and
  1328.  *    layout of a menu.  It is called as a when-idle handler so
  1329.  *    that it only gets done once, even if a group of changes is
  1330.  *    made to the menu.
  1331.  *
  1332.  * Results:
  1333.  *    None.
  1334.  *
  1335.  * Side effects:
  1336.  *    Fields of menu entries are changed to reflect their
  1337.  *    current positions, and the size of the menu window
  1338.  *    itself may be changed.
  1339.  *
  1340.  *--------------------------------------------------------------
  1341.  */
  1342.  
  1343. static void
  1344. ComputeMenuGeometry(clientData)
  1345.     ClientData clientData;        /* Structure describing menu. */
  1346. {
  1347.     Menu *menuPtr = (Menu *) clientData;
  1348.     register MenuEntry *mePtr;
  1349.     XFontStruct *fontPtr;
  1350.     int maxLabelWidth, maxSelectorWidth, maxAccelWidth;
  1351.     int width, height, selectorSpace;
  1352.     int i, y;
  1353.  
  1354.     if (menuPtr->tkwin == NULL) {
  1355.     return;
  1356.     }
  1357.  
  1358.     maxLabelWidth = maxSelectorWidth = maxAccelWidth = 0;
  1359.     y = menuPtr->borderWidth;
  1360.  
  1361.     for (i = 0; i < menuPtr->numEntries; i++) {
  1362.     mePtr = menuPtr->entries[i];
  1363.     selectorSpace = 0;
  1364.     fontPtr = mePtr->fontPtr;
  1365.     if (fontPtr == NULL) {
  1366.         fontPtr = menuPtr->fontPtr;
  1367.     }
  1368.  
  1369.     /*
  1370.      * For each entry, compute the height required by that
  1371.      * particular entry, plus three widths:  the width of the
  1372.      * label, the width to allow for a selector to be displayed
  1373.      * to the left of the label (if any), and the width of the
  1374.      * accelerator to be displayed to the right of the label
  1375.      * (if any).  These sizes depend, of course, on the type
  1376.      * of the entry.
  1377.      */
  1378.  
  1379.     if (mePtr->bitmap != None) {
  1380.         unsigned int bitmapWidth, bitmapHeight;
  1381.  
  1382.         Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap,
  1383.             &bitmapWidth, &bitmapHeight);
  1384.         mePtr->height = bitmapHeight;
  1385.         width = bitmapWidth;
  1386.         if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1387.         selectorSpace = (14*mePtr->height)/10;
  1388.         mePtr->selectorDiameter = (65*mePtr->height)/100;
  1389.         } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1390.         selectorSpace = (14*mePtr->height)/10;
  1391.         mePtr->selectorDiameter = (75*mePtr->height)/100;
  1392.         }
  1393.     } else {
  1394.         mePtr->height = fontPtr->ascent + fontPtr->descent;
  1395.         if (mePtr->label != NULL) {
  1396.         (void) TkMeasureChars(fontPtr, mePtr->label,
  1397.             mePtr->labelLength, 0, (int) 100000,
  1398.             TK_NEWLINES_NOT_SPECIAL, &width);
  1399.         } else {
  1400.         width = 0;
  1401.         }
  1402.         if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1403.         selectorSpace = mePtr->height;
  1404.         mePtr->selectorDiameter = (80*mePtr->height)/100;
  1405.         } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1406.         selectorSpace = mePtr->height;
  1407.         mePtr->selectorDiameter = mePtr->height;
  1408.         }
  1409.     }
  1410.     mePtr->height += 2*menuPtr->activeBorderWidth + 2;
  1411.     if (width > maxLabelWidth) {
  1412.         maxLabelWidth = width;
  1413.     }
  1414.     if (mePtr->type == CASCADE_ENTRY) {
  1415.         width = 2*CASCADE_ARROW_WIDTH;
  1416.     } else if (mePtr->accel != NULL) {
  1417.         (void) TkMeasureChars(fontPtr, mePtr->accel, mePtr->accelLength,
  1418.             0, (int) 100000, TK_NEWLINES_NOT_SPECIAL, &width);
  1419.     } else {
  1420.         width = 0;
  1421.     }
  1422.     if (width > maxAccelWidth) {
  1423.         maxAccelWidth = width;
  1424.     }
  1425.     if (mePtr->type == SEPARATOR_ENTRY) {
  1426.         mePtr->height = 4*menuPtr->borderWidth;
  1427.     }
  1428.     if (selectorSpace > maxSelectorWidth) {
  1429.         maxSelectorWidth = selectorSpace;
  1430.     }
  1431.     mePtr->y = y;
  1432.     y += mePtr->height;
  1433.     }
  1434.  
  1435.     /*
  1436.      * Got all the sizes.  Update fields in the menu structure, then
  1437.      * resize the window if necessary.  Leave margins on either side
  1438.      * of the selector (or just one margin if there is no selector).
  1439.      * Leave another margin on the right side of the label, plus yet
  1440.      * another margin to the right of the accelerator (if there is one).
  1441.      */
  1442.  
  1443.     menuPtr->selectorSpace = maxSelectorWidth + MARGIN_WIDTH;
  1444.     if (maxSelectorWidth != 0) {
  1445.     menuPtr->selectorSpace += MARGIN_WIDTH;
  1446.     }
  1447.     menuPtr->labelWidth = maxLabelWidth + MARGIN_WIDTH;
  1448.     width = menuPtr->selectorSpace + menuPtr->labelWidth + maxAccelWidth
  1449.         + 2*menuPtr->borderWidth + 2*menuPtr->activeBorderWidth;
  1450.     if (maxAccelWidth != 0) {
  1451.     width += MARGIN_WIDTH;
  1452.     }
  1453.     height = y + menuPtr->borderWidth;
  1454.  
  1455.     /*
  1456.      * The X server doesn't like zero dimensions, so round up to at least
  1457.      * 1 (a zero-sized menu should never really occur, anyway).
  1458.      */
  1459.  
  1460.     if (width <= 0) {
  1461.     width = 1;
  1462.     }
  1463.     if (height <= 0) {
  1464.     height = 1;
  1465.     }
  1466.     if ((width != Tk_ReqWidth(menuPtr->tkwin)) ||
  1467.         (height != Tk_ReqHeight(menuPtr->tkwin))) {
  1468.     Tk_GeometryRequest(menuPtr->tkwin, width, height);
  1469.     } else {
  1470.     /*
  1471.      * Must always force a redisplay here if the window is mapped
  1472.      * (even if the size didn't change, something else might have
  1473.      * changed in the menu, such as a label or accelerator).  The
  1474.      * resize will force a redisplay above.
  1475.      */
  1476.  
  1477.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  1478.     }
  1479.  
  1480.     menuPtr->flags &= ~RESIZE_PENDING;
  1481. }
  1482.  
  1483. /*
  1484.  *----------------------------------------------------------------------
  1485.  *
  1486.  * DisplayMenu --
  1487.  *
  1488.  *    This procedure is invoked to display a menu widget.
  1489.  *
  1490.  * Results:
  1491.  *    None.
  1492.  *
  1493.  * Side effects:
  1494.  *    Commands are output to X to display the menu in its
  1495.  *    current mode.
  1496.  *
  1497.  *----------------------------------------------------------------------
  1498.  */
  1499.  
  1500. static void
  1501. DisplayMenu(clientData)
  1502.     ClientData clientData;    /* Information about widget. */
  1503. {
  1504.     register Menu *menuPtr = (Menu *) clientData;
  1505.     register MenuEntry *mePtr;
  1506.     register Tk_Window tkwin = menuPtr->tkwin;
  1507.     Tk_3DBorder bgBorder, activeBorder;
  1508.     XFontStruct *fontPtr;
  1509.     int index, baseline;
  1510.     GC gc;
  1511.     XPoint points[3];
  1512.  
  1513.     menuPtr->flags &= ~REDRAW_PENDING;
  1514.     if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
  1515.     return;
  1516.     }
  1517.  
  1518.     /*
  1519.      * Loop through all of the entries, drawing them one at a time.
  1520.      */
  1521.  
  1522.     for (index = 0; index < menuPtr->numEntries; index++) {
  1523.     mePtr = menuPtr->entries[index];
  1524.     if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
  1525.         continue;
  1526.     }
  1527.     mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
  1528.  
  1529.     /*
  1530.      * Background.
  1531.      */
  1532.  
  1533.     activeBorder = mePtr->activeBorder;
  1534.     if (activeBorder == NULL) {
  1535.         activeBorder = menuPtr->activeBorder;
  1536.     }
  1537.     if ((mePtr->state == tkActiveUid)
  1538.         || (menuPtr->postedCascade == mePtr))  {
  1539.         bgBorder = activeBorder;
  1540.         Tk_Fill3DRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1541.             bgBorder, menuPtr->borderWidth, mePtr->y,
  1542.             Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
  1543.             menuPtr->activeBorderWidth, TK_RELIEF_RAISED);
  1544.         gc = mePtr->activeGC;
  1545.         if (gc == NULL) {
  1546.         gc = menuPtr->activeGC;
  1547.         }
  1548.     } else {
  1549.         bgBorder = mePtr->border;
  1550.         if (bgBorder == NULL) {
  1551.         bgBorder = menuPtr->border;
  1552.         }
  1553.         Tk_Fill3DRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1554.             bgBorder, menuPtr->borderWidth, mePtr->y,
  1555.             Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
  1556.             0, TK_RELIEF_FLAT);
  1557.         if ((mePtr->state == tkDisabledUid)
  1558.             && (menuPtr->disabledFg != NULL)) {
  1559.         gc = mePtr->disabledGC;
  1560.         if (gc == NULL) {
  1561.             gc = menuPtr->disabledGC;
  1562.         }
  1563.         } else {
  1564.         gc = mePtr->textGC;
  1565.         if (gc == NULL) {
  1566.             gc = menuPtr->textGC;
  1567.         }
  1568.         }
  1569.     }
  1570.  
  1571.     /*
  1572.      * Draw label or bitmap for entry.
  1573.      */
  1574.  
  1575.     fontPtr = mePtr->fontPtr;
  1576.     if (fontPtr == NULL) {
  1577.         fontPtr = menuPtr->fontPtr;
  1578.     }
  1579.     baseline = mePtr->y + (mePtr->height + fontPtr->ascent
  1580.         - fontPtr->descent)/2;
  1581.     if (mePtr->bitmap != None) {
  1582.         unsigned int width, height;
  1583.  
  1584.         Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap, &width, &height);
  1585.         XCopyPlane(menuPtr->display, mePtr->bitmap, Tk_WindowId(tkwin),
  1586.             gc, 0, 0, width, height,
  1587.             menuPtr->borderWidth + menuPtr->selectorSpace,
  1588.             (int) (mePtr->y + (mePtr->height - height)/2), 1);
  1589.     } else {
  1590.         baseline = mePtr->y + (mePtr->height + fontPtr->ascent
  1591.             - fontPtr->descent)/2;
  1592.         if (mePtr->label != NULL) {
  1593.         TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1594.             fontPtr, mePtr->label, mePtr->labelLength,
  1595.             menuPtr->borderWidth + menuPtr->selectorSpace,
  1596.             baseline, TK_NEWLINES_NOT_SPECIAL);
  1597.         if (mePtr->underline >= 0) {
  1598.             TkUnderlineChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1599.                 fontPtr, mePtr->label,
  1600.                 menuPtr->borderWidth + menuPtr->selectorSpace,
  1601.                 baseline, TK_NEWLINES_NOT_SPECIAL,
  1602.                 mePtr->underline, mePtr->underline);
  1603.         }
  1604.         }
  1605.     }
  1606.  
  1607.     /*
  1608.      * Draw accelerator or cascade arrow.
  1609.      */
  1610.  
  1611.     if (mePtr->type == CASCADE_ENTRY) {
  1612.         points[0].x = Tk_Width(tkwin) - 2*menuPtr->borderWidth
  1613.             - MARGIN_WIDTH - CASCADE_ARROW_WIDTH;
  1614.         points[0].y = mePtr->y + (mePtr->height - CASCADE_ARROW_HEIGHT)/2;
  1615.         points[1].x = points[0].x;
  1616.         points[1].y = points[0].y + CASCADE_ARROW_HEIGHT;
  1617.         points[2].x = points[0].x + CASCADE_ARROW_WIDTH;
  1618.         points[2].y = points[0].y + CASCADE_ARROW_HEIGHT/2;
  1619.         Tk_Fill3DPolygon(menuPtr->display, Tk_WindowId(tkwin), activeBorder,
  1620.             points, 3, menuPtr->activeBorderWidth,
  1621.             (menuPtr->postedCascade == mePtr) ? TK_RELIEF_SUNKEN
  1622.             : TK_RELIEF_RAISED);
  1623.     } else if (mePtr->accel != NULL) {
  1624.         TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1625.             fontPtr, mePtr->accel, mePtr->accelLength,
  1626.             menuPtr->borderWidth + menuPtr->selectorSpace
  1627.             + menuPtr->labelWidth, baseline, TK_NEWLINES_NOT_SPECIAL);
  1628.     }
  1629.  
  1630.     /*
  1631.      * Draw check-button selector.
  1632.      */
  1633.  
  1634.     if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1635.         int dim, x, y;
  1636.  
  1637.         dim = mePtr->selectorDiameter;
  1638.         x = menuPtr->borderWidth + (menuPtr->selectorSpace - dim)/2;
  1639.         y = mePtr->y + (mePtr->height - dim)/2;
  1640.         Tk_Fill3DRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1641.             menuPtr->border, x, y, dim, dim,
  1642.             menuPtr->activeBorderWidth, TK_RELIEF_SUNKEN);
  1643.         x += menuPtr->activeBorderWidth;
  1644.         y += menuPtr->activeBorderWidth;
  1645.         dim -= 2*menuPtr->activeBorderWidth;
  1646.         if ((dim > 0) && (mePtr->flags & ENTRY_SELECTED)) {
  1647.         XFillRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1648.             menuPtr->selectorGC, x, y, (unsigned int) dim,
  1649.             (unsigned int) dim);
  1650.         }
  1651.     }
  1652.  
  1653.     /*
  1654.      * Draw radio-button selector.
  1655.      */
  1656.  
  1657.     if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1658.         XPoint points[4];
  1659.         int radius;
  1660.  
  1661.         radius = mePtr->selectorDiameter/2;
  1662.         points[0].x = menuPtr->borderWidth
  1663.             + (menuPtr->selectorSpace - mePtr->selectorDiameter)/2;
  1664.         points[0].y = mePtr->y + (mePtr->height)/2;
  1665.         points[1].x = points[0].x + radius;
  1666.         points[1].y = points[0].y + radius;
  1667.         points[2].x = points[1].x + radius;
  1668.         points[2].y = points[0].y;
  1669.         points[3].x = points[1].x;
  1670.         points[3].y = points[0].y - radius;
  1671.         if (mePtr->flags & ENTRY_SELECTED) {
  1672.         XFillPolygon(menuPtr->display, Tk_WindowId(tkwin),
  1673.             menuPtr->selectorGC, points, 4, Convex,
  1674.             CoordModeOrigin);
  1675.         } else {
  1676.         Tk_Fill3DPolygon(menuPtr->display, Tk_WindowId(tkwin),
  1677.             menuPtr->border, points, 4, menuPtr->activeBorderWidth,
  1678.             TK_RELIEF_FLAT);
  1679.         }
  1680.         Tk_Draw3DPolygon(menuPtr->display, Tk_WindowId(tkwin),
  1681.             menuPtr->border, points, 4, menuPtr->activeBorderWidth,
  1682.             TK_RELIEF_SUNKEN);
  1683.     }
  1684.  
  1685.     /*
  1686.      * Draw separator.
  1687.      */
  1688.  
  1689.     if (mePtr->type == SEPARATOR_ENTRY) {
  1690.         XPoint points[2];
  1691.         int margin;
  1692.  
  1693.         margin = (fontPtr->ascent + fontPtr->descent)/2;
  1694.         points[0].x = 2*menuPtr->borderWidth + margin;
  1695.         points[0].y = mePtr->y + mePtr->height/2;
  1696.         points[1].x = Tk_Width(tkwin) - 2*menuPtr->borderWidth - margin;
  1697.         points[1].y = points[0].y;
  1698.         Tk_Draw3DPolygon(menuPtr->display, Tk_WindowId(tkwin),
  1699.             menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
  1700.     }
  1701.  
  1702.     /*
  1703.      * If the entry is disabled with a stipple rather than a special
  1704.      * foreground color, generate the stippled effect.
  1705.      */
  1706.  
  1707.     if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg == NULL)) {
  1708.         XFillRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1709.             menuPtr->disabledGC, menuPtr->borderWidth,
  1710.             mePtr->y,
  1711.             (unsigned) (Tk_Width(tkwin) - 2*menuPtr->borderWidth),
  1712.             (unsigned) mePtr->height);
  1713.     }
  1714.     }
  1715.  
  1716.     Tk_Draw3DRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1717.         menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
  1718.         menuPtr->borderWidth, TK_RELIEF_RAISED);
  1719. }
  1720.  
  1721. /*
  1722.  *--------------------------------------------------------------
  1723.  *
  1724.  * GetMenuIndex --
  1725.  *
  1726.  *    Parse a textual index into a menu and return the numerical
  1727.  *    index of the indicated entry.
  1728.  *
  1729.  * Results:
  1730.  *    A standard Tcl result.  If all went well, then *indexPtr is
  1731.  *    filled in with the entry index corresponding to string
  1732.  *    (ranges from -1 to the number of entries in the menu minus
  1733.  *    one).  Otherwise an error message is left in interp->result.
  1734.  *
  1735.  * Side effects:
  1736.  *    None.
  1737.  *
  1738.  *--------------------------------------------------------------
  1739.  */
  1740.  
  1741. static int
  1742. GetMenuIndex(interp, menuPtr, string, indexPtr)
  1743.     Tcl_Interp *interp;        /* For error messages. */
  1744.     Menu *menuPtr;        /* Menu for which the index is being
  1745.                  * specified. */
  1746.     char *string;        /* Specification of an entry in menu.  See
  1747.                  * manual entry for valid .*/
  1748.     int *indexPtr;        /* Where to store converted relief. */
  1749. {
  1750.     int i, y;
  1751.  
  1752.     if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
  1753.     *indexPtr = menuPtr->active;
  1754.     return TCL_OK;
  1755.     }
  1756.  
  1757.     if ((string[0] == 'l') && (strcmp(string, "last") == 0)) {
  1758.     *indexPtr = menuPtr->numEntries-1;
  1759.     return TCL_OK;
  1760.     }
  1761.  
  1762.     if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
  1763.     *indexPtr = -1;
  1764.     return TCL_OK;
  1765.     }
  1766.  
  1767.     if (string[0] == '@') {
  1768.     if (Tcl_GetInt(interp, string+1,  &y) == TCL_OK) {
  1769.         if (y < 0) {
  1770.         *indexPtr = -1;
  1771.         return TCL_OK;
  1772.         }
  1773.         for (i = 0; i < menuPtr->numEntries; i++) {
  1774.         y -= menuPtr->entries[i]->height;
  1775.         if (y < 0) {
  1776.             break;
  1777.         }
  1778.         }
  1779.         if (i >= menuPtr->numEntries) {
  1780.         i = -1;
  1781.         }
  1782.         *indexPtr = i;
  1783.         return TCL_OK;
  1784.     } else {
  1785.         Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  1786.     }
  1787.     }
  1788.  
  1789.     if (isdigit(string[0])) {
  1790.     if (Tcl_GetInt(interp, string,  &i) == TCL_OK) {
  1791.         if (i >= menuPtr->numEntries) {
  1792.         i = menuPtr->numEntries - 1;
  1793.         } else if (i < 0) {
  1794.         i = -1;
  1795.         }
  1796.         *indexPtr = i;
  1797.         return TCL_OK;
  1798.     }
  1799.     Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  1800.     }
  1801.  
  1802.     for (i = 0; i < menuPtr->numEntries; i++) {
  1803.     char *label;
  1804.  
  1805.     label = menuPtr->entries[i]->label;
  1806.     if ((label != NULL)
  1807.         && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
  1808.         *indexPtr = i;
  1809.         return TCL_OK;
  1810.     }
  1811.     }
  1812.  
  1813.     Tcl_AppendResult(interp, "bad menu entry index \"",
  1814.         string, "\"", (char *) NULL);
  1815.     return TCL_ERROR;
  1816. }
  1817.  
  1818. /*
  1819.  *--------------------------------------------------------------
  1820.  *
  1821.  * MenuEventProc --
  1822.  *
  1823.  *    This procedure is invoked by the Tk dispatcher for various
  1824.  *    events on menus.
  1825.  *
  1826.  * Results:
  1827.  *    None.
  1828.  *
  1829.  * Side effects:
  1830.  *    When the window gets deleted, internal structures get
  1831.  *    cleaned up.  When it gets exposed, it is redisplayed.
  1832.  *
  1833.  *--------------------------------------------------------------
  1834.  */
  1835.  
  1836. static void
  1837. MenuEventProc(clientData, eventPtr)
  1838.     ClientData clientData;    /* Information about window. */
  1839.     XEvent *eventPtr;        /* Information about event. */
  1840. {
  1841.     Menu *menuPtr = (Menu *) clientData;
  1842.     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
  1843.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  1844.     } else if (eventPtr->type == DestroyNotify) {
  1845.     Tcl_DeleteCommand(menuPtr->interp, Tk_PathName(menuPtr->tkwin));
  1846.     menuPtr->tkwin = NULL;
  1847.     if (menuPtr->flags & REDRAW_PENDING) {
  1848.         Tk_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
  1849.     }
  1850.     if (menuPtr->flags & RESIZE_PENDING) {
  1851.         Tk_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
  1852.     }
  1853.     Tk_EventuallyFree((ClientData) menuPtr, DestroyMenu);
  1854.     }
  1855. }
  1856.  
  1857. /*
  1858.  *--------------------------------------------------------------
  1859.  *
  1860.  * MenuVarProc --
  1861.  *
  1862.  *    This procedure is invoked when someone changes the
  1863.  *    state variable associated with a radiobutton or checkbutton
  1864.  *    menu entry.  The entry's selected state is set to match
  1865.  *    the value of the variable.
  1866.  *
  1867.  * Results:
  1868.  *    NULL is always returned.
  1869.  *
  1870.  * Side effects:
  1871.  *    The menu entry may become selected or deselected.
  1872.  *
  1873.  *--------------------------------------------------------------
  1874.  */
  1875.  
  1876.     /* ARGSUSED */
  1877. static char *
  1878. MenuVarProc(clientData, interp, name1, name2, flags)
  1879.     ClientData clientData;    /* Information about menu entry. */
  1880.     Tcl_Interp *interp;        /* Interpreter containing variable. */
  1881.     char *name1;        /* First part of variable's name. */
  1882.     char *name2;        /* Second part of variable's name. */
  1883.     int flags;            /* Describes what just happened. */
  1884. {
  1885.     MenuEntry *mePtr = (MenuEntry *) clientData;
  1886.     Menu *menuPtr;
  1887.     char *value;
  1888.  
  1889.     menuPtr = mePtr->menuPtr;
  1890.  
  1891.     /*
  1892.      * If the variable is being unset, then re-establish the
  1893.      * trace unless the whole interpreter is going away.
  1894.      */
  1895.  
  1896.     if (flags & TCL_TRACE_UNSETS) {
  1897.     mePtr->flags &= ~ENTRY_SELECTED;
  1898.     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
  1899.         Tcl_TraceVar2(interp, name1, name2,
  1900.             TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1901.             MenuVarProc, clientData);
  1902.     }
  1903.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  1904.     return (char *) NULL;
  1905.     }
  1906.  
  1907.     /*
  1908.      * Use the value of the variable to update the selected status of
  1909.      * the menu entry.
  1910.      */
  1911.  
  1912.     value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
  1913.     if (strcmp(value, mePtr->onValue) == 0) {
  1914.     if (mePtr->flags & ENTRY_SELECTED) {
  1915.         return (char *) NULL;
  1916.     }
  1917.     mePtr->flags |= ENTRY_SELECTED;
  1918.     } else if (mePtr->flags & ENTRY_SELECTED) {
  1919.     mePtr->flags &= ~ENTRY_SELECTED;
  1920.     } else {
  1921.     return (char *) NULL;
  1922.     }
  1923.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  1924.     return (char *) NULL;
  1925. }
  1926.  
  1927. /*
  1928.  *----------------------------------------------------------------------
  1929.  *
  1930.  * EventuallyRedrawMenu --
  1931.  *
  1932.  *    Arrange for an entry of a menu, or the whole menu, to be
  1933.  *    redisplayed at some point in the future.
  1934.  *
  1935.  * Results:
  1936.  *    None.
  1937.  *
  1938.  * Side effects:
  1939.  *    A when-idle hander is scheduled to do the redisplay, if there
  1940.  *    isn't one already scheduled.
  1941.  *
  1942.  *----------------------------------------------------------------------
  1943.  */
  1944.  
  1945. static void
  1946. EventuallyRedrawMenu(menuPtr, mePtr)
  1947.     register Menu *menuPtr;    /* Information about menu to redraw. */
  1948.     register MenuEntry *mePtr;    /* Entry to redraw.  NULL means redraw
  1949.                  * all the entries in the menu. */
  1950. {
  1951.     int i;
  1952.     if (menuPtr->tkwin == NULL) {
  1953.     return;
  1954.     }
  1955.     if (mePtr != NULL) {
  1956.     mePtr->flags |= ENTRY_NEEDS_REDISPLAY;
  1957.     } else {
  1958.     for (i = 0; i < menuPtr->numEntries; i++) {
  1959.         menuPtr->entries[i]->flags |= ENTRY_NEEDS_REDISPLAY;
  1960.     }
  1961.     }
  1962.     if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
  1963.         || (menuPtr->flags & REDRAW_PENDING)) {
  1964.     return;
  1965.     }
  1966.     Tk_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
  1967.     menuPtr->flags |= REDRAW_PENDING;
  1968. }
  1969.  
  1970. /*
  1971.  *--------------------------------------------------------------
  1972.  *
  1973.  * PostSubmenu --
  1974.  *
  1975.  *    This procedure arranges for a particular submenu (i.e. the
  1976.  *    menu corresponding to a given cascade entry) to be
  1977.  *    posted.
  1978.  *
  1979.  * Results:
  1980.  *    A standard Tcl return result.  Errors may occur in the
  1981.  *    Tcl commands generated to post and unpost submenus.
  1982.  *
  1983.  * Side effects:
  1984.  *    If there is already a submenu posted, it is unposted.
  1985.  *    The new submenu is then posted.
  1986.  *
  1987.  *--------------------------------------------------------------
  1988.  */
  1989.  
  1990. static int
  1991. PostSubmenu(interp, menuPtr, mePtr)
  1992.     Tcl_Interp *interp;        /* Used for invoking sub-commands and
  1993.                  * reporting errors. */
  1994.     register Menu *menuPtr;    /* Information about menu as a whole. */
  1995.     register MenuEntry *mePtr;    /* Info about submenu that is to be
  1996.                  * posted.  NULL means make sure that
  1997.                  * no submenu is posted. */
  1998. {
  1999.     char string[30];
  2000.     int result, x, y;
  2001.  
  2002.     if (mePtr == menuPtr->postedCascade) {
  2003.     return TCL_OK;
  2004.     }
  2005.  
  2006.     if (menuPtr->postedCascade != NULL) {
  2007.     EventuallyRedrawMenu(menuPtr, menuPtr->postedCascade);
  2008.     result = Tcl_VarEval(interp, menuPtr->postedCascade->name,
  2009.         " unpost", (char *) NULL);
  2010.     menuPtr->postedCascade = NULL;
  2011.     if (result != TCL_OK) {
  2012.         return result;
  2013.     }
  2014.     }
  2015.  
  2016.     if ((mePtr != NULL) && (mePtr->name != NULL)) {
  2017.     Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
  2018.     x += Tk_Width(menuPtr->tkwin);
  2019.     y += mePtr->y;
  2020.     sprintf(string, "%d %d", x, y);
  2021.     result = Tcl_VarEval(interp, mePtr->name, " post ", string,
  2022.         (char *) NULL);
  2023.     if (result != TCL_OK) {
  2024.         return result;
  2025.     }
  2026.     menuPtr->postedCascade = mePtr;
  2027.     }
  2028.     return TCL_OK;
  2029. }
  2030.  
  2031. /*
  2032.  *----------------------------------------------------------------------
  2033.  *
  2034.  * ActivateMenuEntry --
  2035.  *
  2036.  *    This procedure is invoked to make a particular menu entry
  2037.  *    the active one, deactivating any other entry that might
  2038.  *    currently be active.
  2039.  *
  2040.  * Results:
  2041.  *    The return value is a standard Tcl result (errors can occur
  2042.  *    while posting and unposting submenus).
  2043.  *
  2044.  * Side effects:
  2045.  *    Menu entries get redisplayed, and the active entry changes.
  2046.  *    Submenus may get posted and unposted.
  2047.  *
  2048.  *----------------------------------------------------------------------
  2049.  */
  2050.  
  2051. static int
  2052. ActivateMenuEntry(menuPtr, index)
  2053.     register Menu *menuPtr;        /* Menu in which to activate. */
  2054.     int index;                /* Index of entry to activate, or
  2055.                      * -1 to deactivate all entries. */
  2056. {
  2057.     register MenuEntry *mePtr;
  2058.     int result = TCL_OK;
  2059.  
  2060.     if (menuPtr->active >= 0) {
  2061.     mePtr = menuPtr->entries[menuPtr->active];
  2062.  
  2063.     /*
  2064.      * Don't change the state unless it's currently active (state
  2065.      * might already have been changed to disabled).
  2066.      */
  2067.  
  2068.     if (mePtr->state == tkActiveUid) {
  2069.         mePtr->state = tkNormalUid;
  2070.     }
  2071.     EventuallyRedrawMenu(menuPtr, menuPtr->entries[menuPtr->active]);
  2072.     }
  2073.     menuPtr->active = index;
  2074.     if (index >= 0) {
  2075.     mePtr = menuPtr->entries[index];
  2076.     mePtr->state = tkActiveUid;
  2077.     EventuallyRedrawMenu(menuPtr, mePtr);
  2078.     Tk_Preserve((ClientData) mePtr);
  2079.     if (mePtr->type == CASCADE_ENTRY) {
  2080.         if (mePtr->command != NULL) {
  2081.         result = TkCopyAndGlobalEval(menuPtr->interp, mePtr->command);
  2082.         }
  2083.         if (result == TCL_OK) {
  2084.         result = PostSubmenu(menuPtr->interp, menuPtr, mePtr);
  2085.         }
  2086.     } else {
  2087.         result = PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
  2088.     }
  2089.     Tk_Release((ClientData) mePtr);
  2090.     }
  2091.     return result;
  2092. }
  2093.