home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / tcl / tk3.3b1 / tkMenubutton.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-16  |  30.2 KB  |  949 lines

  1. /* 
  2.  * tkMenubutton.c --
  3.  *
  4.  *    This module implements button-like widgets that are used
  5.  *    to invoke pull-down menus.
  6.  *
  7.  * Copyright (c) 1990-1993 The Regents of the University of California.
  8.  * All rights reserved.
  9.  *
  10.  * Permission is hereby granted, without written agreement and without
  11.  * license or royalty fees, to use, copy, modify, and distribute this
  12.  * software and its documentation for any purpose, provided that the
  13.  * above copyright notice and the following two paragraphs appear in
  14.  * all copies of this software.
  15.  * 
  16.  * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
  17.  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  18.  * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
  19.  * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  20.  *
  21.  * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  22.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  23.  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
  24.  * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
  25.  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  26.  */
  27.  
  28. #ifndef lint
  29. static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkMenubutton.c,v 1.40 93/06/16 17:15:58 ouster Exp $ SPRITE (Berkeley)";
  30. #endif
  31.  
  32. #include "tkConfig.h"
  33. #include "default.h"
  34. #include "tkInt.h"
  35.  
  36. /*
  37.  * A data structure of the following type is kept for each
  38.  * widget managed by this file:
  39.  */
  40.  
  41. typedef struct {
  42.     Tk_Window tkwin;        /* Window that embodies the widget.  NULL
  43.                  * means that the window has been destroyed
  44.                  * but the data structures haven't yet been
  45.                  * cleaned up.*/
  46.     Display *display;        /* Display containing widget.  Needed, among
  47.                  * other things, so that resources can bee
  48.                  * freed up even after tkwin has gone away. */
  49.     Tcl_Interp *interp;        /* Interpreter associated with menu button. */
  50.     char *menuName;        /* Name of menu associated with widget.
  51.                  * Malloc-ed. */
  52.  
  53.     /*
  54.      * Information about what's displayed in the menu button:
  55.      */
  56.  
  57.     char *text;            /* Text to display in button (malloc'ed)
  58.                  * or NULL. */
  59.     int textLength;        /* # of characters in text. */
  60.     int underline;        /* Index of character to underline. */
  61.     char *textVarName;        /* Name of variable (malloc'ed) or NULL.
  62.                  * If non-NULL, button displays the contents
  63.                  * of this variable. */
  64.     Pixmap bitmap;        /* Bitmap to display or None.  If not None
  65.                  * then text and textVar and underline
  66.                  * are ignored. */
  67.  
  68.     /*
  69.      * Information used when displaying widget:
  70.      */
  71.  
  72.     Tk_Uid state;        /* State of button for display purposes:
  73.                  * normal, active, or disabled. */
  74.     Tk_3DBorder normalBorder;    /* Structure used to draw 3-D
  75.                  * border and background when window
  76.                  * isn't active.  NULL means no such
  77.                  * border exists. */
  78.     Tk_3DBorder activeBorder;    /* Structure used to draw 3-D
  79.                  * border and background when window
  80.                  * is active.  NULL means no such
  81.                  * border exists. */
  82.     int borderWidth;        /* Width of border. */
  83.     int relief;            /* 3-d effect: TK_RELIEF_RAISED, etc. */
  84.     XFontStruct *fontPtr;    /* Information about text font, or NULL. */
  85.     XColor *normalFg;        /* Foreground color in normal mode. */
  86.     XColor *activeFg;        /* Foreground color in active mode.  NULL
  87.                  * means use normalFg instead. */
  88.     XColor *disabledFg;        /* Foreground color when disabled.  NULL
  89.                  * means use normalFg with a 50% stipple
  90.                  * instead. */
  91.     GC normalTextGC;        /* GC for drawing text in normal mode. */
  92.     GC activeTextGC;        /* GC for drawing text in active mode (NULL
  93.                  * means use normalTextGC). */
  94.     Pixmap gray;        /* Pixmap for displaying disabled text/icon if
  95.                  * disabledFg is NULL. */
  96.     GC disabledGC;        /* Used to produce disabled effect.  If
  97.                  * disabledFg isn't NULL, this GC is used to
  98.                  * draw button text or icon.  Otherwise
  99.                  * text or icon is drawn with normalGC and
  100.                  * this GC is used to stipple background
  101.                  * across it. */
  102.     int leftBearing;        /* Distance from text origin to leftmost drawn
  103.                  * pixel (positive means to right). */
  104.     int rightBearing;        /* Amount text sticks right from its origin. */
  105.     int width, height;        /* If > 0, these specify dimensions to request
  106.                  * for window, in characters for text and in
  107.                  * pixels for bitmaps.  In this case the actual
  108.                  * size of the text string or bitmap is
  109.                  * ignored in computing desired window size. */
  110.     int padX, padY;        /* Extra space around text or bitmap (pixels
  111.                  * on each side). */
  112.     Tk_Anchor anchor;        /* Where text/bitmap should be displayed
  113.                  * inside window region. */
  114.  
  115.     /*
  116.      * Miscellaneous information:
  117.      */
  118.  
  119.     Cursor cursor;        /* Current cursor for window, or None. */
  120.     int flags;            /* Various flags;  see below for
  121.                  * definitions. */
  122. } MenuButton;
  123.  
  124. /*
  125.  * Flag bits for buttons:
  126.  *
  127.  * REDRAW_PENDING:        Non-zero means a DoWhenIdle handler
  128.  *                has already been queued to redraw
  129.  *                this window.
  130.  * POSTED:            Non-zero means that the menu associated
  131.  *                with this button has been posted (typically
  132.  *                because of an active button press).
  133.  */
  134.  
  135. #define REDRAW_PENDING        1
  136. #define POSTED            2
  137.  
  138. /*
  139.  * Information used for parsing configuration specs:
  140.  */
  141.  
  142. static Tk_ConfigSpec configSpecs[] = {
  143.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  144.     DEF_MENUBUTTON_ACTIVE_BG_COLOR, Tk_Offset(MenuButton, activeBorder),
  145.     TK_CONFIG_COLOR_ONLY},
  146.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  147.     DEF_MENUBUTTON_ACTIVE_BG_MONO, Tk_Offset(MenuButton, activeBorder),
  148.     TK_CONFIG_MONO_ONLY},
  149.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  150.     DEF_MENUBUTTON_ACTIVE_FG_COLOR, Tk_Offset(MenuButton, activeFg),
  151.     TK_CONFIG_COLOR_ONLY},
  152.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  153.     DEF_MENUBUTTON_ACTIVE_FG_MONO, Tk_Offset(MenuButton, activeFg),
  154.     TK_CONFIG_MONO_ONLY},
  155.     {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
  156.     DEF_MENUBUTTON_ANCHOR, Tk_Offset(MenuButton, anchor), 0},
  157.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  158.     DEF_MENUBUTTON_BG_COLOR, Tk_Offset(MenuButton, normalBorder),
  159.     TK_CONFIG_COLOR_ONLY},
  160.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  161.     DEF_MENUBUTTON_BG_MONO, Tk_Offset(MenuButton, normalBorder),
  162.     TK_CONFIG_MONO_ONLY},
  163.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  164.     (char *) NULL, 0, 0},
  165.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  166.     (char *) NULL, 0, 0},
  167.     {TK_CONFIG_BITMAP, "-bitmap", "bitmap", "Bitmap",
  168.     DEF_MENUBUTTON_BITMAP, Tk_Offset(MenuButton, bitmap),
  169.     TK_CONFIG_NULL_OK},
  170.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  171.     DEF_MENUBUTTON_BORDER_WIDTH, Tk_Offset(MenuButton, borderWidth), 0},
  172.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  173.     DEF_MENUBUTTON_CURSOR, Tk_Offset(MenuButton, cursor),
  174.     TK_CONFIG_NULL_OK},
  175.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  176.     "DisabledForeground", DEF_MENUBUTTON_DISABLED_FG_COLOR,
  177.     Tk_Offset(MenuButton, disabledFg),
  178.     TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
  179.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  180.     "DisabledForeground", DEF_MENUBUTTON_DISABLED_FG_MONO,
  181.     Tk_Offset(MenuButton, disabledFg),
  182.     TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
  183.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  184.     (char *) NULL, 0, 0},
  185.     {TK_CONFIG_FONT, "-font", "font", "Font",
  186.     DEF_MENUBUTTON_FONT, Tk_Offset(MenuButton, fontPtr), 0},
  187.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  188.     DEF_MENUBUTTON_FG, Tk_Offset(MenuButton, normalFg), 0},
  189.     {TK_CONFIG_INT, "-height", "height", "Height",
  190.     DEF_MENUBUTTON_HEIGHT, Tk_Offset(MenuButton, height), 0},
  191.     {TK_CONFIG_STRING, "-menu", "menu", "Menu",
  192.     DEF_MENUBUTTON_MENU, Tk_Offset(MenuButton, menuName),
  193.     TK_CONFIG_NULL_OK},
  194.     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
  195.     DEF_MENUBUTTON_PADX, Tk_Offset(MenuButton, padX), 0},
  196.     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
  197.     DEF_MENUBUTTON_PADY, Tk_Offset(MenuButton, padY), 0},
  198.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  199.     DEF_MENUBUTTON_RELIEF, Tk_Offset(MenuButton, relief), 0},
  200.     {TK_CONFIG_UID, "-state", "state", "State",
  201.     DEF_MENUBUTTON_STATE, Tk_Offset(MenuButton, state), 0},
  202.     {TK_CONFIG_STRING, "-text", "text", "Text",
  203.     DEF_MENUBUTTON_TEXT, Tk_Offset(MenuButton, text), 0},
  204.     {TK_CONFIG_STRING, "-textvariable", "textVariable", "Variable",
  205.     DEF_MENUBUTTON_TEXT_VARIABLE, Tk_Offset(MenuButton, textVarName),
  206.     TK_CONFIG_NULL_OK},
  207.     {TK_CONFIG_INT, "-underline", "underline", "Underline",
  208.     DEF_MENUBUTTON_UNDERLINE, Tk_Offset(MenuButton, underline), 0},
  209.     {TK_CONFIG_INT, "-width", "width", "Width",
  210.     DEF_MENUBUTTON_WIDTH, Tk_Offset(MenuButton, width), 0},
  211.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  212.     (char *) NULL, 0, 0}
  213. };
  214.  
  215. /*
  216.  * Forward declarations for procedures defined later in this file:
  217.  */
  218.  
  219. static void        ComputeMenuButtonGeometry _ANSI_ARGS_((
  220.                 MenuButton *mbPtr));
  221. static void        MenuButtonEventProc _ANSI_ARGS_((ClientData clientData,
  222.                 XEvent *eventPtr));
  223. static char *        MenuButtonTextVarProc _ANSI_ARGS_((
  224.                 ClientData clientData, Tcl_Interp *interp,
  225.                 char *name1, char *name2, int flags));
  226. static int        MenuButtonWidgetCmd _ANSI_ARGS_((ClientData clientData,
  227.                 Tcl_Interp *interp, int argc, char **argv));
  228. static int        ConfigureMenuButton _ANSI_ARGS_((Tcl_Interp *interp,
  229.                 MenuButton *mbPtr, int argc, char **argv,
  230.                 int flags));
  231. static void        DestroyMenuButton _ANSI_ARGS_((ClientData clientData));
  232. static void        DisplayMenuButton _ANSI_ARGS_((ClientData clientData));
  233.  
  234. /*
  235.  *--------------------------------------------------------------
  236.  *
  237.  * Tk_MenubuttonCmd --
  238.  *
  239.  *    This procedure is invoked to process the "button", "label",
  240.  *    "radiobutton", and "checkbutton" Tcl commands.  See the
  241.  *    user documentation for details on what it does.
  242.  *
  243.  * Results:
  244.  *    A standard Tcl result.
  245.  *
  246.  * Side effects:
  247.  *    See the user documentation.
  248.  *
  249.  *--------------------------------------------------------------
  250.  */
  251.  
  252. int
  253. Tk_MenubuttonCmd(clientData, interp, argc, argv)
  254.     ClientData clientData;    /* Main window associated with
  255.                  * interpreter. */
  256.     Tcl_Interp *interp;        /* Current interpreter. */
  257.     int argc;            /* Number of arguments. */
  258.     char **argv;        /* Argument strings. */
  259. {
  260.     register MenuButton *mbPtr;
  261.     Tk_Window tkwin = (Tk_Window) clientData;
  262.     Tk_Window new;
  263.  
  264.     if (argc < 2) {
  265.     Tcl_AppendResult(interp, "wrong # args:  should be \"",
  266.         argv[0], " pathName ?options?\"", (char *) NULL);
  267.     return TCL_ERROR;
  268.     }
  269.  
  270.     /*
  271.      * Create the new window.
  272.      */
  273.  
  274.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  275.     if (new == NULL) {
  276.     return TCL_ERROR;
  277.     }
  278.  
  279.     /*
  280.      * Initialize the data structure for the button.
  281.      */
  282.  
  283.     mbPtr = (MenuButton *) ckalloc(sizeof(MenuButton));
  284.     mbPtr->tkwin = new;
  285.     mbPtr->display = Tk_Display (new);
  286.     mbPtr->interp = interp;
  287.     mbPtr->underline = -1;
  288.     mbPtr->textVarName = NULL;
  289.     mbPtr->state = tkNormalUid;
  290.     mbPtr->borderWidth = 0;
  291.     mbPtr->relief = TK_RELIEF_FLAT;
  292.     mbPtr->normalTextGC = NULL;
  293.     mbPtr->activeTextGC = NULL;
  294.     mbPtr->gray = None;
  295.     mbPtr->disabledGC = NULL;
  296.     mbPtr->flags = 0;
  297.  
  298.     Tk_SetClass(mbPtr->tkwin, "Menubutton");
  299.     Tk_CreateEventHandler(mbPtr->tkwin, ExposureMask|StructureNotifyMask,
  300.         MenuButtonEventProc, (ClientData) mbPtr);
  301.     Tcl_CreateCommand(interp, Tk_PathName(mbPtr->tkwin), MenuButtonWidgetCmd,
  302.         (ClientData) mbPtr, (void (*)()) NULL);
  303.     if (ConfigureMenuButton(interp, mbPtr, argc-2, argv+2, 0) != TCL_OK) {
  304.     Tk_DestroyWindow(mbPtr->tkwin);
  305.     return TCL_ERROR;
  306.     }
  307.  
  308.     interp->result = Tk_PathName(mbPtr->tkwin);
  309.     return TCL_OK;
  310. }
  311.  
  312. /*
  313.  *--------------------------------------------------------------
  314.  *
  315.  * MenuButtonWidgetCmd --
  316.  *
  317.  *    This procedure is invoked to process the Tcl command
  318.  *    that corresponds to a widget managed by this module.
  319.  *    See the user documentation for details on what it does.
  320.  *
  321.  * Results:
  322.  *    A standard Tcl result.
  323.  *
  324.  * Side effects:
  325.  *    See the user documentation.
  326.  *
  327.  *--------------------------------------------------------------
  328.  */
  329.  
  330. static int
  331. MenuButtonWidgetCmd(clientData, interp, argc, argv)
  332.     ClientData clientData;    /* Information about button widget. */
  333.     Tcl_Interp *interp;        /* Current interpreter. */
  334.     int argc;            /* Number of arguments. */
  335.     char **argv;        /* Argument strings. */
  336. {
  337.     register MenuButton *mbPtr = (MenuButton *) clientData;
  338.     int result = TCL_OK;
  339.     int length;
  340.     char c;
  341.  
  342.     if (argc < 2) {
  343.     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  344.         " option ?arg arg ...?\"", (char *) NULL);
  345.     return TCL_ERROR;
  346.     }
  347.     Tk_Preserve((ClientData) mbPtr);
  348.     c = argv[1][0];
  349.     length = strlen(argv[1]);
  350.     if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)) {
  351.     if (argc > 2) {
  352.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  353.             argv[0], " activate\"", (char *) NULL);
  354.         goto error;
  355.     }
  356.     if (mbPtr->state != tkDisabledUid) {
  357.         mbPtr->state = tkActiveUid;
  358.         Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->activeBorder);
  359.         goto redisplay;
  360.     }
  361.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
  362.     if (argc == 2) {
  363.         result = Tk_ConfigureInfo(interp, mbPtr->tkwin, configSpecs,
  364.             (char *) mbPtr, (char *) NULL, 0);
  365.     } else if (argc == 3) {
  366.         result = Tk_ConfigureInfo(interp, mbPtr->tkwin, configSpecs,
  367.             (char *) mbPtr, argv[2], 0);
  368.     } else {
  369.         result = ConfigureMenuButton(interp, mbPtr, argc-2, argv+2,
  370.             TK_CONFIG_ARGV_ONLY);
  371.     }
  372.     } else if ((c == 'd') && (strncmp(argv[1], "deactivate", length) == 0)) {
  373.     if (argc > 2) {
  374.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  375.             argv[0], " deactivate\"", (char *) NULL);
  376.         goto error;
  377.     }
  378.     if (mbPtr->state != tkDisabledUid) {
  379.         mbPtr->state = tkNormalUid;
  380.         Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->normalBorder);
  381.         goto redisplay;
  382.     }
  383.     } else {
  384.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  385.         "\":  must be activate, configure, or deactivate",
  386.         (char *) NULL);
  387.     goto error;
  388.     }
  389.     done:
  390.     Tk_Release((ClientData) mbPtr);
  391.     return result;
  392.  
  393.     redisplay:
  394.     if (Tk_IsMapped(mbPtr->tkwin) && !(mbPtr->flags & REDRAW_PENDING)) {
  395.     Tk_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
  396.     mbPtr->flags |= REDRAW_PENDING;
  397.     }
  398.     goto done;
  399.  
  400.     error:
  401.     Tk_Release((ClientData) mbPtr);
  402.     return TCL_ERROR;
  403. }
  404.  
  405. /*
  406.  *----------------------------------------------------------------------
  407.  *
  408.  * DestroyMenuButton --
  409.  *
  410.  *    This procedure is invoked to recycle all of the resources
  411.  *    associated with a button widget.  It is invoked as a
  412.  *    when-idle handler in order to make sure that there is no
  413.  *    other use of the button pending at the time of the deletion.
  414.  *
  415.  * Results:
  416.  *    None.
  417.  *
  418.  * Side effects:
  419.  *    Everything associated with the widget is freed up.
  420.  *
  421.  *----------------------------------------------------------------------
  422.  */
  423.  
  424. static void
  425. DestroyMenuButton(clientData)
  426.     ClientData clientData;    /* Info about button widget. */
  427. {
  428.     register MenuButton *mbPtr = (MenuButton *) clientData;
  429.  
  430.     /*
  431.      * Free up all the stuff that requires special handling, then
  432.      * let Tk_FreeOptions handle all the standard option-related
  433.      * stuff.
  434.      */
  435.  
  436.     if (mbPtr->textVarName != NULL) {
  437.     Tcl_UntraceVar(mbPtr->interp, mbPtr->textVarName,
  438.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  439.         MenuButtonTextVarProc, (ClientData) mbPtr);
  440.     }
  441.     if (mbPtr->normalTextGC != None) {
  442.     Tk_FreeGC(mbPtr->display, mbPtr->normalTextGC);
  443.     }
  444.     if (mbPtr->activeTextGC != None) {
  445.     Tk_FreeGC(mbPtr->display, mbPtr->activeTextGC);
  446.     }
  447.     if (mbPtr->gray != None) {
  448.     Tk_FreeBitmap(mbPtr->display, mbPtr->gray);
  449.     }
  450.     if (mbPtr->disabledGC != None) {
  451.     Tk_FreeGC(mbPtr->display, mbPtr->disabledGC);
  452.     }
  453.     Tk_FreeOptions(configSpecs, (char *) mbPtr, mbPtr->display, 0);
  454.     ckfree((char *) mbPtr);
  455. }
  456.  
  457. /*
  458.  *----------------------------------------------------------------------
  459.  *
  460.  * ConfigureMenuButton --
  461.  *
  462.  *    This procedure is called to process an argv/argc list, plus
  463.  *    the Tk option database, in order to configure (or
  464.  *    reconfigure) a menubutton widget.
  465.  *
  466.  * Results:
  467.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  468.  *    returned, then interp->result contains an error message.
  469.  *
  470.  * Side effects:
  471.  *    Configuration information, such as text string, colors, font,
  472.  *    etc. get set for mbPtr;  old resources get freed, if there
  473.  *    were any.  The menubutton is redisplayed.
  474.  *
  475.  *----------------------------------------------------------------------
  476.  */
  477.  
  478. static int
  479. ConfigureMenuButton(interp, mbPtr, argc, argv, flags)
  480.     Tcl_Interp *interp;        /* Used for error reporting. */
  481.     register MenuButton *mbPtr;    /* Information about widget;  may or may
  482.                  * not already have values for some fields. */
  483.     int argc;            /* Number of valid entries in argv. */
  484.     char **argv;        /* Arguments. */
  485.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  486. {
  487.     XGCValues gcValues;
  488.     GC newGC;
  489.     unsigned long mask;
  490.     int result;
  491.  
  492.     /*
  493.      * Eliminate any existing traces on variables monitored by the button.
  494.      */
  495.  
  496.     if (mbPtr->textVarName != NULL) {
  497.     Tcl_UntraceVar(interp, mbPtr->textVarName,
  498.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  499.         MenuButtonTextVarProc, (ClientData) mbPtr);
  500.     }
  501.  
  502.     result = Tk_ConfigureWidget(interp, mbPtr->tkwin, configSpecs,
  503.         argc, argv, (char *) mbPtr, flags);
  504.     if (result != TCL_OK) {
  505.     return TCL_ERROR;
  506.     }
  507.  
  508.     /*
  509.      * A few options need special processing, such as setting the
  510.      * background from a 3-D border, or filling in complicated
  511.      * defaults that couldn't be specified to Tk_ConfigureWidget.
  512.      */
  513.  
  514.     if (mbPtr->state == tkActiveUid) {
  515.     Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->activeBorder);
  516.     } else {
  517.     Tk_SetBackgroundFromBorder(mbPtr->tkwin, mbPtr->normalBorder);
  518.     if ((mbPtr->state != tkNormalUid) && (mbPtr->state != tkDisabledUid)) {
  519.         Tcl_AppendResult(interp, "bad state value \"", mbPtr->state,
  520.             "\":  must be normal, active, or disabled", (char *) NULL);
  521.         mbPtr->state = tkNormalUid;
  522.         return TCL_ERROR;
  523.     }
  524.     }
  525.  
  526.     gcValues.font = mbPtr->fontPtr->fid;
  527.     gcValues.foreground = mbPtr->normalFg->pixel;
  528.     gcValues.background = Tk_3DBorderColor(mbPtr->normalBorder)->pixel;
  529.  
  530.     /*
  531.      * Note: GraphicsExpose events are disabled in GC's because they're
  532.      * used to copy stuff from an off-screen pixmap onto the screen (we know
  533.      * that there's no problem with obscured areas).
  534.      */
  535.  
  536.     gcValues.graphics_exposures = False;
  537.     newGC = Tk_GetGC(mbPtr->tkwin,
  538.         GCForeground|GCBackground|GCFont|GCGraphicsExposures, &gcValues);
  539.     if (mbPtr->normalTextGC != None) {
  540.     Tk_FreeGC(mbPtr->display, mbPtr->normalTextGC);
  541.     }
  542.     mbPtr->normalTextGC = newGC;
  543.  
  544.     gcValues.font = mbPtr->fontPtr->fid;
  545.     gcValues.foreground = mbPtr->activeFg->pixel;
  546.     gcValues.background = Tk_3DBorderColor(mbPtr->activeBorder)->pixel;
  547.     newGC = Tk_GetGC(mbPtr->tkwin, GCForeground|GCBackground|GCFont,
  548.         &gcValues);
  549.     if (mbPtr->activeTextGC != None) {
  550.     Tk_FreeGC(mbPtr->display, mbPtr->activeTextGC);
  551.     }
  552.     mbPtr->activeTextGC = newGC;
  553.  
  554.     gcValues.font = mbPtr->fontPtr->fid;
  555.     gcValues.background = Tk_3DBorderColor(mbPtr->normalBorder)->pixel;
  556.     if (mbPtr->disabledFg != NULL) {
  557.     gcValues.foreground = mbPtr->disabledFg->pixel;
  558.     mask = GCForeground|GCBackground|GCFont;
  559.     } else {
  560.     gcValues.foreground = gcValues.background;
  561.     if (mbPtr->gray == None) {
  562.         mbPtr->gray = Tk_GetBitmap(interp, mbPtr->tkwin,
  563.             Tk_GetUid("gray50"));
  564.         if (mbPtr->gray == None) {
  565.         return TCL_ERROR;
  566.         }
  567.     }
  568.     gcValues.fill_style = FillStippled;
  569.     gcValues.stipple = mbPtr->gray;
  570.     mask = GCForeground|GCFillStyle|GCStipple;
  571.     }
  572.     newGC = Tk_GetGC(mbPtr->tkwin, mask, &gcValues);
  573.     if (mbPtr->disabledGC != None) {
  574.     Tk_FreeGC(mbPtr->display, mbPtr->disabledGC);
  575.     }
  576.     mbPtr->disabledGC = newGC;
  577.  
  578.     if (mbPtr->padX < 0) {
  579.     mbPtr->padX = 0;
  580.     }
  581.     if (mbPtr->padY < 0) {
  582.     mbPtr->padY = 0;
  583.     }
  584.  
  585.     /*
  586.      * Set up a trace on the variable that determines what's displayed
  587.      * in the menu button, if such a trace has been requested.
  588.      */
  589.  
  590.     if ((mbPtr->bitmap == None) && (mbPtr->textVarName != NULL)) {
  591.     char *value;
  592.  
  593.     value = Tcl_GetVar(interp, mbPtr->textVarName, TCL_GLOBAL_ONLY);
  594.     if (value == NULL) {
  595.         Tcl_SetVar(interp, mbPtr->textVarName, mbPtr->text,
  596.             TCL_GLOBAL_ONLY);
  597.     } else {
  598.         if (mbPtr->text != NULL) {
  599.         ckfree(mbPtr->text);
  600.         }
  601.         mbPtr->text = ckalloc((unsigned) (strlen(value) + 1));
  602.         strcpy(mbPtr->text, value);
  603.     }
  604.     Tcl_TraceVar(interp, mbPtr->textVarName,
  605.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  606.         MenuButtonTextVarProc, (ClientData) mbPtr);
  607.     }
  608.  
  609.     /*
  610.      * Recompute the geometry for the button.
  611.      */
  612.  
  613.     ComputeMenuButtonGeometry(mbPtr);
  614.  
  615.     /*
  616.      * Lastly, arrange for the button to be redisplayed.
  617.      */
  618.  
  619.     if (Tk_IsMapped(mbPtr->tkwin) && !(mbPtr->flags & REDRAW_PENDING)) {
  620.     Tk_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
  621.     mbPtr->flags |= REDRAW_PENDING;
  622.     }
  623.  
  624.     return TCL_OK;
  625. }
  626.  
  627. /*
  628.  *----------------------------------------------------------------------
  629.  *
  630.  * DisplayMenuButton --
  631.  *
  632.  *    This procedure is invoked to display a menubutton widget.
  633.  *
  634.  * Results:
  635.  *    None.
  636.  *
  637.  * Side effects:
  638.  *    Commands are output to X to display the menubutton in its
  639.  *    current mode.
  640.  *
  641.  *----------------------------------------------------------------------
  642.  */
  643.  
  644. static void
  645. DisplayMenuButton(clientData)
  646.     ClientData clientData;    /* Information about widget. */
  647. {
  648.     register MenuButton *mbPtr = (MenuButton *) clientData;
  649.     GC gc;
  650.     Tk_3DBorder border;
  651.     Pixmap pixmap;
  652.     int x = 0;            /* Initialization needed only to stop
  653.                  * compiler warning. */
  654.     int y;
  655.     register Tk_Window tkwin = mbPtr->tkwin;
  656.  
  657.     mbPtr->flags &= ~REDRAW_PENDING;
  658.     if ((mbPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
  659.     return;
  660.     }
  661.  
  662.     if ((mbPtr->state == tkDisabledUid) && (mbPtr->disabledFg != NULL)) {
  663.     gc = mbPtr->disabledGC;
  664.     border = mbPtr->normalBorder;
  665.     } else if (mbPtr->state == tkActiveUid) {
  666.     gc = mbPtr->activeTextGC;
  667.     border = mbPtr->activeBorder;
  668.     } else {
  669.     gc = mbPtr->normalTextGC;
  670.     border = mbPtr->normalBorder;
  671.     }
  672.  
  673.     /*
  674.      * In order to avoid screen flashes, this procedure redraws
  675.      * the menu button in a pixmap, then copies the pixmap to the
  676.      * screen in a single operation.  This means that there's no
  677.      * point in time where the on-sreen image has been cleared.
  678.      */
  679.  
  680.     pixmap = XCreatePixmap(mbPtr->display, Tk_WindowId(tkwin),
  681.         Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
  682.     Tk_Fill3DRectangle(mbPtr->display, pixmap, border,
  683.         0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
  684.  
  685.     /*
  686.      * Display bitmap or text for button.
  687.      */
  688.  
  689.     if (mbPtr->bitmap != None) {
  690.     unsigned int width, height;
  691.  
  692.     Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
  693.     switch (mbPtr->anchor) {
  694.         case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
  695.         x += mbPtr->borderWidth + mbPtr->padX;
  696.         break;
  697.         case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
  698.         x += (Tk_Width(tkwin) - width)/2;
  699.         break;
  700.         default:
  701.         x += Tk_Width(tkwin) - mbPtr->borderWidth - mbPtr->padX
  702.             - width;
  703.         break;
  704.     }
  705.     switch (mbPtr->anchor) {
  706.         case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
  707.         y = mbPtr->borderWidth + mbPtr->padY;
  708.         break;
  709.         case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
  710.         y = (Tk_Height(tkwin) - height)/2;
  711.         break;
  712.         default:
  713.         y = Tk_Height(tkwin) - mbPtr->borderWidth - mbPtr->padY
  714.             - height;
  715.         break;
  716.     }
  717.     XCopyPlane(mbPtr->display, mbPtr->bitmap, pixmap,
  718.         gc, 0, 0, width, height, x, y, 1);
  719.     } else {
  720.     switch (mbPtr->anchor) {
  721.         case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
  722.         x = mbPtr->borderWidth + mbPtr->padX - mbPtr->leftBearing;
  723.         break;
  724.         case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
  725.         x = (Tk_Width(tkwin) - mbPtr->leftBearing
  726.             - mbPtr->rightBearing)/2;
  727.         break;
  728.         default:
  729.         x = Tk_Width(tkwin) - mbPtr->borderWidth - mbPtr->padX
  730.             - mbPtr->rightBearing;
  731.         break;
  732.     }
  733.     switch (mbPtr->anchor) {
  734.         case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
  735.         y = mbPtr->borderWidth + mbPtr->fontPtr->ascent
  736.             + mbPtr->padY;
  737.         break;
  738.         case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
  739.         y = (Tk_Height(tkwin) + mbPtr->fontPtr->ascent
  740.             - mbPtr->fontPtr->descent)/2;
  741.         break;
  742.         default:
  743.         y = Tk_Height(tkwin) - mbPtr->borderWidth - mbPtr->padY
  744.             - mbPtr->fontPtr->descent;
  745.         break;
  746.     }
  747.     XDrawString(mbPtr->display, pixmap, gc, x, y, mbPtr->text,
  748.         mbPtr->textLength);
  749.     if (mbPtr->underline >= 0) {
  750.         TkUnderlineChars(mbPtr->display, pixmap, gc, mbPtr->fontPtr,
  751.             mbPtr->text, x, y, TK_NEWLINES_NOT_SPECIAL,
  752.             mbPtr->underline, mbPtr->underline);
  753.     }
  754.     }
  755.  
  756.     /*
  757.      * If the menu button is disabled with a stipple rather than a special
  758.      * foreground color, generate the stippled effect.
  759.      */
  760.  
  761.     if ((mbPtr->state == tkDisabledUid) && (mbPtr->disabledFg == NULL)) {
  762.     XFillRectangle(mbPtr->display, pixmap, mbPtr->disabledGC,
  763.         mbPtr->borderWidth, mbPtr->borderWidth,
  764.         (unsigned) (Tk_Width(tkwin) - 2*mbPtr->borderWidth),
  765.         (unsigned) (Tk_Height(tkwin) - 2*mbPtr->borderWidth));
  766.     }
  767.  
  768.     /*
  769.      * Draw the border last.  This way, if the menu button's contents
  770.      * overflow onto the border they'll be covered up by the border.
  771.      */
  772.  
  773.     if (mbPtr->relief != TK_RELIEF_FLAT) {
  774.     Tk_Draw3DRectangle(mbPtr->display, pixmap, border,
  775.         0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
  776.         mbPtr->borderWidth, mbPtr->relief);
  777.     }
  778.  
  779.     /*
  780.      * Copy the information from the off-screen pixmap onto the screen,
  781.      * then delete the pixmap.
  782.      */
  783.  
  784.     XCopyArea(mbPtr->display, pixmap, Tk_WindowId(tkwin),
  785.     mbPtr->normalTextGC, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, 0);
  786.     XFreePixmap(mbPtr->display, pixmap);
  787. }
  788.  
  789. /*
  790.  *--------------------------------------------------------------
  791.  *
  792.  * MenuButtonEventProc --
  793.  *
  794.  *    This procedure is invoked by the Tk dispatcher for various
  795.  *    events on buttons.
  796.  *
  797.  * Results:
  798.  *    None.
  799.  *
  800.  * Side effects:
  801.  *    When the window gets deleted, internal structures get
  802.  *    cleaned up.  When it gets exposed, it is redisplayed.
  803.  *
  804.  *--------------------------------------------------------------
  805.  */
  806.  
  807. static void
  808. MenuButtonEventProc(clientData, eventPtr)
  809.     ClientData clientData;    /* Information about window. */
  810.     XEvent *eventPtr;        /* Information about event. */
  811. {
  812.     MenuButton *mbPtr = (MenuButton *) clientData;
  813.     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
  814.     if ((mbPtr->tkwin != NULL) && !(mbPtr->flags & REDRAW_PENDING)) {
  815.         Tk_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
  816.         mbPtr->flags |= REDRAW_PENDING;
  817.     }
  818.     } else if (eventPtr->type == DestroyNotify) {
  819.     Tcl_DeleteCommand(mbPtr->interp, Tk_PathName(mbPtr->tkwin));
  820.     mbPtr->tkwin = NULL;
  821.     if (mbPtr->flags & REDRAW_PENDING) {
  822.         Tk_CancelIdleCall(DisplayMenuButton, (ClientData) mbPtr);
  823.     }
  824.     Tk_EventuallyFree((ClientData) mbPtr, DestroyMenuButton);
  825.     }
  826. }
  827.  
  828. /*
  829.  *----------------------------------------------------------------------
  830.  *
  831.  * ComputeMenuButtonGeometry --
  832.  *
  833.  *    After changes in a menu button's text or bitmap, this procedure
  834.  *    recomputes the menu button's geometry and passes this information
  835.  *    along to the geometry manager for the window.
  836.  *
  837.  * Results:
  838.  *    None.
  839.  *
  840.  * Side effects:
  841.  *    The menu button's window may change size.
  842.  *
  843.  *----------------------------------------------------------------------
  844.  */
  845.  
  846. static void
  847. ComputeMenuButtonGeometry(mbPtr)
  848.     register MenuButton *mbPtr;        /* Widget record for menu button. */
  849. {
  850.     XCharStruct bbox;
  851.     int dummy;
  852.     unsigned int width, height;
  853.  
  854.     if (mbPtr->bitmap != None) {
  855.     Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
  856.     if (mbPtr->width > 0) {
  857.         width = mbPtr->width;
  858.     }
  859.     if (mbPtr->height > 0) {
  860.         height = mbPtr->height;
  861.     }
  862.     } else {
  863.     mbPtr->textLength = strlen(mbPtr->text);
  864.     XTextExtents(mbPtr->fontPtr, mbPtr->text, mbPtr->textLength,
  865.         &dummy, &dummy, &dummy, &bbox);
  866.     mbPtr->leftBearing = bbox.lbearing;
  867.     mbPtr->rightBearing = bbox.rbearing;
  868.     width = bbox.rbearing - bbox.lbearing;
  869.     height = mbPtr->fontPtr->ascent + mbPtr->fontPtr->descent;
  870.     if (mbPtr->width > 0) {
  871.         width = mbPtr->width * XTextWidth(mbPtr->fontPtr, "0", 1);
  872.     }
  873.     if (mbPtr->height > 0) {
  874.         height *= mbPtr->height;
  875.     }
  876.     }
  877.  
  878.     width += 2*mbPtr->padX;
  879.     height += 2*mbPtr->padY;
  880.     Tk_GeometryRequest(mbPtr->tkwin, (int) (width + 2*mbPtr->borderWidth),
  881.         (int) (height + 2*mbPtr->borderWidth));
  882.     Tk_SetInternalBorder(mbPtr->tkwin, mbPtr->borderWidth);
  883. }
  884.  
  885. /*
  886.  *--------------------------------------------------------------
  887.  *
  888.  * MenuButtonTextVarProc --
  889.  *
  890.  *    This procedure is invoked when someone changes the variable
  891.  *    whose contents are to be displayed in a menu button.
  892.  *
  893.  * Results:
  894.  *    NULL is always returned.
  895.  *
  896.  * Side effects:
  897.  *    The text displayed in the menu button will change to match the
  898.  *    variable.
  899.  *
  900.  *--------------------------------------------------------------
  901.  */
  902.  
  903.     /* ARGSUSED */
  904. static char *
  905. MenuButtonTextVarProc(clientData, interp, name1, name2, flags)
  906.     ClientData clientData;    /* Information about button. */
  907.     Tcl_Interp *interp;        /* Interpreter containing variable. */
  908.     char *name1;        /* Name of variable. */
  909.     char *name2;        /* Second part of variable name. */
  910.     int flags;            /* Information about what happened. */
  911. {
  912.     register MenuButton *mbPtr = (MenuButton *) clientData;
  913.     char *value;
  914.  
  915.     /*
  916.      * If the variable is unset, then immediately recreate it unless
  917.      * the whole interpreter is going away.
  918.      */
  919.  
  920.     if (flags & TCL_TRACE_UNSETS) {
  921.     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
  922.         Tcl_SetVar2(interp, name1, name2, mbPtr->text,
  923.             flags & TCL_GLOBAL_ONLY);
  924.         Tcl_TraceVar2(interp, name1, name2,
  925.             TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  926.             MenuButtonTextVarProc, clientData);
  927.     }
  928.     return (char *) NULL;
  929.     }
  930.  
  931.     value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
  932.     if (value == NULL) {
  933.     value = "";
  934.     }
  935.     if (mbPtr->text != NULL) {
  936.     ckfree(mbPtr->text);
  937.     }
  938.     mbPtr->text = ckalloc((unsigned) (strlen(value) + 1));
  939.     strcpy(mbPtr->text, value);
  940.     ComputeMenuButtonGeometry(mbPtr);
  941.  
  942.     if ((mbPtr->tkwin != NULL) && Tk_IsMapped(mbPtr->tkwin)
  943.         && !(mbPtr->flags & REDRAW_PENDING)) {
  944.     Tk_DoWhenIdle(DisplayMenuButton, (ClientData) mbPtr);
  945.     mbPtr->flags |= REDRAW_PENDING;
  946.     }
  947.     return (char *) NULL;
  948. }
  949.