/* * tkMenu.c -- * * This module implements menus for the Tk toolkit. The menus * support normal button entries, plus check buttons, radio * buttons, iconic forms of all of the above, and separator * entries. * * Copyright (c) 1990-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * SCCS: @(#) tkMenu.c 1.102 96/03/26 16:07:08 */ #include "tkPort.h" #include "default.h" #include "tkInt.h" /* * One of the following data structures is kept for each entry of each * menu managed by this file: */ typedef struct MenuEntry { int type; /* Type of menu entry; see below for * valid types. */ struct Menu *menuPtr; /* Menu with which this entry is associated. */ char *label; /* Main text label displayed in entry (NULL * if no label). Malloc'ed. */ int labelLength; /* Number of non-NULL characters in label. */ int underline; /* Index of character to underline. */ Pixmap bitmap; /* Bitmap to display in menu entry, or None. * If not None then label is ignored. */ char *imageString; /* Name of image to display (malloc'ed), or * NULL. If non-NULL, bitmap, text, and * textVarName are ignored. */ Tk_Image image; /* Image to display in menu entry, or NULL if * none. */ char *selectImageString; /* Name of image to display when selected * (malloc'ed), or NULL. */ Tk_Image selectImage; /* Image to display in entry when selected, * or NULL if none. Ignored if image is * NULL. */ char *accel; /* Accelerator string displayed at right * of menu entry. NULL means no such * accelerator. Malloc'ed. */ int accelLength; /* Number of non-NULL characters in * accelerator. */ /* * Information related to displaying entry: */ Tk_Uid state; /* State of button for display purposes: * normal, active, or disabled. */ int height; /* Number of pixels occupied by entry in * vertical dimension, including raised * border drawn around entry when active. */ int y; /* Y-coordinate of topmost pixel in entry. */ int indicatorOn; /* True means draw indicator, false means * don't draw it. */ int indicatorDiameter; /* Size of indicator display, in pixels. */ Tk_3DBorder border; /* Structure used to draw background for * entry. NULL means use overall border * for menu. */ XColor *fg; /* Foreground color to use for entry. NULL * means use foreground color from menu. */ Tk_3DBorder activeBorder; /* Used to draw background and border when * element is active. NULL means use * activeBorder from menu. */ XColor *activeFg; /* Foreground color to use when entry is * active. NULL means use active foreground * from menu. */ XFontStruct *fontPtr; /* Text font for menu entries. NULL means * use overall font for menu. */ GC textGC; /* GC for drawing text in entry. NULL means * use overall textGC for menu. */ GC activeGC; /* GC for drawing text in entry when active. * NULL means use overall activeGC for * menu. */ GC disabledGC; /* Used to produce disabled effect for entry. * NULL means use overall disabledGC from * menu structure. See comments for * disabledFg in menu structure for more * information. */ XColor *indicatorFg; /* Color for indicators in radio and check * button entries. NULL means use indicatorFg * GC from menu. */ GC indicatorGC; /* For drawing indicators. None means use * GC from menu. */ /* * Information used to implement this entry's action: */ char *command; /* Command to invoke when entry is invoked. * Malloc'ed. */ char *name; /* Name of variable (for check buttons and * radio buttons) or menu (for cascade * entries). Malloc'ed.*/ char *onValue; /* Value to store in variable when selected * (only for radio and check buttons). * Malloc'ed. */ char *offValue; /* Value to store in variable when not * selected (only for check buttons). * Malloc'ed. */ /* * Miscellaneous information: */ int flags; /* Various flags. See below for definitions. */ } MenuEntry; /* * Flag values defined for menu entries: * * ENTRY_SELECTED: Non-zero means this is a radio or check * button and that it should be drawn in * the "selected" state. * ENTRY_NEEDS_REDISPLAY: Non-zero means the entry should be redisplayed. */ #define ENTRY_SELECTED 1 #define ENTRY_NEEDS_REDISPLAY 4 /* * Types defined for MenuEntries: */ #define COMMAND_ENTRY 0 #define SEPARATOR_ENTRY 1 #define CHECK_BUTTON_ENTRY 2 #define RADIO_BUTTON_ENTRY 3 #define CASCADE_ENTRY 4 #define TEAROFF_ENTRY 5 /* * Mask bits for above types: */ #define COMMAND_MASK TK_CONFIG_USER_BIT #define SEPARATOR_MASK (TK_CONFIG_USER_BIT << 1) #define CHECK_BUTTON_MASK (TK_CONFIG_USER_BIT << 2) #define RADIO_BUTTON_MASK (TK_CONFIG_USER_BIT << 3) #define CASCADE_MASK (TK_CONFIG_USER_BIT << 4) #define TEAROFF_MASK (TK_CONFIG_USER_BIT << 5) #define ALL_MASK (COMMAND_MASK | SEPARATOR_MASK \ | CHECK_BUTTON_MASK | RADIO_BUTTON_MASK | CASCADE_MASK | TEAROFF_MASK) /* * Configuration specs for individual menu entries: */ static Tk_ConfigSpec entryConfigSpecs[] = { {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(MenuEntry, activeBorder), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_NULL_OK}, {TK_CONFIG_COLOR, "-activeforeground", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_ACTIVE_FG, Tk_Offset(MenuEntry, activeFg), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-accelerator", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_ACCELERATOR, Tk_Offset(MenuEntry, accel), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_NULL_OK}, {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_BG, Tk_Offset(MenuEntry, border), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |SEPARATOR_MASK|TEAROFF_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_BITMAP, "-bitmap", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_BITMAP, Tk_Offset(MenuEntry, bitmap), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_NULL_OK}, #ifdef STk_CODE {TK_CONFIG_CLOSURE, "-command", (char *) NULL, (char *) NULL, #else {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL, #endif DEF_MENU_ENTRY_COMMAND, Tk_Offset(MenuEntry, command), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_NULL_OK}, {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_FONT, Tk_Offset(MenuEntry, fontPtr), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_NULL_OK}, {TK_CONFIG_COLOR, "-foreground", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_FG, Tk_Offset(MenuEntry, fg), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-image", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_IMAGE, Tk_Offset(MenuEntry, imageString), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_NULL_OK}, {TK_CONFIG_BOOLEAN, "-indicatoron", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_INDICATOR, Tk_Offset(MenuEntry, indicatorOn), CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_LABEL, Tk_Offset(MenuEntry, label), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK}, #ifdef STk_CODE {TK_CONFIG_MENU, "-menu", (char *) NULL, (char *) NULL, #else {TK_CONFIG_STRING, "-menu", (char *) NULL, (char *) NULL, #endif DEF_MENU_ENTRY_MENU, Tk_Offset(MenuEntry, name), CASCADE_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-offvalue", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_OFF_VALUE, Tk_Offset(MenuEntry, offValue), CHECK_BUTTON_MASK}, {TK_CONFIG_STRING, "-onvalue", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_ON_VALUE, Tk_Offset(MenuEntry, onValue), CHECK_BUTTON_MASK}, {TK_CONFIG_COLOR, "-selectcolor", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_SELECT, Tk_Offset(MenuEntry, indicatorFg), CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-selectimage", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_SELECT_IMAGE, Tk_Offset(MenuEntry, selectImageString), CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_UID, "-state", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_STATE, Tk_Offset(MenuEntry, state), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TEAROFF_MASK|TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_STRING, "-value", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_VALUE, Tk_Offset(MenuEntry, onValue), RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_CHECK_VARIABLE, Tk_Offset(MenuEntry, name), CHECK_BUTTON_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_RADIO_VARIABLE, Tk_Offset(MenuEntry, name), RADIO_BUTTON_MASK}, {TK_CONFIG_INT, "-underline", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_UNDERLINE, Tk_Offset(MenuEntry, underline), COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK |TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, 0} }; /* * A data structure of the following type is kept for each * menu managed by this file: */ typedef struct Menu { Tk_Window tkwin; /* Window that embodies the pane. NULL * means that the window has been destroyed * but the data structures haven't yet been * cleaned up.*/ Display *display; /* Display containing widget. Needed, among * other things, so that resources can be * freed up even after tkwin has gone away. */ Tcl_Interp *interp; /* Interpreter associated with menu. */ Tcl_Command widgetCmd; /* Token for menu's widget command. */ MenuEntry **entries; /* Array of pointers to all the entries * in the menu. NULL means no entries. */ int numEntries; /* Number of elements in entries. */ int active; /* Index of active entry. -1 means * nothing active. */ /* * Information used when displaying widget: */ Tk_3DBorder border; /* Structure used to draw 3-D * border and background for menu. */ int borderWidth; /* Width of border around whole menu. */ Tk_3DBorder activeBorder; /* Used to draw background and border for * active element (if any). */ int activeBorderWidth; /* Width of border around active element. */ int relief; /* 3-d effect: TK_RELIEF_RAISED, etc. */ XFontStruct *fontPtr; /* Text font for menu entries. */ XColor *fg; /* Foreground color for entries. */ GC textGC; /* GC for drawing text and other features * of menu entries. */ XColor *disabledFg; /* Foreground color when disabled. NULL * means use normalFg with a 50% stipple * instead. */ Pixmap gray; /* Bitmap for drawing disabled entries in * a stippled fashion. None means not * allocated yet. */ GC disabledGC; /* Used to produce disabled effect. If * disabledFg isn't NULL, this GC is used to * draw text and icons for disabled entries. * Otherwise text and icons are drawn with * normalGC and this GC is used to stipple * background across them. */ XColor *activeFg; /* Foreground color for active entry. */ GC activeGC; /* GC for drawing active entry. */ XColor *indicatorFg; /* Color for indicators in radio and check * button entries. */ GC indicatorGC; /* For drawing indicators. */ int indicatorSpace; /* Number of pixels to allow for displaying * indicators in menu entries (includes extra * space around indicator). */ int labelWidth; /* Number of pixels to allow for displaying * labels in menu entries. */ /* * Miscellaneous information: */ int tearOff; /* 1 means this is a tear-off menu, so the * first entry always shows a dashed stripe * for tearing off. */ char *tearOffCommand; /* If non-NULL, points to a command to * run whenever the menu is torn-off. */ int transient; /* 1 means menu is only posted briefly as * a popup or pulldown or cascade. 0 means * menu is always visible, e.g. as a torn-off * menu. Determines whether save_under and * override_redirect should be set. */ Tk_Cursor cursor; /* Current cursor for window, or None. */ char *takeFocus; /* Value of -takefocus option; not used in * the C code, but used by keyboard traversal * scripts. Malloc'ed, but may be NULL. */ char *postCommand; /* Command to execute just before posting * this menu, or NULL. Malloc-ed. */ MenuEntry *postedCascade; /* Points to menu entry for cascaded * submenu that is currently posted, or * NULL if no submenu posted. */ int flags; /* Various flags; see below for * definitions. */ } Menu; /* * Flag bits for menus: * * REDRAW_PENDING: Non-zero means a DoWhenIdle handler * has already been queued to redraw * this window. * RESIZE_PENDING: Non-zero means a call to ComputeMenuGeometry * has already been scheduled. */ #define REDRAW_PENDING 1 #define RESIZE_PENDING 2 /* * Configuration specs valid for the menu as a whole: */ static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground", DEF_MENU_ACTIVE_BG_COLOR, Tk_Offset(Menu, activeBorder), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground", DEF_MENU_ACTIVE_BG_MONO, Tk_Offset(Menu, activeBorder), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth", DEF_MENU_ACTIVE_BORDER_WIDTH, Tk_Offset(Menu, activeBorderWidth), 0}, {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background", DEF_MENU_ACTIVE_FG_COLOR, Tk_Offset(Menu, activeFg), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background", DEF_MENU_ACTIVE_FG_MONO, Tk_Offset(Menu, activeFg), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_MENU_BG_COLOR, Tk_Offset(Menu, border), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_MENU_BG_MONO, Tk_Offset(Menu, border), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_MENU_BORDER_WIDTH, Tk_Offset(Menu, borderWidth), 0}, {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", DEF_MENU_CURSOR, Tk_Offset(Menu, cursor), TK_CONFIG_NULL_OK}, {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground", "DisabledForeground", DEF_MENU_DISABLED_FG_COLOR, Tk_Offset(Menu, disabledFg), TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK}, {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground", "DisabledForeground", DEF_MENU_DISABLED_FG_MONO, Tk_Offset(Menu, disabledFg), TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK}, {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_FONT, "-font", "font", "Font", DEF_MENU_FONT, Tk_Offset(Menu, fontPtr), 0}, {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_MENU_FG, Tk_Offset(Menu, fg), 0}, #ifdef STk_CODE {TK_CONFIG_CLOSURE, "-postcommand", "postCommand", "Command", #else {TK_CONFIG_STRING, "-postcommand", "postCommand", "Command", #endif DEF_MENU_POST_COMMAND, Tk_Offset(Menu, postCommand), TK_CONFIG_NULL_OK}, {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_MENU_RELIEF, Tk_Offset(Menu, relief), 0}, {TK_CONFIG_COLOR, "-selectcolor", "selectColor", "Background", DEF_MENU_SELECT_COLOR, Tk_Offset(Menu, indicatorFg), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-selectcolor", "selectColor", "Background", DEF_MENU_SELECT_MONO, Tk_Offset(Menu, indicatorFg), TK_CONFIG_MONO_ONLY}, #ifdef STk_CODE {TK_CONFIG_CLOSURE, "-takefocus", "takeFocus", "TakeFocus", #else {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", #endif DEF_MENU_TAKE_FOCUS, Tk_Offset(Menu, takeFocus), TK_CONFIG_NULL_OK}, {TK_CONFIG_BOOLEAN, "-tearoff", "tearOff", "TearOff", DEF_MENU_TEAROFF, Tk_Offset(Menu, tearOff), 0}, #ifdef STk_CODE {TK_CONFIG_CLOSURE, "-tearoffcommand", "tearOffCommand", "TearOffCommand", #else {TK_CONFIG_STRING, "-tearoffcommand", "tearOffCommand", "TearOffCommand", #endif DEF_MENU_TEAROFF_CMD, Tk_Offset(Menu, tearOffCommand), TK_CONFIG_NULL_OK}, {TK_CONFIG_BOOLEAN, "-transient", "transient", "Transient", DEF_MENU_TRANSIENT, Tk_Offset(Menu, transient), 0}, {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, 0} }; /* * Various geometry definitions: */ #define CASCADE_ARROW_HEIGHT 10 #define CASCADE_ARROW_WIDTH 8 #define DECORATION_BORDER_WIDTH 2 #define MARGIN_WIDTH 2 /* * Forward declarations for procedures defined later in this file: */ static int ActivateMenuEntry _ANSI_ARGS_((Menu *menuPtr, int index)); static void ComputeMenuGeometry _ANSI_ARGS_(( ClientData clientData)); static int ConfigureMenu _ANSI_ARGS_((Tcl_Interp *interp, Menu *menuPtr, int argc, char **argv, int flags)); static int ConfigureMenuEntry _ANSI_ARGS_((Tcl_Interp *interp, Menu *menuPtr, MenuEntry *mePtr, int index, int argc, char **argv, int flags)); static void DestroyMenu _ANSI_ARGS_((char *memPtr)); static void DestroyMenuEntry _ANSI_ARGS_((char *memPtr)); static void DisplayMenu _ANSI_ARGS_((ClientData clientData)); static void EventuallyRedrawMenu _ANSI_ARGS_((Menu *menuPtr, MenuEntry *mePtr)); static int GetMenuIndex _ANSI_ARGS_((Tcl_Interp *interp, Menu *menuPtr, char *string, int lastOK, int *indexPtr)); static int MenuAddOrInsert _ANSI_ARGS_((Tcl_Interp *interp, Menu *menuPtr, char *indexString, int argc, char **argv)); static void MenuCmdDeletedProc _ANSI_ARGS_(( ClientData clientData)); static void MenuEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); static void MenuImageProc _ANSI_ARGS_((ClientData clientData, int x, int y, int width, int height, int imgWidth, int imgHeight)); static MenuEntry * MenuNewEntry _ANSI_ARGS_((Menu *menuPtr, int index, int type)); static void MenuSelectImageProc _ANSI_ARGS_((ClientData clientData, int x, int y, int width, int height, int imgWidth, int imgHeight)); static char * MenuVarProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)); static int MenuWidgetCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv)); static int PostSubmenu _ANSI_ARGS_((Tcl_Interp *interp, Menu *menuPtr, MenuEntry *mePtr)); /* *-------------------------------------------------------------- * * Tk_MenuCmd -- * * This procedure is invoked to process the "menu" Tcl * command. See the user documentation for details on * what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ int Tk_MenuCmd(clientData, interp, argc, argv) ClientData clientData; /* Main window associated with * interpreter. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { Tk_Window tkwin = (Tk_Window) clientData; Tk_Window new; register Menu *menuPtr; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " pathName ?options?\"", (char *) NULL); return TCL_ERROR; } /* * Create the new window. Set override-redirect so the window * manager won't add a border or argue about placement, and set * save-under so that the window can pop up and down without a * lot of re-drawing. */ new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], ""); if (new == NULL) { return TCL_ERROR; } /* * Initialize the data structure for the menu. */ menuPtr = (Menu *) ckalloc(sizeof(Menu)); menuPtr->tkwin = new; menuPtr->display = Tk_Display(new); menuPtr->interp = interp; menuPtr->widgetCmd = Tcl_CreateCommand(interp, Tk_PathName(menuPtr->tkwin), MenuWidgetCmd, (ClientData) menuPtr, MenuCmdDeletedProc); menuPtr->entries = NULL; menuPtr->numEntries = 0; menuPtr->active = -1; menuPtr->border = NULL; menuPtr->borderWidth = 0; menuPtr->relief = TK_RELIEF_FLAT; menuPtr->activeBorder = NULL; menuPtr->activeBorderWidth = 0; menuPtr->fontPtr = NULL; menuPtr->fg = NULL; menuPtr->textGC = None; menuPtr->disabledFg = NULL; menuPtr->gray = None; menuPtr->disabledGC = None; menuPtr->activeFg = NULL; menuPtr->activeGC = None; menuPtr->indicatorFg = NULL; menuPtr->indicatorGC = None; menuPtr->indicatorSpace = 0; menuPtr->labelWidth = 0; menuPtr->tearOff = 1; menuPtr->tearOffCommand = NULL; menuPtr->cursor = None; menuPtr->takeFocus = NULL; menuPtr->postCommand = NULL; menuPtr->postedCascade = NULL; menuPtr->flags = 0; Tk_SetClass(new, "Menu"); Tk_CreateEventHandler(menuPtr->tkwin, ExposureMask|StructureNotifyMask, MenuEventProc, (ClientData) menuPtr); if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) { goto error; } #ifdef STk_CODE STk_sharp_dot_result(interp, Tk_PathName(menuPtr->tkwin)); #else interp->result = Tk_PathName(menuPtr->tkwin); #endif return TCL_OK; error: Tk_DestroyWindow(menuPtr->tkwin); return TCL_ERROR; } /* *-------------------------------------------------------------- * * MenuWidgetCmd -- * * This procedure is invoked to process the Tcl command * that corresponds to a widget managed by this module. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ static int MenuWidgetCmd(clientData, interp, argc, argv) ClientData clientData; /* Information about menu widget. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { register Menu *menuPtr = (Menu *) clientData; register MenuEntry *mePtr; int result = TCL_OK; size_t length; int c; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " option ?arg arg ...?\"", (char *) NULL); return TCL_ERROR; } Tcl_Preserve((ClientData) menuPtr); c = argv[1][0]; length = strlen(argv[1]); if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0) && (length >= 2)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " activate index\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) { goto error; } if (menuPtr->active == index) { goto done; } if (index >= 0) { if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY) || (menuPtr->entries[index]->state == tkDisabledUid)) { index = -1; } } result = ActivateMenuEntry(menuPtr, index); } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0) && (length >= 2)) { if (argc < 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " add type ?options?\"", (char *) NULL); goto error; } if (MenuAddOrInsert(interp, menuPtr, (char *) NULL, argc-2, argv+2) != TCL_OK) { goto error; } } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0) && (length >= 2)) { if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " cget option\"", (char *) NULL); goto error; } result = Tk_ConfigureValue(interp, menuPtr->tkwin, configSpecs, (char *) menuPtr, argv[2], 0); } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0) && (length >= 2)) { if (argc == 2) { result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs, (char *) menuPtr, (char *) NULL, 0); } else if (argc == 3) { result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs, (char *) menuPtr, argv[2], 0); } else { result = ConfigureMenu(interp, menuPtr, argc-2, argv+2, TK_CONFIG_ARGV_ONLY); } } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) { int first, last, i, numDeleted; if ((argc != 3) && (argc != 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " delete first ?last?\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &first) != TCL_OK) { goto error; } if (argc == 3) { last = first; } else { if (GetMenuIndex(interp, menuPtr, argv[3], 0, &last) != TCL_OK) { goto error; } } if (menuPtr->tearOff && (first == 0)) { /* * Sorry, can't delete the tearoff entry; must reconfigure * the menu. */ first = 1; } if ((first < 0) || (last < first)) { goto done; } numDeleted = last + 1 - first; for (i = first; i <= last; i++) { Tcl_EventuallyFree((ClientData) menuPtr->entries[i], DestroyMenuEntry); } for (i = last+1; i < menuPtr->numEntries; i++) { menuPtr->entries[i-numDeleted] = menuPtr->entries[i]; } menuPtr->numEntries -= numDeleted; if ((menuPtr->active >= first) && (menuPtr->active <= last)) { menuPtr->active = -1; } else if (menuPtr->active > last) { menuPtr->active -= numDeleted; } if (!(menuPtr->flags & RESIZE_PENDING)) { menuPtr->flags |= RESIZE_PENDING; Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr); } } else if ((c == 'e') && (length >= 7) && (strncmp(argv[1], "entrycget", length) == 0)) { int index; if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " entrycget index option\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) { goto error; } if (index < 0) { goto done; } mePtr = menuPtr->entries[index]; Tcl_Preserve((ClientData) mePtr); result = Tk_ConfigureValue(interp, menuPtr->tkwin, entryConfigSpecs, (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type); Tcl_Release((ClientData) mePtr); } else if ((c == 'e') && (length >= 7) && (strncmp(argv[1], "entryconfigure", length) == 0)) { int index; if (argc < 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " entryconfigure index ?option value ...?\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) { goto error; } if (index < 0) { goto done; } mePtr = menuPtr->entries[index]; Tcl_Preserve((ClientData) mePtr); if (argc == 3) { result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs, (char *) mePtr, (char *) NULL, COMMAND_MASK << mePtr->type); } else if (argc == 4) { result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs, (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type); } else { result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3, argv+3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type); } Tcl_Release((ClientData) mePtr); } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0) && (length >= 3)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " index string\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) { goto error; } if (index < 0) { #ifdef STk_CODE interp->result = "\"none\""; #else interp->result = "none"; #endif } else { sprintf(interp->result, "%d", index); } } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0) && (length >= 3)) { if (argc < 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " insert index type ?options?\"", (char *) NULL); goto error; } if (MenuAddOrInsert(interp, menuPtr, argv[2], argc-3, argv+3) != TCL_OK) { goto error; } } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0) && (length >= 3)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " invoke index\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) { goto error; } if (index < 0) { goto done; } mePtr = menuPtr->entries[index]; if (mePtr->state == tkDisabledUid) { goto done; } Tcl_Preserve((ClientData) mePtr); if (mePtr->type == CHECK_BUTTON_ENTRY) { if (mePtr->flags & ENTRY_SELECTED) { if (Tcl_SetVar(interp, mePtr->name, mePtr->offValue, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } } else { if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } } } else if (mePtr->type == RADIO_BUTTON_ENTRY) { if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } } if ((result == TCL_OK) && (mePtr->command != NULL)) { result = TkCopyAndGlobalEval(interp, mePtr->command); } if ((result == TCL_OK) && (mePtr->type == CASCADE_ENTRY)) { result = PostSubmenu(menuPtr->interp, menuPtr, mePtr); } Tcl_Release((ClientData) mePtr); } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0) && (length == 4)) { int x, y, tmp, vRootX, vRootY; int vRootWidth, vRootHeight; if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " post x y\"", (char *) NULL); goto error; } if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK) || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) { goto error; } /* * De-activate any active element. */ ActivateMenuEntry(menuPtr, -1); /* * If there is a command for the menu, execute it. This * may change the size of the menu, so be sure to recompute * the menu's geometry if needed. */ if (menuPtr->postCommand != NULL) { result = TkCopyAndGlobalEval(menuPtr->interp, menuPtr->postCommand); if (result != TCL_OK) { return result; } if (menuPtr->flags & RESIZE_PENDING) { Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr); ComputeMenuGeometry((ClientData) menuPtr); } } /* * Adjust the position of the menu if necessary to keep it * visible on the screen. There are two special tricks to * make this work right: * * 1. If a virtual root window manager is being used then * the coordinates are in the virtual root window of * menuPtr's parent; since the menu uses override-redirect * mode it will be in the *real* root window for the screen, * so we have to map the coordinates from the virtual root * (if any) to the real root. Can't get the virtual root * from the menu itself (it will never be seen by the wm) * so use its parent instead (it would be better to have an * an option that names a window to use for this...). * 2. The menu may not have been mapped yet, so its current size * might be the default 1x1. To compute how much space it * needs, use its requested size, not its actual size. */ Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY, &vRootWidth, &vRootHeight); x += vRootX; y += vRootY; tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin)) - Tk_ReqWidth(menuPtr->tkwin); if (x > tmp) { x = tmp; } if (x < 0) { x = 0; } tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin)) - Tk_ReqHeight(menuPtr->tkwin); if (y > tmp) { y = tmp; } if (y < 0) { y = 0; } if ((x != Tk_X(menuPtr->tkwin)) || (y != Tk_Y(menuPtr->tkwin))) { Tk_MoveToplevelWindow(menuPtr->tkwin, x, y); } if (!Tk_IsMapped(menuPtr->tkwin)) { Tk_MapWindow(menuPtr->tkwin); } XRaiseWindow(menuPtr->display, Tk_WindowId(menuPtr->tkwin)); } else if ((c == 'p') && (strncmp(argv[1], "postcascade", length) == 0) && (length > 4)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " postcascade index\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) { goto error; } if ((index < 0) || (menuPtr->entries[index]->type != CASCADE_ENTRY)) { result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL); } else { result = PostSubmenu(interp, menuPtr, menuPtr->entries[index]); } } else if ((c == 't') && (strncmp(argv[1], "type", length) == 0)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " type index\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) { goto error; } if (index < 0) { goto done; } mePtr = menuPtr->entries[index]; switch (mePtr->type) { #ifdef STk_CODE case COMMAND_ENTRY: interp->result = "\"command\""; break; case SEPARATOR_ENTRY: interp->result = "\"separator\""; break; case CHECK_BUTTON_ENTRY: interp->result = "\"checkbutton\""; break; case RADIO_BUTTON_ENTRY: interp->result = "\"radiobutton\""; break; case CASCADE_ENTRY: interp->result = "\"cascade\""; break; case TEAROFF_ENTRY: interp->result = "\"tearoff\""; break; #else case COMMAND_ENTRY: interp->result = "command"; break; case SEPARATOR_ENTRY: interp->result = "separator"; break; case CHECK_BUTTON_ENTRY: interp->result = "checkbutton"; break; case RADIO_BUTTON_ENTRY: interp->result = "radiobutton"; break; case CASCADE_ENTRY: interp->result = "cascade"; break; case TEAROFF_ENTRY: interp->result = "tearoff"; break; #endif } } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) { if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " unpost\"", (char *) NULL); goto error; } Tk_UnmapWindow(menuPtr->tkwin); if (result == TCL_OK) { result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL); } } else if ((c == 'y') && (strncmp(argv[1], "yposition", length) == 0)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " yposition index\"", (char *) NULL); goto error; } if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) { goto error; } if (index < 0) { interp->result = "0"; } else { sprintf(interp->result, "%d", menuPtr->entries[index]->y); } } else { Tcl_AppendResult(interp, "bad option \"", argv[1], "\": must be activate, add, cget, configure, delete, ", "entrycget, entryconfigure, index, insert, invoke, ", "post, postcascade, type, unpost, or yposition", (char *) NULL); goto error; } done: Tcl_Release((ClientData) menuPtr); return result; error: Tcl_Release((ClientData) menuPtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * DestroyMenu -- * * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release * to clean up the internal structure of a menu at a safe time * (when no-one is using it anymore). * * Results: * None. * * Side effects: * Everything associated with the menu is freed up. * *---------------------------------------------------------------------- */ static void DestroyMenu(memPtr) char *memPtr; /* Info about menu widget. */ { register Menu *menuPtr = (Menu *) memPtr; int i; /* * Free up all the stuff that requires special handling, then * let Tk_FreeOptions handle all the standard option-related * stuff. */ for (i = 0; i < menuPtr->numEntries; i++) { DestroyMenuEntry((char *) menuPtr->entries[i]); } if (menuPtr->entries != NULL) { ckfree((char *) menuPtr->entries); } if (menuPtr->textGC != None) { Tk_FreeGC(menuPtr->display, menuPtr->textGC); } if (menuPtr->gray != None) { Tk_FreeBitmap(menuPtr->display, menuPtr->gray); } if (menuPtr->disabledGC != None) { Tk_FreeGC(menuPtr->display, menuPtr->disabledGC); } if (menuPtr->activeGC != None) { Tk_FreeGC(menuPtr->display, menuPtr->activeGC); } if (menuPtr->indicatorGC != None) { Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC); } Tk_FreeOptions(configSpecs, (char *) menuPtr, menuPtr->display, 0); ckfree((char *) menuPtr); } /* *---------------------------------------------------------------------- * * DestroyMenuEntry -- * * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release * to clean up the internal structure of a menu entry at a safe time * (when no-one is using it anymore). * * Results: * None. * * Side effects: * Everything associated with the menu entry is freed up. * *---------------------------------------------------------------------- */ static void DestroyMenuEntry(memPtr) char *memPtr; /* Pointer to entry to be freed. */ { register MenuEntry *mePtr = (MenuEntry *) memPtr; Menu *menuPtr = mePtr->menuPtr; /* * Free up all the stuff that requires special handling, then * let Tk_FreeOptions handle all the standard option-related * stuff. */ if (menuPtr->postedCascade == mePtr) { /* * Ignore errors while unposting the menu, since it's possible * that the menu has already been deleted and the unpost will * generate an error. */ PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL); } if (mePtr->image != NULL) { Tk_FreeImage(mePtr->image); } if (mePtr->textGC != None) { Tk_FreeGC(menuPtr->display, mePtr->textGC); } if (mePtr->activeGC != None) { Tk_FreeGC(menuPtr->display, mePtr->activeGC); } if (mePtr->disabledGC != None) { Tk_FreeGC(menuPtr->display, mePtr->disabledGC); } if (mePtr->indicatorGC != None) { Tk_FreeGC(menuPtr->display, mePtr->indicatorGC); } if (mePtr->name != NULL) { Tcl_UntraceVar(menuPtr->interp, mePtr->name, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, MenuVarProc, (ClientData) mePtr); } Tk_FreeOptions(entryConfigSpecs, (char *) mePtr, menuPtr->display, (COMMAND_MASK << mePtr->type)); ckfree((char *) mePtr); } /* *---------------------------------------------------------------------- * * ConfigureMenu -- * * This procedure is called to process an argv/argc list, plus * the Tk option database, in order to configure (or * reconfigure) a menu widget. * * Results: * The return value is a standard Tcl result. If TCL_ERROR is * returned, then interp->result contains an error message. * * Side effects: * Configuration information, such as colors, font, etc. get set * for menuPtr; old resources get freed, if there were any. * *---------------------------------------------------------------------- */ static int ConfigureMenu(interp, menuPtr, argc, argv, flags) Tcl_Interp *interp; /* Used for error reporting. */ register Menu *menuPtr; /* Information about widget; may or may * not already have values for some fields. */ int argc; /* Number of valid entries in argv. */ char **argv; /* Arguments. */ int flags; /* Flags to pass to Tk_ConfigureWidget. */ { XGCValues gcValues; GC newGC; unsigned long mask; int i; XSetWindowAttributes atts; if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs, argc, argv, (char *) menuPtr, flags) != TCL_OK) { return TCL_ERROR; } /* * A few options need special processing, such as setting the * background from a 3-D border, or filling in complicated * defaults that couldn't be specified to Tk_ConfigureWidget. */ Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border); gcValues.font = menuPtr->fontPtr->fid; gcValues.foreground = menuPtr->fg->pixel; gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel; newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont, &gcValues); if (menuPtr->textGC != None) { Tk_FreeGC(menuPtr->display, menuPtr->textGC); } menuPtr->textGC = newGC; if (menuPtr->disabledFg != NULL) { gcValues.foreground = menuPtr->disabledFg->pixel; mask = GCForeground|GCBackground|GCFont; } else { gcValues.foreground = gcValues.background; if (menuPtr->gray == None) { menuPtr->gray = Tk_GetBitmap(interp, menuPtr->tkwin, Tk_GetUid("gray50")); if (menuPtr->gray == None) { return TCL_ERROR; } } gcValues.fill_style = FillStippled; gcValues.stipple = menuPtr->gray; mask = GCForeground|GCFillStyle|GCStipple; } newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues); if (menuPtr->disabledGC != None) { Tk_FreeGC(menuPtr->display, menuPtr->disabledGC); } menuPtr->disabledGC = newGC; gcValues.font = menuPtr->fontPtr->fid; gcValues.foreground = menuPtr->activeFg->pixel; gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel; newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont, &gcValues); if (menuPtr->activeGC != None) { Tk_FreeGC(menuPtr->display, menuPtr->activeGC); } menuPtr->activeGC = newGC; gcValues.foreground = menuPtr->indicatorFg->pixel; newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCFont, &gcValues); if (menuPtr->indicatorGC != None) { Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC); } menuPtr->indicatorGC = newGC; if (menuPtr->transient) { atts.override_redirect = True; atts.save_under = True; } else { atts.override_redirect = False; atts.save_under = False; } if ((atts.override_redirect != Tk_Attributes(menuPtr->tkwin)->override_redirect) || (atts.save_under != Tk_Attributes(menuPtr->tkwin)->save_under)) { Tk_ChangeWindowAttributes(menuPtr->tkwin, CWOverrideRedirect|CWSaveUnder, &atts); } /* * After reconfiguring a menu, we need to reconfigure all of the * entries in the menu, since some of the things in the children * (such as graphics contexts) may have to change to reflect changes * in the parent. */ for (i = 0; i < menuPtr->numEntries; i++) { MenuEntry *mePtr; mePtr = menuPtr->entries[i]; ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type); } /* * Depending on the -tearOff option, make sure that there is or * isn't an initial tear-off entry at the beginning of the menu. */ if (menuPtr->tearOff) { if ((menuPtr->numEntries == 0) || (menuPtr->entries[0]->type != TEAROFF_ENTRY)) { MenuNewEntry(menuPtr, 0, TEAROFF_ENTRY); } } else if ((menuPtr->numEntries > 0) && (menuPtr->entries[0]->type == TEAROFF_ENTRY)) { Tcl_EventuallyFree((ClientData) menuPtr->entries[0], DestroyMenuEntry); for (i = 1; i < menuPtr->numEntries; i++) { menuPtr->entries[i-1] = menuPtr->entries[i]; } menuPtr->numEntries--; } if (!(menuPtr->flags & RESIZE_PENDING)) { menuPtr->flags |= RESIZE_PENDING; Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * ConfigureMenuEntry -- * * This procedure is called to process an argv/argc list, plus * the Tk option database, in order to configure (or * reconfigure) one entry in a menu. * * Results: * The return value is a standard Tcl result. If TCL_ERROR is * returned, then interp->result contains an error message. * * Side effects: * Configuration information such as label and accelerator get * set for mePtr; old resources get freed, if there were any. * *---------------------------------------------------------------------- */ static int ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags) Tcl_Interp *interp; /* Used for error reporting. */ Menu *menuPtr; /* Information about whole menu. */ register MenuEntry *mePtr; /* Information about menu entry; may * or may not already have values for * some fields. */ int index; /* Index of mePtr within menuPtr's * entries. */ int argc; /* Number of valid entries in argv. */ char **argv; /* Arguments. */ int flags; /* Additional flags to pass to * Tk_ConfigureWidget. */ { XGCValues gcValues; GC newGC, newActiveGC, newDisabledGC; unsigned long mask; Tk_Image image; /* * If this entry is a cascade and the cascade is posted, then unpost * it before reconfiguring the entry (otherwise the reconfigure might * change the name of the cascaded entry, leaving a posted menu * high and dry). */ if (menuPtr->postedCascade == mePtr) { if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL) != TCL_OK) { Tcl_BackgroundError(menuPtr->interp); } } /* * If this entry is a check button or radio button, then remove * its old trace procedure. */ if ((mePtr->name != NULL) && ((mePtr->type == CHECK_BUTTON_ENTRY) || (mePtr->type == RADIO_BUTTON_ENTRY))) { Tcl_UntraceVar(menuPtr->interp, mePtr->name, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, MenuVarProc, (ClientData) mePtr); } #ifdef STk_CODE if (Tk_Menu_ConfigureWidget(interp, &menuPtr->entries[index], menuPtr->tkwin, entryConfigSpecs, #else if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs, #endif argc, argv, (char *) mePtr, flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) { return TCL_ERROR; } /* * The code below handles special configuration stuff not taken * care of by Tk_ConfigureWidget, such as special processing for * defaults, sizing strings, graphics contexts, etc. */ if (mePtr->label == NULL) { mePtr->labelLength = 0; } else { mePtr->labelLength = strlen(mePtr->label); } if (mePtr->accel == NULL) { mePtr->accelLength = 0; } else { mePtr->accelLength = strlen(mePtr->accel); } if (mePtr->state == tkActiveUid) { if (index != menuPtr->active) { ActivateMenuEntry(menuPtr, index); } } else { if (index == menuPtr->active) { ActivateMenuEntry(menuPtr, -1); } if ((mePtr->state != tkNormalUid) && (mePtr->state != tkDisabledUid)) { Tcl_AppendResult(interp, "bad state value \"", mePtr->state, "\": must be normal, active, or disabled", (char *) NULL); mePtr->state = tkNormalUid; return TCL_ERROR; } } if ((mePtr->fontPtr != NULL) || (mePtr->border != NULL) || (mePtr->fg != NULL) || (mePtr->activeBorder != NULL) || (mePtr->activeFg != NULL)) { gcValues.foreground = (mePtr->fg != NULL) ? mePtr->fg->pixel : menuPtr->fg->pixel; gcValues.background = Tk_3DBorderColor( (mePtr->border != NULL) ? mePtr->border : menuPtr->border) ->pixel; gcValues.font = (mePtr->fontPtr != NULL) ? mePtr->fontPtr->fid : menuPtr->fontPtr->fid; /* * Note: disable GraphicsExpose events; we know there won't be * obscured areas when copying from an off-screen pixmap to the * screen and this gets rid of unnecessary events. */ gcValues.graphics_exposures = False; newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont|GCGraphicsExposures, &gcValues); if (menuPtr->disabledFg != NULL) { gcValues.foreground = menuPtr->disabledFg->pixel; mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures; } else { gcValues.foreground = gcValues.background; gcValues.fill_style = FillStippled; gcValues.stipple = menuPtr->gray; mask = GCForeground|GCFillStyle|GCStipple; } newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues); gcValues.foreground = (mePtr->activeFg != NULL) ? mePtr->activeFg->pixel : menuPtr->activeFg->pixel; gcValues.background = Tk_3DBorderColor( (mePtr->activeBorder != NULL) ? mePtr->activeBorder : menuPtr->activeBorder)->pixel; newActiveGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont|GCGraphicsExposures, &gcValues); } else { newGC = None; newActiveGC = None; newDisabledGC = None; } if (mePtr->textGC != None) { Tk_FreeGC(menuPtr->display, mePtr->textGC); } mePtr->textGC = newGC; if (mePtr->activeGC != None) { Tk_FreeGC(menuPtr->display, mePtr->activeGC); } mePtr->activeGC = newActiveGC; if (mePtr->disabledGC != None) { Tk_FreeGC(menuPtr->display, mePtr->disabledGC); } mePtr->disabledGC = newDisabledGC; if (mePtr->indicatorFg != NULL) { gcValues.foreground = mePtr->indicatorFg->pixel; newGC = Tk_GetGC(menuPtr->tkwin, GCForeground, &gcValues); } else { newGC = None; } if (mePtr->indicatorGC != None) { Tk_FreeGC(menuPtr->display, mePtr->indicatorGC); } mePtr->indicatorGC = newGC; if ((mePtr->type == CHECK_BUTTON_ENTRY) || (mePtr->type == RADIO_BUTTON_ENTRY)) { char *value; if (mePtr->name == NULL) { mePtr->name = (char *) ckalloc((unsigned) (mePtr->labelLength + 1)); strcpy(mePtr->name, (mePtr->label == NULL) ? "" : mePtr->label); } if (mePtr->onValue == NULL) { mePtr->onValue = (char *) ckalloc((unsigned) (mePtr->labelLength + 1)); strcpy(mePtr->onValue, (mePtr->label == NULL) ? "" : mePtr->label); } /* * Select the entry if the associated variable has the * appropriate value, initialize the variable if it doesn't * exist, then set a trace on the variable to monitor future * changes to its value. */ value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY); mePtr->flags &= ~ENTRY_SELECTED; if (value != NULL) { if (strcmp(value, mePtr->onValue) == 0) { mePtr->flags |= ENTRY_SELECTED; } } else { Tcl_SetVar(interp, mePtr->name, #ifdef STk_CODE (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "#f", #else (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "", #endif TCL_GLOBAL_ONLY); } Tcl_TraceVar(interp, mePtr->name, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, MenuVarProc, (ClientData) mePtr); } /* * Get the images for the entry, if there are any. Allocate the * new images before freeing the old ones, so that the reference * counts don't go to zero and cause image data to be discarded. */ if (mePtr->imageString != NULL) { image = Tk_GetImage(interp, menuPtr->tkwin, mePtr->imageString, MenuImageProc, (ClientData) mePtr); if (image == NULL) { return TCL_ERROR; } } else { image = NULL; } if (mePtr->image != NULL) { Tk_FreeImage(mePtr->image); } mePtr->image = image; if (mePtr->selectImageString != NULL) { image = Tk_GetImage(interp, menuPtr->tkwin, mePtr->selectImageString, MenuSelectImageProc, (ClientData) mePtr); if (image == NULL) { return TCL_ERROR; } } else { image = NULL; } if (mePtr->selectImage != NULL) { Tk_FreeImage(mePtr->selectImage); } mePtr->selectImage = image; if (!(menuPtr->flags & RESIZE_PENDING)) { menuPtr->flags |= RESIZE_PENDING; Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr); } return TCL_OK; } /* *-------------------------------------------------------------- * * ComputeMenuGeometry -- * * This procedure is invoked to recompute the size and * layout of a menu. It is called as a when-idle handler so * that it only gets done once, even if a group of changes is * made to the menu. * * Results: * None. * * Side effects: * Fields of menu entries are changed to reflect their * current positions, and the size of the menu window * itself may be changed. * *-------------------------------------------------------------- */ static void ComputeMenuGeometry(clientData) ClientData clientData; /* Structure describing menu. */ { Menu *menuPtr = (Menu *) clientData; register MenuEntry *mePtr; XFontStruct *fontPtr; int maxLabelWidth, maxIndicatorWidth, maxAccelWidth; int width, height, indicatorSpace; int i, y; int imageWidth, imageHeight; if (menuPtr->tkwin == NULL) { return; } maxLabelWidth = maxIndicatorWidth = maxAccelWidth = 0; y = menuPtr->borderWidth; for (i = 0; i < menuPtr->numEntries; i++) { mePtr = menuPtr->entries[i]; indicatorSpace = 0; fontPtr = mePtr->fontPtr; if (fontPtr == NULL) { fontPtr = menuPtr->fontPtr; } /* * For each entry, compute the height required by that * particular entry, plus three widths: the width of the * label, the width to allow for an indicator to be displayed * to the left of the label (if any), and the width of the * accelerator to be displayed to the right of the label * (if any). These sizes depend, of course, on the type * of the entry. */ if (mePtr->image != NULL) { Tk_SizeOfImage(mePtr->image, &imageWidth, &imageHeight); imageOrBitmap: mePtr->height = imageHeight; width = imageWidth; if (mePtr->indicatorOn) { if (mePtr->type == CHECK_BUTTON_ENTRY) { indicatorSpace = (14*mePtr->height)/10; mePtr->indicatorDiameter = (65*mePtr->height)/100; } else if (mePtr->type == RADIO_BUTTON_ENTRY) { indicatorSpace = (14*mePtr->height)/10; mePtr->indicatorDiameter = (75*mePtr->height)/100; } } } else if (mePtr->bitmap != None) { Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap, &imageWidth, &imageHeight); goto imageOrBitmap; } else { mePtr->height = fontPtr->ascent + fontPtr->descent; if (mePtr->label != NULL) { (void) TkMeasureChars(fontPtr, mePtr->label, mePtr->labelLength, 0, (int) 100000, 0, TK_NEWLINES_NOT_SPECIAL, &width); } else { width = 0; } if (mePtr->indicatorOn) { if (mePtr->type == CHECK_BUTTON_ENTRY) { indicatorSpace = mePtr->height; mePtr->indicatorDiameter = (80*mePtr->height)/100; } else if (mePtr->type == RADIO_BUTTON_ENTRY) { indicatorSpace = mePtr->height; mePtr->indicatorDiameter = mePtr->height; } } } mePtr->height += 2*menuPtr->activeBorderWidth + 2; if (width > maxLabelWidth) { maxLabelWidth = width; } if (mePtr->type == CASCADE_ENTRY) { width = 2*CASCADE_ARROW_WIDTH; } else if (mePtr->accel != NULL) { (void) TkMeasureChars(fontPtr, mePtr->accel, mePtr->accelLength, 0, (int) 100000, 0, TK_NEWLINES_NOT_SPECIAL, &width); } else { width = 0; } if (width > maxAccelWidth) { maxAccelWidth = width; } if (mePtr->type == SEPARATOR_ENTRY) { mePtr->height = 8; } if (mePtr->type == TEAROFF_ENTRY) { mePtr->height = 12; } if (indicatorSpace > maxIndicatorWidth) { maxIndicatorWidth = indicatorSpace; } mePtr->y = y; y += mePtr->height; } /* * Got all the sizes. Update fields in the menu structure, then * resize the window if necessary. Leave margins on either side * of the indicator (or just one margin if there is no indicator). * Leave another margin on the right side of the label, plus yet * another margin to the right of the accelerator (if there is one). */ menuPtr->indicatorSpace = maxIndicatorWidth + MARGIN_WIDTH; if (maxIndicatorWidth != 0) { menuPtr->indicatorSpace += MARGIN_WIDTH; } menuPtr->labelWidth = maxLabelWidth + MARGIN_WIDTH; width = menuPtr->indicatorSpace + menuPtr->labelWidth + maxAccelWidth + 2*menuPtr->borderWidth + 2*menuPtr->activeBorderWidth; if (maxAccelWidth != 0) { width += MARGIN_WIDTH; } height = y + menuPtr->borderWidth; /* * The X server doesn't like zero dimensions, so round up to at least * 1 (a zero-sized menu should never really occur, anyway). */ if (width <= 0) { width = 1; } if (height <= 0) { height = 1; } if ((width != Tk_ReqWidth(menuPtr->tkwin)) || (height != Tk_ReqHeight(menuPtr->tkwin))) { Tk_GeometryRequest(menuPtr->tkwin, width, height); } else { /* * Must always force a redisplay here if the window is mapped * (even if the size didn't change, something else might have * changed in the menu, such as a label or accelerator). The * resize will force a redisplay above. */ EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL); } menuPtr->flags &= ~RESIZE_PENDING; } /* *---------------------------------------------------------------------- * * DisplayMenu -- * * This procedure is invoked to display a menu widget. * * Results: * None. * * Side effects: * Commands are output to X to display the menu in its * current mode. * *---------------------------------------------------------------------- */ static void DisplayMenu(clientData) ClientData clientData; /* Information about widget. */ { register Menu *menuPtr = (Menu *) clientData; register MenuEntry *mePtr; register Tk_Window tkwin = menuPtr->tkwin; Tk_3DBorder bgBorder, activeBorder; XFontStruct *fontPtr; int index, baseline, strictMotif, leftEdge, y, height; GC gc; XPoint points[3]; menuPtr->flags &= ~REDRAW_PENDING; if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } /* * Loop through all of the entries, drawing them one at a time. */ strictMotif = Tk_StrictMotif(menuPtr->tkwin); leftEdge = menuPtr->borderWidth + menuPtr->indicatorSpace + menuPtr->activeBorderWidth; for (index = 0; index < menuPtr->numEntries; index++) { mePtr = menuPtr->entries[index]; if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) { continue; } mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY; /* * Background. */ bgBorder = mePtr->border; if (bgBorder == NULL) { bgBorder = menuPtr->border; } if (strictMotif) { activeBorder = bgBorder; } else { activeBorder = mePtr->activeBorder; if (activeBorder == NULL) { activeBorder = menuPtr->activeBorder; } } if (mePtr->state == tkActiveUid) { bgBorder = activeBorder; Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin), bgBorder, menuPtr->borderWidth, mePtr->y, Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height, menuPtr->activeBorderWidth, TK_RELIEF_RAISED); } else { Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin), bgBorder, menuPtr->borderWidth, mePtr->y, Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height, 0, TK_RELIEF_FLAT); } /* * Choose the gc for drawing the foreground part of the entry. */ if ((mePtr->state == tkActiveUid) && !strictMotif) { gc = mePtr->activeGC; if (gc == NULL) { gc = menuPtr->activeGC; } } else { if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg != NULL)) { gc = mePtr->disabledGC; if (gc == NULL) { gc = menuPtr->disabledGC; } } else { gc = mePtr->textGC; if (gc == NULL) { gc = menuPtr->textGC; } } } /* * Draw label or bitmap or image for entry. */ fontPtr = mePtr->fontPtr; if (fontPtr == NULL) { fontPtr = menuPtr->fontPtr; } baseline = mePtr->y + (mePtr->height + fontPtr->ascent - fontPtr->descent)/2; if (mePtr->image != NULL) { int width, height; Tk_SizeOfImage(mePtr->image, &width, &height); if ((mePtr->selectImage != NULL) && (mePtr->flags & ENTRY_SELECTED)) { Tk_RedrawImage(mePtr->selectImage, 0, 0, width, height, Tk_WindowId(tkwin), leftEdge, (int) (mePtr->y + (mePtr->height - height)/2)); } else { Tk_RedrawImage(mePtr->image, 0, 0, width, height, Tk_WindowId(tkwin), leftEdge, (int) (mePtr->y + (mePtr->height - height)/2)); } } else if (mePtr->bitmap != None) { int width, height; Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap, &width, &height); XCopyPlane(menuPtr->display, mePtr->bitmap, Tk_WindowId(tkwin), gc, 0, 0, (unsigned) width, (unsigned) height, leftEdge, (int) (mePtr->y + (mePtr->height - height)/2), 1); } else { baseline = mePtr->y + (mePtr->height + fontPtr->ascent - fontPtr->descent)/2; if (mePtr->label != NULL) { TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc, fontPtr, mePtr->label, mePtr->labelLength, leftEdge, baseline, leftEdge, TK_NEWLINES_NOT_SPECIAL); if (mePtr->underline >= 0) { TkUnderlineChars(menuPtr->display, Tk_WindowId(tkwin), gc, fontPtr, mePtr->label, leftEdge, baseline, leftEdge, TK_NEWLINES_NOT_SPECIAL, mePtr->underline, mePtr->underline); } } } /* * Draw accelerator or cascade arrow. */ if (mePtr->type == CASCADE_ENTRY) { points[0].x = Tk_Width(tkwin) - menuPtr->borderWidth - menuPtr->activeBorderWidth - MARGIN_WIDTH - CASCADE_ARROW_WIDTH; points[0].y = mePtr->y + (mePtr->height - CASCADE_ARROW_HEIGHT)/2; points[1].x = points[0].x; points[1].y = points[0].y + CASCADE_ARROW_HEIGHT; points[2].x = points[0].x + CASCADE_ARROW_WIDTH; points[2].y = points[0].y + CASCADE_ARROW_HEIGHT/2; Tk_Fill3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin), activeBorder, points, 3, DECORATION_BORDER_WIDTH, (menuPtr->postedCascade == mePtr) ? TK_RELIEF_SUNKEN : TK_RELIEF_RAISED); } else if (mePtr->accel != NULL) { TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc, fontPtr, mePtr->accel, mePtr->accelLength, leftEdge + menuPtr->labelWidth, baseline, leftEdge + menuPtr->labelWidth, TK_NEWLINES_NOT_SPECIAL); } /* * Draw check-button indicator. */ gc = mePtr->indicatorGC; if (gc == None) { gc = menuPtr->indicatorGC; } if ((mePtr->type == CHECK_BUTTON_ENTRY) && mePtr->indicatorOn) { int dim, x, y; dim = mePtr->indicatorDiameter; x = menuPtr->borderWidth + menuPtr->activeBorderWidth + (menuPtr->indicatorSpace - dim)/2; y = mePtr->y + (mePtr->height - dim)/2; Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin), menuPtr->border, x, y, dim, dim, DECORATION_BORDER_WIDTH, TK_RELIEF_SUNKEN); x += DECORATION_BORDER_WIDTH; y += DECORATION_BORDER_WIDTH; dim -= 2*DECORATION_BORDER_WIDTH; if ((dim > 0) && (mePtr->flags & ENTRY_SELECTED)) { XFillRectangle(menuPtr->display, Tk_WindowId(tkwin), gc, x, y, (unsigned int) dim, (unsigned int) dim); } } /* * Draw radio-button indicator. */ if ((mePtr->type == RADIO_BUTTON_ENTRY) && mePtr->indicatorOn) { XPoint points[4]; int radius; radius = mePtr->indicatorDiameter/2; points[0].x = menuPtr->borderWidth + menuPtr->activeBorderWidth + (menuPtr->indicatorSpace - mePtr->indicatorDiameter)/2; points[0].y = mePtr->y + (mePtr->height)/2; points[1].x = points[0].x + radius; points[1].y = points[0].y + radius; points[2].x = points[1].x + radius; points[2].y = points[0].y; points[3].x = points[1].x; points[3].y = points[0].y - radius; if (mePtr->flags & ENTRY_SELECTED) { XFillPolygon(menuPtr->display, Tk_WindowId(tkwin), gc, points, 4, Convex, CoordModeOrigin); } else { Tk_Fill3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin), menuPtr->border, points, 4, DECORATION_BORDER_WIDTH, TK_RELIEF_FLAT); } Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin), menuPtr->border, points, 4, DECORATION_BORDER_WIDTH, TK_RELIEF_SUNKEN); } /* * Draw separator. */ if (mePtr->type == SEPARATOR_ENTRY) { XPoint points[2]; int margin; margin = (fontPtr->ascent + fontPtr->descent)/2; points[0].x = 0; points[0].y = mePtr->y + mePtr->height/2; points[1].x = Tk_Width(tkwin) - 1; points[1].y = points[0].y; Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin), menuPtr->border, points, 2, 1, TK_RELIEF_RAISED); } /* * Draw tear-off line. */ if (mePtr->type == TEAROFF_ENTRY) { XPoint points[2]; int margin, width, maxX; margin = (fontPtr->ascent + fontPtr->descent)/2; points[0].x = 0; points[0].y = mePtr->y + mePtr->height/2; points[1].y = points[0].y; width = 6; maxX = Tk_Width(tkwin) - 1; while (points[0].x < maxX) { points[1].x = points[0].x + width; if (points[1].x > maxX) { points[1].x = maxX; } Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin), menuPtr->border, points, 2, 1, TK_RELIEF_RAISED); points[0].x += 2*width; } } /* * If the entry is disabled with a stipple rather than a special * foreground color, generate the stippled effect. */ if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg == NULL)) { XFillRectangle(menuPtr->display, Tk_WindowId(tkwin), menuPtr->disabledGC, menuPtr->borderWidth, mePtr->y, (unsigned) (Tk_Width(tkwin) - 2*menuPtr->borderWidth), (unsigned) mePtr->height); } } /* * If there is extra space after the last entry in the menu, * clear it. */ if (menuPtr->numEntries >= 1) { mePtr = menuPtr->entries[menuPtr->numEntries-1]; y = mePtr->y + mePtr->height; } else { y = menuPtr->borderWidth; } height = Tk_Height(tkwin) - menuPtr->borderWidth - y; if (height > 0) { Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), menuPtr->border, menuPtr->borderWidth, y, Tk_Width(tkwin) - 2*menuPtr->borderWidth, height, 0, TK_RELIEF_FLAT); } Tk_Draw3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin), menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), menuPtr->borderWidth, menuPtr->relief); } /* *-------------------------------------------------------------- * * GetMenuIndex -- * * Parse a textual index into a menu and return the numerical * index of the indicated entry. * * Results: * A standard Tcl result. If all went well, then *indexPtr is * filled in with the entry index corresponding to string * (ranges from -1 to the number of entries in the menu minus * one). Otherwise an error message is left in interp->result. * * Side effects: * None. * *-------------------------------------------------------------- */ static int GetMenuIndex(interp, menuPtr, string, lastOK, indexPtr) Tcl_Interp *interp; /* For error messages. */ Menu *menuPtr; /* Menu for which the index is being * specified. */ char *string; /* Specification of an entry in menu. See * manual entry for valid .*/ int lastOK; /* Non-zero means its OK to return index * just *after* last entry. */ int *indexPtr; /* Where to store converted relief. */ { int i, y; if ((string[0] == 'a') && (strcmp(string, "active") == 0)) { *indexPtr = menuPtr->active; return TCL_OK; } if (((string[0] == 'l') && (strcmp(string, "last") == 0)) || ((string[0] == 'e') && (strcmp(string, "end") == 0))) { *indexPtr = menuPtr->numEntries - ((lastOK) ? 0 : 1); return TCL_OK; } if ((string[0] == 'n') && (strcmp(string, "none") == 0)) { *indexPtr = -1; return TCL_OK; } if (string[0] == '@') { if (Tcl_GetInt(interp, string+1, &y) == TCL_OK) { for (i = 0; i < menuPtr->numEntries; i++) { MenuEntry *mePtr = menuPtr->entries[i]; if (y < (mePtr->y + mePtr->height)) { break; } } if (i >= menuPtr->numEntries) { i = menuPtr->numEntries-1; } *indexPtr = i; return TCL_OK; } else { Tcl_SetResult(interp, (char *) NULL, TCL_STATIC); } } if (isdigit(UCHAR(string[0]))) { if (Tcl_GetInt(interp, string, &i) == TCL_OK) { if (i >= menuPtr->numEntries) { if (lastOK) { i = menuPtr->numEntries; } else { i = menuPtr->numEntries-1; } } else if (i < 0) { i = -1; } *indexPtr = i; return TCL_OK; } Tcl_SetResult(interp, (char *) NULL, TCL_STATIC); } for (i = 0; i < menuPtr->numEntries; i++) { char *label; label = menuPtr->entries[i]->label; if ((label != NULL) && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) { *indexPtr = i; return TCL_OK; } } Tcl_AppendResult(interp, "bad menu entry index \"", string, "\"", (char *) NULL); return TCL_ERROR; } /* *-------------------------------------------------------------- * * MenuEventProc -- * * This procedure is invoked by the Tk dispatcher for various * events on menus. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get * cleaned up. When it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void MenuEventProc(clientData, eventPtr) ClientData clientData; /* Information about window. */ XEvent *eventPtr; /* Information about event. */ { Menu *menuPtr = (Menu *) clientData; if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) { EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL); } else if (eventPtr->type == ConfigureNotify) { EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL); } else if (eventPtr->type == DestroyNotify) { if (menuPtr->tkwin != NULL) { menuPtr->tkwin = NULL; Tcl_DeleteCommand(menuPtr->interp, Tcl_GetCommandName(menuPtr->interp, menuPtr->widgetCmd)); } if (menuPtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(DisplayMenu, (ClientData) menuPtr); } if (menuPtr->flags & RESIZE_PENDING) { Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr); } Tcl_EventuallyFree((ClientData) menuPtr, DestroyMenu); } } /* *---------------------------------------------------------------------- * * MenuCmdDeletedProc -- * * This procedure is invoked when a widget command is deleted. If * the widget isn't already in the process of being destroyed, * this command destroys it. * * Results: * None. * * Side effects: * The widget is destroyed. * *---------------------------------------------------------------------- */ static void MenuCmdDeletedProc(clientData) ClientData clientData; /* Pointer to widget record for widget. */ { Menu *menuPtr = (Menu *) clientData; Tk_Window tkwin = menuPtr->tkwin; /* * This procedure could be invoked either because the window was * destroyed and the command was then deleted (in which case tkwin * is NULL) or because the command was deleted, and then this procedure * destroys the widget. */ if (tkwin != NULL) { menuPtr->tkwin = NULL; Tk_DestroyWindow(tkwin); } } /* *---------------------------------------------------------------------- * * MenuNewEntry -- * * This procedure allocates and initializes a new menu entry. * * Results: * The return value is a pointer to a new menu entry structure, * which has been malloc-ed, initialized, and entered into the * entry array for the menu. * * Side effects: * Storage gets allocated. * *---------------------------------------------------------------------- */ static MenuEntry * MenuNewEntry(menuPtr, index, type) Menu *menuPtr; /* Menu that will hold the new entry. */ int index; /* Where in the menu the new entry is to * go. */ int type; /* The type of the new entry. */ { MenuEntry *mePtr; MenuEntry **newEntries; int i; /* * Create a new array of entries with an empty slot for the * new entry. */ newEntries = (MenuEntry **) ckalloc((unsigned) ((menuPtr->numEntries+1)*sizeof(MenuEntry *))); for (i = 0; i < index; i++) { newEntries[i] = menuPtr->entries[i]; } for ( ; i < menuPtr->numEntries; i++) { newEntries[i+1] = menuPtr->entries[i]; } if (menuPtr->numEntries != 0) { ckfree((char *) menuPtr->entries); } menuPtr->entries = newEntries; menuPtr->numEntries++; menuPtr->entries[index] = mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry)); mePtr->type = type; mePtr->menuPtr = menuPtr; mePtr->label = NULL; mePtr->labelLength = 0; mePtr->underline = -1; mePtr->bitmap = None; mePtr->imageString = NULL; mePtr->image = NULL; mePtr->selectImageString = NULL; mePtr->selectImage = NULL; mePtr->accel = NULL; mePtr->accelLength = 0; mePtr->state = tkNormalUid; mePtr->height = 0; mePtr->y = 0; mePtr->indicatorDiameter = 0; mePtr->border = NULL; mePtr->fg = NULL; mePtr->activeBorder = NULL; mePtr->activeFg = NULL; mePtr->fontPtr = NULL; mePtr->textGC = None; mePtr->activeGC = None; mePtr->disabledGC = None; mePtr->indicatorOn = 1; mePtr->indicatorFg = NULL; mePtr->indicatorGC = None; mePtr->command = NULL; mePtr->name = NULL; mePtr->onValue = NULL; mePtr->offValue = NULL; mePtr->flags = 0; return mePtr; } /* *---------------------------------------------------------------------- * * MenuAddOrInsert -- * * This procedure does all of the work of the "add" and "insert" * widget commands, allowing the code for these to be shared. * * Results: * A standard Tcl return value. * * Side effects: * A new menu entry is created in menuPtr. * *---------------------------------------------------------------------- */ static int MenuAddOrInsert(interp, menuPtr, indexString, argc, argv) Tcl_Interp *interp; /* Used for error reporting. */ Menu *menuPtr; /* Widget in which to create new * entry. */ char *indexString; /* String describing index at which * to insert. NULL means insert at * end. */ int argc; /* Number of elements in argv. */ char **argv; /* Arguments to command: first arg * is type of entry, others are * config options. */ { int c, type, i, index; size_t length; MenuEntry *mePtr; if (indexString != NULL) { if (GetMenuIndex(interp, menuPtr, indexString, 1, &index) != TCL_OK) { return TCL_ERROR; } } else { index = menuPtr->numEntries; } if (index < 0) { Tcl_AppendResult(interp, "bad index \"", indexString, "\"", (char *) NULL); return TCL_ERROR; } if (menuPtr->tearOff && (index == 0)) { index = 1; } /* * Figure out the type of the new entry. */ c = argv[0][0]; length = strlen(argv[0]); if ((c == 'c') && (strncmp(argv[0], "cascade", length) == 0) && (length >= 2)) { type = CASCADE_ENTRY; } else if ((c == 'c') && (strncmp(argv[0], "checkbutton", length) == 0) && (length >= 2)) { type = CHECK_BUTTON_ENTRY; } else if ((c == 'c') && (strncmp(argv[0], "command", length) == 0) && (length >= 2)) { type = COMMAND_ENTRY; } else if ((c == 'r') && (strncmp(argv[0], "radiobutton", length) == 0)) { type = RADIO_BUTTON_ENTRY; } else if ((c == 's') && (strncmp(argv[0], "separator", length) == 0)) { type = SEPARATOR_ENTRY; } else { Tcl_AppendResult(interp, "bad menu entry type \"", argv[0], "\": must be cascade, checkbutton, ", "command, radiobutton, or separator", (char *) NULL); return TCL_ERROR; } mePtr = MenuNewEntry(menuPtr, index, type); if (ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-1, argv+1, 0) != TCL_OK) { DestroyMenuEntry((ClientData) mePtr); for (i = index+1; i < menuPtr->numEntries; i++) { menuPtr->entries[i-1] = menuPtr->entries[i]; } menuPtr->numEntries--; return TCL_ERROR; } return TCL_OK; } /* *-------------------------------------------------------------- * * MenuVarProc -- * * This procedure is invoked when someone changes the * state variable associated with a radiobutton or checkbutton * menu entry. The entry's selected state is set to match * the value of the variable. * * Results: * NULL is always returned. * * Side effects: * The menu entry may become selected or deselected. * *-------------------------------------------------------------- */ /* ARGSUSED */ static char * MenuVarProc(clientData, interp, name1, name2, flags) ClientData clientData; /* Information about menu entry. */ Tcl_Interp *interp; /* Interpreter containing variable. */ char *name1; /* First part of variable's name. */ char *name2; /* Second part of variable's name. */ int flags; /* Describes what just happened. */ { MenuEntry *mePtr = (MenuEntry *) clientData; Menu *menuPtr; char *value; menuPtr = mePtr->menuPtr; /* * If the variable is being unset, then re-establish the * trace unless the whole interpreter is going away. */ if (flags & TCL_TRACE_UNSETS) { mePtr->flags &= ~ENTRY_SELECTED; if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { Tcl_TraceVar(interp, mePtr->name, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, MenuVarProc, clientData); } EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL); return (char *) NULL; } /* * Use the value of the variable to update the selected status of * the menu entry. */ value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY); if (value == NULL) { value = ""; } if (strcmp(value, mePtr->onValue) == 0) { if (mePtr->flags & ENTRY_SELECTED) { return (char *) NULL; } mePtr->flags |= ENTRY_SELECTED; } else if (mePtr->flags & ENTRY_SELECTED) { mePtr->flags &= ~ENTRY_SELECTED; } else { return (char *) NULL; } EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL); return (char *) NULL; } /* *---------------------------------------------------------------------- * * EventuallyRedrawMenu -- * * Arrange for an entry of a menu, or the whole menu, to be * redisplayed at some point in the future. * * Results: * None. * * Side effects: * A when-idle hander is scheduled to do the redisplay, if there * isn't one already scheduled. * *---------------------------------------------------------------------- */ static void EventuallyRedrawMenu(menuPtr, mePtr) register Menu *menuPtr; /* Information about menu to redraw. */ register MenuEntry *mePtr; /* Entry to redraw. NULL means redraw * all the entries in the menu. */ { int i; if (menuPtr->tkwin == NULL) { return; } if (mePtr != NULL) { mePtr->flags |= ENTRY_NEEDS_REDISPLAY; } else { for (i = 0; i < menuPtr->numEntries; i++) { menuPtr->entries[i]->flags |= ENTRY_NEEDS_REDISPLAY; } } if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin) || (menuPtr->flags & REDRAW_PENDING)) { return; } Tcl_DoWhenIdle(DisplayMenu, (ClientData) menuPtr); menuPtr->flags |= REDRAW_PENDING; } /* *-------------------------------------------------------------- * * PostSubmenu -- * * This procedure arranges for a particular submenu (i.e. the * menu corresponding to a given cascade entry) to be * posted. * * Results: * A standard Tcl return result. Errors may occur in the * Tcl commands generated to post and unpost submenus. * * Side effects: * If there is already a submenu posted, it is unposted. * The new submenu is then posted. * *-------------------------------------------------------------- */ static int PostSubmenu(interp, menuPtr, mePtr) Tcl_Interp *interp; /* Used for invoking sub-commands and * reporting errors. */ register Menu *menuPtr; /* Information about menu as a whole. */ register MenuEntry *mePtr; /* Info about submenu that is to be * posted. NULL means make sure that * no submenu is posted. */ { char string[30]; int result, x, y; Tk_Window tkwin; if (mePtr == menuPtr->postedCascade) { return TCL_OK; } if (menuPtr->postedCascade != NULL) { /* * Note: when unposting a submenu, we have to redraw the entire * parent menu. This is because of a combination of the following * things: * (a) the submenu partially overlaps the parent. * (b) the submenu specifies "save under", which causes the X * server to make a copy of the information under it when it * is posted. When the submenu is unposted, the X server * copies this data back and doesn't generate any Expose * events for the parent. * (c) the parent may have redisplayed itself after the submenu * was posted, in which case the saved information is no * longer correct. * The simplest solution is just force a complete redisplay of * the parent. */ EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL); result = Tcl_VarEval(interp, menuPtr->postedCascade->name, #ifdef STk_CODE " 'unpost", (char *) NULL); #else " unpost", (char *) NULL); #endif menuPtr->postedCascade = NULL; if (result != TCL_OK) { return result; } } if ((mePtr != NULL) && (mePtr->name != NULL) && Tk_IsMapped(menuPtr->tkwin)) { /* * Make sure that the cascaded submenu is a child of the * parent menu. */ #ifdef STk_CODE /* For STk, the submenu .a.b.c is stored as #..a.b.c * We have to skip the '#.' * This ugly and should be changed */ tkwin = Tk_NameToWindow(interp, mePtr->name+2, menuPtr->tkwin); #else tkwin = Tk_NameToWindow(interp, mePtr->name, menuPtr->tkwin); #endif if (tkwin == NULL) { return TCL_ERROR; } if (Tk_Parent(tkwin) != menuPtr->tkwin) { Tcl_AppendResult(interp, "cascaded sub-menu ", Tk_PathName(tkwin), " must be a child of ", Tk_PathName(menuPtr->tkwin), (char *) NULL); return TCL_ERROR; } /* * Position the cascade with its upper left corner slightly * below and to the left of the upper right corner of the * menu entry (this is an attempt to match Motif behavior). */ Tk_GetRootCoords(menuPtr->tkwin, &x, &y); x += Tk_Width(menuPtr->tkwin) - menuPtr->borderWidth - menuPtr->activeBorderWidth - 2; y += mePtr->y + menuPtr->activeBorderWidth + 2; sprintf(string, "%d %d", x, y); #ifdef STk_CODE result = Tcl_VarEval(interp, mePtr->name, " 'post ", string, #else result = Tcl_VarEval(interp, mePtr->name, " post ", string, #endif (char *) NULL); if (result != TCL_OK) { return result; } menuPtr->postedCascade = mePtr; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ActivateMenuEntry -- * * This procedure is invoked to make a particular menu entry * the active one, deactivating any other entry that might * currently be active. * * Results: * The return value is a standard Tcl result (errors can occur * while posting and unposting submenus). * * Side effects: * Menu entries get redisplayed, and the active entry changes. * Submenus may get posted and unposted. * *---------------------------------------------------------------------- */ static int ActivateMenuEntry(menuPtr, index) register Menu *menuPtr; /* Menu in which to activate. */ int index; /* Index of entry to activate, or * -1 to deactivate all entries. */ { register MenuEntry *mePtr; int result = TCL_OK; if (menuPtr->active >= 0) { mePtr = menuPtr->entries[menuPtr->active]; /* * Don't change the state unless it's currently active (state * might already have been changed to disabled). */ if (mePtr->state == tkActiveUid) { mePtr->state = tkNormalUid; } EventuallyRedrawMenu(menuPtr, menuPtr->entries[menuPtr->active]); } menuPtr->active = index; if (index >= 0) { mePtr = menuPtr->entries[index]; mePtr->state = tkActiveUid; EventuallyRedrawMenu(menuPtr, mePtr); } return result; } /* *---------------------------------------------------------------------- * * MenuImageProc -- * * This procedure is invoked by the image code whenever the manager * for an image does something that affects the size of contents * of an image displayed in a menu entry. * * Results: * None. * * Side effects: * Arranges for the menu to get redisplayed. * *---------------------------------------------------------------------- */ static void MenuImageProc(clientData, x, y, width, height, imgWidth, imgHeight) ClientData clientData; /* Pointer to widget record. */ int x, y; /* Upper left pixel (within image) * that must be redisplayed. */ int width, height; /* Dimensions of area to redisplay * (may be <= 0). */ int imgWidth, imgHeight; /* New dimensions of image. */ { register Menu *menuPtr = ((MenuEntry *) clientData)->menuPtr; if ((menuPtr->tkwin != NULL) && !(menuPtr->flags & RESIZE_PENDING)) { menuPtr->flags |= RESIZE_PENDING; Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr); } } /* *---------------------------------------------------------------------- * * MenuSelectImageProc -- * * This procedure is invoked by the image code whenever the manager * for an image does something that affects the size of contents * of an image displayed in a menu entry when it is selected. * * Results: * None. * * Side effects: * Arranges for the menu to get redisplayed. * *---------------------------------------------------------------------- */ static void MenuSelectImageProc(clientData, x, y, width, height, imgWidth, imgHeight) ClientData clientData; /* Pointer to widget record. */ int x, y; /* Upper left pixel (within image) * that must be redisplayed. */ int width, height; /* Dimensions of area to redisplay * (may be <= 0). */ int imgWidth, imgHeight; /* New dimensions of image. */ { register MenuEntry *mePtr = (MenuEntry *) clientData; if ((mePtr->flags & ENTRY_SELECTED) && !(mePtr->menuPtr->flags & REDRAW_PENDING)) { mePtr->menuPtr->flags |= REDRAW_PENDING; Tcl_DoWhenIdle(DisplayMenu, (ClientData) mePtr->menuPtr); } }