/* * tkButton.c (CTk) -- * * This module implements a collection of button-like * widgets for the Tk toolkit. The widgets implemented * include labels, buttons, check buttons, and radio * buttons. * * 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 "default.h" #include "tkPort.h" #include "tkInt.h" /* * A data structure of the following type is kept for each * widget managed by this file: */ typedef struct { Tk_Window tkwin; /* Window that embodies the button. NULL * means that the window has been destroyed. */ Tcl_Interp *interp; /* Interpreter associated with button. */ Tcl_Command widgetCmd; /* Token for button's widget command. */ int type; /* Type of widget: restricts operations * that may be performed on widget. See * below for possible values. */ /* * Information about what's in the button. */ char *text; /* Text to display in button (malloc'ed) * or NULL. */ int numChars; /* # of characters in text. */ int underline; /* Index of character to underline. < 0 means * don't underline anything. */ char *textVarName; /* Name of variable (malloc'ed) or NULL. * If non-NULL, button displays the contents * of this variable. */ /* * Information used when displaying widget: */ Tk_Uid state; /* State of button for display purposes: * normal, active, or disabled. */ int borderWidth; /* Width of border. */ int width, height; /* If > 0, these specify dimensions to request * for window, in characters. In this case the actual * size of the text string is ignored in * computing desired window size. */ int wrapLength; /* Line length (in pixels) at which to wrap * onto next line. <= 0 means don't wrap * except at newlines. */ int padX, padY; /* Extra space around text (pixels to leave * on each side). Ignored for bitmaps and * images. */ Tk_Anchor anchor; /* Where text/bitmap should be displayed * inside button region. */ Tk_Justify justify; /* Justification to use for multi-line text. */ int indicatorOn; /* True means draw indicator, false means * don't draw it. */ int textWidth; /* Width needed to display text as requested, * in pixels. */ int textHeight; /* Height needed to display text as requested, * in pixels. */ /* * For check and radio buttons, the fields below are used * to manage the variable indicating the button's state. */ char *selVarName; /* Name of variable used to control selected * state of button. Malloc'ed (if * not NULL). */ char *onValue; /* Value to store in variable when * this button is selected. Malloc'ed (if * not NULL). */ char *offValue; /* Value to store in variable when this * button isn't selected. Malloc'ed * (if not NULL). Valid only for check * buttons. */ /* * Miscellaneous information: */ 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 *command; /* Command to execute when button is * invoked; valid for buttons only. * If not NULL, it's malloc-ed. */ int flags; /* Various flags; see below for * definitions. */ } Button; /* * Possible "type" values for buttons. These are the kinds of * widgets supported by this file. The ordering of the type * numbers is significant: greater means more features and is * used in the code. */ #define TYPE_LABEL 0 #define TYPE_BUTTON 1 #define TYPE_CHECK_BUTTON 2 #define TYPE_RADIO_BUTTON 3 /* * Class names for buttons, indexed by one of the type values above. */ static char *classNames[] = {"Label", "Button", "Checkbutton", "Radiobutton"}; /* * Flag bits for buttons: * * REDRAW_PENDING: Non-zero means a DoWhenIdle handler * has already been queued to redraw * this window. * SELECTED: Non-zero means this button is selected, * so special highlight should be drawn. * GOT_FOCUS: Non-zero means this button currently * has the input focus. */ #define REDRAW_PENDING 1 #define SELECTED 2 #define GOT_FOCUS 4 /* * Mask values used to selectively enable entries in the * configuration specs: */ #define LABEL_MASK TK_CONFIG_USER_BIT #define BUTTON_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 ALL_MASK (LABEL_MASK | BUTTON_MASK \ | CHECK_BUTTON_MASK | RADIO_BUTTON_MASK) static int configFlags[] = {LABEL_MASK, BUTTON_MASK, CHECK_BUTTON_MASK, RADIO_BUTTON_MASK}; /* * Information used for parsing configuration specs: */ static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", DEF_BUTTON_ANCHOR, Tk_Offset(Button, anchor), ALL_MASK}, {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL, (char *) NULL, 0, ALL_MASK}, {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_BUTTON_BORDER_WIDTH, Tk_Offset(Button, borderWidth), ALL_MASK}, {TK_CONFIG_STRING, "-command", "command", "Command", DEF_BUTTON_COMMAND, Tk_Offset(Button, command), BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_INT, "-height", "height", "Height", DEF_BUTTON_HEIGHT, Tk_Offset(Button, height), ALL_MASK}, {TK_CONFIG_BOOLEAN, "-indicatoron", "indicatorOn", "IndicatorOn", DEF_BUTTON_INDICATOR, Tk_Offset(Button, indicatorOn), CHECK_BUTTON_MASK|RADIO_BUTTON_MASK}, {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify", DEF_BUTTON_JUSTIFY, Tk_Offset(Button, justify), ALL_MASK}, {TK_CONFIG_STRING, "-offvalue", "offValue", "Value", DEF_BUTTON_OFF_VALUE, Tk_Offset(Button, offValue), CHECK_BUTTON_MASK}, {TK_CONFIG_STRING, "-onvalue", "onValue", "Value", DEF_BUTTON_ON_VALUE, Tk_Offset(Button, onValue), CHECK_BUTTON_MASK}, {TK_CONFIG_PIXELS, "-padx", "padX", "Pad", DEF_BUTTON_PADX, Tk_Offset(Button, padX), ALL_MASK}, {TK_CONFIG_PIXELS, "-pady", "padY", "Pad", DEF_BUTTON_PADY, Tk_Offset(Button, padY), ALL_MASK}, {TK_CONFIG_UID, "-state", "state", "State", DEF_BUTTON_STATE, Tk_Offset(Button, state), BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK}, {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_LABEL_TAKE_FOCUS, Tk_Offset(Button, takeFocus), LABEL_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_BUTTON_TAKE_FOCUS, Tk_Offset(Button, takeFocus), BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-text", "text", "Text", DEF_BUTTON_TEXT, Tk_Offset(Button, text), ALL_MASK}, {TK_CONFIG_STRING, "-textvariable", "textVariable", "Variable", DEF_BUTTON_TEXT_VARIABLE, Tk_Offset(Button, textVarName), ALL_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_INT, "-underline", "underline", "Underline", DEF_BUTTON_UNDERLINE, Tk_Offset(Button, underline), ALL_MASK}, {TK_CONFIG_STRING, "-value", "value", "Value", DEF_BUTTON_VALUE, Tk_Offset(Button, onValue), RADIO_BUTTON_MASK}, {TK_CONFIG_STRING, "-variable", "variable", "Variable", DEF_RADIOBUTTON_VARIABLE, Tk_Offset(Button, selVarName), RADIO_BUTTON_MASK}, {TK_CONFIG_STRING, "-variable", "variable", "Variable", DEF_CHECKBUTTON_VARIABLE, Tk_Offset(Button, selVarName), CHECK_BUTTON_MASK|TK_CONFIG_NULL_OK}, {TK_CONFIG_INT, "-width", "width", "Width", DEF_BUTTON_WIDTH, Tk_Offset(Button, width), ALL_MASK}, {TK_CONFIG_PIXELS, "-wraplength", "wrapLength", "WrapLength", DEF_BUTTON_WRAP_LENGTH, Tk_Offset(Button, wrapLength), ALL_MASK}, {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, 0} }; /* * String to print out in error messages, identifying options for * widget commands for different types of labels or buttons: */ static char *optionStrings[] = { "cget or configure", "cget, configure, flash, or invoke", "cget, configure, deselect, flash, invoke, select, or toggle", "cget, configure, deselect, flash, invoke, or select" }; /* * Forward declarations for procedures defined later in this file: */ static void ButtonCmdDeletedProc _ANSI_ARGS_(( ClientData clientData)); static int ButtonCreate _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv, int type)); static void ButtonEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); static char * ButtonTextVarProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)); static char * ButtonVarProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)); static int ButtonWidgetCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv)); static void ComputeButtonGeometry _ANSI_ARGS_((Button *butPtr)); static int ConfigureButton _ANSI_ARGS_((Tcl_Interp *interp, Button *butPtr, int argc, char **argv, int flags)); static void DestroyButton _ANSI_ARGS_((ClientData clientData)); static void DisplayButton _ANSI_ARGS_((ClientData clientData)); static int InvokeButton _ANSI_ARGS_((Button *butPtr)); /* *-------------------------------------------------------------- * * Tk_ButtonCmd, Tk_CheckbuttonCmd, Tk_LabelCmd, Tk_RadiobuttonCmd -- * * These procedures are invoked to process the "button", "label", * "radiobutton", and "checkbutton" Tcl commands. See the * user documentation for details on what they do. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. These procedures are just wrappers; * they call ButtonCreate to do all of the real work. * *-------------------------------------------------------------- */ int Tk_ButtonCmd(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. */ { return ButtonCreate(clientData, interp, argc, argv, TYPE_BUTTON); } int Tk_CheckbuttonCmd(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. */ { return ButtonCreate(clientData, interp, argc, argv, TYPE_CHECK_BUTTON); } int Tk_LabelCmd(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. */ { return ButtonCreate(clientData, interp, argc, argv, TYPE_LABEL); } int Tk_RadiobuttonCmd(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. */ { return ButtonCreate(clientData, interp, argc, argv, TYPE_RADIO_BUTTON); } /* *-------------------------------------------------------------- * * ButtonCreate -- * * This procedure does all the real work of implementing the * "button", "label", "radiobutton", and "checkbutton" Tcl * commands. See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ static int ButtonCreate(clientData, interp, argc, argv, type) ClientData clientData; /* Main window associated with * interpreter. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ int type; /* Type of button to create: TYPE_LABEL, * TYPE_BUTTON, TYPE_CHECK_BUTTON, or * TYPE_RADIO_BUTTON. */ { register Button *butPtr; Tk_Window tkwin = (Tk_Window) clientData; Tk_Window new; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " pathName ?options?\"", (char *) NULL); return TCL_ERROR; } /* * Create the new window. */ new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL); if (new == NULL) { return TCL_ERROR; } /* * Initialize the data structure for the button. */ butPtr = (Button *) ckalloc(sizeof(Button)); butPtr->tkwin = new; butPtr->widgetCmd = Tcl_CreateCommand(interp, Tk_PathName(butPtr->tkwin), ButtonWidgetCmd, (ClientData) butPtr, ButtonCmdDeletedProc); butPtr->interp = interp; butPtr->type = type; butPtr->text = NULL; butPtr->numChars = 0; butPtr->underline = -1; butPtr->textVarName = NULL; butPtr->state = tkNormalUid; butPtr->borderWidth = 0; butPtr->width = 0; butPtr->height = 0; butPtr->wrapLength = 0; butPtr->padX = 0; butPtr->padY = 0; butPtr->anchor = TK_ANCHOR_CENTER; butPtr->justify = TK_JUSTIFY_CENTER; butPtr->indicatorOn = 0; butPtr->selVarName = NULL; butPtr->onValue = NULL; butPtr->offValue = NULL; butPtr->command = NULL; butPtr->takeFocus = NULL; butPtr->flags = 0; Tk_SetClass(new, classNames[type]); Tk_CreateEventHandler(butPtr->tkwin, CTK_EXPOSE_EVENT_MASK|CTK_DESTROY_EVENT_MASK|CTK_FOCUS_EVENT_MASK, ButtonEventProc, (ClientData) butPtr); if (ConfigureButton(interp, butPtr, argc-2, argv+2, configFlags[type]) != TCL_OK) { Tk_DestroyWindow(butPtr->tkwin); return TCL_ERROR; } Tcl_SetResult(interp, Tk_PathName(butPtr->tkwin), TCL_VOLATILE); return TCL_OK; } /* *-------------------------------------------------------------- * * ButtonWidgetCmd -- * * 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 ButtonWidgetCmd(clientData, interp, argc, argv) ClientData clientData; /* Information about button widget. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { register Button *butPtr = (Button *) clientData; int result = TCL_OK; size_t length; int c; char error_buffer[120]; if (argc < 2) { sprintf(error_buffer, "wrong # args: should be \"%.50s option ?arg arg ...?\"", argv[0]); Tcl_SetResult(interp, error_buffer, TCL_VOLATILE); return TCL_ERROR; } Tk_Preserve((ClientData) butPtr); c = argv[1][0]; length = strlen(argv[1]); 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, butPtr->tkwin, configSpecs, (char *) butPtr, argv[2], configFlags[butPtr->type]); } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0) && (length >= 2)) { if (argc == 2) { result = Tk_ConfigureInfo(interp, butPtr->tkwin, configSpecs, (char *) butPtr, (char *) NULL, configFlags[butPtr->type]); } else if (argc == 3) { result = Tk_ConfigureInfo(interp, butPtr->tkwin, configSpecs, (char *) butPtr, argv[2], configFlags[butPtr->type]); } else { result = ConfigureButton(interp, butPtr, argc-2, argv+2, configFlags[butPtr->type] | TK_CONFIG_ARGV_ONLY); } } else if ((c == 'd') && (strncmp(argv[1], "deselect", length) == 0) && (butPtr->type >= TYPE_CHECK_BUTTON)) { if (argc > 2) { sprintf(error_buffer, "wrong # args: should be \"%.50s deselect\"", argv[0]); Tcl_SetResult(interp, error_buffer, TCL_VOLATILE); goto error; } if (butPtr->type == TYPE_CHECK_BUTTON) { if (Tcl_SetVar(interp, butPtr->selVarName, butPtr->offValue, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } } else if (butPtr->flags & SELECTED) { if (Tcl_SetVar(interp, butPtr->selVarName, "", TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; }; } } else if ((c == 'f') && (strncmp(argv[1], "flash", length) == 0) && (butPtr->type != TYPE_LABEL)) { int i; if (argc > 2) { sprintf(error_buffer, "wrong # args: should be \"%.50s flash\"", argv[0]); Tcl_SetResult(interp, error_buffer, TCL_VOLATILE); goto error; } if (butPtr->state != tkDisabledUid) { for (i = 0; i < 4; i++) { butPtr->state = (butPtr->state == tkNormalUid) ? tkActiveUid : tkNormalUid; DisplayButton((ClientData) butPtr); /* * Special note: must cancel any existing idle handler * for DisplayButton; it's no longer needed, and DisplayButton * cleared the REDRAW_PENDING flag. */ Tcl_CancelIdleCall(DisplayButton, (ClientData) butPtr); Ctk_DisplayFlush(Tk_Display(butPtr->tkwin)); Tcl_Sleep(50); } } } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0) && (butPtr->type > TYPE_LABEL)) { if (argc > 2) { sprintf(error_buffer, "wrong # args: should be \"%.50s invoke\"", argv[0]); Tcl_SetResult(interp, error_buffer, TCL_VOLATILE); goto error; } if (butPtr->state != tkDisabledUid) { result = InvokeButton(butPtr); } } else if ((c == 's') && (strncmp(argv[1], "select", length) == 0) && (butPtr->type >= TYPE_CHECK_BUTTON)) { if (argc > 2) { sprintf(error_buffer, "wrong # args: should be \"%.50s select\"", argv[0]); Tcl_SetResult(interp, error_buffer, TCL_VOLATILE); goto error; } if (Tcl_SetVar(interp, butPtr->selVarName, butPtr->onValue, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } } else if ((c == 't') && (strncmp(argv[1], "toggle", length) == 0) && (length >= 2) && (butPtr->type == TYPE_CHECK_BUTTON)) { if (argc > 2) { sprintf(error_buffer, "wrong # args: should be \"%.50s toggle\"", argv[0]); Tcl_SetResult(interp, error_buffer, TCL_VOLATILE); goto error; } if (butPtr->flags & SELECTED) { if (Tcl_SetVar(interp, butPtr->selVarName, butPtr->offValue, TCL_GLOBAL_ONLY) == NULL) { result = TCL_ERROR; } } else { if (Tcl_SetVar(interp, butPtr->selVarName, butPtr->onValue, TCL_GLOBAL_ONLY) == NULL) { result = TCL_ERROR; } } } else { sprintf(error_buffer, "bad option \"%.50s\": must be %s", argv[1], optionStrings[butPtr->type]); Tcl_SetResult(interp, error_buffer, TCL_VOLATILE); goto error; } Tk_Release((ClientData) butPtr); return result; error: Tk_Release((ClientData) butPtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * DestroyButton -- * * This procedure is invoked by Tk_EventuallyFree or Tk_Release * to clean up the internal structure of a button at a safe time * (when no-one is using it anymore). * * Results: * None. * * Side effects: * Everything associated with the widget is freed up. * *---------------------------------------------------------------------- */ static void DestroyButton(clientData) ClientData clientData; /* Info about entry widget. */ { register Button *butPtr = (Button *) clientData; /* * Free up all the stuff that requires special handling, then * let Tk_FreeOptions handle all the standard option-related * stuff. */ if (butPtr->textVarName != NULL) { Tcl_UntraceVar(butPtr->interp, butPtr->textVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ButtonTextVarProc, (ClientData) butPtr); } if (butPtr->selVarName != NULL) { Tcl_UntraceVar(butPtr->interp, butPtr->selVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ButtonVarProc, (ClientData) butPtr); } Tk_FreeOptions(configSpecs, (char *) butPtr, configFlags[butPtr->type]); ckfree((char *) butPtr); } /* *---------------------------------------------------------------------- * * ConfigureButton -- * * This procedure is called to process an argv/argc list, plus * the Tk option database, in order to configure (or * reconfigure) a button 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 text string, colors, font, * etc. get set for butPtr; old resources get freed, if there * were any. The button is redisplayed. * *---------------------------------------------------------------------- */ static int ConfigureButton(interp, butPtr, argc, argv, flags) Tcl_Interp *interp; /* Used for error reporting. */ register Button *butPtr; /* 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. */ { /* * Eliminate any existing trace on variables monitored by the button. */ if (butPtr->textVarName != NULL) { Tcl_UntraceVar(interp, butPtr->textVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ButtonTextVarProc, (ClientData) butPtr); } if (butPtr->selVarName != NULL) { Tcl_UntraceVar(interp, butPtr->selVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ButtonVarProc, (ClientData) butPtr); } if (Tk_ConfigureWidget(interp, butPtr->tkwin, configSpecs, argc, argv, (char *) butPtr, 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. */ if ((butPtr->state != tkNormalUid) && (butPtr->state != tkActiveUid) && (butPtr->state != tkDisabledUid)) { Tcl_AppendResult(interp, "bad state value \"", butPtr->state, "\": must be normal, active, or disabled", (char *) NULL); butPtr->state = tkNormalUid; return TCL_ERROR; } if (butPtr->padX < 0) { butPtr->padX = 0; } if (butPtr->padY < 0) { butPtr->padY = 0; } if (butPtr->type >= TYPE_CHECK_BUTTON) { char *value; if (butPtr->selVarName == NULL) { butPtr->selVarName = (char *) ckalloc((unsigned) (strlen(Tk_Name(butPtr->tkwin)) + 1)); strcpy(butPtr->selVarName, Tk_Name(butPtr->tkwin)); } /* * Select the button 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, butPtr->selVarName, TCL_GLOBAL_ONLY); butPtr->flags &= ~SELECTED; if (value != NULL) { if (strcmp(value, butPtr->onValue) == 0) { butPtr->flags |= SELECTED; } } else { if (Tcl_SetVar(interp, butPtr->selVarName, (butPtr->type == TYPE_CHECK_BUTTON) ? butPtr->offValue : "", TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } } Tcl_TraceVar(interp, butPtr->selVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ButtonVarProc, (ClientData) butPtr); } if (butPtr->textVarName != NULL) { /* * The button must display the value of a variable: set up a trace * on the variable's value, create the variable if it doesn't * exist, and fetch its current value. */ char *value; value = Tcl_GetVar(interp, butPtr->textVarName, TCL_GLOBAL_ONLY); if (value == NULL) { if (Tcl_SetVar(interp, butPtr->textVarName, butPtr->text, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } } else { if (butPtr->text != NULL) { ckfree(butPtr->text); } butPtr->text = (char *) ckalloc((unsigned) (strlen(value) + 1)); strcpy(butPtr->text, value); } Tcl_TraceVar(interp, butPtr->textVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ButtonTextVarProc, (ClientData) butPtr); } Tk_SetInternalBorder(butPtr->tkwin, butPtr->borderWidth); ComputeButtonGeometry(butPtr); /* * Lastly, arrange for the button to be redisplayed. */ if (Tk_IsMapped(butPtr->tkwin) && !(butPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr); butPtr->flags |= REDRAW_PENDING; } return TCL_OK; } /* *---------------------------------------------------------------------- * * DisplayButton -- * * This procedure is invoked to display a button widget. It is * normally invoked as an idle handler. * * Results: * None. * * Side effects: * Commands are output to X to display the button in its * current mode. The REDRAW_PENDING flag is cleared. * *---------------------------------------------------------------------- */ static void DisplayButton(clientData) ClientData clientData; /* Information about widget. */ { register Button *butPtr = (Button *) clientData; register Tk_Window tkwin = butPtr->tkwin; Ctk_Style style; int x, y; unsigned int width = Tk_Width(tkwin); unsigned int height = Tk_Height(tkwin); char buf[4]; int indicatorSpace = 0; butPtr->flags &= ~REDRAW_PENDING; if ((tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } if ((butPtr->type == TYPE_CHECK_BUTTON) || (butPtr->type == TYPE_RADIO_BUTTON)) { indicatorSpace = 3; buf[0] = (butPtr->type == TYPE_CHECK_BUTTON) ? '[' : '<'; buf[1] = (butPtr->flags & SELECTED) ? '*' : ' '; buf[2] = (butPtr->type == TYPE_CHECK_BUTTON) ? ']' : '>'; buf[3] = '\0'; } if (butPtr->type == TYPE_LABEL) { style = CTK_PLAIN_STYLE; } else if (butPtr->state == tkDisabledUid) { style = CTK_DISABLED_STYLE; } else { style = CTK_BUTTON_STYLE; } switch (butPtr->anchor) { case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW: x = butPtr->borderWidth + butPtr->padX + indicatorSpace; break; case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S: x = (width + indicatorSpace - butPtr->textWidth)/2; break; default: x = width - butPtr->borderWidth - butPtr->padX - butPtr->textWidth; break; } switch (butPtr->anchor) { case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE: y = butPtr->borderWidth + butPtr->padY; break; case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E: y = (height - butPtr->textHeight)/2; break; default: y = height - butPtr->borderWidth - butPtr->padY - butPtr->textHeight; break; } /* * Clear rect. */ Ctk_FillRect(tkwin, 0, 0, width, height, style, ' '); /* * Draw text. */ TkDisplayText(tkwin, style, butPtr->text, butPtr->numChars, x, y, butPtr->textWidth, butPtr->justify, butPtr->underline); /* * Draw Indicator. */ if (indicatorSpace) { x -= indicatorSpace; y += butPtr->textHeight/2; Ctk_DrawString(tkwin, x, y, style, buf, indicatorSpace); } /* * Draw border. */ Ctk_DrawBorder(tkwin, style, (char *)NULL); /* * Position cursor. */ if (butPtr->flags & GOT_FOCUS) { Ctk_SetCursor(tkwin, butPtr->borderWidth, butPtr->borderWidth); } } /* *-------------------------------------------------------------- * * ButtonEventProc -- * * This procedure is invoked by the Tk dispatcher for various * events on buttons. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get * cleaned up. When it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void ButtonEventProc(clientData, eventPtr) ClientData clientData; /* Information about window. */ XEvent *eventPtr; /* Information about event. */ { Button *butPtr = (Button *) clientData; if (eventPtr->type == CTK_EXPOSE_EVENT) { if ((butPtr->tkwin != NULL) && !(butPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr); butPtr->flags |= REDRAW_PENDING; } } else if (eventPtr->type == CTK_DESTROY_EVENT) { if (butPtr->tkwin != NULL) { butPtr->tkwin = NULL; Tcl_DeleteCommand(butPtr->interp, Tcl_GetCommandName(butPtr->interp, butPtr->widgetCmd)); } if (butPtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(DisplayButton, (ClientData) butPtr); } Tk_EventuallyFree((ClientData) butPtr, DestroyButton); } else if (eventPtr->type == CTK_FOCUS_EVENT) { butPtr->flags |= GOT_FOCUS; Ctk_SetCursor(butPtr->tkwin, butPtr->borderWidth, butPtr->borderWidth); } else if (eventPtr->type == CTK_UNFOCUS_EVENT) { butPtr->flags &= ~GOT_FOCUS; } } /* *---------------------------------------------------------------------- * * ButtonCmdDeletedProc -- * * 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 ButtonCmdDeletedProc(clientData) ClientData clientData; /* Pointer to widget record for widget. */ { Button *butPtr = (Button *) clientData; Tk_Window tkwin = butPtr->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) { butPtr->tkwin = NULL; Tk_DestroyWindow(tkwin); } } /* *---------------------------------------------------------------------- * * ComputeButtonGeometry -- * * After changes in a button's text or bitmap, this procedure * recomputes the button's geometry and passes this information * along to the geometry manager for the window. * * Results: * None. * * Side effects: * The button's window may change size. * *---------------------------------------------------------------------- */ static void ComputeButtonGeometry(butPtr) register Button *butPtr; /* Button whose geometry may have changed. */ { int width, height; int indicatorSpace = 0; if (butPtr->type == TYPE_RADIO_BUTTON || butPtr->type == TYPE_CHECK_BUTTON) { indicatorSpace = 3; } butPtr->numChars = strlen(butPtr->text); TkComputeTextGeometry(butPtr->text, butPtr->numChars, butPtr->wrapLength, &butPtr->textWidth, &butPtr->textHeight); width = butPtr->width; if (width < 0) { width = butPtr->textWidth; } height = butPtr->height; if (height < 0) { height = butPtr->textHeight; } width += 2*butPtr->padX; height += 2*butPtr->padY; Tk_GeometryRequest(butPtr->tkwin, width + indicatorSpace + 2*butPtr->borderWidth, height + 2*butPtr->borderWidth); } /* *---------------------------------------------------------------------- * * InvokeButton -- * * This procedure is called to carry out the actions associated * with a button, such as invoking a Tcl command or setting a * variable. This procedure is invoked, for example, when the * button is invoked via the mouse. * * Results: * A standard Tcl return value. Information is also left in * interp->result. * * Side effects: * Depends on the button and its associated command. * *---------------------------------------------------------------------- */ static int InvokeButton(butPtr) register Button *butPtr; /* Information about button. */ { if (butPtr->type == TYPE_CHECK_BUTTON) { if (butPtr->flags & SELECTED) { if (Tcl_SetVar(butPtr->interp, butPtr->selVarName, butPtr->offValue, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } } else { if (Tcl_SetVar(butPtr->interp, butPtr->selVarName, butPtr->onValue, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } } } else if (butPtr->type == TYPE_RADIO_BUTTON) { if (Tcl_SetVar(butPtr->interp, butPtr->selVarName, butPtr->onValue, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } } if ((butPtr->type != TYPE_LABEL) && (butPtr->command != NULL)) { return TkCopyAndGlobalEval(butPtr->interp, butPtr->command); } return TCL_OK; } /* *-------------------------------------------------------------- * * ButtonVarProc -- * * This procedure is invoked when someone changes the * state variable associated with a radio button. Depending * on the new value of the button's variable, the button * may be selected or deselected. * * Results: * NULL is always returned. * * Side effects: * The button may become selected or deselected. * *-------------------------------------------------------------- */ /* ARGSUSED */ static char * ButtonVarProc(clientData, interp, name1, name2, flags) ClientData clientData; /* Information about button. */ Tcl_Interp *interp; /* Interpreter containing variable. */ char *name1; /* Name of variable. */ char *name2; /* Second part of variable name. */ int flags; /* Information about what happened. */ { register Button *butPtr = (Button *) clientData; char *value; /* * If the variable is being unset, then just re-establish the * trace unless the whole interpreter is going away. */ if (flags & TCL_TRACE_UNSETS) { butPtr->flags &= ~SELECTED; if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { Tcl_TraceVar(interp, butPtr->selVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ButtonVarProc, clientData); } goto redisplay; } /* * Use the value of the variable to update the selected status of * the button. */ value = Tcl_GetVar(interp, butPtr->selVarName, TCL_GLOBAL_ONLY); if (value == NULL) { value = ""; } if (strcmp(value, butPtr->onValue) == 0) { if (butPtr->flags & SELECTED) { return (char *) NULL; } butPtr->flags |= SELECTED; } else if (butPtr->flags & SELECTED) { butPtr->flags &= ~SELECTED; } else { return (char *) NULL; } redisplay: if ((butPtr->tkwin != NULL) && Tk_IsMapped(butPtr->tkwin) && !(butPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr); butPtr->flags |= REDRAW_PENDING; } return (char *) NULL; } /* *-------------------------------------------------------------- * * ButtonTextVarProc -- * * This procedure is invoked when someone changes the variable * whose contents are to be displayed in a button. * * Results: * NULL is always returned. * * Side effects: * The text displayed in the button will change to match the * variable. * *-------------------------------------------------------------- */ /* ARGSUSED */ static char * ButtonTextVarProc(clientData, interp, name1, name2, flags) ClientData clientData; /* Information about button. */ Tcl_Interp *interp; /* Interpreter containing variable. */ char *name1; /* Not used. */ char *name2; /* Not used. */ int flags; /* Information about what happened. */ { register Button *butPtr = (Button *) clientData; char *value; /* * If the variable is unset, then immediately recreate it unless * the whole interpreter is going away. */ if (flags & TCL_TRACE_UNSETS) { if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { Tcl_SetVar(interp, butPtr->textVarName, butPtr->text, TCL_GLOBAL_ONLY); Tcl_TraceVar(interp, butPtr->textVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ButtonTextVarProc, clientData); } return (char *) NULL; } value = Tcl_GetVar(interp, butPtr->textVarName, TCL_GLOBAL_ONLY); if (value == NULL) { value = ""; } if (butPtr->text != NULL) { ckfree(butPtr->text); } butPtr->text = (char *) ckalloc((unsigned) (strlen(value) + 1)); strcpy(butPtr->text, value); ComputeButtonGeometry(butPtr); if ((butPtr->tkwin != NULL) && Tk_IsMapped(butPtr->tkwin) && !(butPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr); butPtr->flags |= REDRAW_PENDING; } return (char *) NULL; }