/* * tkMenu.c (CTk) -- * * 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. * Copyright (c) 1994-1995 Cleveland Clinic Foundation * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * @(#) $Id: ctk.shar,v 1.50 1996/01/15 14:47:16 andrewm Exp andrewm $ */ #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. */ 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 indicatorOn; /* True means draw indicator, false means * don't draw it. */ /* * 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_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_STRING, "-command", (char *) NULL, (char *) NULL, DEF_MENU_ENTRY_COMMAND, Tk_Offset(MenuEntry, command), 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}, {TK_CONFIG_STRING, "-menu", (char *) NULL, (char *) NULL, 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_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.*/ 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: */ int borderWidth; /* Width of border around whole menu. */ int labelWidth; /* Number of pixels to allow for displaying * labels in menu entries. */ int columnWidth; /* Number of pixels to allow for a column * of entries (includes space for label, * accelerator, and indicators). */ int numRows; /* Number of entries per column. */ /* * 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 *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. * BORDER_NEEDED: Non-zero means 3-D border must be redrawn * around window during redisplay. Normally * only text portion needs to be redrawn. * GOT_FOCUS: Non-zero means this menu currently * has the input focus. */ #define REDRAW_PENDING 1 #define RESIZE_PENDING 2 #define BORDER_NEEDED 4 #define GOT_FOCUS 8 /* * Configuration specs valid for the menu as a whole: */ static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_MENU_BORDER_WIDTH, Tk_Offset(Menu, borderWidth), 0}, {TK_CONFIG_STRING, "-postcommand", "postCommand", "Command", DEF_MENU_POST_COMMAND, Tk_Offset(Menu, postCommand), TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", 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}, {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, 0} }; /* * 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_((ClientData clientData)); static void DestroyMenuEntry _ANSI_ARGS_((ClientData clientData)); 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 MenuEntry * MenuNewEntry _ANSI_ARGS_((Menu *menuPtr, int index, int type)); 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; } 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->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->borderWidth = 0; menuPtr->labelWidth = 0; menuPtr->columnWidth = 0; menuPtr->numRows = 0; menuPtr->tearOff = 1; menuPtr->takeFocus = NULL; menuPtr->postCommand = NULL; menuPtr->postedCascade = NULL; menuPtr->flags = 0; Tk_SetClass(new, "Menu"); Tk_CreateEventHandler(menuPtr->tkwin, CTK_EXPOSE_EVENT_MASK|CTK_DESTROY_EVENT_MASK|CTK_FOCUS_EVENT_MASK, MenuEventProc, (ClientData) menuPtr); if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) { goto error; } Tcl_SetResult(interp,Tk_PathName(menuPtr->tkwin), TCL_VOLATILE); 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; } Tk_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++) { Tk_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]; Tk_Preserve((ClientData) mePtr); result = Tk_ConfigureValue(interp, menuPtr->tkwin, entryConfigSpecs, (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type); Tk_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]; Tk_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); } Tk_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) { Tcl_SetResult(interp,"none", TCL_STATIC); } else { char buffer[20]; sprintf(buffer, "%d", index); Tcl_SetResult(interp, buffer, TCL_VOLATILE); } } 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; } Tk_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); } Tk_Release((ClientData) mePtr); } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0) && (length == 4)) { Tk_Window tkwin = menuPtr->tkwin; int tmp; char *placeArgv[9]; 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], &tmp) != TCL_OK) || (Tcl_GetInt(interp, argv[3], &tmp) != 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); } } Tk_RestackWindow(tkwin, Above, (Tk_Window) NULL); placeArgv[0] = "place"; placeArgv[1] = Tk_PathName(tkwin); placeArgv[2] = "-x"; placeArgv[3] = argv[2]; placeArgv[4] = "-y"; placeArgv[5] = argv[3]; placeArgv[6] = "-anchor"; placeArgv[7] = "nw"; placeArgv[8] = NULL; result = Tk_PlaceCmd((ClientData) tkwin, interp, 8, placeArgv); } 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) { case COMMAND_ENTRY: Tcl_SetResult(interp,"command",TCL_STATIC); break; case SEPARATOR_ENTRY: Tcl_SetResult(interp,"separator",TCL_STATIC); break; case CHECK_BUTTON_ENTRY: Tcl_SetResult(interp,"checkbutton",TCL_STATIC); break; case RADIO_BUTTON_ENTRY: Tcl_SetResult(interp,"radiobutton",TCL_STATIC); break; case CASCADE_ENTRY: Tcl_SetResult(interp,"cascade",TCL_STATIC); break; case TEAROFF_ENTRY: Tcl_SetResult(interp,"tearoff",TCL_STATIC); break; } } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) { char *placeArgv[4]; if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " unpost\"", (char *) NULL); goto error; } placeArgv[0] = "place"; placeArgv[1] = "forget"; placeArgv[2] = Tk_PathName(menuPtr->tkwin); placeArgv[3] = NULL; result = Tk_PlaceCmd((ClientData) menuPtr->tkwin, interp, 3, placeArgv); 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 || menuPtr->numRows == 0) { Tcl_SetResult(interp,"0",TCL_STATIC); } else { char buffer[20]; sprintf(buffer, "%d", (index%menuPtr->numRows) + menuPtr->borderWidth); Tcl_SetResult(interp,buffer,TCL_VOLATILE); } } 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: Tk_Release((ClientData) menuPtr); return result; error: Tk_Release((ClientData) menuPtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * DestroyMenu -- * * This procedure is invoked by Tk_EventuallyFree or Tk_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(clientData) ClientData clientData; /* Info about menu widget. */ { register Menu *menuPtr = (Menu *) clientData; 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((ClientData) menuPtr->entries[i]); } if (menuPtr->entries != NULL) { ckfree((char *) menuPtr->entries); } Tk_FreeOptions(configSpecs, (char *) menuPtr, 0); ckfree((char *) menuPtr); } /* *---------------------------------------------------------------------- * * DestroyMenuEntry -- * * This procedure is invoked by Tk_EventuallyFree or Tk_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(clientData) ClientData clientData; /* Pointer to entry to be freed. */ { register MenuEntry *mePtr = (MenuEntry *) clientData; 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->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, (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. */ { int i; if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs, argc, argv, (char *) menuPtr, flags) != TCL_OK) { return TCL_ERROR; } /* * 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)) { Tk_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); } Tk_SetInternalBorder(menuPtr->tkwin, menuPtr->borderWidth); 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. */ { /* * 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); } if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs, 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->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, (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "", TCL_GLOBAL_ONLY); } Tcl_TraceVar(interp, mePtr->name, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, MenuVarProc, (ClientData) mePtr); } 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; int maxLabelWidth, maxAccelWidth; int numColumns, width, height; int i; if (menuPtr->tkwin == NULL) { return; } maxLabelWidth = maxAccelWidth = 0; for (i = 0; i < menuPtr->numEntries; i++) { /* * For each entry, compute the label and accelerator width. */ mePtr = menuPtr->entries[i]; if (mePtr->label != NULL) { (void) TkMeasureChars(mePtr->label, mePtr->labelLength, 0, (int) 100000, 0, TK_NEWLINES_NOT_SPECIAL, &width); if (width > maxLabelWidth) { maxLabelWidth = width; } } if (mePtr->accel != NULL) { (void) TkMeasureChars(mePtr->accel, mePtr->accelLength, 0, (int) 100000, 0, TK_NEWLINES_NOT_SPECIAL, &width); if (width > maxAccelWidth) { maxAccelWidth = width; } } } /* * Got all the sizes. Update fields in the menu structure, then * resize the window if necessary. * * Column width is max. label and accelerator widths + 2 spaces * for selection and cascade indicators. */ menuPtr->labelWidth = maxLabelWidth; menuPtr->columnWidth = maxLabelWidth + maxAccelWidth + 2; numColumns = 1 + (menuPtr->numEntries - 1) / (Ctk_DisplayHeight(Tk_Display(menuPtr->tkwin)) - 2*menuPtr->borderWidth); if (numColumns <= 0) { menuPtr->numRows = numColumns = 0; } else { menuPtr->numRows = 1 + (menuPtr->numEntries - 1)/numColumns; } width = menuPtr->columnWidth * numColumns + 2*menuPtr->borderWidth; height = menuPtr->numRows + 2*menuPtr->borderWidth; 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; int index; int x, y, underlineX; Ctk_Style style; int fillChar; if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { goto done; } /* * Loop through all of the entries, drawing them one at a time. */ x = y = menuPtr->borderWidth; for (index = 0; index < menuPtr->numEntries; index++) { mePtr = menuPtr->entries[index]; if ((mePtr->flags & ENTRY_NEEDS_REDISPLAY)) { mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY; /* * Fill Background. */ style = (mePtr->state == tkDisabledUid) ? CTK_DISABLED_STYLE : CTK_PLAIN_STYLE; if (mePtr->type == SEPARATOR_ENTRY) { fillChar = '-'; } else if (mePtr->type == TEAROFF_ENTRY) { fillChar = '='; } else { fillChar = ' '; } Ctk_FillRect(tkwin, x, y, x + menuPtr->columnWidth, y + 1, style, fillChar); if (mePtr->flags & ENTRY_SELECTED) { /* * Draw check/radio-button indicator. */ Ctk_DrawCharacter(tkwin, x, y, style, '*'); } /* * Draw label for entry. */ if (mePtr->label != NULL) { TkDisplayChars(tkwin, style, mePtr->label, mePtr->labelLength, x + 1, y, x + 1, TK_NEWLINES_NOT_SPECIAL); if (mePtr->underline >= 0) { (void) TkMeasureChars(mePtr->label, mePtr->underline, x + 1, x + menuPtr->columnWidth, x + 1, TK_WHOLE_WORDS|TK_AT_LEAST_ONE, &underlineX); TkDisplayChars(tkwin, CTK_UNDERLINE_STYLE, mePtr->label + mePtr->underline, 1, underlineX, y, x + 1, TK_NEWLINES_NOT_SPECIAL); } } /* * Draw accelerator. */ if (mePtr->accel != NULL) { TkDisplayChars(tkwin, style, mePtr->accel, mePtr->accelLength, x + 1 + menuPtr->labelWidth, y, x + 1 + menuPtr->labelWidth, TK_NEWLINES_NOT_SPECIAL); } if (mePtr->type == CASCADE_ENTRY) { /* * Draw cascade arrow. */ Ctk_DrawCharacter(tkwin, x + menuPtr->columnWidth - 1, y, style, '>'); } if (index == menuPtr->active && menuPtr->flags & GOT_FOCUS) { /* * Place cursor. */ Ctk_SetCursor(tkwin, x + 1, y); } } y += 1; if (y == Tk_Height(tkwin) - menuPtr->borderWidth) { y = menuPtr->borderWidth; x += menuPtr->columnWidth; } } /* * Draw border, if necessary. */ if (menuPtr->flags & BORDER_NEEDED) { Ctk_DrawBorder(tkwin, CTK_PLAIN_STYLE, (char *)NULL); } done: menuPtr->flags &= ~(BORDER_NEEDED|REDRAW_PENDING); } /* *-------------------------------------------------------------- * * 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] == '@') { /* * With multi-column menus I really need both the x and y * coordinates here - but I don't think any CTk specific * apps need to specify indexes this way and Tk only uses * one coordinate. */ if (Tcl_GetInt(interp, string+1, &y) == TCL_OK) { i = y - menuPtr->borderWidth; if (i < 0) { i = 0; } else 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 == CTK_EXPOSE_EVENT) { EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL); menuPtr->flags |= BORDER_NEEDED; } else if (eventPtr->type == CTK_DESTROY_EVENT) { 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); } Tk_EventuallyFree((ClientData) menuPtr, DestroyMenu); } else if (eventPtr->type == CTK_FOCUS_EVENT) { menuPtr->flags |= GOT_FOCUS; if (menuPtr->active != -1) { EventuallyRedrawMenu(menuPtr, menuPtr->entries[menuPtr->active]); } } else if (eventPtr->type == CTK_UNFOCUS_EVENT) { menuPtr->flags &= ~GOT_FOCUS; } } /* *---------------------------------------------------------------------- * * 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->accel = NULL; mePtr->accelLength = 0; mePtr->state = tkNormalUid; mePtr->indicatorOn = 1; 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, i; if (mePtr == menuPtr->postedCascade) { return TCL_OK; } if (menuPtr->postedCascade != NULL) { EventuallyRedrawMenu(menuPtr, menuPtr->postedCascade); result = Tcl_VarEval(interp, menuPtr->postedCascade->name, " unpost", (char *) NULL); 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. */ if (Ctk_ParentByName(interp, mePtr->name, menuPtr->tkwin) != menuPtr->tkwin) { Tcl_AppendResult(interp, "cascaded sub-menu ", mePtr->name, " 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). */ for (i = 0; i < menuPtr->numEntries; i++) { if (menuPtr->entries[i] == mePtr) break; } x = Ctk_AbsLeft(menuPtr->tkwin) + (i/menuPtr->numRows + 1)*menuPtr->columnWidth + menuPtr->borderWidth; y = Ctk_AbsTop(menuPtr->tkwin) + (i%menuPtr->numRows) + menuPtr->borderWidth; sprintf(string, "%d %d", x, y); result = Tcl_VarEval(interp, mePtr->name, " post ", string, (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; }